mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
fix(antigravity): preserve tool_result images, native Gemini functionCall/thoughtSignature, and composite /v1beta routing
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user