@broberg/ai-sdk 0.59.0 → 0.59.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.d.ts CHANGED
@@ -52,10 +52,15 @@ interface SearchCache {
52
52
  get(key: string): SearchResult | undefined;
53
53
  set(key: string, value: SearchResult, ttlMs: number): void;
54
54
  }
55
- /** Running spend per key (tenant + day). Async allowed, so it can live in a database. */
55
+ /** Running spend per key (tenant + day). Async allowed, so it can live in a database.
56
+ * `reserve` must CHECK AND ADD in one atomic step (e.g. `UPDATE … SET spent = spent + ?
57
+ * WHERE spent + ? <= cap`) and say whether it fitted — a separate read-then-write lets
58
+ * parallel calls all pass a cap only one of them fits under. `release` gives a
59
+ * reservation back when the call fails. */
56
60
  interface SearchCapStore {
61
+ reserve(key: string, usd: number, capUsd: number): boolean | Promise<boolean>;
62
+ release(key: string, usd: number): void | Promise<void>;
57
63
  getSpent(key: string): number | Promise<number>;
58
- addSpent(key: string, usd: number): void | Promise<void>;
59
64
  }
60
65
  interface SearchOptions {
61
66
  /** false = no cache. Default: one in-process memory cache shared by all calls. */
@@ -2325,8 +2330,8 @@ declare const falStubAdapter: ProviderAdapter;
2325
2330
  * wires the live adapters. */
2326
2331
  declare const stubProviders: Record<string, ProviderAdapter>;
2327
2332
 
2328
- declare const VERSION: "0.59.0";
2329
- declare const SDK_TAG: "@broberg/ai-sdk@0.59.0";
2333
+ declare const VERSION: "0.59.1";
2334
+ declare const SDK_TAG: "@broberg/ai-sdk@0.59.1";
2330
2335
 
2331
2336
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2332
2337
  * per-call override.
package/dist/index.js CHANGED
@@ -192,9 +192,19 @@ var SearchBudgetExceededError = class extends Error {
192
192
  var sharedCache = memorySearchCache();
193
193
  var sharedCapStore = /* @__PURE__ */ (() => {
194
194
  const m = /* @__PURE__ */ new Map();
195
- return { getSpent: (k) => m.get(k) ?? 0, addSpent: (k, usd) => {
196
- m.set(k, (m.get(k) ?? 0) + usd);
197
- } };
195
+ return {
196
+ // Synchronous, so check-and-add cannot interleave with another call.
197
+ reserve: (k, usd, cap) => {
198
+ const s = m.get(k) ?? 0;
199
+ if (s + usd > cap) return false;
200
+ m.set(k, s + usd);
201
+ return true;
202
+ },
203
+ release: (k, usd) => {
204
+ m.set(k, (m.get(k) ?? 0) - usd);
205
+ },
206
+ getSpent: (k) => m.get(k) ?? 0
207
+ };
198
208
  })();
199
209
  var copenhagenDay = (d) => new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Copenhagen" }).format(d);
200
210
  async function callProvider(provider, req, opts) {
@@ -250,10 +260,10 @@ async function search(req, opts = {}) {
250
260
  const key = searchCacheKey(provider, req);
251
261
  const hit = cache?.get(key);
252
262
  if (hit) return { items: hit.items, meta: { ...hit.meta, cached: true, costUsd: 0, latencyMs: 0 } };
253
- if (opts.dailyCapUsd !== void 0) {
254
- const spent = await capStore.getSpent(capKey);
255
- const price = SEARCH_PRICE_USD[provider];
256
- if (spent + price > opts.dailyCapUsd) throw new SearchBudgetExceededError(tenant, day, opts.dailyCapUsd, spent, price);
263
+ const price = SEARCH_PRICE_USD[provider];
264
+ const cap = opts.dailyCapUsd ?? Infinity;
265
+ if (!await capStore.reserve(capKey, price, cap)) {
266
+ throw new SearchBudgetExceededError(tenant, day, cap, await capStore.getSpent(capKey), price);
257
267
  }
258
268
  const t0 = performance.now();
259
269
  try {
@@ -264,7 +274,6 @@ async function search(req, opts = {}) {
264
274
  meta: { provider, latencyMs, cached: false, costUsd: SEARCH_PRICE_USD[provider], ...out.requestId ? { requestId: out.requestId } : {} }
265
275
  };
266
276
  cache?.set(key, result, ttl);
267
- await capStore.addSpent(capKey, result.meta.costUsd);
268
277
  try {
269
278
  await sink?.record(usageRow(provider, req, result.meta.costUsd, latencyMs));
270
279
  } catch {
@@ -272,6 +281,7 @@ async function search(req, opts = {}) {
272
281
  return result;
273
282
  } catch (e) {
274
283
  lastErr = e;
284
+ await capStore.release(capKey, price);
275
285
  if (sink?.recordFailure) {
276
286
  try {
277
287
  await sink.recordFailure({
@@ -3819,8 +3829,8 @@ var aiConfigSchema = z.object({
3819
3829
  import { randomUUID } from "crypto";
3820
3830
 
3821
3831
  // src/version.ts
3822
- var VERSION = "0.59.0";
3823
- var SDK_TAG = "@broberg/ai-sdk@0.59.0";
3832
+ var VERSION = "0.59.1";
3833
+ var SDK_TAG = "@broberg/ai-sdk@0.59.1";
3824
3834
 
3825
3835
  // src/cost/sinks/upmetrics.ts
3826
3836
  function isRetryableStatus(status) {