Conversation
…vents A message_delta that carries null stop_reason/stop_sequence/stop_details overwrote whatever an earlier delta in the same stream had already accumulated, even though the block guards container and context_management against exactly that.
This branch has not been deployed
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.
The bug
MessageStream/BetaMessageStreamaccumulate a runningMessagesnapshot from the stream events. In themessage_deltabranch, three of the accumulated fields are assigned unconditionally:RawMessageDeltaEvent.Deltatypes all three as nullable (src/resources/messages/messages.ts:2763-2778, and the Beta equivalent), so amessage_deltawhose payload does not carry them arrives asnull— and anullthen erases whatever an earlier delta in the same stream already established.finalMessage()reportsstop_reason: nullfor a message that did stop, andstop_details(the refusal classification) disappears.The two fields directly below in the very same block are already guarded against exactly this:
and the repo's own test
keeps accumulated container and context_management when a later message_delta omits them(tests/api-resources/BetaMessageStream.test.ts) drives a two-message_deltastream to prove it. So the "a later delta may omit what an earlier one set" shape is already an accepted case here —stop_*was just left out of it.usage.output_tokensstays unconditional, which is correct: it is typednumber, non-null.The change
Guard the three assignments with the same
!= nullcheck used by their siblings, so a delta only contributes fields it actually carries.Tests
Six new cases (three per stream class) in the existing
tests/api-resources/*MessageStream.test.ts, appended without editing existing tests:message_deltafollowing a refusal delta keepsstop_reason+stop_detailsstop_sequencekeeps it whilestop_reasonupdatesnullkeeps them — same convention the existingcontainertest asserts with an explicitcontainer: nullRegression evidence, same command on both trees (
node@24,vitest run tests/api-resources/MessageStream.test.ts tests/api-resources/BetaMessageStream.test.ts):3c5d9c0, new tests, unmodifiedsrc/libTests 36 passed (36)Full suite after
./scripts/mock --daemon:5 failed | 1791 passed | 57 skipped, versus5 failed | 1785 passedon a stashed clean tree — the same 5 pre-existing failures (tests/tools/agent-toolset.test.ts×4,tests/skills.test.ts×1), none in streaming, so this change adds no regression../scripts/lintpasses (prettier, eslint, build,tsc, attw, publint: 0 errors). Sub-package suites pass: vertex 17, bedrock 62, foundry 12, aws 84, google-cloud 63.Scope notes
The generator never writes to
src/lib/, so this is a hand-written fix in a hand-written file. I did not touch the compaction-delta null handling inBetaMessageStream— #1174 covers that, and it is a different root cause from these three fields.