Skip to content

Conversation

@Pratham-Mishra04
Copy link
Collaborator

Summary

Added support for reasoning capabilities across providers, with a focus on Anthropic's thinking feature. This PR introduces a unified reasoning interface in the Bifrost schema and implements provider-specific mappings.

Changes

  • Added reasoning field to ChatParameters to replace the deprecated reasoning_effort field
  • Implemented support for Anthropic's thinking feature with budget tokens
  • Added caching support for reasoning tokens
  • Refactored Anthropic provider to handle errors properly in request conversion functions
  • Updated UI to display reasoning content in logs
  • Added support for reasoning details with signatures for verification

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 reasoning capabilities with Anthropic models:

# Core/Transports
go version
go test ./...

# Test with Anthropic API
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-3-opus-20240229",
    "messages": [{"role": "user", "content": "Explain quantum computing"}],
    "reasoning": {
      "effort": "high",
      "max_tokens": 1000
    }
  }'

# UI
cd ui
pnpm i
pnpm build

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

The reasoning_effort field is now deprecated in favor of the new reasoning object. Existing code using reasoning_effort will continue to work but should be updated to use the new structure.

Related issues

N/A

Security considerations

Reasoning content is properly sanitized and displayed in the UI.

Checklist

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Expanded structured reasoning support across providers with richer reasoning details, signatures, and streaming deltas.
    • Streaming now includes per-chunk reasoning updates for real-time visibility.
    • UI updated to show Reasoning and Reasoning Details.
  • Bug Fixes

    • Improved validation and early error propagation to prevent invalid requests and nil dereferences.
    • Safer nested error extraction to yield clearer error messages.
  • Refactor

    • Unified reasoning parameters (effort and token budgets) across protocols and conversions.

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

Walkthrough

Structured reasoning (Effort + MaxTokens) and reasoning details were introduced across schemas and providers; many converters now return errors and validate reasoning limits; streaming and non‑streaming flows propagate reasoning content, details, and signatures; call-sites, transports, utilities, and UI were updated accordingly.

Changes

Cohort / File(s) Summary
Anthropic converters & errors
core/providers/anthropic/...
core/providers/anthropic/anthropic.go, core/providers/anthropic/chat.go, core/providers/anthropic/responses.go, core/providers/anthropic/errors.go, core/providers/anthropic/types.go
Converters now return (*req, error), validate inputs (including MinimumReasoningMaxTokens), propagate reasoning and signatures bidirectionally, rename streaming helpers, and add error-mapping helpers.
Provider call-sites
core/providers/azure/azure.go, core/providers/vertex/vertex.go, core/providers/cohere/cohere.go, core/providers/cohere/chat.go, core/providers/openai/chat.go, core/providers/perplexity/chat.go
Callers updated to handle converter errors, perform nil-checks, set Model/Stream only after successful conversion, and adjust reasoning parameter mapping; Cohere added per-message conversion.
Bedrock provider & utils
core/providers/bedrock/...
core/providers/bedrock/bedrock.go, core/providers/bedrock/chat.go, core/providers/bedrock/responses.go, core/providers/bedrock/types.go, core/providers/bedrock/utils.go
Added reasoning content/delta types, convertChatParameters now returns error and enforces reasoning validation, generate per-stream UUIDs, and propagate reasoning details in responses and streams.
OpenAI types & converters
core/providers/openai/types.go, core/providers/openai/utils.go, core/providers/openai/openai.go
Introduced OpenAIMessage/assistant types, custom MarshalJSON() to emit reasoning_effort while omitting reasoning, and converters between OpenAI and Bifrost message shapes.
Schemas: chat, responses, mux
core/schemas/chatcompletions.go, core/schemas/responses.go, core/schemas/mux.go
Replaced flat reasoning_effort with nested Reasoning{Effort, MaxTokens}; added ReasoningDetails types/constants and fields; added custom unmarshal logic for compatibility and legacy mapping.
Streaming framework & plugins
framework/streaming/chat.go, plugins/jsonparser/utils.go, plugins/semanticcache/utils.go
Stream assembly updated to accumulate Reasoning and ReasoningDetails (replacing Thought), adjust delta handling, and extract reasoning_max_tokens metadata.
Transports & parsing
transports/bifrost-http/handlers/inference.go
Added ChatRequest.UnmarshalJSON, switched some parsing to sonic, and migrated from reasoning_effort to nested reasoning in parsing paths.
UI types & view
ui/lib/types/logs.ts, ui/app/workspace/logs/views/logChatMessageView.tsx
Replaced thought with reasoning and reasoning_details; view labels and rendering updated to show "Reasoning".
Cohere provider specifics
core/providers/cohere/chat.go, core/providers/cohere/types.go
Added CohereMessage.ToBifrostChatMessage() per-message conversion; ToCohereChatCompletionRequest now returns (*req, error) with reasoning validation; streaming deltas adjusted.
Minor/utility fixes
plugins/maxim/main.go, assorted streaming/usage adjustments across providers
Safer nested error extraction, improved nil-safety, image token accounting and usage propagation in streaming paths, and various validation/error-return adjustments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant BifrostHTTP as Bifrost-HTTP
  participant Schemas
  participant Converter as ToProviderRequest
  participant Provider
  participant Model
  participant Stream as Streaming

  Client->>BifrostHTTP: POST /chat or /responses (with nested Reasoning)
  BifrostHTTP->>Schemas: Unmarshal (custom JSON -> Reasoning)
  Schemas->>Converter: ToAnthropic/ToCohere/ToBedrockRequest(bifrostReq)
  alt conversion error
    Converter-->>BifrostHTTP: error
    BifrostHTTP-->>Client: HTTP error
  else conversion ok
    Converter->>Provider: provider-specific request (Model set)
    Provider->>Provider: validate Reasoning.MaxTokens >= MinimumReasoningMaxTokens
    Provider->>Model: call external model (stream or non-stream)
    Model->>Stream: emit deltas (include Reasoning & ReasoningDetails)
    Stream->>Client: stream chunks with reasoning deltas and signatures
    Note right of Stream: Non-stream responses also include Reasoning & Details
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus on:

  • Schema public API changes and custom JSON marshal/unmarshal (core/schemas/chatcompletions.go, core/schemas/responses.go, transports/bifrost-http/handlers/inference.go).
  • All call-sites updated for converters now returning errors (Anthropic, Cohere, Azure, Vertex, Bedrock) — verify early-return handling, nil checks, and Stream flag placement.
  • Reasoning validation and MinimumReasoningMaxTokens enforcement across providers and utilities.
  • Streaming assembly and merge logic (framework/streaming/chat.go, Bedrock streaming): ordering, index-based merging, signature handling, and delta shapes.
  • OpenAI custom marshalling and new message shapes: ensure emitted JSON matches external API expectations and converters preserve assistant/tool fields.

Poem

🐇
I nibble tokens, sift them neat,
Reasoning hums in every beat,
Deltas stitch signatures through the night,
Errors caught and conversions bright,
Hop — the rabbit lights the byte! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add reasoning support for chat completions' is clear, concise, and accurately reflects the main change: adding reasoning capabilities across providers with focus on Anthropic's thinking feature.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, changes, type of change, affected areas, testing instructions, breaking changes, and all required checklist items. All major sections from the template are present and completed.
Docstring Coverage ✅ Passed Docstring coverage is 82.61% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea65ac and bfbcf49.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)

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

Copy link
Collaborator Author

Pratham-Mishra04 commented Dec 1, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Pratham-Mishra04 Pratham-Mishra04 linked an issue Dec 1, 2025 that may be closed by this pull request
2 tasks
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from d24ab88 to e1c5d21 Compare December 1, 2025 19:40
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 78eb82c to 3540e84 Compare December 2, 2025 14:40
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from e1c5d21 to b5f6f04 Compare December 2, 2025 14:40
@Pratham-Mishra04 Pratham-Mishra04 marked this pull request as ready for review December 2, 2025 15: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: 6

Caution

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

⚠️ Outside diff range comments (2)
plugins/jsonparser/utils.go (1)

303-323: Preserve reasoning_details in deep copy of stream deltas

deepCopyChatStreamResponseChoiceDelta now copies Reasoning, but it still omits the new ReasoningDetails slice. When the JSON parser plugin is enabled, this means any reasoning_details on the original delta will be dropped in the copied response.

You likely want to keep these details intact, similar to Refusal and ToolCalls:

 func (p *JsonParserPlugin) deepCopyChatStreamResponseChoiceDelta(original *schemas.ChatStreamResponseChoiceDelta) *schemas.ChatStreamResponseChoiceDelta {
   if original == nil {
     return nil
   }

   result := &schemas.ChatStreamResponseChoiceDelta{
-    Role:      original.Role,
-    Reasoning: original.Reasoning, // Shallow copy
-    Refusal:   original.Refusal,   // Shallow copy
-    ToolCalls: original.ToolCalls, // Shallow copy - we don't modify tool calls
+    Role:             original.Role,
+    Reasoning:        original.Reasoning,        // Shallow copy
+    ReasoningDetails: original.ReasoningDetails, // Shallow copy - not modified by plugin
+    Refusal:          original.Refusal,          // Shallow copy
+    ToolCalls:        original.ToolCalls,        // Shallow copy - we don't modify tool calls
   }

   // Deep copy Content pointer if it exists (this is what we modify)
   if original.Content != nil {
     contentCopy := *original.Content
     result.Content = &contentCopy
   }

   return result
 }
core/providers/anthropic/responses.go (1)

296-403: Fix Type field and Nil Effort handling in Anthropic reasoning logic

The ToAnthropicResponsesRequest reasoning block has two critical bugs:

  1. AnthropicThinking.Type incorrectly set to Effort string. Line 332 sets Type: string(*bifrostReq.Params.Reasoning.Effort), but the field is documented to accept only "enabled" or "disabled". Effort values like "minimal", "low", "medium", "high", "auto" are invalid and will create malformed Anthropic requests.

  2. Nil Effort semantics inconsistent with chat path and Bedrock. The current condition (line 326) Effort != nil && Effort != "none" means nil Effort → disabled. However, the chat.go path and Bedrock both treat nil Effort as enabled (only "none" triggers disabled). This causes the same input to enable reasoning on one path and disable it on another.

Apply the suggested fix:

  • Change condition to Effort == nil || Effort != "none"
  • Change Type to "enabled" (not the Effort string)
🧹 Nitpick comments (11)
core/schemas/responses.go (1)

231-236: Responses reasoning schema extension is consistent

Adding MaxTokens *int \json:"max_tokens,omitempty"`alongsideEffort` cleanly models reasoning configuration for Responses; this struct remains backward compatible and matches how chat reasoning is modeled elsewhere. Any provider-specific constraints (e.g., Anthropics’s minimum) can be enforced in the converters rather than here.

core/schemas/chatcompletions.go (2)

154-185: ChatParameters reasoning migration and legacy support look correct

The ChatParameters UnmarshalJSON alias pattern cleanly:

  • Accepts the new nested reasoning object,
  • Maps legacy reasoning_effort into ChatReasoning.Effort, and
  • Rejects payloads that set both, which prevents ambiguous configuration.

ChatReasoning itself mirrors the Responses reasoning shape and keeps the Anthropics-specific max_tokens as an optional field, which is appropriate here.

Also applies to: 186-220, 222-226


468-478: Reasoning details synthesis across assistant messages and streaming deltas is well‑designed

The new JSON handling for:

  • ChatMessage.UnmarshalJSON (splitting base fields vs embedded assistant/tool structs),
  • ChatAssistantMessage.UnmarshalJSON, and
  • ChatStreamResponseChoiceDelta.UnmarshalJSON

correctly keeps the core message fields intact while auto‑populating ReasoningDetails from the flat reasoning string when no details are present. This gives downstream code a single, structured way to consume reasoning without breaking existing payloads that only send reasoning. The mutual‑exclusion checks and alias usage avoid recursion issues.

Also applies to: 480-521, 625-667, 714-731, 755-763, 765-794

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

981-988: LGTM! Token details propagation matches text completion pattern.

The propagation logic is correct. Minor note: the field order here (PromptTokensDetails, then CompletionTokensDetails) differs from the text completion streaming path (lines 515-520) which does CompletionTokensDetails first. Consider aligning the order for consistency, though this has no functional impact.

framework/streaming/chat.go (1)

55-105: LGTM! ReasoningDetails merging logic is correct.

The merge-by-Index approach correctly handles:

  • Text, Summary, Data: accumulated via string concatenation
  • Signature, Type: overwritten (appropriate for final/immutable values)
  • New details: appended when Index not found

The nested loop is O(n×m) but acceptable given typical small detail counts.

For improved readability, consider extracting the detail update logic into a helper function:

func mergeReasoningDetail(existing, incoming *schemas.ChatReasoningDetails) {
    if incoming.Text != nil {
        if existing.Text == nil {
            existing.Text = incoming.Text
        } else {
            *existing.Text += *incoming.Text
        }
    }
    // ... similar for other fields
}
core/providers/openai/utils.go (1)

5-24: Converters look correct; note that ReasoningDetails is intentionally omitted.

The OpenAI↔Bifrost message converters correctly map name, role, content, tool fields, and assistant metadata (refusal, reasoning, annotations, tool calls). They don’t map ChatAssistantMessage.ReasoningDetails, which is fine as long as we never expect those details to survive a round‑trip through OpenAI models; if that becomes a requirement, we’ll need to extend OpenAIChatAssistantMessage or carry details elsewhere.

Also applies to: 26-45

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

58-60: Reasoning extraction for Bedrock chat looks correct; only minor polish possible.

  • In ToBifrostChatResponse, reasoning text/signature from ReasoningContent blocks are mapped into ChatReasoningDetails (type: reasoning.text) and concatenated into a single assistantMessage.Reasoning string. The single‑text‑block unwrap (promoting it to ContentStr) is a nice compatibility touch.
  • In ToBifrostChatCompletionStream, ReasoningContent deltas emit chunks with Reasoning plus a one‑element ReasoningDetails array, and signature‑only deltas emit ReasoningDetails with a populated Signature field.

Two small nits you might consider (not blockers):

  1. reasoningText accumulates with a trailing newline; trimming the final \n before assigning to assistantMessage.Reasoning would avoid a cosmetic artifact.
  2. For signature‑only deltas you reuse ChatReasoningDetailsTypeText; if the intention is to distinguish encrypted/signature‑only entries (e.g., "reasoning.encrypted"), adjusting the Type would make that clearer to downstream consumers.

Functionally this is solid and matches the new reasoning schema.

Also applies to: 103-112, 117-121, 135-141, 293-341

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

7-7: Cross-provider coupling: Cohere uses Anthropic's minimum token constant.

The Cohere provider imports and uses anthropic.MinimumReasoningMaxTokens for validation. This creates a coupling where Cohere's validation depends on Anthropic's constraints.

Consider extracting this constant to a shared location (e.g., schemas package or a dedicated reasoning constants file) to decouple providers:

-import (
-	"github.com/maximhq/bifrost/core/providers/anthropic"
-)
+import (
+	"github.com/maximhq/bifrost/core/schemas"
+)

Then use schemas.MinimumReasoningMaxTokens or similar. This makes the validation logic self-contained and allows each provider to potentially define its own minimum in the future.

Also applies to: 112-113


598-598: Minor: Trailing newline appended to reasoningText.

Line 598 appends "\n" after each thinking block's text, which will leave a trailing newline in the final reasoningText. This is consistent with the Anthropic implementation (line 355) but may be undesirable.

Consider trimming the trailing newline when setting the final reasoning text, or use strings.Join with newline separator:

// Alternative approach using strings.Builder or Join
reasoningText = strings.TrimSuffix(reasoningText, "\n")
transports/bifrost-http/handlers/inference.go (1)

63-89: Custom ChatRequest unmarshalling and reasoning handling look correct

  • Adding "reasoning" to chatParamsKnownFields and wiring it through ChatParameters keeps reasoning out of ExtraParams and aligned with the new structured Reasoning object.
  • The ChatRequest.UnmarshalJSON implementation cleanly separates concerns: it independently unmarshals BifrostParams, Messages, and then delegates to ChatParameters’s own unmarshaller, which avoids the embedded-struct interference you mention in the comment.
  • Switching extractExtraParams over to sonic.Unmarshal keeps JSON parsing consistent with the rest of the file, while still using json.RawMessage as a lightweight buffer type.
  • Initializing req := ChatRequest{ChatParameters: &schemas.ChatParameters{}} before unmarshalling is a good defensive step so the custom unmarshaller can safely populate parameters.

If this path ever shows up as a hot spot, you could reduce the triple-unmarshal in ChatRequest.UnmarshalJSON by unmarshalling once into a small helper struct that embeds all three views, but that’s purely an optimization, not a correctness issue.

Also applies to: 169-199, 268-288, 472-509

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

303-311: Anthropic conversion error handling is solid; consider aligning cached-token aggregation

  • Using ToAnthropicChatRequest / ToAnthropicResponsesRequest inside the CheckContextAndGetRequestBody closures with explicit err checks is the right way to surface conversion failures early and avoid sending bad payloads.
  • Enabling Stream = Ptr(true) only after a successful conversion in both chat and responses streaming paths is also correct.

For the new cached-token usage fields:

  • In HandleAnthropicChatCompletionStreaming, you currently overwrite PromptTokensDetails / CompletionTokensDetails each time usageToProcess has CacheReadInputTokens or CacheCreationInputTokens.
  • In HandleAnthropicResponsesStream, you do the same for InputTokensDetails / OutputTokensDetails.

If Anthropic ever emits multiple usage records per stream, you might want to mirror the “take the max seen so far” pattern you already use for token counts (and that Bedrock uses for cached tokens) instead of blind overwrite, e.g.:

if usageToProcess.CacheReadInputTokens > 0 {
    if usage.PromptTokensDetails == nil {
        usage.PromptTokensDetails = &schemas.ChatPromptTokensDetails{}
    }
    if usageToProcess.CacheReadInputTokens > usage.PromptTokensDetails.CachedTokens {
        usage.PromptTokensDetails.CachedTokens = usageToProcess.CacheReadInputTokens
    }
}

Same idea for the other cached-token fields. Not strictly required now, but it keeps aggregation semantics consistent across providers.

Also applies to: 354-365, 551-560, 647-655, 696-707, 893-902

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3540e84 and b5f6f04.

📒 Files selected for processing (29)
  • core/providers/anthropic/anthropic.go (6 hunks)
  • core/providers/anthropic/chat.go (10 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (4 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (3 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (1 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/semanticcache/utils.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (20)
core/providers/anthropic/errors.go (2)
ui/lib/types/logs.ts (1)
  • BifrostError (233-239)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/openai/openai.go (3)
plugins/mocker/main.go (1)
  • Usage (138-142)
core/providers/perplexity/types.go (1)
  • Usage (69-78)
ui/lib/types/logs.ts (1)
  • CompletionTokensDetails (197-202)
core/providers/vertex/vertex.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
ui/app/workspace/logs/views/logChatMessageView.tsx (2)
ui/lib/utils/validation.ts (2)
  • isJson (539-546)
  • cleanJson (548-558)
ui/app/workspace/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/schemas/mux.go (3)
core/schemas/responses.go (3)
  • ResponsesParametersReasoning (231-236)
  • BifrostResponsesStreamResponse (1422-1460)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1388-1388)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/utils.go (3)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/azure/azure.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/anthropic/responses.go (3)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (231-236)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (2)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
core/providers/openai/chat.go (3)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/anthropic.go (7)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/schemas/chatcompletions.go (1)
  • ChatPromptTokensDetails (821-827)
ui/lib/types/logs.ts (3)
  • CompletionTokensDetails (197-202)
  • ResponsesResponseInputTokens (542-544)
  • ResponsesResponseOutputTokens (546-548)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/schemas/responses.go (2)
  • ResponsesResponseInputTokens (260-266)
  • ResponsesResponseOutputTokens (268-278)
core/providers/bedrock/bedrock.go (2)
core/providers/utils/utils.go (1)
  • CreateBifrostChatCompletionChunkResponse (687-716)
core/schemas/bifrost.go (1)
  • ChatCompletionStreamRequest (90-90)
core/providers/bedrock/responses.go (4)
core/schemas/utils.go (4)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (231-236)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/cohere/cohere.go (1)
core/providers/cohere/chat.go (1)
  • ToCohereChatCompletionRequest (12-199)
core/providers/openai/types.go (2)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/chatcompletions.go (9)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • Alias (189-189)
  • Alias (643-643)
  • Alias (770-770)
  • Alias (860-860)
core/schemas/chatcompletions.go (4)
core/providers/openai/types.go (1)
  • Alias (78-78)
core/schemas/bifrost.go (2)
  • Alias (383-383)
  • Alias (400-400)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (2)
  • Role (13-13)
  • Content (922-930)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (1)
  • BedrockConverseRequest (44-57)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
framework/streaming/chat.go (4)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (1)
  • Type (778-778)
core/providers/bedrock/chat.go (2)
core/schemas/chatcompletions.go (4)
  • ChatReasoningDetails (723-730)
  • ChatReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/anthropic/chat.go (2)
core/providers/anthropic/types.go (7)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicContentBlock (147-159)
  • AnthropicContentBlockTypeThinking (143-143)
  • AnthropicContentBlockTypeText (135-135)
  • AnthropicContentBlockTypeToolUse (137-137)
core/providers/anthropic/utils.go (1)
  • ConvertAnthropicFinishReasonToBifrost (27-32)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (34)
ui/lib/types/logs.ts (2)

122-124: LGTM! Type definitions properly structured.

The addition of reasoning and reasoning_details fields to ChatMessage is well-designed and aligns with the PR's goal of adding unified reasoning support. The optional fields ensure backward compatibility.


126-133: LGTM! Well-designed interface for structured reasoning data.

The ReasoningDetails interface is properly structured with appropriate discriminated union types and optional fields that support various reasoning formats (summary, encrypted, text) as mentioned in the PR objectives.

ui/app/workspace/logs/views/logChatMessageView.tsx (1)

73-94: Consistent renaming from thought to reasoning.

The update is well-executed with consistent changes throughout the rendering logic. The JSON/plain-text rendering approach is maintained appropriately.

Note: The reasoning_details?: ReasoningDetails[] field in the ChatMessage type is currently not rendered in this view, only the message.reasoning string is displayed. This mirrors how the refusal field is handled (string only, despite optional detailed fields in the type). Verify whether this is intentional (e.g., for future use) or if reasoning_details should be rendered to display signatures and verification data as suggested by the PR objectives.

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

11-15: Reasoning min-tokens constant looks appropriate

Defining MinimumReasoningMaxTokens = 1024 at the Anthropic provider level is a good way to centralize the API’s minimum reasoning budget; no issues from this file alone.

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

29-35: Reasoning effort mapping correctly updated to nested params

The switch to bifrostReq.Params.Reasoning.Effort while preserving the "minimal" → "low" mapping keeps Perplexity behaviour consistent with the legacy reasoning_effort field and aligns with the new ChatReasoning shape. No further changes needed here.

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

83-108: Bedrock reasoning content wiring is structurally sound

The new ReasoningContent fields and BedrockReasoningContent{,Text} types integrate cleanly into both full content blocks and streaming deltas, and mirror the nested structure used elsewhere in the Bedrock path. From this file alone the types look correct and ready to be consumed by the Bedrock chat/response converters.

Also applies to: 152-160, 436-441

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

519-524: Streaming thinking deltas are wired consistently

Extending CohereStreamContent with Thinking *string reuses the existing thinking shape from CohereContentBlock and cleanly surfaces thinking deltas in the stream; no issues found.

core/schemas/chatcompletions.go (1)

829-836: ImageTokens field addition is schema-only; provider mapper integration pending

The new ImageTokens *int field in ChatCompletionTokensDetails adds a forward-compatible extension to token accounting, similar to AudioTokens and ReasoningTokens. However, verification shows no provider mappers currently populate this field—none of the provider implementations reference or assign values to image_tokens. If provider support is intended, the mapper implementations need to be updated to extract and populate this field from provider responses.

core/providers/cohere/cohere.go (2)

279-286: LGTM! Proper error propagation from request converter.

The change correctly propagates errors from ToCohereChatCompletionRequest through the request body builder, aligning with the error-first pattern established across providers in this PR.


331-342: LGTM! Streaming path now properly handles conversion errors.

Good defensive pattern: the error is captured and checked before enabling streaming mode. This ensures conversion failures surface to the caller rather than being silently ignored.

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

515-521: LGTM! Token details propagation for streaming.

Correctly propagates CompletionTokensDetails and PromptTokensDetails (including reasoning_tokens) into the aggregated usage for text completion streams.

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

14-14: LGTM! Uses centralized message conversion utility.

The change to ConvertOpenAIMessagesToBifrostMessages properly handles the new message structure including ChatAssistantMessage fields like Reasoning and ReasoningDetails.


28-28: LGTM! Bidirectional conversion with new utility.

Uses ConvertBifrostMessagesToOpenAIMessages for the reverse transformation, maintaining consistency with the input conversion path.


62-65: LGTM! Nil-safe nested field access for reasoning effort.

The triple nil-check (Reasoning != nil && Reasoning.Effort != nil && *Effort == "minimal") correctly handles the transition from flat ReasoningEffort to nested Reasoning.Effort. The "minimal" to "low" mapping maintains compatibility with non-OpenAI providers.

core/providers/bedrock/utils.go (3)

9-11: LGTM! Import for reasoning validation constant.

The import of anthropic.MinimumReasoningMaxTokens enables consistent validation of reasoning token budgets across providers.


14-17: LGTM! Error return enables proper error propagation.

The function signature change to return error allows callers to handle conversion failures gracefully. The nil check for params is appropriate.


133-133: LGTM! Explicit success return.

The return nil on successful completion completes the error-returning function contract.

framework/streaming/chat.go (2)

38-43: LGTM! Consistent pointer initialization for Refusal.

Using bifrost.Ptr(*chunk.Delta.Refusal) aligns with the pattern used elsewhere in the codebase for initializing optional string pointers.


44-54: LGTM! Reasoning content accumulation.

The reasoning accumulation logic correctly mirrors the existing Refusal handling pattern, properly initializing ChatAssistantMessage if needed and concatenating reasoning content across chunks.

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

656-660: New Bedrock→Responses output mapping is straightforward and consistent.

The added conversion of response.Output.Message via convertBedrockMessageToResponsesMessages cleanly populates bifrostResp.Output and aligns with the existing Bedrock↔Responses content‑block helpers. No obvious edge‑case or correctness issues here.

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

190-201: Thinking→Reasoning mapping on inbound Anthropic requests looks sound.

AnthropicMessageRequest.Thinking is correctly surfaced as ResponsesParametersReasoning: "enabled" becomes Effort: "auto" with MaxTokens from BudgetTokens, and any other type produces Effort: "none". This gives Bifrost a normalized view without over‑encoding Anthropic‑specific details.

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

37-40: Good: convertChatParameters errors are now surfaced to callers.

Wrapping convertChatParameters with error propagation prevents silently constructing invalid Bedrock requests (e.g., bad reasoning.max_tokens) and aligns this path with the new validation logic in the utilities.

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

361-373: Azure–Anthropic integration correctly handles new error‑returning converters.

In all Anthropic branches (chat, chat stream, responses, responses stream):

  • You now call anthropic.ToAnthropicChatRequest / ToAnthropicResponsesRequest, check the returned error, and abort early if non‑nil instead of assuming success.
  • On success, you override reqBody.Model with the Azure deployment and, for streaming paths, set reqBody.Stream = Ptr(true) before serialization.

This keeps the Azure provider aligned with the new Anthropic converter signatures and ensures invalid reasoning configs or other request issues surface as proper errors instead of malformed upstream calls.

Also applies to: 461-471, 537-547, 636-646

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

56-65: Embedded pointer structs design is sound, but the mutual exclusion is only documented.

The comment on lines 61-62 correctly warns that only one embedded pointer can be non-nil at a time. However, this invariant is enforced only by convention. Consider adding a validation method or documenting this more prominently in the consuming code to prevent accidental misuse.


74-101: Custom MarshalJSON correctly shadows and transforms the reasoning field.

The implementation properly:

  1. Uses the type alias pattern to avoid infinite recursion
  2. Shadows the Reasoning field to omit it from JSON output
  3. Extracts Reasoning.Effort into reasoning_effort for OpenAI compatibility
core/providers/anthropic/chat.go (5)

421-435: Cached token mapping appears semantically inverted.

The code maps CacheReadInputTokens to PromptTokensDetails.CachedTokens (line 422) and CacheCreationInputTokens to CompletionTokensDetails.CachedTokens (lines 426, 434).

Anthropic's cache_creation_input_tokens represents tokens written to cache (a cost incurred during input processing), while cache_read_input_tokens represents tokens read from cache. Both are input-side metrics. Mapping cache creation tokens to CompletionTokensDetails seems semantically incorrect since completion tokens typically refer to output tokens.

Please verify the intended semantics of these token mappings. If cache creation tokens should be tracked separately from completion details, consider adding a dedicated field or documenting this mapping decision.


647-654: Reasoning details in stream deltas include both summary and details.

The streaming response correctly populates both Reasoning (summary text) and ReasoningDetails (structured details) for thinking content. This provides flexibility for consumers.


664-687: Signature streaming correctly creates separate reasoning detail entries.

The signature delta handling properly creates a ReasoningDetails entry with just the signature, allowing consumers to associate signatures with their corresponding thinking blocks.


744-751: Reasoning-to-thinking conversion in stream response is correct.

The conversion from Bifrost's delta.Reasoning to Anthropic's Thinking delta type maintains consistency with the forward conversion path.


118-122: Unreachable code: condition on line 118 can never be true.

The condition bifrostReq.Params.Reasoning != nil on line 118 is inside an else block that only executes when bifrostReq.Params == nil (from line 25). Since Params is nil, accessing Params.Reasoning would cause a nil pointer dereference if the condition were ever evaluated.

However, looking more closely, line 118 is at the same indentation level as line 122's closing brace, suggesting this is meant to be outside the if bifrostReq.Params != nil block but the logic is incorrect.

-	} else if bifrostReq.Params.Reasoning != nil && bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-		anthropicReq.Thinking = &AnthropicThinking{
-			Type: "disabled",
-		}
 	}

This dead code should be removed. If bifrostReq.Params is nil, then bifrostReq.Params.Reasoning cannot be accessed.

Likely an incorrect or invalid review comment.

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

431-446: Thinking content streaming correctly populates both Reasoning and ReasoningDetails.

The streaming handler for thinking content mirrors the Anthropic implementation, providing consistent behavior across providers.


556-672: ToBifrostChatMessage implementation is thorough and handles edge cases well.

The method correctly:

  1. Handles string content, block content, and thinking blocks
  2. Safely handles nil Function in tool calls (lines 619-622)
  3. Collapses single text blocks to ContentStr (lines 604-607)
  4. Accumulates reasoning details with proper indexing
core/providers/bedrock/bedrock.go (1)

22-22: Consistent per-stream UUID looks good

Generating a single UUID per Bedrock chat stream and reusing it across all chunks and the final summary response is a clean way to provide a stable ID when the provider doesn’t. No functional issues spotted.

Also applies to: 783-785, 871-872, 894-895

core/providers/vertex/vertex.go (1)

291-345: Error propagation from Anthropic converters is correct and safer

Using ToAnthropicChatRequest / ToAnthropicResponsesRequest with explicit err checks inside the CheckContextAndGetRequestBody closures is the right pattern here and avoids streaming or sending requests when conversion fails. The updated blocks in ChatCompletion, ChatCompletionStream, Responses, and ResponsesStream all look consistent.

Also applies to: 532-539, 696-707, 860-868

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 3540e84 to e8afa58 Compare December 4, 2025 13:13
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from b5f6f04 to facc630 Compare December 4, 2025 13:13
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

♻️ Duplicate comments (5)
core/providers/bedrock/utils.go (1)

26-47: Reasoning config may still be overwritten by ExtraParams handling.

The reasoning config is correctly added to AdditionalModelRequestFields at lines 29-47, but lines 72-73 can overwrite the entire map when additionalModelRequestFieldPaths exists in ExtraParams:

bedrockReq.AdditionalModelRequestFields = orderedFields // Overwrites reasoning_config!

This would silently discard the reasoning_config set earlier.

Consider merging instead of replacing:

 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
     if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-        bedrockReq.AdditionalModelRequestFields = orderedFields
+        if bedrockReq.AdditionalModelRequestFields == nil {
+            bedrockReq.AdditionalModelRequestFields = orderedFields
+        } else {
+            for k, v := range orderedFields {
+                bedrockReq.AdditionalModelRequestFields[k] = v
+            }
+        }
     }
 }

Also applies to: 70-75

core/schemas/mux.go (1)

772-787: Preserve Reasoning.MaxTokens in Chat ↔ Responses conversions

The mux conversions still only copy Reasoning.Effort and gate on it, so Reasoning.MaxTokens is silently dropped and MaxTokens‑only configs are ignored:

  • ToResponsesRequest only sets brr.Params.Reasoning when bcr.Params.Reasoning.Effort != nil, and only copies Effort.
  • ToChatRequest only copies Effort back, never MaxTokens.

Given MaxTokens is what actually drives the Anthropic/Bedrock reasoning budget, this is configuration loss across Chat↔Responses flows.

Consider something along these lines:

-    // Handle Reasoning from reasoning_effort
-    if bcr.Params.Reasoning != nil && bcr.Params.Reasoning.Effort != nil {
-        brr.Params.Reasoning = &ResponsesParametersReasoning{
-            Effort: bcr.Params.Reasoning.Effort,
-        }
-    }
+    // Handle structured Reasoning (Effort and MaxTokens)
+    if bcr.Params.Reasoning != nil &&
+        (bcr.Params.Reasoning.Effort != nil || bcr.Params.Reasoning.MaxTokens != nil) {
+        brr.Params.Reasoning = &ResponsesParametersReasoning{
+            Effort:    bcr.Params.Reasoning.Effort,
+            MaxTokens: bcr.Params.Reasoning.MaxTokens,
+        }
+    }

-    // Handle Reasoning from Reasoning
-    if brr.Params.Reasoning != nil {
-        bcr.Params.Reasoning = &ChatReasoning{
-            Effort: brr.Params.Reasoning.Effort,
-        }
-    }
+    // Handle structured Reasoning from Responses -> Chat
+    if brr.Params.Reasoning != nil {
+        bcr.Params.Reasoning = &ChatReasoning{
+            Effort:    brr.Params.Reasoning.Effort,
+            MaxTokens: brr.Params.Reasoning.MaxTokens,
+        }
+    }

This keeps reasoning budgets intact in both directions, even when Effort is omitted but MaxTokens is set.

Also applies to: 809-856

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

9-31: Guard against nil bifrostErr.Error in ToAnthropicChatCompletionError

ToAnthropicChatCompletionError still assumes bifrostErr.Error is non‑nil and dereferences bifrostErr.Error.Message, which can panic if upstream code ever omits the nested error struct.

You can keep this helper robust with a small nil‑check:

 func ToAnthropicChatCompletionError(bifrostErr *schemas.BifrostError) *AnthropicMessageError {
   if bifrostErr == nil {
     return nil
   }

   // Provide blank strings for nil pointer fields
   errorType := ""
   if bifrostErr.Type != nil {
     errorType = *bifrostErr.Type
   }
-
-  // Handle nested error fields with nil checks
-  errorStruct := AnthropicMessageErrorStruct{
-    Type:    errorType,
-    Message: bifrostErr.Error.Message,
-  }
+  // Safely extract nested error message
+  message := ""
+  if bifrostErr.Error != nil {
+    message = bifrostErr.Error.Message
+  }
+
+  errorStruct := AnthropicMessageErrorStruct{
+    Type:    errorType,
+    Message: message,
+  }

This avoids panics regardless of which code path constructs the BifrostError.

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

347-367: Unify reasoning_config map type and avoid overwriting it with extra field paths

Two issues remain around reasoning_config that can lead to lost reasoning settings:

  1. Type mismatch for disabled reasoning
    In ToBedrockResponsesRequest you write:

    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
        "type": "disabled",
    }

    but ToBifrostResponsesRequest later does:

    reasoningConfig, ok := schemas.SafeExtractFromMap(request.AdditionalModelRequestFields, "reasoning_config")
    if reasoningConfigMap, ok := reasoningConfig.(map[string]interface{}); ok {
        // ...
    }

    If a BedrockConverseRequest ever carries the map produced by your own writer, this type assertion will fail and Params.Reasoning will not be populated. Use a uniform map[string]any for both branches:

  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
  •   "type": "disabled",
    
  • }
  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
  •   "type": "disabled",
    
  • }

2. **Overwriting `AdditionalModelRequestFields` with `additionalModelRequestFieldPaths`**  
After setting `reasoning_config` from `Params.Reasoning`, this block:

```go
if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
    if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
        bedrockReq.AdditionalModelRequestFields = orderedFields
    }
}

replaces the entire AdditionalModelRequestFields map, discarding the earlier reasoning_config. If both are meant to coexist, merge orderedFields into the existing map instead of overwriting it.

For example:

if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
    if bedrockReq.AdditionalModelRequestFields == nil {
        bedrockReq.AdditionalModelRequestFields = orderedFields
    } else {
        for k, v := range orderedFields {
            bedrockReq.AdditionalModelRequestFields[k] = v
        }
    }
}

This keeps reasoning budgets configured via Params.Reasoning intact even when callers also use additionalModelRequestFieldPaths.

Also applies to: 452-482

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

126-137: ExtraParams["thinking"] can silently override Params.Reasoning.

This issue was flagged in a previous review. When both Params.Reasoning and ExtraParams["thinking"] are provided, ExtraParams wins silently, which may not be the intended behavior.

🧹 Nitpick comments (7)
transports/bifrost-http/handlers/inference.go (1)

64-89: Custom ChatRequest unmarshalling fixes embedded params, but double‑check ExtraParams behavior

The custom ChatRequest.UnmarshalJSON plus initializing ChatParameters in chatCompletion is a good way to keep BifrostParams (model, fallbacks, stream, …) separate from ChatParameters despite its custom unmarshaller, and adding "reasoning" to chatParamsKnownFields keeps it out of ExtraParams.

One thing to verify: since you now:

  • let schemas.ChatParameters.UnmarshalJSON see the full payload, and
  • later always compute extraParams via extractExtraParams and assign req.ChatParameters.ExtraParams = extraParams,

make sure ChatParameters.UnmarshalJSON itself is not also trying to build ExtraParams, or its work will be silently overwritten here. If it is, consider consolidating ExtraParams population in one place (either the type’s unmarshaller or this handler) for predictable behavior.

Also applies to: 169-199, 476-513

core/schemas/mux.go (1)

1359-1369: Reasoning streaming delta mapping is aligned with new delta.Reasoning field

The new block that emits ResponsesStreamResponseTypeReasoningSummaryTextDelta from delta.Reasoning (with OutputIndex: Ptr(0) and Delta: delta.Reasoning) is consistent with the new ChatStreamResponseChoiceDelta.Reasoning field and the Responses streaming schema. For single‑output streams this looks fine; if you later support multi‑output streaming, you’ll likely want to plumb the actual choice index through instead of hard‑coding 0.

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

38-40: Bedrock reasoning mapping (non‑stream & stream) is coherent with the new schema

  • ToBedrockChatCompletionRequest now surfaces errors from convertChatParameters, which is important given the new reasoning validation there.
  • ToBifrostChatResponse correctly lifts ReasoningContent into ChatReasoningDetails and a consolidated assistantMessage.Reasoning string.
  • Streaming ReasoningContent deltas are converted into ChatStreamResponseChoiceDelta.Reasoning plus ReasoningDetails, which matches how mux.ToBifrostResponsesStreamResponse now consumes reasoning deltas.

Only very minor nits you might consider later:

  • reasoningText is built with a trailing newline; trim if that ever shows up awkwardly in clients.
  • If a Bedrock reasoning delta ever contains both text and a signature in the same payload, you currently only surface one or the other; merging both into a single ChatReasoningDetails entry would preserve more information.

Overall, the reasoning plumbing here looks solid.

Also applies to: 58-60, 103-112, 117-120, 135-141, 293-341

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

551-560: Aggregate cached‑token usage with max semantics instead of overwriting

In both streaming handlers you now populate cached‑token details:

  • Chat streaming: usage.PromptTokensDetails / usage.CompletionTokensDetails from CacheReadInputTokens and CacheCreationInputTokens.
  • Responses streaming: usage.InputTokensDetails / usage.OutputTokensDetails from the same fields.

Right now this is “last writer wins”:

if usageToProcess.CacheReadInputTokens > 0 {
    usage.PromptTokensDetails = &schemas.ChatPromptTokensDetails{ CachedTokens: usageToProcess.CacheReadInputTokens }
}

If Anthropic ever emits multiple events with incremental or non‑monotonic cache counts, you may want to mirror your token counters and Bedrock’s behavior by taking the max instead of overwriting:

if usageToProcess.CacheReadInputTokens > 0 {
    if usage.PromptTokensDetails == nil || usageToProcess.CacheReadInputTokens > usage.PromptTokensDetails.CachedTokens {
        usage.PromptTokensDetails = &schemas.ChatPromptTokensDetails{
            CachedTokens: usageToProcess.CacheReadInputTokens,
        }
    }
}

(and similarly for the other cached‑token fields). This keeps the final usage snapshot consistent regardless of emission order.

Also applies to: 893-902

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

325-344: Simplify nested else clause for cleaner code flow.

The else at line 330 is redundant since the preceding if returns. This can be flattened.

 		if bifrostReq.Params.Reasoning != nil {
 			if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort != "none" {
 				if bifrostReq.Params.Reasoning.MaxTokens != nil {
 					if *bifrostReq.Params.Reasoning.MaxTokens < MinimumReasoningMaxTokens {
 						return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", MinimumReasoningMaxTokens)
-					} else {
-						anthropicReq.Thinking = &AnthropicThinking{
-							Type:         "enabled",
-							BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
-						}
 					}
+					anthropicReq.Thinking = &AnthropicThinking{
+						Type:         "enabled",
+						BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
+					}
 				} else {
 					return nil, fmt.Errorf("reasoning.max_tokens is required for reasoning")
 				}
core/providers/cohere/chat.go (2)

556-672: Well-implemented ToBifrostChatMessage with comprehensive content handling.

The method properly handles:

  • String vs block content
  • Image content blocks
  • Thinking/reasoning blocks with details accumulation
  • Tool calls with nil-safety
  • Assistant message assembly with reasoning

Minor note: Line 598 appends a newline after each thinking block text, which may leave a trailing newline in reasoningText.

 				} else if block.Type == CohereContentBlockTypeThinking && block.Thinking != nil {
 					reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 						Index: len(reasoningDetails),
 						Type:  schemas.BifrostReasoningDetailsTypeText,
 						Text:  block.Thinking,
 					})
-					reasoningText += *block.Thinking + "\n"
+					if reasoningText != "" {
+						reasoningText += "\n"
+					}
+					reasoningText += *block.Thinking
 				}

104-121: Move reasoning minimum token validation constant to shared location.

The constant MinimumReasoningMaxTokens = 1024 is correctly validated here and matches Cohere's official minimum (≥1000 tokens for reasoning), but it's currently defined in the anthropic package. This creates an unnecessary cross-provider dependency; both Cohere and Bedrock import from the anthropic package for this validation. Consider defining this constant in core/schemas where other shared reasoning types (ChatReasoning, ResponsesParametersReasoning) are already centralized, making the constraint universally accessible without provider-specific imports.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5f6f04 and facc630.

📒 Files selected for processing (29)
  • core/providers/anthropic/anthropic.go (6 hunks)
  • core/providers/anthropic/chat.go (10 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (4 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (3 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/semanticcache/utils.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/providers/vertex/vertex.go
  • framework/streaming/chat.go
  • plugins/semanticcache/utils.go
  • ui/lib/types/logs.ts
  • core/providers/openai/openai.go
  • core/providers/cohere/types.go
  • core/providers/openai/utils.go
  • core/providers/cohere/cohere.go
  • core/providers/openai/types.go
  • core/providers/perplexity/chat.go
  • 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/anthropic/types.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/jsonparser/utils.go
  • core/providers/azure/azure.go
  • core/providers/bedrock/utils.go
  • core/providers/bedrock/responses.go
  • core/providers/openai/chat.go
  • core/schemas/mux.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/anthropic/errors.go
  • core/providers/bedrock/types.go
  • core/providers/anthropic/responses.go
  • core/providers/cohere/chat.go
  • core/providers/bedrock/bedrock.go
  • core/providers/anthropic/anthropic.go
  • core/providers/bedrock/chat.go
  • core/providers/anthropic/chat.go
  • core/schemas/chatcompletions.go
🧬 Code graph analysis (12)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (1)
  • BedrockConverseRequest (44-57)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/chat.go (3)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/mux.go (3)
core/schemas/responses.go (3)
  • ResponsesParametersReasoning (232-237)
  • BifrostResponsesStreamResponse (1424-1462)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1390-1390)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
ui/app/workspace/logs/views/logChatMessageView.tsx (2)
ui/lib/utils/validation.ts (2)
  • isJson (539-546)
  • cleanJson (548-558)
ui/app/workspace/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
core/providers/anthropic/errors.go (1)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
core/providers/anthropic/types.go (2)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
core/providers/cohere/chat.go (2)
core/schemas/chatcompletions.go (10)
  • BifrostChatRequest (12-19)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
  • ChatReasoningDetails (723-730)
  • ChatAssistantMessageToolCall (686-692)
  • ChatMessageContent (524-527)
  • ChatAssistantMessage (626-632)
  • ChatMessageRole (457-457)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/bedrock/bedrock.go (2)
core/providers/utils/utils.go (1)
  • CreateBifrostChatCompletionChunkResponse (687-716)
core/schemas/bifrost.go (1)
  • ChatCompletionStreamRequest (90-90)
core/providers/anthropic/anthropic.go (7)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/chatcompletions.go (1)
  • ChatPromptTokensDetails (821-827)
ui/lib/types/logs.ts (3)
  • CompletionTokensDetails (197-202)
  • ResponsesResponseInputTokens (542-544)
  • ResponsesResponseOutputTokens (546-548)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/schemas/responses.go (2)
  • ResponsesResponseInputTokens (261-267)
  • ResponsesResponseOutputTokens (269-279)
core/providers/bedrock/chat.go (1)
core/schemas/chatcompletions.go (6)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/schemas/chatcompletions.go (1)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
⏰ 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 (27)
core/providers/anthropic/types.go (1)

14-14: LGTM! Well-placed constant for reasoning token validation.

The MinimumReasoningMaxTokens constant is appropriately defined here and exported for use by other providers (e.g., Bedrock) that need to validate reasoning configurations against Anthropic's requirements.

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

647-687: LGTM! Proper handling of reasoning and signature streaming deltas.

The streaming conversion correctly:

  • Emits Reasoning content with ReasoningDetails for thinking deltas
  • Emits signature-only ReasoningDetails for signature deltas

This aligns with Anthropic's streaming format where thinking content and signatures arrive in separate delta events.


744-751: LGTM! Consistent use of Reasoning field in streaming.

The streaming response conversion correctly uses the new Reasoning field (renamed from Thought) for thinking content deltas.

ui/app/workspace/logs/views/logChatMessageView.tsx (1)

73-94: LGTM! UI correctly updated to display reasoning content.

The UI changes properly:

  • Rename the field from thought to reasoning to match the backend schema changes
  • Update the display label to "Reasoning"
  • Preserve the JSON detection and formatting behavior
plugins/jsonparser/utils.go (1)

309-314: LGTM! Deep copy correctly updated for Reasoning field rename.

The field rename from Thought to Reasoning is correctly applied in the deep copy function, maintaining the shallow copy behavior appropriate for string pointers.

core/providers/bedrock/bedrock.go (1)

21-22: Per‑stream UUID for Bedrock chat streaming looks correct

Using a goroutine‑local uuid.New().String() as the stream ID and wiring it through each chunk and the final CreateBifrostChatCompletionChunkResponse keeps Bedrock in line with other providers that emit stable response.IDs during streaming. No issues spotted.

Also applies to: 771-783, 859-885

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

11-18: Message and reasoning conversions for OpenAI look consistent

Using ConvertOpenAIMessagesToBifrostMessages/ConvertBifrostMessagesToOpenAIMessages ensures assistant‑side fields (reasoning, annotations, tool calls) survive round‑trips, and updating filterOpenAISpecificParameters to operate on ChatParameters.Reasoning.Effort while normalizing "minimal""low" keeps the previous behavior with the new structured reasoning object. No issues spotted.

Also applies to: 21-33, 61-75

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

303-311: Error‑aware Anthropic request construction is wired correctly

Using ToAnthropicChatRequest / ToAnthropicResponsesRequest directly inside the CheckContextAndGetRequestBody closures (and setting Stream = Ptr(true) only after a successful conversion in the streaming cases) ensures model/parameter validation errors surface as BifrostErrors instead of slipping through. This applies consistently to both non‑streaming and streaming Chat/Responses paths and matches the broader error‑handling refactor in this stack.

Also applies to: 353-365, 647-655, 695-707

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

656-660: Single conversion of Bedrock output message to Responses output is fine

The new early conversion of response.Output.Message via convertBedrockMessageToResponsesMessages into bifrostResp.Output is straightforward and avoids duplicated work later in the function. No issues here.

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

190-201: LGTM! Clean conversion from Anthropic Thinking to Bifrost Reasoning.

The mapping correctly handles both enabled and disabled thinking states, preserving the budget tokens when present.


296-299: Good defensive nil check added.

The early return with a descriptive error message prevents nil pointer dereferences downstream.

core/providers/azure/azure.go (2)

353-360: Proper error propagation from converter function.

The updated code correctly handles the error return from ToAnthropicChatRequest and sets the deployment model after validation.


529-533: LGTM! Consistent error handling pattern.

Error propagation for ToAnthropicResponsesRequest follows the same pattern as the chat completion methods.

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

103-104: LGTM! ReasoningContent field properly added.

The optional field follows the existing pattern for content blocks and enables reasoning support in Bedrock requests/responses.


152-159: Well-structured reasoning types.

The nested structure with ReasoningText containing Text and optional Signature aligns with the Anthropic thinking feature and supports verification via signatures.


438-440: Clean delta structure for streaming reasoning content.

The updated BedrockContentBlockDelta properly separates text, reasoning, and tool use deltas, enabling proper streaming of thinking content.

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

214-221: LGTM! Clean message conversion using new helper.

The refactored code using ToBifrostChatMessage() is cleaner and more maintainable.


242-256: LGTM! Bidirectional reasoning conversion is consistent.

The conversion from Cohere Thinking to Bifrost Reasoning properly mirrors the forward conversion logic, handling both enabled and disabled states.


405-446: LGTM! Stream delta handling properly distinguishes text and thinking content.

The nested conditionals correctly route text deltas and thinking deltas to their respective response fields, including populating ReasoningDetails.

core/schemas/chatcompletions.go (8)

165-165: Clean replacement of deprecated field with structured type.

Replacing ReasoningEffort *string with Reasoning *ChatReasoning provides a cleaner API while maintaining backward compatibility through the custom unmarshaller.


186-220: Robust backward-compatible unmarshalling with clear error handling.

The custom UnmarshalJSON:

  1. Correctly prevents both reasoning_effort and reasoning from being set simultaneously
  2. Converts legacy reasoning_effort to the new Reasoning struct transparently

This ensures smooth migration for API consumers.


222-226: LGTM! ChatReasoning struct is well-designed.

The struct with Effort and MaxTokens fields provides the necessary flexibility for reasoning configuration across providers.


480-521: Necessary custom unmarshaller for ChatMessage with embedded types.

The explicit handling of embedded ChatToolMessage and ChatAssistantMessage is required because embedded structs with their own UnmarshalJSON methods don't compose automatically. The check at lines 514-517 properly gates assistant message assignment.


634-667: Good UX: Synthesizing ReasoningDetails from Reasoning string.

When a simple reasoning string is provided without reasoning_details, the unmarshaller automatically creates a structured ChatReasoningDetails entry. This improves API ergonomics while maintaining full detail support.


714-730: Well-defined reasoning details types with comprehensive fields.

The ChatReasoningDetails struct with Index, Type, Summary, Text, Signature, and Data fields covers all reasoning output variations across providers (summary, encrypted, text). The type constants align with the UI types in ui/lib/types/logs.ts.


768-794: Consistent synthesizing behavior for streaming deltas.

The UnmarshalJSON for ChatStreamResponseChoiceDelta follows the same pattern as ChatAssistantMessage, ensuring consistent behavior between streaming and non-streaming responses.


835-835: LGTM! ImageTokens field addition.

The optional ImageTokens field in ChatCompletionTokensDetails enables tracking image-related token usage for multimodal models.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from e8afa58 to 455b127 Compare December 5, 2025 14:01
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from facc630 to 0822b25 Compare December 5, 2025 14:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

478-482: Potential overwrite of reasoning_config by additionalModelRequestFieldPaths.

At line 480, if additionalModelRequestFieldPaths exists in ExtraParams, it completely replaces AdditionalModelRequestFields with orderedFields. This could discard the reasoning_config set at lines 466-469.

Consider merging instead of replacing:

 			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
 				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-					bedrockReq.AdditionalModelRequestFields = orderedFields
+					// Merge orderedFields into existing AdditionalModelRequestFields
+					if bedrockReq.AdditionalModelRequestFields == nil {
+						bedrockReq.AdditionalModelRequestFields = orderedFields
+					} else {
+						for k, v := range orderedFields {
+							bedrockReq.AdditionalModelRequestFields[k] = v
+						}
+					}
 				}
 			}
core/providers/anthropic/chat.go (1)

720-751: Chat → Anthropic streaming doesn’t yet propagate reasoning signatures

ToAnthropicChatStreamResponse maps:

  • delta.ContentText deltas,
  • delta.Reasoningthinking deltas,
  • tool calls and finish reasons appropriately,

but ignores delta.ReasoningDetails, which is where signature-only reasoning events will land on the Chat side.

This means reasoning signatures carried in ChatStreamResponseChoiceDelta.ReasoningDetails (with no Reasoning text) won’t be reflected back to Anthropic SSE.

If you intend to support signature verification on the Chat streaming path (not just Responses), consider:

  • Detecting delta.ReasoningDetails with a non-nil Signature and emitting an AnthropicStreamDelta of type signature with that value.
  • Potentially also combining text+signature when both are present.

This would mirror the handling you already implemented in the Responses streaming bridge.

♻️ Duplicate comments (10)
core/providers/anthropic/errors.go (1)

21-25: Nil pointer dereference risk on bifrostErr.Error

Line 24 accesses bifrostErr.Error.Message without checking if bifrostErr.Error is nil. This will panic if a BifrostError is passed with a nil Error field.

Apply this diff:

 	// Handle nested error fields with nil checks
+	message := ""
+	if bifrostErr.Error != nil {
+		message = bifrostErr.Error.Message
+	}
+
 	errorStruct := AnthropicMessageErrorStruct{
 		Type:    errorType,
-		Message: bifrostErr.Error.Message,
+		Message: message,
 	}
core/providers/azure/azure.go (2)

452-458: Potential nil pointer dereference after error check.

After checking err != nil, the code directly accesses reqBody.Model and reqBody.Stream at lines 456-457 without verifying reqBody is non-nil. If ToAnthropicChatRequest returns (nil, nil), this will panic.

 			func() (any, error) {
 				reqBody, err := anthropic.ToAnthropicChatRequest(request)
 				if err != nil {
 					return nil, err
 				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic request conversion returned nil")
+				}
 				reqBody.Model = deployment
 				reqBody.Stream = schemas.Ptr(true)
 				return reqBody, nil
 			},

627-632: Same nil check concern as ChatCompletionStream.

The code accesses reqBody.Model and reqBody.Stream without a nil check after error handling.

 			func() (any, error) {
 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic responses request conversion returned nil")
+				}
 				reqBody.Model = deployment
 				reqBody.Stream = schemas.Ptr(true)
 				return reqBody, nil
 			},
core/providers/cohere/chat.go (1)

126-137: ExtraParams can still override Reasoning-based thinking configuration.

Lines 126-137 can overwrite cohereReq.Thinking set from Reasoning (lines 104-121). The past review flagged this but was marked as addressed. If this is intentional (ExtraParams takes precedence), consider adding a comment to document this behavior.

plugins/semanticcache/utils.go (1)

669-671: Extract Reasoning.MaxTokens in cache metadata for accurate cache key generation.

The nil-safety check is correct. However, MaxTokens should also be extracted since requests with identical Effort but different MaxTokens could incorrectly hit the same cache entry.

 if params.Reasoning != nil && params.Reasoning.Effort != nil {
 	metadata["reasoning_effort"] = *params.Reasoning.Effort
+	if params.Reasoning.MaxTokens != nil {
+		metadata["reasoning_max_tokens"] = *params.Reasoning.MaxTokens
+	}
 }
core/schemas/mux.go (1)

772-777: Reasoning.MaxTokens still not round-tripped in Chat ↔ Responses conversions

ChatReasoning.MaxTokens is still dropped when converting via mux:

  • ToResponsesRequest only copies Effort and is gated on Reasoning.Effort != nil, so any configured MaxTokens is ignored, and a Reasoning object with only MaxTokens is skipped entirely.
  • ToChatRequest only copies Effort back from ResponsesParametersReasoning, so MaxTokens on the Responses side never makes it back into ChatReasoning.

Since MaxTokens is the field that ultimately drives Anthropic’s reasoning/thinking budget, this silently loses user config on any path that passes through mux.

Recommend:

  • Treat MaxTokens as first-class in both directions, and
  • Gate on (Effort != nil || MaxTokens != nil) so that MaxTokens alone is enough to propagate reasoning.

Example:

-		// Handle Reasoning from reasoning_effort
-		if bcr.Params.Reasoning != nil && bcr.Params.Reasoning.Effort != nil {
-			brr.Params.Reasoning = &ResponsesParametersReasoning{
-				Effort: bcr.Params.Reasoning.Effort,
-			}
-		}
+		// Handle structured Reasoning (Effort / MaxTokens)
+		if bcr.Params.Reasoning != nil &&
+			(bcr.Params.Reasoning.Effort != nil || bcr.Params.Reasoning.MaxTokens != nil) {
+			brr.Params.Reasoning = &ResponsesParametersReasoning{
+				Effort:    bcr.Params.Reasoning.Effort,
+				MaxTokens: bcr.Params.Reasoning.MaxTokens,
+			}
+		}

and:

-		// Handle Reasoning from Reasoning
-		if brr.Params.Reasoning != nil {
-			bcr.Params.Reasoning = &ChatReasoning{
-				Effort: brr.Params.Reasoning.Effort,
-			}
-		}
+		// Handle structured Reasoning from Responses -> Chat
+		if brr.Params.Reasoning != nil {
+			bcr.Params.Reasoning = &ChatReasoning{
+				Effort:    brr.Params.Reasoning.Effort,
+				MaxTokens: brr.Params.Reasoning.MaxTokens,
+			}
+		}

Also applies to: 851-856

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

14-17: Nil bifrostReq.Params should be handled gracefully, not as an error

convertChatParameters now returns an error when bifrostReq.Params == nil. This is both:

  • Inconsistent with other providers (which treat nil Params as “no overrides”), and
  • A potential behavior break for callers that validly omit params in chat requests.

Given convertInferenceConfig / convertToolConfig only read fields off *ChatParameters, a more robust pattern is to synthesize a default ChatParameters when Params is nil instead of erroring, e.g.:

 func convertChatParameters(bifrostReq *schemas.BifrostChatRequest, bedrockReq *BedrockConverseRequest) error {
-	if bifrostReq.Params == nil {
-		return fmt.Errorf("bifrost request parameters are nil")
-	}
+	if bifrostReq.Params == nil {
+		bifrostReq.Params = &schemas.ChatParameters{
+			ExtraParams: map[string]any{},
+		}
+	}

so downstream conversion can proceed with defaults and you only error on truly invalid nested fields (like missing reasoning.max_tokens when reasoning is enabled).


27-47: reasoning_config can still be overwritten by additionalModelRequestFieldPaths

The new reasoning block correctly populates:

  • reasoning_config.type = "disabled" when Effort is "none", and
  • reasoning_config.type = "enabled" with budget_tokens validated against anthropic.MinimumReasoningMaxTokens.

However, later you do:

if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
	if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
		bedrockReq.AdditionalModelRequestFields = orderedFields
	}
}

which overwrites AdditionalModelRequestFields entirely, discarding the earlier reasoning_config.

Instead, merge the extracted fields into the existing map:

-				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-					bedrockReq.AdditionalModelRequestFields = orderedFields
-				}
+				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
+					if bedrockReq.AdditionalModelRequestFields == nil {
+						bedrockReq.AdditionalModelRequestFields = orderedFields
+					} else {
+						for k, v := range orderedFields {
+							bedrockReq.AdditionalModelRequestFields[k] = v
+						}
+					}
+				}

so reasoning_config and extra model fields can coexist.

Also applies to: 70-75

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

99-117: Fix unsafe / redundant else if on bifrostReq.Params.Reasoning

Inside ToAnthropicChatRequest:

  • The main if bifrostReq.Params != nil { ... } block already handles all Reasoning cases, including Effort == "none" and enabled reasoning with validated MaxTokens.
  • The trailing } else if bifrostReq.Params.Reasoning != nil && ... runs when bifrostReq.Params == nil, but still dereferences bifrostReq.Params.Reasoning, which will panic at runtime if ever evaluated. Logically, Reasoning cannot be non-nil when Params is nil anyway.

This else if is both unnecessary and unsafe. You can simply remove it:

-	} else if bifrostReq.Params.Reasoning != nil && bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-		anthropicReq.Thinking = &AnthropicThinking{
-			Type: "disabled",
-		}
-	}
+	}

The inside-Params branch already sets Thinking.Type = "disabled" when Effort == "none".

Also applies to: 118-121


284-390: Anthropic → Bifrost chat response conversion is mostly solid; usage cache mapping still redundant

The new (*AnthropicMessageResponse).ToBifrostChatResponse:

  • Aggregates text into ChatMessageContent (string when a single text block exists),
  • Converts tool_use blocks into ChatAssistantMessage.ToolCalls,
  • Converts thinking blocks into ChatAssistantMessage.Reasoning plus ReasoningDetails (with signatures),
  • Maps finish reason via ConvertAnthropicFinishReasonToBifrost,
  • Fills BifrostLLMUsage including cached tokens.

Two lingering issues in the usage block:

  1. CacheCreationInputTokens is assigned twice to CompletionTokensDetails.CachedTokens (first in the struct literal, then again in the if CacheCreationInputTokens > 0 block). The second block is redundant.
  2. Semantically, CacheCreationInputTokens reflects input-side cache creation; mapping it to CompletionTokensDetails.CachedTokens (completion-side) may be misleading. Treating it as prompt-side cached tokens (or documenting the choice) would better reflect its meaning.

At minimum you can drop the second assignment:

-			CompletionTokensDetails: &schemas.ChatCompletionTokensDetails{
-				CachedTokens: response.Usage.CacheCreationInputTokens,
-			},
 			TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
 		}
-		if response.Usage.CacheCreationInputTokens > 0 {
-			if bifrostResponse.Usage.CompletionTokensDetails == nil {
-				bifrostResponse.Usage.CompletionTokensDetails = &schemas.ChatCompletionTokensDetails{}
-			}
-			bifrostResponse.Usage.CompletionTokensDetails.CachedTokens = response.Usage.CacheCreationInputTokens
-		}

and optionally revisit whether this field should instead live under PromptTokensDetails.

Also applies to: 417-436

🧹 Nitpick comments (6)
core/providers/perplexity/chat.go (1)

8-172: Consider aligning error handling with other providers in the stack.

According to the broader PR context, other provider converters (e.g., ToAnthropicChatRequest, ToAnthropicResponsesRequest) now return (…, error) to propagate validation failures. This function still returns only *PerplexityChatRequest without an error.

While the current changes don't introduce validation logic requiring errors, consider whether this function should adopt the same signature pattern for consistency and future extensibility—especially if reasoning validation (e.g., minimum token checks) might be added later.

Based on coding guidelines, reviewing in context of the whole stack.

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

56-65: Clarify mutual exclusivity enforcement for embedded pointers.

The comment warns that only one of *ChatToolMessage or *OpenAIChatAssistantMessage should be non-nil, but there's no runtime validation. Consider adding validation in the conversion functions to prevent accidental misuse.

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

592-599: Minor: Trailing newline in accumulated reasoning text.

Line 598 appends "\n" after each thinking block's text. This leaves a trailing newline in reasoningText. Consider using strings.TrimSuffix before assigning to assistantMessage.Reasoning at line 663.

+import "strings"
+
 if len(reasoningDetails) > 0 {
     if assistantMessage == nil {
         assistantMessage = &schemas.ChatAssistantMessage{}
     }
     assistantMessage.ReasoningDetails = reasoningDetails
-    assistantMessage.Reasoning = schemas.Ptr(reasoningText)
+    assistantMessage.Reasoning = schemas.Ptr(strings.TrimSuffix(reasoningText, "\n"))
 }
core/schemas/chatcompletions.go (1)

634-667: Consider extracting common reasoning synthesis logic.

The UnmarshalJSON implementations for ChatAssistantMessage (lines 655-664) and ChatStreamResponseChoiceDelta (lines 782-791) contain nearly identical logic for synthesizing ReasoningDetails from Reasoning. Consider extracting this into a helper function to reduce duplication.

// Helper function to synthesize reasoning details from reasoning text
func synthesizeReasoningDetails(reasoning *string, existingDetails []ChatReasoningDetails) []ChatReasoningDetails {
    if reasoning != nil && len(existingDetails) == 0 {
        text := *reasoning
        return []ChatReasoningDetails{{
            Index: 0,
            Type:  BifrostReasoningDetailsTypeText,
            Text:  &text,
        }}
    }
    return existingDetails
}

Also applies to: 768-794

transports/bifrost-http/handlers/inference.go (1)

504-506: Redundant nil check.

Since ChatParameters is now initialized at line 477 before unmarshalling, this nil check is always false.

-	if req.ChatParameters == nil {
-		req.ChatParameters = &schemas.ChatParameters{}
-	}
-
 	extraParams, err := extractExtraParams(ctx.PostBody(), chatParamsKnownFields)
core/providers/bedrock/chat.go (1)

58-60: Bedrock → Bifrost reasoning aggregation looks correct

The non-streaming path:

  • Collects each ReasoningContent block into ChatReasoningDetails (index, type reasoning.text, text, signature),
  • Concatenates reasoning text into reasoningText and assigns it to ChatAssistantMessage.Reasoning,
  • Falls back to contentStr when there’s exactly one text block, preserving prior UX.

This cleanly separates visible assistant output (content) from reasoning and keeps signatures attached to the corresponding reasoning entries.

Only very minor nit is the trailing newline in reasoningText (due to + "\n"), but that’s unlikely to matter; trimming can be deferred if it ever becomes an issue.

Also applies to: 103-112, 117-120, 135-141

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between facc630 and 0822b25.

📒 Files selected for processing (29)
  • core/providers/anthropic/anthropic.go (6 hunks)
  • core/providers/anthropic/chat.go (10 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (4 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (3 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/semanticcache/utils.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/providers/openai/openai.go
  • core/providers/cohere/types.go
  • core/providers/anthropic/types.go
  • plugins/jsonparser/utils.go
  • core/providers/bedrock/bedrock.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/vertex/vertex.go
  • ui/lib/types/logs.ts
🧰 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:

  • framework/streaming/chat.go
  • core/schemas/mux.go
  • core/providers/openai/utils.go
  • core/providers/perplexity/chat.go
  • plugins/semanticcache/utils.go
  • core/schemas/responses.go
  • core/providers/bedrock/responses.go
  • core/providers/bedrock/utils.go
  • core/providers/openai/chat.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • transports/bifrost-http/handlers/inference.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/anthropic.go
  • core/providers/azure/azure.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/chat.go
  • core/providers/anthropic/errors.go
  • core/providers/cohere/chat.go
  • core/providers/cohere/cohere.go
  • core/providers/anthropic/chat.go
🧬 Code graph analysis (12)
framework/streaming/chat.go (3)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
core/providers/gemini/types.go (1)
  • Type (778-778)
core/schemas/mux.go (3)
core/schemas/responses.go (3)
  • ResponsesParametersReasoning (232-237)
  • BifrostResponsesStreamResponse (1424-1462)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1390-1390)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/utils.go (3)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/bedrock/responses.go (4)
core/schemas/utils.go (4)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/openai/types.go (1)
core/schemas/chatcompletions.go (6)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • ChatReasoning (223-226)
core/providers/anthropic/anthropic.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/bedrock/chat.go (1)
core/schemas/chatcompletions.go (8)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • BifrostChatResponse (26-41)
  • BifrostResponseChoice (704-712)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/providers/anthropic/errors.go (2)
ui/lib/types/logs.ts (1)
  • BifrostError (233-239)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/cohere/chat.go (3)
core/schemas/chatcompletions.go (13)
  • BifrostChatRequest (12-19)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatNonStreamResponseChoice (745-748)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlock (590-597)
  • ChatAssistantMessageToolCall (686-692)
  • ChatMessageContent (524-527)
  • ChatAssistantMessageToolCallFunction (695-698)
  • ChatAssistantMessage (626-632)
core/providers/cohere/types.go (4)
  • CohereChatRequest (14-31)
  • CohereThinking (170-173)
  • ThinkingTypeDisabled (180-180)
  • ThinkingTypeEnabled (179-179)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/cohere/cohere.go (2)
core/providers/cohere/chat.go (1)
  • ToCohereChatCompletionRequest (12-199)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/chat.go (2)
core/schemas/chatcompletions.go (7)
  • BifrostChatRequest (12-19)
  • ChatAssistantMessage (626-632)
  • ChatAssistantMessageToolCall (686-692)
  • ChatContentBlock (590-597)
  • ChatReasoningDetails (723-730)
  • ChatMessageContent (524-527)
  • ChatMessage (469-478)
core/providers/anthropic/types.go (5)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicContentBlock (147-159)
  • AnthropicContentBlockTypeThinking (143-143)
⏰ 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 (49)
core/providers/perplexity/chat.go (1)

30-36: No action needed—backwards compatibility is already handled.

The deprecated reasoning_effort field is converted to the new Reasoning.Effort structure at the schema level during UnmarshalJSON in core/schemas/chatcompletions.go (lines 212-216). When a request contains the legacy field, it is automatically mapped to Reasoning.Effort before reaching this function, so the code correctly receives the converted nested structure.

core/schemas/responses.go (2)

232-237: LGTM!

The ResponsesParametersReasoning struct changes are well-structured. The new MaxTokens field is correctly optional with omitempty, and the updated Effort comment clarifies the behavior that any value other than "none" enables reasoning.


322-325: Comment update is informative.

The inline comment clarifying model-specific reasoning content behavior is helpful for maintainers.

core/providers/azure/azure.go (2)

353-360: Error propagation is correctly implemented.

The error handling for ToAnthropicChatRequest in the non-streaming path properly checks and propagates errors before using the request body.


529-534: Error propagation is correctly implemented.

The error handling for ToAnthropicResponsesRequest in the non-streaming Responses path properly propagates errors.

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

349-367: Reasoning config extraction is well-implemented.

The code correctly extracts reasoning_config from AdditionalModelRequestFields and maps it to the Bifrost schema with appropriate type checking using SafeExtractFromMap, SafeExtractString, and SafeExtractInt.


452-472: Reasoning validation and conversion looks correct.

The validation enforces that max_tokens is required and meets the minimum threshold (MinimumReasoningMaxTokens = 1024). The disabled/enabled reasoning config mapping is properly structured.


656-660: Output conversion refactor is clean.

Moving the output conversion to the top of ToBifrostResponsesResponse improves code organization.

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

307-307: Error propagation correctly simplified.

Removing the wrapping nil error return and directly returning from ToAnthropicChatRequest is cleaner and ensures errors propagate properly.


358-363: Streaming error propagation is correctly implemented.

The error from ToAnthropicChatRequest is now properly checked before setting Stream = true, preventing use of an invalid request body.


551-560: Cached token tracking improves observability.

Mapping CacheReadInputTokens to PromptTokensDetails.CachedTokens and CacheCreationInputTokens to CompletionTokensDetails.CachedTokens surfaces useful cache usage information.


651-651: Error propagation correctly simplified for Responses.

Consistent with the ChatCompletion change.


700-704: Streaming error propagation is correctly implemented for Responses.

The error from ToAnthropicResponsesRequest is properly checked before setting Stream = true.


893-902: Cached token tracking for Responses stream.

Consistent with the chat completion streaming implementation. Maps cache tokens to the appropriate details fields.

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

103-105: LGTM! Reasoning content types are well-structured.

The new BedrockReasoningContent and BedrockReasoningContentText types correctly model Bedrock's reasoning blocks with the required text and optional signature fields for verification support.

Also applies to: 152-159


438-440: LGTM! Delta structure properly supports reasoning streaming.

The refactored BedrockContentBlockDelta now cleanly separates text, reasoning content, and tool use deltas, enabling proper streaming of reasoning content.

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

74-101: LGTM! MarshalJSON correctly transforms reasoning to reasoning_effort.

The implementation properly shadows the embedded Reasoning field to exclude it from output and emits reasoning_effort when Reasoning.Effort is set. The type alias pattern correctly avoids infinite recursion.

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

104-121: LGTM! Reasoning validation is correctly implemented.

The validation properly checks for effort == "none" to disable thinking, requires max_tokens when reasoning is enabled, and enforces the minimum token threshold from anthropic.MinimumReasoningMaxTokens.


423-446: LGTM! Streaming reasoning content is properly handled.

The streaming handler correctly distinguishes between text content and thinking content, mapping thinking to both Reasoning and ReasoningDetails with appropriate type markers.

core/providers/cohere/cohere.go (2)

279-286: LGTM! Error propagation is correctly implemented for ChatCompletion.

The request body builder now properly returns and propagates errors from ToCohereChatCompletionRequest, allowing validation errors (like invalid reasoning tokens) to surface to callers.


331-338: LGTM! ChatCompletionStream correctly handles conversion errors.

The streaming path properly checks for errors from ToCohereChatCompletionRequest before setting the stream flag, ensuring validation failures are propagated.

core/schemas/chatcompletions.go (5)

186-220: LGTM! Backward-compatible unmarshalling for reasoning_effort.

The UnmarshalJSON correctly handles the deprecated reasoning_effort field by converting it to the new Reasoning struct, with proper validation that both fields cannot coexist.


222-226: LGTM! ChatReasoning struct is well-designed.

The struct cleanly separates Effort (to enable/disable reasoning) from MaxTokens (budget), with good documentation noting provider-specific requirements.


480-521: LGTM! ChatMessage unmarshalling correctly handles embedded types.

The implementation properly handles the interplay with ChatAssistantMessage's custom unmarshaller by unmarshalling each component separately and only setting embedded pointers when relevant fields are populated.


714-730: LGTM! ChatReasoningDetails provides comprehensive reasoning representation.

The struct supports multiple reasoning types (summary, encrypted, text) with appropriate fields including Signature for verification, enabling cross-provider reasoning interoperability.


835-835: LGTM! ImageTokens field addition.

The new ImageTokens field properly extends token accounting to include image-related tokens.

transports/bifrost-http/handlers/inference.go (4)

77-77: LGTM!

The field name correctly updated from "reasoning_effort" to "reasoning" to match the new nested reasoning structure.


169-199: LGTM!

The custom UnmarshalJSON properly handles the interference between embedded BifrostParams and the ChatParameters custom unmarshaller. The three-stage unmarshalling approach correctly separates concerns.


271-280: LGTM!

Consistent use of sonic.Unmarshal aligns with the rest of the file.


476-478: LGTM!

Initializing ChatParameters before unmarshalling ensures the custom UnmarshalJSON can properly populate the struct.

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

14-14: LGTM!

Correctly uses the new ConvertOpenAIMessagesToBifrostMessages utility to handle the OpenAI-specific message structure including reasoning fields.


28-28: LGTM!

Symmetric conversion using ConvertBifrostMessagesToOpenAIMessages ensures proper round-trip handling of message structures.


63-65: LGTM!

The nested nil-checks on Reasoning and Reasoning.Effort are correct. The transformation from "minimal" to "low" maintains compatibility with non-OpenAI providers.

framework/streaming/chat.go (3)

39-43: LGTM!

Correctly uses bifrost.Ptr for consistent pointer initialization when accumulating refusal content.


44-54: LGTM!

Reasoning accumulation follows the same pattern as refusal handling, properly initializing ChatAssistantMessage if nil and concatenating reasoning content across chunks.


55-105: LGTM!

The index-based merging of ReasoningDetails correctly handles streaming scenarios where details arrive across multiple chunks. The accumulation behavior (concatenate Text/Summary/Data, overwrite Signature/Type) aligns with expected streaming semantics where signatures are typically final values.

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

5-24: LGTM!

Clean conversion from OpenAI messages to Bifrost format. Correctly handles the nil check on OpenAIChatAssistantMessage before creating the corresponding Bifrost struct.


26-45: LGTM!

Symmetric conversion maintains consistency. Note that ReasoningDetails from ChatAssistantMessage is intentionally not copied to OpenAIChatAssistantMessage since it's a Bifrost-specific accumulated field not part of the OpenAI API.

core/schemas/mux.go (1)

1359-1365: Reasoning streaming delta mapping looks correct

Mapping delta.Reasoning into ResponsesStreamResponseTypeReasoningSummaryTextDelta with Delta set to the reasoning text and a stable OutputIndex is consistent with the new Responses-side reasoning event type and with the Anthropic streaming bridge.

No changes needed here.

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

27-47: Reasoning → reasoning_config mapping and validation look good

The logic for Bedrock reasoning:

  • Disables reasoning when Effort == "none",
  • Requires Reasoning.MaxTokens and enforces >= anthropic.MinimumReasoningMaxTokens when reasoning isn’t explicitly "none",
  • Sends {type: "enabled", budget_tokens: max_tokens} to reasoning_config.

This matches the Anthropic semantics and the PR’s minimum tokens requirement.

No changes needed here beyond the separate map-merge concern already raised.

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

38-40: Good: Bedrock chat now propagates parameter conversion errors

Wrapping convertChatParameters in:

if err := convertChatParameters(...); err != nil {
	return nil, fmt.Errorf("failed to convert chat parameters: %w", err)
}

correctly surfaces validation failures (e.g., invalid reasoning.max_tokens) instead of continuing with a partially-initialized request.


293-341: Streaming reasoning text and signatures are wired through correctly

The streaming handler:

  • Emits ChatStreamResponseChoiceDelta.Reasoning plus a ReasoningDetails entry when ReasoningContentDelta.Text is non-empty.
  • Emits a ReasoningDetails entry with only Signature when ReasoningContentDelta.Signature is present.

This matches the new ChatReasoningDetails schema and ensures both reasoning text and signatures are available to downstream mux/UI layers.

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

190-201: Thinking → Responses Reasoning mapping is aligned and useful

When request.Thinking is present:

  • "enabled" is mapped to ResponsesParametersReasoning{ Effort: "auto", MaxTokens: BudgetTokens }, and
  • "disabled" is mapped to ResponsesParametersReasoning{ Effort: "none" }.

This keeps Anthropic’s thinking config and the unified Responses Reasoning object in sync and is consistent with the broader reasoning model in core/schemas.

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

13-16: Good: ToAnthropicChatRequest now validates nil request/input

Returning an error when bifrostReq == nil || bifrostReq.Input == nil is reasonable; Anthropic chat requires message content, so this protects against constructing malformed provider requests.


211-220: ReasoningDetails → Anthropic thinking blocks mapping is sound

When converting Bifrost messages to Anthropic:

  • Each ChatReasoningDetails entry is turned into an Anthropic thinking content block with Thinking and Signature,
  • These blocks are prepended before visible content, preserving reasoning plus attached signatures.

This is consistent with how Anthropic models thinking content and with the unified reasoning_details schema.


441-541: Bifrost Chat → Anthropic chat response reasoning/tool mapping looks correct

ToAnthropicChatResponse:

  • Propagates usage and cached token details appropriately,
  • Converts FinishReason/StopString back to Anthropic StopReason/StopSequence,
  • Translates ChatAssistantMessage.ReasoningDetails of type reasoning.text into Anthropic thinking content blocks with both text and signature,
  • Adds primary text content (string or blocks) and tool calls as tool_use blocks.

This keeps reasoning/signature and tool metadata intact across the round-trip.


636-655: Streaming thinking and signature deltas to Chat are correctly emitted

In ToBifrostChatCompletionStream:

  • AnthropicStreamDeltaTypeThinking is mapped to ChatStreamResponseChoiceDelta.Reasoning plus a ReasoningDetails entry (type reasoning.text, text populated).
  • AnthropicStreamDeltaTypeSignature produces a delta with ReasoningDetails carrying only Signature.

This matches the Chat streaming schema and aligns with the changes in mux/responses that consume delta.Reasoning and ReasoningDetails.

Also applies to: 665-687


744-751: Reasoning text deltas → Anthropic thinking deltas look good

The else if delta.Reasoning != nil branch correctly:

  • Emits a content_block_delta with Type: thinking,
  • Sets Thinking to the reasoning text.

This aligns with Anthropic’s SSE format and matches how thinking deltas are handled elsewhere in the provider.


852-865: Chat stream error SSE formatting is consistent

ToAnthropicChatStreamError wraps ToAnthropicChatCompletionError into an AnthropicStreamEvent{Type: error} and formats it as:

event: error
data: { ...json... }

This matches standard Anthropic SSE error framing; no changes needed.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 455b127 to 8e37f9a Compare December 6, 2025 10:05
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 0822b25 to 05daf39 Compare December 6, 2025 10:05
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/cohere/chat.go (1)

104-137: ExtraParams thinking still overwrites Reasoning-based thinking.

A previous review flagged that ExtraParams["thinking"] (lines 126-137) can silently overwrite the Thinking configuration set from Reasoning (lines 104-121). The past review comment indicates this was addressed, but the current code still shows the override can occur.

If both Reasoning and ExtraParams["thinking"] are provided, the ExtraParams version will win. Consider guarding the ExtraParams extraction:

 		// Convert extra params
 		if bifrostReq.Params.ExtraParams != nil {
 			// Handle thinking parameter
-			if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
+			if cohereReq.Thinking == nil {
+				if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
 				if thinkingMap, ok := thinkingParam.(map[string]interface{}); ok {
 					thinking := &CohereThinking{}
 					if typeStr, ok := schemas.SafeExtractString(thinkingMap["type"]); ok {
 						thinking.Type = CohereThinkingType(typeStr)
 					}
 					if tokenBudget, ok := schemas.SafeExtractIntPointer(thinkingMap["token_budget"]); ok {
 						thinking.TokenBudget = tokenBudget
 					}
 					cohereReq.Thinking = thinking
 				}
+				}
 			}
♻️ Duplicate comments (7)
core/providers/bedrock/utils.go (2)

14-17: Nil Params now hard-fails Bedrock chat; verify this intentional vs other providers

Other providers typically treat Params == nil as “use defaults” rather than an error. This change makes Bedrock chat fail when callers omit params, which is a behavior change and inconsistent with Gemini/Vertex/Perplexity patterns.

If this isn’t a deliberate divergence, consider initializing a default ChatParameters (with ExtraParams map) instead of returning an error, and keep strict validation only for nested fields like reasoning.max_tokens.


28-47: Reasoning config can be dropped and uses inconsistent map type

Two related issues:

  1. Type mismatch for disabled reasoning config

    Here you set:

    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
        "type": "disabled",
    }

    but ToBifrostResponsesRequest (and similar readers) expect reasoning_config as map[string]interface{} and type‑assert accordingly. When this value comes from our own converter, the .(map[string]interface{}) assertion will fail for the disabled case and we’ll never populate Params.Reasoning.

    Use map[string]any for both enabled and disabled shapes:

  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
  • "type": "disabled",
    
  • }
  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
  • "type": "disabled",
    
  • }
    
    
  1. additionalModelRequestFieldPaths overwrites reasoning_config

    Later you do:

    if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
        if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
            bedrockReq.AdditionalModelRequestFields = orderedFields
        }
    }

    This replaces the entire map, discarding the previously-set reasoning_config. If both need to coexist, merge into the existing map instead of overwriting:

  • if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
  • bedrockReq.AdditionalModelRequestFields = orderedFields
    
  • }
  • if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
  • if bedrockReq.AdditionalModelRequestFields == nil {
    
  •     bedrockReq.AdditionalModelRequestFields = orderedFields
    
  • } else {
    
  •     for k, v := range orderedFields {
    
  •         bedrockReq.AdditionalModelRequestFields[k] = v
    
  •     }
    
  • }
    
  • }
    
    

Same merge pattern should be applied anywhere else we combine reasoning_config with additional model request fields for Bedrock.

Also applies to: 70-75

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

353-360: Still need defensive nil checks after Anthropic request conversion

You now propagate errors from anthropic.ToAnthropic*Request, which is good, but several sites still assume the returned struct is non‑nil:

  • ChatCompletionStream:

    reqBody, err := anthropic.ToAnthropicChatRequest(request)
    if err != nil {
        return nil, err
    }
    reqBody.Model = deployment           // potential nil deref
    reqBody.Stream = schemas.Ptr(true)
  • Responses / ResponsesStream have the same pattern with ToAnthropicResponsesRequest.

  • In non‑stream ChatCompletion, you guard the assignment:

    if reqBody != nil {
        reqBody.Model = deployment
    }

    but if reqBody is nil and err is nil, you’ll serialize a nil body to JSON, producing "null" as the HTTP payload.

For robustness (and to align with earlier review feedback), add a nil check after each conversion and return a clear error if it ever happens:

reqBody, err := anthropic.ToAnthropicChatRequest(request)
if err != nil {
    return nil, err
}
+ if reqBody == nil {
+     return nil, fmt.Errorf("anthropic chat request conversion returned nil")
+ }
reqBody.Model = deployment
reqBody.Stream = schemas.Ptr(true)

Apply the same pattern to the non‑stream chat and both Responses paths.

Also applies to: 452-458, 529-535, 627-633

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

296-299: Unify Reasoning enable/disable semantics with chat path

In ToAnthropicResponsesRequest:

if bifrostReq.Params.Reasoning != nil {
    if Reasoning.Effort != nil && *Effort != "none" {
        // require & validate MaxTokens, set Thinking enabled
    } else {
        // everything else (Effort==nil or "none") → Thinking.Type = "disabled"
    }
}

This treats Effort == nil as “disabled”, even if MaxTokens is set, whereas the chat converter previously treated “any non‑none effort or presence of max_tokens” as enabling thinking (with the same minimum‑tokens check). That means the same Reasoning payload may enable thinking on chat but disable it on responses.

Recommend extracting a small helper (e.g. normalizeReasoningToThinking(reasoning *ResponsesParametersReasoning) (*AnthropicThinking, error)) and using it in both chat and responses so that:

  • Reasoning is enabled when (Effort != nil && *Effort != "none") || MaxTokens != nil,
  • Reasoning is disabled only when Effort != nil && *Effort == "none" and MaxTokens == nil,
  • In the enabled case, MaxTokens is required and checked against MinimumReasoningMaxTokens.

That keeps validation and behavior identical across APIs.

Also applies to: 325-344, 402-403

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

452-472: Align reasoning_config map type and avoid overwriting it with extra request fields

Two problems here mirror those in convertChatParameters:

  1. Disabled config type mismatch

    Disabled reasoning is written as:

    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
        "type": "disabled",
    }

    while ToBifrostResponsesRequest above expects reasoning_config as map[string]interface{}. When we round‑trip a disabled config through Bedrock, the type assertion will fail and we’ll lose Params.Reasoning.

    Use map[string]any for both enabled and disabled cases:

  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
  • "type": "disabled",
    
  • }
  • bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
  • "type": "disabled",
    
  • }
    
    
  1. ExtraParams request fields overwrite reasoning config

    Later:

    if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
        if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
            bedrockReq.AdditionalModelRequestFields = orderedFields
        }
    }

    This replaces the whole map and drops the earlier reasoning_config. For callers who specify both reasoning and additional request fields, reasoning will be silently disabled.

    Merge instead of replace, similar to the suggestion for utils.go:

  • if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
  • bedrockReq.AdditionalModelRequestFields = orderedFields
    
  • }
  • if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
  • if bedrockReq.AdditionalModelRequestFields == nil {
    
  •     bedrockReq.AdditionalModelRequestFields = orderedFields
    
  • } else {
    
  •     for k, v := range orderedFields {
    
  •         bedrockReq.AdditionalModelRequestFields[k] = v
    
  •     }
    
  • }
    
  • }
    
    
    
    
    
    
    

Also applies to: 478-482

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

118-122: Critical: Dead code accessing nil Params - UNFIXED from previous review.

This else if block is unreachable and unsafe. The } on line 118 closes the if bifrostReq.Params != nil block from line 25, placing this else if in the branch where Params IS nil. Accessing bifrostReq.Params.Reasoning on line 118 would cause a panic.

Remove this dead code block:

-	} else if bifrostReq.Params.Reasoning != nil && bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-		anthropicReq.Thinking = &AnthropicThinking{
-			Type: "disabled",
-		}
 	}

If this logic is needed, move it inside the if bifrostReq.Params != nil block before line 117.


419-436: Minor: Redundant assignment and incorrect cache token mapping - UNFIXED from previous review.

Two issues:

  1. Duplicate assignment: Lines 425-427 and 430-435 both assign CacheCreationInputTokens to CompletionTokensDetails.CachedTokens. The conditional block (430-435) is redundant.

  2. Semantic mismatch: CacheCreationInputTokens represents tokens used to create the input cache (a prompt/input operation), not completion. It should be mapped to PromptTokensDetails, not CompletionTokensDetails.

Apply this fix:

 		bifrostResponse.Usage = &schemas.BifrostLLMUsage{
 			PromptTokens: response.Usage.InputTokens,
 			PromptTokensDetails: &schemas.ChatPromptTokensDetails{
 				CachedTokens: response.Usage.CacheReadInputTokens,
 			},
 			CompletionTokens: response.Usage.OutputTokens,
-			CompletionTokensDetails: &schemas.ChatCompletionTokensDetails{
-				CachedTokens: response.Usage.CacheCreationInputTokens,
-			},
 			TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
 		}
-		if response.Usage.CacheCreationInputTokens > 0 {
-			if bifrostResponse.Usage.CompletionTokensDetails == nil {
-				bifrostResponse.Usage.CompletionTokensDetails = &schemas.ChatCompletionTokensDetails{}
-			}
-			bifrostResponse.Usage.CompletionTokensDetails.CachedTokens = response.Usage.CacheCreationInputTokens
+		if response.Usage.CacheCreationInputTokens > 0 {
+			if bifrostResponse.Usage.PromptTokensDetails == nil {
+				bifrostResponse.Usage.PromptTokensDetails = &schemas.ChatPromptTokensDetails{}
+			}
+			bifrostResponse.Usage.PromptTokensDetails.CachedTokens += response.Usage.CacheCreationInputTokens
 		}
🧹 Nitpick comments (3)
core/providers/cohere/chat.go (1)

592-599: Minor: Trailing newline in reasoning text.

The reasoningText accumulation appends "\n" after each thinking block (line 598), which will leave a trailing newline in the final Reasoning field. Consider trimming:

 			} else if block.Type == CohereContentBlockTypeThinking && block.Thinking != nil {
 				reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{
 					Index: len(reasoningDetails),
 					Type:  schemas.BifrostReasoningDetailsTypeText,
 					Text:  block.Thinking,
 				})
 				reasoningText += *block.Thinking + "\n"
 			}

Then at line 663:

-		assistantMessage.Reasoning = schemas.Ptr(reasoningText)
+		assistantMessage.Reasoning = schemas.Ptr(strings.TrimSuffix(reasoningText, "\n"))

This would require adding "strings" to the imports.

core/schemas/chatcompletions.go (1)

222-226: Consider adding validation for MaxTokens minimum.

Based on the PR summary, there's a minimum value (1024) for reasoning MaxTokens. Consider adding validation here or in a separate validator function to ensure requests don't specify values below the minimum.

Example:

 type ChatReasoning struct {
 	Effort    *string `json:"effort,omitempty"`     // "none" |  "minimal" | "low" | "medium" | "high" (any value other than "none" will enable reasoning)
 	MaxTokens *int    `json:"max_tokens,omitempty"` // Maximum number of tokens to generate for the reasoning output (required for anthropic)
 }
+
+// Validate checks if MaxTokens meets the minimum requirement
+func (r *ChatReasoning) Validate() error {
+	if r.MaxTokens != nil && *r.MaxTokens < 1024 {
+		return fmt.Errorf("reasoning max_tokens must be at least 1024, got %d", *r.MaxTokens)
+	}
+	return nil
+}

Based on learnings, validation might be provider-specific and could be enforced in provider adapters instead.

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

103-112: Reasoning aggregation semantics: trailing newline and single string

The reasoning handling correctly captures both plain text and signatures into ReasoningDetails and a single aggregated Reasoning string. Note that you always append "\\n", so assistantMessage.Reasoning will end with a trailing newline; if that’s undesirable in clients, consider strings.TrimRight(reasoningText, "\n") before assigning.

Also applies to: 135-141

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0822b25 and 05daf39.

📒 Files selected for processing (29)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (10 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (4 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (3 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/semanticcache/utils.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/providers/anthropic/errors.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/bedrock/bedrock.go
  • core/providers/anthropic/anthropic.go
  • core/schemas/mux.go
  • core/providers/bedrock/types.go
  • core/providers/anthropic/types.go
  • ui/lib/types/logs.ts
  • core/providers/perplexity/chat.go
  • core/schemas/responses.go
  • plugins/semanticcache/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:

  • transports/bifrost-http/handlers/inference.go
  • core/providers/openai/chat.go
  • plugins/jsonparser/utils.go
  • core/providers/cohere/types.go
  • core/providers/cohere/cohere.go
  • core/providers/bedrock/chat.go
  • core/providers/openai/types.go
  • core/providers/bedrock/responses.go
  • core/providers/openai/openai.go
  • core/providers/openai/utils.go
  • framework/streaming/chat.go
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/chat.go
  • core/providers/cohere/chat.go
  • core/providers/azure/azure.go
  • core/schemas/chatcompletions.go
🧬 Code graph analysis (11)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/cohere/cohere.go (2)
core/providers/cohere/chat.go (1)
  • ToCohereChatCompletionRequest (12-199)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/types.go (1)
core/schemas/chatcompletions.go (6)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • ChatReasoning (223-226)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (4)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/openai.go (3)
plugins/mocker/main.go (1)
  • Usage (138-142)
core/providers/perplexity/types.go (1)
  • Usage (69-78)
ui/lib/types/logs.ts (1)
  • CompletionTokensDetails (197-202)
framework/streaming/chat.go (3)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/anthropic/responses.go (3)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (3)
  • AnthropicMessageRequest (40-59)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
core/providers/vertex/vertex.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-282)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/anthropic/chat.go (4)
core/schemas/chatcompletions.go (13)
  • BifrostChatRequest (12-19)
  • ChatAssistantMessage (626-632)
  • ChatAssistantMessageToolCall (686-692)
  • ChatContentBlock (590-597)
  • ChatReasoningDetails (723-730)
  • ChatContentBlockTypeText (582-582)
  • ChatToolTypeFunction (239-239)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatMessageContent (524-527)
  • ChatMessage (469-478)
  • ChatNonStreamResponseChoice (745-748)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/providers/anthropic/types.go (7)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicContentBlock (147-159)
  • AnthropicContentBlockTypeThinking (143-143)
  • AnthropicContentBlockTypeText (135-135)
  • AnthropicContentBlockTypeToolUse (137-137)
core/schemas/bifrost.go (1)
  • Anthropic (37-37)
core/providers/anthropic/utils.go (1)
  • ConvertAnthropicFinishReasonToBifrost (27-32)
core/providers/cohere/chat.go (3)
core/schemas/chatcompletions.go (14)
  • BifrostChatRequest (12-19)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatNonStreamResponseChoice (745-748)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
  • ChatReasoningDetails (723-730)
  • ChatContentBlock (590-597)
  • ChatAssistantMessageToolCall (686-692)
  • ChatInputImage (600-603)
  • ChatMessageContent (524-527)
  • ChatAssistantMessageToolCallFunction (695-698)
  • ChatAssistantMessage (626-632)
  • ChatMessageRole (457-457)
core/providers/cohere/types.go (5)
  • CohereChatRequest (14-31)
  • CohereThinking (170-173)
  • ThinkingTypeDisabled (180-180)
  • ThinkingTypeEnabled (179-179)
  • CohereMessage (50-56)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
⏰ 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 (30)
core/providers/cohere/types.go (1)

521-524: LGTM!

The Thinking field addition to CohereStreamContent is well-structured with appropriate JSON tagging and aligns with the existing Thinking field in CohereContentBlock (line 152). This enables proper propagation of reasoning content in streaming responses.

core/providers/cohere/cohere.go (2)

279-286: LGTM!

The error propagation from ToCohereChatCompletionRequest is properly handled. The lambda correctly returns the error to CheckContextAndGetRequestBody which will handle it appropriately.


331-338: LGTM!

Good defensive pattern - checking for errors before setting Stream = true. This ensures the streaming flag is only set on a valid request object, and errors are propagated early.

core/providers/cohere/chat.go (5)

242-256: LGTM!

The reverse mapping from CohereThinking to Bifrost ChatReasoning is reasonable. Using Effort="auto" as a default for enabled thinking is appropriate since Cohere's ThinkingTypeEnabled doesn't carry effort granularity.


319-346: LGTM!

Clean refactoring to use the centralized ToBifrostChatMessage() method for response conversion. This improves maintainability by consolidating message conversion logic.


401-447: LGTM!

Good handling of both text and thinking deltas in streaming responses. The thinking content is properly mapped to both Reasoning and ReasoningDetails fields for compatibility.

Note: The Index: 0 is hardcoded in ReasoningDetails, which is acceptable for single thinking blocks per chunk. If multiple thinking blocks could arrive in a single delta, this would need adjustment.


616-648: LGTM!

Robust tool call conversion with proper nil checks:

  • Skipping tool calls with nil Function (lines 619-622)
  • Defaulting to empty string for nil function name (lines 630-633)
  • Safe direct access to Arguments since it's a string, not pointer

This defensive approach prevents potential nil pointer dereferences.


556-571: LGTM!

The ToBifrostChatMessage method centralizes per-message conversion logic cleanly. The content type detection (string vs blocks, single text block optimization) is well-structured.

plugins/jsonparser/utils.go (1)

304-323: LGTM! Field rename aligns with the broader refactoring.

The update from Thought to Reasoning is consistent with the PR's goal of renaming reasoning-related fields across the codebase.

framework/streaming/chat.go (2)

44-105: LGTM! Reasoning accumulation logic is well-structured.

The implementation correctly handles streaming deltas for both the Reasoning field and ReasoningDetails:

  • Reasoning text is accumulated (concatenated) across chunks
  • ReasoningDetails are merged by Index, with text/summary/data accumulated and signature/type overwritten (appropriate for final values)

The complexity is justified given the rich reasoning data model introduced in this PR.


38-43: Good consistency improvement with the Ptr helper.

Using bifrost.Ptr for the initial Refusal assignment aligns with the pattern used for Reasoning and other pointer fields in the codebase.

core/schemas/chatcompletions.go (3)

186-220: Well-designed backward compatibility for deprecated reasoning_effort.

The custom UnmarshalJSON correctly:

  • Rejects requests with both reasoning_effort and reasoning (mutual exclusivity)
  • Maps legacy reasoning_effort to the new nested Reasoning.Effort structure
  • Maintains forward compatibility with the new reasoning object

634-667: Verify the reasoning details synthesis behavior is intentional.

The UnmarshalJSON implementation synthesizes a reasoning_details entry of type "reasoning.text" whenever Reasoning is present but ReasoningDetails is empty.

This implicit transformation may surprise API consumers who expect the response shape to match their input. Consider documenting this behavior or confirming it aligns with the API contract.

If this synthesis is provider-specific (e.g., for providers that return only reasoning text), consider moving the logic closer to those provider adapters rather than in the core schema unmarshaller.


765-794: Consistent synthesis logic in streaming delta.

The same reasoning-to-reasoning_details synthesis logic is applied here for streaming deltas. The same documentation concern from the ChatAssistantMessage unmarshaller applies: ensure this behavior is documented and intentional.

transports/bifrost-http/handlers/inference.go (2)

169-199: Custom unmarshaller correctly handles embedded struct conflicts.

The custom UnmarshalJSON for ChatRequest is necessary because ChatParameters has its own custom unmarshaller that would interfere with sonic's handling of the embedded BifrostParams. The three-step approach (unmarshal BifrostParams, then Messages, then ChatParameters) correctly assembles all fields.


476-478: Good defensive initialization before unmarshalling.

Initializing ChatParameters to a non-nil value before calling sonic.Unmarshal ensures the custom UnmarshalJSON method on ChatParameters can safely populate fields without nil pointer issues.

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

515-520: LGTM! Token usage details correctly propagated in streaming.

The implementation mirrors the existing pattern for basic token counts and correctly propagates the detailed token breakdowns (including reasoning tokens) before clearing response.Usage.


981-986: LGTM! Token usage details correctly propagated in streaming.

Consistent with the text completion streaming implementation, this correctly propagates token usage details including reasoning tokens.

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

63-65: LGTM! Correctly updated for nested Reasoning structure.

The nil checks for request.ChatParameters.Reasoning and request.ChatParameters.Reasoning.Effort appropriately handle the new nested structure, and the logic correctly maps "minimal" to "low" for non-OpenAI providers.


14-14: Good use of centralized conversion utilities.

Delegating to ConvertOpenAIMessagesToBifrostMessages and ConvertBifrostMessagesToOpenAIMessages reduces duplication and ensures consistent message mapping across OpenAI-compatible providers.

Also applies to: 28-28

core/providers/vertex/vertex.go (1)

283-286: LGTM! Proper error propagation from Anthropic converters.

The updated calls to ToAnthropicChatRequest and ToAnthropicResponsesRequest now correctly propagate conversion errors (e.g., validation failures for reasoning parameters) instead of silently ignoring them. This improves error visibility for misconfigured requests.

Also applies to: 518-520, 688-690, 845-847

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

37-40: Error propagation from convertChatParameters looks correct

Wiring convertChatParameters to return an error and propagating it here makes Bedrock chat failures explicit and keeps the call pattern consistent with other providers.

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

349-365: Reasoning extraction from reasoning_config looks structurally sound

Mapping reasoning_configResponsesParameters.Reasoning with:

  • "type": "enabled"Effort: "auto" and MaxTokens from budget_tokens,
  • other types → Effort: "none",

is a reasonable default bridge from Bedrock’s config into the unified Responses schema. Behavior if budget_tokens is missing/invalid is simply “no Reasoning”, which is acceptable as a conservative fallback.


656-660: Output conversion moved up; avoids duplicate work

Converting response.Output.Message to bifrostResp.Output once at the top of ToBifrostResponsesResponse simplifies the function and prevents accidental double-conversion. No issues here.

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

190-201: Thinking → Responses Reasoning bridge looks good

Populating Params.Reasoning from request.Thinking (enabled → Effort: "auto", MaxTokens: BudgetTokens, else → Effort: "none") gives callers a unified view of Anthropic thinking in the Responses schema, while still preserving the raw Thinking object in ExtraParams["thinking"]. This is a sensible mapping.

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

56-72: LGTM: Well-documented embedded pointer pattern.

The embedded pointer structs pattern with the warning comment appropriately documents the constraint that only one should be non-nil at a time. The structure correctly supports reasoning in assistant message responses.


74-101: LGTM: Correct implementation of backward-compatible marshalling.

The custom marshalling correctly:

  • Uses the alias pattern to prevent recursion
  • Shadows the Reasoning field to omit it from JSON output
  • Maps Reasoning.Effort to reasoning_effort for backward compatibility with the deprecated field

This is a sound approach for deprecating reasoning_effort while maintaining API compatibility.

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

13-16: LGTM: Appropriate error handling added.

The signature change to return an error enables proper validation and error propagation, which is essential for the reasoning parameter validation introduced later.


211-220: LGTM: Proper reasoning details conversion.

The conversion correctly:

  • Checks for nil before accessing nested fields
  • Maps Bifrost reasoning details to Anthropic thinking blocks
  • Preserves signature information for verification

647-687: LGTM: Comprehensive streaming reasoning support.

The streaming delta handling correctly:

  • Maps thinking deltas to both Reasoning and ReasoningDetails fields
  • Handles signature deltas separately with proper type and index
  • Maintains consistency with the non-streaming conversion logic

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 8e37f9a to 9e093fc Compare December 8, 2025 11:54
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 05daf39 to 6c16eed Compare December 8, 2025 11:54
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 (2)
core/providers/cohere/chat.go (1)

126-137: ExtraParams thinking still silently overrides Reasoning-based thinking.

Despite the past comment being marked as addressed, the code still unconditionally overwrites cohereReq.Thinking from ExtraParams["thinking"] even if it was already set from bifrostReq.Params.Reasoning (lines 104-121). This means if both are provided, ExtraParams wins silently.

Consider skipping the ExtraParams extraction if Reasoning was already processed:

 		// Convert extra params
 		if bifrostReq.Params.ExtraParams != nil {
 			// Handle thinking parameter
-			if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
+			if cohereReq.Thinking == nil {
+				if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
 				if thinkingMap, ok := thinkingParam.(map[string]interface{}); ok {
 					thinking := &CohereThinking{}
 					if typeStr, ok := schemas.SafeExtractString(thinkingMap["type"]); ok {
 						thinking.Type = CohereThinkingType(typeStr)
 					}
 					if tokenBudget, ok := schemas.SafeExtractIntPointer(thinkingMap["token_budget"]); ok {
 						thinking.TokenBudget = tokenBudget
 					}
 					cohereReq.Thinking = thinking
 				}
+				}
 			}
core/schemas/chatcompletions.go (1)

155-185: The reasoning_effortReasoning mapping will break existing callers that only set reasoning_effort without max_tokens.

The ChatParameters unmarshaller cleanly enforces mutual exclusivity between reasoning and reasoning_effort and converts reasoning_effort into ChatReasoning{Effort: ...}. However, ToAnthropicChatRequest requires that when Reasoning is non-nil, MaxTokens must also be set (error: "reasoning.max_tokens is required for reasoning"). For legacy clients that previously sent only reasoning_effort without reasoning.max_tokens, requests that used to work will now fail at this validation.

If you intend to retain runtime compatibility for reasoning_effort, consider one of:

  • injecting a default MaxTokens when Reasoning.Effort != nil and Reasoning.MaxTokens == nil (minimum is 1024), or
  • relaxing the validation to allow effort-only configs (with a provider-chosen default budget),

and documenting the new behavior clearly if you do intend this to be a hard breaking change.

♻️ Duplicate comments (6)
core/providers/bedrock/responses.go (2)

478-482: Same overwriting issue as in utils.go.

Line 480 directly assigns orderedFields to AdditionalModelRequestFields, which will discard the reasoning_config set at lines 452-472. This is the same issue flagged in bedrock/utils.go. Apply the same merge strategy here.


452-472: Type consistency issue with disabled reasoning config.

At line 457, disabled reasoning is written as map[string]string{"type": "disabled"}:

bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
	"type": "disabled",
}

However, the extraction logic at lines 351-352 in ToBifrostResponsesRequest expects map[string]interface{}. This type mismatch will cause the type assertion to fail, preventing disabled reasoning from being properly round-tripped.

Use map[string]any for consistency:

-				bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+				bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
 					"type": "disabled",
 				}
core/providers/bedrock/utils.go (1)

26-47: Reasoning config may be overwritten by additionalModelRequestFieldPaths.

The reasoning_config is correctly set in AdditionalModelRequestFields at lines 29-46. However, at line 73, if additionalModelRequestFieldPaths exists in ExtraParams, the entire AdditionalModelRequestFields map is replaced:

bedrockReq.AdditionalModelRequestFields = orderedFields // Line 73 - overwrites!

This silently discards the reasoning_config set earlier. Merge instead of replacing:

 			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
 				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-					bedrockReq.AdditionalModelRequestFields = orderedFields
+					if bedrockReq.AdditionalModelRequestFields == nil {
+						bedrockReq.AdditionalModelRequestFields = orderedFields
+					} else {
+						for k, v := range orderedFields {
+							bedrockReq.AdditionalModelRequestFields[k] = v
+						}
+					}
 				}
 			}

Also applies to: 70-75

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

629-634: Same nil safety concern in ResponsesStream path.

Similar to the Responses path, lines 633-634 access reqBody.Model and reqBody.Stream without checking if reqBody is nil after error propagation.

Apply the same defensive check:

 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
+				if reqBody != nil {
 					reqBody.Model = deployment
 					reqBody.Stream = schemas.Ptr(true)
+				}
 				return reqBody, nil
core/providers/anthropic/responses.go (1)

190-201: Reasoning enabled/disabled semantics differ from chat path and ignore MaxTokens-only configs.

In ToBifrostResponsesRequest and ToAnthropicResponsesRequest, reasoning is only considered “enabled” when Reasoning.Effort != nil && *Effort != "none". If Effort is nil but MaxTokens is set, you currently treat this as disabled and set Thinking.Type = "disabled", whereas ToAnthropicChatRequest treats Effort == nil with non‑nil MaxTokens as enabled reasoning and enforces the token budget. This means the same reasoning payload can behave differently depending on whether callers use Chat vs Responses, and MaxTokens-only configurations are silently discarded on the Responses path.

Consider unifying the rule to something like:

  • Enabled if (Effort != nil && *Effort != "none") || MaxTokens != nil,
  • Disabled only if Effort != nil && *Effort == "none" and MaxTokens == nil,
  • In the enabled case, always require and validate MaxTokens >= MinimumReasoningMaxTokens,

and factoring this into a shared helper used by both chat and responses converters to avoid drift.

Also applies to: 325-344

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

13-16: Chat reasoning validation is strict and currently diverges from responses path and reasoning_effort behavior.

ToAnthropicChatRequest now:

  • rejects any request with Params.Reasoning != nil, Effort != "none", and MaxTokens == nil, and
  • enforces MaxTokens >= MinimumReasoningMaxTokens when reasoning is enabled.

That part is good and matches Anthropic’s expectations. However:

  • For Chat, Effort == nil + non‑nil MaxTokens is treated as “enabled reasoning with default effort”, while in ToAnthropicResponsesRequest the same shape is treated as disabled (see comment on responses.go). This is a subtle semantic mismatch between chat and responses.
  • Combined with ChatParameters.UnmarshalJSON, any legacy reasoning_effort usage that doesn’t also specify max_tokens will now produce an error instead of silently mapping to a default budget.

If that’s intentional, documenting it as a hard breaking change would be useful. If not, consider aligning with the shared helper suggested in responses.go and/or giving reasoning_effort a default MaxTokens to preserve prior behavior.

Also applies to: 24-41, 99-117

🧹 Nitpick comments (7)
core/providers/cohere/chat.go (1)

7-7: Cross-package constant dependency.

Using anthropic.MinimumReasoningMaxTokens couples Cohere's validation to Anthropic's package. If reasoning token minimums differ across providers in the future, consider moving this constant to a shared location (e.g., schemas package) or defining a Cohere-specific constant.

transports/bifrost-http/handlers/inference.go (1)

476-478: Optional: Remove redundant initialization.

The ChatParameters field is pre-initialized here, but the custom UnmarshalJSON method (lines 191-193) already handles the nil case and initializes it if needed. While harmless, this redundancy could be removed for cleaner code.

Apply this diff if you prefer to remove the redundancy:

-	req := ChatRequest{
-		ChatParameters: &schemas.ChatParameters{},
-	}
+	var req ChatRequest
core/providers/bedrock/utils.go (1)

14-17: Consider initializing defaults instead of erroring on nil params.

Returning an error when bifrostReq.Params is nil is inconsistent with other providers. For example, Gemini initializes default ChatParameters with an empty ExtraParams map, and Vertex/Perplexity process params conditionally without erroring.

Following the Gemini pattern would improve consistency:

 func convertChatParameters(bifrostReq *schemas.BifrostChatRequest, bedrockReq *BedrockConverseRequest) error {
 	if bifrostReq.Params == nil {
-		return fmt.Errorf("bifrost request parameters are nil")
+		bifrostReq.Params = &schemas.ChatParameters{
+			ExtraParams: make(map[string]interface{}),
+		}
 	}

This approach reserves errors for truly required nested fields (like reasoning.max_tokens when reasoning is enabled).

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

827-839: Signature streaming works but uses brittle heuristics and has a dead enrichment block.

  • The AnthropicStreamDeltaTypeSignature branch stores signatures in state.ReasoningSignatures and reuses ReasoningSummaryTextDelta for both signatures and thinking, then ToAnthropicResponsesStreamResponse guesses whether a delta is a signature by length and base64‑ish characters. That’s workable but brittle; if you ever see short signatures or long/base64‑like reasoning text, it will misclassify. A dedicated stream response type for signatures (or an explicit flag) would be more robust long‑term.
  • At content_block_stop, the block that searches responses for a reasoning item and assigns EncryptedContent (reasoningItem := ...) has no effect when there are no existing reasoning items in responses (which is the common case for this branch). The actual signature propagation happens a few lines later when you attach EncryptedContent to doneItem. That earlier block is effectively dead code and can be removed or wired to emit an explicit reasoning item event if you really want a separate message for signatures.

These are behavioral nits rather than blockers, but cleaning them up would reduce surprise and make the signature path easier to reason about.

Also applies to: 969-989, 1001-1006, 1272-1293, 3057-3071

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

207-216: ReasoningDetails → thinking blocks mapping is lossy but acceptable for now.

When building the Anthropic request, you prepend one thinking block per ChatReasoningDetails, preserving Signature and Text but ignoring other fields (e.g., summary vs text, data). That’s sufficient for Anthropic’s current API, which only accepts raw thinking text + optional signature, but it means callers can’t distinguish between different reasoning detail types once mapped.

If you later rely on reasoning.summary or encrypted data distinctions, you may want to either encode those into the thinking text or extend AnthropicThinking/extra params to carry richer metadata.


472-483: Anthropic ← Bifrost reasoning round-trip only considers text-type details.

In ToAnthropicChatResponse, you rebuild thinking blocks only from ChatAssistantMessage.ReasoningDetails entries with Type == BifrostReasoningDetailsTypeText. Any future reasoning.summary or reasoning.encrypted entries will be ignored here, even though the type system now allows them.

If you plan to support richer reasoning detail types, consider:

  • either treating all non‑nil Text fields the same regardless of Type, or
  • adding a mapping from reasoning.summary / reasoning.encrypted into Anthropic content (e.g., one block per type) and using Signature to distinguish encrypted content.

Also applies to: 503-521


626-645: Streaming reasoning/signature deltas are wired through, but use reasoning.text for signatures.

  • For AnthropicStreamDeltaTypeThinking, you now emit both delta.Reasoning and a single ChatReasoningDetails{Type: reasoning.text, Text: ...}, which is a nice normalized shape.
  • For AnthropicStreamDeltaTypeSignature, you emit a ChatReasoningDetails with Signature set but Type still reasoning.text. Given the ChatReasoningDetailsType enum includes reasoning.encrypted, using that for signature‑only deltas would better match the public type contract and make it easier for UIs to distinguish encrypted/signature payloads from plain text reasoning.

Switching the signature branch to Type: BifrostReasoningDetailsTypeEncrypted (or similar) would be a low‑risk improvement.

Also applies to: 655-677

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05daf39 and 6c16eed.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (10 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • core/providers/openai/openai.go
  • ui/lib/types/logs.ts
  • framework/streaming/chat.go
  • core/providers/cohere/cohere.go
  • core/providers/anthropic/anthropic.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/perplexity/chat.go
  • core/providers/bedrock/bedrock.go
  • plugins/semanticcache/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/errors.go
  • transports/bifrost-http/handlers/inference.go
  • core/providers/bedrock/chat.go
  • core/providers/vertex/vertex.go
  • plugins/maxim/main.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/types.go
  • plugins/jsonparser/utils.go
  • core/schemas/mux.go
  • core/providers/openai/chat.go
  • core/schemas/responses.go
  • core/providers/bedrock/utils.go
  • core/providers/openai/types.go
  • core/providers/anthropic/responses.go
  • core/providers/openai/utils.go
  • core/providers/cohere/chat.go
  • core/providers/azure/azure.go
  • core/providers/cohere/types.go
  • core/providers/anthropic/chat.go
  • core/providers/bedrock/types.go
  • core/schemas/chatcompletions.go
🧠 Learnings (1)
📚 Learning: 2025-12-08T11:50:35.989Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/openai/utils.go:26-45
Timestamp: 2025-12-08T11:50:35.989Z
Learning: OpenAI does not support the `ReasoningDetails` field. `ReasoningDetails` is a Bifrost-internal field and should not be included when converting messages to OpenAI format in the `ConvertBifrostMessagesToOpenAIMessages` function in `core/providers/openai/utils.go`.

Applied to files:

  • core/providers/openai/utils.go
🧬 Code graph analysis (14)
core/providers/anthropic/errors.go (1)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/bedrock/chat.go (2)
core/schemas/chatcompletions.go (8)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • BifrostChatResponse (26-41)
  • BifrostResponseChoice (704-712)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/vertex/vertex.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (3)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/schemas/mux.go (3)
core/schemas/responses.go (2)
  • ResponsesParametersReasoning (232-237)
  • BifrostResponsesStreamResponse (1424-1462)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (44-57)
  • BedrockError (384-388)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/types.go (1)
core/schemas/chatcompletions.go (6)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • ChatReasoning (223-226)
core/providers/anthropic/responses.go (4)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (232-237)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (2)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
core/providers/openai/utils.go (2)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
core/providers/cohere/chat.go (3)
core/schemas/chatcompletions.go (10)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatReasoningDetails (723-730)
  • ChatContentBlock (590-597)
  • ChatContentBlockTypeText (582-582)
  • ChatContentBlockTypeImage (583-583)
  • ChatInputImage (600-603)
  • ChatMessageContent (524-527)
  • ChatAssistantMessage (626-632)
  • ChatMessageRole (457-457)
core/providers/cohere/types.go (5)
  • CohereChatRequest (14-31)
  • CohereThinking (170-173)
  • ThinkingTypeDisabled (180-180)
  • ThinkingTypeEnabled (179-179)
  • CohereMessage (50-56)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/schemas/chatcompletions.go (1)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
⏰ 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 (41)
core/providers/cohere/types.go (1)

520-524: LGTM! Thinking field addition for streaming content.

The Thinking field addition aligns with the reasoning support being added across providers and is consistent with the existing CohereContentBlock struct which also has a Thinking field (line 152).

core/providers/cohere/chat.go (6)

104-121: Reasoning validation logic looks correct.

The validation properly requires MaxTokens when reasoning is enabled and enforces the minimum token budget. The early error returns provide clear feedback.

One consideration: if Effort is set to values other than "none" (e.g., "low", "medium", "high"), all are treated as enabled. Verify this aligns with the unified Bifrost schema for reasoning effort levels.


242-256: LGTM! Reasoning conversion from Cohere to Bifrost format.

The round-trip conversion properly maps ThinkingTypeDisabled to Effort: "none" and enabled thinking to Effort: "auto" with the token budget preserved.


423-446: Streaming thinking delta: hardcoded Index: 0 may lose ordering.

For streaming reasoning content, Index is always set to 0 in each chunk. If the client accumulates ReasoningDetails across chunks, this could cause ordering issues or overwrites. Consider tracking and incrementing the index across streaming events if ordering matters for the downstream consumer.

If the client is expected to append chunks without using the index for positioning, this is acceptable.


616-648: LGTM! Tool call conversion with proper nil safety.

Good defensive handling of nil Function (skip) and nil Name (default to empty string). The index calculation using len(toolCalls) correctly assigns sequential indices.


214-221: Potential nil dereference if req.Messages contains nil elements.

Line 218 dereferences message.ToBifrostChatMessage() which returns nil if the receiver is nil. If req.Messages ever contains nil CohereMessage pointers, this would panic.

Verify that req.Messages cannot contain nil elements, or add a guard:

 	if req.Messages != nil {
 		bifrostMessages := make([]schemas.ChatMessage, len(req.Messages))
 		for i, message := range req.Messages {
+			if msg := message.ToBifrostChatMessage(); msg != nil {
-			bifrostMessages[i] = *message.ToBifrostChatMessage()
+				bifrostMessages[i] = *msg
+			}
 		}
 		bifrostReq.Input = bifrostMessages
 	}

Note: req.Messages is []CohereMessage (not []*CohereMessage), so elements are values, not pointers. The receiver in ToBifrostChatMessage is *CohereMessage, so Go takes the address of each value. This should be safe since value types can't be nil.


342-346: LGTM! Response message conversion.

Clean integration of the ToBifrostChatMessage method for converting the response message.

transports/bifrost-http/handlers/inference.go (3)

77-77: LGTM!

The field name change from "reasoning_effort" to "reasoning" correctly aligns with the new unified reasoning support structure introduced in this PR.


169-199: Well-structured custom unmarshaller.

The custom UnmarshalJSON implementation correctly handles the interaction between embedded BifrostParams and ChatParameters with its own custom unmarshaller. The three-step approach (BifrostParams → Messages → ChatParameters) is sound and properly avoids recursion using the type alias pattern.


270-280: This concern is unfounded. Sonic (github.com/bytedance/sonic) is explicitly designed as a drop-in replacement for encoding/json and fully supports all standard library types, including json.RawMessage. Using sonic.Unmarshal with json.RawMessage is the standard pattern throughout this codebase for deferred JSON parsing—there are no compatibility issues or potential bugs from this approach.

Likely an incorrect or invalid review comment.

core/schemas/responses.go (2)

232-237: LGTM! Schema additions for reasoning support are well-documented.

The MaxTokens field addition and updated comment on Effort provide clear guidance for consumers. The comment noting the Anthropic requirement is helpful for cross-provider compatibility.


323-324: Helpful clarification comment.

The inline comment explaining behavioral differences between gpt-oss models and other OpenAI models regarding reasoning content blocks is useful for understanding model-specific behavior.

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

9-37: Well-implemented error conversion with proper nil safety.

The function correctly:

  • Guards against nil bifrostErr input
  • Safely extracts optional Type field
  • Safely extracts Message from nested error (addressing the past review comment)
  • Returns properly structured AnthropicMessageError with Type: "error"
core/providers/openai/chat.go (2)

14-14: Good refactor to use dedicated message conversion helpers.

Using ConvertOpenAIMessagesToBifrostMessages ensures consistent message transformation with proper handling of assistant message fields like Reasoning, Refusal, and ToolCalls.


62-65: Correctly updated to use nested Reasoning structure.

The filter now properly navigates the nested Reasoning.Effort field with appropriate nil checks, and the conversion from "minimal" to "low" aligns with the provider compatibility requirements.

core/providers/vertex/vertex.go (4)

283-286: Good error propagation from Anthropic converter.

The error handling pattern correctly captures and surfaces conversion errors early, preventing invalid request bodies from being sent to Vertex endpoints.


518-525: Consistent error handling in streaming path.

The streaming path now properly handles errors from ToAnthropicChatRequest. Note the nil check on line 522 (if reqBody != nil) provides additional safety before accessing fields.


689-693: Error propagation added for Responses API path.

Errors from ToAnthropicResponsesRequest are now properly captured and returned, maintaining consistency with the chat completion paths.


846-849: Error propagation added for ResponsesStream path.

Completes the error handling improvements across all Vertex Anthropic integration points.

plugins/jsonparser/utils.go (1)

309-314: Field rename from Thought to Reasoning aligns with schema changes.

The shallow copy is appropriate here since the Reasoning field is not modified by the JSON parser plugin.

core/schemas/mux.go (3)

773-778: Reasoning mapping now preserves MaxTokens in Chat→Responses conversion.

The condition (bcr.Params.Reasoning.Effort != nil || bcr.Params.Reasoning.MaxTokens != nil) ensures that reasoning configuration is mapped when either field is set, addressing the previous review concern about MaxTokens being dropped.


852-858: Complete Reasoning mapping in Responses→Chat conversion.

Both Effort and MaxTokens are now properly propagated from ResponsesParametersReasoning to ChatReasoning, ensuring round-trip consistency.


1361-1372: Streaming delta correctly uses Reasoning field.

The streaming conversion now references delta.Reasoning for reasoning content, aligning with the renamed field structure.

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

12-15: Constant addition for reasoning validation is correct.

The MinimumReasoningMaxTokens = 1024 value aligns with Anthropic's officially documented minimum thinking budget requirement. The constant is properly used for validation across Anthropic, Bedrock, and Cohere provider implementations to ensure reasoning.max_tokens meets the minimum threshold.

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

5-45: LGTM! Conversion utilities correctly omit ReasoningDetails.

The conversion functions correctly handle the transformation between OpenAI and Bifrost message formats. Based on learnings, OpenAI does not support the ReasoningDetails field, so it's properly omitted from both conversion directions while preserving the basic Reasoning text field that OpenAI does support.

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

38-40: Good error propagation from parameter conversion.

The function now correctly propagates errors from convertChatParameters instead of silently ignoring failures, improving error visibility for callers.


104-141: Reasoning content processing integrated correctly.

The code properly collects ReasoningContent blocks, accumulates reasoning text, and attaches both ReasoningDetails and Reasoning to the assistant message when present. The consolidation logic at lines 117-120 correctly simplifies single text blocks.


293-346: Streaming reasoning delta handling is correct.

The early return at lines 298-300 when both Text and Signature are empty prevents the nil pointer issue flagged in previous reviews. The conditional construction at lines 303-344 ensures responses are only created when meaningful data exists.

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

349-367: Reasoning extraction from Bedrock request is correctly implemented.

The code properly extracts reasoning_config from AdditionalModelRequestFields and populates Params.Reasoning with the appropriate effort and max tokens based on the configuration type.


656-660: Output conversion is correctly implemented.

The code properly converts response.Output to bifrostResp.Output at the appropriate location in the function.

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

103-105: LGTM! Reasoning content types added correctly.

The new ReasoningContent field in BedrockContentBlock and the associated BedrockReasoningContent and BedrockReasoningContentText types are well-structured and align with the reasoning support being added across the provider layer.

Also applies to: 152-159


438-441: Delta structure correctly updated for streaming reasoning.

The BedrockContentBlockDelta now includes Text, ReasoningContent, and ToolUse fields, properly supporting streaming of reasoning content deltas alongside existing text and tool use deltas.

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

42-43: LGTM! New message types enable provider-specific handling.

The change from []schemas.ChatMessage to []OpenAIMessage and the introduction of OpenAIChatAssistantMessage provide the necessary structure for handling OpenAI-specific message fields. The embedded pointer pattern correctly flattens assistant message fields into the top-level JSON object.

Also applies to: 56-72


74-101: Custom marshalling correctly transforms reasoning for OpenAI compatibility.

The MarshalJSON implementation cleverly shadows the embedded Reasoning field (line 88) to prevent its emission, while extracting and emitting reasoning_effort (lines 96-98) from Reasoning.Effort. This pattern correctly transforms the internal Bifrost reasoning structure to OpenAI's expected reasoning_effort field without modifying the in-memory representation.

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

531-536: The code is correct as written and does not require an additional nil check.

ToAnthropicResponsesRequest follows Go's standard error-handling pattern: it either returns (nil, error) on all error paths (lines 298, 334, 342 in responses.go) or (*AnthropicMessageRequest, nil) on success (line 407 in responses.go). After the if err != nil check at lines 532-533, reqBody is guaranteed to be non-nil, making the access at line 535 safe. This pattern is correct and idiomatic Go; the comparison to the ChatCompletion path is not applicable, as openai.ToOpenAIResponsesRequest returns only a pointer without an error value, requiring a nil check there.

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

1428-1439: Nil-safe extraction for streaming errors looks good.

Guarding bifrostErr.Error before reading Message prevents nil dereferences in ToAnthropicResponsesStreamError while still emitting a sensible SSE error payload.

core/schemas/chatcompletions.go (2)

468-478: Custom unmarshalling for chat messages and reasoning details is sound and preserves invariants.

  • ChatMessage.UnmarshalJSON first decodes base fields, then ChatToolMessage, then ChatAssistantMessage, only attaching the assistant struct when it actually carries data; this avoids the previous interference from the assistant’s custom unmarshaller while keeping the embedded‑struct flattening behavior.
  • ChatAssistantMessage.UnmarshalJSON and ChatStreamResponseChoiceDelta.UnmarshalJSON both synthesize a single ChatReasoningDetails{Type: reasoning.text} entry when reasoning is present but reasoning_details is empty, which gives downstream code a uniform representation without overriding explicit reasoning_details from providers.

Overall this looks correct and matches the UI ReasoningDetails shape.

Also applies to: 480-521, 625-667, 755-794


714-731: Reasoning details and image_tokens wiring look consistent with the new schema.

The new BifrostReasoningDetailsType constants and ChatReasoningDetails struct mirror the UI types and give you space for summary/encrypted variants, while the streaming delta struct now exposes both reasoning and reasoning_details for incremental updates. Adding ImageTokens as an optional field on ChatCompletionTokensDetails is consistent with how other optional token counters (citations, search queries) are modeled.

Also applies to: 755-763, 829-836

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

304-356: Non-stream response aggregation captures text, tools, reasoning, and usage correctly.

ToBifrostChatResponse’s aggregation logic:

  • correctly collapses a single text block into contentStr while using ChatContentBlocks for mixed content,
  • converts tool_use blocks into ChatAssistantMessageToolCalls with JSON‑marshaled arguments, and
  • maps thinking blocks into ChatReasoningDetails plus a concatenated Reasoning text, while wiring Anthropic cache usage fields into PromptTokensDetails.CachedTokens and CompletionTokensDetails.CachedTokens.

Functionally this looks solid for Anthropic’s message API response shape.

Also applies to: 380-386, 413-426


710-741: Chat stream response reasoning mapping looks correct.

ToAnthropicChatStreamResponse now maps delta.Reasoning back into an Anthropic thinking delta (Type: AnthropicStreamDeltaTypeThinking, Thinking: delta.Reasoning), which matches what ToBifrostChatCompletionStream emits on the forward path and preserves reasoning in SSE form.


842-855: Reusing ToAnthropicChatCompletionError for SSE errors is a nice consolidation.

ToAnthropicChatStreamError delegating to ToAnthropicChatCompletionError and then wrapping the result as an event: error SSE frame centralizes error shaping and avoids duplicating JSON structure here.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 6c16eed to 1219509 Compare December 8, 2025 13:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (6)
core/providers/bedrock/utils.go (1)

32-52: additionalModelRequestFieldPaths still overwrites reasoning_config in AdditionalModelRequestFields.

You correctly populate AdditionalModelRequestFields["reasoning_config"] based on Params.Reasoning, but the later assignment:

if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
	if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
		bedrockReq.AdditionalModelRequestFields = orderedFields
	}
}

replaces the entire map, dropping any previously populated reasoning_config. For requests that use both reasoning and additionalModelRequestFieldPaths, Bedrock will never see the reasoning_config you validated.

Merge, don’t replace, the map so reasoning_config survives:

-			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
-				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-					bedrockReq.AdditionalModelRequestFields = orderedFields
-				}
-			}
+			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
+				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
+					if bedrockReq.AdditionalModelRequestFields == nil {
+						bedrockReq.AdditionalModelRequestFields = orderedFields
+					} else {
+						for k, v := range orderedFields {
+							bedrockReq.AdditionalModelRequestFields[k] = v
+						}
+					}
+				}
+			}

This preserves reasoning_config while still allowing additional fields to be injected.

Also applies to: 94-99

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

431-523: Nil‑guard issue from earlier review is resolved; reasoning → thinking conversion looks safe

This version of ToAnthropicChatResponse now consistently checks choice.ChatNonStreamResponseChoice != nil before accessing promoted fields like choice.StopString and choice.Message, which addresses the earlier nil‑dereference risk in this function.

The additional reasoning handling:

  • Pulls ReasoningDetails from choice.Message.ChatAssistantMessage and turns "reasoning.text" entries into thinking content blocks with optional signatures.
  • Leaves plain text + tool calls mapping unchanged.

One optional enhancement: for Bifrost responses that only set ChatAssistantMessage.Reasoning (and not ReasoningDetails), you might also want to emit a thinking block by synthesizing a single AnthropicContentBlockTypeThinking from that string, similar to what the JSON unmarshallers do for the reverse direction.

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

42-44: Enforce or centralize the “exactly one payload” invariant on OpenAIMessage.

OpenAIMessage now embeds both *schemas.ChatToolMessage and *OpenAIChatAssistantMessage and the comment calls out that only one should be non-nil. There’s still no runtime guard, so if a caller accidentally sets both, their fields will flatten into the same JSON object and may conflict.

Given this is now the core request type for OpenAI chat, consider adding a small helper to make the invariant explicit:

func (m *OpenAIMessage) Validate() error {
	if m.ChatToolMessage != nil && m.OpenAIChatAssistantMessage != nil {
		return fmt.Errorf("OpenAIMessage cannot have both ChatToolMessage and OpenAIChatAssistantMessage set")
	}
	return nil
}

and then have the request-building code call Validate() before marshalling, or expose constructors (e.g. NewToolMessage, NewAssistantMessage) that only set one of the embedded pointers.

The rest of the struct layout and the MarshalJSON logic for reasoning_effort look reasonable.

Also applies to: 56-72

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

104-121: Avoid silently overriding Reasoning-derived Thinking with ExtraParams["thinking"].

Here you:

  • First derive cohereReq.Thinking from the canonical bifrostReq.Params.Reasoning (with validation and Anthropic-style minimum token checks).
  • Then, if ExtraParams["thinking"] is present, you always parse it and assign a new CohereThinking, overwriting the Reasoning-based one.

That means if both are supplied, the looser ExtraParams path silently wins over the validated Reasoning path.

Given Reasoning is the new unified field, consider only reading ExtraParams when cohereReq.Thinking is still nil, e.g.:

-		if bifrostReq.Params.ExtraParams != nil {
-			// Handle thinking parameter
-			if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
+		if bifrostReq.Params.ExtraParams != nil {
+			// Handle legacy thinking parameter only if Reasoning didn't already set Thinking
+			if cohereReq.Thinking == nil {
+				if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
 					if thinkingMap, ok := thinkingParam.(map[string]interface{}); ok {
 						thinking := &CohereThinking{}
@@
-					cohereReq.Thinking = thinking
+					cohereReq.Thinking = thinking
 				}
 			}

This keeps ExtraParams as a backward-compatibility shim without weakening the new Reasoning-based validation.

Also applies to: 128-142

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

347-367: Fix reasoning_config type mismatch and avoid clobbering it when applying additionalModelRequestFieldPaths.

Two issues here:

  1. ToBifrostResponsesRequest expects reasoning_config as map[string]interface{}, but the disabled case in ToBedrockResponsesRequest writes map[string]string. That type assertion will always fail, so Bedrock requests with type: "disabled" never populate Params.Reasoning correctly on the way back.
  2. When additionalModelRequestFieldPaths is present, you replace bedrockReq.AdditionalModelRequestFields wholesale with orderedFields, which discards any previously-set reasoning_config.

Suggest:

  • Use map[string]any for both enabled and disabled configs.
  • Merge orderedFields into any existing AdditionalModelRequestFields rather than replacing it.
@@
-	// Convert additional model request fields to extra params
+	// Convert additional model request fields (currently only reasoning_config) to params
 	if len(request.AdditionalModelRequestFields) > 0 {
@@
 		if bifrostReq.Params.Reasoning != nil {
 			if bedrockReq.AdditionalModelRequestFields == nil {
 				bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
 			}
 			if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-				bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
-					"type": "disabled",
-				}
+				bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
+					"type": "disabled",
+				}
 			} else {
@@
-					bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
+					bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
 						"type":          "enabled",
 						"budget_tokens": *bifrostReq.Params.Reasoning.MaxTokens,
 					}
@@
-			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
-				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-					bedrockReq.AdditionalModelRequestFields = orderedFields
-				}
-			}
+			if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
+				if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
+					if bedrockReq.AdditionalModelRequestFields == nil {
+						bedrockReq.AdditionalModelRequestFields = orderedFields
+					} else {
+						for k, v := range orderedFields {
+							bedrockReq.AdditionalModelRequestFields[k] = v
+						}
+					}
+				}
+			}

Also applies to: 452-472, 474-482

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

296-303: Unify reasoning enablement semantics between Responses and Chat converters.

ToAnthropicResponsesRequest currently:

  • Treats reasoning as enabled only when Reasoning.Effort != nil && *Effort != "none" and MaxTokens is set/valid.
  • Treats Effort == nil (with non-nil Reasoning) as disabled.

But ToAnthropicChatRequest (in core/providers/anthropic/chat.go) treats reasoning as enabled for any Reasoning where Effort == nil or Effort != "none", and only disables when Effort != nil && *Effort == "none".

So the same Reasoning{Effort: nil, MaxTokens: 2048} payload enables reasoning on Chat but disables it on Responses. That’s surprising for callers sharing a common schema.

Consider aligning both paths on a single rule, e.g.:

  • Disabled only when Effort != nil && *Effort == "none" and MaxTokens == nil.
  • Enabled whenever MaxTokens != nil or (Effort != nil && *Effort != "none"), and in the enabled case always require/validate MaxTokens >= MinimumReasoningMaxTokens.

Ideally factor this into a shared helper used by both ToAnthropicChatRequest and ToAnthropicResponsesRequest so validation and error messages stay identical.

Also applies to: 325-344, 402-402

🧹 Nitpick comments (6)
plugins/semanticcache/utils.go (1)

669-671: Reasoning metadata extraction looks consistent and nil‑safe.

Using params.Reasoning with nested nil‑checks for Effort and MaxTokens aligns with the new Reasoning struct and avoids panics, while ensuring Responses cache keys now distinguish both effort and reasoning token budgets. If Chat’s Reasoning.MaxTokens is also user‑visible and affects behavior, you may eventually want to mirror the reasoning_max_tokens extraction there for symmetry, but it’s not blocking for this PR.

Also applies to: 752-754

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

13-118: Anthropic reasoning: strict max_tokens requirement may be too breaking when only reasoning_effort is set

The new reasoning block in ToAnthropicChatRequest looks consistent with Anthropic’s “thinking” API, and the nil checks + MinimumReasoningMaxTokens guard are good.

However, combined with ChatParameters.UnmarshalJSON, any request that specifies only reasoning_effort (e.g. "medium") and targets Anthropic will now fail with reasoning.max_tokens is required for reasoning, whereas previously that extra field would effectively be ignored for Anthropic.

That’s a behavior change from “ignored hint” → “hard error” for existing clients:

  • params.reasoning_effort = "medium"cp.Reasoning.Effort = "medium", MaxTokens = nil
  • Anthropic path now errors unless Reasoning.Effort == "none" or Reasoning.MaxTokens is set and ≥ MinimumReasoningMaxTokens.

Please double-check that this stricter requirement is acceptable for Anthropic callers and clearly documented, or consider a softer fallback (e.g. defaulting MaxTokens when only effort is present, or ignoring reasoning for Anthropic in that case rather than erroring).

Also applies to: 207-216


280-429: Bifrost → Anthropic response mapping mostly looks good; consider setting Object and reusing provider timestamps

The ToBifrostChatResponse implementation is well-structured: it correctly aggregates text, tool calls, and thinking blocks into a single assistant message, and maps cache read/create tokens into PromptTokensDetails / CompletionTokensDetails consistently.

Two non‑blocking improvements to consider:

  1. Set Object explicitly
    For non‑stream Anthropic responses we usually want Object: "chat.completion" on BifrostChatResponse to match other providers and the documented shape.

  2. Prefer provider timestamp if available
    Created is currently time.Now().Unix(). If AnthropicMessageResponse carries a created timestamp, using that instead would make logs/latency calculation more accurate.

Neither is a correctness bug, but aligning these would make Anthropic responses more consistent with the rest of Bifrost.


710-741: Bifrost → Anthropic streaming drops signature-only reasoning details

ToAnthropicChatStreamResponse maps delta.Content and delta.Reasoning back into Anthropic text and thinking deltas, which is good. However, any information present only in delta.ReasoningDetails (for example, a signature chunk created by AnthropicStreamDeltaTypeSignature on the way into Bifrost) is currently not emitted on the way back out.

If this reverse path is expected to carry signatures (e.g., for an Anthropic‑compatible proxy endpoint), consider:

  • Detecting a ReasoningDetails entry that has a Signature but no Text, and
  • Emitting an Anthropic content_block_delta of type signature (or the appropriate delta type) in that case.

That would make the streaming conversion symmetric with the ToBifrostChatCompletionStream logic.

core/schemas/chatcompletions.go (1)

480-521: ChatMessage custom unmarshal correctly reconstructs flattened assistant/tool shapes

The new ChatMessage.UnmarshalJSON:

  • Separately unmarshals base fields (name, role, content),
  • Unmarshals ChatToolMessage to detect tool_call_id, and
  • Unmarshals ChatAssistantMessage (including its own custom logic) and only attaches it when any relevant field is populated,

is a reasonable workaround for the embedded pointer + custom unmarshal combination.

Triple‑unmarshalling is a bit heavier but acceptable given the complexity, and should preserve existing shapes for all roles (assistant/user/tool/system) without recursion issues.

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

564-683: CohereMessage.ToBifrostChatMessage conversion looks correct; minor optional micro-optimization.

Functionally:

  • Handles string vs blocks content, including text and image blocks.
  • Aggregates thinking blocks into ChatReasoningDetails and a newline-joined Reasoning string.
  • Converts tool calls with robust nil checks on Function and Function.Name.

Only minor nit: you call cm.Content.GetBlocks() multiple times inside the same branch. Caching once at the top of the blocks path would avoid repeated work:

-		} else if cm.Content.IsBlocks() {
-			for _, block := range cm.Content.GetBlocks() {
+		} else if cm.Content.IsBlocks() {
+			blocks := cm.Content.GetBlocks()
+			for _, block := range blocks {

Not critical, but it slightly simplifies the loop.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 740fad1 and 727dbd8.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • plugins/maxim/main.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/azure/azure.go
  • core/providers/anthropic/types.go
  • core/providers/openai/utils.go
  • core/providers/openai/openai.go
  • core/providers/anthropic/errors.go
  • transports/bifrost-http/handlers/inference.go
  • core/providers/cohere/cohere.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/types.go
  • framework/streaming/chat.go
  • core/providers/openai/chat.go
  • plugins/semanticcache/utils.go
  • core/providers/bedrock/utils.go
  • plugins/jsonparser/utils.go
  • core/providers/perplexity/chat.go
  • ui/lib/types/logs.ts
  • core/providers/anthropic/responses.go
  • core/schemas/mux.go
  • core/providers/openai/types.go
  • core/providers/bedrock/chat.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/vertex.go
  • core/providers/cohere/chat.go
  • core/schemas/chatcompletions.go
  • core/providers/bedrock/bedrock.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/chat.go
🧠 Learnings (2)
📚 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/types.go
  • framework/streaming/chat.go
  • core/providers/openai/chat.go
  • plugins/semanticcache/utils.go
  • core/providers/bedrock/utils.go
  • plugins/jsonparser/utils.go
  • core/providers/perplexity/chat.go
  • core/providers/anthropic/responses.go
  • core/schemas/mux.go
  • core/providers/openai/types.go
  • core/providers/bedrock/chat.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/vertex.go
  • core/providers/cohere/chat.go
  • core/schemas/chatcompletions.go
  • core/providers/bedrock/bedrock.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/chat.go
📚 Learning: 2025-12-11T11:58:25.307Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: core/providers/openai/responses.go:42-84
Timestamp: 2025-12-11T11:58:25.307Z
Learning: In core/providers/openai/responses.go (and related OpenAI response handling), document and enforce the API format constraint: if ResponsesReasoning != nil and the response contains content blocks, all content blocks should be treated as reasoning blocks by default. Implement type guards or parsing logic accordingly, and add unit tests to verify that when ResponsesReasoning is non-nil, content blocks are labeled as reasoning blocks. Include clear comments in the code explaining the rationale and ensure downstream consumers rely on this behavior.

Applied to files:

  • core/providers/openai/chat.go
  • core/providers/openai/types.go
🧬 Code graph analysis (8)
framework/streaming/chat.go (4)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (1)
  • Type (778-778)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/schemas/mux.go (3)
core/schemas/responses.go (3)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesStreamResponse (1425-1463)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1391-1391)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/anthropic.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/vertex/vertex.go (5)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/schemas/chatcompletions.go (3)
core/providers/openai/types.go (1)
  • Alias (78-78)
core/schemas/bifrost.go (2)
  • Alias (384-384)
  • Alias (401-401)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (4)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (233-238)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
⏰ 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). (27)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • 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 (32)
core/providers/perplexity/chat.go (1)

30-36: LGTM - Reasoning effort mapping correctly updated.

The nil checks are properly ordered to avoid nil pointer dereference, and the "minimal" → "low" mapping aligns with Perplexity's supported effort values. Note that Reasoning.MaxTokens is not propagated to Perplexity, which appears intentional since Perplexity's reasoning configuration uses only effort levels.

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

14-14: LGTM - Message conversion uses new utility functions.

The change to use ConvertOpenAIMessagesToBifrostMessages properly preserves OpenAI-specific assistant message fields (Refusal, Reasoning, Annotations, ToolCalls) during conversion, as shown in the relevant code snippet from utils.go.


63-65: LGTM - Reasoning effort mapping correctly updated.

The triple nil-check pattern (Reasoning != nil && Reasoning.Effort != nil && *Effort == "minimal") safely handles the nested structure. The "minimal" → "low" mapping for non-OpenAI providers is consistent with the Perplexity provider implementation.

core/schemas/responses.go (2)

233-238: LGTM - ResponsesParametersReasoning correctly extended.

The addition of MaxTokens field aligns with ChatReasoning in chatcompletions.go, enabling consistent reasoning configuration across Chat and Responses APIs. The comment update on Effort clarifies the enabling behavior.


324-325: LGTM - Helpful behavioral documentation added.

The comment clarifies the difference between gpt-oss models (reasoning_text blocks only) and other OpenAI models (summaries + encrypted_content), which is valuable context for consumers of this struct.

core/schemas/mux.go (3)

773-778: LGTM - MaxTokens now properly preserved in Chat → Responses conversion.

The condition (bcr.Params.Reasoning.Effort != nil || bcr.Params.Reasoning.MaxTokens != nil) correctly addresses the previously flagged issue where MaxTokens was being dropped. Both fields are now propagated to ResponsesParametersReasoning.


852-858: LGTM - MaxTokens properly preserved in Responses → Chat conversion.

The conversion now maps both Effort and MaxTokens fields, ensuring round-trip preservation of reasoning configuration. Creating ChatReasoning whenever brr.Params.Reasoning is non-nil is appropriate for maintaining structural symmetry.


1361-1372: LGTM - Streaming delta correctly uses Reasoning field.

The change from delta.Thought to delta.Reasoning aligns with the broader schema rename. The ResponsesStreamResponseTypeReasoningSummaryTextDelta event type correctly represents reasoning content in the Responses streaming format.

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

535-539: LGTM - Thinking field added for streaming reasoning support.

The optional Thinking field enables Cohere streaming to capture thinking/reasoning deltas. Using Cohere's terminology ("Thinking") for the provider-specific struct is appropriate, as the conversion layer can map this to the unified Reasoning field in Bifrost schemas.

plugins/jsonparser/utils.go (1)

309-314: LGTM - Field rename from Thought to Reasoning.

The shallow copy of Reasoning is appropriate since the JSON parser plugin modifies only Content, not reasoning data. This change aligns with the broader schema rename from Thought to Reasoning in ChatStreamResponseChoiceDelta.

core/providers/bedrock/bedrock.go (1)

21-21: Stable stream IDs for Bedrock chat streaming are wired correctly.

Generating a single UUID per Bedrock stream and reusing it for all chunks (including the final synthesized chunk) provides consistent IDs without introducing shared state, which matches expectations from other providers’ streaming behavior.

Also applies to: 771-773, 859-869, 882-885

framework/streaming/chat.go (1)

39-43: Reasoning and reasoning_details accumulation from chat stream chunks looks correct.

The accumulator now safely initializes ChatAssistantMessage, concatenates Reasoning text across chunks, and merges ReasoningDetails by index (accumulating text/summary/data and overwriting signature/type when provided), which is consistent with how deltas are emitted and should reconstruct a coherent final assistant message.

Also applies to: 44-55, 56-105

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

303-311: Anthropic request conversion errors are now correctly propagated.

Hooking ToAnthropicChatRequest/ToAnthropicResponsesRequest into CheckContextAndGetRequestBody as real error‑returning closures ensures invalid inputs (e.g., bad reasoning config) surface as BifrostErrors instead of silently constructing bad request bodies, for both streaming and non‑streaming flows.

Also applies to: 357-364, 656-663, 707-714

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

702-710: Bedrock error message extraction is safe and simplified.

Copying bifrostErr.Error.Message into a local message (with existing nil‑guard above) before constructing BedrockError avoids any nested dereference issues and centralizes how the Bedrock error message is derived, without changing type/code mapping.

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

88-113: Reasoning content types for Bedrock are well‑structured and align with usage.

Adding ReasoningContent to BedrockContentBlock plus the BedrockReasoningContent* structs, and mirroring that with a ReasoningContent field on BedrockContentBlockDelta, gives both non‑streaming and streaming paths a clear place to carry reasoning text + optional signature, which matches how ToBifrostChatResponse and the streaming converter consume these fields.

Also applies to: 157-164, 443-447

ui/lib/types/logs.ts (1)

115-124: Log types now expose reasoning content consistently with backend.

Extending ChatMessage with reasoning and reasoning_details plus the ReasoningDetails interface gives the UI enough structure to render both plain reasoning text and richer per‑segment metadata (summary/text/signature/data) coming from providers, without breaking existing tooling that reads ChatMessage.

Also applies to: 126-133

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

24-41: Bedrock chat parameter conversion errors are now surfaced correctly.

Wrapping convertChatParameters and returning a contextual error when it fails ensures issues like invalid reasoning config (e.g., missing or too‑small max_tokens) stop the Bedrock call early instead of producing malformed requests.


59-61: Bedrock reasoning extraction and streaming deltas look correct and robust.

Non‑streaming: you now safely handle ReasoningContent by checking ReasoningText for nil, aggregating reasoningDetails and reasoningText, and attaching both to ChatAssistantMessage alongside any tool calls, with a reasonable collapse to a single text block when applicable.

Streaming: the ReasoningContent delta branch avoids returning a nil response pointer (early exit when both text and signature are absent) and only constructs a chunk when text and/or signature are present, emitting them via Reasoning and ReasoningDetails in a way that matches the accumulator logic in framework/streaming/chat.go.

Also applies to: 121-133, 139-143, 157-163, 335-388

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

633-645: Streaming thinking/signature deltas: structure is consistent with new ReasoningDetails model

The handling for AnthropicStreamDeltaTypeThinking and AnthropicStreamDeltaTypeSignature cleanly maps Anthropic thinking/signature chunks into:

  • Delta.Reasoning + a single ReasoningDetails entry (type "reasoning.text") for thinking deltas.
  • A single ReasoningDetails entry carrying only Signature for signature deltas.

This matches the new ChatReasoningDetails schema, and should give downstreams both a simple reasoning string (for thinking) and richer per‑chunk metadata.

No changes requested here.

Also applies to: 655-677


842-855: Streaming error conversion is straightforward and consistent

ToAnthropicChatStreamError delegates to ToAnthropicChatCompletionError and then wraps the JSON in an SSE event: error frame. The behavior is clear and aligned with the rest of the Anthropic SSE helpers.

No issues from my side.

core/schemas/chatcompletions.go (5)

155-184: ChatParameters reasoning + reasoning_effort aliasing looks sound

The introduction of Reasoning *ChatReasoning on ChatParameters and the custom UnmarshalJSON that:

  • Parses a legacy reasoning_effort string,
  • Rejects payloads that specify both reasoning and reasoning_effort, and
  • Synthesizes cp.Reasoning from reasoning_effort when present,

is a clean way to keep backward compatibility while moving to the richer reasoning object.

The alias pattern with sonic is idiomatic and avoids recursion; no structural issues spotted here.

Also applies to: 186-220, 223-226


626-667: ChatAssistantMessage reasoning → ReasoningDetails synthesis is consistent

The updated ChatAssistantMessage plus UnmarshalJSON:

  • Adds first‑class Reasoning and ReasoningDetails fields, and
  • Automatically synthesizes a single "reasoning.text" ChatReasoningDetails entry when Reasoning is set and ReasoningDetails is empty,

nicely normalizes older “flat string” reasoning into the new structured representation.

This lines up with how the Anthropic adapter consumes ReasoningDetails, and should make downstream UI and logging much easier to keep consistent.


714-731: ReasoningDetails type and constants align with UI expectations

BifrostReasoningDetailsType and ChatReasoningDetails (with summary, text, signature, data) match the UI’s ReasoningDetails contract (reasoning.summary / reasoning.encrypted / reasoning.text).

This gives you enough flexibility to carry summaries, encrypted payloads, and signed reasoning without overloading a single string field. No issues here.


756-794: Streaming delta reasoning synthesis mirrors assistant message behavior

The extended ChatStreamResponseChoiceDelta plus its custom UnmarshalJSON apply the same pattern as ChatAssistantMessage:

  • Accepts both a flat reasoning string and reasoning_details,
  • When only reasoning is present, synthesizes a single "reasoning.text" detail.

That keeps streaming and non‑streaming representations aligned and ensures downstreams can always rely on ReasoningDetails being populated in at least the simple case.

Looks good.


829-840: ImageTokens addition is fine; ensure providers populate it

Adding ImageTokens *int to ChatCompletionTokensDetails is straightforward and matches how other token sub‑counters are modeled.

Follow‑up (outside this file): double‑check that provider adapters that expose image token usage (e.g., OpenAI vision or other multimodal providers) are wired to set this field so it doesn’t remain permanently nil.

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

652-666: Output → Responses mapping looks correct.

Mapping response.Output.Message through convertBedrockMessageToResponsesMessages into bifrostResp.Output is straightforward and symmetric with the existing helpers; no issues spotted.

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

190-201: Thinking → Params.Reasoning mapping is consistent.

Deriving Params.Reasoning from request.Thinking (auto + budget_tokens vs none) while also keeping ExtraParams["thinking"] preserves backwards compatibility and aligns Responses with the Anthropic thinking model.


1428-1439: Safer streaming error marshalling looks good.

Extracting message defensively from bifrostErr.Error before building the Anthropic SSE error avoids nil dereferences and preserves any upstream error text.

core/providers/vertex/vertex.go (1)

283-290: Anthropic conversion/error handling on Vertex paths looks consistent and safe.

All Anthropic branches now:

  • Delegate request shaping to anthropic.ToAnthropicChatRequest / ToAnthropicResponsesRequest with proper error propagation.
  • Only touch reqBody.Model / reqBody.Stream after the conversion, with an explicit nil guard in the non-stream Chat path.

Given the updated converters always return a non-nil request when err == nil, these sites won’t marshal a null body anymore. No additional defensive checks seem necessary.

Also applies to: 518-525, 689-695, 847-854

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

74-101: Custom marshalling for reasoning_effort looks correct.

The alias + aux pattern cleanly:

  • Suppresses the embedded Reasoning field in JSON, and
  • Emits reasoning_effort when r.Reasoning != nil && r.Reasoning.Effort != nil.

This preserves the legacy OpenAI wire format while allowing callers to use the new ChatReasoning field.

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

219-262: CohereThinking ↔ unified Reasoning + ExtraParams mapping looks good.

  • ToBifrostChatRequest maps req.Thinking to Params.Reasoning (Effort:"none" for disabled, "auto" plus MaxTokens for enabled) and also mirrors it into ExtraParams["thinking"].
  • This matches the write side in ToCohereChatCompletionRequest and keeps both the new Reasoning field and the legacy thinking param available to callers.

No functional issues spotted here.

Also applies to: 300-322


409-454: Streaming reasoning/thinking deltas are mapped appropriately.

  • StreamEventContentDelta now distinguishes between text (Content...Text) and thinking (...Thinking), emitting:
    • Delta.Content for text chunks, and
    • Delta.Reasoning plus a ReasoningDetails entry for thinking chunks.
  • StreamEventToolPlanDelta surfaces tool plans through Delta.Reasoning, which is a reasonable choice for plan-like explanations.

This aligns well with the unified streaming reasoning model and should play nicely with downstream consumers.

Also applies to: 457-467

@TejasGhatte TejasGhatte force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 982bc60 to 0f9320e Compare December 12, 2025 06:37
@TejasGhatte TejasGhatte force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 727dbd8 to 785a11f Compare December 12, 2025 06: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: 2

Caution

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

⚠️ Outside diff range comments (2)
core/providers/bedrock/utils.go (1)

94-98: Reasoning config can still be overwritten by ExtraParams handling.

The reasoning_config set at lines 38-50 will be completely replaced if additionalModelRequestFieldPaths exists in ExtraParams (line 97). This was flagged in a previous review but appears not to be fully addressed.

Consider merging instead of replacing:

 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
     if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-        bedrockReq.AdditionalModelRequestFields = orderedFields
+        if bedrockReq.AdditionalModelRequestFields == nil {
+            bedrockReq.AdditionalModelRequestFields = orderedFields
+        } else {
+            for k, v := range orderedFields {
+                bedrockReq.AdditionalModelRequestFields[k] = v
+            }
+        }
     }
 }
core/providers/bedrock/responses.go (1)

478-481: Reasoning config can be overwritten by additionalModelRequestFieldPaths.

Same issue as in utils.go: the reasoning_config set at lines 453-471 will be lost if additionalModelRequestFieldPaths exists in ExtraParams. Apply the same merge pattern suggested for utils.go.

♻️ Duplicate comments (7)
core/providers/bedrock/utils.go (1)

10-10: Consider extracting MinimumReasoningMaxTokens to a shared location.

Importing anthropic just for this constant creates a cross-provider dependency. This constant is used by Bedrock, Cohere, and Anthropic providers. Moving it to core/schemas or a shared constants package would improve modularity.

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

456-459: Type mismatch for disabled reasoning config.

Line 457 uses map[string]string{"type": "disabled"} but the reader at line 351 expects map[string]interface{}. This type assertion will fail when processing Bedrock responses with disabled reasoning.

 if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
         "type": "disabled",
     }
core/providers/anthropic/responses.go (1)

325-344: Reasoning enablement semantics still differ from Chat converter.
This treats Reasoning.Effort == nil as “disabled” (falls into the else), while core/providers/anthropic/chat.go treats Effort == nil as enabled (unless explicitly "none") and requires MaxTokens. This makes identical payloads behave differently between /responses vs /chat/completions.

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

415-426: Cache token mapping may still be semantically off (cache-creation is input-side).
CacheCreationInputTokens is being stored under CompletionTokensDetails.CachedTokens; if those tokens represent cache creation (prompt-side work), they likely belong with prompt/input cached tokens instead.

core/providers/vertex/vertex.go (3)

518-525: Fix nil reqBody handling in Anthropic ChatCompletionStream (can panic on map write).
If anthropic.ToAnthropicChatRequest(request) returns (nil, nil), sonic.Unmarshal likely yields a nil map, and requestBody["anthropic_version"] = ... will panic; plus you’d send an invalid body. Return an error when reqBody == nil and only proceed after setting required fields.

 				reqBody, err := anthropic.ToAnthropicChatRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-					reqBody.Stream = schemas.Ptr(true)
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("chat completion input is not provided")
+				}
+				reqBody.Model = deployment
+				reqBody.Stream = schemas.Ptr(true)

847-854: Fix nil reqBody handling in Anthropic ResponsesStream (can panic on map write).
Same nil-body failure mode as above; fail fast and set fields unconditionally after the nil-check.

 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-					reqBody.Stream = schemas.Ptr(true)
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("responses input is not provided")
+				}
+				reqBody.Model = deployment
+				reqBody.Stream = schemas.Ptr(true)

689-695: Fix nil reqBody handling in Anthropic Responses (can panic on map write).
Same issue: (nil, nil) conversion can later panic when setting anthropic_version on a nil map (and would send an invalid request).

 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("responses input is not provided")
+				}
+				reqBody.Model = deployment
🧹 Nitpick comments (2)
core/schemas/chatcompletions.go (1)

835-835: Document the ImageTokens field usage.

The new ImageTokens field is added without accompanying context. Consider adding a comment explaining when this field is populated and which providers support it, similar to the existing documentation for other token detail fields.

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

190-201: Potential double-source for “thinking” config (ExtraParams + Params.Reasoning).
ToBifrostResponsesRequest writes params.ExtraParams["thinking"] (Line 184-186) and also derives params.Reasoning from request.Thinking (Line 190-201). If both are intended for backward compatibility, consider a short comment clarifying precedence to avoid consumers diverging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 727dbd8 and 785a11f.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • framework/streaming/chat.go
  • core/providers/openai/utils.go
  • core/providers/anthropic/types.go
  • plugins/semanticcache/utils.go
  • plugins/maxim/main.go
  • core/providers/openai/chat.go
  • core/schemas/responses.go
  • core/providers/openai/openai.go
  • core/providers/cohere/cohere.go
  • plugins/jsonparser/utils.go
  • core/providers/bedrock/bedrock.go
  • core/providers/cohere/types.go
  • ui/lib/types/logs.ts
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

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

Files:

  • transports/bifrost-http/handlers/inference.go
  • core/providers/bedrock/utils.go
  • core/providers/vertex/vertex.go
  • core/providers/anthropic/errors.go
  • core/providers/azure/azure.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/chat.go
  • core/providers/anthropic/responses.go
  • core/schemas/mux.go
  • core/providers/anthropic/anthropic.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/cohere/chat.go
  • core/providers/perplexity/chat.go
  • core/providers/openai/types.go
  • core/providers/bedrock/responses.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/chat.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

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

Applied to files:

  • core/providers/openai/types.go
🧬 Code graph analysis (10)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/vertex/vertex.go (5)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/anthropic/errors.go (2)
ui/lib/types/logs.ts (1)
  • BifrostError (233-239)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/bedrock/chat.go (2)
core/schemas/chatcompletions.go (4)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/anthropic/responses.go (3)
core/schemas/responses.go (2)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesRequest (32-39)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (4)
  • AnthropicMessageRequest (40-59)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
  • AnthropicStreamError (397-400)
core/schemas/mux.go (3)
core/schemas/responses.go (3)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesStreamResponse (1425-1463)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1391-1391)
core/schemas/chatcompletions.go (1)
  • ChatReasoning (223-226)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/anthropic.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/openai/types.go (2)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/chatcompletions.go (10)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • Alias (189-189)
  • Alias (643-643)
  • Alias (770-770)
  • Alias (860-860)
  • ChatReasoning (223-226)
⏰ 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 (34)
ui/app/workspace/logs/views/logChatMessageView.tsx (1)

73-94: LGTM! Clean refactor from thought to reasoning.

The changes correctly replace all references from message.thought to message.reasoning with consistent rendering logic for both JSON and non-JSON content. The italic styling applied to reasoning text (line 89) effectively differentiates it from regular message content.

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

108-110: LGTM! Reasoning content types follow established patterns.

The new reasoning content types mirror the existing guard content structure and integrate cleanly into both the content block and delta representations. The nullable pointers and omitempty tags are consistent with Bedrock's API conventions.

Also applies to: 157-164, 444-446

core/schemas/chatcompletions.go (1)

634-667: Reasoning details synthesis is consistent and well-designed.

Both ChatAssistantMessage.UnmarshalJSON and ChatStreamResponseChoiceDelta.UnmarshalJSON apply the same synthesis logic: when Reasoning is non-nil but ReasoningDetails is empty, they automatically create a single reasoning.text entry. This provides a unified interface for consumers while supporting both simple and detailed reasoning representations.

Also applies to: 765-794

transports/bifrost-http/handlers/inference.go (2)

169-199: Custom unmarshaller correctly handles nested custom unmarshallers.

The ChatRequest.UnmarshalJSON method properly coordinates three separate unmarshal operations to avoid conflicts when ChatParameters has its own custom unmarshaller. The pattern of unmarshalling embedded structs separately and then combining them is the correct approach.


476-478: Good defensive initialization before unmarshalling.

Initializing ChatParameters to a non-nil value before calling sonic.Unmarshal ensures the custom ChatParameters.UnmarshalJSON method has a valid target. This prevents potential nil pointer issues.

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

74-101: MarshalJSON correctly transforms reasoning structure.

The custom marshaller properly:

  1. Shadows the Reasoning field to prevent it from being emitted
  2. Safely extracts Reasoning.Effort with nil checks (line 96)
  3. Emits reasoning_effort instead of the nested reasoning object

This transformation is correct for providers that expect the flat reasoning_effort field.

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

104-121: Reasoning validation enforces minimum token requirements.

The code correctly:

  1. Handles the special "none" effort case by setting ThinkingTypeDisabled
  2. Validates that Reasoning.MaxTokens is present and meets the minimum threshold
  3. References anthropic.MinimumReasoningMaxTokens (1024) as a cross-provider standard

This ensures consistent reasoning behavior across providers.


431-454: Reasoning content properly synthesized in both streaming and non-streaming paths.

Both paths correctly:

  • Create ReasoningDetails entries with type BifrostReasoningDetailsTypeText
  • Populate the Reasoning field with concatenated text
  • Handle nil-safety when accessing thinking content

The streaming path (lines 431-454) creates a single detail entry with the delta, while the non-streaming path (lines 600-609) aggregates thinking blocks with newlines (as addressed in the past review).

Also applies to: 600-609


564-683: Per-message conversion provides clean separation of concerns.

The new ToBifrostChatMessage method centralizes conversion logic with proper:

  • Content type handling (string vs blocks)
  • Tool call extraction with nil-safety (lines 630-633)
  • Reasoning details synthesis from thinking blocks
  • Assistant message construction when needed

This refactoring improves maintainability and reduces duplication.

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

30-36: LGTM! Reasoning field access updated with proper nil-safety.

The code correctly navigates the nested Reasoning.Effort path with appropriate nil checks and maintains the special case mapping of "minimal" to "low" for Perplexity.

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

9-37: Excellent nil-safety in error conversion.

The function defensively handles all potential nil pointers:

  • Input bifrostErr (line 11)
  • Nested bifrostErr.Type (lines 16-19)
  • Nested bifrostErr.Error (lines 22-25)

This ensures the converter never panics and always produces a valid AnthropicMessageError, addressing the concerns from the past review.


42-48: Safe nested error population.

The code ensures bifrostErr.Error is non-nil before populating its fields, preventing any nil pointer dereferences during error parsing.

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

307-307: Proper error propagation added for chat completions.

Both the non-streaming (line 307) and streaming (lines 358-364) paths now correctly handle errors from ToAnthropicChatRequest. The streaming path checks for errors before enabling streaming, ensuring invalid requests fail fast.

Also applies to: 358-364


659-659: Proper error propagation added for responses.

Both the non-streaming (line 659) and streaming (lines 708-714) paths now correctly handle errors from ToAnthropicResponsesRequest. This aligns with the chat completion path and ensures consistent error handling across all request types.

Also applies to: 708-714

core/providers/azure/azure.go (4)

353-360: LGTM! Error handling correctly propagated for Anthropic chat requests.

The conversion function now returns an error which is properly checked before proceeding. The nil check on line 357 provides additional safety.


452-460: LGTM! Consistent error handling in streaming path.

The streaming path mirrors the non-streaming implementation with proper error propagation and nil safety checks.


531-538: LGTM! Responses API follows the same error handling pattern.


631-639: LGTM! ResponsesStream completes the consistent error handling pattern.

All four Anthropic-related methods (ChatCompletion, ChatCompletionStream, Responses, ResponsesStream) now properly handle errors from the conversion functions with consistent nil safety checks.

core/schemas/mux.go (3)

773-778: LGTM! Reasoning.MaxTokens is now preserved in ToResponsesRequest.

The condition at line 773 correctly checks for either Effort or MaxTokens being set, ensuring the reasoning configuration is not silently dropped when only MaxTokens is provided. This addresses the previous review concern about MaxTokens being lost during Chat→Responses conversions.


852-858: LGTM! Reasoning config properly round-tripped in ToChatRequest.

Both Effort and MaxTokens are now copied from ResponsesParametersReasoning to ChatReasoning, ensuring complete round-trip preservation of reasoning configuration.


1361-1372: LGTM! Streaming delta correctly uses the new Reasoning field.

The streaming conversion now properly handles delta.Reasoning content and emits the appropriate ResponsesStreamResponseTypeReasoningSummaryTextDelta event type.

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

32-52: Reasoning config handling looks correct.

The reasoning configuration is properly translated to Bedrock's format:

  • Effort "none" maps to type "disabled"
  • Otherwise requires and validates MaxTokens against MinimumReasoningMaxTokens
  • Returns appropriate errors for missing or invalid MaxTokens

702-709: LGTM! Safe message extraction in ToBedrockError.

The explicit nil check before accessing bifrostErr.Error.Message is defensive and clear, even though the earlier check at line 695 would return early if Error were nil.

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

349-367: LGTM! Reasoning config extraction from Bedrock request.

The logic correctly extracts reasoning configuration from AdditionalModelRequestFields:

  • When type is "enabled" and budget_tokens is present, creates Reasoning with Effort "auto" and MaxTokens
  • Otherwise sets Effort to "none"

662-666: LGTM! Output conversion placement improved.

Moving the output message conversion to the top of the function ensures it's processed before other fields, improving code organization.

core/providers/bedrock/chat.go (5)

39-41: LGTM! Error handling for parameter conversion.

The function now properly propagates errors from convertChatParameters with a descriptive error message.


120-133: LGTM! Nil-safe reasoning content collection.

The code properly guards against nil ReasoningText (lines 123-125) before accessing its fields, addressing the previous review concern about potential nil pointer dereference.


139-142: LGTM! Single text block optimization.

Collapsing a single text block to contentStr simplifies the response structure for the common case of text-only responses.


157-163: LGTM! Reasoning properly attached to assistant message.

The code correctly ensures an assistant message exists before attaching reasoning content, preserving both the detailed reasoning entries and the accumulated text summary.


335-388: LGTM! Streaming reasoning content handling.

The implementation correctly:

  1. Returns early (lines 340-342) when neither Text nor Signature is set, preventing nil response issues
  2. Handles Text and Signature cases separately with appropriate ReasoningDetails entries
  3. Addresses the previous review concern about potential nil response pointer
core/providers/anthropic/responses.go (1)

1428-1432: Nil-safe stream error message extraction looks good.
Prevents nil deref when bifrostErr.Error is absent.

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

99-117: Reasoning validation + error-return upgrade is solid.
Failing fast on missing/too-small reasoning.max_tokens will prevent sending invalid Anthropic “thinking” configs downstream.


207-216: ReasoningDetails → thinking blocks mapping looks consistent with signature support.
Good to see Signature carried through into AnthropicContentBlock.


468-503: Nil-guards on promoted fields in ToAnthropicChatResponse are a good fix.
The added choice.ChatNonStreamResponseChoice != nil checks prevent nil derefs when only stream choices are present.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 785a11f to ef2308c Compare December 12, 2025 08:27
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 0f9320e to 14a7a70 Compare December 12, 2025 08:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

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

689-709: Same nil-body hazard in Responses Anthropic path. requestBody["anthropic_version"] = ... (Line 704-706) can panic if reqBody == nil yields a nil map.

Proposed fix:

 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("responses input is not provided")
+ }
+ reqBody.Model = deployment
core/providers/bedrock/chat.go (1)

66-83: Structured-output fast-path may drop other content (incl. reasoning) because it doesn’t require len(content)==1. Comment says “only one tool use and no other content”, but the condition only checks content[0].ToolUse != nil. If Bedrock returns additional blocks (text/reasoning), they’ll be ignored.

Suggested tightening:

- if len(response.Output.Message.Content) > 0 && response.Output.Message.Content[0].ToolUse != nil && structuredOutputToolName == response.Output.Message.Content[0].ToolUse.Name {
+ if len(response.Output.Message.Content) == 1 &&
+    response.Output.Message.Content[0].ToolUse != nil &&
+    structuredOutputToolName == response.Output.Message.Content[0].ToolUse.Name {
♻️ Duplicate comments (15)
plugins/semanticcache/utils.go (1)

669-671: Missing MaxTokens extraction creates cache inconsistency with Responses API.

The Chat API parameter extraction only captures Reasoning.Effort but omits Reasoning.MaxTokens, while the Responses API extraction (lines 752-754) correctly extracts both fields. This inconsistency means Chat API requests with identical Effort but different MaxTokens could incorrectly share cache entries, potentially serving responses with the wrong reasoning budget.

The previous review comment identified this issue for lines 669-671 and was marked as addressed, but the fix was only applied to extractResponsesParametersToMetadata and not to extractChatParametersToMetadata.

Add MaxTokens extraction to match the Responses API implementation:

 	if params.Reasoning != nil {
-		metadata["reasoning_effort"] = *params.Reasoning.Effort
+		if params.Reasoning.Effort != nil {
+			metadata["reasoning_effort"] = *params.Reasoning.Effort
+		}
+		if params.Reasoning.MaxTokens != nil {
+			metadata["reasoning_max_tokens"] = *params.Reasoning.MaxTokens
+		}
 	}
core/schemas/chatcompletions.go (1)

165-220: Mutual-exclusivity error may be harsher than necessary during migration.
UnmarshalJSON hard-errors when both reasoning_effort and reasoning are present (Line 207–209). If the intent is “compat + deprecation”, consider preferring reasoning and ignoring reasoning_effort (optionally logging) rather than rejecting the request outright. This matches a previously raised concern.

-	if aux.Alias != nil && aux.Alias.Reasoning != nil && aux.ReasoningEffort != nil {
-		return fmt.Errorf("both reasoning_effort and reasoning fields cannot be present at the same time")
-	}
+	// If both are set, prefer `reasoning` and ignore legacy `reasoning_effort`.
+	// (Optionally log a deprecation warning at the API boundary.)
core/providers/cohere/chat.go (1)

104-143: Avoid silent override: ExtraParams thinking can overwrite Reasoning-derived Thinking.
Right now cohereReq.Thinking can be set from Params.Reasoning and then overwritten by ExtraParams["thinking"] with no warning. This matches a previously raised concern.

-			if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
+			if cohereReq.Thinking == nil {
+				if thinkingParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "thinking"); ok {
 					if thinkingMap, ok := thinkingParam.(map[string]interface{}); ok {
 						thinking := &CohereThinking{}
@@
 						cohereReq.Thinking = thinking
 					}
 				}
 			}
core/providers/azure/azure.go (4)

351-361: Don’t allow nil reqBody on Anthropic conversion success path.
If ToAnthropicChatRequest ever returns (nil, nil), this code returns nil request body, which will likely serialize to null and fail downstream (or behave unexpectedly). This matches a previously raised concern.

 			if schemas.IsAnthropicModel(deployment) {
 				reqBody, err := anthropic.ToAnthropicChatRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-				}
-				return reqBody, nil
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic request conversion returned nil")
+				}
+				reqBody.Model = deployment
+				return reqBody, nil
 			} else {

451-461: Same nil-reqBody guard needed in Anthropic streaming path.
You set reqBody.Model and reqBody.Stream only if non-nil; better to fail fast if conversion returns nil.


529-539: Same nil-reqBody guard needed for Anthropic Responses conversion.
Otherwise you can send null to anthropic/v1/messages.


630-640: Same nil-reqBody guard needed for Anthropic ResponsesStream conversion.
Otherwise you may start an SSE request with an invalid/null body.

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

56-72: Enforce mutual exclusivity of embedded pointers to avoid JSON collisions.
OpenAIMessage embeds *schemas.ChatToolMessage and *OpenAIChatAssistantMessage, but nothing prevents both being non-nil (which can flatten conflicting fields into the same JSON object). This matches a previously raised concern.

 type OpenAIMessage struct {
@@
 	*schemas.ChatToolMessage
 	*OpenAIChatAssistantMessage
 }
+
+func (m *OpenAIMessage) Validate() error {
+	if m == nil {
+		return nil
+	}
+	if m.ChatToolMessage != nil && m.OpenAIChatAssistantMessage != nil {
+		return fmt.Errorf("OpenAIMessage: only one of ChatToolMessage or OpenAIChatAssistantMessage may be set")
+	}
+	return nil
+}

(Then ensure conversion paths call Validate() before marshaling/sending.)

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

413-426: Usage cache-token mapping still looks semantically inverted (cache creation is prompt-side).
CacheCreationInputTokens is input-side work but is being stored under CompletionTokensDetails.CachedTokens. This matches a previously raised concern, but the current code still shows the same mapping.

 		bifrostResponse.Usage = &schemas.BifrostLLMUsage{
 			PromptTokens: response.Usage.InputTokens,
 			PromptTokensDetails: &schemas.ChatPromptTokensDetails{
-				CachedTokens: response.Usage.CacheReadInputTokens,
+				CachedTokens: response.Usage.CacheReadInputTokens + response.Usage.CacheCreationInputTokens,
 			},
 			CompletionTokens: response.Usage.OutputTokens,
-			CompletionTokensDetails: &schemas.ChatCompletionTokensDetails{
-				CachedTokens: response.Usage.CacheCreationInputTokens,
-			},
 			TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
 		}

(Or, if you want to keep them distinct, add a dedicated field rather than misclassifying it as completion-side cached tokens.)

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

32-99: Reasoning config can be lost + disabled type uses map[string]string (incompatible with readers).

  1. You write disabled as map[string]string (Line 38-40) but other codepaths typically type-assert map[string]interface{} / map[string]any; this will fail to round-trip.
  2. The later additionalModelRequestFieldPaths handling assigns bedrockReq.AdditionalModelRequestFields = orderedFields (Line 97-98), which can silently overwrite reasoning_config added above.

Proposed fix (merge + consistent map type):

-            bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+            bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
                 "type": "disabled",
             }
...
-                bedrockReq.AdditionalModelRequestFields = orderedFields
+                if bedrockReq.AdditionalModelRequestFields == nil {
+                    bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+                }
+                for k, v := range orderedFields {
+                    bedrockReq.AdditionalModelRequestFields[k] = v
+                }

10-10: Avoid cross-provider coupling for MinimumReasoningMaxTokens. Importing core/providers/anthropic just for a shared min-tokens constant couples providers.

Suggestion: move the constant to a shared package (e.g., core/schemas or core/constants) and have both providers import that.

Also applies to: 44-46

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

452-482: reasoning_config can be overwritten by additionalModelRequestFieldPaths. You set reasoning_config (Line 452-472), then later assign bedrockReq.AdditionalModelRequestFields = orderedFields (Line 480-481), discarding it.
Also map[string]string for disabled (Line 457-459) is inconsistent with the parsing side.

Proposed fix:

-                bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+                bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
                     "type": "disabled",
                 }
...
-                    bedrockReq.AdditionalModelRequestFields = orderedFields
+                    if bedrockReq.AdditionalModelRequestFields == nil {
+                        bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+                    }
+                    for k, v := range orderedFields {
+                        bedrockReq.AdditionalModelRequestFields[k] = v
+                    }

349-366: Round-trip break: disabled reasoning_config won’t parse due to map type mismatch. Reader expects map[string]interface{} (Line 351), but writers currently use map[string]string in some paths (see Line 457-459).

core/providers/vertex/vertex.go (2)

518-543: Potential panic: reqBody == nil can lead to nil requestBody map then assignment. If ToAnthropicChatRequest returns (nil, nil), you marshal nil, unmarshal into requestBody (likely nil), then do requestBody["anthropic_version"]=... (Line 537-539) which panics on a nil map.

Proposed fix:

 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
-     reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("chat completion input is not provided")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)

847-871: Same nil-body hazard in ResponsesStream Anthropic path.

Proposed fix:

 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
-     reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("responses input is not provided")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
🧹 Nitpick comments (3)
core/providers/cohere/chat.go (1)

564-683: ToBifrostChatMessage: good nil-safety; consider setting ToolCall.Type when present.
You populate tool calls but don’t set Type (unlike other converters that set "function"). If downstream expects Type, you may want to fill it for parity.

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

702-710: Safe message extraction is good; consider non-empty fallback. message := "" can lead to blank "message" on the wire even when you had other error context.

Suggestion: keep "unknown error" when bifrostErr.Error.Message is empty.

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

121-133: Reasoning extraction is safe; consider trimming trailing newline. You always append "\n" (Line 132-133), which makes assistantMessage.Reasoning end with a newline.

Possible tweak: strings.TrimSuffix(reasoningText, "\n") before assigning.

Also applies to: 157-163

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 785a11f and ef2308c.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/providers/openai/openai.go
  • core/providers/perplexity/chat.go
  • plugins/maxim/main.go
  • core/providers/anthropic/types.go
  • core/providers/anthropic/anthropic.go
  • core/providers/cohere/types.go
  • core/schemas/mux.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • transports/bifrost-http/handlers/inference.go
  • plugins/jsonparser/utils.go
  • core/providers/bedrock/bedrock.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:

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

Applied to files:

  • framework/streaming/chat.go
  • core/providers/cohere/cohere.go
  • core/providers/bedrock/utils.go
  • plugins/semanticcache/utils.go
  • core/schemas/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/openai/utils.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/errors.go
  • core/providers/openai/types.go
  • core/providers/bedrock/chat.go
  • core/providers/vertex/vertex.go
  • core/providers/azure/azure.go
  • core/providers/bedrock/types.go
  • core/providers/anthropic/chat.go
  • core/providers/openai/chat.go
  • core/providers/cohere/chat.go
  • core/schemas/chatcompletions.go
📚 Learning: 2025-12-08T11:50:35.996Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/openai/utils.go:26-45
Timestamp: 2025-12-08T11:50:35.996Z
Learning: OpenAI does not support the `ReasoningDetails` field. `ReasoningDetails` is a Bifrost-internal field and should not be included when converting messages to OpenAI format in the `ConvertBifrostMessagesToOpenAIMessages` function in `core/providers/openai/utils.go`.

Applied to files:

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

Applied to files:

  • core/providers/openai/utils.go
  • core/providers/openai/types.go
  • core/providers/openai/chat.go
🧬 Code graph analysis (11)
framework/streaming/chat.go (3)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/cohere/cohere.go (2)
core/providers/cohere/chat.go (1)
  • ToCohereChatCompletionRequest (12-204)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/utils.go (4)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/gemini/types.go (2)
  • Role (13-13)
  • Content (922-930)
core/providers/anthropic/errors.go (1)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/bedrock/chat.go (1)
core/schemas/chatcompletions.go (8)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • BifrostChatResponse (26-41)
  • BifrostResponseChoice (704-712)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/providers/vertex/vertex.go (5)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/anthropic/chat.go (6)
core/schemas/chatcompletions.go (6)
  • BifrostChatRequest (12-19)
  • ChatAssistantMessage (626-632)
  • BifrostResponseChoice (704-712)
  • ChatNonStreamResponseChoice (745-748)
  • BifrostLLMUsage (812-819)
  • ChatStreamResponseChoice (751-753)
core/providers/anthropic/types.go (5)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • AnthropicContentBlock (147-159)
  • AnthropicMessageResponse (273-282)
  • AnthropicStreamDelta (347-355)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/bifrost.go (5)
  • BifrostResponseExtraFields (285-294)
  • RequestType (83-83)
  • ChatCompletionRequest (89-89)
  • Anthropic (37-37)
  • BifrostError (354-363)
core/schemas/provider.go (1)
  • Provider (280-307)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/chatcompletions.go (3)
core/providers/openai/types.go (1)
  • Alias (78-78)
core/schemas/bifrost.go (2)
  • Alias (384-384)
  • Alias (401-401)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
⏰ 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 (32)
framework/streaming/chat.go (2)

44-54: LGTM! Reasoning accumulation follows established patterns.

The reasoning string accumulation correctly mirrors the existing refusal handling pattern with proper nil checks and initialization.


55-105: Reasoning details accumulation logic is correct.

The index-based matching for streaming reasoning details is appropriate. A few observations:

  1. The O(n×m) search is acceptable given typical chunk sizes are small.
  2. Text, Summary, and Data are correctly accumulated (concatenated).
  3. Signature and Type are correctly overwritten (non-cumulative fields).

One minor consideration: if a provider ever sends duplicate indices within the same chunk, only the first match would be updated. This edge case seems unlikely in practice.

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

9-37: LGTM! Nil safety is properly handled.

The function correctly guards against nil bifrostErr and nil bifrostErr.Error, addressing the previous review concern. The defensive approach ensures robustness across all BifrostError sources.

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

5-24: LGTM! Conversion correctly maps OpenAI messages to Bifrost format.

The function properly handles the embedded OpenAIChatAssistantMessage with nil checks. As confirmed in learnings, ReasoningDetails is intentionally omitted since OpenAI doesn't support this field.


26-45: LGTM! Bidirectional conversion maintains consistency.

The reverse conversion correctly mirrors the OpenAI→Bifrost function. ReasoningDetails is appropriately excluded when converting to OpenAI format since it's a Bifrost-internal field. Based on learnings, this is the correct behavior.

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

14-14: LGTM! Message conversion now uses dedicated utility.

Using ConvertOpenAIMessagesToBifrostMessages ensures consistent field mapping and proper handling of assistant-specific fields during the OpenAI→Bifrost conversion.


28-28: LGTM! Symmetrical conversion for outbound messages.

The use of ConvertBifrostMessagesToOpenAIMessages maintains consistency with the inbound conversion path.


62-65: LGTM! Reasoning effort handling updated for new schema structure.

The triple nil-check chain (Reasoning != nil && Effort != nil && value == "minimal") correctly guards against nil pointer dereferences when accessing the nested reasoning configuration. The "minimal""low" transformation ensures compatibility with providers that don't support the "minimal" effort level.

ui/lib/types/logs.ts (1)

122-133: LGTM! Reasoning fields correctly added to ChatMessage.

The new reasoning and reasoning_details fields align with the backend schema changes. The ReasoningDetails interface correctly models the three reasoning detail types (reasoning.summary, reasoning.encrypted, reasoning.text) with appropriate optional fields for each variant.

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

42-44: Type change (Messages []OpenAIMessage) needs call-site audit for custom message fields.
This changes the request surface from schemas.ChatMessage to OpenAIMessage, so any code that relied on schemas.ChatMessage JSON shape (especially embedded assistant/tool fields) must now go through the converters consistently.


74-101: MarshalJSON behavior is consistent and nil-safe.
Omitting reasoning while emitting reasoning_effort from Reasoning.Effort is implemented cleanly; the nil checks on r.Reasoning / r.Reasoning.Effort are correct.

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

13-17: Good: converter now returns error for nil request/input.
This aligns with the broader “conversion functions return errors” refactor and avoids downstream panics.


99-117: Reasoning validation looks correct, but effort semantics should be clarified.
Right now “enabled” is effectively “effort != none” (including nil effort), and Anthropic requires reasoning.max_tokens. If effort is optional in your API, consider documenting that max_tokens is mandatory for Anthropic whenever reasoning is present and not explicitly "none".


710-750: Good: signature deltas now round-trip back to Anthropic SSE.
The added branch that maps Delta.ReasoningDetails[0].Signature to AnthropicStreamDeltaTypeSignature addresses the “signature-only updates get dropped” issue.

core/schemas/chatcompletions.go (2)

634-667: ReasoningDetails synthesis is sensible; watch index stability.
Synthesizing a single reasoning_details[0] from reasoning is fine, but if providers emit multi-part reasoning in-stream, ensure indices stay stable across chunks/messages (especially when appending provider-native details).


765-794: Delta ReasoningDetails synthesis mirrors message behavior (good).
Same note as above re: index stability and merging when both ReasoningDetails and Reasoning might appear across partial chunks.

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

12-15: Nil-input guard is fine as defensive code.
Not rehashing the error-string wording given prior discussion.


219-226: Per-message conversion path looks cleaner.
Using CohereMessage.ToBifrostChatMessage() reduces duplicated conversion logic and should help keep reasoning/tool-call handling consistent.


247-261: Reasoning ↔ Thinking mapping seems consistent.
Disabled maps to effort: "none", enabled maps to effort: "auto" plus max_tokens when available.

core/providers/cohere/cohere.go (2)

279-286: Good: error propagation from request conversion.
Passing func() (any, error) { return ToCohereChatCompletionRequest(request) } ensures conversion failures don’t get silently marshaled.


331-338: Good: stream request conversion now fails fast and sets Stream=true only on success.
This avoids partially-initialized streaming requests.

core/schemas/responses.go (1)

323-326: Helpful interoperability note (gpt-oss vs other OpenAI models).
This kind of “shape difference” comment is valuable given the custom parsing expectations elsewhere.

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

15-18: Nil Params short-circuit is consistent with “params are optional” behavior.

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

662-666: Output mapping moved to the top reads clearer and avoids duplication.

core/providers/vertex/vertex.go (1)

283-291: ChatCompletion Anthropic path: nil body handled correctly.

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

39-41: Good: errors from parameter conversion are now surfaced.


139-143: Single text block collapse is a nice normalization.


335-389: Streaming reasoning delta: safe “emit only when meaningful” behavior. The early return avoids producing (nil, nil, false) as a “response”.

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

190-201: LGTM!

The conversion from Anthropic's Thinking structure to Bifrost's Reasoning structure is correctly implemented. The mapping preserves the enabled/disabled state and budget tokens appropriately.


296-303: LGTM!

Good defensive check for nil input with clear error message. The signature change to return an error tuple aligns with the broader refactoring to improve error handling across conversion functions.


402-403: LGTM!

Return statement correctly updated to match the new (*AnthropicMessageRequest, error) signature.


1428-1438: LGTM!

Good defensive nil check before accessing nested Error.Message field. This prevents potential nil pointer dereferences when bifrostErr.Error is nil.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (2)
core/providers/bedrock/utils.go (1)

15-52: Fix critical data loss when both reasoning and additionalModelRequestFieldPaths are present.

At line 97 in utils.go (and identically at line 480 in responses.go), the assignment bedrockReq.AdditionalModelRequestFields = orderedFields completely replaces the map, discarding the reasoning_config that was set at lines 38 or 47. When both parameters are present, the reasoning configuration is lost.

Merge reasoning_config from the existing map into orderedFields before assignment, or preserve it by iterating through orderedFields while keeping the reasoning entry intact.

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

637-677: Streaming reasoning/signature deltas must use chunk.Index instead of hardcoding 0.

In ToBifrostChatCompletionStream:

  • thinking deltas emit ReasoningDetails.Index: 0 (line 641)
  • signature deltas emit ReasoningDetails.Index: 0 (line 668)

However, chunk.Index (*int) is available and the downstream accumulation logic in framework/streaming/chat.go:63 matches reasoning details by Index to merge or update them. Hardcoding all indices to 0 causes all reasoning details to merge into a single entry, breaking the separation between thinking and signature blocks.

Fix: Use *chunk.Index instead of 0 when available. The signature field and TypeText type choice are correct by design.

♻️ Duplicate comments (9)
plugins/semanticcache/utils.go (1)

669-671: Critical: Add Reasoning.MaxTokens extraction to Chat API cache metadata.

The previous review comment flagging the missing MaxTokens extraction was marked as addressed, but the fix was only applied to extractResponsesParametersToMetadata (lines 752-754), not here in extractChatParametersToMetadata. Without MaxTokens in the cache metadata, Chat API requests with identical Effort but different MaxTokens will incorrectly hit the same cache entry, leading to wrong responses being served from cache.

Apply this diff to add MaxTokens extraction alongside Effort:

 	if params.Reasoning != nil {
 		if params.Reasoning.Effort != nil {
 			metadata["reasoning_effort"] = *params.Reasoning.Effort
 		}
+		if params.Reasoning.MaxTokens != nil {
+			metadata["reasoning_max_tokens"] = *params.Reasoning.MaxTokens
+		}
 	}
core/providers/bedrock/responses.go (1)

452-472: Fix type mismatch for disabled reasoning config and handle the map overwrite issue.

Two critical issues:

  1. Type mismatch: Line 457 creates map[string]string{"type": "disabled"}, but the enabled case at line 466 uses map[string]any. This inconsistency causes a type assertion failure in ToBifrostResponsesRequest (line 351) which expects map[string]interface{} for the disabled case.

  2. Silent data loss: Lines 480-482 completely replace bedrockReq.AdditionalModelRequestFields = orderedFields when additionalModelRequestFieldPaths exists. This discards the reasoning_config set at lines 457/466 unless the external paths explicitly include it—resulting in reasoning configuration being silently dropped.

Fix the type mismatch by changing line 457 to:

-			bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+			bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{

For the overwrite issue, merge the reasoning config into orderedFields instead of replacing the entire map:

 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
     if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
+        for key, value := range bedrockReq.AdditionalModelRequestFields {
+            orderedFields[key] = value
+        }
         bedrockReq.AdditionalModelRequestFields = orderedFields
     }
 }
core/schemas/chatcompletions.go (1)

165-226: reasoning_effort + reasoning strict exclusivity may be too breaking for a deprecation window.

Right now you return an error if both are present. If you want a smoother migration, prefer reasoning when both exist and ignore reasoning_effort (optionally add a warning elsewhere). This was raised earlier—calling out in case the stack intends “backward compatible”.

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

325-344: Responses reasoning enablement semantics still differ from Chat converter (Effort==nil case).

Here, Effort==nil falls into “disabled”, while core/providers/anthropic/chat.go treats Effort==nil as enabled unless explicitly "none" (and then requires MaxTokens). This will make identical Reasoning payloads behave differently across endpoints.

This was raised previously; please ensure the stack didn’t reintroduce it and unify both converters (ideally via a shared helper) so:

  • Disabled only when Effort != nil && *Effort == "none".
  • Otherwise: require MaxTokens and validate >= MinimumReasoningMaxTokens, then set thinking enabled.
Confirm intended Anthropic “thinking” enable/disable semantics for Bifrost Reasoning:
- Should Effort==nil with MaxTokens set enable thinking?
- Should Effort==nil with MaxTokens nil be treated as disabled or as an error?
core/providers/vertex/vertex.go (3)

518-525: Fail fast when reqBody == nil in ChatCompletionStream (otherwise you can marshal null and later panic).

Right now, reqBody == nil skips assignments but still gets marshalled/unmarshalled; later code writes into requestBody (map) which can be nil.

 				reqBody, err := anthropic.ToAnthropicChatRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-					reqBody.Stream = schemas.Ptr(true)
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic request conversion returned nil")
+				}
+				reqBody.Model = deployment
+				reqBody.Stream = schemas.Ptr(true)

689-695: Fail fast when reqBody == nil in Responses (same “marshal null” risk).

 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic request conversion returned nil")
+				}
+				reqBody.Model = deployment

847-854: Fail fast when reqBody == nil in ResponsesStream (same “marshal null” risk).

 				reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 				if err != nil {
 					return nil, err
 				}
-				if reqBody != nil {
-					reqBody.Model = deployment
-					reqBody.Stream = schemas.Ptr(true)
-				}
+				if reqBody == nil {
+					return nil, fmt.Errorf("anthropic request conversion returned nil")
+				}
+				reqBody.Model = deployment
+				reqBody.Stream = schemas.Ptr(true)
core/providers/openai/types.go (1)

42-44: Enforce mutual exclusivity of embedded message structs (prevents silent JSON field clobbering).

OpenAIMessage can embed both *schemas.ChatToolMessage and *OpenAIChatAssistantMessage; if both are set, marshaling flattens/overwrites fields unpredictably. This was already raised—worth enforcing via a Validate() method (called before marshaling) or a custom MarshalJSON on OpenAIMessage.

Also applies to: 56-65

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

413-426: CacheCreationInputTokens should not populate completion cached tokens.

You map response.Usage.CacheCreationInputTokens into CompletionTokensDetails.CachedTokens (Line 421-423). Cache creation is an input/prompt-side concern in Anthropic usage naming, so this likely belongs on PromptTokensDetails (or a dedicated field if you want to preserve “creation vs read” semantics). Also note this should align with the reverse mapping logic in ToAnthropicChatResponse to avoid round-trip drift across the stack.

🧹 Nitpick comments (4)
core/providers/cohere/chat.go (1)

131-142: Clarify override behavior: ExtraParams["thinking"] overwrites Reasoning.

Lines 131-142 unconditionally set cohereReq.Thinking from ExtraParams["thinking"], which can silently override the Reasoning-based thinking set in lines 104-121. A past review comment noted this issue and was marked as addressed, but the current code still allows the override.

If this is intentional (ExtraParams takes precedence), consider adding a comment explaining the precedence. Otherwise, add a guard:

if cohereReq.Thinking == nil {
    if thinkingParam, ok := schemas.SafeExtractFromMap(...); ok {
        // ...
    }
}

Based on learnings: A past review flagged that ExtraParams["thinking"] can overwrite Reasoning-based thinking. The issue was marked as addressed but the override behavior persists.

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

10-10: Consider extracting MinimumReasoningMaxTokens to a shared constants package.

The constant is imported from the Anthropic provider but used by multiple providers (Bedrock, Cohere per past reviews), creating cross-provider dependencies. Moving this to a shared location like core/constants or core/schemas would reduce coupling.

This is a minor architectural improvement and not blocking.

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

207-217: Only inject “thinking” blocks for assistant-role messages (and avoid empty details).

Right now, any message with ChatAssistantMessage.ReasoningDetails gets converted into Anthropic thinking blocks, regardless of msg.Role. Consider tightening to msg.Role == schemas.ChatMessageRoleAssistant and skipping details where both Text and Signature are nil/empty to avoid emitting invalid/empty content blocks.


505-520: Tool-call argument unmarshal should be interface{} (not forced object).

var input map[string]interface{} will discard/zero-out if arguments aren’t a JSON object. Safer to mirror the request-side handling:

-   var input map[string]interface{}
+   var input interface{}
    if toolCall.Function.Arguments != "" {
        if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &input); err != nil {
-           input = map[string]interface{}{}
+           input = map[string]interface{}{}
        }
    } else {
        input = map[string]interface{}{}
    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 785a11f and ef2308c.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/providers/cohere/types.go
  • core/providers/anthropic/types.go
  • core/providers/openai/openai.go
  • plugins/jsonparser/utils.go
  • framework/streaming/chat.go
  • core/schemas/mux.go
  • core/providers/perplexity/chat.go
  • 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:

  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • plugins/semanticcache/utils.go
  • transports/bifrost-http/handlers/inference.go
  • core/providers/bedrock/bedrock.go
  • core/providers/anthropic/responses.go
  • core/providers/anthropic/errors.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/chat.go
  • core/providers/openai/chat.go
  • core/providers/cohere/chat.go
  • ui/lib/types/logs.ts
  • core/providers/openai/utils.go
  • core/providers/cohere/cohere.go
  • core/providers/bedrock/responses.go
  • core/providers/bedrock/utils.go
  • core/providers/anthropic/anthropic.go
  • core/providers/vertex/vertex.go
  • core/providers/azure/azure.go
  • core/providers/openai/types.go
  • core/schemas/chatcompletions.go
  • core/providers/anthropic/chat.go
  • plugins/maxim/main.go
🧠 Learnings (4)
📚 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:

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

Applied to files:

  • core/providers/openai/chat.go
  • core/providers/openai/utils.go
  • core/providers/openai/types.go
📚 Learning: 2025-12-08T11:50:35.996Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/openai/utils.go:26-45
Timestamp: 2025-12-08T11:50:35.996Z
Learning: OpenAI does not support the `ReasoningDetails` field. `ReasoningDetails` is a Bifrost-internal field and should not be included when converting messages to OpenAI format in the `ConvertBifrostMessagesToOpenAIMessages` function in `core/providers/openai/utils.go`.

Applied to files:

  • core/providers/openai/utils.go
🧬 Code graph analysis (14)
ui/app/workspace/logs/views/logChatMessageView.tsx (2)
ui/lib/utils/validation.ts (2)
  • isJson (539-546)
  • cleanJson (548-558)
ui/app/workspace/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/bedrock/bedrock.go (3)
core/providers/utils/utils.go (1)
  • CreateBifrostChatCompletionChunkResponse (687-716)
core/schemas/bifrost.go (1)
  • ChatCompletionStreamRequest (90-90)
core/schemas/models.go (1)
  • Model (109-129)
core/providers/anthropic/responses.go (3)
core/schemas/responses.go (2)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesRequest (32-39)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (4)
  • AnthropicMessageRequest (40-59)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
  • AnthropicStreamError (397-400)
core/providers/cohere/chat.go (3)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatNonStreamResponseChoice (745-748)
core/providers/cohere/types.go (5)
  • CohereChatRequest (14-32)
  • CohereThinking (171-174)
  • ThinkingTypeDisabled (181-181)
  • ThinkingTypeEnabled (180-180)
  • CohereMessage (51-57)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/openai/utils.go (3)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (4)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (233-238)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/vertex/vertex.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/openai/types.go (2)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/chatcompletions.go (10)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • Alias (189-189)
  • Alias (643-643)
  • Alias (770-770)
  • Alias (860-860)
  • ChatReasoning (223-226)
core/schemas/chatcompletions.go (1)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
core/providers/anthropic/chat.go (4)
core/schemas/chatcompletions.go (10)
  • BifrostChatRequest (12-19)
  • ChatAssistantMessage (626-632)
  • ChatAssistantMessageToolCall (686-692)
  • ChatReasoningDetails (723-730)
  • ChatMessageContent (524-527)
  • ChatMessage (469-478)
  • BifrostResponseChoice (704-712)
  • ChatNonStreamResponseChoice (745-748)
  • BifrostLLMUsage (812-819)
  • ChatStreamResponseChoice (751-753)
core/providers/anthropic/types.go (5)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicContentBlock (147-159)
  • AnthropicContentBlockTypeThinking (143-143)
core/schemas/bifrost.go (1)
  • Anthropic (37-37)
core/schemas/utils.go (1)
  • Ptr (16-18)
plugins/maxim/main.go (1)
core/providers/gemini/types.go (1)
  • Type (778-778)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (44)
plugins/semanticcache/utils.go (1)

748-758: LGTM! Correct reasoning parameter extraction.

The extraction of Reasoning.Effort, Reasoning.MaxTokens, and Reasoning.Summary is correctly implemented with proper nil-safety checks. This ensures that the cache key for the Responses API properly differentiates requests with different reasoning configurations.

plugins/maxim/main.go (1)

472-490: Consider using nil instead of &"" for absent Code and Type fields

The nil-safety fix for bifrostErr.Error is solid. However, passing Code: &code and Type: &errorType will always create non-nil pointers (even to empty strings when no upstream value exists). If maxim-go's GenerationError.Code and GenerationError.Type are pointer fields meant to represent optional values, nil better conveys "unset" than a pointer to an empty string.

If the fields should truly be nil when absent, consider:

-                code := ""
-                errorType := ""
+                var code *string
+                var errorType *string
                 if bifrostErr.Error != nil {
                     message = bifrostErr.Error.Message
                     if bifrostErr.Error.Code != nil {
-                        code = *bifrostErr.Error.Code
+                        code = bifrostErr.Error.Code
                     }
                     if bifrostErr.Error.Type != nil {
-                        errorType = *bifrostErr.Error.Type
+                        errorType = bifrostErr.Error.Type
                     }
                 }
                 genErr := logging.GenerationError{
                     Message: message,
-                    Code:    &code,
-                    Type:    &errorType,
+                    Code:    code,
+                    Type:    errorType,
                 }
core/providers/cohere/cohere.go (2)

282-283: LGTM! Function signature change aligns with error-handling pattern.

The update to ToCohereChatCompletionRequest to return (*CohereChatRequest, error) properly propagates conversion errors. The call site correctly handles the error return value.


332-337: LGTM! Streaming path correctly handles error return.

The streaming conversion properly checks for errors from ToCohereChatCompletionRequest and returns early if an error occurs, maintaining the error-handling contract.

transports/bifrost-http/handlers/inference.go (3)

77-77: LGTM! "reasoning" field correctly added to known fields.

The addition of "reasoning": true to chatParamsKnownFields ensures the new reasoning parameter is recognized and won't be treated as an extra parameter.


476-478: LGTM! Pre-initialization prevents nil pointer issues.

Initializing ChatParameters before unmarshalling ensures the custom UnmarshalJSON on ChatParameters has a non-nil target. This aligns with the approach documented in lines 169-199.


169-199: The bifrostAlias approach is correctly applied and necessary.

The alias prevents infinite recursion: when ChatRequest implements UnmarshalJSON, calling sonic.Unmarshal directly on *ChatRequest would recursively invoke UnmarshalJSON. The alias (which is just BifrostParams without the method) allows sonic to unmarshal the fields without triggering the custom method. The three separate unmarshal calls are intentional—each handles a distinct part of the JSON structure (BifrostParams fields, Messages array, and ChatParameters with its own custom unmarshaller). No changes needed.

ui/lib/types/logs.ts (2)

122-124: LGTM! Reasoning fields added to ChatMessage.

The addition of reasoning?: string and reasoning_details?: ReasoningDetails[] aligns with the PR's goal of introducing reasoning support. The fields follow the existing optional pattern used in the interface.


126-133: Note: ReasoningDetails interface uses optional fields instead of discriminated union.

As noted in past review comments, a discriminated union would provide stronger type safety (ensuring type: "reasoning.summary" requires the summary field). However, the maintainer has indicated this approach is acceptable for now.

Based on learnings: A past review suggested using a discriminated union for type safety, but the maintainer indicated the current approach is acceptable.

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

12-15: LGTM! Error return added for validation failures.

Changing the signature to return (*CohereChatRequest, error) enables proper error propagation for invalid reasoning configurations and nil inputs.


564-683: LGTM! Per-message conversion with comprehensive nil-safety.

The new ToBifrostChatMessage() method properly handles content types (string/blocks), reasoning details, and tool calls with appropriate nil checks throughout. The reasoning text concatenation (lines 606-609) correctly joins multiple thinking blocks with newlines.

ui/app/workspace/logs/views/logChatMessageView.tsx (1)

73-94: LGTM! Terminology updated from "thought" to "reasoning".

The changes consistently rename message.thought to message.reasoning and update the UI label from "Thought Process" to "Reasoning". This aligns with the backend changes introducing the new reasoning field structure.

core/providers/bedrock/bedrock.go (1)

771-772: LGTM! UUID generation addresses missing Bedrock stream identifier.

The comment clearly explains that Bedrock doesn't provide a unique stream identifier, so generating a UUID locally is the correct approach. The generated ID is used consistently throughout the streaming path (lines 859, 882).

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

63-65: LGTM! Reasoning effort mapping updated to nested structure.

The updated code correctly accesses request.ChatParameters.Reasoning.Effort (nested field) with proper nil checks, and preserves the "minimal" → "low" mapping for non-OpenAI providers.


14-14: Both ConvertOpenAIMessagesToBifrostMessages and ConvertBifrostMessagesToOpenAIMessages utility functions are present and properly implemented in core/providers/openai/utils.go. The functions are correctly used in lines 14 and 28 of chat.go.

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

9-37: LGTM! Error conversion with comprehensive nil-safety.

The new ToAnthropicChatCompletionError function properly handles nil inputs at multiple levels:

  • Returns nil if bifrostErr is nil
  • Safely extracts bifrostErr.Type with default empty string
  • Guards against nil bifrostErr.Error (lines 23-25) before accessing nested Message field

This addresses the concern raised in past reviews about nil pointer dereferences.

Based on learnings: A past review flagged the need for nil-safety when accessing bifrostErr.Error. The current implementation includes the necessary guard.

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

5-45: LGTM! Conversion functions correctly handle OpenAI-Bifrost message mapping.

The converters correctly map the supported fields (Name, Role, Content, ChatToolMessage, and nested assistant message fields) between OpenAI and Bifrost formats. The code appropriately excludes ReasoningDetails from both conversions, consistent with learnings that OpenAI doesn't support this field.

Based on learnings, OpenAI doesn't support the ReasoningDetails field, so it should not be included in these conversion functions.

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

303-311: LGTM! Error propagation from ToAnthropicChatRequest is correct.

The non-streaming chat completion path now correctly propagates the error return from ToAnthropicChatRequest, aligning with the updated function signature.


354-368: LGTM! Streaming path correctly handles conversion errors.

The streaming path properly captures and returns errors from ToAnthropicChatRequest before enabling streaming, ensuring errors surface before streaming begins.


655-663: LGTM! Error propagation from ToAnthropicResponsesRequest is correct.

The responses path now correctly propagates the error return from ToAnthropicResponsesRequest, consistent with the updated function signature.


704-718: LGTM! Streaming responses path correctly handles conversion errors.

The streaming responses path properly captures and returns errors from ToAnthropicResponsesRequest before enabling streaming.

core/providers/azure/azure.go (4)

348-368: LGTM! ChatCompletion correctly handles conversion errors with nil safety.

The function properly captures the error from ToAnthropicChatRequest, returns early on error, and checks reqBody != nil before setting the Model field, preventing nil pointer dereferences.


448-465: LGTM! ChatCompletionStream correctly handles conversion errors with nil safety.

The streaming path properly handles errors from ToAnthropicChatRequest and includes a nil check on reqBody before accessing its fields.


526-550: LGTM! Responses correctly handles conversion errors with nil safety.

The function properly captures the error from ToAnthropicResponsesRequest, returns early on error, and checks reqBody != nil before setting the Model field.


627-644: LGTM! ResponsesStream correctly handles conversion errors with nil safety.

The streaming responses path properly handles errors from ToAnthropicResponsesRequest and includes a nil check on reqBody before accessing its fields.

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

702-710: LGTM! Safe error message extraction.

The code now safely checks that bifrostErr.Error != nil before accessing the Message field, preventing nil pointer dereferences.

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

349-367: LGTM! Reasoning config extraction from Bedrock to Bifrost is correct.

The code properly extracts reasoning_config from AdditionalModelRequestFields, handles both enabled and disabled cases, and safely extracts the type and budget_tokens values.


662-666: LGTM! Output conversion is correctly positioned.

The code now converts response.Output to bifrostResp.Output at the top of the function, ensuring the output is populated in the final response.

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

39-41: LGTM! Proper error handling for parameter conversion.

The function now correctly handles and propagates errors from convertChatParameters, ensuring invalid parameters are caught early.


59-60: LGTM! Reasoning content handling is safe and correct.

The code properly:

  • Initializes reasoning state variables
  • Checks ReasoningContent != nil and ReasoningText != nil before accessing fields (lines 122-125), preventing nil pointer dereferences
  • Accumulates reasoning details with proper type and index
  • Attaches reasoning data to the assistant message when present

Also applies to: 121-133, 157-163


139-142: LGTM! Content block collapsing optimization.

The code correctly collapses a single text content block to a simple string, reducing unnecessary nesting while preserving the content.


335-388: LGTM! Streaming reasoning content delta handling is correct.

The streaming path properly:

  • Checks if both Text is empty and Signature is nil, returning early to avoid nil responses (lines 340-342)
  • Constructs appropriate responses for text deltas (lines 345-365)
  • Constructs appropriate responses for signature deltas (lines 366-386)
  • Ensures ReasoningDetails are properly populated in the delta

This prevents the nil response pointer issue mentioned in past reviews.

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

296-299: Good: error-returning converter + nil input guard.

Signature change to (*AnthropicMessageRequest, error) and the bifrostReq == nil check make downstream error handling much safer.

Also applies to: 402-402


1428-1432: Good: nil-safe stream error message extraction.

Avoids nil deref when bifrostErr.Error is missing and still emits a valid SSE error payload.

Also applies to: 1438-1439

core/providers/vertex/vertex.go (1)

283-289: Good: propagate conversion errors from ToAnthropicChatRequest.

This aligns Vertex with the new error-returning Anthropic converters and avoids silent bad requests.

core/schemas/chatcompletions.go (4)

627-667: Good: synthesize reasoning_details when only reasoning is provided.

This keeps downstream consumers consistent (they can always look at reasoning_details) while remaining tolerant to providers that only emit a single reasoning string.

Also applies to: 757-794


714-730: LGTM: reasoning details typing covers summary/text/encrypted (+ signature).

This seems like a solid cross-provider shape (and matches the UI typings you referenced).


835-836: LGTM: image_tokens addition is safe (pointer + omitempty).


186-220: [rewritten comment]
[classification tag]

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

444-446: Schema difference is intentional and correct per AWS API.

The code properly implements AWS Bedrock's intentional structural difference: non-delta ReasoningContent uses a nested reasoningText wrapper, while streaming ContentBlockDelta.ReasoningContent emits the structure directly with text and signature fields. Each type correctly matches its corresponding AWS API response, and unmarshaling will succeed without issues.

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

13-16: Nil/empty input guard is good; ensure callers handle the new error return.

ToAnthropicChatRequest now returns (..., error); please ensure all call sites in the PR stack (#978 + downstack #979/#999) were updated to propagate/handle this (no silent ignored errors).


726-750: Good: signature deltas now round-trip to Anthropic SSE.

The added ReasoningDetails[0].SignatureAnthropicStreamDeltaTypeSignature handling fixes the “signature-only deltas dropped” path.


850-863: Rename to ToAnthropicChatStreamError looks fine; keep SSE formatting consistent.

Looks consistent with the other SSE formatting. Please just sanity-check any external references to the old name in the PR stack (#978/#979/#999).


99-117: The stringly-typed effort check could use typed constants for clarity. The code at lines 99-117 correctly checks bifrostReq.Params.Reasoning, which is already normalized from the deprecated reasoning_effort field by ChatParameters.UnmarshalJSON() during deserialization. No backward-compatibility risk here.

However, replace the *bifrostReq.Params.Reasoning.Effort == "none" string comparison with type-safe constants for the effort values ("none", "minimal", "low", "medium", "high") to avoid potential casing/typo issues.

@TejasGhatte TejasGhatte force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from ef2308c to d592b74 Compare December 12, 2025 09:53
@TejasGhatte TejasGhatte force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 14a7a70 to 0f9320e Compare December 12, 2025 09:53
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 (2)
framework/streaming/chat.go (1)

34-105: Avoid pointer aliasing in ReasoningDetails accumulation (copy strings before storing/mutating).

existingReasoningDetail.Text = reasoningDetail.Text (and similar for Summary/Data/Signature) stores pointers from the chunk directly into the final accumulated message, and later you mutate them via *existing += *incoming. Prefer taking ownership by copying into new pointers (like you do with bifrost.Ptr(...) for Refusal/Reasoning).

Example pattern:

- existingReasoningDetail.Text = reasoningDetail.Text
+ v := *reasoningDetail.Text
+ existingReasoningDetail.Text = bifrost.Ptr(v)

Apply similarly for Summary, Data, and when appending a brand-new reasoningDetail (deep-copy its pointer fields before appending).

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

7-17: Message converters likely drop ReasoningDetails (reasoning signatures won’t round-trip).
ConvertOpenAIMessagesToBifrostMessages / ConvertBifrostMessagesToOpenAIMessages appear to map Reasoning but not ReasoningDetails, so any per-chunk details/signatures may be lost when requests/responses pass through OpenAI provider plumbing.

Suggested fix (in core/providers/openai/utils.go), extend the assistant-message mapping to include ReasoningDetails:

- Reasoning:   message.OpenAIChatAssistantMessage.Reasoning,
+ Reasoning:        message.OpenAIChatAssistantMessage.Reasoning,
+ ReasoningDetails: message.OpenAIChatAssistantMessage.ReasoningDetails,

(and the reverse direction similarly).

Also applies to: 20-33

♻️ Duplicate comments (16)
core/providers/cohere/chat.go (1)

104-121: Critical: Still using Anthropic's minimum token constant for Cohere validation.

Lines 112-113 validate reasoning.max_tokens >= anthropic.MinimumReasoningMaxTokens (1024), but Cohere's thinking feature accepts token budgets as low as 500. This validation will incorrectly reject valid Cohere requests with token budgets between 500-1023.

Define a Cohere-specific constant and use it for validation:

+const (
+	CohereMinimumReasoningMaxTokens = 500
+)
+
 func ToCohereChatCompletionRequest(bifrostReq *schemas.BifrostChatRequest) (*CohereChatRequest, error) {
 	...
-			} else if *bifrostReq.Params.Reasoning.MaxTokens < anthropic.MinimumReasoningMaxTokens {
-				return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", anthropic.MinimumReasoningMaxTokens)
+			} else if *bifrostReq.Params.Reasoning.MaxTokens < CohereMinimumReasoningMaxTokens {
+				return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", CohereMinimumReasoningMaxTokens)
core/providers/bedrock/utils.go (2)

10-10: Avoid importing Anthropic provider just for MinimumReasoningMaxTokens (shared constant).

core/providers/bedrock/utils.go imports core/providers/anthropic only for MinimumReasoningMaxTokens. Consider moving that constant to a shared package (e.g., core/schemas or core/constants) to reduce cross-provider coupling.

Also applies to: 42-46


32-52: additionalModelRequestFieldPaths overwrites reasoning_config (merge instead of replace).

You set bedrockReq.AdditionalModelRequestFields["reasoning_config"] = ..., but later do bedrockReq.AdditionalModelRequestFields = orderedFields, which can silently discard reasoning_config.

 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
   if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-    bedrockReq.AdditionalModelRequestFields = orderedFields
+    if bedrockReq.AdditionalModelRequestFields == nil {
+      bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+    }
+    for k, v := range orderedFields {
+      bedrockReq.AdditionalModelRequestFields[k] = v
+    }
   }
 }

Also applies to: 94-99

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

108-165: Bedrock reasoning types likely don’t match AWS union semantics (missing redactedContent, delta “one-of” enforcement).

If AWS returns redactedContent (or emits signature as a separate delta member), current structs may fail to unmarshal or may misrepresent stream events. Consider modeling reasoningContent as a union that supports at least:

  • reasoningText: { text, signature? }
  • redactedContent: <base64 bytes>
    and for deltas, ensure only one member is present per event.
AWS Bedrock Converse API JSON schema for message.content[].reasoningContent (reasoningText vs redactedContent)
AWS Bedrock ConverseStream JSON schema for contentBlockDelta.delta.reasoningContent (union members per delta event)

Also applies to: 444-447

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

190-201: Don’t set Params.Reasoning to enabled when BudgetTokens is nil (invalid intermediate state).

Right now Thinking.Type=="enabled" can produce Reasoning{Effort:"auto", MaxTokens:nil}. Prefer only setting reasoning when BudgetTokens != nil (and otherwise leave it unset or set none).

 if request.Thinking != nil {
-  if request.Thinking.Type == "enabled" {
+  if request.Thinking.Type == "enabled" && request.Thinking.BudgetTokens != nil {
     params.Reasoning = &schemas.ResponsesParametersReasoning{
       Effort:    schemas.Ptr("auto"),
       MaxTokens: request.Thinking.BudgetTokens,
     }
-  } else {
+  } else if request.Thinking.Type != "enabled" {
     params.Reasoning = &schemas.ResponsesParametersReasoning{
       Effort: schemas.Ptr("none"),
     }
   }
 }

325-344: Unify reasoning enablement rules (Effort==nil currently disables reasoning).

ToAnthropicResponsesRequest currently enables reasoning only when Effort != nil && *Effort != "none". If callers send reasoning: { max_tokens: ... } (no effort), this will disable thinking. Consider enabling when (Effort == nil || *Effort != "none") and then requiring/validating MaxTokens in the enabled case (to align with the general “explicit none disables” convention).

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

56-65: Enforce mutual exclusivity for embedded message variants (tool vs assistant) to avoid invalid JSON.

Relying on a comment is risky because both embedded pointers flatten into the same JSON object. Consider adding func (m OpenAIMessage) Validate() error (or custom MarshalJSON) that errors if both are non-nil, and call it in request building paths.


74-101: Add nil-receiver guard to OpenAIChatRequest.MarshalJSON; verify reasoning_effort mapping matches current OpenAI API.

 func (r *OpenAIChatRequest) MarshalJSON() ([]byte, error) {
+  if r == nil {
+    return []byte("null"), nil
+  }
   type Alias OpenAIChatRequest
   ...
 }
OpenAI Chat Completions request parameter "reasoning_effort": supported values and whether "reasoning" object should be omitted from requests
core/providers/vertex/vertex.go (3)

512-544: Fix nil reqBody panic in Vertex Anthropic streaming request builder.
If reqBody is nil, requestBody becomes nil and Line 537 will panic when indexing the map.

@@
 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
-     reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
@@
 var requestBody map[string]interface{}
 if err := sonic.Unmarshal(reqBytes, &requestBody); err != nil {
     return nil, fmt.Errorf("failed to unmarshal request body: %w", err)
 }
+ if requestBody == nil {
+     return nil, fmt.Errorf("failed to build anthropic request body")
+ }

679-710: Fix nil reqBody panic in Vertex Anthropic Responses request builder.
Same pattern: marshal/unmarshal nil => nil map => Line 704 panics.

@@
 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
@@
 if err := sonic.Unmarshal(reqBytes, &requestBody); err != nil {
     return nil, fmt.Errorf("failed to unmarshal request body: %w", err)
 }
+ if requestBody == nil {
+     return nil, fmt.Errorf("failed to build anthropic request body")
+ }

831-872: Fix nil reqBody panic in Vertex Anthropic ResponsesStream request builder.
Same issue as above; additionally sets Stream.

@@
 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
-     reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
@@
 if err := sonic.Unmarshal(reqBytes, &requestBody); err != nil {
     return nil, fmt.Errorf("failed to unmarshal request body: %w", err)
 }
+ if requestBody == nil {
+     return nil, fmt.Errorf("failed to build anthropic request body")
+ }
core/providers/bedrock/responses.go (1)

452-482: Don’t type-mismatch / overwrite reasoning_config in Bedrock request construction.

  1. Disabled case uses map[string]string, while other paths (and typical readers) expect map[string]any.
  2. additionalModelRequestFieldPaths overwrites AdditionalModelRequestFields, dropping the earlier reasoning_config.
@@
 if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+    bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
         "type": "disabled",
     }
 } else {
@@
 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
     if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-        bedrockReq.AdditionalModelRequestFields = orderedFields
+        if bedrockReq.AdditionalModelRequestFields == nil {
+            bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+        }
+        for k, v := range orderedFields {
+            bedrockReq.AdditionalModelRequestFields[k] = v
+        }
     }
 }
core/schemas/chatcompletions.go (2)

186-220: Avoid hard-breaking clients that send both reasoning_effort and reasoning during migration.
Line 207-209 rejects requests with both fields, which can break “dual-write” clients during rollout; prefer reasoning and ignore reasoning_effort (optionally log a deprecation warning).

@@
- if aux.Alias != nil && aux.Alias.Reasoning != nil && aux.ReasoningEffort != nil {
-     return fmt.Errorf("both reasoning_effort and reasoning fields cannot be present at the same time")
- }
+ if aux.Alias != nil && aux.Alias.Reasoning != nil && aux.ReasoningEffort != nil {
+     // Prefer new field; ignore deprecated field for a non-breaking migration.
+     // TODO: emit deprecation warning via project logger, if available at this layer.
+     aux.ReasoningEffort = nil
+ }
@@
  if aux.ReasoningEffort != nil {
      cp.Reasoning = &ChatReasoning{
          Effort: aux.ReasoningEffort,
      }
  }

480-521: Enforce ChatMessage embedded-type exclusivity + clear stale pointers.
This unmarshaller can set both cm.ChatToolMessage and cm.ChatAssistantMessage (Line 504-518), violating the stated invariant; also, it never resets embedded pointers to nil, which can leak prior state if cm is reused.

 func (cm *ChatMessage) UnmarshalJSON(data []byte) error {
+    // Clear embedded pointers to avoid stale state on reuse.
+    cm.ChatToolMessage = nil
+    cm.ChatAssistantMessage = nil
@@
     if toolMsg.ToolCallID != nil {
         cm.ChatToolMessage = (*ChatToolMessage)(&toolMsg)
     }
@@
     if assistantMsg.Refusal != nil || assistantMsg.Reasoning != nil ||
         len(assistantMsg.ReasoningDetails) > 0 || len(assistantMsg.Annotations) > 0 ||
         len(assistantMsg.ToolCalls) > 0 {
         cm.ChatAssistantMessage = &assistantMsg
     }
+
+    if cm.ChatToolMessage != nil && cm.ChatAssistantMessage != nil {
+        return fmt.Errorf("chat message cannot be both tool and assistant message")
+    }
 
     return nil
 }
core/providers/anthropic/chat.go (2)

207-216: Filter ReasoningDetails before converting to Anthropic thinking blocks.
Currently every entry becomes a thinking block even if Type is reasoning.summary/reasoning.encrypted or Text is nil/empty, which can yield invalid/empty blocks.

 if msg.ChatAssistantMessage != nil && msg.ChatAssistantMessage.ReasoningDetails != nil {
     for _, reasoningDetail := range msg.ChatAssistantMessage.ReasoningDetails {
-        content = append(content, AnthropicContentBlock{
-            Type:      AnthropicContentBlockTypeThinking,
-            Signature: reasoningDetail.Signature,
-            Thinking:  reasoningDetail.Text,
-        })
+        if reasoningDetail.Type != schemas.BifrostReasoningDetailsTypeText ||
+            reasoningDetail.Text == nil || *reasoningDetail.Text == "" {
+            continue
+        }
+        content = append(content, AnthropicContentBlock{
+            Type:      AnthropicContentBlockTypeThinking,
+            Signature: reasoningDetail.Signature,
+            Thinking:  reasoningDetail.Text,
+        })
     }
 }

373-387: Don’t drop signature-only reasoning details in non-stream responses.
assistantMessage.ReasoningDetails is only assigned when reasoningText != "", so signature-only blocks never surface.

- if reasoningText != "" {
+ if len(reasoningDetails) > 0 {
     if assistantMessage == nil {
         assistantMessage = &schemas.ChatAssistantMessage{}
     }
-    assistantMessage.Reasoning = &reasoningText
     assistantMessage.ReasoningDetails = reasoningDetails
+    if reasoningText != "" {
+        assistantMessage.Reasoning = &reasoningText
+    }
 }
🧹 Nitpick comments (2)
core/providers/azure/azure.go (1)

348-366: Prefer failing fast if Anthropic request conversion returns nil (avoid sending null JSON).
Now that anthropic.ToAnthropic*Request returns (req, error), it’s reasonable to treat req == nil as an error to prevent accidental null request bodies.

 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
     return nil, err
 }
- if reqBody != nil {
-     reqBody.Model = deployment
-     reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+     return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
 return reqBody, nil

(Apply the same pattern in the non-stream ChatCompletion branch and in Responses / ResponsesStream.)

Also applies to: 448-462, 526-548, 620-642

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

59-63: Optional: trim trailing newline in aggregated assistantMessage.Reasoning.
Right now reasoningText ends with "\n", which can be noisy for consumers/UI.

Also applies to: 157-163

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef2308c and d592b74.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • core/providers/anthropic/types.go
  • core/providers/openai/utils.go
  • ui/lib/types/logs.ts
  • core/providers/bedrock/bedrock.go
  • plugins/semanticcache/utils.go
  • core/providers/openai/openai.go
  • core/schemas/responses.go
  • core/providers/anthropic/anthropic.go
  • plugins/jsonparser/utils.go
  • core/schemas/mux.go
  • core/providers/cohere/cohere.go
  • transports/bifrost-http/handlers/inference.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:

  • plugins/maxim/main.go
  • core/providers/bedrock/responses.go
  • core/providers/cohere/types.go
  • core/providers/perplexity/chat.go
  • core/providers/bedrock/utils.go
  • core/providers/azure/azure.go
  • core/providers/openai/types.go
  • framework/streaming/chat.go
  • core/providers/anthropic/errors.go
  • core/providers/anthropic/responses.go
  • core/providers/cohere/chat.go
  • core/providers/bedrock/chat.go
  • core/providers/openai/chat.go
  • core/providers/bedrock/types.go
  • core/providers/anthropic/chat.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/vertex/vertex.go
  • core/schemas/chatcompletions.go
🧠 Learnings (2)
📚 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:

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

Applied to files:

  • core/providers/openai/types.go
  • core/providers/openai/chat.go
🧬 Code graph analysis (10)
core/providers/bedrock/responses.go (4)
core/schemas/utils.go (3)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (233-238)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/openai/types.go (1)
core/schemas/chatcompletions.go (6)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • ChatReasoning (223-226)
framework/streaming/chat.go (3)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/anthropic/errors.go (2)
ui/lib/types/logs.ts (1)
  • BifrostError (233-239)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/cohere/chat.go (3)
core/schemas/chatcompletions.go (13)
  • BifrostChatRequest (12-19)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • ChatReasoningDetails (723-730)
  • ChatContentBlock (590-597)
  • ChatAssistantMessageToolCall (686-692)
  • ChatContentBlockTypeText (582-582)
  • ChatContentBlockTypeImage (583-583)
  • ChatInputImage (600-603)
  • ChatMessageContent (524-527)
  • ChatAssistantMessageToolCallFunction (695-698)
  • ChatAssistantMessage (626-632)
  • ChatMessageRole (457-457)
core/providers/cohere/types.go (5)
  • CohereChatRequest (14-32)
  • CohereThinking (171-174)
  • ThinkingTypeDisabled (181-181)
  • ThinkingTypeEnabled (180-180)
  • CohereMessage (51-57)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/bedrock/chat.go (1)
core/schemas/chatcompletions.go (6)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/utils.go (1)
  • Ptr (51-53)
ui/app/workspace/logs/views/logChatMessageView.tsx (2)
ui/lib/utils/validation.ts (2)
  • isJson (539-546)
  • cleanJson (548-558)
ui/app/workspace/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
core/schemas/chatcompletions.go (4)
core/providers/openai/types.go (1)
  • Alias (78-78)
core/schemas/bifrost.go (2)
  • Alias (384-384)
  • Alias (401-401)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (2)
  • Role (13-13)
  • Content (922-930)
⏰ 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). (6)
  • 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 (16)
ui/app/workspace/logs/views/logChatMessageView.tsx (1)

73-94: No changes needed. The code is correct as written.

ChatMessage.reasoning is typed as reasoning?: string; in the type definition, so the guard message.reasoning && ensures it's a non-empty string before use. The call to isJson(message.reasoning) is safe since it expects a string and receives one. The fallback rendering of {message.reasoning} is also safe since it's guaranteed to be a string. No type coercion or additional guards are required.

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

536-538: The Thinking field addition is correct and properly integrated.

The new Thinking field in CohereStreamContent follows the same pattern as the existing Text field and aligns with the thinking support already present in CohereContentBlock (line 153). The processing code in chat.go (lines 431-444) explicitly handles the optional Thinking field, mapping it to the unified Reasoning field in BifrostChatResponse for streaming reasoning deltas. This is consistent with how other providers like Anthropic implement thinking/reasoning streaming support. The test configuration indicates reasoning scenarios are covered.

core/providers/cohere/chat.go (5)

219-226: LGTM: Clean refactor to per-message conversion.

The message conversion now properly delegates to ToBifrostChatMessage(), which provides better encapsulation and reusability across streaming and non-streaming paths.


246-261: LGTM: Reasoning conversion correctly maps Cohere thinking to Bifrost schema.

The conversion appropriately handles both disabled (effort: "none") and enabled ("effort: "auto" + token budget) states. The mapping is consistent with Cohere's binary thinking model.


350-354: LGTM: Response conversion uses refactored message method.

Consistent with the per-message conversion approach introduced in ToBifrostChatRequest.


431-454: LGTM: Thinking deltas properly propagate reasoning content and details.

The streaming path now correctly emits both Reasoning (text) and ReasoningDetails (with type and metadata) for thinking content deltas, maintaining consistency with the non-streaming response structure.


564-682: LGTM: Comprehensive per-message conversion with proper nil-safety and reasoning support.

The implementation correctly handles:

  • Content conversion across string, block, and thinking formats
  • Tool call extraction with defensive nil checks
  • Reasoning details aggregation with proper indexing
  • Optimized single text block to string conversion

The thinking block concatenation (lines 606-609) properly prepends newlines only when needed, addressing the trailing newline concern from previous review.

plugins/maxim/main.go (1)

472-490: Nil-safety fix looks good; consider omitting Code/Type when unknown (nil vs empty string pointer).

Right now Code: &code / Type: &errorType will be non-nil even when empty; if Maxim treats nil differently from "", consider keeping these nil unless populated.

- code := ""
- errorType := ""
+ var code *string
+ var errorType *string
  if bifrostErr.Error != nil {
    message = bifrostErr.Error.Message
    if bifrostErr.Error.Code != nil {
-     code = *bifrostErr.Error.Code
+     code = bifrostErr.Error.Code
    }
    if bifrostErr.Error.Type != nil {
-     errorType = *bifrostErr.Error.Type
+     errorType = bifrostErr.Error.Type
    }
  }
  genErr := logging.GenerationError{
    Message: message,
-   Code:    &code,
-   Type:    &errorType,
+   Code:    code,
+   Type:    errorType,
  }
core/providers/anthropic/errors.go (1)

9-37: Robust Anthropic error conversion with nil-safety.

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

1422-1440: Nil-safe streaming error conversion looks good.

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

61-75: Effort normalization looks good (nested nil-guards).
The Reasoning != nil && Reasoning.Effort != nil guard prevents panics and keeps the normalization localized.

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

38-42: Good: propagate convertChatParameters errors.
Prevents silently emitting malformed Bedrock requests.


121-163: Reasoning extraction + streaming delta handling look solid (nil-guard + no spurious nil response).
The ReasoningText == nil guard and the “only return response when text/signature exists” logic reduce NPEs and awkward (nil, nil, false) returns.

Also applies to: 335-389

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

11-117: Reasoning budget validation and “disabled” handling look correct.
The MinimumReasoningMaxTokens check and explicit disabled mode match the expected Anthropic “thinking” semantics.

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

220-223: The reasoning tokens mapping is correct; Perplexity API does not return reasoning details or signatures.

Perplexity's response structure includes only ReasoningTokens (a token count), not reasoning details or signatures like other providers (Anthropic, Bedrock, Cohere). The code at lines 220–223 correctly handles the available field. No changes needed.


30-36: No issues found. The implementation is correct for Perplexity's API capabilities.

Perplexity's API does not support a reasoning_max_tokens parameter—it only exposes reasoning_effort (low/medium/high) for its Sonar deep-research models, with max_tokens controlling overall completion length. The mapping of "minimal" to "low" is appropriate for Perplexity's supported values. The code is consistent with how responses.go handles the same transformation and correctly implements the nested Reasoning.Effort structure from the unified schema.

Likely an incorrect or invalid review comment.

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)
transports/bifrost-http/handlers/inference.go (1)

62-89: Back-compat bug: keep reasoning_effort as a known field to avoid leaking it into extra_params.
Even if schemas.ChatParameters still supports the deprecated reasoning_effort, removing it from chatParamsKnownFields means requests using the old field will likely get it echoed into ChatParameters.ExtraParams (and potentially forwarded downstream), causing duplicates/undefined behavior during the deprecation window (and across this PR stack).

 var chatParamsKnownFields = map[string]bool{
@@
 	"prompt_cache_key":      true,
+	"reasoning_effort":      true, // deprecated but still accepted; keep out of extra_params
 	"reasoning":             true,
 	"response_format":       true,
@@
 }
core/providers/azure/azure.go (1)

351-365: Don’t allow Anthropic converter to return (nil, nil)—fail fast with an error.
In multiple places, reqBody == nil currently falls through and returns reqBody, nil, which risks sending null JSON to the provider.

Suggested fix (apply in all 4 call sites):

 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
   return nil, err
 }
- if reqBody != nil {
-   reqBody.Model = deployment
- }
- return reqBody, nil
+ if reqBody == nil {
+   return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ return reqBody, nil

(and similarly for ToAnthropicResponsesRequest, and the streaming cases where you also set reqBody.Stream = schemas.Ptr(true)).

Also applies to: 448-463, 529-547, 627-642

core/providers/bedrock/bedrock.go (1)

771-887: Fix inconsistent ChunkIndex semantics between intermediate and final chunks.

The schema defines ChunkIndex as starting from 0 for streaming responses (per core/schemas/bifrost.go), but the current implementation is inconsistent: intermediate chunks correctly use 0-based indexing (ChunkIndex = chunkIndex), while the final chunk uses CreateBifrostChatCompletionChunkResponse which internally adds 1 to the index (ChunkIndex = currentChunkIndex + 1). This causes the final chunk to have a 1-based index while earlier chunks are 0-based.

The root cause is that CreateBifrostChatCompletionChunkResponse unconditionally increments the chunk index. Either this helper should not add 1, or the final response should pass chunkIndex - 1 to maintain consistency. The current suggested fix of adding + 1 to intermediate chunks would break the 0-based semantic expected by the schema.

♻️ Duplicate comments (18)
core/providers/bedrock/types.go (1)

88-113: Bedrock reasoning content/delta still doesn’t model AWS union semantics (and omits redacted/encrypted variant).
This matches the earlier concern: Bedrock “reasoningContent” (especially deltas) is a union where each event carries one of {text, signature, redactedContent}, and responses may include provider-redacted/encrypted content. Current structs still look too “combined” and still lack redactedContent.

Also applies to: 157-165, 442-447

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

56-65: Embedded pointer flattening needs an invariant guard (tool vs assistant).
If both embedded pointers are ever non-nil, JSON output becomes ambiguous/overlapping; a small Validate() (or constructor-only pattern) would make this safer.


74-101: Make MarshalJSON nil-receiver safe.
Calling MarshalJSON on a nil *OpenAIChatRequest can panic (the method dereferences r when checking r.Reasoning). A short early-return keeps it robust.

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

1-12: Decouple MinimumReasoningMaxTokens from core/providers/anthropic.
core/providers/bedrock/utils.go imports core/providers/anthropic solely for anthropic.MinimumReasoningMaxTokens, creating cross-provider coupling. Consider moving the constant to a shared package (e.g., core/schemas or a core/constants) and importing that instead.

Also applies to: 42-46


32-52: Preserve reasoning_config: avoid overwriting AdditionalModelRequestFields and use consistent map types.

  1. bedrockReq.AdditionalModelRequestFields = orderedFields (Line 97) can wipe the earlier reasoning_config. Merge instead.
  2. Prefer map[string]any for the reasoning_config object even in the disabled case for consistency.
- bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+ bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
    "type": "disabled",
  }

  if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
    if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-     bedrockReq.AdditionalModelRequestFields = orderedFields
+     if bedrockReq.AdditionalModelRequestFields == nil {
+       bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+     }
+     for k, v := range orderedFields {
+       bedrockReq.AdditionalModelRequestFields[k] = v
+     }
    }
  }

Also applies to: 73-99

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

347-367: Fix reasoning_config map type + prevent AdditionalModelRequestFields overwrite (drops reasoning).

  • Use map[string]any for disabled reasoning_config (Line 457) to match how you read it (Line 351).
  • Merge additionalModelRequestFieldPaths into existing AdditionalModelRequestFields instead of replacing it (Line 480).
- bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+ bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
    "type": "disabled",
  }

  if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
    if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-     bedrockReq.AdditionalModelRequestFields = orderedFields
+     if bedrockReq.AdditionalModelRequestFields == nil {
+       bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+     }
+     for k, v := range orderedFields {
+       bedrockReq.AdditionalModelRequestFields[k] = v
+     }
    }
  }

Also applies to: 452-482

core/providers/vertex/vertex.go (3)

689-709: Same nil-body hazard in Responses Anthropic path.
Even if it “shouldn’t happen”, this is a cheap guardrail to keep behavior consistent with ChatCompletion.

                 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
                 if err != nil {
                     return nil, err
                 }
-                if reqBody != nil {
-                    reqBody.Model = deployment
-                }
+                if reqBody == nil {
+                    return nil, fmt.Errorf("anthropic responses request conversion returned nil")
+                }
+                reqBody.Model = deployment

847-871: Same nil-body hazard in ResponsesStream Anthropic path.

                 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
                 if err != nil {
                     return nil, err
                 }
-                if reqBody != nil {
-                    reqBody.Model = deployment
-                    reqBody.Stream = schemas.Ptr(true)
-                }
+                if reqBody == nil {
+                    return nil, fmt.Errorf("anthropic responses request conversion returned nil")
+                }
+                reqBody.Model = deployment
+                reqBody.Stream = schemas.Ptr(true)

518-525: Fail fast on nil reqBody in Anthropic streaming path (don’t marshal null).
Right now if the converter ever returns (nil, nil), you’ll send a null request body to Vertex, which is harder to debug and can mask upstream issues.

                 reqBody, err := anthropic.ToAnthropicChatRequest(request)
                 if err != nil {
                     return nil, err
                 }
-                if reqBody != nil {
-                    reqBody.Model = deployment
-                    reqBody.Stream = schemas.Ptr(true)
-                }
+                if reqBody == nil {
+                    return nil, fmt.Errorf("anthropic chat request conversion returned nil")
+                }
+                reqBody.Model = deployment
+                reqBody.Stream = schemas.Ptr(true)
core/providers/cohere/chat.go (2)

12-15: Signature change looks fine; error message is acceptable as a defensive guard.


104-121: Use Cohere-specific minimum token budget (don’t reuse Anthropic’s 1024).
This validation can reject valid Cohere requests (and the error message would be misleading).

+const CohereMinimumThinkingTokenBudget = 500 // TODO: confirm from Cohere docs
...
-               } else if *bifrostReq.Params.Reasoning.MaxTokens < anthropic.MinimumReasoningMaxTokens {
-                   return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", anthropic.MinimumReasoningMaxTokens)
+               } else if *bifrostReq.Params.Reasoning.MaxTokens < CohereMinimumThinkingTokenBudget {
+                   return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", CohereMinimumThinkingTokenBudget)
                } else {
core/providers/anthropic/responses.go (2)

190-201: Don’t set Params.Reasoning to enabled when BudgetTokens is nil.
This can create an invalid intermediate Bifrost request (Effort="auto" but MaxTokens=nil).

-   if request.Thinking != nil {
-       if request.Thinking.Type == "enabled" {
+   if request.Thinking != nil {
+       if request.Thinking.Type == "enabled" && request.Thinking.BudgetTokens != nil {
            params.Reasoning = &schemas.ResponsesParametersReasoning{
                Effort:    schemas.Ptr("auto"),
                MaxTokens: request.Thinking.BudgetTokens,
            }
-       } else {
+       } else {
            params.Reasoning = &schemas.ResponsesParametersReasoning{
                Effort: schemas.Ptr("none"),
            }
        }
    }

325-344: Unify reasoning enable/disable semantics with the chat converter.
Current condition enables reasoning only when Effort != nil && Effort != "none", so Effort==nil + MaxTokens!=nil becomes “disabled” here, which can diverge from ToAnthropicChatRequest. Consider enabling when MaxTokens != nil as well, and disabling only when Effort=="none".

-       if bifrostReq.Params.Reasoning != nil {
-           if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort != "none" {
-               if bifrostReq.Params.Reasoning.MaxTokens != nil {
+       if bifrostReq.Params.Reasoning != nil {
+           // Disable only when explicitly requested.
+           if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
+               anthropicReq.Thinking = &AnthropicThinking{Type: "disabled"}
+           } else if bifrostReq.Params.Reasoning.MaxTokens != nil {
                    if *bifrostReq.Params.Reasoning.MaxTokens < MinimumReasoningMaxTokens {
                        return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", MinimumReasoningMaxTokens)
-                   } else {
-                       anthropicReq.Thinking = &AnthropicThinking{
-                           Type:         "enabled",
-                           BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
-                       }
-                   }
-               } else {
-                   return nil, fmt.Errorf("reasoning.max_tokens is required for reasoning")
-               }
-           } else {
-               anthropicReq.Thinking = &AnthropicThinking{
-                   Type: "disabled",
-               }
+               }
+               anthropicReq.Thinking = &AnthropicThinking{
+                   Type:         "enabled",
+                   BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
+               }
+           } else {
+               return nil, fmt.Errorf("reasoning.max_tokens is required for reasoning")
            }
        }
core/schemas/chatcompletions.go (2)

186-220: Confirm the “both fields present” behavior is the intended contract.
Right now requests that send both reasoning_effort and reasoning will hard-fail. If you want a non-breaking migration, prefer reasoning and ignore reasoning_effort (optionally with a warning).


480-521: Enforce exclusivity: ChatMessage.UnmarshalJSON can set both embedded pointers.
This violates the “only one embedded struct non-nil” invariant and can cause ambiguous marshaling later.

 func (cm *ChatMessage) UnmarshalJSON(data []byte) error {
@@
   if toolMsg.ToolCallID != nil {
     cm.ChatToolMessage = (*ChatToolMessage)(&toolMsg)
   }
@@
   if assistantMsg.Refusal != nil || assistantMsg.Reasoning != nil ||
     len(assistantMsg.ReasoningDetails) > 0 || len(assistantMsg.Annotations) > 0 ||
     len(assistantMsg.ToolCalls) > 0 {
     cm.ChatAssistantMessage = &assistantMsg
   }
+
+  // Enforce invariant: only one embedded pointer may be set
+  if cm.ChatToolMessage != nil && cm.ChatAssistantMessage != nil {
+    return fmt.Errorf("chat message cannot be both tool message (tool_call_id) and assistant message (tool_calls/reasoning/etc.)")
+  }
 
   return nil
 }
core/providers/anthropic/chat.go (3)

207-216: Don’t emit invalid/empty Anthropic thinking blocks from ReasoningDetails.
Right now every ReasoningDetails entry becomes a thinking block even if Type isn’t text or Text is nil/empty, which can produce empty/invalid Anthropic content blocks.

 			// First add reasoning details
 			if msg.ChatAssistantMessage != nil && msg.ChatAssistantMessage.ReasoningDetails != nil {
 				for _, reasoningDetail := range msg.ChatAssistantMessage.ReasoningDetails {
+					if reasoningDetail.Type != schemas.BifrostReasoningDetailsTypeText ||
+						reasoningDetail.Text == nil ||
+						*reasoningDetail.Text == "" {
+						continue
+					}
 					content = append(content, AnthropicContentBlock{
 						Type:      AnthropicContentBlockTypeThinking,
 						Signature: reasoningDetail.Signature,
 						Thinking:  reasoningDetail.Text,
 					})
 				}
 			}

343-387: Signature-only reasoning details are dropped in non-stream Anthropic → Bifrost responses.
assistantMessage.ReasoningDetails = reasoningDetails is only set when reasoningText != "", so signature-only blocks never make it into the Bifrost response.

-	if reasoningText != "" {
+	if len(reasoningDetails) > 0 {
 		if assistantMessage == nil {
 			assistantMessage = &schemas.ChatAssistantMessage{}
 		}
-		assistantMessage.Reasoning = &reasoningText
 		assistantMessage.ReasoningDetails = reasoningDetails
+		if reasoningText != "" {
+			assistantMessage.Reasoning = &reasoningText
+		}
 	}

413-426: Cache token attribution looks wrong (cache creation/read are input-side, not completion-side).
CacheCreationInputTokens and CacheReadInputTokens are “input” tokens; mapping CacheCreationInputTokens into CompletionTokensDetails.CachedTokens is likely semantically incorrect and can break billing/telemetry semantics.

 	if response.Usage != nil {
 		bifrostResponse.Usage = &schemas.BifrostLLMUsage{
 			PromptTokens: response.Usage.InputTokens,
 			PromptTokensDetails: &schemas.ChatPromptTokensDetails{
-				CachedTokens: response.Usage.CacheReadInputTokens,
+				// Both are input-side cache tokens.
+				CachedTokens: response.Usage.CacheReadInputTokens + response.Usage.CacheCreationInputTokens,
 			},
 			CompletionTokens: response.Usage.OutputTokens,
-			CompletionTokensDetails: &schemas.ChatCompletionTokensDetails{
-				CachedTokens: response.Usage.CacheCreationInputTokens,
-			},
 			TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
 		}
 	}
#!/bin/bash
set -euo pipefail

# Locate other providers’ cache token mapping for consistency.
rg -n --type go 'Cache(Read|Creation)InputTokens|PromptTokensDetails|CompletionTokensDetails' -C2 core/providers
🧹 Nitpick comments (10)
transports/bifrost-http/handlers/inference.go (1)

267-288: Consider logging (at debug) when skipping unknown fields due to unmarshal errors.
Right now extractExtraParams silently drops fields it can’t unmarshal (continue), which can make debugging provider-specific params painful.

framework/streaming/chat.go (1)

34-105: Consider deep-copying pointer fields when merging ReasoningDetails to avoid aliasing.
When initializing existingReasoningDetail.Text/Summary/Data/Signature from reasoningDetail.*, prefer copying the string value into a new pointer (similar to how Refusal/Reasoning are handled), instead of assigning the incoming pointer directly.

Example pattern:

- existingReasoningDetail.Text = reasoningDetail.Text
+ existingReasoningDetail.Text = bifrost.Ptr(*reasoningDetail.Text)

(similarly for Summary, Data, Signature).

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

59-61: Avoid trailing newline in aggregated assistantMessage.Reasoning.
reasoningText += ... + "\n" leaves a trailing newline in the final value. Consider joining without a tail newline for more stable UI/log rendering.

-                       reasoningText += contentBlock.ReasoningContent.ReasoningText.Text + "\n"
+                       if len(reasoningText) > 0 {
+                               reasoningText += "\n"
+                       }
+                       reasoningText += contentBlock.ReasoningContent.ReasoningText.Text

Also applies to: 121-133, 157-163

core/providers/vertex/vertex.go (1)

283-295: Good: handle ToAnthropicChatRequest error and set deployment model.
One tweak: if reqBody == nil, the error message should reflect “conversion returned nil” (not “input not provided”), since request is already present here.

-               if reqBody == nil {
-                   return nil, fmt.Errorf("chat completion input is not provided")
-               }
+               if reqBody == nil {
+                   return nil, fmt.Errorf("anthropic chat request conversion returned nil")
+               }
core/providers/cohere/chat.go (3)

247-261: Only set Reasoning.MaxTokens when TokenBudget is present.
Right now you can emit Effort="auto" with MaxTokens=nil, which is an odd “enabled but unspecified” state.

        } else {
            bifrostReq.Params.Reasoning = &schemas.ChatReasoning{
                Effort: schemas.Ptr("auto"),
            }
-           if req.Thinking.TokenBudget != nil {
-               bifrostReq.Params.Reasoning.MaxTokens = req.Thinking.TokenBudget
-           }
+           bifrostReq.Params.Reasoning.MaxTokens = req.Thinking.TokenBudget
        }

413-454: Stream deltas: consider emitting ReasoningDetails consistently (ToolPlanDelta too).
content.thinking emits both Reasoning and ReasoningDetails, but tool_plan emits only Reasoning. If downstream/UI logic prefers reasoning_details, you’ll lose structure here.

 case StreamEventToolPlanDelta:
   if chunk.Delta != nil && chunk.Delta.Message != nil && chunk.Delta.Message.ToolPlan != nil {
     streamResponse := &schemas.BifrostChatResponse{
       Object: "chat.completion.chunk",
       Choices: []schemas.BifrostResponseChoice{{
         Index: 0,
         ChatStreamResponseChoice: &schemas.ChatStreamResponseChoice{
           Delta: &schemas.ChatStreamResponseChoiceDelta{
             Reasoning: chunk.Delta.Message.ToolPlan,
+            ReasoningDetails: []schemas.ChatReasoningDetails{{
+              Index: 0,
+              Type:  schemas.BifrostReasoningDetailsTypeText,
+              Text:  chunk.Delta.Message.ToolPlan,
+            }},
           },
         },
       }},
     }

Also applies to: 466-467


564-683: Set toolCall.Type for generated tool calls to match the rest of the API.
schemas.ChatAssistantMessageToolCall.Type is populated elsewhere as "function"; leaving it nil can cause downstream assumptions/tests to diverge.

             bifrostToolCall := schemas.ChatAssistantMessageToolCall{
                 Index: uint16(len(toolCalls)),
+                Type:  schemas.Ptr("function"),
                 ID:    toolCall.ID,
                 Function: schemas.ChatAssistantMessageToolCallFunction{
                     Name:      functionName,
                     Arguments: functionArguments,
                 },
             }
core/providers/anthropic/chat.go (3)

99-117: Reasoning validation: consider clearer/typed errors and compatibility expectations.
Returning raw fmt.Errorf(...) here may undermine the “error handling standardization” target branch goals; consider routing through a provider-typed error helper (if available) so callers can map to consistent HTTP status / error codes.


637-677: Streaming: ReasoningDetails.Index is always 0 (may break multi-block thinking/signature).
If Anthropic emits multiple thinking/signature blocks over time, hardcoding index 0 can collapse distinct blocks.


742-749: Streaming signature deltas: don’t assume signature is always at ReasoningDetails[0].
If multiple reasoning details exist, emitting the first can miss the actual signature update.

-			} else if len(delta.ReasoningDetails) > 0 && delta.ReasoningDetails[0].Signature != nil && *delta.ReasoningDetails[0].Signature != "" {
+			} else if len(delta.ReasoningDetails) > 0 {
 				// Handle signature deltas
+				var sig *string
+				for i := range delta.ReasoningDetails {
+					if delta.ReasoningDetails[i].Signature != nil && *delta.ReasoningDetails[i].Signature != "" {
+						sig = delta.ReasoningDetails[i].Signature
+						break
+					}
+				}
+				if sig == nil {
+					break
+				}
 				streamResp.Type = "content_block_delta"
 				streamResp.Index = &choice.Index
 				streamResp.Delta = &AnthropicStreamDelta{
 					Type:      AnthropicStreamDeltaTypeSignature,
-					Signature: delta.ReasoningDetails[0].Signature,
+					Signature: sig,
 				}
-			} else if len(delta.ToolCalls) > 0 {
+			} else if len(delta.ToolCalls) > 0 {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef2308c and d592b74.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/schemas/responses.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • core/providers/anthropic/anthropic.go
  • core/providers/perplexity/chat.go
  • plugins/maxim/main.go
  • core/providers/openai/openai.go
  • core/providers/openai/utils.go
  • plugins/semanticcache/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:

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

Applied to files:

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

Applied to files:

  • core/providers/openai/chat.go
  • core/providers/openai/types.go
🧬 Code graph analysis (13)
core/providers/cohere/cohere.go (2)
core/providers/cohere/chat.go (1)
  • ToCohereChatCompletionRequest (12-204)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/vertex/vertex.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
framework/streaming/chat.go (4)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (1)
  • Type (778-778)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/openai/types.go (1)
core/schemas/chatcompletions.go (10)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • Alias (189-189)
  • Alias (643-643)
  • Alias (770-770)
  • Alias (860-860)
  • ChatReasoning (223-226)
core/providers/bedrock/utils.go (3)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-403)
core/providers/anthropic/errors.go (1)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
core/providers/bedrock/bedrock.go (3)
core/providers/utils/utils.go (1)
  • CreateBifrostChatCompletionChunkResponse (687-716)
core/schemas/bifrost.go (1)
  • ChatCompletionStreamRequest (90-90)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/chatcompletions.go (4)
core/providers/openai/types.go (1)
  • Alias (78-78)
core/schemas/bifrost.go (2)
  • Alias (384-384)
  • Alias (401-401)
ui/lib/types/logs.ts (3)
  • ChatMessage (115-124)
  • ChatMessageContent (113-113)
  • ReasoningDetails (126-133)
core/providers/gemini/types.go (2)
  • Role (13-13)
  • Content (922-930)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (3)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (233-238)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/bedrock/chat.go (1)
core/schemas/chatcompletions.go (4)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
core/providers/anthropic/responses.go (3)
core/schemas/responses.go (2)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesRequest (32-39)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (3)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicThinking (65-68)
  • AnthropicStreamError (397-400)
⏰ 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). (12)
  • 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 (31)
core/providers/openai/chat.go (3)

8-17: Good: avoids leaking OpenAI-specific message struct into Bifrost schema.
Switching to ConvertOpenAIMessagesToBifrostMessages(request.Messages) keeps schemas.BifrostChatRequest.Input consistent with the rest of the core pipeline and preserves assistant/tool metadata via ChatAssistantMessage / ChatToolMessage.


21-33: Good: request conversion now round-trips via the new OpenAI message wrapper.
ConvertBifrostMessagesToOpenAIMessages(bifrostReq.Input) should reduce shape drift as more provider-specific assistant fields get added.


62-65: Sanity-check provider intent for "minimal" → "low" coercion.
This looks correct for the “OpenAI-shaped request to non-OpenAI provider” paths (Gemini/Mistral/Vertex) where "minimal" may be unsupported, but worth confirming it shouldn’t happen for true OpenAI outbound requests.

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

534-539: Looks right: streaming content can now carry thinking deltas.
Adding Thinking *string aligns with CohereContentBlockTypeThinking and keeps the struct flexible for both content-start and delta events.

transports/bifrost-http/handlers/inference.go (2)

169-199: Good: custom unmarshal makes embedded + custom-unmarshal fields deterministic under sonic.
This is a pragmatic fix given ChatParameters’ custom unmarshalling and the embedded BifrostParams field flattening.


475-482: Good: ensures ChatParameters is non-nil before unmarshal paths rely on it.
Helps avoid nil edge cases when ChatParameters.UnmarshalJSON expects a receiver.

ui/lib/types/logs.ts (1)

115-133: Type additions are consistent with the backend “reasoning” rollout.
No concerns with adding reasoning / reasoning_details to ChatMessage for log surfacing.

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

11-15: Good: shared minimum makes reasoning budget validation consistent across providers.
Exposing MinimumReasoningMaxTokens is a clean way to centralize the Anthropic “thinking budget” floor used elsewhere in this stack.


39-59: No concerns with OutputFormat wiring here.
Field presence is gated by the beta header comment, and the change is non-invasive.

core/providers/cohere/cohere.go (2)

269-287: Good: don’t swallow request-conversion errors.
Propagating ToCohereChatCompletionRequest errors prevents hard-to-debug nil/partial requests later in the call chain.


327-339: Good: only mark Stream=true after successful request build.
Prevents sending a malformed streaming request body when conversion fails.

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

9-48: LGTM: defensive nil-handling in Anthropic error conversion.
This properly avoids panics when bifrostErr or nested bifrostErr.Error is nil, and enriches parsed errors consistently.

plugins/jsonparser/utils.go (1)

303-323: LGTM: deep-copy updated for Delta.Reasoning.

core/schemas/mux.go (1)

710-869: LGTM: reasoning round-trip now preserves MaxTokens and streaming uses Delta.Reasoning.

Also applies to: 1361-1372

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

39-41: Good: propagate convertChatParameters errors to caller.
Keeps Bedrock request conversion consistent with the “conversion can fail” refactor across providers.


139-143: Nice: normalize single-text contentBlocks back to contentStr.
This keeps the response payload minimal and consistent with other provider converters.


333-388: Streaming reasoning delta handling looks correct (no nil response leaks).
The early return when both Text and Signature are absent prevents accidental (nil, nil, false) responses with ambiguous meaning.

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

296-299: Good: nil request now returns an error.
Prevents silent nil dereferences and matches the broader “conversion can fail” approach.


402-403: Good: updated return signature is correctly wired.


1428-1439: Good: nil-safe extraction of streaming error message.
Avoids panics when bifrostErr.Error is nil.

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

203-204: Good: request converter now returns (req, err) and exits cleanly.


219-226: Good: reuse per-message conversion instead of duplicating mapping logic.


339-341: Good: initialize ChatNonStreamResponseChoice explicitly.


351-354: Good: response message conversion is centralized now.

core/schemas/chatcompletions.go (6)

165-166: Good: ChatParameters.Reasoning is now first-class.


222-226: ChatReasoning shape looks good for cross-provider needs.


627-667: Good: synthesize reasoning_details when only reasoning is provided.
This makes downstream rendering/normalization simpler.


714-730: Reasoning details type/constants look consistent with UI typing.


757-794: Good: streaming delta also synthesizes reasoning_details.


835-836: Good: ImageTokens addition is additive and optional.

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

13-16: All call sites for ToAnthropicChatRequest have been correctly updated with proper error handling.

The signature change to return (*AnthropicMessageRequest, error) has been consistently implemented across the codebase. All six call sites in azure.go, anthropic.go, and vertex.go properly handle the error return value by either checking for errors and propagating them or returning them directly through callback functions.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from d592b74 to 4c3f30d Compare December 12, 2025 11:24
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 0f9320e to 14a7a70 Compare December 12, 2025 11:24
@TejasGhatte TejasGhatte force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 4c3f30d to b584c84 Compare December 12, 2025 11:25
@TejasGhatte TejasGhatte force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 14a7a70 to 0f9320e Compare December 12, 2025 11:25
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-refactor_core_providers_error_handling_standardization branch from 0f9320e to be363a1 Compare December 12, 2025 11:26
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from b584c84 to 0ea65ac Compare December 12, 2025 11:26
@TejasGhatte TejasGhatte force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 0ea65ac to e3cbb41 Compare December 12, 2025 11:40
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from e3cbb41 to 0ea65ac Compare December 12, 2025 12: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: 0

Caution

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

⚠️ Outside diff range comments (12)
transports/bifrost-http/handlers/inference.go (2)

62-89: Don’t forward deprecated reasoning_effort as ExtraParams.
Right now reasoning_effort is not in chatParamsKnownFields, so legacy clients will have it copied into schemas.ChatParameters.ExtraParams via extractExtraParams(), even though schemas.ChatParameters.UnmarshalJSON already consumes it. This can leak a deprecated OpenAI-ish field into provider requests unexpectedly.

 var chatParamsKnownFields = map[string]bool{
   "model":                 true,
   "messages":              true,
   "fallbacks":             true,
   "stream":                true,
@@
   "prompt_cache_key":      true,
   "reasoning":             true,
+  // Deprecated alias; keep as known so it doesn't land in ExtraParams.
+  "reasoning_effort":      true,
   "response_format":       true,
@@
 }

Also applies to: 267-288


980-1071: Pass request type to handleStreamingResponse to avoid sending [DONE] for Responses API when stream closes without chunks.

Responses streams can complete without emitting chunks (edge case). The current logic infers includeEventType from the last chunk received; if none arrive, it remains false and sends [DONE], which violates the Responses API spec. Per documented behavior in router.go: "OpenAI 'responses' API and Anthropic messages API: they signal completion by simply closing the stream, not sending [DONE]."

Refactor handleStreamingResponse to accept a request type parameter and decide DONE-vs-no-DONE by endpoint type, not by last chunk presence.

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

39-50: Guard parseAnthropicError against a nil bifrostErr return.
If providerUtils.HandleProviderAPIError can ever return nil, the bifrostErr.Error == nil check will panic. Consider a defensive fallback.

 func parseAnthropicError(resp *fasthttp.Response) *schemas.BifrostError {
 	var errorResp AnthropicError
 	bifrostErr := providerUtils.HandleProviderAPIError(resp, &errorResp)
+	if bifrostErr == nil {
+		bifrostErr = &schemas.BifrostError{}
+	}
 	if errorResp.Error != nil {
 		if bifrostErr.Error == nil {
 			bifrostErr.Error = &schemas.ErrorField{}
 		}
 		bifrostErr.Error.Type = &errorResp.Error.Type
 		bifrostErr.Error.Message = errorResp.Error.Message
 	}
 	return bifrostErr
 }
core/providers/azure/azure.go (4)

348-366: Don’t allow Anthropic request bodies to silently become JSON null.
If reqBody is nil with err == nil, return an explicit error (otherwise you’ll POST null).

 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
 	return nil, err
 }
- if reqBody != nil {
- 	reqBody.Model = deployment
- }
+ if reqBody == nil {
+ 	return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
 return reqBody, nil

448-466: Same: fail early if Anthropic streaming reqBody is nil.
Otherwise streaming path can also POST null.

 reqBody, err := anthropic.ToAnthropicChatRequest(request)
 if err != nil {
 	return nil, err
 }
- if reqBody != nil {
- 	reqBody.Model = deployment
- 	reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+ 	return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
 return reqBody, nil

526-548: Same issue for Responses: nil body should be an error.

 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
 	return nil, err
 }
- if reqBody != nil {
- 	reqBody.Model = deployment
- }
+ if reqBody == nil {
+ 	return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
 return reqBody, nil

620-645: Same issue for ResponsesStream: nil body should be an error.

 reqBody, err := anthropic.ToAnthropicResponsesRequest(request)
 if err != nil {
 	return nil, err
 }
- if reqBody != nil {
- 	reqBody.Model = deployment
- 	reqBody.Stream = schemas.Ptr(true)
- }
+ if reqBody == nil {
+ 	return nil, fmt.Errorf("anthropic request conversion returned nil")
+ }
+ reqBody.Model = deployment
+ reqBody.Stream = schemas.Ptr(true)
 return reqBody, nil
core/providers/bedrock/responses.go (1)

452-499: Fix disabled reasoning_config type + avoid overwriting it from ExtraParams.

  1. Writing map[string]string breaks the earlier .(map[string]interface{}) read.
  2. additionalModelRequestFieldPaths can replace the whole map, dropping reasoning_config.
 if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
-	bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]string{
+	bedrockReq.AdditionalModelRequestFields["reasoning_config"] = map[string]any{
 		"type": "disabled",
 	}
 } else {
   ...
 }

 if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
 	if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-		bedrockReq.AdditionalModelRequestFields = orderedFields
+		if bedrockReq.AdditionalModelRequestFields == nil {
+			bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+		}
+		for k, v := range orderedFields {
+			// Don't clobber reasoning_config set from Params.Reasoning unless explicitly intended.
+			if k == "reasoning_config" {
+				continue
+			}
+			bedrockReq.AdditionalModelRequestFields[k] = v
+		}
 	}
 }
core/providers/vertex/vertex.go (3)

512-545: Streaming Anthropic path should also fail fast when reqBody == nil.
Right now, nil would flow into marshal/unmarshal with a confusing error. Mirror the non-stream path’s explicit nil check.


679-710: Same: Responses Anthropic path should reject nil reqBody.
Otherwise sonic.Unmarshal into map[string]interface{} can fail on "null".


831-872: Same: ResponsesStream Anthropic path should reject nil reqBody.

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

184-201: Don’t set params.Reasoning to “auto” when thinking.type=="enabled" but budget_tokens is nil

Right now (Line 191-195) you create Effort:"auto" with MaxTokens:nil, which violates your own schema comment (“required for anthropic”) and can break round-trips/logging. Prefer: only set params.Reasoning when BudgetTokens != nil; otherwise leave it unset (or set Effort:"none" only when explicitly disabled).

-	if request.Thinking != nil {
-		if request.Thinking.Type == "enabled" {
-			params.Reasoning = &schemas.ResponsesParametersReasoning{
-				Effort:    schemas.Ptr("auto"),
-				MaxTokens: request.Thinking.BudgetTokens,
-			}
-		} else {
-			params.Reasoning = &schemas.ResponsesParametersReasoning{
-				Effort: schemas.Ptr("none"),
-			}
-		}
-	}
+	if request.Thinking != nil {
+		if request.Thinking.Type == "enabled" {
+			if request.Thinking.BudgetTokens != nil {
+				params.Reasoning = &schemas.ResponsesParametersReasoning{
+					Effort:    schemas.Ptr("auto"),
+					MaxTokens: request.Thinking.BudgetTokens,
+				}
+			}
+		} else {
+			params.Reasoning = &schemas.ResponsesParametersReasoning{
+				Effort: schemas.Ptr("none"),
+			}
+		}
+	}
♻️ Duplicate comments (10)
core/providers/bedrock/utils.go (2)

3-12: Avoid importing core/providers/anthropic just for a constant.
This keeps an unnecessary cross-provider dependency alive (anthropic.MinimumReasoningMaxTokens). Consider moving the constant to a shared package (e.g., core/schemas or core/constants) and importing that from providers.

Search in the repo: where is `MinimumReasoningMaxTokens` referenced across providers, and is there already a shared constants package used by multiple providers?

Also applies to: 42-46


32-52: reasoning_config can still be clobbered by ExtraParams assignment.
You set bedrockReq.AdditionalModelRequestFields["reasoning_config"] first, but later overwrite the whole map when additionalModelRequestFieldPaths is present. This silently drops the reasoning configuration.

 if bifrostReq.Params != nil && bifrostReq.Params.ExtraParams != nil {
   if requestFields, exists := bifrostReq.Params.ExtraParams["additionalModelRequestFieldPaths"]; exists {
     if orderedFields, ok := schemas.SafeExtractOrderedMap(requestFields); ok {
-      bedrockReq.AdditionalModelRequestFields = orderedFields
+      if bedrockReq.AdditionalModelRequestFields == nil {
+        bedrockReq.AdditionalModelRequestFields = make(schemas.OrderedMap)
+      }
+      for k, v := range orderedFields {
+        bedrockReq.AdditionalModelRequestFields[k] = v
+      }
     }
   }

Also applies to: 73-100

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

88-113: Bedrock reasoning-content modeling looks incomplete for real-world payloads (missing redactedContent / delta union semantics).
If AWS emits redacted/encrypted reasoning or signature-only deltas, current structs may fail to represent (and in some cases may not decode cleanly). Suggest aligning with AWS API/SDK union members and adding redactedContent support at least.

 type BedrockReasoningContent struct {
   ReasoningText *BedrockReasoningContentText `json:"reasoningText,omitempty"`
+  // Provider-encrypted reasoning payloads.
+  RedactedContent *string `json:"redactedContent,omitempty"`
 }

 type BedrockReasoningContentText struct {
-  Text      string  `json:"text"`
+  Text      string  `json:"text"`
   Signature *string `json:"signature,omitempty"`
 }
AWS Bedrock Converse/ConverseStream: what is the exact JSON shape of `reasoningContent` for (1) non-streaming ContentBlock and (2) streaming ContentBlockDelta? Specifically: are `text`, `signature`, and `redactedContent` siblings in one object, or union members sent separately?

Also applies to: 157-165, 442-447

core/schemas/chatcompletions.go (3)

154-220: Backward-compat: don’t error when both reasoning and reasoning_effort are present.
Given the deprecation/migration goal, rejecting requests that include both fields is likely an avoidable breaking change. Prefer reasoning (new) and ignore reasoning_effort (old) when both are provided.

-  if aux.Alias != nil && aux.Alias.Reasoning != nil && aux.ReasoningEffort != nil {
-    return fmt.Errorf("both reasoning_effort and reasoning fields cannot be present at the same time")
-  }
+  // If both are present, prefer `reasoning` and ignore the deprecated alias.
 
   if aux.ReasoningEffort != nil {
-    cp.Reasoning = &ChatReasoning{
-      Effort: aux.ReasoningEffort,
-    }
+    if cp.Reasoning == nil {
+      cp.Reasoning = &ChatReasoning{Effort: aux.ReasoningEffort}
+    }
   }
 
-  // ExtraParams etc. are already handled by the alias
+  // Note: ExtraParams is json:"-" and must be populated elsewhere.
   return nil

218-219: Fix misleading comment about ExtraParams.
ExtraParams is json:"-", so it is not populated by the alias unmarshal.


480-521: Enforce the “only one embedded pointer” invariant (and clear old values).
This unmarshal can still set both ChatToolMessage and ChatAssistantMessage for malformed inputs, and it never clears previous embedded pointers if the same struct instance is reused.

 func (cm *ChatMessage) UnmarshalJSON(data []byte) error {
   // Unmarshal the base fields directly
+  cm.ChatToolMessage = nil
+  cm.ChatAssistantMessage = nil
@@
   if toolMsg.ToolCallID != nil {
     cm.ChatToolMessage = (*ChatToolMessage)(&toolMsg)
   }
@@
   if assistantMsg.Refusal != nil || assistantMsg.Reasoning != nil ||
     len(assistantMsg.ReasoningDetails) > 0 || len(assistantMsg.Annotations) > 0 ||
     len(assistantMsg.ToolCalls) > 0 {
     cm.ChatAssistantMessage = &assistantMsg
   }
+
+  if cm.ChatToolMessage != nil && cm.ChatAssistantMessage != nil {
+    return fmt.Errorf("invalid chat message: both tool_call_id and assistant fields are set")
+  }
 
   return nil
 }
core/providers/openai/types.go (1)

56-65: Add runtime enforcement for the “only one embedded pointer” invariant

The comment (Line 61-64) warns about collisions, but nothing prevents ChatToolMessage and OpenAIChatAssistantMessage being set together. Add a Validate() (or custom MarshalJSON) and call it before serialization.

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

474-487: Bug: signature-only ReasoningDetails are still dropped (guard requires Text != nil)

The condition at Line 477 currently prevents emitting thinking blocks when only Signature is present. Remove the reasoningDetail.Text != nil guard.

-				if reasoningDetail.Type == schemas.BifrostReasoningDetailsTypeText && reasoningDetail.Text != nil &&
-					((reasoningDetail.Text != nil && *reasoningDetail.Text != "") ||
-						(reasoningDetail.Signature != nil && *reasoningDetail.Signature != "")) {
+				if reasoningDetail.Type == schemas.BifrostReasoningDetailsTypeText &&
+					((reasoningDetail.Text != nil && *reasoningDetail.Text != "") ||
+						(reasoningDetail.Signature != nil && *reasoningDetail.Signature != "")) {
 					content = append(content, AnthropicContentBlock{
 						Type:      AnthropicContentBlockTypeThinking,
 						Thinking:  reasoningDetail.Text,
 						Signature: reasoningDetail.Signature,
 					})
 				}

415-428: Usage mapping: CacheCreationInputTokens should not be stored under completion cached tokens

CacheCreationInputTokens is an input/prompt cache-creation metric; putting it in CompletionTokensDetails makes dashboards/consumers misinterpret it. Consider mapping both cache-read and cache-create into PromptTokensDetails (with separate fields if you have them), or at least don’t attribute creation to completion.

 		bifrostResponse.Usage = &schemas.BifrostLLMUsage{
 			PromptTokens: response.Usage.InputTokens,
 			PromptTokensDetails: &schemas.ChatPromptTokensDetails{
 				CachedTokens: response.Usage.CacheReadInputTokens,
 			},
 			CompletionTokens: response.Usage.OutputTokens,
-			CompletionTokensDetails: &schemas.ChatCompletionTokensDetails{
-				CachedTokens: response.Usage.CacheCreationInputTokens,
-			},
 			TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
 		}
+		if response.Usage.CacheCreationInputTokens > 0 {
+			// If you only have one cached token bucket, treat cache-creation as prompt-side.
+			bifrostResponse.Usage.PromptTokensDetails.CachedTokens += response.Usage.CacheCreationInputTokens
+		}
core/providers/anthropic/responses.go (1)

325-346: Fix reasoning enablement semantics: current logic drops max_tokens-only reasoning and differs from anthropic/chat.go

ToAnthropicChatRequest treats Effort == nil as “enabled unless explicitly none” and requires MaxTokens; this converter (Line 326-345) only does anything when Effort != nil, so { reasoning: { max_tokens: 1024 } } becomes “no thinking” here but “thinking enabled” in chat. Unify them.

-		if bifrostReq.Params.Reasoning != nil {
-			if bifrostReq.Params.Reasoning.Effort != nil {
-				if *bifrostReq.Params.Reasoning.Effort != "none" {
-					if bifrostReq.Params.Reasoning.MaxTokens != nil {
-						if *bifrostReq.Params.Reasoning.MaxTokens < MinimumReasoningMaxTokens {
-							return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", MinimumReasoningMaxTokens)
-						} else {
-							anthropicReq.Thinking = &AnthropicThinking{
-								Type:         "enabled",
-								BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
-							}
-						}
-					} else {
-						return nil, fmt.Errorf("reasoning.max_tokens is required for reasoning")
-					}
-				} else {
-					anthropicReq.Thinking = &AnthropicThinking{
-						Type: "disabled",
-					}
-				}
-			}
-		}
+		if bifrostReq.Params.Reasoning != nil {
+			// Disable only when explicitly requested.
+			if bifrostReq.Params.Reasoning.Effort != nil && *bifrostReq.Params.Reasoning.Effort == "none" {
+				anthropicReq.Thinking = &AnthropicThinking{Type: "disabled"}
+			} else {
+				if bifrostReq.Params.Reasoning.MaxTokens == nil {
+					return nil, fmt.Errorf("reasoning.max_tokens is required for reasoning")
+				}
+				if *bifrostReq.Params.Reasoning.MaxTokens < MinimumReasoningMaxTokens {
+					return nil, fmt.Errorf("reasoning.max_tokens must be greater than or equal to %d", MinimumReasoningMaxTokens)
+				}
+				anthropicReq.Thinking = &AnthropicThinking{
+					Type:         "enabled",
+					BudgetTokens: bifrostReq.Params.Reasoning.MaxTokens,
+				}
+			}
+		}
🧹 Nitpick comments (5)
transports/bifrost-http/handlers/inference.go (1)

169-199: ChatRequest custom unmarshal is reasonable; consider reducing repeated decoding if it shows up in profiles.
Triple sonic.Unmarshal per request (params, messages, then ChatParameters) is probably fine, but it’s a hot path. If p99 matters, consider a single pass into a temp struct that embeds BifrostParams + []ChatMessage + schemas.ChatParameters and then assigns.

Also applies to: 474-513

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

32-52: Consider treating empty { "reasoning": {} } as “no-op” instead of error.
Currently Reasoning != nil with neither effort nor max_tokens will error. If clients send an empty object during migrations, this becomes a sharp edge.

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

534-539: Enforce delta invariants for text vs thinking.
With Thinking added, consider validating (at decode/convert time) that only one of Text/Thinking is set per delta (and ideally that Type matches), to prevent ambiguous chunk handling.

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

5-45: Good utility split; consider enforcing “only one embedded message struct” invariant.
To avoid producing invalid schemas.ChatMessage / openai.OpenAIMessage when inputs are malformed, consider only copying ChatToolMessage for role=tool, and only copying OpenAIChatAssistantMessage / ChatAssistantMessage for role=assistant. (Not mapping ReasoningDetails here is correct, per repo learnings.)

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

59-63: Reasoning extraction is now nil-safe; consider avoiding a trailing newline in reasoningText
Right now you always append "\n" (Line 132-133). If consumers display this verbatim, it’ll render with a trailing blank line.

Also applies to: 121-133, 157-163

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d592b74 and 0ea65ac.

📒 Files selected for processing (30)
  • core/providers/anthropic/anthropic.go (4 hunks)
  • core/providers/anthropic/chat.go (11 hunks)
  • core/providers/anthropic/errors.go (1 hunks)
  • core/providers/anthropic/responses.go (5 hunks)
  • core/providers/anthropic/types.go (2 hunks)
  • core/providers/azure/azure.go (4 hunks)
  • core/providers/bedrock/bedrock.go (4 hunks)
  • core/providers/bedrock/chat.go (5 hunks)
  • core/providers/bedrock/responses.go (4 hunks)
  • core/providers/bedrock/types.go (3 hunks)
  • core/providers/bedrock/utils.go (4 hunks)
  • core/providers/cohere/chat.go (10 hunks)
  • core/providers/cohere/cohere.go (2 hunks)
  • core/providers/cohere/types.go (1 hunks)
  • core/providers/openai/chat.go (3 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openai/types.go (2 hunks)
  • core/providers/openai/utils.go (1 hunks)
  • core/providers/perplexity/chat.go (1 hunks)
  • core/providers/vertex/vertex.go (4 hunks)
  • core/schemas/chatcompletions.go (7 hunks)
  • core/schemas/mux.go (3 hunks)
  • core/schemas/responses.go (2 hunks)
  • framework/streaming/chat.go (1 hunks)
  • plugins/jsonparser/utils.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/semanticcache/utils.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (5 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/lib/types/logs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/providers/perplexity/chat.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • plugins/maxim/main.go
  • core/providers/openai/openai.go
  • plugins/semanticcache/utils.go
  • core/providers/anthropic/types.go
  • core/schemas/mux.go
  • core/providers/cohere/cohere.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/types.go
  • core/providers/anthropic/anthropic.go
  • core/schemas/responses.go
  • core/providers/openai/utils.go
  • ui/lib/types/logs.ts
  • transports/bifrost-http/handlers/inference.go
  • core/providers/anthropic/errors.go
  • framework/streaming/chat.go
  • core/providers/openai/chat.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/utils.go
  • core/schemas/chatcompletions.go
  • core/providers/bedrock/bedrock.go
  • core/providers/vertex/vertex.go
  • core/providers/cohere/chat.go
  • core/providers/azure/azure.go
  • core/providers/openai/types.go
  • plugins/jsonparser/utils.go
  • core/providers/bedrock/chat.go
  • core/providers/anthropic/chat.go
🧠 Learnings (5)
📚 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/types.go
  • core/providers/anthropic/anthropic.go
  • core/schemas/responses.go
  • core/providers/openai/utils.go
  • transports/bifrost-http/handlers/inference.go
  • core/providers/anthropic/errors.go
  • framework/streaming/chat.go
  • core/providers/openai/chat.go
  • core/providers/bedrock/responses.go
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/types.go
  • core/providers/bedrock/utils.go
  • core/schemas/chatcompletions.go
  • core/providers/bedrock/bedrock.go
  • core/providers/vertex/vertex.go
  • core/providers/cohere/chat.go
  • core/providers/azure/azure.go
  • core/providers/openai/types.go
  • plugins/jsonparser/utils.go
  • core/providers/bedrock/chat.go
  • core/providers/anthropic/chat.go
📚 Learning: 2025-12-08T11:50:35.996Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 978
File: core/providers/openai/utils.go:26-45
Timestamp: 2025-12-08T11:50:35.996Z
Learning: OpenAI does not support the `ReasoningDetails` field. `ReasoningDetails` is a Bifrost-internal field and should not be included when converting messages to OpenAI format in the `ConvertBifrostMessagesToOpenAIMessages` function in `core/providers/openai/utils.go`.

Applied to files:

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

Applied to files:

  • core/providers/openai/utils.go
  • core/providers/openai/chat.go
  • core/providers/openai/types.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/handlers/inference.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
🧬 Code graph analysis (16)
core/providers/anthropic/anthropic.go (4)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-405)
core/providers/openai/utils.go (2)
core/providers/openai/types.go (2)
  • OpenAIMessage (56-65)
  • OpenAIChatAssistantMessage (67-72)
core/schemas/chatcompletions.go (3)
  • ChatMessage (469-478)
  • ChatToolMessage (621-623)
  • ChatAssistantMessage (626-632)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/chatcompletions.go (2)
  • ChatMessage (469-478)
  • ChatParameters (155-184)
ui/lib/types/logs.ts (1)
  • ChatMessage (115-124)
core/providers/anthropic/errors.go (1)
core/providers/anthropic/types.go (2)
  • AnthropicMessageError (376-379)
  • AnthropicMessageErrorStruct (382-385)
framework/streaming/chat.go (2)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (626-632)
core/utils.go (1)
  • Ptr (51-53)
core/providers/openai/chat.go (4)
core/providers/openai/utils.go (2)
  • ConvertOpenAIMessagesToBifrostMessages (5-24)
  • ConvertBifrostMessagesToOpenAIMessages (26-45)
core/schemas/chatcompletions.go (1)
  • ChatParameters (155-184)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/bedrock/responses.go (5)
core/schemas/utils.go (5)
  • SafeExtractFromMap (519-525)
  • SafeExtractString (280-297)
  • SafeExtractInt (300-342)
  • Ptr (16-18)
  • IsAnthropicModel (1043-1045)
core/schemas/responses.go (1)
  • ResponsesParametersReasoning (233-238)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/chatcompletions.go (1)
  • OrderedMap (268-268)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/anthropic/responses.go (4)
core/schemas/responses.go (2)
  • ResponsesParametersReasoning (233-238)
  • BifrostResponsesRequest (32-39)
ui/lib/types/logs.ts (1)
  • ResponsesParametersReasoning (519-526)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/types.go (3)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • AnthropicStreamError (397-400)
core/providers/bedrock/utils.go (4)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • OrderedMap (268-268)
core/providers/bedrock/types.go (2)
  • BedrockConverseRequest (48-62)
  • BedrockError (390-394)
core/schemas/utils.go (1)
  • IsAnthropicModel (1043-1045)
core/providers/anthropic/types.go (1)
  • MinimumReasoningMaxTokens (14-14)
core/providers/bedrock/bedrock.go (2)
core/providers/utils/utils.go (1)
  • CreateBifrostChatCompletionChunkResponse (687-716)
core/schemas/bifrost.go (1)
  • ChatCompletionStreamRequest (90-90)
core/providers/vertex/vertex.go (5)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/schemas/models.go (1)
  • Model (109-129)
core/utils.go (1)
  • Ptr (51-53)
core/schemas/utils.go (1)
  • Ptr (16-18)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-405)
core/providers/cohere/chat.go (2)
core/schemas/chatcompletions.go (15)
  • ChatMessage (469-478)
  • ChatReasoning (223-226)
  • BifrostChatResponse (26-41)
  • BifrostResponseChoice (704-712)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
  • ChatReasoningDetails (723-730)
  • ChatContentBlock (590-597)
  • ChatAssistantMessageToolCall (686-692)
  • ChatContentBlockTypeText (582-582)
  • ChatContentBlockTypeImage (583-583)
  • ChatInputImage (600-603)
  • ChatMessageContent (524-527)
  • ChatAssistantMessage (626-632)
  • ChatMessageRole (457-457)
core/providers/cohere/types.go (4)
  • CohereChatRequest (14-32)
  • CohereThinking (171-174)
  • ThinkingTypeEnabled (180-180)
  • CohereMessage (51-57)
core/providers/azure/azure.go (2)
core/providers/anthropic/chat.go (1)
  • ToAnthropicChatRequest (13-278)
core/providers/anthropic/responses.go (1)
  • ToAnthropicResponsesRequest (296-405)
core/providers/openai/types.go (2)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/chatcompletions.go (10)
  • ChatMessageRole (457-457)
  • ChatMessageContent (524-527)
  • ChatToolMessage (621-623)
  • ChatAssistantMessageAnnotation (670-673)
  • ChatAssistantMessageToolCall (686-692)
  • Alias (189-189)
  • Alias (643-643)
  • Alias (770-770)
  • Alias (860-860)
  • ChatReasoning (223-226)
core/providers/bedrock/chat.go (2)
core/schemas/chatcompletions.go (6)
  • ChatReasoningDetails (723-730)
  • BifrostReasoningDetailsTypeText (719-719)
  • ChatContentBlockTypeText (582-582)
  • ChatAssistantMessage (626-632)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
ui/lib/types/logs.ts (1)
  • ReasoningDetails (126-133)
core/providers/anthropic/chat.go (3)
core/schemas/chatcompletions.go (12)
  • BifrostChatRequest (12-19)
  • ChatAssistantMessage (626-632)
  • ChatAssistantMessageToolCall (686-692)
  • ChatContentBlock (590-597)
  • ChatReasoningDetails (723-730)
  • ChatAssistantMessageToolCallFunction (695-698)
  • ChatToolTypeFunction (239-239)
  • ChatMessageContent (524-527)
  • ChatMessage (469-478)
  • ChatNonStreamResponseChoice (745-748)
  • ChatStreamResponseChoice (751-753)
  • ChatStreamResponseChoiceDelta (756-763)
core/providers/anthropic/types.go (8)
  • AnthropicMessageRequest (40-59)
  • AnthropicThinking (65-68)
  • MinimumReasoningMaxTokens (14-14)
  • AnthropicContentBlock (147-159)
  • AnthropicContentBlockTypeThinking (143-143)
  • AnthropicMessageResponse (273-282)
  • AnthropicContentBlockTypeText (135-135)
  • AnthropicContentBlockTypeToolUse (137-137)
core/providers/anthropic/utils.go (1)
  • ConvertAnthropicFinishReasonToBifrost (27-32)
⏰ 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 (36)
ui/lib/types/logs.ts (1)

115-133: LGTM: UI log types align with backend reasoning + reasoning_details.
Field names and shapes match the new API surface and keep optionality appropriate for logs.

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

702-710: LGTM: safer nested error message extraction.
This avoids nil deref and keeps Bedrock error payload stable.

core/schemas/chatcompletions.go (2)

627-667: LGTM: synthesizing reasoning_details from reasoning reduces downstream branching.
This makes logs/stream aggregation consistent when providers emit only a raw reasoning string.

Also applies to: 756-794


829-840: LGTM: ImageTokens is additive and backwards-safe.
No concerns with the optional pointer field.

core/providers/bedrock/bedrock.go (1)

771-772: LGTM: UUID generation for Bedrock stream identification.

Bedrock doesn't provide a unique stream identifier, so generating one locally using uuid.New().String() is a reasonable approach. The comment clearly documents this design decision, and the ID is used consistently for both per-chunk responses and the final chunk.

core/schemas/responses.go (2)

233-238: LGTM: Reasoning configuration properly documented.

The ResponsesParametersReasoning struct now includes:

  • Effort with clear documentation of allowed values including "none"
  • MaxTokens for Anthropic reasoning budget tokens

The comment clarifies that MaxTokens is required for Anthropic, which aligns with the validation logic in core/providers/anthropic/responses.go that enforces MinimumReasoningMaxTokens.


324-326: LGTM: Model-specific reasoning content block documentation.

The comment clarifies the distinction between gpt-oss models (reasoning_text content blocks) and other OpenAI models (summaries + encrypted_content). This helps consumers understand provider-specific behavior.

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

307-311: LGTM: Error propagation from ToAnthropicChatRequest.

The converter function now returns (*AnthropicMessageRequest, error), and this change properly propagates any conversion errors (such as reasoning validation failures) to the caller. This aligns with the updated signature in core/providers/anthropic/chat.go.


357-364: LGTM: Streaming request conversion with error handling.

The lambda now properly:

  1. Handles the error from ToAnthropicChatRequest
  2. Returns early on error
  3. Sets Stream = true only on success

This pattern ensures validation errors from reasoning configuration are surfaced before the streaming request is made.


659-663: LGTM: Error propagation from ToAnthropicResponsesRequest.

Consistent with the ChatCompletion change, the Responses endpoint now propagates conversion errors properly.


707-714: LGTM: ResponsesStream conversion with proper error handling.

The streaming responses path follows the same pattern as ChatCompletionStream, correctly handling errors from the converter before setting the Stream flag.

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

14-14: LGTM: Message conversion using helper function.

Using ConvertOpenAIMessagesToBifrostMessages properly maps OpenAI-specific message fields (including OpenAIChatAssistantMessage with Reasoning, Refusal, Annotations, ToolCalls) to the Bifrost ChatMessage schema.


28-28: LGTM: Bidirectional message conversion.

Using ConvertBifrostMessagesToOpenAIMessages ensures consistent round-trip conversion and properly maps ChatAssistantMessage fields back to OpenAI format.


63-65: LGTM: Updated reasoning parameter handling with proper nil checks.

The nested nil checks for Reasoning and Reasoning.Effort prevent nil pointer dereferences. The "minimal" to "low" conversion maintains compatibility with providers that don't support the "minimal" effort level (which is OpenAI-specific).

framework/streaming/chat.go (3)

39-43: LGTM: Refusal handling with proper pointer initialization.

Using bifrost.Ptr(*chunk.Delta.Refusal) correctly creates a new pointer for the initial refusal value, avoiding aliasing issues with the original chunk data.


44-54: LGTM: Reasoning accumulation across stream chunks.

The pattern correctly:

  1. Initializes ChatAssistantMessage if nil
  2. Creates a new pointer for the first reasoning chunk
  3. Accumulates subsequent reasoning text via string concatenation

This mirrors the established pattern for Refusal handling.


55-105: LGTM: ReasoningDetails accumulation with index-based merging.

The implementation correctly handles streaming reasoning details:

  • Text/Summary/Data: Accumulated via string concatenation across chunks
  • Signature: Overwritten (correct since signatures are final values)
  • Type: Overwritten on each update (correct since type should be consistent)
  • New entries: Appended when index doesn't exist

The index-based approach ensures that partial updates for the same reasoning block are properly merged while allowing multiple distinct reasoning blocks to coexist.

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

9-37: Nil-safe error conversion looks solid.
This avoids panics on bifrostErr.Type / bifrostErr.Error and returns a stable Anthropic-shaped error.

plugins/jsonparser/utils.go (1)

303-323: Rename alignment (Thought → Reasoning) looks correct.
No additional concerns in this copy path given the plugin only mutates Content.

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

347-368: reasoning_config extraction logic is fine, but depends on consistent map types.
Looks good assuming reasoning_config is always a map[string]any/map[string]interface{}.


658-666: Output conversion placement is an improvement.
Moving output mapping up front makes the response assembly easier to reason about and reduces duplicated conversion risks.

core/providers/vertex/vertex.go (1)

281-299: Nice: explicit nil check avoids hidden marshal failures.
This is the right pattern for the Anthropic converter call-site.

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

296-299: Good: ToAnthropicResponsesRequest now nil-checks and returns an error
This prevents silent panics at call-sites and aligns with the broader stack refactor towards error-returning converters.


1430-1441: Good: nil-safe message extraction for SSE error formatting
Avoids nil deref when bifrostErr.Error is nil.

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

42-44: Watch for downstream breakage: OpenAIChatRequest.Messages is now []OpenAIMessage
Call-sites building requests must populate the new OpenAIMessage shape (esp. tool messages / assistant extras) rather than plain schemas.ChatMessage.


74-104: MarshalJSON correctly emits reasoning_effort and omits reasoning (plus nil-safety)
Looks solid and keeps request payload compatible while still allowing internal Reasoning usage.

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

39-41: Good: convertChatParameters errors are now propagated
This matches the stack-wide move to error-returning converters and avoids silently sending malformed requests.


335-389: Good: reasoning delta emission no longer returns an unexpected nil response pointer
The early-return guard (Line 339-342) fixes the prior “nil response” footgun.

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

11-14: Good: request converter now returns an error instead of constructing invalid requests
Matches the stack’s standardized error handling.

Also applies to: 200-201


103-118: Confirm Cohere semantics: do we really want to require reasoning.max_tokens to enable thinking?
Cohere’s thinking.token_budget is typically optional; this enforces a stricter contract at the Bifrost layer. If intentional, great—otherwise consider allowing enabled-without-budget and letting Cohere decide defaults.


410-451: Good: streaming now forwards Cohere “thinking” into Reasoning + ReasoningDetails
This keeps the UI/logging path consistent with other providers.

Also applies to: 463-464


561-672: Nice: ToBifrostChatMessage preserves thinking blocks into structured ReasoningDetails (without trailing newline)
The join-style concatenation avoids the trailing newline issue seen elsewhere.

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

13-16: Good: converter now returns an error for nil request/input instead of panicking
Fits the stack’s “converter functions return errors” direction.

Also applies to: 277-278


99-117: Reasoning validation looks right for Anthropic, but keep chat/responses semantics aligned
This treats Effort == nil as “enabled unless explicitly none,” which is the behavior responses.go should mirror too (currently it doesn’t).


207-216: Preserving all ReasoningDetails into Anthropic thinking blocks is correct here
This matches the repo learning to keep bidirectional fidelity even for nil/empty Text. Based on learnings.


630-682: Good: streaming now carries reasoning text + signature deltas in both directions
The added delta mappings preserve verification signatures instead of dropping them.

Also applies to: 714-754

Copy link
Collaborator Author

Pratham-Mishra04 commented Dec 12, 2025

Merge activity

  • Dec 12, 1:21 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Dec 12, 1:23 PM UTC: Graphite rebased this pull request as part of a merge.
  • Dec 12, 1:23 PM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from 12-01-refactor_core_providers_error_handling_standardization to graphite-base/978 December 12, 2025 13:22
@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from graphite-base/978 to main December 12, 2025 13:22
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 12-01-feat_added_reasoning_support_to_chat_completions branch from 0ea65ac to bfbcf49 Compare December 12, 2025 13:22
@Pratham-Mishra04 Pratham-Mishra04 merged commit 6d4ed0c into main Dec 12, 2025
8 of 9 checks passed
@Pratham-Mishra04 Pratham-Mishra04 deleted the 12-01-feat_added_reasoning_support_to_chat_completions branch December 12, 2025 13:23
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.

[Bug]: Reasoning tokens are not passed through for Gemini

2 participants