From 91b9bc52a4ec9055d805cb29f7a5716b4bd05687 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:50:40 -0500 Subject: [PATCH] fix(ai): make model capability defaults forward-compatible (#52388) --- packages/ai/src/protocols/openai-responses.ts | 7 ++- packages/ai/src/protocols/zai-chat.ts | 10 +++- packages/ai/test/effort-updates.test.ts | 21 +++++-- packages/ai/test/provider/zai.test.ts | 60 +++++++++++++++---- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/packages/ai/src/protocols/openai-responses.ts b/packages/ai/src/protocols/openai-responses.ts index e36496954b9..610a9883267 100644 --- a/packages/ai/src/protocols/openai-responses.ts +++ b/packages/ai/src/protocols/openai-responses.ts @@ -143,14 +143,15 @@ const adapter = { restoreHostedToolItem: (item: unknown) => (Schema.is(OpenAIResponsesHostedToolItem)(item) ? item : undefined), } satisfies OpenResponses.ProviderAdapter -// GPT-6 Astra, Sol, and Luna accept `configuration_update` only in standard mode (not `reasoning.mode: "pro"` or -// `-pro` slugs), and never alongside automatic `context_management` compaction. +// GPT-6 and later default to `configuration_update` support, except in `reasoning.mode: "pro"` +// or alongside automatic `context_management` compaction. const supportsEffortUpdates = (request: LLMRequest) => { if (request.providerOptions?.contextManagement !== undefined) return false if (Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(request.http?.body?.reasoning)) return false const override = request.model.compatibility?.supportsEffortUpdates if (override !== undefined) return override - return /(?:^|\/)gpt-6-(?:astra|sol|luna)$/i.test(request.model.id) + const match = /(?:^|\/)gpt-(\d+)(?:\.\d+)?(?:-|$)/i.exec(request.model.id) + return match !== null && Number(match[1]) >= 6 } const nativeImageToolInput = (tool: ToolDefinition) => { diff --git a/packages/ai/src/protocols/zai-chat.ts b/packages/ai/src/protocols/zai-chat.ts index 43a1be7ac6e..01f5a3c5b99 100644 --- a/packages/ai/src/protocols/zai-chat.ts +++ b/packages/ai/src/protocols/zai-chat.ts @@ -41,16 +41,20 @@ const Body = Schema.Struct({ user_id: Options.fields.userID, }) +// Tool streaming was introduced in GLM-4.6; later versions inherit support. +const supportsToolStreaming = (modelID: string) => { + const match = /(?:^|\/)glm-(\d+)(?:\.(\d+))?(?:-|$)/i.exec(modelID) + return match !== null && (Number(match[1]) > 4 || (Number(match[1]) === 4 && Number(match[2] ?? 0) >= 6)) +} + const fromRequest = Effect.fn("ZAIChat.fromRequest")(function* (request: LLMRequest) { const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {}) const body = yield* OpenAIChat.protocol.body.from(request) return { ...body, thinking: options.thinking, - // Tool streaming was introduced in GLM-4.6; older models must not receive the opt-in. tool_stream: - options.toolStream ?? - (body.tools?.length && /^glm-(?:4\.[67]|5(?:[.-]|$))/i.test(request.model.id) ? true : undefined), + options.toolStream ?? (body.tools?.length && supportsToolStreaming(request.model.id) ? true : undefined), do_sample: options.doSample, response_format: options.responseFormat, request_id: options.requestID, diff --git a/packages/ai/test/effort-updates.test.ts b/packages/ai/test/effort-updates.test.ts index 0e30911f8ad..f6d3f27aab5 100644 --- a/packages/ai/test/effort-updates.test.ts +++ b/packages/ai/test/effort-updates.test.ts @@ -375,11 +375,24 @@ describe("OpenAI Responses effort updates", () => { ["openai/gpt-6-sol", true], ["gpt-6-luna", true], ["openai/gpt-6-luna", true], - ["gpt-6-astra-2026-09-01", false], - ["gpt-6-sol-pro", false], - ["gpt-6-luna-pro", false], - ["gpt-6-sol-fast", false], + ["gpt-6", true], + ["gpt-6.1-sol", true], + ["openai/gpt-6.1-sol", true], + ["GPT-6.1-SOL", true], + ["gpt-6-astra-2026-09-01", true], + ["gpt-6-sol-pro", true], + ["gpt-6-luna-pro", true], + ["gpt-6-sol-fast", true], + ["gpt-7", true], + ["openai/gpt-7.2-new-family", true], + ["gpt-10.1", true], + ["gpt-5", false], ["gpt-5.6-sol", false], + ["gpt-5.10", false], + ["not-gpt-6-sol", false], + ["gpt-6foo", false], + ["gpt-6.x-sol", false], + ["future-model", false], ] as const) { it.effect(`${supported ? "lowers" : "strips"} markers for ${id}`, () => Effect.gen(function* () { diff --git a/packages/ai/test/provider/zai.test.ts b/packages/ai/test/provider/zai.test.ts index 118380851dc..b7986b7e36f 100644 --- a/packages/ai/test/provider/zai.test.ts +++ b/packages/ai/test/provider/zai.test.ts @@ -54,16 +54,28 @@ it.effect("ZAI Chat retains native thinking fields and scopes tool streaming to baseURL: "https://gateway.example/custom", auth: Auth.header("x-test-key", "fixture"), }) - for (const modelID of [ - "glm-4.5", - "glm-4.5-air", - "glm-4.5-x", - "glm-4.6", - "glm-4.7-flash", - "glm-5.3", - "glm-5.3-flash", - "future-model", - ]) { + for (const [modelID, supported] of [ + ["glm-3.9", false], + ["glm-4", false], + ["glm-4.5", false], + ["glm-4.5-air", false], + ["glm-4.5-x", false], + ["glm-4.6", true], + ["glm-4.7-flash", true], + ["glm-4.8", true], + ["glm-4.10", true], + ["glm-5", true], + ["glm-5.3", true], + ["glm-5.3-flash", true], + ["glm-6", true], + ["glm-10.1-new-family", true], + ["zai/glm-6.1", true], + ["GLM-4.6", true], + ["not-glm-6", false], + ["glm-4.60foo", false], + ["glm-6.x", false], + ["future-model", false], + ] as const) { const result = yield* compileRequest( LLM.request({ model: provider.chat(modelID), @@ -78,9 +90,7 @@ it.effect("ZAI Chat retains native thinking fields and scopes tool streaming to max_tokens: 123, thinking: { type: "enabled", clear_thinking: false }, }) - expect(result.body.tool_stream).toBe( - ["glm-4.6", "glm-4.7-flash", "glm-5.3", "glm-5.3-flash"].includes(modelID) ? true : undefined, - ) + expect(result.body.tool_stream).toBe(supported ? true : undefined) expect(result.body.max_completion_tokens).toBeUndefined() expect(result.body.store).toBeUndefined() expect(result.body.tools).toEqual([ @@ -120,6 +130,30 @@ it.effect("ZAI Chat retains native thinking fields and scopes tool streaming to }), ) +it.effect("ZAI tool streaming defaults require tools and preserve explicit overrides", () => + Effect.gen(function* () { + for (const configure of [ZAI.configure, ZAICodingPlan.configure]) { + const provider = configure({ apiKey: "fixture" }) + for (const item of [ + { id: "glm-6.1", tools: [], toolStream: undefined, expected: undefined }, + { id: "glm-4.5", tools: [tool], toolStream: true, expected: true }, + { id: "glm-6.1", tools: [tool], toolStream: false, expected: false }, + { id: "future-model", tools: [tool], toolStream: true, expected: true }, + ]) { + const result = yield* compileRequest( + LLM.request({ + model: provider.chat(item.id), + prompt: "Hello", + tools: item.tools, + providerOptions: { toolStream: item.toolStream }, + }), + ) + expect(result.body.tool_stream).toBe(item.expected) + } + } + }), +) + it.effect("ZAI lowers effort using the selected native API without inventing thinking settings", () => Effect.gen(function* () { for (const effort of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "future-effort"]) {