@tanstack/ai 0.31.0 → 0.33.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 (83) hide show
  1. package/dist/esm/activities/chat/index.js +24 -3
  2. package/dist/esm/activities/chat/index.js.map +1 -1
  3. package/dist/esm/activities/chat/middleware/types.d.ts +7 -0
  4. package/dist/esm/activities/chat/tools/lazy-tool-manager.d.ts +25 -1
  5. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +26 -2
  6. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  7. package/dist/esm/activities/generateAudio/index.d.ts +7 -0
  8. package/dist/esm/activities/generateAudio/index.js +26 -1
  9. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  10. package/dist/esm/activities/generateImage/adapter.d.ts +8 -4
  11. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  12. package/dist/esm/activities/generateImage/index.d.ts +26 -3
  13. package/dist/esm/activities/generateImage/index.js +38 -2
  14. package/dist/esm/activities/generateImage/index.js.map +1 -1
  15. package/dist/esm/activities/generateSpeech/index.d.ts +7 -0
  16. package/dist/esm/activities/generateSpeech/index.js +26 -1
  17. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  18. package/dist/esm/activities/generateTranscription/index.d.ts +7 -0
  19. package/dist/esm/activities/generateTranscription/index.js +26 -1
  20. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  21. package/dist/esm/activities/generateVideo/adapter.d.ts +65 -6
  22. package/dist/esm/activities/generateVideo/adapter.js +14 -0
  23. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  24. package/dist/esm/activities/generateVideo/index.d.ts +40 -5
  25. package/dist/esm/activities/generateVideo/index.js +52 -2
  26. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  27. package/dist/esm/activities/generateVideo/snap.d.ts +14 -0
  28. package/dist/esm/activities/generateVideo/snap.js +54 -0
  29. package/dist/esm/activities/generateVideo/snap.js.map +1 -0
  30. package/dist/esm/activities/index.d.ts +3 -2
  31. package/dist/esm/activities/index.js +2 -0
  32. package/dist/esm/activities/index.js.map +1 -1
  33. package/dist/esm/activities/middleware/index.d.ts +2 -0
  34. package/dist/esm/activities/middleware/run.d.ts +20 -0
  35. package/dist/esm/activities/middleware/run.js +42 -0
  36. package/dist/esm/activities/middleware/run.js.map +1 -0
  37. package/dist/esm/activities/middleware/types.d.ts +118 -0
  38. package/dist/esm/client.d.ts +1 -1
  39. package/dist/esm/client.js.map +1 -1
  40. package/dist/esm/index.d.ts +4 -0
  41. package/dist/esm/index.js +4 -0
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/middlewares/otel.d.ts +8 -2
  44. package/dist/esm/middlewares/otel.js +145 -95
  45. package/dist/esm/middlewares/otel.js.map +1 -1
  46. package/dist/esm/middlewares/usage-attributes.d.ts +24 -0
  47. package/dist/esm/middlewares/usage-attributes.js +43 -0
  48. package/dist/esm/middlewares/usage-attributes.js.map +1 -0
  49. package/dist/esm/types.d.ts +103 -14
  50. package/dist/esm/utilities/errors.d.ts +13 -0
  51. package/dist/esm/utilities/errors.js +22 -0
  52. package/dist/esm/utilities/errors.js.map +1 -0
  53. package/dist/esm/utilities/media-prompt.d.ts +35 -0
  54. package/dist/esm/utilities/media-prompt.js +43 -0
  55. package/dist/esm/utilities/media-prompt.js.map +1 -0
  56. package/dist/esm/utilities/numbers.d.ts +8 -0
  57. package/dist/esm/utilities/numbers.js +12 -0
  58. package/dist/esm/utilities/numbers.js.map +1 -0
  59. package/package.json +2 -2
  60. package/skills/ai-core/media-generation/SKILL.md +173 -3
  61. package/src/activities/chat/index.ts +32 -4
  62. package/src/activities/chat/middleware/types.ts +7 -0
  63. package/src/activities/chat/tools/lazy-tool-manager.ts +46 -4
  64. package/src/activities/generateAudio/index.ts +42 -1
  65. package/src/activities/generateImage/adapter.ts +16 -3
  66. package/src/activities/generateImage/index.ts +90 -5
  67. package/src/activities/generateSpeech/index.ts +42 -1
  68. package/src/activities/generateTranscription/index.ts +42 -1
  69. package/src/activities/generateVideo/adapter.ts +80 -4
  70. package/src/activities/generateVideo/index.ts +141 -6
  71. package/src/activities/generateVideo/snap.ts +100 -0
  72. package/src/activities/index.ts +4 -0
  73. package/src/activities/middleware/index.ts +20 -0
  74. package/src/activities/middleware/run.ts +88 -0
  75. package/src/activities/middleware/types.ts +173 -0
  76. package/src/client.ts +4 -0
  77. package/src/index.ts +23 -0
  78. package/src/middlewares/otel.ts +195 -120
  79. package/src/middlewares/usage-attributes.ts +65 -0
  80. package/src/types.ts +126 -13
  81. package/src/utilities/errors.ts +29 -0
  82. package/src/utilities/media-prompt.ts +86 -0
  83. package/src/utilities/numbers.ts +15 -0
@@ -8,10 +8,24 @@
8
8
  import { aiEventClient } from '@tanstack/ai-event-client'
9
9
  import { streamGenerationResult } from '../stream-generation-result.js'
10
10
  import { resolveDebugOption } from '../../logger/resolve'
11
+ import {
12
+ createGenerationContext,
13
+ runGenerationError,
14
+ runGenerationFinish,
15
+ runGenerationStart,
16
+ runGenerationUsage,
17
+ } from '../middleware'
18
+ import { resolveMediaPrompt } from '../../utilities/media-prompt'
11
19
  import type { InternalLogger } from '../../logger/internal-logger'
12
20
  import type { DebugOption } from '../../logger/types'
21
+ import type { GenerationMiddleware } from '../middleware'
13
22
  import type { ImageAdapter } from './adapter'
14
- import type { ImageGenerationResult, StreamChunk } from '../../types'
23
+ import type {
24
+ ImageGenerationResult,
25
+ MediaPrompt,
26
+ MediaPromptFor,
27
+ StreamChunk,
28
+ } from '../../types'
15
29
 
16
30
  // ===========================
17
31
  // Activity Kind
@@ -55,6 +69,23 @@ export type ImageSizeForModel<TAdapter, TModel extends string> =
55
69
  : string
56
70
  : string
57
71
 
72
+ /**
73
+ * Extract the prompt type a model accepts from an ImageAdapter via ~types.
74
+ * Adapters declare a per-model input-modality map; models in the map get a
75
+ * `prompt` narrowed to text + their supported part types (text-only models
76
+ * accept `string | Array<TextPart>`), so unsupported media parts fail at
77
+ * compile time. Adapters without a map fall back to the full MediaPrompt.
78
+ */
79
+ export type ImagePromptForModel<TAdapter, TModel extends string> =
80
+ TAdapter extends ImageAdapter<any, any, any, any, infer ModsByName>
81
+ ? string extends keyof ModsByName
82
+ ? // No explicit map - accept the full union
83
+ MediaPrompt
84
+ : TModel extends keyof ModsByName
85
+ ? MediaPromptFor<ModsByName[TModel][number]>
86
+ : MediaPrompt
87
+ : MediaPrompt
88
+
58
89
  // ===========================
59
90
  // Activity Options Type
60
91
  // ===========================
@@ -72,8 +103,16 @@ export type ImageActivityOptions<
72
103
  > = {
73
104
  /** The image adapter to use (must be created with a model) */
74
105
  adapter: TAdapter & { kind: typeof kind }
75
- /** Text description of the desired image(s) */
76
- prompt: string
106
+ /**
107
+ * Description of the desired image(s). Either a plain string, or — for
108
+ * models that support image-conditioned generation — an ordered array of
109
+ * content parts interleaving text with image inputs (image-to-image,
110
+ * reference-guided, edit, multi-reference). Media parts may carry
111
+ * `metadata.role` (`'reference' | 'mask' | 'control' | 'character'`) to
112
+ * disambiguate intent. The accepted part types are narrowed per model via
113
+ * the adapter's input-modality map.
114
+ */
115
+ prompt: ImagePromptForModel<TAdapter, TAdapter['model']>
77
116
  /** Number of images to generate (default: 1) */
78
117
  numberOfImages?: number
79
118
  /** Image size in WIDTHxHEIGHT format (e.g., "1024x1024") */
@@ -92,6 +131,12 @@ export type ImageActivityOptions<
92
131
  * control and/or a custom `Logger`.
93
132
  */
94
133
  debug?: DebugOption
134
+ /**
135
+ * Observe-only middleware notified on start, usage, success, and error. Pass
136
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
137
+ * `GenerationMiddleware` contract for a custom backend.
138
+ */
139
+ middleware?: Array<GenerationMiddleware>
95
140
  } & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']>
96
141
  ? {
97
142
  /** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<
@@ -197,19 +242,49 @@ async function runGenerateImage<
197
242
  >(
198
243
  options: ImageActivityOptions<TAdapter, boolean>,
199
244
  ): Promise<ImageGenerationResult> {
200
- const { adapter, stream: _stream, debug: _debug, ...rest } = options
245
+ const {
246
+ adapter,
247
+ stream: _stream,
248
+ debug: _debug,
249
+ middleware,
250
+ ...rest
251
+ } = options
201
252
  const model = adapter.model
202
253
  const requestId = createId('image')
203
254
  const startTime = Date.now()
204
255
  const logger: InternalLogger = resolveDebugOption(options.debug)
205
256
 
257
+ const mwCtx = createGenerationContext({
258
+ requestId,
259
+ activity: 'image',
260
+ provider: adapter.name,
261
+ model,
262
+ modelOptions: rest.modelOptions,
263
+ createId,
264
+ })
265
+
266
+ await runGenerationStart(middleware, mwCtx)
267
+
268
+ // Devtools events carry the flattened prompt text plus media-part counts —
269
+ // the wire payload stays `prompt: string` regardless of the prompt shape.
270
+ const resolved = resolveMediaPrompt(rest.prompt)
271
+
206
272
  aiEventClient.emit('image:request:started', {
207
273
  requestId,
208
274
  provider: adapter.name,
209
275
  model,
210
- prompt: rest.prompt,
276
+ prompt: resolved.text,
211
277
  numberOfImages: rest.numberOfImages,
212
278
  size: rest.size,
279
+ ...(resolved.images.length > 0 && {
280
+ imageInputCount: resolved.images.length,
281
+ }),
282
+ ...(resolved.videos.length > 0 && {
283
+ videoInputCount: resolved.videos.length,
284
+ }),
285
+ ...(resolved.audios.length > 0 && {
286
+ audioInputCount: resolved.audios.length,
287
+ }),
213
288
  modelOptions: rest.modelOptions,
214
289
  timestamp: startTime,
215
290
  })
@@ -255,8 +330,18 @@ async function runGenerateImage<
255
330
  count: result.images.length,
256
331
  })
257
332
 
333
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)
334
+ await runGenerationFinish(middleware, mwCtx, {
335
+ duration,
336
+ usage: result.usage,
337
+ })
338
+
258
339
  return result
259
340
  } catch (error) {
341
+ await runGenerationError(middleware, mwCtx, {
342
+ error,
343
+ duration: Date.now() - startTime,
344
+ })
260
345
  logger.errors('generateImage activity failed', {
261
346
  error,
262
347
  source: 'generateImage',
@@ -8,8 +8,16 @@
8
8
  import { aiEventClient } from '@tanstack/ai-event-client'
9
9
  import { streamGenerationResult } from '../stream-generation-result.js'
10
10
  import { resolveDebugOption } from '../../logger/resolve'
11
+ import {
12
+ createGenerationContext,
13
+ runGenerationError,
14
+ runGenerationFinish,
15
+ runGenerationStart,
16
+ runGenerationUsage,
17
+ } from '../middleware'
11
18
  import type { InternalLogger } from '../../logger/internal-logger'
12
19
  import type { DebugOption } from '../../logger/types'
20
+ import type { GenerationMiddleware } from '../middleware'
13
21
  import type { TTSAdapter } from './adapter'
14
22
  import type { StreamChunk, TTSResult } from '../../types'
15
23
 
@@ -73,6 +81,12 @@ export interface TTSActivityOptions<
73
81
  * control and/or a custom `Logger`.
74
82
  */
75
83
  debug?: DebugOption
84
+ /**
85
+ * Observe-only middleware notified on start, usage, success, and error. Pass
86
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
87
+ * `GenerationMiddleware` contract for a custom backend.
88
+ */
89
+ middleware?: Array<GenerationMiddleware>
76
90
  }
77
91
 
78
92
  // ===========================
@@ -143,7 +157,13 @@ export function generateSpeech<
143
157
  async function runGenerateSpeech<
144
158
  TAdapter extends TTSAdapter<string, TTSProviderOptions<TAdapter>>,
145
159
  >(options: TTSActivityOptions<TAdapter, boolean>): Promise<TTSResult> {
146
- const { adapter, stream: _stream, debug: _debug, ...rest } = options
160
+ const {
161
+ adapter,
162
+ stream: _stream,
163
+ debug: _debug,
164
+ middleware,
165
+ ...rest
166
+ } = options
147
167
  const model = adapter.model
148
168
  const requestId = createId('speech')
149
169
  const startTime = Date.now()
@@ -153,6 +173,17 @@ async function runGenerateSpeech<
153
173
  (adapter as { name?: string }).name ??
154
174
  'unknown'
155
175
 
176
+ const mwCtx = createGenerationContext({
177
+ requestId,
178
+ activity: 'tts',
179
+ provider: adapter.name,
180
+ model,
181
+ modelOptions: rest.modelOptions,
182
+ createId,
183
+ })
184
+
185
+ await runGenerationStart(middleware, mwCtx)
186
+
156
187
  aiEventClient.emit('speech:request:started', {
157
188
  requestId,
158
189
  provider: adapter.name,
@@ -202,6 +233,12 @@ async function runGenerateSpeech<
202
233
  contentType: result.contentType,
203
234
  })
204
235
 
236
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)
237
+ await runGenerationFinish(middleware, mwCtx, {
238
+ duration,
239
+ usage: result.usage,
240
+ })
241
+
205
242
  return result
206
243
  } catch (error) {
207
244
  const duration = Date.now() - startTime
@@ -215,6 +252,10 @@ async function runGenerateSpeech<
215
252
  modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
216
253
  timestamp: Date.now(),
217
254
  })
255
+ await runGenerationError(middleware, mwCtx, {
256
+ error,
257
+ duration,
258
+ })
218
259
  logger.errors('generateSpeech activity failed', {
219
260
  error,
220
261
  source: 'generateSpeech',
@@ -8,8 +8,16 @@
8
8
  import { aiEventClient } from '@tanstack/ai-event-client'
9
9
  import { streamGenerationResult } from '../stream-generation-result.js'
10
10
  import { resolveDebugOption } from '../../logger/resolve'
11
+ import {
12
+ createGenerationContext,
13
+ runGenerationError,
14
+ runGenerationFinish,
15
+ runGenerationStart,
16
+ runGenerationUsage,
17
+ } from '../middleware'
11
18
  import type { InternalLogger } from '../../logger/internal-logger'
12
19
  import type { DebugOption } from '../../logger/types'
20
+ import type { GenerationMiddleware } from '../middleware'
13
21
  import type { TranscriptionAdapter } from './adapter'
14
22
  import type { StreamChunk, TranscriptionResult } from '../../types'
15
23
 
@@ -76,6 +84,12 @@ export interface TranscriptionActivityOptions<
76
84
  * control and/or a custom `Logger`.
77
85
  */
78
86
  debug?: DebugOption
87
+ /**
88
+ * Observe-only middleware notified on start, usage, success, and error. Pass
89
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
90
+ * `GenerationMiddleware` contract for a custom backend.
91
+ */
92
+ middleware?: Array<GenerationMiddleware>
79
93
  }
80
94
 
81
95
  // ===========================
@@ -174,7 +188,13 @@ async function runGenerateTranscription<
174
188
  >(
175
189
  options: TranscriptionActivityOptions<TAdapter, boolean>,
176
190
  ): Promise<TranscriptionResult> {
177
- const { adapter, stream: _stream, debug: _debug, ...rest } = options
191
+ const {
192
+ adapter,
193
+ stream: _stream,
194
+ debug: _debug,
195
+ middleware,
196
+ ...rest
197
+ } = options
178
198
  const model = adapter.model
179
199
  const requestId = createId('transcription')
180
200
  const startTime = Date.now()
@@ -184,6 +204,17 @@ async function runGenerateTranscription<
184
204
  (adapter as { name?: string }).name ??
185
205
  'unknown'
186
206
 
207
+ const mwCtx = createGenerationContext({
208
+ requestId,
209
+ activity: 'transcription',
210
+ provider: adapter.name,
211
+ model,
212
+ modelOptions: rest.modelOptions,
213
+ createId,
214
+ })
215
+
216
+ await runGenerationStart(middleware, mwCtx)
217
+
187
218
  aiEventClient.emit('transcription:request:started', {
188
219
  requestId,
189
220
  provider: adapter.name,
@@ -220,6 +251,12 @@ async function runGenerateTranscription<
220
251
  { hasText: !!result.text },
221
252
  )
222
253
 
254
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)
255
+ await runGenerationFinish(middleware, mwCtx, {
256
+ duration,
257
+ usage: result.usage,
258
+ })
259
+
223
260
  return result
224
261
  } catch (error) {
225
262
  const duration = Date.now() - startTime
@@ -233,6 +270,10 @@ async function runGenerateTranscription<
233
270
  modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
234
271
  timestamp: Date.now(),
235
272
  })
273
+ await runGenerationError(middleware, mwCtx, {
274
+ error,
275
+ duration,
276
+ })
236
277
  logger.errors('generateTranscription activity failed', {
237
278
  error,
238
279
  source: 'generateTranscription',
@@ -1,10 +1,30 @@
1
1
  import type {
2
+ ModelInputModalitiesByName,
2
3
  VideoGenerationOptions,
3
4
  VideoJobResult,
4
5
  VideoStatusResult,
5
6
  VideoUrlResult,
6
7
  } from '../../types'
7
8
 
9
+ /**
10
+ * Structured description of the durations a video model accepts.
11
+ *
12
+ * Tagged union so the same shape can express discrete enums (OpenAI Sora,
13
+ * Veo), continuous ranges, mixed shapes, and models with no duration field.
14
+ * Consumed by `VideoAdapter.availableDurations()`.
15
+ *
16
+ * @experimental Video generation is an experimental feature and may change.
17
+ */
18
+ export type DurationOptions<T extends string | number | undefined> =
19
+ | { kind: 'discrete'; values: ReadonlyArray<NonNullable<T>> }
20
+ | { kind: 'range'; min: number; max: number; step?: number; unit: 'seconds' }
21
+ | {
22
+ kind: 'mixed'
23
+ values: ReadonlyArray<NonNullable<T>>
24
+ range?: { min: number; max: number; step?: number }
25
+ }
26
+ | { kind: 'none' }
27
+
8
28
  /**
9
29
  * Configuration for video adapter instances
10
30
  *
@@ -31,6 +51,11 @@ export interface VideoAdapterConfig {
31
51
  * - TProviderOptions: Provider-specific options (already resolved)
32
52
  * - TModelProviderOptionsByName: Map from model name to its specific provider options
33
53
  * - TModelSizeByName: Map from model name to its supported sizes
54
+ * - TModelInputModalitiesByName: Map from model name to the non-text prompt
55
+ * modalities it accepts (constrains the `prompt` part types at compile time)
56
+ * - TModelDurationByName: Map from model name to its supported duration
57
+ * union. Defaults to `Record<string, number>` so adapters that haven't
58
+ * declared a map keep today's `duration?: number` typing.
34
59
  */
35
60
  export interface VideoAdapter<
36
61
  TModel extends string = string,
@@ -40,6 +65,10 @@ export interface VideoAdapter<
40
65
  string,
41
66
  string
42
67
  >,
68
+ TModelInputModalitiesByName extends ModelInputModalitiesByName =
69
+ ModelInputModalitiesByName,
70
+ TModelDurationByName extends Record<string, string | number | undefined> =
71
+ Record<string, number>,
43
72
  > {
44
73
  /** Discriminator for adapter kind - used to determine API shape */
45
74
  readonly kind: 'video'
@@ -55,6 +84,8 @@ export interface VideoAdapter<
55
84
  providerOptions: TProviderOptions
56
85
  modelProviderOptionsByName: TModelProviderOptionsByName
57
86
  modelSizeByName: TModelSizeByName
87
+ modelInputModalitiesByName: TModelInputModalitiesByName
88
+ modelDurationByName: TModelDurationByName
58
89
  }
59
90
 
60
91
  /**
@@ -62,7 +93,11 @@ export interface VideoAdapter<
62
93
  * Returns a job ID that can be used to poll for status and retrieve the video.
63
94
  */
64
95
  createVideoJob: (
65
- options: VideoGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
96
+ options: VideoGenerationOptions<
97
+ TProviderOptions,
98
+ TModelSizeByName[TModel],
99
+ TModelDurationByName[TModel]
100
+ >,
66
101
  ) => Promise<VideoJobResult>
67
102
 
68
103
  /**
@@ -75,13 +110,26 @@ export interface VideoAdapter<
75
110
  * Should only be called after status is 'completed'.
76
111
  */
77
112
  getVideoUrl: (jobId: string) => Promise<VideoUrlResult>
113
+
114
+ /**
115
+ * Describe the durations this adapter's model accepts. Returns a tagged
116
+ * union so consumers can render UI / coerce input without provider-specific
117
+ * knowledge.
118
+ */
119
+ availableDurations: () => DurationOptions<TModelDurationByName[TModel]>
120
+
121
+ /**
122
+ * Coerce a raw seconds value to the closest valid duration for this model.
123
+ * Returns `undefined` for models with no duration field.
124
+ */
125
+ snapDuration: (seconds: number) => TModelDurationByName[TModel] | undefined
78
126
  }
79
127
 
80
128
  /**
81
129
  * A VideoAdapter with any/unknown type parameters.
82
130
  * Useful as a constraint in generic functions and interfaces.
83
131
  */
84
- export type AnyVideoAdapter = VideoAdapter<any, any, any, any>
132
+ export type AnyVideoAdapter = VideoAdapter<any, any, any, any, any, any>
85
133
 
86
134
  /**
87
135
  * Abstract base class for video generation adapters.
@@ -99,11 +147,17 @@ export abstract class BaseVideoAdapter<
99
147
  string,
100
148
  string
101
149
  >,
150
+ TModelInputModalitiesByName extends ModelInputModalitiesByName =
151
+ ModelInputModalitiesByName,
152
+ TModelDurationByName extends Record<string, string | number | undefined> =
153
+ Record<string, number>,
102
154
  > implements VideoAdapter<
103
155
  TModel,
104
156
  TProviderOptions,
105
157
  TModelProviderOptionsByName,
106
- TModelSizeByName
158
+ TModelSizeByName,
159
+ TModelInputModalitiesByName,
160
+ TModelDurationByName
107
161
  > {
108
162
  readonly kind = 'video' as const
109
163
  abstract readonly name: string
@@ -114,6 +168,8 @@ export abstract class BaseVideoAdapter<
114
168
  providerOptions: TProviderOptions
115
169
  modelProviderOptionsByName: TModelProviderOptionsByName
116
170
  modelSizeByName: TModelSizeByName
171
+ modelInputModalitiesByName: TModelInputModalitiesByName
172
+ modelDurationByName: TModelDurationByName
117
173
  }
118
174
 
119
175
  protected config: VideoAdapterConfig
@@ -124,13 +180,33 @@ export abstract class BaseVideoAdapter<
124
180
  }
125
181
 
126
182
  abstract createVideoJob(
127
- options: VideoGenerationOptions<TProviderOptions, TModelSizeByName[TModel]>,
183
+ options: VideoGenerationOptions<
184
+ TProviderOptions,
185
+ TModelSizeByName[TModel],
186
+ TModelDurationByName[TModel]
187
+ >,
128
188
  ): Promise<VideoJobResult>
129
189
 
130
190
  abstract getVideoStatus(jobId: string): Promise<VideoStatusResult>
131
191
 
132
192
  abstract getVideoUrl(jobId: string): Promise<VideoUrlResult>
133
193
 
194
+ /**
195
+ * Default implementation returns `{ kind: 'none' }`. Adapters that have
196
+ * declared their per-model duration map should override this.
197
+ */
198
+ availableDurations(): DurationOptions<TModelDurationByName[TModel]> {
199
+ return { kind: 'none' }
200
+ }
201
+
202
+ /**
203
+ * Default implementation returns `undefined`. Adapters that have declared
204
+ * their per-model duration map should override.
205
+ */
206
+ snapDuration(_seconds: number): TModelDurationByName[TModel] | undefined {
207
+ return undefined
208
+ }
209
+
134
210
  protected generateId(): string {
135
211
  return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`
136
212
  }