Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe Anthropic provider's streaming implementation now tracks whether tool call deltas were emitted during the stream and conditionally maps stop reasons accordingly. Stop reason normalization was extended to handle "model_context_window_exceeded" as a "length" stop reason. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Updates the Anthropic provider’s OpenAI-compatibility by normalizing finish_reason values (and extending streaming support for tool calls), then aligns contract golden fixtures and tests accordingly.
Changes:
- Normalize Anthropic
stop_reason→ OpenAI-stylefinish_reason(end_turn/stop_sequence→stop,tool_use→tool_calls, etc.). - Extend SSE stream conversion to emit
tool_callsdeltas (including incrementalargumentsviapartial_json). - Update contract golden JSON outputs and add/adjust tests to assert the new finish reasons.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/contract/testdata/golden/anthropic/messages.golden.json | Golden updated: finish_reason normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_extended_thinking.golden.json | Golden updated: finish_reason normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_multi_turn.golden.json | Golden updated: finish_reason normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_multimodal.golden.json | Golden updated: finish_reason normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_stream.golden.json | Golden updated: streamed final finish_reason normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_with_params.golden.json | Golden updated: stop_sequence normalized to stop. |
| tests/contract/testdata/golden/anthropic/messages_with_tools.golden.json | Golden updated: tool_use normalized to tool_calls. |
| tests/contract/anthropic_test.go | Contract test updated to expect tool_calls finish reason for tools fixture. |
| internal/providers/anthropic/anthropic_test.go | Adds coverage for normalized finish reasons and streaming tool call chunk emission. |
| internal/providers/anthropic/anthropic.go | Implements stop-reason mapping + streaming tool-call delta handling (partial_json). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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)
internal/providers/anthropic/anthropic.go (2)
609-650:⚠️ Potential issue | 🟠 MajorSkip streamed
tool_useblocks that never provide a function name.Line 636 emits a
tool_callsdelta before validatingevent.ContentBlock.Name. That diverges fromextractToolCalls(), which drops nameless tool blocks, and it means the stream path can producefunction.name:""while still settingemittedToolCalls=trueand later upgrading the final finish_reason totool_calls.Proposed fix
-func (sc *streamConverter) getToolCallState(blockIndex int, block *anthropicContent) streamToolCallState { +func (sc *streamConverter) getToolCallState(blockIndex int, block *anthropicContent) (streamToolCallState, bool) { if state, ok := sc.toolCalls[blockIndex]; ok { - return state + return state, true + } + if block == nil || strings.TrimSpace(block.Name) == "" { + return streamToolCallState{}, false } state := streamToolCallState{ Ordinal: sc.nextToolCallIdx, } - if block != nil { - state.ID = block.ID - state.Name = block.Name - } + state.ID = block.ID + state.Name = block.Name sc.toolCalls[blockIndex] = state sc.nextToolCallIdx++ - return state + return state, true } @@ case "content_block_start": if event.ContentBlock != nil && event.ContentBlock.Type == "tool_use" { - state := sc.getToolCallState(event.Index, event.ContentBlock) + state, ok := sc.getToolCallState(event.Index, event.ContentBlock) + if !ok { + return "" + } sc.emittedToolCalls = true return sc.emitChatChunk(map[string]interface{}{🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/providers/anthropic/anthropic.go` around lines 609 - 650, The convertEvent path is emitting a tool_calls delta for content_block_start tool_use even when event.ContentBlock.Name is empty, which diverges from extractToolCalls(); modify convertEvent so that when handling "content_block_start" for a "tool_use" it first verifies event.ContentBlock.Name (or state.Name) is non-empty and if empty simply return "" (do not call getToolCallState, do not set sc.emittedToolCalls, and do not emit any delta); otherwise proceed as now (call getToolCallState, mark emittedToolCalls and emit the tool_calls chunk). Ensure the checks reference convertEvent, getToolCallState, sc.emittedToolCalls and event.ContentBlock.Name/Name to locate the change.
395-425:⚠️ Potential issue | 🟠 MajorMirror the malformed
tool_usefallback in the non-streaming path.Line 399 always maps
tool_usetotool_calls, even whenextractToolCalls(resp.Content)returned nil. That leaves clients withfinish_reason:"tool_calls"and nomessage.tool_calls, which is the same malformed-payload inconsistency the stream path now avoids viamapStreamStopReason().Proposed fix
content := extractTextContent(resp.Content) toolCalls := extractToolCalls(resp.Content) - finishReason := mapAnthropicStopReasonToOpenAI(resp.StopReason) + finishReason := resp.StopReason + if resp.StopReason != "tool_use" || len(toolCalls) > 0 { + finishReason = mapAnthropicStopReasonToOpenAI(resp.StopReason) + } usage := core.Usage{🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/providers/anthropic/anthropic.go` around lines 395 - 425, convertFromAnthropicResponse currently sets finishReason via mapAnthropicStopReasonToOpenAI(resp.StopReason) and always maps tool_use to tool_calls even when extractToolCalls(resp.Content) returns nil, causing finish_reason:"tool_calls" with no message.tool_calls; change convertFromAnthropicResponse to mirror the streaming fallback: after calling toolCalls := extractToolCalls(resp.Content) and finishReason := mapAnthropicStopReasonToOpenAI(resp.StopReason), if finishReason indicates "tool_calls" but toolCalls is nil/empty, set finishReason to the fallback string used in streaming (e.g., "tool_use") so clients don’t receive a finish_reason that promises missing message.tool_calls; use the same symbol names (convertFromAnthropicResponse, extractToolCalls, mapAnthropicStopReasonToOpenAI) to locate where to apply this check and mutation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/providers/anthropic/anthropic_test.go`:
- Around line 374-386: The test uses unchecked type assertions on chunks and
nested fields (chunks, firstChoices, firstChoice, firstDelta, firstToolCalls,
firstToolCall, firstFunction) which can panic; update the assertions to perform
safe type checks (using the comma-ok form) at each step and call t.Fatalf with
descriptive messages when a cast fails or an expected length is wrong, e.g.,
check chunks[0]["choices"] is []interface{} and len==1 before indexing, verify
firstChoices[0] is map[string]interface{} before accessing "delta", verify
firstDelta["tool_calls"] is []interface{} and non-empty, and similar for
subsequent nested maps so failures produce clear error messages instead of
panics.
---
Outside diff comments:
In `@internal/providers/anthropic/anthropic.go`:
- Around line 609-650: The convertEvent path is emitting a tool_calls delta for
content_block_start tool_use even when event.ContentBlock.Name is empty, which
diverges from extractToolCalls(); modify convertEvent so that when handling
"content_block_start" for a "tool_use" it first verifies event.ContentBlock.Name
(or state.Name) is non-empty and if empty simply return "" (do not call
getToolCallState, do not set sc.emittedToolCalls, and do not emit any delta);
otherwise proceed as now (call getToolCallState, mark emittedToolCalls and emit
the tool_calls chunk). Ensure the checks reference convertEvent,
getToolCallState, sc.emittedToolCalls and event.ContentBlock.Name/Name to locate
the change.
- Around line 395-425: convertFromAnthropicResponse currently sets finishReason
via mapAnthropicStopReasonToOpenAI(resp.StopReason) and always maps tool_use to
tool_calls even when extractToolCalls(resp.Content) returns nil, causing
finish_reason:"tool_calls" with no message.tool_calls; change
convertFromAnthropicResponse to mirror the streaming fallback: after calling
toolCalls := extractToolCalls(resp.Content) and finishReason :=
mapAnthropicStopReasonToOpenAI(resp.StopReason), if finishReason indicates
"tool_calls" but toolCalls is nil/empty, set finishReason to the fallback string
used in streaming (e.g., "tool_use") so clients don’t receive a finish_reason
that promises missing message.tool_calls; use the same symbol names
(convertFromAnthropicResponse, extractToolCalls, mapAnthropicStopReasonToOpenAI)
to locate where to apply this check and mutation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0f4c852b-55bb-489a-87a5-c28eb5b4e20a
📒 Files selected for processing (10)
internal/providers/anthropic/anthropic.gointernal/providers/anthropic/anthropic_test.gotests/contract/anthropic_test.gotests/contract/testdata/golden/anthropic/messages.golden.jsontests/contract/testdata/golden/anthropic/messages_extended_thinking.golden.jsontests/contract/testdata/golden/anthropic/messages_multi_turn.golden.jsontests/contract/testdata/golden/anthropic/messages_multimodal.golden.jsontests/contract/testdata/golden/anthropic/messages_stream.golden.jsontests/contract/testdata/golden/anthropic/messages_with_params.golden.jsontests/contract/testdata/golden/anthropic/messages_with_tools.golden.json
Do these changes help Anthropic support?
OpenAI-compatible clients would see a raw Anthropic stop reason instead of
the expected truncation-style finish reason.
says stop_reason: "tool_use" but no tool_calls chunk was ever emitted,
returning raw tool_use is more honest than fabricating OpenAI-style
tool_calls.
Are those arguments/fields used?
“Anthropic said tool_use” from “the client actually received tool call
chunks.”
value, documented for Claude 4.5+ and older models behind a beta header.
this branch. This branch only changes response/stream interpretation plus
tests.
Net assessment:
partial or malformed streams, not the normal happy path.
Residual risk:
backed by a real contract/integration capture, so its value is mainly
robustness rather than coverage of a documented normal stream shape.
Sources:
https://docs.claude.com/en/release-notes/overview
Summary by CodeRabbit
Bug Fixes
Tests