@tanstack/ai 0.22.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.js +1 -0
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +2 -0
- package/dist/esm/activities/chat/stream/processor.d.ts +15 -0
- package/dist/esm/activities/chat/stream/processor.js +56 -0
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/adapter-configuration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/src/activities/chat/index.ts +1 -0
- package/src/activities/chat/middleware/types.ts +2 -0
- package/src/activities/chat/stream/processor.ts +92 -0
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/TanStack/ai.git",
|
|
10
|
-
"directory": "packages/
|
|
10
|
+
"directory": "packages/ai"
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
13
|
"module": "./dist/esm/index.js",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@ag-ui/core": "^0.0.52",
|
|
65
65
|
"@standard-schema/spec": "^1.1.0",
|
|
66
66
|
"partial-json": "^0.1.7",
|
|
67
|
-
"@tanstack/ai-event-client": "0.
|
|
67
|
+
"@tanstack/ai-event-client": "0.4.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@opentelemetry/api": ">=1.9.0"
|
|
@@ -26,7 +26,7 @@ sources:
|
|
|
26
26
|
|
|
27
27
|
> **Before implementing:** Ask the user which provider and model they want.
|
|
28
28
|
> Then fetch the latest available models from the provider's source code
|
|
29
|
-
> (check the adapter's model metadata file, e.g. `packages/
|
|
29
|
+
> (check the adapter's model metadata file, e.g. `packages/ai-openai/src/model-meta.ts`)
|
|
30
30
|
> or from the provider's API/docs to recommend the most current model.
|
|
31
31
|
> The model lists in this skill and its reference files may be outdated.
|
|
32
32
|
> Always verify against the source before recommending a specific model.
|
|
@@ -481,6 +481,7 @@ class TextEngine<
|
|
|
481
481
|
this.middlewareCtx = {
|
|
482
482
|
requestId: this.requestId,
|
|
483
483
|
streamId: this.streamId,
|
|
484
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
484
485
|
threadId: this.threadId,
|
|
485
486
|
// Legacy alias kept on the ctx so middleware that reads
|
|
486
487
|
// `ctx.conversationId` keeps working. Always equals `threadId`.
|
|
@@ -38,6 +38,8 @@ export interface ChatMiddlewareContext {
|
|
|
38
38
|
requestId: string
|
|
39
39
|
/** Unique identifier for this stream */
|
|
40
40
|
streamId: string
|
|
41
|
+
/** AG-UI run identifier for correlating client and server events */
|
|
42
|
+
runId: string
|
|
41
43
|
/**
|
|
42
44
|
* AG-UI thread identifier — a stable per-conversation ID used to
|
|
43
45
|
* correlate client and server devtools events. Resolves to the
|
|
@@ -98,6 +98,17 @@ export interface StreamProcessorEvents {
|
|
|
98
98
|
stepId: string,
|
|
99
99
|
content: string,
|
|
100
100
|
) => void
|
|
101
|
+
onStructuredOutputChange?: (args: {
|
|
102
|
+
phase: 'start' | 'update' | 'complete' | 'error'
|
|
103
|
+
messageId: string
|
|
104
|
+
status: 'streaming' | 'complete' | 'error'
|
|
105
|
+
raw: string
|
|
106
|
+
partial?: unknown
|
|
107
|
+
data?: unknown
|
|
108
|
+
reasoning?: string
|
|
109
|
+
errorMessage?: string
|
|
110
|
+
delta?: string
|
|
111
|
+
}) => void
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
/**
|
|
@@ -116,6 +127,8 @@ export interface StreamProcessorOptions {
|
|
|
116
127
|
initialMessages?: Array<UIMessage>
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
const STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12
|
|
131
|
+
|
|
119
132
|
/**
|
|
120
133
|
* StreamProcessor - State machine for processing AI response streams
|
|
121
134
|
*
|
|
@@ -149,6 +162,13 @@ export class StreamProcessor {
|
|
|
149
162
|
private pendingThinkingStepId: string | null = null
|
|
150
163
|
|
|
151
164
|
private readonly structuredMessageIds: Set<string> = new Set()
|
|
165
|
+
private readonly structuredOutputUpdateBatches = new Map<
|
|
166
|
+
string,
|
|
167
|
+
{
|
|
168
|
+
delta: string
|
|
169
|
+
chunkCount: number
|
|
170
|
+
}
|
|
171
|
+
>()
|
|
152
172
|
|
|
153
173
|
// Run tracking (for concurrent run safety)
|
|
154
174
|
private readonly activeRuns = new Set<string>()
|
|
@@ -401,6 +421,9 @@ export class StreamProcessor {
|
|
|
401
421
|
for (const id of this.structuredMessageIds) {
|
|
402
422
|
if (!keptIds.has(id)) this.structuredMessageIds.delete(id)
|
|
403
423
|
}
|
|
424
|
+
for (const id of this.structuredOutputUpdateBatches.keys()) {
|
|
425
|
+
if (!keptIds.has(id)) this.structuredOutputUpdateBatches.delete(id)
|
|
426
|
+
}
|
|
404
427
|
for (const id of this.messageStates.keys()) {
|
|
405
428
|
if (!keptIds.has(id)) this.messageStates.delete(id)
|
|
406
429
|
}
|
|
@@ -423,6 +446,7 @@ export class StreamProcessor {
|
|
|
423
446
|
this.activeMessageIds.clear()
|
|
424
447
|
this.toolCallToMessage.clear()
|
|
425
448
|
this.structuredMessageIds.clear()
|
|
449
|
+
this.structuredOutputUpdateBatches.clear()
|
|
426
450
|
this.pendingManualMessageId = null
|
|
427
451
|
this.emitMessagesChange()
|
|
428
452
|
}
|
|
@@ -899,6 +923,7 @@ export class StreamProcessor {
|
|
|
899
923
|
delta,
|
|
900
924
|
)
|
|
901
925
|
state.totalTextContent += delta
|
|
926
|
+
this.queueStructuredOutputUpdate(messageId, delta)
|
|
902
927
|
this.emitMessagesChange()
|
|
903
928
|
}
|
|
904
929
|
return
|
|
@@ -1269,12 +1294,14 @@ export class StreamProcessor {
|
|
|
1269
1294
|
}
|
|
1270
1295
|
|
|
1271
1296
|
if (this.structuredMessageIds.has(messageId)) {
|
|
1297
|
+
this.flushStructuredOutputUpdate(messageId)
|
|
1272
1298
|
this.messages = errorStructuredOutputPart(
|
|
1273
1299
|
this.messages,
|
|
1274
1300
|
messageId,
|
|
1275
1301
|
errorMessage,
|
|
1276
1302
|
)
|
|
1277
1303
|
this.structuredMessageIds.delete(messageId)
|
|
1304
|
+
this.emitStructuredOutputChange(messageId, 'error')
|
|
1278
1305
|
this.emitMessagesChange()
|
|
1279
1306
|
}
|
|
1280
1307
|
|
|
@@ -1463,6 +1490,13 @@ export class StreamProcessor {
|
|
|
1463
1490
|
if (targetId) {
|
|
1464
1491
|
this.ensureAssistantMessage(targetId)
|
|
1465
1492
|
this.structuredMessageIds.add(targetId)
|
|
1493
|
+
this.structuredOutputUpdateBatches.delete(targetId)
|
|
1494
|
+
this.events.onStructuredOutputChange?.({
|
|
1495
|
+
phase: 'start',
|
|
1496
|
+
messageId: targetId,
|
|
1497
|
+
status: 'streaming',
|
|
1498
|
+
raw: '',
|
|
1499
|
+
})
|
|
1466
1500
|
}
|
|
1467
1501
|
return
|
|
1468
1502
|
}
|
|
@@ -1476,6 +1510,7 @@ export class StreamProcessor {
|
|
|
1476
1510
|
}
|
|
1477
1511
|
const targetId = v.messageId ?? messageId
|
|
1478
1512
|
if (targetId) {
|
|
1513
|
+
this.flushStructuredOutputUpdate(targetId)
|
|
1479
1514
|
this.messages = completeStructuredOutputPart(
|
|
1480
1515
|
this.messages,
|
|
1481
1516
|
targetId,
|
|
@@ -1484,6 +1519,7 @@ export class StreamProcessor {
|
|
|
1484
1519
|
v.reasoning,
|
|
1485
1520
|
)
|
|
1486
1521
|
this.structuredMessageIds.delete(targetId)
|
|
1522
|
+
this.emitStructuredOutputChange(targetId, 'complete')
|
|
1487
1523
|
this.emitMessagesChange()
|
|
1488
1524
|
}
|
|
1489
1525
|
// Fall through so user `onCustomEvent` callbacks still observe the event.
|
|
@@ -1665,6 +1701,58 @@ export class StreamProcessor {
|
|
|
1665
1701
|
this.events.onTextUpdate?.(messageId, state.currentSegmentText)
|
|
1666
1702
|
}
|
|
1667
1703
|
|
|
1704
|
+
private queueStructuredOutputUpdate(messageId: string, delta: string): void {
|
|
1705
|
+
const existing = this.structuredOutputUpdateBatches.get(messageId)
|
|
1706
|
+
const next = {
|
|
1707
|
+
delta: `${existing?.delta ?? ''}${delta}`,
|
|
1708
|
+
chunkCount: (existing?.chunkCount ?? 0) + 1,
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
this.structuredOutputUpdateBatches.set(messageId, next)
|
|
1712
|
+
|
|
1713
|
+
if (next.chunkCount >= STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE) {
|
|
1714
|
+
this.flushStructuredOutputUpdate(messageId)
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
private flushStructuredOutputUpdate(messageId: string): void {
|
|
1719
|
+
const batch = this.structuredOutputUpdateBatches.get(messageId)
|
|
1720
|
+
if (!batch || batch.chunkCount === 0) return
|
|
1721
|
+
|
|
1722
|
+
this.structuredOutputUpdateBatches.delete(messageId)
|
|
1723
|
+
this.emitStructuredOutputChange(messageId, 'update', batch.delta)
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
private emitStructuredOutputChange(
|
|
1727
|
+
messageId: string,
|
|
1728
|
+
phase: 'update' | 'complete' | 'error',
|
|
1729
|
+
delta?: string,
|
|
1730
|
+
): void {
|
|
1731
|
+
const part = this.messages
|
|
1732
|
+
.find((message) => message.id === messageId)
|
|
1733
|
+
?.parts.find(
|
|
1734
|
+
(
|
|
1735
|
+
messagePart,
|
|
1736
|
+
): messagePart is Extract<MessagePart, { type: 'structured-output' }> =>
|
|
1737
|
+
messagePart.type === 'structured-output',
|
|
1738
|
+
)
|
|
1739
|
+
if (!part) return
|
|
1740
|
+
|
|
1741
|
+
this.events.onStructuredOutputChange?.({
|
|
1742
|
+
phase,
|
|
1743
|
+
messageId,
|
|
1744
|
+
status: part.status,
|
|
1745
|
+
raw: part.raw,
|
|
1746
|
+
...(part.partial !== undefined ? { partial: part.partial } : {}),
|
|
1747
|
+
...(part.data !== undefined ? { data: part.data } : {}),
|
|
1748
|
+
...(part.reasoning !== undefined ? { reasoning: part.reasoning } : {}),
|
|
1749
|
+
...(part.errorMessage !== undefined
|
|
1750
|
+
? { errorMessage: part.errorMessage }
|
|
1751
|
+
: {}),
|
|
1752
|
+
...(delta !== undefined ? { delta } : {}),
|
|
1753
|
+
})
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1668
1756
|
/**
|
|
1669
1757
|
* Emit messages change event
|
|
1670
1758
|
*/
|
|
@@ -1718,13 +1806,16 @@ export class StreamProcessor {
|
|
|
1718
1806
|
// definition a non-errored, never-completed run (the multi-run case:
|
|
1719
1807
|
// run-A errors, run-B is still streaming when finalize fires).
|
|
1720
1808
|
for (const messageId of this.structuredMessageIds) {
|
|
1809
|
+
this.flushStructuredOutputUpdate(messageId)
|
|
1721
1810
|
this.messages = errorStructuredOutputPart(
|
|
1722
1811
|
this.messages,
|
|
1723
1812
|
messageId,
|
|
1724
1813
|
'Stream ended without structured-output.complete',
|
|
1725
1814
|
)
|
|
1815
|
+
this.emitStructuredOutputChange(messageId, 'error')
|
|
1726
1816
|
}
|
|
1727
1817
|
this.structuredMessageIds.clear()
|
|
1818
|
+
this.structuredOutputUpdateBatches.clear()
|
|
1728
1819
|
|
|
1729
1820
|
this.activeMessageIds.clear()
|
|
1730
1821
|
|
|
@@ -1855,6 +1946,7 @@ export class StreamProcessor {
|
|
|
1855
1946
|
this.activeRuns.clear()
|
|
1856
1947
|
this.toolCallToMessage.clear()
|
|
1857
1948
|
this.structuredMessageIds.clear()
|
|
1949
|
+
this.structuredOutputUpdateBatches.clear()
|
|
1858
1950
|
this.pendingManualMessageId = null
|
|
1859
1951
|
this.pendingThinkingStepId = null
|
|
1860
1952
|
this.finishReason = null
|