Commit aebde9c
fix(core): fix message part ordering in buildMessagesFromChunks to use first-delta position (#16073)
## Problem
`buildMessagesFromChunks` emitted text and reasoning parts at `*-end`
time, not when they first appeared in the stream. When spans overlapped
with tool calls (e.g. `text-start` → `tool-call` → `text-end`), the tool
call would appear before the text part in the final message — breaking
step-start insertion logic and agent loop behavior.
## Solution
Refactored part emission to use **first-delta ordering**: parts are
pushed into the `parts` array when their first delta arrives (not on
`*-start` or `*-end`), then mutated in-place as subsequent deltas
arrive. This preserves the true stream order without accumulating deltas
into intermediate arrays.
Key changes:
- **First-delta positioning** — a part is created and pushed to the
array on the first `text-delta` or `reasoning-delta`, establishing its
position relative to tool calls and other parts
- **In-place mutation** — subsequent deltas append directly to the part
via a reference map (`textRefs`/`reasoningRefs`), avoiding `deltas[]`
accumulation and `join("")` at the end
- **Metadata maps** — `*-start` chunks only stash `providerMetadata` in
lightweight maps (`textMeta`/`reasoningMeta`); no part is created until
content actually arrives
- **Redacted reasoning** — still pushed immediately on `reasoning-start`
since redacted spans have no deltas
- **Empty reasoning** — emitted on `reasoning-end` if no deltas arrived,
preserving OpenAI `item_reference` requirements (#9005)
- **Empty text cleanup** — text parts where every delta was an empty
string `''` are filtered out at the end
## Testing
- Added regression test for interleaved `text-start` → `reasoning-start`
→ `reasoning-delta` → `text-delta` ordering (from #15914)
- Updated existing interleaved text/tool-call test to verify first-delta
ordering
- All 29 tests pass
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## ELI5
When the model streams bits of text, thoughts, or tool calls, this
change makes sure each saved message piece appears when its first real
content shows up so the saved order matches what happened live.
## Overview
Refactors buildMessagesFromChunks to use first-delta ordering: text and
reasoning parts are created/pushed when their first content delta
arrives (not at span start or end), preserving true stream-relative
ordering with tool calls and other parts. Provider metadata from
span-start is stashed and applied when content appears; redacted
reasoning is still emitted immediately; empty reasoning parts are
emitted when required to satisfy OpenAI item_reference semantics;
purely-empty text spans are filtered out except when they carry
providerMetadata.
## Changes
- First-delta positioning: create and push text/reasoning parts on first
text-delta/reasoning-delta so part ordering reflects actual content
arrival (#15914).
- In-place mutation: subsequent deltas update the already-pushed part
via reference maps (textRefs/reasoningRefs) instead of buffering and
joining at end.
- *-start metadata handling: text-start and non-redacted reasoning-start
stash providerMetadata in lightweight meta maps (textMeta/reasoningMeta)
without creating parts until content arrives.
- Redacted reasoning: redacted reasoning spans are emitted immediately
on reasoning-start (since no deltas will arrive).
- Empty reasoning: reasoning-end emits an empty reasoning part if no
deltas arrived to satisfy downstream item_reference requirements
(consistent with #9005 rationale).
- Empty text filtering: drop text parts whose deltas were all empty
strings while preserving empty text parts that carry providerMetadata.
- Stream-end/cleanup: text-end cleans refs/meta without forcing a flush;
unclosed reasoning spans with no deltas are emitted as empty reasoning
at stream end.
- Step-start insertion: step-start markers are inserted after filtering
so they remain correctly ordered relative to actual content.
## Tests
- Added regression test for interleaved reasoning/text ordering and
updated tests for interleaved text/tool-call/step-start scenarios.
- Updated inline snapshot expectations for mixed text/reasoning blocks
in options tests.
- Test run: 29 tests pass.
## Changesets
- Adds a patch changeset for @mastra/core documenting the fix: streaming
message parts (text, reasoning, tool calls) are now ordered by actual
arrival sequence.
## Issues Fixed
- Fixes #16007 (message persistence ordering) and #15914
(buildMessagesFromChunks ordering bug).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes #16007
Fixes #15914
---------
Co-authored-by: CalebBarnes <CalebBarnes@users.noreply.github.com>
Co-authored-by: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
Co-authored-by: Abhi Aiyer <abhiaiyer91@gmail.com>
Co-authored-by: Roamin <97863888+roaminro@users.noreply.github.com>1 parent 900d086 commit aebde9c
5 files changed
Lines changed: 215 additions & 154 deletions
File tree
- .changeset
- observability/mastra/src/__snapshots__
- packages/core/src/loop
- test-utils
- workflows/agentic-execution
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 7 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
| 132 | + | |
132 | 133 | | |
133 | 134 | | |
134 | 135 | | |
135 | 136 | | |
136 | | - | |
137 | | - | |
| 137 | + | |
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
| 270 | + | |
270 | 271 | | |
271 | | - | |
272 | | - | |
| 272 | + | |
273 | 273 | | |
274 | 274 | | |
275 | 275 | | |
| |||
305 | 305 | | |
306 | 306 | | |
307 | 307 | | |
| 308 | + | |
308 | 309 | | |
309 | 310 | | |
310 | 311 | | |
311 | 312 | | |
312 | | - | |
313 | | - | |
| 313 | + | |
314 | 314 | | |
315 | 315 | | |
316 | 316 | | |
| |||
323 | 323 | | |
324 | 324 | | |
325 | 325 | | |
326 | | - | |
| 326 | + | |
327 | 327 | | |
328 | 328 | | |
329 | 329 | | |
330 | 330 | | |
331 | | - | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | 331 | | |
336 | 332 | | |
337 | 333 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7571 | 7571 | | |
7572 | 7572 | | |
7573 | 7573 | | |
7574 | | - | |
| 7574 | + | |
7575 | 7575 | | |
7576 | 7576 | | |
7577 | 7577 | | |
7578 | 7578 | | |
7579 | | - | |
7580 | | - | |
| 7579 | + | |
| 7580 | + | |
7581 | 7581 | | |
7582 | 7582 | | |
7583 | 7583 | | |
7584 | | - | |
7585 | | - | |
| 7584 | + | |
| 7585 | + | |
7586 | 7586 | | |
7587 | 7587 | | |
7588 | 7588 | | |
| |||
7608 | 7608 | | |
7609 | 7609 | | |
7610 | 7610 | | |
7611 | | - | |
| 7611 | + | |
7612 | 7612 | | |
7613 | 7613 | | |
7614 | 7614 | | |
7615 | 7615 | | |
7616 | | - | |
7617 | | - | |
| 7616 | + | |
| 7617 | + | |
7618 | 7618 | | |
7619 | 7619 | | |
7620 | 7620 | | |
7621 | | - | |
7622 | | - | |
| 7621 | + | |
| 7622 | + | |
7623 | 7623 | | |
7624 | 7624 | | |
7625 | 7625 | | |
| |||
7673 | 7673 | | |
7674 | 7674 | | |
7675 | 7675 | | |
7676 | | - | |
| 7676 | + | |
7677 | 7677 | | |
7678 | 7678 | | |
7679 | 7679 | | |
7680 | 7680 | | |
7681 | | - | |
7682 | | - | |
| 7681 | + | |
| 7682 | + | |
7683 | 7683 | | |
7684 | 7684 | | |
7685 | 7685 | | |
7686 | | - | |
7687 | | - | |
| 7686 | + | |
| 7687 | + | |
7688 | 7688 | | |
7689 | 7689 | | |
7690 | 7690 | | |
| |||
7752 | 7752 | | |
7753 | 7753 | | |
7754 | 7754 | | |
7755 | | - | |
| 7755 | + | |
7756 | 7756 | | |
7757 | 7757 | | |
7758 | 7758 | | |
7759 | 7759 | | |
7760 | | - | |
7761 | | - | |
| 7760 | + | |
| 7761 | + | |
7762 | 7762 | | |
7763 | 7763 | | |
7764 | 7764 | | |
7765 | | - | |
7766 | | - | |
| 7765 | + | |
| 7766 | + | |
7767 | 7767 | | |
7768 | 7768 | | |
7769 | 7769 | | |
| |||
7789 | 7789 | | |
7790 | 7790 | | |
7791 | 7791 | | |
7792 | | - | |
| 7792 | + | |
7793 | 7793 | | |
7794 | 7794 | | |
7795 | 7795 | | |
7796 | 7796 | | |
7797 | | - | |
7798 | | - | |
| 7797 | + | |
| 7798 | + | |
7799 | 7799 | | |
7800 | 7800 | | |
7801 | 7801 | | |
7802 | | - | |
7803 | | - | |
| 7802 | + | |
| 7803 | + | |
7804 | 7804 | | |
7805 | 7805 | | |
7806 | 7806 | | |
| |||
7854 | 7854 | | |
7855 | 7855 | | |
7856 | 7856 | | |
7857 | | - | |
| 7857 | + | |
7858 | 7858 | | |
7859 | 7859 | | |
7860 | 7860 | | |
7861 | 7861 | | |
7862 | | - | |
7863 | | - | |
| 7862 | + | |
| 7863 | + | |
7864 | 7864 | | |
7865 | 7865 | | |
7866 | 7866 | | |
7867 | | - | |
7868 | | - | |
| 7867 | + | |
| 7868 | + | |
7869 | 7869 | | |
7870 | 7870 | | |
7871 | 7871 | | |
| |||
Lines changed: 77 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
83 | | - | |
84 | | - | |
85 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
86 | 87 | | |
87 | 88 | | |
88 | 89 | | |
| |||
304 | 305 | | |
305 | 306 | | |
306 | 307 | | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
307 | 346 | | |
308 | 347 | | |
309 | 348 | | |
| |||
322 | 361 | | |
323 | 362 | | |
324 | 363 | | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
325 | 399 | | |
326 | 400 | | |
327 | 401 | | |
| |||
0 commit comments