@shop-os/modelkit 0.3.1 → 0.3.3

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.
@@ -8,7 +8,7 @@ import { z } from 'zod';
8
8
 
9
9
  /** The AI SDK video model spelling: a catalog id or a provider video model instance. */
10
10
  type VideoModel = Parameters<typeof experimental_generateVideo>[0]["model"];
11
- type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "adaptive";
11
+ type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "1:4" | "4:1" | "1:8" | "8:1" | "adaptive";
12
12
  type ImageSize = "512" | "1K" | "2K" | "4K";
13
13
  type VideoResolution = "360p" | "480p" | "720p" | "768p" | "1080p" | "2k" | "4k";
14
14
  /** "xhigh" and "max" exist only on gpt-image-2.5; gpt-image-2 rejects them at the provider. */
@@ -393,6 +393,8 @@ interface CatalogCapabilities {
393
393
  videoEdit?: boolean;
394
394
  /** Video rows: first/last frames and reference images are mutually exclusive inputs. */
395
395
  framesExcludeReferences?: boolean;
396
+ /** Video rows: a source video is never sent with first/last frames or reference images. */
397
+ sourceVideoExcludesFrames?: boolean;
396
398
  /** Video rows: a last frame is accepted only together with a first frame. */
397
399
  lastFrameRequiresFirstFrame?: boolean;
398
400
  /** Text rows. `reasoning`: the model streams reasoning the UI should surface. */
@@ -689,6 +691,10 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
689
691
  "21:9": "21:9";
690
692
  "9:21": "9:21";
691
693
  "97:30": "97:30";
694
+ "1:4": "1:4";
695
+ "4:1": "4:1";
696
+ "1:8": "1:8";
697
+ "8:1": "8:1";
692
698
  adaptive: "adaptive";
693
699
  }>>>;
694
700
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -824,6 +830,10 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
824
830
  "21:9": "21:9";
825
831
  "9:21": "9:21";
826
832
  "97:30": "97:30";
833
+ "1:4": "1:4";
834
+ "4:1": "4:1";
835
+ "1:8": "1:8";
836
+ "8:1": "8:1";
827
837
  adaptive: "adaptive";
828
838
  }>>;
829
839
  size: z.ZodOptional<z.ZodEnum<{
@@ -986,6 +996,7 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
986
996
  acceptsSourceVideo: z.ZodOptional<z.ZodBoolean>;
987
997
  videoEdit: z.ZodOptional<z.ZodBoolean>;
988
998
  framesExcludeReferences: z.ZodOptional<z.ZodBoolean>;
999
+ sourceVideoExcludesFrames: z.ZodOptional<z.ZodBoolean>;
989
1000
  lastFrameRequiresFirstFrame: z.ZodOptional<z.ZodBoolean>;
990
1001
  reasoning: z.ZodOptional<z.ZodBoolean>;
991
1002
  vision: z.ZodOptional<z.ZodBoolean>;
package/dist/catalog.d.ts CHANGED
@@ -8,7 +8,7 @@ import { z } from 'zod';
8
8
 
9
9
  /** The AI SDK video model spelling: a catalog id or a provider video model instance. */
10
10
  type VideoModel = Parameters<typeof experimental_generateVideo>[0]["model"];
11
- type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "adaptive";
11
+ type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "1:4" | "4:1" | "1:8" | "8:1" | "adaptive";
12
12
  type ImageSize = "512" | "1K" | "2K" | "4K";
13
13
  type VideoResolution = "360p" | "480p" | "720p" | "768p" | "1080p" | "2k" | "4k";
14
14
  /** "xhigh" and "max" exist only on gpt-image-2.5; gpt-image-2 rejects them at the provider. */
@@ -393,6 +393,8 @@ interface CatalogCapabilities {
393
393
  videoEdit?: boolean;
394
394
  /** Video rows: first/last frames and reference images are mutually exclusive inputs. */
395
395
  framesExcludeReferences?: boolean;
396
+ /** Video rows: a source video is never sent with first/last frames or reference images. */
397
+ sourceVideoExcludesFrames?: boolean;
396
398
  /** Video rows: a last frame is accepted only together with a first frame. */
397
399
  lastFrameRequiresFirstFrame?: boolean;
398
400
  /** Text rows. `reasoning`: the model streams reasoning the UI should surface. */
@@ -689,6 +691,10 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
689
691
  "21:9": "21:9";
690
692
  "9:21": "9:21";
691
693
  "97:30": "97:30";
694
+ "1:4": "1:4";
695
+ "4:1": "4:1";
696
+ "1:8": "1:8";
697
+ "8:1": "8:1";
692
698
  adaptive: "adaptive";
693
699
  }>>>;
694
700
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -824,6 +830,10 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
824
830
  "21:9": "21:9";
825
831
  "9:21": "9:21";
826
832
  "97:30": "97:30";
833
+ "1:4": "1:4";
834
+ "4:1": "4:1";
835
+ "1:8": "1:8";
836
+ "8:1": "8:1";
827
837
  adaptive: "adaptive";
828
838
  }>>;
829
839
  size: z.ZodOptional<z.ZodEnum<{
@@ -986,6 +996,7 @@ declare const CatalogModelSchemaBase: z.ZodObject<{
986
996
  acceptsSourceVideo: z.ZodOptional<z.ZodBoolean>;
987
997
  videoEdit: z.ZodOptional<z.ZodBoolean>;
988
998
  framesExcludeReferences: z.ZodOptional<z.ZodBoolean>;
999
+ sourceVideoExcludesFrames: z.ZodOptional<z.ZodBoolean>;
989
1000
  lastFrameRequiresFirstFrame: z.ZodOptional<z.ZodBoolean>;
990
1001
  reasoning: z.ZodOptional<z.ZodBoolean>;
991
1002
  vision: z.ZodOptional<z.ZodBoolean>;
package/dist/catalog.js CHANGED
@@ -158,13 +158,21 @@ var GEMINI_RATIOS = [
158
158
  "5:4",
159
159
  "adaptive"
160
160
  ];
161
+ var NANO_BANANA_2_1_RATIOS = [
162
+ ...GEMINI_RATIOS.filter((r) => r !== "adaptive"),
163
+ "1:4",
164
+ "4:1",
165
+ "1:8",
166
+ "8:1",
167
+ "adaptive"
168
+ ];
161
169
  var GEMINI_PROMPT_MAX_CHARS = 5e4;
162
170
  var GPT_IMAGE_PROMPT = { referenceSyntax: "numbered", maxChars: 32e3 };
163
- function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars) {
171
+ function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars, aspectRatios = GEMINI_RATIOS) {
164
172
  const prompt = { referenceSyntax: "caption" };
165
173
  if (promptMaxChars !== void 0) prompt.maxChars = promptMaxChars;
166
174
  const constraints = {
167
- aspectRatios: [...GEMINI_RATIOS],
175
+ aspectRatios: [...aspectRatios],
168
176
  sizes,
169
177
  supports: ["safetySettings", "temperature"],
170
178
  prompt
@@ -188,6 +196,21 @@ function geminiImage(id, label, aliases, sizes, display, exposure, references, p
188
196
  return model;
189
197
  }
190
198
  var IMAGE_MODELS = [
199
+ geminiImage(
200
+ "gemini-nano-banana-2.1",
201
+ "Nano Banana 2.1",
202
+ ["nb-2.1"],
203
+ ["1K", "2K", "4K"],
204
+ {
205
+ provider: "google",
206
+ description: "Latest high-efficiency Nano Banana. Better realism, prompt adherence, text rendering and character consistency at Flash speed. Great for campaigns, infographics, and iterative edits.",
207
+ logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
208
+ },
209
+ ["cowork", "space_builder"],
210
+ { images: 14 },
211
+ GEMINI_PROMPT_MAX_CHARS,
212
+ NANO_BANANA_2_1_RATIOS
213
+ ),
191
214
  geminiImage(
192
215
  "gemini-3-pro-image-preview",
193
216
  "Nano Banana Pro",
@@ -2216,7 +2239,8 @@ var VIDEO_INPUT_SPEC_KEYS = [
2216
2239
  "generateAudio",
2217
2240
  "cfgScale",
2218
2241
  "promptExpansion",
2219
- "referenceImages"
2242
+ "referenceImages",
2243
+ "sourceVideo"
2220
2244
  ];
2221
2245
  var IMAGE_INPUT_SPEC_KEYS = [
2222
2246
  "prompt",
@@ -2235,6 +2259,12 @@ var AUDIO_INPUT_SPEC_KEYS = [
2235
2259
  "sourceVideo",
2236
2260
  "instrumental"
2237
2261
  ];
2262
+ var ARRAY_INPUT_SPEC_KEYS = [
2263
+ "firstFrame",
2264
+ "referenceImages",
2265
+ "lastFrame",
2266
+ "references"
2267
+ ];
2238
2268
  var INPUT_SPEC_SUPPORTS_KEYS = [
2239
2269
  "lastFrame",
2240
2270
  "seed",
@@ -2248,6 +2278,7 @@ var INPUT_SPEC_SUPPORTS_KEYS = [
2248
2278
  var VIDEO_KEY_SET = new Set(VIDEO_INPUT_SPEC_KEYS);
2249
2279
  var IMAGE_KEY_SET = new Set(IMAGE_INPUT_SPEC_KEYS);
2250
2280
  var AUDIO_KEY_SET = new Set(AUDIO_INPUT_SPEC_KEYS);
2281
+ var ARRAY_KEY_SET = new Set(ARRAY_INPUT_SPEC_KEYS);
2251
2282
  var ALL_KEY_SET = /* @__PURE__ */ new Set([
2252
2283
  ...VIDEO_INPUT_SPEC_KEYS,
2253
2284
  ...IMAGE_INPUT_SPEC_KEYS,
@@ -2292,13 +2323,30 @@ var ValuesSchema = z2.record(z2.string(), z2.record(z2.string(), ProviderScalarS
2292
2323
  }
2293
2324
  });
2294
2325
  var StaticSchema = z2.record(z2.string(), ProviderScalarSchema);
2326
+ var ArraysSchema = z2.record(z2.string().min(1), z2.array(z2.string()).min(1)).superRefine((arrays, ctx) => {
2327
+ for (const [name, keys] of Object.entries(arrays)) {
2328
+ for (const key of keys) {
2329
+ if (!ARRAY_KEY_SET.has(key)) {
2330
+ ctx.addIssue({
2331
+ code: "custom",
2332
+ path: [name],
2333
+ message: `inputSpec.arrays "${name}" has unknown image key "${key}"`
2334
+ });
2335
+ }
2336
+ }
2337
+ }
2338
+ });
2295
2339
  var InputSpecSchema = z2.strictObject({
2296
2340
  fields: FieldsSchema,
2297
2341
  values: ValuesSchema.optional(),
2298
2342
  static: StaticSchema.optional(),
2343
+ arrays: ArraysSchema.optional(),
2299
2344
  editEndpoint: z2.string().min(1).optional()
2300
2345
  }).superRefine((spec, ctx) => {
2301
- const mappedNames = new Set(Object.values(spec.fields));
2346
+ const mappedNames = /* @__PURE__ */ new Set([
2347
+ ...Object.values(spec.fields),
2348
+ ...Object.keys(spec.arrays ?? {})
2349
+ ]);
2302
2350
  for (const key of Object.keys(spec.static ?? {})) {
2303
2351
  if (mappedNames.has(key)) {
2304
2352
  ctx.addIssue({
@@ -2387,14 +2435,54 @@ function validateInputSpec(raw, row) {
2387
2435
  });
2388
2436
  }
2389
2437
  }
2438
+ const arrayOwner = /* @__PURE__ */ new Map();
2439
+ for (const [name, keys] of Object.entries(spec.arrays ?? {})) {
2440
+ const fieldKey = seenNames.get(name);
2441
+ if (fieldKey !== void 0) {
2442
+ issues.push({
2443
+ path: ["arrays", name],
2444
+ message: `inputSpec.arrays "${name}" is also the provider field of inputSpec.fields "${fieldKey}"`
2445
+ });
2446
+ }
2447
+ for (const key of keys) {
2448
+ if (allowed !== void 0 && !allowed.has(key)) {
2449
+ issues.push({
2450
+ path: ["arrays", name],
2451
+ message: `inputSpec.arrays "${key}" is not supported for ${row.type} rows`
2452
+ });
2453
+ }
2454
+ if (key in spec.fields) {
2455
+ issues.push({
2456
+ path: ["arrays", name],
2457
+ message: `inputSpec.arrays "${key}" is already mapped in inputSpec.fields`
2458
+ });
2459
+ }
2460
+ const owner = arrayOwner.get(key);
2461
+ if (owner === void 0) arrayOwner.set(key, name);
2462
+ else {
2463
+ issues.push({
2464
+ path: ["arrays", name],
2465
+ message: owner === name ? `inputSpec.arrays "${name}" lists "${key}" twice` : `inputSpec.arrays "${owner}" and "${name}" both take "${key}"`
2466
+ });
2467
+ }
2468
+ }
2469
+ }
2390
2470
  const supports = row.constraints.supports ?? [];
2391
2471
  for (const key of INPUT_SPEC_SUPPORTS_KEYS) {
2392
- if (key in spec.fields && !supports.includes(key)) {
2472
+ if (supports.includes(key)) continue;
2473
+ if (key in spec.fields) {
2393
2474
  issues.push({
2394
2475
  path: ["fields", key],
2395
2476
  message: `inputSpec.fields "${key}" needs "${key}" in constraints.supports`
2396
2477
  });
2397
2478
  }
2479
+ const arrayName = arrayOwner.get(key);
2480
+ if (arrayName !== void 0) {
2481
+ issues.push({
2482
+ path: ["arrays", arrayName],
2483
+ message: `inputSpec.arrays "${key}" needs "${key}" in constraints.supports`
2484
+ });
2485
+ }
2398
2486
  }
2399
2487
  if (issues.length > 0) return { ok: false, issues };
2400
2488
  return { ok: true, spec };
@@ -2443,6 +2531,10 @@ var AspectRatioSchema = z3.enum([
2443
2531
  "21:9",
2444
2532
  "9:21",
2445
2533
  "97:30",
2534
+ "1:4",
2535
+ "4:1",
2536
+ "1:8",
2537
+ "8:1",
2446
2538
  "adaptive"
2447
2539
  ]);
2448
2540
  var ImageSizeSchema = z3.enum(["512", "1K", "2K", "4K"]);
@@ -2612,6 +2704,7 @@ var CatalogCapabilitiesSchema = z3.looseObject({
2612
2704
  acceptsSourceVideo: z3.boolean().optional(),
2613
2705
  videoEdit: z3.boolean().optional(),
2614
2706
  framesExcludeReferences: z3.boolean().optional(),
2707
+ sourceVideoExcludesFrames: z3.boolean().optional(),
2615
2708
  lastFrameRequiresFirstFrame: z3.boolean().optional(),
2616
2709
  reasoning: z3.boolean().optional(),
2617
2710
  vision: z3.boolean().optional(),
@@ -3309,6 +3402,26 @@ function resolveVideoOptions(model, request, onUnsupported = "error") {
3309
3402
  });
3310
3403
  delete merged.sourceVideo;
3311
3404
  }
3405
+ const imageInputs = ["firstFrame", "lastFrame", "referenceImages"].filter(
3406
+ (field) => field === "referenceImages" ? compact(merged.referenceImages).length > 0 : merged[field] !== void 0
3407
+ );
3408
+ if (merged.sourceVideo !== void 0 && imageInputs.length > 0 && model.capabilities?.sourceVideoExcludesFrames) {
3409
+ if (onUnsupported === "error") {
3410
+ throw new ModelKitError({
3411
+ code: "unsupported_option",
3412
+ message: `${model.id} takes a source video or images, not both`,
3413
+ model: model.id
3414
+ });
3415
+ }
3416
+ for (const field of imageInputs) {
3417
+ warnings.push({
3418
+ code: "option_ignored",
3419
+ field,
3420
+ message: `${model.id} takes a source video or images, not both; ${field} dropped`
3421
+ });
3422
+ delete merged[field];
3423
+ }
3424
+ }
3312
3425
  if (merged.lastFrame !== void 0 && merged.firstFrame === void 0 && model.capabilities?.lastFrameRequiresFirstFrame) {
3313
3426
  if (onUnsupported === "error") {
3314
3427
  throw new ModelKitError({