diff --git a/docs-site/src/content/docs/fr/guides/providers.md b/docs-site/src/content/docs/fr/guides/providers.md index 988ca03cf70..89b3a7c626e 100644 --- a/docs-site/src/content/docs/fr/guides/providers.md +++ b/docs-site/src/content/docs/fr/guides/providers.md @@ -312,6 +312,7 @@ promotionnels de Cline ne sont accessibles que dans l'IDE ou la CLI Cline, pas p | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (liste statique)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Forfait à jetons (par défaut) : `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Facturation à l'usage : `https://dashscope.aliyuncs.com/compatible-mode/v1` · ou personnalisé | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -495,7 +496,7 @@ une barre trompeuse. > programmation interactifs. L'automatisation générale par API, les services applicatifs personnalisés et les > traitements par lots non interactifs sont interdits et peuvent entraîner la suspension de la clé du forfait. -> **Deux routes GLM :** `zai` correspond à l'abonnement international Z.AI Coding Plan ; `zhipu-bigmodel` +> **Facturation GLM :** `zai` correspond à l'abonnement international Z.AI Coding Plan ; `zhipu-bigmodel` > correspond au point de terminaison national BigModel de Zhipu, facturé à l'usage. Les hôtes, les clés et la > facturation diffèrent : une clé émise pour l'un ne permet pas de s'authentifier auprès de l'autre. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 05f586a8160..5e469798164 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -367,6 +367,7 @@ free-experimentation model. | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| BigModel Coding Plan (Responses, static roster) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token plan (default): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · or Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -582,10 +583,44 @@ negative, or internally inconsistent billing totals produce no report rather tha > interactive coding tools only. General API automation, custom application backends, and > non-interactive batch use are prohibited and may cause the plan key to be suspended. -> **Two GLM routes:** `zai` is the Z.AI international coding-plan subscription; `zhipu-bigmodel` +> **GLM billing routes:** `zai` is the Z.AI international coding-plan subscription; `zhipu-bigmodel` > is Zhipu's domestic BigModel pay-as-you-go endpoint. Different hosts, different keys, different > billing — a key issued for one will not authenticate against the other. +### BigModel Coding Plan over Responses + +Select **Zhipu AI — BigModel Coding Plan (Responses)** (`zhipu-bigmodel-responses`) +for the `openai-responses` endpoint `https://open.bigmodel.cn/api/v1`. This is separate +from `zhipu-bigmodel-coding`, which uses Chat Completions at `/api/coding/paas/v4`. + +The preset uses a **static roster** (`liveModels: false`) taken from the +[official BigModel Codex example](https://docs.bigmodel.cn/cn/coding-plan/tool/codex.md): + +| Model | Context tokens | Upstream selectable effort | Default effort | Reasoning summaries | +| --- | ---: | --- | --- | --- | +| `glm-5.3` | 1,048,576 | `low`, `high`, `max` | `max` | Supported | +| `glm-5-turbo` | 204,800 | None (empty list) | `max` | Supported | + +Both entries declare upstream text-only input. The Codex catalog advertises text and +image because opencodex's existing vision sidecar can describe images for text-only +models. Image handling requires an available, enabled vision sidecar; this does not +declare native BigModel image support. + +The default model is `glm-5.3`; Responses reasoning content is preserved on replay. +The existing Codex export adds its compatibility +`ultra` tier to GLM-5.3 and omits Turbo's default-effort field because Turbo has no +selectable ladder; the provider metadata still records `max` for both models. +For Turbo, outgoing Responses requests omit `reasoning.effort`, including a caller's +`max` or `ultra`, while preserving requested reasoning summaries. This leaves effort +selection to the upstream default; opencodex does not inject a selectable or wire `max`. + +The example's `models.json` is a local catalog file, not a documented HTTP model-list +response. This preset does not perform live model discovery. `glm-5.3-flash` is not +seeded here because its exact Responses metadata is not verified. An existing custom +provider with the same name keeps its configured destination and metadata. +CLI key login also skips the undocumented `/models` probe and reports validation as +unknown; successful key authentication is established by a subsequent inference request. + ### Multiple API keys Key-based providers can also keep multiple keys. Adding a key through the Providers page stores it diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index 33be9fc6947..ae692e1e2c5 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -216,6 +216,7 @@ Cline IDE/CLI のみで API からは使えません。`minimax/minimax-m2.5` | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (静的モデル一覧)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | トークンプラン(デフォルト): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 従量課金: `https://dashscope.aliyuncs.com/compatible-mode/v1` · またはカスタム | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -338,7 +339,7 @@ model ごとに capability が異なるため、provider 全体の parallel tool > コーディングツール専用としています。一般的な API 自動化、カスタムアプリのバックエンド、 > 非対話型バッチ利用は禁止されており、プランキーが停止される場合があります。 -> **GLM の経路は 2 つあります:** `zai` は Z.AI の国際コーディングプラン契約、`zhipu-bigmodel` +> **GLM の課金経路:** `zai` は Z.AI の国際コーディングプラン契約、`zhipu-bigmodel` > は Zhipu の中国国内向け BigModel 従量課金エンドポイントです。ホストもキーも課金も別で、 > 一方で発行したキーはもう一方では認証されません。 diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 7781cc4f52d..c49ede4ec67 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -216,6 +216,7 @@ Cline IDE/CLI에서만 제공되며 API로는 사용할 수 없습니다. `minim | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (정적 모델 목록)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token plan(기본): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 종량제: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 또는 사용자 지정 | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -328,7 +329,7 @@ provider 전체 parallel tool call이나 OpenAI `reasoning_effort`를 광고하 > 안내합니다. 일반 API 자동화, 사용자 애플리케이션 백엔드 및 비대화형 일괄 호출은 금지되며 > 플랜 키가 정지될 수 있습니다. -> **GLM 경로는 두 개입니다:** `zai`는 Z.AI 국제 코딩 플랜 구독이고, `zhipu-bigmodel`은 +> **GLM 과금 경로:** `zai`는 Z.AI 국제 코딩 플랜 구독이고, `zhipu-bigmodel`은 > Zhipu의 중국 내수 BigModel 종량제 엔드포인트입니다. 호스트도 키도 과금도 다르며, 한쪽에서 > 발급한 키는 다른 쪽에서 인증되지 않습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 02d5ba43237..85a9ba69037 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -747,6 +747,12 @@ container usually has no unlocked keychain session, so requests would fail close `${ENV_VAR}` reference in the service environment there instead. Env references are left untouched by `store`. +The `zhipu-bigmodel-responses` preset seeds `glm-5.3` and `glm-5-turbo` with +`liveModels: false` for `https://open.bigmodel.cn/api/v1`. Its static roster and +per-model context, effort, and summary metadata come from the +[BigModel Responses guide](/guides/providers/#bigmodel-coding-plan-over-responses). +The official local `models.json` example does not establish a live `/models` API. + With `liveModels: false`, an empty or omitted `models` list seeds the configured `defaultModel` first, followed by `retainModels`; duplicate ids are removed while preserving first occurrence. A nonempty explicit `models` list instead seeds `models` followed by `retainModels`, without diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index b057cf77c6f..e680f1bd913 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -229,6 +229,7 @@ opencodex поставляется с 79 встроенными пресетам | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (статический список)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token plan (по умолчанию): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · или Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -367,7 +368,7 @@ plan. Ключ создаётся в [дашборде Featherless](https://feat > в интерактивных инструментах программирования. Автоматизация общего API, серверы пользовательских > приложений и неинтерактивные пакетные вызовы запрещены и могут привести к блокировке ключа плана. -> **Два маршрута GLM:** `zai` — это международная подписка Z.AI на coding-план, а `zhipu-bigmodel` — +> **Тарификация GLM:** `zai` — это международная подписка Z.AI на coding-план, а `zhipu-bigmodel` — > внутренняя китайская конечная точка BigModel с оплатой по факту использования. Разные хосты, > разные ключи, разная тарификация: ключ от одного сервиса не подойдёт к другому. diff --git a/docs-site/src/content/docs/tr/guides/providers.md b/docs-site/src/content/docs/tr/guides/providers.md index c5564e74a1f..6ff4f1c0381 100644 --- a/docs-site/src/content/docs/tr/guides/providers.md +++ b/docs-site/src/content/docs/tr/guides/providers.md @@ -355,6 +355,7 @@ yalnızca Cline IDE/CLI içinde mevcuttur; `minimax/minimax-m2.5` belgelenmiş A | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Kodlama) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (statik model listesi)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token planı (varsayılan): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Kullandıkça öde: `https://dashscope.aliyuncs.com/compatible-mode/v1` · veya Özel | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -537,7 +538,7 @@ tutarsız faturalandırma toplamları yanıltıcı bir çubuk yerine hiçbir rap > **Tencent Cloud Coding Plan kullanım kısıtlaması:** Tencent bu aboneliği yalnızca etkileşimli kodlama araçları için belgeler. Genel API otomasyonu, özel uygulama arka uçları ve etkileşimsiz toplu kullanım yasaktır ve plan anahtarının askıya alınmasına neden olabilir. -> **İki GLM rotası:** `zai`, Z.AI uluslararası kodlama planı aboneliğidir; `zhipu-bigmodel`, Zhipu'nun yerel BigModel kullandıkça öde uç noktasıdır. Farklı ana bilgisayarlar, farklı anahtarlar, farklı faturalandırma — biri için verilen bir anahtar diğerine karşı kimlik doğrulaması yapmaz. +> **GLM faturalandırma rotaları:** `zai`, Z.AI uluslararası kodlama planı aboneliğidir; `zhipu-bigmodel`, Zhipu'nun yerel BigModel kullandıkça öde uç noktasıdır. Farklı ana bilgisayarlar, farklı anahtarlar, farklı faturalandırma — biri için verilen bir anahtar diğerine karşı kimlik doğrulaması yapmaz. ### Birden fazla API anahtarı diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index 3fb72a4e6a2..b4010cdae53 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -205,6 +205,7 @@ Cline IDE/CLI 中提供,不能通过 API 使用;`minimax/minimax-m2.5` 是 | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | 智谱 AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (静态模型列表)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token plan(默认): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 按量付费: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 或自定义 | | 腾讯云 Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -316,7 +317,7 @@ Bearer key。公开模型列表只保留同时报告 `model_type: chat` 和 `cha > **腾讯云 Coding Plan 使用限制:**腾讯将此订阅限定为交互式编程工具使用。禁止通用 API > 自动化、自定义应用后端和非交互式批量调用;违规使用可能导致套餐密钥被停用。 -> **两条 GLM 线路:**`zai` 是 Z.AI 的国际 coding plan 订阅,`zhipu-bigmodel` 是智谱国内 +> **GLM 计费线路:**`zai` 是 Z.AI 的国际 coding plan 订阅,`zhipu-bigmodel` 是智谱国内 > BigModel 的按量付费端点。二者主机、密钥与计费均不同,为其中一方签发的密钥无法在另一方通过鉴权。 ### 多个 API 密钥 diff --git a/docs-site/src/content/docs/zh-tw/guides/providers.md b/docs-site/src/content/docs/zh-tw/guides/providers.md index 96cf91ccfb2..d26d093b7ec 100644 --- a/docs-site/src/content/docs/zh-tw/guides/providers.md +++ b/docs-site/src/content/docs/zh-tw/guides/providers.md @@ -273,6 +273,7 @@ IDE/CLI,不透過 API;`minimax/minimax-m2.5` 是文件列出的 API 免費 | NVIDIA NIM | `https://integrate.api.nvidia.com/v1` | | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | +| [BigModel Coding Plan — Responses (靜態模型清單)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | | Qwen Cloud | Token plan(預設):`https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · pay as you go:`https://dashscope.aliyuncs.com/compatible-mode/v1` · 或 Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | @@ -418,7 +419,7 @@ quota probe 只會把 active key 傳送到 canonical A6API host,並拒絕 redi > **Tencent Cloud Coding Plan 使用限制:** Tencent 文件將此訂閱限定為互動式 coding tool。一般 API > automation、自訂 application backend 與非互動 batch 使用都被禁止,並可能造成 plan key 被停用。 -> **兩條 GLM 路徑:** `zai` 是 Z.AI 國際 Coding Plan 訂閱;`zhipu-bigmodel` 是智譜國內 BigModel +> **GLM 計費路徑:** `zai` 是 Z.AI 國際 Coding Plan 訂閱;`zhipu-bigmodel` 是智譜國內 BigModel > pay-as-you-go endpoint。兩者 host、key 與 billing 都不同;其中一邊發出的 key 無法在另一邊通過認證。 ### 多個 API 金鑰 diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 07c0556d5ef..1b8c1b076e1 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -630,6 +630,13 @@ function mapRoutedResponsesReasoningEffort( if (provider.authMode === "forward") return body; if (configuredReasoningEfforts(provider, modelId) === undefined) return body; if (!isPlainObject(body) || !isPlainObject(body.reasoning)) return body; + const declaredEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId) ?? provider.reasoningEfforts; + // An explicitly empty ladder means no effort control, not no reasoning output. + // Omit only effort so the upstream default applies; unknown/non-rankable ladders stay untouched. + if (declaredEfforts?.length === 0 && Object.hasOwn(body.reasoning, "effort")) { + const { effort: _effort, ...reasoning } = body.reasoning; + return { ...body, reasoning: Object.keys(reasoning).length > 0 ? reasoning : undefined }; + } const requested = body.reasoning.effort; if (typeof requested !== "string") return body; diff --git a/src/generated/model-metadata.ts b/src/generated/model-metadata.ts index 662cf6f1a7d..7220aa7509e 100644 --- a/src/generated/model-metadata.ts +++ b/src/generated/model-metadata.ts @@ -31,6 +31,7 @@ const PROVIDER_ALIASES: Record = { "moonshot": "moonshot", "zhipu-bigmodel": "zai", "zhipu-bigmodel-coding": "zai", + "zhipu-bigmodel-responses": "zai", "minimax": "minimax", "minimax-cn": "minimax" } as const; diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 82f70a6a6a4..ef7cb59e004 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -2544,6 +2544,37 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ // yields an empty picker at runtime. note: "Domestic BigModel Coding Plan endpoint (open.bigmodel.cn)", }, + // Narrowed carry of #3641: the official Codex example declares a local static catalog, + // not an HTTP /models contract. Keep Responses separate from the Chat endpoint above. + // Source: https://docs.bigmodel.cn/cn/coding-plan/tool/codex.md (checked 2026-09-07). + { + id: "zhipu-bigmodel-responses", + label: "Zhipu AI — BigModel Coding Plan (Responses)", + baseUrl: "https://open.bigmodel.cn/api/v1", + adapter: "openai-responses", + authKind: "key", + dashboardUrl: "https://bigmodel.cn/console/usercenter/apikeys", + defaultModel: "glm-5.3", + models: ["glm-5.3", "glm-5-turbo"], + liveModels: false, + // The local Codex catalog does not establish an authenticated HTTP /models contract. + apiKeyValidation: "unknown", + jawcodeBundle: "zai", + // A pre-existing same-named custom provider must retain its destination and key boundary. + preserveCustomDestination: true, + modelContextWindows: { "glm-5.3": 1_048_576, "glm-5-turbo": 204_800 }, + modelInputModalities: { "glm-5.3": ["text"], "glm-5-turbo": ["text"] }, + modelReasoningEfforts: { + "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS, + // Explicitly empty: Turbo must not inherit the generic selectable effort ladder. + "glm-5-turbo": [], + }, + modelDefaultReasoningEfforts: { "glm-5.3": "max", "glm-5-turbo": "max" }, + modelSupportsReasoningSummaries: { "glm-5.3": true, "glm-5-turbo": true }, + // Responses replay uses this provider-level flag, not the Chat-path model list. + preserveResponsesReasoningContent: true, + note: "Domestic BigModel Coding Plan Responses endpoint; static model roster", + }, { id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" }, { id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" }, // SiliconFlow publishes an OpenAI-compatible chat endpoint and a dynamic model catalog. Do not diff --git a/structure/01_runtime.md b/structure/01_runtime.md index 7fb1c00997e..1601e5762bb 100644 --- a/structure/01_runtime.md +++ b/structure/01_runtime.md @@ -166,6 +166,22 @@ OAuth presets resolve discovery against the same canonical registry transport as before any adapter-specific transport override, so a stale configured `baseUrl` cannot receive an OAuth bearer token. +The BigModel Coding Plan Responses preset uses the separately documented +`https://open.bigmodel.cn/api/v1` transport and a static catalog. Its provider row +disables live discovery: a local Codex `models.json` example does not establish an +authenticated HTTP models endpoint. Its static context and reasoning metadata are +kept in the canonical registry, including an explicit empty selectable effort +ladder for `glm-5-turbo`. + +Raycast is a managed client export, not an upstream model provider. Its YAML +contribution owns only the unique `providers/[id=opencodex]` entry, with the +existing manifest and fingerprint checks protecting user-owned provider values. +Ambiguous selector matches and incompatible containers cannot be adopted or +mutated. Catalog refresh uses the existing owned-integration activation check; +an unowned client remains disconnected. OpenCodex omits Raycast API-key fields +and exports only to eligible local targets. Pro detection is an advisory hint, +not an authentication or entitlement decision. + ## Remote Hub hardening ownership `src/remote/protocol.ts` owns pure interval/feature negotiation. `src/client/hub-client.ts` owns bounded, schema-validated remote catalog consumption and key-id probes. `src/client/hub-relay.ts` is a fixed-authority management relay with URL, header, body, redirect, and stream bounds. The public data listener remains the direct client→hub path; the loopback management ingress never serves data-plane routes. diff --git a/tests/providers/provider-registry-parity.test.ts b/tests/providers/provider-registry-parity.test.ts index be84701e0b3..238a5808ea4 100644 --- a/tests/providers/provider-registry-parity.test.ts +++ b/tests/providers/provider-registry-parity.test.ts @@ -1,16 +1,17 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, spyOn, test } from "bun:test"; import { buildCatalogEntries } from "../../src/codex/catalog"; import { CURSOR_NO_VISION_MODELS } from "../../src/adapters/cursor/discovery"; import { getModelMetadata, resolveMetadataProvider } from "../../src/generated/model-metadata"; import { buildInitProviders } from "../../src/cli/init"; import { OAUTH_PROVIDERS } from "../../src/oauth"; -import { enrichProviderFromCatalog, KEY_LOGIN_PROVIDERS } from "../../src/oauth/key-providers"; +import { enrichProviderFromCatalog, KEY_LOGIN_PROVIDERS, validateApiKey } from "../../src/oauth/key-providers"; import { deriveFeaturedProviderIds, deriveInitProviders, deriveJawcodeAliases, deriveKeyLoginMap, deriveProviderPresets, + enrichProviderFromRegistry, providerConfigSeed, } from "../../src/providers/derive"; import { PROVIDER_REGISTRY } from "../../src/providers/registry"; @@ -33,7 +34,7 @@ function nativeTemplate(): Record { const EXPECTED_KEY_PROVIDER_IDS = [ "anthropic-apikey", "openai-apikey", "meta-model", "umans", "opencode-go", "neuralwatt", "openrouter", "cline-pass", "cline", "orcarouter", "bizrouter", "groq", "google", "google-vertex", "azure-openai", "deepseek", "cerebras", "chutes", "deepinfra", "hyperbolic", "nscale", "vultr", "baseten", "commandcode", "sambanova", "nebius", "digitalocean", "scaleway", "featherless", "novita", "together", "fireworks", "firepass", "moonshot", - "huggingface", "nvidia", "venice", "zai", "zhipu-bigmodel", "zhipu-bigmodel-coding", "nanogpt", "synthetic", "siliconflow", "qwen-cloud", "tencent-coding-plan", + "huggingface", "nvidia", "venice", "zai", "zhipu-bigmodel", "zhipu-bigmodel-coding", "zhipu-bigmodel-responses", "nanogpt", "synthetic", "siliconflow", "qwen-cloud", "tencent-coding-plan", "volcengine", "volcengine-coding-plan", "volcengine-agent-plan", "qianfan", "alibaba", "alibaba-token-plan", "alibaba-token-plan-intl", "parallel", "zenmux", "litellm", "ollama-cloud", "mistral", "minimax", "minimax-cn", "kimi-code", "opencode-zen", "vercel-ai-gateway", "opencode-free", "xiaomi", "xiaomi-mimo", "kilo", "mimo-free", "mimo", "cloudflare-ai-gateway", "cloudflare-workers-ai", "gitlab-duo", @@ -440,6 +441,104 @@ describe("provider registry parity", () => { expect(glm53Entry?.default_reasoning_level).toBe("max"); }); + test("BigModel Responses exports only the officially documented static Codex models", () => { + // Independent oracle: https://docs.bigmodel.cn/cn/coding-plan/tool/codex.md, + // local models.json example checked 2026-09-07; not an authenticated /models response. + const id = "zhipu-bigmodel-responses"; + const registry = PROVIDER_REGISTRY.find(entry => entry.id === id)!; + expect(registry).toMatchObject({ + adapter: "openai-responses", + baseUrl: "https://open.bigmodel.cn/api/v1", + authKind: "key", + defaultModel: "glm-5.3", + models: ["glm-5.3", "glm-5-turbo"], + liveModels: false, + preserveCustomDestination: true, + preserveResponsesReasoningContent: true, + }); + expect(registry.modelDiscovery).toBeUndefined(); + expect(registry.preserveReasoningContentModels).toBeUndefined(); + const upstreamModalities = { "glm-5.3": ["text"], "glm-5-turbo": ["text"] }; + expect(registry.modelInputModalities).toEqual(upstreamModalities); + expect(KEY_LOGIN_PROVIDERS[id]).toMatchObject({ + models: ["glm-5.3", "glm-5-turbo"], liveModels: false, apiKeyValidation: "unknown", + }); + const provider = providerConfigSeed(registry); + enrichProviderFromRegistry(id, provider); + expect(provider.liveModels).toBe(false); + expect(provider.preserveResponsesReasoningContent).toBe(true); + const models = provider.models!.map(modelId => applyProviderConfigHints(id, provider, { + provider: id, id: modelId, + })); + // The official upstream declaration stays text-only. Catalog hints add image for the + // existing vision sidecar (vision/eligibility.ts), not native BigModel image support. + expect(provider.modelInputModalities).toEqual(upstreamModalities); + expect(models).toMatchObject([ + { id: "glm-5.3", contextWindow: 1_048_576, reasoningEfforts: ["low", "high", "max"], + defaultReasoningEffort: "max", supportsReasoningSummaries: true, inputModalities: ["text", "image"] }, + { id: "glm-5-turbo", contextWindow: 204_800, reasoningEfforts: [], + defaultReasoningEffort: "max", supportsReasoningSummaries: true, inputModalities: ["text", "image"] }, + ]); + const entries = buildCatalogEntries(nativeTemplate(), [], models); + for (const [modelId, window, efforts] of [ + ["glm-5.3", 1_048_576, ["low", "high", "max", "ultra"]], + ["glm-5-turbo", 204_800, []], + ] as const) { + const entry = entries.find(row => row.slug === `${id}/${modelId}`); + expect(entry).toMatchObject({ + context_window: window, supports_reasoning_summaries: true, + input_modalities: ["text", "image"], + }); + // Existing export policy adds a compatibility ultra tier and omits the default + // for empty ladders. The provider/CatalogModel defaults above remain official max. + expect(entry?.default_reasoning_level).toBe(modelId === "glm-5-turbo" ? undefined : "max"); + expect((entry?.supported_reasoning_levels as Array<{ effort: string }>).map(row => row.effort)) + .toEqual([...efforts]); + } + expect(entries.some(entry => String(entry.slug).includes("glm-5.3-flash"))).toBe(false); + }); + + test("BigModel Responses key login does not probe an undocumented models endpoint", async () => { + const fetchSpy = spyOn(globalThis, "fetch").mockImplementation(async () => new Response(null, { status: 403 })); + try { + const id = "zhipu-bigmodel-responses"; + expect(await validateApiKey(id, KEY_LOGIN_PROVIDERS[id], "test-bigmodel-key")).toBe("unknown"); + expect(fetchSpy).not.toHaveBeenCalled(); + } finally { + fetchSpy.mockRestore(); + } + }); + + test("BigModel Responses name collisions preserve custom transport and metadata", () => { + const id = "zhipu-bigmodel-responses"; + // Exercise both a different destination on the same wire and the canonical URL on + // another wire. Neither may acquire this preset's transport or per-model defaults. + for (const transport of [ + { adapter: "openai-responses", baseUrl: "https://custom.example.test/api/v1" }, + { adapter: "openai-chat", baseUrl: "https://open.bigmodel.cn/api/v1" }, + ]) { + const provider: OcxProviderConfig = { + ...transport, authMode: "key", apiKey: "test-custom-key", liveModels: true, + models: ["glm-5.3"], modelContextWindows: { "glm-5.3": 32_768 }, + modelReasoningEfforts: { "glm-5.3": ["medium"] }, + modelDefaultReasoningEfforts: { "glm-5.3": "medium" }, + modelSupportsReasoningSummaries: { "glm-5.3": false }, + }; + const enriched = structuredClone(provider); + enrichProviderFromRegistry(id, enriched); + expect(enriched).toEqual(provider); + const config: OcxConfig = { port: 10100, defaultProvider: id, providers: { [id]: provider } }; + const routed = routeModel(config, `${id}/glm-5.3`); + expect(routed.provider).toMatchObject(provider); + expect(routed.provider.modelContextWindows).toEqual({ "glm-5.3": 32_768 }); + expect(routed.provider.modelReasoningEfforts).toEqual({ "glm-5.3": ["medium"] }); + expect(routed.provider.modelDefaultReasoningEfforts).toEqual({ "glm-5.3": "medium" }); + expect(routed.provider.modelSupportsReasoningSummaries).toEqual({ "glm-5.3": false }); + expect(routed.provider.preserveResponsesReasoningContent).toBeUndefined(); + expect(routed.modelId).toBe("glm-5.3"); + } + }); + test("Anthropic API-key provider mirrors the OAuth entry's models on the key flow", () => { const anthropicOauth = PROVIDER_REGISTRY.find(entry => entry.id === "anthropic"); expect(KEY_LOGIN_PROVIDERS["anthropic-apikey"]).toMatchObject({ @@ -948,6 +1047,7 @@ describe("provider registry parity", () => { "minimax-cn": "minimax", "zhipu-bigmodel": "zai", "zhipu-bigmodel-coding": "zai", + "zhipu-bigmodel-responses": "zai", }); expect(resolveMetadataProvider("gemini")).toBe("google"); expect(resolveMetadataProvider("minimax-cn")).toBe("minimax"); diff --git a/tests/responses/openai-responses-passthrough.test.ts b/tests/responses/openai-responses-passthrough.test.ts index 0277da71d4e..24bc8e0e020 100644 --- a/tests/responses/openai-responses-passthrough.test.ts +++ b/tests/responses/openai-responses-passthrough.test.ts @@ -569,6 +569,58 @@ describe("DeepSeek Responses endpoint contract", () => { } }); + test.each([undefined, "max", "ultra"])("BigModel Turbo omits outbound effort %s and preserves summary requests", (effort) => { + const id = "zhipu-bigmodel-responses"; + const config: OcxConfig = { + port: 10100, + defaultProvider: id, + providers: { [id]: providerConfigSeed(getProviderRegistryEntry(id)!) }, + }; + const route = routeModel(config, `${id}/glm-5-turbo`); + for (const withSummary of [false, true]) { + const raw = { + model: route.modelId, + input: "ping", + ...(effort !== undefined || withSummary ? { + reasoning: { + ...(effort !== undefined ? { effort } : {}), + ...(withSummary ? { summary: "auto" } : {}), + }, + } : {}), + }; + const before = structuredClone(raw); + const request = createResponsesPassthroughAdapter(route.provider).buildRequest(parseRequest(raw)); + const wire = JSON.parse(request.body); + expect(request.url).toBe("https://open.bigmodel.cn/api/v1/responses"); + if (withSummary) expect(wire.reasoning).toEqual({ summary: "auto" }); + else expect(wire).not.toHaveProperty("reasoning"); + expect(raw).toEqual(before); + } + }); + + test("a provider-wide empty ladder removes schema-valid raw effort", () => { + const keyed = { adapter: "openai-responses", baseUrl: "https://example.test/v1", authMode: "key" as const }; + const raw = { model: "model", input: "ping", reasoning: { effort: "high", summary: "auto" } }; + const wire = JSON.parse(createResponsesPassthroughAdapter({ ...keyed, reasoningEfforts: [] }) + .buildRequest(parseRequest(raw)).body); + expect(wire.reasoning).toEqual({ summary: "auto" }); + expect(raw.reasoning.effort).toBe("high"); + }); + + test("empty-ladder repair preserves unknown, non-rankable and native forward effort behavior", () => { + const keyed = { adapter: "openai-responses", baseUrl: "https://example.test/v1", authMode: "key" as const }; + for (const unchanged of [keyed, { ...keyed, reasoningEfforts: ["enabled"] }, { ...provider, reasoningEfforts: [] }]) { + const raw = { model: "gpt-5.6-sol", input: "ping", reasoning: { effort: "ultra" } }; + const wire = JSON.parse(createResponsesPassthroughAdapter(unchanged).buildRequest(parseRequest(raw)).body); + expect(wire.reasoning.effort).toBe("ultra"); + } + // A model-specific nonempty ladder overrides a provider-wide empty declaration. + const wire = JSON.parse(createResponsesPassthroughAdapter({ + ...keyed, reasoningEfforts: [], modelReasoningEfforts: { model: ["low", "high", "max"] }, + }).buildRequest(parseRequest({ model: "model", input: "ping", reasoning: { effort: "ultra" } })).body); + expect(wire.reasoning.effort).toBe("max"); + }); + test("a config saved before the fix is backfilled, and a hand-set path is preserved", () => { const saved = { adapter: "openai-chat", baseUrl: "https://api.deepseek.com", apiKey: "sk-test" } as Parameters[1]; enrichProviderFromRegistry("deepseek", saved);