TanStack AI version
0.44.0
Framework/Library version
@tanstack/ai-client: 0.23.2 | @tanstack/ai-persistence: 0.1.3 | @tanstack/ai-svelte: 0.16.2
Describe the bug and the steps to reproduce it
buildMessagesSnapshotChunk() always assigns a new id snapshot_<run>_<index> and never copies ModelMessage.id index.ts#L2145-L2172. The engine emits that snapshot at the native interrupt boundary index.ts#L2296.
A user message that already has a stable id therefore becomes snapshot_<run>_0 after the snapshot. StreamProcessor then adopts those ids: it already keeps a snapshot id when the wire message has one messages.ts#L512-L527. The client cannot recover the original identity once the server has overwritten it.
That breaks deduplication, reconciliation, and in-place rendering keyed on message.id. It is separate from the sequential client-tool resume race: the snapshot rewrite does not create the extra ordinary request. It only makes that request's existing user message look like new input.
Issue #1063 / PR #1066 fixed UI → model conversion so client-supplied ids copy onto ModelMessage.
Issue #1087 / PR #1101 covers engine-constructed assistant turns that never get an id. This path discards an id that is already present.
Expected behavior
A fix would keep message.id on the snapshot when it is present and generate snapshot_<run>_<index> only when it is absent. That matches the client normalizer, which already prefers the wire id and generates one only as a fallback.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-pnftan6t?file=package.json,index.html,src%2Fmain.ts
Terms & Code of Conduct
TanStack AI version
0.44.0
Framework/Library version
@tanstack/ai-client: 0.23.2 | @tanstack/ai-persistence: 0.1.3 | @tanstack/ai-svelte: 0.16.2
Describe the bug and the steps to reproduce it
buildMessagesSnapshotChunk()always assigns a new idsnapshot_<run>_<index>and never copiesModelMessage.idindex.ts#L2145-L2172. The engine emits that snapshot at the native interrupt boundary index.ts#L2296.A user message that already has a stable id therefore becomes
snapshot_<run>_0after the snapshot.StreamProcessorthen adopts those ids: it already keeps a snapshot id when the wire message has one messages.ts#L512-L527. The client cannot recover the original identity once the server has overwritten it.That breaks deduplication, reconciliation, and in-place rendering keyed on
message.id. It is separate from the sequential client-tool resume race: the snapshot rewrite does not create the extra ordinary request. It only makes that request's existing user message look like new input.Issue #1063 / PR #1066 fixed UI → model conversion so client-supplied ids copy onto
ModelMessage.Issue #1087 / PR #1101 covers engine-constructed assistant turns that never get an id. This path discards an id that is already present.
Expected behavior
A fix would keep
message.idon the snapshot when it is present and generatesnapshot_<run>_<index>only when it is absent. That matches the client normalizer, which already prefers the wire id and generates one only as a fallback.Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-pnftan6t?file=package.json,index.html,src%2Fmain.ts
Terms & Code of Conduct