diff --git a/providers/anthropic/anthropic.go b/providers/anthropic/anthropic.go index 4be919e09..badbb4bd4 100644 --- a/providers/anthropic/anthropic.go +++ b/providers/anthropic/anthropic.go @@ -927,7 +927,6 @@ func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBl if !ok { continue } - // TODO: handle other file types switch { case strings.HasPrefix(file.MediaType, "image/"): base64Encoded := base64.StdEncoding.EncodeToString(file.Data) @@ -936,10 +935,22 @@ func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBl imageBlock.OfImage.CacheControl = anthropic.NewCacheControlEphemeralParam() } anthropicContent = append(anthropicContent, imageBlock) + case file.MediaType == "application/pdf": + base64Encoded := base64.StdEncoding.EncodeToString(file.Data) + docBlock := anthropic.NewDocumentBlock(anthropic.Base64PDFSourceParam{ + Data: base64Encoded, + }) + if cacheControl != nil { + docBlock.OfDocument.CacheControl = anthropic.NewCacheControlEphemeralParam() + } + anthropicContent = append(anthropicContent, docBlock) case strings.HasPrefix(file.MediaType, "text/"): documentBlock := anthropic.NewDocumentBlock(anthropic.PlainTextSourceParam{ Data: string(file.Data), }) + if cacheControl != nil { + documentBlock.OfDocument.CacheControl = anthropic.NewCacheControlEphemeralParam() + } anthropicContent = append(anthropicContent, documentBlock) } } @@ -1135,7 +1146,7 @@ func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBl func hasVisibleUserContent(content []anthropic.ContentBlockParamUnion) bool { for _, block := range content { - if block.OfText != nil || block.OfImage != nil || block.OfToolResult != nil { + if block.OfText != nil || block.OfImage != nil || block.OfDocument != nil || block.OfToolResult != nil { return true } } diff --git a/providers/anthropic/anthropic_test.go b/providers/anthropic/anthropic_test.go index 176b4f276..14781a3f2 100644 --- a/providers/anthropic/anthropic_test.go +++ b/providers/anthropic/anthropic_test.go @@ -263,6 +263,50 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) { require.Empty(t, warnings) }) + t.Run("should keep user messages with PDF content", func(t *testing.T) { + t.Parallel() + + prompt := fantasy.Prompt{ + { + Role: fantasy.MessageRoleUser, + Content: []fantasy.MessagePart{ + fantasy.FilePart{ + Data: []byte("fake pdf data"), + MediaType: "application/pdf", + }, + }, + }, + } + + systemBlocks, messages, warnings := toPrompt(prompt, true) + + require.Empty(t, systemBlocks) + require.Len(t, messages, 1) + require.Empty(t, warnings) + }) + + t.Run("should keep user messages with text document content", func(t *testing.T) { + t.Parallel() + + prompt := fantasy.Prompt{ + { + Role: fantasy.MessageRoleUser, + Content: []fantasy.MessagePart{ + fantasy.FilePart{ + Data: []byte("# Hello World\nSome markdown content"), + MediaType: "text/markdown", + }, + }, + }, + } + + systemBlocks, messages, warnings := toPrompt(prompt, true) + + require.Empty(t, systemBlocks) + require.Len(t, messages, 1) + require.Empty(t, warnings) + }) + t.Run("should drop user messages without visible content", func(t *testing.T) { t.Parallel() @@ -272,7 +316,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) { Content: []fantasy.MessagePart{ fantasy.FilePart{ Data: []byte("not supported"), - MediaType: "application/pdf", + MediaType: "application/zip", }, }, },