@tanstack/ai 0.10.2 → 0.11.0
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.d.ts +4 -0
- package/dist/esm/activities/chat/index.js +50 -16
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.js +5 -4
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +16 -1
- package/dist/esm/activities/chat/stream/processor.js +120 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +1 -0
- package/dist/esm/activities/chat/tools/tool-calls.js +9 -4
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +6 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.d.ts +3 -2
- package/dist/esm/activities/stream-generation-result.js +12 -4
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +2 -2
- package/dist/esm/stream-to-response.js +0 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.d.ts +18 -0
- package/dist/esm/strip-to-spec-middleware.js +20 -0
- package/dist/esm/strip-to-spec-middleware.js.map +1 -0
- package/dist/esm/types.d.ts +229 -95
- package/package.json +3 -2
- package/src/activities/chat/index.ts +90 -25
- package/src/activities/chat/middleware/compose.ts +6 -4
- package/src/activities/chat/stream/processor.ts +152 -25
- package/src/activities/chat/stream/types.ts +1 -0
- package/src/activities/chat/tools/tool-calls.ts +14 -6
- package/src/activities/generateVideo/index.ts +13 -6
- package/src/activities/stream-generation-result.ts +19 -11
- package/src/stream-to-response.ts +2 -4
- package/src/strip-to-spec-middleware.ts +37 -0
- package/src/types.ts +276 -109
|
@@ -93,11 +93,12 @@ export class ToolCallManager {
|
|
|
93
93
|
*/
|
|
94
94
|
addToolCallStartEvent(event: ToolCallStartEvent): void {
|
|
95
95
|
const index = event.index ?? this.toolCallsMap.size
|
|
96
|
+
const name = event.toolCallName
|
|
96
97
|
this.toolCallsMap.set(index, {
|
|
97
98
|
id: event.toolCallId,
|
|
98
99
|
type: 'function',
|
|
99
100
|
function: {
|
|
100
|
-
name
|
|
101
|
+
name,
|
|
101
102
|
arguments: '',
|
|
102
103
|
},
|
|
103
104
|
...(event.providerMetadata && {
|
|
@@ -127,7 +128,10 @@ export class ToolCallManager {
|
|
|
127
128
|
for (const [, toolCall] of this.toolCallsMap.entries()) {
|
|
128
129
|
if (toolCall.id === event.toolCallId) {
|
|
129
130
|
if (event.input !== undefined) {
|
|
130
|
-
|
|
131
|
+
// Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)
|
|
132
|
+
const normalized =
|
|
133
|
+
event.input && typeof event.input === 'object' ? event.input : {}
|
|
134
|
+
toolCall.function.arguments = JSON.stringify(normalized)
|
|
131
135
|
}
|
|
132
136
|
break
|
|
133
137
|
}
|
|
@@ -167,11 +171,12 @@ export class ToolCallManager {
|
|
|
167
171
|
let toolResultContent: string
|
|
168
172
|
if (tool?.execute) {
|
|
169
173
|
try {
|
|
170
|
-
// Parse arguments (normalize
|
|
174
|
+
// Parse arguments (normalize null/non-object to {} for empty tool_use blocks)
|
|
171
175
|
let args: unknown
|
|
172
176
|
try {
|
|
173
177
|
const argsString = toolCall.function.arguments.trim() || '{}'
|
|
174
|
-
|
|
178
|
+
const parsed = JSON.parse(argsString)
|
|
179
|
+
args = parsed && typeof parsed === 'object' ? parsed : {}
|
|
175
180
|
} catch (parseError) {
|
|
176
181
|
throw new Error(
|
|
177
182
|
`Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,
|
|
@@ -233,11 +238,12 @@ export class ToolCallManager {
|
|
|
233
238
|
yield {
|
|
234
239
|
type: 'TOOL_CALL_END',
|
|
235
240
|
toolCallId: toolCall.id,
|
|
241
|
+
toolCallName: toolCall.function.name,
|
|
236
242
|
toolName: toolCall.function.name,
|
|
237
243
|
model: finishEvent.model,
|
|
238
244
|
timestamp: Date.now(),
|
|
239
245
|
result: toolResultContent,
|
|
240
|
-
}
|
|
246
|
+
} as ToolCallEndEvent
|
|
241
247
|
|
|
242
248
|
// Add tool result message
|
|
243
249
|
toolResults.push({
|
|
@@ -543,7 +549,9 @@ export async function* executeToolCalls(
|
|
|
543
549
|
const argsStr = toolCall.function.arguments.trim() || '{}'
|
|
544
550
|
if (argsStr) {
|
|
545
551
|
try {
|
|
546
|
-
|
|
552
|
+
const parsed = JSON.parse(argsStr)
|
|
553
|
+
// Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)
|
|
554
|
+
input = parsed && typeof parsed === 'object' ? parsed : {}
|
|
547
555
|
} catch (parseError) {
|
|
548
556
|
// If parsing fails, throw error to fail fast
|
|
549
557
|
throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)
|
|
@@ -268,11 +268,14 @@ async function* runStreamingVideoGeneration<
|
|
|
268
268
|
const pollingInterval = options.pollingInterval ?? 2000
|
|
269
269
|
const maxDuration = options.maxDuration ?? 600_000
|
|
270
270
|
|
|
271
|
+
const threadId = createId('thread')
|
|
272
|
+
|
|
271
273
|
yield {
|
|
272
274
|
type: 'RUN_STARTED',
|
|
273
275
|
runId,
|
|
276
|
+
threadId,
|
|
274
277
|
timestamp: Date.now(),
|
|
275
|
-
}
|
|
278
|
+
} as StreamChunk
|
|
276
279
|
|
|
277
280
|
try {
|
|
278
281
|
// Create the video generation job
|
|
@@ -289,7 +292,7 @@ async function* runStreamingVideoGeneration<
|
|
|
289
292
|
name: 'video:job:created',
|
|
290
293
|
value: { jobId: jobResult.jobId },
|
|
291
294
|
timestamp: Date.now(),
|
|
292
|
-
}
|
|
295
|
+
} as StreamChunk
|
|
293
296
|
|
|
294
297
|
// Poll for completion
|
|
295
298
|
const startTime = Date.now()
|
|
@@ -308,7 +311,7 @@ async function* runStreamingVideoGeneration<
|
|
|
308
311
|
error: statusResult.error,
|
|
309
312
|
},
|
|
310
313
|
timestamp: Date.now(),
|
|
311
|
-
}
|
|
314
|
+
} as StreamChunk
|
|
312
315
|
|
|
313
316
|
if (statusResult.status === 'completed') {
|
|
314
317
|
const urlResult = await adapter.getVideoUrl(jobResult.jobId)
|
|
@@ -323,14 +326,15 @@ async function* runStreamingVideoGeneration<
|
|
|
323
326
|
expiresAt: urlResult.expiresAt,
|
|
324
327
|
},
|
|
325
328
|
timestamp: Date.now(),
|
|
326
|
-
}
|
|
329
|
+
} as StreamChunk
|
|
327
330
|
|
|
328
331
|
yield {
|
|
329
332
|
type: 'RUN_FINISHED',
|
|
330
333
|
runId,
|
|
334
|
+
threadId,
|
|
331
335
|
finishReason: 'stop',
|
|
332
336
|
timestamp: Date.now(),
|
|
333
|
-
}
|
|
337
|
+
} as StreamChunk
|
|
334
338
|
return
|
|
335
339
|
}
|
|
336
340
|
|
|
@@ -344,12 +348,15 @@ async function* runStreamingVideoGeneration<
|
|
|
344
348
|
yield {
|
|
345
349
|
type: 'RUN_ERROR',
|
|
346
350
|
runId,
|
|
351
|
+
threadId,
|
|
352
|
+
message: error.message || 'Video generation failed',
|
|
353
|
+
code: error.code,
|
|
347
354
|
error: {
|
|
348
355
|
message: error.message || 'Video generation failed',
|
|
349
356
|
code: error.code,
|
|
350
357
|
},
|
|
351
358
|
timestamp: Date.now(),
|
|
352
|
-
}
|
|
359
|
+
} as StreamChunk
|
|
353
360
|
}
|
|
354
361
|
}
|
|
355
362
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* implementations to support `stream: true`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { EventType } from '@ag-ui/core'
|
|
7
8
|
import type { StreamChunk } from '../types'
|
|
8
9
|
|
|
9
10
|
function createId(prefix: string): string {
|
|
@@ -17,46 +18,53 @@ function createId(prefix: string): string {
|
|
|
17
18
|
* to be sent over the same streaming transport as chat.
|
|
18
19
|
*
|
|
19
20
|
* @param generator - An async function that performs the generation and returns the result
|
|
20
|
-
* @param options - Optional configuration (runId)
|
|
21
|
-
* @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events
|
|
21
|
+
* @param options - Optional configuration (runId, threadId)
|
|
22
|
+
* @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events on success, or RUN_STARTED and RUN_ERROR on failure
|
|
22
23
|
*/
|
|
23
24
|
export async function* streamGenerationResult<TResult>(
|
|
24
25
|
generator: () => Promise<TResult>,
|
|
25
|
-
options?: { runId?: string },
|
|
26
|
+
options?: { runId?: string; threadId?: string },
|
|
26
27
|
): AsyncIterable<StreamChunk> {
|
|
27
28
|
const runId = options?.runId ?? createId('run')
|
|
29
|
+
const threadId = options?.threadId ?? createId('thread')
|
|
28
30
|
|
|
29
31
|
yield {
|
|
30
|
-
type:
|
|
32
|
+
type: EventType.RUN_STARTED,
|
|
31
33
|
runId,
|
|
34
|
+
threadId,
|
|
32
35
|
timestamp: Date.now(),
|
|
33
|
-
}
|
|
36
|
+
} as StreamChunk
|
|
34
37
|
|
|
35
38
|
try {
|
|
36
39
|
const result = await generator()
|
|
37
40
|
|
|
38
41
|
yield {
|
|
39
|
-
type:
|
|
42
|
+
type: EventType.CUSTOM,
|
|
40
43
|
name: 'generation:result',
|
|
41
44
|
value: result as unknown,
|
|
42
45
|
timestamp: Date.now(),
|
|
43
|
-
}
|
|
46
|
+
} as StreamChunk
|
|
44
47
|
|
|
45
48
|
yield {
|
|
46
|
-
type:
|
|
49
|
+
type: EventType.RUN_FINISHED,
|
|
47
50
|
runId,
|
|
51
|
+
threadId,
|
|
48
52
|
finishReason: 'stop',
|
|
49
53
|
timestamp: Date.now(),
|
|
50
|
-
}
|
|
54
|
+
} as StreamChunk
|
|
51
55
|
} catch (error: any) {
|
|
52
56
|
yield {
|
|
53
|
-
type:
|
|
57
|
+
type: EventType.RUN_ERROR,
|
|
54
58
|
runId,
|
|
59
|
+
threadId,
|
|
60
|
+
message: error.message || 'Generation failed',
|
|
61
|
+
code: error.code,
|
|
62
|
+
// Deprecated nested form for backward compatibility
|
|
55
63
|
error: {
|
|
56
64
|
message: error.message || 'Generation failed',
|
|
57
65
|
code: error.code,
|
|
58
66
|
},
|
|
59
67
|
timestamp: Date.now(),
|
|
60
|
-
}
|
|
68
|
+
} as StreamChunk
|
|
61
69
|
}
|
|
62
70
|
}
|
|
@@ -40,7 +40,7 @@ export async function streamToText(
|
|
|
40
40
|
* This creates a ReadableStream that emits chunks in SSE format:
|
|
41
41
|
* - Each chunk is prefixed with "data: "
|
|
42
42
|
* - Each chunk is followed by "\n\n"
|
|
43
|
-
* - Stream ends
|
|
43
|
+
* - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)
|
|
44
44
|
*
|
|
45
45
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
46
46
|
* @param abortController - Optional AbortController to abort when stream is cancelled
|
|
@@ -67,8 +67,6 @@ export function toServerSentEventsStream(
|
|
|
67
67
|
)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Send completion marker
|
|
71
|
-
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
|
|
72
70
|
controller.close()
|
|
73
71
|
} catch (error: any) {
|
|
74
72
|
// Don't send error if aborted
|
|
@@ -109,7 +107,7 @@ export function toServerSentEventsStream(
|
|
|
109
107
|
* This creates a Response that emits chunks in SSE format:
|
|
110
108
|
* - Each chunk is prefixed with "data: "
|
|
111
109
|
* - Each chunk is followed by "\n\n"
|
|
112
|
-
* - Stream ends
|
|
110
|
+
* - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)
|
|
113
111
|
*
|
|
114
112
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
115
113
|
* @param init - Optional Response initialization options (including `abortController`)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ChatMiddleware } from './activities/chat/middleware/types'
|
|
2
|
+
import type { StreamChunk } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Strip only the deprecated nested `error` object from RUN_ERROR events.
|
|
6
|
+
* The flat `message`/`code` fields are the spec-compliant form.
|
|
7
|
+
*
|
|
8
|
+
* All other fields pass through unchanged. @ag-ui/core's BaseEventSchema
|
|
9
|
+
* uses `.passthrough()`, so extra fields (model, content, usage,
|
|
10
|
+
* finishReason, toolName, stepId, etc.) are allowed and won't break
|
|
11
|
+
* spec validation or verifyEvents.
|
|
12
|
+
*/
|
|
13
|
+
export function stripToSpec(chunk: StreamChunk): StreamChunk {
|
|
14
|
+
// Only strip the deprecated nested error object from RUN_ERROR
|
|
15
|
+
if (
|
|
16
|
+
(chunk as StreamChunk & { type: string }).type === 'RUN_ERROR' &&
|
|
17
|
+
'error' in chunk
|
|
18
|
+
) {
|
|
19
|
+
const { error: _deprecated, ...rest } = chunk as Record<string, unknown>
|
|
20
|
+
return rest as StreamChunk
|
|
21
|
+
}
|
|
22
|
+
return chunk
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Middleware that ensures events are AG-UI spec compliant.
|
|
27
|
+
* Currently only strips the deprecated nested `error` object from RUN_ERROR.
|
|
28
|
+
* All other fields pass through unchanged (passthrough allowed by spec).
|
|
29
|
+
*/
|
|
30
|
+
export function stripToSpecMiddleware(): ChatMiddleware {
|
|
31
|
+
return {
|
|
32
|
+
name: 'strip-to-spec',
|
|
33
|
+
onChunk(_ctx, chunk) {
|
|
34
|
+
return stripToSpec(chunk)
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|