Skip to content

Forward a data: URIContent to Anthropic as an inline base64 block - #939

Open
PratikDhanave (PratikDhanave) wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:anthropic-datauri-uricontent-base64
Open

Forward a data: URIContent to Anthropic as an inline base64 block#939
PratikDhanave (PratikDhanave) wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:anthropic-datauri-uricontent-base64

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Problem

In provider/anthropicprovider/agent.go, buildMessageParam's *message.URIContent case handles image and PDF only as URL sources (anthropic.URLImageSourceParam / URLPDFSourceParam), which require an external http(s) reference. A URIContent carrying a data: URI is sent with the entire data:image/png;base64,... string as the url, which Anthropic rejects (400 invalid image source). The identical payload as DataContent succeeds — a cross-provider and intra-file inconsistency.

Fix

Detect a data: URI, decode it with message.DecodeDataURI, and emit an inline base64 image/PDF block using the same primitives as the DataContent branch (anthropic.NewImageBlockBase64 / anthropic.Base64PDFSourceParam). Non-data: http(s) URLs keep the URL source.

This mirrors the sibling providers that already special-case data: URIs on URIContent: Gemini (data:InlineData) and OpenAI chat (data: → inline DataContent mapping).

Test

TestBuildMessageParam_DataURIImageForwardedAsBase64 sends a URIContent with a data:image/png;base64,... URI and asserts the outgoing content block is a base64 image source. Fails before the fix (source.type == "url" with the data URI), passes after.

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.
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner August 28, 2026 08:41
Copilot AI lite review requested due to automatic review settings August 28, 2026 08:41
@github-actions github-actions Bot added area:provider Changes files in the provider area area:provider/anthropic Changes files in the provider / anthropic area size:medium At most 100 changed lines across at most 5 files pending-auto-risk Automatic risk classification is in progress labels Aug 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in buildMessageParam and map them to Anthropic base64 image/PDF content blocks.
  • Add a regression test asserting data:-URI URIContent images 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.

Comment on lines +695 to +711
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" {
@github-actions

Copy link
Copy Markdown
Contributor

Scope: user-visible behavior (internal-only implementation change; no exported API surface changed)
Changed Go contract: buildMessageParam in provider/anthropicprovider*message.URIContent case now detects a (redacted) URI prefix, decodes it via message.DecodeDataURI, and emits an inline base64image/PDF block instead of passing the raw(redacted) string as a URL source (which Anthropic rejects with HTTP 400).
Upstream evidence reviewed:

  • Python: python/packages/anthropic/agent_framework_anthropic/_chat_client.py lines 883–889 — the 'uri' case unconditionally emits {"type": "url", "url": content.uri} without a (redacted) special-case. This is not a divergence: Python's content model separates 'data'(inline bytes) from'uri'(external references) at the type level, so a(redacted) URI would arrive as a 'data' content instance, not 'uri'. Go's message.URIContent is a unified type that can carry either form, requiring disambiguation at serialization time.
  • .NET: dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientExtensions.cs — no equivalent content-conversion logic visible; the extension layer delegates to the Anthropic SDK directly. No conflict found.

Result: ✅ Parity approved — no exported API changes. The fix closes a Go-specific bug caused by the language's unified URIContent type, and aligns the Anthropic provider with sibling Gemini and OpenAI providers already in this repo ((redacted) URI → inline base64 block). The upstream Python separation of datavsuri` content types makes this a Go-specific adaptation, not a divergence.

Generated by Go API Consistency Review Agent · sonnet46 · 35 AIC · ⌖ 5.07 AIC · ⊞ 6.4K ·

@github-actions github-actions Bot added parity-approved Go API consistency review found no parity issues risk:medium Contained production impact requiring normal review depth and removed pending-auto-risk Automatic risk classification is in progress labels Aug 28, 2026
@qmuntal

Copy link
Copy Markdown
Member

Fix codereview comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider/anthropic Changes files in the provider / anthropic area area:provider Changes files in the provider area parity-approved Go API consistency review found no parity issues risk:medium Contained production impact requiring normal review depth size:medium At most 100 changed lines across at most 5 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants