Merge pull request #7585 from ResidualBlood/fix/antigravity-pdf-inline-data

fix(antigravity): forward chat PDF file parts as Gemini inlineData
This commit is contained in:
Wesley Liddick
2026-09-28 09:51:28 +08:00
committed by GitHub
4 changed files with 47 additions and 3 deletions
@@ -532,8 +532,8 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
}
parts = append(parts, part)
case "image":
if block.Source != nil && block.Source.Type == "base64" {
case "image", "document":
if block.Source != nil && block.Source.Type == "base64" && strings.TrimSpace(block.Source.Data) != "" {
parts = append(parts, GeminiPart{
InlineData: &GeminiInlineData{
MimeType: block.Source.MediaType,
@@ -646,6 +646,20 @@ func TestGeminiToolConfig_DropsBuiltinsWhenClientFunctionsPresent(t *testing.T)
})
}
func TestBuildParts_DocumentBecomesInlineData(t *testing.T) {
content := `[
{"type":"text","text":"read this"},
{"type":"document","source":{"type":"base64","media_type":"application/pdf","data":"JVBERi0="}}
]`
parts, stripped, err := buildParts(json.RawMessage(content), map[string]string{}, true)
require.NoError(t, err)
require.False(t, stripped)
require.Len(t, parts, 2)
require.NotNil(t, parts[1].InlineData)
require.Equal(t, "application/pdf", parts[1].InlineData.MimeType)
require.Equal(t, "JVBERi0=", parts[1].InlineData.Data)
}
// TestToolConfigAlwaysPresent ensures toolConfig is always emitted, including for
// reasoning models without any tools: upstream rejects requests that omit it.
func TestToolConfigAlwaysPresent(t *testing.T) {
@@ -109,7 +109,23 @@ func TestResponsesToAnthropic_UnknownItemTypeKeepsRecognizableText(t *testing.T)
require.NotContains(t, string(messages[0].Content), "drop me")
}
// user 消息的分片全部不可识别时,以前会退化成 content:"",Anthropic 拒收空内容消息。
// data URI 形式的 input_file 要变成 Anthropic document,供后续 Gemini inlineData 使用。
func TestResponsesToAnthropic_InputFileDataURIBecomesDocument(t *testing.T) {
messages := responsesToAnthropicMessages(t, `[
{"type":"message","role":"user","content":[
{"type":"input_text","text":"read this"},
{"type":"input_file","filename":"token.pdf","file_data":"data:application/pdf;base64,JVBERi0="}
]}
]`)
requireAnthropicMessagesAreSendable(t, messages)
require.Len(t, messages, 1)
require.Contains(t, string(messages[0].Content), `"type":"document"`)
require.Contains(t, string(messages[0].Content), `"media_type":"application/pdf"`)
require.Contains(t, string(messages[0].Content), `"data":"JVBERi0="`)
}
// 只有 file_id、没有 data URI 的 input_file 仍然无法转换,整条消息丢掉。
func TestResponsesToAnthropic_UserMessageWithOnlyUnknownPartsIsDropped(t *testing.T) {
messages := responsesToAnthropicMessages(t, `[
{"type":"message","role":"user","content":[{"type":"input_file","file_id":"file_1"}]}
@@ -530,6 +530,14 @@ func convertResponsesUserToAnthropicContent(raw json.RawMessage) (json.RawMessag
Source: src,
})
}
case "input_file":
src := dataURIToAnthropicFileSource(p.FileData)
if src != nil {
blocks = append(blocks, AnthropicContentBlock{
Type: "document",
Source: src,
})
}
}
}
@@ -617,6 +625,12 @@ func dataURIToAnthropicImageSource(dataURI string) *AnthropicImageSource {
}
}
// dataURIToAnthropicFileSource parses a data URI into a document source.
// file_id-only parts are not convertible here and stay dropped.
func dataURIToAnthropicFileSource(fileData string) *AnthropicImageSource {
return dataURIToAnthropicImageSource(fileData)
}
// mergeConsecutiveMessages merges consecutive messages with the same role
// because Anthropic requires alternating user/assistant turns.
func mergeConsecutiveMessages(messages []AnthropicMessage) []AnthropicMessage {