diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index e459380faa..0063bb753c 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1537,6 +1537,19 @@ 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. + if ( + model.api.id.toLowerCase().includes("deepseek") && + schema.type === undefined && + Array.isArray(schema.anyOf) && + schema.anyOf.length > 0 && + schema.anyOf.every((branch) => isPlainObject(branch) && branch.type === "object") + ) { + schema = { ...schema, type: "object" } + } + return schema } diff --git a/packages/opencode/test/tool/workflow-provider-schema.test.ts b/packages/opencode/test/tool/workflow-provider-schema.test.ts index b7be8f53dc..665fd6159c 100644 --- a/packages/opencode/test/tool/workflow-provider-schema.test.ts +++ b/packages/opencode/test/tool/workflow-provider-schema.test.ts @@ -11,6 +11,10 @@ import { ProviderTransform } from "../../src/provider/transform" const openaiModel = { providerID: "openai", api: { id: "gpt-4.1", npm: "@ai-sdk/openai" } } as never const azureModel = { providerID: "azure", api: { id: "gpt-4.1", npm: "@ai-sdk/azure" } } as never const geminiModel = { providerID: "google", api: { id: "gemini-3-pro", npm: "@ai-sdk/google" } } as never +const deepseekModel = { + providerID: "deepseek", + api: { id: "deepseek-v4-pro", npm: "@ai-sdk/openai-compatible" }, +} as never type JsonSchemaNode = { anyOf?: JsonSchemaNode[] @@ -117,6 +121,16 @@ describe("workflow provider-facing schema", () => { expect(Object.keys(record(resultBranch))).toEqual(expect.arrayContaining(["cursor", "limit"])) }) + test("DeepSeek transformation presents the workflow union as an object-root function schema", () => { + const transformed = ProviderTransform.schema( + deepseekModel, + 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),