fix(antigravity): preserve tool_result images, native Gemini functionCall/thoughtSignature, and composite /v1beta routing

This commit is contained in:
Long Li
2026-10-15 06:55:32 +09:00
parent 373d43533a
commit feab6d80dc
7 changed files with 397 additions and 101 deletions
@@ -588,6 +588,7 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
ID: block.ToolUseID,
},
})
parts = append(parts, extractToolResultInlineParts(block.Content)...)
}
}
@@ -638,6 +639,31 @@ func parseToolResultContent(content json.RawMessage, isError bool) string {
return string(content)
}
func extractToolResultInlineParts(content json.RawMessage) []GeminiPart {
if len(content) == 0 {
return nil
}
var innerBlocks []ContentBlock
if err := json.Unmarshal(content, &innerBlocks); err != nil {
return nil
}
var inlineParts []GeminiPart
for _, item := range innerBlocks {
if (item.Type == "image" || item.Type == "document") &&
item.Source != nil &&
item.Source.Type == "base64" &&
strings.TrimSpace(item.Source.Data) != "" {
inlineParts = append(inlineParts, GeminiPart{
InlineData: &GeminiInlineData{
MimeType: item.Source.MediaType,
Data: item.Source.Data,
},
})
}
}
return inlineParts
}
// buildGenerationConfig 构建 generationConfig
const (
defaultMaxOutputTokens = 64000
@@ -694,3 +694,64 @@ func TestToolConfigAlwaysPresent(t *testing.T) {
})
}
}
func TestTransformClaudeToGemini_ToolResultImagesReachUpstream(t *testing.T) {
claudeReq := &ClaudeRequest{
Model: "claude-sonnet-4-5",
Messages: []ClaudeMessage{
{
Role: "assistant",
Content: json.RawMessage(`[
{"type":"tool_use","id":"toolu_img_1","name":"read_image","input":{"path":"colors.png"}}
]`),
},
{
Role: "user",
Content: json.RawMessage(`[
{
"type":"tool_result",
"tool_use_id":"toolu_img_1",
"content":[
{"type":"text","text":"loaded colors.png"},
{"type":"image","source":{"type":"base64","media_type":"image/png","data":"aGVsbG8="}}
]
},
{
"type":"tool_result",
"tool_use_id":"toolu_img_1",
"content":[
{"type":"image","source":{"type":"base64","media_type":"image/jpeg","data":"d29ybGQ="}}
]
}
]`),
},
},
}
body, err := TransformClaudeToGeminiWithOptions(claudeReq, "project-1", "claude-sonnet-4-5", DefaultTransformOptions())
require.NoError(t, err)
var req V1InternalRequest
require.NoError(t, json.Unmarshal(body, &req))
require.Len(t, req.Request.Contents, 2)
userParts := req.Request.Contents[1].Parts
require.Len(t, userParts, 4)
require.NotNil(t, userParts[0].FunctionResponse)
require.Equal(t, "read_image", userParts[0].FunctionResponse.Name)
require.Equal(t, "toolu_img_1", userParts[0].FunctionResponse.ID)
require.Equal(t, "loaded colors.png", userParts[0].FunctionResponse.Response["result"])
require.NotNil(t, userParts[1].InlineData)
require.Equal(t, "image/png", userParts[1].InlineData.MimeType)
require.Equal(t, "aGVsbG8=", userParts[1].InlineData.Data)
require.NotNil(t, userParts[2].FunctionResponse)
require.Equal(t, "read_image", userParts[2].FunctionResponse.Name)
require.Equal(t, "Command executed successfully.", userParts[2].FunctionResponse.Response["result"])
require.NotNil(t, userParts[3].InlineData)
require.Equal(t, "image/jpeg", userParts[3].InlineData.MimeType)
require.Equal(t, "d29ybGQ=", userParts[3].InlineData.Data)
}