Skip to content

feat: improve Responses API streaming with OpenAI-style lifecycle events - #815

Merged
Pratham-Mishra04 merged 1 commit into
mainfrom
11-11-fix_responses_streaming_accumulation_fixes
Nov 14, 2025
Merged

feat: improve Responses API streaming with OpenAI-style lifecycle events#815
Pratham-Mishra04 merged 1 commit into
mainfrom
11-11-fix_responses_streaming_accumulation_fixes

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

Enhances the Responses API streaming implementation to better align with OpenAI's streaming format, providing more consistent and reliable tool call streaming across providers.

Changes

  • Refactored Anthropic's Responses API streaming to use a stateful accumulator for tracking output items
  • Added similar streaming accumulators to Bedrock and Cohere providers
  • Implemented proper OpenAI-style lifecycle events (created, in_progress, completed) for all providers
  • Improved tool call streaming with proper output indexing and argument accumulation
  • Replaced the chat-to-responses conversion approach with native implementations for each provider
  • Added comprehensive tests for tool call streaming functionality

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 the Responses API streaming with tool calls across different providers:

# Run the tool calls streaming tests
go test ./tests/core-providers -run TestOpenAI/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestAnthropic/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestBedrock/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestCohere/ToolCallsStreamingResponses

Breaking changes

  • Yes
  • No

Related issues

Improves the reliability of tool call streaming in the Responses API.

Security considerations

No security implications.

Checklist

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

Pratham-Mishra04 commented Nov 11, 2025

Copy link
Copy Markdown
Collaborator Author

@coderabbitai

coderabbitai Bot commented Nov 11, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added unified streaming lifecycle events across all AI providers for consistent real-time response handling
    • Enabled streaming tool calls support across multiple providers
    • New responses-to-chat-completion fallback streaming pathway
  • Bug Fixes

    • Updated OpenRouter API endpoints from /alpha/responses to /v1/responses
  • Refactor

    • Improved internal streaming architecture for better state management and lifecycle consistency
  • Tests

    • Expanded tool calls streaming test coverage across all providers

Walkthrough

Refactors streaming across providers to use per-stream state objects, unify final/error emission via ProcessAndSendResponse/ProcessAndSendBifrostError with a stream-end context flag, add Responses←Chat fallback state, and add streaming tool-call tests and flags. Error enrichment and per-output lifecycle events are standardized.

Changes

Cohort / File(s) Summary
Core streaming state & mux
core/schemas/mux.go, core/schemas/bifrost.go
Added ChatToResponsesStreamState with acquire/release pool; updated chat→responses conversion to accept state and return multiple Responses-stream messages; added BifrostContextKeyIsResponsesToChatCompletionFallback.
Anthropic streaming
core/providers/anthropic/anthropic.go, core/providers/anthropic/responses.go, core/providers/anthropic/chat.go
Introduced AnthropicResponsesStreamState and pool; replaced accumulator with stateful streaming; mid-stream errors enriched (RequestType/Provider/ModelRequested), stream-end flag set in ctx, errors via ProcessAndSendBifrostError; successful chunks emitted via ProcessAndSendResponse; small ID-source fix for tool-use blocks.
Bedrock streaming
core/providers/bedrock/bedrock.go, core/providers/bedrock/responses.go
Added BedrockResponsesStreamState and pool; ToBifrostResponsesStream now accepts state and can return multiple responses; added FinalizeBedrockStream to close outputs and emit final Completed responses; unified end/error handling via stream-end indicator in ctx.
Cohere streaming
core/providers/cohere/cohere.go, core/providers/cohere/responses.go
Added CohereResponsesStreamState and pool; ToBifrostResponsesStream signature updated to accept state and return multiple responses; ResponsesStream uses state, enriches errors, sets end-indicator, and emits per-item responses with metadata/latency.
Per-provider fallback flag & post-hook changes
core/providers/openai/openai.go, core/providers/gemini/gemini.go, core/providers/cerebras.go, core/providers/groq.go, core/providers/mistral/mistral.go, core/providers/ollama.go, core/providers/parasail.go, core/providers/perplexity.go, core/providers/sgl.go, core/providers/vertex/vertex.go
ResponsesStream now sets BifrostContextKeyIsResponsesToChatCompletionFallback = true when delegating to ChatCompletionStream; calls now pass postHookRunner directly (removed combined converter wrapper). OpenAI implements a fallback flow converting chat chunks to Responses-style messages and unified final/error flow.
Utilities removed
core/providers/utils/utils.go
Removed HandleStreamEndWithSuccess and GetResponsesChunkConverterCombinedPostHookRunner; callers updated to use stateful converters and unified ProcessAndSendResponse/ProcessAndSendBifrostError paths.
Perplexity cleanup
core/providers/perplexity/responses.go
Removed ToBifrostResponsesResponse wrapper method.
OpenRouter path change
core/providers/openrouter.go
Switched Responses endpoint path from /alpha/responses to /v1/responses.
Bedrock signer removal
core/providers/bedrock/signer.go, core/providers/bedrock/signer_test.go
Deleted Bedrock AWS SigV4 signer implementation and its tests.
Tests & harness
tests/core-providers/config/account.go, many tests/core-providers/*_test.go, tests/core-providers/scenarios/tool_calls_streaming.go, tests/core-providers/tests.go
Added ToolCallsStreaming bool to test scenarios and enabled it across providers; added StreamingToolCallAccumulator test harness and runner RunToolCallsStreamingTest to validate streaming tool-call aggregation.
Changelogs & module
core/changelog.md, transports/changelog.md, core/go.mod
Updated changelogs for unified streaming lifecycle and OpenRouter path; moved github.com/aws/smithy-go to indirect in go.mod.
UI tweaks
ui/app/workspace/logs/views/filters.tsx, ui/app/workspace/logs/views/logEntryDetailsView.tsx
Skip rendering empty filter categories, unify loading/selection visuals, and add break-all styling for log entry values.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Bifrost
    participant Provider
    participant Converter as ToBifrostResponsesStream(state)
    participant Sink

    Note over Client,Bifrost: Start ResponsesStream (ctx may set fallback)
    Client->>Bifrost: Start stream request
    Bifrost->>Provider: Open provider stream
    Provider->>Bifrost: emits chunk/event
    Bifrost->>Converter: pass chunk + stream state
    rect rgb(230,245,255)
      Converter->>Converter: update stream state (IDs, buffers, mappings)
      Converter-->>Sink: emit 0..n Responses-style messages (Created/InProgress, deltas, item_added, args_delta, done)
    end
    alt Completed (final)
      Converter->>Bifrost: emit Completed (usage) with stream-end indicator
      Bifrost->>Client: final response (end)
    else intermediate
      Sink->>Client: streamed partial responses
    end
Loading
sequenceDiagram
    participant Provider
    participant OldConv as Old
    participant NewConv as New

    Provider->>Old: chunk
    Old-->>Provider: single Responses response

    Provider->>New: chunk + accumulator/state
    New->>New: maintain per-output state across chunks
    New-->>Provider: multiple Responses messages per chunk (Created/InProgress, deltas, item_added, done)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • core/providers/*/responses.go (Anthropic, Bedrock, Cohere): mapping correctness, stable IDs, tool-argument buffering, finalization.
    • core/schemas/mux.go and Chat→Responses conversion: multi-response semantics and state lifecycle (acquire/release).
    • core/providers/openai/openai.go: fallback branching, latency/metadata, and unified final/error emission.
    • tests/core-providers/scenarios/tool_calls_streaming.go and updated tests: ensure expectations and flags align with new streaming semantics.
    • Verify call sites after removal of utilities and deleted Bedrock signer/tests.

Possibly related PRs

Suggested reviewers

  • danpiths
  • akshaydeo

Poem

🐰
I hop through chunks with curious paws,
Mapping IDs and mending claws.
Tool calls gather, arguments bind,
Streams align — no bits left behind.
A joyful hop — the stream’s refined!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.97% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: implementing OpenAI-style lifecycle events for Responses API streaming.
Description check ✅ Passed The description follows the template structure, includes all major sections, and clearly documents the changes, affected areas, testing instructions, and checklist items.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 11-11-fix_responses_streaming_accumulation_fixes

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

@coderabbitai
coderabbitai Bot requested a review from TejasGhatte November 11, 2025 12:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
core/schemas/bifrost.go (1)

117-117: Consider adding inline documentation for the new context key.

The new BifrostContextKeyIsResponsesToChatCompletionFallback constant is used across multiple providers but lacks inline documentation explaining its purpose. Consider adding a comment similar to the others in this block (e.g., // bool (set by bifrost)).

-	BifrostContextKeySendBackRawResponse                 BifrostContextKey = "bifrost-send-back-raw-response"                   // bool
-	BifrostContextKeyIsResponsesToChatCompletionFallback BifrostContextKey = "bifrost-is-responses-to-chat-completion-fallback" // bool (set by bifrost)
+	BifrostContextKeySendBackRawResponse                 BifrostContextKey = "bifrost-send-back-raw-response"                   // bool
+	BifrostContextKeyIsResponsesToChatCompletionFallback BifrostContextKey = "bifrost-is-responses-to-chat-completion-fallback" // bool (signals responses use chat-completion fallback path)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6b4fba and e1dc603.

📒 Files selected for processing (37)
  • core/providers/anthropic/anthropic.go (1 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (3 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (3 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (1 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (1 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/utils/utils.go
  • core/providers/perplexity/responses.go
🧰 Additional context used
🧬 Code graph analysis (18)
core/providers/sgl.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/parasail.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/cerebras.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/mistral/mistral.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
tests/core-providers/tests.go (1)
tests/core-providers/scenarios/tool_calls_streaming.go (1)
  • RunToolCallsStreamingTest (217-741)
core/providers/gemini/gemini.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/groq.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/perplexity/perplexity.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/vertex/vertex.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/providers/ollama.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/openai/openai.go (4)
core/schemas/mux.go (2)
  • ChatToResponsesStreamAccumulator (961-976)
  • NewChatToResponsesStreamAccumulator (979-989)
core/schemas/bifrost.go (8)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ChatCompletionStreamRequest (88-88)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/utils/utils.go (5)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProviderSendsDoneMarker (760-769)
  • ProcessAndSendError (605-651)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (5)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
tests/core-providers/scenarios/utils.go (6)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • SampleToolTypeWeather (69-69)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (5)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
core/providers/cohere/cohere.go (4)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (4)
  • ShouldSendBackRawResponse (480-485)
  • HandleStreamEndWithSuccess (716-724)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProcessAndSendResponse (533-563)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/providers/bedrock/bedrock.go (3)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (795-886)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (3)
  • ShouldSendBackRawResponse (480-485)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
⏰ 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 (23)
core/providers/ollama.go (1)

172-180: LGTM! Fallback streaming mode enabled correctly.

The ResponsesStream implementation now properly signals the chat-completion fallback path via context and passes the postHookRunner directly, aligning with the unified streaming architecture described in the PR.

tests/core-providers/config/account.go (1)

29-29: LGTM! Test configuration field added appropriately.

The new ToolCallsStreaming field enables streaming tool call test scenarios across providers, supporting the PR's comprehensive test coverage expansion.

core/providers/sgl.go (1)

169-177: LGTM! Consistent with fallback streaming pattern.

The changes properly enable chat-completion fallback mode for responses streaming and simplify post-hook handling, consistent with the unified streaming architecture.

core/providers/cerebras.go (1)

172-180: LGTM! Fallback streaming pattern applied correctly.

The implementation follows the same pattern as other providers (Ollama, SGL), enabling the unified fallback streaming path for responses.

tests/core-providers/ollama_test.go (1)

35-35: LGTM! Streaming tool call tests enabled.

The addition of ToolCallsStreaming: true appropriately expands test coverage for Ollama's streaming tool call functionality.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Test coverage expanded appropriately.

The addition enables streaming tool call tests for OpenAI, aligning with the PR's goal of comprehensive streaming tool call testing across providers.

tests/core-providers/bedrock_test.go (1)

40-40: LGTM! Bedrock streaming tool call tests enabled.

The test configuration correctly enables streaming tool call scenarios for Bedrock, consistent with the test expansion across all providers in this PR.

tests/core-providers/azure_test.go (1)

42-42: LGTM! Streaming tool calls test enabled for Azure.

This change enables the new streaming tool calls test scenario for Azure, consistent with the PR's objectives to improve Responses API streaming across all providers.

tests/core-providers/anthropic_test.go (1)

38-38: LGTM! Streaming tool calls test enabled for Anthropic.

This change enables the new streaming tool calls test scenario for Anthropic, aligning with the broader PR effort to add streaming support across providers.

tests/core-providers/mistral_test.go (1)

38-38: LGTM! Streaming tool calls test enabled for Mistral.

Consistent test configuration change enabling streaming tool calls for the Mistral provider.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Streaming tool calls test enabled for Cohere.

This change enables the new streaming tool calls test scenario for Cohere, consistent with the PR's comprehensive test coverage improvements.

tests/core-providers/cerebras_test.go (1)

40-40: LGTM! Streaming tool calls test enabled for Cerebras.

Consistent test configuration change enabling streaming tool calls for the Cerebras provider.

core/providers/groq.go (1)

213-219: LGTM! Simplified streaming implementation for Groq.

This refactor removes the post-hook converter wrapper and sets the BifrostContextKeyIsResponsesToChatCompletionFallback context flag, simplifying the Responses-to-Chat streaming path. The change aligns with the PR's objective to replace chat-to-responses conversion with native implementations per provider.

tests/core-providers/tests.go (2)

35-35: LGTM! New streaming test scenario added to test runner.

This adds the new RunToolCallsStreamingTest to the comprehensive test suite, enabling streaming tool call tests across all providers.


77-77: LGTM! Test summary updated to include streaming scenario.

This ensures the new ToolCallsStreaming scenario is reflected in the test summary output, providing visibility into streaming test coverage.

tests/core-providers/openrouter_test.go (1)

37-37: LGTM! Streaming tool calls test enabled for OpenRouter.

This change enables the new streaming tool calls test scenario for OpenRouter, completing the test coverage across all providers in this PR.

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

203-207: Fallback flag wiring looks good

Setting the fallback marker on the context before delegating to ChatCompletionStream and passing the original postHookRunner directly matches the updated accumulator flow—no issues from my side.

core/providers/parasail.go (1)

145-149: Consistent fallback handling

Good to see Parasail flagging the responses-to-chat fallback and handing postHookRunner through unchanged; this keeps behavior aligned with the shared streaming path.

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

347-351: Gemini fallback signal matches the new contract

Applying the context flag here ensures the shared streaming handler can emit responses semantics during fallback, and keeping the original post-hook runner maintains hook behavior. Looks correct.

tests/core-providers/parasail_test.go (1)

35-37: Streaming scenario coverage enabled

Turning on ToolCallsStreaming here is the right move to validate the new streaming accumulators for Parasail.

tests/core-providers/vertex_test.go (1)

35-37: Vertex scenarios now exercise streaming tool calls

Enabling ToolCallsStreaming ensures the Vertex integration is validated against the updated streaming path; no issues spotted.

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

212-212: LGTM: Context value properly flags fallback mode.

The context value injection correctly signals that this is a Responses-to-ChatCompletion fallback path, enabling downstream handlers to perform appropriate chunk conversions.


213-218: The streaming conversion refactoring is correctly implemented.

Verification confirms all requested functionality is in place:

  1. OpenAI handler properly detects BifrostContextKeyIsResponsesToChatCompletionFallback context flag (core/providers/openai/openai.go:706-710)
  2. Chat-to-responses chunk conversion is performed via ToBifrostResponsesStreamResponse() method with the accumulator (core/providers/openai/openai.go:870)
  3. Tool call streaming is fully supported in the conversion path, including function call arguments delta emission (core/schemas/mux.go:1111-1207)

The removal of the GetResponsesChunkConverterCombinedPostHookRunner wrapper and direct passing of postHookRunner is correct—the conversion logic is properly delegated to the OpenAI streaming handler via context detection.

Comment thread core/providers/anthropic/responses.go Outdated
Comment thread core/providers/cohere/responses.go
Comment thread core/schemas/mux.go Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from e1dc603 to bf47123 Compare November 11, 2025 14:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

484-610: Guard MessageID before formatting item IDs.

Lines 484 and 606 build IDs with fmt.Sprintf("msg_%s...", *accumulator.MessageID, …) prior to checking whether MessageID exists. Anthropic can emit chunks without an ID, so this will panic and abort streaming. Please compute the fallback ID first and only append the message prefix when the pointer is non-nil.

-				itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-				if accumulator.MessageID == nil {
-					itemID = fmt.Sprintf("item_%d", outputIndex)
-				}
+				itemID := fmt.Sprintf("item_%d", outputIndex)
+				if accumulator.MessageID != nil && *accumulator.MessageID != "" {
+					itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+				}

Apply the same pattern to the reasoning branch just below.

♻️ Duplicate comments (2)
core/schemas/mux.go (1)

1059-1087: Avoid panicking when MessageID is absent.

Line 1059 calls fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, …) before confirming accumulator.MessageID is non-nil. Several providers legitimately emit chunks without IDs, so this path will panic and kill the stream. Please compute the fallback ID first and only touch *accumulator.MessageID when it’s available.

-			itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-			if accumulator.MessageID == nil {
-				itemID = fmt.Sprintf("item_%d", outputIndex)
-			}
+			itemID := fmt.Sprintf("item_%d", outputIndex)
+			if accumulator.MessageID != nil && *accumulator.MessageID != "" {
+				itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+			}
core/providers/cohere/responses.go (1)

576-632: Handle missing MessageID without panicking.

Lines 577 and 606 dereference *accumulator.MessageID while building the item ID, and only afterwards fall back to item_%d. When Cohere omits a message ID (which it does on some streams), this crashes the goroutine. Please flip the logic so the fallback is computed first, and touch *accumulator.MessageID only when it’s non-nil/non-empty.

-				itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-				if accumulator.MessageID == nil {
-					itemID = fmt.Sprintf("item_%d", outputIndex)
-				}
+				itemID := fmt.Sprintf("item_%d", outputIndex)
+				if accumulator.MessageID != nil && *accumulator.MessageID != "" {
+					itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+				}

Please apply the same guard to the reasoning branch.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1dc603 and bf47123.

📒 Files selected for processing (37)
  • core/providers/anthropic/anthropic.go (1 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (3 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (3 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (1 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (1 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • tests/core-providers/bedrock_test.go
  • tests/core-providers/gemini_test.go
  • core/providers/vertex/vertex.go
  • tests/core-providers/azure_test.go
  • tests/core-providers/groq_test.go
  • tests/core-providers/config/account.go
  • core/providers/cerebras.go
  • core/providers/perplexity/perplexity.go
  • core/providers/sgl.go
  • tests/core-providers/mistral_test.go
  • tests/core-providers/anthropic_test.go
  • tests/core-providers/parasail_test.go
  • tests/core-providers/sgl_test.go
🧰 Additional context used
🧬 Code graph analysis (14)
core/providers/groq.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
tests/core-providers/tests.go (1)
tests/core-providers/scenarios/tool_calls_streaming.go (1)
  • RunToolCallsStreamingTest (217-741)
core/providers/gemini/gemini.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/openai/openai.go (4)
core/schemas/mux.go (2)
  • ChatToResponsesStreamAccumulator (961-976)
  • NewChatToResponsesStreamAccumulator (979-989)
core/schemas/bifrost.go (8)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ChatCompletionStreamRequest (88-88)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • ProviderSendsDoneMarker (760-769)
  • ProcessAndSendError (605-651)
core/providers/ollama.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (19)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/mistral/mistral.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/providers/cohere/cohere.go (4)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (4)
  • ShouldSendBackRawResponse (480-485)
  • HandleStreamEndWithSuccess (716-724)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProcessAndSendResponse (533-563)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (5)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
tests/core-providers/scenarios/utils.go (5)
  • ToolCallInfo (293-297)
  • GetSampleChatTool (129-148)
  • SampleToolTypeWeather (69-69)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (7)
  • BifrostResponsesRequest (32-39)
  • ResponsesParameters (84-111)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
core/providers/bedrock/bedrock.go (4)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (795-886)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/schemas/provider.go (1)
  • Provider (207-234)
core/providers/utils/utils.go (3)
  • ShouldSendBackRawResponse (480-485)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
core/providers/parasail.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (28)
tests/core-providers/cerebras_test.go (1)

40-40: No issues found. Cerebras provider fully supports tool call streaming.

The test configuration change at line 40 is correct. The Cerebras provider properly delegates streaming operations to the OpenAI-compatible API handler (HandleOpenAIChatCompletionStreaming), which includes tool call streaming support. While Cerebras wasn't explicitly listed in the PR summary as a provider receiving a separate accumulator implementation, it was already included via the shared OpenAI-compatible streaming logic. The test will execute correctly when the CEREBRAS_API_KEY environment variable is provided.

tests/core-providers/ollama_test.go (1)

35-35: LGTM! Test configuration updated correctly.

The addition of ToolCallsStreaming: true enables streaming tool call tests for Ollama, consistent with the PR's goal to add comprehensive streaming tests across providers.

core/providers/parasail.go (1)

145-148: LGTM! Streaming refactored correctly.

The changes correctly signal fallback mode via context and pass the postHookRunner directly, aligning with the PR's shift to accumulator-based streaming.

core/schemas/bifrost.go (1)

117-117: LGTM! New context key added correctly.

The new BifrostContextKeyIsResponsesToChatCompletionFallback constant follows the existing pattern and enables consistent signaling of fallback streaming behavior across providers.

tests/core-providers/openrouter_test.go (1)

37-37: LGTM! Test configuration updated correctly.

Enabling ToolCallsStreaming for OpenRouter tests is consistent with the PR's comprehensive streaming test coverage.

core/providers/groq.go (1)

213-216: LGTM! Streaming refactored correctly.

The changes align with the PR's architectural shift, correctly signaling fallback mode and passing postHookRunner directly.

core/providers/ollama.go (1)

173-176: LGTM! Streaming refactored correctly.

Consistent with the refactoring pattern across other providers, correctly implementing the fallback streaming mode.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Test configuration updated correctly.

Enabling ToolCallsStreaming for OpenAI tests completes the comprehensive streaming test coverage across providers.

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

347-350: LGTM! Streaming refactored correctly.

The Gemini provider correctly implements the fallback streaming pattern, completing the consistent refactoring across all providers in this PR.

tests/core-providers/vertex_test.go (1)

36-36: LGTM!

Enabling ToolCallsStreaming for Vertex aligns with the broader PR goal of adding comprehensive streaming tool-call tests across providers.

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

203-209: LGTM!

The context flag and raw postHookRunner pass-through align with the broader refactoring pattern across providers. This enables the fallback streaming path for Responses API calls via ChatCompletionStream.

tests/core-providers/tests.go (2)

35-35: LGTM!

Adding RunToolCallsStreamingTest to the test scenario list enables end-to-end streaming validation for tool calls across all providers.


77-77: LGTM!

Including ToolCallsStreaming in the test summary provides clear visibility into which providers support streaming tool calls.

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

780-781: LGTM!

The comment clarifies that lifecycle events (response.created and response.in_progress) are now emitted by ToBifrostResponsesStream via the accumulator, aligning with the broader refactoring to standardize streaming behavior.

tests/core-providers/cohere_test.go (1)

36-36: LGTM!

Enabling ToolCallsStreaming for Cohere aligns with the broader PR goal of adding comprehensive streaming tool-call tests across providers.

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

704-712: LGTM!

The initialization of the fallback path and accumulator is clean. Checking the context flag and conditionally creating ChatToResponsesStreamAccumulator enables Responses-style streaming via the ChatCompletionStream pathway.


871-921: Verify ChunkIndex usage aligns with accumulator sequence numbers.

The fallback streaming logic correctly handles error and completed events, sets stream end indicators, and propagates per-chunk metadata. However, ChunkIndex is set to response.SequenceNumber (line 904) rather than the loop's chunkIndex variable. If SequenceNumber from the accumulator can diverge from the local chunkIndex, this could cause confusion or inconsistent indexing.

Confirm that using response.SequenceNumber for ChunkIndex is intentional and that it produces the expected monotonic sequence for consumers of the stream.


922-995: LGTM!

The non-fallback path preserves existing behavior, including usage aggregation, finish reason handling, and per-chunk metadata. The refactoring cleanly separates the two streaming modes.


1002-1009: LGTM!

Skipping the final stream-end handler when in fallback mode is correct, as the fallback path emits its own Completed event at line 910-915.

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

632-638: LGTM!

The accumulator is correctly initialized outside the event loop to persist state across events, and the model is captured from the request. Deferring Flush() ensures cleanup even if the goroutine exits early.


676-677: LGTM!

The comment clarifies that lifecycle events (response.created and response.in_progress) are now emitted by ToBifrostResponsesStream via the accumulator, aligning with the broader refactoring.


679-708: LGTM!

The refactored streaming loop correctly handles multi-response events from ToBifrostResponsesStream. Per-response metadata is populated accurately, and the isLastChunk handling properly sets the stream end indicator and returns early.


720-721: LGTM!

Resetting eventData at the end of each outer loop iteration ensures state is cleared for the next SSE event.

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

11-53: LGTM!

The BedrockStreamAccumulator design is well-structured with clear mappings for content indices, tool buffers, IDs, and state flags. The constructor and Flush method ensure proper initialization and cleanup.


587-660: LGTM!

The message start handling correctly emits OpenAI-style lifecycle events (response.created and response.in_progress) and initializes the text output item with a stable ID. The multi-response return pattern is consistent with the broader refactoring.


662-730: Verify sequenceNumber increment doesn't cause index mismatches.

Tool use start handling correctly closes the text item if still open and emits a tool call item. However, line 691 increments sequenceNumber++ locally within this function. This local mutation doesn't affect the caller's sequenceNumber variable, which could lead to duplicate or skipped sequence numbers across events.

Confirm that the local sequenceNumber++ at line 691 is handled correctly by the caller or consider passing sequenceNumber by reference to ensure consistent tracking across all emitted responses.


733-792: LGTM!

Delta handling for text and tool arguments correctly accumulates state in the accumulator and emits per-delta responses. The stop reason handling as a no-op is appropriate since finalization is handled explicitly by FinalizeBedrockStream.


794-886: LGTM!

FinalizeBedrockStream comprehensively closes all open items (text and tool calls), emits function_call_arguments.done with accumulated arguments, and concludes with a response.completed event including usage. This ensures a complete streaming lifecycle.

Comment thread core/schemas/mux.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from bf47123 to 9377f71 Compare November 11, 2025 16:46
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_text_output_standardized_to_content_blocks branch from e6b4fba to 34ac867 Compare November 11, 2025 16:46
@Pratham-Mishra04 Pratham-Mishra04 changed the title feat: improve Responses API streaming with OpenAI-style lifecycle events [DO NOT MERGE] feat: improve Responses API streaming with OpenAI-style lifecycle events Nov 11, 2025
@akshaydeo
akshaydeo changed the base branch from 11-11-fix_responses_text_output_standardized_to_content_blocks to graphite-base/815 November 11, 2025 16:53
@akshaydeo
akshaydeo force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from 9377f71 to 89a1128 Compare November 11, 2025 16:54
@graphite-app
graphite-app Bot changed the base branch from graphite-base/815 to main November 11, 2025 16:55
@akshaydeo
akshaydeo force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from 89a1128 to af3e591 Compare November 11, 2025 16:55
@codecov

codecov Bot commented Nov 11, 2025

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 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/openai/openai.go (1)

998-1009: Ensure Completed is emitted in fallback mode on EOF

In Chat→Responses fallback, if the stream ends without an explicit completed event, nothing is emitted at EOF. Emit response.completed using the accumulator to avoid hanging clients.

-		// Handle scanner errors first
+		// Handle scanner errors first
 		if err := scanner.Err(); err != nil {
 			logger.Warn(fmt.Sprintf("Error reading stream: %v", err))
 			providerUtils.ProcessAndSendError(ctx, postHookRunner, err, responseChan, schemas.ChatCompletionStreamRequest, providerName, request.Model, logger)
-		} else if !isChatCompletionsResponsesFallback {
+		} else if !isChatCompletionsResponsesFallback {
 			response := providerUtils.CreateBifrostChatCompletionChunkResponse(messageID, usage, finishReason, chunkIndex, schemas.ChatCompletionStreamRequest, providerName, request.Model)
 			if postResponseConverter != nil {
 				response = postResponseConverter(response)
 			}
 			response.ExtraFields.Latency = time.Since(startTime).Milliseconds()
 			providerUtils.HandleStreamEndWithSuccess(ctx, providerUtils.GetBifrostResponseForStreamResponse(nil, response, nil, nil, nil), postHookRunner, responseChan)
+		} else {
+			// Fallback mode: force a response.completed if not already sent
+			completed := &schemas.BifrostResponsesStreamResponse{
+				Type:           schemas.ResponsesStreamResponseTypeCompleted,
+				SequenceNumber: 0,
+				Response: &schemas.BifrostResponsesResponse{
+					CreatedAt: responsesStreamAccumulator.CreatedAt,
+					ID:        responsesStreamAccumulator.MessageID,
+				},
+			}
+			completed.ExtraFields.RequestType = schemas.ResponsesStreamRequest
+			completed.ExtraFields.Provider = providerName
+			completed.ExtraFields.ModelRequested = request.Model
+			completed.ExtraFields.ChunkIndex = responsesStreamAccumulator.SequenceNumber
+			completed.ExtraFields.Latency = time.Since(startTime).Milliseconds()
+			ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
+			providerUtils.HandleStreamEndWithSuccess(ctx, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, completed, nil, nil), postHookRunner, responseChan)
 		}
core/providers/cohere/cohere.go (1)

445-466: Fix double increment of chunkIndex in chat streaming

chunkIndex++ occurs twice per event, causing skipped indices and wrong latencies. Keep one increment.

-				chunkIndex++
...
-					lastChunkTime = time.Now()
-					chunkIndex++
+					lastChunkTime = time.Now()

Optional: initialize chunkIndex to -1 and increment once right before sending to make first chunk index 0.

♻️ Duplicate comments (4)
core/schemas/mux.go (2)

1058-1066: Guard MessageID before formatting the text item ID.

If a provider omits cr.ID, accumulator.MessageID stays nil and the fmt.Sprintf("msg_%s…") dereference panics before the fallback path runs. Please guard the dereference first so we safely fall back to the item_%d form.

Apply this diff to fix the panic:

-			itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-			if accumulator.MessageID == nil {
-				itemID = fmt.Sprintf("item_%d", outputIndex)
-			}
+			var itemID string
+			if accumulator.MessageID != nil && *accumulator.MessageID != "" {
+				itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+			} else {
+				itemID = fmt.Sprintf("item_%d", outputIndex)
+			}

1255-1288: Copy the accumulated args before taking its address.

Taking &args from the range loop reuses the same pointer for every tool call, so each function_call_arguments.done chunk ends up with the last arguments string. Please copy the string before taking its address.

Apply this diff to keep each response stable:

-		for toolCallID, args := range accumulator.ToolArgumentBuffers {
+		for toolCallID, args := range accumulator.ToolArgumentBuffers {
 			if args != "" {
 				outputIndex := accumulator.ToolCallOutputIndices[toolCallID]
 				itemID := accumulator.ItemIDs[toolCallID]
 				contentIndex := 1 // Tool calls use content_index:1
 
-				// Emit function_call_arguments.done with full arguments (no item field, just item_id and arguments)
-				response := &BifrostResponsesStreamResponse{
+				// Emit function_call_arguments.done with full arguments (no item field, just item_id and arguments)
+				argsCopy := args
+				response := &BifrostResponsesStreamResponse{
 					Type:           ResponsesStreamResponseTypeFunctionCallArgumentsDone,
 					SequenceNumber: accumulator.SequenceNumber,
 					OutputIndex:    Ptr(outputIndex),
 					ContentIndex:   Ptr(contentIndex),
-					Arguments:      &args,
+					Arguments:      &argsCopy,
 					ExtraFields:    cr.ExtraFields,
 				}
core/providers/anthropic/responses.go (1)

483-491: Fix nil deref when generating text item IDs

Dereferencing accumulator.MessageID before nil check can panic on early text deltas. Compute fallback first, then override when MessageID is set.

Apply:

-				itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-				if accumulator.MessageID == nil {
-					itemID = fmt.Sprintf("item_%d", outputIndex)
-				}
+				itemID := fmt.Sprintf("item_%d", outputIndex)
+				if accumulator.MessageID != nil && *accumulator.MessageID != "" {
+					itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+				}
core/providers/cohere/responses.go (1)

576-580: 🔴 Critical: Nil pointer dereference will cause panic.

Line 577 dereferences *accumulator.MessageID in the format string before line 578 checks if it's nil. When Cohere emits message-start without an ID, this will panic.

A previous review already flagged this exact issue, but the fix wasn't applied correctly.

Apply this diff:

-// Generate stable ID for text item
-itemID := fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-if accumulator.MessageID == nil {
-    itemID = fmt.Sprintf("item_%d", outputIndex)
-}
+// Generate stable ID for text item
+var itemID string
+if accumulator.MessageID != nil {
+    itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+} else {
+    itemID = fmt.Sprintf("item_%d", outputIndex)
+}
🧹 Nitpick comments (3)
core/providers/anthropic/responses.go (2)

529-535: Include ContentIndex in tool call item-added events

For consistency with text blocks and downstream consumers, also set ContentIndex on function_call item-added.

 				return []*schemas.BifrostResponsesStreamResponse{{
 					Type:           schemas.ResponsesStreamResponseTypeOutputItemAdded,
 					SequenceNumber: sequenceNumber,
 					OutputIndex:    schemas.Ptr(outputIndex),
-					Item:           item,
+					ContentIndex:   chunk.Index,
+					Item:           item,
 				}}, nil, false

543-551: Set MCP tool call status to in_progress on start

Align MCP start semantics with function_call starts by marking status "in_progress".

 				item := &schemas.ResponsesMessage{
 					ID:   chunk.ContentBlock.ID,
-					Type: schemas.Ptr(schemas.ResponsesMessageTypeMCPCall),
+					Type: schemas.Ptr(schemas.ResponsesMessageTypeMCPCall),
+					Status: schemas.Ptr("in_progress"),
 					ResponsesToolMessage: &schemas.ResponsesToolMessage{
core/providers/cohere/cohere.go (1)

632-706: Responses accumulator usage looks solid; minor nits

Good: per-response ExtraFields, RawResponse gating, finalization on last chunk. Consider zero-based chunk indices by starting at -1 for parity with other providers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf47123 and af3e591.

📒 Files selected for processing (39)
  • core/providers/anthropic/anthropic.go (1 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (3 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (3 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (1 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (1 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🚧 Files skipped from review as they are similar to previous changes (18)
  • core/providers/groq.go
  • core/providers/vertex/vertex.go
  • core/providers/ollama.go
  • core/providers/cerebras.go
  • core/schemas/bifrost.go
  • tests/core-providers/anthropic_test.go
  • tests/core-providers/mistral_test.go
  • tests/core-providers/cerebras_test.go
  • tests/core-providers/config/account.go
  • tests/core-providers/groq_test.go
  • core/providers/perplexity/perplexity.go
  • tests/core-providers/gemini_test.go
  • tests/core-providers/ollama_test.go
  • tests/core-providers/scenarios/tool_calls_streaming.go
  • tests/core-providers/sgl_test.go
  • core/providers/anthropic/anthropic.go
  • tests/core-providers/openai_test.go
  • tests/core-providers/cohere_test.go
🧰 Additional context used
🧬 Code graph analysis (13)
core/providers/mistral/mistral.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/parasail.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/cohere/cohere.go (4)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (4)
  • ShouldSendBackRawResponse (480-485)
  • HandleStreamEndWithSuccess (716-724)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProcessAndSendResponse (533-563)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/providers/openrouter.go (1)
core/providers/utils/utils.go (1)
  • GetPathFromContext (209-214)
tests/core-providers/tests.go (1)
tests/core-providers/scenarios/tool_calls_streaming.go (1)
  • RunToolCallsStreamingTest (217-741)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/openai/openai.go (4)
core/schemas/mux.go (2)
  • ChatToResponsesStreamAccumulator (961-976)
  • NewChatToResponsesStreamAccumulator (979-989)
core/schemas/bifrost.go (8)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ChatCompletionStreamRequest (88-88)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/utils/utils.go (5)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProviderSendsDoneMarker (760-769)
  • ProcessAndSendError (605-651)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/providers/bedrock/bedrock.go (3)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (795-886)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (3)
  • ShouldSendBackRawResponse (480-485)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/sgl.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/gemini/gemini.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
🔇 Additional comments (19)
core/providers/anthropic/chat.go (1)

705-718: LGTM! Correct alignment of tool call ID source.

The change from ContentBlock.ToolUseID to ContentBlock.ID properly aligns the streaming path with the rest of the file. Throughout this file, ContentBlock.ID is consistently used for tool_use blocks (lines 135, 288, 573, 679, 887), while ToolUseID is reserved for tool_result blocks that reference a previous tool use. This fix ensures the streaming implementation follows the same pattern.

tests/core-providers/openrouter_test.go (1)

37-37: ****

The change is correct and follows the established pattern across all providers. ToolCallsStreaming is intentionally enabled for OpenRouter (consistent with OpenAI, Anthropic, Bedrock, Cohere, and 10+ other providers), and the test infrastructure properly supports this scenario. TestOpenRouter is part of the standard test suite and will execute the ToolCallsStreamingResponses test when the OPENROUTER_API_KEY is provided. While some Beta features are disabled (End2EndToolCalling, ImageURL, etc.), ToolCallsStreaming is explicitly enabled.

tests/core-providers/parasail_test.go (1)

35-36: Streaming scenario flag looks good.

This keeps the Parasail comprehensive suite aligned with the new tool-call streaming harness.

tests/core-providers/vertex_test.go (1)

36-37: Vertex streaming flag aligns with the new tests.

Enabling ToolCallsStreaming here ensures the Vertex run exercises the new accumulator flow.

tests/core-providers/azure_test.go (1)

42-43: Azure config keeps parity with the streaming coverage.

Even with the skip in place, this flag keeps Azure’s scenario matrix consistent with other providers.

tests/core-providers/tests.go (1)

35-36: Exercise and report the new streaming scenario.

Wiring RunToolCallsStreamingTest into the suite and summary gives us parity with the added config flag.

Also applies to: 77-78

tests/core-providers/bedrock_test.go (1)

40-41: LGTM: ToolCallsStreaming enabled

This exercises the new accumulator-based tool-call streaming path in Bedrock tests.

core/providers/parasail.go (1)

145-151: LGTM: Responses→Chat fallback correctly wired

Context flag + direct postHookRunner enables OpenAI-style Responses streaming via fallback.

core/providers/sgl.go (1)

170-176: LGTM: Responses→Chat fallback path

Consistent context flag and hook handling for streaming.

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

203-210: LGTM: Responses→Chat fallback path

Context flag used; passes postHookRunner directly.

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

346-354: LGTM: Responses→Chat fallback path

Flag + direct hook aligns Gemini streaming with shared fallback.

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

11-53: LGTM! Clean accumulator design.

The BedrockStreamAccumulator structure and initialization are well-designed, tracking all necessary state for streaming conversion (content-to-output mappings, tool buffers, IDs, and lifecycle flags). The Flush() method properly resets all state for potential reuse.


587-660: Lifecycle event emission looks correct.

The message start handling properly:

  • Generates a stable message ID when needed
  • Guards against duplicate created/in_progress events with flags
  • Emits output_item.added for the initial text message
  • Dereferences MessageID safely (it's guaranteed non-nil by lines 594-597)

794-886: Comprehensive finalization logic.

FinalizeBedrockStream properly closes all open items:

  • Text items with output_item.done
  • Tool calls with both function_call_arguments.done and output_item.done
  • Final response.completed with usage

The sequence number increment (sequenceNumber + len(responses)) ensures unique sequence numbers across all emitted events.

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

869-872: Proper accumulator lifecycle management.

The accumulator is created once per stream, model is set, and deferred flush ensures cleanup. This is the correct pattern for managing stateful streaming conversions.


884-904: Clean EOF finalization.

On io.EOF, FinalizeBedrockStream is called to produce closing events, and each final response is emitted with proper metadata. The chunk index increment and latency tracking are correctly applied.


944-964: Multi-response emission pattern is sound.

Iterating over the slice returned by ToBifrostResponsesStream and emitting each response with per-response metadata (ChunkIndex, Latency, RawResponse) follows the new streaming contract correctly.

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

11-67: LGTM! Accumulator design is sound.

The CohereStreamAccumulator structure mirrors the Bedrock pattern effectively. The GetOrCreateOutputIndex helper is a nice addition that simplifies content-to-output mapping throughout the streaming logic.


736-808: Tool call handling looks solid.

The tool call lifecycle (start → deltas → end) properly:

  • Closes any open tool plan item at start
  • Assigns unique output indices to avoid collision with text
  • Accumulates arguments across deltas
  • Emits both function_call_arguments.done and output_item.done at end

Comment thread core/providers/cohere/responses.go Outdated
Comment thread core/providers/cohere/responses.go Outdated
@Pratham-Mishra04 Pratham-Mishra04 changed the title [DO NOT MERGE] feat: improve Responses API streaming with OpenAI-style lifecycle events feat: improve Responses API streaming with OpenAI-style lifecycle events Nov 13, 2025
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from af3e591 to eacd85a Compare November 13, 2025 11:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

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

610-614: Fix nil MessageID dereference in reasoning item IDs.

fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, …) is executed before you confirm accumulator.MessageID is non-nil. Cohere, Bedrock, and Anthropic frequently omit message.id on early reasoning chunks, so this panics and kills the stream. Build the item ID only after the nil check so we safely fall back to the anonymous ID path.

-				// Generate stable ID for reasoning item
-				itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-				if accumulator.MessageID == nil {
-					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-				}
+				// Generate stable ID for reasoning item
+				var itemID string
+				if accumulator.MessageID != nil {
+					itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+				} else {
+					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+				}
♻️ Duplicate comments (1)
core/providers/cohere/responses.go (1)

611-614: Prevent nil MessageID panic when emitting reasoning items.

fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, …) still dereferences MessageID before you know it’s set. Cohere emits message-start without IDs, so this panics on the very first reasoning chunk and drops the stream. Guard the dereference exactly as you did for text/tool-plan IDs.

-				// Generate stable ID for reasoning item
-				itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-				if accumulator.MessageID == nil {
-					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-				}
+				// Generate stable ID for reasoning item
+				var itemID string
+				if accumulator.MessageID != nil {
+					itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+				} else {
+					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+				}
🧹 Nitpick comments (4)
core/providers/cohere/cohere.go (2)

651-657: SSE parsing should accumulate multi-line data blocks before unmarshaling

You parse each "data: " line as a complete event. SSE events can contain multiple data lines and are delimited by a blank line. This can split JSON across lines and cause parse errors or truncated events. Accumulate consecutive "data:" lines and only unmarshal on blank-line delimiter, then clear the buffer.

Minimal sketch:

- var eventData string
+ var eventBuf []string
...
- if after, ok := strings.CutPrefix(line, "data: "); ok {
-   eventData = after
- } else {
+ if after, ok := strings.CutPrefix(line, "data: "); ok {
+   eventBuf = append(eventBuf, after)
+   continue
+ }
+ // blank line signals end-of-event
+ if line == "" && len(eventBuf) > 0 {
+   eventData := strings.Join(eventBuf, "\n")
+   eventBuf = eventBuf[:0]
+   // unmarshal eventData and process...
+   // ...
+   continue
+ } else {
    continue
- }
...
- // Reset for next event
- eventData = ""
+ // eventBuf is reset when blank line is seen

Also applies to: 720-722


635-637: Optional: Remove or use accumulator.Model

You set accumulator.Model but don’t use it in this function. Either use it when building envelopes inside ToBifrostResponsesStream or drop the assignment to avoid confusion.

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

635-641: Avoid “msg_msg_…” IDs for text items

MessageID is already prefixed with "msg_". Formatting msg_%s_item_%d yields "msg_msg__item_0".

Use:

-       itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+       itemID = fmt.Sprintf("%s_item_%d", *accumulator.MessageID, outputIndex)

824-875: Finalize should also close tool-call items with empty arguments

You only emit arguments.done + output_item.done when args != "". If a tool call streamed with zero-length args and the stream ends before content_block_stop, the item may remain open.

Consider emitting at least response.output_item.done for any outputIndex present in ToolArgumentBuffers or ItemIDs, even when args == "". Example:

- for outputIndex, args := range accumulator.ToolArgumentBuffers {
-   if args != "" {
+ for outputIndex, args := range accumulator.ToolArgumentBuffers {
+   if true { // always check for closure; gate arguments.done on args != ""
      // ... emit arguments.done only if args != ""
      if args != "" {
         // existing arguments.done emission
      }
      // always emit output_item.done
      // existing output_item.done emission
   }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af3e591 and eacd85a.

📒 Files selected for processing (40)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (1 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (3 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (3 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (1 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/utils/utils.go
  • core/providers/perplexity/responses.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • core/providers/ollama.go
  • tests/core-providers/parasail_test.go
  • core/providers/gemini/gemini.go
  • core/providers/mistral/mistral.go
  • tests/core-providers/sgl_test.go
  • tests/core-providers/openai_test.go
  • core/providers/sgl.go
  • tests/core-providers/gemini_test.go
  • tests/core-providers/ollama_test.go
  • core/providers/anthropic/chat.go
  • tests/core-providers/azure_test.go
  • core/providers/parasail.go
  • tests/core-providers/cohere_test.go
  • core/providers/vertex/vertex.go
  • tests/core-providers/bedrock_test.go
  • tests/core-providers/vertex_test.go
  • core/schemas/bifrost.go
  • tests/core-providers/groq_test.go
  • tests/core-providers/tests.go
  • tests/core-providers/openrouter_test.go
  • core/providers/groq.go
  • tests/core-providers/mistral_test.go
🧰 Additional context used
🧬 Code graph analysis (11)
core/providers/openrouter.go (1)
core/providers/utils/utils.go (1)
  • GetPathFromContext (209-214)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/perplexity/perplexity.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/bedrock.go (4)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (800-891)
core/schemas/bifrost.go (3)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/schemas/provider.go (1)
  • Provider (207-234)
core/providers/utils/utils.go (3)
  • ShouldSendBackRawResponse (480-485)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
core/schemas/mux.go (3)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/openai/openai.go (5)
core/schemas/mux.go (2)
  • ChatToResponsesStreamAccumulator (961-977)
  • NewChatToResponsesStreamAccumulator (980-991)
core/schemas/bifrost.go (8)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ChatCompletionStreamRequest (88-88)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/utils/utils.go (5)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProviderSendsDoneMarker (760-769)
  • ProcessAndSendError (605-651)
core/schemas/chatcompletions.go (1)
  • ChatStreamResponseChoice (529-531)
core/providers/cerebras.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (5)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
tests/core-providers/scenarios/utils.go (5)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (5)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/providers/cohere/cohere.go (4)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (4)
  • ShouldSendBackRawResponse (480-485)
  • HandleStreamEndWithSuccess (716-724)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProcessAndSendResponse (533-563)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
🔇 Additional comments (5)
core/changelog.md (1)

1-3: Verify changelog entries align with PR scope and clarify entry 2.

The PR introduces significant streaming refactors across multiple providers (Anthropic, Bedrock, Cohere, etc.) with stateful accumulators and lifecycle events. However, only two new changelog entries are present:

  1. Entry on line 2 ("responses text output standardization to content blocks") is vague and doesn't convey what "standardization" means or its impact to users.
  2. Entry on line 3 (openrouter endpoint shift) is clear and specific.
  3. The removed entry on line 1 lacks context—why was it removed? If superseded, clarify the relationship.

Please clarify whether these entries adequately represent the scope of user-facing changes, or if additional entries are needed for the streaming accumulator and lifecycle event improvements.

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

211-219: LGTM! Clean refactor aligning with PR objectives.

The changes properly inject the fallback context key and simplify the hook runner usage by removing the wrapper layer. This aligns with the broader pattern applied across providers in this PR and improves code maintainability.

core/providers/openrouter.go (1)

214-214: OpenRouter /v1/responses endpoint is supported — changes are correct.

OpenRouter provides a Responses API at the v1 endpoint and it is OpenAI Responses‑compatible. The migration from /alpha/responses to /v1/responses on lines 214 and 233 is appropriate and consistent across both the non-streaming and streaming response methods.

core/providers/cerebras.go (1)

172-180: LGTM! Clean refactoring aligned with the broader streaming improvements.

The changes correctly implement the Responses-to-Chat fallback pattern:

  1. The context flag properly signals the fallback path to downstream handlers
  2. Passing postHookRunner directly simplifies the architecture by removing the wrapper layer

These changes align with the PR's goal of standardizing streaming across providers.

Optional verification to ensure integration completeness:

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

484-491: LGTM: Safe text item ID generation

Nil-check before dereferencing MessageID prevents panics on reordered/partial streams. Good fix.

Comment thread core/providers/bedrock/bedrock.go
Comment thread core/providers/bedrock/responses.go
Comment thread tests/core-providers/scenarios/tool_calls_streaming.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from eacd85a to 6c2ea61 Compare November 13, 2025 12:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
core/providers/cohere/responses.go (1)

610-614: Nil-pointer panic when building reasoning item ID.

Dereferences *accumulator.MessageID before nil-check. Reorder.

Apply:

-                itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-                if accumulator.MessageID == nil {
-                    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-                }
+                var itemID string
+                if accumulator.MessageID != nil {
+                    itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+                } else {
+                    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+                }
core/providers/bedrock/bedrock.go (1)

885-905: Signal stream completion and set total latency on the last final response.

Finalize emits responses but never calls HandleStreamEndWithSuccess; also the final response latency should be total.

Apply:

-                    for _, finalResponse := range finalResponses {
+                    for i, finalResponse := range finalResponses {
                         finalResponse.ExtraFields = schemas.BifrostResponseExtraFields{
                             RequestType:     schemas.ResponsesStreamRequest,
                             Provider:        providerName,
                             ModelRequested:  request.Model,
                             ModelDeployment: deployment,
                             ChunkIndex:      chunkIndex,
-                            Latency:         time.Since(lastChunkTime).Milliseconds(),
+                            Latency:         time.Since(lastChunkTime).Milliseconds(),
                         }
                         chunkIndex++
                         lastChunkTime = time.Now()
                         if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) {
                             finalResponse.ExtraFields.RawResponse = "{}" // Final event has no payload
                         }
-                        providerUtils.ProcessAndSendResponse(ctx, postHookRunner, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, finalResponse, nil, nil), responseChan)
+                        // Ensure the very last response signals stream end and uses total latency
+                        if i == len(finalResponses)-1 {
+                            finalResponse.ExtraFields.Latency = time.Since(startTime).Milliseconds()
+                            providerUtils.HandleStreamEndWithSuccess(
+                                ctx,
+                                providerUtils.GetBifrostResponseForStreamResponse(nil, nil, finalResponse, nil, nil),
+                                postHookRunner,
+                                responseChan,
+                            )
+                        } else {
+                            providerUtils.ProcessAndSendResponse(ctx, postHookRunner, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, finalResponse, nil, nil), responseChan)
+                        }
                     }
🧹 Nitpick comments (3)
core/schemas/mux.go (1)

1112-1221: Handle multiple tool_call deltas per chunk, not just the first.

Only delta.ToolCalls[0] is processed; chunks may carry multiple tool calls. Iterate and emit for each to avoid lost calls.

If helpful, I can draft a safe loop that preserves sequenceNumber per emitted response.

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

576-586: Remove redundant fallback assignment.

itemID is set with a nil-safe branch already; the second if accumulator.MessageID == nil { ... } is dead code.

Apply:

-                if accumulator.MessageID == nil {
-                    itemID = fmt.Sprintf("item_%d", outputIndex)
-                }

804-810: Include ContentIndex on tool-call start events.

For parity with deltas and ends, attach ContentIndex to output_item.added for tool calls.

Apply:

-                responses = append(responses, &schemas.BifrostResponsesStreamResponse{
+                responses = append(responses, &schemas.BifrostResponsesStreamResponse{
                     Type:           schemas.ResponsesStreamResponseTypeOutputItemAdded,
                     SequenceNumber: sequenceNumber + len(responses),
                     OutputIndex:    schemas.Ptr(outputIndex),
-                    Item:           item,
+                    ContentIndex:   chunk.Index,
+                    Item:           item,
                 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eacd85a and 6c2ea61.

📒 Files selected for processing (40)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (1 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (3 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (3 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (1 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (2 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • tests/core-providers/tests.go
  • tests/core-providers/anthropic_test.go
  • core/providers/vertex/vertex.go
  • core/providers/ollama.go
  • tests/core-providers/azure_test.go
  • core/providers/cerebras.go
  • tests/core-providers/cohere_test.go
  • tests/core-providers/vertex_test.go
  • tests/core-providers/config/account.go
  • core/providers/groq.go
  • core/providers/mistral/mistral.go
  • core/providers/anthropic/anthropic.go
  • core/providers/openrouter.go
  • core/changelog.md
  • core/providers/gemini/gemini.go
  • tests/core-providers/openrouter_test.go
  • tests/core-providers/sgl_test.go
  • tests/core-providers/bedrock_test.go
  • tests/core-providers/scenarios/tool_calls_streaming.go
  • core/providers/openai/openai.go
  • tests/core-providers/openai_test.go
  • core/providers/parasail.go
🧰 Additional context used
🧬 Code graph analysis (8)
core/providers/sgl.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/perplexity/perplexity.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (15)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/cohere/cohere.go (4)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (4)
  • ShouldSendBackRawResponse (480-485)
  • HandleStreamEndWithSuccess (716-724)
  • GetBifrostResponseForStreamResponse (791-819)
  • ProcessAndSendResponse (533-563)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/providers/bedrock/bedrock.go (3)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (799-890)
core/schemas/bifrost.go (2)
  • BifrostResponseExtraFields (282-291)
  • RequestType (81-81)
core/providers/utils/utils.go (3)
  • ShouldSendBackRawResponse (480-485)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (791-819)
🔇 Additional comments (22)
tests/core-providers/cerebras_test.go (1)

40-40: LGTM! Enables streaming test coverage for tool calls.

This change correctly enables the new ToolCallsStreaming test scenario for Cerebras, aligning with the PR's objective to enhance tool call streaming across providers. The test configuration is syntactically correct and consistent with similar updates across other provider tests.

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

212-218: LGTM! Consistent fallback streaming implementation.

The changes correctly set the fallback context flag and simplify the call by passing postHookRunner directly. This aligns with the broader PR pattern for enabling Responses-to-Chat fallback streaming across providers.

core/schemas/bifrost.go (1)

103-118: LGTM! New context key properly defined.

The addition of BifrostContextKeyIsResponsesToChatCompletionFallback enables the fallback streaming behavior across providers. The constant is well-named and follows existing conventions.

tests/core-providers/groq_test.go (1)

43-43: LGTM! Enables tool call streaming tests.

The addition of ToolCallsStreaming: true ensures that Groq's streaming tool call functionality is properly tested, aligning with the PR's comprehensive streaming improvements.

tests/core-providers/parasail_test.go (1)

35-35: LGTM! Enables tool call streaming tests.

Consistent with other providers, this enables streaming tool call tests for Parasail.

tests/core-providers/mistral_test.go (1)

38-38: LGTM! Enables tool call streaming tests.

This change ensures Mistral's tool call streaming is tested, consistent with the PR's goals.

tests/core-providers/ollama_test.go (2)

26-26: Model version updated.

The model was changed from "llama3.2" to "llama3.1:latest". Please confirm this change is intentional and that llama3.1 provides the necessary tool call support for the streaming tests.


35-35: LGTM! Enables tool call streaming tests.

Consistent with other providers, this enables streaming tool call tests for Ollama.

core/providers/sgl.go (1)

170-176: LGTM! Consistent fallback streaming implementation.

The changes correctly implement the fallback streaming pattern, matching the approach used in other providers like Perplexity.

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

706-732: Tool call ID field change is correct.

The struct definition in core/providers/anthropic/types.go explicitly documents that the ID field is for tool_use content (line 145), while ToolUseID is for tool_result content (line 144). The code change to use chunk.ContentBlock.ID is appropriate and aligns with the documented field purposes in the Anthropic content block structure.

tests/core-providers/gemini_test.go (1)

41-41: Enablement looks good.

ToolCallsStreaming: true aligns Gemini with the new streaming tests. No issues.

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

632-639: Good accumulator setup and teardown.

Creating one CohereStreamAccumulator per stream and flushing on exit is correct. Model propagation via accumulator is fine.


679-707: Per-response metadata and chunkIndex handling look correct.

Incrementing chunkIndex per emitted response maintains contiguous sequence numbers; latency is computed per response. LGTM.

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

35-44: LGTM! Clean initialization of the accumulator state.

The constructor properly initializes all maps and sets a consistent CreatedAt timestamp that will be used across lifecycle events.


62-80: LGTM! Solid mapping logic between Anthropic and OpenAI indexing schemes.

The method correctly handles both nil content indices and reuses existing mappings, ensuring consistent output index assignment across multiple events for the same content block.


396-442: LGTM! Proper OpenAI-style lifecycle event emission.

The implementation correctly emits both response.created and response.in_progress events at message start, using consistent timestamps and sequence numbering. The flags prevent duplicate emissions.


478-507: LGTM! Text block handling correctly generates stable item IDs.

The nil check for MessageID (line 485) is correctly ordered before any dereference, using a fallback format when the ID is unavailable. The past review concern has been properly addressed.


509-536: LGTM! Proper initialization of tool call streaming state.

The code correctly initializes the ToolArgumentBuffers for accumulating arguments and emits output_item.added with in_progress status, following the OpenAI streaming pattern.


597-635: LGTM! Tool argument streaming properly differentiates between computer tools and regular tools.

The logic correctly accumulates computer tool arguments silently (for later parsing) while streaming regular tool arguments incrementally. The defensive initialization (lines 610-612) guards against edge cases, and the MCP differentiation ensures correct event types are emitted.


715-761: LGTM! Proper completion of tool call streaming with argument finalization.

The code correctly emits arguments.done events for accumulated tool arguments before emitting output_item.done, and properly cleans up per-output state. The sequence number adjustment accounts for the optional arguments.done event.


764-783: LGTM! Clean stream completion with consistent lifecycle events.

The MessageStop handler properly emits response.completed with the stored message ID and timestamp, and correctly signals stream completion with the boolean return value. The MessageDelta passthrough is appropriate since item-level completion is already handled.


394-809: Excellent refactoring! The OpenAI-style lifecycle events implementation is well-structured.

The stateful accumulator design elegantly handles the impedance mismatch between Anthropic's content-indexed streaming and OpenAI's output-indexed format. Key strengths:

  • Consistent per-output state: The maps (ItemIDs, ToolArgumentBuffers, MCPCallOutputIndices) maintain state keyed by output index, ensuring deltas and completions reference the correct items
  • Proper lifecycle emission: The created → in_progress → [deltas] → completed flow matches OpenAI's pattern
  • Special-case handling: Computer tools and MCP calls are correctly differentiated with appropriate early returns and event types
  • Sequence integrity: Multiple responses per event correctly adjust sequence numbers

The past nil-dereference concern has been properly addressed (lines 485-489).

Comment thread core/providers/bedrock/responses.go
Comment thread core/providers/cohere/responses.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch 2 times, most recently from f6d7664 to 7a0cd1f Compare November 14, 2025 04:35

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Nov 14, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Nov 14, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 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/anthropic/anthropic.go (1)

469-595: Avoid emitting a “success” final chunk after a streaming error in ChatCompletion

When event.ToBifrostChatCompletionStream() returns a bifrostErr (Line 548), the code:

  • enriches the error with RequestType, Provider, and ModelRequested,
  • sets BifrostContextKeyStreamEndIndicator, and
  • calls ProcessAndSendBifrostError, then breaks the scan loop.

Because the goroutine then falls through to the post-loop else block (Lines 591–595), it also constructs and sends a final chat completion “done” chunk via CreateBifrostChatCompletionChunkResponse, effectively signalling both an error and a successful completion for the same stream.

That’s misleading for consumers and inconsistent with the new lifecycle semantics.

You likely want to stop the goroutine entirely after sending the error. A minimal fix is:

-			if bifrostErr != nil {
-				bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{
-					RequestType:    schemas.ChatCompletionStreamRequest,
-					Provider:       providerType,
-					ModelRequested: modelName,
-				}
-				ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
-				providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, bifrostErr, responseChan, logger)
-				break
-			}
+			if bifrostErr != nil {
+				bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{
+					RequestType:    schemas.ChatCompletionStreamRequest,
+					Provider:       providerType,
+					ModelRequested: modelName,
+				}
+				ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
+				providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, bifrostErr, responseChan, logger)
+				return
+			}

This avoids sending a trailing “done” chunk after an error while preserving the enriched error event and stream-end flag.

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

574-647: Function/MCP arguments deltas: Bifrost-side emission is fine, but back-conversion likely drops them

On AnthropicStreamDeltaTypeInputJSON, you now:

  • Accumulate per-output arguments in ToolArgumentBuffers[outputIndex].
  • Emit either response.function_call_arguments.delta or response.mcp_call_arguments.delta with Delta = chunk.Delta.PartialJSON and OutputIndex/ItemID set.

This is reasonable for Bifrost’s internal representation. However, in the same file, ToAnthropicResponsesStreamResponse’s ResponsesStreamResponseTypeFunctionCallArgumentsDelta branch currently reads from bifrostResp.Arguments rather than bifrostResp.Delta when constructing PartialJSON. That means any deltas produced by this new path (which only set Delta) will result in Anthropic SSE events with an empty partial_json when converting back to Anthropic.

I’d suggest making the converter tolerant of both fields so we don’t break existing callers:

 case schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDelta:
   streamResp.Type = AnthropicStreamEventTypeContentBlockDelta
   if bifrostResp.ContentIndex != nil {
     streamResp.Index = bifrostResp.ContentIndex
   }
-  if bifrostResp.Arguments != nil {
-    streamResp.Delta = &AnthropicStreamDelta{
-      Type:        AnthropicStreamDeltaTypeInputJSON,
-      PartialJSON: bifrostResp.Arguments,
-    }
-  }
+  var partialJSON *string
+  if bifrostResp.Delta != nil {
+    partialJSON = bifrostResp.Delta
+  } else if bifrostResp.Arguments != nil { // backward-compat
+    partialJSON = bifrostResp.Arguments
+  }
+  if partialJSON != nil {
+    streamResp.Delta = &AnthropicStreamDelta{
+      Type:        AnthropicStreamDeltaTypeInputJSON,
+      PartialJSON: partialJSON,
+    }
+  }

This keeps your new emission logic intact while ensuring the reverse mapping remains correct for both new and legacy producers.

♻️ Duplicate comments (2)
core/providers/cohere/responses.go (1)

611-614: 🔴 Critical: Fix nil pointer dereference when generating reasoning item ID.

Line 611 dereferences *accumulator.MessageID before checking for nil on line 612. When Cohere emits a message-start event without an ID, this will panic.

Apply this fix:

-// Generate stable ID for reasoning item
-itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-if accumulator.MessageID == nil {
-    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-}
+// Generate stable ID for reasoning item
+var itemID string
+if accumulator.MessageID == nil {
+    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+} else {
+    itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+}
core/providers/bedrock/responses.go (1)

824-857: 🔴 Critical: Fix pointer-to-range-variable bug in tool argument finalization.

Taking &args on line 849 where args is the range variable will cause all function_call_arguments.done responses to point to the same memory location. After the loop completes, they'll all reference the last value, corrupting earlier events.

Apply this fix:

 // Close any open tool call items and emit function_call_arguments.done
 for outputIndex, args := range accumulator.ToolArgumentBuffers {
     if args != "" {
         itemID := accumulator.ItemIDs[outputIndex]
         callID := accumulator.ToolCallIDs[outputIndex]
         toolName := accumulator.ToolCallNames[outputIndex]

         // Create item with tool message info for the done event
         var doneItem *schemas.ResponsesMessage
         if callID != "" || toolName != "" {
             doneItem = &schemas.ResponsesMessage{
                 ResponsesToolMessage: &schemas.ResponsesToolMessage{},
             }
             if callID != "" {
                 doneItem.ResponsesToolMessage.CallID = &callID
             }
             if toolName != "" {
                 doneItem.ResponsesToolMessage.Name = &toolName
             }
         }

         // Emit function_call_arguments.done with full arguments
+        argsCopy := args
         response := &schemas.BifrostResponsesStreamResponse{
             Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
             SequenceNumber: sequenceNumber + len(responses),
             OutputIndex:    schemas.Ptr(outputIndex),
-            Arguments:      &args,
+            Arguments:      &argsCopy,
         }
🧹 Nitpick comments (10)
tests/core-providers/openrouter_test.go (1)

37-39: Consider clarifying the comment on line 39.

The changes appropriately disable ToolCallsStreaming for the beta Responses API. However, the comment on line 39 may cause confusion since End2EndToolCalling is being enabled (set to true) while the comment references a beta limitation.

If End2EndToolCalling uses a stable non-streaming path or doesn't rely on the beta Responses API, consider updating the comment to clarify this distinction, for example:

End2EndToolCalling:    true, // Non-streaming path is stable

Otherwise, if both fields are affected by the same beta limitation, briefly explain why one is enabled and the other disabled.

tests/core-providers/scenarios/tool_calls_streaming.go (2)

168-178: Consider more robust complete-JSON detection.

The heuristic for detecting complete JSON (checking if it starts with { and ends with }) is somewhat fragile. It won't handle:

  • JSON with leading/trailing whitespace
  • JSON arrays ([...])
  • Multi-line JSON with different formatting

However, the current safeguard of only replacing when existing.Arguments != "" prevents data loss on the first chunk. A more robust approach might check if the string is valid, complete JSON using json.Valid() and comparing length/structure.

That said, the current implementation is reasonable given the complexity of handling different provider streaming formats, and the comment acknowledges the edge case.

Example alternative:

-		argsStr := *arguments
-		if len(argsStr) > 0 && argsStr[0] == '{' && argsStr[len(argsStr)-1] == '}' && existing.Arguments != "" {
-			// This looks like complete arguments, but only replace if we already have partial args
-			// Otherwise, this might be the first chunk which happens to be complete
-			existing.Arguments = argsStr
+		argsStr := strings.TrimSpace(*arguments)
+		if json.Valid([]byte(argsStr)) && existing.Arguments != "" {
+			// This is valid complete JSON, and we already have partial args - likely a "done" event
+			existing.Arguments = argsStr
 		} else {
 			// Incremental chunk, append
-			existing.Arguments += argsStr
+			existing.Arguments += *arguments
 		}

772-784: Consider stricter JSON validation for final tool calls.

The validation attempts to parse arguments as JSON (line 774) but only logs a warning if parsing fails, then falls back to checking for non-empty content. While this flexibility is useful for handling partial JSON during streaming, by the time we're in validateStreamingToolCalls, we're validating the final accumulated tool calls, not individual chunks.

At this stage, arguments should be complete and valid JSON. Consider making JSON validation mandatory for the final result:

 		// Try to parse arguments as JSON to ensure they're valid
 		var args map[string]interface{}
 		if err := json.Unmarshal([]byte(toolCall.Arguments), &args); err != nil {
-			t.Logf("⚠️ %s: Tool call %d arguments are not valid JSON: %v", apiName, i, err)
-			// Don't fail on this - some providers might send partial JSON during streaming
-			// But we should at least have some content
-			if strings.TrimSpace(toolCall.Arguments) == "" {
-				t.Errorf("❌ %s: Tool call %d has empty arguments", apiName, i)
-			}
+			t.Errorf("❌ %s: Tool call %d has invalid JSON arguments: %v. Arguments: %s", apiName, i, err, toolCall.Arguments)
 		} else {
 			t.Logf("✅ %s: Tool call %d has valid JSON arguments: %s", apiName, i, toolCall.Arguments)
 		}

If some providers legitimately produce non-JSON arguments, this should be documented with a comment explaining why.

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

606-620: SpeechStream end-of-stream now uses unified response pipeline

Switching the final SpeechStream “done” chunk to:

  • set BifrostContextKeyStreamEndIndicator on ctx, and
  • send via ProcessAndSendResponse(GetBifrostResponseForStreamResponse(..., response, nil))

correctly routes the last event through post-hooks and the shared stream envelope logic, while preserving usage and latency metadata.

If you want strictly uniform semantics, you could also set the stream-end indicator before calling ProcessAndSendError in the scanner.Err() path, mirroring the success path.


863-883: TranscriptionStream final “done” chunk handling is aligned with speech stream

The updated end-of-stream handling:

  • constructs a BifrostTranscriptionStreamResponse with full text and usage,
  • sets BifrostContextKeyStreamEndIndicator on ctx, and
  • sends via ProcessAndSendResponse(GetBifrostResponseForStreamResponse(..., response))

is consistent with the new lifecycle model and ensures post-hooks see a single, well-formed final event.

Similar to SpeechStream, consider setting the stream-end indicator in the scanner.Err() error path if you want downstream consumers to rely solely on the context flag to detect completion vs. transport errors.

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

726-847: ResponsesStream accumulator and finalization logic look coherent

For ResponsesStream:

  • Missing BodyStream now triggers a Bifrost operation error with BifrostContextKeyStreamEndIndicator set (Lines 726–733).
  • A per-stream AnthropicStreamAccumulator is created once, flushed via defer, and passed into event.ToBifrostResponsesStream(chunkIndex, accumulator) (Lines 743–749, 794).
  • Each emitted response is wrapped with consistent ExtraFields (request type, provider, model, chunk index, per-chunk latency, optional raw response) and sent via ProcessAndSendResponse.
  • For the final response (isLastChunk && i == len(responses)-1), the code ensures response.Response is non-nil, attaches accumulated usage, overwrites latency with total duration, sets BifrostContextKeyStreamEndIndicator, and returns after sending the final event (Lines 822–832).

This matches the accumulator-based lifecycle model introduced elsewhere in the PR and should give clients a clear, single “completed” Responses event.

You may also want to mirror the new pattern by setting BifrostContextKeyStreamEndIndicator before ProcessAndSendError in the scanner.Err() path to keep completion signalling fully uniform between success and transport-error endings.

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

452-485: ChatCompletionStream error path correctly enriched; consider chunkIndex semantics

The new block after event.ToBifrostChatCompletionStream:

  • populates BifrostErrorExtraFields with RequestType, Provider, and ModelRequested,
  • sets BifrostContextKeyStreamEndIndicator, and
  • routes the error through ProcessAndSendBifrostError,

is a solid improvement for observability and consistent stream termination signalling.

Note that chunkIndex is incremented both just before processing the event (Line 445) and again after sending a response (Line 474). This means indices advance by 2 per event and also bump for events that produce no response. If the intent is “one increment per emitted chunk”, you might consider removing one of these increments to make ChunkIndex strictly contiguous per client-visible message.


677-723: ResponsesStream multi-response handling and finalization align with the new lifecycle model

Within the SSE loop:

  • event.ToBifrostResponsesStream(chunkIndex, accumulator) now returns a slice of responses plus isLastChunk.
  • On bifrostErr, the code enriches the error with RequestType, Provider, and ModelRequested, sets BifrostContextKeyStreamEndIndicator, and sends it via ProcessAndSendBifrostError, then breaks (Lines 681–689).
  • For each non-nil response in responses, you set consistent ExtraFields (request type, provider, model, current chunkIndex, per-chunk latency, optional raw payload) and increment chunkIndex per emitted item (Lines 692–703).
  • For the final item of the final chunk, you ensure response.Response is non-nil, set total latency, mark the stream end in context, send via ProcessAndSendResponse, and return from the goroutine (Lines 708–715).

This is a clean implementation of accumulator-driven Responses streaming and mirrors the Anthropic changes.

As with other providers, you may optionally set BifrostContextKeyStreamEndIndicator in the scanner.Err() error path as well, so downstream code sees a consistent “terminal” signal regardless of whether the stream ends normally or via transport error.

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

705-714: Chat → Responses fallback logic looks sound; consider a tiny readability tweak

The isResponsesToChatCompletionsFallback gate and ChatToResponsesStreamAccumulator usage cleanly separate fallback vs native chat streaming; error and completion events correctly mark the stream end via context and use the Responses stream pathway.

Only nit: the inner for _, response := range spreadResponses in the fallback branch shadows the outer response variable, which can be mildly confusing in a long function. Renaming the inner loop variable (e.g. resp) would slightly improve clarity without behavioral change.

Also applies to: 873-923, 1004-1012

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

577-585: Remove redundant duplicate nil check.

Lines 578-582 already handle both nil and non-nil cases for MessageID. The second check on lines 583-585 is redundant and will unconditionally overwrite the previous assignment when MessageID is nil, making the else branch (lines 580-582) unreachable in that case.

Apply this diff:

 // Generate stable ID for text item
 var itemID string
 if accumulator.MessageID == nil {
     itemID = fmt.Sprintf("item_%d", outputIndex)
 } else {
     itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
 }
-if accumulator.MessageID == nil {
-    itemID = fmt.Sprintf("item_%d", outputIndex)
-}
 accumulator.ItemIDs[outputIndex] = itemID
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c2ea61 and 7a0cd1f.

📒 Files selected for processing (41)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (7 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
✅ Files skipped from review due to trivial changes (1)
  • core/changelog.md
🚧 Files skipped from review as they are similar to previous changes (16)
  • core/providers/perplexity/perplexity.go
  • core/providers/openrouter.go
  • tests/core-providers/tests.go
  • core/schemas/bifrost.go
  • core/providers/groq.go
  • tests/core-providers/config/account.go
  • tests/core-providers/mistral_test.go
  • core/providers/anthropic/chat.go
  • tests/core-providers/ollama_test.go
  • core/providers/parasail.go
  • tests/core-providers/openai_test.go
  • core/providers/cerebras.go
  • tests/core-providers/gemini_test.go
  • tests/core-providers/cerebras_test.go
  • core/providers/sgl.go
  • tests/core-providers/anthropic_test.go
🧰 Additional context used
🧬 Code graph analysis (13)
core/providers/gemini/gemini.go (2)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/utils/utils.go (2)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
core/providers/ollama.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/mistral/mistral.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/vertex/vertex.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (15)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/anthropic/anthropic.go (4)
core/schemas/bifrost.go (5)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • ResponsesStreamRequest (90-90)
core/schemas/provider.go (1)
  • Provider (207-234)
core/providers/utils/utils.go (3)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/providers/bedrock/bedrock.go (3)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostResponseExtraFields (282-291)
  • ResponsesStreamRequest (90-90)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/providers/bedrock/responses.go (2)
  • NewBedrockStreamAccumulator (28-38)
  • FinalizeBedrockStream (799-890)
core/providers/anthropic/responses.go (2)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/types.go (9)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/providers/openai/openai.go (4)
core/schemas/bifrost.go (6)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
core/providers/utils/utils.go (6)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ProcessAndSendBifrostError (569-599)
  • ProviderSendsDoneMarker (749-758)
  • ProcessAndSendError (605-651)
  • CreateBifrostChatCompletionChunkResponse (684-713)
core/schemas/mux.go (2)
  • ChatToResponsesStreamAccumulator (961-977)
  • NewChatToResponsesStreamAccumulator (980-991)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/cohere/cohere.go (3)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ResponsesStreamRequest (90-90)
  • BifrostResponseExtraFields (282-291)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/providers/cohere/responses.go (1)
  • NewCohereStreamAccumulator (26-34)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (3)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • ChatTool (201-205)
tests/core-providers/scenarios/utils.go (6)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • SampleToolTypeWeather (69-69)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (6)
  • BifrostResponsesRequest (32-39)
  • ResponsesParameters (84-111)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
🔇 Additional comments (25)
transports/changelog.md (3)

2-2: Well-documented feature entry for the main streaming improvements.

The changelog entry accurately captures the core feature of this PR: unified streaming lifecycle events aligned with OpenAI's response types across all providers. The entry is clear, concise, and appropriately emphasizes the cross-provider scope and the OpenAI alignment benefit.


3-3: Clear and specific chore entry for the OpenRouter provider update.

The changelog entry appropriately documents the provider-specific API endpoint shift from alpha/responses to v1/responses. The entry is precise about the scope (OpenRouter), the change, and the affected component.


2-3: Verify changelog comprehensiveness against the full scope of provider changes.

Based on the PR summary, changes span multiple providers (Anthropic, Bedrock, Cohere, Gemini, OpenAI, Groq, Parasail, SGL, Vertex, etc.) with refactoring of streaming accumulators and native implementations. Confirm that the current changelog entries adequately reflect all user-facing and integration-impacting changes, or consider adding separate entries if other providers have breaking or notable API-surface changes similar to OpenRouter's endpoint shift.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Streaming tool calls enabled for Cohere.

Enabling ToolCallsStreaming is consistent with the PR's goal to add streaming support across providers. The change aligns well with the existing ToolCalls test scenario and the test command listed in the PR objectives confirms this functionality has been verified.

core/providers/ollama.go (1)

172-180: LGTM! Clean fallback implementation.

The changes correctly implement the Responses-to-Chat fallback mechanism:

  • The context flag propagation (line 173) enables downstream handlers to detect and handle the fallback scenario appropriately.
  • Passing postHookRunner directly (line 176) aligns with the PR's removal of the combined post-hook converter wrapper, simplifying the streaming pipeline.

This implementation is consistent with similar changes across other providers and supports the broader refactoring to improve streaming consistency.

tests/core-providers/groq_test.go (1)

43-43: Groq provider supports tool call streaming through shared OpenAI-compatible handler—verification complete.

The OpenAI streaming handler (which Groq delegates to) explicitly processes tool calls in delta events at line 976 of core/providers/openai/openai.go. Tool calls are detected via len(choice.ChatStreamResponseChoice.Delta.ToolCalls) > 0 and streamed directly to the response channel, so ToolCallsStreaming: true is correctly configured for Groq.

tests/core-providers/scenarios/tool_calls_streaming.go (4)

18-42: LGTM! Well-structured accumulator design.

The accumulator struct and constructor are well-designed with clear separation between Chat and Responses APIs. The ItemIDToKey mapping is a good addition for efficient lookups during streaming.


188-216: LGTM! Clean getter implementations.

The getter methods properly handle nil pointers and provide a consistent interface for both Chat and Responses APIs.


218-385: LGTM! Robust test orchestration with good retry logic.

The test implementation has several good practices:

  • 3-attempt retry logic handles flaky provider responses
  • Safety limit (500 responses) prevents infinite loops
  • Logs only on first attempt to reduce noise
  • Validates before calling helper to catch issues early
  • Early return on success avoids unnecessary retries

The verbose logging with emojis and detailed field tracking is appropriate for this complex streaming behavior test.


387-743: LGTM! Comprehensive Responses API streaming test.

The Responses API test properly handles multiple streaming event types and extracts tool call information from various field locations. The detailed field-location logging (e.g., "streamResp.Item.ResponsesToolMessage.CallID") is particularly helpful for debugging provider-specific streaming formats.

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

201-209: ResponsesStream fallback flag wiring looks correct

Setting BifrostContextKeyIsResponsesToChatCompletionFallback on ctx and passing postHookRunner directly into ChatCompletionStream is consistent with the new shared streaming path and doesn’t introduce correctness issues.

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

664-673: Vertex ResponsesStream correctly marks chat-fallback path

Using context.WithValue with BifrostContextKeyIsResponsesToChatCompletionFallback and forwarding postHookRunner to ChatCompletionStream aligns Vertex with other providers’ fallback streaming behavior and keeps authorization and error handling centralized.

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

345-353: Gemini ResponsesStream correctly marks chat-fallback and uses native post-hooks

Marking the Responses→Chat fallback via BifrostContextKeyIsResponsesToChatCompletionFallback and passing the original postHookRunner into ChatCompletionStream matches the new global streaming design and keeps Gemini consistent with other providers.

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

469-478: Empty BodyStream handling now correctly marks stream end

On the resp.BodyStream() == nil path in HandleAnthropicChatCompletionStreaming, the code now sets BifrostContextKeyStreamEndIndicator on ctx before calling ProcessAndSendBifrostError. This is a good improvement: downstream consumers can reliably treat this as a terminal condition.

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

633-640: Cohere ResponsesStream accumulator initialization is appropriate

Creating a NewCohereStreamAccumulator() once per stream, storing request.Model into accumulator.Model when present, and flushing it via defer accumulator.Flush() provides the necessary per-session state for OpenAI-style Responses lifecycle events without leaking state across requests.

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

542-555: TextCompletionStream end-of-stream handling is consistent

Setting BifrostContextKeyStreamEndIndicator before the final aggregated chunk and routing it through ProcessAndSendResponse aligns this path with the unified streaming lifecycle and should be safe; no functional issues spotted here.


1336-1344: Unified stream-end signalling across Responses/Speech/Transcription streaming

The added BifrostContextKeyStreamEndIndicator assignments before emitting response.completed or final usage chunks for Responses, SpeechStream, and TranscriptionStream are consistent with the rest of the provider-utils pipeline and should help downstream consumers detect terminal events reliably.

Also applies to: 1708-1713, 1978-1985

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

14-60: Accumulator and lifecycle state are well-structured

The extended AnthropicStreamAccumulator plus Flush/GetOrCreateOutputIndex give a clear, per-output-index state model (IDs, argument buffers, MCP flags, created/in_progress flags), and the reset semantics look correct for multi-response streams. No functional issues here.

Also applies to: 62-80


395-441: MessageStart → created/in_progress events match Responses lifecycle

The MessageStart branch now emitting response.created and response.in_progress once per stream (guarded by HasEmittedCreated / HasEmittedInProgress) with a stable CreatedAt from the accumulator is aligned with OpenAI-style Responses lifecycle and looks correct.


444-571: Output-item creation for text, function, MCP, and computer calls is coherent

The ContentBlockStart handling now:

  • Maps content_indexoutput_index via GetOrCreateOutputIndex.
  • Emits response.output_item.added for text, function_call, MCP_call, and computer_call with stable OutputIndex.
  • Generates stable item_ids (with a safe nil check on MessageID), and initializes buffers / MCP flags only where needed.

This design cleanly supports multi-output responses and tool calls; I don’t see correctness issues in this block.


657-762: *ContentBlockStop completion and _arguments.done emission are well-ordered

For ContentBlockStop you:

  • Handle computer tools specially (parsing accumulated JSON, emitting a completed computer_call item, and clearing the dedicated accumulator fields).
  • For other tools/text, first emit function_call_arguments.done / mcp_call_arguments.done when an argument buffer exists, then always emit response.output_item.done with status "completed" and any known item_id.

This sequence matches the expected Responses lifecycle ordering and correctly clears argument buffers/MCP flags to avoid leaking state into subsequent outputs.


770-783: MessageStop → response.completed behaves as expected

The MessageStop branch synthesizing a response.completed with CreatedAt from the accumulator and optional ID is consistent with the earlier created/in_progress events and correctly signals terminal completion (with isStreamCompleted = true to the caller).

tests/core-providers/sgl_test.go (1)

24-46: Enabling ToolCallsStreaming for SGL tests is consistent with the new streaming coverage

Adding ToolCallsStreaming: true alongside ToolCalls: true aligns SGL with other providers’ comprehensive streaming tests and should help exercise the new tool-call streaming paths.

tests/core-providers/parasail_test.go (1)

24-45: Parasail test config: ToolCallsStreaming flag looks good

Turning on ToolCallsStreaming in the Parasail scenarios matches the rest of the suite and should expand coverage for streamed tool-call behavior without affecting non-streaming tests.

tests/core-providers/bedrock_test.go (1)

24-51: Bedrock tests correctly enable ToolCallsStreaming

Setting ToolCallsStreaming: true here is in line with the new Bedrock streaming accumulator and ensures the comprehensive test harness will hit the streamed tool-call paths.

Comment thread tests/core-providers/scenarios/tool_calls_streaming.go
@coderabbitai

coderabbitai Bot commented Nov 14, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

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

469-477: Avoid emitting a final “success” chunk after a streaming error in Anthropic chat

When event.ToBifrostChatCompletionStream() returns a bifrostErr, you set the stream-end indicator and send a BifrostError, but only break the scan loop. The code then falls through to the “no scanner error” branch and emits a final aggregated completion chunk, so the client sees both an error and a successful end-of-stream event for the same stream.

Change the error path to return from the goroutine instead of just breaking, so no success chunk is sent after an error.

-            if bifrostErr != nil {
-                bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{
-                    RequestType:    schemas.ChatCompletionStreamRequest,
-                    Provider:       providerType,
-                    ModelRequested: modelName,
-                }
-                ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
-                providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, bifrostErr, responseChan, logger)
-                break
-            }
+            if bifrostErr != nil {
+                bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{
+                    RequestType:    schemas.ChatCompletionStreamRequest,
+                    Provider:       providerType,
+                    ModelRequested: modelName,
+                }
+                ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
+                providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, bifrostErr, responseChan, logger)
+                return
+            }

Also applies to: 548-558, 587-595

♻️ Duplicate comments (3)
core/providers/cohere/responses.go (1)

610-616: Fix potential nil-pointer dereference when generating reasoning item IDs

Here, accumulator.MessageID is dereferenced before checking for nil:

// Generate stable ID for reasoning item
itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
}

If Cohere ever emits a reasoning block before a message_start with an ID (or omits the ID), this will panic.

Reorder the logic to guard the dereference:

-// Generate stable ID for reasoning item
-itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-if accumulator.MessageID == nil {
-    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-}
+// Generate stable ID for reasoning item
+var itemID string
+if accumulator.MessageID != nil {
+    itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+} else {
+    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+}

This matches the safe pattern you already use elsewhere for text and tool-plan IDs.

tests/core-providers/scenarios/tool_calls_streaming.go (1)

45-71: Don’t treat Index == 0 as “unset” when keying Chat tool calls

AccumulateChatToolCall still treats toolCall.Index == 0 as a signal to fall back to choiceIndex:

key = int(toolCall.Index)
if key == 0 {
    key = choiceIndex
}
...
key = int(toolCall.Index)
if key == 0 {
    key = choiceIndex
}

But Index is a legitimate 0-based position (the first tool call is index 0 in the core accumulator). Using choiceIndex here can mis-bucket tool calls, especially when multiple calls are present.

Since you already have a separate found flag for ID matches, you can safely use int(toolCall.Index) directly:

-    key = int(toolCall.Index)
-    if key == 0 {
-        key = choiceIndex
-    }
+    key = int(toolCall.Index)
...
-    key = int(toolCall.Index)
-    if key == 0 {
-        key = choiceIndex
-    }
+    key = int(toolCall.Index)

This preserves the true tool-call index and avoids collisions.

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

11-53: Fix pointer-to-range-variable usage and always finalize tool-call items

Two issues in the Bedrock streaming accumulator/finalizer:

  1. Pointer-to-range-variable bug (critical)
    In FinalizeBedrockStream, you take addresses of range variables:

    • Arguments: &args
    • OutputIndex: &outputIndex

    inside:

    for outputIndex, args := range accumulator.ToolArgumentBuffers {
        if args != "" {
            // ...
            response := &schemas.BifrostResponsesStreamResponse{
                // ...
                OutputIndex:    schemas.Ptr(outputIndex), // or &outputIndex currently
                Arguments:      &args,
            }
            // ...
        }
    }

    Because args and outputIndex are reused on each iteration, all emitted responses end up pointing at the same underlying variables, so later iterations overwrite earlier events’ Arguments and OutputIndex. This is the same class of issue that was flagged earlier in this file.

    Fix by copying the values (or by using schemas.Ptr consistently) before taking addresses:

    for outputIndex, args := range accumulator.ToolArgumentBuffers {
        if args != "" {
  •      itemID := accumulator.ItemIDs[outputIndex]
    
  •      callID := accumulator.ToolCallIDs[outputIndex]
    
  •      toolName := accumulator.ToolCallNames[outputIndex]
    
  •      itemID := accumulator.ItemIDs[outputIndex]
    
  •      callID := accumulator.ToolCallIDs[outputIndex]
    
  •      toolName := accumulator.ToolCallNames[outputIndex]
    
  •      // Copy loop variables to avoid pointer-to-range-variable bugs
    
  •      outputIndexCopy := outputIndex
    
  •      argsCopy := args
         // ...
    
  •      response := &schemas.BifrostResponsesStreamResponse{
    
  •      response := &schemas.BifrostResponsesStreamResponse{
             Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
    
  •          SequenceNumber: sequenceNumber + len(responses),
    
  •          OutputIndex:    schemas.Ptr(outputIndex),
    
  •          Arguments:      &args,
    
  •          SequenceNumber: sequenceNumber + len(responses),
    
  •          OutputIndex:    schemas.Ptr(outputIndexCopy),
    
  •          Arguments:      &argsCopy,
         }
         // ...
    
  •      responses = append(responses, &schemas.BifrostResponsesStreamResponse{
    
  •      responses = append(responses, &schemas.BifrostResponsesStreamResponse{
             Type:           schemas.ResponsesStreamResponseTypeOutputItemDone,
    
  •          SequenceNumber: sequenceNumber + len(responses),
    
  •          OutputIndex:    schemas.Ptr(outputIndex),
    
  •          SequenceNumber: sequenceNumber + len(responses),
    
  •          OutputIndex:    schemas.Ptr(outputIndexCopy),
             Item:           outputItemDone,
         })
     }
    
    }
    
    
  1. Tool calls with empty arguments never closed (behavioral edge)
    The if args != "" guard means that tool calls for which no argument deltas were streamed (e.g., functions without arguments) will never get:

    • function_call_arguments.done, nor
    • output_item.done for that tool call.

    To keep Bedrock aligned with OpenAI-style Responses semantics, you likely want these events even when the arguments string is empty.

    Consider removing the args != "" check and always emitting both function_call_arguments.done and output_item.done for any entry present in ToolArgumentBuffers (using an empty string for Arguments when appropriate).

Also applies to: 585-596, 798-890

🧹 Nitpick comments (6)
core/providers/cohere/cohere.go (2)

452-484: Guard against double-increment of chunkIndex in ChatCompletionStream

Within the chat stream loop, chunkIndex is incremented once before calling ToBifrostChatCompletionStream and again after enqueuing a non-nil response. This makes ExtraFields.ChunkIndex skip values (1, 3, 5, …) which is probably unintended and can confuse consumers relying on contiguous indices:

chunkIndex++
...
response.ExtraFields.ChunkIndex = chunkIndex
...
lastChunkTime = time.Now()
chunkIndex++ // second increment

Consider incrementing chunkIndex only once per emitted response (e.g., drop the initial increment or the post-send increment) so chunk indices remain contiguous and easier to reason about.


491-494: Consider marking scanner I/O errors as stream-ending

On scanner errors you call ProcessAndSendError but don’t set schemas.BifrostContextKeyStreamEndIndicator on the context, unlike the bifrostErr and isLastChunk paths. If any downstream hooks rely on that flag to finalize state, consider setting it here as well for consistency:

ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
providerUtils.ProcessAndSendError(...)
core/providers/cohere/responses.go (2)

576-587: Remove redundant MessageID == nil check in text item ID generation

In the text StreamEventContentStart branch, itemID is already set for both nil and non-nil MessageID, then immediately re-checked for nil:

var itemID string
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("item_%d", outputIndex)
} else {
    itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
}
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("item_%d", outputIndex)
}

The second if accumulator.MessageID == nil is dead code and can be removed to simplify the branch.


498-542: Lifecycle emission gated on chunk.ID may skip events if IDs are absent

response.created / response.in_progress are only emitted when chunk.ID != nil. If Cohere ever omits ID on message_start, the lifecycle events will be skipped for that stream:

case StreamEventMessageStart:
    if chunk.ID != nil {
        accumulator.MessageID = chunk.ID
        ...
        // emit Created / InProgress
    }

If you want lifecycle events to be guaranteed, consider emitting them regardless of ID presence (using CreatedAt + a nil ID), and only populating MessageID when available. If you’re confident Cohere always provides IDs, leaving this as-is is acceptable but does rely on that invariant.

tests/core-providers/scenarios/tool_calls_streaming.go (1)

441-691: Consider per-attempt cancellable contexts to avoid goroutine leaks on early exit

In both streaming tests, when streamError is set you break the for range loop but don’t cancel the underlying request context. The provider goroutine will keep writing to the channel until completion, which can block if the channel buffer fills after the test stops reading.

Consider deriving a per-attempt context with context.WithCancel and calling cancel() when you decide to abort an attempt, so provider-side goroutines can terminate promptly.

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

542-554: Consistent stream-end signaling across OpenAI streaming variants

Adding BifrostContextKeyStreamEndIndicator before the final ProcessAndSendResponse in:

  • HandleOpenAITextCompletionStreaming final chunk,
  • ChatCompletionStreaming’s final aggregate chunk (non-fallback),
  • ResponsesStreaming’s Type == Completed chunk,
  • SpeechStream and TranscriptionStream chunks carrying Usage,

gives a uniform way for downstream consumers to detect end-of-stream across all OpenAI streaming APIs. The changes are minimal and do not alter chunk ordering.

For readability, consider renaming the inner response variable in the fallback loop (the BifrostResponsesStreamResponse) to avoid shadowing the outer BifrostChatResponse, e.g. respChunk.

Also applies to: 1000-1012, 1340-1343, 1708-1712, 1980-1983

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18a9284 and 7a0cd1f.

📒 Files selected for processing (41)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (7 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🔇 Additional comments (52)
tests/core-providers/sgl_test.go (1)

36-36: Enable tool call streaming tests for SGL provider.

The ToolCallsStreaming field is properly defined in the TestScenarios struct, and a complete test implementation exists in RunToolCallsStreamingTest that checks if the flag is enabled before executing the test scenarios. The test is integrated into the framework and tracked with other test scenarios. This change correctly enables the existing tool call streaming test scenario for the SGL provider, following the same pattern used by other providers.

tests/core-providers/mistral_test.go (1)

38-38: Review comment is incorrect and should be disregarded.

The code change is valid. Mistral provider uses the shared openai.HandleOpenAIChatCompletionStreaming handler (lines 170-183 of mistral.go), which is employed by 13+ providers including Anthropic, Bedrock, Cohere, Groq, and others—all of which enable ToolCallsStreaming: true in their test configurations. The Mistral test file runs runAllComprehensiveTests, which includes the tool calls streaming test scenario. Additionally, Mistral API officially supports streaming chat completions together with tool calling through server-sent events, so the test will execute successfully.

Likely an incorrect or invalid review comment.

tests/core-providers/tests.go (1)

35-35: LGTM!

The additions of RunToolCallsStreamingTest to the test scenarios list and its corresponding entry in the test summary display are consistent with the PR's objective to add comprehensive streaming tool call tests across providers.

Also applies to: 77-77

core/providers/openrouter.go (1)

214-214: LGTM!

The endpoint migration from /alpha/responses to /v1/responses aligns OpenRouter with the v1 API surface. This is a straightforward version upgrade with no behavioral changes.

Also applies to: 233-233

tests/core-providers/groq_test.go (1)

43-43: LGTM!

Enabling ToolCallsStreaming: true for Groq aligns with the broader PR pattern of enabling streaming tool call tests across multiple providers.

tests/core-providers/config/account.go (1)

29-29: LGTM!

The addition of the ToolCallsStreaming field to the TestScenarios struct is well-documented and necessary for enabling streaming tool call tests across providers.

core/providers/cerebras.go (1)

173-179: LGTM!

The changes to ResponsesStream align with the unified streaming lifecycle pattern:

  • Setting BifrostContextKeyIsResponsesToChatCompletionFallback signals the fallback streaming path
  • Passing postHookRunner directly (instead of wrapped) is consistent with the refactored approach across other providers
tests/core-providers/openrouter_test.go (1)

37-39: LGTM!

The test configuration appropriately disables ToolCallsStreaming for OpenRouter due to the Beta status of their responses API, while still enabling End2EndToolCalling. The inline comment clearly documents the reasoning.

core/schemas/bifrost.go (1)

117-117: LGTM!

The addition of BifrostContextKeyIsResponsesToChatCompletionFallback follows the established pattern for context keys and is well-documented. This key enables unified fallback streaming behavior across providers, as evidenced by its usage in multiple provider implementations.

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

212-218: LGTM!

The ResponsesStream implementation follows the same unified streaming pattern applied across other providers:

  • Setting BifrostContextKeyIsResponsesToChatCompletionFallback enables fallback path detection
  • Direct postHookRunner usage aligns with the refactored streaming architecture
core/providers/sgl.go (1)

168-176: ResponsesStream fallback flag wiring looks correct

Propagating schemas.BifrostContextKeyIsResponsesToChatCompletionFallback on the context and reusing postHookRunner into ChatCompletionStream is a clean way to mark the fallback path without altering stream semantics. No issues spotted here.

core/changelog.md (1)

1-2: Changelog entries accurately describe the streaming changes

The new bullets cleanly summarize the unified lifecycle events and OpenRouter /v1/responses migration and are consistent with the rest of the changelog.

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

11-47: Accumulator design for Cohere responses streaming is solid

CohereStreamAccumulator cleanly centralizes per-stream state (output indices, argument buffers, stable item IDs, lifecycle flags) and Flush correctly resets all fields, including CreatedAt, for reuse. This aligns with the OpenAI-style multi-item streaming model.


681-742: Tool-plan handling and text item reuse look correct

The StreamEventToolPlanDelta branch sensibly treats tool plans as a regular assistant text item, reusing a dedicated output_index (default 0), generating a stable item ID, emitting output_item.added once, and then streaming deltas via output_text.delta. Closing any open tool-plan item in ContentStart/ToolCallStart using ToolPlanOutputIndex is a good way to ensure well-formed item lifecycles.


745-888: Tool call start/delta/end sequencing for Responses streaming is well structured

The StreamEventToolCallStart, StreamEventToolCallDelta, and StreamEventToolCallEnd cases correctly:

  • Allocate a dedicated output_index per tool call to avoid collisions with text items.
  • Track stable itemIDs per output_index.
  • Accumulate Function.Arguments in ToolArgumentBuffers across deltas.
  • Emit function_call_arguments.delta per chunk and a single function_call_arguments.done with the full JSON payload, followed by output_item.done.

This matches the OpenAI Responses API tool-call lifecycle and should work well with the new tests.

tests/core-providers/scenarios/tool_calls_streaming.go (4)

101-186: Responses tool-call accumulator is robust and flexible

AccumulateResponsesToolCall uses sensible heuristics (preferring itemID, then callID, then name) plus the ItemIDToKey map to stitch together deltas, added events, and done events across providers. The “complete JSON vs incremental chunk” handling for Arguments also matches how different providers stream payloads. Looks good.


218-385: Streaming test harness for Chat tool calls is comprehensive

The Chat streaming test:

  • Retries up to 3 times with clear logging.
  • Validates non-nil channel and responses.
  • Accumulates tool-call fragments across chunks and asserts that final calls have ID, name, and arguments before deeper validation.

The overall structure is solid and should catch most regressions in tool-call streaming.


387-742: Responses streaming test harness mirrors Chat coverage effectively

The Responses streaming test mirrors the Chat harness, inspecting FunctionCallArgumentsDelta, OutputItemAdded, and FunctionCallArgumentsDone events, accumulating by callID/itemID, and validating that final tool calls contain ID, name, and arguments. This provides good cross-provider coverage for the new Responses streaming lifecycle.


745-793: Streaming validation helper is clear and appropriately strict

validateStreamingToolCalls gives good diagnostics (including JSON parse attempts) while still enforcing that all tool calls have non-empty ID, name, and arguments via require.NotEmpty. This strikes a good balance between robustness and strictness for the tool-call streaming tests.

tests/core-providers/anthropic_test.go (1)

38-38: LGTM! Streaming tool calls enabled for Anthropic tests.

This change enables streaming tool call tests for Anthropic, aligning with the PR's objective to improve Responses API streaming across providers.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Streaming tool calls enabled for OpenAI tests.

Consistent with the streaming enablement across other providers.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Streaming tool calls enabled for Cohere tests.

tests/core-providers/ollama_test.go (2)

26-26: Verify that the model change from llama3.2 to llama3.1:latest is intentional.

The model version appears to be downgraded. Please confirm that llama3.1:latest provides the necessary tool calling and streaming capabilities that this PR requires, or if this change addresses a specific compatibility issue.


35-35: LGTM! Streaming tool calls enabled for Ollama tests.

tests/core-providers/parasail_test.go (1)

35-35: LGTM! Streaming tool calls enabled for Parasail tests.

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

202-210: LGTM! Refactored to use context flag for fallback streaming.

This change improves the streaming implementation by:

  • Setting BifrostContextKeyIsResponsesToChatCompletionFallback in context to signal the fallback path
  • Passing postHookRunner directly instead of wrapping it, allowing unified handling downstream

This aligns with the PR's objective to improve streaming consistency across providers.

core/providers/ollama.go (1)

172-180: LGTM! Consistent refactoring with other providers.

This change mirrors the Mistral provider implementation, using the context flag approach for fallback streaming. The consistency across providers strengthens the implementation.

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

704-732: Field reference corrected for tool call ID.

The change from chunk.ContentBlock.ToolUseID to chunk.ContentBlock.ID at line 718 appears to be a bug fix. When handling the ContentBlockStart event for tool_use blocks, the tool call ID should be sourced from the ID field of the content block.

Please verify that ContentBlock.ID is the correct field according to Anthropic's streaming API documentation for tool_use content blocks.

core/providers/groq.go (1)

212-220: LGTM: Fallback streaming path correctly implemented.

The changes enable a fallback-aware streaming flow for Responses API by setting a context flag and passing the postHookRunner directly to ChatCompletionStream. This aligns with the broader pattern across providers.

tests/core-providers/azure_test.go (1)

36-54: LGTM: Tool call streaming enabled for Azure tests.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Azure provider, aligning with the broader test coverage improvements across providers.

transports/changelog.md (1)

1-3: LGTM: Changelog entries accurately document the PR changes.

The changelog appropriately documents the unified streaming lifecycle events feature and the OpenRouter API path update.

tests/core-providers/bedrock_test.go (1)

34-52: LGTM: Tool call streaming enabled for Bedrock tests.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Bedrock provider, consistent with the test coverage improvements across other providers.

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

665-673: LGTM: Fallback streaming path correctly implemented.

The changes enable a fallback-aware streaming flow for Responses API by setting a context flag and passing the postHookRunner directly to ChatCompletionStream. This matches the pattern applied consistently across other providers.

tests/core-providers/cerebras_test.go (1)

33-51: LGTM: Tool call streaming enabled for Cerebras tests.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Cerebras provider, maintaining consistency with the test suite improvements.

tests/core-providers/gemini_test.go (1)

35-57: LGTM: Tool call streaming enabled for Gemini tests.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Gemini provider, consistent with the broader test coverage enhancements.

core/providers/parasail.go (1)

144-152: LGTM: Fallback streaming path correctly implemented.

The changes enable a fallback-aware streaming flow for Responses API by setting a context flag and passing the postHookRunner directly to ChatCompletionStream. This follows the consistent pattern applied across all providers in this PR.

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

345-353: Responses→ChatCompletion fallback flag wiring looks correct

Setting BifrostContextKeyIsResponsesToChatCompletionFallback before delegating to ChatCompletionStream cleanly opts Gemini into the shared OpenAI-style Responses streaming path without extra converters. No issues from a correctness standpoint.


601-620: Consistent use of stream-end indicator for Gemini speech/transcription

Marking BifrostContextKeyStreamEndIndicator and using ProcessAndSendResponse for the final Done chunks aligns Gemini’s speech/transcription streaming lifecycle with the rest of the providers. The change is safe and improves consistency.

Also applies to: 881-883

tests/core-providers/vertex_test.go (1)

24-46: Enabling Vertex tool-call streaming tests is appropriate

Turning on ToolCallsStreaming: true here correctly aligns Vertex tests with the new streaming tooling across providers. Assuming provider support exists, this is a good coverage expansion.

Please run the Vertex suite once after this stack lands (when credentials are available) to confirm streaming tool-calls behave as expected.

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

469-477: ResponsesStream accumulator + lifecycle events look correct

The ResponsesStream path for Anthropic now:

  • Signals empty-body errors with BifrostContextKeyStreamEndIndicator set.
  • Uses ToBifrostResponsesStream(chunkIndex, accumulator) to emit multiple lifecycle events per SSE chunk.
  • Enriches streaming conversion errors with RequestType/Provider/ModelRequested and terminates the stream on such errors.
  • Only attaches usage and stream-end for the last response slice element when isLastChunk is true.

This matches the intended OpenAI-style Responses streaming model. No further issues spotted here.

Also applies to: 726-735, 783-785, 794-804, 806-837

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

705-714: Responses→ChatCompletion fallback path in OpenAI streaming looks sound

The new isResponsesToChatCompletionsFallback branch:

  • Detects the fallback via BifrostContextKeyIsResponsesToChatCompletionFallback and initializes a ChatToResponsesStreamAccumulator.
  • Converts each chat stream chunk into one or more BifrostResponsesStreamResponse items, with per-chunk RequestType/Provider/ModelRequested/ChunkIndex set from SequenceNumber.
  • Properly short-circuits on error-type responses and on ResponsesStreamResponseTypeCompleted, marking the stream end via BifrostContextKeyStreamEndIndicator and returning from the goroutine.

The non-fallback branch retains the prior chat streaming behavior (usage aggregation, finishReason handling, final aggregate chunk) with only the end-indicator added. This all aligns well with the new Responses lifecycle.

Also applies to: 816-823, 851-865, 873-923, 1000-1012

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

13-32: LGTM: Well-designed accumulator for stateful streaming conversion.

The AnthropicStreamAccumulator struct provides comprehensive state tracking for OpenAI-style lifecycle events, content-to-output index mapping, and per-item buffering. The design appropriately uses maps for dynamic tracking and flags for lifecycle management.


34-44: LGTM: Constructor properly initializes all fields.

All map fields are correctly initialized using make(), and the timestamp is set appropriately. The initialization ensures no nil pointer dereferences will occur during streaming.


46-60: LGTM: Comprehensive state reset in Flush.

The Flush method correctly resets all accumulator fields, including recreating maps to clear previous entries and resetting lifecycle flags. This ensures clean state between streams.


62-80: LGTM: Correct output index mapping logic.

The GetOrCreateOutputIndex method properly handles both nil and non-nil content indices, maintaining stable mappings across streaming chunks. The logic ensures consistent output indices for the same content indices throughout the stream.


394-809: LGTM: Comprehensive streaming conversion with proper lifecycle management.

The ToBifrostResponsesStream method correctly:

  • Emits OpenAI-style lifecycle events (created, in_progress, completed)
  • Uses the accumulator to maintain stable output indices and item IDs
  • Handles all content types (text, function calls, MCP calls, computer tools)
  • Properly accumulates and emits tool arguments
  • Returns multiple responses per chunk when appropriate
  • Safely checks for nil before dereferencing (e.g., lines 485-489)

The past review concern about nil dereference has been properly addressed.

core/schemas/mux.go (3)

960-977: LGTM: Well-structured accumulator for Chat-to-Responses conversion.

The ChatToResponsesStreamAccumulator struct provides comprehensive state tracking for converting Chat API streaming to Responses API format. The design appropriately includes:

  • Tool call tracking with multiple map indices for flexible lookups
  • Text item lifecycle flags
  • Monotonic sequence numbering
  • Lifecycle event flags

979-991: LGTM: Constructor properly initializes all accumulator fields.

All map fields are correctly initialized, and counters/timestamps are set appropriately. This ensures safe access during streaming conversion.


993-1350: LGTM: Robust Chat-to-Responses streaming conversion with proper state management.

The ToBifrostResponsesStreamResponse method correctly:

  • Emits OpenAI-style lifecycle events at appropriate times
  • Manages text and tool call items with proper state tracking
  • Creates stable item IDs (safely checking for nil at lines 1063-1067)
  • Accumulates tool arguments and emits done events on completion
  • Uses argsCopy to avoid range variable address issues (line 1281)
  • Increments sequence numbers monotonically
  • Sets ExtraFields consistently across all responses

Both past review concerns have been properly addressed.

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

725-734: LGTM: Improved error metadata and stream end signaling.

The error handling now enriches BifrostError with request type, provider, and model information, improving observability. Setting the stream end indicator in the context before processing errors and final responses ensures proper stream lifecycle management.

Also applies to: 762-763


871-874: LGTM: Proper accumulator initialization and lifecycle management.

The BedrockStreamAccumulator is correctly initialized before the streaming loop, the model is set for context, and Flush is deferred to ensure cleanup. The accumulator is properly passed to ToBifrostResponsesStream for stateful conversion.

Also applies to: 947-947


886-908: LGTM: Proper stream finalization and multi-response handling.

The EOF handling correctly:

  • Sets the stream end indicator in context
  • Calls FinalizeBedrockStream to emit closing events
  • Populates metadata for each final response
  • Includes usage information in finalization

The multi-response loop properly iterates over responses from the accumulator-aware conversion, setting metadata and incrementing counters for each response. This aligns with the new multi-part streaming design.

Also applies to: 958-978

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
core/providers/bedrock/responses.go (1)

675-702: Fix tool-call indexing for multiple calls and pointer-to-range-variable bug in finalization

Two issues here:

  1. Static outputIndex for tool calls (multi-tool-call bug)
    In case chunk.Start.ToolUse != nil (around Line 675), you always use outputIndex := 1 and set CurrentOutputIndex = 2. If Bedrock ever emits multiple concurrent tool uses, they will all share output_index = 1, and later deltas/argument buffers will collide.

    Use CurrentOutputIndex to allocate unique indices, reserving 0 for text:

  •       // This is a function call starting - use output_index 1
    
  •       outputIndex := 1
    
  •       accumulator.ContentIndexToOutputIndex[contentBlockIndex] = outputIndex
    
  •       accumulator.CurrentOutputIndex = 2 // Next available index
    
  •       // Assign a new output index for this tool call (reserve 0 for text)
    
  •       outputIndex := accumulator.CurrentOutputIndex
    
  •       if outputIndex == 0 {
    
  •           outputIndex = 1
    
  •       }
    
  •       accumulator.CurrentOutputIndex = outputIndex + 1
    
  •       accumulator.ContentIndexToOutputIndex[contentBlockIndex] = outputIndex
    
    
    
  1. Arguments: &args on a range variable (all arguments.done share the last value)
    In FinalizeBedrockStream (around Line 824), for outputIndex, args := range accumulator.ToolArgumentBuffers and then Arguments: &args reuses the address of the range variable. Every function_call_arguments.done will end up pointing at the last args value, corrupting earlier events.

    Copy args per iteration before taking its address:

  • for outputIndex, args := range accumulator.ToolArgumentBuffers {
  • for outputIndex, args := range accumulator.ToolArgumentBuffers {
    if args != "" {
    @@
  •       // Emit function_call_arguments.done with full arguments
    
  •       response := &schemas.BifrostResponsesStreamResponse{
    
  •       // Emit function_call_arguments.done with full arguments
    
  •       argsCopy := args
    
  •       response := &schemas.BifrostResponsesStreamResponse{
              Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
              SequenceNumber: sequenceNumber + len(responses),
              OutputIndex:    schemas.Ptr(outputIndex),
    
  •           Arguments:      &args,
    
  •           Arguments:      &argsCopy,
          }
    
    
    

Both fixes are important to keep multi-tool-call streams correct and avoid subtle data corruption.

Also applies to: 823-874

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

576-586: Fix potential nil dereference when generating reasoning item IDs

In ToBifrostResponsesStream, the reasoning branch (Line 611) formats the item ID as:

itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
}

This dereferences accumulator.MessageID before checking for nil. If Cohere ever emits a StreamEventContentStart for a thinking block before a message_start with an ID (or with a missing ID), this will panic during streaming.

Align this with the safer pattern used for the text branch by checking first:

-                // Generate stable ID for reasoning item
-                itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-                if accumulator.MessageID == nil {
-                    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-                }
+                // Generate stable ID for reasoning item
+                var itemID string
+                if accumulator.MessageID != nil {
+                    itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+                } else {
+                    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+                }

(Optionally, you can also drop the redundant second if accumulator.MessageID == nil in the text branch to keep both branches consistent.)

Also applies to: 605-614

tests/core-providers/scenarios/tool_calls_streaming.go (1)

60-71: Fix the fallback logic that incorrectly treats Index=0 as "not set".

Lines 61-63 and 68-70 contain logic that falls back to choiceIndex when toolCall.Index == 0. Since Index is a uint16 (not a pointer), zero is its default value and also a valid array index for the first tool call. This logic will incorrectly override legitimate Index=0 values.

Based on the codebase analysis in the past review, framework/streaming/accumulator.go:245 assigns Index=uint16(len(existingToolCalls)), meaning the first tool call legitimately receives Index=0.

Apply this diff to remove the incorrect zero-check:

 func (acc *StreamingToolCallAccumulator) AccumulateChatToolCall(choiceIndex int, toolCall schemas.ChatAssistantMessageToolCall) {
   // Prefer ID as key if available, otherwise use index
   key := -1
   var found bool
   if toolCall.ID != nil && *toolCall.ID != "" {
     // Try to find existing tool call by ID first
     for k, existing := range acc.ChatToolCalls {
       if existing.ID != nil && *existing.ID == *toolCall.ID {
         key = k
         found = true
         break
       }
     }
     // If not found by ID, use index
     if !found {
       key = int(toolCall.Index)
-      if key == 0 {
-        key = choiceIndex
-      }
     }
   } else {
     // Use the tool call index (if available) or choice index as the key
     key = int(toolCall.Index)
-    if key == 0 {
-      key = choiceIndex
-    }
   }

If you need to distinguish "not provided" from "provided as 0", change the schema field Index to *uint16 and check for nil. Otherwise, use toolCall.Index directly.

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

469-477: ChatCompletion streaming: improved error context and explicit end-of-stream

The updates here are solid:

  • Line [469-477]: When BodyStream is nil, constructing a BifrostOperationError, marking BifrostContextKeyStreamEndIndicator, and sending via ProcessAndSendBifrostError makes the failure explicit to downstream hooks/consumers.
  • Line [548-558]: For ToBifrostChatCompletionStream errors, enriching ExtraFields with RequestType, Provider, and ModelRequested, then marking the stream as ended before dispatching the error, significantly improves debuggability and ensures consumers see a terminal event.
  • Line [587-595]: The final synthetic chunk created by CreateBifrostChatCompletionChunkResponse, with total latency and stream-end indicator, gives a clean “completed” event that matches the new lifecycle semantics.

You might optionally consider also setting BifrostContextKeyStreamEndIndicator in the scanner error branch for symmetry, unless ProcessAndSendError already handles that internally.

Also applies to: 548-558, 587-595

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

705-715: Chat→Responses fallback path looks correct; consider ChunkIndex semantics

The Responses-fallback branch in HandleOpenAIChatCompletionStreaming is well-structured: it uses ChatToResponsesStreamAccumulator, propagates provider/model metadata, and correctly marks the stream end on error and completed chunks.

One nuance: response.ExtraFields.ChunkIndex is set from response.SequenceNumber rather than a separate per-chunk counter, so in fallback mode the notion of “chunk index” differs from other OpenAI streaming paths (which usually increment once per SSE chunk). If any consumers assume ChunkIndex is a simple 0..N per-stream counter, consider switching to a dedicated per-response counter (e.g., the existing chunkIndex) and keeping SequenceNumber purely for Responses-level ordering.

Also applies to: 873-923, 1004-1012

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

414-417: Avoid double-incrementing chunkIndex in ChatCompletionStream

chunkIndex is incremented once before processing each SSE event (Line 445) and again after emitting a response (Line 474). Because ExtraFields.ChunkIndex is set between these increments, the sequence becomes 1, 3, 5, ... rather than contiguous 0..N.

If you want ChunkIndex to represent a simple per-chunk counter (matching other providers), drop the pre-scan increment and only increment when you actually emit a response:

-                chunkIndex++
-
                 // Extract response ID from message-start events
                 if event.Type == StreamEventMessageStart && event.ID != nil {
                     responseID = *event.ID
                 }
@@
-                    lastChunkTime = time.Now()
-                    chunkIndex++
+                    lastChunkTime = time.Now()
+                    chunkIndex++

This keeps ChunkIndex contiguous and still monotonic.

Also applies to: 445-447, 452-462, 465-475, 480-484

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18a9284 and 7a0cd1f.

📒 Files selected for processing (41)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (7 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🔇 Additional comments (51)
core/providers/perplexity/perplexity.go (1)

212-215: LGTM! Aligns with native streaming implementation.

The changes correctly mark the fallback path with the context flag and remove the post-hook wrapper, aligning with the PR's shift toward native per-provider streaming implementations instead of chat-to-responses conversion wrappers.

core/providers/openrouter.go (1)

214-214: LGTM! OpenRouter responses API endpoint updated to v1.

The endpoint path has been correctly updated from /alpha/responses to /v1/responses for both standard and streaming responses methods, reflecting the API maturation.

Also applies to: 233-233

transports/changelog.md (1)

2-3: LGTM! Changelog accurately documents the PR changes.

The entries correctly capture the unified streaming lifecycle events feature and the OpenRouter endpoint migration, aligning with the PR objectives.

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

718-718: LGTM! Corrects tool block ID source.

The change from chunk.ContentBlock.ToolUseID to chunk.ContentBlock.ID correctly uses the standard ID field for tool-use blocks during the ContentBlockStart event, ensuring proper tool call identification in streaming.

core/changelog.md (1)

1-2: LGTM! Changelog accurately reflects core changes.

The entries correctly document the unified streaming lifecycle events and the OpenRouter responses API endpoint migration, consistent with the PR's core enhancements.

tests/core-providers/anthropic_test.go (1)

38-38: LGTM! Enables streaming tool call tests for Anthropic.

Adding ToolCallsStreaming: true appropriately expands test coverage to validate the new streaming lifecycle events for tool calls, aligning with the PR's comprehensive streaming enhancements.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Enables streaming tool call tests for Cohere.

Adding ToolCallsStreaming: true appropriately extends test coverage to validate streaming tool call functionality for the Cohere provider, consistent with the PR's cross-provider streaming improvements.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Enables streaming tool call tests for OpenAI.

Adding ToolCallsStreaming: true appropriately expands test coverage for OpenAI's streaming tool calls, which serves as the reference implementation for the unified streaming lifecycle events introduced in this PR.

tests/core-providers/tests.go (1)

35-35: LGTM! Test scenario integration is consistent.

The new ToolCallsStreamingTest scenario is properly added to both the test execution list and the summary printout, following the existing pattern for test scenarios.

Also applies to: 77-77

tests/core-providers/ollama_test.go (2)

35-35: LGTM! Streaming test scenario enabled.

The addition of ToolCallsStreaming: true aligns with the PR objective of testing streaming tool calls across providers.


26-26: I'll search for information about the llama3.1 model in Ollama and its streaming capabilities to verify the change.

Based on my verification, Llama 3.1 405B is the first openly available model that rivals the top AI models when it comes to state-of-the-art capabilities in general knowledge, steerability, math, tool use, and multilingual translation. The model has been officially released in the Ollama library and includes multiple size variants (8B, 70B, and 405B).

Ollama now supports tool calling with popular models such as Llama 3.1, with streaming tool calls capability. Llama 3.1 has been fine-tuned on function callings and supports single, nested, and parallel function calling, as well as multi-turn function calling.

The change from llama3.2 to llama3.1:latest is valid and aligns with Ollama's documented support for streaming tool calls in this model family. The codebase also references both models together in other test files (main.go line 128), confirming their compatibility with the project's testing infrastructure.

tests/core-providers/groq_test.go (1)

43-43: LGTM! Streaming test enabled for Groq.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Groq provider, consistent with the PR's objective.

tests/core-providers/sgl_test.go (1)

36-36: LGTM! Streaming test enabled for SGL.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the SGL provider.

tests/core-providers/mistral_test.go (1)

38-38: LGTM! Streaming test enabled for Mistral.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Mistral provider.

tests/core-providers/azure_test.go (1)

42-42: LGTM! Streaming test enabled for Azure.

The addition of ToolCallsStreaming: true enables streaming tool call tests for the Azure provider.

core/providers/sgl.go (1)

169-177: LGTM! Unified fallback streaming pattern implemented.

The ResponsesStream method now:

  1. Sets the BifrostContextKeyIsResponsesToChatCompletionFallback context flag to enable the fallback streaming path
  2. Passes postHookRunner directly instead of wrapping it with a converter

This aligns with the PR's objective of implementing OpenAI-style streaming lifecycle events and standardizing the streaming approach across providers.

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

202-210: LGTM! Unified fallback streaming pattern implemented.

The ResponsesStream method now follows the same pattern as other providers:

  1. Sets the BifrostContextKeyIsResponsesToChatCompletionFallback context flag to enable the fallback streaming path
  2. Passes postHookRunner directly instead of wrapping it

This change standardizes the streaming approach across providers and aligns with the PR's objective of implementing OpenAI-style streaming lifecycle events.

tests/core-providers/config/account.go (1)

29-29: LGTM!

The ToolCallsStreaming field addition is well-placed and clearly documented. It logically extends the test scenarios to support streaming tool calls functionality.

core/providers/groq.go (1)

213-219: LGTM! Consistent streaming refactoring pattern.

The changes properly inject the fallback context flag and pass the postHookRunner directly, aligning with the unified streaming lifecycle approach across providers. This pattern is consistent with similar changes in Cerebras, Ollama, and other providers in this PR.

core/providers/cerebras.go (1)

173-179: LGTM! Consistent streaming refactoring.

The streaming refactoring follows the same pattern as other providers (Groq, Ollama), properly setting the fallback context flag and passing the postHookRunner directly for unified lifecycle handling.

core/providers/ollama.go (1)

173-179: LGTM! Consistent streaming refactoring.

The changes maintain consistency with the streaming refactoring pattern used in Groq and Cerebras providers, properly implementing the unified streaming lifecycle.

tests/core-providers/parasail_test.go (1)

35-35: LGTM! Enabling tool call streaming for Parasail.

The addition of ToolCallsStreaming: true properly enables the new streaming functionality for Parasail tests, consistent with other providers in this PR.

tests/core-providers/cerebras_test.go (1)

40-40: LGTM! Enabling tool call streaming for Cerebras.

The addition of ToolCallsStreaming: true enables the new streaming functionality for Cerebras tests, consistent with the broader streaming enhancement across providers.

tests/core-providers/bedrock_test.go (1)

40-40: LGTM! Enabling tool call streaming for Bedrock.

The addition of ToolCallsStreaming: true enables the new streaming functionality for Bedrock tests, completing the streaming enhancement across all supported providers.

tests/core-providers/openrouter_test.go (1)

37-39: The review comment is based on an incorrect assumption about flag dependency.

End2EndToolCalling and ToolCallsStreaming are independent features with separate test implementations. The End2EndToolCalling test (tests/core-providers/scenarios/end_to_end_tool_calling.go) only guards on its own flag and makes no reference to ToolCallsStreaming. The test conducts a two-step conversation: first requesting a tool call without streaming, then providing the tool result for conversational synthesis—both steps use non-streaming APIs.

OpenRouter's configuration—with ToolCallsStreaming:false but End2EndToolCalling:true—is correct. The Beta API limitation only prevents streaming of tool calls, not the full end-to-end tool calling workflow.

Likely an incorrect or invalid review comment.

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

665-672: ResponsesStream fallback flag wiring looks correct

Setting BifrostContextKeyIsResponsesToChatCompletionFallback on ctx and delegating directly to ChatCompletionStream keeps the fallback behavior explicit and consistent with other providers. No issues spotted here.

core/schemas/bifrost.go (1)

101-118: New context key for Responses→Chat fallback is well-scoped

BifrostContextKeyIsResponsesToChatCompletionFallback is added in the same typed-constant block, with clear semantics and comments. This matches how providers now tag fallback streaming and keeps context key usage type-safe.

tests/core-providers/gemini_test.go (1)

35-56: Enabling ToolCallsStreaming for Gemini tests is appropriate

Turning on ToolCallsStreaming alongside ToolCalls for Gemini will exercise the new streaming tool-call path without changing test structure. Looks good.

core/providers/parasail.go (1)

144-151: Parasail ResponsesStream correctly tagged as chat-fallback

Using context.WithValue to set BifrostContextKeyIsResponsesToChatCompletionFallback and delegating directly to ChatCompletionStream matches the intended Responses-via-Chat fallback pattern and stays consistent with other providers.

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

346-353: Gemini ResponsesStream now correctly signals chat-fallback

Setting BifrostContextKeyIsResponsesToChatCompletionFallback before calling ChatCompletionStream keeps the Responses-over-OpenAI-chat behavior explicit and aligns Gemini with the unified streaming design.


606-620: SpeechStream finalization uses unified end-of-stream handling

Switching the final “done” speech chunk to:

  • mark BifrostContextKeyStreamEndIndicator on ctx, and
  • send via ProcessAndSendResponse with a BifrostSpeechStreamResponse

makes Gemini speech streaming consistent with other providers’ lifecycle semantics. Logic and latency/usage fields look correct.


863-883: TranscriptionStream final chunk handling is consistent and stateful

The final transcription “done” chunk now:

  • aggregates full text and usage,
  • sets ChunkIndex and total Latency, and
  • marks BifrostContextKeyStreamEndIndicator before calling ProcessAndSendResponse.

This matches the new unified streaming lifecycle; no issues noticed.

tests/core-providers/vertex_test.go (1)

30-46: Vertex ToolCallsStreaming scenario correctly enabled

Adding ToolCallsStreaming: true next to ToolCalls: true ensures Vertex’s tool-call streaming path is exercised by the comprehensive test suite, consistent with other providers.

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

726-735: ResponsesStream: accumulator-based streaming and lifecycle look correct

The Responses streaming changes are well-structured:

  • Line [726-735]: Handling empty BodyStream by emitting a BifrostOperationError with BifrostContextKeyStreamEndIndicator set ensures consumers see a definitive terminal error.
  • Line [783-785]: The comment clarifying that response.created / response.in_progress now come from ToBifrostResponsesStream documents the new responsibility split nicely.
  • Line [794-804]: On ToBifrostResponsesStream errors, enriching ExtraFields (ResponsesStreamRequest, provider, model), marking stream-end, and sending via ProcessAndSendBifrostError provides good observability and avoids silent partial streams.
  • Line [806-832]: Iterating over the responses slice, assigning monotonically increasing ChunkIndex/latency, optionally attaching raw SSE payload, and on the last chunk:
    • ensuring response.Response is non-nil,
    • attaching aggregated usage,
    • overriding latency with total stream duration, and
    • marking BifrostContextKeyStreamEndIndicator before ProcessAndSendResponse

matches the desired OpenAI-style lifecycle with a clear final “completed” event.

Overall, the accumulator pattern and lifecycle handling look sound.

Also applies to: 783-785, 794-804, 806-832

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

542-554: Consistent stream-end signaling across OpenAI streaming handlers

Using schemas.BifrostContextKeyStreamEndIndicator before the final ProcessAndSendResponse / ProcessAndSendBifrostError call in Text, Chat, Responses, Speech, and Transcription streams makes the lifecycle explicit and consistent for downstream hooks. The wiring and latency handling look correct and aligned across handlers.

Also applies to: 860-862, 1340-1344, 1710-1712, 1982-1984

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

11-25: Accumulator design for Bedrock streaming is sound

BedrockStreamAccumulator and Flush cover the right state (content→output mapping, tool IDs/names, item IDs, lifecycle flags, timestamps). The reset logic reinitializes maps and timestamps correctly, which should make reuse safe per stream.

Also applies to: 27-53

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

633-640: ResponsesStream accumulator integration for Cohere looks correct

Creating a CohereStreamAccumulator once per stream, threading it into event.ToBifrostResponsesStream, and then enriching each emitted ResponsesStreamResponse with ChunkIndex, latency, and raw payload is consistent with the new OpenAI-style lifecycle. The end-of-stream branch correctly ensures the final response has total latency and sets the stream-end indicator on the context before ProcessAndSendResponse.

Also applies to: 677-723

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

11-47: CohereStreamAccumulator and helpers look solid

CohereStreamAccumulator, NewCohereStreamAccumulator, Flush, and GetOrCreateOutputIndex cover the needed state for Cohere Responses streaming (content indices, tool arguments, stable item IDs, lifecycle flags). Reset semantics and output-index allocation logic look correct for reuse across a single stream.

core/schemas/mux.go (2)

960-991: ChatToResponsesStreamAccumulator captures necessary streaming state

The accumulator fields and NewChatToResponsesStreamAccumulator initialization cover all the pieces needed for Chat→Responses streaming (tool call argument buffers, item IDs, output indices, lifecycle flags, and a monotonic SequenceNumber). This centralizes state management cleanly and keeps callers simple.


993-1350: Chat→Responses streaming conversion logic looks correct and robust

ToBifrostResponsesStreamResponse cleanly translates Chat stream deltas into Responses-style events:

  • Emits response.created and response.in_progress once per stream using the accumulator.
  • Creates a stable text item (output_index:0) on first content delta and emits output_text.delta events with consistent item_id.
  • Handles tool-call deltas with per-tool-call output indices, argument accumulation, and both function_call_arguments.delta and function_call_arguments.done followed by output_item.done.
  • Properly emits reasoning and refusal deltas, and a final response.completed with converted usage.
  • Uses local copies (argsCopy) for arguments when emitting .done events to avoid pointer-to-range-variable issues.

Overall, the sequencing and lifecycle semantics match the intended OpenAI-style Responses streaming model.

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

725-734: LGTM: Error enrichment improves observability.

The error handling correctly enriches BifrostError with request metadata (RequestType, Provider, ModelRequested) and sets the stream end indicator before propagating the error. This ensures consistent error reporting across the streaming path.


762-763: LGTM: Stream end indicator correctly set.

The stream end indicator is properly set in context before sending the final response, enabling post-hook runners to detect stream completion.


886-908: Verify that all finalization responses should carry the stream-end indicator.

The stream end indicator is set at Line 886 before the finalization loop, which means all final responses (including usage, completed events, etc.) will carry the stream-end indicator. If only the last response should signal stream completion, consider moving the context assignment inside the loop:

ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
if err == io.EOF {
    // End of stream - finalize any open items
    finalResponses := FinalizeBedrockStream(accumulator, chunkIndex, usage)
    for i, finalResponse := range finalResponses {
        finalResponse.ExtraFields = schemas.BifrostResponseExtraFields{
            RequestType:     schemas.ResponsesStreamRequest,
            Provider:        providerName,
            ModelRequested:  request.Model,
            ModelDeployment: deployment,
            ChunkIndex:      chunkIndex,
            Latency:         time.Since(lastChunkTime).Milliseconds(),
        }
        chunkIndex++
        lastChunkTime = time.Now()

        if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) {
            finalResponse.ExtraFields.RawResponse = "{}"
        }
        
        // Only set stream end indicator on the last response
        ctxToUse := ctx
        if i == len(finalResponses)-1 {
            ctxToUse = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
        }

        providerUtils.ProcessAndSendResponse(ctxToUse, postHookRunner, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, finalResponse, nil, nil), responseChan)
    }
    break
}

If the current behavior (all final responses carry the indicator) is intentional for this provider, this comment can be disregarded.

tests/core-providers/scenarios/tool_calls_streaming.go (3)

102-186: LGTM: Robust tool call accumulation with multi-identifier tracking.

The method correctly handles accumulation across multiple streaming chunks by:

  • Using itemID as the primary identifier (most stable across chunks)
  • Falling back to callID or name when itemID is unavailable
  • Migrating to better keys when identifiers become available (lines 152-161)
  • Detecting complete JSON arguments to handle both incremental and complete "done" events (lines 169-176)

218-743: LGTM: Comprehensive streaming test with detailed observability.

The test harness correctly:

  • Tests both Chat Completions and Responses API streaming paths
  • Implements retry logic to handle non-deterministic LLM responses
  • Provides extensive field-level logging (lines 306-317, 511-525) for debugging provider-specific streaming formats
  • Validates final accumulated tool calls for completeness

The detailed logging will be valuable for diagnosing provider-specific streaming issues.


745-793: LGTM: Validation logic appropriately handles streaming edge cases.

The validation function correctly:

  • Checks for required fields (ID, Name, Arguments)
  • Attempts JSON parsing without failing on invalid JSON (lines 774-783), which is appropriate since arguments might be partially streamed
  • Uses require.NotEmpty to ensure test failure when critical fields are missing
core/providers/anthropic/responses.go (5)

13-60: LGTM: Well-designed accumulator for stateful streaming conversion.

The AnthropicStreamAccumulator correctly maintains per-output state for streaming:

  • ContentIndexToOutputIndex maps Anthropic's content indices to stable output indices (lines 22)
  • ToolArgumentBuffers accumulates incremental tool arguments (line 23)
  • MCPCallOutputIndices distinguishes MCP calls for correct event type emission (line 24)
  • ItemIDs provides stable item identifiers (line 25)
  • CreatedAt ensures consistent timestamps across lifecycle events (line 29)

The Flush method properly resets all state (lines 46-60), preventing state leakage across streams.


62-80: LGTM: Output index mapping ensures stable indexing.

The method correctly:

  • Handles nil content indices by allocating a new output index (lines 64-69)
  • Returns existing mappings for known content indices (lines 71-73)
  • Creates new mappings and increments the counter atomically (lines 75-79)

This ensures consistent output indexing across streaming chunks, which is essential for OpenAI-style Responses API streaming.


396-442: LGTM: OpenAI-style lifecycle events correctly implemented.

The MessageStart handler properly:

  • Initializes accumulator state (MessageID, Model, CreatedAt) at lines 399-404
  • Emits response.created event with guard flag to prevent duplicates (lines 409-423)
  • Emits response.in_progress event with same CreatedAt for consistency (lines 426-437)
  • Increments SequenceNumber for each event in the slice (line 433)
  • Returns multiple responses to support multi-event emission (line 440)

This aligns with OpenAI's Responses API streaming lifecycle.


574-635: LGTM: Tool argument accumulation correctly handles MCP and function calls.

The ContentBlockDelta handler properly:

  • Maps content index to stable output index (lines 576-577)
  • Initializes and accumulates tool arguments in buffers (lines 609-613)
  • Distinguishes MCP calls from regular function calls using MCPCallOutputIndices (lines 617-621)
  • Emits the appropriate delta type (MCPCallArgumentsDelta vs FunctionCallArgumentsDelta) based on the tool type
  • Attaches item IDs for stable item references (lines 623-634)

This enables correct incremental tool argument streaming for both MCP and standard function calls.


657-762: LGTM: Content block finalization correctly handles all tool types.

The ContentBlockStop handler properly:

  • Finalizes computer tool calls by parsing accumulated JSON and converting actions (lines 662-712)
  • Emits appropriate arguments.done events for tool calls, distinguishing MCP from function calls (lines 718-742)
  • Cleans up argument buffers and MCP tracking after emission (lines 740-741)
  • Emits output_item.done for all content blocks with stable item IDs (lines 744-760)
  • Uses multiple response emission to send both arguments.done and item.done events (lines 717, 753-760)

This ensures complete tool call lifecycle events are emitted in the correct order.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
core/providers/cohere/responses.go (1)

576-587: Fix nil-pointer dereference in reasoning ID generation and remove redundant text ID branch.

In ToBifrostResponsesStream:

  • Text item ID generation:
var itemID string
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("item_%d", outputIndex)
} else {
    itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
}
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("item_%d", outputIndex)
}

The second if accumulator.MessageID == nil is redundant and can be dropped; it reassigns the same value.

  • Reasoning item ID generation:
itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
if accumulator.MessageID == nil {
    itemID = fmt.Sprintf("reasoning_%d", outputIndex)
}

This dereferences accumulator.MessageID before checking for nil, which can panic if Cohere ever omits the message ID on a message_start event (or if the code is reused in a different context). This is the same issue that was previously flagged in this file and should be fixed defensively.

Suggested fix:

-            // Generate stable ID for text item
-            var itemID string
-            if accumulator.MessageID == nil {
-                itemID = fmt.Sprintf("item_%d", outputIndex)
-            } else {
-                itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
-            }
-            if accumulator.MessageID == nil {
-                itemID = fmt.Sprintf("item_%d", outputIndex)
-            }
+            // Generate stable ID for text item
+            var itemID string
+            if accumulator.MessageID != nil {
+                itemID = fmt.Sprintf("msg_%s_item_%d", *accumulator.MessageID, outputIndex)
+            } else {
+                itemID = fmt.Sprintf("item_%d", outputIndex)
+            }
             accumulator.ItemIDs[outputIndex] = itemID
@@
-            // Generate stable ID for reasoning item
-            itemID := fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
-            if accumulator.MessageID == nil {
-                itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-            }
+            // Generate stable ID for reasoning item
+            var itemID string
+            if accumulator.MessageID != nil {
+                itemID = fmt.Sprintf("msg_%s_reasoning_%d", *accumulator.MessageID, outputIndex)
+            } else {
+                itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+            }
             accumulator.ItemIDs[outputIndex] = itemID

This preserves the ID format while avoiding any nil dereference and cleans up the redundant branch.

Also applies to: 610-616

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

798-890: Fix Arguments pointer-to-range-variable bug in FinalizeBedrockStream.

In FinalizeBedrockStream, you iterate over accumulator.ToolArgumentBuffers:

for outputIndex, args := range accumulator.ToolArgumentBuffers {
    …
    response := &schemas.BifrostResponsesStreamResponse{
        Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
        SequenceNumber: sequenceNumber + len(responses),
        OutputIndex:    schemas.Ptr(outputIndex),
        Arguments:      &args,
    }
    …
    responses = append(responses, response)
    …
}

Taking &args (the range variable) means all response.Arguments pointers share the same underlying storage; subsequent iterations overwrite the string value, so earlier events can end up seeing the last tool call’s arguments. This is the same class of bug previously flagged in similar code.

Suggested fix (make a per-iteration copy before taking the address):

-    for outputIndex, args := range accumulator.ToolArgumentBuffers {
+    for outputIndex, args := range accumulator.ToolArgumentBuffers {
         if args != "" {
             itemID := accumulator.ItemIDs[outputIndex]
             callID := accumulator.ToolCallIDs[outputIndex]
             toolName := accumulator.ToolCallNames[outputIndex]
@@
-            // Emit function_call_arguments.done with full arguments
-            response := &schemas.BifrostResponsesStreamResponse{
+            // Emit function_call_arguments.done with full arguments
+            argsCopy := args
+            response := &schemas.BifrostResponsesStreamResponse{
                 Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
                 SequenceNumber: sequenceNumber + len(responses),
                 OutputIndex:    schemas.Ptr(outputIndex),
-                Arguments:      &args,
+                Arguments:      &argsCopy,
             }

This ensures each function_call_arguments.done event gets its own stable pointer to the correct arguments string.

tests/core-providers/scenarios/tool_calls_streaming.go (1)

60-71: Remove fallback logic that treats Index=0 as "not set".

Lines 61-63 and 68-70 fallback to choiceIndex when toolCall.Index == 0, treating zero as "not set". However, Index is a uint16 field (not optional), and zero is a valid value for the first tool call. Past reviews confirmed that providers legitimately send Index=0 for the first tool call.

Apply this diff:

 	} else {
 		// Use the tool call index (if available) or choice index as the key
 		key = int(toolCall.Index)
-		if key == 0 {
-			key = choiceIndex
-		}
 	}

And similarly for lines 61-63:

 		// If not found by ID, use index
 		if !found {
 			key = int(toolCall.Index)
-			if key == 0 {
-				key = choiceIndex
-			}
 		}

Based on past reviews.

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

587-595: Consider marking scanner errors as stream-terminal too

For successful completion, you set BifrostContextKeyStreamEndIndicator before emitting the final chunk; for empty-body and per-event errors, you do the same. For consistency, you may want to also set this flag in the scanner.Err() path so downstream consumers can uniformly treat any end condition (success or error) as terminal based on the context flag rather than inferring from channel closure only.

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

411-418: Avoid double-incrementing chunkIndex in ChatCompletionStream.

chunkIndex is incremented once before parsing the event and again after sending the response:

chunkIndex++           // before processingresponse.ExtraFields.ChunkIndex = chunkIndexlastChunkTime = time.Now()
chunkIndex++           // after processing

This yields values like 1, 3, 5… instead of contiguous indices and makes the counter harder to reason about.

Consider keeping a single increment (e.g., initialize chunkIndex := -1 and increment only once when you’re about to emit a chunk) so ChunkIndex matches other providers’ semantics and stays continuous.

Also applies to: 445-475

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18a9284 and 7a0cd1f.

📒 Files selected for processing (41)
  • core/changelog.md (1 hunks)
  • core/providers/anthropic/anthropic.go (7 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (8 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
💤 Files with no reviewable changes (2)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
🔇 Additional comments (47)
core/changelog.md (1)

1-2: Changelog entries are clear and well-documented.

Both entries accurately reflect the core changes in this PR: the introduction of unified streaming lifecycle events across providers and the provider path adjustment. The entries follow the standard changelog format and are descriptive without being verbose.

transports/changelog.md (1)

2-3: LGTM!

The changelog entries accurately describe the streaming lifecycle improvements and the OpenRouter API version migration.

core/schemas/bifrost.go (1)

117-117: LGTM!

The new context key follows naming conventions and clearly signals the fallback streaming mode. It's used consistently across multiple providers to enable unified streaming behavior.

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

212-218: LGTM - Consistent fallback pattern.

The changes enable the unified streaming flow by setting the fallback context flag and passing the postHookRunner directly. This pattern is consistently applied across multiple providers (Perplexity, Mistral, SGL, Vertex, etc.), aligning with the PR's objective to standardize streaming lifecycle events.

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

203-209: LGTM - Consistent fallback pattern.

The changes mirror the same pattern applied across other providers, enabling unified streaming behavior through the fallback context flag.

core/providers/sgl.go (1)

170-176: LGTM - Consistent fallback pattern.

The changes align with the unified streaming flow implemented across all providers.

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

666-672: LGTM - Consistent fallback pattern.

The changes complete the unified streaming flow implementation across all providers in this PR.

core/providers/openrouter.go (1)

214-214: No issues found — OpenRouter v1 responses API is documented and available.

The search results confirm that the /v1/responses endpoint is officially documented at OpenRouter with examples, authentication details, and full feature documentation including streaming, tool calling, and web search support. The endpoint change from /alpha/responses to /v1/responses is valid and represents movement to an officially versioned API.

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

718-718: ContentBlock.ID is the correct field for tool use blocks.

The ContentBlock "id" on tool_use blocks is a unique identifier for that specific tool invocation and is emitted in the content_block_start for tool_use. The code change from chunk.ContentBlock.ToolUseID to chunk.ContentBlock.ID is correct and aligns with Anthropic's streaming API specification.

core/providers/cerebras.go (1)

171-179: ResponsesStream fallback flag wiring looks correct

Setting BifrostContextKeyIsResponsesToChatCompletionFallback on the context and delegating to ChatCompletionStream while preserving postHookRunner cleanly marks this as a responses→chat fallback path without changing behavior otherwise.

core/providers/ollama.go (1)

171-179: Consistent responses→chat fallback handling

The added context flag and direct delegation to ChatCompletionStream keep the behavior consistent with other providers while clearly signaling the fallback mode downstream.

core/providers/groq.go (1)

211-219: Groq ResponsesStream correctly tagged as chat-fallback

Using context.WithValue to set BifrostContextKeyIsResponsesToChatCompletionFallback before calling ChatCompletionStream gives the unified fallback signal with no other behavior changes.

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

469-477: Good: empty chat stream now marks terminal error

On an empty BodyStream, you now set BifrostContextKeyStreamEndIndicator and route the BifrostError via ProcessAndSendBifrostError, which makes this condition visible as a proper terminal event to downstream handlers.


548-557: Mid-stream Anthropic chat errors are now enriched and terminal

Enriching bifrostErr.ExtraFields with request type, provider, and model, then setting the stream-end indicator and sending via ProcessAndSendBifrostError (followed by break) makes mid-stream failures much easier to attribute and correctly terminates the stream.


726-735: Good: empty responses stream is surfaced as terminal error

When BodyStream is nil in ResponsesStream, you’re now marking the stream as ended and emitting a structured BifrostError via ProcessAndSendBifrostError, which aligns with the chat-streaming path and avoids silent truncation.


777-805: Responses accumulator error handling is enriched and terminal

On ToBifrostResponsesStream errors, enriching ExtraFields with ResponsesStreamRequest metadata, setting the end-indicator, and dispatching via ProcessAndSendBifrostError before breaking cleanly terminates the stream and gives callers full context about the failure.


806-833: Final responses chunk handling and end-indicator look coherent

Iterating over responses while:

  • assigning consistent ExtraFields (including ChunkIndex),
  • optionally attaching raw event data,
  • and, for the last response of the last chunk, attaching usage, overriding latency with total duration, setting the end-indicator, and returning after ProcessAndSendResponse

matches the intended multi-part streaming semantics and clearly marks the terminal event.

tests/core-providers/anthropic_test.go (1)

32-49: Enabling ToolCallsStreaming in Anthropic tests aligns with new behavior

Turning on ToolCallsStreaming in the comprehensive test config ensures the new streaming tool-call path for Anthropic is exercised end-to-end.

tests/core-providers/sgl_test.go (1)

36-36: LGTM! Test configuration updated to enable tool call streaming.

The addition of ToolCallsStreaming: true aligns with the PR's objective to add comprehensive tool call streaming tests across providers.

tests/core-providers/ollama_test.go (2)

26-26: Verify the model change rationale.

The ChatModel was changed from "llama3.2" to "llama3.1:latest". This change is not mentioned in the PR summary. Please confirm whether this change is intentional and necessary for tool call streaming support.

Consider documenting the reason for this model change in the PR description or a code comment, especially if llama3.1 has better tool calling capabilities than llama3.2.


35-35: LGTM! Test configuration updated to enable tool call streaming.

The addition of ToolCallsStreaming: true is consistent with other provider test configurations.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Test configuration updated to enable tool call streaming.

The addition aligns with the PR's objective to enable comprehensive tool call streaming tests across all providers.

tests/core-providers/tests.go (2)

35-35: LGTM! Test scenario properly integrated.

The RunToolCallsStreamingTest function is correctly added to the test scenarios slice, ensuring it will be executed during comprehensive testing.


77-77: LGTM! Summary output properly updated.

The ToolCallsStreaming scenario is correctly included in the test summary output, providing visibility into which providers support this feature.

tests/core-providers/mistral_test.go (1)

38-38: LGTM! Test configuration updated to enable tool call streaming.

The addition is consistent with other provider test configurations in this PR.

tests/core-providers/azure_test.go (1)

42-42: LGTM! Test configuration updated to enable tool call streaming.

The addition is consistent with other provider configurations. Note that this test is currently skipped (Line 14), so the streaming functionality won't be exercised until the skip is removed.

tests/core-providers/config/account.go (1)

29-29: LGTM! Field properly defined and documented.

The ToolCallsStreaming field is well-positioned after the ToolCalls field and includes a clear documentation comment. The naming is consistent with the existing test scenario fields.

tests/core-providers/parasail_test.go (1)

35-35: LGTM! Test configuration updated to enable tool call streaming.

The addition completes the consistent rollout of tool call streaming support across all provider test configurations.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Streaming tool calls enabled for Cohere.

Enabling ToolCallsStreaming aligns with the PR's goal of unified streaming across providers.

tests/core-providers/openrouter_test.go (1)

37-39: LGTM! Appropriate handling of Beta API limitations.

Disabling streaming tool calls for OpenRouter's Beta responses API is a sensible approach, with clear inline documentation. Enabling End2EndToolCalling separately provides good test coverage for non-streaming scenarios.

tests/core-providers/groq_test.go (1)

43-43: LGTM! Streaming tool calls enabled for Groq.

Configuration change aligns with the unified streaming approach across providers.

tests/core-providers/cerebras_test.go (1)

40-40: LGTM! Streaming tool calls enabled for Cerebras.

Test configuration change is consistent with the broader streaming enablement.

tests/core-providers/bedrock_test.go (1)

40-40: LGTM! Streaming tool calls enabled for Bedrock.

Configuration update aligns with unified streaming approach.

core/providers/parasail.go (1)

144-152: LGTM! Clean refactor to unified streaming flow.

The context flag BifrostContextKeyIsResponsesToChatCompletionFallback signals the fallback behavior, and passing postHookRunner directly (removing the wrapper) simplifies the streaming path. This aligns well with the PR's goal of consistent OpenAI-style streaming across providers.

tests/core-providers/vertex_test.go (1)

36-36: LGTM! Streaming tool calls enabled for Vertex.

Test configuration aligns with unified streaming enablement across providers.

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

346-354: LGTM! Unified streaming flow for ResponsesStream.

Setting the fallback context flag and passing postHookRunner directly mirrors the pattern in parasail.go, ensuring consistent streaming behavior across providers.


618-619: LGTM! Consistent stream-end handling in SpeechStream.

Using ProcessAndSendResponse with the stream-end indicator (line 618) provides unified lifecycle event handling for the final chunk, aligning with OpenAI-style streaming patterns.


881-882: LGTM! Consistent stream-end handling in TranscriptionStream.

The same stream-end pattern as SpeechStream ensures uniform lifecycle events across streaming operations.

tests/core-providers/gemini_test.go (1)

35-56: Enabling ToolCallsStreaming for Gemini tests looks consistent.

Setting ToolCallsStreaming: true alongside ToolCalls, MultipleToolCalls, and End2EndToolCalling matches the intent of this PR and should exercise the new Responses-style streaming paths for Gemini without regressions.

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

552-554: Unified stream-end handling and Responses→Chat fallback look sound.

  • The new Responses→Chat fallback in HandleOpenAIChatCompletionStreaming correctly:

    • Detects fallback via BifrostContextKeyIsResponsesToChatCompletionFallback.
    • Uses ChatToResponsesStreamAccumulator + ToBifrostResponsesStreamResponse to emit Responses-style events.
    • Enriches errors with RequestType, Provider, and ModelRequested, sends them via ProcessAndSendBifrostError, and terminates the stream.
    • For non-error chunks, sets ChunkIndex, per-chunk latency, and optional RawResponse, and emits a final Completed response with a stream-end indicator.
  • The consistent use of BifrostContextKeyStreamEndIndicator plus ProcessAndSendResponse at the terminal chunk in:

    • HandleOpenAITextCompletionStreaming,
    • HandleOpenAIChatCompletionStreaming (non-fallback path),
    • HandleOpenAIResponsesStreaming,
    • SpeechStream, and
    • TranscriptionStream / TranscriptionStream (usage branch)
      keeps lifecycle semantics aligned across request types.

I don’t see functional issues in these changes; behavior is more uniform and error reporting is richer.

Also applies to: 705-714, 873-923, 1004-1012, 1340-1344, 1708-1712, 1980-1984

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

633-640: Accumulator-backed ResponsesStream wiring for Cohere looks correct.

  • Creating accumulator := NewCohereStreamAccumulator() once per stream and storing request.Model on it matches the new Responses accumulator pattern.
  • Passing chunkIndex into event.ToBifrostResponsesStream and then incrementing chunkIndex once per emitted response yields contiguous SequenceNumbers across the stream and per-response ChunkIndex metadata.
  • Final chunk handling (isLastChunk && i == len(responses)-1) correctly:
    • Ensures response.Response is non-nil,
    • Sets the final latency from startTime,
    • Marks the context with BifrostContextKeyStreamEndIndicator, and
    • Emits a single terminal Completed response before returning.

This part of the integration looks solid.

Also applies to: 677-719

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

11-47: Accumulator structure and helpers are well-shaped.

CohereStreamAccumulator, NewCohereStreamAccumulator, Flush, and GetOrCreateOutputIndex give you a clear, reusable state container for mapping Cohere streaming events into Responses-style outputs. The separation of concerns (content index mapping, per-output buffers, lifecycle flags) makes the downstream logic in ToBifrostResponsesStream easier to reason about.

No changes needed here.

Also applies to: 49-67

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

11-38: Bedrock stream accumulator design aligns with the new Responses lifecycle.

BedrockStreamAccumulator, NewBedrockStreamAccumulator, and Flush cleanly encapsulate streaming state: content-index mapping, per-output tool-call data, stable item IDs, and lifecycle flags. This matches the pattern used in other providers and is a solid foundation for the downstream conversion logic.

No changes needed here.

Also applies to: 40-53


585-596: Responses-style conversion logic for Bedrock stream events looks correct.

  • Message-start handling:

    • Generates a stable message ID once and emits response.created and response.in_progress with consistent CreatedAt.
    • Adds a text output_item.added at output_index=0 with a stable item ID tied to the message ID.
  • Tool-use lifecycle:

    • On tool-use start, closes the open text item once via an output_item.done.
    • Maps contentBlockIndex to output_index=1, stores tool call IDs/names, initializes the argument buffer, and emits an output_item.added with type function_call and status=in_progress.
  • Delta handling:

    • Text deltas become output_text.delta with correct OutputIndex, ContentIndex, and optional ItemID.
    • Tool-use deltas are accumulated into ToolArgumentBuffers[outputIndex] and emitted as function_call_arguments.delta events, also tagged with OutputIndex, ContentIndex, and ItemID.
  • Stop reasons are deliberately ignored here, with closure deferred to FinalizeBedrockStream, which is appropriate given the richer multi-item lifecycle.

The overall structure matches the intended OpenAI-style lifecycle and looks good.

Also applies to: 667-697, 737-787, 789-793

core/schemas/mux.go (1)

960-1350: LGTM! Streaming accumulator and lifecycle event handling are well-implemented.

The ChatToResponsesStreamAccumulator type and ToBifrostResponsesStreamResponse implementation correctly handle:

  • Safe nil checks before dereferencing MessageID
  • Proper variable copying to avoid range loop capture issues
  • OpenAI-style lifecycle events (created, in_progress, completed)
  • Per-output indexing and stable item ID generation

The past review comments confirm that nil dereference and variable capture issues have been addressed.

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

871-978: LGTM! Stateful streaming with accumulator is correctly implemented.

The ResponsesStream method properly:

  • Initializes the BedrockStreamAccumulator and sets model context
  • Sets the stream-end indicator in context before emitting final responses
  • Enriches errors with request metadata (provider, model, request type)
  • Emits multiple per-event responses with correct metadata

The past review discussion confirms that setting the context end indicator before finalization is the correct approach for Bedrock's event stream handling.

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

21-809: LGTM! Anthropic streaming refactor with lifecycle events is well-implemented.

The updated AnthropicStreamAccumulator and ToBifrostResponsesStream correctly:

  • Track per-output state with ContentIndexToOutputIndex mapping
  • Emit OpenAI-style lifecycle events (created, in_progress, completed)
  • Generate stable item IDs with safe nil checks for MessageID
  • Manage tool argument buffers and MCP call tracking
  • Properly sequence arguments.done before item.done events

The past review comments confirm that the nil dereference issue in item ID generation has been addressed (lines 485-489).

@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch 2 times, most recently from 790f8e2 to e11b2d2 Compare November 14, 2025 06:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
core/providers/cohere/responses.go (1)

669-675: Critical: nil pointer deref when generating reasoning item ID.

fmt.Sprintf("msg_%s_reasoning_%d", *state.MessageID, …) dereferences state.MessageID before nil-check; will panic when Cohere omits ID in message-start. Guard before dereference.

-				// Generate stable ID for reasoning item
-				itemID := fmt.Sprintf("msg_%s_reasoning_%d", *state.MessageID, outputIndex)
-				if state.MessageID == nil {
-					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-				}
+				// Generate stable ID for reasoning item
+				var itemID string
+				if state.MessageID != nil {
+					itemID = fmt.Sprintf("msg_%s_reasoning_%d", *state.MessageID, outputIndex)
+				} else {
+					itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+				}
core/providers/bedrock/responses.go (1)

920-929: Critical: address-of-range-variable bug in finalize.

Taking &args inside for outputIndex, args := range ... makes all emitted .Arguments point to the same memory (last value). Copy before taking the address.

-			response := &schemas.BifrostResponsesStreamResponse{
+			argsCopy := args
+			response := &schemas.BifrostResponsesStreamResponse{
 				Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
 				SequenceNumber: sequenceNumber + len(responses),
 				OutputIndex:    schemas.Ptr(outputIndex),
-				Arguments:      &args,
+				Arguments:      &argsCopy,
 			}
🧹 Nitpick comments (11)
ui/app/workspace/logs/views/filters.tsx (1)

185-186: Consider adding accessibility labels for the loading spinner.

The loading spinner lacks accessibility attributes, so screen reader users won't be informed that content is loading.

Apply this diff to improve accessibility:

 {isLoading ? (
-  <div className="border-primary h-3 w-3 animate-spin rounded-full border border-t-transparent" />
+  <div 
+    className="border-primary h-3 w-3 animate-spin rounded-full border border-t-transparent" 
+    role="status"
+    aria-label="Loading"
+  />
 ) : (
   <Check className="text-primary-foreground size-3" />
 )}
tests/core-providers/scenarios/tool_calls_streaming.go (1)

45-93: Remove unused choiceIndex parameter.

The choiceIndex parameter is declared but never used in the method body. After the fixes from previous reviews (commits 6c2ea61 and 790f8e2), the code now correctly uses toolCall.Index directly without fallback, making this parameter obsolete.

Apply this diff to clean up the signature:

-func (acc *StreamingToolCallAccumulator) AccumulateChatToolCall(choiceIndex int, toolCall schemas.ChatAssistantMessageToolCall) {
+func (acc *StreamingToolCallAccumulator) AccumulateChatToolCall(toolCall schemas.ChatAssistantMessageToolCall) {
   // Prefer ID as key if available, otherwise use index
   key := -1

Then update the call site at line 313:

-  accumulator.AccumulateChatToolCall(choice.Index, toolCall)
+  accumulator.AccumulateChatToolCall(toolCall)
core/providers/cohere/responses.go (3)

635-646: Redundant/contradictory itemID assignment.

itemID is set for nil/non-nil MessageID, then re-assigned again on Line 642. Remove the duplicate branch to avoid confusion.

-				var itemID string
-				if state.MessageID == nil {
-					itemID = fmt.Sprintf("item_%d", outputIndex)
-				} else {
-					itemID = fmt.Sprintf("msg_%s_item_%d", *state.MessageID, outputIndex)
-				}
-				if state.MessageID == nil {
-					itemID = fmt.Sprintf("item_%d", outputIndex)
-				}
+				var itemID string
+				if state.MessageID != nil {
+					itemID = fmt.Sprintf("msg_%s_item_%d", *state.MessageID, outputIndex)
+				} else {
+					itemID = fmt.Sprintf("item_%d", outputIndex)
+				}

560-601: Emit lifecycle even when chunk.ID is absent.

Currently response.created/response.in_progress are gated by chunk.ID != nil. Some Cohere streams can omit IDs in message-start; you should emit lifecycle with a nil ID as allowed by schema and still set CreatedAt.


604-624: Tool plan and text reuse the same output_index and ID; avoid duplicate item.added with same ID.

Flow: tool-plan opens a “message” item at output_index 0, then ContentStart closes it and immediately emits another output_item.added for the same output_index/ID. This produces two items with the same ID, which can confuse consumers.

Prefer either:

  • Treat tool-plan as part of the same text item (don’t close on ContentStart; continue deltas), or
  • After closing tool-plan, allocate a fresh output_index for the new text item and remap ContentIndexToOutputIndex.

Also applies to: 747-785, 656-663

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

445-475: Double-increment of chunkIndex per event.

chunkIndex++ at Line 445 and again at Line 474 causes per-event increments twice. Consider a single, consistent increment per emitted chunk.

Would you like me to propose a small diff to increment only after successful emit?

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

469-477: Unify stream-end indicator handling across all terminal paths in chat streaming

You now set BifrostContextKeyStreamEndIndicator for the “empty body” case and when ToBifrostChatCompletionStream returns a bifrostErr, but not when scanner.Err() is non-nil at the end of the loop. If downstream post-hooks rely on this flag to flush or finalize state, the scanner error path will behave differently.

Consider also setting the flag before ProcessAndSendError so every terminal path (normal completion, provider error, transport error, scanner error) consistently marks the stream as ended:

-		if err := scanner.Err(); err != nil {
-			logger.Warn(fmt.Sprintf("Error reading %s stream: %v", providerType, err))
-			providerUtils.ProcessAndSendError(ctx, postHookRunner, err, responseChan, schemas.ChatCompletionStreamRequest, providerType, modelName, logger)
-		} else {
+		if err := scanner.Err(); err != nil {
+			logger.Warn(fmt.Sprintf("Error reading %s stream: %v", providerType, err))
+			ctx = context.WithValue(ctx, schemas.BifrostContextKeyStreamEndIndicator, true)
+			providerUtils.ProcessAndSendError(ctx, postHookRunner, err, responseChan, schemas.ChatCompletionStreamRequest, providerType, modelName, logger)
+		} else {

Also applies to: 587-590

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

14-33: State struct and pool lifecycle are sound, but map reset and CreatedAt logic can be simplified

The AnthropicResponsesStreamState shape and pooling are appropriate for per-stream accumulation, and getOrCreateOutputIndex gives a clean mapping from content_index to output_index. Two minor cleanups:

  • acquireAnthropicResponsesStreamState and flush both reset maps and timestamps; with flush updated to use clear (see comment in anthropic.go), CreatedAt will always be set by acquire, so the if state.CreatedAt == 0 guard in ToBifrostResponsesStream is effectively dead code.
  • Given acquire always overwrites CreatedAt, you can initialize CreatedAt to 0 in the pool New function to make intent clearer and avoid confusion about when the timestamp is actually taken.

These are small readability/perf wins; behavior is already correct.

Also applies to: 35-112, 114-132


529-560: Stable text item IDs are generated safely; minor optional tweak

The new text block path allocates stable itemIDs per output_index and stores them in state.ItemIDs, and you now guard the MessageID dereference with a nil check, avoiding the earlier panic risk.

If you want to be extra defensive, you could also treat empty MessageID as “unset” so you don’t end up with IDs like msg__item_0:

-				if state.MessageID == nil {
+				if state.MessageID == nil || *state.MessageID == "" {
					itemID = fmt.Sprintf("item_%d", outputIndex)
				} else {
					itemID = fmt.Sprintf("msg_%s_item_%d", *state.MessageID, outputIndex)
				}

Not required for correctness, but it keeps IDs a bit cleaner.


561-589: Function and MCP tool output_item.added events look good; consider adding ContentIndex for consistency

The function and MCP tool branches correctly:

  • Tag items as function_call vs mcp_call,
  • Initialize ToolArgumentBuffers[outputIndex],
  • Track ItemIDs and MCP-specific indices for later *_arguments.delta/done events.

One minor consistency improvement: unlike the computer/text branches, these output_item.added events don’t populate ContentIndex. If downstream consumers expect content_index to be set on all output items (even tools), you may want to mirror the text/computer behavior:

return []*schemas.BifrostResponsesStreamResponse{{
    Type:           schemas.ResponsesStreamResponseTypeOutputItemAdded,
    SequenceNumber: sequenceNumber,
    OutputIndex:    schemas.Ptr(outputIndex),
-   Item:           item,
+   ContentIndex:   chunk.Index,
+   Item:           item,
}}, nil, false

Purely optional; current behavior is consistent with some tooling that only keys tools by output_index.

Also applies to: 590-623


709-814: Tool/computer content_block_stop closure is correct; small cleanup opportunity

For content_block_stop you:

  • Correctly finalize the computer tool path by unmarshaling the accumulated JSON into a ResponsesComputerToolCallAction, emitting a single output_item.done, and clearing the computer-specific fields.
  • For function/MCP tools, first emit the appropriate *_arguments.done event when arguments were buffered, then an output_item.done with a completed status and stable item_id, bumping SequenceNumber when needed.

Two minor improvements you could make:

  • Even when no arguments were accumulated, you might still delete(state.ToolArgumentBuffers, outputIndex) and delete(state.MCPCallOutputIndices, outputIndex) to keep the maps lean until flush() runs.
  • For non-tool text blocks, doneItem only carries Status and optional ID. If clients benefit from knowing the message type here, you could optionally reuse the original type (e.g., message) when available.

Neither affects correctness, but they’d tighten the state lifecycle a bit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0cd1f and e11b2d2.

📒 Files selected for processing (46)
  • core/changelog.md (1 hunks)
  • core/go.mod (1 hunks)
  • core/providers/anthropic/anthropic.go (8 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (7 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/bedrock/signer.go (0 hunks)
  • core/providers/bedrock/signer_test.go (0 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
  • ui/app/workspace/logs/views/filters.tsx (1 hunks)
  • ui/app/workspace/logs/views/logEntryDetailsView.tsx (1 hunks)
💤 Files with no reviewable changes (4)
  • core/providers/utils/utils.go
  • core/providers/bedrock/signer_test.go
  • core/providers/bedrock/signer.go
  • core/providers/perplexity/responses.go
🚧 Files skipped from review as they are similar to previous changes (25)
  • core/providers/parasail.go
  • core/providers/openrouter.go
  • core/providers/anthropic/chat.go
  • tests/core-providers/openrouter_test.go
  • tests/core-providers/tests.go
  • tests/core-providers/azure_test.go
  • tests/core-providers/sgl_test.go
  • core/providers/mistral/mistral.go
  • tests/core-providers/vertex_test.go
  • core/providers/ollama.go
  • tests/core-providers/cerebras_test.go
  • tests/core-providers/gemini_test.go
  • tests/core-providers/mistral_test.go
  • tests/core-providers/anthropic_test.go
  • tests/core-providers/bedrock_test.go
  • core/providers/gemini/gemini.go
  • tests/core-providers/openai_test.go
  • tests/core-providers/cohere_test.go
  • tests/core-providers/parasail_test.go
  • core/schemas/bifrost.go
  • core/providers/vertex/vertex.go
  • core/providers/sgl.go
  • transports/changelog.md
  • core/providers/groq.go
  • tests/core-providers/config/account.go
🧰 Additional context used
🧬 Code graph analysis (12)
core/providers/cerebras.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/perplexity/perplexity.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/anthropic/anthropic.go (2)
core/schemas/bifrost.go (5)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • ResponsesStreamRequest (90-90)
core/providers/utils/utils.go (3)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
ui/app/workspace/logs/views/filters.tsx (1)
ui/lib/constants/logs.ts (1)
  • RequestTypeLabels (77-102)
core/providers/openai/openai.go (3)
core/schemas/bifrost.go (8)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • ChatCompletionStreamRequest (88-88)
core/providers/utils/utils.go (6)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ProcessAndSendBifrostError (569-599)
  • ProviderSendsDoneMarker (749-758)
  • ProcessAndSendError (605-651)
  • CreateBifrostChatCompletionChunkResponse (684-713)
core/schemas/mux.go (3)
  • ChatToResponsesStreamState (962-978)
  • AcquireChatToResponsesStreamState (1002-1043)
  • ReleaseChatToResponsesStreamState (1046-1077)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/bedrock/bedrock.go (3)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostResponseExtraFields (282-291)
  • ResponsesStreamRequest (90-90)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/providers/bedrock/responses.go (1)
  • FinalizeBedrockStream (875-966)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (7)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
  • BifrostChatResponse (25-40)
  • ChatStreamResponseChoice (529-531)
tests/core-providers/scenarios/utils.go (5)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (7)
  • BifrostResponsesRequest (32-39)
  • ResponsesParameters (84-111)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/cohere/cohere.go (3)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ResponsesStreamRequest (90-90)
  • BifrostResponseExtraFields (282-291)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/schemas/mux.go (3)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/providers/anthropic/responses.go (2)
core/providers/anthropic/types.go (5)
  • AnthropicStreamEvent (312-321)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicToolNameComputer (184-184)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
core/schemas/responses.go (22)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesComputerToolCallAction (590-600)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
🪛 GitHub Actions: Run tests and upload coverage
core/go.mod

[warning] 1-1: CI: Warning: package version mismatches or unrelated warnings detected in workspace

🔇 Additional comments (38)
core/changelog.md (2)

1-2: Changelog entries accurately reflect PR scope.

Both entries accurately capture the key changes and follow conventional commit format. The lifecycle events entry reflects the core feature work, while the OpenRouter entry documents a provider-specific configuration update.

Minor observation: The first entry is somewhat verbose. Consider shortening to: "feat: unified streaming lifecycle events across providers" if brevity is preferred, though the current wording is also acceptable for clarity.


2-2: Verify scope of provider API path changes.

The OpenRouter entry indicates a shift from alpha/responses to v1/responses. Please confirm whether other providers (Bedrock, Cohere, etc.) received similar API path updates that should also be documented in the changelog, or if this change was OpenRouter-specific.

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

42-44: LGTM! Appropriate text wrapping for log content.

The addition of break-all prevents horizontal overflow when displaying long technical strings (URLs, tokens, IDs) in log entries, which is appropriate for this log viewer component.

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

211-219: Verification successful—changes approved.

The downstream handler correctly checks the context flag (lines 708-714 in HandleOpenAIChatCompletionStreaming), properly converts and emits Responses format chunks when the flag is set (lines 874-924), and maintains correct post-hook runner handling throughout. The refactoring pattern is consistent across all providers and integrates cleanly with the unified streaming approach.

core/go.mod (1)

32-32: The move of smithy-go to indirect dependencies is correct and requires no action.

Verification confirms that smithy-go v1.23.1 is properly placed as an indirect dependency. The codebase contains no direct imports of github.com/aws/smithy-go, which means it is correctly pulled transitively by aws-sdk-go-v2 v1.39.5. This is the proper Go module state and aligns with best practices—dependencies that are not directly imported should not appear in the direct dependency block.

The change appears to be an intentional result of go mod tidy during the PR refactoring, not an unintended side effect. AWS SDK v2 functionality remains intact with this dependency structure.

core/providers/cerebras.go (1)

173-176: Downstream handling confirmed; verify test coverage scope.

The downstream implementation is correct—openai/openai.go lines 708–714 properly check the BifrostContextKeyIsResponsesToChatCompletionFallback flag and apply the appropriate conversion logic. The code change is sound.

No tests matching the Cerebras ResponsesStream pattern were found in the tests/ directory. Per the PR objectives (which specify tests for OpenAI, Anthropic, Bedrock, and Cohere but not Cerebras), confirm whether Cerebras test coverage is in scope for this PR.

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

164-166: LGTM: Empty category filtering works correctly.

The filter prevents rendering CommandGroup sections for categories with no options, which improves the UI by avoiding empty sections.

tests/core-providers/ollama_test.go (2)

35-35: Enabling ToolCallsStreaming for Ollama is supported.

Ollama has added streaming tool-call support and lists Llama 3.1 as a supported model. However, some client libraries/SDK integrations have reported streaming-with-tools bugs or incomplete support, so behavior can vary by SDK/version. Ensure your Ollama release and client library versions are compatible to avoid unexpected test failures.


26-26: Update the central Ollama configuration and document the model change rationale.

The PR changed ollama_test.go from llama3.2 to llama3.1:latest, which represents an upgrade from the 3B to the 8B model. However, this creates inconsistencies:

  • llama3.1:latest is 4.9GB (8B model), while llama3.2 defaults to the smaller 3B variant
  • tests/core-providers/config/account.go (line 705) still references llama3.2
  • tests/core-providers/README.md (line 392) still references llama3.2 as the default
  • No code comments explain the model version/size change

The :latest tag is stable (pinned 11 months ago), so that concern is unfounded. Both models support tool calling, so the upgrade to the larger 8B model may be intentional for improved performance.

Fix: Update config/account.go to match the 8B model choice, and add a comment explaining why the larger model is preferred for testing.

tests/core-providers/groq_test.go (1)

43-43: LGTM!

The addition of the ToolCallsStreaming flag enables tool call streaming tests for the Groq provider, consistent with the PR's goal of adding comprehensive streaming tool-call test coverage.

tests/core-providers/scenarios/tool_calls_streaming.go (5)

18-42: LGTM!

The accumulator types and constructor are well-structured for managing streaming tool call fragments across both Chat Completions and Responses APIs. The separation of concerns with dedicated maps for each API type is clean.


182-210: LGTM!

The getter methods cleanly convert the internal map structures to slices of ToolCallInfo, providing a consistent interface for both Chat Completions and Responses API results.


212-737: LGTM!

The test orchestration is comprehensive with appropriate retry logic (3 attempts) to handle flaky provider behavior. The extensive per-chunk logging will be valuable for debugging streaming issues across different providers. The circuit breaker at 500 responses prevents runaway streams.


739-787: LGTM!

The validation logic strikes a good balance between strictness and pragmatism. It requires non-empty ID, name, and arguments for each tool call, but allows arguments that aren't valid JSON (with a warning), recognizing that streaming can produce intermediate non-valid JSON states. The use of require.NotEmpty ensures the essential fields are present.


162-171: ****

The heuristic is not as fragile as initially suggested. The logic at lines 164-171 includes an important guard: it only replaces arguments when existing.Arguments != "", which means the first delta chunk is always appended, preventing false positives. The done event handler (line 663) provides the definitive override, passing the complete arguments which will correctly replace any partial accumulation. While the heuristic could theoretically misfire on unusual formatting, the combination of the existing arguments guard and the done event override ensures correct behavior in practice, which aligns with how providers (Anthropic, Bedrock, Cohere) consistently emit ResponsesStreamResponseTypeFunctionCallArgumentsDone events.

Likely an incorrect or invalid review comment.

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

633-637: State pooling: good acquire/release hygiene.

Creating and releasing streamState outside the loop with defer is correct and prevents leaks.

core/schemas/mux.go (2)

1110-1140: Lifecycle emission independent of message ID: LGTM.

Emitting response.created/response.in_progress keyed off the first role delta avoids ID gating pitfalls seen in providers that omit IDs.


1361-1400: Correct fix for pointer-to-range var on arguments.

Copying args before taking its address prevents aliasing across events. Solid.

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

874-918: Fallback Responses-stream path looks consistent.

  • Splitting chat chunks into Responses events and routing via ProcessAndSendResponse is correct.
  • Final-chunk end-indicator is set.

Please confirm tests cover tool-call arguments accumulation in fallback mode.


1341-1345: End-indicator propagation on Completed: LGTM.

Setting BifrostContextKeyStreamEndIndicator before the final send aligns with unified stream termination.

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

666-741: Lifecycle and initial text item emission: LGTM.

Creating response.created/in_progress and first output_item.added is consistent and sequence numbers are monotonic via sequenceNumber + len(responses).


865-869: Finalization properly invoked on stream end — verification successful.

FinalizeBedrockStream is called in the EOF handler at end-of-stream (bedrock.go:889) with streamState, sequenceNumber, and usage parameters, confirming the stream finalization flow is correct.

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

725-734: LGTM! Error enrichment follows unified pattern.

The error handling correctly enriches bifrostErr with metadata (request type, provider, model) and sets the stream end indicator before emission, aligning with the unified error handling approach across providers.


762-763: LGTM! Stream completion properly signaled.

The stream end indicator is correctly set in the context before emitting the final response, consistent with the established pattern confirmed in previous reviews.


871-874: LGTM! Accumulator pattern correctly implemented.

The stream state accumulator is properly acquired from a pool, initialized with the model, and deferred for release. This enables the stateful tracking needed for OpenAI-style tool call streaming.


886-908: LGTM! Finalization properly emits all pending items.

The finalization path correctly:

  • Sets the stream end indicator before calling FinalizeBedrockStream
  • Emits all final responses (text item done, tool call done events, response.completed) with proper metadata
  • Increments chunkIndex for each final response to maintain proper ordering

As confirmed in previous reviews, setting the context value before the finalization loop achieves the same goal as calling HandleStreamEndWithSuccess.


947-977: LGTM! Per-event processing correctly handles multiple lifecycle responses.

The refactored streaming logic properly:

  • Calls ToBifrostResponsesStream which now returns multiple responses per event (enabling lifecycle events like output_item.delta, function_call_arguments.delta)
  • Emits each response with correctly populated metadata (including incrementing chunkIndex per response)
  • Enriches errors with request metadata before emission

The iteration ensures proper ordering and tracking of all emitted responses.

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

548-557: Mid-stream chat errors are correctly enriched and terminated

The extra BifrostErrorExtraFields (request type, provider, model) plus setting the stream-end indicator before ProcessAndSendBifrostError gives downstream consumers enough context and a clean terminal event. This is aligned with the new streaming conventions.


587-595: Final chat chunk handling and stream-end signaling look consistent

On clean termination you create a final chunk with usage/finish reason, stamp total latency, mark BifrostContextKeyStreamEndIndicator, and send it through ProcessAndSendResponse. This matches the intended “single terminal response” pattern.


726-735: ResponsesStream: good use of stream-end indicator on empty body

Marking BifrostContextKeyStreamEndIndicator before sending the “provider returned an empty response” error keeps this terminal path aligned with the rest of the Responses streaming lifecycle.


794-804: ResponsesStream mid-stream errors are enriched and treated as terminal

The new block that attaches RequestType, Provider, and ModelRequested, sets the stream-end indicator, and dispatches via ProcessAndSendBifrostError is consistent with the chat stream path and should make error handling much easier to consume.


806-832: Final Responses chunk logic correctly produces a single terminal response.completed

The isLastChunk branch ensures that the last ResponsesStreamResponse gets usage attached, total latency, and the end-indicator before being sent, and the early return avoids double-emitting that final message. This fits the OpenAI-style lifecycle you’re targeting.

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

447-494: MessageStart lifecycle emission matches OpenAI Responses semantics

Using AnthropicStreamEventTypeMessageStart to emit both response.created and response.in_progress, with state.MessageID/state.CreatedAt shared across them and SequenceNumber derived from the incoming sequenceNumber, looks correct. The HasEmittedCreated/HasEmittedInProgress guards also protect you from duplicate lifecycle events if Anthropic ever repeats message_start.


496-527: Computer tool content_block_start handling is correct and nicely isolated

Capturing ComputerToolID, the associated ChunkIndex, and accumulating PartialJSON only when both match lets you special-case the computer tool path without interfering with normal function/MCP streams. Emitting a single output_item.added for the computer_call item aligns with the downstream Responses semantics.


626-687: Tool argument buffering maps onto the various *.delta events correctly

The content_block_delta path:

  • Reuses output_index via getOrCreateOutputIndex,
  • Distinguishes computer tool accumulation from function/MCP calls,
  • Buffers PartialJSON per output_index and emits response.function_call_arguments.delta vs response.mcp_call_arguments.delta based on MCPCallOutputIndices,
  • Adds ItemID when available.

This is the right shape to support OpenAI-style tool argument streaming and should interoperate cleanly with the new output_item.added/done lifecycle.


822-835: MessageStop correctly emits a terminal response.completed with stream completion flag

Handling AnthropicStreamEventTypeMessageStop by emitting a response.completed event that carries state.CreatedAt and state.MessageID, and returning true for isLastChunk, gives the upstream streaming loop exactly one place to attach final usage and latency and mark the stream as ended. This aligns with the lifecycle design used in ResponsesStream.


689-699: Reasoning deltas and reverse mapping are consistent

For AnthropicStreamDeltaTypeThinking you now include OutputIndex on ResponsesStreamResponseTypeReasoningSummaryTextDelta, and the reverse mapping in ToAnthropicResponsesStreamResponse converts those back into thinking content_block deltas. That should make reasoning streams behave predictably in both directions.

Also applies to: 953-964


2472-2590: Computer tool action conversions look correct and reversible enough

The convertResponsesToAnthropicComputerAction / convertAnthropicToResponsesComputerAction pair cover all the expected action types (click variants, move, type, keypress, scroll, drag, screenshot, wait) and correctly translate between OpenAI-style fields and Anthropic’s input JSON (including coordinates and scroll direction/amount).

There’s some inevitable lossiness around scroll_amount due to integer division, but for UI control that’s acceptable and doesn’t impact correctness of the Responses streaming protocol.

Also applies to: 2592-2702

Comment thread ui/app/workspace/logs/views/filters.tsx Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from e11b2d2 to dcd8d8e Compare November 14, 2025 07:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

900-949: Fix pointer-to-range variable in finalize: Arguments points to reused memory

Taking &args from a map range causes all .Arguments to share the last value. Copy before taking address.

-    for outputIndex, args := range state.ToolArgumentBuffers {
+    for outputIndex, args := range state.ToolArgumentBuffers {
       if args != "" {
         ...
-        response := &schemas.BifrostResponsesStreamResponse{
+        argsCopy := args
+        response := &schemas.BifrostResponsesStreamResponse{
           Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
           SequenceNumber: sequenceNumber + len(responses),
           OutputIndex:    schemas.Ptr(outputIndex),
-          Arguments:      &args,
+          Arguments:      &argsCopy,
         }
🧹 Nitpick comments (11)
core/changelog.md (1)

1-1: Consider making the first changelog entry more specific.

The entry "added unified streaming lifecycle events across all providers to fully align with OpenAI's streaming response types" is generic. Based on the PR objectives, the changes include:

  • OpenAI-style lifecycle events (created, in_progress, completed)
  • Streaming accumulators for output item tracking
  • Tool call streaming improvements with proper indexing

Consider specifying which providers are affected or which event types are now supported to make the changelog more informative.

For example:

- feat: added unified streaming lifecycle events across all providers to fully align with OpenAI's streaming response types.
+ feat: added OpenAI-style streaming lifecycle events (created, in_progress, completed, output items) with streaming accumulators across Anthropic, Bedrock, and Cohere providers for improved tool call streaming.
ui/app/workspace/logs/views/filters.tsx (1)

170-175: Optional: DRY up the per-category isLoading checks

The isLoading computation repeats category === ... checks that all map to the same filterDataLoading flag for models/keys (Lines 172-174). If you want to tighten this up, you could group those categories:

-const isLoading =
-  (category === "Providers" && providersLoading) ||
-  (category === "Models" && filterDataLoading) ||
-  (category === "Selected Keys" && filterDataLoading) ||
-  (category === "Virtual Keys" && filterDataLoading);
+const isKeyDataCategory =
+  category === "Models" ||
+  category === "Selected Keys" ||
+  category === "Virtual Keys";
+const isLoading =
+  (category === "Providers" && providersLoading) ||
+  (isKeyDataCategory && filterDataLoading);

Purely a readability/maintainability tweak; current logic is already correct.

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

95-129: Prefer clearing maps over re-allocating in flush()

Recreating maps each time increases allocations/GC. Use clear(state.Map) to retain capacity and reduce churn; you already use clear() in acquire().

- state.ContentIndexToOutputIndex = make(map[int]int)
- state.ToolArgumentBuffers = make(map[int]string)
- state.MCPCallOutputIndices = make(map[int]bool)
- state.ItemIDs = make(map[int]string)
+ clear(state.ContentIndexToOutputIndex)
+ clear(state.ToolArgumentBuffers)
+ clear(state.MCPCallOutputIndices)
+ clear(state.ItemIDs)

900-949: Emit tool-call finals in deterministic order

Map iteration order is random, which can make SequenceNumber ordering non-deterministic across tool calls. Sort outputIndex keys before emitting.

- for outputIndex, args := range state.ToolArgumentBuffers {
+ keys := make([]int, 0, len(state.ToolArgumentBuffers))
+ for k := range state.ToolArgumentBuffers { keys = append(keys, k) }
+ sort.Ints(keys)
+ for _, outputIndex := range keys {
+   args := state.ToolArgumentBuffers[outputIndex]
    if args != "" {
      ...
    }
  }

675-717: Consider more unique Response ID generation

Using CreatedAt-derived "msg_" risks collisions across concurrent streams started within the same second. Prefer a ULID/uuid or include a monotonic counter.

core/schemas/mux.go (1)

1245-1254: Start tool-call OutputIndex at 0 when no text item

You skip index 0 even if no text deltas ever arrive, leaving outputs starting at 1. Consider assigning 0 when TextItemAdded=false to keep indices compact and consistent.

- outputIndex := state.CurrentOutputIndex
- if outputIndex == 0 {
-   outputIndex = 1 // Skip 0 if text is using it
- }
+ outputIndex := state.CurrentOutputIndex
+ if state.TextItemAdded { // only skip 0 if text exists
+   if outputIndex == 0 { outputIndex = 1 }
+ }
tests/core-providers/scenarios/tool_calls_streaming.go (3)

95-180: Distinguish “delta” vs “done” instead of brace-heuristic

The “complete JSON” check via leading/trailing braces is brittle (arrays, whitespace, nested braces). Pass a boolean (isComplete) from the caller based on event type (delta vs arguments.done) and set/replace accordingly.

-func (acc *StreamingToolCallAccumulator) AccumulateResponsesToolCall(callID *string, name *string, arguments *string, itemID *string) {
+func (acc *StreamingToolCallAccumulator) AccumulateResponsesToolCall(callID *string, name *string, arguments *string, itemID *string, isComplete bool) {
   ...
-  if arguments != nil && *arguments != "" {
-    argsStr := *arguments
-    if len(argsStr) > 0 && argsStr[0] == '{' && argsStr[len(argsStr)-1] == '}' && existing.Arguments != "" {
-      existing.Arguments = argsStr
-    } else {
-      existing.Arguments += argsStr
-    }
-  }
+  if arguments != nil && *arguments != "" {
+    if isComplete {
+      existing.Arguments = *arguments
+    } else {
+      existing.Arguments += *arguments
+    }
+  }

Then call with isComplete=true for FunctionCallArgumentsDone.


182-197: Return tool calls in stable order

Map iteration is random; sort by ID or Name for deterministic test behavior and logs.

- for _, toolCall := range acc.ChatToolCalls {
+ keys := make([]int, 0, len(acc.ChatToolCalls))
+ for k := range acc.ChatToolCalls { keys = append(keys, k) }
+ sort.Ints(keys)
+ for _, k := range keys {
+   toolCall := acc.ChatToolCalls[k]
    ...
 }

539-679: Plumb isComplete=true for arguments.done

Update the calls to AccumulateResponsesToolCall to pass isComplete=false for .delta and true for .done to leverage the stronger accumulator semantics above.

- accumulator.AccumulateResponsesToolCall(callID, name, arguments, itemID)
+ accumulator.AccumulateResponsesToolCall(callID, name, arguments, itemID, false)
...
- accumulator.AccumulateResponsesToolCall(callID, name, streamResp.Arguments, itemID)
+ accumulator.AccumulateResponsesToolCall(callID, name, streamResp.Arguments, itemID, true)
core/providers/anthropic/responses.go (2)

97-112: Use clear() to reuse map capacity in flush()

Reallocating maps each flush increases GC. Prefer clear() like you do elsewhere.

- state.ContentIndexToOutputIndex = make(map[int]int)
- state.ToolArgumentBuffers = make(map[int]string)
- state.MCPCallOutputIndices = make(map[int]bool)
- state.ItemIDs = make(map[int]string)
+ clear(state.ContentIndexToOutputIndex)
+ clear(state.ToolArgumentBuffers)
+ clear(state.MCPCallOutputIndices)
+ clear(state.ItemIDs)

822-836: Include usage in response.completed when available

Anthropic MessageStop often carries usage; propagate chunk.Usage into ResponsesResponse. This aligns with OpenAI-style completion payloads.

- response := &schemas.BifrostResponsesResponse{
-   CreatedAt: state.CreatedAt,
- }
+ response := &schemas.BifrostResponsesResponse{
+   CreatedAt: state.CreatedAt,
+ }
+ if chunk.Usage != nil {
+   response.Usage = &schemas.ResponsesResponseUsage{
+     InputTokens:  chunk.Usage.InputTokens,
+     OutputTokens: chunk.Usage.OutputTokens,
+     TotalTokens:  chunk.Usage.InputTokens + chunk.Usage.OutputTokens,
+   }
+ }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e11b2d2 and dcd8d8e.

📒 Files selected for processing (46)
  • core/changelog.md (1 hunks)
  • core/go.mod (1 hunks)
  • core/providers/anthropic/anthropic.go (8 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (7 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/bedrock/signer.go (0 hunks)
  • core/providers/bedrock/signer_test.go (0 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
  • ui/app/workspace/logs/views/filters.tsx (1 hunks)
  • ui/app/workspace/logs/views/logEntryDetailsView.tsx (1 hunks)
💤 Files with no reviewable changes (4)
  • core/providers/perplexity/responses.go
  • core/providers/bedrock/signer.go
  • core/providers/utils/utils.go
  • core/providers/bedrock/signer_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • tests/core-providers/config/account.go
  • core/schemas/bifrost.go
  • core/providers/perplexity/perplexity.go
  • tests/core-providers/cerebras_test.go
  • core/providers/openrouter.go
  • core/providers/cerebras.go
  • tests/core-providers/tests.go
  • core/providers/sgl.go
  • tests/core-providers/groq_test.go
  • tests/core-providers/cohere_test.go
  • core/providers/vertex/vertex.go
  • core/providers/mistral/mistral.go
  • tests/core-providers/mistral_test.go
  • core/providers/gemini/gemini.go
  • core/providers/bedrock/bedrock.go
  • transports/changelog.md
🧰 Additional context used
🧬 Code graph analysis (13)
core/providers/groq.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/parasail.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/ollama.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/anthropic/anthropic.go (2)
core/schemas/bifrost.go (5)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • ResponsesStreamRequest (90-90)
core/providers/utils/utils.go (3)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
core/providers/anthropic/chat.go (1)
ui/lib/types/logs.ts (1)
  • ContentBlock (100-111)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (13)
  • CohereStreamEvent (381-386)
  • StreamEventMessageStart (366-366)
  • StreamEventContentStart (367-367)
  • CohereContentBlockTypeText (128-128)
  • CohereContentBlockTypeThinking (130-130)
  • StreamEventContentDelta (368-368)
  • StreamEventContentEnd (369-369)
  • StreamEventToolPlanDelta (370-370)
  • StreamEventToolCallStart (371-371)
  • StreamEventToolCallDelta (372-372)
  • StreamEventToolCallEnd (373-373)
  • StreamEventCitationEnd (375-375)
  • StreamEventMessageEnd (376-376)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeReasoning (297-297)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/openai/openai.go (3)
core/schemas/bifrost.go (8)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • ChatCompletionStreamRequest (88-88)
core/providers/utils/utils.go (6)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ProcessAndSendBifrostError (569-599)
  • ProviderSendsDoneMarker (749-758)
  • ProcessAndSendError (605-651)
  • CreateBifrostChatCompletionChunkResponse (684-713)
core/schemas/mux.go (3)
  • ChatToResponsesStreamState (962-978)
  • AcquireChatToResponsesStreamState (1002-1043)
  • ReleaseChatToResponsesStreamState (1046-1077)
core/providers/cohere/cohere.go (2)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ResponsesStreamRequest (90-90)
  • BifrostResponseExtraFields (282-291)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
ui/app/workspace/logs/views/filters.tsx (1)
ui/lib/constants/logs.ts (1)
  • RequestTypeLabels (77-102)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (17)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/anthropic/responses.go (2)
core/providers/anthropic/types.go (12)
  • AnthropicStreamEvent (312-321)
  • AnthropicStreamEventTypeMessageStart (301-301)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicToolNameComputer (184-184)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (7)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
  • BifrostChatResponse (25-40)
  • ChatStreamResponseChoice (529-531)
tests/core-providers/scenarios/utils.go (6)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • SampleToolTypeWeather (69-69)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (6)
  • BifrostResponsesRequest (32-39)
  • ResponsesParameters (84-111)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
🔇 Additional comments (48)
core/changelog.md (1)

2-2: LGTM!

The OpenRouter provider path update is clearly documented and follows the changelog entry format.

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

42-44: LGTM! Sensible overflow fix for log entries.

The addition of break-all prevents long strings (IDs, URLs, JSON, etc.) from causing horizontal overflow in the log details view. This is a good UI improvement for displaying technical content.

Note: This UI change appears unrelated to the main PR objectives (streaming API improvements), but it's a valid enhancement.

core/providers/groq.go (1)

213-219: Fallback implementation is correct and properly integrated.

The context flag BifrostContextKeyIsResponsesToChatCompletionFallback is correctly defined, set across all applicable providers, and actively consumed in downstream code (e.g., core/providers/openai/openai.go:708-714) to handle the Responses→ChatCompletion fallback path.

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

164-200: Loading-state handling and filter-group rendering look solid

The updated block correctly:

  • Skips categories with no options (Line 165), avoiding empty groups in the menu.
  • Uses provider/filter loading booleans instead of string literals for loading detection (Lines 170-175), addressing the earlier brittleness.
  • Disables interactions and shows a spinner consistently while loading (Lines 176-191).

No correctness issues stand out here; this is a clear improvement over the previous implementation.

core/providers/ollama.go (1)

172-180: Code changes verified as correct — no issues found.

Verification confirms:

  1. The context flag BifrostContextKeyIsResponsesToChatCompletionFallback is properly detected and handled in HandleOpenAIChatCompletionStreaming (openai.go:708-711), with correct type checking and usage.

  2. The postHookRunner parameter is correctly threaded through the call chain:

    • Received by Ollama.ChatCompletionStream
    • Passed to HandleOpenAIChatCompletionStreaming (line 149)
    • Included in the function signature (openai.go:700)
  3. The implementation pattern is consistent with other providers (SGL, Groq, Parasail, etc.), all following the same unified streaming approach.

Test coverage search was inconclusive due to environment limitations, but code correctness is confirmed.

core/go.mod (1)

32-32: Verified: smithy-go dependency correctly marked as indirect.

Confirmation: No direct smithy-go imports exist in the codebase. The Bedrock provider imports AWS SDK v2 packages (aws, protocol/eventstream, signer/v4, config), which transitively depend on smithy-go. The indirect marking at line 32 of core/go.mod is correct and requires no changes.

tests/core-providers/sgl_test.go (1)

36-36: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.

tests/core-providers/ollama_test.go (2)

35-35: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.


26-26: Based on the verification results, Ollama now supports streaming responses with tool calling, and Llama 3.1 is listed as a compatible model. However, there are important nuances:

The model change is supported, but with known limitations:

Ollama supports streaming responses with tool calling, which enables chat applications to stream content and call tools in real time. The test scenarios you're concerned about (ToolCalls, ToolCallsStreaming, and End2EndToolCalling) should be compatible with llama3.1:latest.

That said, multiple recent GitHub issues indicate that streaming tool call support has had reliability challenges in practice. Workarounds for tool calling and streaming are proving highly unreliable and often barely functional, with streaming of the initial tool-calling intent partially working but getting reliably streamed final answers after tool execution fraught with issues.

tests/core-providers/openrouter_test.go (1)

37-39: LGTM! Appropriate configuration for Beta API.

Explicitly setting ToolCallsStreaming: false with the Beta comment is sensible, while still enabling End2EndToolCalling for non-streaming tool call tests.

tests/core-providers/anthropic_test.go (1)

38-38: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.

tests/core-providers/vertex_test.go (1)

36-36: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.

tests/core-providers/azure_test.go (1)

42-42: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.

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

705-705: LGTM! Comment cleanup.

Trailing whitespace removed.


718-718: I need to examine the context around line 718 to verify this is indeed handling tool_use content blocks.

Now let me check the actual code context around line 718:

Based on my comprehensive verification using official Anthropic API documentation, the struct definition in the codebase, and confirmed field mappings from the Spring AI framework:

The change from chunk.ContentBlock.ToolUseID to chunk.ContentBlock.ID is correct.

The struct definition clearly separates these fields by use case:

  • ID field is for tool_use content blocks
  • ToolUseID field is for tool_result content blocks

The struct in core/providers/anthropic/types.go correctly reflects this distinction with its field comments and JSON tags. The code change aligns with the Anthropic API specification.

tests/core-providers/parasail_test.go (1)

35-35: LGTM! Streaming test coverage enabled.

The addition of ToolCallsStreaming: true aligns with the PR's goal of adding comprehensive streaming tool call tests across providers.

tests/core-providers/bedrock_test.go (1)

40-40: LGTM! Test configuration properly enables tool call streaming.

The addition of ToolCallsStreaming: true aligns with the PR's objective to test streaming tool calls across all providers.

tests/core-providers/openai_test.go (1)

46-46: LGTM! Test configuration properly enables tool call streaming.

The addition of ToolCallsStreaming: true aligns with the PR's objective to test streaming tool calls for OpenAI.

core/providers/parasail.go (1)

145-151: LGTM! Parasail correctly adopts the unified fallback mechanism.

Setting BifrostContextKeyIsResponsesToChatCompletionFallback and passing through the raw postHookRunner aligns with the global fallback pattern introduced across providers.

tests/core-providers/gemini_test.go (1)

41-41: LGTM! Test configuration properly enables tool call streaming.

The addition of ToolCallsStreaming: true aligns with the PR's objective to test streaming tool calls for Gemini.

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

469-478: LGTM! Empty body error handling correctly signals stream end.

Setting BifrostContextKeyStreamEndIndicator before calling ProcessAndSendBifrostError ensures downstream consumers know the stream is terminating.


549-558: LGTM! Mid-stream error handling properly enriches and terminates.

Enriching errors with ExtraFields and setting the end indicator before emission ensures consistent error reporting across the streaming flow.


746-748: LGTM! Stateful streaming properly manages lifecycle.

Acquiring AnthropicResponsesStreamState with deferred release ensures clean resource management for the new per-stream state tracking.


822-832: LGTM! Final chunk handling correctly terminates the stream.

The logic properly identifies the last response in the last chunk (isLastChunk && i == len(responses)-1), sets the end indicator, and returns early to prevent duplicate emissions.

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

705-715: LGTM! Fallback detection and state lifecycle properly managed.

The type-safe context value extraction and deferred state release ensure clean resource management for the Chat-to-Responses fallback path.


877-902: LGTM! Fallback error handling correctly constructs and terminates.

Error construction from response fields and early termination with end indicator ensure proper error propagation in the Chat-to-Responses fallback path.


913-918: LGTM! Fallback completion correctly terminates with metrics.

Setting total latency and the end indicator before the final send ensures proper stream completion in the Chat-to-Responses fallback path.


1005-1012: LGTM! Non-fallback final emission properly guarded.

The conditional check !isResponsesToChatCompletionsFallback prevents duplicate final responses, and the end indicator ensures proper stream termination.


552-553: LGTM! End indicators consistently applied across streaming endpoints.

All streaming paths (text completion, responses, speech, transcription) correctly set BifrostContextKeyStreamEndIndicator before final emission, ensuring uniform stream termination signaling.

Also applies to: 1343-1344, 1711-1712, 1983-1984

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

453-462: LGTM! Chat completion streaming correctly handles errors and completion.

Error enrichment with ExtraFields and end indicator management ensure consistent error reporting and proper stream termination.

Also applies to: 482-484


633-636: LGTM! Stream state properly initialized with model context.

Acquiring CohereResponsesStreamState with deferred release and assigning the model ensures stateful tracking across the streaming session.


677-716: LGTM! Multi-response handling correctly processes and terminates.

The iteration over responses with final chunk detection (isLastChunk && i == len(responses)-1) and early return ensures proper per-chunk processing and clean stream termination.


718-719: LGTM! Event data properly reset between events.

Resetting eventData after processing prevents stale data accumulation across SSE events.

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

12-40: LGTM! Stream state struct and pool properly designed.

CohereResponsesStreamState correctly tracks content-to-output mapping, tool arguments, and lifecycle flags. The pool initialization ensures clean state objects.


42-106: LGTM! State lifecycle methods properly manage resources.

The acquire/release/flush pattern ensures clean state reuse. Defensive nil checks and clear() usage (Go 1.21+) prevent state leakage across streams.


108-126: LGTM! Output index mapping correctly maintains stable indices.

The getOrCreateOutputIndex method ensures deterministic mapping from Cohere's content indices to output indices, supporting stable item IDs across deltas.


559-601: LGTM! Lifecycle events correctly emitted with message ID.

The message-start handler properly emits response.created and response.in_progress events. Gating on chunk.ID != nil is appropriate per maintainer confirmation that Cohere always provides IDs.


635-645: LGTM! ID generation safely handles missing message IDs.

The nil checks precede dereferences in both text and reasoning item ID generation, preventing panics when message IDs are absent. Past review concerns have been addressed.

Also applies to: 670-673


740-801: LGTM! Tool plan streaming correctly creates and accumulates deltas.

The logic properly creates a message item on first tool plan delta and safely generates IDs with nil guards. Subsequent deltas correctly accumulate via output_text.delta events.


803-948: LGTM! Tool call lifecycle correctly implements OpenAI-style events.

The sequence—closing prior items, creating function_call with arguments buffer, accumulating deltas, emitting arguments.done and item.done—properly mirrors the OpenAI Responses API streaming pattern.


1018-1055: LGTM! Stream completion correctly emits final event with usage.

The message-end handler properly constructs response.completed with usage metrics and returns isLastChunk = true, signaling the end of the stream.

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

669-705: Lifecycle emission looks correct and contiguous

created then in_progress use sequenceNumber + len(responses); no local increments that would skip numbers. Good.


750-773: Closing text item before tool-call start is correct

Emits output_item.done for text, then opens function_call with contiguous sequencing. Good.

core/schemas/mux.go (3)

1001-1043: State reset is solid

Maps cleared and scalar fields reset; CreatedAt set on acquire for per-stream consistency. Looks good.


1110-1139: Lifecycle events sequencing is correct

response.created then response.in_progress with monotonic state.SequenceNumber. Good.


1361-1422: Good fix: copy args before taking address

Using argsCopy avoids the address-of-range-variable pitfall and preserves prior events. 👍

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

529-560: Stable item ID generation avoids nil deref — good

Fallback to "item_" when MessageID is nil prevents panics on re-ordered streams. 👍


869-912: Verify mapping: output_item.added → message_start for non-computer items

For ResponsesStreamResponseTypeOutputItemAdded, the non-computer branch emits Anthropic message_start. Typically item-added maps closer to content_block_start (e.g., text/tool-use). Please verify against Anthropic SSE expectations; adjust to content_block_start if needed.

@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 11-11-fix_responses_streaming_accumulation_fixes branch from dcd8d8e to cce49c1 Compare November 14, 2025 09:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

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

64-72: Fix invalid for range over config.ConcurrencyAndBufferSize.Concurrency

The pre-warm loop in NewBedrockProvider is invalid Go. Concurrency is an int type (core/schemas/provider.go:56), and for range only works with arrays, slices, strings, maps, channels, or pointers to arrays—not integers. The code would fail to compile.

Additionally, the nested loop causes Concurrency² items to be pooled instead of Concurrency. Both Cohere and Anthropic providers correctly use a single loop.

Fix by removing the outer for range loop:

-	// Pre-warm response pools
-	for range config.ConcurrencyAndBufferSize.Concurrency {
-		for i := 0; i < config.ConcurrencyAndBufferSize.Concurrency; i++ {
-			bedrockChatResponsePool.Put(&BedrockConverseResponse{})
-		}
-	}
+	// Pre-warm response pools
+	for i := 0; i < config.ConcurrencyAndBufferSize.Concurrency; i++ {
+		bedrockChatResponsePool.Put(&BedrockConverseResponse{})
+	}
♻️ Duplicate comments (2)
core/providers/cohere/responses.go (1)

670-673: 🔴 Critical: Nil pointer dereference in reasoning item ID generation.

Line 670 dereferences state.MessageID before checking if it's nil on line 671. This will panic when MessageID is nil.

Apply this diff to fix:

-			// Generate stable ID for reasoning item
-			itemID := fmt.Sprintf("msg_%s_reasoning_%d", *state.MessageID, outputIndex)
-			if state.MessageID == nil {
-				itemID = fmt.Sprintf("reasoning_%d", outputIndex)
-			}
+			// Generate stable ID for reasoning item
+			var itemID string
+			if state.MessageID != nil {
+				itemID = fmt.Sprintf("msg_%s_reasoning_%d", *state.MessageID, outputIndex)
+			} else {
+				itemID = fmt.Sprintf("reasoning_%d", outputIndex)
+			}

Note: Past review comments indicate this bug was supposedly addressed, but it persists in the current code. Based on learnings.

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

661-872: Fix pointer-to-range-variable bug in FinalizeBedrockStream’s Arguments field

In FinalizeBedrockStream, you have:

for outputIndex, args := range state.ToolArgumentBuffers {
    if args != "" {
        ...
        response := &schemas.BifrostResponsesStreamResponse{
            Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
            SequenceNumber: sequenceNumber + len(responses),
            OutputIndex:    schemas.Ptr(outputIndex),
            Arguments:      &args,
        }
        ...
        responses = append(responses, response)
        ...
    }
}

Because args is the loop variable in a for … range over a map, Go reuses that variable for each iteration. Storing &args means all FunctionCallArgumentsDone responses end up pointing at the same memory and will see the last args string, corrupting earlier tool-call completions. This is the same issue previously flagged in older commits.

You need a per-iteration copy before taking the address:

-	for outputIndex, args := range state.ToolArgumentBuffers {
+	for outputIndex, args := range state.ToolArgumentBuffers {
 		if args != "" {
 			itemID := state.ItemIDs[outputIndex]
 			callID := state.ToolCallIDs[outputIndex]
 			toolName := state.ToolCallNames[outputIndex]
 			...
-			// Emit function_call_arguments.done with full arguments
-			response := &schemas.BifrostResponsesStreamResponse{
+			// Emit function_call_arguments.done with full arguments
+			argsCopy := args
+			response := &schemas.BifrostResponsesStreamResponse{
 				Type:           schemas.ResponsesStreamResponseTypeFunctionCallArgumentsDone,
 				SequenceNumber: sequenceNumber + len(responses),
 				OutputIndex:    schemas.Ptr(outputIndex),
-				Arguments:      &args,
+				Arguments:      &argsCopy,
 			}

This ensures each function_call_arguments.done carries the correct arguments string corresponding to its own tool call.

Also applies to: 874-965

🧹 Nitpick comments (6)
core/providers/cohere/responses.go (1)

636-644: Refactor: Remove redundant nil check.

Lines 637-641 correctly handle the nil check, but lines 642-644 redundantly check the same condition and overwrite the result. This is inefficient but not a bug.

Apply this diff to remove the redundancy:

 			// Generate stable ID for text item
 			var itemID string
 			if state.MessageID == nil {
 				itemID = fmt.Sprintf("item_%d", outputIndex)
 			} else {
 				itemID = fmt.Sprintf("msg_%s_item_%d", *state.MessageID, outputIndex)
 			}
-			if state.MessageID == nil {
-				itemID = fmt.Sprintf("item_%d", outputIndex)
-			}
 			state.ItemIDs[outputIndex] = itemID
core/providers/anthropic/anthropic.go (1)

469-478: ChatCompletionStream stream-end signaling and error enrichment look correct; consider aligning scanner error path

The new handling for:

  • empty body (resp.BodyStream() == nil),
  • mid-stream bifrostErr from ToBifrostChatCompletionStream, and
  • the final synthetic chunk

correctly sets BifrostContextKeyStreamEndIndicator in ctx, enriches BifrostErrorExtraFields, and routes through ProcessAndSendBifrostError / ProcessAndSendResponse. This matches the intended OpenAI-style lifecycle and centralizes stream termination behavior.

One minor consistency gap: in the scanner.Err() branch (Lines 587–590) you still call ProcessAndSendError without setting the stream-end indicator or attaching BifrostErrorExtraFields. If downstream components rely on the context key to detect terminal events, you may want to set the end-indicator and wrap this error similarly to other mid-stream failures.

Also applies to: 548-558, 587-595

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

414-417: Avoid double-incrementing chunkIndex in ChatCompletionStream

Inside ChatCompletionStream you currently:

  • Increment chunkIndex once per SSE line before ToBifrostChatCompletionStream() (Line 445), and
  • Increment chunkIndex again after emitting a response (Line 474).

Because response.ExtraFields.ChunkIndex is set using the value between these two increments, the effective sequence of chunk_index values becomes 1, 3, 5, … instead of contiguous 0, 1, 2, … and differs from other providers’ behavior.

Unless you deliberately want to encode both “frame index” and “emitted chunk index” into the same counter, it would be cleaner and more consistent to increment chunkIndex in just one place—typically only when you actually emit a chunk (i.e., remove the pre-ToBifrostChatCompletionStream increment).

Also applies to: 445-447, 452-462, 464-487

core/schemas/mux.go (1)

1082-1436: Chat→Responses streaming conversion is comprehensive; be aware of single-tool-call-per-chunk behavior

ToBifrostResponsesStreamResponse now:

  • Emits response.created and response.in_progress once per stream when it sees the first chunk with a delta.Role.
  • Lazily creates and tracks a text output item (output_index:0) and streams text via response.output_text.delta, closing it with output_item.done when tool calls start or at FinishReason.
  • Handles tool calls by:
    • Resolving a stable tool call ID (using ID when present, otherwise ToolCallIndexToID).
    • Assigning each tool call an output_index (starting from 1), emitting output_item.added for the function call, and accumulating arguments in ToolArgumentBuffers.
    • Streaming response.function_call_arguments.delta as arguments arrive.
  • On completion, it:
    • Closes any open text item with output_item.done.
    • Iterates ToolArgumentBuffers and emits response.function_call_arguments.done (using argsCopy to avoid pointer-to-range issues) followed by output_item.done for each tool call.
    • Emits a final response.completed with usage converted via ToResponsesResponseUsage.
  • Finally, it stamps all emitted responses with ExtraFields.RequestType = ResponsesStreamRequest and copies search results / videos / citations.

Two minor behavioral notes:

  • Only the first delta.ToolCalls entry in a chunk is processed; if a provider ever emits multiple tool calls in a single delta, those additional calls will be ignored. If multi-tool-call-per-chunk becomes relevant, you may want to iterate the slice instead of taking just index 0.
  • For tool-call–only streams (no text deltas), the first tool call uses output_index 1, with index 0 effectively unused. This is structurally valid but slightly different from “first output item is always index 0”; if clients assume contiguous 0-based indices regardless of content type, you may want to conditionally reserve 0 only when a text item is actually created.

Functionally, though, the conversion is consistent and should handle the intended streaming tool-call scenarios correctly.

tests/core-providers/scenarios/tool_calls_streaming.go (1)

160-172: Simplify the complete vs incremental arguments detection.

The heuristic at lines 162-171 uses JSON shape (starts with {, ends with }) and existing.Arguments != "" to decide whether to replace or append. This is fragile because:

  1. If the first chunk happens to be complete JSON, it appends (line 170)
  2. If a subsequent chunk repeats the complete JSON, it replaces (line 167)
  3. The logic conflates "looks complete" with "is from a done event"

Consider tracking the source event type (delta vs done) in the caller and passing a flag like isComplete bool to make the logic explicit and less error-prone.

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

98-112: Use clear() consistently in flush() method.

The acquireAnthropicResponsesStreamState function uses clear(map) (lines 59, 64, 69, 74), but flush() recreates maps with make() (lines 102-105). For consistency and efficiency, use clear() in both places.

Apply this diff:

 func (state *AnthropicResponsesStreamState) flush() {
 	state.ChunkIndex = nil
 	state.AccumulatedJSON = ""
 	state.ComputerToolID = nil
-	state.ContentIndexToOutputIndex = make(map[int]int)
-	state.ToolArgumentBuffers = make(map[int]string)
-	state.MCPCallOutputIndices = make(map[int]bool)
-	state.ItemIDs = make(map[int]string)
+	clear(state.ContentIndexToOutputIndex)
+	clear(state.ToolArgumentBuffers)
+	clear(state.MCPCallOutputIndices)
+	clear(state.ItemIDs)
 	state.CurrentOutputIndex = 0
 	state.MessageID = nil
 	state.Model = nil
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcd8d8e and cce49c1.

📒 Files selected for processing (46)
  • core/changelog.md (1 hunks)
  • core/go.mod (1 hunks)
  • core/providers/anthropic/anthropic.go (8 hunks)
  • core/providers/anthropic/chat.go (2 hunks)
  • core/providers/anthropic/responses.go (7 hunks)
  • core/providers/bedrock/bedrock.go (5 hunks)
  • core/providers/bedrock/responses.go (3 hunks)
  • core/providers/bedrock/signer.go (0 hunks)
  • core/providers/bedrock/signer_test.go (0 hunks)
  • core/providers/cerebras.go (1 hunks)
  • core/providers/cohere/cohere.go (4 hunks)
  • core/providers/cohere/responses.go (4 hunks)
  • core/providers/gemini/gemini.go (3 hunks)
  • core/providers/groq.go (1 hunks)
  • core/providers/mistral/mistral.go (1 hunks)
  • core/providers/ollama.go (1 hunks)
  • core/providers/openai/openai.go (6 hunks)
  • core/providers/openrouter.go (2 hunks)
  • core/providers/parasail.go (1 hunks)
  • core/providers/perplexity/perplexity.go (1 hunks)
  • core/providers/perplexity/responses.go (0 hunks)
  • core/providers/sgl.go (1 hunks)
  • core/providers/utils/utils.go (0 hunks)
  • core/providers/vertex/vertex.go (1 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mux.go (3 hunks)
  • tests/core-providers/anthropic_test.go (1 hunks)
  • tests/core-providers/azure_test.go (1 hunks)
  • tests/core-providers/bedrock_test.go (1 hunks)
  • tests/core-providers/cerebras_test.go (1 hunks)
  • tests/core-providers/cohere_test.go (1 hunks)
  • tests/core-providers/config/account.go (1 hunks)
  • tests/core-providers/gemini_test.go (1 hunks)
  • tests/core-providers/groq_test.go (1 hunks)
  • tests/core-providers/mistral_test.go (1 hunks)
  • tests/core-providers/ollama_test.go (2 hunks)
  • tests/core-providers/openai_test.go (1 hunks)
  • tests/core-providers/openrouter_test.go (1 hunks)
  • tests/core-providers/parasail_test.go (1 hunks)
  • tests/core-providers/scenarios/tool_calls_streaming.go (1 hunks)
  • tests/core-providers/sgl_test.go (1 hunks)
  • tests/core-providers/tests.go (2 hunks)
  • tests/core-providers/vertex_test.go (1 hunks)
  • transports/changelog.md (1 hunks)
  • ui/app/workspace/logs/views/filters.tsx (1 hunks)
  • ui/app/workspace/logs/views/logEntryDetailsView.tsx (1 hunks)
💤 Files with no reviewable changes (4)
  • core/providers/perplexity/responses.go
  • core/providers/utils/utils.go
  • core/providers/bedrock/signer.go
  • core/providers/bedrock/signer_test.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • tests/core-providers/sgl_test.go
  • tests/core-providers/groq_test.go
  • core/providers/mistral/mistral.go
  • core/providers/gemini/gemini.go
  • core/providers/sgl.go
  • core/providers/vertex/vertex.go
  • tests/core-providers/cerebras_test.go
  • tests/core-providers/vertex_test.go
  • core/providers/anthropic/chat.go
  • core/providers/parasail.go
  • core/schemas/bifrost.go
  • core/providers/ollama.go
  • core/providers/groq.go
  • ui/app/workspace/logs/views/logEntryDetailsView.tsx
  • tests/core-providers/mistral_test.go
  • tests/core-providers/ollama_test.go
  • core/providers/perplexity/perplexity.go
  • core/go.mod
  • core/changelog.md
  • tests/core-providers/openai_test.go
  • tests/core-providers/parasail_test.go
  • tests/core-providers/bedrock_test.go
🧰 Additional context used
🧬 Code graph analysis (13)
core/providers/cerebras.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
core/providers/openai/openai.go (4)
core/schemas/bifrost.go (8)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostContextKeyIsResponsesToChatCompletionFallback (117-117)
  • BifrostError (351-360)
  • ErrorField (369-376)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
  • ChatCompletionStreamRequest (88-88)
core/providers/utils/utils.go (6)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ProcessAndSendBifrostError (569-599)
  • ProviderSendsDoneMarker (749-758)
  • ProcessAndSendError (605-651)
  • CreateBifrostChatCompletionChunkResponse (684-713)
core/schemas/mux.go (3)
  • ChatToResponsesStreamState (962-978)
  • AcquireChatToResponsesStreamState (1002-1043)
  • ReleaseChatToResponsesStreamState (1046-1077)
core/schemas/responses.go (2)
  • ResponsesStreamResponseTypeError (1409-1409)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/anthropic.go (2)
core/schemas/bifrost.go (5)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • ResponsesStreamRequest (90-90)
core/providers/utils/utils.go (3)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
core/providers/bedrock/responses.go (2)
core/providers/bedrock/types.go (1)
  • BedrockStreamEvent (363-380)
core/schemas/responses.go (18)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/cohere/cohere.go (3)
core/schemas/bifrost.go (6)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • ChatCompletionStreamRequest (88-88)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • ResponsesStreamRequest (90-90)
  • BifrostResponseExtraFields (282-291)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/schemas/responses.go (1)
  • BifrostResponsesResponse (45-82)
core/providers/bedrock/bedrock.go (3)
core/schemas/bifrost.go (4)
  • BifrostErrorExtraFields (418-422)
  • RequestType (81-81)
  • BifrostContextKeyStreamEndIndicator (111-111)
  • BifrostResponseExtraFields (282-291)
core/providers/utils/utils.go (4)
  • ProcessAndSendBifrostError (569-599)
  • ProcessAndSendResponse (533-563)
  • GetBifrostResponseForStreamResponse (780-808)
  • ShouldSendBackRawResponse (480-485)
core/providers/bedrock/responses.go (1)
  • FinalizeBedrockStream (875-966)
tests/core-providers/tests.go (1)
tests/core-providers/scenarios/tool_calls_streaming.go (1)
  • RunToolCallsStreamingTest (213-737)
core/schemas/mux.go (4)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (25-40)
core/schemas/responses.go (20)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeReasoningSummaryTextDelta (1378-1378)
  • ResponsesStreamResponseTypeRefusalDelta (1363-1363)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesResponseUsage (250-257)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/bifrost.go (2)
  • RequestType (81-81)
  • ResponsesStreamRequest (90-90)
core/providers/cohere/responses.go (2)
core/providers/cohere/types.go (1)
  • CohereStreamEvent (381-386)
core/schemas/responses.go (16)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessage (304-316)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeOutputTextAnnotationAdded (1401-1401)
  • ResponsesStreamResponseTypeOutputTextAnnotationDone (1402-1402)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/anthropic/responses.go (2)
core/providers/anthropic/types.go (12)
  • AnthropicStreamEvent (312-321)
  • AnthropicStreamEventTypeMessageStart (301-301)
  • AnthropicStreamEventTypeContentBlockStart (303-303)
  • AnthropicContentBlockTypeToolUse (129-129)
  • AnthropicToolNameComputer (184-184)
  • AnthropicContentBlockTypeText (127-127)
  • AnthropicContentBlockTypeMCPToolUse (133-133)
  • AnthropicStreamEventTypeContentBlockDelta (304-304)
  • AnthropicStreamDeltaTypeText (326-326)
  • AnthropicStreamDeltaTypeInputJSON (327-327)
  • AnthropicStreamEventTypeMessageDelta (306-306)
  • AnthropicStreamEventTypeMessageStop (302-302)
core/schemas/responses.go (21)
  • BifrostResponsesStreamResponse (1412-1450)
  • BifrostResponsesResponse (45-82)
  • ResponsesStreamResponseTypeCreated (1348-1348)
  • ResponsesStreamResponseTypeInProgress (1349-1349)
  • ResponsesMessageTypeMessage (280-280)
  • ResponsesInputMessageRoleAssistant (321-321)
  • ResponsesMessage (304-316)
  • ResponsesMessageContent (328-333)
  • ResponsesMessageContentBlock (388-399)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesToolMessage (450-470)
  • ResponsesStreamResponseTypeOutputTextDelta (1360-1360)
  • ResponsesStreamResponseType (1345-1345)
  • ResponsesStreamResponseTypeMCPCallArgumentsDelta (1386-1386)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesMessageTypeComputerCall (282-282)
  • ResponsesStreamResponseTypeOutputItemDone (1355-1355)
  • ResponsesStreamResponseTypeMCPCallArgumentsDone (1387-1387)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
  • ResponsesStreamResponseTypeCompleted (1350-1350)
core/providers/openrouter.go (1)
core/providers/utils/utils.go (1)
  • GetPathFromContext (209-214)
ui/app/workspace/logs/views/filters.tsx (1)
ui/lib/constants/logs.ts (1)
  • RequestTypeLabels (77-102)
tests/core-providers/scenarios/tool_calls_streaming.go (3)
core/schemas/chatcompletions.go (7)
  • ChatAssistantMessageToolCall (483-488)
  • ChatAssistantMessageToolCallFunction (491-494)
  • BifrostChatRequest (11-18)
  • ChatParameters (154-183)
  • ChatTool (201-205)
  • BifrostChatResponse (25-40)
  • ChatStreamResponseChoice (529-531)
tests/core-providers/scenarios/utils.go (5)
  • ToolCallInfo (293-297)
  • CreateBasicChatMessage (218-225)
  • GetSampleChatTool (129-148)
  • CreateBasicResponsesMessage (227-235)
  • GetSampleResponsesTool (150-169)
core/schemas/responses.go (7)
  • BifrostResponsesRequest (32-39)
  • ResponsesParameters (84-111)
  • BifrostResponsesStreamResponse (1412-1450)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDelta (1366-1366)
  • ResponsesStreamResponseTypeOutputItemAdded (1354-1354)
  • ResponsesMessageTypeFunctionCall (285-285)
  • ResponsesStreamResponseTypeFunctionCallArgumentsDone (1367-1367)
🔇 Additional comments (30)
transports/changelog.md (1)

2-3: LGTM! Changelog entries accurately reflect the PR scope.

Both entries clearly capture the major changes: unified streaming lifecycle events alignment with OpenAI-style formatting across all providers, and the OpenRouter API path upgrade. The entries follow the established changelog format and style.

tests/core-providers/openrouter_test.go (2)

37-37: Appropriate to disable streaming for Beta API.

Disabling ToolCallsStreaming is a sensible decision given that OpenRouter's responses API is in Beta and may not fully support the enhanced OpenAI-style streaming lifecycle events introduced in this PR.


39-39: Reconcile Beta API dependency with feature enablement.

End2EndToolCalling is enabled while ToolCallsStreaming, ImageURL, and ImageBase64 remain disabled—all due to OpenRouter's Responses API being in Beta. The test uses the Responses API, making the inconsistency unclear. Either add a comment explaining why End2EndToolCalling is exempt from Beta concerns, or align the configuration by disabling it to match the conservative approach applied to other Beta-dependent features.

core/providers/openrouter.go (1)

214-214: LGTM! OpenRouter v1/responses endpoint is verified and properly implemented.

The v1/responses endpoint is in beta stage, and this migration from /alpha/responses to /v1/responses is appropriately made across both Responses and ResponsesStream methods. The API is stateless with each request independent, making this endpoint update straightforward. The use of GetPathFromContext provides good flexibility for context-based overrides.

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

164-200: Nice improvement to loading state handling!

The changes successfully address the previous review concern about incomplete loading detection. The implementation now:

  1. Covers all loading categories (lines 170-174): Providers, Models, Selected Keys, and Virtual Keys all show loading indicators consistently.
  2. Filters empty categories (line 165): Prevents rendering filter groups with no options, improving the UI.
  3. Provides consistent visual feedback (lines 181-191): Loading spinner and checkmark states are clearly distinguished.

The loading items are properly disabled (line 179) and non-interactive during data fetch, which prevents any edge cases with user interaction.

core/providers/cerebras.go (1)

172-180: Approve fallback pattern implementation; context flag flow and postHookRunner delegation verified.

The changes correctly implement the responses-to-chat-completion fallback. Verification confirms:

  • Context flag: BifrostContextKeyIsResponsesToChatCompletionFallback is properly set (line 173) and consumed in the OpenAI handler (core/providers/openai/openai.go lines 708–712) to acquire ChatToResponsesStreamState for conversion.
  • PostHookRunner: Correctly passed directly to ChatCompletionStream (line 176), which accepts this parameter (core/providers/cerebras.go line 135).
  • Conversion logic: Handled via ChatToResponsesStreamState.ToBifrostResponsesStreamResponse() in the OpenAI handler.

Comprehensive tests exist (tests/core-providers/cerebras_test.go) with streaming scenarios enabled. Verify that the ResponsesStream method is covered by the streaming test suite.

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

705-716: LGTM! Fallback pathway for Responses-to-Chat-Completion is well-structured.

The introduction of the fallback detection mechanism and state management for converting Chat Completion streams to Responses format is clean and follows the established patterns in the codebase. The state is properly acquired and released via defer.


552-553: LGTM! Stream end indicators consistently applied.

The unified approach of setting BifrostContextKeyStreamEndIndicator in context before final sends across all streaming endpoints (text completion, chat completion, responses, speech, transcription) provides consistent stream lifecycle signaling.

Also applies to: 1011-1012, 1343-1344, 1711-1712, 1983-1984

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

12-127: LGTM! Well-structured streaming state management.

The introduction of CohereResponsesStreamState with sync.Pool-based lifecycle management, deterministic output index mapping via getOrCreateOutputIndex, and comprehensive state tracking for content blocks, tool calls, and citations provides robust streaming coordination. The acquire/release pattern with flush ensures proper cleanup.

tests/core-providers/azure_test.go (1)

42-42: LGTM! Test configuration updated for streaming tool calls.

The addition of ToolCallsStreaming: true enables streaming tool call tests for Azure, aligning with the broader PR objective of enhancing Responses API streaming across providers.

tests/core-providers/config/account.go (1)

29-29: LGTM! New test scenario field added.

The ToolCallsStreaming field is properly documented and follows the established pattern for test scenario flags in the TestScenarios struct.

tests/core-providers/anthropic_test.go (1)

38-38: LGTM! Anthropic streaming tool calls enabled.

Consistent with the PR's goal of enabling streaming tool call tests across providers.

tests/core-providers/cohere_test.go (1)

36-36: LGTM! Cohere streaming tool calls enabled.

Enables streaming tool call tests for Cohere provider.

tests/core-providers/gemini_test.go (1)

41-41: LGTM! Gemini streaming tool calls enabled.

Enables streaming tool call tests for Gemini provider.

tests/core-providers/tests.go (1)

35-35: LGTM! Streaming tool calls test scenario integrated.

The RunToolCallsStreamingTest scenario is properly integrated into both the test execution flow (line 35) and the summary reporting (line 77), providing comprehensive coverage for streaming tool call functionality across providers.

Also applies to: 77-77

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

726-735: Anthropic ResponsesStream stateful accumulator and final-chunk handling look solid

The switch to a pooled AnthropicResponsesStreamState, plus event.ToBifrostResponsesStream(chunkIndex, streamState) returning multiple responses, is wired correctly:

  • State is acquired once per stream and released via defer, avoiding per-chunk allocation.
  • You no longer emit response.created/response.in_progress manually; ToBifrostResponsesStream handles lifecycle events, and you simply propagate the resulting responses.
  • Per-response ExtraFields (RequestType, Provider, ModelRequested, ChunkIndex, per-chunk Latency) and optional RawResponse are set consistently.
  • On isLastChunk for the last response, you attach usage, override Latency with total duration, set the stream-end indicator in ctx, send via ProcessAndSendResponse, and return to terminate the goroutine cleanly.
  • Error cases attach BifrostErrorExtraFields and set the end-indicator before ProcessAndSendBifrostError.

This matches the new unified streaming model and should behave correctly across tool-call scenarios.

Also applies to: 746-749, 783-785, 794-804, 806-832

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

633-637: Cohere ResponsesStream stateful conversion and stream-end handling look consistent

The new CohereResponsesStreamState integration in ResponsesStream looks correct:

  • State is acquired once per stream, seeded with request.Model, and released via defer.
  • event.ToBifrostResponsesStream(chunkIndex, streamState) yields multiple responses; you iterate them, set ExtraFields (RequestType, Provider, ModelRequested, ChunkIndex, per-chunk Latency), and optionally attach RawResponse.
  • On bifrostErr, you enrich ExtraFields, set the stream-end indicator in ctx, and route via ProcessAndSendBifrostError, then break out of the loop.
  • On the last response of the last chunk (isLastChunk && i == len(responses)-1) you ensure response.Response is non-nil, attach usage and total latency, set the end-indicator, send via ProcessAndSendResponse, and return from the goroutine.

This aligns well with the updated Anthropic/Bedrock flows and should produce well-formed lifecycle events for Responses streaming.

Also applies to: 677-687, 689-715, 718-719

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

724-733: Bedrock ChatCompletionStream: error metadata and stream-end signaling look correct

In ChatCompletionStream:

  • On bifrostErr from ToBifrostChatCompletionStream, you now set BifrostErrorExtraFields (RequestType, Provider, ModelRequested), mark the stream-end indicator in ctx, and dispatch via ProcessAndSendBifrostError.
  • For the synthetic final chunk created by CreateBifrostChatCompletionChunkResponse, you set ModelDeployment, total Latency, mark the stream-end indicator, and send through ProcessAndSendResponse.

This aligns Bedrock with the unified streaming lifecycle and ensures consumers see consistent metadata and a clear terminal event.

Also applies to: 758-763


871-899: Bedrock ResponsesStream: stateful Responses accumulator and EOF finalization path look good

The updated ResponsesStream uses BedrockResponsesStreamState and FinalizeBedrockStream correctly:

  • State is acquired once per stream, seeded with request.Model, and released via defer.
  • For each eventstream message, you convert via streamEvent.ToBifrostResponsesStream(chunkIndex, streamState) and iterate the resulting responses, setting ExtraFields (RequestType, Provider, ModelRequested, ModelDeployment, ChunkIndex, per-chunk Latency) and optional RawResponse, then sending them via ProcessAndSendResponse.
  • On conversion errors (bifrostErr), you enrich ExtraFields, set the stream-end indicator in ctx, and send via ProcessAndSendBifrostError, then return.
  • On decoder EOF, you set the stream-end indicator before calling FinalizeBedrockStream(streamState, chunkIndex, usage), then loop over the returned final responses, populating metadata (including synthetic "{}" RawResponse), and sending them via ProcessAndSendResponse.

Sequence numbers inside FinalizeBedrockStream are based on the chunkIndex you pass in, and ChunkIndex in ExtraFields is incremented in lockstep, so ordering is consistent from a client perspective. The overall pattern matches Anthropic/Cohere’s new stateful Responses streaming.

Also applies to: 886-913, 939-977

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

12-29: BedrockResponsesStreamState pooling and reset logic look correct

The introduction of BedrockResponsesStreamState plus sync.Pool helpers is structured well:

  • All per-stream maps (ContentIndexToOutputIndex, ToolArgumentBuffers, ItemIDs, ToolCallIDs, ToolCallNames) are allocated once in the pool constructor and cleared in acquireBedrockResponsesStreamState / flush() rather than reallocated per chunk.
  • Non-map fields (CurrentOutputIndex, MessageID, Model, CreatedAt, lifecycle flags) are reset both on acquire and in flush() before returning to the pool, so reused state doesn’t leak data across streams.
  • CreatedAt is stamped once at acquire-time, giving a stable created_at across the stream; MessageID is generated lazily on first chunk.Role in ToBifrostResponsesStream.

This is a good balance between reuse and safety for the Responses streaming accumulator.

Also applies to: 46-85, 95-129

core/schemas/mux.go (1)

3-7: ChatToResponsesStreamState pooling and lifecycle management look robust

The ChatToResponsesStreamState design and pooling logic are sound:

  • All map fields (ToolArgumentBuffers, ItemIDs, ToolCallNames, ToolCallIndexToID, ToolCallOutputIndices) are allocated once in the pool constructor and cleared in AcquireChatToResponsesStreamState / ReleaseChatToResponsesStreamState, avoiding per-chunk allocations while preventing state bleed between streams.
  • Scalar fields (MessageID, Model, CreatedAt, flags, CurrentOutputIndex, SequenceNumber) are reset on acquire and release, so each stream starts from a clean base.
  • CreatedAt is stamped at acquire-time, giving a stable created_at across lifecycle events for a stream.

Overall, this gives you a reusable but safe accumulator for Chat→Responses streaming.

Also applies to: 961-999, 1001-1077

tests/core-providers/scenarios/tool_calls_streaming.go (4)

18-42: LGTM! Clean type definitions and constructor.

The accumulator types and constructor are well-structured with proper initialization of all maps.


45-93: LGTM! Key lookup logic correctly fixed.

The changes from previous reviews have been properly applied: using key = -1 as a not-found sentinel and treating Index directly without zero-check fallbacks. The accumulation logic is correct.


183-210: LGTM! Clean getter methods.

The getter methods correctly convert accumulated state to ToolCallInfo slices.


213-787: LGTM! Comprehensive test with good retry and validation logic.

The test orchestration is well-structured with:

  • Retry logic (3 attempts) for handling flaky LLM responses
  • Extensive per-chunk logging for debugging streaming behavior
  • Proper validation that all tool calls have ID, name, and arguments
  • Graceful handling of invalid JSON during streaming (logs warning but doesn't fail prematurely)

The verbose logging is appropriate for a test file debugging streaming behavior.

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

14-49: LGTM! Well-structured state type with proper pool initialization.

The AnthropicResponsesStreamState type and pool are correctly set up with all maps initialized and lifecycle flags properly defaulted.


115-132: LGTM! Clean output index mapping logic.

The getOrCreateOutputIndex method correctly maintains stable mapping between content indices and output indices.


446-494: LGTM! OpenAI-style lifecycle events properly implemented.

The MessageStart handling correctly emits response.created and response.in_progress events with proper deduplication via state flags. Sequence number incrementation accounts for previously emitted events in the same batch.


496-624: LGTM! ContentBlockStart handling is correct with nil-safe itemID generation.

The handling of different content block types is well-structured:

  • Computer tool, text, function call, and MCP tool use cases are all properly handled
  • Lines 537-541 correctly check state.MessageID == nil before dereferencing (addresses past review)
  • State tracking (ItemIDs, ToolArgumentBuffers, MCPCallOutputIndices) is properly initialized
  • OutputItemAdded events are emitted with appropriate fields

626-861: LGTM! Comprehensive and correct delta, stop, and completion handling.

The streaming conversion logic is well-implemented:

  • Text, InputJSON, and thinking deltas properly reference state for itemIDs and accumulation
  • ContentBlockStop correctly emits arguments.done followed by output_item.done
  • Sequence number incrementation (line 807) properly accounts for events emitted in the current batch
  • Computer tool accumulation and final emission is handled correctly
  • State cleanup (buffers, tracking maps) happens at appropriate points
  • MessageStop emits final response.completed event

Pratham-Mishra04 commented Nov 14, 2025

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Nov 14, 10:29 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Nov 14, 10:29 AM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 merged commit a330b2b into main Nov 14, 2025
5 of 6 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 11-11-fix_responses_streaming_accumulation_fixes branch November 14, 2025 10:29
akshaydeo pushed a commit that referenced this pull request Nov 17, 2025
…nts (#815)

## Summary

Enhances the Responses API streaming implementation to better align with OpenAI's streaming format, providing more consistent and reliable tool call streaming across providers.

## Changes

- Refactored Anthropic's Responses API streaming to use a stateful accumulator for tracking output items
- Added similar streaming accumulators to Bedrock and Cohere providers
- Implemented proper OpenAI-style lifecycle events (created, in_progress, completed) for all providers
- Improved tool call streaming with proper output indexing and argument accumulation
- Replaced the chat-to-responses conversion approach with native implementations for each provider
- Added comprehensive tests for tool call streaming functionality

## Type of change

- [ ] Bug fix
- [x] Feature
- [x] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (Next.js)
- [ ] Docs

## How to test

Test the Responses API streaming with tool calls across different providers:

```sh
# Run the tool calls streaming tests
go test ./tests/core-providers -run TestOpenAI/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestAnthropic/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestBedrock/ToolCallsStreamingResponses
go test ./tests/core-providers -run TestCohere/ToolCallsStreamingResponses
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Improves the reliability of tool call streaming in the Responses API.

## Security considerations

No security implications.

## Checklist

- [x] I added/updated tests where appropriate
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
@coderabbitai coderabbitai Bot mentioned this pull request Dec 4, 2025
18 tasks
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.

1 participant