Skip to content

Conversation

@TejasGhatte
Copy link
Collaborator

@TejasGhatte TejasGhatte commented Jan 13, 2026

Summary

Improves Anthropic and Gemini provider compatibility by fixing index handling and enhancing thought signature support.

Changes

  • Anthropic: Prioritize OutputIndex over ContentIndex for consistent global indexing
  • Anthropic: Add missing signature fields to thinking content blocks
  • Anthropic: Add proper stop reason handling in message_stop events
  • Anthropic: Enhance citations structure to support multiple citation types
  • Gemini: Improve thought signature handling by embedding signatures in tool IDs
  • Cohere: Fix AdditionalProperties handling in tool function parameters
  • Schemas: Add support for complex AdditionalProperties structures (both boolean and object 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

Test Anthropic API compatibility with Claude 3 models:

# Test Anthropic provider changes
go test ./core/providers/anthropic/...

# Test Gemini provider changes
go test ./core/providers/gemini/...

# Run all tests
go test ./...

Test with real API calls to verify proper index handling and thought signature preservation.

Breaking changes

  • Yes
  • No

Related issues

Fixes issues with Anthropic API compatibility and Gemini thought signature handling.

Security considerations

No security implications. Changes focus on API compatibility and data structure handling.

Checklist

  • I added/updated tests where appropriate
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Warning

Rate limit exceeded

@TejasGhatte has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 38 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 363249d and ee628de.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/changelog.md
📝 Walkthrough

Walkthrough

Adds thought/signature extraction/propagation for Gemini and Anthropic, WebSearch argument sanitization and synthetic delta handling, changes JSON schema AdditionalProperties to a union struct (bool or ordered map), and extends Anthropic citation types and content fields with custom JSON marshal/unmarshal.

Changes

Cohort / File(s) Summary
Anthropic: responses & types
core/providers/anthropic/responses.go, core/providers/anthropic/types.go
Add public Signature on Anthropic content blocks; propagate signature through thinking/redacted_thinking and tool paths; add URL, EncryptedContent, PageAge; replace legacy citation config with AnthropicCitations and new citation types plus custom JSON marshalling.
Anthropic: websearch delta & utils
core/providers/anthropic/responses.go, core/providers/anthropic/utils.go
Track WebSearch tool item IDs for delta suppression, sanitize WebSearch arguments (remove blocked_domains when allowed_domains present), emit synthetic input_json_delta events using OutputIndex→ContentIndex fallback, emit content_block_stop on completion and clear tracking.
Anthropic: chat minor
core/providers/anthropic/chat.go
Replace literal "tool_result" with AnthropicContentBlockTypeToolResult constant; no behavioral change.
Gemini: signatures & reasoning propagation
core/providers/gemini/responses.go, core/providers/gemini/chat.go, core/providers/gemini/types.go, core/providers/gemini/utils.go
Introduce _ts_ separator parsing for CallIDs, extract/base64-decode thought signatures, add Part.ThoughtSignature []byte, attach signatures to emitted function-call IDs/parts, convert thought text into reasoningDetails and add Encrypted reasoning entries.
Schemas: AdditionalProperties & params
core/schemas/chatcompletions.go, core/schemas/responses.go
Replace AdditionalProperties *bool with *AdditionalPropertiesStruct (union of AdditionalPropertiesBool *bool or AdditionalPropertiesMap *OrderedMap); add marshal/unmarshal logic and integrate into ToolFunctionParameters and ResponsesTextConfig schemas.
Provider utils: additionalProperties handling
core/providers/cohere/utils.go, core/providers/gemini/utils.go, core/providers/anthropic/utils.go
When building JSON schemas or converting params, support boolean or object additionalProperties by populating schemas.AdditionalPropertiesStruct (bool → AdditionalPropertiesBool, map → AdditionalPropertiesMap via SafeExtractOrderedMap).
Tests & internal testutil
core/internal/testutil/structured_outputs.go, core/providers/gemini/gemini_test.go, tests/integrations/python/tests/test_google.py
Update tests/test utilities to use AdditionalPropertiesStruct; add multiple Gemini‑3 integration tests validating thought signatures, parallel/sequential function calls, thinking levels, and signature validation behavior.
Changelogs & minor cleanup
core/changelog.md, transports/changelog.md, transports/bifrost-http/integrations/router.go
Expand changelog entries and minor whitespace/router cleanup.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Provider
  participant ResponsesConverter
  participant FunctionExecutor
  participant StreamEmitter

  Client->>Provider: send content / tool call (CallID may include "_ts_"+base64)
  Provider->>ResponsesConverter: deliver content, CallID, tool args (may include WebSearch args)
  ResponsesConverter->>ResponsesConverter: parse CallID, extract/decode thoughtSignature -> Part.ThoughtSignature
  ResponsesConverter->>FunctionExecutor: emit function call (base CallID, signature in metadata / embedded)
  ResponsesConverter->>ResponsesConverter: sanitize WebSearch args, record webSearchItemIDs, synthesize input_json_delta if applicable
  FunctionExecutor-->>ResponsesConverter: return result (tool output, StopReason/indices)
  ResponsesConverter->>StreamEmitter: emit deltas/stops with Signature and indices (OutputIndex preferred, ContentIndex fallback)
  StreamEmitter->>Client: deliver enriched stream events
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
I tuck a signature in every tiny hop,
citations nestle where the indexes stop.
Bool or map — schemas stretch and bend,
WebSearch hushes, then deltas mend.
A nibble, a hop — the stream hops on.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: claude code fixes' is vague and generic; it lacks specific details about what code issues are being addressed in the changeset. Replace with a more descriptive title that clearly identifies the main fix, such as 'fix: improve Anthropic/Gemini provider compatibility with better index handling and signature support'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers the main changes, type of change, affected areas, and testing guidance. It follows most sections of the template but omits the 'Related issues' hyperlinks and lacks detailed 'How to test' steps.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% 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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 01-09-fix_claude_code_openai

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 13, 2026

@TejasGhatte TejasGhatte changed the title fix: claude code openai fix: claude code fixes Jan 13, 2026
@TejasGhatte TejasGhatte marked this pull request as ready for review January 13, 2026 10:19
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 (3)
core/providers/gemini/responses.go (1)

1048-1094: State tracking uses different ID than emitted events.

The state maps (ItemIDs, ToolCallIDs) are populated at lines 1056-1057 with the original toolUseID before the thought signature is appended (lines 1069-1073). However, the emitted events use the modified toolUseID with the signature appended.

This inconsistency means state.ItemIDs[outputIndex] won't match the ItemID in the emitted events, which could cause issues in closeGeminiOpenItems (line 1393) when it looks up itemID := state.ItemIDs[outputIndex].

🔧 Proposed fix - update state after signature attachment
 	state.ItemIDs[outputIndex] = toolUseID
 	state.ToolCallIDs[outputIndex] = toolUseID
 	state.ToolCallNames[outputIndex] = part.FunctionCall.Name

 	// Convert args to JSON string
 	argsJSON := ""
 	if part.FunctionCall.Args != nil {
 		if argsBytes, err := sonic.Marshal(part.FunctionCall.Args); err == nil {
 			argsJSON = string(argsBytes)
 		}
 	}
 	state.ToolArgumentBuffers[outputIndex] = argsJSON

 	// Attach thought signature to ID if present
 	if len(part.ThoughtSignature) > 0 {
 		encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
 		toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+		// Update state maps with the signature-appended ID
+		state.ItemIDs[outputIndex] = toolUseID
+		state.ToolCallIDs[outputIndex] = toolUseID
 	}
core/providers/anthropic/responses.go (2)

731-734: Fix potential nil deref on chunk.Delta in message_delta handling.

if chunk.Delta.StopReason != nil { ... } will panic if chunk.Delta == nil (you already treat it as nullable later in the same block).

Proposed diff
 case AnthropicStreamEventTypeMessageDelta:
-	if chunk.Delta.StopReason != nil {
+	if chunk.Delta != nil && chunk.Delta.StopReason != nil {
 		state.StopReason = schemas.Ptr(ConvertAnthropicFinishReasonToBifrost(*chunk.Delta.StopReason))
 	}

898-924: OutputIndex-first mapping is consistent; ensure synthetic deltas use the same chosen index.

The OutputIndex fallback logic is good, but generateSyntheticInputJSONDeltas(..., bifrostResp.ContentIndex) will reintroduce ContentIndex-based indexing for the synthetic deltas, desyncing them from the content_block_start index when OutputIndex is present.

Proposed diff
-			if shouldGenerateDeltas && argumentsJSON != "" {
+			if shouldGenerateDeltas && argumentsJSON != "" {
 				// Generate synthetic input_json_delta events by chunking the JSON
-				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+				index := bifrostResp.OutputIndex
+				if index == nil {
+					index = bifrostResp.ContentIndex
+				}
+				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, index)
 				events = append(events, deltaEvents...)
 			}

Also applies to: 1057-1063, 1073-1078, 1094-1099, 1128-1133, 1156-1161, 1198-1203, 1220-1225

🤖 Fix all issues with AI agents
In @core/providers/anthropic/responses.go:
- Around line 988-989: The code is incorrectly populating the tool-result
correlation into the tool_use-specific field ToolUseID; remove the assignment to
contentBlock.ToolUseID and instead set the block's standard id field used for
tool_use blocks (e.g., contentBlock.ID =
bifrostResp.Item.ResponsesToolMessage.CallID) while keeping contentBlock.Name
as-is, so you only emit the canonical id for tool_use blocks and avoid emitting
a separate tool_use_id.

In @core/providers/gemini/responses.go:
- Around line 469-489: The streaming branch currently assigns the full CallID
(including the thought signature) to functionCall.ID; change it to mirror the
non-streaming path by splitting bifrostResp.Item.ResponsesToolMessage.CallID on
thoughtSignatureSeparator, set functionCall.ID to the base ID (parts[0]) and
store the signature portion (parts[1]) only in functionCallPart.ThoughtSignature
(after base64 decoding) so functionCall.ID matches the non-streaming path; keep
the existing behavior of creating functionCallPart := &Part{ FunctionCall:
functionCall } and appending to candidate.Content.Parts.

In @core/schemas/chatcompletions.go:
- Around line 340-359: The UnmarshalJSON for AdditionalPropertiesStruct must
explicitly handle JSON null to avoid creating a non-nil pointer to a nil map; in
AdditionalPropertiesStruct.UnmarshalJSON check for the literal JSON null (e.g.,
bytes.Equal(data, []byte("null")) or equivalent) and if so set
AdditionalPropertiesBool = nil and AddtionalPropertiesMap = nil and return nil;
otherwise proceed with the existing boolean then map unmarshal attempts. Ensure
you clear both fields on null so MarshalJSON remains symmetric.
- Around line 312-315: The struct AdditionalPropertiesStruct has a typo in the
exported field AddtionalPropertiesMap — rename it to AdditionalPropertiesMap
(preserving pointer type *OrderedMap) and update all internal references to the
corrected name, including the methods MarshalJSON (where the field is
read/assigned) and UnmarshalJSON (where it’s populated), and any external usages
such as core/providers/cohere/utils.go; ensure you keep JSON
marshaling/unmarshaling behavior identical after the rename and run tests to
catch any missed references.

In @transports/bifrost-http/integrations/router.go:
- Around line 386-387: Remove the stray debug print in router.go that prints
"New request started" with fmt.Printf; replace it with a structured debug log
using the router's logger or delete it entirely. Specifically, remove the
fmt.Printf call near the request handling code and, if request-level logging is
desired, call g.logger.Debug with context (e.g., "New request started", "path",
string(ctx.Path()), "method", method, and any request ID) so logging uses
g.logger rather than stdout and includes useful context.
🧹 Nitpick comments (3)
core/providers/gemini/responses.go (1)

2388-2414: Minor: Dead code assignment.

Line 2412 (thoughtSig = "") has no effect since thoughtSig is never used after this point. This can be safely removed.

🧹 Suggested cleanup
 				if thoughtSig != "" {
 					var err error
 					part.ThoughtSignature, err = base64.StdEncoding.DecodeString(thoughtSig)
 					if err != nil {
 						// Silently ignore decode errors - ID will be used without signature
-						thoughtSig = ""
 					}
 				}
core/providers/cohere/utils.go (1)

76-80: Consider handling the object case for additionalProperties.

The new AdditionalPropertiesStruct supports both boolean and object types, but only the boolean case is handled here. If Cohere can return additionalProperties as an object (schema definition), it won't be captured.

♻️ Suggested enhancement to handle both cases
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
+	} else if orderedAddProps, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
+			AddtionalPropertiesMap: &orderedAddProps,
+		}
 	}
core/providers/anthropic/types.go (1)

248-334: Harden AnthropicCitations (un)marshal against reuse + make marshal receiver value-based.

Two small robustness tweaks:

  • In UnmarshalJSON, clear ac.Config when setting TextCitations and clear ac.TextCitations when setting Config.
  • Consider making MarshalJSON a value receiver (func (ac AnthropicCitations) MarshalJSON) so it’s honored even if a non-pointer instance is marshaled later.
Proposed diff
-// Custom marshal/unmarshal methods
-func (ac *AnthropicCitations) MarshalJSON() ([]byte, error) {
+// Custom marshal/unmarshal methods
+func (ac AnthropicCitations) MarshalJSON() ([]byte, error) {
 	if ac.Config != nil && ac.TextCitations != nil {
 		return nil, fmt.Errorf("AnthropicCitations: both Config and TextCitations are set; only one should be non-nil")
 	}
@@
 	return sonic.Marshal(nil)
 }
 
 func (ac *AnthropicCitations) UnmarshalJSON(data []byte) error {
+	// Defensive reset (helps if the same instance is reused)
+	ac.Config = nil
+	ac.TextCitations = nil
+
 	// Try to unmarshal as array of citations
 	var textCitations []AnthropicTextCitation
 	if err := sonic.Unmarshal(data, &textCitations); err == nil {
 		ac.TextCitations = textCitations
 		return nil
 	}
@@
 	var config schemas.Citations
 	if err := sonic.Unmarshal(data, &config); err == nil {
 		ac.Config = &config
 		return nil
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f9996 and c31cae0.

📒 Files selected for processing (8)
  • anthropic-event-order.txt
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • transports/bifrost-http/integrations/router.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/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/integrations/router.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
🧠 Learnings (8)
📚 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/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/integrations/router.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • 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/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/integrations/router.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • 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/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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-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/router.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/router.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/responses.go
  • core/providers/anthropic/types.go
🧬 Code graph analysis (4)
core/providers/cohere/utils.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/gemini/responses.go (3)
core/schemas/responses.go (1)
  • ResponsesToolMessage (475-495)
ui/lib/types/logs.ts (2)
  • ResponsesToolMessage (419-425)
  • FunctionCall (165-168)
core/providers/gemini/types.go (3)
  • Part (1027-1051)
  • FunctionCall (1233-1243)
  • FunctionResponse (1246-1268)
core/providers/anthropic/responses.go (2)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (3)
  • AnthropicContentBlockTypeToolUse (207-207)
  • AnthropicStreamDelta (517-525)
  • AnthropicStreamEvent (496-505)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (699-702)
core/schemas/responses.go (1)
  • Citations (421-423)
⏰ 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). (4)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (9)
core/providers/gemini/responses.go (4)

15-16: LGTM - Clear constant definition for thought signature separator.

The separator constant is well-documented. The implementation gracefully handles decode failures by falling back to the full ID, which mitigates the edge case where a user-defined tool ID might contain _ts_.


249-298: LGTM - Robust thought signature extraction with fallback logic.

The extraction logic correctly handles:

  1. Signatures embedded in CallID with graceful decode failure handling
  2. Fallback to look-ahead for reasoning messages
  3. Tracking consumed indices to avoid double-processing

The error handling on decode failure (lines 263-265) appropriately falls back to the full ID.


1878-1882: LGTM - Consistent thought signature encoding for non-streaming responses.

The encoding format matches the streaming path, ensuring consistent ID format across both response paths.


2452-2463: LGTM - Correct base ID extraction for function responses.

The function response correctly strips the thought signature from the CallID, ensuring proper matching with the original function call's base ID.

core/schemas/chatcompletions.go (2)

276-282: LGTM!

The ToolFunctionParameters struct update correctly integrates the new AdditionalPropertiesStruct type, enabling support for both boolean and object representations of additionalProperties in JSON schemas.


317-338: LGTM!

The MarshalJSON implementation correctly handles the mutually exclusive nature of the bool/map representation with proper validation and fallback to null when both are nil.

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

219-237: New web_search_result fields look consistent; verify they’re only emitted for that block type.

URL, EncryptedContent, and PageAge are correctly omitempty, but ensure your converters don’t accidentally populate them for non-web_search_result blocks (some clients validate by type).

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

937-945: Signature on content_block_start: verify Anthropic clients expect signature:"" (vs omitted).

Setting Signature = Ptr("") forces "signature":"" into the event (since it’s omitempty but pointer is non-nil). If the goal is “field present but empty until signature_delta,” confirm downstream parsers don’t interpret empty signature as “final signature is empty” / invalid.

Also applies to: 953-958, 981-985

core/schemas/responses.go (1)

421-423: The type Citations definition is unique in the package and has no compilation issues.

The original concern about a "compile-blocker" is incorrect. Go allows a type named Citations to coexist with struct fields named Citations []string without conflict, as they exist in different namespaces. The new Citations struct is already in use in core/providers/anthropic/types.go (e.g., Config *schemas.Citations), confirming intentional design.

A doc comment clarifying this struct's purpose (e.g., "// Citations configuration for citation handling") would be helpful for maintainability, but this is a minor suggestion, not a compilation concern.

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch 2 times, most recently from 1ce1ee8 to 91e06c6 Compare January 13, 2026 13:09
@TejasGhatte TejasGhatte force-pushed the 01-12-feat_send_back_raw_request_with_bifrost_errors branch from 28f9996 to 1cb5e03 Compare January 13, 2026 13:09
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/gemini/responses.go (1)

1040-1064: Fix inconsistent tool call IDs: state maps store original ID while emitted events use augmented ID.

The state.ItemIDs[outputIndex] is set at line 1047 with the original toolUseID, but at line 1063 toolUseID is augmented with the base64-encoded ThoughtSignature. The emitted events at lines 1069-1102 (both output_item.added and output_item.function_call_arguments_done) use the augmented ID. However, when closeGeminiOpenItems retrieves the ID from state.ItemIDs[outputIndex] at line 1384, it gets the original unaugmented ID and emits the final output_item.done with this original ID. This causes the same tool call to have different IDs across its lifecycle—augmented in initial events, original in the final done event—breaking ID correlation on the client side.

🤖 Fix all issues with AI agents
In @core/providers/anthropic/responses.go:
- Line 937: Remove the empty signature initialization on content_block_start
events: do not set contentBlock.Signature = schemas.Ptr("") in the
content_block_start handling (so the Signature field is nil/omitted), since
signature metadata is delivered via signature_delta events; ensure only
signature_delta handling (e.g., the existing signature_delta logic around
signature_delta event handling) sets contentBlock.Signature, and remove or guard
any assignment that forces an empty string pointer for ContentBlock.Signature to
prevent `"signature": ""` from being serialized.
🧹 Nitpick comments (4)
core/providers/cohere/utils.go (1)

75-80: Consider handling object-type additionalProperties for completeness.

The boolean case is correctly wrapped in the new AdditionalPropertiesStruct. However, the schema now also supports object-type additionalProperties via AdditionalPropertiesMap. If Cohere's API ever sends additionalProperties as an object (e.g., {"type": "string"}), it would be silently ignored.

♻️ Suggested enhancement
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
+	} else if addPropsMap, ok := paramsMap["additionalProperties"].(map[string]interface{}); ok {
+		orderedMap := schemas.OrderedMap(addPropsMap)
+		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
+			AdditionalPropertiesMap: &orderedMap,
+		}
 	}
core/providers/gemini/responses.go (2)

15-17: Consider documenting the separator collision risk.

The separator _ts_ could theoretically collide with tool IDs that legitimately contain this substring. While SplitN(..., 2) ensures only the first occurrence is used for splitting, a tool named my_ts_tool would be incorrectly parsed if a thought signature is appended.

Consider documenting this constraint or using a more unique separator (e.g., ::ts:: or a control character if the ID format permits).


2397-2404: Consider debug logging for base64 decode failures.

When base64.StdEncoding.DecodeString fails, the error is silently swallowed. While graceful degradation is appropriate, a debug-level log would help troubleshoot cases where thought signatures are unexpectedly lost.

♻️ Optional: Add debug logging
 if thoughtSig != "" {
     var err error
     part.ThoughtSignature, err = base64.StdEncoding.DecodeString(thoughtSig)
     if err != nil {
-        // Silently ignore decode errors - ID will be used without signature
-        thoughtSig = ""
+        // Log decode error for debugging, ID will be used without signature
+        // log.Debug().Err(err).Str("thoughtSig", thoughtSig).Msg("failed to decode thought signature")
+        part.ThoughtSignature = nil
     }
 }
core/providers/anthropic/types.go (1)

304-334: UnmarshalJSON order may cause incorrect parsing for empty config objects.

The current UnmarshalJSON implementation tries to unmarshal as []AnthropicTextCitation first (line 321), then falls back to schemas.Citations config (line 328). However, an empty JSON object {} would successfully unmarshal as an empty schemas.Citations struct, but it would also fail to unmarshal as an array, so the current order works.

The potential issue is with the config object: schemas.Citations has only an optional Enabled *bool field. If Anthropic sends {"enabled": true}, this correctly becomes a config. But if the response contains an empty array [], this would unmarshal as an empty TextCitations slice rather than treating it as "no citations" - which may be the intended behavior but is worth verifying.

Consider adding a nil/empty check or documentation clarifying the expected behavior for edge cases.

📝 Optional: Add clarifying comment for unmarshal order rationale
 func (ac *AnthropicCitations) UnmarshalJSON(data []byte) error {
-	// Try to unmarshal as array of citations
+	// Try to unmarshal as array of citations first (response format)
+	// Note: Empty array [] will result in empty TextCitations slice
 	var textCitations []AnthropicTextCitation
 	if err := sonic.Unmarshal(data, &textCitations); err == nil {
 		ac.TextCitations = textCitations
 		return nil
 	}
 
-	// Try to unmarshal as config object first
+	// Fall back to config object (request format: {"enabled": true})
 	var config schemas.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 c31cae0 and 1ce1ee8.

📒 Files selected for processing (7)
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • transports/bifrost-http/integrations/router.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/schemas/responses.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/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/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/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/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/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/types.go
🧬 Code graph analysis (5)
core/schemas/chatcompletions.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)
core/providers/anthropic/responses.go (3)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (1)
  • AnthropicStreamDelta (517-525)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (705-708)
core/schemas/responses.go (1)
  • Citations (421-423)
core/providers/cohere/utils.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/gemini/responses.go (2)
core/schemas/responses.go (2)
  • ResponsesToolMessage (475-495)
  • ResponsesReasoning (745-748)
core/providers/gemini/types.go (3)
  • Part (1027-1051)
  • FunctionCall (1233-1243)
  • Content (1013-1021)
⏰ 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 (14)
core/schemas/chatcompletions.go (2)

276-282: LGTM!

The field type change correctly supports both boolean and object representations for additionalProperties, aligning with JSON Schema specification. The custom JSON marshalling ensures backward compatibility with existing JSON payloads.


312-365: Well-structured union type with proper JSON handling.

The implementation correctly handles:

  • Mutual exclusivity validation in MarshalJSON
  • Direct marshaling of bool/object without wrapper (maintains JSON compatibility)
  • Graceful null handling in UnmarshalJSON
  • Clear error messages when input is neither boolean nor object
core/providers/gemini/responses.go (5)

249-289: LGTM!

The thought signature extraction logic correctly prioritizes embedded signatures in CallID over lookahead reasoning messages. The consumedIndices map properly prevents double-processing, and error handling gracefully degrades when decoding fails.


460-480: LGTM!

The streaming path correctly mirrors the non-streaming extraction logic, maintaining consistency in how thought signatures are parsed from CallIDs.


1869-1886: LGTM!

The non-streaming Gemini-to-Bifrost conversion correctly encodes and appends the thought signature to the CallID, maintaining consistency with the streaming path in processGeminiFunctionCallPart.


2442-2449: LGTM!

The FunctionResponse ID assignment correctly uses the CallID for proper matching between function calls and their responses.


2388-2396: Gemini API accepts arbitrary ID strings—no format validation concern.

The Gemini API FunctionCall.ID field accepts arbitrary strings with no documented format requirements. The only constraint is that if an ID is provided, the client must echo the same ID in the response for matching. Keeping the full CallID with the embedded _ts_base64signature suffix will not cause validation failures or issues with the Gemini API.

Likely an incorrect or invalid review comment.

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

898-924: LGTM - Consistent index handling in computer tool and text content blocks.

The pattern of prioritizing OutputIndex with ContentIndex as fallback is correctly applied for both computer tool calls (lines 898-902) and other content blocks (lines 920-924). This aligns with the PR objective of consistent global indexing for Anthropic.


1056-1062: LGTM - OutputTextDelta index handling updated correctly.

The fallback pattern and inline comment clearly document the rationale for preferring OutputIndex over ContentIndex.


1197-1224: LGTM - MCP delta and completion handlers follow consistent index pattern.

Both MCPCallArgumentsDelta (lines 1197-1202) and MCPCallCompleted (lines 1219-1224) correctly apply the OutputIndex-first, ContentIndex-fallback pattern.


1169-1192: Confirm the JSON serialization behavior of StopSequence in AnthropicStreamDelta responses.

The asymmetry is real: when bifrostResp.Response.StopReason is nil, StopSequence is set to an empty string pointer (""), but when a stop reason is present, it's set to nil. Since AnthropicStreamDelta.StopSequence is defined as *string with json:"stop_sequence" (no omitempty), these serialize differently—empty string becomes "stop_sequence": "" while nil becomes "stop_sequence": null. Verify this mixed behavior matches Anthropic's expected response format.

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

218-238: LGTM - AnthropicContentBlock expanded with web search and metadata fields.

The new fields (URL, EncryptedContent, PageAge) properly support web search result content blocks. The existing Signature field placement is appropriate for thinking content. Field documentation via inline comments is clear.


248-291: LGTM - Comprehensive citation type definitions.

The AnthropicTextCitation struct properly supports all five citation location types with their respective fields:

  • char_location: StartCharIndex, EndCharIndex
  • page_location: StartPageNumber, EndPageNumber
  • content_block_location: StartBlockIndex, EndBlockIndex
  • web_search_result_location: EncryptedIndex, Title, URL
  • search_result_location: SearchResultIndex, Source

The use of pointer types for optional fields and clear inline documentation makes the intent clear.


305-307: The field design is sound as-is.

The Citations field is correctly tagged with json:"citations,omitempty", so when the AnthropicCitations value itself is nil, the field is omitted entirely from the JSON output. When AnthropicCitations is non-nil but both Config and TextCitations are nil, MarshalJSON returns null, which is acceptable behavior. The validation check preventing both fields from being set simultaneously is appropriate.

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: 4

Caution

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

⚠️ Outside diff range comments (3)
core/providers/gemini/responses.go (2)

496-501: Bug: OutputItemAdded thought signature uses raw base64 string bytes instead of decoding.
This will produce a different ThoughtSignature than the non-stream path (and than other stream paths that decode).

Proposed fix
 case schemas.ResponsesStreamResponseTypeOutputItemAdded:
-	if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.EncryptedContent != nil {
-		candidate.Content.Parts = append(candidate.Content.Parts, &Part{
-			ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
-		})
-	}
+	if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.ResponsesReasoning.EncryptedContent != nil {
+		if decoded, err := base64.StdEncoding.DecodeString(*bifrostResp.Item.ResponsesReasoning.EncryptedContent); err == nil {
+			candidate.Content.Parts = append(candidate.Content.Parts, &Part{
+				ThoughtSignature: decoded,
+			})
+		}
+	}

1060-1103: Bug: stream state stores the pre-signature tool ID, but emitted events use the post-signature ID.
closeGeminiOpenItems() later emits output_item.done using state.ItemIDs[outputIndex], which currently won’t match the earlier output_item.added / function_call_arguments.done IDs when a thought signature exists.

Proposed fix
 	toolUseID := part.FunctionCall.ID
 	if toolUseID == "" {
 		toolUseID = part.FunctionCall.Name // Fallback to name as ID
 	}

-	state.ItemIDs[outputIndex] = toolUseID
-	state.ToolCallIDs[outputIndex] = toolUseID
-	state.ToolCallNames[outputIndex] = part.FunctionCall.Name
+	state.ToolCallNames[outputIndex] = part.FunctionCall.Name

 	// Convert args to JSON string
 	argsJSON := ""
 	if part.FunctionCall.Args != nil {
 		if argsBytes, err := sonic.Marshal(part.FunctionCall.Args); err == nil {
 			argsJSON = string(argsBytes)
 		}
 	}
 	state.ToolArgumentBuffers[outputIndex] = argsJSON

 	// Attach thought signature to ID if present
 	if len(part.ThoughtSignature) > 0 {
 		encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
 		toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
 	}
+
+	// Store the final ID (must match emitted events)
+	state.ItemIDs[outputIndex] = toolUseID
+	state.ToolCallIDs[outputIndex] = toolUseID
core/schemas/chatcompletions.go (1)

276-308: Add unit tests for ToolFunctionParameters.UnmarshalJSON with stringified JSON parameters.

The implementation now supports xAI-style stringified JSON parameters ("parameters": "{...json...}") in addition to standard JSON objects. This is a behavior change that should be covered by tests including:

  • Valid stringified JSON input → parsed correctly
  • Invalid/malformed stringified JSON → error handling

Note: additionalProperties correctly supports both boolean and object (schema) per JSON Schema spec, which the AdditionalPropertiesStruct implementation handles. OpenAI requires additionalProperties: false (boolean) for Structured Outputs; xAI accepts stringified parameters.

🤖 Fix all issues with AI agents
In @core/providers/anthropic/types.go:
- Around line 248-334: When unmarshalling in AnthropicCitations.UnmarshalJSON,
clear the mutually exclusive field before setting the other to avoid stale state
(i.e., set ac.Config = nil before assigning TextCitations and set
ac.TextCitations = nil before assigning Config); apply the same fix to
AdditionalPropertiesStruct.UnmarshalJSON for its mutually exclusive fields so
MarshalJSON's exclusivity contract can't be violated by reused struct instances.

In @core/providers/cohere/utils.go:
- Around line 75-80: The current block only handles boolean additionalProperties
and drops object-valued cases; extend it to also call SafeExtractOrderedMap on
paramsMap["additionalProperties"] when it isn't a bool, convert the returned
ordered map into the same schema map type you use for `properties` (reuse the
conversion logic used around the `properties` extraction), and set
result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
AdditionalPropertiesMap: <convertedMap> } so both boolean and object forms are
supported alongside the existing AdditionalPropertiesBool handling.

In @core/providers/gemini/responses.go:
- Around line 15-17: The code uses base64.StdEncoding to encode thought
signatures for Gemini tool IDs which yields illegal characters (+, /, =) and
will break IDs; update the encoding to a URL-safe variant such as
base64.RawURLEncoding (or base64.URLEncoding) wherever the thought signature is
encoded (look for calls that produce the signature string paired with
thoughtSignatureSeparator) so the resulting tool ID matches
/^[a-zA-Z0-9_-]{1,64}$/; ensure padding is removed (use RawURLEncoding) or
otherwise sanitize/replace forbidden characters consistently so all generated
tool IDs conform to Gemini constraints.

In @core/schemas/chatcompletions.go:
- Around line 312-365: UnmarshalJSON on AdditionalPropertiesStruct must clear
the opposite field when one variant is decoded to avoid stale state; update
AdditionalPropertiesStruct.UnmarshalJSON so that when the input is "null" both
AdditionalPropertiesBool and AdditionalPropertiesMap are set to nil, when a
boolean is successfully decoded set AdditionalPropertiesBool to the value and
set AdditionalPropertiesMap = nil, and when a map (OrderedMap) is successfully
decoded set AdditionalPropertiesMap to the value and set
AdditionalPropertiesBool = nil; ensure the function still returns an error only
if neither decode succeeds.
🧹 Nitpick comments (4)
core/schemas/responses.go (1)

421-423: Add a doc comment for exported Citations and clarify intent (request-config vs response citations).
This is an exported schema type and is easy to confuse with existing Citations []string fields and annotation citations.

Proposed tweak
+// Citations represents provider-specific citations configuration (e.g., enabling citations in downstream requests).
 type Citations struct {
 	Enabled *bool `json:"enabled,omitempty"`
 }
core/providers/gemini/responses.go (3)

249-289: Non-stream thought-signature extraction + “consume next reasoning message” looks coherent; add a regression test for ordering.
Specifically: function_call followed by reasoning(encrypted_content), and the variant where signature is embedded in CallID.


460-480: Streaming path: signature parsing is OK, but keep the encoding consistent with the producer.
Right now this assumes the CallID always embeds a base64.StdEncoding payload; if you later switch to URL-safe encoding, this must be updated everywhere.


1869-1873: CallID augmentation in non-stream output path matches streaming intent; watch for ID bloat.
If ThoughtSignature is large, embedding it into CallID can produce very large IDs; consider a size cap / fallback strategy.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce1ee8 and 91e06c6.

📒 Files selected for processing (7)
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • transports/bifrost-http/integrations/router.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.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/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.go
  • 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/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.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/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.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-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/gemini/responses.go
  • core/providers/cohere/utils.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/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/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/types.go
  • core/providers/anthropic/responses.go
🧬 Code graph analysis (2)
core/providers/cohere/utils.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/anthropic/responses.go (4)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (4)
  • AnthropicStreamDelta (517-525)
  • AnthropicStopReasonEndTurn (433-433)
  • AnthropicStreamEvent (496-505)
  • AnthropicStreamEventTypeContentBlockDelta (488-488)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/schemas/responses.go (1)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1420-1420)
⏰ 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). (1)
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (9)
core/providers/gemini/responses.go (1)

2380-2404: Good: Responses→Gemini parses signature from CallID but keeps the full ID for round-tripping.
One tweak: if decode succeeds, consider also validating you got non-empty bytes (to avoid treating "" as a real signature).

Also applies to: 2446-2447

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

219-237: Fields are valid and correctly implemented per Anthropic API.

The url, encrypted_content, and page_age fields are confirmed as valid Anthropic API fields for web_search_result content blocks. All are correctly declared as optional (*string with omitempty tags) and the JSON tag names match the specification exactly.

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

898-902: LGTM! OutputIndex prioritization is correctly implemented.

The change properly prefers OutputIndex for global Anthropic indexing with ContentIndex as a fallback, ensuring consistent index handling for computer tool content blocks.


919-937: LGTM! Consistent index handling and signature initialization.

The OutputIndex-first pattern is correctly applied, and initializing Signature to an empty string for thinking blocks ensures proper field presence in the Anthropic streaming response.


946-993: LGTM! Signature field propagated through all thinking block paths.

The signature initialization is consistently applied across:

  • Line 953: Misclassified FunctionCall that contains reasoning content
  • Line 983: First block when thinking is enabled

This ensures metadata integrity for encrypted thinking content across all edge cases.


1054-1098: LGTM! Consistent OutputIndex-first pattern across all delta handlers.

The index handling is uniformly applied to:

  • OutputTextDelta (lines 1056-1061)
  • FunctionCallArgumentsDelta (lines 1072-1076)
  • ReasoningSummaryTextDelta (lines 1093-1097)

All include proper fallback to ContentIndex when OutputIndex is not available.


1127-1161: LGTM! OutputItemDone handlers consistently updated.

Both the computer tool path (lines 1127-1131) and the general content block path (lines 1155-1159) now use the same OutputIndex-first pattern with ContentIndex fallback.


1165-1192: LGTM! Proper stop reason handling in message_stop events.

The implementation correctly:

  1. Sets default StopReason to end_turn with an empty StopSequence pointer (lines 1169-1172)
  2. Overrides with the converted Bifrost stop reason when available, with StopSequence: nil (lines 1187-1189)

This aligns with Anthropic's API specification where stop_sequence is present but empty for end_turn and may be omitted for other stop reasons.


1194-1224: LGTM! MCP handlers consistently updated with OutputIndex-first pattern.

Both MCPCallArgumentsDelta (lines 1197-1201) and MCPCallCompleted (lines 1219-1223) follow the same index selection logic as other handlers, ensuring consistent global indexing for MCP tool calls.

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from 91e06c6 to 3dfb95c Compare January 13, 2026 13:40
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/schemas/chatcompletions.go:
- Around line 276-282: The type of AdditionalProperties changed from *bool to
*AdditionalPropertiesStruct; update all places that pass a *bool to instead
allocate an AdditionalPropertiesStruct with its AdditionalPropertiesBool field
set to the existing *bool (e.g. replace schemas.Ptr(additionalProps) or
&additionalProps with &AdditionalPropertiesStruct{AdditionalPropertiesBool:
schemas.Ptr(additionalProps)}), specifically in the helper constructing
ToolFunctionParameters.AdditionalProperties in core/providers/gemini/utils.go
(current call creating schemas.Ptr(additionalProps)), in the test at
core/providers/gemini/gemini_test.go (replace schemas.Ptr(false) with
&AdditionalPropertiesStruct{AdditionalPropertiesBool: schemas.Ptr(false)}), in
core/providers/anthropic/utils.go (wrap &additionalProps into
&AdditionalPropertiesStruct{AdditionalPropertiesBool: &additionalProps} or
schemas.Ptr), and in core/internal/testutil/structured_outputs.go at the two
locations mentioned; keep the new struct wrapping consistent with
core/providers/cohere/utils.go.
🧹 Nitpick comments (3)
core/providers/gemini/responses.go (2)

15-16: Consider documenting the separator choice and potential collision risks.

The separator _ts_ is embedded in tool IDs to carry thought signatures. If a legitimate tool ID contains _ts_, it could be incorrectly parsed as having a signature.

Consider either:

  1. Using a more unique separator (e.g., __bifrost_ts__)
  2. Adding validation that the signature part is valid base64 before treating it as a signature

2380-2404: Silent error handling for signature decode is appropriate here.

The code silently ignores base64 decode errors (lines 2399-2402), which is reasonable since an invalid signature shouldn't break the entire conversion. The comment at line 2401 documents this behavior.

However, consider logging at debug level for troubleshooting purposes.

core/schemas/chatcompletions.go (1)

336-338: Consider returning []byte("null") directly instead of Marshal(nil).

For consistency with other marshal methods in this file (e.g., line 401) and to avoid an unnecessary function call:

♻️ Suggested minor optimization
 	// If both are nil, return null
-	return Marshal(nil)
+	return []byte("null"), nil
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce1ee8 and 91e06c6.

📒 Files selected for processing (7)
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • transports/bifrost-http/integrations/router.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/providers/cohere/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/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/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/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.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/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/schemas/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/responses.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/types.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/responses.go
  • core/providers/anthropic/types.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
🧬 Code graph analysis (2)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (705-708)
core/schemas/responses.go (1)
  • Citations (421-423)
core/providers/gemini/responses.go (2)
core/schemas/responses.go (3)
  • ResponsesToolMessage (475-495)
  • ResponsesMessageTypeReasoning (312-312)
  • ResponsesReasoning (745-748)
core/providers/gemini/types.go (4)
  • Part (1027-1051)
  • FunctionCall (1233-1243)
  • Type (822-822)
  • Content (1013-1021)
⏰ 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). (1)
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (25)
core/providers/gemini/responses.go (6)

249-289: Thought signature extraction and look-ahead logic is well-structured.

The implementation correctly:

  1. Extracts signatures from CallID using the separator
  2. Falls back to look-ahead for reasoning messages with encrypted content
  3. Tracks consumed indices to avoid duplicate processing

One consideration: the look-ahead at line 277 only checks i+1, but if there are multiple function calls in sequence, each would only look at its immediate next message. This appears intentional for matching function calls with their associated reasoning.


460-480: Streaming path correctly mirrors the batch path for thought signature extraction.

The signature extraction logic is consistent with the non-streaming path, using the same separator and base64 decoding approach.


1060-1085: Thought signature attachment to function call IDs is correctly implemented.

The logic properly encodes the signature and appends it to the tool use ID using the defined separator. The augmented ID is then propagated to both the addedEvent and doneEvent.


1086-1102: LGTM for done event emission with augmented tool ID.

The stream response correctly includes the augmented toolUseID in both the ItemID and nested CallID fields, ensuring consistency for downstream consumers.


1869-1873: Batch response path correctly attaches thought signature to callID.

This mirrors the streaming path logic, ensuring consistency between batch and streaming responses when converting Gemini candidates to Responses output.


2446-2446: Full CallID with thought signature is intentionally preserved for FunctionResponse.ID.

At line 2446, the assignment of the full CallID (including the _ts_ separator and base64 signature) to FunctionResponse.ID is deliberate, as documented by the explicit comment at line 2391: // Keep the full CallID as-is (don't strip thought signature). The thought signature is extracted separately (lines 2386-2388) for downstream uses, while the complete ID is propagated through FunctionResponse.ID consistently. The Gemini API should handle this augmented ID format as designed.

core/schemas/chatcompletions.go (1)

312-365: Well-implemented union type for AdditionalPropertiesStruct.

The implementation correctly:

  1. Validates mutual exclusivity in MarshalJSON (line 322-324)
  2. Handles null input in UnmarshalJSON (lines 343-347)
  3. Tries boolean first, then map, which matches JSON schema semantics
  4. Returns a clear error message when neither type matches (line 364)

One minor note: the empty line at 320 before the validation is unnecessary but doesn't affect functionality.

core/schemas/responses.go (1)

421-423: New Citations type added for citation control.

This is a clean, minimal struct following the existing patterns in this file. The omitempty tag ensures the field is excluded from JSON when nil.

Based on the PR summary, this type supports the enhanced citations structure for Anthropic provider compatibility.

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

898-902: LGTM!

The OutputIndex-first indexing with ContentIndex fallback is correctly implemented for computer tool handling, consistent with the PR objective to prioritize OutputIndex for global Anthropic indexing.


919-924: LGTM!

Consistent OutputIndex-first indexing pattern for text and other content blocks.


937-937: LGTM!

Correctly initializes the signature field to an empty string for thinking blocks in content_block_start events, per Anthropic's specification. The actual signature will be delivered via signature_delta events. Based on learnings, this is the expected behavior.


953-953: LGTM!

Consistent empty signature initialization for thinking blocks that were misclassified as function calls.


983-983: LGTM!

Consistent empty signature initialization for the first-block reasoning case.


1056-1062: LGTM!

OutputIndex-first indexing correctly applied to OutputTextDelta with helpful inline comment.


1072-1077: LGTM!

Consistent OutputIndex-first indexing for FunctionCallArgumentsDelta.


1093-1098: LGTM!

Consistent OutputIndex-first indexing for ReasoningSummaryTextDelta.


1127-1132: LGTM!

Consistent OutputIndex-first indexing for computer tool OutputItemDone events.


1155-1160: LGTM!

Consistent OutputIndex-first indexing for content_block_stop events.


1197-1202: LGTM!

Consistent OutputIndex-first indexing for MCPCallArgumentsDelta.


1219-1224: LGTM!

Consistent OutputIndex-first indexing for MCPCallCompleted events.


1169-1192: Verify StopSequence field handling consistency.

The default case initializes StopSequence to an empty string (Line 1171), but when StopReason is explicitly provided from the response (Line 1189), StopSequence is set to nil. Since the type definition for AnthropicStreamDelta.StopSequence has no omitempty tag, these will serialize differently in JSON (empty string vs. null).

Without access to Anthropic API specifications or test cases defining the expected behavior, it's unclear if this inconsistency is intentional. If both cases should produce the same output format, consider standardizing to use schemas.Ptr("") consistently.

♻️ Suggested fix for consistency
 		if bifrostResp.Response.StopReason != nil {
 			anthropicContentDeltaEvent.Delta = &AnthropicStreamDelta{
 				StopReason:   schemas.Ptr(ConvertBifrostFinishReasonToAnthropic(*bifrostResp.Response.StopReason)),
-				StopSequence: nil,
+				StopSequence: schemas.Ptr(""),
 			}
 		}
core/providers/anthropic/types.go (4)

219-238: LGTM!

The new fields (Signature, URL, EncryptedContent, PageAge) are correctly added to AnthropicContentBlock with appropriate json tags and omitempty. These align with the PR objectives for thought signature support and enhanced citation handling.


248-256: LGTM!

The AnthropicCitationType enum covers all citation location types supported by the Anthropic API.


258-291: LGTM!

Well-structured union type for AnthropicTextCitation that cleanly handles multiple citation formats with helpful comments indicating field applicability.


293-334: LGTM!

The AnthropicCitations union type with custom marshal/unmarshal logic correctly handles both request format (config object) and response format (citation array). The mutual exclusivity validation in MarshalJSON and the order of attempts in UnmarshalJSON (array first, then object) are appropriate for the expected usage patterns.

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch 2 times, most recently from 903a34e to a994828 Compare January 13, 2026 14:06
@TejasGhatte TejasGhatte force-pushed the 01-12-feat_send_back_raw_request_with_bifrost_errors branch from 1cb5e03 to 5888ee9 Compare January 13, 2026 16:47
@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from a994828 to 8a99f70 Compare January 13, 2026 16:47
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 (7)
core/providers/gemini/chat.go (1)

360-368: Streaming path doesn't associate ThoughtSignature with tool call ID.

In the non-streaming path (lines 186-198), when handling ThoughtSignature for a FunctionCall, the code sets reasoningDetail.ID = schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)).

In the streaming path here, ThoughtSignature handling doesn't check if the part has a FunctionCall and doesn't set the corresponding ID. This inconsistency could cause issues downstream when correlating signatures with tool calls.

🐛 Suggested fix to match non-streaming behavior
 			// Handle thought signature separately (not part of the switch since it can co-exist with other types)
 			if part.ThoughtSignature != nil {
 				thoughtSig := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
-				reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
+				reasoningDetail := schemas.ChatReasoningDetails{
 					Index:     len(reasoningDetails),
 					Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
 					Signature: &thoughtSig,
-				})
+				}
+
+				// Associate with tool call if present
+				if part.FunctionCall != nil {
+					callID := part.FunctionCall.Name
+					if part.FunctionCall.ID != "" {
+						callID = part.FunctionCall.ID
+					}
+					if strings.Contains(callID, thoughtSignatureSeparator) {
+						parts := strings.SplitN(callID, thoughtSignatureSeparator, 2)
+						if len(parts) == 2 {
+							callID = parts[0]
+						}
+					}
+					reasoningDetail.ID = schemas.Ptr(fmt.Sprintf("tool_call_%s", callID))
+				}
+
+				reasoningDetails = append(reasoningDetails, reasoningDetail)
 			}
core/providers/gemini/responses.go (4)

246-287: Avoid double-encoding and unintended overwrites when deriving ThoughtSignature from CallID.
Right now you (a) keep functionCall.ID = callID (possibly already containing the signature) and (b) may later append the signature again in other conversion paths. Also, the lookahead reasoning message overwrites ThoughtSignature even if you successfully extracted one from the CallID.

Proposed fix (guard overwrites + avoid double-append patterns)
-                        // Use thought signature from CallID if we extracted one
-                        if len(thoughtSignature) > 0 {
+                        // Use thought signature from CallID if we extracted one
+                        if len(thoughtSignature) > 0 {
                             part.ThoughtSignature = thoughtSignature
                         } else {
                             // Otherwise, look ahead to see if the next message is a reasoning message with encrypted content
                             // (thought signature for this function call)
                             if i+1 < len(bifrostResp.Output) {
                                 nextMsg := bifrostResp.Output[i+1]
                                 if nextMsg.Type != nil && *nextMsg.Type == schemas.ResponsesMessageTypeReasoning &&
                                     nextMsg.ResponsesReasoning != nil && nextMsg.ResponsesReasoning.EncryptedContent != nil {
                                     decodedSig, err := base64.StdEncoding.DecodeString(*nextMsg.ResponsesReasoning.EncryptedContent)
                                     if err == nil {
                                         part.ThoughtSignature = decodedSig
                                         // Mark this reasoning message as consumed
                                         consumedIndices[i+1] = true
                                     }
                                 }
                             }
                         }
-                    // Preserve thought signature from ResponsesReasoning message (required for Gemini 3 Pro)
-                    // Look ahead to see if the next message is a reasoning message with encrypted content
-                    if i+1 < len(messages) {
+                    // Preserve thought signature from ResponsesReasoning message (required for Gemini 3 Pro)
+                    // Only if we don't already have one (e.g., extracted from CallID).
+                    if len(part.ThoughtSignature) == 0 && i+1 < len(messages) {
                         nextMsg := messages[i+1]
                         if nextMsg.Type != nil && *nextMsg.Type == schemas.ResponsesMessageTypeReasoning &&
                             nextMsg.ResponsesReasoning != nil && nextMsg.ResponsesReasoning.EncryptedContent != nil {
                             decodedSig, err := base64.StdEncoding.DecodeString(*nextMsg.ResponsesReasoning.EncryptedContent)
                             if err == nil {
                                 part.ThoughtSignature = decodedSig
                             }
                         }
                     }

-->


493-499: BUG: OutputItemAdded reasoning signature path likely doesn’t compile and mishandles base64.

  • bifrostResp.Item.EncryptedContent doesn’t match the schema shape shown elsewhere (encrypted content lives under bifrostResp.Item.ResponsesReasoning.EncryptedContent).
  • Even if it compiled, []byte(base64String) is not decoded bytes.
Proposed fix
 case schemas.ResponsesStreamResponseTypeOutputItemAdded:
-    if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.EncryptedContent != nil {
-        candidate.Content.Parts = append(candidate.Content.Parts, &Part{
-            ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
-        })
-    }
+    if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.ResponsesReasoning.EncryptedContent != nil {
+        if decodedSig, err := base64.StdEncoding.DecodeString(*bifrostResp.Item.ResponsesReasoning.EncryptedContent); err == nil {
+            candidate.Content.Parts = append(candidate.Content.Parts, &Part{
+                ThoughtSignature: decodedSig,
+            })
+        }
+    }

-->


1057-1100: Streaming state inconsistency: state.ItemIDs/ToolCallIDs store pre-signature ID, but events use post-signature ID.
This can cause later close/finalization (output_item.done) to reference a different ID than output_item.added/arguments.done.

Proposed fix (compute final ID first, then store in state)
-    toolUseID := part.FunctionCall.ID
+    toolUseID := part.FunctionCall.ID
     if toolUseID == "" {
         toolUseID = part.FunctionCall.Name // Fallback to name as ID
     }
 
-    state.ItemIDs[outputIndex] = toolUseID
-    state.ToolCallIDs[outputIndex] = toolUseID
-    state.ToolCallNames[outputIndex] = part.FunctionCall.Name
-
     // Convert args to JSON string
     argsJSON := ""
     if part.FunctionCall.Args != nil {
         if argsBytes, err := sonic.Marshal(part.FunctionCall.Args); err == nil {
             argsJSON = string(argsBytes)
         }
     }
     state.ToolArgumentBuffers[outputIndex] = argsJSON
 
     // Attach thought signature to ID if present
     if len(part.ThoughtSignature) > 0 {
         encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
-        toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+        // Avoid double-append if caller already embedded it.
+        if !strings.Contains(toolUseID, thoughtSignatureSeparator) {
+            toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+        }
     }
+
+    // Now that toolUseID is finalized, store it in state
+    state.ItemIDs[outputIndex] = toolUseID
+    state.ToolCallIDs[outputIndex] = toolUseID
+    state.ToolCallNames[outputIndex] = part.FunctionCall.Name

-->


2377-2414: Don’t overwrite decoded signature from CallID with lookahead unless required.
The lookahead currently clobbers part.ThoughtSignature even when thoughtSig decoded successfully, which can make the ID/signature pair inconsistent.

(See earlier diff suggestion to gate the lookahead with len(part.ThoughtSignature) == 0.)

-->

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

1043-1047: Synthetic input_json deltas likely use the wrong index after OutputIndex-first migration.
You now set streamResp.Index from OutputIndex first, but synthetic deltas still use bifrostResp.ContentIndex, which can desync delta events from the block they belong to.

Proposed fix (pass the same index you used for content_block_start)
-            if shouldGenerateDeltas && argumentsJSON != "" {
+            if shouldGenerateDeltas && argumentsJSON != "" {
                 // Generate synthetic input_json_delta events by chunking the JSON
-                deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+                deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, streamResp.Index)
                 events = append(events, deltaEvents...)
             }

-->

Also applies to: 3844-3877

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

38-49: Header docstring test case numbering is inconsistent with actual test methods.

The header lists test cases 28-40, but after renaming test_33_file_delete to test_34_file_delete, the numbering no longer aligns. Additionally, there's now a duplicate test_34 (both file_delete and batch_create_with_file). Consider updating the header to reflect the actual test case structure.

🤖 Fix all issues with AI agents
In @core/providers/gemini/responses.go:
- Around line 1866-1871: The code appends a base64 thought signature to callID
unconditionally, which can produce duplicate signatures like "..._ts_sig_ts_sig"
if callID already contains thoughtSignatureSeparator; change the logic in the
block that references callID, part.ThoughtSignature and
thoughtSignatureSeparator to only append the signature when
part.ThoughtSignature is non-empty AND callID does not already contain
thoughtSignatureSeparator (e.g., use strings.Contains(callID,
thoughtSignatureSeparator) to guard the append), and ensure strings is imported
if needed.
- Around line 457-477: The code decodes thoughtSig with base64.StdEncoding which
fails for URL-safe variants; update the logic around thoughtSig (extracted from
bifrostResp.Item.ResponsesToolMessage.CallID using thoughtSignatureSeparator) to
normalize URL-safe base64 before decoding: replace '-' with '+' and '_' with '/'
(or alternatively use base64.URLEncoding), add '=' padding so len%4==0, then
call DecodeString and set functionCallPart.ThoughtSignature on success (same
target: the block that currently calls base64.StdEncoding.DecodeString and
assigns functionCallPart.ThoughtSignature).

In @tests/integrations/python/tests/test_google.py:
- Around line 2118-2131: The test assigns response_3 from
client.models.generate_content (using conversation_without_sig and
calculator_tool) but never uses it, and the two print statements use f-strings
without placeholders; either use response_3 in an assertion/log or remove the
variable assignment to avoid unused variable warnings, and convert the print
calls to regular strings (e.g., remove the leading f) or include actual
placeholders like {response_3} if you intend to log the response; update the
code around the response_3 assignment and the two print lines accordingly.
- Around line 2012-2013: The test method name
test_30e_gemini_3_signature_validation_strict and its docstring disagree
(docstring says "Test Case 30f"); fix by making them consistent: either rename
the method to test_30f_gemini_3_signature_validation_strict if the test is
actually case 30f, or update the docstring to "Test Case 30e: Gemini 3 - Strict
validation of thought signatures in function calling" to match the existing
method name; update any references in test suite metadata if you choose to
rename the method.
- Around line 2291-2293: The test function name test_34_file_delete collides
with test_34_batch_create_with_file; revert the rename by renaming
test_34_file_delete back to test_33_file_delete (or alternatively renumber the
subsequent batch tests test_34_batch_create_with_file...test_39_* to start at
35) so each test function has a unique identifier; locate the function
definition for test_34_file_delete and update its name (and any references) to
test_33_file_delete, or update the names of test_34_batch_create_with_file and
following batch test functions to increment by one.
🧹 Nitpick comments (10)
core/changelog.md (1)

6-6: Improve entry clarity.

The phrase "through integration" is vague. Based on the PR summary, the Anthropic changes include:

  • Prioritizing OutputIndex over ContentIndex for consistent global indexing
  • Adding missing signature fields to thinking content blocks
  • Adding proper stop reason handling in message_stop events

Consider making this entry more specific about which streaming aspect was fixed, for example:

  • fix: anthropic streaming events index handling and signature fields
  • fix: anthropic streaming events metadata and stop reason handling
core/providers/anthropic/types.go (1)

318-336: Consider reversing unmarshal order for clarity.

The UnmarshalJSON tries to unmarshal as an array of citations first (line 321), then as a config object (line 328). While this works because arrays and objects are syntactically distinct in JSON, the order is counterintuitive given the comment on lines 294-295 that describes request format (config) first, then response format (array).

This is a minor readability concern and not a functional issue since JSON parsing distinguishes between arrays and objects unambiguously.

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

1145-1155: Consider using else if for mutually exclusive branches.

The two conditions are mutually exclusive (bool vs map extraction), but using separate if statements obscures this intent. An else if would make the mutual exclusivity explicit.

♻️ Suggested refactor
 	// Extract additionalProperties
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/cohere/utils.go (1)

76-86: Consider using else if for mutually exclusive branches.

Same pattern as in gemini/utils.go - the bool and map extraction are mutually exclusive, so else if would make the intent clearer.

♻️ Suggested refactor
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/providers/anthropic/utils.go (1)

768-778: Consider using else if for mutually exclusive branches.

Consistent with the pattern in gemini/utils.go and cohere/utils.go - the bool and map extraction are mutually exclusive.

♻️ Suggested refactor
 		if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesBool: &additionalProps,
 			}
-		}
-
-		if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+		} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesMap: &additionalProps,
 			}
 		}
tests/integrations/python/tests/test_google.py (5)

1749-1750: Remove extraneous f-prefix from string literal.

Line 1750 has an f-string without any placeholders.

Suggested fix
                 if hasattr(part, 'thought_signature') and part.thought_signature:
                     signatures.append((idx, len(part.thought_signature)))
                     print(f"    → Has signature ({len(part.thought_signature)} bytes)")
                 else:
-                    print(f"    → No signature")
+                    print("    → No signature")

1894-1899: Remove extraneous f-prefix from string literals.

Lines 1895 and 1898 have f-strings without any placeholders.

Suggested fix
         # Should have either a second function call with signature or text response
         if second_fc:
-            print(f"\n✓ Sequential function calling: Step 2 has function call with signature")
+            print("\n✓ Sequential function calling: Step 2 has function call with signature")
             assert second_signature is not None, "Second function call should also have thought signature"
         else:
-            print(f"\n✓ Model provided text response after first function call")
+            print("\n✓ Model provided text response after first function call")
             assert has_text, "Should have text response"

1941-1955: Remove extraneous f-prefix from string literals.

Lines 1942 and 1954 have f-strings without any placeholders.

Suggested fix
-        print(f"\n  Summary:")
+        print("\n  Summary:")
         print(f"    Thought parts: {thought_parts}")
         print(f"    Text parts: {text_parts}")
         print(f"    Signatures on parts: {signatures_found}")
         
         # According to docs: signature should be in the last part (when thinking is enabled)
         if len(signatures_found) > 0:
             last_part_idx = len(parts) - 1
             # Signature might be on the last part or on a thought part
             print(f"    Last part index: {last_part_idx}")
             print(f"✓ Found {len(signatures_found)} thought signature(s) in text response")
         else:
-            print(f"  Note: No explicit thought signatures found (may be internal)")
+            print("  Note: No explicit thought signatures found (may be internal)")

1999-2008: Remove extraneous f-prefix from string literals.

Lines 2001 and 2007 have f-strings without any placeholders.

Suggested fix
             # Minimal should have very few or no thinking tokens
             if level == "minimal":
-                print(f"    ✓ Minimal thinking level (expected: minimal/no thinking)")
+                print("    ✓ Minimal thinking level (expected: minimal/no thinking)")
             elif level == "high":
                 # High should have substantial thinking
                 if thought_tokens > 0:
                     print(f"    ✓ High thinking level with {thought_tokens} thought tokens")
                 else:
-                    print(f"    ! High thinking but no thought tokens reported")
+                    print("    ! High thinking but no thought tokens reported")

2092-2093: Remove extraneous f-prefix from string literal.

Suggested fix
             assert response_2.candidates, "Request with signature should succeed"
-            print(f"    ✓ Request WITH signature succeeded")
+            print("    ✓ Request WITH signature succeeded")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91e06c6 and 8a99f70.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.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/gemini/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/gemini_test.go
  • transports/changelog.md
  • core/providers/anthropic/types.go
  • core/schemas/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/structured_outputs.go
  • core/changelog.md
  • core/schemas/chatcompletions.go
  • tests/integrations/python/tests/test_google.py
🧠 Learnings (9)
📚 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/gemini/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/schemas/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.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/gemini/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/schemas/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/responses.go
  • core/internal/testutil/structured_outputs.go
  • 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/gemini/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/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/gemini/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/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/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: 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/gemini/gemini_test.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/structured_outputs.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
🧬 Code graph analysis (10)
core/providers/gemini/chat.go (3)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (857-865)
  • BifrostReasoningDetailsTypeText (853-853)
  • BifrostReasoningDetailsTypeEncrypted (852-852)
core/schemas/utils.go (1)
  • Ptr (14-16)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (127-134)
core/providers/gemini/responses.go (2)
core/schemas/responses.go (3)
  • ResponsesToolMessage (475-495)
  • ResponsesMessageTypeReasoning (312-312)
  • ResponsesReasoning (745-748)
core/providers/gemini/types.go (4)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • Type (825-825)
  • Content (1016-1024)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (707-710)
core/schemas/responses.go (1)
  • Citations (421-423)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/internal/testutil/structured_outputs.go (2)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/chatcompletions.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)
tests/integrations/python/tests/test_google.py (2)
tests/integrations/python/tests/utils/common.py (1)
  • skip_if_no_api_key (1830-1841)
core/providers/gemini/types.go (4)
  • Tool (647-674)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
🪛 Ruff (0.14.10)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1750-1750: f-string without any placeholders

Remove extraneous f prefix

(F541)


1768-1768: Unused method argument: test_config

(ARG002)


1895-1895: f-string without any placeholders

Remove extraneous f prefix

(F541)


1898-1898: f-string without any placeholders

Remove extraneous f prefix

(F541)


1902-1902: Unused method argument: test_config

(ARG002)


1942-1942: f-string without any placeholders

Remove extraneous f prefix

(F541)


1954-1954: f-string without any placeholders

Remove extraneous f prefix

(F541)


1957-1957: Unused method argument: test_config

(ARG002)


2001-2001: f-string without any placeholders

Remove extraneous f prefix

(F541)


2007-2007: f-string without any placeholders

Remove extraneous f prefix

(F541)


2012-2012: Unused method argument: test_config

(ARG002)


2093-2093: f-string without any placeholders

Remove extraneous f prefix

(F541)


2095-2095: Do not catch blind exception: Exception

(BLE001)


2120-2120: Local variable response_3 is assigned to but never used

Remove assignment to unused variable response_3

(F841)


2129-2129: f-string without any placeholders

Remove extraneous f prefix

(F541)


2130-2130: f-string without any placeholders

Remove extraneous f prefix

(F541)


2132-2132: Do not catch blind exception: Exception

(BLE001)


2292-2292: Unused method argument: test_config

(ARG002)

⏰ 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). (7)
  • 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 (22)
core/changelog.md (2)

1-2: Verify changelog scope: entries may be from other PRs in the stack.

Lines 1-2 don't appear to match the scope of PR #1319 as described in the PR summary (which focuses on Anthropic/Gemini/Cohere provider fixes). These entries may belong to other PRs in the Graphite stack (e.g., the target branch name 01-12-feat_send_back_raw_request_with_bifrost_errors suggests line 2 could be from PR #1313 or the base branch).

Please verify:

  • Are these entries intentionally included to represent cumulative stack changes?
  • Or should each PR maintain separate changelog entries?

3-5: LGTM!

These changelog entries accurately reflect the core fixes in this PR for schema AdditionalProperties handling, Gemini thought signatures, and Anthropic citations structure.

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

22-23: LGTM!

The new thoughtSignatureSeparator constant is clearly documented and provides a consistent separator for embedding thought signatures in tool IDs. The choice of "_ts_" as the separator is reasonable and unlikely to conflict with typical ID formats.

transports/changelog.md (1)

1-6: LGTM!

The changelog entries accurately summarize the key changes in this PR stack, covering the AdditionalProperties structure, thought signature handling, citations enhancements, and streaming fixes.

core/schemas/chatcompletions.go (2)

312-367: Well-structured dual-mode type for JSON schema compatibility.

The AdditionalPropertiesStruct correctly models JSON schema's additionalProperties which can be either a boolean or an object schema. The marshal/unmarshal implementations properly handle:

  • Mutual exclusivity validation in MarshalJSON
  • Null handling in UnmarshalJSON
  • Type detection by attempting boolean first, then object

276-282: LGTM - Field type enhancement for richer JSON schema support.

The change from *bool to *AdditionalPropertiesStruct enables proper modeling of JSON schema's additionalProperties, which can be either a boolean or an object schema. This is correctly aligned with OpenAPI/JSON Schema specifications.

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

258-291: LGTM - Comprehensive citation model.

The AnthropicTextCitation struct elegantly handles multiple citation types (char_location, page_location, content_block_location, web_search_result_location, search_result_location) with well-documented optional fields for each type. The design uses optional pointers appropriately for type-specific fields.


219-238: LGTM - Content block expansion for web search support.

The AnthropicContentBlock now properly supports web_search_result content type with URL, EncryptedContent, and PageAge fields, along with the enhanced citations structure. This aligns with Anthropic's API capabilities for web search and document citations.

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

421-426: LGTM - Tests correctly updated for new AdditionalPropertiesStruct.

The test properly constructs AdditionalPropertiesStruct with the boolean value wrapper, aligning with the schema changes in core/schemas/chatcompletions.go and core/schemas/responses.go.


537-542: LGTM - Consistent update for streaming test.

The streaming test follows the same pattern for constructing AdditionalPropertiesStruct, maintaining consistency with the non-streaming test.

core/schemas/responses.go (2)

132-140: LGTM!

The AdditionalProperties field correctly uses the new *AdditionalPropertiesStruct type, aligning with the struct-based representation introduced in core/schemas/chatcompletions.go. The JSON tag is preserved, maintaining API compatibility.


421-424: LGTM!

The new Citations struct is straightforward and provides a clean representation for citations configuration. This aligns with the broader citations enhancements mentioned in the PR objectives.

core/providers/gemini/gemini_test.go (1)

558-561: LGTM!

Test correctly updated to use the new AdditionalPropertiesStruct wrapper, aligning with the schema changes across the codebase.

core/providers/gemini/chat.go (3)

94-102: LGTM!

Correctly handles thought/reasoning text by adding to reasoningDetails with BifrostReasoningDetailsTypeText type and skipping the regular content path.


130-160: Potential duplicate reasoning details when both embedded and standalone signatures are present.

If a FunctionCall part has both an embedded signature in CallID AND a separate ThoughtSignature field set, this code will add two reasoning detail entries for the same tool call:

  1. Lines 152-160: From extracted embedded signature
  2. Lines 177-201: From standalone ThoughtSignature

The comment on line 176 says "standalone thought signature (not embedded in CallID)" but there's no guard to skip if a signature was already extracted from the CallID.

Please verify:

  1. Can Gemini API send both an embedded signature in CallID AND a separate ThoughtSignature field for the same part?
  2. If not, consider adding an early continue or flag to prevent duplicate entries:
♻️ Suggested guard
+				// Track if we already processed a signature from CallID
+				var signatureHandled bool
+
 				// Extract thought signature from CallID if embedded (Gemini 3 behavior)
 				var extractedSig []byte
 				if strings.Contains(callID, thoughtSignatureSeparator) {
 					parts := strings.SplitN(callID, thoughtSignatureSeparator, 2)
 					if len(parts) == 2 {
 						if decoded, err := base64.StdEncoding.DecodeString(parts[1]); err == nil {
 							extractedSig = decoded
 							callID = parts[0] // Use base ID without signature for the tool call
+							signatureHandled = true
 						}
 					}
 				}
 ...
 				// Handle standalone thought signature (not embedded in CallID)
-				if part.ThoughtSignature != nil {
+				if part.ThoughtSignature != nil && !signatureHandled {

Also applies to: 176-202


388-388: LGTM!

Correctly updated the delta content check to include ReasoningDetails, ensuring chunks with only reasoning content are properly emitted.

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

898-904: Indexing change looks consistent: OutputIndex-first with ContentIndex fallback.
This aligns with the PR objective (“prioritize OutputIndex over ContentIndex”) and should stabilize global indexing across mixed event types. Based on learnings, the empty signature at content_block_start is correct (actual signature arrives via signature_delta).

-->

Also applies to: 919-924


935-984: Good: initialize Signature to "" on thinking/redacted_thinking content_block_start.
This matches the expected Anthropic streaming shape where the signature arrives later via signature_delta. Based on learnings, this is the right behavior.

-->


1166-1192: Remove this suggestion: the code is correct as-is.

The code correctly sets stop_sequence to nil (null in JSON) per Anthropic's streaming API specification. According to Anthropic's SSE contract, stop_sequence should be null when no custom stop sequence triggered the end of generation—it's only non-null when a specific custom stop string was matched. The proposed fix to keep "" (empty string) would violate the spec.

The lack of omitempty on the StopSequence field is intentional: the field must be present in the JSON output (either with a string value or null). The current implementation at line 1189 setting StopSequence: nil is correct.

Note: The initial assignment of schemas.Ptr("") at line 1171 is suboptimal and could be initialized to nil directly for clarity, but the final value after the conditional check is spec-compliant.

Likely an incorrect or invalid review comment.

tests/integrations/python/tests/test_google.py (3)

1681-1766: New Gemini 3 parallel function calls test looks good.

The test properly validates thought signature handling for parallel function calls, correctly checking that signatures are attached to function call parts. The assertion that the first function call should have the signature aligns with Gemini documentation.


1767-1900: Sequential function calls test is well-structured.

The test demonstrates proper multi-turn conversation handling with thought signature preservation, which is critical for Gemini 3's function calling workflow.


1956-2010: Thinking levels test provides good coverage.

Testing all four thinking levels (minimal, low, medium, high) ensures the Bifrost implementation properly handles the thinking_config parameter variations.

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 (3)
core/providers/anthropic/responses.go (1)

898-924: Fix synthetic input_json_delta indexing to follow OutputIndex-first. Right now generateSyntheticInputJSONDeltas(..., bifrostResp.ContentIndex) can emit deltas under a different index than the associated content_block_start / output_item.added after the OutputIndex-first change.

Proposed fix (prefer OutputIndex for synthetic deltas)
-               deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+               idx := bifrostResp.OutputIndex
+               if idx == nil {
+                 idx = bifrostResp.ContentIndex
+               }
+               deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, idx)
                events = append(events, deltaEvents...)

Also applies to: 1056-1062, 1072-1077, 1093-1114, 1127-1161, 1197-1202, 1013-1047

core/providers/gemini/chat.go (1)

130-160: Stream path should link thought signatures to tool calls (missing ReasoningDetails.ID). Non-stream uses ID: tool_call_<callID>; streaming currently omits it for both embedded signatures and standalone ThoughtSignature.

Proposed fix (match non-stream linking behavior)
                 toolCalls = append(toolCalls, toolCall)

                 // If we extracted a signature from CallID, add it to reasoning details
                 if len(extractedSig) > 0 {
                   thoughtSig := base64.StdEncoding.EncodeToString(extractedSig)
                   reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
                     Index:     len(reasoningDetails),
                     Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
                     Signature: &thoughtSig,
+                    ID:        schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
                   })
                 }

...

            if part.ThoughtSignature != nil {
              thoughtSig := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
              reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
                Index:     len(reasoningDetails),
                Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
                Signature: &thoughtSig,
+               // If this Part is also a tool call, associate the signature with the tool call ID
+               // (strip embedded signature suffix if present, same as non-stream path).
+               // ID: schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
              })
            }

(Second hunk needs the same “derive callID / strip suffix” logic you already have in non-stream.)

Also applies to: 176-202, 318-351, 360-368

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

687-692: Fix AdditionalProperties handling (now a struct, old code dereferences it like *bool). schema["additionalProperties"] = *format.JSONSchema.AdditionalProperties is no longer valid and should be split into bool vs map branches.

Proposed fix
-   if format.JSONSchema.Type != nil && *format.JSONSchema.Type == "object" {
-     schema["additionalProperties"] = false
-   } else if format.JSONSchema.AdditionalProperties != nil {
-     schema["additionalProperties"] = *format.JSONSchema.AdditionalProperties
-   }
+   if format.JSONSchema.AdditionalProperties != nil {
+     ap := format.JSONSchema.AdditionalProperties
+     if ap.AdditionalPropertiesBool != nil {
+       schema["additionalProperties"] = *ap.AdditionalPropertiesBool
+     } else if ap.AdditionalPropertiesMap != nil {
+       schema["additionalProperties"] = *ap.AdditionalPropertiesMap
+     }
+   } else if format.JSONSchema.Type != nil && *format.JSONSchema.Type == "object" {
+     // Maintain prior default for objects when not specified.
+     schema["additionalProperties"] = false
+   }

Also applies to: 768-778

🤖 Fix all issues with AI agents
In @tests/integrations/python/tests/test_google.py:
- Around line 2291-2293: Rename the duplicate/misnumbered test definitions so
the sequence is contiguous: change the test function named test_34_file_delete
to test_33_file_delete (so test_33 is present) and ensure the later test remains
test_34_batch_create_with_file; update any references or parametrize decorators
that reference test_34_file_delete to the new test_33_file_delete name (look for
symbols test_34_file_delete and test_34_batch_create_with_file in the file and
rename the first to test_33_file_delete).
🧹 Nitpick comments (9)
core/changelog.md (1)

3-6: Improve changelog specificity and completeness.

While these entries align with the PR objectives, consider enhancing clarity:

  1. Line 6 lacks specificity: "anthropic streaming events through integration" is vague. The PR summary mentions multiple specific Anthropic fixes:

    • Prioritizing OutputIndex over ContentIndex for consistent global indexing
    • Adding missing signature fields to thinking content blocks
    • Adding proper stop reason handling in message_stop events
  2. Consider separate entries: Breaking line 6 into specific entries would better document what was fixed and aid future debugging.

Example improvement:

-fix: anthropic streaming events through integration
+fix: prioritize OutputIndex over ContentIndex in Anthropic for consistent global indexing
+fix: add signature fields to Anthropic thinking content blocks
+fix: add stop reason handling in Anthropic message_stop events
core/providers/cohere/utils.go (1)

76-86: Consider using else if for cleaner control flow.

The current implementation works correctly because SafeExtractOrderedMap returns false for boolean values. However, using else if would make the mutual exclusivity explicit and avoid an unnecessary function call when the boolean branch succeeds.

♻️ Suggested improvement
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/internal/testutil/structured_outputs.go (1)

406-434: Avoid sharing mutable schema maps across parallel tests (potential data race/flakes). Consider deep-copying structuredOutputSchema (esp. "properties", "required") per test run before passing to request builders.

Proposed tweak (defensive copies + Ptr helper)
-           props := structuredOutputSchema["properties"].(map[string]interface{})
-           additionalProps := structuredOutputSchema["additionalProperties"].(bool)
+           props := maps.Clone(structuredOutputSchema["properties"].(map[string]interface{}))
+           additionalProps := structuredOutputSchema["additionalProperties"].(bool)

            responsesReq := &schemas.BifrostResponsesRequest{
              ...
              Params: &schemas.ResponsesParameters{
                Text: &schemas.ResponsesTextConfig{
                  Format: &schemas.ResponsesTextConfigFormat{
                    JSONSchema: &schemas.ResponsesTextConfigFormatJSONSchema{
                      ...
-                     AdditionalProperties: &schemas.AdditionalPropertiesStruct{
-                       AdditionalPropertiesBool: &additionalProps,
-                     },
+                     AdditionalProperties: &schemas.AdditionalPropertiesStruct{
+                       AdditionalPropertiesBool: schemas.Ptr(additionalProps),
+                     },
                    },
                  },
                },
              },
            }

(You’ll need import "maps"; if you want to avoid that, we can inline a small helper clone.)

Also applies to: 523-549

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

1145-1155: AdditionalProperties bool/map handling looks good; prefer schemas.Ptr(...) for the bool.

Small clarity tweak
 if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
   jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
-    AdditionalPropertiesBool: &additionalProps,
+    AdditionalPropertiesBool: schemas.Ptr(additionalProps),
   }
 }
core/providers/gemini/responses.go (1)

2377-2401: Consider silently ignoring decode errors instead of resetting thoughtSig.

The error handling sets thoughtSig = "" after a decode failure, but part.ThoughtSignature will already be nil (empty slice from failed decode), so this assignment is redundant. The logic works correctly, but for clarity, you could simplify by just logging or ignoring the error.

Minor simplification
 				if thoughtSig != "" {
-					var err error
-					part.ThoughtSignature, err = base64.StdEncoding.DecodeString(thoughtSig)
-					if err != nil {
-						// Silently ignore decode errors - ID will be used without signature
-						thoughtSig = ""
+					if decoded, err := base64.StdEncoding.DecodeString(thoughtSig); err == nil {
+						part.ThoughtSignature = decoded
 					}
+					// Silently ignore decode errors - ID will be used without signature
 				}
tests/integrations/python/tests/test_google.py (4)

1681-1766: Test for parallel function calls with thought signatures is well-structured.

The test properly validates that:

  1. Parallel function calls are made when requested
  2. At least the first function call has a thought signature
  3. According to Gemini docs, only the first function call should have the signature

Minor static analysis issues:

  • Line 1750: f-string without placeholders - replace f" → No signature" with " → No signature"
  • test_config parameter is unused (common in pytest fixtures, acceptable)
Remove extraneous f-prefix on Line 1750
-                    print(f"    → No signature")
+                    print("    → No signature")

1767-1899: Sequential function calls test validates multi-turn signature preservation.

The test properly builds a conversation with the signature preserved on the function call part, which is required for Gemini 3 thinking mode to work correctly across turns.

Minor static analysis issues on lines 1895 and 1898 - f-strings without placeholders:

Remove extraneous f-prefixes
-            print(f"\n✓ Sequential function calling: Step 2 has function call with signature")
+            print("\n✓ Sequential function calling: Step 2 has function call with signature")
         else:
-            print(f"\n✓ Model provided text response after first function call")
+            print("\n✓ Model provided text response after first function call")

2093-2139: Broad exception catching may mask real errors.

Lines 2095 and 2132 catch Exception broadly. While this is intentional for the "informational" step 3 test (testing without signature), the step 2 exception handler at line 2095 should either catch a more specific exception type or log the exception for debugging.

Additionally, response_3 on line 2120 is assigned but never used per static analysis.

Address unused variable and consider narrowing exception scope
         try:
             conversation_without_sig = [
                 types.Content(role="user", parts=[types.Part(text="Calculate 45 * 23")]),
                 types.Content(
                     role="model",
                     parts=[types.Part(function_call=fc)]  # WITHOUT signature
                 ),
                 types.Content(
                     role="user",
                     parts=[types.Part(
                         function_response=types.FunctionResponse(
                             name=fc.name,
                             response={"result": "1035"}
                         )
                     )]
                 )
             ]
             
-            response_3 = client.models.generate_content(
+            _response_3 = client.models.generate_content(  # Prefix with _ to indicate intentionally unused
                 model=model,
                 contents=conversation_without_sig,
                 config=types.GenerateContentConfig(
                     tools=[calculator_tool],
                     thinking_config=types.ThinkingConfig(thinking_level="medium")
                 )
             )
             
-            print(f"    ! Request WITHOUT signature succeeded (SDK may auto-handle)")
-            print(f"    Note: Gemini 3 docs state this should fail, but SDK might preserve signatures")
+            print("    ! Request WITHOUT signature succeeded (SDK may auto-handle)")
+            print("    Note: Gemini 3 docs state this should fail, but SDK might preserve signatures")
             
         except Exception as e:

1957-2009: Thinking levels test provides good coverage for Gemini 3 modes.

The test iterates through all thinking levels (minimal, low, medium, high) and validates token usage and thought parts. Minor f-string issues exist on lines 2001 and 2007.

Remove extraneous f-prefixes
             if level == "minimal":
-                print(f"    ✓ Minimal thinking level (expected: minimal/no thinking)")
+                print("    ✓ Minimal thinking level (expected: minimal/no thinking)")
             elif level == "high":
                 # High should have substantial thinking
                 if thought_tokens > 0:
                     print(f"    ✓ High thinking level with {thought_tokens} thought tokens")
                 else:
-                    print(f"    ! High thinking but no thought tokens reported")
+                    print("    ! High thinking but no thought tokens reported")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91e06c6 and 8a99f70.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/schemas/responses.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/gemini/types.go
  • core/changelog.md
  • core/providers/gemini/chat.go
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/changelog.md
  • core/schemas/chatcompletions.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.go
🧠 Learnings (10)
📚 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/gemini/types.go
  • core/providers/gemini/chat.go
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/cohere/utils.go
  • 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/gemini/types.go
  • core/providers/gemini/chat.go
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/cohere/utils.go
  • 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/gemini/types.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.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/gemini/types.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.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/structured_outputs.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
  • core/providers/anthropic/types.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
  • core/providers/anthropic/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_google.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
📚 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/gemini/gemini_test.go
🧬 Code graph analysis (7)
core/internal/testutil/structured_outputs.go (2)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/responses.go (3)
core/schemas/responses.go (4)
  • ResponsesToolMessage (475-495)
  • ResponsesMessageTypeReasoning (312-312)
  • ResponsesReasoning (745-748)
  • BifrostResponsesStreamResponse (1446-1485)
ui/lib/types/logs.ts (3)
  • ResponsesToolMessage (419-425)
  • FunctionCall (165-168)
  • ResponsesReasoning (433-436)
core/providers/gemini/types.go (4)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • Type (825-825)
  • Content (1016-1024)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (707-710)
core/schemas/responses.go (1)
  • Citations (421-423)
🪛 Ruff (0.14.10)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1750-1750: f-string without any placeholders

Remove extraneous f prefix

(F541)


1768-1768: Unused method argument: test_config

(ARG002)


1895-1895: f-string without any placeholders

Remove extraneous f prefix

(F541)


1898-1898: f-string without any placeholders

Remove extraneous f prefix

(F541)


1902-1902: Unused method argument: test_config

(ARG002)


1942-1942: f-string without any placeholders

Remove extraneous f prefix

(F541)


1954-1954: f-string without any placeholders

Remove extraneous f prefix

(F541)


1957-1957: Unused method argument: test_config

(ARG002)


2001-2001: f-string without any placeholders

Remove extraneous f prefix

(F541)


2007-2007: f-string without any placeholders

Remove extraneous f prefix

(F541)


2012-2012: Unused method argument: test_config

(ARG002)


2093-2093: f-string without any placeholders

Remove extraneous f prefix

(F541)


2095-2095: Do not catch blind exception: Exception

(BLE001)


2120-2120: Local variable response_3 is assigned to but never used

Remove assignment to unused variable response_3

(F841)


2129-2129: f-string without any placeholders

Remove extraneous f prefix

(F541)


2130-2130: f-string without any placeholders

Remove extraneous f prefix

(F541)


2132-2132: Do not catch blind exception: Exception

(BLE001)


2292-2292: Unused method argument: test_config

(ARG002)

⏰ 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). (15)
  • 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/changelog.md (1)

1-2: Verify changelog scope for stacked PR.

Lines 1-2 don't appear in this PR's summary and seem to document changes from downstack PR #1313 (based on the target branch name "01-12-feat_send_back_raw_request_with_bifrost_errors").

Typically, each PR's changelog documents only its own changes. Please confirm whether these entries should be included here or if they belong to the downstack PR's changelog.

transports/changelog.md (1)

1-6: LGTM!

The changelog entries accurately document the changes in this PR stack, covering AdditionalProperties support, thought signature handling, citations enhancement, and streaming event fixes.

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

218-238: LGTM!

The AnthropicContentBlock struct is well-extended with the new fields for signatures and web search results. The field organization and JSON tags are consistent.


248-291: LGTM!

The new AnthropicCitationType constants and AnthropicTextCitation struct comprehensively cover all Anthropic citation location types (char_location, page_location, content_block_location, web_search_result_location, search_result_location) with appropriate optional fields for each type.


293-336: LGTM!

The AnthropicCitations union type correctly handles the dual representation (request config vs response citations) with proper mutual exclusivity validation in MarshalJSON and type detection in UnmarshalJSON. Based on the learning that Anthropic streaming content_block_start events include signature fields set to empty strings, this implementation aligns well with the expected API behavior.

core/schemas/chatcompletions.go (2)

276-282: LGTM!

The field type change from *bool to *AdditionalPropertiesStruct enables support for both boolean and object forms of additionalProperties, which is required for proper OpenAPI schema compatibility.


312-367: LGTM!

The AdditionalPropertiesStruct implementation correctly handles:

  • Mutual exclusivity validation in MarshalJSON
  • Explicit null handling in UnmarshalJSON
  • Proper type detection for boolean vs object forms
  • Error reporting for invalid input types

The union type pattern is well-suited for this use case where JSON Schema's additionalProperties can be either a boolean or a schema object.

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

22-23: LGTM!

The thoughtSignatureSeparator constant is well-documented and provides a clean mechanism for embedding thought signatures in tool IDs for Gemini's function calling flow. The constant is used consistently across all three Gemini provider files (responses.go, chat.go) with no hard-coded literals, following proper encapsulation practices.

core/providers/gemini/gemini_test.go (1)

558-561: Test expectation correctly updated for AdditionalPropertiesStruct. Looks aligned with the new bool-wrapper representation.

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

937-954: Signature lifecycle for thinking blocks looks correct (empty on start, real value via signature_delta). This matches Anthropic streaming behavior (signature delivered later) and preserves Signature into the emitted reasoning content parts.

Also applies to: 428-439, 533-551

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

774-778: Map-form additionalProperties support is a solid addition. This matches the schema union representation and keeps parity with Gemini’s schema handling.

Also applies to: 1151-1155


31-94: Stack-level AdditionalProperties sweep needed. Search the stack for *AdditionalProperties dereferencing patterns and verify safe usage across providers. Found instances in:

  • core/providers/anthropic/utils.go:690
  • core/providers/cohere/responses.go:903
  • core/providers/gemini/responses.go:2092
  • core/schemas/utils.go:801

All current instances have proper nil checks, but verify consistency with the recent AdditionalPropertiesStruct refactoring (commit "additional properties struct fixes") to ensure all providers correctly handle the AdditionalPropertiesBool vs AdditionalPropertiesMap union type.

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

246-286: Thought signature extraction and look-ahead logic is well implemented.

The implementation correctly:

  1. Extracts thought signature from CallID if it contains the separator
  2. Falls back to look-ahead for a reasoning message with EncryptedContent
  3. Uses consumedIndices to track consumed reasoning messages

The code handles edge cases properly by checking for decode errors before using the signature.


457-477: Streaming path correctly mirrors non-streaming signature extraction.

The logic extracts the base64-encoded thought signature from the CallID (after the separator) and decodes it to attach to the function call part, maintaining consistency with the non-streaming path.


1057-1099: Function call streaming: signature embedded in CallID for downstream propagation.

The implementation correctly appends the base64-encoded thought signature to the tool ID using the separator, ensuring the signature travels with the function call through the streaming pipeline.


1866-1870: Non-streaming output conversion matches streaming approach.

The implementation attaches thought signature to callID in the same format ({callID}{separator}{base64sig}) as the streaming path, ensuring consistent handling across both paths.


2440-2444: FunctionResponse.ID assignment looks correct.

The ID is correctly assigned from msg.ResponsesToolMessage.CallID without stripping the thought signature separator, preserving the full ID for response matching.

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from 8a99f70 to cd8b2ea Compare January 13, 2026 18:46
@TejasGhatte TejasGhatte changed the base branch from 01-12-feat_send_back_raw_request_with_bifrost_errors to graphite-base/1319 January 13, 2026 19:02
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: 2

Caution

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

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

898-924: Keep synthetic tool content_block_delta indices consistent with the OutputIndex-first content_block_start.

You’re now emitting content_block_start with Index = OutputIndex (fallback ContentIndex), but synthetic input_json_delta generation still keys off bifrostResp.ContentIndex. If these diverge (or ContentIndex is nil), the reconstructed Anthropic stream can become internally inconsistent.

Proposed fix
-				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+				idx := bifrostResp.OutputIndex
+				if idx == nil {
+					idx = bifrostResp.ContentIndex
+				}
+				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, idx)
 				events = append(events, deltaEvents...)

Also applies to: 1013-1047, 1056-1062, 1072-1077, 1093-1098, 1127-1132, 1155-1160, 1198-1202, 1220-1224

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

1057-1100: Bug: update stream state maps after you append thoughtSignature into toolUseID.

state.ItemIDs[outputIndex] / state.ToolCallIDs[outputIndex] are set before toolUseID is modified. If a thought signature is appended, subsequent logic (like closeGeminiOpenItems) will emit done events using the old ID, not the one actually sent to clients.

Proposed fix
 	// Attach thought signature to ID if present
 	if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
 		encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
 		toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+		// Keep state consistent with emitted IDs
+		state.ItemIDs[outputIndex] = toolUseID
+		state.ToolCallIDs[outputIndex] = toolUseID
 	}
core/providers/gemini/chat.go (1)

318-350: Add ID field to streaming ReasoningDetails for extracted signatures.

In the streaming path (lines 318-350), when extracting a thought signature from CallID, append the ReasoningDetails with ID: schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)). Currently, the ID field is omitted, which prevents the signature from being matched back to the tool call during round-tripping, as expected by the matching logic in utils.go (lines 951-966).

The non-streaming path in the same file correctly sets this ID field; the streaming path should do the same for consistency.

For lines 360-368 (standalone part.ThoughtSignature): This appears to be a separate case without an associated callID, so the applicability of the "tool_call_" ID pattern is unclear and may require separate consideration.

🤖 Fix all issues with AI agents
In @core/providers/gemini/chat.go:
- Around line 94-103: The non-streaming handler currently appends thought text
and then uses `continue`, which prevents capturing `part.ThoughtSignature` when
a part has both text and a signature; remove the `continue` in the non-stream
path (the block that checks `if part.Text != "" && part.Thought`) and
restructure so that the `ThoughtSignature` processing (the same logic used later
in the non-stream path and mirrored by the streaming path handling) runs
unconditionally after the type-specific handling; ensure `reasoningDetails` is
still appended for text and that `part.ThoughtSignature` is processed afterwards
(matching the streaming path behavior that allows signatures to co-exist with
other types).

In @core/schemas/chatcompletions.go:
- Around line 312-368: AdditionalPropertiesStruct.MarshalJSON currently returns
JSON null when both fields are nil, which can produce `"additionalProperties":
null` and violate JSON Schema expectations; change MarshalJSON so that when both
AdditionalPropertiesBool and AdditionalPropertiesMap are nil it emits an empty
object (e.g., marshal an empty OrderedMap/object) instead of null, and make
UnmarshalJSON defensively normalize an empty object back to both fields nil
(i.e., if unmarshalling into OrderedMap yields an empty map, set
AdditionalPropertiesMap = nil and AdditionalPropertiesBool = nil) so empty
structs don’t round-trip into `"additionalProperties": null`.
🧹 Nitpick comments (4)
core/providers/gemini/responses.go (1)

247-287: ThoughtSignature-in-CallID extraction looks consistent, but guard delimiter collisions.

If thoughtSignatureSeparator can appear in “base” IDs (from clients), SplitN(..., 2) will parse the first occurrence. Consider parsing by the last occurrence (or enforcing/validating the format before split) so tool IDs remain unambiguous.

Also applies to: 458-477, 2377-2401

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

1165-1193: Fix struct tag or standardize null handling for stop_sequence.

AnthropicStreamDelta.StopSequence is tagged json:"stop_sequence" without omitempty, so setting it to nil serializes as "stop_sequence": null. This is inconsistent with AnthropicMessageResponse.StopSequence, which has omitempty. While Anthropic's API permits null for stop_sequence, the codebase is inconsistent: line 1172 sets it to schemas.Ptr("") (empty string), while line 1189 sets it to nil. Either add omitempty to the struct tag to omit the field entirely, or consistently use schemas.Ptr("") across both code paths.

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

248-336: Citations union type is OK structurally; consider tightening validation + clarifying {} behavior.
Today, citations: {} will successfully unmarshal as schemas.Citations with Enabled=nil (since it’s tried after slice). If {} is invalid per Anthropic, consider returning an error when config is “empty”. Also consider validating required fields per AnthropicCitationType (e.g., cited_text present is good, but location-specific required fields aren’t enforced).

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

1145-1155: AdditionalPropertiesStruct handling is good; consider using schemas.Ptr for consistency and confirm marshaling matches expected JSON shape.
Right now bool uses &additionalProps while tests tend to use schemas.Ptr(false). Not a functional issue, but consistency helps. More importantly: please confirm AdditionalPropertiesStruct JSON marshaling produces either false or {...} (not an object wrapper) as expected by downstream.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a99f70 and cd8b2ea.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • core/providers/cohere/utils.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/responses.go
  • transports/changelog.md
  • core/providers/anthropic/utils.go
  • core/providers/gemini/types.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/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.go
  • core/schemas/chatcompletions.go
  • tests/integrations/python/tests/test_google.py
🧠 Learnings (9)
📚 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/responses.go
  • core/providers/anthropic/types.go
📚 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/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.go
  • core/schemas/chatcompletions.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/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.go
  • 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/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.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/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.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/responses.go
  • core/providers/anthropic/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/gemini/gemini_test.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: 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_google.py
🧬 Code graph analysis (7)
core/providers/anthropic/responses.go (4)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (3)
  • AnthropicStreamDelta (519-527)
  • AnthropicStopReasonEndTurn (435-435)
  • AnthropicStreamEvent (498-507)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/schemas/responses.go (1)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1420-1420)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/gemini/responses.go (2)
core/schemas/responses.go (1)
  • ResponsesReasoning (745-748)
core/providers/gemini/types.go (3)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • Content (1016-1024)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (707-710)
core/schemas/responses.go (1)
  • Citations (421-423)
core/schemas/chatcompletions.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)
tests/integrations/python/tests/test_google.py (1)
core/providers/gemini/types.go (4)
  • Tool (647-674)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1750-1750: f-string without any placeholders

Remove extraneous f prefix

(F541)


1768-1768: Unused method argument: test_config

(ARG002)


1895-1895: f-string without any placeholders

Remove extraneous f prefix

(F541)


1898-1898: f-string without any placeholders

Remove extraneous f prefix

(F541)


1902-1902: Unused method argument: test_config

(ARG002)


1942-1942: f-string without any placeholders

Remove extraneous f prefix

(F541)


1954-1954: f-string without any placeholders

Remove extraneous f prefix

(F541)


1957-1957: Unused method argument: test_config

(ARG002)


2001-2001: f-string without any placeholders

Remove extraneous f prefix

(F541)


2007-2007: f-string without any placeholders

Remove extraneous f prefix

(F541)


2012-2012: Unused method argument: test_config

(ARG002)


2093-2093: f-string without any placeholders

Remove extraneous f prefix

(F541)


2095-2095: Do not catch blind exception: Exception

(BLE001)


2132-2132: Do not catch blind exception: Exception

(BLE001)

⏰ 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). (3)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (8)
core/providers/anthropic/responses.go (1)

934-984: Good: initialize Signature to empty string on thinking blocks.

Setting contentBlock.Signature = Ptr("") at content_block_start is the right behavior (actual signature arrives later via signature_delta). Based on learnings, this should not be treated as missing/invalid.

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

1866-1870: Good: batch path mirrors streaming CallID augmentation.

This keeps tool-call matching stable across streaming vs non-streaming conversions.


2443-2444: Nice: include FunctionResponse.ID to preserve tool-call correlation.

This should help Gemini match function responses to calls more reliably.

core/schemas/chatcompletions.go (1)

276-282: API change verified: all callers properly updated to use *AdditionalPropertiesStruct instead of *bool.

The type change from *bool to *AdditionalPropertiesStruct has been completed throughout the codebase. All 30+ usages across providers (Anthropic, Cohere, Gemini, Bedrock), test utilities, and core schemas properly construct &schemas.AdditionalPropertiesStruct{AdditionalPropertiesBool: ...} or {AdditionalPropertiesMap: ...}. No legacy *bool assignments remain.

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

219-238: AnthropicContentBlock looks compatible, but please double-check downstream consumers for the new/reshuffled fields (esp. Signature + citations).
Signature being a *string with omitempty is good for the “emit empty string when required” pattern (non-nil pointer). For the stack: ensure any conversion/normalization code (outside this file) doesn’t assume citations is a single shape anymore.

core/providers/gemini/gemini_test.go (1)

558-562: Test update matches the new AdditionalPropertiesStruct API.

core/providers/gemini/chat.go (1)

130-160: CallID signature extraction behavior should be validated against Gemini 3 contract (esp. separator choice + base64 variants).
You decode then re-encode the signature, which implicitly “validates” base64 and normalizes it; that’s fine, but please confirm this matches the expected signature format and that tool-call correlation IDs remain stable across tool responses.

Also applies to: 318-328

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

1681-2140: Fix ARG002, F541, and BLE001 lint violations; make signature assertions more defensive.

All five new test methods (test_30a through test_30e) have unused test_config parameters. Remove it from signatures or mark with _ = test_config if intentional.

Several print(f"...") statements contain no placeholders (e.g., print(f" → No signature")); replace with plain strings.

In test_30e, the broad except Exception as e: catches should either narrow the exception type or include # noqa: BLE001 with justification.

Additionally, the assertion assert signatures[0][0] == 0 assumes the first function call always carries the signature—Gemini's behavior may vary. Instead, verify that signatures exist somewhere, log their distribution, and defer to integration test feedback rather than strict index pinning.

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from cd8b2ea to a4107d0 Compare January 14, 2026 05:37
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 (5)
core/providers/anthropic/responses.go (2)

1165-1191: Set StopSequence to schemas.Ptr("") for consistency with the default initialization above.

The AnthropicStreamDelta.StopSequence field has no omitempty tag, so setting it to nil serializes as "stop_sequence": null. The initial Delta on line 1171 sets it to schemas.Ptr(""), so this conditional override should match that pattern to avoid sending null values to the Anthropic API.

Proposed fix
 		if bifrostResp.Response.StopReason != nil {
 			anthropicContentDeltaEvent.Delta = &AnthropicStreamDelta{
 				StopReason:   schemas.Ptr(ConvertBifrostFinishReasonToAnthropic(*bifrostResp.Response.StopReason)),
-				StopSequence: nil,
+				StopSequence: schemas.Ptr(""),
 			}
 		}

898-924: Use consistent index preference for synthetic input_json_delta events.

content_block_start events use OutputIndex with ContentIndex as fallback, but synthetic tool-argument deltas at line 1045 ignore OutputIndex and always use ContentIndex. When both indices are present and differ, downstream synthetic deltas will target the wrong block index.

Fix
-				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+				// Use the same index as content_block_start for consistency
+				synthIndex := bifrostResp.OutputIndex
+				if synthIndex == nil {
+					synthIndex = bifrostResp.ContentIndex
+				}
+				deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, synthIndex)
core/providers/gemini/chat.go (1)

176-202: Ensure these signature sources are mutually exclusive.

When part.FunctionCall has an embedded signature in CallID that was extracted and stored in part.ThoughtSignature by the responses layer, both code paths (lines 130-150 and 176-202) will execute, adding duplicate reasoning details for the same signature. Consider adding a guard to skip the standalone ThoughtSignature path when the signature was already extracted from CallID.

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

493-498: Fix possible nil-deref + incorrect signature bytes in OutputItemAdded conversion.

The condition checks bifrostResp.Item.EncryptedContent != nil but then dereferences bifrostResp.Item.ResponsesReasoning.EncryptedContent (can be nil) and also assigns the base64 string bytes directly to Part.ThoughtSignature instead of base64-decoding it. This can crash or corrupt signatures.

Proposed fix
 case schemas.ResponsesStreamResponseTypeOutputItemAdded:
-    if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.EncryptedContent != nil {
-        candidate.Content.Parts = append(candidate.Content.Parts, &Part{
-            ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
-        })
-    }
+    if bifrostResp.Item != nil &&
+        bifrostResp.Item.ResponsesReasoning != nil &&
+        bifrostResp.Item.ResponsesReasoning.EncryptedContent != nil {
+        if decoded, err := base64.StdEncoding.DecodeString(*bifrostResp.Item.ResponsesReasoning.EncryptedContent); err == nil {
+            candidate.Content.Parts = append(candidate.Content.Parts, &Part{
+                ThoughtSignature: decoded,
+            })
+        }
+    }

1057-1103: Streaming: toolUseID mutation after state bookkeeping breaks ID consistency.

state.ItemIDs[outputIndex] / state.ToolCallIDs[outputIndex] are set before appending the thought signature to toolUseID (Line 1057-1062). The emitted output_item.added / function_call_arguments.done use the mutated ID, but later closures (e.g., closeGeminiOpenItems) will use the old ID from state.ItemIDs, causing mismatched IDs across events.

Proposed fix
  toolUseID := part.FunctionCall.ID
  if toolUseID == "" {
      toolUseID = part.FunctionCall.Name // Fallback to name as ID
  }

+ // Attach thought signature to ID if present (must happen before storing IDs in state maps)
+ if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
+     encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
+     toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+ }

  state.ItemIDs[outputIndex] = toolUseID
  state.ToolCallIDs[outputIndex] = toolUseID
  state.ToolCallNames[outputIndex] = part.FunctionCall.Name

- // Attach thought signature to ID if present
- if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
-     encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
-     toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
- }
-
🤖 Fix all issues with AI agents
In `@core/schemas/chatcompletions.go`:
- Around line 319-338: The marshaler emits JSON null when AdditionalProperties
is a non-nil pointer whose variant fields are both nil, which prevents omitempty
from dropping the field; update ToolFunctionParameters.UnmarshalJSON to
normalize that case by setting t.AdditionalProperties = nil whenever
t.AdditionalProperties != nil and both
t.AdditionalProperties.AdditionalPropertiesBool and
t.AdditionalProperties.AdditionalPropertiesMap are nil so subsequent marshaling
(AdditionalPropertiesStruct.MarshalJSON) will omit the field instead of emitting
null.

In `@tests/integrations/python/tests/test_google.py`:
- Around line 1681-1766: The assertion signatures[0][0] == 0 is flaky because
parts before function calls can exist; instead record the part index of each
function call when building function_calls (e.g., collect function_call_indices
alongside function_calls in the loop) and assert the thought signature index
equals the first function call's part index (assert signatures[0][0] ==
function_call_indices[0]) so you compare against the first function-call part
index rather than absolute part index 0.
- Around line 1681-2140: Rename the unused parameter test_config to _test_config
in all five test functions test_30a_gemini_3_parallel_function_calls_signatures,
test_30b_gemini_3_sequential_function_calls_signatures,
test_30c_gemini_3_thought_signatures_in_text_responses,
test_30d_gemini_3_thinking_levels, and
test_30e_gemini_3_signature_validation_strict to fix ARG002; remove the
unnecessary f-string prefixes from the two print statements that have no
placeholders (the prints currently using f"  Only {len(function_calls)} function
call(s) made" and f"    FC: {fc.name}, Signature: {len(sig) if sig else 0}
bytes" should be regular strings or formatted properly without an f if no
placeholders); and replace the bare except Exception as e blocks in test_30e
(the two try/except blocks handling conversation_without_sig and response_2)
with except (google.genai.errors.APIError, ValueError) as e and add a short
inline comment noting these are catching SDK/API and validation errors for
integration test handling.
♻️ Duplicate comments (1)
core/providers/gemini/responses.go (1)

247-286: Standard base64 in embedded IDs: confirm Gemini’s ID charset/length rules + decode robustness.
Right now the signature embedded into CallID/toolUseID uses base64.StdEncoding and extraction uses base64.StdEncoding.DecodeString only. If Gemini constrains function_call.id (or any “tool ID” field) to URL-safe characters / max length, +, /, = (and long strings) can break requests; also if any path produces URL-safe base64, decoding will fail and the code silently drops the signature in some places.

Gemini API / Google GenAI: what are the character-set and length constraints for function_call.id (tool call ID)? Are '+' '/' '=' allowed, and what is the max length?

If constraints exist, consider:

  • Using base64.RawURLEncoding (or another allowed encoding) only for the embedded-ID representation.
  • Adding a small helper to decode either standard or URL-safe base64 (handling missing padding) and using it consistently in all “extract from CallID” sites.

Also applies to: 457-477, 1057-1062, 1866-1871, 2377-2401

🧹 Nitpick comments (3)
core/providers/cohere/utils.go (1)

75-86: Minor: make the object case an else if for clarity (bool and object are mutually exclusive).

Proposed refactor
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/providers/anthropic/utils.go (1)

768-778: Consider using else if for mutually exclusive branches.

The two conditions are functionally mutually exclusive (a value can't be both a bool and an ordered map), but using else if would make this explicit and marginally more efficient by avoiding the second type assertion when the first succeeds.

♻️ Suggested improvement
 		if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesBool: &additionalProps,
 			}
-		}
-
-		if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+		} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesMap: &additionalProps,
 			}
 		}
core/providers/gemini/utils.go (1)

1146-1155: Consider using else if for mutually exclusive branches.

Same pattern as in core/providers/anthropic/utils.go - the two conditions are mutually exclusive, and using else if would make this explicit.

♻️ Suggested improvement
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd8b2ea and a4107d0.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/internal/testutil/structured_outputs.go
  • core/providers/gemini/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/schemas/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • tests/integrations/python/tests/test_google.py
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/changelog.md
🧠 Learnings (10)
📚 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/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/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/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/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/schemas/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.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/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.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/gemini/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/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
  • core/providers/anthropic/types.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
  • core/providers/anthropic/types.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-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/gemini/gemini_test.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_google.py
🧬 Code graph analysis (9)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/anthropic/responses.go (2)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (3)
  • AnthropicStreamDelta (519-527)
  • AnthropicStopReasonEndTurn (435-435)
  • AnthropicStreamEvent (498-507)
core/schemas/chatcompletions.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/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (707-710)
core/schemas/responses.go (1)
  • Citations (421-423)
tests/integrations/python/tests/test_google.py (2)
tests/integrations/python/tests/utils/common.py (1)
  • skip_if_no_api_key (1830-1841)
core/providers/gemini/types.go (4)
  • Tool (647-674)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1750-1750: f-string without any placeholders

Remove extraneous f prefix

(F541)


1768-1768: Unused method argument: test_config

(ARG002)


1895-1895: f-string without any placeholders

Remove extraneous f prefix

(F541)


1898-1898: f-string without any placeholders

Remove extraneous f prefix

(F541)


1902-1902: Unused method argument: test_config

(ARG002)


1942-1942: f-string without any placeholders

Remove extraneous f prefix

(F541)


1954-1954: f-string without any placeholders

Remove extraneous f prefix

(F541)


1957-1957: Unused method argument: test_config

(ARG002)


2001-2001: f-string without any placeholders

Remove extraneous f prefix

(F541)


2007-2007: f-string without any placeholders

Remove extraneous f prefix

(F541)


2012-2012: Unused method argument: test_config

(ARG002)


2093-2093: f-string without any placeholders

Remove extraneous f prefix

(F541)


2095-2095: Do not catch blind exception: Exception

(BLE001)


2132-2132: Do not catch blind exception: Exception

(BLE001)

⏰ 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). (1)
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (12)
core/changelog.md (1)

1-6: Changelog entries look consistent with the PR scope; verify no duplicates across the Graphite stack.

Since #1319 is part of a stack, ensure sibling PRs (#1313, etc.) aren’t also adding the same entries (avoid double-release notes).

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

934-946: Empty signature on thinking content_block_start is correct (don’t remove).

Keeping contentBlock.Signature = schemas.Ptr("") on start aligns with Anthropic streaming behavior; the real signature arrives via signature_delta. Based on learnings, this should remain as-is.

Also applies to: 951-959, 979-984

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

219-238: Expanded AnthropicContentBlock shape looks consistent with new features (signature/citations).


248-336: Citations union type is good; verify required fields match Anthropic’s emitted schema (esp. cited_text).

This is an external-contract surface (Anthropic + sonic marshaling), so please sanity-check with real responses / fixtures that AnthropicTextCitation required/optional fields align (e.g., cited_text always present, and type-specific fields appear as expected).

core/schemas/chatcompletions.go (1)

276-282: Public API shift verified—all callsites successfully migrated from *bool to *AdditionalPropertiesStruct. No remaining code treats ToolFunctionParameters.AdditionalProperties as a simple pointer-to-bool. All 8+ usages across providers (Anthropic, Cohere, Gemini), utilities, and tests properly initialize the union type with exactly one variant set. The implementation correctly handles JSON round-tripping: null values marshal as null and unmarshal to a struct with both fields nil.

core/schemas/responses.go (2)

132-140: LGTM: Type alignment for AdditionalProperties.

The change from *bool to *AdditionalPropertiesStruct correctly aligns this struct with the new union type that supports both boolean and map representations. This matches the AdditionalPropertiesStruct definition in core/schemas/chatcompletions.go:311-314.


421-423: LGTM: Simple Citations type addition.

The new Citations struct is well-defined with proper JSON tags and optional semantics via the pointer type.

core/providers/gemini/gemini_test.go (1)

558-561: LGTM: Test correctly updated for new AdditionalPropertiesStruct type.

The test properly uses the new wrapper struct with AdditionalPropertiesBool field, maintaining test coverage for the structured outputs conversion with the updated type system.

core/providers/gemini/chat.go (4)

94-102: Acknowledged: Thought text handling with continue.

Based on the past review discussion, TejasGhatte confirmed that part.Text, part.Thought, and part.ThoughtSignature don't appear together in the same part from the Gemini API. The continue here is intentional to skip regular text processing for thought content.


130-160: LGTM: Thought signature extraction from CallID.

The logic correctly:

  1. Detects the embedded signature separator in CallID
  2. Base64-decodes the signature portion
  3. Uses the base ID for the tool call
  4. Re-encodes and stores the signature in reasoningDetails with proper type and ID linkage

318-350: LGTM: Streaming path correctly mirrors non-streaming logic.

The signature extraction from CallID in the streaming path follows the same pattern as the non-streaming path, ensuring consistent behavior.


388-388: LGTM: Delta content check includes ReasoningDetails.

The hasDeltaContent check correctly includes len(delta.ReasoningDetails) > 0 to ensure chunks with only reasoning content are not skipped.

✏️ 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: 6

Caution

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

⚠️ Outside diff range comments (3)
core/providers/anthropic/responses.go (2)

898-924: Keep synthetic input_json_delta indices consistent with OutputIndex-first indexing.

You now set streamResp.Index from bifrostResp.OutputIndex (Line 898-924), but synthetic deltas still use bifrostResp.ContentIndex (Line 1045). If those diverge, the deltas will attach to the wrong content block.

Proposed fix
-                deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+                // Use the same index we used for content_block_start to keep deltas aligned.
+                deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, streamResp.Index)
                 events = append(events, deltaEvents...)

Also applies to: 1013-1047


1165-1192: Fix StopSequence to always emit null instead of empty string in message_delta.

The StopSequence field in AnthropicStreamDelta lacks omitempty, so nil serializes as "stop_sequence": null. Per Anthropic's specification, when no custom stop sequence triggered the stop (e.g., for "end_turn", "max_tokens"), stop_sequence must be null, not an empty string. Currently, line 1169 initializes StopSequence to schemas.Ptr(""), which only gets overwritten to nil if bifrostResp.Response.StopReason != nil (line 1187). If StopReason is unexpectedly nil for a Completed response, the empty string would escape to the client. Initialize StopSequence to nil instead and only set it to a non-null value when an actual stop sequence was matched.

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

1039-1061: Fix ID mismatch: you mutate toolUseID after saving it into state maps.

At Line 1044-1047 you store toolUseID into state.ItemIDs / state.ToolCallIDs, then at Line 1057-1061 you append the signature and overwrite toolUseID. This will cause later closures (e.g., closeGeminiOpenItems using state.ItemIDs) to emit output_item.done with the old ID, not the ID used in output_item.added / arguments.done.

Proposed fix
  toolUseID := part.FunctionCall.ID
  if toolUseID == "" {
    toolUseID = part.FunctionCall.Name // Fallback to name as ID
  }

- state.ItemIDs[outputIndex] = toolUseID
- state.ToolCallIDs[outputIndex] = toolUseID
- state.ToolCallNames[outputIndex] = part.FunctionCall.Name
-
- // Attach thought signature to ID if present
- if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
-   encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
-   toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
- }
+ // Attach thought signature to ID if present
+ if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
+   encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
+   toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
+ }
+
+ state.ItemIDs[outputIndex] = toolUseID
+ state.ToolCallIDs[outputIndex] = toolUseID
+ state.ToolCallNames[outputIndex] = part.FunctionCall.Name
🤖 Fix all issues with AI agents
In `@core/providers/gemini/chat.go`:
- Around line 342-350: The streaming-path block that appends a
schemas.ChatReasoningDetails for an extracted signature (variables extractedSig,
thoughtSig, reasoningDetails) is missing the ID field; update that append to
include ID: schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)) (matching the
non-streaming path) so the appended schemas.ChatReasoningDetails has Index,
Type, Signature and ID set for correlation with the tool call.

In `@tests/integrations/python/tests/test_google.py`:
- Around line 1767-1900: The test
test_30b_gemini_3_sequential_function_calls_signatures should be made
lint-clean: add assertions that response_1.candidates and response_2.candidates
are non-empty before indexing (e.g., assert response_1.candidates and assert
response_2.candidates) to avoid potential IndexErrors, remove any unnecessary
f-string prefixes where the string contains no placeholders (convert f"…" to
"…"), and replace the unused test_config parameter by adding
`@pytest.mark.usefixtures`("test_config") to the test decorator and removing the
test_config argument from the function signature; update references to
response_1/response_2 and printing strings accordingly to avoid ARG002/F541
warnings.
- Around line 2011-2140: The test test_30e_gemini_3_signature_validation_strict
currently uses broad except Exception blocks and lacks asserts on API responses;
update it to (1) assert response_1.candidates and response_2.candidates
immediately after each generate_content call to ensure candidates exist (refer
to response_1 and response_2), and (2) replace broad except Exception handlers
around the WITH/without-signature calls with either specific SDK exception types
thrown by client.models.generate_content (catch that specific exception class)
or keep the broad except but add an explicit BLE001 suppression comment (noqa)
on that except to satisfy Ruff; ensure you reference the try/except blocks that
wrap the client.models.generate_content calls and the test function name
test_30e_gemini_3_signature_validation_strict when making the changes.
- Around line 1681-1766: The test
test_30a_gemini_3_parallel_function_calls_signatures incorrectly asserts
signatures[0][0] == 0 assuming the first function_call is at part index 0;
instead find the actual index of the first function_call in
response.candidates[0].content.parts (e.g., record first_fc_index when you
append to function_calls inside the loop) and assert signatures[0][0] ==
first_fc_index so the check validates that the thought signature is on the first
function_call part regardless of leading text/thinking parts.
- Around line 1901-2010: Several print statements in
test_30c_gemini_3_thought_signatures_in_text_responses and
test_30d_gemini_3_thinking_levels use f-strings but contain no placeholders
(triggering Ruff F541); change those f-prefixed strings to plain strings. Locate
the prints in the two test functions (e.g., the "  Summary:" print in test_30c
and the "    ✓ Minimal thinking level (expected: minimal/no thinking)" and "   
! High thinking but no thought tokens reported" prints in test_30d) and remove
the leading "f" so they become normal string literals while leaving f-strings
with actual placeholders (like prints referencing idx, level, thought_tokens,
etc.) unchanged.
♻️ Duplicate comments (2)
core/schemas/chatcompletions.go (1)

312-367: Guard against emitting additionalProperties: null when unmarshalling receives null.

AdditionalPropertiesStruct.MarshalJSON() returns null when both fields are nil. Since the field has omitempty tag in ToolFunctionParameters, a non-nil pointer with empty contents will serialize to "additionalProperties": null — which violates JSON Schema (additionalProperties must be a boolean or schema object, not null).

This occurs when unmarshalling JSON with "additionalProperties": null. The struct pointer remains non-nil with both fields cleared. Add normalization after ToolFunctionParameters.UnmarshalJSON() to collapse empty AdditionalPropertiesStruct back to nil:

Normalization code
 func (t *ToolFunctionParameters) UnmarshalJSON(data []byte) error {
 	// ... existing unmarshal logic ...
 	*t = ToolFunctionParameters(temp)
+	if t.AdditionalProperties != nil &&
+		t.AdditionalProperties.AdditionalPropertiesBool == nil &&
+		t.AdditionalProperties.AdditionalPropertiesMap == nil {
+		t.AdditionalProperties = nil
+	}
 	return nil
 }

Apply the same normalization pattern in any other types that use AdditionalPropertiesStruct with the omitempty tag (e.g., in responses.go).

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

1057-1061: (Already raised before) Confirm base64 encoding is valid for Gemini function-call IDs.

You’re appending base64.StdEncoding into CallID (Line 1059-1061, 1868-1870), which introduces +, /, =. If Gemini enforces an ID charset/length constraint, this will break tool calls.

Gemini API FunctionCall.id / tool call id constraints: allowed characters and max length. Is it safe to embed standard base64 (+/=) into the ID?

Also applies to: 1866-1870

🧹 Nitpick comments (5)
core/providers/cohere/utils.go (1)

75-86: Prefer else if + avoid shadowing for additionalProperties extraction.

This is clearer and prevents accidental overwrite if SafeExtractOrderedMap ever starts accepting more input kinds.

Proposed diff
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsMap, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
-			AdditionalPropertiesMap: &addPropsVal,
+			AdditionalPropertiesMap: &addPropsMap,
 		}
 	}
core/schemas/responses.go (1)

421-424: Citations type is fine, but the name is very generic in schemas.

If this is Responses-specific config, consider a more specific exported name (to avoid future collisions like ResponsesCitations / CitationsConfig).

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

768-778: Use else if for mutually exclusive conditions.

The two branches handling additionalProperties (boolean vs. map) are mutually exclusive, but they're written as sequential if statements. This works correctly because SafeExtractOrderedMap will return false for a boolean value, but using else if makes the intent clearer and avoids the unnecessary type check when the boolean branch succeeds.

♻️ Suggested improvement
 	if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/gemini/chat.go (1)

360-368: Standalone ThoughtSignature in streaming also lacks ID correlation.

Similar to the CallID extraction path, standalone ThoughtSignature handling in streaming doesn't set the ID field, unlike the non-streaming counterpart at lines 177-202. Consider adding ID tracking for consistency if the signature relates to a tool call.

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

1145-1155: Use else if for mutually exclusive conditions.

Same pattern issue as in core/providers/anthropic/utils.go - the boolean and map branches are mutually exclusive but written as sequential if statements.

♻️ Suggested improvement
 	// Extract additionalProperties
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd8b2ea and a4107d0.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/providers/gemini/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/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/responses.go
  • core/internal/testutil/structured_outputs.go
  • tests/integrations/python/tests/test_google.py
  • core/schemas/chatcompletions.go
  • core/changelog.md
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/providers/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/responses.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/responses.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/responses.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/gemini/chat.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/types.go
  • core/providers/gemini/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/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.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/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/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/gemini/gemini_test.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/structured_outputs.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_google.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 (9)
core/providers/gemini/chat.go (3)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (857-865)
  • BifrostReasoningDetailsTypeText (853-853)
  • BifrostReasoningDetailsTypeEncrypted (852-852)
core/schemas/utils.go (1)
  • Ptr (14-16)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (127-134)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/anthropic/responses.go (3)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
  • AnthropicStreamDelta (519-527)
  • AnthropicStreamEvent (498-507)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/internal/testutil/structured_outputs.go (2)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (312-315)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (707-710)
core/schemas/responses.go (1)
  • Citations (421-423)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1750-1750: f-string without any placeholders

Remove extraneous f prefix

(F541)


1768-1768: Unused method argument: test_config

(ARG002)


1895-1895: f-string without any placeholders

Remove extraneous f prefix

(F541)


1898-1898: f-string without any placeholders

Remove extraneous f prefix

(F541)


1902-1902: Unused method argument: test_config

(ARG002)


1942-1942: f-string without any placeholders

Remove extraneous f prefix

(F541)


1954-1954: f-string without any placeholders

Remove extraneous f prefix

(F541)


1957-1957: Unused method argument: test_config

(ARG002)


2001-2001: f-string without any placeholders

Remove extraneous f prefix

(F541)


2007-2007: f-string without any placeholders

Remove extraneous f prefix

(F541)


2012-2012: Unused method argument: test_config

(ARG002)


2093-2093: f-string without any placeholders

Remove extraneous f prefix

(F541)


2095-2095: Do not catch blind exception: Exception

(BLE001)


2132-2132: Do not catch blind exception: Exception

(BLE001)

⏰ 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). (1)
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (10)
core/changelog.md (1)

1-6: Changelog entries look consistent with the PR’s intent.

Given this PR is in a stack, just ensure these bullets won’t be duplicated/conflicting with adjacent PR changelogs in the stack merge.

core/schemas/chatcompletions.go (1)

276-308: ToolFunctionParameters: migration + xAI string-JSON support looks coherent.

Good direction for provider compatibility (bool-or-object additionalProperties + parameters-as-string).

core/schemas/responses.go (1)

132-140: ResponsesTextConfigFormatJSONSchema: AdditionalPropertiesStruct adoption is consistent with the schema migration.

Just ensure downstream provider builders populate this as either bool or object and avoid allocating an empty union struct (to prevent additionalProperties: null).

core/providers/gemini/gemini_test.go (1)

558-561: LGTM!

The test correctly uses the new AdditionalPropertiesStruct type with AdditionalPropertiesBool to represent the boolean false value, aligning with the updated schema definition in core/schemas/chatcompletions.go.

core/providers/gemini/chat.go (2)

94-102: Thought text handling correctly separates reasoning from content.

The logic properly routes thought/reasoning text to reasoningDetails with type BifrostReasoningDetailsTypeText instead of mixing it with regular content blocks. The continue ensures subsequent content processing is skipped for thought blocks.

Based on the previous discussion where the developer confirmed that part.Thought, part.Text, and part.ThoughtSignature don't appear together in the same part, this implementation is correct.


388-391: LGTM!

Good update to include ReasoningDetails in the delta content check, ensuring streaming chunks with only reasoning content are properly emitted.

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

421-426: LGTM!

The test correctly constructs AdditionalPropertiesStruct with the boolean value wrapped in AdditionalPropertiesBool, consistent with the schema changes across the codebase.


537-542: LGTM!

Streaming test correctly mirrors the non-streaming test's usage of AdditionalPropertiesStruct.

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

934-984: Empty signature init for thinking blocks on content_block_start is correct (Line 937, 953, 983).

This matches Anthropic streaming behavior: signature arrives later via signature_delta. Based on learnings, this should not be “optimized away”.

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

248-336: Citations union (config vs array) marshal/unmarshal looks solid.

Mutual exclusivity checks + clearing the opposite field in UnmarshalJSON (Line 322-333) prevents stale-state issues on reused instances.

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

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from a4107d0 to b0cf7c7 Compare January 14, 2026 06:31
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1319 to 01-12-feat_send_back_raw_request_with_bifrost_errors January 14, 2026 06:31
@TejasGhatte TejasGhatte changed the base branch from 01-12-feat_send_back_raw_request_with_bifrost_errors to graphite-base/1319 January 14, 2026 06:41
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: 4

Caution

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

⚠️ Outside diff range comments (1)
core/providers/gemini/responses.go (1)

2404-2415: Logic inconsistency: Look-ahead should be conditional.

This block unconditionally checks the next message and overrides any thought signature already extracted from CallID. This differs from the similar logic at lines 269-286 which uses an else clause to only look ahead when no signature was found in the CallID.

Consider making this conditional to maintain consistency:

🔧 Suggested fix
+				// Only look ahead if we didn't find a signature in the CallID
+				if len(part.ThoughtSignature) == 0 {
 				// Preserve thought signature from ResponsesReasoning message (required for Gemini 3 Pro)
 				// Look ahead to see if the next message is a reasoning message with encrypted content
 				if i+1 < len(messages) {
 					nextMsg := messages[i+1]
 					if nextMsg.Type != nil && *nextMsg.Type == schemas.ResponsesMessageTypeReasoning &&
 						nextMsg.ResponsesReasoning != nil && nextMsg.ResponsesReasoning.EncryptedContent != nil {
 						decodedSig, err := base64.StdEncoding.DecodeString(*nextMsg.ResponsesReasoning.EncryptedContent)
 						if err == nil {
 							part.ThoughtSignature = decodedSig
 						}
 					}
 				}
+				}
🤖 Fix all issues with AI agents
In `@core/providers/anthropic/responses.go`:
- Around line 919-924: The code chooses streamResp.Index using OutputIndex first
but the synthetic input_json_delta events still use bifrostResp.ContentIndex,
causing mismatched indices; compute a single chosenIndex (prefer
bifrostResp.OutputIndex, fallback to bifrostResp.ContentIndex), assign it to
streamResp.Index, and use that same chosenIndex when emitting synthetic events
(e.g., input_json_delta/content_block_delta) so content_block_start and
subsequent deltas share the identical index; apply the same change at the other
occurrence mentioned (around the 1043–1047 block).

In `@core/providers/gemini/responses.go`:
- Around line 2395-2402: The code decodes a tool signature using
base64.StdEncoding.DecodeString but the signature was URL-safe encoded; replace
the decoder call to use base64.RawURLEncoding (e.g.,
base64.RawURLEncoding.DecodeString(thoughtSig)) when assigning
part.ThoughtSignature and keep the existing error handling (clearing thoughtSig
on decode error) intact so signatures encoded without padding or URL-safe
characters are decoded correctly.
- Around line 1867-1871: The code appends a base64 signature to callID using
base64.StdEncoding.EncodeToString which produces '+', '/', '=' characters that
violate Gemini tool-ID constraints; change the encoding to a URL-safe variant
(e.g., base64.RawURLEncoding.EncodeToString) when constructing thoughtSig from
part.ThoughtSignature so the resulting callID (combined with
thoughtSignatureSeparator) contains only URL-safe characters, matching the
streaming-path fix that handled the same issue.
♻️ Duplicate comments (7)
core/schemas/chatcompletions.go (1)

324-379: Avoid emitting additionalProperties: null when callers construct an “empty” struct.
AdditionalPropertiesStruct.MarshalJSON() returns null when both fields are nil; if any code sets ToolFunctionParameters.AdditionalProperties = &AdditionalPropertiesStruct{}, JSON becomes "additionalProperties": null (likely invalid JSON Schema).

One defensive option
 func (a AdditionalPropertiesStruct) MarshalJSON() ([]byte, error) {
@@
-    // If both are nil, return null
-    return Marshal(nil)
+    // If both are nil, callers should omit the field (set pointer to nil).
+    return nil, fmt.Errorf("additionalProperties cannot be null; omit the field instead")
 }
tests/integrations/python/tests/test_google.py (2)

1771-1904: Add candidate/content guards before indexing in test_30b_*.
This currently indexes response_1.candidates[0] / response_2.candidates[0] without asserting candidates exist; failures will show up as IndexError instead of a useful assertion message. Also test_config is unused (ARG002).

Proposed diff
 `@skip_if_no_api_key`("gemini")
-def test_30b_gemini_3_sequential_function_calls_signatures(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30b_gemini_3_sequential_function_calls_signatures(self):
@@
         response_1 = client.models.generate_content(
@@
         )
+        assert response_1.candidates, "Response should have candidates"
@@
         for part in response_1.candidates[0].content.parts:
@@
         response_2 = client.models.generate_content(
@@
         )
+        assert response_2.candidates, "Response should have candidates"

2015-2144: Fix unused test_config parameter + add candidates guard + replace broad exception handlers.

  1. Unused test_config parameter (ARG002): Use @pytest.mark.usefixtures("test_config") and remove it from the method signature, or rename to _test_config.

  2. Missing candidates guard: Add assert response_1.candidates, "Response should have candidates" before line 2047 where the code accesses response_1.candidates[0].content.parts, to prevent IndexError if the response has no candidates.

  3. Broad except Exception handlers (BLE001) at lines 2099 and 2136: Since google.api_core.exceptions is not imported, either:

    • Import and catch specific exception types: from google.api_core import exceptions and use except (exceptions.InvalidArgument, ValueError) as e:
    • Or add # noqa: BLE001 if intentionally catching all exceptions in integration tests.

For HTTP 400 validation errors, the SDK raises google.api_core.exceptions.InvalidArgument; for client-side validation failures, it raises ValueError or TypeError.

Proposed diff (shape)
 `@skip_if_no_api_key`("gemini")
-def test_30e_gemini_3_signature_validation_strict(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30e_gemini_3_signature_validation_strict(self):
@@
         response_1 = client.models.generate_content(
@@
         )
+        assert response_1.candidates, "Response should have candidates"
@@
         for part in response_1.candidates[0].content.parts:
@@
-        except Exception as e:
+        except (ValueError, TypeError) as e:  # or import google.api_core.exceptions
             pytest.fail(f"Request with signature should not fail: {e}")
@@
-        except Exception as e:
+        except (ValueError, TypeError) as e:
             error_msg = str(e).lower()
core/providers/gemini/chat.go (1)

342-350: Missing ID field in streaming path signature extraction.

The non-streaming path (lines 154-159) sets ID: schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)) for reasoning details extracted from CallID, but this streaming path doesn't set the ID field. This inconsistency could make it harder to correlate signatures with their corresponding tool calls in streaming responses.

🔧 Suggested fix
 				// If we extracted a signature from CallID, add it to reasoning details
 				if len(extractedSig) > 0 {
 					thoughtSig := base64.StdEncoding.EncodeToString(extractedSig)
 					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 						Index:     len(reasoningDetails),
 						Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
 						Signature: &thoughtSig,
+						ID:        schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
 					})
 				}
core/providers/gemini/responses.go (3)

1057-1061: Critical: Use URL-safe base64 encoding for tool IDs.

Gemini tool IDs must match ^[a-zA-Z0-9_-]{1,64}$. Using base64.StdEncoding produces +, /, and = characters that will cause Gemini to reject these tool IDs. Use base64.RawURLEncoding which produces only URL-safe characters (- and _).

🔧 Suggested fix
 	// Attach thought signature to ID if present
 	if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
-		encoded := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
+		encoded := base64.RawURLEncoding.EncodeToString(part.ThoughtSignature)
 		toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
 	}

457-477: Use URL-safe base64 decoding for signatures extracted from tool IDs.

The signature in CallID is encoded using base64 for embedding in tool IDs. Since tool IDs require URL-safe characters, the decoding should use base64.RawURLEncoding to match the encoding scheme.

🔧 Suggested fix
 				if thoughtSig != "" {
-					if decodedSig, err := base64.StdEncoding.DecodeString(thoughtSig); err == nil {
+					if decodedSig, err := base64.RawURLEncoding.DecodeString(thoughtSig); err == nil {
 						functionCallPart.ThoughtSignature = decodedSig
 					}
 				}

246-262: Use URL-safe base64 decoding to match encoding used for tool IDs.

Per Gemini's tool ID constraints (^[a-zA-Z0-9_-]{1,64}$), signatures embedded in tool IDs must be URL-safe encoded. The decoding here should use base64.RawURLEncoding to match, otherwise it will fail when receiving properly URL-safe encoded signatures.

🔧 Suggested fix
-							if decodedSig, err := base64.StdEncoding.DecodeString(parts[1]); err == nil {
+							if decodedSig, err := base64.RawURLEncoding.DecodeString(parts[1]); err == nil {
🧹 Nitpick comments (8)
tests/integrations/python/tests/test_google.py (3)

1681-1770: Reduce flake risk + fix unused fixture arg in test_30a_*.

  • test_config is unused (Ruff ARG002). Prefer @pytest.mark.usefixtures("test_config") and drop the arg, or rename to _test_config.
  • If the model returns 0 function calls, the current else-branch fails with a misleading “should have thought signature” assertion; explicitly assert/guard len(function_calls) > 0 (or pytest.skip(...) if Gemini didn’t tool-call).
Proposed diff
 `@skip_if_no_api_key`("gemini")
-def test_30a_gemini_3_parallel_function_calls_signatures(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30a_gemini_3_parallel_function_calls_signatures(self):
@@
         # Validate parallel function calls
         assert response.candidates, "Response should have candidates"
@@
         for idx, part in enumerate(response.candidates[0].content.parts):
@@
-        # According to Gemini docs: only the FIRST function call should have the signature
+        assert len(function_calls) > 0, "Expected at least one tool call; model may have answered without tools"
+        # According to Gemini docs: only the FIRST function call should have the signature
         if len(function_calls) > 1:
@@
-        else:
+        else:
             print(f"  Only {len(function_calls)} function call(s) made")
             # If only one call, it should still have a signature
             assert len(signatures) > 0, "Function call should have thought signature"

1905-1960: Consider asserting at least one of thought/text parts (test currently mostly informational).
Right now this will pass even if the model returns an empty/odd structure as long as candidates exists; if that’s intentional, OK, but otherwise add a minimal invariant like “has any thought/text part”.


1961-2014: Consider minimal invariants per thinking level (or explicitly mark as observational).
As written, this doesn’t assert anything about level differences; if this is meant to validate behavior changes across the stack, consider at least one stable assertion (or convert to pytest.skip-style observational logging).

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

75-86: Prefer else if for the map case (clear mutual exclusivity).
Right now the map block can overwrite the bool block (even if unlikely); else if makes intent explicit.

Proposed diff
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/providers/gemini/types.go (1)

22-24: Delimiter constant looks fine; consider guarding against ID collisions.

If tool IDs can contain "_ts_", decoding may split incorrectly. Consider centralizing encode/decode helpers that either escape the base ID or use an unambiguous encoding scheme (e.g., always append base64 signature with a fixed prefix length).

core/providers/gemini/gemini_test.go (1)

558-562: Test update matches the new AdditionalPropertiesStruct API.

One small improvement: assert that the converted Gemini responseJsonSchema still has additionalProperties: false to ensure the wrapper is being serialized/mapped as intended (and not silently dropped).

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

768-778: Consider using else if for mutual exclusivity clarity.

The current two separate if blocks work correctly because SafeExtractOrderedMap returns false for boolean values. However, using else if would make the mutual exclusivity explicit and slightly more efficient by avoiding the second type check when the first succeeds.

♻️ Optional: Use else-if for clarity
 	if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/gemini/utils.go (1)

1145-1155: AdditionalProperties handling matches cross-provider pattern.

The dual-path handling for boolean vs. map AdditionalProperties is consistent with the Anthropic provider implementation. Same optional suggestion applies: consider using else if for explicit mutual exclusivity.

♻️ Optional: Use else-if for clarity (same as Anthropic)
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4107d0 and b0cf7c7.

📒 Files selected for processing (17)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 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/providers/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • tests/integrations/python/tests/test_google.py
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.go
🧠 Learnings (12)
📚 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/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • 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/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.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/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/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/gemini/gemini_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/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.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/gemini/gemini_test.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/types.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/responses.go
  • core/providers/cohere/utils.go
  • core/providers/anthropic/types.go
📚 Learning: 2025-12-12T10:28:54.988Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/anthropic/chat.go:207-216
Timestamp: 2025-12-12T10:28:54.988Z
Learning: In core/providers/anthropic/chat.go, within ToAnthropicChatRequest, ensure the mapping from ReasoningDetails to Anthropic thinking blocks preserves all fields from ReasoningDetails, including when Text is nil or empty. Do not drop or coerce missing Text; propagate nil/empty values to maintain complete bidirectional mapping of Anthropic events in Bifrost. Add tests to verify that ReasoningDetails with Text nil or "" round-trip without loss, and update code to handle nullable Text without dropping data. If there are multiple blocks, preserve order and all fields during mapping.

Applied to files:

  • core/providers/anthropic/chat.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/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/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_google.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
📚 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/structured_outputs.go
🧬 Code graph analysis (11)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/gemini/chat.go (3)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (869-877)
  • BifrostReasoningDetailsTypeText (865-865)
  • BifrostReasoningDetailsTypeEncrypted (864-864)
core/schemas/utils.go (1)
  • Ptr (14-16)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (127-134)
core/providers/anthropic/chat.go (1)
core/providers/anthropic/types.go (1)
  • AnthropicContentBlockTypeToolResult (209-209)
core/providers/anthropic/responses.go (3)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
  • AnthropicStreamDelta (519-527)
  • AnthropicStreamEvent (498-507)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/internal/testutil/structured_outputs.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/providers/gemini/responses.go (2)
core/schemas/responses.go (3)
  • ResponsesToolMessage (475-495)
  • ResponsesMessageTypeReasoning (312-312)
  • ResponsesReasoning (745-748)
core/providers/gemini/types.go (5)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • Type (825-825)
  • Content (1016-1024)
  • FunctionResponse (1249-1271)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (719-722)
core/schemas/responses.go (1)
  • Citations (421-423)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1772-1772: Unused method argument: test_config

(ARG002)


1906-1906: Unused method argument: test_config

(ARG002)


1961-1961: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2099-2099: Do not catch blind exception: Exception

(BLE001)


2136-2136: Do not catch blind exception: Exception

(BLE001)

⏰ 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). (11)
  • 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 (18)
tests/integrations/python/tests/test_google.py (2)

661-661: Nice cleanup: avoid f-string without placeholders.


1597-1599: Nice cleanup: consistent static header printing.

core/schemas/chatcompletions.go (2)

276-282: Public schema change looks consistent: additionalProperties can now be bool OR object.
Good direction for compatibility with JSON Schema producers that emit object-valued additionalProperties.


286-320: Good normalization: prevents additionalProperties: null from bypassing omitempty.
This keeps round-trips cleaner by converting “present but empty” into “absent”.

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

219-237: ContentBlock extensions look coherent (signature/tool/web-search fields).
One note: if you rely on sending signature: "" in some flows, ensure the pointer is set to a non-nil empty string (omitempty won’t drop it in that case). Based on learnings, signature may arrive later via deltas.

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

937-937: Signature initialization to empty string for thinking blocks is consistent with streaming expectations.

Keeping contentBlock.Signature = schemas.Ptr("") on content_block_start matches the expected “signature arrives later via signature_delta” behavior. Based on learnings, this is correct.

Also applies to: 953-954, 983-984


1169-1172: Fix StopSequence: nil at line 1189—will serialize as stop_sequence: null.

AnthropicStreamDelta.StopSequence has a non-omitempty JSON tag (json:"stop_sequence"), so assigning nil will serialize as null in the response. If the Anthropic API or clients expect a string value, this breaks the contract. Use schemas.Ptr("") instead to match the behavior at line 1171.

Proposed fix
-                StopSequence: nil,
+                StopSequence: schemas.Ptr(""),

Likely an incorrect or invalid review comment.

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

177-181: Good: use AnthropicContentBlockTypeToolResult instead of a raw string.

This reduces drift from the canonical type constants.

core/schemas/responses.go (2)

131-140: LGTM! AdditionalProperties type change aligns with cross-provider standardization.

The change from *bool to *AdditionalPropertiesStruct enables this field to represent either a boolean or an object/map, which is consistent with the JSON Schema specification where additionalProperties can be either. This aligns with similar changes in core/providers/anthropic/utils.go and core/providers/gemini/utils.go.


421-423: New Citations struct looks good.

Simple and focused struct for citation enablement. Consider whether this struct should live closer to where it's consumed (e.g., alongside other citation-related types) or if there are plans to expand it with additional fields.

core/providers/gemini/chat.go (3)

94-102: Thought/reasoning handling looks correct.

The continue statement here is appropriate since the author confirmed that part.Text, part.Thought, and part.ThoughtSignature don't appear together in the same part from Gemini's API.


130-160: Signature extraction from CallID is well-implemented.

The Gemini 3 behavior of embedding thought signatures in CallIDs is handled correctly:

  1. Detects the separator in CallID
  2. Base64 decodes the signature portion
  3. Uses the base ID for the tool call
  4. Creates a properly linked reasoning detail with tool_call_<id> format

388-388: Delta content check is correct.

The updated check properly considers ReasoningDetails along with other delta content types, aligning with the removal of the separate Reasoning field.

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

421-427: Test utility correctly updated for AdditionalPropertiesStruct.

The test properly wraps the boolean additionalProps in the new AdditionalPropertiesStruct wrapper type, maintaining consistency with the schema changes.


537-542: Streaming test also correctly updated.

Consistent with the non-streaming test update above.

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

268-286: LGTM!

The conditional look-ahead logic (using else) correctly avoids checking the next message when a signature was already extracted from the CallID. The StdEncoding usage here is appropriate since EncryptedContent is a JSON field, not a tool ID.


1063-1099: LGTM!

The addedEvent and doneEvent construction is consistent, both using the same augmented toolUseID. The guard against double-appending signatures (line 1058) properly prevents ID corruption on round-trips.


2440-2448: LGTM!

The FunctionResponse.ID is correctly set to the full CallID, preserving the embedded thought signature for round-trip compatibility with Gemini's API.

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

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from b0cf7c7 to c33a2d1 Compare January 14, 2026 07:15
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1319 to 01-12-feat_send_back_raw_request_with_bifrost_errors January 14, 2026 07:15
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/anthropic/responses.go (1)

1043-1047: Index mismatch: synthetic deltas still use ContentIndex instead of the computed index.

The content_block_start events at lines 919-924 prioritize OutputIndex, but synthetic input_json_delta events at line 1045 still use bifrostResp.ContentIndex. This causes mismatched indices between content_block_start and content_block_delta events when OutputIndex ≠ ContentIndex.

🐛 Proposed fix
 		if shouldGenerateDeltas && argumentsJSON != "" {
 			// Generate synthetic input_json_delta events by chunking the JSON
-			deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, bifrostResp.ContentIndex)
+			// Use the same index as content_block_start for consistency
+			index := bifrostResp.OutputIndex
+			if index == nil {
+				index = bifrostResp.ContentIndex
+			}
+			deltaEvents := generateSyntheticInputJSONDeltas(argumentsJSON, index)
 			events = append(events, deltaEvents...)
 		}
🤖 Fix all issues with AI agents
In `@tests/integrations/python/tests/test_google.py`:
- Around line 1822-1839: Before indexing into
response_1.candidates[0].content.parts, add explicit assertions that response_1
has a non-empty candidates list and that the first candidate has a content with
a non-empty parts list to produce clear failures instead of
IndexError/AttributeError; e.g., assert response_1.candidates and
len(response_1.candidates) > 0 and hasattr(response_1.candidates[0], 'content')
and getattr(response_1.candidates[0].content, 'parts', None), then proceed to
iterate and set first_fc/first_signature as before. Apply the same pattern
(assert candidates exists/non-empty and content.parts exists/non-empty) to the
other blocks mentioned (around lines 1880-1904, 1924-1928, 2052-2066)
referencing the same symbols (response_1, response, candidates, content, parts,
first_fc, first_signature) so tests fail with clear assertion messages.
♻️ Duplicate comments (4)
core/schemas/chatcompletions.go (1)

324-350: Prevent accidental emission of additionalProperties: null when callers construct an “empty” struct.
AdditionalPropertiesStruct.MarshalJSON() still returns null when both fields are nil; if any code ever does AdditionalProperties: &AdditionalPropertiesStruct{}, JSON output will include additionalProperties: null (some JSON Schema consumers reject this). Consider either (a) treating “both nil” as {} (empty schema) or (b) returning an error to force callers to omit the field. Also consider marshaling the map by value.

Safer MarshalJSON behavior (one possible direction)
 func (a AdditionalPropertiesStruct) MarshalJSON() ([]byte, error) {
@@
 	// If map is set, marshal as object
 	if a.AdditionalPropertiesMap != nil {
-		return Marshal(a.AdditionalPropertiesMap)
+		return Marshal(*a.AdditionalPropertiesMap)
 	}
 
-	// If both are nil, return null
-	return Marshal(nil)
+	// If both are nil, avoid emitting `null` (prefer empty schema or omit field).
+	return Marshal(OrderedMap{})
 }
#!/bin/bash
# Find places that may construct an empty AdditionalPropertiesStruct pointer
rg -n --type=go 'AdditionalProperties:\s*&schemas\.AdditionalPropertiesStruct\s*\{\s*\}' -S
rg -n --type=go 'AdditionalProperties:\s*&AdditionalPropertiesStruct\s*\{\s*\}' -S
tests/integrations/python/tests/test_google.py (2)

1681-1683: Fix Ruff ARG002 without losing the test_config fixture.
These tests accept test_config but don’t use it. Prefer @pytest.mark.usefixtures("test_config") and drop the arg (vs renaming, which would change fixture injection).

Proposed diff
 `@skip_if_no_api_key`("gemini")
-def test_30a_gemini_3_parallel_function_calls_signatures(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30a_gemini_3_parallel_function_calls_signatures(self):
@@
 `@skip_if_no_api_key`("gemini")
-def test_30b_gemini_3_sequential_function_calls_signatures(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30b_gemini_3_sequential_function_calls_signatures(self):
@@
 `@skip_if_no_api_key`("gemini")
-def test_30c_gemini_3_thought_signatures_in_text_responses(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30c_gemini_3_thought_signatures_in_text_responses(self):
@@
 `@skip_if_no_api_key`("gemini")
-def test_30d_gemini_3_thinking_levels(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30d_gemini_3_thinking_levels(self):
@@
 `@skip_if_no_api_key`("gemini")
-def test_30e_gemini_3_signature_validation_strict(self, test_config):
+@pytest.mark.usefixtures("test_config")
+def test_30e_gemini_3_signature_validation_strict(self):

Also applies to: 1771-1773, 1905-1907, 1960-1962, 2015-2017


2099-2142: Avoid broad except Exception (or explicitly suppress BLE001) in test_30e.
If this is intentionally broad for an integration test, add # noqa: BLE001. Otherwise, catch the SDK’s concrete exception types.

Option A (keep broad catch, suppress Ruff)
-        except Exception as e:
+        except Exception as e:  # noqa: BLE001
             pytest.fail(f"Request with signature should not fail: {e}")
@@
-        except Exception as e:
+        except Exception as e:  # noqa: BLE001
             error_msg = str(e).lower()
google genai (google-genai) python sdk: what exception types does client.models.generate_content raise? (API/validation errors)
core/providers/gemini/chat.go (1)

342-350: Streaming path missing ID field in reasoning details for extracted signatures.

The non-streaming path (lines 154-159) sets ID: schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)) for reasoning details extracted from CallID, but the streaming path here doesn't set the ID field. This inconsistency could make it harder to correlate signatures with their corresponding tool calls in streaming responses.

🔧 Suggested fix
 			// If we extracted a signature from CallID, add it to reasoning details
 			if len(extractedSig) > 0 {
 				thoughtSig := base64.StdEncoding.EncodeToString(extractedSig)
 				reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 					Index:     len(reasoningDetails),
 					Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
 					Signature: &thoughtSig,
+					ID:        schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
 				})
 			}
🧹 Nitpick comments (8)
core/changelog.md (2)

1-1: Use imperative mood for consistency.

The entry uses past tense ("added") while the other entries follow the imperative mood convention ("add", "improve", "enhance"). For consistency with conventional commits format, use "add" instead of "added".

📝 Proposed fix
-chore: added case-insensitive helper methods for header and query parameter lookups in HTTPRequest
+chore: add case-insensitive helper methods for header and query parameter lookups in HTTPRequest

6-6: Clarify the Anthropic streaming fix description.

The description "anthropic streaming events through integration" is vague. According to the PR summary, there are multiple specific Anthropic changes (OutputIndex/ContentIndex prioritization, signature fields in thinking blocks, stop reason handling). Consider being more specific about what was fixed to improve changelog clarity.

Example improvements

Consider splitting or clarifying the entry:

-fix: anthropic streaming events through integration
+fix: improve anthropic streaming with proper index handling and stop reason support

Or if these are distinct fixes, consider separate entries:

- fix: prioritize OutputIndex over ContentIndex in Anthropic streaming
- fix: add signature fields to Anthropic thinking content blocks
- fix: add stop reason handling in Anthropic message_stop events
core/providers/anthropic/utils.go (1)

768-778: Use else if to clarify mutually exclusive branches.

The two if blocks at lines 768-771 and 774-777 handle mutually exclusive cases (boolean vs. map), but they're written as sequential if statements. While functionally correct (since SafeExtractOrderedMap won't match a boolean), using else if would make the intent clearer and prevent accidental overwrites if the type-checking logic changes.

♻️ Suggested refactor
 	if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/cohere/utils.go (1)

76-86: Use else if to clarify mutually exclusive branches.

Same pattern issue as in anthropic/utils.go - the boolean and map extraction are mutually exclusive but written as sequential if statements. Using else if would be clearer.

♻️ Suggested refactor
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/providers/gemini/chat.go (1)

318-328: Consider extracting signature extraction logic to a helper function.

The signature extraction from CallID (lines 130-140 in non-stream, lines 318-328 in stream) is duplicated. Consider extracting to a helper function like extractSignatureFromCallID(callID string) (baseID string, signature []byte) to improve maintainability.

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

1145-1155: Consider using else if for mutually exclusive type handling.

The two if blocks handle the boolean and object cases for additionalProperties. While logically correct (a value can only be one type), using else if on line 1151 would make the mutual exclusivity explicit and skip the unnecessary SafeExtractOrderedMap call when the boolean branch matches.

♻️ Suggested refactor
 	// Extract additionalProperties
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/gemini/responses.go (1)

268-286: Inconsistent base64 encoding: uses StdEncoding for EncryptedContent but RawURLEncoding for CallID signatures.

Line 278 uses base64.StdEncoding.DecodeString to decode EncryptedContent from reasoning messages, while line 256 uses base64.RawURLEncoding.DecodeString for signatures embedded in CallIDs. This is intentional since EncryptedContent comes from a different source (Gemini API's native format), but consider adding a comment to clarify the distinction.

Suggested clarification comment
 						} else {
 							// Otherwise, look ahead to see if the next message is a reasoning message with encrypted content
 							// (thought signature for this function call)
 							if i+1 < len(bifrostResp.Output) {
 								nextMsg := bifrostResp.Output[i+1]
 								if nextMsg.Type != nil && *nextMsg.Type == schemas.ResponsesMessageTypeReasoning &&
 									nextMsg.ResponsesReasoning != nil && nextMsg.ResponsesReasoning.EncryptedContent != nil {
+									// EncryptedContent uses standard base64 (from Gemini API native format)
 									decodedSig, err := base64.StdEncoding.DecodeString(*nextMsg.ResponsesReasoning.EncryptedContent)
core/providers/anthropic/types.go (1)

293-336: AnthropicCitations dual-mode marshal/unmarshal handles request vs response formats correctly.

The implementation properly distinguishes between:

  • Request format: {enabled: true} marshaled via Config
  • Response format: [{type: "...", cited_text: "...", ...}] marshaled via TextCitations

However, there's a subtle issue in UnmarshalJSON: the order of attempts matters for robustness.

Consider swapping unmarshal order for better error handling

Currently, the code tries array first, then config. Since an empty array [] would successfully unmarshal as []AnthropicTextCitation (empty slice), and config objects like {enabled: true} would fail array unmarshal, the current order works. However, consider adding a check for ambiguous cases:

 func (ac *AnthropicCitations) UnmarshalJSON(data []byte) error {
+	// Handle null case
+	if string(data) == "null" {
+		return nil
+	}
+
 	// Try to unmarshal as array of citations
 	var textCitations []AnthropicTextCitation
 	if err := sonic.Unmarshal(data, &textCitations); err == nil {

This would handle null JSON values gracefully.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0cf7c7 and c33a2d1.

📒 Files selected for processing (17)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/internal/testutil/structured_outputs.go
  • transports/changelog.md
  • core/providers/gemini/gemini_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/providers/anthropic/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/changelog.md
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
  • tests/integrations/python/tests/test_google.py
🧠 Learnings (12)
📚 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/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/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/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/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/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.go
📚 Learning: 2025-12-12T10:28:54.988Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/anthropic/chat.go:207-216
Timestamp: 2025-12-12T10:28:54.988Z
Learning: In core/providers/anthropic/chat.go, within ToAnthropicChatRequest, ensure the mapping from ReasoningDetails to Anthropic thinking blocks preserves all fields from ReasoningDetails, including when Text is nil or empty. Do not drop or coerce missing Text; propagate nil/empty values to maintain complete bidirectional mapping of Anthropic events in Bifrost. Add tests to verify that ReasoningDetails with Text nil or "" round-trip without loss, and update code to handle nullable Text without dropping data. If there are multiple blocks, preserve order and all fields during mapping.

Applied to files:

  • core/providers/anthropic/chat.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/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/utils.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/chat.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/providers/gemini/responses.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/cohere/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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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: 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_google.py
🧬 Code graph analysis (10)
core/providers/anthropic/chat.go (1)
core/providers/anthropic/types.go (1)
  • AnthropicContentBlockTypeToolResult (209-209)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/schemas/chatcompletions.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)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/providers/gemini/responses.go (2)
core/providers/gemini/types.go (3)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • FunctionResponse (1249-1271)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/chat.go (5)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (869-877)
  • BifrostReasoningDetailsTypeText (865-865)
  • BifrostReasoningDetailsTypeEncrypted (864-864)
ui/app/page.tsx (1)
  • Index (5-7)
core/providers/gemini/types.go (3)
  • Type (825-825)
  • Role (25-25)
  • Content (1016-1024)
core/schemas/utils.go (1)
  • Ptr (14-16)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (127-134)
core/providers/anthropic/responses.go (3)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
  • AnthropicStreamDelta (519-527)
  • AnthropicStreamEvent (498-507)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
tests/integrations/python/tests/test_google.py (2)
tests/integrations/python/tests/utils/common.py (1)
  • skip_if_no_api_key (1830-1841)
core/providers/gemini/types.go (4)
  • Tool (647-674)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1772-1772: Unused method argument: test_config

(ARG002)


1906-1906: Unused method argument: test_config

(ARG002)


1961-1961: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2099-2099: Do not catch blind exception: Exception

(BLE001)


2136-2136: Do not catch blind exception: Exception

(BLE001)

⏰ 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). (16)
  • 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 (28)
tests/integrations/python/tests/test_google.py (2)

661-674: Good: removed unnecessary f-string in PDF test output.
No further changes needed here.


1595-1601: Good: static token-usage header print.
No further changes needed here.

core/schemas/chatcompletions.go (1)

276-320: Nice: additionalProperties now supports both boolean and object, with null→omitted normalization.
The post-unmarshal normalization avoids additionalProperties: null leaking via omitempty when the struct is reused.

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

22-24: Separator collision is already handled safely. The implementation correctly uses strings.SplitN(id, thoughtSignatureSeparator, 2) at all parsing sites, which prevents signatures containing _ts_ from breaking the split. All usages in responses.go and chat.go follow this pattern consistently, so no changes are needed.

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

178-178: LGTM - Good use of constant instead of magic string.

This change correctly uses the AnthropicContentBlockTypeToolResult constant, consistent with other type assignments in this file (lines 156, 191, 230, etc.).

core/schemas/responses.go (2)

131-140: LGTM - Type change aligns with broader AdditionalProperties refactoring.

The change from *bool to *AdditionalPropertiesStruct for AdditionalProperties is consistent with the schema updates across the codebase to support both boolean and object types for JSON schema's additionalProperties.


421-423: No action required — the Citations struct is actively used.

The struct is referenced in core/providers/anthropic/types.go (line 298, 328) as *schemas.Citations within the AnthropicCitations type, where it handles citation configuration during JSON marshaling/unmarshaling for Anthropic API requests. This is a valid schema definition pattern where central schema types are used by provider-specific implementations.

Likely an incorrect or invalid review comment.

core/providers/gemini/chat.go (3)

94-102: LGTM - Thought/reasoning text handling.

The logic correctly separates thought content (where part.Thought is true) into reasoningDetails rather than regular content blocks.


130-160: LGTM - Signature extraction from CallID with proper ID correlation.

The non-streaming path correctly:

  1. Extracts embedded signatures from CallID using the separator
  2. Uses the base ID (without signature) for the tool call
  3. Adds reasoning details with an ID linking to the tool call

388-388: LGTM - Updated delta content check.

The hasDeltaContent check correctly includes ReasoningDetails to ensure chunks with only reasoning content are emitted.

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

898-924: Index prioritization pattern looks correct.

The consistent use of OutputIndex with fallback to ContentIndex for Anthropic global indexing is properly implemented across both computer tool and regular content block handling paths.


937-937: Empty signature initialization is correct per Anthropic spec.

Based on learnings, Anthropic's streaming API requires an empty signature field in content_block_start events for thinking blocks. The actual signature value arrives later via signature_delta events.


946-993: Consistent signature handling across reasoning/thinking block variants.

The signature initialization at lines 953 and 983 follows the same pattern established for thinking blocks, correctly handling edge cases where reasoning content may be misclassified or thinking is enabled for the first block.


1054-1098: Consistent OutputIndex prioritization across delta event handlers.

The OutputIndex-first pattern with ContentIndex fallback is consistently applied across OutputTextDelta, FunctionCallArgumentsDelta, and ReasoningSummaryTextDelta handlers, aligning with the content_block_start indexing.


1118-1161: Correct index handling in OutputItemDone event emission.

The OutputIndex-first pattern is properly applied for both computer tool completion and general content block completion paths.


1165-1192: StopSequence handling correctly differentiates default vs explicit stop reasons.

The code intentionally uses StopSequence: schemas.Ptr("") for the default case (line 1171) and StopSequence: nil when an explicit StopReason is provided from the response (line 1189). This ensures the field is present in the default end_turn case but omitted when a specific stop reason (like tool_use) is set.


1194-1224: MCP call handlers follow consistent indexing pattern.

Both MCPCallArgumentsDelta and MCPCallCompleted event handlers properly apply the OutputIndex-first pattern with ContentIndex fallback.

core/providers/gemini/responses.go (8)

246-261: LGTM - Thought signature extraction from CallID is correctly implemented.

The code properly extracts the thought signature using base64.RawURLEncoding.DecodeString which matches the encoding used elsewhere when embedding signatures into CallIDs. The SplitN with limit 2 ensures only the first separator is used for splitting.


457-477: LGTM - Streaming path correctly extracts and decodes thought signatures.

The streaming function call handling properly:

  1. Extracts signature from CallID using the separator
  2. Preserves the full CallID (including signature) in functionCall.ID
  3. Decodes using base64.RawURLEncoding matching the encoding path

This is now consistent with the non-streaming path.


1057-1061: LGTM - Signature embedding correctly uses RawURLEncoding and guards against double-appending.

The guard !strings.Contains(toolUseID, thoughtSignatureSeparator) prevents double-appending signatures, and base64.RawURLEncoding produces URL-safe characters compliant with Gemini's tool ID constraints.


1065-1099: LGTM - Function call streaming events correctly propagate the augmented tool ID.

Both the addedEvent and doneEvent use the same toolUseID (with embedded signature), ensuring consistent ID correlation across the streaming lifecycle.


1866-1870: LGTM - Non-streaming path now matches streaming with guard and RawURLEncoding.

The fix addresses the past review comment by:

  1. Using base64.RawURLEncoding.EncodeToString (URL-safe)
  2. Including the guard !strings.Contains(callID, thoughtSignatureSeparator) to prevent double-appending

1903-1915: Good defensive handling for extracting function name from response ID.

The code properly handles the case where FunctionResponse.Name is empty by falling back to extracting it from the ID, stripping the thought signature suffix if present. This ensures the function name is always populated.


2388-2412: LGTM - Function call conversion correctly extracts and decodes thought signatures.

The code:

  1. Extracts signature from CallID when separator is present
  2. Preserves the full CallID for the FunctionCall.ID
  3. Decodes using base64.RawURLEncoding.DecodeString matching the encoding path
  4. Silently handles decode errors by clearing thoughtSig

2450-2455: LGTM - FunctionResponse correctly preserves the full CallID.

The FunctionResponse.ID is set to the full CallID, maintaining consistency with how function calls embed their thought signatures.

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

219-238: LGTM - AnthropicContentBlock fields properly extended.

The new fields (URL, EncryptedContent, PageAge) support web search results, and the Type field correctly no longer uses omitempty since it's always required. The Citations field type change from a simple config to *AnthropicCitations enables the dual request/response handling.

Based on learnings, the URL field for web search results and EncryptedContent for redacted thinking align with Anthropic's API schema.


248-291: Well-structured citation types covering all Anthropic citation variants.

The AnthropicTextCitation struct comprehensively covers all five citation types with appropriate optional fields:

  • char_location: character indices + document fields
  • page_location: page numbers + document fields
  • content_block_location: block indices + document fields
  • web_search_result_location: encrypted_index, title, url
  • search_result_location: search_result_index, source, block indices

Based on learnings, the FileID field for document-based citations and URL field for web search citations match the official Anthropic API schema.


304-316: MarshalJSON correctly validates mutual exclusivity.

The validation at line 305-307 prevents invalid state where both Config and TextCitations are set, and the fallback to nil when both are unset is appropriate.

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

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from c33a2d1 to 973d8fb Compare January 14, 2026 11:03
@TejasGhatte TejasGhatte force-pushed the 01-12-feat_send_back_raw_request_with_bifrost_errors branch from 9e1faa8 to 4cf1e95 Compare January 14, 2026 11:03
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/gemini/responses.go (1)

1057-1100: Fix mismatched streaming item IDs when appending thought signature to toolUseID.

You populate state.ItemIDs / state.ToolCallIDs with the pre-signature toolUseID, then conditionally mutate toolUseID (Lines 1057-1061). Later, close/cleanup paths use state.ItemIDs[...] to emit output_item.done, which can disagree with the ItemID emitted in output_item.added / function_call_arguments.done. This can break client correlation.

Proposed fix
 	toolUseID := part.FunctionCall.ID
 	if toolUseID == "" {
 		toolUseID = part.FunctionCall.Name // Fallback to name as ID
 	}

-	state.ItemIDs[outputIndex] = toolUseID
-	state.ToolCallIDs[outputIndex] = toolUseID
-	state.ToolCallNames[outputIndex] = part.FunctionCall.Name
-
 	// Convert args to JSON string
 	argsJSON := ""
 	if part.FunctionCall.Args != nil {
 		if argsBytes, err := sonic.Marshal(part.FunctionCall.Args); err == nil {
 			argsJSON = string(argsBytes)
 		}
 	}
 	state.ToolArgumentBuffers[outputIndex] = argsJSON

 	// Attach thought signature to ID if present
 	if len(part.ThoughtSignature) > 0 && !strings.Contains(toolUseID, thoughtSignatureSeparator) {
 		encoded := base64.RawURLEncoding.EncodeToString(part.ThoughtSignature)
 		toolUseID = fmt.Sprintf("%s%s%s", toolUseID, thoughtSignatureSeparator, encoded)
 	}
+
+	// Persist the final ID (post-augmentation) so subsequent done/close events match.
+	state.ItemIDs[outputIndex] = toolUseID
+	state.ToolCallIDs[outputIndex] = toolUseID
+	state.ToolCallNames[outputIndex] = part.FunctionCall.Name
🤖 Fix all issues with AI agents
In `@core/providers/anthropic/responses.go`:
- Around line 62-64: The global sync.Map webSearchItemIDs can leak when streams
error or clients disconnect; modify the code so keys are tracked per-stream and
cleaned up on release: when you add entries to webSearchItemIDs (the current add
at the WebSearch tool registration point), also append the key to the
stream-scoped state (e.g., a []string field on the anthropic responses stream
state), and then in releaseAnthropicResponsesStreamState iterate that per-stream
key list and call webSearchItemIDs.Delete(key) to remove them; alternatively, if
you prefer keeping the global map, ensure releaseAnthropicResponsesStreamState
is updated to detect and delete any webSearchItemIDs associated with the current
stream (matching whatever ID you store) and call that cleanup on all
terminal/error/disconnect paths (including where output_item.done would have
deleted) so entries cannot persist after stream termination.
♻️ Duplicate comments (4)
tests/integrations/python/tests/test_google.py (1)

1827-1839: Missing defensive asserts before indexing into response structure.

The code accesses response_1.candidates[0].content.parts at line 1827 without first asserting that candidates, content, and parts are present. This could lead to confusing IndexError or AttributeError instead of clear test failure messages.

Suggested fix
         response_1 = client.models.generate_content(
             model=model,
             contents="Search for information about Python programming and then process the results.",
             config=types.GenerateContentConfig(
                 tools=[search_tool, process_tool],
                 thinking_config=types.ThinkingConfig(
                     thinking_level="medium"
                 )
             )
         )
         
+        assert response_1.candidates, "Response should have candidates"
+        assert response_1.candidates[0].content, "Candidate should have content"
+        assert response_1.candidates[0].content.parts, "Content should have parts"
+        
         # Extract first function call and signature
         first_fc = None
         first_signature = None
         
         for part in response_1.candidates[0].content.parts:
core/providers/gemini/chat.go (2)

94-103: Non-stream: continue can drop ThoughtSignature if it co-exists with thought text.

This is the same risk previously discussed: if Gemini ever returns thought text and a ThoughtSignature in the same Part, the signature won’t be captured.

Proposed fix (process signature after the thought-text handling)
-				if part.Text != "" && part.Thought {
+				if part.Text != "" && part.Thought {
 					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 						Index: len(reasoningDetails),
 						Type:  schemas.BifrostReasoningDetailsTypeText,
 						Text:  &part.Text,
 					})
-					continue
 				}
 
+				// Allow ThoughtSignature to co-exist with thought text.
+				if part.ThoughtSignature != nil {
+					thoughtSig := base64.StdEncoding.EncodeToString(part.ThoughtSignature)
+					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
+						Index:     len(reasoningDetails),
+						Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
+						Signature: &thoughtSig,
+					})
+				}
+
+				if part.Text != "" && part.Thought {
+					continue
+				}

342-350: Streaming: add reasoningDetails[].ID when signature is extracted from CallID (to match non-stream).

Non-stream sets ID: tool_call_<callID> for extracted signature (Line 158-159). Streaming extraction appends an encrypted reasoning detail without ID, making correlation harder.

Proposed fix
 				if len(extractedSig) > 0 {
 					thoughtSig := base64.StdEncoding.EncodeToString(extractedSig)
 					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 						Index:     len(reasoningDetails),
 						Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
 						Signature: &thoughtSig,
+						ID:        schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
 					})
 				}
core/providers/anthropic/responses.go (1)

2912-2934: Third instance of duplicated WebSearch sanitization.

This is the third location with identical WebSearch sanitization logic. As noted earlier, this should be extracted to a shared helper function.

🧹 Nitpick comments (7)
tests/integrations/python/tests/test_google.py (2)

2099-2100: Consider catching specific SDK exceptions instead of bare Exception.

The bare except Exception catches at lines 2099 and 2136 could mask unexpected errors. For better test diagnostics, catch specific exception types from the Google GenAI SDK.

Suggested approach
+        from google.genai.errors import APIError
+
         # Step 2: Test WITH signature (should succeed)
         try:
             # ... existing code ...
             
-        except Exception as e:
+        except (APIError, ValueError) as e:  # Catch SDK-specific errors
             pytest.fail(f"Request with signature should not fail: {e}")
         
         # Step 3: Test WITHOUT signature
         try:
             # ... existing code ...
             
-        except Exception as e:
+        except (APIError, ValueError) as e:  # Catch SDK-specific errors
             error_msg = str(e).lower()
             if "signature" in error_msg or "400" in error_msg or "validation" in error_msg:
                 print(f"    ✓ Request without signature failed as expected: {type(e).__name__}")
             else:
                 print(f"    ? Request failed with unexpected error: {e}")

Alternatively, add # noqa: BLE001 comments if the broad catch is intentional for test resilience.

Also applies to: 2136-2141


2052-2066: Add defensive asserts before accessing nested response structure.

Line 2056 accesses response_1.candidates[0].content.parts without verifying the response structure exists. Add assertions to provide clear failure messages.

Suggested fix
         response_1 = client.models.generate_content(
             model=model,
             contents="Calculate 45 * 23 using the calculator tool.",
             config=types.GenerateContentConfig(
                 tools=[calculator_tool],
                 thinking_config=types.ThinkingConfig(
                     thinking_level="medium"
                 )
             )
         )
         
+        assert response_1.candidates, "Response should have candidates"
+        assert response_1.candidates[0].content, "Candidate should have content"
+        assert response_1.candidates[0].content.parts, "Content should have parts"
+        
         # Extract function call and signature
         fc = None
         sig = None
         for part in response_1.candidates[0].content.parts:
core/providers/cohere/utils.go (1)

75-86: Consider using else if for cleaner mutual exclusion.

The logic is correct since a value can't be both bool and map[string]interface{} simultaneously. However, using else if would make the mutual exclusivity explicit and avoid an unnecessary type check.

♻️ Suggested improvement
 	// Extract additionalProperties
 	if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &addPropsVal,
 		}
-	}
-
-	if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
+	} else if addPropsVal, ok := schemas.SafeExtractOrderedMap(paramsMap["additionalProperties"]); ok {
 		result.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &addPropsVal,
 		}
 	}
core/providers/gemini/utils.go (1)

1146-1155: Consider using else if for cleaner mutual exclusion (same as Cohere).

Same pattern as core/providers/cohere/utils.go. The logic is correct, but else if would make the intent clearer.

♻️ Suggested improvement
 	// Extract additionalProperties
 	if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesBool: &additionalProps,
 		}
-	}
-
-	if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
+	} else if additionalProps, ok := schemas.SafeExtractOrderedMap(normalizedSchemaMap["additionalProperties"]); ok {
 		jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 			AdditionalPropertiesMap: &additionalProps,
 		}
 	}
core/providers/anthropic/types.go (1)

303-336: AnthropicCitations union marshal: guard against empty-but-non-nil TextCitations causing false “both set”.

Right now Config != nil && TextCitations != nil will error even if TextCitations is an empty slice that was make(...)’d accidentally. Low risk, but this type looks public and easy to misuse.

Possible tweak
 func (ac *AnthropicCitations) MarshalJSON() ([]byte, error) {
-	if ac.Config != nil && ac.TextCitations != nil {
+	if ac.Config != nil && len(ac.TextCitations) > 0 {
 		return nil, fmt.Errorf("AnthropicCitations: both Config and TextCitations are set; only one should be non-nil")
 	}
 
 	if ac.Config != nil {
 		return sonic.Marshal(ac.Config)
 	}
-	if ac.TextCitations != nil {
+	if len(ac.TextCitations) > 0 {
 		return sonic.Marshal(ac.TextCitations)
 	}
 	return sonic.Marshal(nil)
 }
core/providers/anthropic/utils.go (1)

768-778: Use else if to make mutual exclusivity explicit.

The two independent if statements work correctly because schemaMap["additionalProperties"] can only be a bool OR a map, never both. However, using else if would make this mutual exclusivity clearer and prevent any future confusion.

Suggested improvement
 		if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesBool: &additionalProps,
 			}
-		}
-
-		if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
+		} else if additionalProps, ok := schemas.SafeExtractOrderedMap(schemaMap["additionalProperties"]); ok {
 			jsonSchema.AdditionalProperties = &schemas.AdditionalPropertiesStruct{
 				AdditionalPropertiesMap: &additionalProps,
 			}
 		}
core/providers/anthropic/responses.go (1)

1054-1077: Extract duplicated WebSearch sanitization logic into a helper function.

The same sanitization logic (removing blocked_domains when allowed_domains is present) is duplicated in three locations:

  1. Here (lines 1054-1077)
  2. WebSearch completion handling (lines 1182-1196)
  3. convertBifrostFunctionCallToAnthropicToolUse (lines 2912-2932)
Suggested helper function
// sanitizeWebSearchArguments removes blocked_domains if allowed_domains is present
// Anthropic only allows one domain filter type at a time
func sanitizeWebSearchArguments(argumentsJSON string) string {
	var toolArgs map[string]interface{}
	if err := json.Unmarshal([]byte(argumentsJSON), &toolArgs); err != nil {
		return argumentsJSON
	}

	_, hasAllowed := toolArgs["allowed_domains"]
	_, hasBlocked := toolArgs["blocked_domains"]

	if hasAllowed && hasBlocked {
		delete(toolArgs, "blocked_domains")
		if sanitizedBytes, err := json.Marshal(toolArgs); err == nil {
			return string(sanitizedBytes)
		}
	}
	return argumentsJSON
}

Then replace all three occurrences with:

if bifrostResp.Item.ResponsesToolMessage.Name != nil &&
    *bifrostResp.Item.ResponsesToolMessage.Name == "WebSearch" {
    argumentsJSON = sanitizeWebSearchArguments(argumentsJSON)
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c33a2d1 and 973d8fb.

📒 Files selected for processing (17)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
✅ Files skipped from review due to trivial changes (1)
  • core/changelog.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/providers/gemini/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/providers/anthropic/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/responses.go
  • core/providers/gemini/chat.go
  • tests/integrations/python/tests/test_google.py
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
🧠 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/providers/anthropic/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • 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/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/schemas/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/internal/testutil/structured_outputs.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/types.go
📚 Learning: 2025-12-12T10:28:54.988Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/anthropic/chat.go:207-216
Timestamp: 2025-12-12T10:28:54.988Z
Learning: In core/providers/anthropic/chat.go, within ToAnthropicChatRequest, ensure the mapping from ReasoningDetails to Anthropic thinking blocks preserves all fields from ReasoningDetails, including when Text is nil or empty. Do not drop or coerce missing Text; propagate nil/empty values to maintain complete bidirectional mapping of Anthropic events in Bifrost. Add tests to verify that ReasoningDetails with Text nil or "" round-trip without loss, and update code to handle nullable Text without dropping data. If there are multiple blocks, preserve order and all fields during mapping.

Applied to files:

  • core/providers/anthropic/chat.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/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/anthropic/utils.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/chat.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/utils.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/gemini/utils.go
  • core/providers/anthropic/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/gemini/gemini_test.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_google.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/structured_outputs.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
🧬 Code graph analysis (12)
core/providers/anthropic/chat.go (2)
core/providers/gemini/types.go (1)
  • Type (825-825)
core/providers/anthropic/types.go (1)
  • AnthropicContentBlockTypeToolResult (209-209)
core/providers/cohere/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/responses.go (3)
core/schemas/responses.go (1)
  • ResponsesToolMessage (475-495)
core/providers/gemini/types.go (3)
  • Part (1030-1054)
  • FunctionCall (1236-1246)
  • FunctionResponse (1249-1271)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/responses.go (1)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/providers/gemini/chat.go (2)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (869-877)
  • BifrostReasoningDetailsTypeText (865-865)
  • BifrostReasoningDetailsTypeEncrypted (864-864)
core/schemas/utils.go (1)
  • Ptr (14-16)
tests/integrations/python/tests/test_google.py (2)
tests/integrations/python/tests/utils/common.py (1)
  • skip_if_no_api_key (1830-1841)
core/providers/gemini/types.go (3)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
core/providers/anthropic/responses.go (4)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
  • AnthropicStreamEvent (498-507)
  • AnthropicStreamDelta (519-527)
core/schemas/responses.go (1)
  • ResponsesToolMessage (475-495)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/schemas/chatcompletions.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)
core/providers/anthropic/types.go (2)
core/schemas/chatcompletions.go (1)
  • CacheControl (719-722)
core/schemas/responses.go (1)
  • Citations (421-423)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1772-1772: Unused method argument: test_config

(ARG002)


1906-1906: Unused method argument: test_config

(ARG002)


1961-1961: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2099-2099: Do not catch blind exception: Exception

(BLE001)


2136-2136: Do not catch blind exception: Exception

(BLE001)

⏰ 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 (23)
tests/integrations/python/tests/test_google.py (3)

1681-1770: Well-structured test for parallel function call signatures.

The signature assertion fix correctly uses function_call_part_indices[0] instead of a hardcoded index, handling cases where thought/text parts precede function calls. The test properly validates Gemini 3's behavior of attaching thought signatures only to the first function call in parallel scenarios.

Consider adding a defensive assert for response.candidates[0].content.parts before iteration (line 1742) to provide clearer error messages on unexpected response structure, though the candidates assertion provides baseline protection.


1905-1959: LGTM!

The test properly validates thought signatures in text responses with appropriate assertions for response structure at lines 1925-1927. The implementation correctly iterates through parts to identify thought/text parts and their associated signatures.


1960-2013: LGTM!

The test correctly validates different thinking levels (minimal, low, medium, high) and checks for thought tokens in usage metadata. The parametric approach ensures comprehensive coverage of Gemini 3's thinking configuration options.

core/schemas/chatcompletions.go (3)

276-282: LGTM - Type change to support both boolean and object additionalProperties.

The field type change from *bool to *AdditionalPropertiesStruct correctly enables the schema to represent both additionalProperties: false and additionalProperties: { "type": "string" } patterns from JSON Schema.


297-302: LGTM - Normalization ensures omitempty works correctly.

Both parsing paths (string and object formats) properly normalize an empty AdditionalPropertiesStruct to nil, preventing "additionalProperties": null in marshaled output.

Also applies to: 313-318


324-379: LGTM - Well-structured union type implementation.

The AdditionalPropertiesStruct correctly:

  • Validates mutual exclusivity in MarshalJSON
  • Handles explicit null in UnmarshalJSON
  • Clears the opposite field when setting one variant (prevents stale state on struct reuse)
core/schemas/responses.go (2)

131-140: LGTM - Consistent type update for AdditionalProperties.

The field type change aligns with ToolFunctionParameters.AdditionalProperties in chatcompletions.go, ensuring consistent handling of both boolean and object representations across the codebase.


421-423: LGTM - Simple struct for citation configuration.

The Citations struct is appropriately minimal with just an Enabled field.

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

421-427: LGTM - Test correctly uses new AdditionalPropertiesStruct wrapper.

The test properly wraps the boolean value in the new struct type, maintaining compatibility with the updated schema definitions.


537-543: LGTM - Streaming test updated consistently.

Same correct pattern applied to the streaming test case.

core/providers/gemini/responses.go (4)

246-286: Signature extraction + “consume next reasoning message” logic looks coherent; verify edge cases with multiple tool calls.

The CallID-embedded signature decode (RawURLEncoding) and fallback to the next reasoning EncryptedContent (StdEncoding) is consistent. The consumedIndices guard should prevent emitting the signature reasoning message twice.

I’d just sanity-check scenarios with multiple adjacent tool calls to ensure a single reasoning message can’t be “claimed” by the wrong call (since the fallback is “next message”).


457-477: Streaming conversion: good separation between CallID and decoded ThoughtSignature.

Extracting the signature substring from CallID, decoding it (RawURLEncoding), and attaching it to functionCallPart.ThoughtSignature is the right direction and keeps the full CallID intact.


1866-1915: Nice: avoid double-appending signatures and strip signature for function-response “name” fallback.

  • The !strings.Contains(callID, thoughtSignatureSeparator) guard prevents duplicate ..._ts_..._ts_....
  • The function response fallback that strips the signature from part.FunctionResponse.ID before assigning ResponsesToolMessage.Name should help keep “name” stable for correlation.

2388-2412: Round-trip handling for CallID-embedded signatures is consistent.

Decoding the signature from ResponsesToolMessage.CallID via base64.RawURLEncoding and attaching it to Part.ThoughtSignature keeps the “signature transport” consistent across conversions.

Also applies to: 2454-2455

core/providers/gemini/chat.go (1)

130-160: CallID signature extraction + ID normalization is consistent and readable.

Stripping the embedded signature for the tool call ID while keeping the decoded signature in reasoningDetails (with a stable tool_call_<baseId> reference) is a good approach.

Also applies to: 191-198

core/providers/gemini/gemini_test.go (1)

558-562: Test update matches the new AdditionalPropertiesStruct shape.

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

219-237: Expanded content blocks + citation structs look consistent with the intended schema.

The added Signature/Data fields and the richer citation types (including file_id and url) fit the stated goal of better thought-signature and citations support.

Also applies to: 248-292

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

177-180: Good: use AnthropicContentBlockTypeToolResult constant instead of string literal.

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

902-928: Consistent OutputIndex-first indexing pattern applied.

The changes correctly prioritize OutputIndex over ContentIndex for Anthropic SSE events, with appropriate fallbacks. This ensures consistent global indexing across content_block_start and subsequent delta events.


938-941: Signature initialization follows Anthropic streaming spec.

Setting Signature to an empty string in content_block_start for thinking blocks is correct per Anthropic's API specification. The actual signature value arrives later via signature_delta events. Based on learnings, this is required behavior for the Anthropic SDK.


1113-1126: WebSearch delta suppression correctly implemented.

The pattern of skipping argument deltas for WebSearch tools and emitting sanitized synthetic deltas on completion is correctly implemented. This ensures Anthropic receives valid arguments without conflicting domain filters.

Note: This implementation depends on proper cleanup of webSearchItemIDs, which was flagged in an earlier comment.


1169-1226: WebSearch completion handling follows correct event sequence.

The implementation correctly generates sanitized synthetic delta events followed by content_block_stop for WebSearch tools. The tracking cleanup at line 1222 properly removes the entry from webSearchItemIDs.


1278-1301: StopReason handling correctly differentiates default and explicit cases.

The implementation correctly handles two cases:

  • Default: Sets StopReason to end_turn with empty StopSequence
  • Explicit: Converts from Bifrost format with nil StopSequence

This maintains proper Anthropic API compatibility.

✏️ 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: 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/anthropic/responses.go (1)

1278-1299: Use nil instead of schemas.Ptr("") for StopSequence to match Anthropic SSE schema.

Per Anthropic's streaming API spec, stop_sequence should be null (not empty string) when no custom stop sequence triggered the model. Since StopSequence in AnthropicStreamDelta lacks the omitempty JSON tag, it will always be marshaled. Line 1280 currently sets it to schemas.Ptr(""), which serializes as "stop_sequence": "", while line 1298 correctly uses nil. Both should be nil unless an actual stop sequence value is present.

🤖 Fix all issues with AI agents
In `@core/providers/anthropic/types.go`:
- Around line 248-336: The marshal/unmarshal logic must normalize empty values
so "unset" behaves like omitted: in MarshalJSON, treat TextCitations with length
0 as nil and treat Config pointer whose dereferenced value equals the zero-value
of schemas.Citations as nil (use reflect.DeepEqual or a clear isEmpty helper),
change the mutual-check to consider "set" only when Config is
non-nil-and-not-empty and TextCitations has len>0, then marshal the non-empty
one (and return sonic.Marshal(nil) only when both are empty/nil); in
UnmarshalJSON, after successful unmarshalling set TextCitations to nil when
len==0 and set Config to nil when the unmarshalled config equals the zero-value
before assigning, and keep the existing attempt-order (array first, then config)
and the final error message in UnmarshalJSON (functions:
AnthropicCitations.MarshalJSON and AnthropicCitations.UnmarshalJSON).
♻️ Duplicate comments (5)
core/providers/anthropic/responses.go (1)

62-65: Global webSearchItemIDs can leak across interrupted streams.

This is still a process-global sync.Map with cleanup only on the “happy path” (output_item.done for WebSearch). If a stream errors / client disconnects before that, entries can remain indefinitely.

#!/bin/bash
# Find all deletes/cleanup paths for webSearchItemIDs and where stream teardown happens.
rg -n --type go 'webSearchItemIDs\.(Store|Delete|Load)' -C2
rg -n --type go 'releaseAnthropicResponsesStreamState|acquireAnthropicResponsesStreamState' -C2
rg -n --type go 'ToAnthropicResponsesStreamResponse\(' -C2
core/providers/gemini/chat.go (2)

94-103: Non-stream path can still drop ThoughtSignature due to continue.

If Gemini ever returns a part with both Thought=true text and ThoughtSignature (or other data you handle later), the continue skips the signature logic. This was previously raised and still applies to the current structure.

Proposed diff (remove `continue`, keep processing other fields)
-				// Handle thought/reasoning text separately - add to reasoning details
-				if part.Text != "" && part.Thought {
-					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
-						Index: len(reasoningDetails),
-						Type:  schemas.BifrostReasoningDetailsTypeText,
-						Text:  &part.Text,
-					})
-					continue
-				}
-
-				// Handle regular text
-				if part.Text != "" {
-					contentBlocks = append(contentBlocks, schemas.ChatContentBlock{
-						Type: schemas.ChatContentBlockTypeText,
-						Text: &part.Text,
-					})
-				}
+				// Handle text vs thought text, but don't `continue` (signatures can co-exist).
+				if part.Text != "" {
+					if part.Thought {
+						reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
+							Index: len(reasoningDetails),
+							Type:  schemas.BifrostReasoningDetailsTypeText,
+							Text:  &part.Text,
+						})
+					} else {
+						contentBlocks = append(contentBlocks, schemas.ChatContentBlock{
+							Type: schemas.ChatContentBlockTypeText,
+							Text: &part.Text,
+						})
+					}
+				}
Gemini GenerateContentResponse Part fields: can a single part contain thought=true text and thoughtSignature and/or functionCall simultaneously?

Also applies to: 176-202


342-350: Streaming extracted-signature reasoningDetails is missing correlation ID.

Non-stream adds ID: tool_call_<callID> when a signature is extracted from CallID; streaming doesn’t, so consumers can’t reliably associate signature ↔ tool call. This was previously raised and appears to have regressed / still be present in the current code.

Proposed diff
 				if len(extractedSig) > 0 {
 					thoughtSig := base64.StdEncoding.EncodeToString(extractedSig)
 					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 						Index:     len(reasoningDetails),
 						Type:      schemas.BifrostReasoningDetailsTypeEncrypted,
 						Signature: &thoughtSig,
+						ID:        schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)),
 					})
 				}
core/schemas/chatcompletions.go (1)

331-379: Consider hardening MarshalJSON to avoid emitting additionalProperties: null from empty/invalid struct values.

Even with the ToolFunctionParameters normalization, other callers could still create a non-nil *AdditionalPropertiesStruct with both variants nil and accidentally serialize null. Consider either:

  • returning an error when both variants are nil, or
  • normalizing empty/invalid instances to “omit field” at the parent level (where omitempty can apply).
tests/integrations/python/tests/test_google.py (1)

1681-2144: Fix Ruff violations + add basic response asserts in the new Gemini 3 tests.

  1. ARG002: test_config is unused in test_30atest_30e. Easiest fix is renaming to _test_config (or drop the arg and use @pytest.mark.usefixtures("test_config")).
  2. BLE001: replace broad except Exception in test_30e with SDK-specific exceptions, or add # noqa: BLE001 on those except lines.
  3. Add assert response_*.candidates (and ideally content.parts) before indexing in test_30b / test_30e to avoid IndexError-style failures.
Proposed diff (minimal, lint-focused)
@@
-    def test_30a_gemini_3_parallel_function_calls_signatures(self, test_config):
+    def test_30a_gemini_3_parallel_function_calls_signatures(self, _test_config):
@@
-    def test_30b_gemini_3_sequential_function_calls_signatures(self, test_config):
+    def test_30b_gemini_3_sequential_function_calls_signatures(self, _test_config):
@@
-        # Extract first function call and signature
+        assert response_1.candidates, "Response should have candidates"
+        assert response_1.candidates[0].content, "Candidate should have content"
+        assert response_1.candidates[0].content.parts, "Candidate content should have parts"
+        # Extract first function call and signature
@@
-    def test_30c_gemini_3_thought_signatures_in_text_responses(self, test_config):
+    def test_30c_gemini_3_thought_signatures_in_text_responses(self, _test_config):
@@
-    def test_30d_gemini_3_thinking_levels(self, test_config):
+    def test_30d_gemini_3_thinking_levels(self, _test_config):
@@
-    def test_30e_gemini_3_signature_validation_strict(self, test_config):
+    def test_30e_gemini_3_signature_validation_strict(self, _test_config):
@@
-        for part in response_1.candidates[0].content.parts:
+        assert response_1.candidates, "Response should have candidates"
+        assert response_1.candidates[0].content, "Candidate should have content"
+        assert response_1.candidates[0].content.parts, "Candidate content should have parts"
+        for part in response_1.candidates[0].content.parts:
@@
-        except Exception as e:
+        except Exception as e:  # noqa: BLE001
             pytest.fail(f"Request with signature should not fail: {e}")
@@
-        except Exception as e:
+        except Exception as e:  # noqa: BLE001
             error_msg = str(e).lower()
🧹 Nitpick comments (4)
core/providers/anthropic/types.go (1)

219-238: Good field additions for thinking/web_search_result blocks; watch omitempty + pointer semantics.

Signature/Data + web search fields look consistent with the rest of the model, and using pointers keeps omitempty behavior predictable. 👍
One practical gotcha: any code that sets Citations: &AnthropicCitations{} (instead of leaving it nil) will still serialize a citations key (likely as null), because omitempty only omits nil pointers. Consider guarding in constructors / converters to keep it nil unless you intend to emit it.

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

1054-1077: Deduplicate WebSearch-args sanitization + fix misleading comment (“remove both”).

The same “if allowed_domains and blocked_domains both exist, drop blocked_domains” logic appears in multiple places, which risks drift. Also the comment says “remove both allowed_domains and blocked_domains” but the code removes only blocked_domains.

Consider a shared helper (keeps behavior consistent across streaming + non-stream conversions):

Proposed diff (sketch)
+func sanitizeAnthropicWebSearchArgs(argumentsJSON string) string {
+	var toolArgs map[string]interface{}
+	if err := json.Unmarshal([]byte(argumentsJSON), &toolArgs); err != nil {
+		return argumentsJSON
+	}
+	_, hasAllowed := toolArgs["allowed_domains"]
+	_, hasBlocked := toolArgs["blocked_domains"]
+	if hasAllowed && hasBlocked {
+		delete(toolArgs, "blocked_domains")
+		if b, err := json.Marshal(toolArgs); err == nil {
+			return string(b)
+		}
+	}
+	return argumentsJSON
+}

Then replace each inline sanitization block with:

-// Sanitize websearch tool arguments to remove both allowed_domains and blocked_domains
-// Anthropic only allows one or the other, not both
+// Sanitize WebSearch tool arguments: if both allowed_domains and blocked_domains exist, drop blocked_domains.
 if shouldGenerateDeltas && argumentsJSON != "" {
   if bifrostResp.Item.ResponsesToolMessage.Name != nil &&
      *bifrostResp.Item.ResponsesToolMessage.Name == "WebSearch" {
-     ... inline sanitize ...
+     argumentsJSON = sanitizeAnthropicWebSearchArgs(argumentsJSON)
   }
 }

Also applies to: 1169-1197, 2912-2935

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

2414-2425: Look-ahead may overwrite signature already extracted from CallID.

The look-ahead logic at lines 2414-2425 unconditionally sets part.ThoughtSignature from the next reasoning message, potentially overwriting a valid signature that was already extracted from the CallID at lines 2405-2412.

Consider adding a guard to preserve the CallID-extracted signature:

♻️ Suggested fix
-				// Preserve thought signature from ResponsesReasoning message (required for Gemini 3 Pro)
-				// Look ahead to see if the next message is a reasoning message with encrypted content
-				if i+1 < len(messages) {
+				// Preserve thought signature from ResponsesReasoning message (required for Gemini 3 Pro)
+				// Look ahead only if we didn't already extract a signature from CallID
+				if len(part.ThoughtSignature) == 0 && i+1 < len(messages) {
 					nextMsg := messages[i+1]
 					if nextMsg.Type != nil && *nextMsg.Type == schemas.ResponsesMessageTypeReasoning &&
 						nextMsg.ResponsesReasoning != nil && nextMsg.ResponsesReasoning.EncryptedContent != nil {
 						decodedSig, err := base64.StdEncoding.DecodeString(*nextMsg.ResponsesReasoning.EncryptedContent)
 						if err == nil {
 							part.ThoughtSignature = decodedSig
 						}
 					}
 				}
core/providers/anthropic/utils.go (1)

768-778: AdditionalProperties wrapping is correct; consider making exclusivity explicit + verify request-side parity.

  • Minor: make the second check an else if to encode “one-of” more clearly (and avoid accidental overwrites if future types broaden).
  • Please also verify the Responses → Anthropic path preserves AdditionalPropertiesMap when present (this change is only the Anthropic → Responses direction).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 973d8fb and 363249d.

📒 Files selected for processing (17)
  • core/changelog.md
  • core/internal/testutil/structured_outputs.go
  • core/providers/anthropic/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/utils.go
  • core/providers/cohere/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • core/providers/gemini/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/responses.go
  • tests/integrations/python/tests/test_google.py
  • transports/bifrost-http/integrations/router.go
  • transports/changelog.md
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/integrations/router.go
✅ Files skipped from review due to trivial changes (1)
  • core/changelog.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • core/providers/cohere/utils.go
  • core/providers/anthropic/chat.go
  • core/schemas/responses.go
  • core/providers/gemini/types.go
  • core/internal/testutil/structured_outputs.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/anthropic/utils.go
  • transports/changelog.md
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.go
  • tests/integrations/python/tests/test_google.py
🧠 Learnings (14)
📚 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/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • 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/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/anthropic/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/responses.go
  • 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/utils.go
  • core/providers/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/types.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
  • core/providers/anthropic/types.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
  • core/providers/anthropic/types.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
  • core/providers/anthropic/types.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
  • core/providers/anthropic/types.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/gemini/utils.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/gemini_test.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/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/gemini/gemini_test.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: 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_google.py
🧬 Code graph analysis (8)
core/providers/anthropic/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/utils.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • SafeExtractOrderedMap (549-572)
core/providers/gemini/chat.go (2)
core/schemas/chatcompletions.go (3)
  • ChatReasoningDetails (869-877)
  • BifrostReasoningDetailsTypeText (865-865)
  • BifrostReasoningDetailsTypeEncrypted (864-864)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/gemini/gemini_test.go (2)
core/schemas/chatcompletions.go (1)
  • AdditionalPropertiesStruct (324-327)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/chatcompletions.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)
core/providers/anthropic/responses.go (4)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/types.go (2)
  • AnthropicStreamEvent (498-507)
  • AnthropicStreamDelta (519-527)
core/schemas/responses.go (2)
  • ResponsesToolMessage (475-495)
  • ResponsesMessageTypeFunctionCall (300-300)
core/providers/anthropic/utils.go (1)
  • ConvertBifrostFinishReasonToAnthropic (88-93)
core/providers/anthropic/types.go (4)
core/schemas/chatcompletions.go (1)
  • CacheControl (719-722)
core/schemas/responses.go (1)
  • Citations (421-423)
plugins/governance/main.go (1)
  • Config (33-35)
plugins/maxim/main.go (1)
  • Config (30-33)
tests/integrations/python/tests/test_google.py (1)
core/providers/gemini/types.go (4)
  • Tool (647-674)
  • Content (1016-1024)
  • Part (1030-1054)
  • FunctionResponse (1249-1271)
🪛 Ruff (0.14.11)
tests/integrations/python/tests/test_google.py

1682-1682: Unused method argument: test_config

(ARG002)


1772-1772: Unused method argument: test_config

(ARG002)


1906-1906: Unused method argument: test_config

(ARG002)


1961-1961: Unused method argument: test_config

(ARG002)


2016-2016: Unused method argument: test_config

(ARG002)


2099-2099: Do not catch blind exception: Exception

(BLE001)


2136-2136: Do not catch blind exception: Exception

(BLE001)

⏰ 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 (15)
core/providers/anthropic/responses.go (2)

902-928: OutputIndex-first indexing is consistently applied (good).

The OutputIndex→ContentIndex fallback pattern is now used across start/delta/stop conversions, which should prevent index mismatches for downstream clients.

Also applies to: 1096-1104, 1121-1127, 1141-1148, 1236-1241, 1262-1269, 1303-1311, 1325-1333


941-949: Keep empty signature on thinking content_block_start (expected Anthropic streaming behavior).

Initializing contentBlock.Signature = schemas.Ptr("") for thinking blocks is intentional here; the actual signature arrives via signature_delta later. This matches the repo’s established Anthropic streaming behavior. Based on learnings, this should not be removed.

Also applies to: 955-962, 983-1002

core/providers/gemini/gemini_test.go (1)

558-562: Test updated correctly for AdditionalPropertiesStruct.

This matches the new schema shape (AdditionalPropertiesBool) and keeps the intent of additionalProperties: false.

core/providers/gemini/responses.go (8)

246-286: LGTM - Thought signature extraction with proper encoding.

The extraction logic correctly:

  1. Uses RawURLEncoding for decoding signatures embedded in tool IDs (line 256)
  2. Falls back to StdEncoding for JSON field values from reasoning messages (line 278)
  3. Tracks consumed reasoning messages to avoid duplicate processing

338-345: LGTM - Standalone reasoning message handling.

Correctly decodes encrypted content using StdEncoding (appropriate for JSON field values) and creates a Part with the thought signature.


457-477: LGTM - Streaming path signature extraction is consistent.

The streaming path correctly mirrors the non-streaming extraction logic:

  • Extracts signature portion from CallID
  • Preserves full CallID in functionCall.ID
  • Uses RawURLEncoding for decoding (matching the encoding used at line 1059)

1057-1099: LGTM - Function call signature embedding with proper guards.

The implementation correctly:

  1. Guards against double-appending with !strings.Contains(toolUseID, thoughtSignatureSeparator)
  2. Uses RawURLEncoding to produce Gemini-compliant tool IDs (only a-zA-Z0-9_-)
  3. Propagates the augmented ID consistently to both addedEvent and doneEvent

1866-1871: LGTM - Non-streaming path matches streaming implementation.

Consistent with processGeminiFunctionCallPart: uses the same guard against double-appending and RawURLEncoding for Gemini tool ID compliance.


1903-1915: LGTM - Function response name extraction handles embedded signatures.

Correctly extracts the base tool name when the ID contains an embedded thought signature, falling back to the full ID when no signature is present.


2450-2457: LGTM - FunctionResponse ID preserves full CallID.

Correctly sets the FunctionResponse.ID to the full CallID, maintaining consistency for function call/response correlation.


887-890: Verify: StdEncoding is appropriate for JSON Signature fields.

Lines 888, 992, 1516, 1601, 1846, and 2026 all use base64.StdEncoding for encoding ThoughtSignature into JSON fields (not tool IDs). This is correct since JSON field values can contain any characters, unlike tool IDs which have character constraints.

transports/changelog.md (1)

1-6: Changelog entries look consistent with the PR/stack scope.

No issues here—this reads like a concise rollup of the stack’s user-facing changes.

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

1145-1155: LGTM: AdditionalProperties now supports bool-or-object consistently.

core/schemas/chatcompletions.go (1)

276-320: Schema API change + “null → omitted” normalization is solid.

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

661-661: No concerns.

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

@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from 363249d to e443fbf Compare January 15, 2026 08:43
@TejasGhatte TejasGhatte force-pushed the 01-12-feat_send_back_raw_request_with_bifrost_errors branch from 69a68b6 to 37fc678 Compare January 15, 2026 08:43
@TejasGhatte TejasGhatte changed the base branch from 01-12-feat_send_back_raw_request_with_bifrost_errors to graphite-base/1319 January 15, 2026 08:51
@TejasGhatte TejasGhatte force-pushed the 01-09-fix_claude_code_openai branch from e443fbf to ee628de Compare January 15, 2026 08:57
@TejasGhatte TejasGhatte changed the base branch from graphite-base/1319 to 01-12-feat_send_back_raw_request_with_bifrost_errors January 15, 2026 08:57
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-12-feat_send_back_raw_request_with_bifrost_errors to graphite-base/1319 January 15, 2026 14:13
@akshaydeo akshaydeo changed the base branch from graphite-base/1319 to main January 15, 2026 14:13
@akshaydeo akshaydeo merged commit 8d8d83a into main Jan 15, 2026
3 of 4 checks passed
@akshaydeo akshaydeo deleted the 01-09-fix_claude_code_openai 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.

3 participants