From 33c03dbd56a4c3b30ba5e5f546a41d571e345bc6 Mon Sep 17 00:00:00 2001 From: Lex Date: Fri, 14 Aug 2026 15:31:57 +0800 Subject: [PATCH] fix(provider): object-root union schemas for all openai-compatible backends The DeepSeek-only guard missed GLM and other OpenAI-compatible relays, which silently emit empty tool arguments for implicit-root anyOf function schemas. Generalize the fix to the @ai-sdk/openai-compatible transport. --- packages/opencode/src/provider/transform.ts | 10 ++++++---- .../test/tool/workflow-provider-schema.test.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 0063bb753c..6291a8fca6 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1537,11 +1537,13 @@ export function schema(model: Provider.Model, schema: JSONSchema7): JSONSchema7 schema = sanitizeGemini(schema) } - // DeepSeek rejects function schemas whose root type is implicit. Effect - // emits object-only discriminated unions as a root `anyOf`; retaining the - // union while declaring its shared object type preserves every branch. + // OpenAI-compatible backends (DeepSeek, GLM, and other relays) reject + // function schemas whose root type is implicit — the model emits empty + // tool arguments instead of erroring. Effect emits object-only + // discriminated unions as a root `anyOf`; retaining the union while + // declaring its shared object type preserves every branch. if ( - model.api.id.toLowerCase().includes("deepseek") && + model.api.npm === "@ai-sdk/openai-compatible" && schema.type === undefined && Array.isArray(schema.anyOf) && schema.anyOf.length > 0 && diff --git a/packages/opencode/test/tool/workflow-provider-schema.test.ts b/packages/opencode/test/tool/workflow-provider-schema.test.ts index 665fd6159c..ee7565a16c 100644 --- a/packages/opencode/test/tool/workflow-provider-schema.test.ts +++ b/packages/opencode/test/tool/workflow-provider-schema.test.ts @@ -15,6 +15,10 @@ const deepseekModel = { providerID: "deepseek", api: { id: "deepseek-v4-pro", npm: "@ai-sdk/openai-compatible" }, } as never +const glmModel = { + providerID: "local-proxy", + api: { id: "glm-5.3", npm: "@ai-sdk/openai-compatible" }, +} as never type JsonSchemaNode = { anyOf?: JsonSchemaNode[] @@ -131,6 +135,16 @@ describe("workflow provider-facing schema", () => { expect(branchByAction(transformed, "start", "spec_path")).toHaveLength(1) }) + test("OpenAI-compatible transports (GLM relay) also get the object-root union", () => { + const transformed = ProviderTransform.schema( + glmModel, + ToolJsonSchema.fromSchema(Parameters as never), + ) as JsonSchemaNode + expect(transformed.type).toBe("object") + expect(branches(transformed)).toHaveLength(10) + expect(branchByAction(transformed, "start", "spec_path")).toHaveLength(1) + }) + test("post-change byte sizes stay at the recorded evidence", async () => { const evidence = (await Bun.file( new URL("./fixtures/workflow-parameters-post-change.json", import.meta.url),