@broberg/ai-sdk 0.62.1 → 0.63.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/README.md CHANGED
@@ -221,8 +221,15 @@ You don't have to wire a sink. If `UPMETRICS_API_KEY` is in the env, a bare
221
221
  call-sites passed no sink, leaving ~91% of Mistral spend invisible. No key in
222
222
  the env → no sink, no crash (ship-dark).
223
223
 
224
+ **Every client needs a name (0.63+, F034.4).** `createAI()` throws at startup unless
225
+ `UPMETRICS_AGENT_NAME` is set or you pass `agentName` — even when no key is present, so
226
+ your tests catch it before production does. Owner order 2026-10-15: «Ingen må være
227
+ anonyme» — measured over 30 days, 8 repos had sent their spend as `unknown`. Not needed
228
+ with an explicit `costSink` or `costSink: null`.
229
+
224
230
  ```ts
225
- createAI(); // key in env → tracked; no key → nothing happens
231
+ createAI({ agentName: "my-app" }); // key in env → tracked; no key → nothing happens
232
+ createAI(); // same, if UPMETRICS_AGENT_NAME is set — else throws
226
233
  createAI({ costSink: mySink }); // explicit sink wins
227
234
  createAI({ costSink: null }); // explicit OPT-OUT — see below
228
235
  ```
@@ -230,7 +237,7 @@ createAI({ costSink: null }); // explicit OPT-OUT — see below
230
237
  | Env var | Effect |
231
238
  |---|---|
232
239
  | `UPMETRICS_API_KEY` | **The only switch.** Present → tracking on. |
233
- | `UPMETRICS_AGENT_NAME` | Row label. Set it — the fallback is `npm_package_name`, which is empty for a process not started via an npm/bun script (a `node dist/…` or Docker service logs as `unknown`). |
240
+ | `UPMETRICS_AGENT_NAME` | Row label, **required** (or `agentName`) since 0.63.0 — set it in production AND in your test env. Up to 0.62 the fallback was `npm_package_name`, empty outside an npm/bun script, so `node dist/…` and Docker services logged as `unknown`. |
234
241
  | `UPMETRICS_BASE_URL` | Ingest host. Defaults to `https://upmetrics.org`. |
235
242
  | `UPMETRICS_COMPLIANCE` | `1` sets compliance mode. `Usage` carries no prompt/response text either way. |
236
243
 
package/dist/index.d.ts CHANGED
@@ -1864,6 +1864,9 @@ declare const aiConfigSchema: z.ZodObject<{
1864
1864
  * to opt OUT explicitly — for a consumer that already reports its own costs
1865
1865
  * and would otherwise be counted twice (F034.3). An object is used as-is. */
1866
1866
  costSink: z.ZodOptional<z.ZodNullable<z.ZodType<CostSink, z.ZodTypeDef, CostSink>>>;
1867
+ /** F034.4 — who is calling, as Upmetrics groups it (e.g. "cms"). Required when
1868
+ * `costSink` is omitted, unless UPMETRICS_AGENT_NAME is set. */
1869
+ agentName: z.ZodOptional<z.ZodString>;
1867
1870
  budget: z.ZodOptional<z.ZodObject<{
1868
1871
  perCallUsd: z.ZodOptional<z.ZodNumber>;
1869
1872
  rollingUsd: z.ZodOptional<z.ZodNumber>;
@@ -1896,6 +1899,7 @@ declare const aiConfigSchema: z.ZodObject<{
1896
1899
  transport: "http" | "subprocess";
1897
1900
  }>> | undefined;
1898
1901
  providers?: Record<string, ProviderAdapter> | undefined;
1902
+ agentName?: string | undefined;
1899
1903
  budget?: {
1900
1904
  perCallUsd?: number | undefined;
1901
1905
  rollingUsd?: number | undefined;
@@ -1916,6 +1920,7 @@ declare const aiConfigSchema: z.ZodObject<{
1916
1920
  transport: "http" | "subprocess";
1917
1921
  }>> | undefined;
1918
1922
  providers?: Record<string, ProviderAdapter> | undefined;
1923
+ agentName?: string | undefined;
1919
1924
  budget?: {
1920
1925
  perCallUsd?: number | undefined;
1921
1926
  rollingUsd?: number | undefined;
@@ -2334,8 +2339,8 @@ declare const falStubAdapter: ProviderAdapter;
2334
2339
  * wires the live adapters. */
2335
2340
  declare const stubProviders: Record<string, ProviderAdapter>;
2336
2341
 
2337
- declare const VERSION: "0.62.1";
2338
- declare const SDK_TAG: "@broberg/ai-sdk@0.62.1";
2342
+ declare const VERSION: "0.63.0";
2343
+ declare const SDK_TAG: "@broberg/ai-sdk@0.63.0";
2339
2344
 
2340
2345
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2341
2346
  * per-call override.
package/dist/index.js CHANGED
@@ -340,11 +340,13 @@ async function search(req, opts = {}) {
340
340
  const nowMs = () => (opts.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
341
341
  let lastErr;
342
342
  for (const [i, provider] of chain.entries()) {
343
- const coolKey = cooldownKey(provider, opts);
344
- if (i < chain.length - 1 && (coolingUntil.get(coolKey) ?? 0) > nowMs()) continue;
345
343
  const key = searchCacheKey(provider, req);
346
344
  const hit = cache?.get(key);
347
345
  if (hit) return { items: hit.items, meta: { ...hit.meta, cached: true, costUsd: 0, latencyMs: 0 } };
346
+ const coolKey = cooldownKey(provider, opts);
347
+ const until = coolingUntil.get(coolKey);
348
+ if (until !== void 0 && until <= nowMs()) coolingUntil.delete(coolKey);
349
+ else if (until !== void 0 && i < chain.length - 1) continue;
348
350
  const price = SEARCH_PRICE_USD[provider];
349
351
  const cap = opts.dailyCapUsd ?? Infinity;
350
352
  if (!await capStore.reserve(capKey, price, cap)) {
@@ -3908,6 +3910,9 @@ var aiConfigSchema = z.object({
3908
3910
  * to opt OUT explicitly — for a consumer that already reports its own costs
3909
3911
  * and would otherwise be counted twice (F034.3). An object is used as-is. */
3910
3912
  costSink: z.custom().nullable().optional(),
3913
+ /** F034.4 — who is calling, as Upmetrics groups it (e.g. "cms"). Required when
3914
+ * `costSink` is omitted, unless UPMETRICS_AGENT_NAME is set. */
3915
+ agentName: z.string().optional(),
3911
3916
  budget: budgetSchema.optional(),
3912
3917
  availability: availabilitySchema.optional()
3913
3918
  });
@@ -3916,8 +3921,8 @@ var aiConfigSchema = z.object({
3916
3921
  import { randomUUID } from "crypto";
3917
3922
 
3918
3923
  // src/version.ts
3919
- var VERSION = "0.62.1";
3920
- var SDK_TAG = "@broberg/ai-sdk@0.62.1";
3924
+ var VERSION = "0.63.0";
3925
+ var SDK_TAG = "@broberg/ai-sdk@0.63.0";
3921
3926
 
3922
3927
  // src/cost/sinks/upmetrics.ts
3923
3928
  function isRetryableStatus(status) {
@@ -3927,6 +3932,7 @@ var MAX_QUEUE = 30;
3927
3932
  var MAX_ATTEMPTS = 5;
3928
3933
  var MAX_BACKOFF_MS = 3e4;
3929
3934
  function upmetricsSink(config) {
3935
+ if (!config.agentName?.trim()) throw new Error("upmetricsSink(): agentName is required (e.g. your repo name) \u2014 anonymous cost rows are not allowed");
3930
3936
  const doFetch = config.fetch ?? fetch;
3931
3937
  const url = `${config.baseUrl.replace(/\/$/, "")}/api/agent`;
3932
3938
  const retry = config.retry ?? true;
@@ -4256,21 +4262,28 @@ var ROUTED_SPECS = [
4256
4262
  DEFAULT_BATCH_SPEC,
4257
4263
  DEFAULT_TRANSCRIBE_SPEC
4258
4264
  ];
4259
- function defaultCostSink() {
4265
+ function defaultCostSink(agentName) {
4260
4266
  const apiKey = process.env.UPMETRICS_API_KEY;
4261
4267
  if (!apiKey) return void 0;
4262
4268
  return upmetricsSink({
4263
4269
  baseUrl: process.env.UPMETRICS_BASE_URL ?? "https://upmetrics.org",
4264
4270
  apiKey,
4265
- agentName: process.env.UPMETRICS_AGENT_NAME ?? process.env.npm_package_name ?? "unknown",
4271
+ agentName,
4266
4272
  complianceMode: process.env.UPMETRICS_COMPLIANCE === "1"
4267
4273
  });
4268
4274
  }
4275
+ function requireAgentName(configured) {
4276
+ const name = (configured ?? process.env.UPMETRICS_AGENT_NAME ?? "").trim();
4277
+ if (name) return name;
4278
+ throw new Error(
4279
+ 'createAI(): no agent name \u2014 every AI call must say who made it (owner order 2026-10-15). Set UPMETRICS_AGENT_NAME=<your repo> (production AND test env), or pass createAI({ agentName: "<your repo>" }). Not needed with an explicit costSink, or costSink: null.'
4280
+ );
4281
+ }
4269
4282
  function createAI(config = {}) {
4270
4283
  const cfg = aiConfigSchema.parse(config);
4271
4284
  if (cfg.byok) assertByokProviders(cfg.providers);
4272
4285
  const providers = cfg.providers ?? defaultProviders;
4273
- const costSink = cfg.costSink === void 0 ? defaultCostSink() : cfg.costSink ?? void 0;
4286
+ const costSink = cfg.costSink === void 0 ? defaultCostSink(requireAgentName(cfg.agentName)) : cfg.costSink ?? void 0;
4274
4287
  const budget = cfg.budget ? new BudgetGuard(cfg.budget) : void 0;
4275
4288
  const estTokens = (s) => Math.ceil(s.length / 4);
4276
4289
  async function preflight(spec, estInTokens, estOutTokens) {