@broberg/ai-sdk 0.59.0 → 0.60.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.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. */
@@ -1877,8 +1882,9 @@ declare const aiConfigSchema: z.ZodObject<{
1877
1882
  }>>;
1878
1883
  }, "strip", z.ZodTypeAny, {
1879
1884
  labels?: Record<string, string> | undefined;
1880
- promptCache?: boolean | undefined;
1885
+ costSink?: CostSink | null | undefined;
1881
1886
  byok?: boolean | undefined;
1887
+ promptCache?: boolean | undefined;
1882
1888
  strictPinning?: boolean | undefined;
1883
1889
  defaults?: Partial<Record<"vision" | "video" | "embedding" | "fast" | "smart" | "powerful" | "cheap", {
1884
1890
  provider: string;
@@ -1886,7 +1892,6 @@ declare const aiConfigSchema: z.ZodObject<{
1886
1892
  transport: "http" | "subprocess";
1887
1893
  }>> | undefined;
1888
1894
  providers?: Record<string, ProviderAdapter> | undefined;
1889
- costSink?: CostSink | null | undefined;
1890
1895
  budget?: {
1891
1896
  perCallUsd?: number | undefined;
1892
1897
  rollingUsd?: number | undefined;
@@ -1897,8 +1902,9 @@ declare const aiConfigSchema: z.ZodObject<{
1897
1902
  } | undefined;
1898
1903
  }, {
1899
1904
  labels?: Record<string, string> | undefined;
1900
- promptCache?: boolean | undefined;
1905
+ costSink?: CostSink | null | undefined;
1901
1906
  byok?: boolean | undefined;
1907
+ promptCache?: boolean | undefined;
1902
1908
  strictPinning?: boolean | undefined;
1903
1909
  defaults?: Partial<Record<"vision" | "video" | "embedding" | "fast" | "smart" | "powerful" | "cheap", {
1904
1910
  provider: string;
@@ -1906,7 +1912,6 @@ declare const aiConfigSchema: z.ZodObject<{
1906
1912
  transport: "http" | "subprocess";
1907
1913
  }>> | undefined;
1908
1914
  providers?: Record<string, ProviderAdapter> | undefined;
1909
- costSink?: CostSink | null | undefined;
1910
1915
  budget?: {
1911
1916
  perCallUsd?: number | undefined;
1912
1917
  rollingUsd?: number | undefined;
@@ -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.60.0";
2334
+ declare const SDK_TAG: "@broberg/ai-sdk@0.60.0";
2330
2335
 
2331
2336
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2332
2337
  * per-call override.
@@ -2853,6 +2858,25 @@ declare function streamTransport(req: StreamTransportRequest): AsyncIterable<str
2853
2858
 
2854
2859
  declare const SEARCH_PRICE_USD: Record<SearchProviderId, number>;
2855
2860
 
2861
+ declare const webSearchTool: Tool;
2862
+ /** What the app decides — never the model. */
2863
+ interface WebSearchContext extends SearchOptions {
2864
+ /** Customer data may be in the query: only Zero-Data-Retention providers. */
2865
+ zdr?: boolean;
2866
+ /** Attribution, e.g. { tenantId } — also selects the tenant's daily cap. */
2867
+ labels?: Record<string, string>;
2868
+ }
2869
+ interface WebSearchToolResult {
2870
+ provider: string;
2871
+ results: {
2872
+ title: string;
2873
+ url: string;
2874
+ snippet: string;
2875
+ }[];
2876
+ }
2877
+ /** Run a `web_search` tool call. Throws on invalid arguments before any request. */
2878
+ declare function runWebSearch(args: Record<string, unknown>, ctx?: WebSearchContext): Promise<WebSearchToolResult>;
2879
+
2856
2880
  interface SearchRoute {
2857
2881
  /** Readable name — shows up in tests and in the reason a provider was picked. */
2858
2882
  name: string;
@@ -2873,4 +2897,4 @@ declare function memorySearchCache(max?: number): SearchCache;
2873
2897
 
2874
2898
  declare function search(req: SearchRequest, opts?: SearchOptions): Promise<SearchResult>;
2875
2899
 
2876
- export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AzureVoiceInfo, BatchJob, BatchRequestItem, BatchResultItem, type BflAdapterConfig, type BflCredits, BudgetConfig, BudgetExceededError, BudgetGuard, BudgetStore, CONFIG_DERIVED_HOSTS, Capability, type ChatInput, ChatResult, ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type Contracts, type CostQuery, CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_SEARCH_ROUTES, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, ImageResult, type JudgeInput, JudgeResult, LOCALLY_PINNED_HOSTS, type MockupInput, type MockupResult, type ModerationInput, ModerationResult, type OcrInput, OcrResult, type OpenAICompatibleConfig, type PodcastInput, PodcastResult, type PriceDriftResult, type PriceDriftRow, type PricingEntry, ProviderAdapter, type RefreshOptions, type RefreshResult, Region, type RerankInput, type RerankResult, type RouteForecast, SDK_TAG, SEARCH_CACHE_TTL_MS, SEARCH_PRICE_USD, SearchBudgetExceededError, type SearchCache, type SearchCapStore, type SearchCredentials, type SearchItem, SearchKeyMissingError, type SearchOptions, type SearchProviderId, type SearchPurpose, type SearchRequest, type SearchResult, type SearchRoute, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, Tier, TierSpec, Tool, ToolCall, type TrainStyleInput, TrainStyleResult, type TranscribeInput, TranscribeResult, type TranslateInput, TranslateResult, Transport, type TransportRequest, type TransportResponse, type TtsInput, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSink, type UpmetricsSinkConfig, type UpmetricsSinkStats, Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, ZDR_PROVIDERS, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, byokAdapter, chatInputSchema, checkPriceDrift, checkVoice, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, isByokKeyed, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, memorySearchCache, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, routeSearch, search, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, typesafeAdapter, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
2900
+ export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AzureVoiceInfo, BatchJob, BatchRequestItem, BatchResultItem, type BflAdapterConfig, type BflCredits, BudgetConfig, BudgetExceededError, BudgetGuard, BudgetStore, CONFIG_DERIVED_HOSTS, Capability, type ChatInput, ChatResult, ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type Contracts, type CostQuery, CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_SEARCH_ROUTES, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, ImageResult, type JudgeInput, JudgeResult, LOCALLY_PINNED_HOSTS, type MockupInput, type MockupResult, type ModerationInput, ModerationResult, type OcrInput, OcrResult, type OpenAICompatibleConfig, type PodcastInput, PodcastResult, type PriceDriftResult, type PriceDriftRow, type PricingEntry, ProviderAdapter, type RefreshOptions, type RefreshResult, Region, type RerankInput, type RerankResult, type RouteForecast, SDK_TAG, SEARCH_CACHE_TTL_MS, SEARCH_PRICE_USD, SearchBudgetExceededError, type SearchCache, type SearchCapStore, type SearchCredentials, type SearchItem, SearchKeyMissingError, type SearchOptions, type SearchProviderId, type SearchPurpose, type SearchRequest, type SearchResult, type SearchRoute, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, Tier, TierSpec, Tool, ToolCall, type TrainStyleInput, TrainStyleResult, type TranscribeInput, TranscribeResult, type TranslateInput, TranslateResult, Transport, type TransportRequest, type TransportResponse, type TtsInput, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSink, type UpmetricsSinkConfig, type UpmetricsSinkStats, Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, type WebSearchContext, type WebSearchToolResult, ZDR_PROVIDERS, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, byokAdapter, chatInputSchema, checkPriceDrift, checkVoice, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, isByokKeyed, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, memorySearchCache, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, routeSearch, runWebSearch, search, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, typesafeAdapter, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, webSearchTool, wouldProviderRouteTo, wouldRouteTo };
package/dist/index.js CHANGED
@@ -188,13 +188,62 @@ var SearchBudgetExceededError = class extends Error {
188
188
  requestedUsd;
189
189
  };
190
190
 
191
+ // src/search/tool.ts
192
+ var PURPOSES = ["agent", "grounding", "discovery", "monitor"];
193
+ var webSearchTool = {
194
+ name: "web_search",
195
+ description: "Search the open web. Returns up to 10 results with title, url and snippet. Set lang to the query's language (e.g. 'da', 'en'). Set fresh when the answer must be recent (news, prices, events).",
196
+ parameters: {
197
+ type: "object",
198
+ properties: {
199
+ query: { type: "string", minLength: 1, maxLength: 1024, description: "What to search for." },
200
+ lang: { type: "string", description: "Language of the query, e.g. 'da' or 'en'." },
201
+ limit: { type: "integer", minimum: 1, maximum: 10, description: "Number of results (default 10)." },
202
+ purpose: { type: "string", enum: PURPOSES, description: "agent (default), grounding, discovery or monitor." },
203
+ fresh: { type: "boolean", description: "Results must be recent." }
204
+ },
205
+ required: ["query"],
206
+ additionalProperties: false
207
+ }
208
+ };
209
+ async function runWebSearch(args, ctx = {}) {
210
+ const { query, lang, limit, purpose, fresh } = args;
211
+ if (typeof query !== "string" || query.trim() === "") throw new Error("web_search: `query` must be a non-empty string");
212
+ if (lang !== void 0 && typeof lang !== "string") throw new Error("web_search: `lang` must be a string");
213
+ if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1 || limit > 10)) throw new Error("web_search: `limit` must be an integer 1\u201310");
214
+ if (purpose !== void 0 && !PURPOSES.includes(purpose)) throw new Error(`web_search: \`purpose\` must be one of ${PURPOSES.join(", ")}`);
215
+ if (fresh !== void 0 && typeof fresh !== "boolean") throw new Error("web_search: `fresh` must be a boolean");
216
+ const { zdr, labels, ...opts } = ctx;
217
+ const req = {
218
+ query,
219
+ ...lang !== void 0 ? { lang } : {},
220
+ ...limit !== void 0 ? { limit } : {},
221
+ ...purpose !== void 0 ? { purpose } : {},
222
+ ...fresh !== void 0 ? { fresh } : {},
223
+ ...zdr ? { zdr } : {},
224
+ ...labels ? { labels } : {}
225
+ };
226
+ const r = await search(req, opts);
227
+ return { provider: r.meta.provider, results: r.items.map((i) => ({ title: i.title, url: i.url, snippet: i.description })) };
228
+ }
229
+
191
230
  // src/search/index.ts
192
231
  var sharedCache = memorySearchCache();
193
232
  var sharedCapStore = /* @__PURE__ */ (() => {
194
233
  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
- } };
234
+ return {
235
+ // Synchronous, so check-and-add cannot interleave with another call.
236
+ reserve: (k, usd, cap) => {
237
+ const s = m.get(k) ?? 0;
238
+ if (s + usd > cap) return false;
239
+ m.set(k, s + usd);
240
+ return true;
241
+ },
242
+ release: (k, usd) => {
243
+ m.set(k, (m.get(k) ?? 0) - usd);
244
+ },
245
+ getSpent: (k) => m.get(k) ?? 0
246
+ };
198
247
  })();
199
248
  var copenhagenDay = (d) => new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Copenhagen" }).format(d);
200
249
  async function callProvider(provider, req, opts) {
@@ -250,10 +299,10 @@ async function search(req, opts = {}) {
250
299
  const key = searchCacheKey(provider, req);
251
300
  const hit = cache?.get(key);
252
301
  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);
302
+ const price = SEARCH_PRICE_USD[provider];
303
+ const cap = opts.dailyCapUsd ?? Infinity;
304
+ if (!await capStore.reserve(capKey, price, cap)) {
305
+ throw new SearchBudgetExceededError(tenant, day, cap, await capStore.getSpent(capKey), price);
257
306
  }
258
307
  const t0 = performance.now();
259
308
  try {
@@ -264,7 +313,6 @@ async function search(req, opts = {}) {
264
313
  meta: { provider, latencyMs, cached: false, costUsd: SEARCH_PRICE_USD[provider], ...out.requestId ? { requestId: out.requestId } : {} }
265
314
  };
266
315
  cache?.set(key, result, ttl);
267
- await capStore.addSpent(capKey, result.meta.costUsd);
268
316
  try {
269
317
  await sink?.record(usageRow(provider, req, result.meta.costUsd, latencyMs));
270
318
  } catch {
@@ -272,6 +320,7 @@ async function search(req, opts = {}) {
272
320
  return result;
273
321
  } catch (e) {
274
322
  lastErr = e;
323
+ await capStore.release(capKey, price);
275
324
  if (sink?.recordFailure) {
276
325
  try {
277
326
  await sink.recordFailure({
@@ -3819,8 +3868,8 @@ var aiConfigSchema = z.object({
3819
3868
  import { randomUUID } from "crypto";
3820
3869
 
3821
3870
  // src/version.ts
3822
- var VERSION = "0.59.0";
3823
- var SDK_TAG = "@broberg/ai-sdk@0.59.0";
3871
+ var VERSION = "0.60.0";
3872
+ var SDK_TAG = "@broberg/ai-sdk@0.60.0";
3824
3873
 
3825
3874
  // src/cost/sinks/upmetrics.ts
3826
3875
  function isRetryableStatus(status) {
@@ -5437,6 +5486,7 @@ export {
5437
5486
  resolveTier,
5438
5487
  resolveVoice,
5439
5488
  routeSearch,
5489
+ runWebSearch,
5440
5490
  search,
5441
5491
  sqliteBudgetStore,
5442
5492
  sqliteSink,
@@ -5453,6 +5503,7 @@ export {
5453
5503
  usdFromMicro,
5454
5504
  vertexAdapter,
5455
5505
  visionInputSchema,
5506
+ webSearchTool,
5456
5507
  wouldProviderRouteTo,
5457
5508
  wouldRouteTo
5458
5509
  };