mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
fix: forward attached filenames to Anthropic chat models (#26051)
Previously, fantasy's Anthropic provider adapter accepted PDF and text FileParts but dropped the filename on the floor, so Claude (direct or via Bedrock) saw the document bytes without any handle and could not answer questions like "what's in foo.pdf". Other providers (OpenAI, Gemini, OpenRouter, Vercel) already forwarded filenames. Bumps `coder/fantasy` past [coder/fantasy#38](https://github.com/coder/fantasy/pull/38), which sanitizes `FilePart.Filename` and sets it as the Anthropic `DocumentBlockParam.Title` for both `application/pdf` and `text/*` attachments, and emits a `CallWarning` for unsupported `FilePart` media types instead of silently dropping them. On this side, plumbs the resolved filename through `partsToMessageParts` so the `FilePart` literal carries it into the provider. The existing `TestModelFromConfig_AnthropicPDFFilePartReachesProvider` regression test is extended to assert the outbound Anthropic request includes the sanitized title (`quarterly_report.v1.pdf` becomes `quarterly report v1 pdf`). Closes CODAGT-545
This commit is contained in:
@@ -1588,6 +1588,7 @@ func partsToMessageParts(
|
||||
continue
|
||||
}
|
||||
result = append(result, fantasy.FilePart{
|
||||
Filename: name,
|
||||
Data: data,
|
||||
MediaType: mediaType,
|
||||
ProviderOptions: opts,
|
||||
|
||||
@@ -502,6 +502,7 @@ func TestConvertMessagesWithFiles_MixedResolvedAndMissingFilePartsInSingleMessag
|
||||
|
||||
filePart, ok := fantasy.AsMessagePart[fantasy.FilePart](prompt[0].Content[0])
|
||||
require.True(t, ok, "expected first part to stay a FilePart")
|
||||
require.Equal(t, "resolved.png", filePart.Filename)
|
||||
require.Equal(t, resolvedData, filePart.Data)
|
||||
require.Equal(t, "image/png", filePart.MediaType)
|
||||
|
||||
|
||||
@@ -1401,10 +1401,11 @@ func TestModelFromConfig_ExtraHeaders(t *testing.T) {
|
||||
// path that lets a user-uploaded PDF actually reach Claude/Bedrock: a
|
||||
// fantasy.FilePart with MediaType "application/pdf" must be serialized as an
|
||||
// Anthropic "document" content block with a base64 source carrying the PDF
|
||||
// bytes. Older fantasy versions silently dropped PDF FileParts in the
|
||||
// Anthropic provider, so the user message ended up empty and the model never
|
||||
// saw the document. See coder/fantasy#37 (cherry-pick of upstream
|
||||
// charmbracelet/fantasy#197). The Generate call would fail outright on the
|
||||
// bytes and a sanitized filename as the document title. Older fantasy versions
|
||||
// silently dropped PDF FileParts in the Anthropic provider, so the user
|
||||
// message ended up empty and the model never saw the document. The underlying
|
||||
// PDF block support came from coder/fantasy#37, a cherry-pick of upstream
|
||||
// charmbracelet/fantasy#197. The Generate call would fail outright on the
|
||||
// regressed code path because the dropped FilePart leaves the request with
|
||||
// zero messages.
|
||||
func TestModelFromConfig_AnthropicPDFFilePartReachesProvider(t *testing.T) {
|
||||
@@ -1423,6 +1424,7 @@ func TestModelFromConfig_AnthropicPDFFilePartReachesProvider(t *testing.T) {
|
||||
|
||||
var blocks []struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Source struct {
|
||||
Type string `json:"type"`
|
||||
MediaType string `json:"media_type"`
|
||||
@@ -1439,6 +1441,11 @@ func TestModelFromConfig_AnthropicPDFFilePartReachesProvider(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, "base64", block.Source.Type, "PDF document block must use a base64 source")
|
||||
assert.Equal(t, wantData, block.Source.Data, "PDF bytes must round-trip base64 unchanged")
|
||||
assert.Equal(t,
|
||||
"quarterly report v1 pdf",
|
||||
block.Title,
|
||||
"PDF filename must reach Anthropic as a sanitized document title",
|
||||
)
|
||||
if block.Source.MediaType != "" {
|
||||
assert.Equal(t, "application/pdf", block.Source.MediaType)
|
||||
}
|
||||
@@ -1462,7 +1469,11 @@ func TestModelFromConfig_AnthropicPDFFilePartReachesProvider(t *testing.T) {
|
||||
{
|
||||
Role: fantasy.MessageRoleUser,
|
||||
Content: []fantasy.MessagePart{
|
||||
fantasy.FilePart{Data: pdfData, MediaType: "application/pdf"},
|
||||
fantasy.FilePart{
|
||||
Filename: "quarterly_report.v1.pdf",
|
||||
Data: pdfData,
|
||||
MediaType: "application/pdf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -94,7 +94,11 @@ replace github.com/spf13/afero => github.com/aslilac/afero v0.0.0-20250403163713
|
||||
// emit a Base64 PDF document block for application/pdf FileParts on the
|
||||
// Anthropic provider so user-uploaded PDFs actually reach Claude/Bedrock
|
||||
// instead of being silently dropped.
|
||||
// 11) coder/fantasy#39, support Anthropic thinking_display natively.
|
||||
// 11) coder/fantasy#38, forward PDF and text filenames as a sanitized
|
||||
// Anthropic document title so Claude can refer to attachments by
|
||||
// name, and warn on unsupported FilePart media types instead of
|
||||
// silently dropping them.
|
||||
// 12) coder/fantasy#39, support Anthropic thinking_display natively.
|
||||
// See: https://github.com/coder/fantasy/commits/a2a3f2171ec8
|
||||
replace charm.land/fantasy => github.com/coder/fantasy v0.0.0-20260604204802-a2a3f2171ec8
|
||||
|
||||
|
||||
Reference in New Issue
Block a user