Skip to content

[Bug]: ZCode client config export omits reasoning configuration, preventing thought level controls in ZCode UI #4147

Closed

Description

@richardfeiliu-a11y

Client or integration

Other

Area

Catalog / models

Summary

When OpenCodex exports the managed provider configuration to ~/.zcode/v2/config.json via ocx zcode or integration sync, buildZcodeClientConfig in src/clients/config-export/zcode.ts generates entries containing only name, modalities, and limit.context. It omits the reasoning block completely.

As a result, ZCode's UI never shows the "Thought Level" (思考强度) select dropdown for reasoning-capable models (e.g. gemini-3.8-flash, deepseek-v4-flash, claude-opus-4-6-thinking, grok-4.6, muse-spark-1.3-contributor, etc.), even though the wire protocols (reasoning_effort in Chat Completions and Responses) are fully supported and functional.

Reproduction

  1. Configure at least one reasoning-capable model in OpenCodex (e.g., google-antigravity/gemini-3.8-flash or CommandCode/deepseek/deepseek-v4-flash).
  2. Run ocx zcode (or enable ZCode integration from OpenCodex) to export configuration to ~/.zcode/v2/config.json.
  3. Inspect ~/.zcode/v2/config.json under provider.opencodex.models: observe that all exported model entries lack the reasoning block (reasoning: { enabled: true, variants: [...], defaultVariant: "..." }).
  4. Launch ZCode desktop app and open a chat session with one of these models: observe that the "Thought Level" control is completely missing from the session bar because ZCode requires model.reasoning.levels to render the picker.

Version

2.48.0

Operating system

macOS 15 (Darwin arm64)

Provider and model

google-antigravity/gemini-3.8-flash, CommandCode/deepseek/deepseek-v4-flash, opencode-go/muse-spark-1.3-contributor

Logs or error output

// Observed in src/clients/config-export/zcode.ts:
export interface ZcodeModelEntry {
  name?: string;
  limit?: { context: number; output?: number };
  modalities: { input: string[]; output: string[] };
  // Missing reasoning specification
}

Redacted configuration

{
  "provider": {
    "opencodex": {
      "models": {
        "google-antigravity/gemini-3.8-flash": {
          "name": "gemini-3.8-flash (google)",
          "limit": { "context": 1048576 },
          "modalities": { "input": ["text", "image"], "output": ["text"] }
        }
      }
    }
  }
}

Expected Behavior

src/clients/config-export/zcode.ts should export reasoning when model.reasoningEfforts is present, similar to omp.ts and dsh.ts:

export interface ZcodeModelEntry {
  name?: string;
  limit?: { context: number; output?: number };
  modalities: { input: string[]; output: string[] };
  reasoning?: {
    enabled: boolean;
    variants: string[];
    defaultVariant?: string;
  };
}

// In buildZcodeClientConfig:
if (model.reasoningEfforts && model.reasoningEfforts.length > 0) {
  entry.reasoning = {
    enabled: true,
    variants: model.reasoningEfforts.filter(e => e !== "ultra"),
    ...(model.defaultReasoningEffort ? { defaultVariant: model.defaultReasoningEffort } : {}),
  };
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. github-actions commented on Sep 9, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. lidge-jun commented on Sep 9, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 48 / 80

    이 이슈는 ZCode 데스크톱 클라이언트로 OpenCodex가 내보내는 ~/.zcode/v2/config.json 안의 모델 항목에 생각 강도(Thought Level) UI를 켜는 reasoning 블록이 빠져 있다는 버그 보고입니다. 재현 경로는 명확합니다. ocx zcode(또는 통합 sync)가 src/clients/config-export/zcode.ts의 buildZcodeClientConfig를 타고, 지금 HEAD(6d99a1883)에서도 각 모델 엔트리는 name / modalities / (있을 때만) limit.context만 넣고 끝납니다. ZcodeModelEntry 타입 자체에 reasoning 필드가 없고, 루프 안에서도 model.reasoningEfforts를 전혀 읽지 않습니다. 그래서 카탈로그에 노력 사다리가 있는 google-antigravity/gemini-3.8-flash, CommandCode/.../deepseek-v4-flash, opencode-go/muse-spark-1.3-contributor 같은 행도 ZCode 쪽에서는 평문 모델처럼 보이며, 세션 바의 Thought Level 셀렉트가 안 뜹니다. 프록시 와이어(reasoning_effort Chat/Responses)는 이미 동작하는데 클라이언트 export 메타만 빠진 상태라, 사용자는 “모델은 되는데 UI가 없다”로 체감합니다.

    같은 export 패밀리를 보면 이 구멍은 ZCode만의 공백입니다. omp.ts는 reasoningEfforts가 있으면 reasoning: true와 thinking.efforts를 넣고, dsh.ts는 reasoningEfforts 맵을 넣으며, OpenCode V2 블록도 variants 형태로 노력 목록을 냅니다. 테스트(tests/config/client-config-export.test.ts)도 omp/dsh/OpenCode 쪽 reasoning 방출을 이미 잠가 두었고, zcode 경로는 “limit/modalities만” 수준으로만 검증되는 편입니다. 지금 dev tip은 Codex 할당량 등록 지연(#3848)과 OAuth plan 필드(#4146) 쪽이지만, 클라이언트 export 계약 자체는 이미 안정된 축이라 스키마만 맞추면 작은 패치로 닫을 수 있는 catalog/clients 버그입니다. 보고자가 제안한 형태(enabled + variants + defaultVariant)는 omp/dsh와 결이 같고, defaultReasoningEffort가 ladder에 있을 때만 default를 넣는 기존 export 규칙과도 맞습니다. 다만 본문 재현 문단은 ZCode가 model.reasoning.levels를 본다고도 적혀 있어서, 실제 ZCode 3.x가 읽는 키 이름(variants vs levels 등)을 한 번 더 확인한 뒤 타입/방출을 맞춰야 합니다. ultra 필터·none 제외 같은 vocabulary 정리는 omp/OpenCode 쪽 sanitize와 같은 정책을 재사용하는 편이 안전합니다.

    src/clients/config-export/zcode.ts ZcodeModelEntry - reasoning 관련 필드가 타입에 없어서, 설령 런타임에 객체를 붙여도 계약/문서/테스트가 따라가지 못한다

    src/clients/config-export/zcode.ts buildZcodeClientConfig - normalizeExportModels 루프가 reasoningEfforts/defaultReasoningEffort를 무시하고 name·modalities·limit만 써서 Thought Level 메타가 항상 비다

    이슈 본문 Expected vs Reproduction - Expected는 reasoning.variants/defaultVariant인데 Reproduction은 model.reasoning.levels라고 해서, ZCode 실측 스키마가 어느 쪽인지 확인 없이 패치하면 UI가 계속 안 뜰 수 있다

    tests/config/client-config-export.test.ts (및 tests/providers/zcode-client.test.ts) - zcode export에 reasoning ladder fixture가 없어 회귀를 잠그지 못한다

    메인테이너의 판단이 필요한 지점

    • ZCode 실측 스키마 키를 variants/defaultVariant로 확정할지, levels 등 다른 키로 맞출지 (3.7.7/3.8.1에서 이미 검증한 계약 주석과 충돌 여부)
    • ultra/none을 다른 클라이언트와 동일하게 숨길지, ZCode UI 어휘에 맞춰 그대로 둘지
    • 이 이슈만 단독 PR로 받을지, 가까운 clients export polish와 묶을지

    너의 추천
    ZCode 실측 config(또는 공개 스키마)로 reasoning 키 이름을 한 줄 확정한 뒤, zcode.ts에 omp/dsh와 같은 “ladder 있으면 방출”을 넣고 client-config-export 테스트에 reasoning 있는/없는 fixture를 추가하는 작은 PR로 머지하세요. types/config split과 무관하니 close-don't-rebase 대상이 아닙니다.

    이 댓글은 grok-bot이 작성했습니다

  3. richardfeiliu-a11y commented on Sep 9, 2026

    @richardfeiliu-a11y
    ContributorAuthor

    Confirmed: ZCode's on-disk schema is reasoning.variants / reasoning.defaultVariant, not levels.

    I checked a live ~/.zcode/v2/config.json (ZCode 3.x on macOS) and the shipped parser in ZCode.app (app.asar). Builtin providers already persist this shape, for example builtin:bigmodel / GLM-5.3:

    {
      "reasoning": {
        "enabled": true,
        "variants": ["low", "max", "high"],
        "defaultVariant": "max"
      }
    }

    There is no levels key on disk. model.reasoning.levels is the in-memory object after parse. The named helper openCodeReasoningToModelReasoning does:

    • variants → levels: Object.fromEntries(variants.map(v => [v, {}]))
    • defaultVariant → defaultLevel

    The inverse modelReasoningToOpenCodeReasoning writes Object.keys(levels) back to variants. The UI gate mentioned in the reproduction is that runtime levels object; the exporter should still emit the OpenCode-compatible on-disk keys (enabled + variants + defaultVariant). That matches the existing 3.7.7 / 3.8.1 contract comment on ZcodeModelEntry. A local ~/.zcode/v2/config.json patched with this block made the Thought Level control appear.

    On vocabulary, I would follow OpenCode V2 rather than omp:

    • Drop none (Codex omit-sentinel; OpenCode V2 already filters it). ZCode builtins use off / enabled for that idea, not none.
    • Keep ultra when the catalog ladder includes it. ZCode renders variants as picker labels and forwards the selected id as reasoning_effort; hiding ultra would drop a real catalog tier. omp's no-ultra vocabulary looks omp-specific.
    • Set defaultVariant only when defaultReasoningEffort is in the emitted variants list.

    I can open a small standalone PR against dev for src/clients/config-export/zcode.ts plus export tests (reasoning present / absent). Prefer not to bundle it with other client-export polish.

  4. richardfeiliu-a11y commented on Sep 9, 2026

    @richardfeiliu-a11y
    ContributorAuthor

    Opened a standalone PR against dev with the on-disk schema confirmed above (enabled + variants + defaultVariant): #4153

    Vocabulary matches OpenCode V2: drop none, keep catalog ultra, and set defaultVariant only when it remains in the emitted ladder.

  5. lidge-jun commented on Sep 9, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4153, merged as 2ce5f381f. Thanks @richardfeiliu-a11y — the PR landed as your own commit, unmodified.

    The ZCode export now emits a Thought Level block for every model whose catalog entry carries a reasoning ladder, so the picker appears instead of the model looking like a plain one.

    What made this reviewable in one pass was your schema check. The issue body said ZCode reads model.reasoning.levels while the Expected block said reasoning.variants, and rather than picking one you read a live ~/.zcode/v2/config.json and the shipped parser in ZCode.app and reported that the on-disk keys are enabled / variants / defaultVariant, with levels being what the parser builds in memory. This repository independently agrees: src/integrations/ownership-policy.ts already treats models.*.reasoning as a 3.8.1 key with enabled/variants, and tests/clients/integrations-writer.test.ts carries a fixture in exactly that shape. Without that, a patch written to the issue text would have shipped a key ZCode never reads and the picker still would not have appeared.

    The vocabulary decisions in the PR are the right ones for this client. none is dropped because it is Codex's omit-sentinel rather than a picker option. ultra is kept, against the Expected snippet in this issue, because ZCode forwards the selected variant as reasoning_effort and ultra is a real tier — omp's no-ultra vocabulary is specific to omp. And defaultVariant is written only when it survives the filter.

    One operational note for anyone reading this later: a fork pull request does not start repository CI by itself. Cross-platform CI and React Doctor sat at action_required until approved, which is why the check list looked thin for a while.

    Evidence: exact-head CI at abf35fa947b05ae82d7fd22c0a4971a129b9629f concluded success — gh run view 34411681339 --exit-status returns 0 for Cross-platform CI, with React Doctor, Enforce PR target branch and PR hygiene also 0, and nothing cancelled at that SHA. git merge-base --is-ancestor abf35fa94 origin/dev passes. Independent review recorded at devlog/_plan/260910_post249_round2/_research/_audit_pr4153.md, verdict PASS with no blocker.

    Closing by hand because pull requests here target dev, so GitHub does not auto-close a linked issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entries

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions