@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/CHANGELOG.md +19 -0
- package/README.md +2 -1
- package/dist/catalog.cjs +118 -5
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +12 -1
- package/dist/catalog.d.ts +12 -1
- package/dist/catalog.js +118 -5
- package/dist/catalog.js.map +1 -1
- package/dist/index.cjs +136 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +134 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,25 @@ header in the release PR.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.3.3] - Unreleased
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `inputSpec.arrays` joins several image inputs into one provider array field, for a model that takes its start frame, end frame and storyboard frames in one list. It maps a provider field to an ordered list of canonical keys: `arrays: { images: ["firstFrame", "referenceImages", "lastFrame"] }` sends the first frame, then the reference images, then the last frame. The field is omitted when none of the inputs is set. Video rows take `firstFrame`, `referenceImages` and `lastFrame`; image rows take `references`. A key is mapped once: in `fields` or in one array. An array name must not repeat a `fields` provider name or a `static` key, and `lastFrame` in an array needs `lastFrame` in `constraints.supports`.
|
|
16
|
+
- `sourceVideo` is an `inputSpec.fields` key on video rows, so a Replicate video row can send a source clip (for example `sourceVideo: "start_video"`). The row also needs `capabilities.acceptsSourceVideo`, or the resolver drops the clip before the adapter runs.
|
|
17
|
+
- `capabilities.sourceVideoExcludesFrames`: a source video is never sent with a first frame, a last frame or reference images. `resolveVideoOptions` refuses the mix under `"error"` and drops the images with an `option_ignored` warning for each under `"coerce"`.
|
|
18
|
+
|
|
19
|
+
### Live rows to add after publish
|
|
20
|
+
Add a Replicate video row that uses `inputSpec.arrays` or maps `sourceVideo` (for example `black-forest-labs/flux-3`) only after every app that will call it runs 0.3.3. An older kit reads such a spec as invalid: the row stays in the catalog and every call to it fails. A row of this shape should set `capabilities.lastFrameRequiresFirstFrame`, because a lone end frame would be sent as the first element of the array.
|
|
21
|
+
|
|
22
|
+
## [0.3.2] - Unreleased
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Aspect ratios `1:4`, `4:1`, `1:8` and `8:1` in the `AspectRatio` type and the row schema. Gemini Nano Banana 2.1 serves them at 1K, 2K and 4K.
|
|
26
|
+
- Snapshot row `gemini-nano-banana-2.1` (alias `nb-2.1`, 1K/2K/4K, 14 references, the four new ratios plus the usual Gemini set). It runs on the existing `google-image` adapter.
|
|
27
|
+
|
|
28
|
+
### Live rows to update after publish
|
|
29
|
+
The live `gemini-nano-banana-2.1` rows (dev, stg, prod) keep the old ratio list until the four ratios are added in pro-admin. An app on 0.3.1 or older rejects a row that carries a ratio it does not know, so edit the live rows only after every app that reads the catalog runs 0.3.2.
|
|
30
|
+
|
|
12
31
|
## [0.3.1] - Unreleased
|
|
13
32
|
|
|
14
33
|
### Added
|
package/README.md
CHANGED
|
@@ -218,12 +218,13 @@ instead of a kit change. It is read by the Replicate and fal adapters only. `fie
|
|
|
218
218
|
`values` remaps canonical values per key, and `static` merges literal provider fields
|
|
219
219
|
last. Video rows use `VIDEO_INPUT_SPEC_KEYS` (`prompt`, `durationSeconds`,
|
|
220
220
|
`aspectRatio`, `resolution`, `firstFrame`, `lastFrame`, `seed`, `negativePrompt`,
|
|
221
|
-
`generateAudio`, `cfgScale`, `promptExpansion`, `referenceImages`); image rows use
|
|
221
|
+
`generateAudio`, `cfgScale`, `promptExpansion`, `referenceImages`, `sourceVideo`); image rows use
|
|
222
222
|
`IMAGE_INPUT_SPEC_KEYS` (`prompt`, `references`, `aspectRatio`, `size`, `outputFormat`,
|
|
223
223
|
`seed`; map `references` to the provider's array field and remap `size` and `outputFormat`
|
|
224
224
|
with `values`); audio rows use
|
|
225
225
|
`AUDIO_INPUT_SPEC_KEYS` (`prompt`, `durationSeconds`, `seed`, `negativePrompt`,
|
|
226
226
|
`references`, `sourceVideo`, `instrumental`). `aspectRatio: "adaptive"` is omitted. A fal image row has no kind and builds its whole input from the spec; the optional `editEndpoint` (fal rows only) is the fal endpoint called instead of `providerModelId` when the request has references. fal rows without an `inputSpec` keep the Muse-shaped request and Ideogram keeps its own path; an `inputSpec` on an Ideogram row replaces that path. `editEndpoint` needs a `references` entry in `fields`. `inputSpec` on fal video rows is not supported.
|
|
227
|
+
`arrays` joins several image inputs into one provider array field, in the listed order: `arrays: { images: ["firstFrame", "referenceImages", "lastFrame"] }` (video rows: `firstFrame`, `referenceImages`, `lastFrame`; image rows: `references`). A key is mapped once, in `fields` or in one array, and the field is omitted when none of its inputs is set. A row that maps `sourceVideo` also needs `capabilities.acceptsSourceVideo`; set `capabilities.sourceVideoExcludesFrames` when the provider rejects a source video next to images.
|
|
227
228
|
`validateInputSpec` is the shared contract the write schema, `dataOnlyCapable`, and
|
|
228
229
|
the Replicate mapper all check. `dataOnlyCapable(row)` reports whether such a row would
|
|
229
230
|
be served.
|
package/dist/catalog.cjs
CHANGED
|
@@ -222,13 +222,21 @@ var GEMINI_RATIOS = [
|
|
|
222
222
|
"5:4",
|
|
223
223
|
"adaptive"
|
|
224
224
|
];
|
|
225
|
+
var NANO_BANANA_2_1_RATIOS = [
|
|
226
|
+
...GEMINI_RATIOS.filter((r) => r !== "adaptive"),
|
|
227
|
+
"1:4",
|
|
228
|
+
"4:1",
|
|
229
|
+
"1:8",
|
|
230
|
+
"8:1",
|
|
231
|
+
"adaptive"
|
|
232
|
+
];
|
|
225
233
|
var GEMINI_PROMPT_MAX_CHARS = 5e4;
|
|
226
234
|
var GPT_IMAGE_PROMPT = { referenceSyntax: "numbered", maxChars: 32e3 };
|
|
227
|
-
function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars) {
|
|
235
|
+
function geminiImage(id, label, aliases, sizes, display, exposure, references, promptMaxChars, aspectRatios = GEMINI_RATIOS) {
|
|
228
236
|
const prompt = { referenceSyntax: "caption" };
|
|
229
237
|
if (promptMaxChars !== void 0) prompt.maxChars = promptMaxChars;
|
|
230
238
|
const constraints = {
|
|
231
|
-
aspectRatios: [...
|
|
239
|
+
aspectRatios: [...aspectRatios],
|
|
232
240
|
sizes,
|
|
233
241
|
supports: ["safetySettings", "temperature"],
|
|
234
242
|
prompt
|
|
@@ -252,6 +260,21 @@ function geminiImage(id, label, aliases, sizes, display, exposure, references, p
|
|
|
252
260
|
return model;
|
|
253
261
|
}
|
|
254
262
|
var IMAGE_MODELS = [
|
|
263
|
+
geminiImage(
|
|
264
|
+
"gemini-nano-banana-2.1",
|
|
265
|
+
"Nano Banana 2.1",
|
|
266
|
+
["nb-2.1"],
|
|
267
|
+
["1K", "2K", "4K"],
|
|
268
|
+
{
|
|
269
|
+
provider: "google",
|
|
270
|
+
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.",
|
|
271
|
+
logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
|
|
272
|
+
},
|
|
273
|
+
["cowork", "space_builder"],
|
|
274
|
+
{ images: 14 },
|
|
275
|
+
GEMINI_PROMPT_MAX_CHARS,
|
|
276
|
+
NANO_BANANA_2_1_RATIOS
|
|
277
|
+
),
|
|
255
278
|
geminiImage(
|
|
256
279
|
"gemini-3-pro-image-preview",
|
|
257
280
|
"Nano Banana Pro",
|
|
@@ -2280,7 +2303,8 @@ var VIDEO_INPUT_SPEC_KEYS = [
|
|
|
2280
2303
|
"generateAudio",
|
|
2281
2304
|
"cfgScale",
|
|
2282
2305
|
"promptExpansion",
|
|
2283
|
-
"referenceImages"
|
|
2306
|
+
"referenceImages",
|
|
2307
|
+
"sourceVideo"
|
|
2284
2308
|
];
|
|
2285
2309
|
var IMAGE_INPUT_SPEC_KEYS = [
|
|
2286
2310
|
"prompt",
|
|
@@ -2299,6 +2323,12 @@ var AUDIO_INPUT_SPEC_KEYS = [
|
|
|
2299
2323
|
"sourceVideo",
|
|
2300
2324
|
"instrumental"
|
|
2301
2325
|
];
|
|
2326
|
+
var ARRAY_INPUT_SPEC_KEYS = [
|
|
2327
|
+
"firstFrame",
|
|
2328
|
+
"referenceImages",
|
|
2329
|
+
"lastFrame",
|
|
2330
|
+
"references"
|
|
2331
|
+
];
|
|
2302
2332
|
var INPUT_SPEC_SUPPORTS_KEYS = [
|
|
2303
2333
|
"lastFrame",
|
|
2304
2334
|
"seed",
|
|
@@ -2312,6 +2342,7 @@ var INPUT_SPEC_SUPPORTS_KEYS = [
|
|
|
2312
2342
|
var VIDEO_KEY_SET = new Set(VIDEO_INPUT_SPEC_KEYS);
|
|
2313
2343
|
var IMAGE_KEY_SET = new Set(IMAGE_INPUT_SPEC_KEYS);
|
|
2314
2344
|
var AUDIO_KEY_SET = new Set(AUDIO_INPUT_SPEC_KEYS);
|
|
2345
|
+
var ARRAY_KEY_SET = new Set(ARRAY_INPUT_SPEC_KEYS);
|
|
2315
2346
|
var ALL_KEY_SET = /* @__PURE__ */ new Set([
|
|
2316
2347
|
...VIDEO_INPUT_SPEC_KEYS,
|
|
2317
2348
|
...IMAGE_INPUT_SPEC_KEYS,
|
|
@@ -2356,13 +2387,30 @@ var ValuesSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.re
|
|
|
2356
2387
|
}
|
|
2357
2388
|
});
|
|
2358
2389
|
var StaticSchema = import_zod2.z.record(import_zod2.z.string(), ProviderScalarSchema);
|
|
2390
|
+
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) => {
|
|
2391
|
+
for (const [name, keys] of Object.entries(arrays)) {
|
|
2392
|
+
for (const key of keys) {
|
|
2393
|
+
if (!ARRAY_KEY_SET.has(key)) {
|
|
2394
|
+
ctx.addIssue({
|
|
2395
|
+
code: "custom",
|
|
2396
|
+
path: [name],
|
|
2397
|
+
message: `inputSpec.arrays "${name}" has unknown image key "${key}"`
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2359
2403
|
var InputSpecSchema = import_zod2.z.strictObject({
|
|
2360
2404
|
fields: FieldsSchema,
|
|
2361
2405
|
values: ValuesSchema.optional(),
|
|
2362
2406
|
static: StaticSchema.optional(),
|
|
2407
|
+
arrays: ArraysSchema.optional(),
|
|
2363
2408
|
editEndpoint: import_zod2.z.string().min(1).optional()
|
|
2364
2409
|
}).superRefine((spec, ctx) => {
|
|
2365
|
-
const mappedNames = new Set(
|
|
2410
|
+
const mappedNames = /* @__PURE__ */ new Set([
|
|
2411
|
+
...Object.values(spec.fields),
|
|
2412
|
+
...Object.keys(spec.arrays ?? {})
|
|
2413
|
+
]);
|
|
2366
2414
|
for (const key of Object.keys(spec.static ?? {})) {
|
|
2367
2415
|
if (mappedNames.has(key)) {
|
|
2368
2416
|
ctx.addIssue({
|
|
@@ -2451,14 +2499,54 @@ function validateInputSpec(raw, row) {
|
|
|
2451
2499
|
});
|
|
2452
2500
|
}
|
|
2453
2501
|
}
|
|
2502
|
+
const arrayOwner = /* @__PURE__ */ new Map();
|
|
2503
|
+
for (const [name, keys] of Object.entries(spec.arrays ?? {})) {
|
|
2504
|
+
const fieldKey = seenNames.get(name);
|
|
2505
|
+
if (fieldKey !== void 0) {
|
|
2506
|
+
issues.push({
|
|
2507
|
+
path: ["arrays", name],
|
|
2508
|
+
message: `inputSpec.arrays "${name}" is also the provider field of inputSpec.fields "${fieldKey}"`
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
for (const key of keys) {
|
|
2512
|
+
if (allowed !== void 0 && !allowed.has(key)) {
|
|
2513
|
+
issues.push({
|
|
2514
|
+
path: ["arrays", name],
|
|
2515
|
+
message: `inputSpec.arrays "${key}" is not supported for ${row.type} rows`
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
if (key in spec.fields) {
|
|
2519
|
+
issues.push({
|
|
2520
|
+
path: ["arrays", name],
|
|
2521
|
+
message: `inputSpec.arrays "${key}" is already mapped in inputSpec.fields`
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
const owner = arrayOwner.get(key);
|
|
2525
|
+
if (owner === void 0) arrayOwner.set(key, name);
|
|
2526
|
+
else {
|
|
2527
|
+
issues.push({
|
|
2528
|
+
path: ["arrays", name],
|
|
2529
|
+
message: owner === name ? `inputSpec.arrays "${name}" lists "${key}" twice` : `inputSpec.arrays "${owner}" and "${name}" both take "${key}"`
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2454
2534
|
const supports = row.constraints.supports ?? [];
|
|
2455
2535
|
for (const key of INPUT_SPEC_SUPPORTS_KEYS) {
|
|
2456
|
-
if (
|
|
2536
|
+
if (supports.includes(key)) continue;
|
|
2537
|
+
if (key in spec.fields) {
|
|
2457
2538
|
issues.push({
|
|
2458
2539
|
path: ["fields", key],
|
|
2459
2540
|
message: `inputSpec.fields "${key}" needs "${key}" in constraints.supports`
|
|
2460
2541
|
});
|
|
2461
2542
|
}
|
|
2543
|
+
const arrayName = arrayOwner.get(key);
|
|
2544
|
+
if (arrayName !== void 0) {
|
|
2545
|
+
issues.push({
|
|
2546
|
+
path: ["arrays", arrayName],
|
|
2547
|
+
message: `inputSpec.arrays "${key}" needs "${key}" in constraints.supports`
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2462
2550
|
}
|
|
2463
2551
|
if (issues.length > 0) return { ok: false, issues };
|
|
2464
2552
|
return { ok: true, spec };
|
|
@@ -2507,6 +2595,10 @@ var AspectRatioSchema = import_zod3.z.enum([
|
|
|
2507
2595
|
"21:9",
|
|
2508
2596
|
"9:21",
|
|
2509
2597
|
"97:30",
|
|
2598
|
+
"1:4",
|
|
2599
|
+
"4:1",
|
|
2600
|
+
"1:8",
|
|
2601
|
+
"8:1",
|
|
2510
2602
|
"adaptive"
|
|
2511
2603
|
]);
|
|
2512
2604
|
var ImageSizeSchema = import_zod3.z.enum(["512", "1K", "2K", "4K"]);
|
|
@@ -2676,6 +2768,7 @@ var CatalogCapabilitiesSchema = import_zod3.z.looseObject({
|
|
|
2676
2768
|
acceptsSourceVideo: import_zod3.z.boolean().optional(),
|
|
2677
2769
|
videoEdit: import_zod3.z.boolean().optional(),
|
|
2678
2770
|
framesExcludeReferences: import_zod3.z.boolean().optional(),
|
|
2771
|
+
sourceVideoExcludesFrames: import_zod3.z.boolean().optional(),
|
|
2679
2772
|
lastFrameRequiresFirstFrame: import_zod3.z.boolean().optional(),
|
|
2680
2773
|
reasoning: import_zod3.z.boolean().optional(),
|
|
2681
2774
|
vision: import_zod3.z.boolean().optional(),
|
|
@@ -3373,6 +3466,26 @@ function resolveVideoOptions(model, request, onUnsupported = "error") {
|
|
|
3373
3466
|
});
|
|
3374
3467
|
delete merged.sourceVideo;
|
|
3375
3468
|
}
|
|
3469
|
+
const imageInputs = ["firstFrame", "lastFrame", "referenceImages"].filter(
|
|
3470
|
+
(field) => field === "referenceImages" ? compact(merged.referenceImages).length > 0 : merged[field] !== void 0
|
|
3471
|
+
);
|
|
3472
|
+
if (merged.sourceVideo !== void 0 && imageInputs.length > 0 && model.capabilities?.sourceVideoExcludesFrames) {
|
|
3473
|
+
if (onUnsupported === "error") {
|
|
3474
|
+
throw new ModelKitError({
|
|
3475
|
+
code: "unsupported_option",
|
|
3476
|
+
message: `${model.id} takes a source video or images, not both`,
|
|
3477
|
+
model: model.id
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
for (const field of imageInputs) {
|
|
3481
|
+
warnings.push({
|
|
3482
|
+
code: "option_ignored",
|
|
3483
|
+
field,
|
|
3484
|
+
message: `${model.id} takes a source video or images, not both; ${field} dropped`
|
|
3485
|
+
});
|
|
3486
|
+
delete merged[field];
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3376
3489
|
if (merged.lastFrame !== void 0 && merged.firstFrame === void 0 && model.capabilities?.lastFrameRequiresFirstFrame) {
|
|
3377
3490
|
if (onUnsupported === "error") {
|
|
3378
3491
|
throw new ModelKitError({
|