TanStack AI version
0.44.0
Framework/Library version
@tanstack/ai-client: 0.23.2 | @tanstack/ai-persistence: 0.1.2 | @tanstack/ai-svelte: 0.16.2
Describe the bug and the steps to reproduce it
Issue #1063 / PR #1066 and Issue #1064 / PR #1069 fixed UI → model conversion so client-supplied UIMessage.id and UIMessage.createdAt copy onto ModelMessage.
Those fixes are ancestors of @tanstack/ai@0.44.0, and the conversion path now preserves both fields in messages.ts#L186-L193 (and the assistant-segment builders beside it).
Server-generated assistant turns do not pass through that conversion.
When the model emits tool calls, addAssistantToolCallMessage() appends a new ModelMessage with only role, content, toolCalls, and optional thinking — no id or createdAt index.ts#L1948-L1963.
That message is what withPersistence saves on the interrupt / agent-loop boundary via ctx.messages, so a hydrated tool-call turn gets a fresh client id and timestamp.
The terminal-text persistence path is only a partial fix. finishedTranscript() stamps the streamed messageId onto the final assistant reply but still omits createdAt middleware.ts#L438-L459.
Streaming snapshots do the same: they optionally set id and never set createdAt.
So after #1063 / #1064, UI-originated messages round-trip, but server-authored assistant bubbles still lose stable identity and wall-clock time across persist → hydrate. That breaks in-place rendering for tool-call turns and leaves terminal turns without a durable createdAt.
Expected behavior
- When the engine appends an assistant tool-call turn, stamp the current stream
messageId (and a createdAt) onto that ModelMessage.
- When persistence appends a terminal assistant turn in
finishedTranscript() (and in streaming snapshots), copy the same createdAt contract used for UI → model conversion, not only id.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-cvbrdixn?file=package.json,index.html,src%2Fmain.ts
Screenshots or Videos (Optional)
No response
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct
TanStack AI version
0.44.0
Framework/Library version
@tanstack/ai-client: 0.23.2 | @tanstack/ai-persistence: 0.1.2 | @tanstack/ai-svelte: 0.16.2
Describe the bug and the steps to reproduce it
Issue #1063 / PR #1066 and Issue #1064 / PR #1069 fixed UI → model conversion so client-supplied
UIMessage.idandUIMessage.createdAtcopy ontoModelMessage.Those fixes are ancestors of
@tanstack/ai@0.44.0, and the conversion path now preserves both fields in messages.ts#L186-L193 (and the assistant-segment builders beside it).Server-generated assistant turns do not pass through that conversion.
When the model emits tool calls,
addAssistantToolCallMessage()appends a newModelMessagewith onlyrole,content,toolCalls, and optionalthinking— noidorcreatedAtindex.ts#L1948-L1963.That message is what
withPersistencesaves on the interrupt / agent-loop boundary viactx.messages, so a hydrated tool-call turn gets a fresh client id and timestamp.The terminal-text persistence path is only a partial fix.
finishedTranscript()stamps the streamedmessageIdonto the final assistant reply but still omitscreatedAtmiddleware.ts#L438-L459.Streaming snapshots do the same: they optionally set
idand never setcreatedAt.So after #1063 / #1064, UI-originated messages round-trip, but server-authored assistant bubbles still lose stable identity and wall-clock time across persist → hydrate. That breaks in-place rendering for tool-call turns and leaves terminal turns without a durable
createdAt.Expected behavior
messageId(and acreatedAt) onto thatModelMessage.finishedTranscript()(and in streaming snapshots), copy the samecreatedAtcontract used for UI → model conversion, not onlyid.Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-cvbrdixn?file=package.json,index.html,src%2Fmain.ts
Screenshots or Videos (Optional)
No response
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct