From 1bfae0deeb7c9440beed74014d3117e1967579b0 Mon Sep 17 00:00:00 2001 From: HyeokjaeLee Date: Mon, 27 Jul 2026 00:11:44 +0900 Subject: [PATCH 1/2] fix(ai): send max_tokens for Z.AI providers Z.AI (zai, zai-coding-cn, api.z.ai, open.bigmodel.cn) ignores max_completion_tokens and only honors max_tokens, so the configured output cap never applied and Z.AI fell back to its 65536 default. Add isZai to detectCompat's useMaxTokens condition. --- packages/ai/src/api/openai-completions.ts | 8 +++++- .../openai-completions-tool-choice.test.ts | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts index 29bb064f9bb..f0d114dfdb5 100644 --- a/packages/ai/src/api/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -1417,7 +1417,13 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet isAntLing; const useMaxTokens = - baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing; + baseUrl.includes("chutes.ai") || + isMoonshot || + isCloudflareAiGateway || + isTogether || + isNvidia || + isAntLing || + isZai; const isGrok = provider === "xai" || baseUrl.includes("api.x.ai"); const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com"); diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index b585bb431d8..d337a258f12 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -1389,6 +1389,33 @@ describe("openai-completions tool_choice", () => { } }); + it("sends max_tokens for Z.AI completions models", async () => { + // Z.AI resolves maxTokensField via detectCompat (isZai), not per-model compat like OpenCode. + const cases = [getModel("zai", "glm-5.1")!, getModel("zai", "glm-5.2")!] as const; + + for (const model of cases) { + let payload: unknown; + + await streamSimple( + model, + { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + maxTokens: 123, + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { max_tokens?: number; max_completion_tokens?: number }; + expect(params.max_tokens).toBe(123); + expect(params.max_completion_tokens).toBeUndefined(); + } + }); + it("omits reasoning effort for OpenCode Grok Build", async () => { const model = getModel("opencode", "grok-build-0.1")!; let payload: unknown; From 2ffe87bc790549a07990f59067439b98705536a4 Mon Sep 17 00:00:00 2001 From: HyeokjaeLee Date: Tue, 28 Jul 2026 15:53:21 +0900 Subject: [PATCH 2/2] fix(ai): set zai max_tokens compat in model generator Per review feedback: the model generator now maps Z.AI to max_tokens, so regenerated Z.AI model entries carry compat.maxTokensField="max_tokens" (Z.AI ignores max_completion_tokens). The runtime detectCompat change is kept as a fallback for custom/self-hosted Z.AI base URLs not in the catalog, matching how moonshot/together/nvidia are handled in both places. --- packages/ai/scripts/generate-models.ts | 2 +- packages/ai/test/openai-completions-tool-choice.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index be2e08a76aa..80661ec948c 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -578,7 +578,7 @@ function detectOpenAICompletionsCompat(model: Model<"openai-completions">): Open isAntLing; const useMaxTokens = - baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing; + baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing || isZai; const isGrok = provider === "xai" || baseUrl.includes("api.x.ai"); const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com"); diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index d337a258f12..284b6329b51 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -1390,10 +1390,10 @@ describe("openai-completions tool_choice", () => { }); it("sends max_tokens for Z.AI completions models", async () => { - // Z.AI resolves maxTokensField via detectCompat (isZai), not per-model compat like OpenCode. const cases = [getModel("zai", "glm-5.1")!, getModel("zai", "glm-5.2")!] as const; for (const model of cases) { + expect(model.compat?.maxTokensField).toBe("max_tokens"); let payload: unknown; await streamSimple(