@broberg/ai-sdk 0.56.1 → 0.58.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 +164 -73
- package/dist/index.js +246 -25
- package/dist/index.js.map +1 -1
- package/dist/{registry-U66vzTm0.d.ts → registry-VqJrONWk.d.ts} +2 -2
- package/dist/registry.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,238 @@ import {
|
|
|
26
26
|
getPrice
|
|
27
27
|
} from "./chunk-QVMGFHCF.js";
|
|
28
28
|
|
|
29
|
+
// src/search/brave.ts
|
|
30
|
+
async function braveSearch(req, apiKey, f) {
|
|
31
|
+
const params = new URLSearchParams({ q: req.query, count: String(Math.min(req.limit ?? 10, 20)) });
|
|
32
|
+
if (req.lang) params.set("search_lang", req.lang);
|
|
33
|
+
if (req.country) params.set("country", req.country);
|
|
34
|
+
const res = await f(`https://api.search.brave.com/res/v1/web/search?${params}`, {
|
|
35
|
+
headers: { accept: "application/json", "X-Subscription-Token": apiKey }
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) throw Object.assign(new Error(`brave search: HTTP ${res.status} ${(await res.text()).slice(0, 200)}`), { status: res.status });
|
|
38
|
+
const body = await res.json();
|
|
39
|
+
const items = (body.web?.results ?? []).map((r) => ({
|
|
40
|
+
title: r.title ?? "",
|
|
41
|
+
url: r.url ?? "",
|
|
42
|
+
description: r.description ?? "",
|
|
43
|
+
provider: "brave",
|
|
44
|
+
...r.language ? { lang: r.language } : {},
|
|
45
|
+
...r.page_age ? { publishedAt: new Date(r.page_age).toISOString() } : {}
|
|
46
|
+
}));
|
|
47
|
+
return { items, requestId: res.headers.get("x-request-id") ?? void 0 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/search/cloudflare.ts
|
|
51
|
+
var CLOUDFLARE_MAX_QUERY = 1024;
|
|
52
|
+
var CLOUDFLARE_MAX_LIMIT = 10;
|
|
53
|
+
async function cloudflareSearch(req, creds, f) {
|
|
54
|
+
if (req.query.length < 1 || req.query.length > CLOUDFLARE_MAX_QUERY) {
|
|
55
|
+
throw new Error(`search: Cloudflare takes a query of 1\u2013${CLOUDFLARE_MAX_QUERY} characters, got ${req.query.length}`);
|
|
56
|
+
}
|
|
57
|
+
const provider = req.provider.split(":")[1];
|
|
58
|
+
const res = await f(`https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(creds.accountId)}/ai/websearch/`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { authorization: `Bearer ${creds.apiToken}`, "content-type": "application/json" },
|
|
61
|
+
body: JSON.stringify({ query: req.query, provider, limit: Math.min(req.limit ?? CLOUDFLARE_MAX_LIMIT, CLOUDFLARE_MAX_LIMIT) })
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) throw Object.assign(new Error(`cloudflare search (${provider}): HTTP ${res.status} ${(await res.text()).slice(0, 200)}`), { status: res.status });
|
|
64
|
+
const body = await res.json();
|
|
65
|
+
const inner = body.result ?? body;
|
|
66
|
+
const items = (inner.items ?? []).map((r) => ({
|
|
67
|
+
title: r.title ?? "",
|
|
68
|
+
url: r.url ?? "",
|
|
69
|
+
description: r.description ?? "",
|
|
70
|
+
provider: req.provider
|
|
71
|
+
}));
|
|
72
|
+
return { items, requestId: inner.metadata?.requestId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/search/prices.ts
|
|
76
|
+
var SEARCH_PRICE_USD = {
|
|
77
|
+
"cloudflare:ceramic": 25e-5,
|
|
78
|
+
"cloudflare:linkup": 5e-3,
|
|
79
|
+
"cloudflare:exa": 7e-3,
|
|
80
|
+
brave: 5e-3
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/search/router.ts
|
|
84
|
+
var DEFAULT_SEARCH_ROUTES = [
|
|
85
|
+
{ name: "fresh-or-country", when: { fresh: true }, chain: ["brave", "cloudflare:linkup"] },
|
|
86
|
+
{ name: "english-agent", when: { english: true, purpose: ["agent", "grounding"] }, chain: ["cloudflare:ceramic", "cloudflare:linkup", "brave"] },
|
|
87
|
+
{ name: "english-other", when: { english: true }, chain: ["cloudflare:linkup", "cloudflare:ceramic", "brave"] },
|
|
88
|
+
{ name: "non-english", when: {}, chain: ["cloudflare:linkup", "brave"] }
|
|
89
|
+
];
|
|
90
|
+
var ZDR_PROVIDERS = /* @__PURE__ */ new Set(["cloudflare:ceramic", "cloudflare:linkup"]);
|
|
91
|
+
var isEnglish = (lang) => !lang || /^en\b/i.test(lang);
|
|
92
|
+
function routeSearch(req, routes = DEFAULT_SEARCH_ROUTES) {
|
|
93
|
+
if (req.provider) return [req.provider];
|
|
94
|
+
const fresh = Boolean(req.fresh || req.country);
|
|
95
|
+
const english = isEnglish(req.lang);
|
|
96
|
+
const route = routes.find(
|
|
97
|
+
(r) => (r.when.fresh === void 0 || r.when.fresh === fresh) && (r.when.english === void 0 || r.when.english === english) && (r.when.purpose === void 0 || r.when.purpose.includes(req.purpose ?? "agent"))
|
|
98
|
+
) ?? routes[routes.length - 1];
|
|
99
|
+
let chain = route.chain.filter((p) => english || p !== "cloudflare:ceramic");
|
|
100
|
+
chain = chain.filter((p) => p !== "cloudflare:exa");
|
|
101
|
+
if (req.zdr) chain = chain.filter((p) => ZDR_PROVIDERS.has(p));
|
|
102
|
+
return chain;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/search/cache.ts
|
|
106
|
+
var SEARCH_CACHE_TTL_MS = {
|
|
107
|
+
monitor: 5 * 6e4,
|
|
108
|
+
agent: 60 * 6e4,
|
|
109
|
+
grounding: 60 * 6e4,
|
|
110
|
+
discovery: 7 * 24 * 60 * 6e4
|
|
111
|
+
};
|
|
112
|
+
function searchCacheKey(provider, req) {
|
|
113
|
+
const q = req.query.normalize("NFKC").trim().toLowerCase().replace(/\s+/g, " ");
|
|
114
|
+
return JSON.stringify([provider, req.lang ?? "", req.country ?? "", req.limit ?? 10, q]);
|
|
115
|
+
}
|
|
116
|
+
function memorySearchCache(max = 1e3) {
|
|
117
|
+
const m = /* @__PURE__ */ new Map();
|
|
118
|
+
return {
|
|
119
|
+
get(k) {
|
|
120
|
+
const e = m.get(k);
|
|
121
|
+
if (!e) return void 0;
|
|
122
|
+
if (Date.now() > e.until) {
|
|
123
|
+
m.delete(k);
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
return e.v;
|
|
127
|
+
},
|
|
128
|
+
set(k, v, ttlMs) {
|
|
129
|
+
if (m.size >= max) m.delete(m.keys().next().value);
|
|
130
|
+
m.set(k, { v, until: Date.now() + ttlMs });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/cost/failure.ts
|
|
136
|
+
function classifyFailure(err) {
|
|
137
|
+
const e = err;
|
|
138
|
+
const name = e?.name ?? "";
|
|
139
|
+
const message = String(e?.message ?? "");
|
|
140
|
+
if (name === "AbortError" || name === "TimeoutError" || /timed? ?out/i.test(message.split("\n")[0] ?? "")) {
|
|
141
|
+
return { errorCode: "timeout", errorKind: "timeout" };
|
|
142
|
+
}
|
|
143
|
+
let status = typeof e?.status === "number" ? e.status : void 0;
|
|
144
|
+
if (status === void 0) {
|
|
145
|
+
const m = /(?:^|\s)([45]\d{2})(?=[:\s]|$)/.exec(message.split("\n")[0] ?? "");
|
|
146
|
+
if (m) status = Number(m[1]);
|
|
147
|
+
}
|
|
148
|
+
if (status !== void 0) {
|
|
149
|
+
const kind = status === 401 || status === 403 ? "auth" : status === 404 ? "not_found" : status === 429 ? "rate_limit" : status >= 500 ? "server" : "other";
|
|
150
|
+
return { errorCode: String(status), errorKind: kind };
|
|
151
|
+
}
|
|
152
|
+
if (name === "TypeError" && /fetch failed|network|ECONN|ENOTFOUND/i.test(message + (e?.cause?.code ?? ""))) {
|
|
153
|
+
return { errorCode: "network", errorKind: "network" };
|
|
154
|
+
}
|
|
155
|
+
return { errorCode: "unknown", errorKind: "other" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/search/types.ts
|
|
159
|
+
var SearchKeyMissingError = class extends Error {
|
|
160
|
+
constructor(provider, envVar) {
|
|
161
|
+
super(`search: ${provider} needs ${envVar} (env or options.credentials)`);
|
|
162
|
+
this.provider = provider;
|
|
163
|
+
this.envVar = envVar;
|
|
164
|
+
this.name = "SearchKeyMissingError";
|
|
165
|
+
}
|
|
166
|
+
provider;
|
|
167
|
+
envVar;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/search/index.ts
|
|
171
|
+
var sharedCache = memorySearchCache();
|
|
172
|
+
async function callProvider(provider, req, opts) {
|
|
173
|
+
const f = opts.fetch ?? fetch;
|
|
174
|
+
const c = opts.credentials ?? {};
|
|
175
|
+
if (provider === "brave") {
|
|
176
|
+
const key = c.braveApiKey ?? process.env.BRAVE_API_KEY;
|
|
177
|
+
if (!key) throw new SearchKeyMissingError(provider, "BRAVE_API_KEY");
|
|
178
|
+
return braveSearch({ ...req, provider }, key, f);
|
|
179
|
+
}
|
|
180
|
+
if (provider.startsWith("cloudflare:")) {
|
|
181
|
+
const accountId = c.cloudflareAccountId ?? process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
182
|
+
const apiToken = c.cloudflareApiToken ?? process.env.CLOUDFLARE_API_TOKEN;
|
|
183
|
+
if (!accountId) throw new SearchKeyMissingError(provider, "CLOUDFLARE_ACCOUNT_ID");
|
|
184
|
+
if (!apiToken) throw new SearchKeyMissingError(provider, "CLOUDFLARE_API_TOKEN");
|
|
185
|
+
return cloudflareSearch({ ...req, provider }, { accountId, apiToken }, f);
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`search: unknown provider "${String(provider)}"`);
|
|
188
|
+
}
|
|
189
|
+
function usageRow(provider, req, costUsd, latencyMs) {
|
|
190
|
+
return {
|
|
191
|
+
provider,
|
|
192
|
+
model: "web-search",
|
|
193
|
+
region: "unknown",
|
|
194
|
+
// Brave and Cloudflare's gateway do not let us say where a query is served
|
|
195
|
+
transport: "http",
|
|
196
|
+
inputTokens: 0,
|
|
197
|
+
outputTokens: 0,
|
|
198
|
+
cacheReadTokens: 0,
|
|
199
|
+
cacheCreationTokens: 0,
|
|
200
|
+
costUsd,
|
|
201
|
+
costBasis: "computed",
|
|
202
|
+
latencyMs,
|
|
203
|
+
capability: "search",
|
|
204
|
+
...req.purpose ? { purpose: req.purpose } : {},
|
|
205
|
+
...req.labels ? { labels: req.labels } : {},
|
|
206
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function search(req, opts = {}) {
|
|
210
|
+
const chain = routeSearch(req);
|
|
211
|
+
if (chain.length === 0) throw new Error("search: no provider fits this request (zdr excludes every candidate)");
|
|
212
|
+
const cache = opts.cache === false ? void 0 : opts.cache ?? sharedCache;
|
|
213
|
+
const ttl = SEARCH_CACHE_TTL_MS[req.purpose ?? "agent"];
|
|
214
|
+
const sink = opts.costSink;
|
|
215
|
+
let lastErr;
|
|
216
|
+
for (const provider of chain) {
|
|
217
|
+
const key = searchCacheKey(provider, req);
|
|
218
|
+
const hit = cache?.get(key);
|
|
219
|
+
if (hit) return { items: hit.items, meta: { ...hit.meta, cached: true, costUsd: 0, latencyMs: 0 } };
|
|
220
|
+
const t0 = performance.now();
|
|
221
|
+
try {
|
|
222
|
+
const out = await callProvider(provider, req, opts);
|
|
223
|
+
const latencyMs = Math.round(performance.now() - t0);
|
|
224
|
+
const result = {
|
|
225
|
+
items: dedupe(out.items),
|
|
226
|
+
meta: { provider, latencyMs, cached: false, costUsd: SEARCH_PRICE_USD[provider], ...out.requestId ? { requestId: out.requestId } : {} }
|
|
227
|
+
};
|
|
228
|
+
cache?.set(key, result, ttl);
|
|
229
|
+
try {
|
|
230
|
+
await sink?.record(usageRow(provider, req, result.meta.costUsd, latencyMs));
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
} catch (e) {
|
|
235
|
+
lastErr = e;
|
|
236
|
+
if (sink?.recordFailure) {
|
|
237
|
+
try {
|
|
238
|
+
await sink.recordFailure({
|
|
239
|
+
provider,
|
|
240
|
+
model: "web-search",
|
|
241
|
+
transport: "http",
|
|
242
|
+
capability: "search",
|
|
243
|
+
...req.purpose ? { purpose: req.purpose } : {},
|
|
244
|
+
...req.labels ? { labels: req.labels } : {},
|
|
245
|
+
latencyMs: Math.round(performance.now() - t0),
|
|
246
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
247
|
+
...classifyFailure(e)
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw lastErr;
|
|
255
|
+
}
|
|
256
|
+
function dedupe(items) {
|
|
257
|
+
const seen = /* @__PURE__ */ new Set();
|
|
258
|
+
return items.filter((i) => seen.has(i.url) ? false : (seen.add(i.url), true));
|
|
259
|
+
}
|
|
260
|
+
|
|
29
261
|
// src/cost/default-hosts.ts
|
|
30
262
|
var DEFAULT_BASE_URLS = {
|
|
31
263
|
anthropic: "https://api.anthropic.com",
|
|
@@ -3548,8 +3780,8 @@ var aiConfigSchema = z.object({
|
|
|
3548
3780
|
import { randomUUID } from "crypto";
|
|
3549
3781
|
|
|
3550
3782
|
// src/version.ts
|
|
3551
|
-
var VERSION = "0.
|
|
3552
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
3783
|
+
var VERSION = "0.58.0";
|
|
3784
|
+
var SDK_TAG = "@broberg/ai-sdk@0.58.0";
|
|
3553
3785
|
|
|
3554
3786
|
// src/cost/sinks/upmetrics.ts
|
|
3555
3787
|
function isRetryableStatus(status) {
|
|
@@ -3830,29 +4062,6 @@ function assertByokProviders(providers) {
|
|
|
3830
4062
|
}
|
|
3831
4063
|
}
|
|
3832
4064
|
|
|
3833
|
-
// src/cost/failure.ts
|
|
3834
|
-
function classifyFailure(err) {
|
|
3835
|
-
const e = err;
|
|
3836
|
-
const name = e?.name ?? "";
|
|
3837
|
-
const message = String(e?.message ?? "");
|
|
3838
|
-
if (name === "AbortError" || name === "TimeoutError" || /timed? ?out/i.test(message.split("\n")[0] ?? "")) {
|
|
3839
|
-
return { errorCode: "timeout", errorKind: "timeout" };
|
|
3840
|
-
}
|
|
3841
|
-
let status = typeof e?.status === "number" ? e.status : void 0;
|
|
3842
|
-
if (status === void 0) {
|
|
3843
|
-
const m = /(?:^|\s)([45]\d{2})(?=[:\s]|$)/.exec(message.split("\n")[0] ?? "");
|
|
3844
|
-
if (m) status = Number(m[1]);
|
|
3845
|
-
}
|
|
3846
|
-
if (status !== void 0) {
|
|
3847
|
-
const kind = status === 401 || status === 403 ? "auth" : status === 404 ? "not_found" : status === 429 ? "rate_limit" : status >= 500 ? "server" : "other";
|
|
3848
|
-
return { errorCode: String(status), errorKind: kind };
|
|
3849
|
-
}
|
|
3850
|
-
if (name === "TypeError" && /fetch failed|network|ECONN|ENOTFOUND/i.test(message + (e?.cause?.code ?? ""))) {
|
|
3851
|
-
return { errorCode: "network", errorKind: "network" };
|
|
3852
|
-
}
|
|
3853
|
-
return { errorCode: "unknown", errorKind: "other" };
|
|
3854
|
-
}
|
|
3855
|
-
|
|
3856
4065
|
// src/client.ts
|
|
3857
4066
|
var DEFAULT_IMAGE_SPEC = {
|
|
3858
4067
|
provider: "fal",
|
|
@@ -4364,6 +4573,10 @@ function createAI(config = {}) {
|
|
|
4364
4573
|
}
|
|
4365
4574
|
});
|
|
4366
4575
|
},
|
|
4576
|
+
async search(req, opts) {
|
|
4577
|
+
const labels = { ...cfg.labels, ...req.labels };
|
|
4578
|
+
return search({ ...req, ...Object.keys(labels).length ? { labels } : {} }, { ...opts, ...costSink ? { costSink } : {} });
|
|
4579
|
+
},
|
|
4367
4580
|
async judge(input) {
|
|
4368
4581
|
input = judgeInputSchema.parse(input);
|
|
4369
4582
|
const stateText = typeof input.state === "string" ? input.state : JSON.stringify(input.state);
|
|
@@ -5102,6 +5315,7 @@ export {
|
|
|
5102
5315
|
CONFIG_DERIVED_HOSTS,
|
|
5103
5316
|
DEFAULT_BASE_URLS,
|
|
5104
5317
|
DEFAULT_CLIP_SEC,
|
|
5318
|
+
DEFAULT_SEARCH_ROUTES,
|
|
5105
5319
|
DEFAULT_TIER_MAP,
|
|
5106
5320
|
ELEVENLABS_DANISH_VOICES,
|
|
5107
5321
|
LOCALLY_PINNED_HOSTS,
|
|
@@ -5109,10 +5323,14 @@ export {
|
|
|
5109
5323
|
ModelUnavailableError,
|
|
5110
5324
|
PRICING_STALE_AFTER_DAYS,
|
|
5111
5325
|
SDK_TAG,
|
|
5326
|
+
SEARCH_CACHE_TTL_MS,
|
|
5327
|
+
SEARCH_PRICE_USD,
|
|
5328
|
+
SearchKeyMissingError,
|
|
5112
5329
|
StreamHttpError,
|
|
5113
5330
|
UpmetricsCostError,
|
|
5114
5331
|
VERSION,
|
|
5115
5332
|
VoiceUnavailableError,
|
|
5333
|
+
ZDR_PROVIDERS,
|
|
5116
5334
|
aiConfigSchema,
|
|
5117
5335
|
alignWordTimings,
|
|
5118
5336
|
anthropicAdapter,
|
|
@@ -5155,6 +5373,7 @@ export {
|
|
|
5155
5373
|
makeContracts,
|
|
5156
5374
|
makeOpenAICompatibleAdapter,
|
|
5157
5375
|
matchLabel,
|
|
5376
|
+
memorySearchCache,
|
|
5158
5377
|
messageSchema,
|
|
5159
5378
|
mistralAdapter,
|
|
5160
5379
|
mistralStubAdapter,
|
|
@@ -5177,6 +5396,8 @@ export {
|
|
|
5177
5396
|
resolveModel,
|
|
5178
5397
|
resolveTier,
|
|
5179
5398
|
resolveVoice,
|
|
5399
|
+
routeSearch,
|
|
5400
|
+
search,
|
|
5180
5401
|
sqliteBudgetStore,
|
|
5181
5402
|
sqliteSink,
|
|
5182
5403
|
streamTransport,
|