Skip to content

Z.AI provider is pinned to Chat Completions although the same key serves the Responses protocol #4297

Closed

Description

@lidge-jun

Client or integration

Codex CLI

Provider or upstream service

zai (Z.AI GLM Coding Plan)

OpenCodex version

2.50.0 (installed), dev at 29d632f

Endpoint or capability

/v1/responses upstream wire selection

Current behaviour

The zai row is pinned to Chat Completions:

id: "zai", label: "Z.AI - GLM Coding Plan", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",

Because the upstream wire is chosen by the routed provider's adapter rather than by the inbound route (src/server/adapter-resolve.ts), every request to a GLM model leaves over Chat Completions no matter which surface the client used. In practice that path is the unstable one.

Z.AI serves the same subscription and the same key over three protocols, and the Responses endpoint is the one its own documentation points Codex-family clients at. OpenCodex already ships a Responses row for the domestic portal (zhipu-bigmodel-responses on open.bigmodel.cn/api/v1) but has no equivalent for api.z.ai.

Expected behaviour

zai should default to the Responses protocol on https://api.z.ai/api/v1, with Chat Completions still reachable as an explicit per-model opt-in.

Switching costs no models. Every id in the current roster answers on the Responses endpoint (measured against a live key, 2026-09-12):

POST /api/v1/responses  glm-5.3        -> 200
POST /api/v1/responses  glm-5.3-flash  -> 200
POST /api/v1/responses  glm-5.2        -> 200
POST /api/v1/responses  glm-5.1        -> 200
POST /api/v1/responses  glm-5          -> 200
POST /api/v1/responses  glm-4.6        -> 200
POST /api/v1/responses  glm-5-turbo    -> 200

GET /api/v1/models also returns a Codex-shaped catalog carrying input_modalities, context_window and supported_reasoning_levels, which is strictly richer than what the Chat endpoint's /models exposes.

Minimal redacted request or reproduction

curl -s -o /dev/null -w '%{http_code}\n' https://api.z.ai/api/v1/responses \
  -H "Authorization: Bearer <redacted>" -H 'Content-Type: application/json' \
  -d '{"model":"glm-5.3","input":"hi","max_output_tokens":16}'

curl -s -o /dev/null -w '%{http_code}\n' https://api.z.ai/api/v1/chat/completions \
  -H "Authorization: Bearer <redacted>" -H 'Content-Type: application/json' \
  -d '{"model":"glm-5.3","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'

Actual response or error

200   # /api/v1/responses
403   # /api/v1/chat/completions -> {"error":{"code":"model_access_denied", ...}}
200   # /api/coding/paas/v4/chat/completions

The two wires live under different path prefixes. That matters for the fix: swapping the adapter alone cannot reach the right endpoint, because resolveWireProtocolOverride changes provider.adapter and leaves provider.baseUrl untouched.

Upstream documentation

https://docs.z.ai/guides/llm/glm-5.3

| Protocol | Base URL |
| OpenAI Chat Completion Protocol | https://api.z.ai/api/coding/paas/v4 |
| OpenAI Response Protocol | https://api.z.ai/api/v1 |
| Anthropic Message Protocol | https://api.z.ai/api/anthropic |

https://docs.z.ai/devpack/latest-model

Claude Code / Goose (Anthropic-compatible): https://api.z.ai/api/anthropic
Codex: https://api.z.ai/api/v1
Other OpenAI-compatible tools: https://api.z.ai/api/coding/paas/v4

The same page also warns that a previously subscribed Coding Plan key may be limited to the Chat protocol. That did not reproduce on the key measured above, but it is the reason Chat has to stay reachable rather than be removed.

Suggested mapping or implementation notes

Add chatCompletionsPath as the mirror of the existing responsesPath, then point one row at both endpoints:

baseUrl: "https://api.z.ai",
adapter: "openai-responses",
responsesPath: "/api/v1/responses",
chatCompletionsPath: "/api/coding/paas/v4/chat/completions",

Chat then becomes the existing per-model opt-in: a user writes modelAdapters with openai-chat, resolveWireProtocolOverride swaps the adapter, and the new path field sends it to the coding prefix. A model that Chat refuses is pinned to Responses through modelWireDefaults, the same mechanism the xAI row uses for grok-4.20-multi-agent.

One dependency worth flagging: modelSuffixBracketStrip is honoured only by the openai-chat and ollama-native adapters today. The roster contains glm-5.3[1m] and glm-5.2[1m], and the bracketed id returns 400 model_not_found upstream, so the strip has to be applied on the Responses path as well before the default flips.

Additional context and attachments

The free-directory glm id is separate and stays on the Chat endpoint. Existing zai users change protocol silently, so this needs a release note plus the modelAdapters opt-in instructions for anyone on a Chat-only key.

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. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 64 / 80

    설명

    이 이슈는 Z.AI(zai) 제공자 행이 Chat Completions 어댑터에 고정되어 있어서, 같은 구독 키가 Responses 프로토콜을 받는데도 프록시가 항상 Chat 경로로만 나가는 문제를 말합니다. 지금 dev HEAD cb7f96cbc의 src/providers/registry.ts를 보면 id: "zai"는 baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat"입니다. 업스트림 와이어는 inbound 라우트가 아니라 라우팅된 provider의 adapter로 고르기 때문에(src/server/adapter-resolve.ts 계열), 클라이언트가 /v1/responses로 들어와도 GLM 모델은 Chat Completions로 나갑니다.

    이미 국내 BigModel 쪽에는 Responses 행이 있습니다. zhipu-bigmodel-responses는 https://open.bigmodel.cn/api/v1에 adapter: "openai-responses"로 앉아 있고, modelInputModalities도 glm-5.3-flash에 image를 명시합니다. 국제 api.z.ai에는 그에 해당하는 Responses 기본 행이 없습니다. 이슈 본문의 live probe(여러 GLM id가 POST /api/v1/responses에서 200, Chat은 같은 키로 403이 날 수 있음)와 /api/v1/models가 Codex형 카탈로그를 돌려준다는 관찰은, “키는 있는데 우리가 Chat만 고른다”는 진단과 맞습니다.

    왜 지금 중요한가. tip은 omo export(#4290/#4294), Devin ACP(#4288), operator account selection(#4284), WS(#4256), oauth pool-kernel(#4279) 쪽에 에너지가 가 있지만, Z.AI는 실제 유료 코딩 플랜 사용자 경로입니다. Chat 경로가 불안정하거나 403이면 “프록시 버그”로 보이기 쉽고, Responses로 바꾸면 모델 목록을 줄이지 않고도 안정 와이어를 얻을 수 있다는 제안은 비용 대비 효과가 큽니다. #4295(ZCode가 Chat으로 붙는 문제)·#4296(Chat 행 modality 누락)과도 같은 Z.AI/클라이언트 묶음입니다.

    우선순위 64입니다. 데이터와 재현이 분명하고, 레지스트리에 Responses 기본(또는 형제 행)만 추가하면 되는 쪽에 가깝습니다. 다만 baseUrl을 coding/paas/v4에서 api/v1로 바꾸는 순간 기존 Chat-only 워크플로·번들(jawcodeBundle: "zai")·키 검증 가정이 흔들릴 수 있어, “기본을 Responses로” vs “형제 행 추가”를 메인테이너가 골라야 합니다.

    라인 - src/providers/registry.ts id: "zai" - 현재 adapter: "openai-chat" + coding paas v4. 이슈 제안은 Responses 기본 + Chat은 모델 단위 opt-in입니다.
    경로/심볼 - zhipu-bigmodel-responses - 이미 있는 Responses 행을 api.z.ai용으로 복제할지, zai 행의 adapter/baseUrl만 바꿀지 결정이 필요합니다. 국내/국제 호스트를 한 행에 섞으면 안 됩니다.
    경로/심볼 - src/server/adapter-resolve.ts - “inbound가 Responses여도 provider adapter가 Chat이면 Chat으로 나간다”는 이슈 설명이 HEAD 설계와 맞습니다. 이 이슈만으로 adapter-resolve 정책을 바꾸기보다, provider 행을 고치는 편이 범위가 작습니다.
    관련 - #4296 - Chat 행에 modelInputModalities가 없으면 export가 flash를 text-only로 광고합니다. Responses로 옮기면 카탈로그가 풍부해질 수 있지만, Chat 형제 행을 남긴다면 #4296은 따로 고쳐야 합니다.

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

    • zai 기본을 Responses(https://api.z.ai/api/v1)로 바꿀지, zai-responses 같은 형제 행을 추가하고 기본만 옮길지
    • 기존 Chat coding paas 경로를 모델 opt-in으로 남길지, 문서화만 하고 제거할지
    • liveModels/GET /models Codex 카탈로그를 국제 행에도 켤지

    너의 추천
    수락하세요. 구현은 zhipu-bigmodel-responses를 참고해 api.z.ai용 Responses 행(또는 zai 기본 전환)을 registry에 추가하고, Chat paas는 명시적 opt-in으로 남기는 쪽이 안전합니다. #4296·#4295와 한 묶음으로 계획하되, PR은 registry 와이어 전환과 export kind 변경을 섞지 말고 나누세요.

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

  2. added 2 commits that reference this issue on Sep 11, 2026
    19f7064
    e4b32ed
  3. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    OwnerAuthor

    Landed on `dev` in #4307. PRs here target `dev`, so closing by hand. zai now defaults to the Responses protocol on https://api.z.ai/api/v1/responses, with Chat reachable per model through modelAdapters via the new chatCompletionsPath field.

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 reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions