@tanstack/ai 0.23.1 → 0.25.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.
Files changed (74) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +3 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.d.ts +33 -9
  4. package/dist/esm/activities/chat/index.js +19 -9
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/chat/messages.js +2 -1
  7. package/dist/esm/activities/chat/messages.js.map +1 -1
  8. package/dist/esm/activities/chat/middleware/compose.d.ts +14 -14
  9. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  10. package/dist/esm/activities/chat/middleware/types.d.ts +21 -21
  11. package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
  12. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  13. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  14. package/dist/esm/activities/chat/stream/processor.d.ts +1 -0
  15. package/dist/esm/activities/chat/stream/processor.js +35 -12
  16. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  17. package/dist/esm/activities/chat/tools/tool-calls.d.ts +15 -5
  18. package/dist/esm/activities/chat/tools/tool-calls.js +59 -19
  19. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/tool-definition.d.ts +12 -8
  21. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  22. package/dist/esm/activities/error-payload.d.ts +26 -0
  23. package/dist/esm/activities/error-payload.js +12 -1
  24. package/dist/esm/activities/error-payload.js.map +1 -1
  25. package/dist/esm/activities/generateAudio/index.js +9 -0
  26. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  27. package/dist/esm/activities/generateSpeech/index.js +9 -0
  28. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  29. package/dist/esm/adapter-internals.d.ts +1 -1
  30. package/dist/esm/adapter-internals.js +3 -2
  31. package/dist/esm/client.d.ts +1 -1
  32. package/dist/esm/client.js +3 -1
  33. package/dist/esm/client.js.map +1 -1
  34. package/dist/esm/index.d.ts +3 -1
  35. package/dist/esm/index.js +9 -1
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/esm/tool-registry.d.ts +7 -7
  38. package/dist/esm/tool-registry.js +1 -1
  39. package/dist/esm/tool-registry.js.map +1 -1
  40. package/dist/esm/types.d.ts +56 -59
  41. package/dist/esm/utilities/ag-ui-wire.js +1 -1
  42. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  43. package/dist/esm/utilities/chat-params.d.ts +8 -3
  44. package/dist/esm/utilities/chat-params.js +6 -2
  45. package/dist/esm/utilities/chat-params.js.map +1 -1
  46. package/dist/esm/utilities/tool-result.d.ts +21 -0
  47. package/dist/esm/utilities/tool-result.js +37 -0
  48. package/dist/esm/utilities/tool-result.js.map +1 -0
  49. package/dist/esm/utilities/usage.d.ts +31 -0
  50. package/dist/esm/utilities/usage.js +11 -0
  51. package/dist/esm/utilities/usage.js.map +1 -0
  52. package/package.json +2 -2
  53. package/src/activities/chat/adapter.ts +3 -0
  54. package/src/activities/chat/index.ts +219 -47
  55. package/src/activities/chat/messages.ts +2 -1
  56. package/src/activities/chat/middleware/compose.ts +23 -17
  57. package/src/activities/chat/middleware/types.ts +21 -21
  58. package/src/activities/chat/runtime-context-types.ts +68 -0
  59. package/src/activities/chat/stream/message-updaters.ts +2 -1
  60. package/src/activities/chat/stream/processor.ts +48 -8
  61. package/src/activities/chat/tools/tool-calls.ts +138 -43
  62. package/src/activities/chat/tools/tool-definition.ts +25 -31
  63. package/src/activities/error-payload.ts +44 -0
  64. package/src/activities/generateAudio/index.ts +10 -0
  65. package/src/activities/generateSpeech/index.ts +10 -0
  66. package/src/adapter-internals.ts +4 -1
  67. package/src/client.ts +5 -1
  68. package/src/index.ts +10 -0
  69. package/src/tool-registry.ts +16 -14
  70. package/src/types.ts +118 -79
  71. package/src/utilities/ag-ui-wire.ts +4 -1
  72. package/src/utilities/chat-params.ts +22 -7
  73. package/src/utilities/tool-result.ts +60 -0
  74. package/src/utilities/usage.ts +41 -0
@@ -2,9 +2,9 @@ import type {
2
2
  JSONSchema,
3
3
  ModelMessage,
4
4
  StreamChunk,
5
+ TokenUsage,
5
6
  Tool,
6
7
  ToolCall,
7
- UsageTotals,
8
8
  } from '../../../types'
9
9
  import type { SystemPrompt } from '../../../system-prompts'
10
10
 
@@ -34,7 +34,7 @@ export type ChatMiddlewarePhase =
34
34
  * Stable context object passed to all middleware hooks.
35
35
  * Created once per chat() invocation and shared across all hooks.
36
36
  */
37
- export interface ChatMiddlewareContext {
37
+ export interface ChatMiddlewareContext<TContext = unknown> {
38
38
  /** Unique identifier for this chat request */
39
39
  requestId: string
40
40
  /** Unique identifier for this stream */
@@ -64,8 +64,8 @@ export interface ChatMiddlewareContext {
64
64
  signal?: AbortSignal
65
65
  /** Abort the chat run with a reason */
66
66
  abort: (reason?: string) => void
67
- /** Opaque user-provided value from chat() options */
68
- context: unknown
67
+ /** Runtime context provided by chat() options */
68
+ context: TContext
69
69
  /**
70
70
  * Defer a non-blocking side-effect promise.
71
71
  * Deferred promises do not block streaming and are awaited
@@ -266,11 +266,11 @@ export interface ToolPhaseCompleteInfo {
266
266
  * Token usage statistics passed to the onUsage hook.
267
267
  * Extracted from the RUN_FINISHED chunk when usage data is present.
268
268
  *
269
- * Includes optional provider-reported `cost`/`costDetails` (see {@link UsageTotals}).
270
- * Kept as an interface extending `UsageTotals` to preserve declaration merging for
269
+ * Includes optional provider-reported `cost`/`costDetails` (see {@link TokenUsage}).
270
+ * Kept as an interface extending `TokenUsage` to preserve declaration merging for
271
271
  * this publicly exported type.
272
272
  */
273
- export interface UsageInfo extends UsageTotals {}
273
+ export interface UsageInfo extends TokenUsage {}
274
274
 
275
275
  // ===========================
276
276
  // Terminal Hook Info
@@ -287,7 +287,7 @@ export interface FinishInfo {
287
287
  /** Final accumulated text content */
288
288
  content: string
289
289
  /** Final usage totals, if available (optionally including provider-reported cost) */
290
- usage?: UsageTotals | undefined
290
+ usage?: TokenUsage | undefined
291
291
  }
292
292
 
293
293
  /**
@@ -343,7 +343,7 @@ export interface ErrorInfo {
343
343
  * }
344
344
  * ```
345
345
  */
346
- export interface ChatMiddleware {
346
+ export interface ChatMiddleware<TContext = unknown> {
347
347
  /** Optional name for debugging and identification */
348
348
  name?: string
349
349
 
@@ -355,7 +355,7 @@ export interface ChatMiddleware {
355
355
  * Only the fields you return are overwritten — everything else is preserved.
356
356
  */
357
357
  onConfig?: (
358
- ctx: ChatMiddlewareContext,
358
+ ctx: ChatMiddlewareContext<TContext>,
359
359
  config: ChatMiddlewareConfig,
360
360
  ) =>
361
361
  | void
@@ -379,7 +379,7 @@ export interface ChatMiddleware {
379
379
  * outputSchema or apply structured-output-specific behavior.
380
380
  */
381
381
  onStructuredOutputConfig?: (
382
- ctx: ChatMiddlewareContext,
382
+ ctx: ChatMiddlewareContext<TContext>,
383
383
  config: StructuredOutputMiddlewareConfig,
384
384
  ) =>
385
385
  | void
@@ -390,14 +390,14 @@ export interface ChatMiddleware {
390
390
  /**
391
391
  * Called when the chat run starts (after initial onConfig).
392
392
  */
393
- onStart?: (ctx: ChatMiddlewareContext) => void | Promise<void>
393
+ onStart?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>
394
394
 
395
395
  /**
396
396
  * Called at the start of each agent loop iteration, after a new assistant message ID
397
397
  * is created. Use this to observe iteration boundaries.
398
398
  */
399
399
  onIteration?: (
400
- ctx: ChatMiddlewareContext,
400
+ ctx: ChatMiddlewareContext<TContext>,
401
401
  info: IterationInfo,
402
402
  ) => void | Promise<void>
403
403
 
@@ -408,7 +408,7 @@ export interface ChatMiddleware {
408
408
  * @returns void (pass through), chunk (replace), chunk[] (expand), null (drop)
409
409
  */
410
410
  onChunk?: (
411
- ctx: ChatMiddlewareContext,
411
+ ctx: ChatMiddlewareContext<TContext>,
412
412
  chunk: StreamChunk,
413
413
  ) =>
414
414
  | void
@@ -422,7 +422,7 @@ export interface ChatMiddleware {
422
422
  * Can observe, transform args, skip execution, or abort the run.
423
423
  */
424
424
  onBeforeToolCall?: (
425
- ctx: ChatMiddlewareContext,
425
+ ctx: ChatMiddlewareContext<TContext>,
426
426
  hookCtx: ToolCallHookContext,
427
427
  ) => BeforeToolCallDecision | Promise<BeforeToolCallDecision>
428
428
 
@@ -430,7 +430,7 @@ export interface ChatMiddleware {
430
430
  * Called after a tool execution completes (success or failure).
431
431
  */
432
432
  onAfterToolCall?: (
433
- ctx: ChatMiddlewareContext,
433
+ ctx: ChatMiddlewareContext<TContext>,
434
434
  info: AfterToolCallInfo,
435
435
  ) => void | Promise<void>
436
436
 
@@ -439,7 +439,7 @@ export interface ChatMiddleware {
439
439
  * Provides aggregate data about tool execution results, approvals, and client tools.
440
440
  */
441
441
  onToolPhaseComplete?: (
442
- ctx: ChatMiddlewareContext,
442
+ ctx: ChatMiddlewareContext<TContext>,
443
443
  info: ToolPhaseCompleteInfo,
444
444
  ) => void | Promise<void>
445
445
 
@@ -448,7 +448,7 @@ export interface ChatMiddleware {
448
448
  * Called once per model iteration that reports usage.
449
449
  */
450
450
  onUsage?: (
451
- ctx: ChatMiddlewareContext,
451
+ ctx: ChatMiddlewareContext<TContext>,
452
452
  usage: UsageInfo,
453
453
  ) => void | Promise<void>
454
454
 
@@ -457,7 +457,7 @@ export interface ChatMiddleware {
457
457
  * Exactly one of onFinish/onAbort/onError will be called per run.
458
458
  */
459
459
  onFinish?: (
460
- ctx: ChatMiddlewareContext,
460
+ ctx: ChatMiddlewareContext<TContext>,
461
461
  info: FinishInfo,
462
462
  ) => void | Promise<void>
463
463
 
@@ -466,7 +466,7 @@ export interface ChatMiddleware {
466
466
  * Exactly one of onFinish/onAbort/onError will be called per run.
467
467
  */
468
468
  onAbort?: (
469
- ctx: ChatMiddlewareContext,
469
+ ctx: ChatMiddlewareContext<TContext>,
470
470
  info: AbortInfo,
471
471
  ) => void | Promise<void>
472
472
 
@@ -475,7 +475,7 @@ export interface ChatMiddleware {
475
475
  * Exactly one of onFinish/onAbort/onError will be called per run.
476
476
  */
477
477
  onError?: (
478
- ctx: ChatMiddlewareContext,
478
+ ctx: ChatMiddlewareContext<TContext>,
479
479
  info: ErrorInfo,
480
480
  ) => void | Promise<void>
481
481
  }
@@ -0,0 +1,68 @@
1
+ import type { ChatMiddleware } from './middleware/types'
2
+
3
+ /**
4
+ * Shared type-level helpers for inferring the runtime `context` requirement
5
+ * from typed tools and middleware.
6
+ *
7
+ * These primitives are consumed by both the chat activity options
8
+ * (`./index.ts`, which merges tool + middleware requirements) and the tool
9
+ * execution layer (`./tools/tool-calls.ts`, which only sees tools). They live
10
+ * here so the two call sites share one definition instead of maintaining
11
+ * divergent copies.
12
+ */
13
+
14
+ /** True only when `T` is exactly `unknown`. */
15
+ type IsUnknown<T> = unknown extends T
16
+ ? [T] extends [unknown]
17
+ ? true
18
+ : false
19
+ : false
20
+
21
+ /**
22
+ * Drops an `unknown` context requirement to `never` so that untyped tools and
23
+ * middleware (which default `TContext` to `unknown`) contribute no requirement
24
+ * to the merged context.
25
+ */
26
+ type KnownContext<T> = IsUnknown<T> extends true ? never : T
27
+
28
+ /**
29
+ * Merge two inferred context requirements, treating `never` as "no
30
+ * requirement". Using this instead of a raw intersection keeps a `never`
31
+ * (untyped) contributor from collapsing the whole merge to `never`.
32
+ */
33
+ export type MergeContext<TLeft, TRight> = [TLeft] extends [never]
34
+ ? TRight
35
+ : [TRight] extends [never]
36
+ ? TLeft
37
+ : TLeft & TRight
38
+
39
+ /** Collapse a union of context requirements into their intersection. */
40
+ export type UnionToIntersection<T> = [T] extends [never]
41
+ ? never
42
+ : (T extends unknown ? (value: T) => void : never) extends (
43
+ value: infer TIntersection,
44
+ ) => void
45
+ ? TIntersection
46
+ : never
47
+
48
+ /** Strip `undefined` from a context requirement. */
49
+ export type DefinedContext<T> = Exclude<T, undefined>
50
+
51
+ /**
52
+ * Extract the `context` requirement declared by a tool execute function's
53
+ * second argument, dropping `unknown` (untyped) contexts to `never`.
54
+ */
55
+ type ContextFromExecute<T> = T extends (...args: any) => any
56
+ ? NonNullable<Parameters<T>[1]> extends { context: infer TUserContext }
57
+ ? KnownContext<TUserContext>
58
+ : never
59
+ : never
60
+
61
+ /** Extract the context requirement declared by a single tool. */
62
+ export type ContextFromTool<T> = T extends { execute?: infer TExecute }
63
+ ? ContextFromExecute<TExecute>
64
+ : never
65
+
66
+ /** Extract the context requirement declared by a single middleware. */
67
+ export type ContextFromMiddleware<T> =
68
+ T extends ChatMiddleware<infer TContext> ? KnownContext<TContext> : never
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { parsePartialJSON } from './json-parser'
9
9
  import type {
10
+ ContentPart,
10
11
  StructuredOutputPart,
11
12
  ThinkingPart,
12
13
  ToolCallPart,
@@ -107,7 +108,7 @@ export function updateToolResultPart(
107
108
  messages: Array<UIMessage>,
108
109
  messageId: string,
109
110
  toolCallId: string,
110
- content: string,
111
+ content: string | Array<ContentPart>,
111
112
  state: ToolResultState,
112
113
  error?: string,
113
114
  ): Array<UIMessage> {
@@ -18,6 +18,7 @@
18
18
  * adapter contract, single-shot flows, and expected UIMessage output.
19
19
  */
20
20
  import { generateMessageId, uiMessageToModelMessages } from '../messages.js'
21
+ import { normalizeToolResult } from '../../../utilities/tool-result'
21
22
  import { defaultJSONParser } from './json-parser'
22
23
  import {
23
24
  appendStructuredOutputDelta,
@@ -321,7 +322,7 @@ export class StreamProcessor {
321
322
  )
322
323
 
323
324
  // Step 2: Create a tool-result part (for LLM conversation history)
324
- const content = typeof output === 'string' ? output : JSON.stringify(output)
325
+ const content = normalizeToolResult(output)
325
326
  const toolResultState: ToolResultState = error ? 'error' : 'complete'
326
327
 
327
328
  updatedMessages = updateToolResultPart(
@@ -1170,30 +1171,51 @@ export class StreamProcessor {
1170
1171
  // Step 1: Update the tool-call part's output field (for UI consistency
1171
1172
  // with client tools — see GitHub issue #176)
1172
1173
  let output: unknown
1173
- try {
1174
- output = JSON.parse(chunk.result)
1175
- } catch {
1174
+ if (Array.isArray(chunk.result)) {
1176
1175
  output = chunk.result
1176
+ } else {
1177
+ try {
1178
+ output = JSON.parse(chunk.result)
1179
+ } catch {
1180
+ output = chunk.result
1181
+ }
1177
1182
  }
1178
1183
  this.messages = updateToolCallWithOutput(
1179
1184
  this.messages,
1180
1185
  chunk.toolCallId,
1181
1186
  output,
1187
+ chunk.state === 'output-error' ? 'input-complete' : undefined,
1182
1188
  )
1183
1189
 
1184
1190
  // Step 2: Create/update the tool-result part (for LLM conversation history)
1185
- const resultState: ToolResultState = 'complete'
1191
+ const resultState: ToolResultState =
1192
+ chunk.state === 'output-error' ? 'error' : 'complete'
1186
1193
  this.messages = updateToolResultPart(
1187
1194
  this.messages,
1188
1195
  messageId,
1189
1196
  chunk.toolCallId,
1190
1197
  chunk.result,
1191
1198
  resultState,
1199
+ resultState === 'error'
1200
+ ? this.extractToolResultError(output)
1201
+ : undefined,
1192
1202
  )
1193
1203
  this.emitMessagesChange()
1194
1204
  }
1195
1205
  }
1196
1206
 
1207
+ private extractToolResultError(output: unknown): string {
1208
+ if (
1209
+ output &&
1210
+ typeof output === 'object' &&
1211
+ 'error' in output &&
1212
+ typeof output.error === 'string'
1213
+ ) {
1214
+ return output.error
1215
+ }
1216
+ return typeof output === 'string' ? output : 'Tool execution failed'
1217
+ }
1218
+
1197
1219
  /**
1198
1220
  * Handle TOOL_CALL_RESULT event (AG-UI spec).
1199
1221
  *
@@ -1218,16 +1240,19 @@ export class StreamProcessor {
1218
1240
  this.messages,
1219
1241
  chunk.toolCallId,
1220
1242
  output,
1243
+ chunk.state === 'output-error' ? 'input-complete' : undefined,
1221
1244
  )
1222
1245
 
1223
1246
  // Step 2: Create/update the tool-result part
1224
- const resultState: ToolResultState = 'complete'
1247
+ const resultState: ToolResultState =
1248
+ chunk.state === 'output-error' ? 'error' : 'complete'
1225
1249
  this.messages = updateToolResultPart(
1226
1250
  this.messages,
1227
1251
  messageId,
1228
1252
  chunk.toolCallId,
1229
1253
  chunk.content,
1230
1254
  resultState,
1255
+ resultState === 'error' ? this.extractToolResultError(output) : undefined,
1231
1256
  )
1232
1257
  this.emitMessagesChange()
1233
1258
  }
@@ -1274,7 +1299,10 @@ export class StreamProcessor {
1274
1299
  chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
1275
1300
  ): void {
1276
1301
  this.hasError = true
1277
- const runId = (chunk as any).runId as string | undefined
1302
+ const runId =
1303
+ 'runId' in chunk && typeof chunk.runId === 'string'
1304
+ ? chunk.runId
1305
+ : undefined
1278
1306
  if (runId) {
1279
1307
  this.activeRuns.delete(runId)
1280
1308
  } else {
@@ -1305,7 +1333,19 @@ export class StreamProcessor {
1305
1333
  this.emitMessagesChange()
1306
1334
  }
1307
1335
 
1308
- this.events.onError?.(new Error(errorMessage))
1336
+ // Attach the provider's structured error body (`rawEvent`) and `code` to
1337
+ // the surfaced Error so consumers can recover the upstream detail that the
1338
+ // RUN_ERROR's `message` alone discards. Both are optional and added only
1339
+ // when present, keeping the Error backward compatible.
1340
+ const error = new Error(errorMessage)
1341
+ const code = chunk.code ?? chunk.error?.code
1342
+ if (code !== undefined) {
1343
+ Object.assign(error, { code })
1344
+ }
1345
+ if (chunk.rawEvent !== undefined) {
1346
+ Object.assign(error, { rawEvent: chunk.rawEvent })
1347
+ }
1348
+ this.events.onError?.(error)
1309
1349
  }
1310
1350
 
1311
1351
  /**