Skip to content

fix: adds file id for anthropic files integration and adds forwarding of content type - #4956

Merged
akshaydeo merged 2 commits into
devfrom
07-06-fix_adds_file_id_for_anthropic_files_integration
Jul 6, 2026
Merged

fix: adds file id for anthropic files integration and adds forwarding of content type #4956
akshaydeo merged 2 commits into
devfrom
07-06-fix_adds_file_id_for_anthropic_files_integration

Conversation

@sammaji

@sammaji sammaji commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

This PR adds content_type support to Anthropic file uploads and enables file_id references in document/image content blocks via the Files API beta header. Previously, file uploads always used the browser-inferred content type from CreateFormFile, and there was no way to reference already-uploaded files by ID in message content.

Changes

  • Replaced writer.CreateFormFile with a manually constructed textproto.MIMEHeader in FileUpload, allowing the caller-supplied ContentType to be set on the multipart part rather than relying on the default application/octet-stream.
  • Added ContentType *string to AnthropicFileUploadRequest and BifrostFileUploadRequest so the MIME type flows from the HTTP transport through to the provider.
  • In the HTTP integration, content_type is now read from the multipart form field first, falling back to the Content-Type header on the file part itself.
  • Added a "file" source case in toBifrostResponsesDocumentBlock to map Anthropic file_id references back into the Bifrost schema.
  • Added ConvertResponsesFileBlockToAnthropic handling for FileID-only blocks, setting source.type = "file" and populating file_id before returning early.
  • Moved FileID from ResponsesMessageContentBlock to ResponsesInputMessageContentBlockFile, where it semantically belongs alongside FileData, FileURL, and Filename.
  • Added automatic injection of the files-api-2025-04-14 beta header when any message content block contains a "file" source type, consistent with how other beta headers are appended.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./...
  1. Upload a file via the Anthropic files route with an explicit content_type form field (e.g., application/pdf) and verify the multipart part carries that content type.
  2. Upload a file where only the file part's Content-Type header is set and confirm it is used as the fallback.
  3. Send a Responses-path message with a document block referencing a file_id and confirm the files-api-2025-04-14 beta header is automatically added to the outgoing request.
  4. Confirm that a response containing a "file" source block is correctly mapped back to ResponsesInputMessageContentBlockFile.FileID.

Breaking changes

  • Yes
  • No

Related issues

Security considerations

No new auth surfaces or secrets handling introduced. The ContentType value is trimmed and validated before being set as a MIME header to avoid header injection.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Anthropic file_id handling across response conversion and beta header detection, preserves multipart upload Content-Type values through Anthropic and OpenAI upload paths, and includes formatting-only updates in related type and route code.

Changes

File ID and upload content-type support

Layer / File(s) Summary
Anthropic file_id conversion
core/providers/anthropic/responses.go, core/providers/anthropic/utils.go
convertContentBlockToAnthropic accepts a top-level FileID, toBifrostResponsesDocumentBlock populates FileID from file-type sources, ConvertResponsesFileBlockToAnthropic gains a fileID parameter with an early-return path emitting type: "file", and beta header injection detects file-backed content blocks to add the files-api header.
Multipart upload Content-Type propagation
core/providers/anthropic/types.go, core/providers/anthropic/anthropic.go, transports/bifrost-http/integrations/anthropic.go, transports/bifrost-http/handlers/inference.go
AnthropicFileUploadRequest gains content_type, Anthropic and inference upload parsing preserve multipart file Content-Type, and Anthropic file uploads create multipart parts with an explicit MIME header when a content type is present.
OpenAI lifecycle route wiring and multipart parsing
transports/bifrost-http/integrations/openai.go
Responses lifecycle routes retain PreCallback wiring with formatting cleanup, and file upload multipart parsing derives ContentType from the form field or file part header.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTPHandlers
  participant AnthropicIntegration
  participant AnthropicProvider
  participant AnthropicUtils

  Client->>HTTPHandlers: Multipart upload with file part
  HTTPHandlers->>HTTPHandlers: Capture file part Content-Type
  HTTPHandlers->>AnthropicIntegration: Parsed upload request with ContentType
  AnthropicIntegration->>AnthropicProvider: BifrostFileUploadRequest with ContentType
  AnthropicProvider->>AnthropicProvider: Build MIMEHeader and create multipart part

  Client->>AnthropicUtils: Message content block with file source
  AnthropicUtils->>AnthropicUtils: Detect source.type=="file", append files-api header
  AnthropicUtils->>AnthropicUtils: ConvertResponsesFileBlockToAnthropic emits type:"file"/file_id
Loading

Possibly related PRs

  • maximhq/bifrost#4832: Both PRs modify Anthropic conversion logic in core/providers/anthropic/utils.go to preserve uploaded file_id by setting Anthropic document sources to type: "file" with file_id.

Suggested reviewers: TejasGhatte, danpiths, akshaydeo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is specific and matches the main change: Anthropic file IDs and content-type forwarding.
Description check ✅ Passed The description covers the required template sections and includes the main changes, testing, breaking changes, and security notes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-06-fix_adds_file_id_for_anthropic_files_integration

Comment @coderabbitai help to get the list of available commands.

sammaji commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@sammaji
sammaji force-pushed the 07-06-fix_adds_file_id_for_anthropic_files_integration branch from 3e5358b to 3d5ae28 Compare July 6, 2026 13:22
@sammaji
sammaji marked this pull request as ready for review July 6, 2026 13:23
Copilot AI review requested due to automatic review settings July 6, 2026 13:23
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge; the core logic for content-type forwarding, file-ID round-tripping, and beta-header injection is correct.

The CRLF injection guard is present and correct, the file-source beta-header scan correctly uses continue for nil content blocks, and the ConvertResponsesFileBlockToAnthropic refactor preserves existing behavior. The two findings are a redundant (dead) code block in the ContentType guard and a no-op ContentType field parsed but never consumed by the OpenAI provider — neither causes incorrect behavior.

core/providers/anthropic/anthropic.go has a dead first-assignment block in the ContentType guard that should be cleaned up; transports/bifrost-http/integrations/openai.go parses content_type but the OpenAI provider never consumes it.

Important Files Changed

Filename Overview
core/providers/anthropic/anthropic.go Replaces CreateFormFile with a manual MIME header for explicit content-type; the CRLF-injection guard is correct but the first if request.ContentType != nil assignment block is dead code overwritten by the second block.
core/providers/anthropic/utils.go Adds file-source beta-header detection and refactors ConvertResponsesFileBlockToAnthropic to accept fileID; the continue guard is correctly used and the logic is consistent with the caching-scope pattern.
core/providers/anthropic/responses.go Adds "file" source case in toBifrostResponsesDocumentBlock and gates file-block conversion on FileID; document-path round-trip is correct.
transports/bifrost-http/integrations/openai.go Parses content_type from multipart form, but OpenAIProvider.FileUpload still calls CreateFormFile without using request.ContentType, so the parsed value is silently discarded.
transports/bifrost-http/integrations/anthropic.go Adds content_type reading (form field then file-part header fallback) and threads it into BifrostFileUploadRequest; logic is consistent with the inference handler.
transports/bifrost-http/handlers/inference.go Adds filePartContentType fallback for content_type; no logic issues.
core/providers/anthropic/types.go Adds ContentType *string field to AnthropicFileUploadRequest; straightforward schema addition.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant HTTPTransport as HTTP Transport
    participant AnthropicIntegration as Anthropic Integration
    participant AnthropicProvider as Anthropic Provider
    participant AnthropicAPI as Anthropic API

    Note over Client,AnthropicAPI: File Upload Flow
    Client->>HTTPTransport: POST /files (multipart: file + content_type field)
    HTTPTransport->>HTTPTransport: Read content_type form field
    HTTPTransport->>HTTPTransport: Fallback to file part Content-Type header
    HTTPTransport->>AnthropicIntegration: "BifrostFileUploadRequest{ContentType: &ct}"
    AnthropicIntegration->>AnthropicProvider: FileUpload(request)
    AnthropicProvider->>AnthropicProvider: CRLF-validate ContentType
    AnthropicProvider->>AnthropicProvider: Build textproto.MIMEHeader with Content-Type
    AnthropicProvider->>AnthropicAPI: POST /v1/files (multipart with correct Content-Type)
    AnthropicAPI-->>Client: "FileUploadResponse{id: file_xyz}"

    Note over Client,AnthropicAPI: Message with file_id reference
    Client->>HTTPTransport: "POST /responses (content block: type=file, file_id=file_xyz)"
    HTTPTransport->>AnthropicProvider: BifrostRequest with ResponsesInputMessageContentBlockFile
    AnthropicProvider->>AnthropicProvider: ConvertResponsesFileBlockToAnthropic(fileBlock, fileID)
    AnthropicProvider->>AnthropicProvider: "Build source type=file with file_id"
    AnthropicProvider->>AnthropicProvider: "AddMissingBetaHeadersToContext detects source.type==file"
    AnthropicProvider->>AnthropicProvider: Inject files-api-2025-04-14 beta header
    AnthropicProvider->>AnthropicAPI: POST /v1/messages + anthropic-beta: files-api-2025-04-14
    AnthropicAPI-->>AnthropicProvider: "Response with document block source.type=file"
    AnthropicProvider->>AnthropicProvider: toBifrostResponsesDocumentBlock case file sets FileID
    AnthropicProvider-->>Client: "ResponsesMessageContentBlock{FileID: &file_xyz}"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant HTTPTransport as HTTP Transport
    participant AnthropicIntegration as Anthropic Integration
    participant AnthropicProvider as Anthropic Provider
    participant AnthropicAPI as Anthropic API

    Note over Client,AnthropicAPI: File Upload Flow
    Client->>HTTPTransport: POST /files (multipart: file + content_type field)
    HTTPTransport->>HTTPTransport: Read content_type form field
    HTTPTransport->>HTTPTransport: Fallback to file part Content-Type header
    HTTPTransport->>AnthropicIntegration: "BifrostFileUploadRequest{ContentType: &ct}"
    AnthropicIntegration->>AnthropicProvider: FileUpload(request)
    AnthropicProvider->>AnthropicProvider: CRLF-validate ContentType
    AnthropicProvider->>AnthropicProvider: Build textproto.MIMEHeader with Content-Type
    AnthropicProvider->>AnthropicAPI: POST /v1/files (multipart with correct Content-Type)
    AnthropicAPI-->>Client: "FileUploadResponse{id: file_xyz}"

    Note over Client,AnthropicAPI: Message with file_id reference
    Client->>HTTPTransport: "POST /responses (content block: type=file, file_id=file_xyz)"
    HTTPTransport->>AnthropicProvider: BifrostRequest with ResponsesInputMessageContentBlockFile
    AnthropicProvider->>AnthropicProvider: ConvertResponsesFileBlockToAnthropic(fileBlock, fileID)
    AnthropicProvider->>AnthropicProvider: "Build source type=file with file_id"
    AnthropicProvider->>AnthropicProvider: "AddMissingBetaHeadersToContext detects source.type==file"
    AnthropicProvider->>AnthropicProvider: Inject files-api-2025-04-14 beta header
    AnthropicProvider->>AnthropicAPI: POST /v1/messages + anthropic-beta: files-api-2025-04-14
    AnthropicAPI-->>AnthropicProvider: "Response with document block source.type=file"
    AnthropicProvider->>AnthropicProvider: toBifrostResponsesDocumentBlock case file sets FileID
    AnthropicProvider-->>Client: "ResponsesMessageContentBlock{FileID: &file_xyz}"
Loading

Reviews (5): Last reviewed commit: "fix: adds file id for anthropic files in..." | Re-trigger Greptile

Comment thread core/providers/anthropic/utils.go

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

This PR extends the Anthropic integration to (1) preserve/forward file upload MIME types end-to-end and (2) support referencing already-uploaded files via file_id in document/image content blocks, including automatic injection of the required Anthropic Files API beta header when such blocks are present.

Changes:

  • Propagates an optional content_type from the HTTP multipart upload through BifrostFileUploadRequest and into Anthropic multipart file upload construction.
  • Adds conversion support for source.type = "file" / file_id blocks in Anthropic Responses ↔ Bifrost schema mapping.
  • Adds Files API beta header auto-injection when requests include "file"-sourced content blocks.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
transports/bifrost-http/integrations/anthropic.go Reads content_type from multipart form data (or falls back to the file part header) and forwards it into the core upload request.
core/schemas/responses.go Moves file_id onto the embedded file content block type so it lives alongside other file fields.
core/providers/anthropic/utils.go Adds detection of "file" sources in message blocks to auto-inject the Files API beta header.
core/providers/anthropic/types.go Extends Anthropic file upload request shape with optional content_type and includes formatting-only adjustments.
core/providers/anthropic/responses.go Maps Anthropic "file" sources back into ResponsesInputMessageContentBlockFile.FileID.
core/providers/anthropic/anthropic.go Builds multipart parts with an explicit MIME header so the file part can carry the caller-supplied Content-Type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/providers/anthropic/utils.go
Comment thread core/providers/anthropic/utils.go
Comment thread core/providers/anthropic/anthropic.go Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/providers/anthropic/anthropic.go`:
- Around line 2173-2180: The multipart file upload header in the Anthropic file
creation flow now drops Content-Type when request.ContentType is unset, changing
the previous behavior from writer.CreateFormFile. Update the header construction
around multipart.FileContentDisposition/CreatePart so that it still sets a
default Content-Type of application/octet-stream when request.ContentType is nil
or trims to empty, and only overrides it when a non-empty value is provided.
- Around line 2173-2180: The multipart part construction in anthroptic request
handling is using an unsanitized Content-Type value, allowing header injection
through the outgoing request. In the code path that builds the MIME header
before writer.CreatePart, validate request.ContentType by rejecting any value
containing CR/LF and preferably parsing it with mime.ParseMediaType before
calling header.Set("Content-Type", ...). Keep the existing filename handling
as-is, and ensure only a well-formed media type is written into the header.

In `@core/providers/anthropic/utils.go`:
- Around line 1207-1223: The file-source scan in the message loop stops too
early when it hits a text-only message because the
`message.Content.ContentBlocks == nil` branch in the `hasFileSource` logic uses
the wrong flow control. Update the scan in `utils.go` so the outer loop skips
messages with nil `ContentBlocks` instead of terminating, while keeping the
existing `hasFileSource` and `appendUniqueHeader` behavior intact, so later file
blocks can still trigger `AnthropicFilesAPIBetaHeader`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 170f4580-ad80-4263-b8c1-dd483f541127

📥 Commits

Reviewing files that changed from the base of the PR and between aecdf1c and 3d5ae28.

📒 Files selected for processing (6)
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • transports/bifrost-http/integrations/anthropic.go

Comment thread core/providers/anthropic/anthropic.go Outdated
Comment thread core/providers/anthropic/utils.go
@sammaji
sammaji force-pushed the 07-06-fix_adds_file_id_for_anthropic_files_integration branch from 3d5ae28 to 64c64ce Compare July 6, 2026 14:16

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
transports/bifrost-http/integrations/openai.go (1)

2569-2574: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the content-type fallback into a shared helper.

This 3-line precedence pattern (form field → multipart part Content-Type header) is duplicated verbatim in transports/bifrost-http/handlers/inference.go's fileUpload. Extracting a small helper (e.g., resolveMultipartContentType(form, fileHeader) *string) would keep the two upload paths from silently diverging if the precedence logic changes later.

♻️ Proposed shared helper
// resolveMultipartContentType returns the caller-supplied content_type form
// field, falling back to the uploaded file part's Content-Type header.
func resolveMultipartContentType(form *multipart.Form, fileHeader *multipart.FileHeader) *string {
	if values := form.Value["content_type"]; len(values) > 0 && values[0] != "" {
		return &values[0]
	}
	if partContentType := strings.TrimSpace(fileHeader.Header.Get("Content-Type")); partContentType != "" {
		return &partContentType
	}
	return nil
}
-	if contentTypeValues := form.Value["content_type"]; len(contentTypeValues) > 0 && contentTypeValues[0] != "" {
-		uploadReq.ContentType = &contentTypeValues[0]
-	} else if partContentType := strings.TrimSpace(fileHeader.Header.Get("Content-Type")); partContentType != "" {
-		uploadReq.ContentType = &partContentType
-	}
+	uploadReq.ContentType = resolveMultipartContentType(form, fileHeader)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@transports/bifrost-http/integrations/openai.go` around lines 2569 - 2574, The
multipart content-type fallback logic is duplicated between the OpenAI upload
path and the inference upload path, so extract it into a shared helper like
resolveMultipartContentType(form, fileHeader) and use that from the upload
handling code. Move the precedence rules currently in the upload flow into the
helper so both callers rely on the same implementation and won’t diverge if the
form-field vs multipart-header behavior changes later.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@transports/bifrost-http/integrations/openai.go`:
- Around line 2569-2574: The multipart content-type fallback logic is duplicated
between the OpenAI upload path and the inference upload path, so extract it into
a shared helper like resolveMultipartContentType(form, fileHeader) and use that
from the upload handling code. Move the precedence rules currently in the upload
flow into the helper so both callers rely on the same implementation and won’t
diverge if the form-field vs multipart-header behavior changes later.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ce23b05-ea69-4252-9b0d-22f487081007

📥 Commits

Reviewing files that changed from the base of the PR and between 3d5ae28 and 64c64ce.

📒 Files selected for processing (8)
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/anthropic.go
  • transports/bifrost-http/integrations/openai.go
✅ Files skipped from review due to trivial changes (2)
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 6, 2026
Comment thread core/providers/anthropic/utils.go
Comment thread core/providers/anthropic/anthropic.go
@sammaji
sammaji force-pushed the 07-06-fix_adds_file_id_for_anthropic_files_integration branch from df83d64 to e717c35 Compare July 6, 2026 17:34
@sammaji
sammaji force-pushed the 07-06-chore_renames_newenvvar_to_newsecretvar_in_docs branch from aecdf1c to 68f1201 Compare July 6, 2026 17:34

akshaydeo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jul 6, 5:38 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 6, 5:40 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from 07-06-chore_renames_newenvvar_to_newsecretvar_in_docs to graphite-base/4956 July 6, 2026 17:39
@akshaydeo
akshaydeo changed the base branch from graphite-base/4956 to dev July 6, 2026 17:39
@akshaydeo
akshaydeo dismissed stale reviews from Pratham-Mishra04 and coderabbitai[bot] July 6, 2026 17:39

The base branch was changed.

@akshaydeo
akshaydeo merged commit 86e2330 into dev Jul 6, 2026
10 of 11 checks passed
@akshaydeo
akshaydeo deleted the 07-06-fix_adds_file_id_for_anthropic_files_integration branch July 6, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants