diff --git a/src/api/transform/__tests__/gemini-format.spec.ts b/src/api/transform/__tests__/gemini-format.spec.ts index 14ab6f8d8f..eae940a42c 100644 --- a/src/api/transform/__tests__/gemini-format.spec.ts +++ b/src/api/transform/__tests__/gemini-format.spec.ts @@ -463,4 +463,131 @@ describe("convertAnthropicMessageToGemini", () => { }, ]) }) + + it("should return empty array when all content blocks are skipped", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "reasoning" as any, + text: "Thinking...", + }, + { + type: "thinking" as any, + text: "More thinking...", + }, + ], + } + + const result = convertAnthropicMessageToGemini(anthropicMessage) + + expect(result).toEqual([]) + }) + + it("should handle message with only empty tool results", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "tool-123", + content: null as any, + }, + { + type: "tool_result", + tool_use_id: "tool-456", + content: undefined as any, + }, + ], + } + + const result = convertAnthropicMessageToGemini(anthropicMessage) + + expect(result).toEqual([]) + }) + + it("should filter out empty parts from mixed content", () => { + const toolIdToName = new Map() + toolIdToName.set("tool-123", "calculator") + + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: [ + { type: "text", text: "Let me help you" }, + { + type: "tool_result", + tool_use_id: "tool-999", + content: null as any, // This should be filtered out + }, + { + type: "reasoning" as any, + text: "Thinking...", // This should be filtered out + }, + { + type: "tool_use", + id: "tool-123", + name: "calculator", + input: { a: 1, b: 2 }, + }, + ], + } + + const result = convertAnthropicMessageToGemini(anthropicMessage, { toolIdToName }) + + expect(result).toEqual([ + { + role: "model", + parts: [ + { text: "Let me help you" }, + { + functionCall: { + name: "calculator", + args: { a: 1, b: 2 }, + }, + thoughtSignature: "skip_thought_signature_validator", + }, + ], + }, + ]) + }) + + it("should handle thoughtSignature blocks that are excluded", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: [ + { + type: "thoughtSignature", + thoughtSignature: "some-signature", + } as any, + { type: "text", text: "Response text" }, + ], + } + + // When includeThoughtSignatures is false, thoughtSignature blocks should be filtered out + const result = convertAnthropicMessageToGemini(anthropicMessage, { includeThoughtSignatures: false }) + + expect(result).toEqual([ + { + role: "model", + parts: [{ text: "Response text" }], + }, + ]) + }) + + it("should handle message with only thoughtSignature when excluded", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: [ + { + type: "thoughtSignature", + thoughtSignature: "some-signature", + } as any, + ], + } + + // When includeThoughtSignatures is false and there's only a thoughtSignature block + const result = convertAnthropicMessageToGemini(anthropicMessage, { includeThoughtSignatures: false }) + + expect(result).toEqual([]) + }) }) diff --git a/src/api/transform/gemini-format.ts b/src/api/transform/gemini-format.ts index f9d855dda9..dffa5a9fe6 100644 --- a/src/api/transform/gemini-format.ts +++ b/src/api/transform/gemini-format.ts @@ -47,7 +47,7 @@ export function convertAnthropicContentToGemini( return [{ text: content }] } - return content.flatMap((block): Part | Part[] => { + const parts = content.flatMap((block): Part | Part[] => { // Handle thoughtSignature blocks first if (isThoughtSignatureContentBlock(block)) { if (includeThoughtSignatures && typeof block.thoughtSignature === "string") { @@ -135,6 +135,14 @@ export function convertAnthropicContentToGemini( return [] } }) + + // Filter out any empty arrays that were returned by flatMap + // This prevents sending empty parts to the Gemini API which would cause errors + return parts.filter((part): part is Part => { + // Check if the part is actually a Part object (not an empty array) + // Empty arrays from flatMap will be filtered out here + return part && typeof part === "object" && Object.keys(part).length > 0 + }) } export function convertAnthropicMessageToGemini( @@ -143,6 +151,8 @@ export function convertAnthropicMessageToGemini( ): Content[] { const parts = convertAnthropicContentToGemini(message.content, options) + // Skip creating Content objects if there are no valid parts + // This prevents sending empty messages to the Gemini API if (parts.length === 0) { return [] }