@shop-os/modelkit 0.2.14 → 0.2.16

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/CHANGELOG.md CHANGED
@@ -9,6 +9,24 @@ header in the release PR.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.2.16] - Unreleased
13
+
14
+ ### Added
15
+ - Gemini Omni 1.1 Flash video extension. The row declares `supports: ["videoExtension"]` and `constraints.extension: { segmentSeconds: 10, maxTotalSeconds: 40 }`. A `taskType: "extend"` request continues the clip by `durationSeconds` and the result is the **full cumulative clip** (probe 2026-09-24). The first extension may upload a source video (≤ 10 s, `sourceVideo`); follow-ups chain with `VideoRequest.previousInteractionId`, taken from the previous result's new `VideoResult.providerRef.interactionId`. The Interactions API rejects `previous_interaction_id` together with a video task, so chained calls carry only the prompt (no `generation_config`) and no aspect ratio.
16
+ - `SupportedOption` / catalog `supports` gain `"videoExtension"`; `ModelConstraints.extension` is typed and validated.
17
+ - A chained extension (`previousInteractionId` set) sends the prompt alone: `referenceImages`, `referenceVideos` and `firstFrame` are dropped with an `option_ignored` warning each, since the API rejects the id next to a video task.
18
+ - `resolveVideoOptions` keeps `taskType: "extend"` on a `videoExtension` row (it is otherwise dropped as unsupported) and drops `previousInteractionId` with an `option_ignored` warning on rows without the capability.
19
+
20
+ ## [0.2.15] - Unreleased
21
+
22
+ ### Added
23
+ - `MediaRef.durationSeconds`: the measured length of a video or audio ref. The kit never measures it; callers pass it when known.
24
+ - `ReferenceLimits.videoSeconds` (`VideoSecondsLimits`: `min`, `max`, `total`, `editMin`): per-video and combined length limits for reference videos. `editMin` raises the per-video minimum when `taskType` is `edit` (Seedance 2.5: 4 s). Rows without it accept any length.
25
+ - `ModelConstraints.sourceVideoSeconds` (`min`, `max`, `editMin`, `inReferenceTotal`): length limits for the source video. `inReferenceTotal` counts the source toward the combined reference limit where the provider sends it as a reference video (Seedance).
26
+ - `videoLengthViolation(model, input)`: pure checker that returns the first length violation as a user-facing message, else `undefined`. Unknown lengths are skipped. Exported from the root and `/catalog` entries.
27
+ - `resolveVideoOptions` rejects a reference or source video outside the row limits with a non-retryable `unsupported_option` error before any provider call. Videos dropped by the reference caps do not count.
28
+ - Snapshot rows carry the provider limits: MiniMax H3 and H3 Max reference (2-15 s each, 15 s combined), Seedance 2.0 and 2.0 Fast (2-15 s each, 15 s combined), Seedance 2.5 (2-30 s each, 30 s combined, 4 s minimum for edit), Gemini Omni 1.0 and 1.1 (10 s), Kling O1 v2v source video (3-10.05 s).
29
+
12
30
  ## [0.2.14] - Unreleased
13
31
 
14
32
  - Keep AI SDK 6 and its compatible provider packages, including Bedrock and usage tracking.
package/dist/catalog.cjs CHANGED
@@ -48,7 +48,8 @@ __export(catalog_entry_exports, {
48
48
  resolveImageOptions: () => resolveImageOptions,
49
49
  resolveOptions: () => resolveOptions,
50
50
  resolveVideoOptions: () => resolveVideoOptions,
51
- sortCatalogRows: () => sortCatalogRows
51
+ sortCatalogRows: () => sortCatalogRows,
52
+ videoLengthViolation: () => videoLengthViolation
52
53
  });
53
54
  module.exports = __toCommonJS(catalog_entry_exports);
54
55
 
@@ -1416,7 +1417,8 @@ var VIDEO_MODELS = [
1416
1417
  // reference clip keeps its own framing.
1417
1418
  aspectRatios: [...OMNI_RATIOS],
1418
1419
  durations: [3, 4, 5, 6, 7, 8, 9, 10],
1419
- references: { images: 6, videos: 1 },
1420
+ references: { images: 6, videos: 1, videoSeconds: { max: 10 } },
1421
+ sourceVideoSeconds: { max: 10 },
1420
1422
  supports: []
1421
1423
  },
1422
1424
  defaults: {},
@@ -1442,8 +1444,16 @@ var VIDEO_MODELS = [
1442
1444
  aspectRatios: [...OMNI_RATIOS],
1443
1445
  resolutions: ["360p", "720p", "1080p", "4k"],
1444
1446
  durations: [3, 4, 5, 6, 7, 8, 9, 10],
1445
- references: { images: 6, videos: 1 },
1446
- supports: []
1447
+ references: { images: 6, videos: 1, videoSeconds: { max: 10 } },
1448
+ sourceVideoSeconds: { max: 10 },
1449
+ // Extension (probe 2026-09-24, 360p): each `taskType: "extend"` call
1450
+ // renders `durationSeconds` more and returns the FULL cumulative clip
1451
+ // (3 s -> 6 s -> 9 s); the provider caps the clip at 40 s. A first
1452
+ // extension can upload a <= 10 s source with task "extend"; later ones
1453
+ // chain by `previousInteractionId` (the API rejects the id together
1454
+ // with a video task, so those calls carry the prompt alone).
1455
+ supports: ["videoExtension"],
1456
+ extension: { segmentSeconds: 10, maxTotalSeconds: 40 }
1447
1457
  },
1448
1458
  defaults: {},
1449
1459
  usage: { ...VIDEO_USAGE },
@@ -1451,7 +1461,7 @@ var VIDEO_MODELS = [
1451
1461
  display: {
1452
1462
  provider: "google",
1453
1463
  logo: GOOGLE_LOGO2,
1454
- description: "Google Gemini Omni Flash 1.1 conversational video generation and editing; text, image, and up to 10-second video input, 3-10 second output at up to 4K and 24 FPS."
1464
+ 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."
1455
1465
  },
1456
1466
  capabilities: { acceptsSourceVideo: true }
1457
1467
  },
@@ -1470,7 +1480,14 @@ var VIDEO_MODELS = [
1470
1480
  resolutions: ["480p", "720p", "1080p", "4k"],
1471
1481
  durations: { min: 4, max: 15 },
1472
1482
  durationPresets: [4, 6, 8, 10, 12, 15],
1473
- references: { images: 9, videos: 3, audios: 3, audioOnly: false },
1483
+ references: {
1484
+ images: 9,
1485
+ videos: 3,
1486
+ audios: 3,
1487
+ audioOnly: false,
1488
+ videoSeconds: { min: 2, max: 15, total: 15 }
1489
+ },
1490
+ sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
1474
1491
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1475
1492
  supports: ["generateAudio", "seed", "lastFrame", "watermark"]
1476
1493
  },
@@ -1502,7 +1519,14 @@ var VIDEO_MODELS = [
1502
1519
  aspectRatios: [...SEEDANCE_RATIOS],
1503
1520
  resolutions: ["480p", "720p"],
1504
1521
  durations: { min: 4, max: 15 },
1505
- references: { images: 9, videos: 3, audios: 3, audioOnly: false },
1522
+ references: {
1523
+ images: 9,
1524
+ videos: 3,
1525
+ audios: 3,
1526
+ audioOnly: false,
1527
+ videoSeconds: { min: 2, max: 15, total: 15 }
1528
+ },
1529
+ sourceVideoSeconds: { min: 2, max: 15, inReferenceTotal: true },
1506
1530
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1507
1531
  supports: ["generateAudio", "seed", "lastFrame", "watermark"]
1508
1532
  },
@@ -1536,7 +1560,13 @@ var VIDEO_MODELS = [
1536
1560
  aspectRatios: [...SEEDANCE_RATIOS],
1537
1561
  resolutions: ["480p", "720p", "1080p"],
1538
1562
  durations: { min: 4, max: 30 },
1539
- references: { images: 30, videos: 10, audios: 10 },
1563
+ references: {
1564
+ images: 30,
1565
+ videos: 10,
1566
+ audios: 10,
1567
+ videoSeconds: { min: 2, max: 30, total: 30, editMin: 4 }
1568
+ },
1569
+ sourceVideoSeconds: { min: 2, max: 30, editMin: 4, inReferenceTotal: true },
1540
1570
  ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1541
1571
  supports: ["generateAudio", "seed", "lastFrame", "watermark", "taskType"]
1542
1572
  },
@@ -1748,7 +1778,14 @@ var VIDEO_MODELS = [
1748
1778
  aspectRatios: [...H3_REFERENCE_RATIOS],
1749
1779
  resolutions: ["480p", "768p", "2k", "4k"],
1750
1780
  durations: { min: 5, max: 15 },
1751
- references: { images: 9, videos: 3, audios: 3, total: 12, audioOnly: false },
1781
+ references: {
1782
+ images: 9,
1783
+ videos: 3,
1784
+ audios: 3,
1785
+ total: 12,
1786
+ audioOnly: false,
1787
+ videoSeconds: { min: 2, max: 15, total: 15 }
1788
+ },
1752
1789
  supports: ["seed", "promptExpansion", "safetyChecker"]
1753
1790
  },
1754
1791
  defaults: {
@@ -1779,7 +1816,14 @@ var VIDEO_MODELS = [
1779
1816
  aspectRatios: [...H3_REFERENCE_RATIOS],
1780
1817
  resolutions: ["480p", "768p"],
1781
1818
  durations: { min: 5, max: 15 },
1782
- references: { images: 9, videos: 3, audios: 3, total: 12, audioOnly: false },
1819
+ references: {
1820
+ images: 9,
1821
+ videos: 3,
1822
+ audios: 3,
1823
+ total: 12,
1824
+ audioOnly: false,
1825
+ videoSeconds: { min: 2, max: 15, total: 15 }
1826
+ },
1783
1827
  supports: ["seed", "promptExpansion", "safetyChecker"]
1784
1828
  },
1785
1829
  defaults: {
@@ -1811,6 +1855,7 @@ var VIDEO_MODELS = [
1811
1855
  aspectRatios: [...KLING_RATIOS],
1812
1856
  durations: { min: 3, max: 10 },
1813
1857
  requires: ["sourceVideo"],
1858
+ sourceVideoSeconds: { min: 3, max: 10.05 },
1814
1859
  supports: []
1815
1860
  },
1816
1861
  defaults: { durationSeconds: 5, aspectRatio: "16:9" },
@@ -2147,6 +2192,7 @@ var SupportedOptionSchema = import_zod2.z.enum([
2147
2192
  "watermark",
2148
2193
  "safetyChecker",
2149
2194
  "taskType",
2195
+ "videoExtension",
2150
2196
  "instrumental"
2151
2197
  ]);
2152
2198
  var UsageUnitSchema = import_zod2.z.enum([
@@ -2167,13 +2213,20 @@ var ProviderOptionValueSchema = import_zod2.z.lazy(
2167
2213
  ])
2168
2214
  );
2169
2215
  var ProviderOptionsSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.record(import_zod2.z.string(), ProviderOptionValueSchema));
2216
+ var VideoSecondsLimitsSchema = import_zod2.z.looseObject({
2217
+ min: import_zod2.z.number().optional(),
2218
+ max: import_zod2.z.number().optional(),
2219
+ total: import_zod2.z.number().optional(),
2220
+ editMin: import_zod2.z.number().optional()
2221
+ });
2170
2222
  var ReferenceLimitsSchema = import_zod2.z.looseObject({
2171
2223
  images: import_zod2.z.number().optional(),
2172
2224
  imagesWithVideo: import_zod2.z.number().optional(),
2173
2225
  videos: import_zod2.z.number().optional(),
2174
2226
  audios: import_zod2.z.number().optional(),
2175
2227
  total: import_zod2.z.number().optional(),
2176
- audioOnly: import_zod2.z.boolean().optional()
2228
+ audioOnly: import_zod2.z.boolean().optional(),
2229
+ videoSeconds: VideoSecondsLimitsSchema.optional()
2177
2230
  });
2178
2231
  var ModelConstraintsSchema = import_zod2.z.looseObject({
2179
2232
  aspectRatios: import_zod2.z.array(AspectRatioSchema).optional(),
@@ -2186,14 +2239,21 @@ var ModelConstraintsSchema = import_zod2.z.looseObject({
2186
2239
  references: ReferenceLimitsSchema.optional(),
2187
2240
  requires: import_zod2.z.array(RequiredInputSchema).optional(),
2188
2241
  supports: import_zod2.z.array(SupportedOptionSchema).optional(),
2189
- ratioIgnoredWith: import_zod2.z.array(import_zod2.z.enum(["firstFrame", "sourceVideo"])).optional()
2242
+ ratioIgnoredWith: import_zod2.z.array(import_zod2.z.enum(["firstFrame", "sourceVideo"])).optional(),
2243
+ sourceVideoSeconds: VideoSecondsLimitsSchema.omit({ total: true }).extend({ inReferenceTotal: import_zod2.z.boolean().optional() }).optional(),
2244
+ extension: import_zod2.z.looseObject({ segmentSeconds: import_zod2.z.number(), maxTotalSeconds: import_zod2.z.number() }).optional()
2190
2245
  });
2191
2246
  var MediaRefSchema = import_zod2.z.union([
2192
- import_zod2.z.looseObject({ url: import_zod2.z.string(), label: import_zod2.z.string().optional() }),
2247
+ import_zod2.z.looseObject({
2248
+ url: import_zod2.z.string(),
2249
+ label: import_zod2.z.string().optional(),
2250
+ durationSeconds: import_zod2.z.number().optional()
2251
+ }),
2193
2252
  import_zod2.z.looseObject({
2194
2253
  bytes: import_zod2.z.instanceof(Uint8Array),
2195
2254
  mime: import_zod2.z.string(),
2196
- label: import_zod2.z.string().optional()
2255
+ label: import_zod2.z.string().optional(),
2256
+ durationSeconds: import_zod2.z.number().optional()
2197
2257
  })
2198
2258
  ]);
2199
2259
  var ImageSafetySettingSchema = import_zod2.z.looseObject({ category: import_zod2.z.string(), threshold: import_zod2.z.string() });
@@ -2682,6 +2742,49 @@ function trim(field, refs, limit, model, warnings) {
2682
2742
  });
2683
2743
  return refs.slice(0, limit);
2684
2744
  }
2745
+ function knownLength(value) {
2746
+ return value !== void 0 && Number.isFinite(value) ? value : void 0;
2747
+ }
2748
+ function seconds(len, limit) {
2749
+ let digits = 1;
2750
+ while (digits < 3 && Number(len.toFixed(digits)) === limit) digits++;
2751
+ return len.toFixed(digits);
2752
+ }
2753
+ function effectiveMin(limits, taskType) {
2754
+ if (taskType !== "edit" || limits?.editMin === void 0) return limits?.min;
2755
+ return Math.max(limits.min ?? 0, limits.editMin);
2756
+ }
2757
+ function videoLengthViolation(model, input) {
2758
+ const limits = model.constraints.references?.videoSeconds;
2759
+ const refMin = effectiveMin(limits, input.taskType);
2760
+ const lengths = (input.referenceVideos ?? []).map(knownLength);
2761
+ if (limits?.max !== void 0) {
2762
+ const max = limits.max;
2763
+ const i = lengths.findIndex((len) => len !== void 0 && len > max);
2764
+ if (i >= 0)
2765
+ return `${model.label} takes reference videos up to ${String(max)} s each; video ${i + 1} is ${seconds(lengths[i], max)} s.`;
2766
+ }
2767
+ if (refMin !== void 0) {
2768
+ const i = lengths.findIndex((len) => len !== void 0 && len < refMin);
2769
+ if (i >= 0)
2770
+ return `${model.label} takes reference videos of at least ${String(refMin)} s each; video ${i + 1} is ${seconds(lengths[i], refMin)} s.`;
2771
+ }
2772
+ const source = knownLength(input.sourceVideo);
2773
+ const sourceLimits = model.constraints.sourceVideoSeconds;
2774
+ if (limits?.total !== void 0) {
2775
+ const refSum = lengths.reduce((acc, len) => acc + (len ?? 0), 0);
2776
+ const sum = refSum + (sourceLimits?.inReferenceTotal ? source ?? 0 : 0);
2777
+ if (sum > limits.total)
2778
+ return `${model.label} takes reference videos up to ${String(limits.total)} s combined; these add up to ${seconds(sum, limits.total)} s.`;
2779
+ }
2780
+ if (source === void 0) return void 0;
2781
+ const sourceMin = effectiveMin(sourceLimits, input.taskType);
2782
+ if (sourceLimits?.max !== void 0 && source > sourceLimits.max)
2783
+ return `${model.label} takes a source video up to ${String(sourceLimits.max)} s; this one is ${seconds(source, sourceLimits.max)} s.`;
2784
+ if (sourceMin !== void 0 && source < sourceMin)
2785
+ return `${model.label} takes a source video of at least ${String(sourceMin)} s; this one is ${seconds(source, sourceMin)} s.`;
2786
+ return void 0;
2787
+ }
2685
2788
  function resolveImageOptions(model, request, onUnsupported = "error") {
2686
2789
  const warnings = [];
2687
2790
  const c = model.constraints;
@@ -2867,9 +2970,25 @@ function resolveVideoOptions(model, request, onUnsupported = "error") {
2867
2970
  model: model.id
2868
2971
  });
2869
2972
  }
2973
+ const lengthViolation = videoLengthViolation(model, {
2974
+ referenceVideos: videos.map((v) => v.durationSeconds),
2975
+ taskType: merged.taskType,
2976
+ ...merged.sourceVideo?.durationSeconds !== void 0 && {
2977
+ sourceVideo: merged.sourceVideo.durationSeconds
2978
+ }
2979
+ });
2980
+ if (lengthViolation !== void 0) {
2981
+ throw new ModelKitError({
2982
+ code: "unsupported_option",
2983
+ message: lengthViolation,
2984
+ model: model.id,
2985
+ retryable: false
2986
+ });
2987
+ }
2870
2988
  setOrDelete(merged, "referenceImages", images);
2871
2989
  setOrDelete(merged, "referenceVideos", videos);
2872
2990
  setOrDelete(merged, "referenceAudios", audios);
2991
+ const keepExtend = merged.taskType === "extend" && (model.constraints.supports ?? []).includes("videoExtension");
2873
2992
  dropUnsupported(merged, model, warnings, [
2874
2993
  "seed",
2875
2994
  "negativePrompt",
@@ -2879,8 +2998,17 @@ function resolveVideoOptions(model, request, onUnsupported = "error") {
2879
2998
  "lastFrame",
2880
2999
  "watermark",
2881
3000
  "safetyChecker",
2882
- "taskType"
3001
+ ...keepExtend ? [] : ["taskType"]
2883
3002
  ]);
3003
+ if (merged.previousInteractionId !== void 0 && !keepExtend) {
3004
+ warnings.push({
3005
+ code: "option_ignored",
3006
+ field: "previousInteractionId",
3007
+ from: merged.previousInteractionId,
3008
+ message: `${model.id} does not support previousInteractionId; ignored`
3009
+ });
3010
+ delete merged.previousInteractionId;
3011
+ }
2884
3012
  return { request: merged, warnings };
2885
3013
  }
2886
3014
  function resolveAudioOptions(model, request, onUnsupported = "error") {