diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 9d83f265c7c..30e3144bd3e 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -809,4 +809,96 @@ describe("VertexHandler", () => { ) }) }) + + describe("reasoning message filtering", () => { + it("should filter out reasoning type messages before sending to API", async () => { + handler = new AnthropicVertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + // Include a reasoning type message that should be filtered out + const messagesWithReasoning = [ + { + role: "user", + content: "Hello", + } as Anthropic.Messages.MessageParam, + { + type: "reasoning", + encrypted_content: "encrypted_payload", + summary: [], + } as any, // This should be filtered out + { + role: "assistant", + content: "Hi there!", + } as Anthropic.Messages.MessageParam, + ] + + const mockStream = [ + { + type: "message_start", + message: { + usage: { + input_tokens: 10, + output_tokens: 0, + }, + }, + }, + { + type: "content_block_start", + index: 0, + content_block: { + type: "text", + text: "Response", + }, + }, + ] + + const asyncIterator = { + async *[Symbol.asyncIterator]() { + for (const chunk of mockStream) { + yield chunk + } + }, + } + + const mockCreate = vitest.fn().mockResolvedValue(asyncIterator) + ;(handler["client"].messages as any).create = mockCreate + + const stream = handler.createMessage("System prompt", messagesWithReasoning) + const chunks: ApiStreamChunk[] = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + // Verify that the reasoning message was filtered out + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: "Hello", + cache_control: { type: "ephemeral" }, + }, + ], + }, + { + role: "assistant", + content: "Hi there!", + }, + ], + }), + ) + + // Verify the reasoning message was not included + const calledMessages = mockCreate.mock.calls[0][0].messages + expect(calledMessages).toHaveLength(2) + expect(calledMessages.every((msg: any) => msg.type !== "reasoning")).toBe(true) + }) + }) }) diff --git a/src/api/providers/anthropic-vertex.ts b/src/api/providers/anthropic-vertex.ts index c70a15926d3..6e607171707 100644 --- a/src/api/providers/anthropic-vertex.ts +++ b/src/api/providers/anthropic-vertex.ts @@ -70,6 +70,20 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple reasoning: thinking, } = this.getModel() + // Filter out reasoning type messages that are not valid for Vertex AI API + // The message list can include provider-specific meta entries such as + // `{ type: "reasoning", ... }` that are intended only for providers like + // openai-native. Vertex AI doesn't recognize these and will return an error. + type ReasoningMetaLike = { type?: string } + + const filteredMessages = messages.filter((message): message is Anthropic.Messages.MessageParam => { + const meta = message as ReasoningMetaLike + if (meta.type === "reasoning") { + return false + } + return true + }) + /** * Vertex API has specific limitations for prompt caching: * 1. Maximum of 4 blocks can have cache_control @@ -92,7 +106,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple system: supportsPromptCache ? [{ text: systemPrompt, type: "text" as const, cache_control: { type: "ephemeral" } }] : systemPrompt, - messages: supportsPromptCache ? addCacheBreakpoints(messages) : messages, + messages: supportsPromptCache ? addCacheBreakpoints(filteredMessages) : filteredMessages, stream: true, }