From 1d5c4e2ef6655f31f7adc258fd627ea573e13343 Mon Sep 17 00:00:00 2001 From: mikemikimike <13286568797@163.com> Date: Mon, 17 Aug 2026 22:53:52 +0800 Subject: [PATCH 1/4] fix: preserve fallback structured output timestamp ordering --- .changeset/gentle-dots-fallback-timestamps.md | 5 +++ packages/ai/src/activities/chat/index.ts | 2 + .../chat-structured-output-stream.test.ts | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 .changeset/gentle-dots-fallback-timestamps.md diff --git a/.changeset/gentle-dots-fallback-timestamps.md b/.changeset/gentle-dots-fallback-timestamps.md new file mode 100644 index 000000000..43420ced7 --- /dev/null +++ b/.changeset/gentle-dots-fallback-timestamps.md @@ -0,0 +1,5 @@ +--- +'@tanstack/ai': patch +--- + +Fix fallback structured-output lifecycle timestamps being emitted before the provider request settles. diff --git a/packages/ai/src/activities/chat/index.ts b/packages/ai/src/activities/chat/index.ts index 8f009a688..4f2b750bd 100644 --- a/packages/ai/src/activities/chat/index.ts +++ b/packages/ai/src/activities/chat/index.ts @@ -5019,6 +5019,8 @@ async function* fallbackStructuredOutputStream( return } + const completedAt = Date.now() + yield { type: EventType.TEXT_MESSAGE_START, messageId, diff --git a/packages/ai/tests/chat-structured-output-stream.test.ts b/packages/ai/tests/chat-structured-output-stream.test.ts index 84e996908..a64d578b7 100644 --- a/packages/ai/tests/chat-structured-output-stream.test.ts +++ b/packages/ai/tests/chat-structured-output-stream.test.ts @@ -480,6 +480,44 @@ describe('chat({ outputSchema, stream: true })', () => { expect(finished).toBeDefined() expect('usage' in finished!).toBe(false) }) + + it('keeps synthesized lifecycle timestamps ordered after a delayed provider result', async () => { + const adapter = makeAdapter({ + structuredOutput: async () => { + await new Promise((resolve) => setTimeout(resolve, 5)) + return { data: validPerson, rawText: JSON.stringify(validPerson) } + }, + }) + + const stream = chat({ + adapter, + messages: [{ role: 'user', content: 'extract' }], + outputSchema: PersonSchema, + stream: true, + }) + + const chunks = await collectChunks(stream) + const start = chunks.find( + (c) => + c.type === EventType.CUSTOM && + (c as { name?: string }).name === 'structured-output.start', + ) + const content = chunks.find((c) => c.type === EventType.TEXT_MESSAGE_CONTENT) + const complete = chunks.find( + (c) => + c.type === EventType.CUSTOM && + (c as { name?: string }).name === 'structured-output.complete', + ) + const finished = chunks.find((c) => c.type === EventType.RUN_FINISHED) + + expect(start).toBeDefined() + expect(content).toBeDefined() + expect(complete).toBeDefined() + expect(finished).toBeDefined() + expect(start!.timestamp).toBeLessThanOrEqual(content!.timestamp) + expect(content!.timestamp).toBeLessThanOrEqual(complete!.timestamp) + expect(complete!.timestamp).toBeLessThanOrEqual(finished!.timestamp) + }) }) describe('lifecycle ordering', () => { From 5e48677fd180dfd937a4ec18000d15ba11173d01 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:00:20 +0000 Subject: [PATCH 2/4] ci: apply automated fixes --- packages/ai/tests/chat-structured-output-stream.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ai/tests/chat-structured-output-stream.test.ts b/packages/ai/tests/chat-structured-output-stream.test.ts index a64d578b7..a300b2539 100644 --- a/packages/ai/tests/chat-structured-output-stream.test.ts +++ b/packages/ai/tests/chat-structured-output-stream.test.ts @@ -502,7 +502,9 @@ describe('chat({ outputSchema, stream: true })', () => { c.type === EventType.CUSTOM && (c as { name?: string }).name === 'structured-output.start', ) - const content = chunks.find((c) => c.type === EventType.TEXT_MESSAGE_CONTENT) + const content = chunks.find( + (c) => c.type === EventType.TEXT_MESSAGE_CONTENT, + ) const complete = chunks.find( (c) => c.type === EventType.CUSTOM && From 2204f17a7367b25eea943f8ce8265b101df68f60 Mon Sep 17 00:00:00 2001 From: mikemikimike <13286568797@163.com> Date: Wed, 19 Aug 2026 12:10:18 +0800 Subject: [PATCH 3/4] test(ai): cover fallback structured output lifecycle ordering --- packages/ai/src/activities/chat/index.ts | 2 - .../chat-structured-output-stream.test.ts | 45 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/activities/chat/index.ts b/packages/ai/src/activities/chat/index.ts index 4f2b750bd..8f009a688 100644 --- a/packages/ai/src/activities/chat/index.ts +++ b/packages/ai/src/activities/chat/index.ts @@ -5019,8 +5019,6 @@ async function* fallbackStructuredOutputStream( return } - const completedAt = Date.now() - yield { type: EventType.TEXT_MESSAGE_START, messageId, diff --git a/packages/ai/tests/chat-structured-output-stream.test.ts b/packages/ai/tests/chat-structured-output-stream.test.ts index a300b2539..29dc0a08a 100644 --- a/packages/ai/tests/chat-structured-output-stream.test.ts +++ b/packages/ai/tests/chat-structured-output-stream.test.ts @@ -497,11 +497,15 @@ describe('chat({ outputSchema, stream: true })', () => { }) const chunks = await collectChunks(stream) + const runStarted = chunks.find((c) => c.type === EventType.RUN_STARTED) const start = chunks.find( (c) => c.type === EventType.CUSTOM && (c as { name?: string }).name === 'structured-output.start', ) + const textStart = chunks.find( + (c) => c.type === EventType.TEXT_MESSAGE_START, + ) const content = chunks.find( (c) => c.type === EventType.TEXT_MESSAGE_CONTENT, ) @@ -512,13 +516,48 @@ describe('chat({ outputSchema, stream: true })', () => { ) const finished = chunks.find((c) => c.type === EventType.RUN_FINISHED) + expect(runStarted).toBeDefined() expect(start).toBeDefined() + expect(textStart).toBeDefined() expect(content).toBeDefined() expect(complete).toBeDefined() expect(finished).toBeDefined() - expect(start!.timestamp).toBeLessThanOrEqual(content!.timestamp) - expect(content!.timestamp).toBeLessThanOrEqual(complete!.timestamp) - expect(complete!.timestamp).toBeLessThanOrEqual(finished!.timestamp) + expect(runStarted!.timestamp!).toBeLessThanOrEqual(start!.timestamp!) + expect(start!.timestamp!).toBeLessThanOrEqual(textStart!.timestamp!) + expect(textStart!.timestamp!).toBeLessThanOrEqual(content!.timestamp!) + expect(content!.timestamp!).toBeLessThanOrEqual(complete!.timestamp!) + expect(complete!.timestamp!).toBeLessThanOrEqual(finished!.timestamp!) + }) + + it('keeps synthesized error lifecycle timestamps ordered after a delayed provider rejection', async () => { + const adapter = makeAdapter({ + structuredOutput: async () => { + await new Promise((resolve) => setTimeout(resolve, 5)) + throw new Error('provider rejected the request') + }, + }) + + const stream = chat({ + adapter, + messages: [{ role: 'user', content: 'extract' }], + outputSchema: PersonSchema, + stream: true, + }) + + const chunks = await collectChunks(stream) + const runStarted = chunks.find((c) => c.type === EventType.RUN_STARTED) + const start = chunks.find( + (c) => + c.type === EventType.CUSTOM && + (c as { name?: string }).name === 'structured-output.start', + ) + const error = chunks.find((c) => c.type === EventType.RUN_ERROR) + + expect(runStarted).toBeDefined() + expect(start).toBeDefined() + expect(error).toBeDefined() + expect(runStarted!.timestamp!).toBeLessThanOrEqual(start!.timestamp!) + expect(start!.timestamp!).toBeLessThanOrEqual(error!.timestamp!) }) }) From a8dea6c865fdbbe05dbe152b56ad9a4f97360cc1 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:32:01 +0000 Subject: [PATCH 4/4] ci: apply automated fixes --- scripts/lovable-gateway.models.json | 387 ++++++---------------------- 1 file changed, 82 insertions(+), 305 deletions(-) diff --git a/scripts/lovable-gateway.models.json b/scripts/lovable-gateway.models.json index 8a193376e..79c6babbc 100644 --- a/scripts/lovable-gateway.models.json +++ b/scripts/lovable-gateway.models.json @@ -12,15 +12,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -85,15 +78,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -150,15 +136,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -222,12 +201,8 @@ "context_window": 8192, "max_tokens": 16384, "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -265,12 +240,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -308,15 +279,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -406,12 +370,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -449,15 +409,8 @@ "max_tokens": 65000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -522,15 +475,8 @@ "max_tokens": 32768, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -595,15 +541,8 @@ "max_tokens": 32768, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -668,15 +607,8 @@ "max_tokens": 65000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -740,15 +672,8 @@ "context_window": 65536, "max_tokens": 4096, "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -813,12 +738,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -856,15 +777,8 @@ "max_tokens": 64000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -954,15 +868,8 @@ "max_tokens": 64000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1027,15 +934,8 @@ "max_tokens": 64000, "knowledge": "2026-03", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1100,12 +1000,8 @@ "max_tokens": 0, "knowledge": "2025-05", "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": { @@ -1133,15 +1029,8 @@ "max_tokens": 0, "knowledge": "2025-11", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1192,13 +1081,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1226,13 +1110,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1260,13 +1139,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1291,13 +1165,8 @@ "context_window": 16000, "max_tokens": 2000, "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] + "input": ["text", "audio"], + "output": ["text"] }, "pricing": { "input": { @@ -1342,12 +1211,8 @@ "context_window": 2000, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -1384,13 +1249,8 @@ "context_window": 16000, "max_tokens": 2000, "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] + "input": ["text", "audio"], + "output": ["text"] }, "pricing": { "input": { @@ -1436,13 +1296,8 @@ "max_tokens": 128000, "knowledge": "2024-09-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1526,13 +1381,8 @@ "max_tokens": 128000, "knowledge": "2024-05-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1616,13 +1466,8 @@ "max_tokens": 128000, "knowledge": "2024-05-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1671,13 +1516,8 @@ "max_tokens": 128000, "knowledge": "2024-10", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1761,13 +1601,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1866,13 +1701,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1956,13 +1786,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2011,13 +1836,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2081,13 +1901,8 @@ "max_tokens": 128000, "knowledge": "2025-12-01", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2186,13 +2001,8 @@ "max_tokens": 128000, "knowledge": "2025-12-01", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2256,13 +2066,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2361,13 +2166,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2466,13 +2266,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2570,13 +2365,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] + "input": ["text", "image"], + "output": ["image"] }, "pricing": { "input": { @@ -2621,13 +2411,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] + "input": ["text", "image"], + "output": ["image"] }, "pricing": { "input": { @@ -2672,12 +2457,8 @@ "context_window": 8191, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": { @@ -2704,12 +2485,8 @@ "context_window": 8191, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": {