@shop-os/modelkit 0.3.7 → 0.4.0

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
@@ -929,7 +929,9 @@ var OTHER_MODELS = [
929
929
  },
930
930
  {
931
931
  id: "bg-remove",
932
- aliases: [],
932
+ // Task ids the two emitters report on usage events (trigger-workflows,
933
+ // vibe-triggers); the usage price lookup resolves them to this row.
934
+ aliases: ["prodia-background-remover", "prodia-remove-background"],
933
935
  label: "Background Remover",
934
936
  type: "bg-remove",
935
937
  adapter: "prodia",
@@ -3402,6 +3404,36 @@ function describeThrown(cause) {
3402
3404
 
3403
3405
  // src/retry.ts
3404
3406
  var textBody = import_zod6.z.string();
3407
+ var BILLING_CODES = /* @__PURE__ */ new Set([
3408
+ "insufficient_quota",
3409
+ "billing_hard_limit_reached",
3410
+ "billing_not_active",
3411
+ "payment_required",
3412
+ "insufficient_balance",
3413
+ "insufficient_credits",
3414
+ "insufficient_credit"
3415
+ ]);
3416
+ var errorCodes = import_zod6.z.object({
3417
+ code: import_zod6.z.string().optional(),
3418
+ type: import_zod6.z.string().optional(),
3419
+ error: import_zod6.z.object({ code: import_zod6.z.string().optional(), type: import_zod6.z.string().optional() }).optional()
3420
+ });
3421
+ var BILLING_TEXT = /insufficient_quota|payment[ _]required|exhausted balance|insufficient (?:credits?|balance|funds)|billing_hard_limit/i;
3422
+ var BILLING_TEXT_STATUSES = /* @__PURE__ */ new Set([403, 429]);
3423
+ function billingCode(body) {
3424
+ const parsed = errorCodes.safeParse(body);
3425
+ if (!parsed.success) return false;
3426
+ const { code, type, error } = parsed.data;
3427
+ return [code, type, error?.code, error?.type].some(
3428
+ (value) => value !== void 0 && BILLING_CODES.has(value.toLowerCase())
3429
+ );
3430
+ }
3431
+ function isBillingFailure(status, body, text2) {
3432
+ if (status === 402) return true;
3433
+ if (status === void 0 || status < 400 || status >= 500) return false;
3434
+ if (billingCode(body)) return true;
3435
+ return BILLING_TEXT_STATUSES.has(status) && BILLING_TEXT.test(text2);
3436
+ }
3405
3437
  function classifyHttpError(status, body, provider, model) {
3406
3438
  const asText = textBody.safeParse(body);
3407
3439
  const text2 = asText.success ? asText.data : JSON.stringify(body);
@@ -3410,6 +3442,7 @@ function classifyHttpError(status, body, provider, model) {
3410
3442
  );
3411
3443
  let code = "provider_rejected";
3412
3444
  if (blocked) code = "content_blocked";
3445
+ else if (isBillingFailure(status, body, text2)) code = "provider_billing";
3413
3446
  else if (status === 401 || status === 403) code = "auth";
3414
3447
  else if (status === 429) code = "rate_limited";
3415
3448
  else if (status === 408 || status === 504) code = "timeout";
@@ -3472,12 +3505,11 @@ function sleep(ms, signal) {
3472
3505
  rejectAborted();
3473
3506
  return;
3474
3507
  }
3475
- let timer;
3476
3508
  const onAbort = () => {
3477
- if (timer !== void 0) clearTimeout(timer);
3509
+ clearTimeout(timer);
3478
3510
  rejectAborted();
3479
3511
  };
3480
- timer = setTimeout(() => {
3512
+ const timer = setTimeout(() => {
3481
3513
  signal?.removeEventListener("abort", onAbort);
3482
3514
  resolve();
3483
3515
  }, ms);
@@ -9258,6 +9290,13 @@ function createModelKit(config, adapters) {
9258
9290
  model: model.id
9259
9291
  });
9260
9292
  }
9293
+ function timed(event, startedAt) {
9294
+ if (event.latencyMs !== void 0) return event;
9295
+ return { ...event, latencyMs: Math.max(0, Date.now() - startedAt) };
9296
+ }
9297
+ function textTimed(event, stepCount, startedAt) {
9298
+ return stepCount === 1 ? timed(event, startedAt) : event;
9299
+ }
9261
9300
  async function emit(event) {
9262
9301
  try {
9263
9302
  await config.onUsage?.(event);
@@ -9341,17 +9380,21 @@ function createModelKit(config, adapters) {
9341
9380
  const abortSignal = combineSignals(req.abortSignal, call.signal);
9342
9381
  if (abortSignal) options.abortSignal = abortSignal;
9343
9382
  if (call.maxRetries !== void 0) options.maxRetries = call.maxRetries;
9383
+ const startedAt = Date.now();
9344
9384
  const result = await (0, import_ai5.generateImage)(options);
9345
9385
  const images = result.images.map(
9346
9386
  (file) => toGeneratedImage({ bytes: file.uint8Array, mime: file.mediaType })
9347
9387
  );
9348
- const usage2 = {
9349
- model: model.modelId,
9350
- provider: model.provider,
9351
- modality: "IMAGE",
9352
- unit: "images",
9353
- quantity: images.length
9354
- };
9388
+ const usage2 = timed(
9389
+ {
9390
+ model: model.modelId,
9391
+ provider: model.provider,
9392
+ modality: "IMAGE",
9393
+ unit: "images",
9394
+ quantity: images.length
9395
+ },
9396
+ startedAt
9397
+ );
9355
9398
  await emit(usage2);
9356
9399
  return { image: images[0], images, usage: usage2, raw: result, warnings };
9357
9400
  }
@@ -9424,18 +9467,22 @@ function createModelKit(config, adapters) {
9424
9467
  if (providerOptions) options.providerOptions = providerOptions;
9425
9468
  if (abortSignal) options.abortSignal = abortSignal;
9426
9469
  logCoercions(logger, model.modelId, warnings);
9470
+ const startedAt = Date.now();
9427
9471
  const pending = (0, import_ai5.experimental_generateVideo)(options);
9428
9472
  let settled;
9429
9473
  let failed = false;
9430
9474
  const done = pending.then(async (result) => {
9431
- const usage2 = {
9432
- model: model.modelId,
9433
- provider: model.provider,
9434
- modality: "VIDEO",
9435
- unit: "seconds",
9436
- quantity: seconds2 ?? 0,
9437
- ...seconds2 !== void 0 && { durationSeconds: seconds2 }
9438
- };
9475
+ const usage2 = timed(
9476
+ {
9477
+ model: model.modelId,
9478
+ provider: model.provider,
9479
+ modality: "VIDEO",
9480
+ unit: "seconds",
9481
+ quantity: seconds2 ?? 0,
9482
+ ...seconds2 !== void 0 && { durationSeconds: seconds2 }
9483
+ },
9484
+ startedAt
9485
+ );
9439
9486
  settled = {
9440
9487
  video: { bytes: result.video.uint8Array, mime: result.video.mediaType },
9441
9488
  usage: usage2,
@@ -9492,8 +9539,12 @@ function createModelKit(config, adapters) {
9492
9539
  message: `${adapter.id} cannot generate images`,
9493
9540
  model: model.id
9494
9541
  });
9542
+ const startedAt = Date.now();
9495
9543
  const result = await adapter.generateImage(request, ctx);
9496
- const usage2 = { ...result.usage, model: model.id, unit: model.usage.unit };
9544
+ const usage2 = timed(
9545
+ { ...result.usage, model: model.id, unit: model.usage.unit },
9546
+ startedAt
9547
+ );
9497
9548
  await emit(usage2);
9498
9549
  const images = result.images.map(toGeneratedImage);
9499
9550
  const first = images[0];
@@ -9536,6 +9587,7 @@ function createModelKit(config, adapters) {
9536
9587
  message: `${adapter.id} cannot generate video`,
9537
9588
  model: model.id
9538
9589
  });
9590
+ const startedAt = Date.now();
9539
9591
  const job = await adapter.generateVideo(request, ctx);
9540
9592
  const result = job.result.bind(job);
9541
9593
  let settled;
@@ -9545,11 +9597,14 @@ function createModelKit(config, adapters) {
9545
9597
  result(opts) {
9546
9598
  if (settled) return settled;
9547
9599
  const pending = result(opts).then(
9548
- (out) => emit({
9549
- ...out.usage,
9550
- model: model.id,
9551
- unit: model.usage.unit
9552
- }).then(() => out),
9600
+ async (out) => {
9601
+ const usage2 = timed(
9602
+ { ...out.usage, model: model.id, unit: model.usage.unit },
9603
+ startedAt
9604
+ );
9605
+ await emit(usage2);
9606
+ return { ...out, usage: usage2 };
9607
+ },
9553
9608
  (cause) => {
9554
9609
  settled = void 0;
9555
9610
  throw cause;
@@ -9586,9 +9641,14 @@ function createModelKit(config, adapters) {
9586
9641
  model: model.id
9587
9642
  });
9588
9643
  }
9644
+ const startedAt = Date.now();
9589
9645
  const result = await adapter.generateAudio(resolved, ctx);
9590
- await emit({ ...result.usage, model: model.id, unit: model.usage.unit });
9591
- return result;
9646
+ const usage2 = timed(
9647
+ { ...result.usage, model: model.id, unit: model.usage.unit },
9648
+ startedAt
9649
+ );
9650
+ await emit(usage2);
9651
+ return { ...result, usage: usage2 };
9592
9652
  },
9593
9653
  async prepareMediaPart(modelId, ref, mediaType, call = {}) {
9594
9654
  mediaRetryOptions(call);
@@ -9617,11 +9677,13 @@ function createModelKit(config, adapters) {
9617
9677
  const { webhookUrl, ...rest } = call;
9618
9678
  const { adapter, ctx } = videoToolTarget(req.model, rest);
9619
9679
  if (!adapter.submitVideoTool) throw noVideoTools(adapter, ctx.model);
9620
- return adapter.submitVideoTool(
9680
+ const submittedAt = Date.now();
9681
+ const submission = await adapter.submitVideoTool(
9621
9682
  req,
9622
9683
  ctx,
9623
9684
  webhookUrl !== void 0 ? { webhookUrl } : {}
9624
9685
  );
9686
+ return { ...submission, submittedAt };
9625
9687
  },
9626
9688
  async videoToolStatus(sub, call = {}) {
9627
9689
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9633,8 +9695,10 @@ function createModelKit(config, adapters) {
9633
9695
  const { adapter, ctx } = videoToolTarget(sub.model, rest);
9634
9696
  if (!adapter.videoToolResult) throw noVideoTools(adapter, ctx.model);
9635
9697
  const result = await adapter.videoToolResult(sub, ctx, webhook);
9636
- await emit({ ...result.usage, model: ctx.model.id, unit: ctx.model.usage.unit });
9637
- return result;
9698
+ const base = { ...result.usage, model: ctx.model.id, unit: ctx.model.usage.unit };
9699
+ const usage2 = sub.submittedAt === void 0 ? base : timed(base, sub.submittedAt);
9700
+ await emit(usage2);
9701
+ return { ...result, usage: usage2 };
9638
9702
  },
9639
9703
  async cancelVideoTool(sub, call = {}) {
9640
9704
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9656,6 +9720,7 @@ function createModelKit(config, adapters) {
9656
9720
  (model) => textTarget(model, call),
9657
9721
  options.providerOptions
9658
9722
  );
9723
+ const startedAt = Date.now();
9659
9724
  const result = await (0, import_ai5.generateText)({
9660
9725
  ...options,
9661
9726
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9665,7 +9730,8 @@ function createModelKit(config, adapters) {
9665
9730
  options.prepareStep ?? options.experimental_prepareStep
9666
9731
  )
9667
9732
  });
9668
- for (const event of steps.usage(result.steps)) await emit(withRowUnit(event));
9733
+ for (const event of steps.usage(result.steps))
9734
+ await emit(textTimed(withRowUnit(event), result.steps.length, startedAt));
9669
9735
  return result;
9670
9736
  },
9671
9737
  streamText(options, call = {}) {
@@ -9676,6 +9742,7 @@ function createModelKit(config, adapters) {
9676
9742
  options.providerOptions
9677
9743
  );
9678
9744
  const callerOnFinish = options.onFinish;
9745
+ const startedAt = Date.now();
9679
9746
  return (0, import_ai5.streamText)({
9680
9747
  ...options,
9681
9748
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9685,7 +9752,8 @@ function createModelKit(config, adapters) {
9685
9752
  options.prepareStep ?? options.experimental_prepareStep
9686
9753
  ),
9687
9754
  onFinish: async (event) => {
9688
- for (const usage2 of steps.usage(event.steps)) await emit(withRowUnit(usage2));
9755
+ for (const usage2 of steps.usage(event.steps))
9756
+ await emit(textTimed(withRowUnit(usage2), event.steps.length, startedAt));
9689
9757
  await callerOnFinish?.(event);
9690
9758
  }
9691
9759
  });
@@ -9698,15 +9766,22 @@ function createModelKit(config, adapters) {
9698
9766
  model: languageModel2,
9699
9767
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions)
9700
9768
  };
9769
+ const startedAt = Date.now();
9701
9770
  const result = await (0, import_ai5.generateObject)(sdkOptions);
9702
9771
  await emit(
9703
- withRowUnit(textUsageEvent(entry, provider, result.usage, result.providerMetadata))
9772
+ timed(
9773
+ withRowUnit(
9774
+ textUsageEvent(entry, provider, result.usage, result.providerMetadata)
9775
+ ),
9776
+ startedAt
9777
+ )
9704
9778
  );
9705
9779
  return result;
9706
9780
  },
9707
9781
  streamObject(options, call = {}) {
9708
9782
  const { entry, languageModel: languageModel2, provider } = textTarget(options.model, call);
9709
9783
  const callerOnFinish = options.onFinish;
9784
+ const startedAt = Date.now();
9710
9785
  const sdkOptions = {
9711
9786
  ...options,
9712
9787
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9714,8 +9789,16 @@ function createModelKit(config, adapters) {
9714
9789
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions),
9715
9790
  onFinish: async (event) => {
9716
9791
  await emit(
9717
- withRowUnit(
9718
- textUsageEvent(entry, provider, event.usage, event.providerMetadata)
9792
+ timed(
9793
+ withRowUnit(
9794
+ textUsageEvent(
9795
+ entry,
9796
+ provider,
9797
+ event.usage,
9798
+ event.providerMetadata
9799
+ )
9800
+ ),
9801
+ startedAt
9719
9802
  )
9720
9803
  );
9721
9804
  await callerOnFinish?.(event);