Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
39 changes: 34 additions & 5 deletions src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand All @@ -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",
Expand Down
43 changes: 34 additions & 9 deletions tests/providers/provider-registry-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,27 +451,45 @@ 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({
adapter: "openai-responses",
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);
Expand All @@ -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}`);
Expand All @@ -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 () => {
Expand Down
Loading