mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: filter out empty parts in Gemini format conversion to prevent API errors
- Added filtering logic to remove empty parts from flatMap results - Prevents "required oneof field data must have one initialized field" errors - Added comprehensive tests for edge cases with empty content blocks - Fixes #9816
This commit is contained in:
parent
94c997c9d6
commit
3f64b95b24
2 changed files with 138 additions and 1 deletions
|
|
@ -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<string, string>()
|
||||
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([])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue