@shop-os/modelkit 0.2.0 → 0.2.2

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,36 @@ header in the release PR.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.2.2] - 2026-09-15
13
+
14
+ Option audit against provider docs, limited to options a product surface sets.
15
+
16
+ ### Added
17
+ - Veo 3.1, 3.1 Fast and 3.1 Lite accept `seed` (sent as `config.seed`).
18
+ - Seedream 5.0 Pro accepts `outputFormat` png/jpeg (sent as `output_format`; webp is coerced to png with a warning).
19
+ - Gemini 3 image rows declare `references.images: 14`, the provider cap.
20
+
21
+ ### Fixed
22
+ - `liveCatalog` and the prime state are one process-wide singleton (on `globalThis`). The root and `./catalog` bundles each carried their own copy, so a server that primed through the root entry still served the snapshot to code importing `@shop-os/modelkit/catalog`.
23
+
24
+ ### Changed
25
+ - gpt-image-2.5-sunburst/flare `qualityOptions` add `xhigh` and `max`.
26
+ - Veo 3.1 Fast and Lite durations add 6 s. Veo 3.1 Lite drops `references` (not supported for Lite).
27
+ - Gemini Omni rows allow 3 reference videos (was 1).
28
+ - Seedance rows declare `ratioIgnoredWith: ["firstFrame", "sourceVideo"]` to match the adapter.
29
+ - kling-v3-omni-video aspect ratios are 16:9, 9:16, 1:1, adaptive (provider list).
30
+ - seedream-4.5 drops the 1K tier (provider minimum 2K). gemini-2.5-flash-image is 1K only. muse-image-1.0 drops 4:5 and 5:4.
31
+ - qwen-image-2-pro no longer requires a reference image.
32
+ - Prodia nano-banana rows drop `seed` (the adapter never sent it).
33
+
34
+ ## [0.2.1] - 2026-09-15
35
+
36
+ ### Added
37
+ - `gpt-image-2`, `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare` declare `outputFormat`; the `openai-image` adapter sends `output_format` (png or jpeg) and labels the result mime to match.
38
+
39
+ ### Changed
40
+ - `outputFormat: "webp"` on GPT image models is coerced to png with an `option_ignored` warning: the API returns PNG bytes for webp (openai/openai-node#1850).
41
+
12
42
  ## [0.2.0] - 2026-09-14
13
43
 
14
44
  ### Added
@@ -210,6 +240,8 @@ First publish, renamed from `@shop-os/model-sdk`.
210
240
  type, one usage event with the canonical id.
211
241
 
212
242
  [Unreleased]: https://github.com/Shop-OS/shopos-backend-ts/compare/modelkit-0.2.0...dev
243
+ [0.2.2]: https://github.com/Shop-OS/shopos-backend-ts/compare/modelkit-0.2.1...modelkit-0.2.2
244
+ [0.2.1]: https://github.com/Shop-OS/shopos-backend-ts/compare/modelkit-0.2.0...modelkit-0.2.1
213
245
  [0.2.0]: https://github.com/Shop-OS/shopos-backend-ts/compare/modelkit-0.1.11...modelkit-0.2.0
214
246
  [0.1.11]: https://github.com/Shop-OS/shopos-backend-ts/compare/f0981d9b9...bb3b16354
215
247
  [0.1.10]: https://github.com/Shop-OS/shopos-backend-ts/compare/f17e822e2...f0981d9b9
package/dist/catalog.cjs CHANGED
@@ -32,6 +32,7 @@ __export(catalog_entry_exports, {
32
32
  VIDEO_MODELS: () => VIDEO_MODELS,
33
33
  isModelKitError: () => isModelKitError,
34
34
  liveCatalog: () => liveCatalog,
35
+ liveState: () => liveState,
35
36
  nearestAspectRatio: () => nearestAspectRatio,
36
37
  primeCatalog: () => primeCatalog,
37
38
  resetLiveCatalog: () => resetLiveCatalog,
@@ -162,7 +163,7 @@ var GEMINI_RATIOS = [
162
163
  "5:4",
163
164
  "adaptive"
164
165
  ];
165
- function geminiImage(id, label, aliases, sizes, display, exposure) {
166
+ function geminiImage(id, label, aliases, sizes, display, exposure, references) {
166
167
  const model = {
167
168
  id,
168
169
  aliases,
@@ -175,7 +176,8 @@ function geminiImage(id, label, aliases, sizes, display, exposure) {
175
176
  constraints: {
176
177
  aspectRatios: [...GEMINI_RATIOS],
177
178
  sizes,
178
- supports: ["safetySettings", "temperature"]
179
+ supports: ["safetySettings", "temperature"],
180
+ ...references ? { references } : {}
179
181
  },
180
182
  defaults: { size: "1K", aspectRatio: "1:1" },
181
183
  usage: { ...IMAGE_USAGE, slug: "gemini" },
@@ -195,7 +197,8 @@ var IMAGE_MODELS = [
195
197
  description: "Create stunning, production-quality visuals with exceptional realism, detail, and typography. Best for campaigns, e-commerce, product photography, and hero creatives.",
196
198
  logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
197
199
  },
198
- "catalog"
200
+ "catalog",
201
+ { images: 14 }
199
202
  ),
200
203
  geminiImage(
201
204
  "gemini-3.1-flash-image-preview",
@@ -208,7 +211,8 @@ var IMAGE_MODELS = [
208
211
  description: "Generate high-quality images at speed. Perfect for brainstorming, moodboards, concept testing, creative exploration, and large batch workflows.",
209
212
  logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
210
213
  },
211
- "catalog"
214
+ "catalog",
215
+ { images: 14 }
212
216
  ),
213
217
  geminiImage(
214
218
  "gemini-3.1-flash-lite-image",
@@ -220,14 +224,15 @@ var IMAGE_MODELS = [
220
224
  description: "The fastest and most affordable image model. Ideal for rapid drafts, bulk exploration, and iterative concept testing at 1K resolution.",
221
225
  logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
222
226
  },
223
- "catalog"
227
+ "catalog",
228
+ { images: 14 }
224
229
  ),
225
230
  // trigger-workflows validates 1K only; pricing seeds 2K/4K rows so allow all three.
226
231
  geminiImage(
227
232
  "gemini-2.5-flash-image",
228
233
  "Gemini 2.5 Flash Image",
229
234
  [],
230
- ["1K", "2K", "4K"],
235
+ ["1K"],
231
236
  {
232
237
  provider: "google",
233
238
  description: "Nano Banana (legacy Gemini 2.5 Flash Image) native image generation and editing model for high-volume, low-latency creative workflows."
@@ -258,7 +263,7 @@ var IMAGE_MODELS = [
258
263
  sizes: ["1K", "2K", "4K"],
259
264
  qualityOptions: ["auto", "low", "medium", "high"],
260
265
  references: { images: 16 },
261
- supports: ["quality", "count"]
266
+ supports: ["quality", "count", "outputFormat"]
262
267
  },
263
268
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
264
269
  usage: { ...IMAGE_USAGE, slug: "gpt" },
@@ -278,7 +283,7 @@ var IMAGE_MODELS = [
278
283
  adapter: "openai-image",
279
284
  providerModelId: "gpt-image-2.5-sunburst",
280
285
  lifecycle: "active",
281
- exposure: "internal",
286
+ exposure: "catalog",
282
287
  constraints: {
283
288
  exactSizes: {
284
289
  "1:1": "1024x1024",
@@ -291,9 +296,9 @@ var IMAGE_MODELS = [
291
296
  },
292
297
  aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
293
298
  sizes: ["1K", "2K", "4K"],
294
- qualityOptions: ["auto", "low", "medium", "high"],
299
+ qualityOptions: ["auto", "low", "medium", "high", "xhigh", "max"],
295
300
  references: { images: 16 },
296
- supports: ["quality", "count"]
301
+ supports: ["quality", "count", "outputFormat"]
297
302
  },
298
303
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
299
304
  usage: { ...IMAGE_USAGE },
@@ -312,7 +317,7 @@ var IMAGE_MODELS = [
312
317
  adapter: "openai-image",
313
318
  providerModelId: "gpt-image-2.5-flare",
314
319
  lifecycle: "active",
315
- exposure: "internal",
320
+ exposure: "catalog",
316
321
  constraints: {
317
322
  exactSizes: {
318
323
  "1:1": "1024x1024",
@@ -325,9 +330,9 @@ var IMAGE_MODELS = [
325
330
  },
326
331
  aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
327
332
  sizes: ["1K", "2K", "4K"],
328
- qualityOptions: ["auto", "low", "medium", "high"],
333
+ qualityOptions: ["auto", "low", "medium", "high", "xhigh", "max"],
329
334
  references: { images: 16 },
330
- supports: ["quality", "count"]
335
+ supports: ["quality", "count", "outputFormat"]
331
336
  },
332
337
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
333
338
  usage: { ...IMAGE_USAGE },
@@ -357,8 +362,6 @@ var IMAGE_MODELS = [
357
362
  "2:3",
358
363
  "3:4",
359
364
  "9:16",
360
- "4:5",
361
- "5:4",
362
365
  "9:21",
363
366
  "adaptive"
364
367
  ],
@@ -390,7 +393,7 @@ var IMAGE_MODELS = [
390
393
  references: { images: 10 },
391
394
  // Ark's Images API has no output-format, quality or count field; only
392
395
  // the seed is forwarded, so anything else must warn, not vanish.
393
- supports: ["seed"]
396
+ supports: ["seed", "outputFormat"]
394
397
  },
395
398
  defaults: { size: "1K", aspectRatio: "1:1" },
396
399
  usage: { ...IMAGE_USAGE },
@@ -426,7 +429,7 @@ var IMAGE_MODELS = [
426
429
  "21:9",
427
430
  "9:21"
428
431
  ],
429
- sizes: ["1K", "2K", "4K"],
432
+ sizes: ["2K", "4K"],
430
433
  references: { images: 10 },
431
434
  supports: ["seed"]
432
435
  },
@@ -478,7 +481,7 @@ var IMAGE_MODELS = [
478
481
  aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
479
482
  sizes: ["1K", "2K", "4K"],
480
483
  requires: ["references"],
481
- supports: ["seed"]
484
+ supports: []
482
485
  },
483
486
  defaults: { size: "1K", aspectRatio: "1:1" },
484
487
  usage: { ...IMAGE_USAGE },
@@ -503,7 +506,7 @@ var IMAGE_MODELS = [
503
506
  aspectRatios: ["1:1", "2:3", "3:4", "4:3", "9:16", "16:9", "21:9"],
504
507
  sizes: ["1K"],
505
508
  requires: ["references"],
506
- supports: ["seed"]
509
+ supports: []
507
510
  },
508
511
  defaults: { size: "1K", aspectRatio: "1:1" },
509
512
  usage: { ...IMAGE_USAGE },
@@ -555,7 +558,6 @@ var IMAGE_MODELS = [
555
558
  aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"],
556
559
  sizes: [],
557
560
  references: { images: 1 },
558
- requires: ["references"],
559
561
  supports: []
560
562
  },
561
563
  defaults: { aspectRatio: "1:1" },
@@ -1274,7 +1276,7 @@ var VIDEO_MODELS = [
1274
1276
  resolutions: ["720p", "1080p", "4k"],
1275
1277
  durations: [8],
1276
1278
  references: { images: 3 },
1277
- supports: ["lastFrame"]
1279
+ supports: ["lastFrame", "seed"]
1278
1280
  },
1279
1281
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
1280
1282
  usage: { ...VIDEO_USAGE, slug: "veo" },
@@ -1302,9 +1304,9 @@ var VIDEO_MODELS = [
1302
1304
  constraints: {
1303
1305
  aspectRatios: [...OMNI_RATIOS],
1304
1306
  resolutions: ["720p", "1080p", "4k"],
1305
- durations: [4, 8],
1307
+ durations: [4, 6, 8],
1306
1308
  references: { images: 3 },
1307
- supports: ["lastFrame"]
1309
+ supports: ["lastFrame", "seed"]
1308
1310
  },
1309
1311
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
1310
1312
  usage: { ...VIDEO_USAGE },
@@ -1331,9 +1333,8 @@ var VIDEO_MODELS = [
1331
1333
  constraints: {
1332
1334
  aspectRatios: [...OMNI_RATIOS],
1333
1335
  resolutions: ["720p", "1080p"],
1334
- durations: [4, 8],
1335
- references: { images: 3 },
1336
- supports: ["lastFrame"]
1336
+ durations: [4, 6, 8],
1337
+ supports: ["lastFrame", "seed"]
1337
1338
  },
1338
1339
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
1339
1340
  usage: { ...VIDEO_USAGE },
@@ -1365,7 +1366,7 @@ var VIDEO_MODELS = [
1365
1366
  // reference clip keeps its own framing.
1366
1367
  aspectRatios: [...OMNI_RATIOS],
1367
1368
  durations: [3, 4, 5, 6, 7, 8, 9, 10],
1368
- references: { images: 6, videos: 1 },
1369
+ references: { images: 6, videos: 3 },
1369
1370
  supports: []
1370
1371
  },
1371
1372
  defaults: {},
@@ -1391,7 +1392,7 @@ var VIDEO_MODELS = [
1391
1392
  aspectRatios: [...OMNI_RATIOS],
1392
1393
  resolutions: ["360p", "720p", "1080p", "4k"],
1393
1394
  durations: [3, 4, 5, 6, 7, 8, 9, 10],
1394
- references: { images: 6, videos: 1 },
1395
+ references: { images: 6, videos: 3 },
1395
1396
  supports: []
1396
1397
  },
1397
1398
  defaults: {},
@@ -1420,7 +1421,7 @@ var VIDEO_MODELS = [
1420
1421
  durations: { min: 4, max: 15 },
1421
1422
  durationPresets: [4, 6, 8, 10, 12, 15],
1422
1423
  references: { images: 9, videos: 3, audios: 3, audioOnly: false },
1423
- ratioIgnoredWith: ["firstFrame"],
1424
+ ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1424
1425
  supports: ["generateAudio", "seed", "lastFrame", "watermark"]
1425
1426
  },
1426
1427
  defaults: {
@@ -1452,7 +1453,7 @@ var VIDEO_MODELS = [
1452
1453
  resolutions: ["480p", "720p"],
1453
1454
  durations: { min: 4, max: 15 },
1454
1455
  references: { images: 9, videos: 3, audios: 3, audioOnly: false },
1455
- ratioIgnoredWith: ["firstFrame"],
1456
+ ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1456
1457
  supports: ["generateAudio", "seed", "lastFrame", "watermark"]
1457
1458
  },
1458
1459
  defaults: {
@@ -1485,7 +1486,7 @@ var VIDEO_MODELS = [
1485
1486
  resolutions: ["480p", "720p", "1080p"],
1486
1487
  durations: { min: 4, max: 30 },
1487
1488
  references: { images: 30, videos: 10, audios: 10 },
1488
- ratioIgnoredWith: ["firstFrame"],
1489
+ ratioIgnoredWith: ["firstFrame", "sourceVideo"],
1489
1490
  supports: ["generateAudio", "seed", "lastFrame", "watermark"]
1490
1491
  },
1491
1492
  defaults: {
@@ -1604,18 +1605,7 @@ var VIDEO_MODELS = [
1604
1605
  lifecycle: "active",
1605
1606
  exposure: "catalog",
1606
1607
  constraints: {
1607
- aspectRatios: [
1608
- "1:1",
1609
- "16:9",
1610
- "9:16",
1611
- "4:3",
1612
- "3:4",
1613
- "3:2",
1614
- "2:3",
1615
- "21:9",
1616
- "4:5",
1617
- "adaptive"
1618
- ],
1608
+ aspectRatios: [...KLING_RATIOS],
1619
1609
  resolutions: ["720p", "1080p"],
1620
1610
  durations: { min: 3, max: 15 },
1621
1611
  references: { images: 7 },
@@ -2077,13 +2067,18 @@ var CatalogSchema = import_zod.z.array(CatalogModelSchemaBase).superRefine((rows
2077
2067
  });
2078
2068
 
2079
2069
  // src/catalog/live.ts
2080
- var liveCatalog = new Catalog(MODELS);
2070
+ var KEY = /* @__PURE__ */ Symbol.for("@shop-os/modelkit/live-catalog");
2071
+ var store = globalThis;
2072
+ var liveState = store[KEY] ??= {
2073
+ catalog: new Catalog(MODELS),
2074
+ memo: void 0,
2075
+ timer: void 0,
2076
+ revision: void 0,
2077
+ origin: "snapshot"
2078
+ };
2079
+ var liveCatalog = liveState.catalog;
2081
2080
 
2082
2081
  // src/catalog/load.ts
2083
- var memo;
2084
- var timer;
2085
- var revision;
2086
- var origin = "snapshot";
2087
2082
  function rowCount() {
2088
2083
  return liveCatalog.list({ lifecycle: ["active", "deprecated", "retired"] }).length;
2089
2084
  }
@@ -2113,7 +2108,7 @@ function logPrimed(logger, result) {
2113
2108
  async function fetchRemote(source, logger) {
2114
2109
  const fetchImpl = source.fetch ?? globalThis.fetch;
2115
2110
  const headers = authHeaders(source.auth);
2116
- if (revision !== void 0) headers["If-None-Match"] = `"${revision}"`;
2111
+ if (liveState.revision !== void 0) headers["If-None-Match"] = `"${liveState.revision}"`;
2117
2112
  const res = await fetchImpl(`${source.baseUrl}/models/catalog`, {
2118
2113
  headers,
2119
2114
  signal: AbortSignal.timeout(source.timeoutMs ?? 1e4)
@@ -2137,16 +2132,16 @@ async function prime(source, logger) {
2137
2132
  try {
2138
2133
  const fetched = await fetchRemote(source, logger);
2139
2134
  if ("kept" in fetched) {
2140
- const result2 = { origin, rowCount: rowCount() };
2141
- if (revision !== void 0) result2.revision = revision;
2135
+ const result2 = { origin: liveState.origin, rowCount: rowCount() };
2136
+ if (liveState.revision !== void 0) result2.revision = liveState.revision;
2142
2137
  logPrimed(logger, result2);
2143
2138
  return result2;
2144
2139
  }
2145
2140
  liveCatalog.replace(fetched.rows);
2146
- origin = "remote";
2147
- revision = fetched.revision;
2141
+ liveState.origin = "remote";
2142
+ liveState.revision = fetched.revision;
2148
2143
  await writeCache(source, logger, fetched.rows, fetched.revision);
2149
- const result = { origin, rowCount: rowCount() };
2144
+ const result = { origin: liveState.origin, rowCount: rowCount() };
2150
2145
  if (fetched.revision !== void 0) result.revision = fetched.revision;
2151
2146
  logPrimed(logger, result);
2152
2147
  return result;
@@ -2158,19 +2153,23 @@ async function prime(source, logger) {
2158
2153
  if (!cached) throw new Error("no cached catalog");
2159
2154
  const { rows, revision: rev } = parseEnvelope(JSON.parse(cached));
2160
2155
  liveCatalog.replace(rows);
2161
- origin = "last-good";
2162
- revision = rev;
2163
- const result = { origin, rowCount: rowCount(), error };
2156
+ liveState.origin = "last-good";
2157
+ liveState.revision = rev;
2158
+ const result = {
2159
+ origin: liveState.origin,
2160
+ rowCount: rowCount(),
2161
+ error
2162
+ };
2164
2163
  if (rev !== void 0) result.revision = rev;
2165
2164
  logPrimed(logger, result);
2166
2165
  return result;
2167
2166
  } catch (cacheErr) {
2168
2167
  logger.warn("[modelkit] catalog cache failed, using snapshot", cacheErr);
2169
2168
  liveCatalog.replace([...MODELS]);
2170
- origin = "snapshot";
2171
- revision = void 0;
2169
+ liveState.origin = "snapshot";
2170
+ liveState.revision = void 0;
2172
2171
  const result = {
2173
- origin,
2172
+ origin: liveState.origin,
2174
2173
  rowCount: rowCount(),
2175
2174
  error
2176
2175
  };
@@ -2184,35 +2183,36 @@ async function refresh(source, logger) {
2184
2183
  const fetched = await fetchRemote(source, logger);
2185
2184
  if ("kept" in fetched) return;
2186
2185
  liveCatalog.replace(fetched.rows);
2187
- origin = "remote";
2188
- revision = fetched.revision;
2186
+ liveState.origin = "remote";
2187
+ liveState.revision = fetched.revision;
2189
2188
  await writeCache(source, logger, fetched.rows, fetched.revision);
2190
2189
  } catch (err) {
2191
2190
  logger.warn("[modelkit] catalog refresh failed, keeping current rows", err);
2192
2191
  }
2193
2192
  }
2194
2193
  function primeCatalog(source) {
2194
+ const memo = liveState.memo;
2195
2195
  if (memo) return memo;
2196
2196
  const logger = source.logger ?? console;
2197
- memo = prime(source, logger).then((result) => {
2197
+ liveState.memo = prime(source, logger).then((result) => {
2198
2198
  const ttlMs = source.ttlMs ?? 3e5;
2199
- if (ttlMs > 0 && timer === void 0) {
2200
- timer = setInterval(() => void refresh(source, logger), ttlMs);
2201
- const maybeUnref = timer;
2199
+ if (ttlMs > 0 && liveState.timer === void 0) {
2200
+ liveState.timer = setInterval(() => void refresh(source, logger), ttlMs);
2201
+ const maybeUnref = liveState.timer;
2202
2202
  maybeUnref.unref?.();
2203
2203
  }
2204
2204
  return result;
2205
2205
  });
2206
- return memo;
2206
+ return liveState.memo;
2207
2207
  }
2208
2208
  function resetLiveCatalog() {
2209
- if (timer !== void 0) {
2210
- clearInterval(timer);
2211
- timer = void 0;
2209
+ if (liveState.timer !== void 0) {
2210
+ clearInterval(liveState.timer);
2211
+ liveState.timer = void 0;
2212
2212
  }
2213
- memo = void 0;
2214
- revision = void 0;
2215
- origin = "snapshot";
2213
+ liveState.memo = void 0;
2214
+ liveState.revision = void 0;
2215
+ liveState.origin = "snapshot";
2216
2216
  liveCatalog.replace([...MODELS]);
2217
2217
  }
2218
2218