fix(compat): map non-stream messages←responses stop_reason#4
Merged
Conversation
Non-streaming Anthropic /v1/messages requests forwarded to a Responses upstream returned stop_reason=end_turn even when the reply contained a tool_use block, so Anthropic clients treated the turn as finished and never executed the tool (broken tool loop). Derive stop_reason from the converted content blocks and the Responses status, mirroring the streaming path (compat_bridge._messages_stop_reason_from_response): - tool_use block present -> "tool_use" - status=incomplete + max_*tokens -> "max_tokens" - otherwise -> "end_turn" mapper cannot import compat_bridge (compat_bridge imports mapper), so a small local helper keeps the dependency one-directional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Non-streaming Anthropic
/v1/messagesrequests forwarded to a Responses-API upstream returnedstop_reason: "end_turn"even when the converted reply contained atool_useblock. Anthropic clients (Claude Code / SDK) key offstop_reason == "tool_use"to execute the tool and continue the turn — reportingend_turnsilently breaks the tool loop in this path.responses_response_to_messages_response(mapper.py) now derivesstop_reasonfrom the converted content blocks and the Responses object, mirroring the already-correct streaming path (compat_bridge._messages_stop_reason_from_response):tool_useblock present →tool_usestatus=incomplete+incomplete_details.reason ∈ {max_output_tokens, max_tokens}→max_tokensend_turnWhy a local helper (not reuse)
mappercannot importcompat_bridge—compat_bridgeimportsmapper, so the dependency must stay one-directional. A ~12-line local helper keeps that constraint; logic is identical to the streaming mapper.Scope
messages←responsesstop_reason. No interface/shape change; text-only replies still reportend_turn.stop_sequence(upstream doesn't return the matched sequence), streaminginput_tokens(unknown atmessage_startemit time),document/PDF forwarding (needs upstream support check), deadchat_response_to_messages_response.How to test
pytest aegisgate/tests/test_openai_request_mapping.pyNew regression tests (written test-first, watched fail then pass):
..._preserves_function_callsnow also assertsstop_reason == "tool_use"..._text_only_is_end_turn..._maps_max_tokensValidation performed
pytest test_openai_request_mapping.py→ 9 passedruff checkclean,mypy mapper.pycleanRisks / breaking changes
None expected. Behavior only changes for non-stream
messages←responsesreplies that are tool calls or hit max_output_tokens — those were already incorrect.🤖 Generated with Claude Code