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). 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 () => {