Forward a data: URIContent to Anthropic as an inline base64 block - #939
Conversation
buildMessageParam's URIContent case only handled image and PDF as URL sources (anthropic.URLImageSourceParam / URLPDFSourceParam), which require an external http(s) reference. A URIContent carrying a data: URI was sent with the whole data: string as the url, which Anthropic rejects (400). Decode the data: URI and emit a base64 image/PDF block instead, reusing the same primitives as the DataContent branch (NewImageBlockBase64 / Base64PDFSourceParam). This mirrors the Gemini provider (data: -> InlineData) and the OpenAI chat provider (data: -> inline), which already special-case data: URIs on URIContent. Non-data http(s) URLs keep the URL source.
There was a problem hiding this comment.
Pull request overview
Fixes Anthropic provider handling of message.URIContent that contains data: URIs by converting them into inline base64 image/PDF blocks (instead of incorrectly sending the full data: URI as a URL source, which Anthropic rejects).
Changes:
- Detect
data:URIs inbuildMessageParamand map them to Anthropic base64 image/PDF content blocks. - Add a regression test asserting
data:-URIURIContentimages are forwarded as base64 sources.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| provider/anthropicprovider/agent.go | Adds data: URI detection/decoding and forwards inline content as base64 blocks for Anthropic. |
| provider/anthropicprovider/agent_test.go | Adds a regression test for data: URI image forwarding behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| case strings.HasPrefix(strings.ToLower(c.URI), "data:"): | ||
| // A data: URI carries the bytes inline. Anthropic's URL image/PDF | ||
| // sources require an external http(s) reference, so a data: URI sent | ||
| // as a url source is rejected; decode it and send a base64 block | ||
| // instead, mirroring the DataContent branch and the Gemini/OpenAI | ||
| // data: handling. | ||
| data, mediaType, err := message.DecodeDataURI(c.URI) | ||
| if err != nil { | ||
| break | ||
| } | ||
| encoded := base64.StdEncoding.EncodeToString(data) | ||
| switch { | ||
| case strings.HasPrefix(mediaType, "image/"): | ||
| content = append(content, anthropic.NewImageBlockBase64(mediaType, encoded)) | ||
| case isPDFMediaType(mediaType): | ||
| content = append(content, anthropic.NewDocumentBlock(anthropic.Base64PDFSourceParam{Data: encoded})) | ||
| } |
| for _, b := range blocks { | ||
| block, _ := b.(map[string]any) | ||
| source, _ := block["source"].(map[string]any) | ||
| if block["type"] == "image" && source["type"] == "base64" && source["media_type"] == "image/png" { |
|
Scope: user-visible behavior (internal-only implementation change; no exported API surface changed)
Result: ✅ Parity approved — no exported API changes. The fix closes a Go-specific bug caused by the language's unified
|
Problem
In
provider/anthropicprovider/agent.go,buildMessageParam's*message.URIContentcase handles image and PDF only as URL sources (anthropic.URLImageSourceParam/URLPDFSourceParam), which require an external http(s) reference. AURIContentcarrying adata:URI is sent with the entiredata:image/png;base64,...string as theurl, which Anthropic rejects (400 invalid image source). The identical payload asDataContentsucceeds — a cross-provider and intra-file inconsistency.Fix
Detect a
data:URI, decode it withmessage.DecodeDataURI, and emit an inline base64 image/PDF block using the same primitives as theDataContentbranch (anthropic.NewImageBlockBase64/anthropic.Base64PDFSourceParam). Non-data:http(s) URLs keep the URL source.This mirrors the sibling providers that already special-case
data:URIs onURIContent: Gemini (data:→InlineData) and OpenAI chat (data:→ inlineDataContentmapping).Test
TestBuildMessageParam_DataURIImageForwardedAsBase64sends aURIContentwith adata:image/png;base64,...URI and asserts the outgoing content block is abase64image source. Fails before the fix (source.type == "url"with the data URI), passes after.