@shop-os/modelkit 0.3.8 → 0.4.1

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
@@ -341,35 +341,43 @@ var IMAGE_MODELS = [
341
341
  GEMINI_PROMPT_MAX_CHARS,
342
342
  NANO_BANANA_2_1_RATIOS
343
343
  ),
344
- geminiImage(
345
- "gemini-3-pro-image-preview",
346
- "Nano Banana Pro",
347
- ["nb-pro"],
348
- ["1K", "2K", "4K"],
349
- {
350
- provider: "google",
351
- description: "Create stunning, production-quality visuals with exceptional realism, detail, and typography. Best for campaigns, e-commerce, product photography, and hero creatives.",
352
- logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
353
- },
354
- ["cowork", "space_builder"],
355
- { images: 14 },
356
- GEMINI_PROMPT_MAX_CHARS
357
- ),
358
- geminiImage(
359
- "gemini-3.1-flash-image-preview",
360
- "Nano Banana 2",
361
- ["nb-2"],
362
- // Flash 3.1 is the only Gemini image model with the 512 tier.
363
- ["512", "1K", "2K", "4K"],
364
- {
365
- provider: "google",
366
- description: "Generate high-quality images at speed. Perfect for brainstorming, concept boards, concept testing, creative exploration, and large batch workflows.",
367
- logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
368
- },
369
- ["cowork", "space_builder"],
370
- { images: 14 },
371
- GEMINI_PROMPT_MAX_CHARS
372
- ),
344
+ // Google retired the -preview endpoints for the GA ids; the catalog ids stay because they
345
+ // are pricing keys and stored model ids.
346
+ {
347
+ ...geminiImage(
348
+ "gemini-3-pro-image-preview",
349
+ "Nano Banana Pro",
350
+ ["nb-pro"],
351
+ ["1K", "2K", "4K"],
352
+ {
353
+ provider: "google",
354
+ description: "Create stunning, production-quality visuals with exceptional realism, detail, and typography. Best for campaigns, e-commerce, product photography, and hero creatives.",
355
+ logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
356
+ },
357
+ ["cowork", "space_builder"],
358
+ { images: 14 },
359
+ GEMINI_PROMPT_MAX_CHARS
360
+ ),
361
+ providerModelId: "gemini-3-pro-image"
362
+ },
363
+ {
364
+ ...geminiImage(
365
+ "gemini-3.1-flash-image-preview",
366
+ "Nano Banana 2",
367
+ ["nb-2"],
368
+ // Flash 3.1 is the only Gemini image model with the 512 tier.
369
+ ["512", "1K", "2K", "4K"],
370
+ {
371
+ provider: "google",
372
+ description: "Generate high-quality images at speed. Perfect for brainstorming, concept boards, concept testing, creative exploration, and large batch workflows.",
373
+ logo: "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png"
374
+ },
375
+ ["cowork", "space_builder"],
376
+ { images: 14 },
377
+ GEMINI_PROMPT_MAX_CHARS
378
+ ),
379
+ providerModelId: "gemini-3.1-flash-image"
380
+ },
373
381
  geminiImage(
374
382
  "gemini-3.1-flash-lite-image",
375
383
  "Nano Banana Lite",
@@ -1503,6 +1511,7 @@ var OMNI_PROMPT = { referenceSyntax: "caption", maxChars: 5e4 };
1503
1511
  var BYTEDANCE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/3bba5d81-4e57-4e71-911b-cdfa05acca2c_compressed.png";
1504
1512
  var GOOGLE_LOGO2 = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png";
1505
1513
  var VIDEO_MODELS = [
1514
+ // Google shuts down the Veo 3.1 previews and Omni Flash 1.0 on 2026-10-22.
1506
1515
  {
1507
1516
  id: "veo-3.1",
1508
1517
  aliases: [
@@ -1515,8 +1524,9 @@ var VIDEO_MODELS = [
1515
1524
  type: "video",
1516
1525
  adapter: "google-genai",
1517
1526
  providerModelId: "veo-3.1-generate-preview",
1518
- lifecycle: "active",
1519
- exposure: ["cowork", "space_builder"],
1527
+ lifecycle: "deprecated",
1528
+ replacedBy: "gemini-omni-1.1-flash",
1529
+ exposure: [],
1520
1530
  constraints: {
1521
1531
  aspectRatios: [...OMNI_RATIOS],
1522
1532
  resolutions: ["720p", "1080p", "4k"],
@@ -1551,7 +1561,8 @@ var VIDEO_MODELS = [
1551
1561
  type: "video",
1552
1562
  adapter: "google-genai",
1553
1563
  providerModelId: "veo-3.1-fast-generate-preview",
1554
- lifecycle: "active",
1564
+ lifecycle: "deprecated",
1565
+ replacedBy: "gemini-omni-1.1-flash",
1555
1566
  exposure: [],
1556
1567
  constraints: {
1557
1568
  aspectRatios: [...OMNI_RATIOS],
@@ -1581,7 +1592,8 @@ var VIDEO_MODELS = [
1581
1592
  type: "video",
1582
1593
  adapter: "google-genai",
1583
1594
  providerModelId: "veo-3.1-lite-generate-preview",
1584
- lifecycle: "active",
1595
+ lifecycle: "deprecated",
1596
+ replacedBy: "gemini-omni-1.1-flash",
1585
1597
  exposure: [],
1586
1598
  constraints: {
1587
1599
  aspectRatios: [...OMNI_RATIOS],
@@ -1612,8 +1624,9 @@ var VIDEO_MODELS = [
1612
1624
  type: "video",
1613
1625
  adapter: "google-genai",
1614
1626
  providerModelId: "gemini-omni-flash-preview",
1615
- lifecycle: "active",
1616
- exposure: ["cowork", "space_builder"],
1627
+ lifecycle: "deprecated",
1628
+ replacedBy: "gemini-omni-1.1-flash",
1629
+ exposure: [],
1617
1630
  constraints: {
1618
1631
  // 1.0 rejects `resolution`; it renders 720p. No defaults: the adapter
1619
1632
  // sends ratio and duration only when the caller set them, so a
@@ -2168,7 +2181,8 @@ var VIDEO_MODELS = [
2168
2181
  },
2169
2182
  defaults: {},
2170
2183
  usage: { ...VIDEO_USAGE },
2171
- pricingKind: "video_per_second",
2184
+ // Higgsfield bills DoP per generation: a fixed-length clip with no duration knob.
2185
+ pricingKind: "flat",
2172
2186
  display: {
2173
2187
  provider: "higgsfield",
2174
2188
  description: "Higgsfield DOP image-to-video model; animates a required first-frame image from a text prompt with an optional last-frame image and motion controls."
@@ -3404,6 +3418,36 @@ function describeThrown(cause) {
3404
3418
 
3405
3419
  // src/retry.ts
3406
3420
  var textBody = import_zod6.z.string();
3421
+ var BILLING_CODES = /* @__PURE__ */ new Set([
3422
+ "insufficient_quota",
3423
+ "billing_hard_limit_reached",
3424
+ "billing_not_active",
3425
+ "payment_required",
3426
+ "insufficient_balance",
3427
+ "insufficient_credits",
3428
+ "insufficient_credit"
3429
+ ]);
3430
+ var errorCodes = import_zod6.z.object({
3431
+ code: import_zod6.z.string().optional(),
3432
+ type: import_zod6.z.string().optional(),
3433
+ error: import_zod6.z.object({ code: import_zod6.z.string().optional(), type: import_zod6.z.string().optional() }).optional()
3434
+ });
3435
+ var BILLING_TEXT = /insufficient_quota|payment[ _]required|exhausted balance|insufficient (?:credits?|balance|funds)|billing_hard_limit/i;
3436
+ var BILLING_TEXT_STATUSES = /* @__PURE__ */ new Set([403, 429]);
3437
+ function billingCode(body) {
3438
+ const parsed = errorCodes.safeParse(body);
3439
+ if (!parsed.success) return false;
3440
+ const { code, type, error } = parsed.data;
3441
+ return [code, type, error?.code, error?.type].some(
3442
+ (value) => value !== void 0 && BILLING_CODES.has(value.toLowerCase())
3443
+ );
3444
+ }
3445
+ function isBillingFailure(status, body, text2) {
3446
+ if (status === 402) return true;
3447
+ if (status === void 0 || status < 400 || status >= 500) return false;
3448
+ if (billingCode(body)) return true;
3449
+ return BILLING_TEXT_STATUSES.has(status) && BILLING_TEXT.test(text2);
3450
+ }
3407
3451
  function classifyHttpError(status, body, provider, model) {
3408
3452
  const asText = textBody.safeParse(body);
3409
3453
  const text2 = asText.success ? asText.data : JSON.stringify(body);
@@ -3412,6 +3456,7 @@ function classifyHttpError(status, body, provider, model) {
3412
3456
  );
3413
3457
  let code = "provider_rejected";
3414
3458
  if (blocked) code = "content_blocked";
3459
+ else if (isBillingFailure(status, body, text2)) code = "provider_billing";
3415
3460
  else if (status === 401 || status === 403) code = "auth";
3416
3461
  else if (status === 429) code = "rate_limited";
3417
3462
  else if (status === 408 || status === 504) code = "timeout";
@@ -3474,12 +3519,11 @@ function sleep(ms, signal) {
3474
3519
  rejectAborted();
3475
3520
  return;
3476
3521
  }
3477
- let timer;
3478
3522
  const onAbort = () => {
3479
- if (timer !== void 0) clearTimeout(timer);
3523
+ clearTimeout(timer);
3480
3524
  rejectAborted();
3481
3525
  };
3482
- timer = setTimeout(() => {
3526
+ const timer = setTimeout(() => {
3483
3527
  signal?.removeEventListener("abort", onAbort);
3484
3528
  resolve();
3485
3529
  }, ms);
@@ -9260,6 +9304,13 @@ function createModelKit(config, adapters) {
9260
9304
  model: model.id
9261
9305
  });
9262
9306
  }
9307
+ function timed(event, startedAt) {
9308
+ if (event.latencyMs !== void 0) return event;
9309
+ return { ...event, latencyMs: Math.max(0, Date.now() - startedAt) };
9310
+ }
9311
+ function textTimed(event, stepCount, startedAt) {
9312
+ return stepCount === 1 ? timed(event, startedAt) : event;
9313
+ }
9263
9314
  async function emit(event) {
9264
9315
  try {
9265
9316
  await config.onUsage?.(event);
@@ -9343,17 +9394,21 @@ function createModelKit(config, adapters) {
9343
9394
  const abortSignal = combineSignals(req.abortSignal, call.signal);
9344
9395
  if (abortSignal) options.abortSignal = abortSignal;
9345
9396
  if (call.maxRetries !== void 0) options.maxRetries = call.maxRetries;
9397
+ const startedAt = Date.now();
9346
9398
  const result = await (0, import_ai5.generateImage)(options);
9347
9399
  const images = result.images.map(
9348
9400
  (file) => toGeneratedImage({ bytes: file.uint8Array, mime: file.mediaType })
9349
9401
  );
9350
- const usage2 = {
9351
- model: model.modelId,
9352
- provider: model.provider,
9353
- modality: "IMAGE",
9354
- unit: "images",
9355
- quantity: images.length
9356
- };
9402
+ const usage2 = timed(
9403
+ {
9404
+ model: model.modelId,
9405
+ provider: model.provider,
9406
+ modality: "IMAGE",
9407
+ unit: "images",
9408
+ quantity: images.length
9409
+ },
9410
+ startedAt
9411
+ );
9357
9412
  await emit(usage2);
9358
9413
  return { image: images[0], images, usage: usage2, raw: result, warnings };
9359
9414
  }
@@ -9426,18 +9481,22 @@ function createModelKit(config, adapters) {
9426
9481
  if (providerOptions) options.providerOptions = providerOptions;
9427
9482
  if (abortSignal) options.abortSignal = abortSignal;
9428
9483
  logCoercions(logger, model.modelId, warnings);
9484
+ const startedAt = Date.now();
9429
9485
  const pending = (0, import_ai5.experimental_generateVideo)(options);
9430
9486
  let settled;
9431
9487
  let failed = false;
9432
9488
  const done = pending.then(async (result) => {
9433
- const usage2 = {
9434
- model: model.modelId,
9435
- provider: model.provider,
9436
- modality: "VIDEO",
9437
- unit: "seconds",
9438
- quantity: seconds2 ?? 0,
9439
- ...seconds2 !== void 0 && { durationSeconds: seconds2 }
9440
- };
9489
+ const usage2 = timed(
9490
+ {
9491
+ model: model.modelId,
9492
+ provider: model.provider,
9493
+ modality: "VIDEO",
9494
+ unit: "seconds",
9495
+ quantity: seconds2 ?? 0,
9496
+ ...seconds2 !== void 0 && { durationSeconds: seconds2 }
9497
+ },
9498
+ startedAt
9499
+ );
9441
9500
  settled = {
9442
9501
  video: { bytes: result.video.uint8Array, mime: result.video.mediaType },
9443
9502
  usage: usage2,
@@ -9494,8 +9553,12 @@ function createModelKit(config, adapters) {
9494
9553
  message: `${adapter.id} cannot generate images`,
9495
9554
  model: model.id
9496
9555
  });
9556
+ const startedAt = Date.now();
9497
9557
  const result = await adapter.generateImage(request, ctx);
9498
- const usage2 = { ...result.usage, model: model.id, unit: model.usage.unit };
9558
+ const usage2 = timed(
9559
+ { ...result.usage, model: model.id, unit: model.usage.unit },
9560
+ startedAt
9561
+ );
9499
9562
  await emit(usage2);
9500
9563
  const images = result.images.map(toGeneratedImage);
9501
9564
  const first = images[0];
@@ -9538,6 +9601,7 @@ function createModelKit(config, adapters) {
9538
9601
  message: `${adapter.id} cannot generate video`,
9539
9602
  model: model.id
9540
9603
  });
9604
+ const startedAt = Date.now();
9541
9605
  const job = await adapter.generateVideo(request, ctx);
9542
9606
  const result = job.result.bind(job);
9543
9607
  let settled;
@@ -9547,11 +9611,14 @@ function createModelKit(config, adapters) {
9547
9611
  result(opts) {
9548
9612
  if (settled) return settled;
9549
9613
  const pending = result(opts).then(
9550
- (out) => emit({
9551
- ...out.usage,
9552
- model: model.id,
9553
- unit: model.usage.unit
9554
- }).then(() => out),
9614
+ async (out) => {
9615
+ const usage2 = timed(
9616
+ { ...out.usage, model: model.id, unit: model.usage.unit },
9617
+ startedAt
9618
+ );
9619
+ await emit(usage2);
9620
+ return { ...out, usage: usage2 };
9621
+ },
9555
9622
  (cause) => {
9556
9623
  settled = void 0;
9557
9624
  throw cause;
@@ -9588,9 +9655,14 @@ function createModelKit(config, adapters) {
9588
9655
  model: model.id
9589
9656
  });
9590
9657
  }
9658
+ const startedAt = Date.now();
9591
9659
  const result = await adapter.generateAudio(resolved, ctx);
9592
- await emit({ ...result.usage, model: model.id, unit: model.usage.unit });
9593
- return result;
9660
+ const usage2 = timed(
9661
+ { ...result.usage, model: model.id, unit: model.usage.unit },
9662
+ startedAt
9663
+ );
9664
+ await emit(usage2);
9665
+ return { ...result, usage: usage2 };
9594
9666
  },
9595
9667
  async prepareMediaPart(modelId, ref, mediaType, call = {}) {
9596
9668
  mediaRetryOptions(call);
@@ -9619,11 +9691,13 @@ function createModelKit(config, adapters) {
9619
9691
  const { webhookUrl, ...rest } = call;
9620
9692
  const { adapter, ctx } = videoToolTarget(req.model, rest);
9621
9693
  if (!adapter.submitVideoTool) throw noVideoTools(adapter, ctx.model);
9622
- return adapter.submitVideoTool(
9694
+ const submittedAt = Date.now();
9695
+ const submission = await adapter.submitVideoTool(
9623
9696
  req,
9624
9697
  ctx,
9625
9698
  webhookUrl !== void 0 ? { webhookUrl } : {}
9626
9699
  );
9700
+ return { ...submission, submittedAt };
9627
9701
  },
9628
9702
  async videoToolStatus(sub, call = {}) {
9629
9703
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9635,8 +9709,10 @@ function createModelKit(config, adapters) {
9635
9709
  const { adapter, ctx } = videoToolTarget(sub.model, rest);
9636
9710
  if (!adapter.videoToolResult) throw noVideoTools(adapter, ctx.model);
9637
9711
  const result = await adapter.videoToolResult(sub, ctx, webhook);
9638
- await emit({ ...result.usage, model: ctx.model.id, unit: ctx.model.usage.unit });
9639
- return result;
9712
+ const base = { ...result.usage, model: ctx.model.id, unit: ctx.model.usage.unit };
9713
+ const usage2 = sub.submittedAt === void 0 ? base : timed(base, sub.submittedAt);
9714
+ await emit(usage2);
9715
+ return { ...result, usage: usage2 };
9640
9716
  },
9641
9717
  async cancelVideoTool(sub, call = {}) {
9642
9718
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9658,6 +9734,7 @@ function createModelKit(config, adapters) {
9658
9734
  (model) => textTarget(model, call),
9659
9735
  options.providerOptions
9660
9736
  );
9737
+ const startedAt = Date.now();
9661
9738
  const result = await (0, import_ai5.generateText)({
9662
9739
  ...options,
9663
9740
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9667,7 +9744,8 @@ function createModelKit(config, adapters) {
9667
9744
  options.prepareStep ?? options.experimental_prepareStep
9668
9745
  )
9669
9746
  });
9670
- for (const event of steps.usage(result.steps)) await emit(withRowUnit(event));
9747
+ for (const event of steps.usage(result.steps))
9748
+ await emit(textTimed(withRowUnit(event), result.steps.length, startedAt));
9671
9749
  return result;
9672
9750
  },
9673
9751
  streamText(options, call = {}) {
@@ -9678,6 +9756,7 @@ function createModelKit(config, adapters) {
9678
9756
  options.providerOptions
9679
9757
  );
9680
9758
  const callerOnFinish = options.onFinish;
9759
+ const startedAt = Date.now();
9681
9760
  return (0, import_ai5.streamText)({
9682
9761
  ...options,
9683
9762
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9687,7 +9766,8 @@ function createModelKit(config, adapters) {
9687
9766
  options.prepareStep ?? options.experimental_prepareStep
9688
9767
  ),
9689
9768
  onFinish: async (event) => {
9690
- for (const usage2 of steps.usage(event.steps)) await emit(withRowUnit(usage2));
9769
+ for (const usage2 of steps.usage(event.steps))
9770
+ await emit(textTimed(withRowUnit(usage2), event.steps.length, startedAt));
9691
9771
  await callerOnFinish?.(event);
9692
9772
  }
9693
9773
  });
@@ -9700,15 +9780,22 @@ function createModelKit(config, adapters) {
9700
9780
  model: languageModel2,
9701
9781
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions)
9702
9782
  };
9783
+ const startedAt = Date.now();
9703
9784
  const result = await (0, import_ai5.generateObject)(sdkOptions);
9704
9785
  await emit(
9705
- withRowUnit(textUsageEvent(entry, provider, result.usage, result.providerMetadata))
9786
+ timed(
9787
+ withRowUnit(
9788
+ textUsageEvent(entry, provider, result.usage, result.providerMetadata)
9789
+ ),
9790
+ startedAt
9791
+ )
9706
9792
  );
9707
9793
  return result;
9708
9794
  },
9709
9795
  streamObject(options, call = {}) {
9710
9796
  const { entry, languageModel: languageModel2, provider } = textTarget(options.model, call);
9711
9797
  const callerOnFinish = options.onFinish;
9798
+ const startedAt = Date.now();
9712
9799
  const sdkOptions = {
9713
9800
  ...options,
9714
9801
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9716,8 +9803,16 @@ function createModelKit(config, adapters) {
9716
9803
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions),
9717
9804
  onFinish: async (event) => {
9718
9805
  await emit(
9719
- withRowUnit(
9720
- textUsageEvent(entry, provider, event.usage, event.providerMetadata)
9806
+ timed(
9807
+ withRowUnit(
9808
+ textUsageEvent(
9809
+ entry,
9810
+ provider,
9811
+ event.usage,
9812
+ event.providerMetadata
9813
+ )
9814
+ ),
9815
+ startedAt
9721
9816
  )
9722
9817
  );
9723
9818
  await callerOnFinish?.(event);