From f4577c78bf98c3d443846239c92a75cfecb965fa Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 15 Apr 2026 14:45:36 -0500 Subject: [PATCH] wip --- packages/opencode/src/provider/transform.ts | 13 ++++++- .../opencode/test/provider/transform.test.ts | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 3e2b61e039..61561ec969 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -101,7 +101,18 @@ export namespace ProviderTransform { return msg }) } - if (model.api.npm === "@ai-sdk/anthropic") { + if (["@ai-sdk/anthropic", "@ai-sdk/google-vertex/anthropic"].includes(model.api.npm)) { + // Anthropic rejects assistant turns where tool_use blocks are followed by non-tool + // content, e.g. [tool_use, tool_use, text], with: + // `tool_use` ids were found without `tool_result` blocks immediately after... + // + // Reorder that invalid shape into [text] + [tool_use, tool_use]. Consecutive + // assistant messages are later merged by the provider/SDK, so preserving the + // original [tool_use...] then [text] order still produces the invalid payload. + // + // The root cause appears to be somewhere upstream where the stream is originally + // processed. We were unable to locate an exact narrower reproduction elsewhere, + // so we keep this transform in place for the time being. msgs = msgs.flatMap((msg) => { if (msg.role !== "assistant" || !Array.isArray(msg.content)) return [msg] diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 1d99b06af3..4952a126b3 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -1337,6 +1337,44 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } }, ]) }) + + test("splits vertex anthropic assistant messages when text trails tool calls", () => { + const model = { + ...anthropicModel, + providerID: "google-vertex-anthropic", + api: { + id: "claude-sonnet-4@20250514", + url: "https://us-central1-aiplatform.googleapis.com", + npm: "@ai-sdk/google-vertex/anthropic", + }, + } + + const msgs = [ + { + role: "assistant", + content: [ + { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } }, + { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } }, + { type: "text", text: "I checked your home directory and looked for PDF files." }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, model, {}) as any[] + + expect(result).toHaveLength(2) + expect(result[0]).toMatchObject({ + role: "assistant", + content: [{ type: "text", text: "I checked your home directory and looked for PDF files." }], + }) + expect(result[1]).toMatchObject({ + role: "assistant", + content: [ + { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } }, + { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } }, + ], + }) + }) }) describe("ProviderTransform.message - strip openai metadata when store=false", () => {