Skip to content

ZCode integration attaches over Chat Completions instead of the proxy's native Responses surface #4295

Closed

Description

@lidge-jun

Client or integration

Other (ZCode desktop app, client integration exported by ocx)

Provider or upstream service

ZCode (Z.ai desktop coding agent) as a client of the OpenCodex proxy

OpenCodex version

2.50.0 (installed), dev at 29d632f

Endpoint or capability

/v1/chat/completions (current) vs /v1/responses (proposed)

Current behaviour

src/clients/config-export/zcode.ts writes the provider block with kind "openai-compatible". ZCode dispatches that kind to {baseURL}/chat/completions, so every turn from ZCode lands on the proxy's Chat Completions surface.

That surface is not the proxy's native inbound. src/server/chat-completions.ts translates the Chat body into a Responses body, replays it through handleResponses, then translates the Responses stream back into Chat SSE. Two translations per turn, and tool-call deltas plus reasoning blocks change shape on the way through.

Expected behaviour

ZCode should attach over the Responses protocol, which the proxy serves natively at POST /v1/responses (src/server/index.ts:1994), so no translation happens at all.

ZCode 3.11.2 supports exactly three provider kinds and dispatches each to a fixed path. Extracted verbatim from the shipped bundle:

bt=t.enum(["anthropic","openai","openai-compatible"])

function fL(e){switch(e){case"anthropic":return"/v1/messages";case"openai":return"/responses";case"openai-compatible":return"/chat/completions"}}
s(fL,"getDefaultModelProviderEndpointPathForKind")

So kind "openai" with the existing baseURL (proxy root + /v1) resolves to /v1/responses. The reasoning block schema (enabled / variants / defaultVariant) is identical across kinds; only the wire field differs, and kind "openai" emits reasoning.effort, which /v1/responses reads natively.

The repository already anticipates this. tests/responses/responses-inbound-store-default.test.ts opens with:

Generic Responses-API clients (AI-SDK apps such as ZCode) omit `store`

Minimal redacted request or reproduction

ocx integration client enable --client zcode
python3 -c "import json,os;d=json.load(open(os.path.expanduser('~/.zcode/v2/config.json')));p=d['provider']['opencodex'];print(p['kind'], p['options']['baseURL'])"

Actual response or error

openai-compatible http://127.0.0.1:10100/v1
# ZCode therefore posts to http://127.0.0.1:10100/v1/chat/completions

Upstream documentation

ZCode publishes no configuration specification. The authority used here is the shipped application bundle, /Applications/ZCode.app/Contents/Resources/app.asar from ZCode 3.11.2, where the kind enum and the kind-to-path dispatch quoted above were extracted. The baseURL normalizer confirms suffix handling:

function lp(e,o){let r={anthropic:["/v1/messages","/messages"],openai:["/responses"],"openai-compatible":["/chat/completions"]}, ... }
s(lp,"normalizeModelProviderBaseUrlForKind")

Suggested mapping or implementation notes

Change the exported kind to "openai" and keep baseURL as it is. ZCode strips only the /responses suffix for that kind, so the /v1 root is preserved and requests land on /v1/responses.

An existing installation that has not hand-edited its config follows automatically: kind is not a refreshable path, but recordedBlockIsOwned still matches the old fingerprint, so the record becomes stale rather than conflict and a refresh rewrites the fragment. Only a user who edited kind or options themselves needs --overwrite-conflict.

Additional context and attachments

mcode already exports an anthropic-messages block and dsh already exports an openai-responses block, so this is a correction to one client rather than a new capability.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    streamingSSE, WebSocket, terminal stream frames
    toolstool_calls, MCP, web-search / sidecar tools
    on Sep 11, 2026
  2. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 61 / 80

    설명

    이 이슈는 ZCode 데스크톱 연동 export가 provider kind를 openai-compatible으로 써서, ZCode가 프록시의 Chat Completions면({baseURL}/chat/completions)으로 붙는 문제를 말합니다. 지금 dev HEAD cb7f96cbc의 src/clients/config-export/zcode.ts는 주석부터 “ZCode dials the OpenAI Chat Completions surface (openai-compatible)”라고 적고, 생성 문서도 kind: "openai-compatible"입니다. 프록시의 네이티브 inbound는 POST /v1/responses이고, Chat 면은 src/server/chat-completions.ts가 Chat→Responses→Chat으로 두 번 번역합니다. 도구 델타·reasoning 블록 모양이 그 과정에서 바뀌기 쉽습니다.

    이슈가 가져온 ZCode 3.11.2 번들 근거는 명확합니다. kind enum이 anthropic / openai / openai-compatible 세 가지뿐이고, openai는 /responses, openai-compatible는 /chat/completions로 고정됩니다. 기존 baseURL(프록시 루트 + /v1)을 유지한 채 kind만 openai로 바꾸면 /v1/responses로 붙습니다. reasoning 스키마(enabled/variants/defaultVariant)는 kind 공통이고, openai는 reasoning.effort를 내며 프록시 Responses가 그걸 읽습니다. 저장소 테스트 주석(tests/responses/responses-inbound-store-default.test.ts)도 “Generic Responses-API clients (… ZCode)”를 이미 전제로 적습니다.

    왜 지금 중요한가. tip에 omo가 14번째 export 클라이언트로 들어갔고(#4290/#4294), zcode는 그 목록의 loopback-only 클라이언트 중 하나입니다. ZCode 사용자가 Chat 번역층을 항상 거치면, “프록시가 Responses를 잘하는데 클라이언트만 우회한다”는 상태가 됩니다. #4297(Z.AI provider가 Chat에 고정)과 이름이 비슷해 보이지만 레이어가 다릅니다. 이쪽은 클라이언트 export kind, 저쪽은 업스트림 provider adapter입니다.

    우선순위 61입니다. 변경 후보는 zcode.ts의 kind 한 줄과 문서/테스트 정도라 범위가 작고, 이득(번역 제거)이 큽니다. 다만 이미 openai-compatible로 저장된 사용자 ~/.zcode/v2/config.json을 export가 덮어쓸지, 병합 규칙이 기존 kind를 보존하는지를 확인해야 합니다. 업그레이드 시 조용히 kind가 바뀌면 동작이 급변합니다.

    라인 - src/clients/config-export/zcode.ts - kind: "openai-compatible"이 직접 원인입니다. openai로 바꾸는 제안이 ZCode 번들 디스패치와 맞습니다.
    경로/심볼 - buildZcodeClientConfig / contribution fragment - fragment가 ["provider", OPENCODE_PROVIDER_ID]만 쓰므로, kind 변경은 그 provider 블록 전체에 적용됩니다. 다른 필드(baseURL, models, modalities)와의 정합 테스트를 같이 갱신하세요.
    경로/심볼 - 기존 설치 업그레이드 - ocx integration client enable --client zcode가 기존 kind를 덮어쓰는지, 사용자가 수동으로 고친 값을 보존하는지 확인이 필요합니다.
    관련 - #4296 - ZCode가 modalities를 읽어 첨부를 막으므로, kind를 Responses로 바꿔도 flash modality가 text면 첨부는 계속 막힙니다. 이 이슈만으로 첨부 버그가 끝나지 않습니다.

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

    • 기본 kind를 openai로 바꿀지, 설정 플래그로 Responses/Chat을 선택하게 할지
    • 이미 openai-compatible으로 저장된 사용자 설정을 자동 이주할지, 문서에 수동 변경만 안내할지
    • ZCode 구버전(kind 동작이 다른 빌드) 지원을 어디까지 약속할지

    너의 추천
    수락하세요. zcode.ts 기본 kind를 openai로 바꾸고, export/integration 테스트에 “baseURL + kind → /v1/responses” 기대를 잠근 뒤, 릴리즈 노트에 “기존 zcode 설정은 kind를 openai로 바꾸라”는 한 줄을 넣으세요. #4296과 함께 보면 첨부 증상까지 같이 사라집니다. #4297과는 PR을 섞지 마세요.

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

  3. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    OwnerAuthor

    Landed on `dev` in #4302 (merge commit 5edb7b7). PRs here target `dev`, so GitHub does not auto-close; closing by hand. The export now writes kind "openai", which ZCode dispatches to {baseURL}/responses, and the serialized /v1 root survives its per-kind suffix normalization, so turns land on the proxy route registered at src/server/index.ts:1994 with no translation.

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

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions