From 846dfadf7a88e98cf3dfbb2128f7e3710481d704 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 11 Sep 2026 10:06:51 +0900 Subject: [PATCH 1/2] provider: seed GLM-5.3-Flash on the BigModel Responses preset The Responses preset shipped a two-model roster read off the models.json sample on BigModel's Codex page. That sample is a starter catalog, not the endpoint's roster, and taking it for the latter left Flash off a subscription that sells it. Three upstream pages disagree with the old reading, all checked 2026-09-11: - coding-plan/latest-model.md pins Codex to https://open.bigmodel.cn/api/v1 -- this preset's exact baseUrl -- and states GLM Coding Plan supports GLM-5.3 and GLM-5.3-Flash for every tier, then treats glm-5.3-flash as an already-callable id in that tool. - coding-plan/overview.md states GLM-5-Turbo calls are auto-switched to GLM-5.3-Flash. The preset already lists glm-5-turbo, so it was already reaching Flash on this endpoint under another name. - guide/models/vlm/glm-5.3-flash.md gives native multimodal input, a 1M window, and text parameters "consistent with GLM-5.3". Flash is seeded into the roster and all five sibling per-model maps. Its context tracks the 5.3 sibling on this row (1_048_576) rather than the Chat row's 1_000_000: both are documented as "1M", and this preset expresses that family's 1M the way BigModel's own Codex declaration does, so one preset does not claim two sizes for one documented window. Flash declares ["text", "image"]. It is the only row here that can actually see an image; the other two are text-only upstream and get image back from the vision sidecar at catalog-build time. Declaring Flash text-only would push a native VLM's pictures through a describe-it-first detour and hand the model prose about an image it could have read -- the defect ZAI_GLM_5X_SIDECAR_VISION_MODELS already exists to prevent on the Chat rows. The oracle moves in this same commit, because a test asserting the old roster is not evidence for the new one, it is the thing being changed. It previously locked models to two entries and asserted glm-5.3-flash was absent from the export. It now pins the three-model contract, Flash's exported window, ladder (low/high/max plus the compatibility ultra tier), default effort and native image modality, and keeps asserting the part no document supports: there is still no HTTP /models contract here, so liveModels and apiKeyValidation must not drift. Closes #4201 --- src/providers/registry.ts | 39 ++++++++++++++--- .../provider-registry-parity.test.ts | 43 +++++++++++++++---- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/providers/registry.ts b/src/providers/registry.ts index d4c6c542d0..c4ea553d2d 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -2640,6 +2640,23 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ // 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). + // + // #4201 completes the roster. The `models.json` example on that Codex page is a *starter + // catalog*, not the set of models the endpoint serves, and reading it as the latter is what + // left Flash off a subscription that sells it. Three upstream pages say so directly, all + // checked 2026-09-11: + // - coding-plan/latest-model.md pins Codex to THIS baseUrl + // (`Codex:https://open.bigmodel.cn/api/v1`) and opens with GLM Coding Plan supporting + // GLM-5.3 and GLM-5.3-Flash for every tier (Max & Pro & Lite), then treats + // `glm-5.3-flash` as an already-callable id in that same tool. + // - coding-plan/overview.md: every plan supports GLM-5.3 and GLM-5.3-Flash, and calls to + // GLM-5-Turbo are auto-switched to GLM-5.3-Flash. Turbo below is therefore an alias of + // the very model this row omitted, which is the clearest statement that the endpoint + // serves Flash: it was already serving it under another name. + // - guide/models/vlm/glm-5.3-flash.md: native multimodal input, 1M context, and text + // parameters explicitly "consistent with GLM-5.3". + // No authenticated /models probe is implied by any of this, so `liveModels` and + // `apiKeyValidation` below are deliberately unchanged. { id: "zhipu-bigmodel-responses", label: "Zhipu AI — BigModel Coding Plan (Responses)", @@ -2648,22 +2665,34 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ authKind: "key", dashboardUrl: "https://bigmodel.cn/console/usercenter/apikeys", defaultModel: "glm-5.3", - models: ["glm-5.3", "glm-5-turbo"], + models: ["glm-5.3", "glm-5.3-flash", "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"] }, + // Flash tracks its 5.3 sibling on this row rather than the Chat row's 1_000_000. Both + // models are documented as "1M", and this preset expresses that family's 1M the way + // BigModel's own Codex declaration does. Splitting the two would leave one preset + // claiming two different sizes for one documented window. + modelContextWindows: { "glm-5.3": 1_048_576, "glm-5.3-flash": 1_048_576, "glm-5-turbo": 204_800 }, + // Flash is the only row here that can actually see an image. Its siblings are declared + // text-only and get `image` back from the vision sidecar at catalog-build time; declaring + // Flash text-only would route a native VLM's pictures through a describe-it-first detour + // and hand the model prose about an image it could have read (same defect + // ZAI_GLM_5X_SIDECAR_VISION_MODELS exists to prevent on the Chat rows). + modelInputModalities: { "glm-5.3": ["text"], "glm-5.3-flash": ["text", "image"], "glm-5-turbo": ["text"] }, modelReasoningEfforts: { "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS, + // Same three effective tiers: upstream documents Flash's text parameters as identical + // to GLM-5.3, and the Codex effort table folds every inbound value into low/high/max. + "glm-5.3-flash": 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 }, + modelDefaultReasoningEfforts: { "glm-5.3": "max", "glm-5.3-flash": "max", "glm-5-turbo": "max" }, + modelSupportsReasoningSummaries: { "glm-5.3": true, "glm-5.3-flash": 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", diff --git a/tests/providers/provider-registry-parity.test.ts b/tests/providers/provider-registry-parity.test.ts index ab9a5c64a3..1dcc89ca1f 100644 --- a/tests/providers/provider-registry-parity.test.ts +++ b/tests/providers/provider-registry-parity.test.ts @@ -451,9 +451,20 @@ 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. + test("BigModel Responses exports the documented Coding Plan roster for the Codex endpoint", () => { + // Independent oracle, all checked 2026-09-11 and none of them an authenticated /models + // response. The earlier version of this test read the models.json sample on + // https://docs.bigmodel.cn/cn/coding-plan/tool/codex.md as the endpoint's whole roster and + // hard-locked two models. It is a starter catalog, and three other upstream pages contradict + // that reading (#4201): + // - coding-plan/latest-model.md binds Codex to https://open.bigmodel.cn/api/v1 and states + // GLM-5.3 and GLM-5.3-Flash are available to every plan tier. + // - coding-plan/overview.md states GLM-5-Turbo calls are auto-switched to GLM-5.3-Flash, + // so this preset was already reaching Flash through the Turbo id it does list. + // - guide/models/vlm/glm-5.3-flash.md gives native multimodal input, a 1M window, and text + // parameters "consistent with GLM-5.3". + // What stays locked is the part no document supports: there is still no HTTP /models + // contract here, so liveModels and apiKeyValidation must not drift. const id = "zhipu-bigmodel-responses"; const registry = PROVIDER_REGISTRY.find(entry => entry.id === id)!; expect(registry).toMatchObject({ @@ -461,17 +472,24 @@ describe("provider registry parity", () => { baseUrl: "https://open.bigmodel.cn/api/v1", authKind: "key", defaultModel: "glm-5.3", - models: ["glm-5.3", "glm-5-turbo"], + models: ["glm-5.3", "glm-5.3-flash", "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"] }; + // Flash is the one row upstream documents as natively multimodal; the other two are text. + // Pinned separately from the global VLM rule above so that copying glm-5.3's ["text"] onto + // Flash fails here, naming this preset, rather than only in a loop over every provider. + const upstreamModalities = { + "glm-5.3": ["text"], "glm-5.3-flash": ["text", "image"], "glm-5-turbo": ["text"], + }; expect(registry.modelInputModalities).toEqual(upstreamModalities); + expect(registry.modelInputModalities?.["glm-5.3-flash"]).toContain("image"); + expect(registry.noVisionModels ?? []).not.toContain("glm-5.3-flash"); expect(KEY_LOGIN_PROVIDERS[id]).toMatchObject({ - models: ["glm-5.3", "glm-5-turbo"], liveModels: false, apiKeyValidation: "unknown", + models: ["glm-5.3", "glm-5.3-flash", "glm-5-turbo"], liveModels: false, apiKeyValidation: "unknown", }); const provider = providerConfigSeed(registry); enrichProviderFromRegistry(id, provider); @@ -480,18 +498,23 @@ describe("provider registry parity", () => { 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. + // glm-5.3 and glm-5-turbo stay text-only upstream and get image back from the existing + // vision sidecar (vision/eligibility.ts). Flash already declares image, so its catalog + // modality is the model's own capability rather than a sidecar detour — the rows look + // alike below, and this assertion is what keeps the reason for them different. 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.3-flash", 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.3-flash", 1_048_576, ["low", "high", "max", "ultra"]], ["glm-5-turbo", 204_800, []], ] as const) { const entry = entries.find(row => row.slug === `${id}/${modelId}`); @@ -505,7 +528,9 @@ describe("provider registry parity", () => { 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); + // The reported gap: Flash reaches the exported catalog for this preset, once, under its + // own slug rather than only as the Turbo alias upstream silently redirects. + expect(entries.filter(entry => String(entry.slug) === `${id}/glm-5.3-flash`)).toHaveLength(1); }); test("BigModel Responses key login does not probe an undocumented models endpoint", async () => { From c838ff5f5110cafbf9d2e473cc61be64faef4bef Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 11 Sep 2026 16:54:47 +0900 Subject: [PATCH 2/2] docs(providers): seed Flash in the BigModel Responses roster the page describes The preset now seeds glm-5.3-flash, and both pages still said it did not. The guide went further and told the reader why it was excluded, which stops being staleness and becomes a false statement the moment this lands. The modality note is split rather than rewritten: 5.3 and Turbo still reach images through the vision sidecar, while Flash declares native text and image input, so the page should not describe one mechanism for all three rows. --- .../src/content/docs/guides/providers.md | 25 ++++++++++++------- .../docs/reference/configuration/providers.md | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index d4a0f1c724..d93dbe0af5 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -817,18 +817,23 @@ Select **Zhipu AI — BigModel Coding Plan (Responses)** (`zhipu-bigmodel-respon 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): +The preset uses a **static roster** (`liveModels: false`) taken from the published +[GLM Coding Plan documentation](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.3-flash` | 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. +`glm-5.3` and `glm-5-turbo` declare upstream text-only input. The Codex catalog +advertises text and image for them because opencodex's existing vision sidecar can +describe images for text-only models; that path requires an available, enabled vision +sidecar and does not claim native BigModel image support. + +`glm-5.3-flash` is the exception: it declares native `text` and `image` input, because +upstream documents it as a natively multimodal model. It therefore reads pictures +directly instead of being routed through the describe-it-first sidecar detour. The default model is `glm-5.3`; Responses reasoning content is preserved on replay. The existing Codex export adds its compatibility @@ -839,9 +844,11 @@ For Turbo, outgoing Responses requests omit `reasoning.effort`, including a call 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. +response, and not the set of models the endpoint serves — the Coding Plan pages state +that every plan tier reaches GLM-5.3 and GLM-5.3-Flash, and that GLM-5-Turbo calls are +auto-switched to Flash, so this endpoint was already serving Flash under the Turbo id. +This preset still does not perform live model discovery. 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. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index d40de409d2..e2769ab807 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -836,7 +836,7 @@ 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 +The `zhipu-bigmodel-responses` preset seeds `glm-5.3`, `glm-5.3-flash` 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).