Skip to content

fix(stream): preserve per-segment ids so assistant parts keep chronological order#164

Merged
vutuanlinh2k2 merged 2 commits into
mainfrom
fix/interleaved-assistant-parts
Jul 2, 2026
Merged

fix(stream): preserve per-segment ids so assistant parts keep chronological order#164
vutuanlinh2k2 merged 2 commits into
mainfrom
fix/interleaved-assistant-parts

Conversation

@vutuanlinh2k2

Copy link
Copy Markdown
Contributor

Problem

normalizePersistedPart strips id from text/reasoning parts, so getPartKey collapses every segment to text:current / reasoning:current: all reasoning merges into one part, all text into one part, and interleaving (reasoning → tool → reasoning → text) is destroyed at persist time. finalizeAssistantParts then stamps the full final text into every text part.

Harness adapters already emit stable per-segment part ids (OpenCode native ids; claude-code streaming path assigns a UUID per thinking block) — the fidelity is thrown away here.

Change

  • normalizePersistedPart: text/reasoning parts carry id (or partId) through when present. Absent id = legacy single-segment flow, unchanged — an id is never invented.
  • mergePersistedPart: an empty text snapshot no longer erases accumulated text (now matches the reasoning branch).
  • finalizeAssistantParts gains a per-id contract with the invariant concat(text segments) === final text:
    • no text parts → append finalText as one part (unchanged)
    • no text part has an id (legacy) → overwrite with finalText (byte-identical to today)
    • per-id segments: equal → untouched; finalText extends the stream (e.g. an appended failure diagnostic) → id-less remainder segment appended; diverged → collapse to one authoritative segment at the last text position (finalText is what content/post-processing consume — it must win, while reasoning/tool chronology survives)

Back-compat

Id-less flows (creative-agent call sites, old persisted rows, router-style text streams) behave exactly as before — covered by dedicated tests.

Tests

New tests/stream-normalizer.test.ts: id preservation/no-invention, per-id keying vs *:current fallback, per-id merge semantics, all finalize branches, and end-to-end order integrity for reasoning → tool → reasoning → text.

Context

Foundation for tangle-network/gtm-agent#448 (chronological chat transcript); gtm consumes this via the auto-published patch bump. Note: will conflict textually with fix/dangling-tool-terminalization if that lands too — whichever merges second needs a trivial rebase (the terminalize wrapping is tool-only, orthogonal to these branches).

…ogical order

normalizePersistedPart carries id/partId through for text and reasoning
parts (absent id = legacy single-segment flow, unchanged). finalize gains
a per-id contract: concat(text segments) must equal the final text —
equal → untouched, extended → id-less remainder segment appended,
diverged → collapse to one authoritative segment at the last text
position. Id-less flows are byte-identical to the previous behavior.
@vutuanlinh2k2 vutuanlinh2k2 requested a review from tangletools July 2, 2026 08:29
@vutuanlinh2k2 vutuanlinh2k2 merged commit 562a568 into main Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant