@tanstack/ai 0.40.0 → 0.42.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 (57) hide show
  1. package/dist/esm/activities/chat/agent-loop-strategies.d.ts +40 -3
  2. package/dist/esm/activities/chat/agent-loop-strategies.js +4 -0
  3. package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
  4. package/dist/esm/activities/chat/index.d.ts +5 -0
  5. package/dist/esm/activities/chat/index.js +102 -33
  6. package/dist/esm/activities/chat/index.js.map +1 -1
  7. package/dist/esm/activities/chat/messages.js +7 -0
  8. package/dist/esm/activities/chat/messages.js.map +1 -1
  9. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -0
  10. package/dist/esm/activities/chat/stream/message-updaters.js +3 -1
  11. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  12. package/dist/esm/activities/chat/stream/processor.js +18 -1
  13. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  14. package/dist/esm/activities/chat/tools/tool-definition.d.ts +14 -11
  15. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  16. package/dist/esm/activities/generateAudio/index.d.ts +1 -1
  17. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  18. package/dist/esm/activities/generateImage/index.d.ts +1 -1
  19. package/dist/esm/activities/generateImage/index.js +1 -1
  20. package/dist/esm/activities/generateImage/index.js.map +1 -1
  21. package/dist/esm/activities/generateSpeech/index.d.ts +1 -1
  22. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  23. package/dist/esm/activities/generateTranscription/index.d.ts +1 -1
  24. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  25. package/dist/esm/activities/generateVideo/index.d.ts +12 -12
  26. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  27. package/dist/esm/index.d.ts +3 -3
  28. package/dist/esm/index.js +4 -1
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/realtime/event-emitter.d.ts +5 -0
  31. package/dist/esm/realtime/event-emitter.js +27 -0
  32. package/dist/esm/realtime/event-emitter.js.map +1 -0
  33. package/dist/esm/realtime/index.d.ts +1 -0
  34. package/dist/esm/realtime/index.js.map +1 -1
  35. package/dist/esm/realtime/types.d.ts +9 -1
  36. package/dist/esm/types.d.ts +43 -2
  37. package/package.json +1 -1
  38. package/skills/ai-core/chat-experience/SKILL.md +58 -0
  39. package/skills/ai-core/media-generation/SKILL.md +42 -2
  40. package/skills/ai-core/middleware/SKILL.md +18 -3
  41. package/skills/ai-core/tool-calling/SKILL.md +15 -1
  42. package/src/activities/chat/agent-loop-strategies.ts +43 -3
  43. package/src/activities/chat/index.ts +144 -36
  44. package/src/activities/chat/messages.ts +9 -0
  45. package/src/activities/chat/stream/message-updaters.ts +7 -1
  46. package/src/activities/chat/stream/processor.ts +30 -2
  47. package/src/activities/chat/tools/tool-definition.ts +33 -11
  48. package/src/activities/generateAudio/index.ts +2 -2
  49. package/src/activities/generateImage/index.ts +2 -2
  50. package/src/activities/generateSpeech/index.ts +2 -2
  51. package/src/activities/generateTranscription/index.ts +2 -2
  52. package/src/activities/generateVideo/index.ts +29 -15
  53. package/src/index.ts +3 -1
  54. package/src/realtime/event-emitter.ts +46 -0
  55. package/src/realtime/index.ts +2 -0
  56. package/src/realtime/types.ts +9 -0
  57. package/src/types.ts +43 -2
@@ -17,10 +17,10 @@ import {
17
17
  runGenerationFinish,
18
18
  runGenerationStart,
19
19
  runGenerationUsage,
20
- } from '../middleware'
20
+ } from '../middleware/run'
21
21
  import type { InternalLogger } from '../../logger/internal-logger'
22
22
  import type { DebugOption } from '../../logger/types'
23
- import type { GenerationMiddleware } from '../middleware'
23
+ import type { GenerationMiddleware } from '../middleware/types'
24
24
  import type { VideoAdapter } from './adapter'
25
25
  import type {
26
26
  MediaPrompt,
@@ -47,7 +47,7 @@ export const kind = 'video' as const
47
47
  * Extract provider options from a VideoAdapter via ~types.
48
48
  */
49
49
  export type VideoProviderOptions<TAdapter> =
50
- TAdapter extends VideoAdapter<any, any, any, any>
50
+ TAdapter extends VideoAdapter<any, any, any, any, any, any>
51
51
  ? TAdapter['~types']['providerOptions']
52
52
  : object
53
53
 
@@ -55,7 +55,14 @@ export type VideoProviderOptions<TAdapter> =
55
55
  * Extract the size type for a VideoAdapter's model via ~types.
56
56
  */
57
57
  export type VideoSizeForAdapter<TAdapter> =
58
- TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>
58
+ TAdapter extends VideoAdapter<
59
+ infer TModel,
60
+ any,
61
+ any,
62
+ infer TSizeMap,
63
+ any,
64
+ any
65
+ >
59
66
  ? TModel extends keyof TSizeMap
60
67
  ? TSizeMap[TModel]
61
68
  : string
@@ -68,7 +75,14 @@ export type VideoSizeForAdapter<TAdapter> =
68
75
  * without a map fall back to the full MediaPrompt.
69
76
  */
70
77
  export type VideoPromptForAdapter<TAdapter> =
71
- TAdapter extends VideoAdapter<infer TModel, any, any, any, infer ModsByName>
78
+ TAdapter extends VideoAdapter<
79
+ infer TModel,
80
+ any,
81
+ any,
82
+ any,
83
+ infer ModsByName,
84
+ any
85
+ >
72
86
  ? string extends keyof ModsByName
73
87
  ? MediaPrompt
74
88
  : TModel extends keyof ModsByName
@@ -108,7 +122,7 @@ function createId(prefix: string): string {
108
122
  * The model is extracted from the adapter's model property.
109
123
  */
110
124
  interface VideoActivityBaseOptions<
111
- TAdapter extends VideoAdapter<string, any, any, any>,
125
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
112
126
  > {
113
127
  /** The video adapter to use (must be created with a model) */
114
128
  adapter: TAdapter & { kind: typeof kind }
@@ -124,7 +138,7 @@ interface VideoActivityBaseOptions<
124
138
  * @experimental Video generation is an experimental feature and may change.
125
139
  */
126
140
  export type VideoCreateOptions<
127
- TAdapter extends VideoAdapter<string, any, any, any>,
141
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
128
142
  TStream extends boolean = false,
129
143
  > = VideoActivityBaseOptions<TAdapter> & {
130
144
  /** Request type - create a new job (default if not specified) */
@@ -191,7 +205,7 @@ export type VideoCreateOptions<
191
205
  * @experimental Video generation is an experimental feature and may change.
192
206
  */
193
207
  export interface VideoStatusOptions<
194
- TAdapter extends VideoAdapter<string, any, any, any>,
208
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
195
209
  > extends VideoActivityBaseOptions<TAdapter> {
196
210
  /** Request type - get job status */
197
211
  request: 'status'
@@ -205,7 +219,7 @@ export interface VideoStatusOptions<
205
219
  * @experimental Video generation is an experimental feature and may change.
206
220
  */
207
221
  export interface VideoUrlOptions<
208
- TAdapter extends VideoAdapter<string, any, any, any>,
222
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
209
223
  > extends VideoActivityBaseOptions<TAdapter> {
210
224
  /** Request type - get video URL */
211
225
  request: 'url'
@@ -220,7 +234,7 @@ export interface VideoUrlOptions<
220
234
  * @experimental Video generation is an experimental feature and may change.
221
235
  */
222
236
  export type VideoActivityOptions<
223
- TAdapter extends VideoAdapter<string, any, any, any>,
237
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
224
238
  TRequest extends 'create' | 'status' | 'url' = 'create',
225
239
  TStream extends boolean = false,
226
240
  > = TRequest extends 'status'
@@ -296,7 +310,7 @@ export type VideoActivityResult<
296
310
  * ```
297
311
  */
298
312
  export function generateVideo<
299
- TAdapter extends VideoAdapter<string, any, any, any>,
313
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
300
314
  TStream extends boolean = false,
301
315
  >(
302
316
  options: VideoCreateOptions<TAdapter, TStream>,
@@ -314,7 +328,7 @@ export function generateVideo<
314
328
  * Internal implementation of non-streaming video job creation.
315
329
  */
316
330
  async function runCreateVideoJob<
317
- TAdapter extends VideoAdapter<string, any, any, any>,
331
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
318
332
  >(options: VideoCreateOptions<TAdapter, boolean>): Promise<VideoJobResult> {
319
333
  const { adapter, prompt, size, duration, modelOptions, middleware } = options
320
334
  const model = adapter.model
@@ -383,7 +397,7 @@ function sleep(ms: number): Promise<void> {
383
397
  * Handles the full job lifecycle: create job → poll for status → stream updates → yield final result.
384
398
  */
385
399
  async function* runStreamingVideoGeneration<
386
- TAdapter extends VideoAdapter<string, any, any, any>,
400
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
387
401
  >(options: VideoCreateOptions<TAdapter, true>): AsyncIterable<StreamChunk> {
388
402
  const { adapter, prompt, size, duration, modelOptions, middleware } = options
389
403
  const model = adapter.model
@@ -582,7 +596,7 @@ async function* runStreamingVideoGeneration<
582
596
  * ```
583
597
  */
584
598
  export async function getVideoJobStatus<
585
- TAdapter extends VideoAdapter<string, any, any, any>,
599
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
586
600
  >(options: {
587
601
  adapter: TAdapter & { kind: typeof kind }
588
602
  jobId: string
@@ -692,7 +706,7 @@ export async function getVideoJobStatus<
692
706
  * Create typed options for the generateVideo() function without executing.
693
707
  */
694
708
  export function createVideoOptions<
695
- TAdapter extends VideoAdapter<string, any, any, any>,
709
+ TAdapter extends VideoAdapter<string, any, any, any, any, any>,
696
710
  TStream extends boolean = false,
697
711
  >(
698
712
  options: VideoCreateOptions<TAdapter, TStream>,
package/src/index.ts CHANGED
@@ -93,6 +93,7 @@ export { brandProviderTool } from './tools/provider-tool'
93
93
  // Agent loop strategies
94
94
  export {
95
95
  maxIterations,
96
+ maxToolCalls,
96
97
  untilFinishReason,
97
98
  combineStrategies,
98
99
  } from './activities/chat/agent-loop-strategies'
@@ -179,12 +180,13 @@ export { normalizeSystemPrompts } from './system-prompts'
179
180
  export { detectImageMimeType } from './utils'
180
181
 
181
182
  // Realtime
182
- export { realtimeToken } from './realtime/index'
183
+ export { realtimeToken, createRealtimeEventEmitter } from './realtime/index'
183
184
  export type {
184
185
  RealtimeToken,
185
186
  RealtimeTokenAdapter,
186
187
  RealtimeTokenOptions,
187
188
  RealtimeSessionConfig,
189
+ RealtimeToolConfig,
188
190
  VADConfig,
189
191
  RealtimeMessage,
190
192
  RealtimeMessagePart,
@@ -0,0 +1,46 @@
1
+ import type {
2
+ RealtimeEvent,
3
+ RealtimeEventHandler,
4
+ RealtimeEventPayloads,
5
+ } from './types'
6
+
7
+ /**
8
+ * Handlers are stored with a `never` payload so any specific
9
+ * `RealtimeEventHandler<TEvent>` is assignable in (contravariance), keeping the
10
+ * heterogeneous handler map type-safe without `any`. `emit` narrows back to the
11
+ * event's real payload type via its signature; the lone `as never` at the call
12
+ * site is the inverse of that stored `never`.
13
+ */
14
+ type StoredHandler = (payload: never) => void
15
+
16
+ export function createRealtimeEventEmitter() {
17
+ const eventHandlers = new Map<RealtimeEvent, Set<StoredHandler>>()
18
+
19
+ return {
20
+ emit<TEvent extends RealtimeEvent>(
21
+ event: TEvent,
22
+ payload: RealtimeEventPayloads[TEvent],
23
+ ) {
24
+ const handlers = eventHandlers.get(event)
25
+ if (!handlers) return
26
+ for (const handler of handlers) {
27
+ handler(payload as never)
28
+ }
29
+ },
30
+ on<TEvent extends RealtimeEvent>(
31
+ event: TEvent,
32
+ handler: RealtimeEventHandler<TEvent>,
33
+ ): () => void {
34
+ let handlers = eventHandlers.get(event)
35
+ if (!handlers) {
36
+ handlers = new Set<StoredHandler>()
37
+ eventHandlers.set(event, handlers)
38
+ }
39
+ handlers.add(handler)
40
+
41
+ return () => {
42
+ handlers.delete(handler)
43
+ }
44
+ },
45
+ }
46
+ }
@@ -1,5 +1,7 @@
1
1
  import type { RealtimeToken, RealtimeTokenOptions } from './types'
2
2
 
3
+ export { createRealtimeEventEmitter } from './event-emitter'
4
+
3
5
  // Re-export all types
4
6
  export type * from './types'
5
7
 
@@ -4,6 +4,8 @@ import type { AnyClientTool } from '../activities/chat/tools/tool-definition'
4
4
  // Token Types
5
5
  // ============================================================================
6
6
 
7
+ import type { UsageInfo } from '../activities/chat/middleware/types'
8
+
7
9
  /**
8
10
  * Voice activity detection configuration
9
11
  */
@@ -24,6 +26,7 @@ export interface RealtimeToolConfig {
24
26
  name: string
25
27
  description: string
26
28
  inputSchema?: Record<string, any>
29
+ outputSchema?: Record<string, any>
27
30
  }
28
31
 
29
32
  /**
@@ -246,6 +249,8 @@ export type RealtimeEvent =
246
249
  | 'message_complete'
247
250
  | 'interrupted'
248
251
  | 'error'
252
+ | 'go_away' // Event that signals that the current connection will soon be terminated
253
+ | 'usage'
249
254
 
250
255
  /**
251
256
  * Event payloads for realtime events
@@ -263,6 +268,8 @@ export interface RealtimeEventPayloads {
263
268
  message_complete: { message: RealtimeMessage }
264
269
  interrupted: { messageId?: string }
265
270
  error: { error: Error }
271
+ go_away: { timeLeft?: string }
272
+ usage: UsageInfo
266
273
  }
267
274
 
268
275
  /**
@@ -355,6 +362,8 @@ export interface RealtimeConnection {
355
362
  // Session management
356
363
  /** Update session configuration */
357
364
  updateSession: (config: Partial<RealtimeSessionConfig>) => void
365
+ /** Update the ephemeral token (e.g. on refresh); provider may reconnect */
366
+ updateToken?: (token: RealtimeToken) => void
358
367
  /** Interrupt the current response */
359
368
  interrupt: () => void
360
369
 
package/src/types.ts CHANGED
@@ -372,6 +372,15 @@ export interface ToolCallPart<TMetadata = unknown> {
372
372
  id: string
373
373
  name: string
374
374
  arguments: string // JSON string (may be incomplete)
375
+ /**
376
+ * Parsed tool input. Set from the parsed arguments once they are complete
377
+ * (`state: 'input-complete'` and later). `undefined` while the raw
378
+ * `arguments` string is still streaming, and may stay `undefined` for a call
379
+ * that terminates in an error state — the raw `arguments` string is always
380
+ * available as a fallback. Typed per-tool on the client `ToolCallPart` (see
381
+ * `@tanstack/ai-client`); `unknown` on this base type.
382
+ */
383
+ input?: unknown
375
384
  state: ToolCallState
376
385
  /** Approval metadata if tool requires user approval */
377
386
  approval?: {
@@ -822,12 +831,24 @@ export interface ResponseFormat<TData = any> {
822
831
  * State passed to agent loop strategy for determining whether to continue
823
832
  */
824
833
  export interface AgentLoopState {
825
- /** Current iteration count (0-indexed) */
834
+ /** Current iteration count (0-indexed). One iteration = one model turn. */
826
835
  iterationCount: number
827
836
  /** Current messages array */
828
837
  messages: Array<ModelMessage>
829
838
  /** Finish reason from the last response */
830
839
  finishReason: string | null
840
+ /**
841
+ * Cumulative tool calls counted so far in this run (model-emitted during the
842
+ * agent loop, including ones skipped by `maxToolCallsPerTurn`, and pending
843
+ * tools from the inbound message list when resumed). Not a recount of full
844
+ * message history; not model turns.
845
+ */
846
+ toolCallCount: number
847
+ /**
848
+ * Tool calls in the most recent budgeted batch — a live model turn or a
849
+ * pending/resume batch (0 when the last phase produced no tool calls).
850
+ */
851
+ lastTurnToolCallCount: number
831
852
  }
832
853
 
833
854
  /**
@@ -838,8 +859,10 @@ export interface AgentLoopState {
838
859
  *
839
860
  * @example
840
861
  * ```typescript
841
- * // Continue for up to 5 iterations
862
+ * // Continue for up to 5 iterations (model turns, not tool calls)
842
863
  * const strategy: AgentLoopStrategy = ({ iterationCount }) => iterationCount < 5;
864
+ * // Cap total tool calls across the run
865
+ * const byTools: AgentLoopStrategy = ({ toolCallCount }) => toolCallCount < 20;
843
866
  * ```
844
867
  */
845
868
  export type AgentLoopStrategy = (state: AgentLoopState) => boolean
@@ -876,6 +899,24 @@ export interface TextOptions<
876
899
  */
877
900
  systemPrompts?: Array<SystemPrompt>
878
901
  agentLoopStrategy?: AgentLoopStrategy
902
+ /**
903
+ * Maximum number of tool calls to **execute** from a single model turn (or
904
+ * pending/resume batch). `0` skips all execution for that batch.
905
+ *
906
+ * Models can emit many parallel tool calls in one turn. `agentLoopStrategy`
907
+ * (including `maxIterations` / `maxToolCalls`) is only evaluated between
908
+ * turns, so without this cap a single runaway turn can still execute an
909
+ * unbounded fan-out.
910
+ *
911
+ * When set, only the first `maxToolCallsPerTurn` calls are executed; the
912
+ * remainder receive error tool results so the message history stays
913
+ * consistent. Unset means no per-turn execution cap. Must be a non-negative
914
+ * finite number when set.
915
+ *
916
+ * Pair with the `maxToolCalls(n)` strategy for a cumulative **emitted**-call
917
+ * budget across the run (skipped calls still count toward that budget).
918
+ */
919
+ maxToolCallsPerTurn?: number
879
920
  /**
880
921
  * Optional configuration for lazy-tool discovery (tools marked `lazy: true`).
881
922
  * Tunes how much of each lazy tool's description appears in the discovery