@shop-os/modelkit 0.2.27 → 0.3.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.
package/dist/catalog.js CHANGED
@@ -1293,6 +1293,11 @@ var NEGATIVE_PROMPT = "blur, distort, and low quality";
1293
1293
  var FRAMES_EXCLUDE_REFERENCES = { framesExcludeReferences: true };
1294
1294
  var KLING_PROMPT = { referenceSyntax: "numbered", maxChars: 2500 };
1295
1295
  var SEEDANCE_PROMPT = { referenceSyntax: "numbered", maxChars: 3e4 };
1296
+ var SEEDANCE_2_AUDIO = {
1297
+ audioSeconds: { min: 2, max: 15, total: 15 },
1298
+ audioFormats: ["wav", "mp3"],
1299
+ audioMaxBytes: 15 * 1024 * 1024
1300
+ };
1296
1301
  var H3_PROMPT = { referenceSyntax: "numbered", maxChars: 5e4 };
1297
1302
  var OMNI_PROMPT = { referenceSyntax: "caption", maxChars: 5e4 };
1298
1303
  var BYTEDANCE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/3bba5d81-4e57-4e71-911b-cdfa05acca2c_compressed.png";
@@ -1429,7 +1434,7 @@ var VIDEO_MODELS = [
1429
1434
  description: "Google's conversational video model with native audio, human support, and natural language editing. Aspect ratio only \u2014 resolution and duration are model-determined.",
1430
1435
  logo: GOOGLE_LOGO2
1431
1436
  },
1432
- capabilities: { acceptsSourceVideo: true }
1437
+ capabilities: { acceptsSourceVideo: true, videoEdit: true }
1433
1438
  },
1434
1439
  {
1435
1440
  id: "gemini-omni-1.1-flash",
@@ -1454,7 +1459,7 @@ var VIDEO_MODELS = [
1454
1459
  // with a video task, so those calls carry the prompt alone).
1455
1460
  // First-to-last-frame interpolation: `<LAST_FRAME>` goes with `<FIRST_FRAME>`.
1456
1461
  supports: ["videoExtension", "lastFrame"],
1457
- extension: { segmentSeconds: 10, maxTotalSeconds: 40 },
1462
+ extension: { segmentSeconds: 10, maxTotalSeconds: 40, returns: "joined" },
1458
1463
  prompt: OMNI_PROMPT,
1459
1464
  audio: "always"
1460
1465
  },
@@ -1466,7 +1471,11 @@ var VIDEO_MODELS = [
1466
1471
  logo: GOOGLE_LOGO2,
1467
1472
  description: "Google Gemini Omni Flash 1.1 conversational video generation, editing and extension; text, image, and up to 10-second video input, 3-10 second output at up to 4K and 24 FPS, extendable to 40 seconds."
1468
1473
  },
1469
- capabilities: { acceptsSourceVideo: true, lastFrameRequiresFirstFrame: true }
1474
+ capabilities: {
1475
+ acceptsSourceVideo: true,
1476
+ videoEdit: true,
1477
+ lastFrameRequiresFirstFrame: true
1478
+ }
1470
1479
  },
1471
1480
  {
1472
1481
  // vibe-triggers and Space Builder drop the ratio when a frame is wired; Cowork always sends it.
@@ -1488,7 +1497,8 @@ var VIDEO_MODELS = [
1488
1497
  videos: 3,
1489
1498
  audios: 3,
1490
1499
  audioOnly: false,
1491
- videoSeconds: { min: 2, max: 15, total: 15 }
1500
+ videoSeconds: { min: 2, max: 15, total: 15 },
1501
+ ...SEEDANCE_2_AUDIO
1492
1502
  },
1493
1503
  sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
1494
1504
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
@@ -1510,7 +1520,7 @@ var VIDEO_MODELS = [
1510
1520
  description: "High-quality AI video generation with reference image conditioning. Supports 4-15 second clips with human preprocessing.",
1511
1521
  logo: BYTEDANCE_LOGO
1512
1522
  },
1513
- capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
1523
+ capabilities: { acceptsSourceVideo: true, videoEdit: false, ...FRAMES_EXCLUDE_REFERENCES }
1514
1524
  },
1515
1525
  {
1516
1526
  id: "seedance-2.0-fast",
@@ -1530,7 +1540,8 @@ var VIDEO_MODELS = [
1530
1540
  videos: 3,
1531
1541
  audios: 3,
1532
1542
  audioOnly: false,
1533
- videoSeconds: { min: 2, max: 15, total: 15 }
1543
+ videoSeconds: { min: 2, max: 15, total: 15 },
1544
+ ...SEEDANCE_2_AUDIO
1534
1545
  },
1535
1546
  sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
1536
1547
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
@@ -1552,7 +1563,7 @@ var VIDEO_MODELS = [
1552
1563
  logo: BYTEDANCE_LOGO,
1553
1564
  description: "Speed- and cost-optimized ByteDance Seedance 2.0 Fast; text plus image, video, and audio references to generate or edit 4-15 second clips."
1554
1565
  },
1555
- capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
1566
+ capabilities: { acceptsSourceVideo: true, videoEdit: false, ...FRAMES_EXCLUDE_REFERENCES }
1556
1567
  },
1557
1568
  {
1558
1569
  // omni_reference_task_type edit/extend: adaptive ratio, edit sends duration -1.
@@ -1572,12 +1583,20 @@ var VIDEO_MODELS = [
1572
1583
  images: 30,
1573
1584
  videos: 10,
1574
1585
  audios: 10,
1575
- videoSeconds: { min: 2, max: 30, total: 30, editMin: 4 }
1586
+ videoSeconds: { min: 2, max: 30, total: 30, editMin: 4 },
1587
+ // BytePlus: 2-30 s per clip, 30 s in all, wav/mp3, 15 MB each.
1588
+ audioSeconds: { min: 2, max: 30, total: 30 },
1589
+ audioFormats: ["wav", "mp3"],
1590
+ audioMaxBytes: 15 * 1024 * 1024
1576
1591
  },
1577
1592
  sourceVideoSeconds: { min: 2, max: 30, editMin: 4, inReferenceTotal: true },
1578
1593
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1579
1594
  // No seed: 2.x echoes it but does not honour it (two seeded runs differed, 2026-10-01).
1580
- supports: ["generateAudio", "lastFrame", "watermark", "taskType"],
1595
+ supports: ["generateAudio", "lastFrame", "watermark", "taskType", "videoExtension"],
1596
+ // Extend (probe 2026-10-02, 480p): returns ONLY the added footage, continuing from
1597
+ // the source's last frame at the source's framing and fps but at the requested
1598
+ // resolution, so the caller joins the clips. Forward only in practice.
1599
+ extension: { segmentSeconds: 30, maxTotalSeconds: 60, returns: "new" },
1581
1600
  prompt: SEEDANCE_PROMPT,
1582
1601
  audio: "optional"
1583
1602
  },
@@ -1594,7 +1613,7 @@ var VIDEO_MODELS = [
1594
1613
  logo: BYTEDANCE_LOGO,
1595
1614
  description: "ByteDance Seedance 2.5 for long-form storytelling and omni-reference generation/editing; up to 50 reference assets and 4-30 second clips at up to 1080p."
1596
1615
  },
1597
- capabilities: { acceptsSourceVideo: true, ...FRAMES_EXCLUDE_REFERENCES }
1616
+ capabilities: { acceptsSourceVideo: true, videoEdit: true, ...FRAMES_EXCLUDE_REFERENCES }
1598
1617
  },
1599
1618
  {
1600
1619
  id: "grok-imagine",
@@ -1958,11 +1977,116 @@ var VIDEO_MODELS = [
1958
1977
  }
1959
1978
  ];
1960
1979
 
1980
+ // src/catalog/video-tools.ts
1981
+ var TOPAZ_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/topazlabs.png";
1982
+ var TOPAZ_SOURCE_SECONDS = { max: 300 };
1983
+ var TOPAZ_USAGE = { unit: "seconds" };
1984
+ var VIDEO_TOOL_MODELS = [
1985
+ {
1986
+ id: "topaz-precision",
1987
+ aliases: [],
1988
+ label: "Topaz Precision",
1989
+ type: "video-tool",
1990
+ adapter: "fal",
1991
+ providerModelId: "topaz/upscale/video/precision",
1992
+ lifecycle: "active",
1993
+ exposure: ["cowork"],
1994
+ constraints: {
1995
+ supports: [],
1996
+ sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
1997
+ tool: { kind: "upscale", upscaleFactor: { min: 1, max: 4 }, maxOutputLongEdge: 3840 }
1998
+ },
1999
+ defaults: {},
2000
+ providerOptions: { fal: { model: "Proteus" } },
2001
+ usage: TOPAZ_USAGE,
2002
+ pricingKind: "video_per_second",
2003
+ display: {
2004
+ provider: "topaz",
2005
+ logo: TOPAZ_LOGO,
2006
+ description: "Topaz Proteus upscaling: sharper, cleaner footage up to 4K while staying faithful to the source.",
2007
+ sortOrder: 20
2008
+ }
2009
+ },
2010
+ {
2011
+ id: "topaz-starlight",
2012
+ aliases: [],
2013
+ label: "Topaz Starlight",
2014
+ type: "video-tool",
2015
+ adapter: "fal",
2016
+ providerModelId: "topaz/upscale/video/generative",
2017
+ lifecycle: "active",
2018
+ exposure: ["cowork"],
2019
+ constraints: {
2020
+ supports: [],
2021
+ sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
2022
+ tool: { kind: "upscale", upscaleFactor: { min: 1, max: 4 }, maxOutputLongEdge: 3840 }
2023
+ },
2024
+ defaults: {},
2025
+ providerOptions: { fal: { model: "Starlight Precise 2.6" } },
2026
+ usage: TOPAZ_USAGE,
2027
+ pricingKind: "video_per_second",
2028
+ display: {
2029
+ provider: "topaz",
2030
+ logo: TOPAZ_LOGO,
2031
+ description: "Topaz Starlight Precise 2.6: generative upscaling that rebuilds fine detail, up to 4K. Slower and costlier than Precision.",
2032
+ sortOrder: 10
2033
+ }
2034
+ },
2035
+ {
2036
+ id: "topaz-apollo",
2037
+ aliases: [],
2038
+ label: "Topaz Apollo",
2039
+ type: "video-tool",
2040
+ adapter: "fal",
2041
+ providerModelId: "topaz/interpolate/video",
2042
+ lifecycle: "active",
2043
+ exposure: ["cowork"],
2044
+ constraints: {
2045
+ supports: [],
2046
+ sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
2047
+ tool: { kind: "interpolate", fps: { min: 16, max: 120 } }
2048
+ },
2049
+ defaults: {},
2050
+ providerOptions: { fal: { model: "Apollo" } },
2051
+ usage: TOPAZ_USAGE,
2052
+ pricingKind: "video_per_second",
2053
+ display: {
2054
+ provider: "topaz",
2055
+ logo: TOPAZ_LOGO,
2056
+ description: "Topaz Apollo frame interpolation: smoother motion at up to 120 fps, same length as the source."
2057
+ }
2058
+ },
2059
+ {
2060
+ id: "topaz-hyperion-2.5",
2061
+ aliases: [],
2062
+ label: "Topaz Hyperion 2.5",
2063
+ type: "video-tool",
2064
+ adapter: "fal",
2065
+ providerModelId: "topaz/sdr-to-hdr/video",
2066
+ lifecycle: "active",
2067
+ exposure: ["cowork"],
2068
+ constraints: {
2069
+ supports: [],
2070
+ sourceVideoSeconds: TOPAZ_SOURCE_SECONDS,
2071
+ tool: { kind: "hdr", outputFormats: ["h265", "prores"] }
2072
+ },
2073
+ defaults: {},
2074
+ usage: TOPAZ_USAGE,
2075
+ pricingKind: "video_per_second",
2076
+ display: {
2077
+ provider: "topaz",
2078
+ logo: TOPAZ_LOGO,
2079
+ description: "Topaz Hyperion 2.5 SDR to HDR: a 10-bit HDR master in H.265 (HDR10) or ProRes 422 HQ, at the source's size and frame rate."
2080
+ }
2081
+ }
2082
+ ];
2083
+
1961
2084
  // src/catalog/models.ts
1962
2085
  var MODELS = [
1963
2086
  ...TEXT_MODELS,
1964
2087
  ...IMAGE_MODELS,
1965
2088
  ...VIDEO_MODELS,
2089
+ ...VIDEO_TOOL_MODELS,
1966
2090
  ...OTHER_MODELS
1967
2091
  ];
1968
2092
 
@@ -2129,6 +2253,10 @@ var ALL_KEY_SET = /* @__PURE__ */ new Set([
2129
2253
  ...IMAGE_INPUT_SPEC_KEYS,
2130
2254
  ...AUDIO_INPUT_SPEC_KEYS
2131
2255
  ]);
2256
+ var TYPES_BY_ADAPTER = /* @__PURE__ */ new Map([
2257
+ ["replicate", ["video", "image", "audio"]],
2258
+ ["fal", ["image"]]
2259
+ ]);
2132
2260
  var ALLOWED_KEYS_BY_TYPE = /* @__PURE__ */ new Map([
2133
2261
  ["video", VIDEO_KEY_SET],
2134
2262
  ["image", IMAGE_KEY_SET],
@@ -2167,7 +2295,8 @@ var StaticSchema = z2.record(z2.string(), ProviderScalarSchema);
2167
2295
  var InputSpecSchema = z2.strictObject({
2168
2296
  fields: FieldsSchema,
2169
2297
  values: ValuesSchema.optional(),
2170
- static: StaticSchema.optional()
2298
+ static: StaticSchema.optional(),
2299
+ editEndpoint: z2.string().min(1).optional()
2171
2300
  }).superRefine((spec, ctx) => {
2172
2301
  const mappedNames = new Set(Object.values(spec.fields));
2173
2302
  for (const key of Object.keys(spec.static ?? {})) {
@@ -2192,18 +2321,21 @@ function validateInputSpec(raw, row) {
2192
2321
  };
2193
2322
  }
2194
2323
  const spec = parsed.data;
2195
- if (row.adapter !== "replicate") {
2324
+ const supportedTypes = TYPES_BY_ADAPTER.get(row.adapter);
2325
+ if (supportedTypes === void 0) {
2196
2326
  return {
2197
2327
  ok: false,
2198
- issues: [{ path: [], message: "inputSpec is only supported on replicate rows" }]
2328
+ issues: [
2329
+ { path: [], message: "inputSpec is only supported on replicate and fal rows" }
2330
+ ]
2199
2331
  };
2200
2332
  }
2201
2333
  const issues = [];
2202
2334
  const allowed = ALLOWED_KEYS_BY_TYPE.get(row.type);
2203
- if (allowed === void 0) {
2335
+ if (allowed === void 0 || !supportedTypes.includes(row.type)) {
2204
2336
  issues.push({
2205
2337
  path: [],
2206
- message: `inputSpec is not supported for "${row.type}" rows`
2338
+ message: row.adapter === "fal" ? `inputSpec on fal rows is only supported for image rows, not "${row.type}"` : `inputSpec is not supported for "${row.type}" rows`
2207
2339
  });
2208
2340
  } else {
2209
2341
  for (const key of Object.keys(spec.fields)) {
@@ -2223,6 +2355,19 @@ function validateInputSpec(raw, row) {
2223
2355
  }
2224
2356
  }
2225
2357
  }
2358
+ if (spec.editEndpoint !== void 0) {
2359
+ if (row.adapter !== "fal") {
2360
+ issues.push({
2361
+ path: ["editEndpoint"],
2362
+ message: "inputSpec.editEndpoint is only supported on fal rows"
2363
+ });
2364
+ } else if (!("references" in spec.fields)) {
2365
+ issues.push({
2366
+ path: ["editEndpoint"],
2367
+ message: 'inputSpec.editEndpoint needs a "references" entry in inputSpec.fields'
2368
+ });
2369
+ }
2370
+ }
2226
2371
  const seenNames = /* @__PURE__ */ new Map();
2227
2372
  for (const [key, name] of Object.entries(spec.fields)) {
2228
2373
  const first = seenNames.get(name);
@@ -2268,7 +2413,15 @@ var ADAPTER_IDS = [
2268
2413
  "higgsfield",
2269
2414
  "xai-video"
2270
2415
  ];
2271
- var ModelTypeSchema = z3.enum(["text", "image", "video", "audio", "upscale", "bg-remove"]);
2416
+ var ModelTypeSchema = z3.enum([
2417
+ "text",
2418
+ "image",
2419
+ "video",
2420
+ "audio",
2421
+ "upscale",
2422
+ "bg-remove",
2423
+ "video-tool"
2424
+ ]);
2272
2425
  var LifecycleSchema = z3.enum(["active", "deprecated", "retired"]);
2273
2426
  var SurfaceSchema = z3.enum(["cowork", "space_builder"]);
2274
2427
  var SURFACES2 = SurfaceSchema.options;
@@ -2348,7 +2501,22 @@ var ReferenceLimitsSchema = z3.looseObject({
2348
2501
  audios: z3.number().optional(),
2349
2502
  total: z3.number().optional(),
2350
2503
  audioOnly: z3.boolean().optional(),
2351
- videoSeconds: VideoSecondsLimitsSchema.optional()
2504
+ videoSeconds: VideoSecondsLimitsSchema.optional(),
2505
+ audioSeconds: z3.looseObject({
2506
+ min: z3.number().optional(),
2507
+ max: z3.number().optional(),
2508
+ total: z3.number().optional()
2509
+ }).optional(),
2510
+ audioFormats: z3.array(z3.string()).optional(),
2511
+ audioMaxBytes: z3.number().positive().optional()
2512
+ });
2513
+ var RangeSchema = z3.looseObject({ min: z3.number(), max: z3.number() });
2514
+ var VideoToolConstraintsSchema = z3.looseObject({
2515
+ kind: z3.enum(["upscale", "interpolate", "hdr"]),
2516
+ upscaleFactor: RangeSchema.optional(),
2517
+ maxOutputLongEdge: z3.number().positive().optional(),
2518
+ fps: RangeSchema.optional(),
2519
+ outputFormats: z3.array(z3.enum(["h265", "prores"])).min(1).optional()
2352
2520
  });
2353
2521
  var ModelConstraintsSchema = z3.looseObject({
2354
2522
  aspectRatios: z3.array(AspectRatioSchema).optional(),
@@ -2363,7 +2531,14 @@ var ModelConstraintsSchema = z3.looseObject({
2363
2531
  supports: z3.array(SupportedOptionSchema).optional(),
2364
2532
  ratioIgnoredWith: z3.array(z3.enum(["firstFrame", "sourceVideo"])).optional(),
2365
2533
  sourceVideoSeconds: VideoSecondsLimitsSchema.omit({ total: true }).extend({ inReferenceTotal: z3.boolean().optional() }).optional(),
2366
- extension: z3.looseObject({ segmentSeconds: z3.number(), maxTotalSeconds: z3.number() }).optional(),
2534
+ // segmentSeconds/maxTotalSeconds stay required: a kit before 0.3.0 drops a row whose
2535
+ // extension lacks them, so every extension block keeps them.
2536
+ extension: z3.looseObject({
2537
+ segmentSeconds: z3.number(),
2538
+ maxTotalSeconds: z3.number(),
2539
+ returns: z3.enum(["new", "joined"]).optional()
2540
+ }).optional(),
2541
+ tool: VideoToolConstraintsSchema.optional(),
2367
2542
  // Loose on purpose: the read path keeps a row whose value is invalid (the accessors in
2368
2543
  // conventions.ts read it as "not set"); CatalogModelSchema below rejects it on write.
2369
2544
  audio: z3.unknown().optional(),
@@ -2435,6 +2610,7 @@ var CatalogDisplaySchema = z3.looseObject({
2435
2610
  });
2436
2611
  var CatalogCapabilitiesSchema = z3.looseObject({
2437
2612
  acceptsSourceVideo: z3.boolean().optional(),
2613
+ videoEdit: z3.boolean().optional(),
2438
2614
  framesExcludeReferences: z3.boolean().optional(),
2439
2615
  lastFrameRequiresFirstFrame: z3.boolean().optional(),
2440
2616
  reasoning: z3.boolean().optional(),
@@ -2477,6 +2653,13 @@ var CatalogModelSchemaBase = z3.looseObject({
2477
2653
  message: "active text rows need contextWindow"
2478
2654
  });
2479
2655
  }
2656
+ if (row.type === "video-tool" && row.constraints.tool === void 0) {
2657
+ ctx.addIssue({
2658
+ code: "custom",
2659
+ path: ["constraints", "tool"],
2660
+ message: "video-tool rows need constraints.tool"
2661
+ });
2662
+ }
2480
2663
  if (row.replacedBy !== void 0 && row.replacedBy === row.id) {
2481
2664
  ctx.addIssue({
2482
2665
  code: "custom",
@@ -2782,6 +2965,114 @@ function resetLiveCatalog() {
2782
2965
  liveCatalog.replace([...MODELS]);
2783
2966
  }
2784
2967
 
2968
+ // src/catalog/video-tools-spec.ts
2969
+ import { z as z6 } from "zod";
2970
+ var RangeSchema2 = z6.object({ min: z6.number(), max: z6.number() });
2971
+ var ToolSchema = z6.object({
2972
+ kind: z6.enum(["upscale", "interpolate", "hdr"]),
2973
+ upscaleFactor: RangeSchema2.optional(),
2974
+ maxOutputLongEdge: z6.number().positive().optional(),
2975
+ fps: RangeSchema2.optional(),
2976
+ outputFormats: z6.array(z6.enum(["h265", "prores"])).min(1).optional()
2977
+ });
2978
+ function videoToolConstraints(row) {
2979
+ if (row.type !== "video-tool") return void 0;
2980
+ const parsed = ToolSchema.safeParse(row.constraints.tool);
2981
+ if (!parsed.success) return void 0;
2982
+ const { kind, upscaleFactor, maxOutputLongEdge, fps, outputFormats } = parsed.data;
2983
+ return {
2984
+ kind,
2985
+ ...upscaleFactor !== void 0 && { upscaleFactor },
2986
+ ...maxOutputLongEdge !== void 0 && { maxOutputLongEdge },
2987
+ ...fps !== void 0 && { fps },
2988
+ ...outputFormats !== void 0 && { outputFormats }
2989
+ };
2990
+ }
2991
+ function resolutionTier(width, height) {
2992
+ const shortEdge = Math.min(width, height);
2993
+ if (shortEdge <= 360) return "360p";
2994
+ if (shortEdge <= 480) return "480p";
2995
+ if (shortEdge <= 720) return "720p";
2996
+ if (shortEdge <= 1080) return "1080p";
2997
+ if (shortEdge <= 1440) return "2k";
2998
+ return "4k";
2999
+ }
3000
+ function fail(error) {
3001
+ return { ok: false, error };
3002
+ }
3003
+ function planVideoTool(row, req) {
3004
+ const tool = videoToolConstraints(row);
3005
+ if (!tool) return fail(`${row.id} is not a video tool`);
3006
+ const { width, height, durationSeconds, fps } = req.source;
3007
+ if (!(width > 0 && height > 0 && durationSeconds > 0)) {
3008
+ return fail("The source video's width, height and duration are required");
3009
+ }
3010
+ const maxSeconds = row.constraints.sourceVideoSeconds?.max;
3011
+ if (maxSeconds !== void 0 && durationSeconds > maxSeconds) {
3012
+ return fail(`${row.label} takes source videos up to ${maxSeconds} seconds.`);
3013
+ }
3014
+ const base = {
3015
+ durationSeconds,
3016
+ ...fps !== void 0 && { fps }
3017
+ };
3018
+ if (tool.kind === "upscale") {
3019
+ const factor = req.upscaleFactor;
3020
+ if (factor === void 0) return fail("An upscale factor is required");
3021
+ const range = tool.upscaleFactor;
3022
+ if (range && (factor < range.min || factor > range.max)) {
3023
+ return fail(`${row.label} upscales by ${range.min}x to ${range.max}x.`);
3024
+ }
3025
+ const outWidth = Math.round(width * factor);
3026
+ const outHeight = Math.round(height * factor);
3027
+ const limit = tool.maxOutputLongEdge;
3028
+ if (limit !== void 0 && Math.max(outWidth, outHeight) > limit) {
3029
+ return fail(
3030
+ `A ${factor}x upscale of a ${width}x${height} video passes the ${limit}px limit.`
3031
+ );
3032
+ }
3033
+ return {
3034
+ ok: true,
3035
+ output: {
3036
+ ...base,
3037
+ width: outWidth,
3038
+ height: outHeight,
3039
+ resolution: resolutionTier(outWidth, outHeight),
3040
+ format: "h264"
3041
+ }
3042
+ };
3043
+ }
3044
+ if (tool.kind === "interpolate") {
3045
+ const target = req.targetFps;
3046
+ if (target === void 0 || !Number.isInteger(target)) {
3047
+ return fail("A whole-number target frame rate is required");
3048
+ }
3049
+ const range = tool.fps;
3050
+ if (range && (target < range.min || target > range.max)) {
3051
+ return fail(`${row.label} outputs ${range.min} to ${range.max} fps.`);
3052
+ }
3053
+ return {
3054
+ ok: true,
3055
+ output: {
3056
+ durationSeconds,
3057
+ fps: target,
3058
+ width,
3059
+ height,
3060
+ resolution: resolutionTier(width, height),
3061
+ format: "h264"
3062
+ }
3063
+ };
3064
+ }
3065
+ const formats = tool.outputFormats ?? ["h265"];
3066
+ const format = req.outputFormat ?? formats[0] ?? "h265";
3067
+ if (!formats.includes(format)) {
3068
+ return fail(`${row.label} writes ${formats.join(" or ")}.`);
3069
+ }
3070
+ return {
3071
+ ok: true,
3072
+ output: { ...base, width, height, resolution: resolutionTier(width, height), format }
3073
+ };
3074
+ }
3075
+
2785
3076
  // src/resolve-options.ts
2786
3077
  var SIZE_ORDER = ["512", "1K", "2K", "4K"];
2787
3078
  var RESOLUTION_ORDER = ["360p", "480p", "720p", "768p", "1080p", "2k", "4k"];
@@ -3270,6 +3561,7 @@ export {
3270
3561
  SurfaceSchema,
3271
3562
  TEXT_MODELS,
3272
3563
  VIDEO_MODELS,
3564
+ VIDEO_TOOL_MODELS,
3273
3565
  billedAudio,
3274
3566
  compareCatalogOrder,
3275
3567
  isListed,
@@ -3280,14 +3572,17 @@ export {
3280
3572
  nearestAspectRatio,
3281
3573
  normalizeExposure,
3282
3574
  outputAudio,
3575
+ planVideoTool,
3283
3576
  primeCatalog,
3284
3577
  promptConventions,
3285
3578
  resetLiveCatalog,
3579
+ resolutionTier,
3286
3580
  resolveAudioOptions,
3287
3581
  resolveImageOptions,
3288
3582
  resolveOptions,
3289
3583
  resolveVideoOptions,
3290
3584
  sortCatalogRows,
3291
- videoLengthViolation
3585
+ videoLengthViolation,
3586
+ videoToolConstraints
3292
3587
  };
3293
3588
  //# sourceMappingURL=catalog.js.map