@tanstack/ai 0.5.0 → 0.6.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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/activities/chat/index.js +47 -6
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/stream/processor.d.ts +3 -0
  5. package/dist/esm/activities/chat/stream/processor.js +10 -4
  6. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  7. package/dist/esm/activities/chat/tools/tool-calls.d.ts +5 -3
  8. package/dist/esm/activities/chat/tools/tool-calls.js +57 -3
  9. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  10. package/dist/esm/activities/chat/tools/tool-definition.d.ts +2 -2
  11. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  12. package/dist/esm/activities/generateImage/adapter.d.ts +8 -2
  13. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  14. package/dist/esm/activities/generateImage/index.d.ts +8 -6
  15. package/dist/esm/activities/generateImage/index.js.map +1 -1
  16. package/dist/esm/activities/generateVideo/adapter.d.ts +11 -5
  17. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  18. package/dist/esm/activities/generateVideo/index.d.ts +20 -14
  19. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  20. package/dist/esm/extend-adapter.d.ts +114 -0
  21. package/dist/esm/extend-adapter.js +15 -0
  22. package/dist/esm/extend-adapter.js.map +1 -0
  23. package/dist/esm/index.d.ts +2 -0
  24. package/dist/esm/index.js +3 -0
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/esm/types.d.ts +35 -8
  27. package/package.json +1 -1
  28. package/src/activities/chat/index.ts +75 -4
  29. package/src/activities/chat/stream/processor.ts +22 -4
  30. package/src/activities/chat/tools/tool-calls.ts +88 -5
  31. package/src/activities/chat/tools/tool-definition.ts +3 -0
  32. package/src/activities/generateImage/adapter.ts +9 -2
  33. package/src/activities/generateImage/index.ts +19 -9
  34. package/src/activities/generateVideo/adapter.ts +19 -4
  35. package/src/activities/generateVideo/index.ts +30 -16
  36. package/src/extend-adapter.ts +182 -0
  37. package/src/index.ts +4 -0
  38. package/src/types.ts +36 -6
@@ -24,6 +24,7 @@ import type { AnyTextAdapter } from './adapter'
24
24
  import type {
25
25
  AgentLoopStrategy,
26
26
  ConstrainedModelMessage,
27
+ CustomEvent,
27
28
  InferSchemaType,
28
29
  ModelMessage,
29
30
  RunFinishedEvent,
@@ -601,17 +602,30 @@ class TextEngine<
601
602
 
602
603
  const { approvals, clientToolResults } = this.collectClientState()
603
604
 
604
- const executionResult = await executeToolCalls(
605
+ const generator = executeToolCalls(
605
606
  pendingToolCalls,
606
607
  this.tools,
607
608
  approvals,
608
609
  clientToolResults,
610
+ (eventName, data) => this.createCustomEventChunk(eventName, data),
609
611
  )
610
612
 
613
+ // Consume the async generator, yielding custom events and collecting the return value
614
+ const executionResult = yield* this.drainToolCallGenerator(generator)
615
+
611
616
  if (
612
617
  executionResult.needsApproval.length > 0 ||
613
618
  executionResult.needsClientExecution.length > 0
614
619
  ) {
620
+ if (executionResult.results.length > 0) {
621
+ for (const chunk of this.emitToolResults(
622
+ executionResult.results,
623
+ finishEvent,
624
+ )) {
625
+ yield chunk
626
+ }
627
+ }
628
+
615
629
  for (const chunk of this.emitApprovalRequests(
616
630
  executionResult.needsApproval,
617
631
  finishEvent,
@@ -660,17 +674,30 @@ class TextEngine<
660
674
 
661
675
  const { approvals, clientToolResults } = this.collectClientState()
662
676
 
663
- const executionResult = await executeToolCalls(
677
+ const generator = executeToolCalls(
664
678
  toolCalls,
665
679
  this.tools,
666
680
  approvals,
667
681
  clientToolResults,
682
+ (eventName, data) => this.createCustomEventChunk(eventName, data),
668
683
  )
669
684
 
685
+ // Consume the async generator, yielding custom events and collecting the return value
686
+ const executionResult = yield* this.drainToolCallGenerator(generator)
687
+
670
688
  if (
671
689
  executionResult.needsApproval.length > 0 ||
672
690
  executionResult.needsClientExecution.length > 0
673
691
  ) {
692
+ if (executionResult.results.length > 0) {
693
+ for (const chunk of this.emitToolResults(
694
+ executionResult.results,
695
+ finishEvent,
696
+ )) {
697
+ yield chunk
698
+ }
699
+ }
700
+
674
701
  for (const chunk of this.emitApprovalRequests(
675
702
  executionResult.needsApproval,
676
703
  finishEvent,
@@ -833,7 +860,7 @@ class TextEngine<
833
860
  timestamp: Date.now(),
834
861
  model: finishEvent.model,
835
862
  name: 'approval-requested',
836
- data: {
863
+ value: {
837
864
  toolCallId: approval.toolCallId,
838
865
  toolName: approval.toolName,
839
866
  input: approval.input,
@@ -870,7 +897,7 @@ class TextEngine<
870
897
  timestamp: Date.now(),
871
898
  model: finishEvent.model,
872
899
  name: 'tool-input-available',
873
- data: {
900
+ value: {
874
901
  toolCallId: clientTool.toolCallId,
875
902
  toolName: clientTool.toolName,
876
903
  input: clientTool.input,
@@ -1052,6 +1079,50 @@ class TextEngine<
1052
1079
  }
1053
1080
  }
1054
1081
 
1082
+ /**
1083
+ * Drain an executeToolCalls async generator, yielding any CustomEvent chunks
1084
+ * and returning the final ExecuteToolCallsResult.
1085
+ */
1086
+ private async *drainToolCallGenerator(
1087
+ generator: AsyncGenerator<
1088
+ CustomEvent,
1089
+ {
1090
+ results: Array<ToolResult>
1091
+ needsApproval: Array<ApprovalRequest>
1092
+ needsClientExecution: Array<ClientToolRequest>
1093
+ },
1094
+ void
1095
+ >,
1096
+ ): AsyncGenerator<
1097
+ StreamChunk,
1098
+ {
1099
+ results: Array<ToolResult>
1100
+ needsApproval: Array<ApprovalRequest>
1101
+ needsClientExecution: Array<ClientToolRequest>
1102
+ },
1103
+ void
1104
+ > {
1105
+ let next = await generator.next()
1106
+ while (!next.done) {
1107
+ yield next.value
1108
+ next = await generator.next()
1109
+ }
1110
+ return next.value
1111
+ }
1112
+
1113
+ private createCustomEventChunk(
1114
+ eventName: string,
1115
+ value: Record<string, any>,
1116
+ ): CustomEvent {
1117
+ return {
1118
+ type: 'CUSTOM',
1119
+ timestamp: Date.now(),
1120
+ model: this.params.model,
1121
+ name: eventName,
1122
+ value,
1123
+ }
1124
+ }
1125
+
1055
1126
  private createId(prefix: string): string {
1056
1127
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
1057
1128
  }
@@ -73,6 +73,13 @@ export interface StreamProcessorEvents {
73
73
  approvalId: string
74
74
  }) => void
75
75
 
76
+ // Custom events from server-side tools
77
+ onCustomEvent?: (
78
+ eventType: string,
79
+ data: unknown,
80
+ context: { toolCallId?: string },
81
+ ) => void
82
+
76
83
  // Granular events for UI optimization (character-by-character, state tracking)
77
84
  onTextUpdate?: (messageId: string, content: string) => void
78
85
  onToolCallStateChange?: (
@@ -807,8 +814,8 @@ export class StreamProcessor {
807
814
  chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,
808
815
  ): void {
809
816
  // Handle client tool input availability - trigger client-side execution
810
- if (chunk.name === 'tool-input-available' && chunk.data) {
811
- const { toolCallId, toolName, input } = chunk.data as {
817
+ if (chunk.name === 'tool-input-available' && chunk.value) {
818
+ const { toolCallId, toolName, input } = chunk.value as {
812
819
  toolCallId: string
813
820
  toolName: string
814
821
  input: any
@@ -820,11 +827,12 @@ export class StreamProcessor {
820
827
  toolName,
821
828
  input,
822
829
  })
830
+ return
823
831
  }
824
832
 
825
833
  // Handle approval requests
826
- if (chunk.name === 'approval-requested' && chunk.data) {
827
- const { toolCallId, toolName, input, approval } = chunk.data as {
834
+ if (chunk.name === 'approval-requested' && chunk.value) {
835
+ const { toolCallId, toolName, input, approval } = chunk.value as {
828
836
  toolCallId: string
829
837
  toolName: string
830
838
  input: any
@@ -849,6 +857,16 @@ export class StreamProcessor {
849
857
  input,
850
858
  approvalId: approval.id,
851
859
  })
860
+ return
861
+ }
862
+
863
+ // Forward non-system custom events to onCustomEvent callback
864
+ if (this.events.onCustomEvent) {
865
+ const toolCallId =
866
+ chunk.value && typeof chunk.value === 'object'
867
+ ? (chunk.value as any).toolCallId
868
+ : undefined
869
+ this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })
852
870
  }
853
871
  }
854
872
 
@@ -1,5 +1,6 @@
1
1
  import { isStandardSchema, parseWithStandardSchema } from './schema-converter'
2
2
  import type {
3
+ CustomEvent,
3
4
  ModelMessage,
4
5
  RunFinishedEvent,
5
6
  Tool,
@@ -7,6 +8,7 @@ import type {
7
8
  ToolCallArgsEvent,
8
9
  ToolCallEndEvent,
9
10
  ToolCallStartEvent,
11
+ ToolExecutionContext,
10
12
  } from '../../../types'
11
13
 
12
14
  /**
@@ -250,7 +252,46 @@ interface ExecuteToolCallsResult {
250
252
  }
251
253
 
252
254
  /**
253
- * Execute tool calls based on their configuration
255
+ * Helper that runs a tool execution promise while polling for pending custom events.
256
+ * Yields any custom events that are emitted during execution, then returns the
257
+ * execution result.
258
+ */
259
+ async function* executeWithEventPolling<T>(
260
+ executionPromise: Promise<T>,
261
+ pendingEvents: Array<CustomEvent>,
262
+ ): AsyncGenerator<CustomEvent, T, void> {
263
+ // Use an object to track mutable state across the async boundary
264
+ const state = { done: false, result: undefined as T }
265
+ const executionWithFlag = executionPromise.then((r) => {
266
+ state.done = true
267
+ state.result = r
268
+ return r
269
+ })
270
+
271
+ while (!state.done) {
272
+ // Wait for either the execution to complete or a short timeout
273
+ await Promise.race([
274
+ executionWithFlag,
275
+ new Promise((resolve) => setTimeout(resolve, 10)),
276
+ ])
277
+
278
+ // Flush any pending events
279
+ while (pendingEvents.length > 0) {
280
+ yield pendingEvents.shift()!
281
+ }
282
+ }
283
+
284
+ // Final flush in case events were emitted right at completion
285
+ while (pendingEvents.length > 0) {
286
+ yield pendingEvents.shift()!
287
+ }
288
+
289
+ return state.result
290
+ }
291
+
292
+ /**
293
+ * Execute tool calls based on their configuration.
294
+ * Yields CustomEvent chunks during tool execution for real-time progress updates.
254
295
  *
255
296
  * Handles three cases:
256
297
  * 1. Client tools (no execute) - request client to execute
@@ -261,13 +302,18 @@ interface ExecuteToolCallsResult {
261
302
  * @param tools - Available tools with their configurations
262
303
  * @param approvals - Map of approval decisions (approval.id -> approved boolean)
263
304
  * @param clientResults - Map of client-side execution results (toolCallId -> result)
305
+ * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)
264
306
  */
265
- export async function executeToolCalls(
307
+ export async function* executeToolCalls(
266
308
  toolCalls: Array<ToolCall>,
267
309
  tools: ReadonlyArray<Tool>,
268
310
  approvals: Map<string, boolean> = new Map(),
269
311
  clientResults: Map<string, any> = new Map(),
270
- ): Promise<ExecuteToolCallsResult> {
312
+ createCustomEventChunk?: (
313
+ eventName: string,
314
+ value: Record<string, any>,
315
+ ) => CustomEvent,
316
+ ): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {
271
317
  const results: Array<ToolResult> = []
272
318
  const needsApproval: Array<ApprovalRequest> = []
273
319
  const needsClientExecution: Array<ClientToolRequest> = []
@@ -326,6 +372,29 @@ export async function executeToolCalls(
326
372
  }
327
373
  }
328
374
 
375
+ // Create a ToolExecutionContext for this tool call with event emission
376
+ const pendingEvents: Array<CustomEvent> = []
377
+ const context: ToolExecutionContext = {
378
+ toolCallId: toolCall.id,
379
+ emitCustomEvent: (eventName: string, value: Record<string, any>) => {
380
+ if (createCustomEventChunk) {
381
+ pendingEvents.push(
382
+ createCustomEventChunk(eventName, {
383
+ ...value,
384
+ toolCallId: toolCall.id,
385
+ }),
386
+ )
387
+ }
388
+ },
389
+ }
390
+
391
+ // Helper to flush any pending events
392
+ function* flushEvents(): Generator<CustomEvent> {
393
+ while (pendingEvents.length > 0) {
394
+ yield pendingEvents.shift()!
395
+ }
396
+ }
397
+
329
398
  // CASE 1: Client-side tool (no execute function)
330
399
  if (!tool.execute) {
331
400
  // Check if tool needs approval
@@ -402,8 +471,15 @@ export async function executeToolCalls(
402
471
  // Execute after approval
403
472
  const startTime = Date.now()
404
473
  try {
405
- let result = await tool.execute(input)
474
+ const executionPromise = Promise.resolve(
475
+ tool.execute(input, context),
476
+ )
477
+ let result = yield* executeWithEventPolling(
478
+ executionPromise,
479
+ pendingEvents,
480
+ )
406
481
  const duration = Date.now() - startTime
482
+ yield* flushEvents()
407
483
 
408
484
  // Validate output against outputSchema if provided (for Standard Schema compliant schemas)
409
485
  if (
@@ -426,6 +502,7 @@ export async function executeToolCalls(
426
502
  })
427
503
  } catch (error: unknown) {
428
504
  const duration = Date.now() - startTime
505
+ yield* flushEvents()
429
506
  const message =
430
507
  error instanceof Error ? error.message : 'Unknown error'
431
508
  results.push({
@@ -460,8 +537,13 @@ export async function executeToolCalls(
460
537
  // CASE 3: Normal server tool - execute immediately
461
538
  const startTime = Date.now()
462
539
  try {
463
- let result = await tool.execute(input)
540
+ const executionPromise = Promise.resolve(tool.execute(input, context))
541
+ let result = yield* executeWithEventPolling(
542
+ executionPromise,
543
+ pendingEvents,
544
+ )
464
545
  const duration = Date.now() - startTime
546
+ yield* flushEvents()
465
547
 
466
548
  // Validate output against outputSchema if provided (for Standard Schema compliant schemas)
467
549
  if (
@@ -482,6 +564,7 @@ export async function executeToolCalls(
482
564
  })
483
565
  } catch (error: unknown) {
484
566
  const duration = Date.now() - startTime
567
+ yield* flushEvents()
485
568
  const message = error instanceof Error ? error.message : 'Unknown error'
486
569
  results.push({
487
570
  toolCallId: toolCall.id,
@@ -4,6 +4,7 @@ import type {
4
4
  JSONSchema,
5
5
  SchemaInput,
6
6
  Tool,
7
+ ToolExecutionContext,
7
8
  } from '../../../types'
8
9
 
9
10
  /**
@@ -112,6 +113,7 @@ export interface ToolDefinition<
112
113
  server: (
113
114
  execute: (
114
115
  args: InferSchemaType<TInput>,
116
+ context?: ToolExecutionContext,
115
117
  ) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
116
118
  ) => ServerTool<TInput, TOutput, TName>
117
119
 
@@ -193,6 +195,7 @@ export function toolDefinition<
193
195
  server(
194
196
  execute: (
195
197
  args: InferSchemaType<TInput>,
198
+ context?: ToolExecutionContext,
196
199
  ) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
197
200
  ): ServerTool<TInput, TOutput, TName> {
198
201
  return {
@@ -1,5 +1,12 @@
1
1
  import type { ImageGenerationOptions, ImageGenerationResult } from '../../types'
2
2
 
3
+ /**
4
+ * Resolve the size type for a model from the model-size map.
5
+ * If the map has an index signature (i.e. no explicit keys), falls back to string.
6
+ * If the model is an explicit key, uses its mapped size type.
7
+ * Otherwise falls back to string.
8
+ */
9
+
3
10
  /**
4
11
  * Configuration for image adapter instances
5
12
  */
@@ -49,7 +56,7 @@ export interface ImageAdapter<
49
56
  * Generate images from a prompt
50
57
  */
51
58
  generateImages: (
52
- options: ImageGenerationOptions<TProviderOptions>,
59
+ options: ImageGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
53
60
  ) => Promise<ImageGenerationResult>
54
61
  }
55
62
 
@@ -95,7 +102,7 @@ export abstract class BaseImageAdapter<
95
102
  }
96
103
 
97
104
  abstract generateImages(
98
- options: ImageGenerationOptions<TProviderOptions>,
105
+ options: ImageGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
99
106
  ): Promise<ImageGenerationResult>
100
107
 
101
108
  protected generateId(): string {
@@ -61,9 +61,9 @@ export type ImageSizeForModel<TAdapter, TModel extends string> =
61
61
  *
62
62
  * @template TAdapter - The image adapter type
63
63
  */
64
- export interface ImageActivityOptions<
65
- TAdapter extends ImageAdapter<string, object, any, any>,
66
- > {
64
+ export type ImageActivityOptions<
65
+ TAdapter extends ImageAdapter<string, any, any, any>,
66
+ > = {
67
67
  /** The image adapter to use (must be created with a model) */
68
68
  adapter: TAdapter & { kind: typeof kind }
69
69
  /** Text description of the desired image(s) */
@@ -72,9 +72,19 @@ export interface ImageActivityOptions<
72
72
  numberOfImages?: number
73
73
  /** Image size in WIDTHxHEIGHT format (e.g., "1024x1024") */
74
74
  size?: ImageSizeForModel<TAdapter, TAdapter['model']>
75
- /** Provider-specific options for image generation */
76
- modelOptions?: ImageProviderOptionsForModel<TAdapter, TAdapter['model']>
77
- }
75
+ } & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']>
76
+ ? {
77
+ /** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<
78
+ TAdapter,
79
+ TAdapter['model']
80
+ >
81
+ }
82
+ : {
83
+ /** Provider-specific options for image generation */ modelOptions: ImageProviderOptionsForModel<
84
+ TAdapter,
85
+ TAdapter['model']
86
+ >
87
+ })
78
88
 
79
89
  // ===========================
80
90
  // Activity Result Type
@@ -137,7 +147,7 @@ function createId(prefix: string): string {
137
147
  * ```
138
148
  */
139
149
  export async function generateImage<
140
- TAdapter extends ImageAdapter<string, object, any, any>,
150
+ TAdapter extends ImageAdapter<string, any, any, any>,
141
151
  >(options: ImageActivityOptions<TAdapter>): ImageActivityResult {
142
152
  const { adapter, ...rest } = options
143
153
  const model = adapter.model
@@ -150,7 +160,7 @@ export async function generateImage<
150
160
  model,
151
161
  prompt: rest.prompt,
152
162
  numberOfImages: rest.numberOfImages,
153
- size: rest.size as string | undefined,
163
+ size: rest.size,
154
164
  modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
155
165
  timestamp: startTime,
156
166
  })
@@ -193,7 +203,7 @@ export async function generateImage<
193
203
  * Create typed options for the generateImage() function without executing.
194
204
  */
195
205
  export function createImageOptions<
196
- TAdapter extends ImageAdapter<string, object, any, any>,
206
+ TAdapter extends ImageAdapter<string, any, any, any>,
197
207
  >(options: ImageActivityOptions<TAdapter>): ImageActivityOptions<TAdapter> {
198
208
  return options
199
209
  }
@@ -29,10 +29,14 @@ export interface VideoAdapterConfig {
29
29
  * Generic parameters:
30
30
  * - TModel: The specific model name (e.g., 'sora-2')
31
31
  * - TProviderOptions: Provider-specific options (already resolved)
32
+ * - TModelProviderOptionsByName: Map from model name to its specific provider options
33
+ * - TModelSizeByName: Map from model name to its supported sizes
32
34
  */
33
35
  export interface VideoAdapter<
34
36
  TModel extends string = string,
35
37
  TProviderOptions extends object = Record<string, unknown>,
38
+ TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,
39
+ TModelSizeByName extends Record<string, string> = Record<string, string>,
36
40
  > {
37
41
  /** Discriminator for adapter kind - used to determine API shape */
38
42
  readonly kind: 'video'
@@ -46,6 +50,8 @@ export interface VideoAdapter<
46
50
  */
47
51
  '~types': {
48
52
  providerOptions: TProviderOptions
53
+ modelProviderOptionsByName: TModelProviderOptionsByName
54
+ modelSizeByName: TModelSizeByName
49
55
  }
50
56
 
51
57
  /**
@@ -53,7 +59,7 @@ export interface VideoAdapter<
53
59
  * Returns a job ID that can be used to poll for status and retrieve the video.
54
60
  */
55
61
  createVideoJob: (
56
- options: VideoGenerationOptions<TProviderOptions>,
62
+ options: VideoGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
57
63
  ) => Promise<VideoJobResult>
58
64
 
59
65
  /**
@@ -72,7 +78,7 @@ export interface VideoAdapter<
72
78
  * A VideoAdapter with any/unknown type parameters.
73
79
  * Useful as a constraint in generic functions and interfaces.
74
80
  */
75
- export type AnyVideoAdapter = VideoAdapter<any, any>
81
+ export type AnyVideoAdapter = VideoAdapter<any, any, any, any>
76
82
 
77
83
  /**
78
84
  * Abstract base class for video generation adapters.
@@ -85,7 +91,14 @@ export type AnyVideoAdapter = VideoAdapter<any, any>
85
91
  export abstract class BaseVideoAdapter<
86
92
  TModel extends string = string,
87
93
  TProviderOptions extends object = Record<string, unknown>,
88
- > implements VideoAdapter<TModel, TProviderOptions> {
94
+ TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,
95
+ TModelSizeByName extends Record<string, string> = Record<string, string>,
96
+ > implements VideoAdapter<
97
+ TModel,
98
+ TProviderOptions,
99
+ TModelProviderOptionsByName,
100
+ TModelSizeByName
101
+ > {
89
102
  readonly kind = 'video' as const
90
103
  abstract readonly name: string
91
104
  readonly model: TModel
@@ -93,6 +106,8 @@ export abstract class BaseVideoAdapter<
93
106
  // Type-only property - never assigned at runtime
94
107
  declare '~types': {
95
108
  providerOptions: TProviderOptions
109
+ modelProviderOptionsByName: TModelProviderOptionsByName
110
+ modelSizeByName: TModelSizeByName
96
111
  }
97
112
 
98
113
  protected config: VideoAdapterConfig
@@ -103,7 +118,7 @@ export abstract class BaseVideoAdapter<
103
118
  }
104
119
 
105
120
  abstract createVideoJob(
106
- options: VideoGenerationOptions<TProviderOptions>,
121
+ options: VideoGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
107
122
  ): Promise<VideoJobResult>
108
123
 
109
124
  abstract getVideoStatus(jobId: string): Promise<VideoStatusResult>
@@ -30,10 +30,20 @@ export const kind = 'video' as const
30
30
  * Extract provider options from a VideoAdapter via ~types.
31
31
  */
32
32
  export type VideoProviderOptions<TAdapter> =
33
- TAdapter extends VideoAdapter<any, any>
33
+ TAdapter extends VideoAdapter<any, any, any, any>
34
34
  ? TAdapter['~types']['providerOptions']
35
35
  : object
36
36
 
37
+ /**
38
+ * Extract the size type for a VideoAdapter's model via ~types.
39
+ */
40
+ export type VideoSizeForAdapter<TAdapter> =
41
+ TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>
42
+ ? TModel extends keyof TSizeMap
43
+ ? TSizeMap[TModel]
44
+ : string
45
+ : string
46
+
37
47
  // ===========================
38
48
  // Activity Options Types
39
49
 
@@ -47,7 +57,7 @@ function createId(prefix: string): string {
47
57
  * The model is extracted from the adapter's model property.
48
58
  */
49
59
  interface VideoActivityBaseOptions<
50
- TAdapter extends VideoAdapter<string, object>,
60
+ TAdapter extends VideoAdapter<string, any, any, any>,
51
61
  > {
52
62
  /** The video adapter to use (must be created with a model) */
53
63
  adapter: TAdapter & { kind: typeof kind }
@@ -59,20 +69,24 @@ interface VideoActivityBaseOptions<
59
69
  *
60
70
  * @experimental Video generation is an experimental feature and may change.
61
71
  */
62
- export interface VideoCreateOptions<
63
- TAdapter extends VideoAdapter<string, object>,
64
- > extends VideoActivityBaseOptions<TAdapter> {
72
+ export type VideoCreateOptions<
73
+ TAdapter extends VideoAdapter<string, any, any, any>,
74
+ > = VideoActivityBaseOptions<TAdapter> & {
65
75
  /** Request type - create a new job (default if not specified) */
66
76
  request?: 'create'
67
77
  /** Text description of the desired video */
68
78
  prompt: string
69
- /** Video size in WIDTHxHEIGHT format (e.g., "1280x720") */
70
- size?: string
79
+ /** Video size — format depends on the provider (e.g., "16:9", "1280x720") */
80
+ size?: VideoSizeForAdapter<TAdapter>
71
81
  /** Video duration in seconds */
72
82
  duration?: number
73
- /** Provider-specific options for video generation */
74
- modelOptions?: VideoProviderOptions<TAdapter>
75
- }
83
+ } & ({} extends VideoProviderOptions<TAdapter>
84
+ ? {
85
+ /** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>
86
+ }
87
+ : {
88
+ /** Provider-specific options for video generation */ modelOptions: VideoProviderOptions<TAdapter>
89
+ })
76
90
 
77
91
  /**
78
92
  * Options for polling the status of a video generation job.
@@ -80,7 +94,7 @@ export interface VideoCreateOptions<
80
94
  * @experimental Video generation is an experimental feature and may change.
81
95
  */
82
96
  export interface VideoStatusOptions<
83
- TAdapter extends VideoAdapter<string, object>,
97
+ TAdapter extends VideoAdapter<string, any, any, any>,
84
98
  > extends VideoActivityBaseOptions<TAdapter> {
85
99
  /** Request type - get job status */
86
100
  request: 'status'
@@ -94,7 +108,7 @@ export interface VideoStatusOptions<
94
108
  * @experimental Video generation is an experimental feature and may change.
95
109
  */
96
110
  export interface VideoUrlOptions<
97
- TAdapter extends VideoAdapter<string, object>,
111
+ TAdapter extends VideoAdapter<string, any, any, any>,
98
112
  > extends VideoActivityBaseOptions<TAdapter> {
99
113
  /** Request type - get video URL */
100
114
  request: 'url'
@@ -109,7 +123,7 @@ export interface VideoUrlOptions<
109
123
  * @experimental Video generation is an experimental feature and may change.
110
124
  */
111
125
  export type VideoActivityOptions<
112
- TAdapter extends VideoAdapter<string, object>,
126
+ TAdapter extends VideoAdapter<string, any, any, any>,
113
127
  TRequest extends 'create' | 'status' | 'url' = 'create',
114
128
  > = TRequest extends 'status'
115
129
  ? VideoStatusOptions<TAdapter>
@@ -161,7 +175,7 @@ export type VideoActivityResult<
161
175
  * ```
162
176
  */
163
177
  export async function generateVideo<
164
- TAdapter extends VideoAdapter<string, object>,
178
+ TAdapter extends VideoAdapter<string, any, any, any>,
165
179
  >(options: VideoCreateOptions<TAdapter>): Promise<VideoJobResult> {
166
180
  const { adapter, prompt, size, duration, modelOptions } = options
167
181
  const model = adapter.model
@@ -201,7 +215,7 @@ export async function generateVideo<
201
215
  * ```
202
216
  */
203
217
  export async function getVideoJobStatus<
204
- TAdapter extends VideoAdapter<string, object>,
218
+ TAdapter extends VideoAdapter<string, any, any, any>,
205
219
  >(options: {
206
220
  adapter: TAdapter & { kind: typeof kind }
207
221
  jobId: string
@@ -301,7 +315,7 @@ export async function getVideoJobStatus<
301
315
  * Create typed options for the generateVideo() function without executing.
302
316
  */
303
317
  export function createVideoOptions<
304
- TAdapter extends VideoAdapter<string, object>,
318
+ TAdapter extends VideoAdapter<string, any, any, any>,
305
319
  >(options: VideoCreateOptions<TAdapter>): VideoCreateOptions<TAdapter> {
306
320
  return options
307
321
  }