diff --git a/packages/ai/src/providers/anthropic-wire.ts b/packages/ai/src/providers/anthropic-wire.ts index b5d3f33d6..11665e0d5 100644 --- a/packages/ai/src/providers/anthropic-wire.ts +++ b/packages/ai/src/providers/anthropic-wire.ts @@ -89,8 +89,17 @@ export type WebSearchToolResultBlockParam = { export function isAnthropicWebSearchHistoryBlock(block: { type: string; name?: unknown; + id?: unknown; + tool_use_id?: unknown; + content?: unknown; }): block is WebSearchServerToolUseBlockParam | WebSearchToolResultBlockParam { - return block.type === "web_search_tool_result" || (block.type === "server_tool_use" && block.name === "web_search"); + if (block.type === "server_tool_use") { + return block.name === "web_search" && typeof block.id === "string" && block.id.length > 0; + } + if (block.type === "web_search_tool_result") { + return typeof block.tool_use_id === "string" && block.tool_use_id.length > 0 && Object.hasOwn(block, "content"); + } + return false; } export type ThinkingBlockParam = { diff --git a/packages/ai/test/auth-gateway-anthropic-messages.test.ts b/packages/ai/test/auth-gateway-anthropic-messages.test.ts index a519c3170..538df9197 100644 --- a/packages/ai/test/auth-gateway-anthropic-messages.test.ts +++ b/packages/ai/test/auth-gateway-anthropic-messages.test.ts @@ -344,6 +344,28 @@ describe("anthropic-messages parseRequest", () => { ]); }); + it("flattens malformed web-search history blocks instead of preserving invalid replay state", () => { + const parsed = parseRequest({ + model: "claude-opus-4-7", + max_tokens: 8, + messages: [ + { role: "user", content: "weather?" }, + { + role: "assistant", + content: [ + { type: "server_tool_use", name: "web_search" }, + { type: "web_search_tool_result", tool_use_id: "srvtoolu_1" }, + { type: "web_search_tool_result", content: [] }, + ], + }, + ], + }); + const assistant = parsed.context.messages.find(message => message.role === "assistant"); + expect(assistant?.content).toHaveLength(3); + expect(assistant?.content.every(block => block.type === "text")).toBe(true); + expect(assistant?.content.some(block => block.type === "anthropicServerTool")).toBe(false); + }); + it("does not retain a partial code-execution server-tool history", () => { const parsed = parseRequest({ model: "claude-opus-4-7",