Skip to content

Conversation

@TejasGhatte
Copy link
Collaborator

@TejasGhatte TejasGhatte commented Jan 5, 2026

Summary

Enhanced the Anthropic citations handling to support both request configuration and response parsing with a unified type structure.

Changes

  • Replaced AnthropicCitationsConfig with a more versatile AnthropicCitations type
  • Added AnthropicTextCitation to represent individual citations in responses
  • Implemented custom JSON marshaling/unmarshaling for AnthropicCitations to handle both request and response formats
  • Added proper field documentation for citation-related types

Type of change

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

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

# Core/Transports
go version
go test ./core/providers/anthropic/...

Breaking changes

  • Yes
  • No

Related issues

Enhances Anthropic provider to properly handle citations in both requests and responses.

Security considerations

No security implications.

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
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Web Search tool support with per-call sources, domain filtering, and multi-output handling
    • Citations support for attributing content to documents and web search results, including streaming citation deltas
  • Streaming

    • Extended streaming to include web search lifecycle events and citation deltas with annotation conversion
  • Tests

    • Large expansion of cross-provider integration tests and utilities covering web search and citation scenarios

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds web-search tool and citation support: new Anthropic types and stream deltas, citation/annotation conversions between Anthropic and OpenAI formats, context-aware request/response signature changes, web-search source propagation utilities, and extensive test additions for web search and citations.

Changes

Cohort / File(s) Summary
Anthropic types & deltas
core/providers/anthropic/types.go
New block type web_search_tool_result, new stream delta type citations_delta, AnthropicStreamDelta.Citation field, and UnmarshalJSON now accepts single ContentBlock objects.
Anthropic responses & streaming logic
core/providers/anthropic/responses.go
Expanded AnthropicResponsesStreamState with web-search fields and content-index map; added web_search lifecycle handling and per-output indexing; citation <-> annotation converters; convertBifrostWebSearchCallToAnthropicBlocks; many functions now accept *schemas.BifrostContext.
Anthropic utils / conversions
core/providers/anthropic/utils.go
Request-building now accepts *schemas.BifrostContext; propagate citations into document/file blocks; added sanitizeWebSearchArguments, attachWebSearchSourcesToCall, extractWebSearchSources.
OpenAI provider handling
core/providers/openai/*.go
Added stripping/filtering for Citations and unsupported annotations; filterSupportedAnnotations preserves OpenAI-native annotation types; tool web-search source fields trimmed for OpenAI paths.
Schemas: chat & responses
core/schemas/*.go
Citations added to message content blocks and Bifrost payloads; renamed chat annotation CitationURLCitation; extended annotation attribution fields; web-search-related fields (queries, sources, MaxUses, filters) added; JSON marshal/unmarshal added for tool unions.
Provider plumbing & signatures
core/providers/*/utils.go, core/providers/*/anthropic.go, transports/bifrost-http/integrations/anthropic.go
Multiple functions changed to accept/forward *schemas.BifrostContext and call sites updated to pass context into Anthropic converters.
Responses conversion & deltas
core/providers/anthropic/responses.go, core/providers/openai/responses.go
Emit/translate citation deltas, convert web_search calls/results to Anthropic blocks, attach sources to web_search_call actions, and harmonize streaming flows for computer and web search.
Tests & test utilities
core/internal/testutil/*, tests/integrations/*, tests/integrations/typescript/*
Added WebSearchTool test flag and RunWebSearchToolTest; large Python and TypeScript integration test additions for citations and web_search (streaming & non-streaming); many new test helpers and fixtures for citation/annotation creation and validation.
Other small updates
core/schemas/utils.go, core/providers/openai/responses.go, provider-specific utils in azure/ and vertex/
Deep-copy tweaks to use URLCitation fields; OpenAI responses filter enhancements; updated Anthropic request call sites in Azure/Vertex adapters.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Bifrost
  participant Anthropic
  participant WebSearch

  Client->>Bifrost: Send Responses request (includes web_search tool)
  Bifrost->>Anthropic: ToAnthropicResponsesRequest(ctx, request)
  Anthropic->>Client: stream delta: server_tool_use (web_search in_progress)
  Anthropic->>WebSearch: Execute query (tool id, args)
  WebSearch-->>Anthropic: Return results (sources, snippets)
  Anthropic->>Bifrost: convert results -> web_search_tool_result block + citations
  Anthropic->>Client: stream delta: web_search_tool_result and citations_delta
  Anthropic->>Client: stream event: web_search_call_completed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐇 I hopped through deltas, types, and streams,

I threaded citations into searchy dreams,
Blocks now carry sources bright,
Calls and results arrive in light,
A rabbit cheers these code-filled beams.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: enhancing Anthropic citations handling for both request config and response parsing, which aligns with the PR's core objective of unifying citation type structures.
Description check ✅ Passed The description covers all required template sections including Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, and Checklist; all items are appropriately filled out with relevant details.
Docstring Coverage ✅ Passed Docstring coverage is 93.26% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 01-05-fix_anthropic_type_citations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Collaborator Author

TejasGhatte commented Jan 5, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #1241

@TejasGhatte TejasGhatte marked this pull request as ready for review January 5, 2026 07:40
@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from a2b8079 to a423283 Compare January 5, 2026 07:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
core/providers/anthropic/types.go (2)

250-259: Minor doc comment inconsistency.

The comment on line 250 says "AnthropicCitation" but the type is named AnthropicTextCitation. Consider aligning the comment with the actual type name.

🔎 Proposed fix
-// AnthropicCitation represents a single citation in a response
+// AnthropicTextCitation represents a single text citation in a response
 type AnthropicTextCitation struct {

271-280: Consider adding mutual exclusivity validation.

The AnthropicContent.MarshalJSON (lines 166-169) validates that only one of ContentStr or ContentBlocks is set. For consistency, consider adding similar validation here to catch programming errors where both Enabled and TextCitations are set.

🔎 Proposed fix
 func (ac *AnthropicCitations) MarshalJSON() ([]byte, error) {
+	if ac.Enabled != nil && ac.TextCitations != nil {
+		return nil, fmt.Errorf("both Enabled and TextCitations are set; only one should be non-nil")
+	}
 	if ac.Enabled != nil {
 		return sonic.Marshal(ac.Enabled)
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0cbf1 and a423283.

📒 Files selected for processing (3)
  • core/changelog.md
  • core/providers/anthropic/types.go
  • transports/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/changelog.md
  • core/providers/anthropic/types.go
  • transports/changelog.md
🧠 Learnings (3)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/anthropic/types.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/providers/anthropic/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/types.go
🔇 Additional comments (4)
transports/changelog.md (1)

2-3: LGTM!

Changelog entries are appropriate and accurately reflect the changes made in this PR for anthropic citations handling.

core/changelog.md (1)

1-2: LGTM!

Changelog entries are properly formatted and accurately document the anthropic citations enhancement.

core/providers/anthropic/types.go (2)

232-232: LGTM!

The Citations field type update to *AnthropicCitations correctly supports the unified handling of both request configuration and response parsing.


282-298: No issue here—the code correctly handles the API contract.

The technical concern about sonic.Unmarshal accepting arbitrary JSON objects is valid, but it's not a practical problem: the Anthropic API documentation guarantees that citations are always returned as arrays within content blocks, never as single objects. The unmarshaling order (config object first, then array) is intentional for the two distinct formats: request configuration ({enabled: true}) versus response data ([{type: "...", cited_text: "...", ...}]). The code comment already documents this distinction, and the implementation correctly reflects the API contract.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from a423283 to a8be297 Compare January 6, 2026 14:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In @core/providers/anthropic/responses.go:
- Around line 1455-1470: Guard against nils when scanning citations: check that
message.Content != nil before accessing ContentBlocks and that block.Citations
!= nil and block.Citations.Enabled != nil before dereferencing
*block.Citations.Enabled, then set hasCitationsEnabled accordingly (use existing
variables bifrostReq.Input, ContentBlocks, block.Citations.Enabled,
hasCitationsEnabled). Also fix the misleading comment above this block: replace
the assertion "Citations cannot be used together with Structured Outputs in
anthropic" with a clear TODO/clarification noting you preserved the existing
behavior of setting anthropicReq.OutputFormat via
convertResponsesTextConfigToAnthropicOutputFormat(bifrostReq.Params.Text) when
citations are present, and confirm the intended behavior against Anthropic docs
(invert the condition only if docs say the opposite).
🧹 Nitpick comments (1)
core/providers/anthropic/responses.go (1)

2438-2454: Citation ↔ annotation conversion and streaming mapping are consistent; note minor field loss

The new bidirectional wiring between Anthropic citations and OpenAI-style annotations looks coherent:

  • Non-streaming:

    • convertAnthropicContentBlocksToResponsesMessages now maps block.Citations.TextCitations into ResponsesOutputMessageContentText.Annotations, preserving type, document index, ranges (char/page/block), filename/title, URL, and source where applicable.
    • convertContentBlockToAnthropic does the reverse for text blocks, building AnthropicCitations.TextCitations from ResponsesOutputMessageContentText.Annotations.
    • Document blocks round-trip request-side citation config via block.Citations.ConfigresultBlock.Citations.
  • Streaming:

    • AnthropicStreamDeltaTypeCitations + AnthropicStreamDelta.Citation are converted to OutputTextAnnotationAdded via convertAnthropicCitationToAnnotation in ToBifrostResponsesStream, and back to citations_delta in ToAnthropicResponsesStreamResponse via convertAnnotationToAnthropicCitation. This matches the expectations in the new streaming tests.

One small trade-off: EncryptedIndex and SearchResultIndex from AnthropicTextCitation are not surfaced onto schemas.ResponsesOutputMessageContentTextAnnotation, so those two fields are effectively dropped when going through Bifrost. If the annotation schema doesn’t currently expose equivalents and UI/log consumers don’t need them, this is fine; if you later want richer web/search citation introspection, you’ll need to extend ResponsesOutputMessageContentTextAnnotation and wire those fields through in both conversion helpers.

Overall, the mappings look symmetric for the main citation types and should satisfy the new integration tests.

Also applies to: 2461-2462, 3428-3445, 3464-3467, 3516-3518, 3624-3791, 554-573, 1122-1135

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a423283 and a8be297.

📒 Files selected for processing (10)
  • core/changelog.md
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/config.yml
  • tests/integrations/tests/test_anthropic.py
  • transports/changelog.md
✅ Files skipped from review due to trivial changes (2)
  • transports/changelog.md
  • core/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/anthropic/utils.go
  • tests/integrations/config.yml
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • tests/integrations/tests/test_anthropic.py
  • core/providers/anthropic/responses.go
🧠 Learnings (7)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-17T08:44:08.788Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1114
File: core/schemas/chatcompletions.go:224-228
Timestamp: 2025-12-17T08:44:08.788Z
Learning: In core/schemas/chatcompletions.go, ensure the schema structures mirror OpenAI's API specifications exactly. Use the valid values for fields like ChatAudioParameters.Format and ChatAudioParameters.Voice as defined by OpenAI's documentation, and avoid adding additional inline documentation or constants to maintain direct compatibility with OpenAI's API.

Applied to files:

  • core/schemas/chatcompletions.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-24T07:38:16.990Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: tests/integrations/tests/test_google.py:2030-2030
Timestamp: 2025-12-24T07:38:16.990Z
Learning: In Python tests under tests/integrations/tests, allow a fixture parameter named test_config in test functions even if unused; do not flag it as unused. This is an internal convention to ensure consistency for integration tests.

Applied to files:

  • tests/integrations/tests/test_anthropic.py
🧬 Code graph analysis (4)
core/schemas/chatcompletions.go (1)
core/schemas/responses.go (1)
  • Citations (424-426)
core/providers/anthropic/types.go (2)
core/schemas/responses.go (1)
  • Citations (424-426)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (653-656)
core/schemas/responses.go (5)
  • Citations (424-426)
  • ResponsesMessageContentBlock (406-422)
  • ResponsesInputMessageContentBlockFile (432-437)
  • ResponsesOutputMessageContentText (448-451)
  • ResponsesOutputMessageContentTextAnnotation (453-473)
tests/integrations/tests/test_anthropic.py (2)
tests/integrations/tests/utils/parametrize.py (2)
  • get_cross_provider_params_for_scenario (12-47)
  • format_provider_model (126-141)
tests/integrations/tests/utils/common.py (1)
  • assert_valid_chat_response (815-847)
🪛 Ruff (0.14.10)
tests/integrations/tests/test_anthropic.py

1773-1773: Unused method argument: test_config

(ARG002)


1837-1837: Unused method argument: test_config

(ARG002)


1912-1912: Unused method argument: test_config

(ARG002)

🔇 Additional comments (16)
core/schemas/chatcompletions.go (1)

644-644: LGTM!

The Citations field follows the same pattern as CacheControl (pointer with omitempty), enabling optional citation metadata on content blocks.

core/schemas/responses.go (3)

421-421: LGTM!

The Citations field addition is consistent with the pattern in ChatContentBlock and follows the established convention for optional provider-specific metadata.


424-426: LGTM!

The Citations struct provides a clean abstraction for citation configuration with a single enabled flag. The pointer type allows distinguishing between explicitly disabled vs. not specified.


464-472: LGTM!

The Anthropic-specific citation fields extend the annotation structure to capture granular location metadata (page numbers, character indices, block indices). The optional pointer types and clear documentation ensure backward compatibility.

core/providers/openai/types.go (6)

119-123: LGTM!

The Citations field is correctly stripped from chat content blocks when marshaling for OpenAI, consistent with the handling of other provider-specific fields like CacheControl.


295-320: LGTM!

The annotation filtering logic properly preserves OpenAI-native citation types while removing unsupported ones. The handling of empty results (setting to nil) is appropriate and maintains clean JSON output.


343-358: LGTM!

Citations are correctly stripped from tool output blocks, ensuring consistent handling across all content block types.


388-390: LGTM!

The helper function correctly identifies Citations as a field requiring stripping, maintaining consistency with the overall filtering strategy.


406-414: LGTM!

The helper correctly identifies both Citations and Annotations for filtering in responses messages. The distinction from chat messages (which don't check annotations) aligns with the different processing paths.


432-451: LGTM!

The filterSupportedAnnotations helper is well-implemented with clear logic to preserve only OpenAI-native citation types (file_citation, url_citation, container_file_citation, file_path). The function efficiently pre-allocates the result slice and handles edge cases correctly.

core/providers/anthropic/utils.go (2)

160-162: LGTM!

Citations are correctly propagated to the Anthropic document block by wrapping them in AnthropicCitations. This enables the custom JSON marshaling logic to handle both request-side config and response-side citation data.


230-239: LGTM!

The function signature correctly accepts citations as a parameter and propagates them using the same wrapping pattern as ConvertToAnthropicDocumentBlock. This enables consistent citation handling across chat and responses conversion paths.

tests/integrations/config.yml (2)

177-341: LGTM!

The citations configuration correctly enables the feature only for Anthropic, consistent with the PR's focus on enhancing Anthropic-specific citation handling. Other providers appropriately have citations disabled.


388-388: LGTM!

The citations scenario is correctly mapped to the "chat" capability, as citations are returned as part of chat completion responses.

tests/integrations/tests/test_anthropic.py (1)

1772-2009: Citations integration tests look good; unused test_config is intentional

The new PDF, text, and streaming citation tests are well-aligned with the existing integration patterns: they gate on a citations scenario, exercise both content and structure (types, indices, document indices), and validate streaming citations_delta events without over-constraining model behavior.

Ruff’s ARG002 about the unused test_config parameter on these tests can be ignored here: keeping the test_config fixture in the signature is consistent with the existing convention for integration tests under tests/integrations/tests, and helps keep parametrization uniform. Based on learnings, this is expected and should not be “fixed”.

core/providers/anthropic/types.go (1)

232-233: Unified citations model and streaming delta extension look correct

The new AnthropicTextCitation + AnthropicCitations union cleanly separates request config (schemas.Citations) from response citations ([]AnthropicTextCitation), and the custom JSON (un)marshalling correctly discriminates between object vs array payloads. Adding Citations *AnthropicCitations onto AnthropicContentBlock and Citation *AnthropicTextCitation plus citations_delta into AnthropicStreamDelta integrates this model into both batch and streaming paths without disrupting existing behavior.

The only subtlety is that any JSON object (including null) will be treated as config; given the current Anthropic API shapes (config object vs array), this is acceptable and keeps the union logic simple.

Also applies to: 245-276, 281-315, 490-496, 500-506

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from a8be297 to 5f89a20 Compare January 6, 2026 14:58
@TejasGhatte TejasGhatte changed the title fix: anthropic type citations fix: anthropic citations handling for request config and response parsing Jan 6, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/openai/types.go (1)

354-358: Critical: Incomplete condition causes Citations to leak in tool output blocks.

Line 354's needsBlockCopy condition is missing the block.Citations != nil check, even though:

  • Line 343 checks for Citations to trigger hasToolModification
  • Line 358 clears Citations unconditionally

If a tool output block has only Citations (without CacheControl or FileType), needsBlockCopy will be false, the original block will be used, and Citations won't be stripped.

🔎 Proposed fix
-needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
🤖 Fix all issues with AI Agents
In @core/providers/anthropic/types.go:
- Around line 278-315: The AnthropicCitations type can end up in an ambiguous
state; update UnmarshalJSON to attempt parsing the array form first (unmarshal
into []AnthropicTextCitation before schemas.Citations) to avoid false-positive
matches on generic objects, and add validation in both UnmarshalJSON and
MarshalJSON to ensure exactly one of Config or TextCitations is populated
(return an error if both or neither are set); modify MarshalJSON to return an
error when both fields are non-nil (or when neither is set) rather than silently
choosing one.
🧹 Nitpick comments (3)
core/providers/openai/types.go (1)

342-374: Consider filtering annotations in tool output blocks.

Tool output blocks (lines 342-374) strip CacheControl and Citations but don't filter annotations like content blocks do (lines 308-320). Since tool output blocks can have ResponsesOutputMessageContentText with annotations, consider applying the same filterSupportedAnnotations logic to ensure non-OpenAI citation types are filtered from tool outputs as well.

🔎 Proposed enhancement

After line 358, add annotation filtering logic similar to lines 308-320:

blockCopy.CacheControl = nil
blockCopy.Citations = nil

// Filter out unsupported citation types from annotations
if blockCopy.ResponsesOutputMessageContentText != nil && len(blockCopy.ResponsesOutputMessageContentText.Annotations) > 0 {
	textCopy := *blockCopy.ResponsesOutputMessageContentText
	filteredAnnotations := filterSupportedAnnotations(textCopy.Annotations)
	if len(filteredAnnotations) > 0 {
		textCopy.Annotations = filteredAnnotations
		blockCopy.ResponsesOutputMessageContentText = &textCopy
	} else {
		textCopy.Annotations = nil
		blockCopy.ResponsesOutputMessageContentText = &textCopy
	}
}

// Strip FileType from file block
if blockCopy.ResponsesInputMessageContentBlockFile != nil && blockCopy.ResponsesInputMessageContentBlockFile.FileType != nil {
	// ...
}
core/schemas/chatcompletions.go (1)

633-645: Citations field wiring into ChatContentBlock looks consistent

Adding Citations *Citations as an optional field reuses the shared Citations type and mirrors the pattern already used for CacheControl, so it won’t affect OpenAI‑compatible payloads when unset. You might optionally expand the comment above to mention citations as another non‑OpenAI extension for clarity.

tests/integrations/tests/test_anthropic.py (1)

1772-2010: Citations tests cover PDF, text, and streaming flows; consider a small robustness guard

The three new tests exercise citations end‑to‑end (including streaming citations_delta events) with good structural assertions on type, indices, and document index, and they’re correctly scoped via the new "citations" scenario so only providers that support citations participate.

One minor hardening you could consider is defensively handling citation.cited_text in the debug prints so an upstream change that ever makes it nullable doesn’t cause a TypeError during slicing:

Example defensive preview handling
-                    print(f"✓ Found PDF citation: pages {citation.start_page_number}-{citation.end_page_number}, "
-                          f"text: '{citation.cited_text[:50]}...'")
+                    text_preview = citation.cited_text or ""
+                    if len(text_preview) > 50:
+                        text_preview = text_preview[:50]
+                    print(
+                        f"✓ Found PDF citation: pages {citation.start_page_number}-{citation.end_page_number}, "
+                        f"text: '{text_preview}...'"
+                    )

You could apply the same pattern to the other citation preview prints in tests 34 and 35 if desired; it won’t affect test expectations, only resiliency.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8be297 and 5f89a20.

📒 Files selected for processing (10)
  • core/changelog.md
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/config.yml
  • tests/integrations/tests/test_anthropic.py
  • transports/changelog.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • transports/changelog.md
  • core/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/schemas/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • tests/integrations/tests/test_anthropic.py
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/utils.go
  • tests/integrations/config.yml
🧠 Learnings (7)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/utils.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/schemas/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/utils.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
📚 Learning: 2025-12-24T07:38:16.990Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: tests/integrations/tests/test_google.py:2030-2030
Timestamp: 2025-12-24T07:38:16.990Z
Learning: In Python tests under tests/integrations/tests, allow a fixture parameter named test_config in test functions even if unused; do not flag it as unused. This is an internal convention to ensure consistency for integration tests.

Applied to files:

  • tests/integrations/tests/test_anthropic.py
📚 Learning: 2025-12-17T08:44:08.788Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1114
File: core/schemas/chatcompletions.go:224-228
Timestamp: 2025-12-17T08:44:08.788Z
Learning: In core/schemas/chatcompletions.go, ensure the schema structures mirror OpenAI's API specifications exactly. Use the valid values for fields like ChatAudioParameters.Format and ChatAudioParameters.Voice as defined by OpenAI's documentation, and avoid adding additional inline documentation or constants to maintain direct compatibility with OpenAI's API.

Applied to files:

  • core/schemas/chatcompletions.go
🧬 Code graph analysis (5)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (653-656)
core/schemas/responses.go (5)
  • Citations (424-426)
  • ResponsesMessageContentBlock (406-422)
  • ResponsesInputMessageContentBlockFile (432-437)
  • ResponsesOutputMessageContentText (448-451)
  • ResponsesOutputMessageContentTextAnnotation (453-473)
core/providers/anthropic/types.go (2)
core/schemas/responses.go (1)
  • Citations (424-426)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
tests/integrations/tests/test_anthropic.py (2)
tests/integrations/tests/utils/parametrize.py (2)
  • get_cross_provider_params_for_scenario (12-47)
  • format_provider_model (126-141)
tests/integrations/tests/utils/common.py (1)
  • assert_valid_chat_response (815-847)
core/schemas/chatcompletions.go (1)
core/schemas/responses.go (1)
  • Citations (424-426)
core/providers/anthropic/utils.go (2)
core/schemas/responses.go (1)
  • Citations (424-426)
core/providers/anthropic/types.go (3)
  • AnthropicCitations (281-286)
  • AnthropicContentBlock (218-235)
  • AnthropicSource (238-243)
🪛 Ruff (0.14.10)
tests/integrations/tests/test_anthropic.py

1773-1773: Unused method argument: test_config

(ARG002)


1837-1837: Unused method argument: test_config

(ARG002)


1912-1912: Unused method argument: test_config

(ARG002)

🔇 Additional comments (16)
core/providers/openai/types.go (5)

119-123: LGTM! Citations correctly stripped from chat content blocks.

The logic properly identifies and clears the Citations field when marshaling chat messages, consistent with how CacheControl is handled.


295-320: LGTM! Comprehensive annotation filtering for responses messages.

The implementation correctly strips the Citations field and filters annotations to retain only OpenAI-native citation types (file_citation, url_citation, container_file_citation, file_path). The logic properly handles both cases where some annotations remain and where all are filtered out.


388-390: LGTM! Citations check added correctly.

The addition properly detects the Citations field in chat message content blocks, consistent with the function's purpose.


406-414: LGTM! Citations and annotations checks added.

The function correctly identifies when Citations or Annotations need processing. Note that it triggers the copy path even when all annotations are OpenAI-native (which would pass through the filter unchanged), but this conservative approach is acceptable given the minor performance impact.


432-451: LGTM! Clean and efficient annotation filtering.

The function correctly filters to retain only OpenAI-native citation types. The implementation is clear, uses an efficient pre-allocated slice, and properly handles empty input.

core/schemas/responses.go (1)

407-422: Citations schema and Anthropic annotation extensions are modeled cleanly

The shared Citations type and its use on ResponsesMessageContentBlock give a consistent place to carry citation config, and the Anthropic‑specific annotation fields (start_char_index, page/block indices, source) are all optional so they won’t impact providers that don’t emit them. This is a straightforward, backwards‑compatible extension of the responses schema.

Also applies to: 424-426, 453-473

tests/integrations/config.yml (1)

177-177: Citations scenario wiring in integration config is consistent

The new citations flags per provider and the scenario_capabilities.citations: "chat" entry line up with how get_cross_provider_params_for_scenario("citations") derives (provider, model) pairs, and they currently scope the test to Anthropic only. Configuration changes look correct.

Also applies to: 219-219, 260-260, 302-302, 341-341, 388-388

core/providers/anthropic/utils.go (1)

153-163: Citations propagation into Anthropic document blocks is consistent

Both document helpers now populate documentBlock.Citations from the shared schemas.Citations config via AnthropicCitations.Config, so requests that enable citations on file/document blocks will correctly reach Anthropic while remaining a no‑op when citations is nil. The two conversions (ChatContentBlock → document and Responses file block → document) are aligned.

Also applies to: 230-239

core/providers/anthropic/types.go (2)

245-276: LGTM!

The AnthropicTextCitation struct is well-designed with clear documentation for each citation type and appropriate optional fields.


495-495: LGTM!

The citations delta type and field additions follow the existing pattern for streaming deltas and are correctly integrated into the streaming infrastructure.

Also applies to: 505-505

core/providers/anthropic/responses.go (6)

554-573: LGTM!

The citations delta handling correctly converts Anthropic citations to OpenAI-style annotations and emits the appropriate streaming event, following the established pattern for other delta types.


1122-1137: LGTM!

The reverse streaming conversion from OpenAI annotations to Anthropic citations is correctly implemented and follows the established streaming response pattern.


2447-2463: LGTM!

The citation-to-annotation conversion in text blocks properly checks for nil values and correctly populates the annotations array from Anthropic citations.


3439-3454: LGTM!

The reverse conversion from OpenAI annotations to Anthropic citations in text blocks is correctly implemented with appropriate nil checks and proper structure initialization.


3518-3527: LGTM!

The citation config mapping in document block conversion correctly handles the request-side citation configuration with appropriate nil checks.


3722-3728: Verify that mapping OpenAI native citation types to char_location is acceptable.

The fallback case for OpenAI-specific citation types (file_citation, url_citation, container_file_citation, file_path) maps them all to Anthropic's char_location type. This normalization might lose semantic information about the original citation type.

Confirm that:

  1. Anthropic's API doesn't have equivalent types for these OpenAI-specific citations
  2. Losing the distinction between these types is acceptable for the round-trip conversion
  3. The Title field (line 3728) is sufficient to preserve any additional context from the original OpenAI citation

If type preservation is important, consider adding a comment explaining why this mapping is necessary.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 5f89a20 to 487c94d Compare January 7, 2026 11:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/providers/openai/types.go (1)

114-137: Tool-output sanitization misses citations-only blocks

In the tool-output sanitization, hasToolModification correctly treats block.Citations != nil as a reason to strip, but the inner needsBlockCopy only checks CacheControl and FileType. A block with only Citations set will trigger hasToolModification but then be copied through unchanged in the else branch, leaving provider-specific citations in the payload.

Recommend aligning needsBlockCopy with the outer check:

  • Include block.Citations != nil in needsBlockCopy.
  • Optionally also include ResponsesOutputMessageContentText annotations if you intend to sanitize/normalize tool-output text like regular content blocks.

This keeps OpenAI-facing payloads consistent with the content-block path.

Proposed fix for tool output block copying
- for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
-     needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+ for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
+     needsBlockCopy := block.CacheControl != nil ||
+         block.Citations != nil ||
+         (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
      if needsBlockCopy {
          blockCopy := block
          blockCopy.CacheControl = nil
          blockCopy.Citations = nil
          // Strip FileType from file block
core/providers/anthropic/responses.go (1)

2443-2471: Ensure full round‑trip of page/content‑block citation metadata

The citations plumbing end-to-end is mostly solid:

  • Anthropic text blocks with Citations.TextCitations become ResponsesMessageContentBlock text blocks with ResponsesOutputMessageContentText.Annotations.
  • Text/file blocks going back to Anthropic construct AnthropicCitations.TextCitations and pass block.Citations into document blocks.
  • Document blocks returning to Bifrost rehydrate ResponsesMessageContentBlock.Citations.

However, the per-type converters drop some Anthropic-specific fields on round trip:

  • In convertAnthropicCitationToAnnotation, AnthropicCitationTypePageLocation and AnthropicCitationTypeContentBlockLocation only populate StartCharIndex/EndCharIndex (or none), never StartPageNumber/EndPageNumber or StartBlockIndex/EndBlockIndex on the annotation.
  • In convertAnnotationToAnthropicCitation, the page_location and content_block_location branches read those page/block fields back from the annotation, so a full Anthropic → annotation → Anthropic cycle will lose page/block indices.

If you want lossless conversions for these richer citation types, consider:

  • For page_location: set annotation.StartPageNumber / EndPageNumber from citation.StartPageNumber / EndPageNumber in convertAnthropicCitationToAnnotation.
  • For content_block_location: set annotation.StartBlockIndex / EndBlockIndex from citation.StartBlockIndex / EndBlockIndex in convertAnthropicCitationToAnnotation.

Everything else (char_location, web/search result, search_result_location) looks symmetric.

Sketch of a lossless mapping
case AnthropicCitationTypePageLocation:
-    annotation.StartCharIndex = citation.StartCharIndex
-    annotation.EndCharIndex = citation.EndCharIndex
-    annotation.Filename = citation.DocumentTitle
+    annotation.StartCharIndex = citation.StartCharIndex
+    annotation.EndCharIndex = citation.EndCharIndex
+    annotation.StartPageNumber = citation.StartPageNumber
+    annotation.EndPageNumber = citation.EndPageNumber
+    annotation.Filename = citation.DocumentTitle

case AnthropicCitationTypeContentBlockLocation:
-    annotation.StartCharIndex = citation.StartCharIndex
-    annotation.EndCharIndex = citation.EndCharIndex
-    annotation.Filename = citation.DocumentTitle
+    annotation.StartCharIndex = citation.StartCharIndex
+    annotation.EndCharIndex = citation.EndCharIndex
+    annotation.StartBlockIndex = citation.StartBlockIndex
+    annotation.EndBlockIndex = citation.EndBlockIndex
+    annotation.Filename = citation.DocumentTitle

This keeps the richer location metadata intact across conversions.

Also applies to: 3436-3476, 3518-3527, 3633-3732

🧹 Nitpick comments (4)
core/providers/openai/types.go (2)

119-131: Consider including FileURL in chat content-block copy condition

hasFieldsToStripInChatMessage treats both FileType and FileURL as reasons to strip, but needsBlockCopy in the chat marshalling path only looks at FileType:

needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.File != nil && block.File.FileType != nil)

This means blocks with only FileURL set will go through the “copy” path at the message level but keep FileURL intact, despite the comment “Strip FileType and FileURL from file block”.

If the intent is to remove both fields from OpenAI-bound requests, also gate on FileURL here.

Suggested tweak
- needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.File != nil && block.File.FileType != nil)
+ needsBlockCopy := block.CacheControl != nil ||
+     block.Citations != nil ||
+     (block.File != nil && (block.File.FileType != nil || block.File.FileURL != nil))

295-377: Annotation filtering helper is consistent with OpenAI-native types

The new filterSupportedAnnotations helper and its use in OpenAIResponsesRequestInput.MarshalJSON look solid: only OpenAI-native types (file_citation, url_citation, container_file_citation, file_path) are preserved, with others dropped and annotations cleared when empty. Combined with the extended hasFieldsToStripInResponsesMessage, this gives a clean separation between internal/Anthropic-style annotations and what actually goes out to OpenAI.

Optional: avoid allocating when all annotations are supported

If you ever want a micro-optimization, you could detect the “all supported” case and return the original slice to avoid reallocating:

func filterSupportedAnnotations(annotations []schemas.ResponsesOutputMessageContentTextAnnotation) []schemas.ResponsesOutputMessageContentTextAnnotation {
    if len(annotations) == 0 {
        return annotations
    }
    out := annotations[:0]
    for _, a := range annotations {
        switch a.Type {
        case "file_citation", "url_citation", "container_file_citation", "file_path":
            out = append(out, a)
        }
    }
    if len(out) == len(annotations) {
        return annotations
    }
    return append([]schemas.ResponsesOutputMessageContentTextAnnotation(nil), out...)
}

Purely optional; current version is already correct.

Also applies to: 399-415, 432-451

core/schemas/responses.go (1)

404-427: Schema extensions for citations and Anthropic metadata are consistent

Adding Citations *Citations to ResponsesMessageContentBlock and Anthropic-style location fields to ResponsesOutputMessageContentTextAnnotation lines up with how conversions now map AnthropicTextCitation into annotations and back. Struct layout and JSON tags look correct.

You may optionally broaden the Type comment on ResponsesOutputMessageContentTextAnnotation to include Anthropic citation types (char_location, page_location, etc.) so the doc matches actual allowed values.

Also applies to: 453-473

core/providers/anthropic/types.go (1)

288-329: Consider separate request/response types for better type safety.

While the current implementation with dual-purpose AnthropicCitations is functional, using a single struct to represent both request configuration (Config) and response citations (TextCitations) adds complexity. The custom marshal/unmarshal logic is correct, but separate types would provide:

  1. Compile-time type safety (no need for runtime validation in MarshalJSON)
  2. Clearer intent in function signatures
  3. Simpler serialization logic (no conditional branching)

Based on Pratham's earlier suggestion, consider:

// For requests
type AnthropicCitationsConfig struct {
    Config *schemas.Citations
}

// For responses
type AnthropicCitationsResponse struct {
    Citations []AnthropicTextCitation
}

However, the current implementation does work correctly and the validation in MarshalJSON (lines 300-302) prevents invalid states, so this is more of a maintainability consideration.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f89a20 and 487c94d.

📒 Files selected for processing (10)
  • core/changelog.md
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/config.yml
  • tests/integrations/tests/test_anthropic.py
  • transports/changelog.md
✅ Files skipped from review due to trivial changes (1)
  • transports/changelog.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/schemas/chatcompletions.go
  • core/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • core/providers/openai/types.go
  • tests/integrations/config.yml
  • core/providers/anthropic/types.go
  • tests/integrations/tests/test_anthropic.py
  • core/providers/anthropic/responses.go
🧠 Learnings (6)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/schemas/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-24T07:38:16.990Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: tests/integrations/tests/test_google.py:2030-2030
Timestamp: 2025-12-24T07:38:16.990Z
Learning: In Python tests under tests/integrations/tests, allow a fixture parameter named test_config in test functions even if unused; do not flag it as unused. This is an internal convention to ensure consistency for integration tests.

Applied to files:

  • tests/integrations/tests/test_anthropic.py
🧬 Code graph analysis (3)
core/providers/anthropic/utils.go (3)
core/schemas/responses.go (2)
  • Citations (424-426)
  • ResponsesInputMessageContentBlockFile (432-437)
core/providers/anthropic/types.go (3)
  • AnthropicCitations (291-296)
  • AnthropicContentBlock (218-235)
  • AnthropicSource (238-243)
core/schemas/chatcompletions.go (1)
  • CacheControl (653-656)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (653-656)
core/schemas/responses.go (5)
  • Citations (424-426)
  • ResponsesMessageContentBlock (406-422)
  • ResponsesInputMessageContentBlockFile (432-437)
  • ResponsesOutputMessageContentText (448-451)
  • ResponsesOutputMessageContentTextAnnotation (453-473)
core/providers/anthropic/responses.go (2)
core/providers/anthropic/types.go (8)
  • AnthropicCitations (291-296)
  • AnthropicTextCitation (258-286)
  • AnthropicCitationTypeCharLocation (248-248)
  • AnthropicCitationTypePageLocation (249-249)
  • AnthropicCitationTypeContentBlockLocation (250-250)
  • AnthropicCitationTypeWebSearchResultLocation (251-251)
  • AnthropicCitationTypeSearchResultLocation (252-252)
  • AnthropicCitationType (245-245)
core/schemas/responses.go (3)
  • Citations (424-426)
  • ResponsesOutputMessageContentTextAnnotation (453-473)
  • ResponsesOutputMessageContentText (448-451)
🪛 Ruff (0.14.10)
tests/integrations/tests/test_anthropic.py

1773-1773: Unused method argument: test_config

(ARG002)


1837-1837: Unused method argument: test_config

(ARG002)


1912-1912: Unused method argument: test_config

(ARG002)

🔇 Additional comments (6)
core/providers/anthropic/utils.go (1)

152-162: Citations propagation into Anthropic document blocks looks correct

Using AnthropicCitations{Config: ...} on document blocks in both chat and responses conversions cleanly wires request-side citation config into Anthropic’s schema and matches AnthropicCitations’ split between Config and TextCitations. No issues from a correctness standpoint.

Also applies to: 229-239

core/providers/anthropic/responses.go (3)

554-573: Streaming citation deltas ↔ annotation events are wired correctly

The new AnthropicStreamDeltaTypeCitations handling in ToBifrostResponsesStream and the reverse mapping from ResponsesStreamResponseTypeOutputTextAnnotationAdded back to AnthropicStreamDeltaTypeCitations give a clean, symmetric bridge between Anthropic citation deltas and OpenAI-style annotation events. Index/outputIndex wiring and optional ItemID usage look consistent with the existing text/thinking delta patterns.

Also applies to: 1122-1136


3436-3476: Citations on text and document blocks are propagated correctly

The new logic in convertContentBlockToAnthropic and toBifrostResponsesDocumentBlock correctly:

  • Converts ResponsesOutputMessageContentText.Annotations into AnthropicCitations.TextCitations on text blocks.
  • Passes block.Citations into ConvertResponsesFileBlockToAnthropic so document blocks carry AnthropicCitations.Config.
  • Restores block.Citations.Config back onto ResponsesMessageContentBlock.Citations when converting documents from Anthropic to Bifrost.

This matches the AnthropicCitations design (request config vs response data) and keeps both directions consistent.

Also applies to: 3470-3476, 3518-3527


1455-1479: Comment contradicts current OutputFormat–citations gating

The code now enables anthropicReq.OutputFormat only when at least one input file block has Citations.Enabled == true, but the comment still states “Citations cannot be used together with Structured Outputs in anthropic.” That reads as the opposite of what the code does and is likely to confuse future maintainers.

Suggest either updating the comment to describe the actual (and apparently intentional) behavior, or replacing it with a short TODO that references Anthropic’s docs and clarifies the intended interaction between citations and structured outputs.

Likely an incorrect or invalid review comment.

tests/integrations/config.yml (1)

177-177: LGTM! Citations configuration correctly targets Anthropic.

The configuration appropriately enables citations only for Anthropic (line 219) while keeping it disabled for other providers. The scenario capability mapping (line 388) correctly associates citations with the "chat" capability.

Also applies to: 219-219, 260-260, 302-302, 341-341, 388-388

core/providers/anthropic/types.go (1)

509-509: LGTM! Streaming citation support properly integrated.

The addition of AnthropicStreamDeltaTypeCitations and the Citation field to AnthropicStreamDelta correctly extends the streaming API to support incremental citation delivery, consistent with other delta types in the codebase.

Also applies to: 519-519

@TejasGhatte TejasGhatte changed the base branch from main to graphite-base/1241 January 12, 2026 13:44
@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch 2 times, most recently from 745ef57 to 94aa2bd Compare January 12, 2026 13:51
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1241 to main January 12, 2026 13:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/providers/openai/types.go (1)

264-379: Bug: citations in tool output blocks won’t reliably be stripped.
Two problems:

  1. hasFieldsToStripInResponsesMessage doesn’t consider block.Citations inside ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks, so needsCopy may stay false and the request will marshal with citations intact. (Line 417-428)
  2. Even when tool-output copying is triggered, needsBlockCopy omits block.Citations != nil, so citations won’t be cleared for blocks that only have citations. (Line 354-359)
Proposed fix
diff --git a/core/providers/openai/types.go b/core/providers/openai/types.go
@@
-            for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
-                needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+            for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
+                needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
                 if needsBlockCopy {
                     blockCopy := block
                     blockCopy.CacheControl = nil
                     blockCopy.Citations = nil
@@
 func hasFieldsToStripInResponsesMessage(msg schemas.ResponsesMessage) bool {
@@
     if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Output != nil {
         if msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
             for _, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
                 if block.CacheControl != nil {
                     return true
                 }
+                if block.Citations != nil {
+                    return true
+                }
                 if block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil {
                     return true
                 }
             }
         }
     }
     return false
 }

Also applies to: 399-430

core/schemas/responses.go (1)

514-531: Bug: duplicated ResponsesComputerToolCallAction branch in MarshalJSON.
You check ResponsesComputerToolCallAction twice; the second is dead/unreachable and likely meant to be another action variant. (Line 515-523)

Proposed fix
diff --git a/core/schemas/responses.go b/core/schemas/responses.go
@@
 func (action ResponsesToolMessageActionStruct) MarshalJSON() ([]byte, error) {
     if action.ResponsesComputerToolCallAction != nil {
         return Marshal(action.ResponsesComputerToolCallAction)
     }
     if action.ResponsesWebSearchToolCallAction != nil {
         return Marshal(action.ResponsesWebSearchToolCallAction)
     }
-    if action.ResponsesComputerToolCallAction != nil {
-        return Marshal(action.ResponsesComputerToolCallAction)
-    }
     if action.ResponsesLocalShellToolCallAction != nil {
         return Marshal(action.ResponsesLocalShellToolCallAction)
     }
     if action.ResponsesMCPApprovalRequestAction != nil {
         return Marshal(action.ResponsesMCPApprovalRequestAction)
     }
     return nil, fmt.Errorf("responses tool message action struct is neither a computer tool call action nor a web search tool call action nor a local shell tool call action nor a mcp approval request action")
 }
🤖 Fix all issues with AI agents
In @core/internal/testutil/tests.go:
- Line 36: The test summary function printTestSummary omits the WebSearchTool
entry even though RunWebSearchToolTest was added to the execution list; update
the testScenarios slice inside printTestSummary to include the WebSearchTool
entry by adding {"WebSearchTool", testConfig.Scenarios.WebSearchTool} alongside
the other scenario entries so the web search tool status is reported in the
summary.

In @core/providers/anthropic/responses.go:
- Around line 2767-2814: The grouped conversion in
convertAnthropicContentBlocksToResponsesMessagesGrouped fails because
web_search_result blocks look for an already-emitted web_search_call in
bifrostMessages (scanning for ResponsesMessageTypeWebSearchCall and matching
CallID) even though server_tool_use/web_search_call is still only stored in
pendingToolUseBlocks; update the logic so that when encountering an
AnthropicContentBlockTypeWebSearchResult you attach Sources to the pending
tool-use entry instead of (or in addition to) scanning bifrostMessages: locate
the pendingToolUseBlocks entry keyed by block.ToolUseID (the same identifier
used to emit the ResponsesToolMessage.CallID), create/ensure its
ResponsesToolMessage.Action.ResponsesWebSearchToolCallAction exists, and append
the constructed ResponsesWebSearchToolCallActionSearchSource items there so
Sources get emitted when the tool-use is flushed; apply the same fix for the
duplicate logic around lines 2832-2877.

In @core/providers/openai/types.go:
- Around line 295-321: The code sets
ResponsesOutputMessageContentText.Annotations = nil expecting the field to be
omitted, but the struct tag is json:"annotations" so nil serializes as null;
update the ResponsesOutputMessageContentText type to use
json:"annotations,omitempty" (or otherwise add omitempty) so that setting
Annotations = nil actually omits the field, and leave the filtering logic in the
block that sets textCopy.Annotations = nil as-is; refer to the
ResponsesOutputMessageContentText type and its Annotations field and the code
that sets textCopy.Annotations = nil inside the loop.
🧹 Nitpick comments (8)
core/providers/openai/responses.go (1)

203-207: Shallow copy mutates original tool's ResponsesToolWebSearch.

The pattern newTool := tool copies the struct, but ResponsesToolWebSearch is a pointer field. Setting newTool.ResponsesToolWebSearch.MaxUses = nil also mutates the original tool's underlying struct. This is consistent with the existing ComputerUsePreview branch (line 201), but both share this issue.

If mutation is unintended, deep-copy the nested struct:

♻️ Suggested fix
 } else if tool.Type == schemas.ResponsesToolTypeWebSearch && tool.ResponsesToolWebSearch != nil && tool.ResponsesToolWebSearch.MaxUses != nil {
-    // create new tool and assign it to the filtered tools
-    newTool := tool
-    newTool.ResponsesToolWebSearch.MaxUses = nil
-    filteredTools = append(filteredTools, newTool)
+    // create new tool with cloned WebSearch to avoid mutating original
+    newTool := tool
+    webSearchCopy := *tool.ResponsesToolWebSearch
+    webSearchCopy.MaxUses = nil
+    newTool.ResponsesToolWebSearch = &webSearchCopy
+    filteredTools = append(filteredTools, newTool)
 }
core/internal/testutil/web_search_tool.go (2)

14-145: Web-search test is likely flaky; consider making the query/provider assertions more deterministic.
Current prompt (“current weather…”) may produce non-tool answers or variable tool payloads across models/providers. Consider asserting primarily on the presence/shape of web_search_call + at least one source, not on semantic answer content.


147-161: Align retry config with core/internal/testutil convention. (Line 148-160)
This file constructs ResponsesRetryConfig directly and sets Conditions, which conflicts with the established “GetTestRetryConfigForScenario → typed config w/ empty Conditions slice” pattern. Based on learnings, please standardize this helper to match the shared approach to keep retries consistent across the suite.

core/schemas/responses.go (1)

446-472: Schema tagging: avoid emitting null for optional fields (and enable “omit annotations” behavior).

  • ResponsesOutputMessageContentText.Annotations lacks omitempty, so “remove annotations array” attempts will still serialize as null. (Line 446-449)
  • ResponsesWebSearchToolCallActionSearchSource.Title/EncryptedContent/PageAge are pointers but lack omitempty, so they’ll serialize as null. (Line 685-688)
  • ResponsesToolWebSearchFilters.AllowedDomains lacks omitempty, so it can serialize as null too. (Line 1247-1249)

Also applies to: 679-688, 1245-1249

core/providers/anthropic/responses.go (2)

3556-3614: Use CallID as fallback for web_search tool IDs.
convertBifrostWebSearchCallToAnthropicBlocks uses msg.ID as the server_tool_use id / tool_use_id linkage. If a caller sets ResponsesToolMessage.CallID but not msg.ID, the linkage breaks. Consider preferring msg.ResponsesToolMessage.CallID (then fall back to msg.ID).


1640-1641: Setting params.Include unconditionally may override caller intent.
You default Include to web_search_call.action.sources. If callers pass a non-empty include list elsewhere, ensure this doesn’t unexpectedly overwrite it (you later allow extraParams["include"] to override, but not bifrostReq.Params.Include).

core/providers/anthropic/types.go (2)

262-292: Minor documentation fix needed.

The comment on line 266 appears incomplete - it should read "File ID for char_location..." for consistency with other field comments.

📝 Suggested documentation fix
-	// File ID char_location, page_location, content_block_location
+	// File ID for char_location, page_location, content_block_location
 	FileID *string `json:"file_id,omitempty"`

319-335: Misleading comment in UnmarshalJSON.

The comment on line 327 says "Try to unmarshal as config object first" but the code actually tries the array format first (line 321). This comment should be updated to reflect the actual order.

📝 Suggested fix
 func (ac *AnthropicCitations) UnmarshalJSON(data []byte) error {
-	// Try to unmarshal as array of citations
+	// Try to unmarshal as array of citations (response format)
 	var textCitations []AnthropicTextCitation
 	if err := sonic.Unmarshal(data, &textCitations); err == nil {
 		ac.TextCitations = textCitations
 		return nil
 	}

-	// Try to unmarshal as config object first
+	// Try to unmarshal as config object (request format)
 	var config schemas.Citations
 	if err := sonic.Unmarshal(data, &config); err == nil {
 		ac.Config = &config
 		return nil
 	}

 	return fmt.Errorf("citations field is neither a config object nor an array of citations")
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 487c94d and 94aa2bd.

📒 Files selected for processing (13)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/providers/anthropic/utils.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/schemas/chatcompletions.go
  • core/providers/openai/responses.go
  • core/internal/testutil/account.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/schemas/utils.go
  • core/schemas/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
🧠 Learnings (11)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/chatcompletions.go
  • core/providers/openai/responses.go
  • core/internal/testutil/account.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/schemas/utils.go
  • core/schemas/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/schemas/chatcompletions.go
  • core/providers/openai/responses.go
  • core/internal/testutil/account.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/schemas/utils.go
  • core/schemas/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-17T08:44:08.788Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1114
File: core/schemas/chatcompletions.go:224-228
Timestamp: 2025-12-17T08:44:08.788Z
Learning: In core/schemas/chatcompletions.go, ensure the schema structures mirror OpenAI's API specifications exactly. Use the valid values for fields like ChatAudioParameters.Format and ChatAudioParameters.Voice as defined by OpenAI's documentation, and avoid adding additional inline documentation or constants to maintain direct compatibility with OpenAI's API.

Applied to files:

  • core/schemas/chatcompletions.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/internal/testutil/tests.go
📚 Learning: 2025-12-15T10:16:21.909Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/huggingface/huggingface_test.go:12-63
Timestamp: 2025-12-15T10:16:21.909Z
Learning: In provider tests under core/providers/<provider>/*_test.go, do not require or flag the use of defer for Shutdown(); instead call client.Shutdown() at the end of each test function. This pattern appears consistent across all provider tests. Apply this rule only within this path; for other tests or resources, defer may still be appropriate.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-12T06:41:13.643Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1153
File: core/schemas/utils.go:1159-1183
Timestamp: 2026-01-12T06:41:13.643Z
Learning: In core/schemas/utils.go, IsGrokReasoningModel (and the grokReasoningModels list) is the canonical utility for model matching. Treat it as the source of truth and not as a duplicate; if extending model support, update this file rather than introducing separate matching logic, and verify changes here to avoid diverging behavior.

Applied to files:

  • core/schemas/utils.go
🧬 Code graph analysis (7)
core/schemas/chatcompletions.go (1)
core/schemas/responses.go (1)
  • Citations (422-424)
core/providers/openai/responses.go (1)
core/schemas/responses.go (2)
  • ResponsesToolTypeWebSearch (1053-1053)
  • ResponsesToolWebSearch (1236-1243)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (651-654)
core/schemas/responses.go (5)
  • Citations (422-424)
  • ResponsesMessageContentBlock (404-420)
  • ResponsesInputMessageContentBlockFile (430-435)
  • ResponsesOutputMessageContentText (446-449)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
core/internal/testutil/tests.go (1)
core/internal/testutil/web_search_tool.go (1)
  • RunWebSearchToolTest (15-145)
core/schemas/utils.go (2)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessageAnnotationCitation (747-754)
core/providers/gemini/types.go (1)
  • Type (822-822)
core/providers/anthropic/types.go (3)
core/schemas/chatcompletions.go (1)
  • CacheControl (651-654)
core/schemas/responses.go (1)
  • Citations (422-424)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
core/providers/anthropic/responses.go (3)
core/providers/anthropic/types.go (2)
  • AnthropicContentBlock (219-239)
  • AnthropicContent (158-161)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/responses.go (5)
  • ResponsesMessage (319-332)
  • ResponsesToolMessage (485-505)
  • ResponsesWebSearchToolCallAction (670-677)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
  • ResponsesOutputMessageContentText (446-449)
🔇 Additional comments (15)
core/internal/testutil/account.go (1)

61-61: LGTM!

Clean addition of the WebSearchTool scenario flag, following the established pattern with appropriate documentation comment.

core/schemas/chatcompletions.go (2)

642-642: LGTM!

The Citations field addition enables provider-specific citation metadata to flow through content blocks, aligning with the PR objective of unified citation handling.


742-743: LGTM!

Renaming Citation to URLCitation improves clarity by distinguishing URL-based citations from other potential citation types, making the API more self-documenting.

core/schemas/utils.go (1)

642-659: LGTM!

Deep copy logic correctly updated to reference URLCitation field, maintaining proper handling of all pointer fields within the citation structure.

core/providers/openai/openai_test.go (1)

53-53: LGTM!

Enabling WebSearchTool test coverage for OpenAI is appropriate given OpenAI's web search support via the Responses API.

core/providers/anthropic/anthropic_test.go (1)

45-45: LGTM!

Enabling WebSearchTool for Anthropic tests aligns with the PR's objective of enhancing Anthropic citations and web search tool handling.

core/providers/openai/types.go (1)

115-137: Good: strip block.Citations alongside CacheControl / file fields when marshalling Chat requests. (Line 119-130)
This keeps provider-specific request-only fields out of OpenAI chat payloads.

core/schemas/responses.go (1)

669-677: Web search fields/events: verify downstream compatibility.
Adding Queries alongside Query, plus new response.web_search_call.* stream event types, likely needs corresponding handling in stream accumulators/routers/clients. Please ensure all consumers recognize these event types and don’t drop them as unknown.

Also applies to: 1429-1433

core/providers/anthropic/responses.go (3)

269-357: Web-search streaming state machine: looks plausible; please verify sequencing/content_index expectations.
You emit OpenAI-style output_item.added + web_search_call.in_progress/searching/completed, then later output_item.done for the web_search_call when the result block stops. Make sure downstream consumers don’t assume ContentIndex in output_item.done refers to the original query block (you currently use the result block’s index).

Also applies to: 689-825


18-161: State pooling/reset changes look good.
ContentIndexToBlockType is properly initialized/cleared, and web-search fields are reset in both acquire and flush paths.


1-4640: Stack awareness (Graphite): please share stacked PR list so I can re-check for duplicated/conflicting changes.
The guidelines require reviewing in the context of the whole stack; I only have this PR’s file subset.

core/providers/anthropic/types.go (4)

204-216: LGTM!

The new content block type constants are well-defined and follow the existing naming conventions. They properly cover web search, MCP tools, and thinking-related content types.


219-239: LGTM!

The new fields (URL, EncryptedContent, PageAge) align with Anthropic's web search result content structure, and the Citations field type change to *AnthropicCitations supports the new unified citation handling.


249-257: LGTM!

Citation type constants are comprehensive and match Anthropic's supported citation location types.


515-528: LGTM!

The streaming delta type and field additions properly support citation streaming. Using a single *AnthropicTextCitation (rather than a slice) for the Citation field is appropriate since deltas typically contain individual citations.

@TejasGhatte TejasGhatte changed the base branch from main to graphite-base/1241 January 14, 2026 11:03
@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 94aa2bd to 0c08da7 Compare January 14, 2026 11:03
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1241 to 01-09-fix_claude_code_openai January 14, 2026 11:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/schemas/responses.go (2)

514-531: Remove duplicated ResponsesComputerToolCallAction branch in MarshalJSON (merge artifact).
ResponsesToolMessageActionStruct.MarshalJSON checks ResponsesComputerToolCallAction twice (Line 515-517 and again at Line 521-523).

Proposed diff
@@
 	if action.ResponsesWebSearchToolCallAction != nil {
 		return Marshal(action.ResponsesWebSearchToolCallAction)
 	}
-	if action.ResponsesComputerToolCallAction != nil {
-		return Marshal(action.ResponsesComputerToolCallAction)
-	}
 	if action.ResponsesLocalShellToolCallAction != nil {
 		return Marshal(action.ResponsesLocalShellToolCallAction)
 	}

669-688: Add omitempty to optional pointer and slice fields to prevent null values in JSON serialization.

The Anthropic-specific optional fields in ResponsesWebSearchToolCallActionSearchSource (Title, EncryptedContent, PageAge at lines 685-688) are pointer types but lack omitempty tags. When nil, they serialize as JSON null instead of being omitted. Similarly, AllowedDomains in ResponsesToolWebSearchFilters (line 1246) is a slice field missing omitempty, which will serialize as null when nil—inconsistent with BlockedDomains on the next line that already uses omitempty.

Proposed diff
@@
 type ResponsesWebSearchToolCallActionSearchSource struct {
@@
-	Title            *string `json:"title"`
-	EncryptedContent *string `json:"encrypted_content"`
-	PageAge          *string `json:"page_age"`
+	Title            *string `json:"title,omitempty"`
+	EncryptedContent *string `json:"encrypted_content,omitempty"`
+	PageAge          *string `json:"page_age,omitempty"`
 }
@@
 type ResponsesToolWebSearchFilters struct {
-	AllowedDomains []string `json:"allowed_domains"`           // Allowed domains for the search
+	AllowedDomains []string `json:"allowed_domains,omitempty"` // Allowed domains for the search
 	BlockedDomains []string `json:"blocked_domains,omitempty"` // Blocked domains for the search, only used in anthropic
 }
🤖 Fix all issues with AI agents
In `@core/internal/testutil/web_search_tool.go`:
- Around line 125-132: The log message incorrectly claims "Found text response
with citations" whenever a non-empty text block (block.Text) is found even if no
citations exist; update the logging in the test (where block, hasTextResponse
and block.ResponsesOutputMessageContentText.Annotations are used) so that you
only log "Found text response with citations" when
ResponsesOutputMessageContentText != nil and len(Annotations) > 0, otherwise log
a clear alternative such as "Found text response without citations"; keep
hasTextResponse logic unchanged and still check for annotations afterward using
the same t.Logf calls.

In `@core/providers/anthropic/responses.go`:
- Around line 3710-3768: convertBifrostWebSearchCallToAnthropicBlocks currently
sets webSearchResultBlock.ToolUseID = msg.ID which can be nil; ensure the result
block always has a non-nil ToolUseID by generating or reusing an ID: if msg.ID
is nil, create a stable/new ID, assign it to serverToolUseBlock.ID (and
optionally to msg.ID) before appending serverToolUseBlock, and then set
webSearchResultBlock.ToolUseID to that same ID instead of msg.ID; this
guarantees the result block links to the server_tool_use block.
- Around line 3027-3055: convertAnthropicCitationToAnnotation currently maps
page_location and content_block_location to StartCharIndex/EndCharIndex; update
it so page_location populates StartPageNumber and EndPageNumber,
content_block_location populates StartBlockIndex and EndBlockIndex, and only the
character-range citation types populate StartCharIndex/EndCharIndex; adjust any
type switches in convertAnthropicCitationToAnnotation (and its other occurrence)
to set the correct fields (StartPageNumber/EndPageNumber for page_location,
StartBlockIndex/EndBlockIndex for content_block_location) and avoid overwriting
those into char index fields.
- Around line 273-360: The code registers web-search result blocks in the map
(state.ContentIndexToBlockType[*chunk.Index] =
AnthropicContentBlockTypeWebSearchToolResult) but never removes that entry when
the result is fully handled, causing a map-entry leak; also returning
nil,nil,false when a ToolUseID doesn't match silently hides ordering/ID
problems. Fix by: when you handle a web-search result that matches
state.WebSearchToolID (in the branch that sets state.WebSearchResult and returns
WebSearchCallCompleted), delete the map entry for the chunk index
(delete(state.ContentIndexToBlockType, *chunk.Index)) and clear
state.WebSearchToolID/WebSearchResult/WebSearchOutputIndex as appropriate; in
the non-matching-tool-id branch replace the silent nil return with a logged
warning or an error response (or return a diagnostic response instead of nil) so
ordering/ID mismatches are visible. Also ensure content_block_stop removes any
leftover ContentIndexToBlockType entries for web-search indices.
- Around line 693-842: The web-search completion currently fires whenever
state.WebSearchResult and state.WebSearchToolID are non-nil, which can misfire
for unrelated content stops and never cleans up state.ContentIndexToBlockType;
change the condition that detects a web_search_tool_result end to also require
chunk.Index to be non-nil and equal to the content-block index associated with
the pending web search (use whatever index you store for the web-search result —
e.g. compare *chunk.Index to the stored web-search content index tied to
state.WebSearchResult/WebSearchOutputIndex or add a new state field if needed),
then after creating the completed web_search_call response delete the
corresponding entry from state.ContentIndexToBlockType and clear the stored
web-search index/state (state.WebSearchToolID, state.WebSearchOutputIndex,
state.WebSearchResult, state.AccumulatedJSON) so the handler only runs for the
correct content_block_stop and the tracking map is cleaned up.
- Around line 664-684: The citation delta re-emission logic must mirror other
ContentBlockDelta handling: in ToAnthropicResponsesStreamResponse when building
the OutputTextAnnotationAdded (case AnthropicStreamDeltaTypeCitations / handling
of ContentBlockDelta for citations), prefer bifrostResp.OutputIndex when non-nil
and only fall back to bifrostResp.ContentIndex otherwise; update the index
selection logic (check OutputIndex first, then ContentIndex) for
OutputIndex/ContentIndex assignment so citations get the same stream index
fallback behavior as OutputTextDelta, ToolCallInputDelta, and
ReasoningSummaryTextDelta.

In `@tests/integrations/python/tests/test_anthropic.py`:
- Around line 1776-1839: Rename the unused fixture parameter test_config to
_test_config in the test_33_citations_pdf signature to satisfy ARG002, and add a
robust assertion inside the PDF citation loop (in test_33_citations_pdf)
ensuring citation.end_page_number >= citation.start_page_number to catch
inverted page ranges; keep all other citation structure assertions as-is and run
ruff/pytest to verify no linter errors.
- Around line 1840-1914: The test function test_34_citations_text has an unused
parameter test_config (ARG002) and weak citation validation; rename the
parameter to _test_config to silence Ruff and add a non-empty check for text
citations by asserting citation.cited_text is truthy (e.g., assert
citation.cited_text, "cited_text should not be empty") inside the loop that
inspects block.citations (keep existing checks for type == "char_location",
document_index == 0, start_char_index/end_char_index relations).
- Around line 1915-2014: In test_35_citations_streaming rename the unused
parameter test_config to _test_config to silence ARG002, then simplify the
streaming loop by removing over-defensive hasattr checks and using direct
attribute access with explicit assertions (e.g., assume event.type, event.delta,
event.delta.type, event.delta.text/citation exist and assert when missing)
inside the for event in stream block (refer to symbols:
test_35_citations_streaming, anthropic_client.messages.create, event.type,
event.delta, event.delta.type, event.delta.text, event.delta.citation,
citation); finally, after collecting citations add an assertion that at least
one citation.document_index == 0 to match non-streaming expectations and keep
the rest of the validations (citation.type == "char_location", cited_text
presence, start/end indices checks).
- Around line 2099-2240: Remove dead/unused code and fix lint issues in
test_37_web_search_streaming: delete the unused variables search_queries and
has_citation_delta, and remove the entire try/except block that imports json and
does nothing with delta.partial_json (the input_json_delta handling), replacing
it with no-op or simply skip handling input_json_delta; change the bare except
to not be used (delete the block instead of keeping a bare except), update the
assertion message that checks complete_text to say "about news" instead of
"about weather", and fix f-strings with no placeholders by converting prints
that use plain strings (e.g., lines printing "✓ Streaming validation:" and
similar) to simple string literals or ensure they include placeholders if
intended; keep all other streaming checks (has_server_tool_use,
has_search_tool_result, search_results, text_parts) intact in
test_37_web_search_streaming.
- Around line 2015-2098: In test_36_web_search_non_streaming rename the unused
parameter test_config to _test_config to silence ARG002, remove the unused local
variable has_citations and any assignments/uses of it (it’s never asserted), and
change the final print call print(f"✓ Web search (non-streaming) test passed!")
to a plain string print("✓ Web search (non-streaming) test passed!") to remove
the unnecessary f-string; keep the citations-related assertions inside the loop
if you intend to require citations, otherwise delete that block entirely.

In `@tests/integrations/python/tests/test_openai.py`:
- Around line 2835-2927: Remove the unused has_citations variable or make it
part of an assertion (in test_52_web_search_non_streaming) so Ruff no longer
flags it; change pointless f-strings like print(f"✓ Web search (non-streaming)
test passed!") to plain prints without interpolation; and relax the strict
weather-keyword assertion: instead of always asserting any(keyword in
output_text), assert that either the text contains a weather-related keyword OR
the web_search_call produced non-empty action.sources or a non-completed/
refusal status (check search_status and action.sources) so the test won't flake
when a provider/tool refuses or degrades.
♻️ Duplicate comments (4)
core/providers/openai/types.go (1)

315-319: Previous feedback: Annotations = nil serializes as null, not omitted.

As noted in the previous review, the Annotations field lacks omitempty, so setting it to nil results in "annotations": null in JSON rather than omitting the field entirely. The comment "remove the annotations array" is slightly misleading.

This may be acceptable if the OpenAI API tolerates null for this field.

core/providers/anthropic/responses.go (3)

1912-1936: Comment/behavior mismatch for structured outputs vs citations (still misleading).

The comment says citations can’t be used with structured outputs, but the code sets anthropicReq.OutputFormat only when citations are enabled. Either the comment is wrong or the condition is wrong (same concern as previously raised).

At minimum, update the comment to reflect the actual intent (and ideally link to the Anthropic constraint you’re implementing).


2899-2946: Grouped conversion: web-search sources attachment likely still broken.

In convertAnthropicContentBlocksToResponsesMessagesGrouped, server_tool_use is accumulated in pendingToolUseBlocks but web_search_result tries to find an already-emitted web_search_call by scanning bifrostMessages. If the call hasn’t been flushed yet, sources won’t attach.


4378-4505: Citation round-tripping is still broken for url_citation and page/block citations.

Two blockers:

  1. convertAnnotationToAnthropicCitation: case "url_citation" never sets citation.Type to web_search_result_location, so you emit an invalid Anthropic citation type (“url_citation”).
  2. convertAnthropicCitationToAnnotation: page/block location mapping likely drops the actual page/block indices (see earlier comment).
Proposed diff (url_citation mapping)
 case "url_citation":
+    citation.Type = AnthropicCitationTypeWebSearchResultLocation
     citation.URL = annotation.URL
     citation.Title = annotation.Title
     citation.EncryptedIndex = annotation.EncryptedIndex
+    citation.DocumentIndex = annotation.Index
+    if annotation.Text != nil {
+        citation.CitedText = *annotation.Text
+    }
🧹 Nitpick comments (3)
tests/integrations/python/config.yml (1)

186-186: Minor: Inconsistent key quoting style.

Line 186 uses quoted key "web_search" while line 249 uses unquoted web_search. Consider using consistent formatting across the file for maintainability.

♻️ Suggested fix
-    "web_search": true
+    web_search: true

Also applies to: 249-249

core/internal/testutil/web_search_tool.go (1)

148-161: Consider using GetTestRetryConfigForScenario() for consistency.

Based on learnings, the convention in this directory is to use GetTestRetryConfigForScenario() to obtain a base retry config. However, the specialized retry behavior here (5 attempts, 2-10s delays) may be intentional for web search tests which can be flaky due to external API calls.

If consistency is preferred, consider extracting base config values from GetTestRetryConfigForScenario() and overriding only the necessary fields.

core/providers/anthropic/responses.go (1)

19-165: Stream-state pooling looks fine; avoid reallocating maps in flush() if pool reuse is the goal.

acquireAnthropicResponsesStreamState() carefully clear()s maps, but (*AnthropicResponsesStreamState).flush() re-allocates them with make(...), which defeats reuse and adds churn (esp. for long-lived servers).

Proposed diff
 func (state *AnthropicResponsesStreamState) flush() {
@@
-    state.ContentIndexToOutputIndex = make(map[int]int)
-    state.ContentIndexToBlockType = make(map[int]AnthropicContentBlockType)
-    state.ToolArgumentBuffers = make(map[int]string)
-    state.MCPCallOutputIndices = make(map[int]bool)
-    state.ItemIDs = make(map[int]string)
-    state.ReasoningSignatures = make(map[int]string)
-    state.TextContentIndices = make(map[int]bool)
-    state.ReasoningContentIndices = make(map[int]bool)
+    if state.ContentIndexToOutputIndex != nil { clear(state.ContentIndexToOutputIndex) } else { state.ContentIndexToOutputIndex = make(map[int]int) }
+    if state.ContentIndexToBlockType != nil { clear(state.ContentIndexToBlockType) } else { state.ContentIndexToBlockType = make(map[int]AnthropicContentBlockType) }
+    if state.ToolArgumentBuffers != nil { clear(state.ToolArgumentBuffers) } else { state.ToolArgumentBuffers = make(map[int]string) }
+    if state.MCPCallOutputIndices != nil { clear(state.MCPCallOutputIndices) } else { state.MCPCallOutputIndices = make(map[int]bool) }
+    if state.ItemIDs != nil { clear(state.ItemIDs) } else { state.ItemIDs = make(map[int]string) }
+    if state.ReasoningSignatures != nil { clear(state.ReasoningSignatures) } else { state.ReasoningSignatures = make(map[int]string) }
+    if state.TextContentIndices != nil { clear(state.TextContentIndices) } else { state.TextContentIndices = make(map[int]bool) }
+    if state.ReasoningContentIndices != nil { clear(state.ReasoningContentIndices) } else { state.ReasoningContentIndices = make(map[int]bool) }
@@
 }

Also applies to: 82-86, 121-123, 147-152

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94aa2bd and 0c08da7.

📒 Files selected for processing (16)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/providers/openai/responses.go
  • core/schemas/utils.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/account.go
  • core/schemas/chatcompletions.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_anthropic.py
  • core/providers/anthropic/responses.go
  • tests/integrations/python/config.yml
  • core/internal/testutil/tests.go
  • tests/integrations/python/tests/test_openai.py
🧠 Learnings (15)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/tests.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/tests.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/tests.go
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/web_search_tool.go
  • core/internal/testutil/tests.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
📚 Learning: 2026-01-14T04:32:14.023Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/config.yml:170-171
Timestamp: 2026-01-14T04:32:14.023Z
Learning: Guideline: In HuggingFace provider configuration, follow the provider-prefixed model path format fal-ai/<namespace>/<model> where the first segment fal-ai/ is the provider routing prefix and the remainder is the actual model path on fal.ai. Do not double-include the provider prefix. Example: fal-ai/flux/dev is the actual model path; when combined with the provider prefix you get fal-ai/fal-ai/flux/dev, which correctly indicates provider + model path.

Applied to files:

  • tests/integrations/python/config.yml
🧬 Code graph analysis (7)
core/internal/testutil/web_search_tool.go (5)
core/internal/testutil/utils.go (1)
  • CreateBasicResponsesMessage (276-284)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/internal/testutil/test_retry_framework.go (1)
  • WithResponsesTestRetry (443-593)
core/internal/testutil/test_retry_conditions.go (2)
  • ResponsesEmptyCondition (989-989)
  • ResponsesGenericResponseCondition (1061-1061)
core/internal/testutil/response_validation.go (1)
  • ResponseExpectations (18-43)
core/providers/anthropic/utils.go (2)
core/schemas/responses.go (1)
  • Citations (422-424)
core/providers/anthropic/types.go (3)
  • AnthropicCitations (297-302)
  • AnthropicContentBlock (219-239)
  • AnthropicSource (242-247)
core/providers/anthropic/types.go (1)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (720-723)
core/schemas/responses.go (5)
  • Citations (422-424)
  • ResponsesMessageContentBlock (404-420)
  • ResponsesInputMessageContentBlockFile (430-435)
  • ResponsesOutputMessageContentText (446-449)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
core/schemas/responses.go (2)
core/schemas/json_wasm.go (1)
  • Marshal (8-10)
core/schemas/json_native.go (1)
  • Marshal (8-10)
core/internal/testutil/tests.go (1)
core/internal/testutil/web_search_tool.go (1)
  • RunWebSearchToolTest (15-145)
tests/integrations/python/tests/test_openai.py (1)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_with_vk_for_scenario (50-101)
  • format_provider_model (126-141)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_anthropic.py

1777-1777: Unused method argument: test_config

(ARG002)


1841-1841: Unused method argument: test_config

(ARG002)


1916-1916: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2079-2079: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2097-2097: f-string without any placeholders

Remove extraneous f prefix

(F541)


2100-2100: Unused method argument: test_config

(ARG002)


2138-2138: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2194-2194: Do not use bare except

(E722)


2194-2195: try-except-pass detected, consider logging the exception

(S110)


2204-2204: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2226-2226: f-string without any placeholders

Remove extraneous f prefix

(F541)


2235-2235: f-string without any placeholders

Remove extraneous f prefix

(F541)


2239-2239: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/test_openai.py

2898-2898: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2926-2926: f-string without any placeholders

Remove extraneous f prefix

(F541)


3036-3036: f-string without any placeholders

Remove extraneous f prefix

(F541)


3043-3043: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (17)
core/internal/testutil/tests.go (1)

36-36: LGTM!

The RunWebSearchToolTest is correctly added to both the execution list and the test summary. This properly integrates the web search tool test into the comprehensive test suite.

Also applies to: 109-109

core/providers/anthropic/utils.go (2)

160-162: LGTM - Citations propagation correctly implemented.

The citations are properly wrapped in AnthropicCitations{Config: block.Citations} which aligns with the type definition where Config *schemas.Citations is used for request configuration.

Also applies to: 237-239


230-230: All callers of ConvertResponsesFileBlockToAnthropic have been properly updated with the new citations parameter. The function has one call site in core/providers/anthropic/responses.go, which correctly passes all three required arguments.

tests/integrations/python/config.yml (1)

198-198: LGTM!

The feature flags are appropriately configured:

  • Citations enabled only for Anthropic, reflecting the PR's focus on Anthropic citations handling
  • Web search enabled for providers that support it
  • Scenario capabilities correctly map to the "chat" capability type

Also applies to: 262-262, 459-459, 472-472

core/providers/openai/types.go (2)

432-459: LGTM - Clean filtering implementation.

The filterSupportedAnnotations function correctly:

  • Preserves OpenAI-native annotation types (file_citation, container_file_citation, file_path)
  • For url_citation, constructs a new annotation with only the OpenAI-spec fields, effectively stripping Anthropic-specific fields like encrypted_index
  • Silently drops unsupported annotation types

119-123: LGTM - Consistent field stripping pattern.

The changes correctly extend the existing pattern for stripping non-OpenAI fields:

  • Citations field detection added to both hasFieldsToStripInChatMessage and hasFieldsToStripInResponsesMessage
  • Block copying now clears Citations alongside CacheControl
  • Annotation presence check added for responses messages

Also applies to: 388-414

core/internal/testutil/web_search_tool.go (1)

15-73: LGTM - Well-structured web search tool test.

The test implementation:

  • Correctly gates execution on testConfig.Scenarios.WebSearchTool
  • Follows the Responses API testing pattern with proper retry configuration
  • Validates both web_search_call presence and text response content
  • Uses appropriate assertions for required conditions

Also applies to: 140-144

core/schemas/responses.go (2)

404-424: Citations config at content-block level looks consistent with the “{ enabled: true }” shape.
Adding Citations *Citations to ResponsesMessageContentBlock (Line 419) is a reasonable place to carry request-time citations config while keeping response-time citations in Annotations.


1429-1432: Web-search streaming event types: good addition; ensure parsing code matches these names.
The new ResponsesStreamResponseTypeWebSearchCallInProgress / Completed constants (Line 1429-1432) should align with whatever emits/consumes these in provider handlers.

core/providers/anthropic/types.go (3)

203-216: New content-block type for web-search tool result: LGTM.
AnthropicContentBlockTypeWebSearchToolResult is additive and doesn’t disturb existing variants.


259-337: Citations modeling + union marshal/unmarshal is in good shape (includes file_id + url).
AnthropicTextCitation includes optional file_id and web_search_result_location.url (Line 267, 287), and AnthropicCitations correctly enforces “config XOR response array” with array-first unmarshal (Line 304-336). Based on learnings, this matches the desired citation shapes.


512-530: No action needed—citations_delta wire format is correctly implemented.

The field name (citation) and cardinality (single object per delta, not array) match the Anthropic streaming specification. Each citations_delta event delivers one AnthropicTextCitation to be appended to the content block's citations list.

core/providers/anthropic/responses.go (5)

590-596: Accumulating InputJSON for web_search/computer tools: LGTM.

Reusing ChunkIndex + AccumulatedJSON to suppress intermediary deltas for special blocks is a clean approach, and the “pointer-to-local-variable” avoidance for outputIndex storage is good.


1772-1772: Defaulting params.Include to web search sources is reasonable.

This makes web_search results usable by default without callers remembering include strings.


4214-4219: File/document citations config plumbing: LGTM.

Passing block.Citations into ConvertResponsesFileBlockToAnthropic and mapping block.Citations.Config back onto the Bifrost file block is the right linkage.

Also applies to: 4267-4269


1-4794: All referenced schema types and enums are already defined in-tree and verified to exist:

Bifrost schemas (core/schemas/responses.go):

  • ResponsesMessageTypeWebSearchCall (line 299)
  • ResponsesStreamResponseTypeWebSearchCall* variants including InProgress, Searching, Completed (lines 1429–1433)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (line 1460)
  • ResponsesToolMessageActionStruct (line 507)
  • ResponsesWebSearchToolCallAction and ResponsesWebSearchToolCallActionSearchSource (lines 670–680)
  • Citations struct (line 422)

Anthropic provider types (core/providers/anthropic/types.go):

  • AnthropicContentBlockTypeWebSearchToolResult and WebSearchResult (lines 210–211)
  • AnthropicStreamDeltaTypeCitations (line 517)
  • AnthropicCitationType enum including WebSearchResultLocation (lines 249–256)
  • AnthropicCitations struct (line 297)

No schema definitions are missing. All types used throughout this PR file compile correctly against existing schema definitions in the repository.


3865-3889: Tool conversion correctly handles mutual exclusivity via downstream sanitization. The code properly carries both AllowedDomains and BlockedDomains at tool definition time, and the sanitization logic (lines 1317–1337, 1460–1472, 3489–3503) correctly removes blocked_domains when both are present during API serialization—matching Anthropic's requirement that these fields are mutually exclusive. The strategy of keeping allowed_domains and discarding blocked_domains is documented and consistent across the codebase.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 0c08da7 to a8b9749 Compare January 14, 2026 12:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
core/schemas/responses.go (1)

514-555: Fix ResponsesToolMessageActionStruct JSON union logic (currently incorrect + ambiguous).

  • MarshalJSON has a duplicated ResponsesComputerToolCallAction branch (Line 518-523), and does not enforce “exactly one” variant.
  • UnmarshalJSON is dangerous: unmarshalling into ResponsesWebSearchToolCallAction will often succeed for non-web-search payloads (any object with a type string), so you can silently misclassify actions.
Proposed fix: validate exclusivity in MarshalJSON + discriminate by action.type in UnmarshalJSON
 func (action ResponsesToolMessageActionStruct) MarshalJSON() ([]byte, error) {
+	// Validate union: exactly one must be set
+	set := 0
+	if action.ResponsesWebSearchToolCallAction != nil {
+		set++
+	}
+	if action.ResponsesComputerToolCallAction != nil {
+		set++
+	}
+	if action.ResponsesLocalShellToolCallAction != nil {
+		set++
+	}
+	if action.ResponsesMCPApprovalRequestAction != nil {
+		set++
+	}
+	if set != 1 {
+		return nil, fmt.Errorf("responses tool message action struct must have exactly one variant set, got %d", set)
+	}
+
 	if action.ResponsesWebSearchToolCallAction != nil {
 		return Marshal(action.ResponsesWebSearchToolCallAction)
 	}
 	if action.ResponsesComputerToolCallAction != nil {
 		return Marshal(action.ResponsesComputerToolCallAction)
 	}
-	if action.ResponsesComputerToolCallAction != nil {
-		return Marshal(action.ResponsesComputerToolCallAction)
-	}
 	if action.ResponsesLocalShellToolCallAction != nil {
 		return Marshal(action.ResponsesLocalShellToolCallAction)
 	}
 	if action.ResponsesMCPApprovalRequestAction != nil {
 		return Marshal(action.ResponsesMCPApprovalRequestAction)
 	}
 	return nil, fmt.Errorf("responses tool message action struct is neither a computer tool call action nor a web search tool call action nor a local shell tool call action nor a mcp approval request action")
 }
 
 func (action *ResponsesToolMessageActionStruct) UnmarshalJSON(data []byte) error {
-	var webSearchToolCallAction ResponsesWebSearchToolCallAction
-	if err := Unmarshal(data, &webSearchToolCallAction); err == nil {
-		action.ResponsesWebSearchToolCallAction = &webSearchToolCallAction
-		return nil
-	}
-	var computerToolCallAction ResponsesComputerToolCallAction
-	if err := Unmarshal(data, &computerToolCallAction); err == nil {
-		action.ResponsesComputerToolCallAction = &computerToolCallAction
-		return nil
-	}
+	// Discriminate by "type" to avoid false-positive unmarshals
+	var peek struct {
+		Type string `json:"type"`
+	}
+	if err := Unmarshal(data, &peek); err != nil {
+		return fmt.Errorf("failed to read tool action type: %w", err)
+	}
+
+	switch peek.Type {
+	// Web search action types
+	case "search", "open_page", "find":
+		var a ResponsesWebSearchToolCallAction
+		if err := Unmarshal(data, &a); err != nil {
+			return fmt.Errorf("failed to unmarshal web_search action: %w", err)
+		}
+		action.ResponsesWebSearchToolCallAction = &a
+		return nil
+	// Computer action types (per schema comment)
+	case "click", "double_click", "drag", "keypress", "move", "screenshot", "scroll", "type", "wait", "zoom":
+		var a ResponsesComputerToolCallAction
+		if err := Unmarshal(data, &a); err != nil {
+			return fmt.Errorf("failed to unmarshal computer action: %w", err)
+		}
+		action.ResponsesComputerToolCallAction = &a
+		return nil
+	// Local shell action types (per schema)
+	case "exec":
+		var a ResponsesLocalShellToolCallAction
+		if err := Unmarshal(data, &a); err != nil {
+			return fmt.Errorf("failed to unmarshal local_shell action: %w", err)
+		}
+		action.ResponsesLocalShellToolCallAction = &a
+		return nil
+	// MCP approval request action types (per schema)
+	case "mcp_approval_request":
+		var a ResponsesMCPApprovalRequestAction
+		if err := Unmarshal(data, &a); err != nil {
+			return fmt.Errorf("failed to unmarshal mcp approval request action: %w", err)
+		}
+		action.ResponsesMCPApprovalRequestAction = &a
+		return nil
+	default:
+		return fmt.Errorf("unknown tool action type: %s", peek.Type)
+	}
 	var localShellToolCallAction ResponsesLocalShellToolCallAction
 	if err := Unmarshal(data, &localShellToolCallAction); err == nil {
 		action.ResponsesLocalShellToolCallAction = &localShellToolCallAction
 		return nil
 	}
 	var mcpApprovalRequestAction ResponsesMCPApprovalRequestAction
 	if err := Unmarshal(data, &mcpApprovalRequestAction); err == nil {
 		action.ResponsesMCPApprovalRequestAction = &mcpApprovalRequestAction
 		return nil
 	}
 	return fmt.Errorf("responses tool message action struct is neither a computer tool call action nor a web search tool call action nor a local shell tool call action nor a mcp approval request action")
 }
core/providers/openai/types.go (2)

114-137: Chat request scrubbing bug: FileURL won’t be stripped unless another field triggers block copy.
needsBlockCopy (Line 119) checks FileType but not FileURL, yet the copy logic strips both. If a block only has FileURL, it will be left intact.

Proposed fix
-                    needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.File != nil && block.File.FileType != nil)
+                    needsBlockCopy := block.CacheControl != nil ||
+                        block.Citations != nil ||
+                        (block.File != nil && (block.File.FileType != nil || block.File.FileURL != nil))

295-374: Responses tool-output scrubbing bug: citations can leak due to inconsistent “needs copy” checks.
There are two issues:

  1. hasFieldsToStripInResponsesMessage ignores block.Citations inside tool output blocks, so needsCopy can stay false and skip the whole mutation pass.
  2. Even when mutation runs, needsBlockCopy in the tool-output loop (Line 354) ignores block.Citations, so a “citations-only” block won’t be copied and won’t get Citations=nil.
Proposed fix
 func hasFieldsToStripInResponsesMessage(msg schemas.ResponsesMessage) bool {
@@
 	if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Output != nil {
 		if msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
 			for _, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
 				if block.CacheControl != nil {
 					return true
 				}
+				if block.Citations != nil {
+					return true
+				}
 				if block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil {
 					return true
 				}
 			}
 		}
 	}
 	return false
 }
-                            needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+                            needsBlockCopy := block.CacheControl != nil ||
+                                block.Citations != nil ||
+                                (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
🤖 Fix all issues with AI agents
In `@core/providers/anthropic/responses.go`:
- Around line 3852-3855: Remove the debug fmt.Println calls left in the response
handling code: delete the printlns that output "tool", tool.Name, "tool type",
*tool.Type and the ones around bifrostTool (including the json.MarshalIndent +
fmt.Println of "bifrostTool"). Locate these prints in the function that
processes tools (references: variable tool, field tool.Name, tool.Type, and
variable bifrostTool) and either remove them entirely or replace them with a
proper logger.Debug/trace call if persistent debugging is required, ensuring any
json.MarshalIndent calls are only used for logging and errors are handled (do
not leave unused ignored errors).
- Around line 1782-1783: The code unconditionally sets params.Include =
[]string{"web_search_call.action.sources"} which is later overwritten by any
user-supplied ExtraParams.include; change the logic in the function where
params.Include is initialized (look for the params variable and the ExtraParams
merge near lines referencing ExtraParams) to merge/append the required
"web_search_call.action.sources" into an existing include slice instead of
replacing it: check if params.Include or ExtraParams["include"] exists,
normalize to a slice of strings, ensure "web_search_call.action.sources" is
present only once, and assign the merged slice back to params.Include so
user-provided includes are preserved while guaranteeing the required field is
included.
- Around line 3265-3276: The code only sets Sources when
msg.ResponsesToolMessage.Action.ResponsesWebSearchToolCallAction already exists,
so if Action or ResponsesWebSearchToolCallAction is nil the sources aren't
attached; modify the block handling the web_search_call to ensure
msg.ResponsesToolMessage.Action is initialized if nil and then ensure
msg.ResponsesToolMessage.Action.ResponsesWebSearchToolCallAction is created when
absent (create a new ResponsesWebSearchToolCallAction struct) before assigning
.Sources = sources, mirroring the grouped conversion pattern used around the
grouped conversion code paths.
♻️ Duplicate comments (9)
tests/integrations/python/tests/test_openai.py (2)

2864-2927: Address lint issues: unused has_citations variable and f-strings without placeholders.

The has_citations variable (Line 2866) is assigned but never used in assertions or logging. Additionally, Line 2926 uses an f-string without placeholders.

Proposed fix
-        has_citations = False
         search_status = None
         output_text = ""

Either remove has_citations entirely or use it in an assertion/log. Also fix the f-string:

-        print(f"✓ Web search (non-streaming) test passed!")
+        print("✓ Web search (non-streaming) test passed!")

2964-3043: Add time-based streaming timeout and fix f-strings without placeholders.

The streaming loop (Line 2964) relies solely on a chunk cap (Line 3017-3019), but if the iterator stalls, the test can hang indefinitely. Also, Lines 3036 and 3043 use f-strings without placeholders.

Proposed fix
+        import time
+        start = time.time()
         for chunk in stream:
             chunk_count += 1
             
             # ... existing chunk processing ...
             
-            # Safety check
-            if chunk_count > 5000:
+            # Safety checks (time-based + chunk cap)
+            if time.time() - start > 300:
+                raise TimeoutError("Streaming web_search exceeded 300s")
+            if chunk_count > 5000:
                 break

Fix f-strings:

-        print(f"✓ Streaming validation:")
+        print("✓ Streaming validation:")
-        print(f"✓ Web search (streaming) test passed!")
+        print("✓ Web search (streaming) test passed!")
tests/integrations/python/tests/test_anthropic.py (5)

1776-1839: Fix Ruff unused fixture arg + tighten PDF citation assertions (end_page_number >= start_page_number).
This matches prior review feedback on the same lines.


1840-1914: Fix Ruff unused fixture arg + assert text citations have non-empty cited_text.
This matches prior review feedback on the same lines.


1915-2014: Streaming citations test: simplify hasattr-heavy checks + fix unused fixture arg + strengthen expectations.
This matches prior review feedback on the same lines.


2015-2098: Web search (non-streaming): remove unused has_citations + fix unused fixture arg + remove unnecessary f-string.
This matches prior review feedback on the same lines.


2099-2240: Web search (streaming): delete dead input_json_delta try/except + fix Ruff warnings + fix “about weather” assertion message.
This matches prior review feedback on the same lines.

core/providers/anthropic/responses.go (2)

3720-3778: Ensure stable ID generation when msg.ID is nil.

The function uses msg.ID directly for both serverToolUseBlock.ID (line 3736) and webSearchResultBlock.ToolUseID (line 3768). If msg.ID is nil, the result block will have no linkage to the tool use block.

Per the past review comment, consider generating a stable ID when msg.ID is nil:

Proposed fix
 func convertBifrostWebSearchCallToAnthropicBlocks(msg *schemas.ResponsesMessage) []AnthropicContentBlock {
 	if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Action == nil || msg.ResponsesToolMessage.Action.ResponsesWebSearchToolCallAction == nil {
 		return nil
 	}

 	var blocks []AnthropicContentBlock
 	action := msg.ResponsesToolMessage.Action.ResponsesWebSearchToolCallAction

+	// Ensure we have an ID for linking blocks
+	blockID := msg.ID
+	if blockID == nil {
+		blockID = schemas.Ptr("ws_" + providerUtils.GetRandomString(24))
+	}
+
 	// 1. Create server_tool_use block for the web search
 	serverToolUseBlock := AnthropicContentBlock{
 		Type: AnthropicContentBlockTypeServerToolUse,
 		Name: schemas.Ptr("web_search"),
+		ID:   blockID,
 	}

-	if msg.ID != nil {
-		serverToolUseBlock.ID = msg.ID
-	}

4411-4424: Citation field mapping inconsistency for page_location and content_block_location.

For page_location (lines 4411-4416) and content_block_location (lines 4418-4423), the function sets StartCharIndex/EndCharIndex, but:

  • page_location should populate StartPageNumber/EndPageNumber
  • content_block_location should populate StartBlockIndex/EndBlockIndex

This matches the past review concern. The reverse conversion at lines 4475-4489 correctly uses the proper fields, creating an asymmetry.

Proposed fix
 	case AnthropicCitationTypePageLocation:
 		// Page location fields
-		annotation.StartCharIndex = citation.StartCharIndex
-		annotation.EndCharIndex = citation.EndCharIndex
+		annotation.StartPageNumber = citation.StartPageNumber
+		annotation.EndPageNumber = citation.EndPageNumber
 		annotation.Filename = citation.DocumentTitle
 		annotation.FileID = citation.FileID

 	case AnthropicCitationTypeContentBlockLocation:
 		// Content block location fields
-		annotation.StartCharIndex = citation.StartCharIndex
-		annotation.EndCharIndex = citation.EndCharIndex
+		annotation.StartBlockIndex = citation.StartBlockIndex
+		annotation.EndBlockIndex = citation.EndBlockIndex
 		annotation.Filename = citation.DocumentTitle
 		annotation.FileID = citation.FileID
🧹 Nitpick comments (4)
core/internal/testutil/web_search_tool.go (1)

148-162: Consider using GetTestRetryConfigForScenario() for consistency.

The retry config uses hardcoded values. Per established patterns in this directory, test files typically use GetTestRetryConfigForScenario() to obtain a generic retry config, then construct the typed config. If web search tests genuinely need different parameters, this deviation is acceptable.

Pattern from other test utilities
func WebSearchRetryConfig() ResponsesRetryConfig {
    genericConfig := GetTestRetryConfigForScenario("WebSearchTool")
    return ResponsesRetryConfig{
        MaxAttempts: genericConfig.MaxAttempts,
        BaseDelay:   genericConfig.BaseDelay,
        MaxDelay:    genericConfig.MaxDelay,
        Conditions: []ResponsesRetryCondition{
            &ResponsesEmptyCondition{},
            &ResponsesGenericResponseCondition{},
        },
        OnRetry: genericConfig.OnRetry,
    }
}

Based on learnings, using GetTestRetryConfigForScenario() ensures consistent retry behavior across test files.

core/schemas/responses.go (2)

451-472: Keep annotation “Anthropic specific fields” but consider validating/normalizing indexes where used.
Schema change is fine; ensure downstream converters/tests enforce invariants (e.g., char/page ranges) rather than relying on raw provider values.


669-688: Consider adding omitempty to new pointer fields on ResponsesWebSearchToolCallActionSearchSource.
Title, EncryptedContent, PageAge are pointers but tagged without omitempty, so they’ll serialize as null when unset (unlike most other optional fields in this file).

core/providers/anthropic/responses.go (1)

1152-1176: Verify index preference order for web search call encoding.

For web search calls (lines 1160-1164), ContentIndex is checked before OutputIndex, but for computer calls (lines 1135-1139) and other content blocks (lines 1181-1185), OutputIndex is checked first. This inconsistency may cause indexing issues when re-encoding streams.

Consider aligning the preference order:

Proposed fix
-			if bifrostResp.ContentIndex != nil {
-				streamResp.Index = bifrostResp.ContentIndex
-			} else if bifrostResp.OutputIndex != nil {
+			if bifrostResp.OutputIndex != nil {
 				streamResp.Index = bifrostResp.OutputIndex
+			} else if bifrostResp.ContentIndex != nil {
+				streamResp.Index = bifrostResp.ContentIndex
 			}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c08da7 and a8b9749.

📒 Files selected for processing (16)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/providers/openai/openai_test.go
  • core/internal/testutil/account.go
  • core/providers/openai/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
  • tests/integrations/python/config.yml
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • tests/integrations/python/tests/test_openai.py
  • core/internal/testutil/tests.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • tests/integrations/python/tests/test_anthropic.py
  • core/schemas/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/utils.go
🧠 Learnings (16)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/utils.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/utils.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/internal/testutil/tests.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/utils.go
📚 Learning: 2025-12-15T10:16:21.909Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/huggingface/huggingface_test.go:12-63
Timestamp: 2025-12-15T10:16:21.909Z
Learning: In provider tests under core/providers/<provider>/*_test.go, do not require or flag the use of defer for Shutdown(); instead call client.Shutdown() at the end of each test function. This pattern appears consistent across all provider tests. Apply this rule only within this path; for other tests or resources, defer may still be appropriate.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/test_anthropic.py
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
📚 Learning: 2026-01-12T06:41:13.643Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1153
File: core/schemas/utils.go:1159-1183
Timestamp: 2026-01-12T06:41:13.643Z
Learning: In core/schemas/utils.go, IsGrokReasoningModel (and the grokReasoningModels list) is the canonical utility for model matching. Treat it as the source of truth and not as a duplicate; if extending model support, update this file rather than introducing separate matching logic, and verify changes here to avoid diverging behavior.

Applied to files:

  • core/schemas/utils.go
🧬 Code graph analysis (8)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (720-723)
core/schemas/responses.go (5)
  • Citations (422-424)
  • ResponsesMessageContentBlock (404-420)
  • ResponsesInputMessageContentBlockFile (430-435)
  • ResponsesOutputMessageContentText (446-449)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
tests/integrations/python/tests/test_openai.py (1)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_with_vk_for_scenario (50-101)
  • format_provider_model (126-141)
core/providers/anthropic/utils.go (3)
core/schemas/responses.go (2)
  • Citations (422-424)
  • ResponsesInputMessageContentBlockFile (430-435)
core/providers/anthropic/types.go (3)
  • AnthropicCitations (297-302)
  • AnthropicContentBlock (219-239)
  • AnthropicSource (242-247)
core/schemas/chatcompletions.go (1)
  • CacheControl (720-723)
core/providers/anthropic/responses.go (3)
core/providers/anthropic/types.go (8)
  • AnthropicContentBlock (219-239)
  • AnthropicContentBlockType (201-201)
  • AnthropicToolNameWebSearch (364-364)
  • AnthropicContentBlockTypeWebSearchToolResult (210-210)
  • AnthropicContentBlockTypeWebSearchResult (211-211)
  • AnthropicStreamEvent (499-508)
  • AnthropicContent (158-161)
  • AnthropicCitations (297-302)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/responses.go (10)
  • ResponsesMessage (319-332)
  • ResponsesMessageTypeWebSearchCall (299-299)
  • ResponsesToolMessage (485-505)
  • ResponsesToolMessageActionStruct (507-512)
  • ResponsesWebSearchToolCallAction (670-677)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1460-1460)
  • ResponsesComputerToolCallAction (628-639)
  • ResponsesComputerToolCall (616-618)
  • ResponsesStreamResponseTypeOutputItemDone (1412-1412)
  • Citations (422-424)
tests/integrations/python/tests/test_anthropic.py (2)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_for_scenario (12-47)
  • format_provider_model (126-141)
tests/integrations/python/tests/utils/common.py (1)
  • assert_valid_chat_response (815-847)
core/schemas/responses.go (2)
core/schemas/json_wasm.go (2)
  • Marshal (8-10)
  • Unmarshal (22-24)
core/schemas/json_native.go (2)
  • Marshal (8-10)
  • Unmarshal (18-20)
core/internal/testutil/web_search_tool.go (4)
core/schemas/responses.go (7)
  • ResponsesMessage (319-332)
  • ResponsesTool (1063-1081)
  • ResponsesToolWebSearch (1236-1243)
  • ResponsesToolWebSearchUserLocation (1252-1258)
  • ResponsesMessageTypeWebSearchCall (299-299)
  • ResponsesWebSearchToolCallAction (670-677)
  • ResponsesMessageTypeMessage (295-295)
core/internal/testutil/utils.go (1)
  • CreateBasicResponsesMessage (276-284)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/internal/testutil/test_retry_framework.go (4)
  • TestRetryContext (174-179)
  • WithResponsesTestRetry (443-593)
  • ResponsesRetryConfig (212-219)
  • ResponsesRetryCondition (138-141)
core/schemas/utils.go (1)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessageAnnotationCitation (816-823)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_openai.py

2898-2898: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2926-2926: f-string without any placeholders

Remove extraneous f prefix

(F541)


3036-3036: f-string without any placeholders

Remove extraneous f prefix

(F541)


3043-3043: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/test_anthropic.py

1777-1777: Unused method argument: test_config

(ARG002)


1841-1841: Unused method argument: test_config

(ARG002)


1916-1916: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2079-2079: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2097-2097: f-string without any placeholders

Remove extraneous f prefix

(F541)


2100-2100: Unused method argument: test_config

(ARG002)


2138-2138: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2194-2194: Do not use bare except

(E722)


2194-2195: try-except-pass detected, consider logging the exception

(S110)


2204-2204: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2226-2226: f-string without any placeholders

Remove extraneous f prefix

(F541)


2235-2235: f-string without any placeholders

Remove extraneous f prefix

(F541)


2239-2239: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (24)
core/schemas/utils.go (1)

640-661: Field rename from Citation to URLCitation looks correct.

The deep copy logic correctly mirrors the schema change in chatcompletions.go, where the ChatAssistantMessageAnnotation field was renamed from Citation to URLCitation. All field accesses (StartIndex, EndIndex, Title, URL, Sources, Type) are consistently updated.

core/internal/testutil/web_search_tool.go (1)

15-146: Well-structured web search test implementation.

The test correctly:

  • Guards on testConfig.Scenarios.WebSearchTool
  • Constructs proper ResponsesTool with web search configuration
  • Validates both web_search_call presence and text response content
  • Includes comprehensive logging for debugging

The log message fix at lines 129-133 appropriately distinguishes between responses with and without citations.

core/internal/testutil/tests.go (2)

36-36: Web search test correctly added to the comprehensive test suite.

The RunWebSearchToolTest function is properly registered in the test scenarios list.


109-109: Summary entry correctly added for WebSearchTool.

The summary reporting now includes the WebSearchTool scenario, ensuring test status is properly reported.

core/schemas/responses.go (1)

404-424: Citations schema addition looks consistent with existing “provider-specific optional fields” pattern.
Adding Citations *Citations on ResponsesMessageContentBlock aligns with how CacheControl is modeled.

core/providers/anthropic/utils.go (2)

152-163: Citations config propagation into Anthropic document blocks is correct + minimal.
This cleanly maps schemas.CitationsAnthropicCitations{Config: ...} for request formatting.


229-304: All callsites have been properly updated. The single invocation at core/providers/anthropic/responses.go:4229 passes all three required parameters (block.ResponsesInputMessageContentBlockFile, block.CacheControl, block.Citations) in the correct order, matching the function signature.

Likely an incorrect or invalid review comment.

core/providers/anthropic/anthropic_test.go (1)

25-70: Enabling WebSearchTool in Anthropic comprehensive tests makes sense for this stack.
Please ensure testutil.RunAllComprehensiveTests skips (not fails) when the backing integration/provider doesn’t have web search enabled in the current environment.

core/providers/openai/types.go (1)

432-459: Annotation filtering helper is reasonable; please verify supported types against OpenAI Responses API.
Given this is guarding an external API surface, double-check the allowed set and required fields for each annotation type.

core/providers/anthropic/responses.go (15)

18-47: LGTM on state structure enhancements.

The new web search tracking fields (WebSearchToolID, WebSearchOutputIndex, WebSearchResult, ContentIndexToBlockType) are well-documented and properly typed for tracking web search lifecycle during streaming.


82-86: LGTM on pool initialization and cleanup.

The ContentIndexToBlockType map and web search state fields are properly initialized in acquireAnthropicResponsesStreamState and cleaned up in flush, following the established pattern for other state maps.

Also applies to: 121-123, 147-151


273-364: Web search streaming lifecycle implementation looks correct.

The handling properly:

  1. Emits output_item.added, web_search_call.in_progress, and web_search_call.searching for server_tool_use blocks
  2. Tracks result blocks in ContentIndexToBlockType and cleans up on match (line 350)
  3. Emits web_search_call.completed when result matches active search

The early deletion at line 350 is correct since we've matched and are emitting the completed event.


668-688: Citation delta streaming conversion looks correct.

The handling converts Anthropic citations to OpenAI annotations during streaming. Passing an empty string for fullText is appropriate since we don't accumulate the full text during streaming - the comment at line 671 clarifies this intentional limitation.


697-849: Content block stop handling for web search is well-structured.

The implementation correctly:

  1. Distinguishes between query block completion (line 751-756) and result block completion
  2. Extracts query from accumulated JSON and sources from result content blocks
  3. Cleans up state (WebSearchToolID, WebSearchOutputIndex, WebSearchResult, ContentIndexToBlockType)
  4. Skips generic output_item.done for result blocks that were already handled

1309-1322: LGTM on web search query extraction for synthetic deltas.

The extraction of the query from ResponsesWebSearchToolCallAction and marshaling to JSON follows the same pattern as other tool types.


1436-1452: LGTM on annotation to citation re-encoding.

The conversion back to Anthropic citation format follows the correct index preference order (OutputIndex first, then ContentIndex) and uses the bidirectional conversion helper.


1550-1640: Web search call completion re-encoding is comprehensive.

The implementation correctly:

  1. Emits content_block_stop for the query block
  2. Creates web_search_tool_result with sources when present
  3. Calculates the next index for the result block
  4. Skips OpenAI-style lifecycle events that have no Anthropic equivalent

1921-1947: Citations vs structured outputs handling correctly addressed.

The implementation now:

  1. Has proper nil guards for message.Content, ContentBlocks, block.Citations, and Citations.Enabled
  2. Only sets OutputFormat when citations are NOT enabled (line 1944), fixing the previously inverted logic
  3. Comment at line 1922 correctly states the constraint

2560-2593: Web search call message conversion follows established patterns.

The handling correctly:

  1. Flushes pending tool results before processing
  2. Prepends reasoning blocks to maintain Anthropic's ordering requirements
  3. Uses the dedicated helper for block conversion
  4. Tracks the server_tool_use block's ID for tool result matching

2891-2956: Web search result handling in grouped conversion correctly attaches sources.

The implementation now searches backwards through bifrostMessages to find the matching web_search_call by CallID and properly attaches sources. This addresses the previous concern about sources not being attached.


2998-3012: LGTM on web search tool use block conversion.

The handling correctly identifies AnthropicToolNameWebSearch and extracts the query to create a ResponsesWebSearchToolCallAction.


4196-4211: LGTM on content block citation propagation.

The conversion correctly attaches OpenAI annotations as Anthropic citations to text content blocks, using the bidirectional helper.


3885-3890: LGTM on web search filter conversion.

Both conversion directions properly handle MaxUses and BlockedDomains for web search tool configuration.

Also applies to: 4073-4078


4282-4285: LGTM on document block citation config extraction.

The nil-safe check properly extracts the citations configuration from the Anthropic block.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
core/schemas/utils.go (1)

636-660: Deep-copy URLCitation.Sources; current copy can still share underlying maps/slices.
copySources := *annotation.URLCitation.Sources only copies the interface header; it doesn’t deep copy the underlying structure.

Proposed fix
 				if annotation.URLCitation.Sources != nil {
-					copySources := *annotation.URLCitation.Sources
-					copyAnnotation.URLCitation.Sources = &copySources
+					// Deep copy to avoid sharing underlying maps/slices via interface{}.
+					deep := DeepCopy(*annotation.URLCitation.Sources)
+					copyAnnotation.URLCitation.Sources = Ptr(deep)
 				}
core/providers/openai/types.go (1)

343-358: Inconsistent Citations check in tool output block processing.

Line 343 correctly checks for block.Citations != nil when determining if tool output blocks need modification, but line 354 inside the loop omits this check. This means blocks with only Citations set (no CacheControl or FileType) won't be copied and stripped.

🔧 Suggested fix
 						for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
-							needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+							needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
 							if needsBlockCopy {
core/schemas/responses.go (2)

514-555: Fix duplicate code and ambiguous tool-action JSON unmarshalling (risk of action misclassification).

MarshalJSON has a duplicated check for ResponsesComputerToolCallAction (lines 5–6 and 8–9). UnmarshalJSON attempts to unmarshal web_search first; since optional fields are present across all action variants, a computer, shell, or MCP action payload could unmarshal "successfully" into ResponsesWebSearchToolCallAction before attempting the correct type, causing incorrect decoding.

The Type field exists as a required string in all four action types and contains distinct values ("search", "open_page", "find" for web search; "click", "double_click", etc. for computer; "exec" for shell; "mcp_approval_request" for MCP). Use this field to disambiguate unmarshalling, as the codebase already does for similar union types.

Proposed fix
 func (action ResponsesToolMessageActionStruct) MarshalJSON() ([]byte, error) {
+	// Validation: at most one action variant should be set
+	set := 0
+	if action.ResponsesComputerToolCallAction != nil { set++ }
+	if action.ResponsesWebSearchToolCallAction != nil { set++ }
+	if action.ResponsesLocalShellToolCallAction != nil { set++ }
+	if action.ResponsesMCPApprovalRequestAction != nil { set++ }
+	if set > 1 {
+		return nil, fmt.Errorf("multiple tool actions set; exactly one must be non-nil")
+	}
+
 	if action.ResponsesWebSearchToolCallAction != nil {
 		return Marshal(action.ResponsesWebSearchToolCallAction)
 	}
 	if action.ResponsesComputerToolCallAction != nil {
 		return Marshal(action.ResponsesComputerToolCallAction)
 	}
-	if action.ResponsesComputerToolCallAction != nil {
-		return Marshal(action.ResponsesComputerToolCallAction)
-	}
 	if action.ResponsesLocalShellToolCallAction != nil {
 		return Marshal(action.ResponsesLocalShellToolCallAction)
 	}
 	if action.ResponsesMCPApprovalRequestAction != nil {
 		return Marshal(action.ResponsesMCPApprovalRequestAction)
 	}
 	return nil, fmt.Errorf("responses tool message action struct is neither a computer tool call action nor a web search tool call action nor a local shell tool call action nor a mcp approval request action")
 }
 
 func (action *ResponsesToolMessageActionStruct) UnmarshalJSON(data []byte) error {
-	var webSearchToolCallAction ResponsesWebSearchToolCallAction
-	if err := Unmarshal(data, &webSearchToolCallAction); err == nil {
-		action.ResponsesWebSearchToolCallAction = &webSearchToolCallAction
-		return nil
-	}
-	var computerToolCallAction ResponsesComputerToolCallAction
-	if err := Unmarshal(data, &computerToolCallAction); err == nil {
-		action.ResponsesComputerToolCallAction = &computerToolCallAction
-		return nil
-	}
+	// Disambiguate by the "type" field first.
+	var probe struct {
+		Type string `json:"type"`
+	}
+	if err := Unmarshal(data, &probe); err != nil {
+		return err
+	}
+	switch probe.Type {
+	case "search", "open_page", "find":
+		var v ResponsesWebSearchToolCallAction
+		if err := Unmarshal(data, &v); err != nil { return err }
+		action.ResponsesWebSearchToolCallAction = &v
+		return nil
+	case "click", "double_click", "drag", "keypress", "move", "screenshot", "scroll", "type", "wait", "zoom":
+		var v ResponsesComputerToolCallAction
+		if err := Unmarshal(data, &v); err != nil { return err }
+		action.ResponsesComputerToolCallAction = &v
+		return nil
+	case "exec":
+		var v ResponsesLocalShellToolCallAction
+		if err := Unmarshal(data, &v); err != nil { return err }
+		action.ResponsesLocalShellToolCallAction = &v
+		return nil
+	case "mcp_approval_request":
+		var v ResponsesMCPApprovalRequestAction
+		if err := Unmarshal(data, &v); err != nil { return err }
+		action.ResponsesMCPApprovalRequestAction = &v
+		return nil
+	default:
+		return fmt.Errorf("unknown tool action type: %q", probe.Type)
+	}
-	var localShellToolCallAction ResponsesLocalShellToolCallAction
-	if err := Unmarshal(data, &localShellToolCallAction); err == nil {
-		action.ResponsesLocalShellToolCallAction = &localShellToolCallAction
-		return nil
-	}
-	var mcpApprovalRequestAction ResponsesMCPApprovalRequestAction
-	if err := Unmarshal(data, &mcpApprovalRequestAction); err == nil {
-		action.ResponsesMCPApprovalRequestAction = &mcpApprovalRequestAction
-		return nil
-	}
-	return fmt.Errorf("responses tool message action struct is neither a computer tool call action nor a web search tool call action nor a local shell tool call action nor a mcp approval request action")
 }

669-688: Add omitempty to optional Anthropic web-search source fields.

The Title, EncryptedContent, and PageAge fields are pointers that can be nil, but their JSON tags lack omitempty. This causes them to serialize as null when unset instead of being omitted entirely. This is inconsistent with other optional Anthropic-specific fields in the same file (e.g., StartCharIndex, EndCharIndex at line 464-467, which all use omitempty). Update the tags to omit these fields when absent, which is safer for downstream consumers and follows Go conventions.

Proposed fix
 type ResponsesWebSearchToolCallActionSearchSource struct {
 	Type string `json:"type"` // always "url"
 	URL  string `json:"url"`
 
 	// Anthropic specific fields
-	Title            *string `json:"title"`
-	EncryptedContent *string `json:"encrypted_content"`
-	PageAge          *string `json:"page_age"`
+	Title            *string `json:"title,omitempty"`
+	EncryptedContent *string `json:"encrypted_content,omitempty"`
+	PageAge          *string `json:"page_age,omitempty"`
 }
🤖 Fix all issues with AI agents
In `@core/providers/anthropic/responses.go`:
- Around line 273-364: The two appended web-search responses use hard-coded
sequenceNumber+1/+2; change them to sequenceNumber+len(responses) so each new
response uses sequenceNumber + current responses length (use the local responses
slice built before appending) to match the project pattern; also when handling
AnthropicContentBlockTypeWebSearchToolResult and the ToolUseID does not match
state.WebSearchToolID, emit a warning/error via the existing logger (e.g.,
processLogger or the module logger) including the chunk.ContentBlock.ToolUseID,
state.WebSearchToolID and chunk.Index to make ordering/tool-ID mismatches
visible instead of silently returning nil.
- Around line 668-688: Streaming citations currently call
convertAnthropicCitationToAnnotation with an empty fullText, so URL position
indices aren't computed; fix by accumulating streaming text deltas per content
index (like the existing TextContentIndices map) and use that accumulated string
when handling AnthropicStreamDeltaTypeCitations: update the stream state to
maintain a map[int]string keyed by chunk.Index, append chunk.Delta.Text pieces
as they arrive, and when chunk.Delta.Citation != nil pass the accumulated text
for that content index into convertAnthropicCitationToAnnotation (instead of "")
so start_index and end_index are computed the same as the non-streaming path;
ensure you create/initialize the map in the same state struct used by other
stream handlers and keep itemID/response construction unchanged.

In `@tests/integrations/python/tests/test_openai.py`:
- Around line 2864-2900: The test sets has_citations but never uses it and also
uses an f-string with no placeholders; update the code to either assert/log the
citations presence or remove the unused variable and correct the print call: if
you want to validate citations, replace the assignment to has_citations when
content_block.annotations exists and add an assertion (e.g., assert
has_citations or assert len(content_block.annotations) > 0) or emit a log using
has_citations, and change the stray f-string print to a normal string print (or
include a placeholder like print(f"Found {citation_count} citations") if you
want dynamic output); refer to the variables has_citations,
content_block.annotations, citation_count, and the print(...) calls around the
message/output handling to locate and fix the code.
♻️ Duplicate comments (13)
tests/integrations/python/tests/test_anthropic.py (5)

1776-1839: Fix Ruff ARG002 and strengthen PDF citation assertions.
Rename unused test_config to _test_config, and add end_page_number >= start_page_number (and ideally non-empty cited_text).


1840-1914: Fix Ruff ARG002 and strengthen text citation assertions.
Rename unused test_config to _test_config, and assert citation.cited_text is non-empty.


1915-2014: Fix Ruff ARG002 + simplify streaming citation validation.
Rename unused test_config to _test_config; also consider dropping over-defensive hasattr checks and asserting at least one document_index == 0 to match non-streaming expectations.


2015-2098: Fix Ruff (ARG002/F841/F541) in web_search non-streaming test.
Rename test_config to _test_config, remove or assert has_citations (currently unused), and drop the unnecessary f prefix in the final print.


2099-2240: Fix Ruff (ARG002/F841/E722/S110/F541) + correct “weather” assertion message to “news”.
Remove dead input_json_delta try/except, remove unused search_queries / has_citation_delta, convert placeholder-less f-strings to plain strings, and update complete_text assertion message to match the prompt (“news”, not “weather”).

tests/integrations/python/tests/test_openai.py (1)

3036-3043: Remove extraneous f-string prefixes.

Lines 3036 and 3043 use f-strings without any placeholders.

🔧 Suggested fix
-        print(f"✓ Streaming validation:")
+        print("✓ Streaming validation:")
         print(f"  - Chunks received: {chunk_count}")
         print(f"  - Search queries: {len(search_queries)}")
         print(f"  - Citations: {len(citations)}")
         print(f"  - Text length: {len(complete_text)} characters")
         print(f"  - First 150 chars: {complete_text[:150]}...")
         
-        print(f"✓ Web search (streaming) test passed!")
+        print("✓ Web search (streaming) test passed!")
core/providers/anthropic/responses.go (7)

1782-1830: Default include gets overwritten by user ExtraParams.include (merge instead).
You set params.Include = ["web_search_call.action.sources"] but overwrite it if ExtraParams["include"] exists, dropping the required include.

Proposed fix (merge + de-dupe)
-	params.Include = []string{"web_search_call.action.sources"}
+	params.Include = []string{"web_search_call.action.sources"}
@@
 	if include, ok := schemas.SafeExtractStringSlice(request.ExtraParams["include"]); ok {
-		params.Include = include
+		seen := map[string]bool{}
+		merged := make([]string, 0, 1+len(include))
+		for _, v := range params.Include {
+			if v == "" || seen[v] { continue }
+			seen[v] = true
+			merged = append(merged, v)
+		}
+		for _, v := range include {
+			if v == "" || seen[v] { continue }
+			seen[v] = true
+			merged = append(merged, v)
+		}
+		params.Include = merged
 	}

3852-3903: Remove debug fmt.Println + ignored marshal error before merge.
These will spam logs and hide marshal failures.

Proposed fix
-	fmt.Println("tool", tool.Name)
 	// Handle special tool types first
 	if tool.Type != nil {
-		fmt.Println("tool type", *tool.Type)
 		switch *tool.Type {
@@
-			bfToolJSON, _ := json.MarshalIndent(bifrostTool, "", "  ")
-			fmt.Println("bifrostTool", string(bfToolJSON))
 			return bifrostTool

759-837: Web-search completion can misfire on the wrong content_block_stop index.
Current condition if state.WebSearchResult != nil && state.WebSearchToolID != nil doesn’t verify that the stopping block is the web_search_tool_result block. This can emit a completed web_search_call on an unrelated stop event.

Proposed fix (tie completion to the stopping index via ContentIndexToBlockType)
-			// Check if this is the end of a web_search_tool_result block
-			if state.WebSearchResult != nil && state.WebSearchToolID != nil {
+			// Check if this is the end of a web_search_tool_result block (by index)
+			if state.WebSearchResult != nil && state.WebSearchToolID != nil &&
+				chunk.Index != nil &&
+				state.ContentIndexToBlockType != nil &&
+				state.ContentIndexToBlockType[*chunk.Index] == AnthropicContentBlockTypeWebSearchToolResult {
@@
-				// Clear all web search state
+				// Clear tracking + all web search state
+				delete(state.ContentIndexToBlockType, *chunk.Index)
 				state.WebSearchToolID = nil
 				state.WebSearchOutputIndex = nil
 				state.WebSearchResult = nil
 				state.AccumulatedJSON = ""
-
-				if chunk.Index != nil {
-					delete(state.ContentIndexToBlockType, *chunk.Index)
-				}

Also: avoid deleting the map entry early at Lines 349-351 (keep it until stop), otherwise this index-based check won’t work.

Anthropic web_search_tool_result lifecycle: is completion expected on content_block_stop of the result block specifically?

2909-2956: Grouped conversion likely can’t attach web-search sources (searches emitted messages, but tool-use is pending).
In convertAnthropicContentBlocksToResponsesMessagesGrouped, server_tool_use is stored in pendingToolUseBlocks and only emitted later; web_search_result currently scans bifrostMessages for an already-emitted web_search_call, so Sources can remain unattached.

In Anthropic non-stream responses, can web_search_result blocks appear before server_tool_use is emitted/processed, and should we attach sources to the pending tool-use entry instead?

3241-3276: Ungrouped conversion: sources only attach if action already exists (create it).
At Line 3269, you skip attaching Sources when ResponsesWebSearchToolCallAction is nil; but earlier you sometimes construct web_search_call without an action.


3720-3778: Ensure ToolUseID is never nil for web_search_tool_result blocks.
ToolUseID: msg.ID can be nil; this creates an un-linkable result block. Generate/reuse an ID and use it consistently.

Proposed fix
 func convertBifrostWebSearchCallToAnthropicBlocks(msg *schemas.ResponsesMessage) []AnthropicContentBlock {
@@
-	// 1. Create server_tool_use block for the web search
+	// Ensure stable tool-use id
+	toolUseID := msg.ID
+	if toolUseID == nil {
+		toolUseID = schemas.Ptr("ws_" + providerUtils.GetRandomString(24))
+		// optional: also reflect back on msg.ID if callers rely on it
+		// msg.ID = toolUseID
+	}
+
+	// 1. Create server_tool_use block for the web search
 	serverToolUseBlock := AnthropicContentBlock{
 		Type: AnthropicContentBlockTypeServerToolUse,
 		Name: schemas.Ptr("web_search"),
 	}
 
-	if msg.ID != nil {
-		serverToolUseBlock.ID = msg.ID
-	}
+	serverToolUseBlock.ID = toolUseID
@@
 			webSearchResultBlock := AnthropicContentBlock{
 				Type:      AnthropicContentBlockTypeWebSearchToolResult,
-				ToolUseID: msg.ID,
+				ToolUseID: toolUseID,
 				Content: &AnthropicContent{
 					ContentBlocks: resultBlocks,
 				},
 			}

4393-4451: Fix citation→annotation field mapping for page/content-block locations.
page_location and content_block_location currently populate StartCharIndex/EndCharIndex, but the schema has dedicated StartPageNumber/EndPageNumber and StartBlockIndex/EndBlockIndex.

Proposed fix
 switch citation.Type {
 case AnthropicCitationTypeCharLocation:
 	annotation.StartCharIndex = citation.StartCharIndex
 	annotation.EndCharIndex = citation.EndCharIndex
 	annotation.Filename = citation.DocumentTitle
 	annotation.FileID = citation.FileID
 
 case AnthropicCitationTypePageLocation:
-	annotation.StartCharIndex = citation.StartCharIndex
-	annotation.EndCharIndex = citation.EndCharIndex
+	annotation.StartPageNumber = citation.StartPageNumber
+	annotation.EndPageNumber = citation.EndPageNumber
 	annotation.Filename = citation.DocumentTitle
 	annotation.FileID = citation.FileID
 
 case AnthropicCitationTypeContentBlockLocation:
-	annotation.StartCharIndex = citation.StartCharIndex
-	annotation.EndCharIndex = citation.EndCharIndex
+	annotation.StartBlockIndex = citation.StartBlockIndex
+	annotation.EndBlockIndex = citation.EndBlockIndex
 	annotation.Filename = citation.DocumentTitle
 	annotation.FileID = citation.FileID
Anthropic citation types: confirm the exact field sets for char_location vs page_location vs content_block_location (and whether page/content_block ever include char indices too).
🧹 Nitpick comments (5)
tests/integrations/python/config.yml (1)

176-473: Looks consistent; consider unifying key style for web_search and confirm capability mapping.
OpenAI uses "web_search": true while Anthropic uses web_search: true—both work, but pick one style. Also confirm scenario_capabilities.web_search: "chat" matches how the runner chooses models for web-search scenarios.

core/internal/testutil/web_search_tool.go (1)

148-162: Consider aligning with GetTestRetryConfigForScenario() pattern.

Based on learnings, test retry configs in this directory should use GetTestRetryConfigForScenario() to obtain a generic config, then construct a typed config with an empty Conditions slice, copying only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail. The current implementation hardcodes values and includes custom conditions.

If this deviation is intentional for web search-specific behavior, consider adding a comment explaining why. Otherwise, consider refactoring to use the standard pattern.

♻️ Example refactor using standard pattern
func WebSearchRetryConfig() ResponsesRetryConfig {
	genericConfig := GetTestRetryConfigForScenario("WebSearchTool")
	return ResponsesRetryConfig{
		MaxAttempts: genericConfig.MaxAttempts,
		BaseDelay:   genericConfig.BaseDelay,
		MaxDelay:    genericConfig.MaxDelay,
		Conditions:  []ResponsesRetryCondition{}, // Empty per convention
		OnRetry:     genericConfig.OnRetry,
		OnFinalFail: genericConfig.OnFinalFail,
	}
}
core/providers/anthropic/responses.go (1)

18-165: Pooling regression: flush() re-allocates all maps (defeats sync.Pool).
flush() uses make(map...) for every map; prefer clear(...) (like acquire... does) to reduce GC churn during streaming.

Proposed fix
 func (state *AnthropicResponsesStreamState) flush() {
@@
-	state.ContentIndexToOutputIndex = make(map[int]int)
-	state.ContentIndexToBlockType = make(map[int]AnthropicContentBlockType)
-	state.ToolArgumentBuffers = make(map[int]string)
-	state.MCPCallOutputIndices = make(map[int]bool)
-	state.ItemIDs = make(map[int]string)
-	state.ReasoningSignatures = make(map[int]string)
-	state.TextContentIndices = make(map[int]bool)
-	state.ReasoningContentIndices = make(map[int]bool)
+	if state.ContentIndexToOutputIndex != nil { clear(state.ContentIndexToOutputIndex) } else { state.ContentIndexToOutputIndex = make(map[int]int) }
+	if state.ContentIndexToBlockType != nil { clear(state.ContentIndexToBlockType) } else { state.ContentIndexToBlockType = make(map[int]AnthropicContentBlockType) }
+	if state.ToolArgumentBuffers != nil { clear(state.ToolArgumentBuffers) } else { state.ToolArgumentBuffers = make(map[int]string) }
+	if state.MCPCallOutputIndices != nil { clear(state.MCPCallOutputIndices) } else { state.MCPCallOutputIndices = make(map[int]bool) }
+	if state.ItemIDs != nil { clear(state.ItemIDs) } else { state.ItemIDs = make(map[int]string) }
+	if state.ReasoningSignatures != nil { clear(state.ReasoningSignatures) } else { state.ReasoningSignatures = make(map[int]string) }
+	if state.TextContentIndices != nil { clear(state.TextContentIndices) } else { state.TextContentIndices = make(map[int]bool) }
+	if state.ReasoningContentIndices != nil { clear(state.ReasoningContentIndices) } else { state.ReasoningContentIndices = make(map[int]bool) }
 	state.CurrentOutputIndex = 0
@@
 }
core/schemas/responses.go (2)

1235-1249: Add omitempty to AllowedDomains since the field is optional in both OpenAI and Anthropic APIs.

Both OpenAI and Anthropic APIs specify that allowed_domains is optional. When omitted, all domains are allowed (OpenAI) or no restriction is applied. Adding omitempty will exclude the field from JSON when empty, providing cleaner serialization and matching the pattern already used in the Anthropic provider types.

Suggested change
 type ResponsesToolWebSearchFilters struct {
-	AllowedDomains []string `json:"allowed_domains"`           // Allowed domains for the search
+	AllowedDomains []string `json:"allowed_domains,omitempty"` // Allowed domains for the search
 	BlockedDomains []string `json:"blocked_domains,omitempty"` // Blocked domains for the search, only used in anthropic
 }

404-424: Add documentation clarifying Citations config semantics and Anthropic requirements.

The Citations config field in ResponsesMessageContentBlock needs clearer documentation. Per Anthropic's API spec, an explicit citations.enabled boolean is required (you cannot rely on presence alone), and citations must be enabled uniformly across all documents in a request—they cannot be mixed. Since your Enabled field is *bool (allowing nil), add a comment clarifying:

  • What nil/null means for this field (should it default to false, be rejected, or treated as unset?)
  • That per Anthropic's requirements, an explicit true/false value may be needed, not an omitted/nil state
  • That this Citations object is request-side config, distinguishing it from any Citations []string response fields from other providers
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8b9749 and 7ee61d0.

📒 Files selected for processing (16)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/providers/openai/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/anthropic_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/schemas/utils.go
  • core/providers/anthropic/utils.go
  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/test_anthropic.py
  • core/internal/testutil/web_search_tool.go
  • core/schemas/responses.go
  • tests/integrations/python/config.yml
  • core/providers/anthropic/responses.go
🧠 Learnings (18)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/schemas/utils.go
  • core/providers/anthropic/utils.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/schemas/utils.go
  • core/providers/anthropic/utils.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/schemas/utils.go
  • core/providers/anthropic/utils.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T13:30:28.760Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: plugins/semanticcache/test_utils.go:545-559
Timestamp: 2026-01-14T13:30:28.760Z
Learning: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/schemas/utils.go
  • core/providers/anthropic/utils.go
  • core/internal/testutil/web_search_tool.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-15T10:16:21.909Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/huggingface/huggingface_test.go:12-63
Timestamp: 2025-12-15T10:16:21.909Z
Learning: In provider tests under core/providers/<provider>/*_test.go, do not require or flag the use of defer for Shutdown(); instead call client.Shutdown() at the end of each test function. This pattern appears consistent across all provider tests. Apply this rule only within this path; for other tests or resources, defer may still be appropriate.

Applied to files:

  • core/providers/openai/openai_test.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/openai/openai_test.go
  • core/providers/openai/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-12T06:41:13.643Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1153
File: core/schemas/utils.go:1159-1183
Timestamp: 2026-01-12T06:41:13.643Z
Learning: In core/schemas/utils.go, IsGrokReasoningModel (and the grokReasoningModels list) is the canonical utility for model matching. Treat it as the source of truth and not as a duplicate; if extending model support, update this file rather than introducing separate matching logic, and verify changes here to avoid diverging behavior.

Applied to files:

  • core/schemas/utils.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/test_anthropic.py
📚 Learning: 2026-01-14T04:32:14.023Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/config.yml:170-171
Timestamp: 2026-01-14T04:32:14.023Z
Learning: Guideline: In HuggingFace provider configuration, follow the provider-prefixed model path format fal-ai/<namespace>/<model> where the first segment fal-ai/ is the provider routing prefix and the remainder is the actual model path on fal.ai. Do not double-include the provider prefix. Example: fal-ai/flux/dev is the actual model path; when combined with the provider prefix you get fal-ai/fal-ai/flux/dev, which correctly indicates provider + model path.

Applied to files:

  • tests/integrations/python/config.yml
🧬 Code graph analysis (7)
core/internal/testutil/tests.go (1)
core/internal/testutil/web_search_tool.go (1)
  • RunWebSearchToolTest (15-146)
core/schemas/utils.go (2)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessageAnnotationCitation (816-823)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/providers/anthropic/utils.go (1)
core/providers/anthropic/types.go (3)
  • AnthropicCitations (297-302)
  • AnthropicContentBlock (219-239)
  • AnthropicSource (242-247)
tests/integrations/python/tests/test_openai.py (1)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_with_vk_for_scenario (50-101)
  • format_provider_model (126-141)
tests/integrations/python/tests/test_anthropic.py (2)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_for_scenario (12-47)
  • format_provider_model (126-141)
tests/integrations/python/tests/utils/common.py (1)
  • assert_valid_chat_response (815-847)
core/internal/testutil/web_search_tool.go (4)
core/internal/testutil/utils.go (1)
  • CreateBasicResponsesMessage (276-284)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/internal/testutil/test_retry_framework.go (4)
  • TestRetryContext (174-179)
  • WithResponsesTestRetry (443-593)
  • ResponsesRetryConfig (212-219)
  • ResponsesRetryCondition (138-141)
core/internal/testutil/response_validation.go (1)
  • ResponseExpectations (18-43)
core/schemas/responses.go (2)
core/schemas/json_native.go (2)
  • Marshal (8-10)
  • Unmarshal (18-20)
core/schemas/json_wasm.go (2)
  • Marshal (8-10)
  • Unmarshal (22-24)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_openai.py

2898-2898: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2926-2926: f-string without any placeholders

Remove extraneous f prefix

(F541)


3036-3036: f-string without any placeholders

Remove extraneous f prefix

(F541)


3043-3043: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/test_anthropic.py

1777-1777: Unused method argument: test_config

(ARG002)


1841-1841: Unused method argument: test_config

(ARG002)


1916-1916: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2079-2079: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2097-2097: f-string without any placeholders

Remove extraneous f prefix

(F541)


2100-2100: Unused method argument: test_config

(ARG002)


2138-2138: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2194-2194: Do not use bare except

(E722)


2194-2195: try-except-pass detected, consider logging the exception

(S110)


2204-2204: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2226-2226: f-string without any placeholders

Remove extraneous f prefix

(F541)


2235-2235: f-string without any placeholders

Remove extraneous f prefix

(F541)


2239-2239: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (13)
core/internal/testutil/account.go (1)

21-62: Good additive scenario flag; ensure the harness actually consumes it.
This is a safe extension (defaults to false in existing literals). Just verify the stack’s test runner/orchestrator wires WebSearchTool into scenario selection/output so the flag isn’t silently ignored.

core/providers/openai/openai_test.go (1)

42-81: Good: enables WebSearchTool coverage for OpenAI.
Just confirm CI accounts have access to web-search/tooling so this doesn’t become a flaky failure (otherwise gate via an env toggle).

core/internal/testutil/tests.go (1)

36-36: LGTM!

The RunWebSearchToolTest is correctly added to the test scenarios slice, and the corresponding entry is properly included in printTestSummary at line 109. The integration follows the existing pattern for test scenario registration.

core/providers/openai/types.go (2)

432-459: LGTM!

The filterSupportedAnnotations function correctly filters to OpenAI-native citation types and strips non-OpenAI fields (like encrypted_index) from url_citation annotations. The implementation is efficient with pre-allocation and handles edge cases properly.


388-414: LGTM!

The hasFieldsToStrip helper functions are correctly updated to detect Citations fields and annotation presence, ensuring the MarshalJSON logic properly identifies messages requiring processing.

core/providers/anthropic/utils.go (2)

160-162: LGTM!

The citations propagation correctly wraps block.Citations in AnthropicCitations{Config: ...} when converting to Anthropic document blocks, aligning with the dual-purpose AnthropicCitations struct that separates request configuration from response citations.


229-239: All callers of ConvertResponsesFileBlockToAnthropic have been properly updated. The only call site at core/providers/anthropic/responses.go:4229 correctly passes all three parameters, including the new citations parameter.

tests/integrations/python/tests/test_openai.py (2)

2839-2856: LGTM!

The non-streaming web search test is well-structured with proper validation of web_search_call presence, search status, message output, and weather-related content. The parametrization with get_cross_provider_params_with_vk_for_scenario("web_search") ensures proper provider/model filtering.


2929-2954: LGTM!

The streaming web search test correctly configures user location and validates the streaming event lifecycle including web_search_call, text deltas, and citation annotations. The chunk collection and validation logic is appropriate for streaming responses.

core/internal/testutil/web_search_tool.go (2)

14-76: LGTM!

The RunWebSearchToolTest function is well-structured:

  • Properly checks testConfig.Scenarios.WebSearchTool before execution
  • Uses bifrost.Ptr() consistently for pointer creation (per learnings)
  • Correctly configures web search tool with user location
  • Integrates with the retry framework via WithResponsesTestRetry

85-145: LGTM!

The response validation logic correctly checks for:

  • web_search_call output type with query and sources
  • Message output with text content
  • Citation annotations presence

The log messages now properly distinguish between responses with and without citations (lines 129-133).

core/schemas/responses.go (1)

1429-1433: Good: explicit stream event types for web_search lifecycle.
Adding response.web_search_call.in_progress / .completed makes the stream contract clearer.

core/providers/anthropic/responses.go (1)

1436-1453: Good: annotation↔citation delta mapping uses OutputIndex fallback pattern.
This matches the indexing approach used by other delta types.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 7ee61d0 to 63311e8 Compare January 15, 2026 08:43
@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from 363249d to e443fbf Compare January 15, 2026 08:43
@TejasGhatte TejasGhatte changed the base branch from 01-09-fix_claude_code_openai to graphite-base/1241 January 15, 2026 08:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
core/providers/openai/types.go (2)

115-137: Chat content block stripping misses FileURL-only cases.

hasFieldsToStripInChatMessage() (Lines 381-397) treats block.File.FileURL as a strip trigger, but needsBlockCopy (Line 119) won’t copy/strip when only FileURL is set (and FileType is nil), so FileURL can leak through.

Proposed fix
-					needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.File != nil && block.File.FileType != nil)
+					needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.File != nil && (block.File.FileType != nil || block.File.FileURL != nil))

Also applies to: 381-397


338-374: Responses tool-output blocks: Citations stripping is incomplete (detection vs copy).

You detect tool-output block.Citations != nil (Line 343) and even set blockCopy.Citations = nil (Line 358), but:

  • The per-block needsBlockCopy (Line 354) omits block.Citations != nil, so Citations-only blocks won’t be copied/stripped.
  • hasFieldsToStripInResponsesMessage() doesn’t consider tool-output block.Citations, so needsCopy (Lines 270-276) may stay false and skip the whole strip pass.
Proposed fix
 func hasFieldsToStripInResponsesMessage(msg schemas.ResponsesMessage) bool {
@@
 	if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Output != nil {
 		if msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
 			for _, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
 				if block.CacheControl != nil {
 					return true
 				}
+				if block.Citations != nil {
+					return true
+				}
 				if block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil {
 					return true
 				}
 			}
 		}
 	}
 	return false
 }
@@
-							needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+							needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)

Also applies to: 399-430

core/providers/anthropic/types.go (1)

183-207: Clearing opposite fields on unmarshal and validating single-block Type are both necessary for robustness.

The MarshalJSON validation proves the stale-field risk is real—it explicitly rejects having both ContentStr and ContentBlocks set simultaneously. However, UnmarshalJSON doesn't clear the opposite field, allowing reuse of the same AnthropicContent instance to accumulate stale state across unmarshals. Additionally, the single-block unmarshal path accepts any JSON object (including empty ones with Type="") since sonic ignores unknown fields, bypassing validation.

The fix is straightforward and consistent with the AnthropicCitations pattern already in this file (lines 330–348), which clears the opposite field on each unmarshal success path.

Proposed diff
 func (mc *AnthropicContent) UnmarshalJSON(data []byte) error {
 	// First, try to unmarshal as a direct string
 	var stringContent string
 	if err := sonic.Unmarshal(data, &stringContent); err == nil {
+		mc.ContentBlocks = nil
 		mc.ContentStr = &stringContent
 		return nil
 	}

 	// Try to unmarshal as a direct array of ContentBlock
 	var arrayContent []AnthropicContentBlock
 	if err := sonic.Unmarshal(data, &arrayContent); err == nil {
+		mc.ContentStr = nil
 		mc.ContentBlocks = arrayContent
 		return nil
 	}

 	// Try to unmarshal as a single ContentBlock object (e.g., web_search_tool_result_error)
 	// If successful, wrap it in an array
 	var singleBlock AnthropicContentBlock
 	if err := sonic.Unmarshal(data, &singleBlock); err == nil {
+		// Guard against arbitrary objects being accepted as an "empty" block.
+		if singleBlock.Type == "" {
+			return fmt.Errorf("content single-object form missing required field: type")
+		}
+		mc.ContentStr = nil
 		mc.ContentBlocks = []AnthropicContentBlock{singleBlock}
 		return nil
 	}

 	return fmt.Errorf("content field is neither a string nor an array of ContentBlock")
 }
core/providers/anthropic/responses.go (1)

3396-3404: Remove claude-cli user agent gating from WebSearch argument sanitization.

Anthropic's API documentation specifies that allowed_domains and blocked_domains are mutually exclusive for all WebSearch requests, not just Claude CLI. The sanitization logic should apply universally to prevent all clients from sending conflicting domain filters, not just when the user agent contains "claude-cli". Apply sanitizeWebSearchArguments() unconditionally to all WebSearch tool calls.

🤖 Fix all issues with AI agents
In `@core/schemas/responses.go`:
- Around line 1278-1392: UnmarshalJSON on ResponsesTool currently accepts
unknown t.Type values because the switch over t.Type has no default; update the
ResponsesTool.UnmarshalJSON implementation to add a default case that returns a
clear error (e.g., fmt.Errorf("unknown ResponsesTool type: %s", t.Type)) so
unknown/unsupported tool types fail instead of silently succeeding; place the
default at the end of the switch in the UnmarshalJSON method and ensure the
error includes the raw type string for debugging.

In `@tests/integrations/python/tests/test_anthropic.py`:
- Around line 2247-2258: Remove the dead try/except block that attempts to parse
delta.partial_json but only contains pass and uses a bare except: locate the
branch checking hasattr(delta, "type") and delta.type == "input_json_delta" and
the nested if hasattr(delta, "partial_json") then delete the try/except and its
empty body (the import json, assignment partial = delta.partial_json, the
conditional if "query" in partial and the pass). If you actually need to use
partial_json, replace the removed code with explicit, minimal handling (e.g.,
parse JSON with json.loads and handle JSONDecodeError) referenced around the
delta and partial_json symbols.

In `@tests/integrations/python/tests/utils/common.py`:
- Around line 2752-2814: There are two definitions of
assert_valid_openai_annotation causing Ruff F811; remove the duplicate and keep
a single consolidated implementation (merge the richer validation logic) that
preserves the same default for expected_type, accepts both dicts and objects for
annotation, and uses a small helper to read fields (e.g., value =
annotation.get(key) if isinstance(annotation, dict) else getattr(annotation,
key, None)) so all hasattr checks are replaced with safe lookups; delete the
other definition (the one around lines 3017-3039 mentioned in the comment) so
only the consolidated function remains.
- Around line 2780-2790: The assertion message for the end_index check uses an
unnecessary f-string; in the block that checks hasattr(annotation,
"start_index") and hasattr(annotation, "end_index") (and the assertions on
annotation.start_index and annotation.end_index) replace the f-string
f"end_index should be > start_index" with a plain string "end_index should be >
start_index" to satisfy Ruff F541.
♻️ Duplicate comments (3)
tests/integrations/python/tests/test_openai.py (2)

2841-2927: Fix Ruff failures and reduce cross-provider flakiness in non-streaming web_search test.

  • has_citations is set but never used (Ruff F841).
  • Several print(f"...") have no placeholders (Ruff F541).
  • assert search_status == "completed" can be flaky across providers/tools (degraded/refusal/partial modes).
Proposed diff
@@
-        has_citations = False
         search_status = None
         output_text = ""
@@
-                            if hasattr(content_block, "annotations") and content_block.annotations:
-                                has_citations = True
+                            if hasattr(content_block, "annotations") and content_block.annotations:
                                 citation_count = len(content_block.annotations)
                                 print(f"✓ Found {citation_count} citations")
@@
-        assert search_status == "completed", f"Web search should be completed, got status: {search_status}"
+        assert search_status in ["completed", "failed"], f"Unexpected web search status: {search_status}"
@@
-        print(f"✓ Web search (non-streaming) test passed!")
+        print("✓ Web search (non-streaming) test passed!")

2928-3048: Streaming loop needs a time-based timeout; also fix Ruff F541 prints.

The loop for chunk in stream: can still hang indefinitely if the iterator stalls; the chunk cap alone doesn’t prevent that. Also, there are multiple f-strings without placeholders.

Proposed diff
@@
         citations = []
         search_queries = []
-        
+
+        start = time.time()
         for chunk in stream:
             chunk_count += 1
@@
             # Safety check
+            if time.time() - start > 300:
+                raise TimeoutError("Streaming web_search exceeded 300s")
             if chunk_count > 5000:
                 break
@@
-        print(f"✓ Streaming validation:")
+        print("✓ Streaming validation:")
@@
-        print(f"✓ Web search (streaming) test passed!")
+        print("✓ Web search (streaming) test passed!")
core/providers/anthropic/responses.go (1)

1805-1806: params.Include unconditionally set may be overwritten by user params.

At line 1806, params.Include is unconditionally set to ["web_search_call.action.sources"]. However, if the user provides include in ExtraParams (line 1793-1794), that user value takes precedence since it's set earlier.

Looking at the flow:

  1. Line 1793-1794: User's include from ExtraParams is extracted
  2. Line 1805-1806: params.Include is set to ["web_search_call.action.sources"]

This means the user's include value is actually overwritten, not preserved. If you want to merge them, append instead of replacing.

Proposed fix to merge includes
 	if provider == schemas.OpenAI && request.Tools != nil {
 		for _, tool := range request.Tools {
 			if tool.Type != nil && (*tool.Type == AnthropicToolTypeComputer20250124 || *tool.Type == AnthropicToolTypeComputer20251124) {
 				params.Truncation = schemas.Ptr("auto")
 				break
 			}
 		}

-		params.Include = []string{"web_search_call.action.sources"}
+		// Merge with any existing includes rather than overwriting
+		if params.Include == nil {
+			params.Include = []string{"web_search_call.action.sources"}
+		} else {
+			// Check if already present to avoid duplicates
+			hasWebSearchSources := false
+			for _, inc := range params.Include {
+				if inc == "web_search_call.action.sources" {
+					hasWebSearchSources = true
+					break
+				}
+			}
+			if !hasWebSearchSources {
+				params.Include = append(params.Include, "web_search_call.action.sources")
+			}
+		}
 	}
🧹 Nitpick comments (15)
tests/integrations/python/config.yml (1)

186-198: Inconsistent key quoting style.

Line 186 uses quotes around "web_search" while line 198 and all other keys in the file are unquoted. This inconsistency can make the file harder to maintain.

🔧 Suggested fix
-    "web_search": true
+    web_search: true
core/internal/testutil/web_search_tool.go (2)

15-146: WebSearchTool test may be flaky if the model doesn’t choose to call the tool.

The test hard-requires a web_search_call output (Line 141) but the prompt doesn’t explicitly require tool usage (Line 28). If the model answers from prior knowledge, retries likely won’t help.

Consider making the prompt/tool-choice more explicit (e.g., “Use web search to answer…”), or set a tool-choice/requirement if the Responses params support it.


148-169: Align retry-config construction with core/internal/testutil conventions.

WebSearchRetryConfig() hardcodes retry values and conditions. In this directory, we typically derive shared retry timings from GetTestRetryConfigForScenario() and then build the typed config (based on learnings).

Based on learnings, consider pulling MaxAttempts/BaseDelay/MaxDelay/(OnRetry/OnFinalFail) from the generic scenario retry config, then layering the web-search-specific Conditions.

tests/integrations/python/tests/utils/common.py (2)

2649-2689: Ruff TRY003 warnings: decide whether to simplify messages or suppress locally.

Ruff flags the raise ValueError(...) lines (TRY003). If TRY003 is enforced in CI, this will fail lint.

Options:

  • Add # noqa: TRY003 on the specific raise lines (test utils tend to be a reasonable place for targeted noqa).
  • Or shorten/remove the custom message and rely on type/value context in the failing test.

Also applies to: 2922-2946


2816-2867: Streaming collectors should fail loudly on timeout/safety limits.

Both collect_anthropic_streaming_citations and collect_openai_streaming_annotations break on timeout / excessive chunks, which can hide failures and return partial data.

Prefer raising TimeoutError / AssertionError when the timeout or max-chunk limit is hit, so callers don’t accidentally proceed with incomplete data.

Also applies to: 2869-2916

core/providers/azure/utils.go (1)

12-58: Add nil guards (ctx/request) and consider avoiding in-place mutation of request.Model.

ctx.Value(...) (Line 17) and request.Model = deployment (Line 40) will panic if ctx/request is nil; and mutating request.Model can be surprising if the caller reuses request (e.g., retries/fallbacks).

Proposed diff
 func getRequestBodyForAnthropicResponses(ctx *schemas.BifrostContext, request *schemas.BifrostResponsesRequest, deployment string, providerName schemas.ModelProvider, isStreaming bool) ([]byte, *schemas.BifrostError) {
+	if ctx == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("ctx is nil"), providerName)
+	}
+	if request == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("request is nil"), providerName)
+	}
+
 	var jsonBody []byte
 	var err error
@@
 	} else {
 		// Convert request to Anthropic format
-		request.Model = deployment
-		reqBody, err := anthropic.ToAnthropicResponsesRequest(ctx, request)
+		reqCopy := *request
+		reqCopy.Model = deployment
+		reqBody, err := anthropic.ToAnthropicResponsesRequest(ctx, &reqCopy)
 		if err != nil {
 			return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, err, providerName)
 		}
core/providers/anthropic/utils.go (2)

30-76: Same nil-safety concern as Azure: guard ctx/request in request-body builder.

ctx.Value(...) (Line 35) and ToAnthropicResponsesRequest(ctx, request) (Line 56) will panic if ctx is nil, and request is assumed non-nil throughout.

Proposed diff
 func getRequestBodyForResponses(ctx *schemas.BifrostContext, request *schemas.BifrostResponsesRequest, providerName schemas.ModelProvider, isStreaming bool) ([]byte, *schemas.BifrostError) {
+	if ctx == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("ctx is nil"), providerName)
+	}
+	if request == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("request is nil"), providerName)
+	}
+
 	var jsonBody []byte
 	var err error

795-844: Web search arguments sanitization logic seems fine; consider also dropping empty singleton arrays (optional).

Today it only resolves the “both present” conflict case; if callers provide only allowed_domains: [] (or only blocked_domains: []), that empty filter will be left as-is. That’s probably OK, but if Anthropic treats empty arrays specially, you may want to normalize those too.

core/schemas/responses.go (1)

704-708: Consider adding omitempty to Anthropic-specific source fields.

The Title, EncryptedContent, and PageAge fields are missing omitempty tags. This means they'll serialize as empty strings/null even when not set, which may cause issues with providers that don't expect these fields.

🔧 Suggested fix
 	// Anthropic specific fields
-	Title            *string `json:"title"`
-	EncryptedContent *string `json:"encrypted_content"`
-	PageAge          *string `json:"page_age"`
+	Title            *string `json:"title,omitempty"`
+	EncryptedContent *string `json:"encrypted_content,omitempty"`
+	PageAge          *string `json:"page_age,omitempty"`
tests/integrations/python/tests/test_anthropic.py (4)

1904-1904: Unused loop variable idx in enumerate.

The idx variable from enumerate is not used. Use _ to indicate intentionally unused variable.

🔧 Suggested fix
-        for idx, doc_info in enumerate(CITATION_MULTI_DOCUMENT_SET):
+        for _idx, doc_info in enumerate(CITATION_MULTI_DOCUMENT_SET):

2115-2144: Unused has_citations variable - consider removing or asserting.

The has_citations flag is set to True when citations are found (line 2142) but never used in assertions or reported. Either remove the unused variable or add validation.

🔧 Option 1: Remove unused variable
         # Check for web search tool use
         has_web_search = False
         has_search_results = False
-        has_citations = False
         search_query = None
🔧 Option 2: Add citation assertion (if citations are expected)
         # Validate that web search was performed
         assert has_web_search, "Response should contain web_search tool use"
         assert has_search_results, "Response should contain web search results"
         assert search_query is not None, "Web search should have a query"
+        # Note: Citations may or may not be present depending on search results
+        if has_citations:
+            print(f"✓ Citations were included in response")

2199-2207: Clean up unused variables in streaming test.

search_queries and has_citation_delta are initialized but never used. Remove them to reduce confusion.

🔧 Suggested fix
         # Collect streaming events
         text_parts = []
-        search_queries = []
         search_results = []
         citations = []
         chunk_count = 0
         has_server_tool_use = False
         has_search_tool_result = False
-        has_citation_delta = False

And remove the assignment on line 2267:

-                        elif hasattr(delta, "type") and delta.type == "citations_delta":
-                            has_citation_delta = True
+                        elif hasattr(delta, "type") and delta.type == "citations_delta":

2706-2708: Broad exception catch in error handling test is acceptable but could be improved.

While catching Exception is flagged by linters, it's reasonable in an error-handling test. Consider logging the exception type for debugging purposes.

🔧 Optional improvement
         except Exception as e:
             # Some errors might be raised as exceptions
-            print(f"✓ Exception caught (expected for error scenarios): {type(e).__name__}")
+            print(f"✓ Exception caught (expected for error scenarios): {type(e).__name__}: {e}")
core/providers/anthropic/responses.go (2)

318-326: Sequence numbers use hardcoded offsets instead of +len(responses) pattern.

Lines 318 and 326 use hardcoded sequenceNumber + 1 and sequenceNumber + 2 for the web search lifecycle events. The codebase pattern (e.g., lines 229, 414, 548, 959) is sequenceNumber + len(responses) to avoid duplicate/collision risks if the response list changes.

Proposed fix
 				// Emit web_search_call.in_progress
 				responses = append(responses, &schemas.BifrostResponsesStreamResponse{
 					Type:           schemas.ResponsesStreamResponseTypeWebSearchCallInProgress,
-					SequenceNumber: sequenceNumber + 1,
+					SequenceNumber: sequenceNumber + len(responses),
 					OutputIndex:    schemas.Ptr(outputIndex),
 					ItemID:         chunk.ContentBlock.ID,
 				})

 				// Emit web_search_call.searching
 				responses = append(responses, &schemas.BifrostResponsesStreamResponse{
 					Type:           schemas.ResponsesStreamResponseTypeWebSearchCallSearching,
-					SequenceNumber: sequenceNumber + 2,
+					SequenceNumber: sequenceNumber + len(responses),
 					OutputIndex:    schemas.Ptr(outputIndex),
 					ItemID:         chunk.ContentBlock.ID,
 				})

760-773: Web search result handling parses query but doesn't validate JSON structure.

When parsing AccumulatedJSON at line 767, if the JSON is malformed or doesn't contain a query key, the code silently proceeds with empty values. While this may be acceptable, consider logging a warning for debugging when query extraction fails.

if err := json.Unmarshal([]byte(state.AccumulatedJSON), &inputMap); err == nil {
    if q, ok := inputMap["query"].(string); ok {
        query = q
        queries = []string{q}
    }
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ee61d0 and 63311e8.

📒 Files selected for processing (22)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • transports/bifrost-http/integrations/anthropic.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/providers/openai/responses.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/tests.go
  • core/schemas/chatcompletions.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/schemas/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • tests/integrations/python/tests/test_openai.py
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • tests/integrations/python/config.yml
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic_test.go
  • tests/integrations/python/tests/utils/common.py
  • core/providers/anthropic/responses.go
  • tests/integrations/python/tests/test_anthropic.py
🧠 Learnings (22)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/schemas/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • core/schemas/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T13:30:28.760Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: plugins/semanticcache/test_utils.go:545-559
Timestamp: 2026-01-14T13:30:28.760Z
Learning: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Applied to files:

  • core/schemas/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-12T06:41:13.643Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1153
File: core/schemas/utils.go:1159-1183
Timestamp: 2026-01-12T06:41:13.643Z
Learning: In core/schemas/utils.go, IsGrokReasoningModel (and the grokReasoningModels list) is the canonical utility for model matching. Treat it as the source of truth and not as a duplicate; if extending model support, update this file rather than introducing separate matching logic, and verify changes here to avoid diverging behavior.

Applied to files:

  • core/schemas/utils.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2025-12-29T09:14:16.633Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: transports/bifrost-http/handlers/middlewares.go:246-256
Timestamp: 2025-12-29T09:14:16.633Z
Learning: In the bifrost HTTP transport, fasthttp.RequestCtx is the primary context carrier and should be passed directly to functions that expect a context.Context. Do not convert to context.Context unless explicitly required. Ensure tracer implementations and related components are designed to accept fasthttp.RequestCtx directly, and document this architectural decision for maintainers.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:40:42.227Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:40:42.227Z
Learning: When handling unsupported operations across providers, avoid hardcoding provider constants (e.g., schemas.Bedrock). Use the provider.GetProviderKey() (or equivalent API) to obtain the actual provider key from configuration, ensuring errors and messages adapt to custom provider names. Apply this pattern to all core/provider implementations (not just bedrock) to improve configurability and maintainability.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:49:14.252Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:49:14.252Z
Learning: In provider implementations, when raising unsupported-operation errors, pass provider.GetProviderKey() to NewUnsupportedOperationError so error messages and ExtraFields.Provider reflect the provider's alias. Apply this pattern consistently across all provider files (not just this one) to ensure accurate error context and branding.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/openai/types.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • tests/integrations/python/tests/test_anthropic.py
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
📚 Learning: 2026-01-14T04:32:14.023Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/config.yml:170-171
Timestamp: 2026-01-14T04:32:14.023Z
Learning: Guideline: In HuggingFace provider configuration, follow the provider-prefixed model path format fal-ai/<namespace>/<model> where the first segment fal-ai/ is the provider routing prefix and the remainder is the actual model path on fal.ai. Do not double-include the provider prefix. Example: fal-ai/flux/dev is the actual model path; when combined with the provider prefix you get fal-ai/fal-ai/flux/dev, which correctly indicates provider + model path.

Applied to files:

  • tests/integrations/python/config.yml
📚 Learning: 2025-12-15T10:16:21.909Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/huggingface/huggingface_test.go:12-63
Timestamp: 2025-12-15T10:16:21.909Z
Learning: In provider tests under core/providers/<provider>/*_test.go, do not require or flag the use of defer for Shutdown(); instead call client.Shutdown() at the end of each test function. This pattern appears consistent across all provider tests. Apply this rule only within this path; for other tests or resources, defer may still be appropriate.

Applied to files:

  • core/providers/anthropic/anthropic_test.go
🧬 Code graph analysis (9)
core/schemas/utils.go (2)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessageAnnotationCitation (816-823)
core/providers/gemini/types.go (1)
  • Type (825-825)
transports/bifrost-http/integrations/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesResponse (2076-2137)
core/providers/anthropic/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/anthropic/types.go (1)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
core/providers/anthropic/utils.go (3)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/anthropic/types.go (2)
  • AnthropicCitations (305-310)
  • AnthropicContentBlock (227-247)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/azure/utils.go (3)
core/schemas/context.go (1)
  • BifrostContext (32-43)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (30-37)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/vertex/utils.go (3)
core/schemas/context.go (1)
  • BifrostContext (32-43)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (30-37)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/vertex/vertex.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/openai/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (720-723)
core/schemas/responses.go (5)
  • Citations (422-424)
  • ResponsesMessageContentBlock (404-420)
  • ResponsesInputMessageContentBlockFile (430-435)
  • ResponsesOutputMessageContentText (446-449)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_openai.py

2900-2900: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2926-2926: f-string without any placeholders

Remove extraneous f prefix

(F541)


3035-3035: f-string without any placeholders

Remove extraneous f prefix

(F541)


3047-3047: f-string without any placeholders

Remove extraneous f prefix

(F541)


3095-3095: f-string without any placeholders

Remove extraneous f prefix

(F541)


3097-3097: f-string without any placeholders

Remove extraneous f prefix

(F541)


3099-3099: f-string without any placeholders

Remove extraneous f prefix

(F541)


3101-3101: f-string without any placeholders

Remove extraneous f prefix

(F541)


3143-3143: f-string without any placeholders

Remove extraneous f prefix

(F541)


3150-3150: f-string without any placeholders

Remove extraneous f prefix

(F541)


3192-3192: f-string without any placeholders

Remove extraneous f prefix

(F541)


3247-3247: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/utils/common.py

2686-2686: Avoid specifying long messages outside the exception class

(TRY003)


2789-2789: f-string without any placeholders

Remove extraneous f prefix

(F541)


2945-2945: Avoid specifying long messages outside the exception class

(TRY003)


3017-3017: Redefinition of unused assert_valid_openai_annotation from line 2752: assert_valid_openai_annotation redefined here

(F811)

tests/integrations/python/tests/test_anthropic.py

1783-1783: Unused method argument: test_config

(ARG002)


1839-1839: Unused method argument: test_config

(ARG002)


1895-1895: Unused method argument: test_config

(ARG002)


1904-1904: Loop control variable idx not used within loop body

Rename unused idx to _idx

(B007)


1964-1964: f-string without any placeholders

Remove extraneous f prefix

(F541)


1971-1971: Unused method argument: test_config

(ARG002)


2025-2025: Unused method argument: test_config

(ARG002)


2079-2079: Unused method argument: test_config

(ARG002)


2142-2142: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2160-2160: f-string without any placeholders

Remove extraneous f prefix

(F541)


2163-2163: Unused method argument: test_config

(ARG002)


2201-2201: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2257-2257: Do not use bare except

(E722)


2257-2258: try-except-pass detected, consider logging the exception

(S110)


2267-2267: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2289-2289: f-string without any placeholders

Remove extraneous f prefix

(F541)


2298-2298: f-string without any placeholders

Remove extraneous f prefix

(F541)


2302-2302: f-string without any placeholders

Remove extraneous f prefix

(F541)


2305-2305: Unused method argument: test_config

(ARG002)


2355-2355: f-string without any placeholders

Remove extraneous f prefix

(F541)


2358-2358: Unused method argument: test_config

(ARG002)


2411-2411: f-string without any placeholders

Remove extraneous f prefix

(F541)


2414-2414: Unused method argument: test_config

(ARG002)


2443-2443: f-string without any placeholders

Remove extraneous f prefix

(F541)


2477-2477: f-string without any placeholders

Remove extraneous f prefix

(F541)


2480-2480: Unused method argument: test_config

(ARG002)


2524-2524: f-string without any placeholders

Remove extraneous f prefix

(F541)


2526-2526: f-string without any placeholders

Remove extraneous f prefix

(F541)


2528-2528: f-string without any placeholders

Remove extraneous f prefix

(F541)


2531-2531: Unused method argument: test_config

(ARG002)


2573-2573: f-string without any placeholders

Remove extraneous f prefix

(F541)


2578-2578: f-string without any placeholders

Remove extraneous f prefix

(F541)


2587-2587: f-string without any placeholders

Remove extraneous f prefix

(F541)


2590-2590: Unused method argument: test_config

(ARG002)


2655-2655: f-string without any placeholders

Remove extraneous f prefix

(F541)


2658-2658: Unused method argument: test_config

(ARG002)


2704-2704: f-string without any placeholders

Remove extraneous f prefix

(F541)


2706-2706: Do not catch blind exception: Exception

(BLE001)


2710-2710: f-string without any placeholders

Remove extraneous f prefix

(F541)


2713-2713: Unused method argument: test_config

(ARG002)


2754-2754: f-string without any placeholders

Remove extraneous f prefix

(F541)


2762-2762: f-string without any placeholders

Remove extraneous f prefix

(F541)


2765-2765: Unused method argument: test_config

(ARG002)


2815-2815: f-string without any placeholders

Remove extraneous f prefix

(F541)


2817-2817: f-string without any placeholders

Remove extraneous f prefix

(F541)


2819-2819: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 63311e8 to e805aea Compare January 15, 2026 10:00
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1241 to 01-09-fix_claude_code_openai January 15, 2026 10:00
@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from e805aea to 7a02931 Compare January 15, 2026 10:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
core/providers/vertex/utils.go (1)

12-83: Add nil-guards for ctx/request (avoid panics in ctx.Value / request access).
Right now ctx being nil will panic at Line 17, and request being nil will panic at Line 18/44. Consider failing fast with a BifrostError.

Proposed patch
 func getRequestBodyForAnthropicResponses(ctx *schemas.BifrostContext, request *schemas.BifrostResponsesRequest, deployment string, providerName schemas.ModelProvider, isStreaming bool) ([]byte, *schemas.BifrostError) {
+	if ctx == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("ctx is nil"), providerName)
+	}
+	if request == nil {
+		return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("request is nil"), providerName)
+	}
+
 	var jsonBody []byte
 	var err error
 
 	// Check if raw request body should be used
 	if useRawBody, ok := ctx.Value(schemas.BifrostContextKeyUseRawRequestBody).(bool); ok && useRawBody {
 		jsonBody = request.GetRawRequestBody()
+		if len(jsonBody) == 0 {
+			return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("raw request body is empty"), providerName)
+		}
core/providers/openai/types.go (1)

295-413: Bug: tool-output blocks with Citations won’t be stripped.
You detect block.Citations != nil at Line 379-381, but:

  • needsBlockCopy at Line 389 ignores block.Citations, so citations-only blocks remain unchanged.
  • hasFieldsToStripInResponsesMessage at Line 466-475 ignores citations entirely, so citations-only tool outputs won’t even trigger needsCopy.
Proposed patch
@@
-				if msg.ResponsesToolMessage.Output != nil && msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
+				if msg.ResponsesToolMessage.Output != nil && msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
 					hasToolModification := false
 					for _, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
 						if block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil) {
 							hasToolModification = true
 							break
 						}
 					}
@@
 						for j, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
-							needsBlockCopy := block.CacheControl != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
+							needsBlockCopy := block.CacheControl != nil || block.Citations != nil || (block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil)
 							if needsBlockCopy {
 								blockCopy := block
 								blockCopy.CacheControl = nil
 								blockCopy.Citations = nil
@@
 func hasFieldsToStripInResponsesMessage(msg schemas.ResponsesMessage) bool {
@@
 	if msg.ResponsesToolMessage != nil {
@@
 		// Check output blocks
 		if msg.ResponsesToolMessage.Output != nil && msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks != nil {
 			for _, block := range msg.ResponsesToolMessage.Output.ResponsesFunctionToolCallOutputBlocks {
 				if block.CacheControl != nil {
 					return true
 				}
+				if block.Citations != nil {
+					return true
+				}
 				if block.ResponsesInputMessageContentBlockFile != nil && block.ResponsesInputMessageContentBlockFile.FileType != nil {
 					return true
 				}
 			}
 		}
 	}
 	return false
 }

Also applies to: 456-477

core/providers/anthropic/utils.go (1)

30-76: Add nil-guards for ctx/request in getRequestBodyForResponses.
Same panic risk as Vertex: ctx.Value(...) at Line 35 will panic if ctx is nil; request nil will panic at Line 36.

🤖 Fix all issues with AI agents
In `@core/providers/anthropic/types.go`:
- Around line 198-206: The code currently accepts a successfully unmarshaled
single AnthropicContentBlock even if its required Type field is empty; after the
sonic.Unmarshal into singleBlock (inside the single-object branch) add a guard
that rejects blocks with empty Type (e.g., if singleBlock.Type == "" return
fmt.Errorf("content block missing required type field")), otherwise set
mc.ContentBlocks = []AnthropicContentBlock{singleBlock} and return nil;
reference AnthropicContentBlock, singleBlock, mc.ContentBlocks and
sonic.Unmarshal when making the change.

In `@core/providers/anthropic/utils.go`:
- Around line 801-874: sanitizeWebSearchArguments: broaden the condition so it
handles cases where only one of "allowed_domains" or "blocked_domains" exists —
parse toolArgs and if either key exists, treat an empty array value as a
candidate for deletion; if one is empty and the other is absent or non-empty,
delete the empty key so the resulting JSON contains exactly one non-empty domain
filter (or none if both empty), then re-marshal toolArgs.
attachWebSearchSourcesToCall: guard against nil derefs by checking
msg.ResponsesToolMessage before accessing or initializing Action (i.e., if
msg.ResponsesToolMessage == nil, allocate it), then ensure
Action.ResponsesWebSearchToolCallAction is initialized before setting its Type
and Sources; reference these symbols: sanitizeWebSearchArguments,
attachWebSearchSourcesToCall, ResponsesToolMessage, Action,
ResponsesWebSearchToolCallAction, allowed_domains, blocked_domains.

In `@core/providers/openai/responses.go`:
- Around line 203-219: The code is mutating shared nested pointers by doing
newTool := tool (shallow copy) but then modifying both
tool.ResponsesToolWebSearch and newTool.ResponsesToolWebSearch.Filters; instead
deep-copy the nested pointer fields before mutating: create newTool := tool,
then if tool.ResponsesToolWebSearch != nil allocate a new ResponsesToolWebSearch
value and assign it to newTool.ResponsesToolWebSearch (copying fields), and if
ResponsesToolWebSearch.Filters != nil allocate a new
ResponsesToolWebSearchFilters value and assign it to
newTool.ResponsesToolWebSearch.Filters (copying fields); then perform the
MaxUses niling and BlockedDomains/AllowedDomains checks only on
newTool.ResponsesToolWebSearch and its Filters so the original tool remains
unchanged before appending newTool to filteredTools.

In `@tests/integrations/python/tests/test_anthropic.py`:
- Around line 2401-2465: The tests test_42_web_search_multi_turn and
test_45_web_search_with_prompt_caching append
response1.content/response2.content directly into messages which can break
cross-provider serialization; instead serialize the response blocks exactly as
done in test_05_end2end_tool_calling (convert each block in response.content
into its serialized/dict form or plain text representation) before calling
messages.append so the conversation messages contain only serializable items
when re-sent to anthropic_client.messages.create.

In `@tests/integrations/typescript/src/utils/common.ts`:
- Around line 955-1128: In assertValidAnthropicCitation, the code currently
unconditionally asserts citation.cited_text and its type which incorrectly
requires cited_text for WebSearchCitation; modify assertValidAnthropicCitation
so the checks expect(citation.cited_text) and typeof citation.cited_text ===
'string' are only executed when expectedType !== 'web_search_result_location'
(i.e., move/remove the unconditional assertions and place them inside the
existing non-web_search_result_location branch that also checks document_index)
so WebSearchCitation (WebSearchCitation) remains allowed to omit cited_text.

In `@tests/integrations/typescript/tests/test-openai.test.ts`:
- Around line 75-76: Remove the unused import symbol assertWebSearchSourcesValid
from the import list in the test file (it is imported but never used); update
the import statement that currently includes assertValidOpenAIAnnotation and
assertWebSearchSourcesValid to only import the used symbol(s) (e.g.,
assertValidOpenAIAnnotation).
♻️ Duplicate comments (7)
tests/integrations/python/tests/utils/common.py (1)

2624-3043: Python test util is currently broken due to leftover/duplicate assert_valid_openai_annotation fragment.
After validate_domain_filter, there’s a dangling docstring/code block referencing undefined annotation/expected_type (Ruff F821), consistent with the previously noted duplicate-definition issue.

Proposed patch (remove the stray fragment; keep only one function)
@@
 def validate_domain_filter(sources, allowed=None, blocked=None):
@@
                 assert not is_blocked, f"Domain {domain} should be blocked by {blocked_pattern}"
-
-    """
-    Validate OpenAI Responses API annotation structure.
-    
-    Args:
-        annotation: Annotation object or dict to validate
-        expected_type: Expected annotation type (default: "url_citation")
-    """
-    # Handle both dict and object types
-    if isinstance(annotation, dict):
-        assert "type" in annotation, "Annotation should have type"
-        assert annotation["type"] == expected_type, f"Expected {expected_type}, got {annotation['type']}"
-        
-        if expected_type == "url_citation":
-            assert "url" in annotation, "URL citation should have url"
-            assert annotation["url"], "URL should not be empty"
-    else:
-        assert hasattr(annotation, "type"), "Annotation should have type"
-        assert annotation.type == expected_type, f"Expected {expected_type}, got {annotation.type}"
-        
-        if expected_type == "url_citation":
-            assert hasattr(annotation, "url"), "URL citation should have url"
-            assert annotation.url, "URL should not be empty"
tests/integrations/python/tests/test_anthropic.py (3)

1782-1893: Fix Ruff ARG002 + strengthen citation assertions (PDF + text).

These tests don’t use test_config (ARG002), and the citation checks could be slightly tighter (e.g., inverted page range / empty cited text). This mirrors earlier review feedback.

Proposed diff
-    def test_33_citations_pdf(self, anthropic_client, test_config, provider, model):
+    def test_33_citations_pdf(self, anthropic_client, _test_config, provider, model):
@@
                     assert_valid_anthropic_citation(
                         citation,
                         expected_type="page_location",
                         document_index=0
                     )
+                    assert citation.end_page_number >= citation.start_page_number, \
+                        "end_page_number should be >= start_page_number"

-    def test_34_citations_text(self, anthropic_client, test_config, provider, model):
+    def test_34_citations_text(self, anthropic_client, _test_config, provider, model):
@@
                     assert_valid_anthropic_citation(
                         citation,
                         expected_type="char_location",
                         document_index=0
                     )
+                    assert citation.cited_text, "Citation cited_text should not be empty"

2078-2161: Web search (non-streaming): remove unused has_citations + pointless f-string, and avoid “today” flake.

has_citations is assigned but never used (F841) and the final print(f"...") is an f-string with no placeholders (F541). Also, “positive news story from today” will vary by date/timezone and can make this test non-deterministic across runs.

Proposed diff
-        has_citations = False
         search_query = None
@@
-                elif block.type == "text":
-                    if hasattr(block, "citations") and block.citations:
-                        has_citations = True
+                elif block.type == "text":
+                    if hasattr(block, "citations") and block.citations:
                         citation_count = len(block.citations)
                         print(f"✓ Found {citation_count} citations in response")
@@
-        print(f"✓ Web search (non-streaming) test passed!")
+        print("✓ Web search (non-streaming) test passed!")

2162-2290: Web search (streaming): dead/unused vars + “weather/news” assertion mismatch + missing time-based timeout.

This block still has unused search_queries/has_citation_delta (F841), asserts “about weather” while the prompt is about “positive news”, and relies only on a chunk cap (can hang if the stream stalls). This matches earlier review feedback.

Proposed diff
-    def test_39_web_search_streaming(self, anthropic_client, test_config, provider, model):
+    def test_39_web_search_streaming(self, anthropic_client, _test_config, provider, model):
@@
-        search_queries = []
         search_results = []
         citations = []
         chunk_count = 0
@@
-        has_citation_delta = False
+        import time
+        start = time.time()
@@
-                        elif hasattr(block, "type") and block.type == "web_search_tool_result":
-                            print(f"block: {block}")
+                        elif hasattr(block, "type") and block.type == "web_search_tool_result":
                             has_search_tool_result = True
@@
-                        elif hasattr(delta, "type") and delta.type == "citations_delta":
-                            has_citation_delta = True
+                        elif hasattr(delta, "type") and delta.type == "citations_delta":
                             if hasattr(delta, "citation"):
                                 citation = delta.citation
                                 citations.append(citation)
@@
-            # Safety check
+            # Safety checks (time-based + chunk cap)
+            if time.time() - start > 300:
+                raise TimeoutError("Streaming web_search exceeded 300s")
             if chunk_count > 5000:
                 break
@@
-        assert len(complete_text) > 0, "Should receive text content about weather"
+        assert len(complete_text) > 0, "Should receive text content about news"
@@
-        print(f"  - Chunks received: {chunk_count}")
-        print(f"  - Search results: {len(search_results)}")
-        print(f"  - Citations: {len(citations)}")
-        print(f"  - Text length: {len(complete_text)} characters")
-        print(f"  - First 150 chars: {complete_text[:150]}...")
+        print(f"  - Chunks received: {chunk_count}")
+        print(f"  - Search results: {len(search_results)}")
+        print(f"  - Citations: {len(citations)}")
+        print(f"  - Text length: {len(complete_text)} characters")
+        print(f"  - First 150 chars: {complete_text[:150]}...")
tests/integrations/python/tests/test_openai.py (2)

2841-2928: Web search (non-streaming): fix unused has_citations + pointless f-string; consider de-flaking “completed” + weather keyword assertions.

Ruff will flag has_citations (F841) and the final print(f"...") (F541). Also, search_status == "completed" and the strict weather-keyword assertion can be flaky across providers/degraded modes (this mirrors earlier review feedback).

Proposed diff
-        has_citations = False
         search_status = None
         output_text = ""
@@
-        print(f"✓ Web search (non-streaming) test passed!")
+        print("✓ Web search (non-streaming) test passed!")

2929-3050: Web search (streaming): add time-based timeout (chunk cap alone can hang) + remove pointless f-strings.

The loop can still hang indefinitely if the iterator stalls. This was previously called out and still appears to be missing here.

Proposed diff
         for chunk in stream:
             chunk_count += 1
+            if time.time() - start > 300:
+                raise TimeoutError("Streaming web_search exceeded 300s")
@@
-        print(f"✓ Streaming validation:")
+        print("✓ Streaming validation:")
@@
-        print(f"✓ Web search (streaming) test passed!")
+        print("✓ Web search (streaming) test passed!")
Note

This assumes you add start = time.time() before the loop.

core/schemas/responses.go (1)

1318-1392: UnmarshalJSON silently accepts unknown tool types.

The switch statement lacks a default case, so unrecognized tool types (e.g., future versioned types like "web_search_2025_08_26" mentioned in the struct comment on line 1085) will unmarshal successfully with only common fields set and no embedded struct. This can cause silent data loss.

Consider adding a default case:

🛠️ Proposed fix
 	case ResponsesToolTypeWebSearchPreview:
 		var webSearchPreviewTool ResponsesToolWebSearchPreview
 		if err := Unmarshal(data, &webSearchPreviewTool); err != nil {
 			return err
 		}
 		t.ResponsesToolWebSearchPreview = &webSearchPreviewTool
+
+	default:
+		// Unknown tool types are accepted for forward compatibility,
+		// but only common fields (Type, Name, Description, CacheControl) are populated
 	}
 
 	return nil

Alternatively, if forward compatibility is not desired, return an error:

default:
    return fmt.Errorf("unknown ResponsesTool type: %s", t.Type)
🧹 Nitpick comments (9)
tests/integrations/python/config.yml (1)

186-186: Inconsistent key quoting.

The "web_search" key is quoted here, but the same key is unquoted at line 249 for Anthropic (web_search: true). All other keys in this file are unquoted. Remove the quotes for consistency.

Suggested fix
-    "web_search": true
+    web_search: true
core/providers/anthropic/types.go (2)

212-224: New web_search_tool_result block type looks fine—ensure all switch/handlers cover it.

Given this expands the enum surface, double-check any switch block.Type logic (especially in streaming conversion and error mapping) handles AnthropicContentBlockTypeWebSearchToolResult explicitly or via a safe default.


528-540: Citations delta wiring: add a minimal invariant to prevent mis-parsing.

With delta.citation introduced, it’s worth ensuring downstream code only reads Delta.Citation when Delta.Type == AnthropicStreamDeltaTypeCitations (and ignores it otherwise), to avoid future payload-shape ambiguity.

tests/integrations/typescript/tests/test-anthropic.test.ts (2)

1814-1831: Consider making citationsByDoc dynamic based on document count.

The citationsByDoc is hardcoded to track only 2 documents. If CITATION_MULTI_DOCUMENT_SET changes to include more documents, citations from additional documents won't be tracked properly.

♻️ Suggested improvement
-        const citationsByDoc: Record<number, number> = { 0: 0, 1: 0 }
+        const citationsByDoc: Record<number, number> = {}
+        // Initialize tracking for all documents
+        CITATION_MULTI_DOCUMENT_SET.forEach((_, idx) => {
+          citationsByDoc[idx] = 0
+        })

2019-2070: Optional: Consider asserting citation presence in non-streaming web search test.

The test checks for hasCitations but only logs a warning if not found (line 2040). If citations are expected to be present when web search is used, consider making this an assertion for stronger test coverage. However, this may be intentional if citations aren't guaranteed.

-        // Validate that web search was performed
-        expect(hasWebSearch).toBe(true)
-        expect(hasSearchResults).toBe(true)
-        expect(searchQuery).not.toBeNull()
+        // Validate that web search was performed
+        expect(hasWebSearch).toBe(true)
+        expect(hasSearchResults).toBe(true)
+        expect(searchQuery).not.toBeNull()
+        // Optionally assert citations if they are expected
+        // expect(hasCitations).toBe(true)
tests/integrations/typescript/tests/test-openai.test.ts (1)

1914-1970: Consider validating that returned sources match allowed domain patterns.

The test configures allowed_domains with wildcard patterns but doesn't validate that returned sources actually match these patterns. This is where the imported assertWebSearchSourcesValid helper could be utilized.

♻️ Suggested improvement
          if (searchSources.length > 0) {
            console.log(`✓ Found ${searchSources.length} search sources`)
+           // Validate sources match allowed domain patterns
+           assertWebSearchSourcesValid(searchSources, ['wikipedia.org/*', '*.edu'])
            searchSources.slice(0, 3).forEach((source, i) => {
              const sourceObj = source as { url?: string }
              if (sourceObj.url) {
                console.log(`  Source ${i + 1}: ${sourceObj.url}`)
              }
            })
          }
core/providers/anthropic/responses.go (3)

315-331: Use sequenceNumber + len(responses) for consistent sequence numbering.

Lines 318 and 326 use hard-coded sequenceNumber + 1 and sequenceNumber + 2. The codebase convention (e.g., lines 229, 414, 548, 959) is to use sequenceNumber + len(responses) to ensure correct sequencing when responses are added or removed.

Proposed fix
 				// Emit web_search_call.in_progress
 				responses = append(responses, &schemas.BifrostResponsesStreamResponse{
 					Type:           schemas.ResponsesStreamResponseTypeWebSearchCallInProgress,
-					SequenceNumber: sequenceNumber + 1,
+					SequenceNumber: sequenceNumber + len(responses),
 					OutputIndex:    schemas.Ptr(outputIndex),
 					ItemID:         chunk.ContentBlock.ID,
 				})

 				// Emit web_search_call.searching
 				responses = append(responses, &schemas.BifrostResponsesStreamResponse{
 					Type:           schemas.ResponsesStreamResponseTypeWebSearchCallSearching,
-					SequenceNumber: sequenceNumber + 2,
+					SequenceNumber: sequenceNumber + len(responses),
 					OutputIndex:    schemas.Ptr(outputIndex),
 					ItemID:         chunk.ContentBlock.ID,
 				})

361-364: Add logging for tool ID mismatch to aid debugging.

When the tool use ID doesn't match the expected web search tool ID, the code silently returns nil. This makes diagnosing ordering issues or state corruption in production difficult.

Proposed fix
 				// If no matching tool ID, skip (shouldn't happen in normal flow)
+				// Log warning for debugging unexpected state
+				// Consider using a logger if available in context
 				return nil, nil, false

Consider adding a warning log with the mismatched IDs (chunk.ContentBlock.ToolUseID vs state.WebSearchToolID) to help diagnose issues.


4317-4329: URL index fallback may produce misleading indices.

When the URL is not found in fullText, the fallback assigns StartIndex = 0 and EndIndex = len(fullText). This indicates the annotation covers the entire text, which may not be semantically accurate.

Consider either:

  1. Leaving the indices as nil when URL isn't found
  2. Adding a comment explaining why this fallback behavior is acceptable
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63311e8 and 7a02931.

📒 Files selected for processing (25)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • tests/integrations/typescript/src/utils/common.ts
  • tests/integrations/typescript/tests/test-anthropic.test.ts
  • tests/integrations/typescript/tests/test-openai.test.ts
  • transports/bifrost-http/integrations/anthropic.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/internal/testutil/tests.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/azure/utils.go
  • core/schemas/utils.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/openai/openai_test.go
  • core/internal/testutil/account.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • tests/integrations/typescript/src/utils/common.ts
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • tests/integrations/python/tests/test_openai.py
  • core/providers/anthropic/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/utils/common.py
  • core/providers/anthropic/anthropic.go
  • tests/integrations/typescript/tests/test-anthropic.test.ts
  • core/providers/anthropic/responses.go
  • tests/integrations/typescript/tests/test-openai.test.ts
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_anthropic.py
🧠 Learnings (19)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2026-01-14T13:30:28.760Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: plugins/semanticcache/test_utils.go:545-559
Timestamp: 2026-01-14T13:30:28.760Z
Learning: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2025-12-29T09:14:16.633Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: transports/bifrost-http/handlers/middlewares.go:246-256
Timestamp: 2025-12-29T09:14:16.633Z
Learning: In the bifrost HTTP transport, fasthttp.RequestCtx is the primary context carrier and should be passed directly to functions that expect a context.Context. Do not convert to context.Context unless explicitly required. Ensure tracer implementations and related components are designed to accept fasthttp.RequestCtx directly, and document this architectural decision for maintainers.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:40:42.227Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:40:42.227Z
Learning: When handling unsupported operations across providers, avoid hardcoding provider constants (e.g., schemas.Bedrock). Use the provider.GetProviderKey() (or equivalent API) to obtain the actual provider key from configuration, ensuring errors and messages adapt to custom provider names. Apply this pattern to all core/provider implementations (not just bedrock) to improve configurability and maintainability.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:49:14.252Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:49:14.252Z
Learning: In provider implementations, when raising unsupported-operation errors, pass provider.GetProviderKey() to NewUnsupportedOperationError so error messages and ExtraFields.Provider reflect the provider's alias. Apply this pattern consistently across all provider files (not just this one) to ensure accurate error context and branding.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/anthropic/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • tests/integrations/python/tests/test_anthropic.py
📚 Learning: 2026-01-14T04:32:14.023Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/config.yml:170-171
Timestamp: 2026-01-14T04:32:14.023Z
Learning: Guideline: In HuggingFace provider configuration, follow the provider-prefixed model path format fal-ai/<namespace>/<model> where the first segment fal-ai/ is the provider routing prefix and the remainder is the actual model path on fal.ai. Do not double-include the provider prefix. Example: fal-ai/flux/dev is the actual model path; when combined with the provider prefix you get fal-ai/fal-ai/flux/dev, which correctly indicates provider + model path.

Applied to files:

  • tests/integrations/python/config.yml
🧬 Code graph analysis (7)
transports/bifrost-http/integrations/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesResponse (2076-2137)
core/providers/anthropic/types.go (1)
ui/lib/types/logs.ts (1)
  • Citation (243-250)
core/providers/vertex/utils.go (3)
core/schemas/context.go (1)
  • BifrostContext (32-43)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (30-37)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/vertex/vertex.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/anthropic/utils.go (3)
core/schemas/responses.go (4)
  • BifrostResponsesRequest (30-37)
  • Citations (422-424)
  • ResponsesWebSearchToolCallAction (691-698)
  • ResponsesWebSearchToolCallActionSearchSource (701-709)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
core/providers/anthropic/types.go (2)
  • AnthropicCitations (305-310)
  • AnthropicContentBlock (227-247)
tests/integrations/python/tests/utils/common.py (1)
tests/integrations/typescript/src/utils/common.ts (2)
  • CITATION_TEXT_DOCUMENT (956-963)
  • CITATION_MULTI_DOCUMENT_SET (966-977)
core/providers/anthropic/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_openai.py

2900-2900: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2927-2927: f-string without any placeholders

Remove extraneous f prefix

(F541)


3037-3037: f-string without any placeholders

Remove extraneous f prefix

(F541)


3049-3049: f-string without any placeholders

Remove extraneous f prefix

(F541)


3096-3096: f-string without any placeholders

Remove extraneous f prefix

(F541)


3098-3098: f-string without any placeholders

Remove extraneous f prefix

(F541)


3100-3100: f-string without any placeholders

Remove extraneous f prefix

(F541)


3102-3102: f-string without any placeholders

Remove extraneous f prefix

(F541)


3144-3144: f-string without any placeholders

Remove extraneous f prefix

(F541)


3151-3151: f-string without any placeholders

Remove extraneous f prefix

(F541)


3193-3193: f-string without any placeholders

Remove extraneous f prefix

(F541)


3248-3248: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/utils/common.py

2686-2686: Avoid specifying long messages outside the exception class

(TRY003)


2771-2771: Do not assign a lambda expression, use a def

Rewrite getter as a def

(E731)


2772-2772: Do not assign a lambda expression, use a def

Rewrite has as a def

(E731)


2950-2950: Avoid specifying long messages outside the exception class

(TRY003)


3030-3030: Undefined name annotation

(F821)


3031-3031: Undefined name annotation

(F821)


3032-3032: Undefined name annotation

(F821)


3032-3032: Undefined name expected_type

(F821)


3032-3032: Undefined name expected_type

(F821)


3032-3032: Undefined name annotation

(F821)


3034-3034: Undefined name expected_type

(F821)


3035-3035: Undefined name annotation

(F821)


3036-3036: Undefined name annotation

(F821)


3038-3038: Undefined name annotation

(F821)


3039-3039: Undefined name annotation

(F821)


3039-3039: Undefined name expected_type

(F821)


3039-3039: Undefined name expected_type

(F821)


3039-3039: Undefined name annotation

(F821)


3041-3041: Undefined name expected_type

(F821)


3042-3042: Undefined name annotation

(F821)


3043-3043: Undefined name annotation

(F821)

tests/integrations/python/tests/test_anthropic.py

1783-1783: Unused method argument: test_config

(ARG002)


1839-1839: Unused method argument: test_config

(ARG002)


1895-1895: Unused method argument: test_config

(ARG002)


1904-1904: Loop control variable idx not used within loop body

Rename unused idx to _idx

(B007)


1964-1964: f-string without any placeholders

Remove extraneous f prefix

(F541)


1971-1971: Unused method argument: test_config

(ARG002)


2025-2025: Unused method argument: test_config

(ARG002)


2079-2079: Unused method argument: test_config

(ARG002)


2142-2142: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2160-2160: f-string without any placeholders

Remove extraneous f prefix

(F541)


2163-2163: Unused method argument: test_config

(ARG002)


2201-2201: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2254-2254: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2292-2292: Unused method argument: test_config

(ARG002)


2342-2342: f-string without any placeholders

Remove extraneous f prefix

(F541)


2345-2345: Unused method argument: test_config

(ARG002)


2398-2398: f-string without any placeholders

Remove extraneous f prefix

(F541)


2401-2401: Unused method argument: test_config

(ARG002)


2430-2430: f-string without any placeholders

Remove extraneous f prefix

(F541)


2464-2464: f-string without any placeholders

Remove extraneous f prefix

(F541)


2467-2467: Unused method argument: test_config

(ARG002)


2511-2511: f-string without any placeholders

Remove extraneous f prefix

(F541)


2513-2513: f-string without any placeholders

Remove extraneous f prefix

(F541)


2515-2515: f-string without any placeholders

Remove extraneous f prefix

(F541)


2518-2518: Unused method argument: test_config

(ARG002)


2560-2560: f-string without any placeholders

Remove extraneous f prefix

(F541)


2565-2565: f-string without any placeholders

Remove extraneous f prefix

(F541)


2574-2574: f-string without any placeholders

Remove extraneous f prefix

(F541)


2577-2577: Unused method argument: test_config

(ARG002)


2647-2647: f-string without any placeholders

Remove extraneous f prefix

(F541)


2650-2650: Unused method argument: test_config

(ARG002)


2696-2696: f-string without any placeholders

Remove extraneous f prefix

(F541)


2698-2698: Do not catch blind exception: Exception

(BLE001)


2702-2702: f-string without any placeholders

Remove extraneous f prefix

(F541)


2705-2705: Unused method argument: test_config

(ARG002)


2746-2746: f-string without any placeholders

Remove extraneous f prefix

(F541)


2754-2754: f-string without any placeholders

Remove extraneous f prefix

(F541)


2757-2757: Unused method argument: test_config

(ARG002)


2807-2807: f-string without any placeholders

Remove extraneous f prefix

(F541)


2809-2809: f-string without any placeholders

Remove extraneous f prefix

(F541)


2811-2811: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (33)
tests/integrations/python/config.yml (4)

198-198: LGTM!

Setting citations: false for OpenAI is appropriate given the PR's focus on Anthropic citations handling.


249-249: LGTM!

Enabling web_search and citations for Anthropic aligns with the PR objectives for enhanced citations handling and web search support.

Also applies to: 262-262


303-303: LGTM!

Adding citations: false for Gemini, Bedrock, and Cohere maintains parity across provider configurations while correctly indicating citations support is not yet available for these providers.

Also applies to: 385-385, 424-424


459-459: LGTM!

Mapping both web_search and citations to the "chat" capability is appropriate since these features operate within chat completions, consistent with similar mappings like prompt_caching and count_tokens.

Also applies to: 472-472

transports/bifrost-http/integrations/anthropic.go (1)

77-84: Good: pass ctx into ToAnthropicResponsesResponse.

This keeps the /v1/messages non-passthrough conversion aligned with the updated provider converter signature and the ctx-aware message conversion path.

core/providers/anthropic/anthropic.go (1)

2093-2098: LGTM: CountTokens now uses ToAnthropicResponsesRequest(ctx, request).

core/providers/vertex/vertex.go (1)

1522-1530: LGTM: Vertex CountTokens uses ctx-aware Anthropic request conversion.

tests/integrations/typescript/src/utils/common.ts (1)

518-533: Good defensive typing on choices.length assertion.
The cast at Line 521 avoids TS narrowing issues when choices is unknown.

core/providers/anthropic/utils.go (1)

159-303: Citations propagation into document blocks looks good.
Setting documentBlock.Citations = &AnthropicCitations{Config: ...} in both chat-file and responses-file conversion paths is the right place to do it.

tests/integrations/typescript/tests/test-anthropic.test.ts (5)

70-91: LGTM! New imports for citation and web search testing utilities.

The imports are well-organized and align with the new test coverage being added. All imported utilities (assertValidAnthropicCitation, collectAnthropicStreamingCitations, createAnthropicDocument, etc.) are appropriately used in the new test sections below.


1640-1706: LGTM! PDF citations test is well-structured.

The test properly validates:

  • Response structure with citations
  • Citation type (page_location) appropriate for PDF documents
  • Page number ranges in citations

The use of assertValidAnthropicCitation helper ensures consistent validation across tests.


1708-1774: LGTM! Text citations test correctly validates character-based location citations.

The test appropriately uses char_location citation type for text documents and validates start_char_index/end_char_index ranges.


1860-1978: LGTM! Streaming citation tests are well-implemented.

The tests correctly:

  • Use the collectAnthropicStreamingCitations helper for clean stream processing
  • Validate appropriate citation types (char_location for text, page_location for PDF)
  • Assert on chunk count, content length, and citation presence

2136-2149: Acceptable: Silent catch for partial JSON parsing.

The empty catch block is intentional here since streaming JSON deltas may be incomplete. The comment explains this well.

tests/integrations/typescript/tests/test-openai.test.ts (3)

1783-1848: LGTM! Annotation conversion test properly validates citation structure.

The test correctly:

  • Uses assertValidOpenAIAnnotation to validate annotation structure
  • Handles the case where annotations may not be present (logs warning)
  • Validates the url_citation type for web search results

1851-1911: LGTM! User location test validates localized web search configuration.

The test properly validates that the web search tool accepts and uses user location configuration, and asserts both web_search_call and message are present in the output.


1973-2042: LGTM! Multi-turn web search test correctly validates conversation continuity.

The test properly:

  • Builds conversation history by appending first response output to subsequent input
  • Validates message presence in follow-up response
  • Tests the web search tool across multiple turns
core/providers/anthropic/responses.go (6)

1793-1807: Verify include parameter merge logic.

The code at line 1793-1795 extracts include from ExtraParams, but then lines 1805-1807 unconditionally set params.Include = []string{"web_search_call.action.sources"} when tools are present. This appears to still overwrite any user-provided includes rather than merging them.

The past review comment indicated this was addressed, but the current code structure suggests the fix may not be complete. Please verify the intended behavior.


1080-1080: LGTM: Function signature update for context propagation.

The change from context.Context to *schemas.BifrostContext enables proper access to context values like user agent for claude-cli specific handling. This is consistent with other function signatures updated in this PR.


2962-2990: LGTM: Citation to annotation conversion for text blocks.

The implementation correctly extracts fullText from the text block and converts Anthropic citations to OpenAI annotations. This enables proper index computation for the non-streaming path, addressing the limitation noted in the streaming path.


4278-4340: LGTM: Comprehensive citation type mapping.

The convertAnthropicCitationToAnnotation function correctly maps all Anthropic citation types (char_location, page_location, content_block_location, web_search_result_location, search_result_location) to the appropriate OpenAI annotation fields. The type-specific field mapping is accurate.


2876-2880: No action required. The function attachWebSearchSourcesToCall is defined in core/providers/anthropic/utils.go at line 848 with the correct signature, and all call sites at lines 2876 and 3167 match the function parameters.


3614-3622: [Your rewritten review comment text here]
[Exactly ONE classification tag]

tests/integrations/python/tests/test_anthropic.py (2)

88-94: New citation utilities import: good direction; ensure the stack keeps the surface area stable.

Imports look consistent with the new cross-provider citation/web_search coverage; just make sure the upstream/downstack PRs in the Graphite stack don’t rename/move these helpers (otherwise these integration tests will be brittle).


1970-2077: Streaming citations tests already enforce timeout protection—no action needed.

The collect_anthropic_streaming_citations helper already implements a time-based timeout of 30 seconds by default (configurable via the timeout parameter). The timeout is checked in every iteration (if time.time() - start_time > timeout: break), and there's also a secondary safety limit (max 2000 chunks). CI will not hang due to iterator stalls.

tests/integrations/python/tests/test_openai.py (2)

148-150: Importing assert_valid_openai_annotation is the right abstraction for cross-provider citation checks.


3051-3249: [Rewritten review comment]
[Classification tag]

core/schemas/responses.go (6)

419-424: LGTM - Citations support for content blocks.

The new Citations field and struct appropriately extend content blocks for Anthropic citation configuration. The placement alongside CacheControl maintains consistency with other provider-specific fields.


462-472: LGTM - Anthropic citation annotation fields.

The new annotation fields properly extend ResponsesOutputMessageContentTextAnnotation to support Anthropic's richer citation metadata (character/page/block indices, source, encrypted index).


530-576: LGTM - Type-based unmarshaling with proper error handling.

The rewritten UnmarshalJSON correctly uses type-based dispatch with a default case that returns an error for unknown action types (line 573-574). This is a solid pattern that prevents silent failures.


695-709: LGTM - Web search extensions for Anthropic.

The added Queries field and Anthropic-specific source fields (Title, EncryptedContent, PageAge) properly extend web search capabilities to support Anthropic's web search tool format.


1551-1560: LGTM - Web search tool configuration extensions.

The MaxUses and BlockedDomains fields appropriately extend web search tool configuration for Anthropic's API requirements.


1740-1744: LGTM - Web search streaming event constants.

The new ResponsesStreamResponseTypeWebSearchCallInProgress and ResponsesStreamResponseTypeWebSearchCallCompleted constants appropriately extend streaming event coverage for web search operations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@TejasGhatte TejasGhatte force-pushed the 01-05-fix_anthropic_type_citations branch from 7a02931 to 3697c69 Compare January 15, 2026 11:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/providers/vertex/utils.go (1)

12-51: Guard ctx against nil and avoid mutating the caller’s request in-place.

ctx.Value(...) will panic if ctx can ever be nil. Also request.Model = deployment mutates the shared request pointer; safer to copy the request (shallow copy) before overriding Model.

Proposed fix
 func getRequestBodyForAnthropicResponses(ctx *schemas.BifrostContext, request *schemas.BifrostResponsesRequest, deployment string, providerName schemas.ModelProvider, isStreaming bool) ([]byte, *schemas.BifrostError) {
+    if ctx == nil {
+        return nil, providerUtils.NewBifrostOperationError(schemas.ErrRequestBodyConversion, fmt.Errorf("nil bifrost context"), providerName)
+    }
@@
-        // Convert request to Anthropic format
-        request.Model = deployment
-        reqBody, err := anthropic.ToAnthropicResponsesRequest(ctx, request)
+        // Convert request to Anthropic format (avoid mutating caller's request)
+        reqCopy := *request
+        reqCopy.Model = deployment
+        reqBody, err := anthropic.ToAnthropicResponsesRequest(ctx, &reqCopy)
core/schemas/utils.go (1)

636-662: DeepCopyChatMessage drops ChatContentBlock.Citations and shallow-copies URLCitation.Sources.

If ChatContentBlock.Citations is now part of the schema (it’s referenced in this stack’s Anthropic converters), deepCopyChatContentBlock should preserve it; otherwise citations config can disappear when messages are deep-copied. Also, URLCitation.Sources is copied as an interface header only—if it contains slices/maps, the underlying data remains shared.

Proposed patch
diff --git a/core/schemas/utils.go b/core/schemas/utils.go
@@
 				if annotation.URLCitation.Sources != nil {
-					copySources := *annotation.URLCitation.Sources
-					copyAnnotation.URLCitation.Sources = &copySources
+					// Avoid sharing underlying maps/slices carried inside the interface{}
+					copySources := DeepCopy(*annotation.URLCitation.Sources)
+					copyAnnotation.URLCitation.Sources = &copySources
 				}
@@
 func deepCopyChatContentBlock(original ChatContentBlock) ChatContentBlock {
 	copy := ChatContentBlock{
 		Type: original.Type,
 	}
 
+	if original.Citations != nil {
+		copyCitations := *original.Citations
+		copy.Citations = &copyCitations
+	}
+
 	if original.Text != nil {
 		copyText := *original.Text
 		copy.Text = &copyText
 	}

Also applies to: 694-749

🤖 Fix all issues with AI agents
In `@core/providers/anthropic/responses.go`:
- Around line 1805-1807: The code currently overwrites params.Include with
["web_search_call.action.sources"]; instead, merge the default include with any
user-provided includes: check params.Include (the slice) and if it's nil/empty
set it to the default, otherwise append "web_search_call.action.sources" only if
not already present (to avoid duplicates); update the assignment near where
params.Include is set in responses.go so the default is preserved and merged
rather than unconditionally replacing user includes.

In `@tests/integrations/python/tests/utils/common.py`:
- Around line 2821-2921: The helpers collect_anthropic_streaming_citations and
collect_openai_streaming_annotations currently break out and return partial data
when the timeout or safety caps are hit; change those break statements to raise
explicit exceptions (e.g., raise TimeoutError when time.time() - start_time >
timeout and raise ValueError when chunk_count exceeds the safety cap) so tests
fail loudly; update the exception messages to include the function name
(collect_anthropic_streaming_citations / collect_openai_streaming_annotations),
current chunk_count and any relevant partial state to aid debugging.
- Around line 2752-2819: The lambdas assigned to getter and has inside
assert_valid_openai_annotation's else block cause Ruff E731; replace "getter =
lambda k: getattr(annotation, k, None)" and "has = lambda k: hasattr(annotation,
k)" with regular def functions (e.g., def getter(k): return getattr(annotation,
k, None) and def has(k): return hasattr(annotation, k)) defined in the same else
block so behavior is identical and the linter error is resolved.

In `@tests/integrations/typescript/src/utils/common.ts`:
- Around line 1064-1065: The end-page validation wrongly forbids single-page
citations; update the second assertion so pageCitation.end_page_number is
allowed to equal the start (use
expect(pageCitation.end_page_number).toBeGreaterThanOrEqual(pageCitation.start_page_number))
while keeping the existing start-page check
(expect(pageCitation.start_page_number).toBeGreaterThanOrEqual(1)).
♻️ Duplicate comments (20)
tests/integrations/typescript/src/utils/common.ts (1)

1087-1091: assertValidAnthropicCitation incorrectly requires cited_text for web_search_result_location.

Lines 1087-1088 unconditionally assert citation.cited_text is defined, but WebSearchCitation.cited_text is optional (as shown in the interface at lines 1003-1004). This will cause false test failures for valid web search citations that omit cited_text.

Proposed fix
   // Check basic structure
   expect(citation.type).toBeDefined()
   expect(citation.type).toBe(expectedType)

-  // Check required fields
-  expect(citation.cited_text).toBeDefined()
-  expect(typeof citation.cited_text).toBe('string')
-  
   if (expectedType !== 'web_search_result_location') {
-    expect(citation.cited_text?.length ?? 0).toBeGreaterThan(0)
+    // cited_text is required for char_location and page_location
+    expect((citation as CharLocationCitation | PageLocationCitation).cited_text).toBeDefined()
+    expect(typeof (citation as CharLocationCitation | PageLocationCitation).cited_text).toBe('string')
+    expect((citation as CharLocationCitation | PageLocationCitation).cited_text.length).toBeGreaterThan(0)
     
     // Check document reference
     expect((citation as CharLocationCitation | PageLocationCitation).document_index).toBeDefined()
     expect((citation as CharLocationCitation | PageLocationCitation).document_index).toBe(documentIndex)
   }
tests/integrations/python/tests/test_openai.py (2)

2841-2928: Fix Ruff F841/F541 in test_52_web_search_non_streaming (lint/CI risk).

  • has_citations is assigned but never used.
  • print(f"✓ Web search (non-streaming) test passed!") is an f-string without placeholders.
Proposed fix
-        has_citations = False
+        # Track citations only if used for assertions/logging
         search_status = None
         output_text = ""
@@
-                            if hasattr(content_block, "annotations") and content_block.annotations:
-                                has_citations = True
+                            if hasattr(content_block, "annotations") and content_block.annotations:
                                 citation_count = len(content_block.annotations)
                                 print(f"✓ Found {citation_count} citations")
@@
-        print(f"✓ Web search (non-streaming) test passed!")
+        print("✓ Web search (non-streaming) test passed!")

2929-3050: Add time-based streaming timeout; fix Ruff F541 prints in test_53_web_search_streaming.

Chunk caps don’t prevent hangs if the iterator stalls. Also multiple f-strings have no placeholders.

Proposed fix
 def test_53_web_search_streaming(self, provider, model, vk_enabled):
@@
-        for chunk in stream:
+        start = time.time()
+        for chunk in stream:
             chunk_count += 1
+            if time.time() - start > 300:
+                raise TimeoutError("Streaming web_search exceeded 300s")
@@
-        print(f"✓ Streaming validation:")
-        print(f"  - Chunks received: {chunk_count}")
-        print(f"  - Search queries: {len(search_queries)}")
-        print(f"  - Citations: {len(citations)}")
-        print(f"  - Text length: {len(complete_text)} characters")
-        print(f"  - First 150 chars: {complete_text[:150]}...")
+        print("✓ Streaming validation:")
+        print(f"  - Chunks received: {chunk_count}")
+        print(f"  - Search queries: {len(search_queries)}")
+        print(f"  - Citations: {len(citations)}")
+        print(f"  - Text length: {len(complete_text)} characters")
+        print(f"  - First 150 chars: {complete_text[:150]}...")
@@
-        print(f"✓ Web search (streaming) test passed!")
+        print("✓ Web search (streaming) test passed!")
core/schemas/responses.go (1)

1318-1392: UnmarshalJSON silently accepts unknown tool types.

The switch statement lacks a default case, so unrecognized tool types will unmarshal successfully with only common fields set. This could hide API changes or typos. Consider adding a default case that either returns an error or logs a warning.

🔧 Suggested fix
 	case ResponsesToolTypeWebSearchPreview:
 		var webSearchPreviewTool ResponsesToolWebSearchPreview
 		if err := Unmarshal(data, &webSearchPreviewTool); err != nil {
 			return err
 		}
 		t.ResponsesToolWebSearchPreview = &webSearchPreviewTool
+
+	default:
+		// Unknown type - still set common fields but log/warn
+		// Or return error: return fmt.Errorf("unknown ResponsesTool type: %s", t.Type)
 	}
 
 	return nil
tests/integrations/python/tests/test_anthropic.py (16)

1782-1836: Ruff: Unused test_config parameter.

Rename test_config to _test_config to suppress ARG002.


1838-1892: Ruff: Unused test_config parameter.

Already flagged in past review - rename to _test_config.


1894-1968: Multiple Ruff issues already flagged in past review.

  • test_config_test_config (ARG002)
  • idx_idx (B007)
  • Line 1964: Remove f prefix from f-string without placeholders (F541)
  • Line 1935: Consider citations_by_doc = {i: 0 for i in range(len(documents))} for flexibility

1970-2022: Ruff: Unused test_config parameter.

Already flagged - rename to _test_config.


2024-2076: Ruff: Unused test_config parameter.

Rename to _test_config to suppress ARG002.


2078-2160: Multiple Ruff issues already flagged in past review.

  • Line 2079: test_config_test_config (ARG002)
  • Line 2142: Remove unused has_citations variable (F841)
  • Line 2160: Remove f prefix (F541)

2162-2289: Multiple Ruff issues and assertion message mismatch already flagged in past review.

  • Unused variables: test_config, search_queries, has_citation_delta
  • f-strings without placeholders at lines 2276, 2285, 2289
  • Line 2274: Assertion message says "about weather" but prompt asks about news

2291-2342: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Line 2342: Remove f prefix (F541)

2344-2398: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Line 2398: Remove f prefix (F541)

2400-2464: Ruff issues already flagged; serialization is correct.

  • test_config_test_config (ARG002)
  • Lines 2430, 2464: Remove f prefix (F541)

Note: serialize_anthropic_content(response1.content) at line 2435 is correctly used for cross-provider compatibility.


2466-2515: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Lines 2511, 2513, 2515: Remove f prefix (F541)

2517-2574: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Lines 2560, 2565, 2574: Remove f prefix (F541)

2576-2647: Ruff issues already flagged; serialization is correct.

  • test_config_test_config (ARG002)
  • Line 2647: Remove f prefix (F541)

Note: serialize_anthropic_content(response1.content) at line 2615 is correctly used.


2649-2702: Ruff issues already flagged + minor numbering gap.

  • test_config_test_config (ARG002)
  • Lines 2696, 2702: Remove f prefix (F541)
  • Line 2698: Broad except Exception is flagged (BLE001), but acceptable for error handling test

Note: Test numbering skips from 45 to 47 (no test_46). Consider renumbering for consistency.


2704-2754: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Lines 2746, 2754: Remove f prefix (F541)

2756-2811: Ruff issues already flagged.

  • test_config_test_config (ARG002)
  • Lines 2807, 2809, 2811: Remove f prefix (F541)
🧹 Nitpick comments (16)
tests/integrations/python/config.yml (1)

186-186: Inconsistent quoting style for web_search key.

Line 186 uses quoted "web_search" while line 249 uses unquoted web_search. Both work in YAML, but consistent styling improves maintainability.

Suggested fix for consistency
-    "web_search": true
+    web_search: true

Also applies to: 249-249

tests/integrations/python/tests/utils/common.py (1)

2691-2750: validate_citation_indices / assert_valid_anthropic_citation: consider dict-support consistency.

These helpers assume SDK objects (hasattr + attribute access). If upstream parsing ever passes dicts (like your OpenAI helper supports), these will fail. Either document “object-only” explicitly or add the same dict/object accessor pattern here too.

tests/integrations/python/tests/test_openai.py (1)

3153-3194: test_56_web_search_wildcard_domains is currently non-assertive.

It prints sources but doesn’t assert domain filtering. Once validate_domain_filter(...) supports "*.edu" / "wikipedia.org/*" patterns (see common.py), consider asserting all sources match the allowed set.

tests/integrations/typescript/tests/test-openai.test.ts (1)

1778-2041: Web-search tests are permissive (can “pass” without annotations/sources).

In “Annotation Conversion” and “Wildcard Domains”, the test succeeds even when no annotations/sources are present (it only logs). Consider asserting at least one annotation/source (or making it conditional on provider capability) so these tests actually catch regressions.

Also, the repeated (client as unknown as { responses: ... }).responses casts are brittle against OpenAI SDK changes—worth verifying the pinned SDK version and/or centralizing this into a typed helper.

Example tightening (minimal)
-          if (hasAnnotations) {
+          expect(hasAnnotations).toBe(true)
+          if (hasAnnotations) {
             console.log(`✓ Found ${annotations.length} annotations`)
@@
-          if (searchSources.length > 0) {
+          expect(searchSources.length).toBeGreaterThan(0)
+          if (searchSources.length > 0) {
             console.log(`✓ Found ${searchSources.length} search sources`)
tests/integrations/typescript/tests/test-anthropic.test.ts (2)

1776-1854: Multi-document citations test hard-codes doc indices (0/1).

This will break (or silently mis-report) if CITATION_MULTI_DOCUMENT_SET ever changes length. Derive citationsByDoc from documents.length, and use optional chaining when logging titles.

Suggested adjustment
-        const citationsByDoc: Record<number, number> = { 0: 0, 1: 0 }
+        const citationsByDoc: Record<number, number> = {}
+        for (let i = 0; i < documents.length; i++) citationsByDoc[i] = 0
@@
-          const docTitle = CITATION_MULTI_DOCUMENT_SET[Number(docIdx)].title
+          const docTitle = CITATION_MULTI_DOCUMENT_SET[Number(docIdx)]?.title ?? 'Unknown'
           console.log(`  - Document ${docIdx} (${docTitle}): ${count} citations`)

1984-2193: Verify web_search tool type + streamed event shapes against the Anthropic SDK version used in tests.

The tests assume:

  • tool type string web_search_20250305
  • content blocks like server_tool_use and web_search_tool_result
  • deltas like input_json_delta with partial_json

If these are shimmed by Bifrost (vs. native Anthropic SDK types), consider adding a short comment in-test to clarify the expected provenance to reduce future SDK-upgrade confusion.

core/providers/anthropic/utils.go (2)

30-76: getRequestBodyForResponses: ensure ctx is guaranteed non-nil (pointer) or guard before ctx.Value(...).

If *schemas.BifrostContext can ever be nil, this will panic. Either enforce non-nil at callers or add a defensive nil check and treat “use raw body” as false when ctx is nil.


795-844: sanitizeWebSearchArguments docstring vs behavior mismatch; consider minor hardening for null/invalid types.

The current implementation only resolves conflicts when both allowed_domains and blocked_domains exist (good), and it preserves single-key empty arrays (also good per learnings). The comment currently reads like it also strips single-key empty arrays.

Optional: treat null / non-array values as “empty/invalid” when both keys are present to reduce provider-side validation errors.

core/schemas/responses.go (1)

1104-1276: Consider extracting a helper to reduce repetition.

The MarshalJSON implementation is correct, but the marshal→unmarshal→merge pattern is repeated 10 times. A helper function could reduce duplication:

♻️ Optional refactor using a helper
func mergeStructFields(result map[string]interface{}, v interface{}) error {
	if v == nil {
		return nil
	}
	bytes, err := Marshal(v)
	if err != nil {
		return err
	}
	var fields map[string]interface{}
	if err := Unmarshal(bytes, &fields); err != nil {
		return err
	}
	for k, val := range fields {
		result[k] = val
	}
	return nil
}

Then each case becomes:

case ResponsesToolTypeFunction:
	if err := mergeStructFields(result, t.ResponsesToolFunction); err != nil {
		return nil, err
	}
core/providers/anthropic/responses.go (7)

362-364: Silent failure on tool ID mismatch hinders debugging.

When state.WebSearchToolID doesn't match chunk.ContentBlock.ToolUseID, the code silently returns nil, nil, false. This makes diagnosing ordering issues or protocol mismatches difficult. Consider adding a warning log to make such mismatches visible.

Proposed logging addition
 			// If no matching tool ID, skip (shouldn't happen in normal flow)
+			// Log a warning to help diagnose unexpected mismatches
+			// logger.Warn("web_search_tool_result tool ID mismatch", "expected", state.WebSearchToolID, "got", chunk.ContentBlock.ToolUseID)
 			return nil, nil, false

668-688: Streaming citations: position indices remain uncomputed.

As noted in prior reviews, passing an empty string to convertAnthropicCitationToAnnotation means start_index and end_index won't be computed for web search citations in streaming mode. The comment at line 671 acknowledges this ("For streaming, we don't compute indices yet").

This is acceptable for initial implementation, but consider tracking accumulated text per content index (similar to existing TextContentIndices tracking) in a future iteration to achieve parity with the non-streaming path.


759-761: Web search completion condition could misfire.

The condition at line 760 checks state.WebSearchResult != nil && state.WebSearchToolID != nil without verifying that chunk.Index corresponds to the web search result block. If another content_block_stop arrives while web search state is present, this could prematurely trigger completion.

Consider adding an index check to ensure this fires only for the correct block:

Proposed additional guard
 		// Check if this is the end of a web_search_tool_result block
-		if state.WebSearchResult != nil && state.WebSearchToolID != nil {
+		if state.WebSearchResult != nil && state.WebSearchToolID != nil &&
+			chunk.Index != nil && state.WebSearchOutputIndex != nil {

1160-1164: Minor: Index preference inconsistent with other block types.

For web search calls, ContentIndex is preferred over OutputIndex (lines 1160-1164), while most other blocks prefer OutputIndex first (e.g., lines 1181-1185). This inconsistency could lead to subtle indexing bugs.

Consider aligning the preference order with other block types for consistency, unless there's a specific reason for this difference.


1318-1319: Silent error on JSON marshaling.

If json.Marshal fails at line 1318, the error is silently ignored and no deltas are generated. While unlikely to fail for a simple map, consider logging the error for debugging purposes.


1448-1449: Side effect: In-place modification of input parameter.

At line 1449, bifrostResp.Item.ResponsesToolMessage.Arguments is modified in place. This could cause unexpected behavior if the caller reuses the bifrostResp object after this function returns.

Consider either documenting this behavior or working with a copy of the arguments string.


4317-4329: URL substring search for index computation may be unreliable.

The current approach searches for the URL string within fullText to compute start_index and end_index (lines 4318-4329). However, URLs might not appear verbatim in the cited text, or might appear multiple times.

The fallback at lines 4325-4328 sets indices to the entire text range (0 to len), which is a safe default but loses precision.

Consider whether this is the intended behavior for web search citations, or if there's a different source for position information from the Anthropic API.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a02931 and 3697c69.

📒 Files selected for processing (25)
  • core/internal/testutil/account.go
  • core/internal/testutil/tests.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/azure/utils.go
  • core/providers/openai/openai_test.go
  • core/providers/openai/responses.go
  • core/providers/openai/types.go
  • core/providers/vertex/utils.go
  • core/providers/vertex/vertex.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/schemas/utils.go
  • tests/integrations/python/config.yml
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • tests/integrations/typescript/src/utils/common.ts
  • tests/integrations/typescript/tests/test-anthropic.test.ts
  • tests/integrations/typescript/tests/test-openai.test.ts
  • transports/bifrost-http/integrations/anthropic.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • core/providers/openai/openai_test.go
  • core/providers/vertex/vertex.go
  • core/internal/testutil/tests.go
  • core/providers/anthropic/anthropic_test.go
  • core/providers/anthropic/types.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/account.go
  • core/providers/azure/utils.go
  • tests/integrations/typescript/tests/test-openai.test.ts
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • tests/integrations/typescript/tests/test-anthropic.test.ts
  • core/internal/testutil/web_search_tool.go
  • tests/integrations/python/config.yml
  • core/providers/anthropic/utils.go
  • core/schemas/utils.go
  • core/providers/anthropic/responses.go
  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_anthropic.py
  • tests/integrations/typescript/src/utils/common.ts
🧠 Learnings (23)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/account.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/schemas/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2025-12-29T11:54:55.836Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 1153
File: framework/configstore/rdb.go:2221-2246
Timestamp: 2025-12-29T11:54:55.836Z
Learning: In Go reviews, do not flag range-over-int patterns like for i := range n as compile-time errors, assuming Go 1.22+ semantics. Only flag actual range-capable values (slices, arrays, maps, channels, strings) and other compile-time issues. This applies to all Go files across the repository.

Applied to files:

  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/account.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/schemas/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2026-01-14T04:40:11.480Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1312
File: framework/modelcatalog/pricing.go:276-426
Timestamp: 2026-01-14T04:40:11.480Z
Learning: In the Bifrost codebase, ImageUsage and other usage types guarantee that TotalTokens is populated (computed as InputTokens + OutputTokens if providers don’t supply TotalTokens). Reviewers can rely on this invariant and should not assume TotalTokens may be missing when input/output tokens exist. When implementing tiering logic or token-based decisions, you can safely use TotalTokens without extra null/zero guards, provided you’re in a context where InputTokens and OutputTokens are present. If a branch might discard tokens, ensure the invariant is preserved or add explicit checks only where the inputs are confirmed to be valid.

Applied to files:

  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/account.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/schemas/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2026-01-14T13:30:28.760Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: plugins/semanticcache/test_utils.go:545-559
Timestamp: 2026-01-14T13:30:28.760Z
Learning: In the maximhq/bifrost repository, prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths, including test utilities, to improve consistency and readability. Replace occurrences of &value where a *T is expected with bifrost.Ptr(value) (or an equivalent call) and ensure the function is in scope and used correctly for the target pointer type.

Applied to files:

  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/account.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • transports/bifrost-http/integrations/anthropic.go
  • core/providers/openai/responses.go
  • core/internal/testutil/web_search_tool.go
  • core/providers/anthropic/utils.go
  • core/schemas/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/responses.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/openai/responses.go
📚 Learning: 2025-12-14T14:43:30.902Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 980
File: core/providers/openai/images.go:10-22
Timestamp: 2025-12-14T14:43:30.902Z
Learning: Enforce the OpenAI image generation SSE event type values across the OpenAI image flow in the repository: use "image_generation.partial_image" for partial chunks, "image_generation.completed" for the final result, and "error" for errors. Apply this consistently in schemas, constants, tests, accumulator routing, and UI code within core/providers/openai (and related Go files) to ensure uniform event typing and avoid mismatches.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/openai/responses.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • core/providers/openai/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:40:42.227Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:40:42.227Z
Learning: When handling unsupported operations across providers, avoid hardcoding provider constants (e.g., schemas.Bedrock). Use the provider.GetProviderKey() (or equivalent API) to obtain the actual provider key from configuration, ensuring errors and messages adapt to custom provider names. Apply this pattern to all core/provider implementations (not just bedrock) to improve configurability and maintainability.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • core/providers/openai/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-10T11:27:47.535Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1256
File: core/providers/openai/openai.go:2276-2385
Timestamp: 2026-01-10T11:27:47.535Z
Learning: Validate image generation requests for nil and missing prompts before dispatch. Follow the same pattern used here: core/bifrost.go validates nil/empty prompts, providerUtils.CheckContextAndGetRequestBody returns a structured error when the request converter yields nil, and apply this across all providers (including OpenAI) to avoid sending null bodies.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • core/providers/openai/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T07:49:14.252Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1326
File: core/providers/bedrock/bedrock.go:1347-1355
Timestamp: 2026-01-15T07:49:14.252Z
Learning: In provider implementations, when raising unsupported-operation errors, pass provider.GetProviderKey() to NewUnsupportedOperationError so error messages and ExtraFields.Provider reflect the provider's alias. Apply this pattern consistently across all provider files (not just this one) to ensure accurate error context and branding.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • core/providers/openai/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T10:53:44.658Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1326
File: core/providers/gemini/gemini.go:1679-1754
Timestamp: 2026-01-14T10:53:44.658Z
Learning: Validate image generation inputs in core/bifrost.go before invoking any provider handler. Ensure in all provider implementations (e.g., core/providers/gemini/gemini.go) that the request and request.Input are non-nil before use, to prevent nil dereferences and provide clear error handling. Apply this invariant broadly to all providers and add tests for nil input scenarios.

Applied to files:

  • core/providers/openai/types.go
  • core/providers/azure/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/utils.go
  • core/providers/openai/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-17T08:44:08.788Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1114
File: core/schemas/chatcompletions.go:224-228
Timestamp: 2025-12-17T08:44:08.788Z
Learning: In core/schemas/chatcompletions.go, ensure the schema structures mirror OpenAI's API specifications exactly. Use the valid values for fields like ChatAudioParameters.Format and ChatAudioParameters.Voice as defined by OpenAI's documentation, and avoid adding additional inline documentation or constants to maintain direct compatibility with OpenAI's API.

Applied to files:

  • core/schemas/chatcompletions.go
📚 Learning: 2025-12-19T08:29:20.286Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1095
File: core/internal/testutil/count_tokens.go:30-67
Timestamp: 2025-12-19T08:29:20.286Z
Learning: In core/internal/testutil test files, enforce using GetTestRetryConfigForScenario() to obtain a generic retry config, then construct a typed retry config (e.g., CountTokensRetryConfig, EmbeddingRetryConfig, TranscriptionRetryConfig) with an empty Conditions slice. Copy only MaxAttempts, BaseDelay, MaxDelay, OnRetry, and OnFinalFail from the generic config. This convention should be consistently applied across all test files in this directory.

Applied to files:

  • core/internal/testutil/account.go
  • core/internal/testutil/web_search_tool.go
📚 Learning: 2026-01-11T14:08:10.341Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1298
File: core/providers/anthropic/anthropic.go:682-699
Timestamp: 2026-01-11T14:08:10.341Z
Learning: In Anthroplic streaming implementations (and analogous providers), ensure that the final 'summary' chunk, which carries usage information and metadata, is emitted after all delta chunks and uses a chunk index of last_delta_index + 1. This differentiates the summary chunk from content delta chunks. Apply this convention consistently in the anthropic provider code and in similar streaming providers, and consider adding a targeted test to assert the ordering and chunk index logic.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-13T13:36:35.221Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/responses.go:937-937
Timestamp: 2026-01-13T13:36:35.221Z
Learning: In core/providers/anthropic/responses.go, when handling Anthropic API streaming responses, ensure that content_block_start events include a signature field set to an empty string (e.g., contentBlock.Signature = ""). The actual signature is delivered later via signature_delta events. This behavior is per Anthropic's specification and should not be treated as an error. This guideline should apply to all Anthropic response handling files under core/providers/anthropic/ and similar go files that process streaming blocks.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: For Anthropic citation types (page_location, char_location, content_block_location), ensure there is an optional string field file_id to reference uploaded files. Update the Go structs modeling these citations to include FileID *string (or string with omitempty) and document its optionality in comments, so code consuming these types can handle absence of file_id gracefully.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-15T11:16:33.414Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1241
File: core/providers/anthropic/utils.go:801-874
Timestamp: 2026-01-15T11:16:33.414Z
Learning: In the Anthropic provider (core/providers/anthropic), allow empty arrays for allowed_domains and blocked_domains in the tool arguments JSON. Update or confirm sanitization logic so it does not strip empty arrays when only one of the domain filter fields is present (i.e., if the list is empty but the field is specified, keep it). This applies to code in core/providers/anthropic/utils.go (and related files in the same package if they share the same sanitization behavior). Ensure tests cover cases with empty arrays and with both fields omitted.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2026-01-14T06:57:42.750Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1319
File: core/providers/anthropic/types.go:248-336
Timestamp: 2026-01-14T06:57:42.750Z
Learning: In core/providers/anthropic/types.go, ensure the web_search_result_location citation type includes a string field named 'url' alongside the existing fields 'encrypted_index', 'title', and 'cited_text'. If the field is missing, add it with type string and appropriate struct tags (e.g., json and/or db tags) and update any related serialization or usage accordingly.

Applied to files:

  • core/providers/anthropic/anthropic.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2025-12-29T09:14:16.633Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: transports/bifrost-http/handlers/middlewares.go:246-256
Timestamp: 2025-12-29T09:14:16.633Z
Learning: In the bifrost HTTP transport, fasthttp.RequestCtx is the primary context carrier and should be passed directly to functions that expect a context.Context. Do not convert to context.Context unless explicitly required. Ensure tracer implementations and related components are designed to accept fasthttp.RequestCtx directly, and document this architectural decision for maintainers.

Applied to files:

  • transports/bifrost-http/integrations/anthropic.go
📚 Learning: 2026-01-14T04:32:14.023Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/config.yml:170-171
Timestamp: 2026-01-14T04:32:14.023Z
Learning: Guideline: In HuggingFace provider configuration, follow the provider-prefixed model path format fal-ai/<namespace>/<model> where the first segment fal-ai/ is the provider routing prefix and the remainder is the actual model path on fal.ai. Do not double-include the provider prefix. Example: fal-ai/flux/dev is the actual model path; when combined with the provider prefix you get fal-ai/fal-ai/flux/dev, which correctly indicates provider + model path.

Applied to files:

  • tests/integrations/python/config.yml
📚 Learning: 2026-01-12T06:41:13.643Z
Learnt from: TejasGhatte
Repo: maximhq/bifrost PR: 1153
File: core/schemas/utils.go:1159-1183
Timestamp: 2026-01-12T06:41:13.643Z
Learning: In core/schemas/utils.go, IsGrokReasoningModel (and the grokReasoningModels list) is the canonical utility for model matching. Treat it as the source of truth and not as a duplicate; if extending model support, update this file rather than introducing separate matching logic, and verify changes here to avoid diverging behavior.

Applied to files:

  • core/schemas/utils.go
📚 Learning: 2026-01-13T17:10:07.064Z
Learnt from: Radheshg04
Repo: maximhq/bifrost PR: 1312
File: tests/integrations/python/tests/test_openai.py:1166-1258
Timestamp: 2026-01-13T17:10:07.064Z
Learning: In tests under tests/integrations/python, prefer using the OpenAI image generation model 'gpt-image-1' via the config key providers.openai.image_generation for image-generation scenarios. This avoids DALLE-3 parameter limitations (e.g., n>1, quality/size combos). Ensure tests reference this provider in mocks/fixtures and document why this choice is used for test determinism.

Applied to files:

  • tests/integrations/python/tests/test_openai.py
  • tests/integrations/python/tests/utils/common.py
  • tests/integrations/python/tests/test_anthropic.py
🧬 Code graph analysis (12)
core/providers/openai/types.go (1)
core/schemas/responses.go (8)
  • Citations (422-424)
  • ResponsesMessageContentBlock (404-420)
  • ResponsesInputMessageContentBlockFile (430-435)
  • ResponsesOutputMessageContentText (446-449)
  • ResponsesToolMessage (485-505)
  • ResponsesWebSearchToolCallAction (691-698)
  • ResponsesWebSearchToolCallActionSearchSource (701-709)
  • ResponsesOutputMessageContentTextAnnotation (451-472)
core/schemas/chatcompletions.go (2)
core/schemas/responses.go (1)
  • Citations (422-424)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/providers/azure/utils.go (3)
core/schemas/context.go (1)
  • BifrostContext (32-43)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (30-37)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
tests/integrations/typescript/tests/test-openai.test.ts (2)
tests/integrations/typescript/src/utils/parametrize.ts (4)
  • getCrossProviderParamsWithVkForScenario (82-112)
  • ProviderModelVkParam (15-17)
  • shouldSkipNoProviders (141-143)
  • formatProviderModel (134-136)
tests/integrations/typescript/src/utils/common.ts (1)
  • assertValidOpenAIAnnotation (1165-1224)
core/providers/anthropic/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (1862-2018)
transports/bifrost-http/integrations/anthropic.go (1)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesResponse (2076-2137)
core/providers/openai/responses.go (1)
core/schemas/responses.go (4)
  • ResponsesToolComputerUsePreview (1538-1544)
  • ResponsesToolTypeWebSearch (1074-1074)
  • ResponsesToolWebSearch (1547-1554)
  • ResponsesToolWebSearchFilters (1557-1560)
core/internal/testutil/web_search_tool.go (4)
core/internal/testutil/utils.go (1)
  • CreateBasicResponsesMessage (276-284)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/internal/testutil/test_retry_framework.go (4)
  • TestRetryContext (174-179)
  • WithResponsesTestRetry (443-593)
  • ResponsesRetryConfig (212-219)
  • ResponsesRetryCondition (138-141)
core/internal/testutil/test_retry_conditions.go (2)
  • ResponsesEmptyCondition (989-989)
  • ResponsesGenericResponseCondition (1061-1061)
core/schemas/utils.go (1)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessageAnnotationCitation (816-823)
core/providers/anthropic/responses.go (4)
core/providers/anthropic/types.go (3)
  • AnthropicContentBlock (227-247)
  • AnthropicContentBlockType (209-209)
  • AnthropicContent (158-161)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/responses.go (3)
  • ResponsesMessage (319-332)
  • ResponsesToolMessage (485-505)
  • Citations (422-424)
core/schemas/bifrost.go (1)
  • BifrostContextKeyUserAgent (141-141)
tests/integrations/python/tests/test_openai.py (2)
tests/integrations/python/tests/utils/common.py (1)
  • assert_valid_openai_annotation (2752-2818)
tests/integrations/python/tests/utils/parametrize.py (2)
  • get_cross_provider_params_with_vk_for_scenario (50-101)
  • format_provider_model (126-141)
tests/integrations/python/tests/utils/common.py (1)
tests/integrations/typescript/src/utils/common.ts (2)
  • CITATION_TEXT_DOCUMENT (956-963)
  • CITATION_MULTI_DOCUMENT_SET (966-977)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_openai.py

2900-2900: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2927-2927: f-string without any placeholders

Remove extraneous f prefix

(F541)


3037-3037: f-string without any placeholders

Remove extraneous f prefix

(F541)


3049-3049: f-string without any placeholders

Remove extraneous f prefix

(F541)


3096-3096: f-string without any placeholders

Remove extraneous f prefix

(F541)


3098-3098: f-string without any placeholders

Remove extraneous f prefix

(F541)


3100-3100: f-string without any placeholders

Remove extraneous f prefix

(F541)


3102-3102: f-string without any placeholders

Remove extraneous f prefix

(F541)


3144-3144: f-string without any placeholders

Remove extraneous f prefix

(F541)


3151-3151: f-string without any placeholders

Remove extraneous f prefix

(F541)


3193-3193: f-string without any placeholders

Remove extraneous f prefix

(F541)


3248-3248: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integrations/python/tests/utils/common.py

2686-2686: Avoid specifying long messages outside the exception class

(TRY003)


2771-2771: Do not assign a lambda expression, use a def

Rewrite getter as a def

(E731)


2772-2772: Do not assign a lambda expression, use a def

Rewrite has as a def

(E731)


2950-2950: Avoid specifying long messages outside the exception class

(TRY003)

tests/integrations/python/tests/test_anthropic.py

1783-1783: Unused method argument: test_config

(ARG002)


1839-1839: Unused method argument: test_config

(ARG002)


1895-1895: Unused method argument: test_config

(ARG002)


1904-1904: Loop control variable idx not used within loop body

Rename unused idx to _idx

(B007)


1964-1964: f-string without any placeholders

Remove extraneous f prefix

(F541)


1971-1971: Unused method argument: test_config

(ARG002)


2025-2025: Unused method argument: test_config

(ARG002)


2079-2079: Unused method argument: test_config

(ARG002)


2142-2142: Local variable has_citations is assigned to but never used

Remove assignment to unused variable has_citations

(F841)


2160-2160: f-string without any placeholders

Remove extraneous f prefix

(F541)


2163-2163: Unused method argument: test_config

(ARG002)


2201-2201: Local variable search_queries is assigned to but never used

Remove assignment to unused variable search_queries

(F841)


2254-2254: Local variable has_citation_delta is assigned to but never used

Remove assignment to unused variable has_citation_delta

(F841)


2292-2292: Unused method argument: test_config

(ARG002)


2342-2342: f-string without any placeholders

Remove extraneous f prefix

(F541)


2345-2345: Unused method argument: test_config

(ARG002)


2398-2398: f-string without any placeholders

Remove extraneous f prefix

(F541)


2401-2401: Unused method argument: test_config

(ARG002)


2430-2430: f-string without any placeholders

Remove extraneous f prefix

(F541)


2464-2464: f-string without any placeholders

Remove extraneous f prefix

(F541)


2467-2467: Unused method argument: test_config

(ARG002)


2511-2511: f-string without any placeholders

Remove extraneous f prefix

(F541)


2513-2513: f-string without any placeholders

Remove extraneous f prefix

(F541)


2515-2515: f-string without any placeholders

Remove extraneous f prefix

(F541)


2518-2518: Unused method argument: test_config

(ARG002)


2560-2560: f-string without any placeholders

Remove extraneous f prefix

(F541)


2565-2565: f-string without any placeholders

Remove extraneous f prefix

(F541)


2574-2574: f-string without any placeholders

Remove extraneous f prefix

(F541)


2577-2577: Unused method argument: test_config

(ARG002)


2647-2647: f-string without any placeholders

Remove extraneous f prefix

(F541)


2650-2650: Unused method argument: test_config

(ARG002)


2696-2696: f-string without any placeholders

Remove extraneous f prefix

(F541)


2698-2698: Do not catch blind exception: Exception

(BLE001)


2702-2702: f-string without any placeholders

Remove extraneous f prefix

(F541)


2705-2705: Unused method argument: test_config

(ARG002)


2746-2746: f-string without any placeholders

Remove extraneous f prefix

(F541)


2754-2754: f-string without any placeholders

Remove extraneous f prefix

(F541)


2757-2757: Unused method argument: test_config

(ARG002)


2807-2807: f-string without any placeholders

Remove extraneous f prefix

(F541)


2809-2809: f-string without any placeholders

Remove extraneous f prefix

(F541)


2811-2811: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

akshaydeo commented Jan 15, 2026

Merge activity

  • Jan 15, 2:12 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jan 15, 2:15 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo changed the base branch from 01-09-fix_claude_code_openai to graphite-base/1241 January 15, 2026 14:15
@akshaydeo akshaydeo changed the base branch from graphite-base/1241 to main January 15, 2026 14:15
@akshaydeo akshaydeo merged commit 26af9bd into main Jan 15, 2026
4 checks passed
@akshaydeo akshaydeo deleted the 01-05-fix_anthropic_type_citations branch January 15, 2026 14:15
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