@broberg/ai-sdk 0.52.0 → 0.53.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.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  listMediaPrices,
19
19
  pricingFreshness,
20
20
  pricingGeneratedAt
21
- } from "./chunk-O4LR6X4U.js";
21
+ } from "./chunk-ZSLRCOYM.js";
22
22
 
23
23
  // src/cost/default-hosts.ts
24
24
  var DEFAULT_BASE_URLS = {
@@ -517,6 +517,7 @@ function anthropicAdapter(config = {}) {
517
517
  cacheReadTokens: data.usage?.cache_read_input_tokens ?? 0,
518
518
  cacheCreationTokens: data.usage?.cache_creation_input_tokens ?? 0
519
519
  });
520
+ if (typeof data.model === "string" && data.model) usage.servedModel = data.model;
520
521
  const result = { text, usage };
521
522
  if (toolCalls.length > 0) result.toolCalls = toolCalls;
522
523
  return result;
@@ -809,6 +810,7 @@ function makeOpenAICompatibleAdapter(config) {
809
810
  if (config.costFromResponseField && typeof data.usage?.cost === "number") {
810
811
  usage.costUsd = data.usage.cost;
811
812
  }
813
+ if (typeof data.model === "string" && data.model) usage.servedModel = data.model;
812
814
  const result = { text, usage };
813
815
  if (toolCalls && toolCalls.length > 0) result.toolCalls = toolCalls;
814
816
  return result;
@@ -876,6 +878,7 @@ function makeOpenAICompatibleAdapter(config) {
876
878
  if (config.costFromResponseField && typeof chunk.usage.cost === "number") {
877
879
  usage.costUsd = chunk.usage.cost;
878
880
  }
881
+ if (typeof chunk.model === "string" && chunk.model) usage.servedModel = chunk.model;
879
882
  yield { type: "usage", costUsd: usage.costUsd, model: usage.model, usage };
880
883
  }
881
884
  }
@@ -3520,8 +3523,8 @@ var aiConfigSchema = z.object({
3520
3523
  import { randomUUID } from "crypto";
3521
3524
 
3522
3525
  // src/version.ts
3523
- var VERSION = "0.52.0";
3524
- var SDK_TAG = "@broberg/ai-sdk@0.52.0";
3526
+ var VERSION = "0.53.0";
3527
+ var SDK_TAG = "@broberg/ai-sdk@0.53.0";
3525
3528
 
3526
3529
  // src/cost/sinks/upmetrics.ts
3527
3530
  function isRetryableStatus(status) {
@@ -3571,6 +3574,9 @@ function upmetricsSink(config) {
3571
3574
  // capability/transport — no ingest-schema change, and without it the one
3572
3575
  // field built for auditability existed only in memory.
3573
3576
  region: usage.region,
3577
+ // F071.2 — the model that answered (contract with upmetrics F048). Omitted when
3578
+ // the provider did not say, rather than echoing the requested model as a guess.
3579
+ ...usage.servedModel ? { served_model: usage.servedModel } : {},
3574
3580
  // F050: HOW cost_usd was arrived at. upmetrics already distinguishes
3575
3581
  // reported / computed / unpriced — we were sending an assumed number in
3576
3582
  // the same field as a measured one, so their labels could not be right
@@ -3597,6 +3603,36 @@ function upmetricsSink(config) {
3597
3603
  void config.complianceMode;
3598
3604
  return JSON.stringify(body);
3599
3605
  }
3606
+ function buildFailureBody(f) {
3607
+ const endedAt = new Date(new Date(f.ts).getTime()).toISOString();
3608
+ const startedAt = new Date(new Date(f.ts).getTime() - (f.latencyMs || 0)).toISOString();
3609
+ const body = {
3610
+ mode: "record",
3611
+ agent_kind: config.agentKind ?? (f.capability === "embedding" ? "embedding" : "chatbot"),
3612
+ agent_name: config.agentName,
3613
+ provider: f.provider,
3614
+ model: f.model,
3615
+ status: "error",
3616
+ input_tokens: 0,
3617
+ output_tokens: 0,
3618
+ cost_usd: 0,
3619
+ duration_ms: f.latencyMs,
3620
+ started_at: startedAt,
3621
+ ended_at: endedAt,
3622
+ tags: {
3623
+ ...f.labels,
3624
+ capability: f.capability,
3625
+ transport: f.transport,
3626
+ error_code: f.errorCode,
3627
+ error_kind: f.errorKind,
3628
+ sdk: SDK_TAG,
3629
+ idempotencyKey: randomUUID()
3630
+ }
3631
+ };
3632
+ if (f.tier !== void 0) body.tier = f.tier;
3633
+ if (f.purpose !== void 0) body.purpose = f.purpose;
3634
+ return JSON.stringify(body);
3635
+ }
3600
3636
  async function send(body) {
3601
3637
  try {
3602
3638
  const res = await doFetch(url, {
@@ -3690,6 +3726,30 @@ function upmetricsSink(config) {
3690
3726
  config.onError?.(err);
3691
3727
  }
3692
3728
  },
3729
+ async recordFailure(failure) {
3730
+ try {
3731
+ const body = buildFailureBody(failure);
3732
+ const r = await send(body);
3733
+ if (r.kind === "ok") {
3734
+ counts.sent += 1;
3735
+ return;
3736
+ }
3737
+ if (r.kind === "reject") {
3738
+ counts.rejected += 1;
3739
+ config.onError?.(r.err);
3740
+ return;
3741
+ }
3742
+ if (!retry) {
3743
+ lose(r.err);
3744
+ return;
3745
+ }
3746
+ queue.push({ body, attempts: 1 });
3747
+ enforceCap();
3748
+ schedule();
3749
+ } catch (err) {
3750
+ config.onError?.(err);
3751
+ }
3752
+ },
3693
3753
  async flush() {
3694
3754
  if (timer) {
3695
3755
  clearTimeout(timer);
@@ -3745,6 +3805,29 @@ function assertByokProviders(providers) {
3745
3805
  }
3746
3806
  }
3747
3807
 
3808
+ // src/cost/failure.ts
3809
+ function classifyFailure(err) {
3810
+ const e = err;
3811
+ const name = e?.name ?? "";
3812
+ const message = String(e?.message ?? "");
3813
+ if (name === "AbortError" || name === "TimeoutError" || /timed? ?out/i.test(message.split("\n")[0] ?? "")) {
3814
+ return { errorCode: "timeout", errorKind: "timeout" };
3815
+ }
3816
+ let status = typeof e?.status === "number" ? e.status : void 0;
3817
+ if (status === void 0) {
3818
+ const m = /(?:^|\s)([45]\d{2})(?=[:\s]|$)/.exec(message.split("\n")[0] ?? "");
3819
+ if (m) status = Number(m[1]);
3820
+ }
3821
+ if (status !== void 0) {
3822
+ const kind = status === 401 || status === 403 ? "auth" : status === 404 ? "not_found" : status === 429 ? "rate_limit" : status >= 500 ? "server" : "other";
3823
+ return { errorCode: String(status), errorKind: kind };
3824
+ }
3825
+ if (name === "TypeError" && /fetch failed|network|ECONN|ENOTFOUND/i.test(message + (e?.cause?.code ?? ""))) {
3826
+ return { errorCode: "network", errorKind: "network" };
3827
+ }
3828
+ return { errorCode: "unknown", errorKind: "other" };
3829
+ }
3830
+
3748
3831
  // src/client.ts
3749
3832
  var DEFAULT_IMAGE_SPEC = {
3750
3833
  provider: "fal",
@@ -3840,6 +3923,13 @@ function createAI(config = {}) {
3840
3923
  if (!usage.ts) usage.ts = (/* @__PURE__ */ new Date()).toISOString();
3841
3924
  return usage;
3842
3925
  }
3926
+ async function reportFailure(f, err) {
3927
+ if (!costSink?.recordFailure) return;
3928
+ try {
3929
+ await costSink.recordFailure({ ...f, ...classifyFailure(err), ts: (/* @__PURE__ */ new Date()).toISOString() });
3930
+ } catch {
3931
+ }
3932
+ }
3843
3933
  async function report(usage) {
3844
3934
  if (!costSink) return;
3845
3935
  try {
@@ -3875,8 +3965,8 @@ function createAI(config = {}) {
3875
3965
  for (let i = 0; i < routes.length; i++) {
3876
3966
  const spec = routes[i];
3877
3967
  await preflight(spec, opts.estIn, opts.estOut);
3968
+ const t0 = performance.now();
3878
3969
  try {
3879
- const t0 = performance.now();
3880
3970
  const res = await opts.invoke(spec);
3881
3971
  enrich(res.usage, opts.capability, i === 0 ? opts.tier : void 0, opts.purpose, performance.now() - t0, opts.labels);
3882
3972
  await settle(res.usage);
@@ -3884,6 +3974,20 @@ function createAI(config = {}) {
3884
3974
  return res;
3885
3975
  } catch (e) {
3886
3976
  lastErr = e;
3977
+ const labels = { ...cfg.labels, ...opts.labels };
3978
+ await reportFailure(
3979
+ {
3980
+ provider: spec.provider,
3981
+ model: spec.model,
3982
+ transport: spec.transport,
3983
+ capability: opts.capability,
3984
+ ...i === 0 && opts.tier ? { tier: opts.tier } : {},
3985
+ ...opts.purpose ? { purpose: opts.purpose } : {},
3986
+ ...Object.keys(labels).length ? { labels } : {},
3987
+ latencyMs: Math.round(performance.now() - t0)
3988
+ },
3989
+ e
3990
+ );
3887
3991
  }
3888
3992
  }
3889
3993
  throw lastErr;
@@ -3953,6 +4057,19 @@ function createAI(config = {}) {
3953
4057
  return;
3954
4058
  } catch (e) {
3955
4059
  lastErr = e;
4060
+ await reportFailure(
4061
+ {
4062
+ provider: spec.provider,
4063
+ model: spec.model,
4064
+ transport: spec.transport,
4065
+ capability: "chat",
4066
+ ...i === 0 ? { tier } : {},
4067
+ ...input.purpose ? { purpose: input.purpose } : {},
4068
+ ...Object.keys({ ...cfg.labels, ...input.labels }).length ? { labels: { ...cfg.labels, ...input.labels } } : {},
4069
+ latencyMs: Math.round(performance.now() - t0)
4070
+ },
4071
+ e
4072
+ );
3956
4073
  if (emitted || !eligibleForFallback(e)) {
3957
4074
  yield errorEvent(e);
3958
4075
  return;
@@ -4499,6 +4616,9 @@ function multiSink(sinks) {
4499
4616
  return {
4500
4617
  async record(usage) {
4501
4618
  await Promise.allSettled(sinks.map(async (s) => s.record(usage)));
4619
+ },
4620
+ async recordFailure(failure) {
4621
+ await Promise.allSettled(sinks.map(async (s) => s.recordFailure?.(failure)));
4502
4622
  }
4503
4623
  };
4504
4624
  }