@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.
package/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ var index_exports = {};
33
33
  __export(index_exports, {
34
34
  ADAPTER_IDS: () => ADAPTER_IDS,
35
35
  ADAPTER_KEYS: () => ADAPTER_KEYS,
36
+ ARRAY_INPUT_SPEC_KEYS: () => ARRAY_INPUT_SPEC_KEYS,
36
37
  AUDIO_INPUT_SPEC_KEYS: () => AUDIO_INPUT_SPEC_KEYS,
37
38
  Catalog: () => Catalog,
38
39
  CatalogModelSchema: () => CatalogModelSchema,
@@ -75,6 +76,7 @@ __export(index_exports, {
75
76
  googleImageAdapter: () => googleImageAdapter,
76
77
  hasAwsCredentials: () => hasAwsCredentials,
77
78
  higgsfieldAdapter: () => higgsfieldAdapter,
79
+ isArrayInputSpecKey: () => isArrayInputSpecKey,
78
80
  isInputSpecKey: () => isInputSpecKey,
79
81
  isListed: () => isListed,
80
82
  isListedOn: () => isListedOn,
@@ -286,13 +288,21 @@ var GEMINI_RATIOS = [
286
288
  "5:4",
287
289
  "adaptive"
288
290
  ];
291
+ var NANO_BANANA_2_1_RATIOS = [
292
+ ...GEMINI_RATIOS.filter((r) => r !== "adaptive"),
293
+ "1:4",
294
+ "4:1",
295
+ "1:8",
296
+ "8:1",
297
+ "adaptive"
298
+ ];
289
299
  var GEMINI_PROMPT_MAX_CHARS = 5e4;
290
300
  var GPT_IMAGE_PROMPT = { referenceSyntax: "numbered", maxChars: 32e3 };
291
- function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars) {
301
+ function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars, aspectRatios = GEMINI_RATIOS) {
292
302
  const prompt = { referenceSyntax: "caption" };
293
303
  if (promptMaxChars !== void 0) prompt.maxChars = promptMaxChars;
294
304
  const constraints = {
295
- aspectRatios: [...GEMINI_RATIOS],
305
+ aspectRatios: [...aspectRatios],
296
306
  sizes,
297
307
  supports: ["safetySettings", "temperature"],
298
308
  prompt
@@ -316,6 +326,21 @@ function geminiImage(id, label, aliases, sizes, display, exposure, references, p
316
326
  return model;
317
327
  }
318
328
  var IMAGE_MODELS = [
329
+ geminiImage(
330
+ "gemini-nano-banana-2.1",
331
+ "Nano Banana 2.1",
332
+ ["nb-2.1"],
333
+ ["1K", "2K", "4K"],
334
+ {
335
+ provider: "google",
336
+ 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.",
337
+ logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
338
+ },
339
+ ["cowork", "space_builder"],
340
+ { images: 14 },
341
+ GEMINI_PROMPT_MAX_CHARS,
342
+ NANO_BANANA_2_1_RATIOS
343
+ ),
319
344
  geminiImage(
320
345
  "gemini-3-pro-image-preview",
321
346
  "Nano Banana Pro",
@@ -2332,7 +2357,8 @@ var VIDEO_INPUT_SPEC_KEYS = [
2332
2357
  "generateAudio",
2333
2358
  "cfgScale",
2334
2359
  "promptExpansion",
2335
- "referenceImages"
2360
+ "referenceImages",
2361
+ "sourceVideo"
2336
2362
  ];
2337
2363
  var IMAGE_INPUT_SPEC_KEYS = [
2338
2364
  "prompt",
@@ -2351,6 +2377,12 @@ var AUDIO_INPUT_SPEC_KEYS = [
2351
2377
  "sourceVideo",
2352
2378
  "instrumental"
2353
2379
  ];
2380
+ var ARRAY_INPUT_SPEC_KEYS = [
2381
+ "firstFrame",
2382
+ "referenceImages",
2383
+ "lastFrame",
2384
+ "references"
2385
+ ];
2354
2386
  var INPUT_SPEC_SUPPORTS_KEYS = [
2355
2387
  "lastFrame",
2356
2388
  "seed",
@@ -2364,6 +2396,7 @@ var INPUT_SPEC_SUPPORTS_KEYS = [
2364
2396
  var VIDEO_KEY_SET = new Set(VIDEO_INPUT_SPEC_KEYS);
2365
2397
  var IMAGE_KEY_SET = new Set(IMAGE_INPUT_SPEC_KEYS);
2366
2398
  var AUDIO_KEY_SET = new Set(AUDIO_INPUT_SPEC_KEYS);
2399
+ var ARRAY_KEY_SET = new Set(ARRAY_INPUT_SPEC_KEYS);
2367
2400
  var ALL_KEY_SET = /* @__PURE__ */ new Set([
2368
2401
  ...VIDEO_INPUT_SPEC_KEYS,
2369
2402
  ...IMAGE_INPUT_SPEC_KEYS,
@@ -2408,13 +2441,30 @@ var ValuesSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.re
2408
2441
  }
2409
2442
  });
2410
2443
  var StaticSchema = import_zod2.z.record(import_zod2.z.string(), ProviderScalarSchema);
2444
+ var ArraysSchema = import_zod2.z.record(import_zod2.z.string().min(1), import_zod2.z.array(import_zod2.z.string()).min(1)).superRefine((arrays, ctx) => {
2445
+ for (const [name, keys] of Object.entries(arrays)) {
2446
+ for (const key of keys) {
2447
+ if (!ARRAY_KEY_SET.has(key)) {
2448
+ ctx.addIssue({
2449
+ code: "custom",
2450
+ path: [name],
2451
+ message: `inputSpec.arrays "${name}" has unknown image key "${key}"`
2452
+ });
2453
+ }
2454
+ }
2455
+ }
2456
+ });
2411
2457
  var InputSpecSchema = import_zod2.z.strictObject({
2412
2458
  fields: FieldsSchema,
2413
2459
  values: ValuesSchema.optional(),
2414
2460
  static: StaticSchema.optional(),
2461
+ arrays: ArraysSchema.optional(),
2415
2462
  editEndpoint: import_zod2.z.string().min(1).optional()
2416
2463
  }).superRefine((spec, ctx) => {
2417
- const mappedNames = new Set(Object.values(spec.fields));
2464
+ const mappedNames = /* @__PURE__ */ new Set([
2465
+ ...Object.values(spec.fields),
2466
+ ...Object.keys(spec.arrays ?? {})
2467
+ ]);
2418
2468
  for (const key of Object.keys(spec.static ?? {})) {
2419
2469
  if (mappedNames.has(key)) {
2420
2470
  ctx.addIssue({
@@ -2428,6 +2478,9 @@ var InputSpecSchema = import_zod2.z.strictObject({
2428
2478
  function isInputSpecKey(key) {
2429
2479
  return ALL_KEY_SET.has(key);
2430
2480
  }
2481
+ function isArrayInputSpecKey(key) {
2482
+ return ARRAY_KEY_SET.has(key);
2483
+ }
2431
2484
  function validateInputSpec(raw, row) {
2432
2485
  const parsed = InputSpecSchema.safeParse(raw);
2433
2486
  if (!parsed.success) {
@@ -2506,14 +2559,54 @@ function validateInputSpec(raw, row) {
2506
2559
  });
2507
2560
  }
2508
2561
  }
2562
+ const arrayOwner = /* @__PURE__ */ new Map();
2563
+ for (const [name, keys] of Object.entries(spec.arrays ?? {})) {
2564
+ const fieldKey = seenNames.get(name);
2565
+ if (fieldKey !== void 0) {
2566
+ issues.push({
2567
+ path: ["arrays", name],
2568
+ message: `inputSpec.arrays "${name}" is also the provider field of inputSpec.fields "${fieldKey}"`
2569
+ });
2570
+ }
2571
+ for (const key of keys) {
2572
+ if (allowed !== void 0 && !allowed.has(key)) {
2573
+ issues.push({
2574
+ path: ["arrays", name],
2575
+ message: `inputSpec.arrays "${key}" is not supported for ${row.type} rows`
2576
+ });
2577
+ }
2578
+ if (key in spec.fields) {
2579
+ issues.push({
2580
+ path: ["arrays", name],
2581
+ message: `inputSpec.arrays "${key}" is already mapped in inputSpec.fields`
2582
+ });
2583
+ }
2584
+ const owner = arrayOwner.get(key);
2585
+ if (owner === void 0) arrayOwner.set(key, name);
2586
+ else {
2587
+ issues.push({
2588
+ path: ["arrays", name],
2589
+ message: owner === name ? `inputSpec.arrays "${name}" lists "${key}" twice` : `inputSpec.arrays "${owner}" and "${name}" both take "${key}"`
2590
+ });
2591
+ }
2592
+ }
2593
+ }
2509
2594
  const supports = row.constraints.supports ?? [];
2510
2595
  for (const key of INPUT_SPEC_SUPPORTS_KEYS) {
2511
- if (key in spec.fields && !supports.includes(key)) {
2596
+ if (supports.includes(key)) continue;
2597
+ if (key in spec.fields) {
2512
2598
  issues.push({
2513
2599
  path: ["fields", key],
2514
2600
  message: `inputSpec.fields "${key}" needs "${key}" in constraints.supports`
2515
2601
  });
2516
2602
  }
2603
+ const arrayName = arrayOwner.get(key);
2604
+ if (arrayName !== void 0) {
2605
+ issues.push({
2606
+ path: ["arrays", arrayName],
2607
+ message: `inputSpec.arrays "${key}" needs "${key}" in constraints.supports`
2608
+ });
2609
+ }
2517
2610
  }
2518
2611
  if (issues.length > 0) return { ok: false, issues };
2519
2612
  return { ok: true, spec };
@@ -2562,6 +2655,10 @@ var AspectRatioSchema = import_zod3.z.enum([
2562
2655
  "21:9",
2563
2656
  "9:21",
2564
2657
  "97:30",
2658
+ "1:4",
2659
+ "4:1",
2660
+ "1:8",
2661
+ "8:1",
2565
2662
  "adaptive"
2566
2663
  ]);
2567
2664
  var ImageSizeSchema = import_zod3.z.enum(["512", "1K", "2K", "4K"]);
@@ -2731,6 +2828,7 @@ var CatalogCapabilitiesSchema = import_zod3.z.looseObject({
2731
2828
  acceptsSourceVideo: import_zod3.z.boolean().optional(),
2732
2829
  videoEdit: import_zod3.z.boolean().optional(),
2733
2830
  framesExcludeReferences: import_zod3.z.boolean().optional(),
2831
+ sourceVideoExcludesFrames: import_zod3.z.boolean().optional(),
2734
2832
  lastFrameRequiresFirstFrame: import_zod3.z.boolean().optional(),
2735
2833
  reasoning: import_zod3.z.boolean().optional(),
2736
2834
  vision: import_zod3.z.boolean().optional(),
@@ -4258,6 +4356,17 @@ function applyInputSpec(spec, req, refToUrl2, opts) {
4258
4356
  const mapped = spec.values?.[canonical]?.[String(value)];
4259
4357
  out[providerField] = mapped ?? serialised;
4260
4358
  }
4359
+ for (const [providerField, keys] of Object.entries(spec.arrays ?? {})) {
4360
+ const urls = [];
4361
+ for (const key of keys) {
4362
+ if (!isArrayInputSpecKey(key)) continue;
4363
+ const value = canonicalValue(req, key);
4364
+ if (value === void 0) continue;
4365
+ if (Array.isArray(value)) urls.push(...value.map(refToUrl2));
4366
+ else if (isMediaRef(value)) urls.push(refToUrl2(value));
4367
+ }
4368
+ if (urls.length > 0) out[providerField] = urls;
4369
+ }
4261
4370
  for (const [key, value] of Object.entries(spec.static ?? {})) {
4262
4371
  out[key] = value;
4263
4372
  }
@@ -8377,6 +8486,26 @@ function resolveVideoOptions(model, request, onUnsupported = "error") {
8377
8486
  });
8378
8487
  delete merged.sourceVideo;
8379
8488
  }
8489
+ const imageInputs = ["firstFrame", "lastFrame", "referenceImages"].filter(
8490
+ (field) => field === "referenceImages" ? compact(merged.referenceImages).length > 0 : merged[field] !== void 0
8491
+ );
8492
+ if (merged.sourceVideo !== void 0 && imageInputs.length > 0 && model.capabilities?.sourceVideoExcludesFrames) {
8493
+ if (onUnsupported === "error") {
8494
+ throw new ModelKitError({
8495
+ code: "unsupported_option",
8496
+ message: `${model.id} takes a source video or images, not both`,
8497
+ model: model.id
8498
+ });
8499
+ }
8500
+ for (const field of imageInputs) {
8501
+ warnings.push({
8502
+ code: "option_ignored",
8503
+ field,
8504
+ message: `${model.id} takes a source video or images, not both; ${field} dropped`
8505
+ });
8506
+ delete merged[field];
8507
+ }
8508
+ }
8380
8509
  if (merged.lastFrame !== void 0 && merged.firstFrame === void 0 && model.capabilities?.lastFrameRequiresFirstFrame) {
8381
8510
  if (onUnsupported === "error") {
8382
8511
  throw new ModelKitError({
@@ -9487,6 +9616,7 @@ var describe = (modelId) => createDefaultModelKit().describe(modelId);
9487
9616
  0 && (module.exports = {
9488
9617
  ADAPTER_IDS,
9489
9618
  ADAPTER_KEYS,
9619
+ ARRAY_INPUT_SPEC_KEYS,
9490
9620
  AUDIO_INPUT_SPEC_KEYS,
9491
9621
  Catalog,
9492
9622
  CatalogModelSchema,
@@ -9529,6 +9659,7 @@ var describe = (modelId) => createDefaultModelKit().describe(modelId);
9529
9659
  googleImageAdapter,
9530
9660
  hasAwsCredentials,
9531
9661
  higgsfieldAdapter,
9662
+ isArrayInputSpecKey,
9532
9663
  isInputSpecKey,
9533
9664
  isListed,
9534
9665
  isListedOn,