diff --git a/providers/anthropic/anthropic.go b/providers/anthropic/anthropic.go index 2f05b2f1289a12b3577357ed7c5a584ef17ca932..882a22c9c51b5301302da7135f024088e4d6eac9 100644 --- a/providers/anthropic/anthropic.go +++ b/providers/anthropic/anthropic.go @@ -831,7 +831,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) @@ -840,10 +839,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) } } @@ -1050,7 +1061,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 d447954cbf1d78819e6091aedfa3da6af8b742fc..69c41f94ff38214f4393b09d68caf8ca371cfbf1 100644 --- a/providers/anthropic/anthropic_test.go +++ b/providers/anthropic/anthropic_test.go @@ -262,6 +262,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() @@ -271,7 +315,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) { Content: []fantasy.MessagePart{ fantasy.FilePart{ Data: []byte("not supported"), - MediaType: "application/pdf", + MediaType: "application/zip", }, }, },