egaki 0.5.0 → 0.6.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.
@@ -1,12 +1,28 @@
1
- // Single source of truth for all image generation models.
1
+ // Single source of truth for all image and video generation models.
2
2
  // Every model's provider, pricing, features, and generation strategy lives here.
3
3
  // Other modules derive their data from this catalog — no separate registries.
4
4
  //
5
5
  // To add a model: add an entry to CATALOG with the right shared fragment spread.
6
6
  // To update pricing: change the cost field on the model entry.
7
7
  // To add a provider: create a new shared fragment and add models using it.
8
+ //
9
+ // Provider option schemas are sourced from the @ai-sdk/* packages and upstream API docs.
10
+ // Each shared fragment has a URL comment pointing to the source of truth so options
11
+ // are easy to verify and update when providers change their APIs.
12
+ //
13
+ // Sources:
14
+ // xAI image: node_modules/@ai-sdk/xai/dist/index.d.ts → xaiImageModelOptions
15
+ // https://docs.x.ai/docs/api-reference#create-image
16
+ // xAI video: node_modules/@ai-sdk/xai/dist/index.d.ts → XaiVideoModelOptions
17
+ // https://docs.x.ai/docs/api-reference#create-video
18
+ // OpenAI: node_modules/@ai-sdk/openai/dist/index.d.ts → imageGeneration args
19
+ // https://platform.openai.com/docs/api-reference/images
20
+ // Fal image: https://ai-sdk.dev/providers/ai-sdk-providers/fal
21
+ // node_modules/@ai-sdk/fal/dist/index.d.ts → FalImageModelOptions
22
+ // Fal video: node_modules/@ai-sdk/fal/dist/index.d.ts → FalVideoModelOptions
23
+ // https://ai-sdk.dev/providers/ai-sdk-providers/fal
8
24
 
9
- // ─── types ───────────────────────────────────────────────────────────────────
25
+ // ─── cost types ──────────────────────────────────────────────────────────────
10
26
 
11
27
  export type PerImageCost = {
12
28
  type: 'per-image'
@@ -22,7 +38,55 @@ export type PerTokenCost = {
22
38
  outputPerM: number
23
39
  }
24
40
 
41
+ export type VideoDurationPricingTier = {
42
+ /** USD per second for this variant */
43
+ costPerSecond: number
44
+ /** Optional resolution discriminator (e.g. 720p, 1080p, 4k) */
45
+ resolution?: string
46
+ /** Optional mode discriminator (e.g. std, pro) */
47
+ mode?: string
48
+ /** Optional audio discriminator */
49
+ audio?: boolean
50
+ }
51
+
52
+ export type PerVideoSecondCost = {
53
+ type: 'per-video-second'
54
+ /** Fallback duration when request does not specify one (seconds) */
55
+ defaultDurationSec: number
56
+ tiers: VideoDurationPricingTier[]
57
+ }
58
+
59
+ export type UnknownCost = {
60
+ type: 'unknown'
61
+ }
62
+
25
63
  export type ModelCost = PerImageCost | PerTokenCost
64
+ export type VideoModelCost = PerVideoSecondCost | UnknownCost
65
+ export type AnyCost = ModelCost | VideoModelCost
66
+
67
+ // ─── provider option types ───────────────────────────────────────────────────
68
+ // Declares provider-specific options that can be passed via providerOptions.
69
+ // Lives in the catalog so CLI flag descriptions and validation are derived
70
+ // from a single source of truth.
71
+
72
+ export type ProviderOption = {
73
+ /** CLI flag name, e.g. 'quality', 'output-format' */
74
+ flag: string
75
+ /** The key sent to providerOptions.{provider}.{key} */
76
+ providerKey: string
77
+ /** Human description for --help */
78
+ description: string
79
+ /** Enum values if constrained, shown in flag description */
80
+ values?: string[]
81
+ /** Server-side default when not specified */
82
+ defaultValue?: string
83
+ /** Value type */
84
+ type: 'string' | 'number' | 'boolean'
85
+ /** Whether this flag accepts multiple values (array) */
86
+ array?: boolean
87
+ }
88
+
89
+ // ─── feature types ───────────────────────────────────────────────────────────
26
90
 
27
91
  export type ModelFeatures = {
28
92
  /** supports input images for editing */
@@ -39,10 +103,28 @@ export type ModelFeatures = {
39
103
  multipleImages: boolean
40
104
  }
41
105
 
42
- export type ModelEntry = {
106
+ export type VideoModelFeatures = {
107
+ /** Supports text prompt to video generation */
108
+ textToVideo: boolean
109
+ /** Supports image-to-video prompt object ({ image, text }) */
110
+ imageToVideo: boolean
111
+ /** Optional capabilities exposed by provider */
112
+ capabilities: Array<'t2v' | 'i2v' | 'r2v' | 'motion-control' | 'editing' | 'extension'>
113
+ /** Optional supported aspect ratios */
114
+ aspectRatios?: string[]
115
+ /** Optional supported resolutions */
116
+ resolutions?: string[]
117
+ /** Optional duration range in seconds */
118
+ durationRangeSec?: { min: number; max: number }
119
+ seed: boolean
120
+ multipleVideos: boolean
121
+ }
122
+
123
+ // ─── entry types (discriminated union by strategy) ───────────────────────────
124
+
125
+ export type ImageModelEntry = {
43
126
  id: string
44
127
  name: string
45
- /** Optional longer description shown in `egaki models` output */
46
128
  description?: string
47
129
  provider: string
48
130
  strategy: 'image' | 'text'
@@ -50,8 +132,28 @@ export type ModelEntry = {
50
132
  released: string
51
133
  cost: ModelCost
52
134
  features: ModelFeatures
135
+ /** Provider-specific options passable via providerOptions.{provider} */
136
+ providerOptions?: ProviderOption[]
53
137
  }
54
138
 
139
+ export type VideoModelEntry = {
140
+ id: string
141
+ name: string
142
+ description?: string
143
+ provider: string
144
+ strategy: 'video'
145
+ released: string
146
+ cost: VideoModelCost
147
+ features: VideoModelFeatures
148
+ /** Provider-specific options passable via providerOptions.{provider} */
149
+ providerOptions?: ProviderOption[]
150
+ }
151
+
152
+ /** @deprecated Use ImageModelEntry instead */
153
+ export type ModelEntry = ImageModelEntry
154
+
155
+ export type AnyModelEntry = ImageModelEntry | VideoModelEntry
156
+
55
157
  // ─── shared fragments ────────────────────────────────────────────────────────
56
158
  // Spread these into model entries to avoid repeating common fields.
57
159
 
@@ -79,9 +181,17 @@ const googleText = {
79
181
  },
80
182
  }
81
183
 
184
+ // Source: node_modules/@ai-sdk/openai/dist/index.d.ts → imageGeneration args
185
+ // Docs: https://platform.openai.com/docs/api-reference/images/create
186
+ const openaiImageProviderOptions: ProviderOption[] = [
187
+ { flag: 'quality', providerKey: 'quality', description: 'Image quality', values: ['auto', 'low', 'medium', 'high'], defaultValue: 'auto', type: 'string' },
188
+ { flag: 'output-format', providerKey: 'outputFormat', description: 'Output format', values: ['png', 'jpeg', 'webp'], defaultValue: 'png', type: 'string' },
189
+ ]
190
+
82
191
  const openaiImage = {
83
192
  provider: 'openai',
84
193
  strategy: 'image' as const,
194
+ providerOptions: openaiImageProviderOptions,
85
195
  }
86
196
 
87
197
  const replicateImage = {
@@ -89,9 +199,17 @@ const replicateImage = {
89
199
  strategy: 'image' as const,
90
200
  }
91
201
 
202
+ // Source: node_modules/@ai-sdk/fal/dist/index.d.ts → FalImageModelOptions (lazy passthrough)
203
+ // Docs: https://ai-sdk.dev/providers/ai-sdk-providers/fal#provider-options
204
+ const falImageProviderOptions: ProviderOption[] = [
205
+ { flag: 'output-format', providerKey: 'outputFormat', description: 'Output format', values: ['png', 'jpeg'], type: 'string' },
206
+ { flag: 'negative-prompt', providerKey: 'negativePrompt', description: 'What to avoid in the image', type: 'string' },
207
+ ]
208
+
92
209
  const falImage = {
93
210
  provider: 'fal',
94
211
  strategy: 'image' as const,
212
+ providerOptions: falImageProviderOptions,
95
213
  }
96
214
 
97
215
  const bflImage = {
@@ -104,11 +222,37 @@ const recraftImage = {
104
222
  strategy: 'image' as const,
105
223
  }
106
224
 
225
+ // Source: node_modules/@ai-sdk/xai/dist/index.d.ts → xaiImageModelOptions
226
+ // Docs: https://docs.x.ai/docs/api-reference#create-image
227
+ const xaiImageProviderOptions: ProviderOption[] = [
228
+ { flag: 'quality', providerKey: 'quality', description: 'Image quality', values: ['low', 'medium', 'high'], defaultValue: 'medium', type: 'string' },
229
+ { flag: 'resolution', providerKey: 'resolution', description: 'Output resolution', values: ['1k', '2k'], defaultValue: '1k', type: 'string' },
230
+ { flag: 'output-format', providerKey: 'output_format', description: 'Output format', values: ['png', 'jpeg'], defaultValue: 'png', type: 'string' },
231
+ ]
232
+
107
233
  const xaiImage = {
108
234
  provider: 'xai',
109
235
  strategy: 'image' as const,
236
+ providerOptions: xaiImageProviderOptions,
110
237
  }
111
238
 
239
+ // Source: node_modules/@ai-sdk/xai/dist/index.d.ts → XaiVideoModelOptions
240
+ // Docs: https://docs.x.ai/docs/api-reference#create-video
241
+ const xaiVideoProviderOptions: ProviderOption[] = [
242
+ { flag: 'resolution', providerKey: 'resolution', description: 'Output resolution', values: ['480p', '720p'], defaultValue: '720p', type: 'string' },
243
+ { flag: 'mode', providerKey: 'mode', description: 'Video operation mode', values: ['edit-video', 'extend-video', 'reference-to-video'], type: 'string' },
244
+ // video-url is not a CLI flag; it's set internally from --input in edit/extend modes.
245
+ // Kept here so findProviderOption() can verify the model supports it and resolve the correct providerKey.
246
+ { flag: 'video-url', providerKey: 'videoUrl', description: 'Source video URL for editing or extension (internal)', type: 'string' },
247
+ { flag: 'reference-images', providerKey: 'referenceImageUrls', description: 'Reference images for R2V (1-7 files or URLs)', type: 'string', array: true },
248
+ ]
249
+
250
+ // Source: node_modules/@ai-sdk/fal/dist/index.d.ts → FalVideoModelOptions
251
+ // Docs: https://ai-sdk.dev/providers/ai-sdk-providers/fal
252
+ const falVideoProviderOptions: ProviderOption[] = [
253
+ { flag: 'negative-prompt', providerKey: 'negativePrompt', description: 'What to avoid in the video', type: 'string' },
254
+ ]
255
+
112
256
  const vertexImagen = {
113
257
  provider: 'vertex',
114
258
  strategy: 'image' as const,
@@ -1068,10 +1212,396 @@ export const CATALOG: ModelEntry[] = [
1068
1212
  },
1069
1213
  ]
1070
1214
 
1215
+ // ─── video catalog ───────────────────────────────────────────────────────────
1216
+
1217
+ const commonVideoRatios = ['16:9', '9:16', '1:1', '4:3', '3:4']
1218
+
1219
+ export const VIDEO_CATALOG: VideoModelEntry[] = [
1220
+ {
1221
+ id: 'veo-3.1-generate-001',
1222
+ name: 'Veo 3.1',
1223
+ provider: 'google',
1224
+ strategy: 'video',
1225
+ released: '2026-01',
1226
+ cost: {
1227
+ type: 'per-video-second',
1228
+ defaultDurationSec: 8,
1229
+ tiers: [
1230
+ { resolution: '720p', audio: false, costPerSecond: 0.2 },
1231
+ { resolution: '720p', audio: true, costPerSecond: 0.4 },
1232
+ { resolution: '1080p', audio: false, costPerSecond: 0.2 },
1233
+ { resolution: '1080p', audio: true, costPerSecond: 0.4 },
1234
+ { resolution: '4k', audio: false, costPerSecond: 0.4 },
1235
+ { resolution: '4k', audio: true, costPerSecond: 0.6 },
1236
+ ],
1237
+ },
1238
+ features: {
1239
+ textToVideo: true,
1240
+ imageToVideo: false,
1241
+ capabilities: ['t2v'],
1242
+ aspectRatios: ['16:9', '9:16'],
1243
+ resolutions: ['720p', '1080p', '4k'],
1244
+ durationRangeSec: { min: 4, max: 8 },
1245
+ seed: true,
1246
+ multipleVideos: true,
1247
+ },
1248
+ },
1249
+ {
1250
+ id: 'veo-3.1-fast-generate-001',
1251
+ name: 'Veo 3.1 Fast',
1252
+ provider: 'google',
1253
+ strategy: 'video',
1254
+ released: '2026-01',
1255
+ cost: {
1256
+ type: 'per-video-second',
1257
+ defaultDurationSec: 8,
1258
+ tiers: [
1259
+ { resolution: '720p', audio: false, costPerSecond: 0.1 },
1260
+ { resolution: '720p', audio: true, costPerSecond: 0.15 },
1261
+ { resolution: '1080p', audio: false, costPerSecond: 0.1 },
1262
+ { resolution: '1080p', audio: true, costPerSecond: 0.15 },
1263
+ { resolution: '4k', audio: false, costPerSecond: 0.3 },
1264
+ { resolution: '4k', audio: true, costPerSecond: 0.35 },
1265
+ ],
1266
+ },
1267
+ features: {
1268
+ textToVideo: true,
1269
+ imageToVideo: false,
1270
+ capabilities: ['t2v'],
1271
+ aspectRatios: ['16:9', '9:16'],
1272
+ resolutions: ['720p', '1080p', '4k'],
1273
+ durationRangeSec: { min: 4, max: 8 },
1274
+ seed: true,
1275
+ multipleVideos: true,
1276
+ },
1277
+ },
1278
+ {
1279
+ id: 'veo-3.0-generate-001',
1280
+ name: 'Veo 3.0',
1281
+ provider: 'google',
1282
+ strategy: 'video',
1283
+ released: '2025-12',
1284
+ cost: {
1285
+ type: 'per-video-second',
1286
+ defaultDurationSec: 8,
1287
+ tiers: [
1288
+ { resolution: '720p', audio: false, costPerSecond: 0.2 },
1289
+ { resolution: '720p', audio: true, costPerSecond: 0.4 },
1290
+ { resolution: '1080p', audio: false, costPerSecond: 0.2 },
1291
+ { resolution: '1080p', audio: true, costPerSecond: 0.4 },
1292
+ ],
1293
+ },
1294
+ features: {
1295
+ textToVideo: true,
1296
+ imageToVideo: false,
1297
+ capabilities: ['t2v'],
1298
+ aspectRatios: ['16:9', '9:16'],
1299
+ resolutions: ['720p', '1080p'],
1300
+ durationRangeSec: { min: 4, max: 8 },
1301
+ seed: true,
1302
+ multipleVideos: true,
1303
+ },
1304
+ },
1305
+ {
1306
+ id: 'veo-3.0-fast-generate-001',
1307
+ name: 'Veo 3.0 Fast',
1308
+ provider: 'google',
1309
+ strategy: 'video',
1310
+ released: '2025-12',
1311
+ cost: {
1312
+ type: 'per-video-second',
1313
+ defaultDurationSec: 8,
1314
+ tiers: [
1315
+ { resolution: '720p', audio: false, costPerSecond: 0.1 },
1316
+ { resolution: '720p', audio: true, costPerSecond: 0.15 },
1317
+ { resolution: '1080p', audio: false, costPerSecond: 0.1 },
1318
+ { resolution: '1080p', audio: true, costPerSecond: 0.15 },
1319
+ ],
1320
+ },
1321
+ features: {
1322
+ textToVideo: true,
1323
+ imageToVideo: false,
1324
+ capabilities: ['t2v'],
1325
+ aspectRatios: ['16:9', '9:16'],
1326
+ resolutions: ['720p', '1080p'],
1327
+ durationRangeSec: { min: 4, max: 8 },
1328
+ seed: true,
1329
+ multipleVideos: true,
1330
+ },
1331
+ },
1332
+ // ── Vertex: Veo ──────────────────────────────────────────────────────
1333
+ {
1334
+ id: 'vertex/veo-3.1-generate-001',
1335
+ name: 'Veo 3.1 (Vertex)',
1336
+ provider: 'vertex',
1337
+ strategy: 'video',
1338
+ released: '2026-01',
1339
+ cost: {
1340
+ type: 'per-video-second',
1341
+ defaultDurationSec: 8,
1342
+ tiers: [
1343
+ { resolution: '720p', audio: false, costPerSecond: 0.2 },
1344
+ { resolution: '720p', audio: true, costPerSecond: 0.4 },
1345
+ { resolution: '1080p', audio: false, costPerSecond: 0.2 },
1346
+ { resolution: '1080p', audio: true, costPerSecond: 0.4 },
1347
+ { resolution: '4k', audio: false, costPerSecond: 0.4 },
1348
+ { resolution: '4k', audio: true, costPerSecond: 0.6 },
1349
+ ],
1350
+ },
1351
+ features: {
1352
+ textToVideo: true,
1353
+ imageToVideo: false,
1354
+ capabilities: ['t2v'],
1355
+ aspectRatios: ['16:9', '9:16'],
1356
+ resolutions: ['720p', '1080p', '4k'],
1357
+ durationRangeSec: { min: 4, max: 8 },
1358
+ seed: true,
1359
+ multipleVideos: true,
1360
+ },
1361
+ },
1362
+ {
1363
+ id: 'vertex/veo-3.1-fast-generate-001',
1364
+ name: 'Veo 3.1 Fast (Vertex)',
1365
+ provider: 'vertex',
1366
+ strategy: 'video',
1367
+ released: '2026-01',
1368
+ cost: {
1369
+ type: 'per-video-second',
1370
+ defaultDurationSec: 8,
1371
+ tiers: [
1372
+ { resolution: '720p', audio: false, costPerSecond: 0.1 },
1373
+ { resolution: '720p', audio: true, costPerSecond: 0.15 },
1374
+ { resolution: '1080p', audio: false, costPerSecond: 0.1 },
1375
+ { resolution: '1080p', audio: true, costPerSecond: 0.15 },
1376
+ { resolution: '4k', audio: false, costPerSecond: 0.3 },
1377
+ { resolution: '4k', audio: true, costPerSecond: 0.35 },
1378
+ ],
1379
+ },
1380
+ features: {
1381
+ textToVideo: true,
1382
+ imageToVideo: false,
1383
+ capabilities: ['t2v'],
1384
+ aspectRatios: ['16:9', '9:16'],
1385
+ resolutions: ['720p', '1080p', '4k'],
1386
+ durationRangeSec: { min: 4, max: 8 },
1387
+ seed: true,
1388
+ multipleVideos: true,
1389
+ },
1390
+ },
1391
+ {
1392
+ id: 'grok-imagine-video',
1393
+ name: 'Grok Imagine Video',
1394
+ provider: 'xai',
1395
+ strategy: 'video',
1396
+ released: '2026-03',
1397
+ cost: {
1398
+ type: 'per-video-second',
1399
+ defaultDurationSec: 5,
1400
+ tiers: [
1401
+ { resolution: '480p', costPerSecond: 0.05 },
1402
+ { resolution: '720p', costPerSecond: 0.07 },
1403
+ ],
1404
+ },
1405
+ features: {
1406
+ textToVideo: true,
1407
+ imageToVideo: true,
1408
+ capabilities: ['t2v', 'i2v', 'editing', 'extension', 'r2v'],
1409
+ aspectRatios: ['1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3'],
1410
+ resolutions: ['480p', '720p'],
1411
+ durationRangeSec: { min: 1, max: 15 },
1412
+ seed: false,
1413
+ multipleVideos: false,
1414
+ },
1415
+ providerOptions: xaiVideoProviderOptions,
1416
+ },
1417
+ {
1418
+ id: 'kling-v2.6-t2v',
1419
+ name: 'Kling v2.6 T2V',
1420
+ provider: 'klingai',
1421
+ strategy: 'video',
1422
+ released: '2025-12',
1423
+ cost: {
1424
+ type: 'per-video-second',
1425
+ defaultDurationSec: 5,
1426
+ tiers: [
1427
+ { mode: 'std', costPerSecond: 0.042 },
1428
+ { mode: 'pro', audio: false, costPerSecond: 0.07 },
1429
+ { mode: 'pro', audio: true, costPerSecond: 0.14 },
1430
+ ],
1431
+ },
1432
+ features: {
1433
+ textToVideo: true,
1434
+ imageToVideo: false,
1435
+ capabilities: ['t2v'],
1436
+ aspectRatios: commonVideoRatios,
1437
+ durationRangeSec: { min: 5, max: 10 },
1438
+ seed: false,
1439
+ multipleVideos: false,
1440
+ },
1441
+ },
1442
+ {
1443
+ id: 'wan-v2.6-t2v',
1444
+ name: 'Wan v2.6 T2V',
1445
+ provider: 'alibaba',
1446
+ strategy: 'video',
1447
+ released: '2026-01',
1448
+ cost: {
1449
+ type: 'per-video-second',
1450
+ defaultDurationSec: 5,
1451
+ tiers: [
1452
+ { resolution: '720p', costPerSecond: 0.1 },
1453
+ { resolution: '1080p', costPerSecond: 0.15 },
1454
+ ],
1455
+ },
1456
+ features: {
1457
+ textToVideo: true,
1458
+ imageToVideo: false,
1459
+ capabilities: ['t2v'],
1460
+ resolutions: ['720p', '1080p'],
1461
+ durationRangeSec: { min: 2, max: 15 },
1462
+ seed: false,
1463
+ multipleVideos: false,
1464
+ },
1465
+ },
1466
+ // ── Fal video models ─────────────────────────────────────────────────
1467
+ {
1468
+ id: 'luma-ray-2',
1469
+ name: 'Luma Ray 2 (Fal)',
1470
+ provider: 'fal',
1471
+ strategy: 'video',
1472
+ released: '2025-11',
1473
+ cost: { type: 'unknown' },
1474
+ features: {
1475
+ textToVideo: true,
1476
+ imageToVideo: true,
1477
+ capabilities: ['t2v', 'i2v'],
1478
+ aspectRatios: commonVideoRatios,
1479
+ seed: true,
1480
+ multipleVideos: false,
1481
+ },
1482
+ providerOptions: falVideoProviderOptions,
1483
+ },
1484
+ {
1485
+ id: 'minimax-video',
1486
+ name: 'MiniMax Video (Fal)',
1487
+ provider: 'fal',
1488
+ strategy: 'video',
1489
+ released: '2025-10',
1490
+ cost: { type: 'unknown' },
1491
+ features: {
1492
+ textToVideo: true,
1493
+ imageToVideo: false,
1494
+ capabilities: ['t2v'],
1495
+ aspectRatios: commonVideoRatios,
1496
+ seed: false,
1497
+ multipleVideos: false,
1498
+ },
1499
+ providerOptions: falVideoProviderOptions,
1500
+ },
1501
+ {
1502
+ id: 'hunyuan-video',
1503
+ name: 'Hunyuan Video (Fal)',
1504
+ provider: 'fal',
1505
+ strategy: 'video',
1506
+ released: '2025-09',
1507
+ cost: { type: 'unknown' },
1508
+ features: {
1509
+ textToVideo: true,
1510
+ imageToVideo: true,
1511
+ capabilities: ['t2v', 'i2v'],
1512
+ aspectRatios: commonVideoRatios,
1513
+ seed: true,
1514
+ multipleVideos: false,
1515
+ },
1516
+ providerOptions: falVideoProviderOptions,
1517
+ },
1518
+ ]
1519
+
1520
+ // ─── provider option helpers ─────────────────────────────────────────────────
1521
+
1522
+ /**
1523
+ * Collect the valid values for a CLI flag across all providers in the catalog.
1524
+ * Returns a map of provider name → enum values array. Only includes providers
1525
+ * that declare values for the given flag. Used by cli.ts to auto-generate
1526
+ * flag descriptions like "xAI: low, medium, high. OpenAI: auto, low, medium, high".
1527
+ */
1528
+ export function getProviderValuesForFlag(
1529
+ flag: string,
1530
+ catalogs: AnyModelEntry[][] = [CATALOG, VIDEO_CATALOG],
1531
+ ): Map<string, string[]> {
1532
+ const result = new Map<string, string[]>()
1533
+ for (const catalog of catalogs) {
1534
+ for (const entry of catalog) {
1535
+ if (!entry.providerOptions) continue
1536
+ for (const opt of entry.providerOptions) {
1537
+ if (opt.flag === flag && opt.values?.length && !result.has(entry.provider)) {
1538
+ result.set(entry.provider, opt.values)
1539
+ }
1540
+ }
1541
+ }
1542
+ }
1543
+ return result
1544
+ }
1545
+
1546
+ /**
1547
+ * Build a human-readable description suffix listing provider-specific values
1548
+ * for a flag. Includes default values when available.
1549
+ * Example: "xai: low, medium, high (default: medium). openai: auto, low, medium, high (default: auto)"
1550
+ */
1551
+ export function describeProviderValues(
1552
+ flag: string,
1553
+ catalogs?: AnyModelEntry[][],
1554
+ ): string {
1555
+ const allCatalogs = catalogs ?? [CATALOG, VIDEO_CATALOG]
1556
+ // collect {provider → {values, defaultValue}} for this flag
1557
+ const providerInfo = new Map<string, { values: string[]; defaultValue?: string }>()
1558
+ for (const catalog of allCatalogs) {
1559
+ for (const entry of catalog) {
1560
+ if (!entry.providerOptions) continue
1561
+ for (const opt of entry.providerOptions) {
1562
+ if (opt.flag === flag && opt.values?.length && !providerInfo.has(entry.provider)) {
1563
+ providerInfo.set(entry.provider, {
1564
+ values: opt.values,
1565
+ defaultValue: opt.defaultValue,
1566
+ })
1567
+ }
1568
+ }
1569
+ }
1570
+ }
1571
+ if (providerInfo.size === 0) return ''
1572
+ return [...providerInfo.entries()]
1573
+ .map(([provider, info]) => {
1574
+ const vals = info.values.join(', ')
1575
+ const def = info.defaultValue ? ` (default: ${info.defaultValue})` : ''
1576
+ return `${provider}: ${vals}${def}`
1577
+ })
1578
+ .join('. ')
1579
+ }
1580
+
1581
+ /**
1582
+ * Find the ProviderOption definition for a given flag and provider from a model entry.
1583
+ * Returns undefined if the model doesn't support this flag.
1584
+ */
1585
+ export function findProviderOption(
1586
+ entry: AnyModelEntry,
1587
+ flag: string,
1588
+ ): ProviderOption | undefined {
1589
+ return entry.providerOptions?.find((opt) => opt.flag === flag)
1590
+ }
1591
+
1071
1592
  // ─── lookup helpers ──────────────────────────────────────────────────────────
1072
1593
 
1073
- const catalogIndex = new Map(CATALOG.map((m) => [m.id, m]))
1594
+ const imageCatalogIndex = new Map(CATALOG.map((m) => [m.id, m]))
1595
+ const videoCatalogIndex = new Map(VIDEO_CATALOG.map((m) => [m.id, m]))
1596
+
1597
+ export function findModel(id: string): ImageModelEntry | undefined {
1598
+ return imageCatalogIndex.get(id)
1599
+ }
1600
+
1601
+ export function findVideoModel(id: string): VideoModelEntry | undefined {
1602
+ return videoCatalogIndex.get(id)
1603
+ }
1074
1604
 
1075
- export function findModel(id: string): ModelEntry | undefined {
1076
- return catalogIndex.get(id)
1605
+ export function findAnyModel(id: string): AnyModelEntry | undefined {
1606
+ return imageCatalogIndex.get(id) ?? videoCatalogIndex.get(id)
1077
1607
  }