@shop-os/modelkit 0.3.8 → 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
@@ -3404,6 +3404,36 @@ function describeThrown(cause) {
3404
3404
 
3405
3405
  // src/retry.ts
3406
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
+ }
3407
3437
  function classifyHttpError(status, body, provider, model) {
3408
3438
  const asText = textBody.safeParse(body);
3409
3439
  const text2 = asText.success ? asText.data : JSON.stringify(body);
@@ -3412,6 +3442,7 @@ function classifyHttpError(status, body, provider, model) {
3412
3442
  );
3413
3443
  let code = "provider_rejected";
3414
3444
  if (blocked) code = "content_blocked";
3445
+ else if (isBillingFailure(status, body, text2)) code = "provider_billing";
3415
3446
  else if (status === 401 || status === 403) code = "auth";
3416
3447
  else if (status === 429) code = "rate_limited";
3417
3448
  else if (status === 408 || status === 504) code = "timeout";
@@ -3474,12 +3505,11 @@ function sleep(ms, signal) {
3474
3505
  rejectAborted();
3475
3506
  return;
3476
3507
  }
3477
- let timer;
3478
3508
  const onAbort = () => {
3479
- if (timer !== void 0) clearTimeout(timer);
3509
+ clearTimeout(timer);
3480
3510
  rejectAborted();
3481
3511
  };
3482
- timer = setTimeout(() => {
3512
+ const timer = setTimeout(() => {
3483
3513
  signal?.removeEventListener("abort", onAbort);
3484
3514
  resolve();
3485
3515
  }, ms);
@@ -9260,6 +9290,13 @@ function createModelKit(config, adapters) {
9260
9290
  model: model.id
9261
9291
  });
9262
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
+ }
9263
9300
  async function emit(event) {
9264
9301
  try {
9265
9302
  await config.onUsage?.(event);
@@ -9343,17 +9380,21 @@ function createModelKit(config, adapters) {
9343
9380
  const abortSignal = combineSignals(req.abortSignal, call.signal);
9344
9381
  if (abortSignal) options.abortSignal = abortSignal;
9345
9382
  if (call.maxRetries !== void 0) options.maxRetries = call.maxRetries;
9383
+ const startedAt = Date.now();
9346
9384
  const result = await (0, import_ai5.generateImage)(options);
9347
9385
  const images = result.images.map(
9348
9386
  (file) => toGeneratedImage({ bytes: file.uint8Array, mime: file.mediaType })
9349
9387
  );
9350
- const usage2 = {
9351
- model: model.modelId,
9352
- provider: model.provider,
9353
- modality: "IMAGE",
9354
- unit: "images",
9355
- quantity: images.length
9356
- };
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
+ );
9357
9398
  await emit(usage2);
9358
9399
  return { image: images[0], images, usage: usage2, raw: result, warnings };
9359
9400
  }
@@ -9426,18 +9467,22 @@ function createModelKit(config, adapters) {
9426
9467
  if (providerOptions) options.providerOptions = providerOptions;
9427
9468
  if (abortSignal) options.abortSignal = abortSignal;
9428
9469
  logCoercions(logger, model.modelId, warnings);
9470
+ const startedAt = Date.now();
9429
9471
  const pending = (0, import_ai5.experimental_generateVideo)(options);
9430
9472
  let settled;
9431
9473
  let failed = false;
9432
9474
  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
- };
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
+ );
9441
9486
  settled = {
9442
9487
  video: { bytes: result.video.uint8Array, mime: result.video.mediaType },
9443
9488
  usage: usage2,
@@ -9494,8 +9539,12 @@ function createModelKit(config, adapters) {
9494
9539
  message: `${adapter.id} cannot generate images`,
9495
9540
  model: model.id
9496
9541
  });
9542
+ const startedAt = Date.now();
9497
9543
  const result = await adapter.generateImage(request, ctx);
9498
- 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
+ );
9499
9548
  await emit(usage2);
9500
9549
  const images = result.images.map(toGeneratedImage);
9501
9550
  const first = images[0];
@@ -9538,6 +9587,7 @@ function createModelKit(config, adapters) {
9538
9587
  message: `${adapter.id} cannot generate video`,
9539
9588
  model: model.id
9540
9589
  });
9590
+ const startedAt = Date.now();
9541
9591
  const job = await adapter.generateVideo(request, ctx);
9542
9592
  const result = job.result.bind(job);
9543
9593
  let settled;
@@ -9547,11 +9597,14 @@ function createModelKit(config, adapters) {
9547
9597
  result(opts) {
9548
9598
  if (settled) return settled;
9549
9599
  const pending = result(opts).then(
9550
- (out) => emit({
9551
- ...out.usage,
9552
- model: model.id,
9553
- unit: model.usage.unit
9554
- }).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
+ },
9555
9608
  (cause) => {
9556
9609
  settled = void 0;
9557
9610
  throw cause;
@@ -9588,9 +9641,14 @@ function createModelKit(config, adapters) {
9588
9641
  model: model.id
9589
9642
  });
9590
9643
  }
9644
+ const startedAt = Date.now();
9591
9645
  const result = await adapter.generateAudio(resolved, ctx);
9592
- await emit({ ...result.usage, model: model.id, unit: model.usage.unit });
9593
- 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 };
9594
9652
  },
9595
9653
  async prepareMediaPart(modelId, ref, mediaType, call = {}) {
9596
9654
  mediaRetryOptions(call);
@@ -9619,11 +9677,13 @@ function createModelKit(config, adapters) {
9619
9677
  const { webhookUrl, ...rest } = call;
9620
9678
  const { adapter, ctx } = videoToolTarget(req.model, rest);
9621
9679
  if (!adapter.submitVideoTool) throw noVideoTools(adapter, ctx.model);
9622
- return adapter.submitVideoTool(
9680
+ const submittedAt = Date.now();
9681
+ const submission = await adapter.submitVideoTool(
9623
9682
  req,
9624
9683
  ctx,
9625
9684
  webhookUrl !== void 0 ? { webhookUrl } : {}
9626
9685
  );
9686
+ return { ...submission, submittedAt };
9627
9687
  },
9628
9688
  async videoToolStatus(sub, call = {}) {
9629
9689
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9635,8 +9695,10 @@ function createModelKit(config, adapters) {
9635
9695
  const { adapter, ctx } = videoToolTarget(sub.model, rest);
9636
9696
  if (!adapter.videoToolResult) throw noVideoTools(adapter, ctx.model);
9637
9697
  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;
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 };
9640
9702
  },
9641
9703
  async cancelVideoTool(sub, call = {}) {
9642
9704
  const { adapter, ctx } = videoToolTarget(sub.model, call);
@@ -9658,6 +9720,7 @@ function createModelKit(config, adapters) {
9658
9720
  (model) => textTarget(model, call),
9659
9721
  options.providerOptions
9660
9722
  );
9723
+ const startedAt = Date.now();
9661
9724
  const result = await (0, import_ai5.generateText)({
9662
9725
  ...options,
9663
9726
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9667,7 +9730,8 @@ function createModelKit(config, adapters) {
9667
9730
  options.prepareStep ?? options.experimental_prepareStep
9668
9731
  )
9669
9732
  });
9670
- 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));
9671
9735
  return result;
9672
9736
  },
9673
9737
  streamText(options, call = {}) {
@@ -9678,6 +9742,7 @@ function createModelKit(config, adapters) {
9678
9742
  options.providerOptions
9679
9743
  );
9680
9744
  const callerOnFinish = options.onFinish;
9745
+ const startedAt = Date.now();
9681
9746
  return (0, import_ai5.streamText)({
9682
9747
  ...options,
9683
9748
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9687,7 +9752,8 @@ function createModelKit(config, adapters) {
9687
9752
  options.prepareStep ?? options.experimental_prepareStep
9688
9753
  ),
9689
9754
  onFinish: async (event) => {
9690
- 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));
9691
9757
  await callerOnFinish?.(event);
9692
9758
  }
9693
9759
  });
@@ -9700,15 +9766,22 @@ function createModelKit(config, adapters) {
9700
9766
  model: languageModel2,
9701
9767
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions)
9702
9768
  };
9769
+ const startedAt = Date.now();
9703
9770
  const result = await (0, import_ai5.generateObject)(sdkOptions);
9704
9771
  await emit(
9705
- withRowUnit(textUsageEvent(entry, provider, result.usage, result.providerMetadata))
9772
+ timed(
9773
+ withRowUnit(
9774
+ textUsageEvent(entry, provider, result.usage, result.providerMetadata)
9775
+ ),
9776
+ startedAt
9777
+ )
9706
9778
  );
9707
9779
  return result;
9708
9780
  },
9709
9781
  streamObject(options, call = {}) {
9710
9782
  const { entry, languageModel: languageModel2, provider } = textTarget(options.model, call);
9711
9783
  const callerOnFinish = options.onFinish;
9784
+ const startedAt = Date.now();
9712
9785
  const sdkOptions = {
9713
9786
  ...options,
9714
9787
  ...textAbortOptions(options.abortSignal, call.signal),
@@ -9716,8 +9789,16 @@ function createModelKit(config, adapters) {
9716
9789
  providerOptions: mergeTextProviderOptions(entry, options.providerOptions),
9717
9790
  onFinish: async (event) => {
9718
9791
  await emit(
9719
- withRowUnit(
9720
- 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
9721
9802
  )
9722
9803
  );
9723
9804
  await callerOnFinish?.(event);