Conversation
_reconstruct_response_from_stream_events rebuilt streamed responses for conversation history with branches for text and tool_use only, so thinking and redacted_thinking blocks were dropped. A streamed call therefore recorded a complete-looking response holding no reasoning content, while the non-streaming path stored the same blocks verbatim -- history was inconsistent between the two transports. Accumulate thinking_delta text and the signature_delta value into a thinking block, and keep a redacted_thinking block's opaque data payload. Block ordering is untouched, so reasoning blocks still precede visible text. No consumer branches on block type, and the non-streaming path already emits these blocks, so no downstream reader sees a new shape. Omp-Session: 01a067a2-92a1-7000-85fe-5f7a3a650a38
sjawhar
marked this pull request as ready for review
September 3, 2026 23:22
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.
Capture
thinkingandredacted_thinkingblocks when rebuilding a streamed response for conversation history._reconstruct_response_from_stream_eventshad branches fortextandtool_useonly; the line this replaces read# thinking blocks are intentionally excluded from historywith no recorded rationale (its origin is not reachable — the line predates this clone's grafted root). A streamed call therefore recorded a complete-looking response holding no reasoning content, while the non-streaming path already stored the same blocks verbatim via an unfilteredmodel_dump(). History was inconsistent between the two transports depending only on whether the client streamed.Change
thinkingblocks accumulatethinking_deltatext and record thesignature_deltavalue. The signature arrives as a separate event from the text deltas, which is the failure mode a half-implementation would hit.redacted_thinkingblocks keep their opaquedatapayload — dropping it loses the only record that a reasoning block was present.Why this is not a new data shape
Nothing under
history/,debug/,ui/orstatic/referencesthinkingat all. The event's consumers (passthrough_materialize/payloads.py,debug/service.py) handle the streaming and non-streaming events symmetrically, and already receive these blocks today from non-streaming calls. This makes streaming produce what non-streaming already produces.Deliberately out of scope
The streaming path's
record_inbound_response/record_outbound_responsepass nobody=, sorequest_logs.response_bodystays NULL for streamed calls. Filling it would duplicate every streamed body into a second table under the 8 MB truncation rule, for no gain here. Separate concern.Tests
Three cases added to
TestReconstructResponseFromStreamEvents, written first and confirmed failing for the right reason (blocks dropped entirely —len([]) == 0):test_captures_thinking_text_and_signature— multi-delta text accumulation plus the separately-arriving signature.test_preserves_thinking_before_text_in_block_order— index ordering across mixed block types.test_captures_redacted_thinking_payload— opaque payload preserved.uv run pytest ...::TestReconstructResponseFromStreamEvents→ 11/11 pass.Coverage is unit-level only. The tests construct the SDK's own pydantic event models (
RawContentBlockStartEvent,ThinkingDelta,SignatureDelta), so a wrong block shape fails construction rather than passing a hand-rolled dict. What they do not prove is the live streaming path reaching this function — that link is exercised by the existingtext/tool_usecases.MockAnthropicServeremits only hardcodedtext/tool_useblocks, so an end-to-endmock_e2ecase needs a new mock response type; that is a follow-on rather than scope creep here.Pre-existing failures, not from this change
scripts/dev_checks.shreports three. They reproduce identically on unmodifiedmainunder xdist (uv run -m pytest -q --no-cov -n 4, which is whatdev_checks.shruns by default) and all pass single-process, so they are parallelism-sensitive and unrelated to this diff:tests/luthien_cli/test_onboard.py::test_find_docker_ports_respects_env_varstests/luthien_proxy/unit_tests/test_config_registry.py::TestResolveDefaults::test_default_value_when_no_overridestests/luthien_proxy/unit_tests/test_config_registry.py::TestResolvePriority::test_db_ignored_for_non_db_settableChangelog fragment:
changelog.d/capture-streaming-thinking.md.