TanStack AI version
0.43.0
Framework/Library version
@tanstack/ai-client: 0.23.0 | @tanstack/ai-groq: 0.5.4 | @tanstack/ai-persistence: 0.1.0 | @tanstack/ai-svelte: 0.16.0
Describe the bug and the steps to reproduce it
Issue
TanStack AI has two structured-output modes:
- Native combined mode: the adapter declares
supportsCombinedToolsAndSchema(), schema-constrained JSON is produced as the agent loop's final text, and that text lives in accumulatedContent / FinishInfo.content.
- Separate finalization mode: tools and reasoning run first, then a second structured-output request runs. This is the path required for Groq.
Groq's API rejects response_format: json_schema together with tools and stream, so @tanstack/ai-groq forces the separate finalization path in text.ts#L109-L117.
On that finalization path, the engine deliberately does not fold finalization TEXT_MESSAGE_CONTENT deltas into accumulatedContent. The source comment says doing so would pollute agent-loop state and leak raw JSON into FinishInfo.content on onFinish (index.ts#L2913-L2924). Middleware docs match that contract: info.content is the agent loop's text; the structured result is delivered via the structured-output.complete CUSTOM event observed in onChunk with ctx.phase === 'structuredOutput'.
withPersistence only appends the terminal assistant turn from FinishInfo.content in finishedTranscript() (middleware.ts#L426-L459, used from onFinish at L1603-L1616). FinishInfo itself has no structured-output field (types.ts#L383-L392).
So on Groq (and any other separate-finalization adapter), the structured result streams live to the client but is not written into the persisted ModelMessage[] transcript. Native-combined adapters often still "persist" the JSON only because it happens to be info.content.
This does not seem like a Groq emission bug. Keeping finalization JSON out of accumulatedContent is intentional phase separation. The gap is that persistence has no alternate path for the structured result after finalization.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-ibnwwemz?file=package.json,index.html,src%2Fmain.ts
Terms & Code of Conduct
TanStack AI version
0.43.0
Framework/Library version
@tanstack/ai-client: 0.23.0 | @tanstack/ai-groq: 0.5.4 | @tanstack/ai-persistence: 0.1.0 | @tanstack/ai-svelte: 0.16.0
Describe the bug and the steps to reproduce it
Issue
TanStack AI has two structured-output modes:
supportsCombinedToolsAndSchema(), schema-constrained JSON is produced as the agent loop's final text, and that text lives inaccumulatedContent/FinishInfo.content.Groq's API rejects
response_format: json_schematogether withtoolsandstream, so@tanstack/ai-groqforces the separate finalization path intext.ts#L109-L117.On that finalization path, the engine deliberately does not fold finalization
TEXT_MESSAGE_CONTENTdeltas intoaccumulatedContent. The source comment says doing so would pollute agent-loop state and leak raw JSON intoFinishInfo.contentononFinish(index.ts#L2913-L2924). Middleware docs match that contract:info.contentis the agent loop's text; the structured result is delivered via thestructured-output.completeCUSTOM event observed inonChunkwithctx.phase === 'structuredOutput'.withPersistenceonly appends the terminal assistant turn fromFinishInfo.contentinfinishedTranscript()(middleware.ts#L426-L459, used fromonFinishat L1603-L1616).FinishInfoitself has no structured-output field (types.ts#L383-L392).So on Groq (and any other separate-finalization adapter), the structured result streams live to the client but is not written into the persisted
ModelMessage[]transcript. Native-combined adapters often still "persist" the JSON only because it happens to beinfo.content.This does not seem like a Groq emission bug. Keeping finalization JSON out of
accumulatedContentis intentional phase separation. The gap is that persistence has no alternate path for the structured result after finalization.Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/vitejs-vite-ibnwwemz?file=package.json,index.html,src%2Fmain.ts
Terms & Code of Conduct