llm-sdk-js 0.2.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/adapters/anthropic.d.ts +13 -0
  4. package/dist/adapters/anthropic.d.ts.map +1 -0
  5. package/dist/adapters/groq.d.ts +10 -0
  6. package/dist/adapters/groq.d.ts.map +1 -0
  7. package/dist/adapters/index.d.ts +6 -0
  8. package/dist/adapters/index.d.ts.map +1 -0
  9. package/dist/adapters/ollama.d.ts +11 -0
  10. package/dist/adapters/ollama.d.ts.map +1 -0
  11. package/dist/adapters/openai-compatible.d.ts +16 -0
  12. package/dist/adapters/openai-compatible.d.ts.map +1 -0
  13. package/dist/adapters/openai.d.ts +10 -0
  14. package/dist/adapters/openai.d.ts.map +1 -0
  15. package/dist/adapters/sse.d.ts +7 -0
  16. package/dist/adapters/sse.d.ts.map +1 -0
  17. package/dist/adapters/zod-schema.d.ts +5 -0
  18. package/dist/adapters/zod-schema.d.ts.map +1 -0
  19. package/dist/cache/index.d.ts +6 -0
  20. package/dist/cache/index.d.ts.map +1 -0
  21. package/dist/cache/key.d.ts +8 -0
  22. package/dist/cache/key.d.ts.map +1 -0
  23. package/dist/cache/memory.d.ts +10 -0
  24. package/dist/cache/memory.d.ts.map +1 -0
  25. package/dist/cache/types.d.ts +15 -0
  26. package/dist/cache/types.d.ts.map +1 -0
  27. package/dist/errors.d.ts +33 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/extract.d.ts +5 -0
  30. package/dist/extract.d.ts.map +1 -0
  31. package/dist/index.cjs +1391 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +1381 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy/cache.d.ts +4 -0
  38. package/dist/policy/cache.d.ts.map +1 -0
  39. package/dist/policy/fallback.d.ts +7 -0
  40. package/dist/policy/fallback.d.ts.map +1 -0
  41. package/dist/policy/retry.d.ts +4 -0
  42. package/dist/policy/retry.d.ts.map +1 -0
  43. package/dist/policy/timeout.d.ts +13 -0
  44. package/dist/policy/timeout.d.ts.map +1 -0
  45. package/dist/pricing/index.d.ts +16 -0
  46. package/dist/pricing/index.d.ts.map +1 -0
  47. package/dist/router/create-router.d.ts +4 -0
  48. package/dist/router/create-router.d.ts.map +1 -0
  49. package/dist/router/messages.d.ts +4 -0
  50. package/dist/router/messages.d.ts.map +1 -0
  51. package/dist/router/model-ref.d.ts +5 -0
  52. package/dist/router/model-ref.d.ts.map +1 -0
  53. package/dist/router/options.d.ts +7 -0
  54. package/dist/router/options.d.ts.map +1 -0
  55. package/dist/router/request.d.ts +4 -0
  56. package/dist/router/request.d.ts.map +1 -0
  57. package/dist/router/router-impl.d.ts +16 -0
  58. package/dist/router/router-impl.d.ts.map +1 -0
  59. package/dist/router/state.d.ts +11 -0
  60. package/dist/router/state.d.ts.map +1 -0
  61. package/dist/router/types.d.ts +30 -0
  62. package/dist/router/types.d.ts.map +1 -0
  63. package/dist/router.d.ts +5 -0
  64. package/dist/router.d.ts.map +1 -0
  65. package/dist/types.d.ts +184 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/package.json +78 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1391 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/errors.ts
6
+ var BadRequest = class extends Error {
7
+ constructor(message, options) {
8
+ super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
9
+ this.name = "BadRequest";
10
+ }
11
+ };
12
+ var AllProvidersFailed = class extends Error {
13
+ attempts;
14
+ constructor(attempts, message = "All providers failed") {
15
+ super(message);
16
+ this.name = "AllProvidersFailed";
17
+ this.attempts = attempts;
18
+ }
19
+ };
20
+ var ProviderError = class extends Error {
21
+ kind;
22
+ provider;
23
+ retryable;
24
+ status;
25
+ retryAfterMs;
26
+ constructor(message, options) {
27
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
28
+ this.name = "ProviderError";
29
+ this.kind = options.kind;
30
+ this.provider = options.provider;
31
+ this.retryable = options.retryable ?? (options.kind === "rate_limit" || options.kind === "timeout" || options.kind === "overloaded" || options.kind === "server_error" || options.kind === "network");
32
+ if (options.status !== void 0) this.status = options.status;
33
+ if (options.retryAfterMs !== void 0) this.retryAfterMs = options.retryAfterMs;
34
+ }
35
+ };
36
+ function classify(status) {
37
+ if (status === 429) return "rate_limit";
38
+ if (status === 408 || status === 504) return "timeout";
39
+ if (status === 401 || status === 403) return "auth";
40
+ if (status === 529) return "overloaded";
41
+ if (status === 400 || status === 404 || status === 422) return "bad_request";
42
+ if (status >= 500) return "server_error";
43
+ return "unknown";
44
+ }
45
+ function providerErrorFromHttp(provider, status, body, retryAfterHeader) {
46
+ const kind = classify(status);
47
+ const retryAfterMs = parseRetryAfterHeader(retryAfterHeader);
48
+ return new ProviderError(`Request failed (${status}): ${body}`, {
49
+ kind,
50
+ provider,
51
+ status,
52
+ retryable: kind === "rate_limit" || kind === "timeout" || kind === "server_error" || kind === "overloaded",
53
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {}
54
+ });
55
+ }
56
+ function parseRetryAfterHeader(header) {
57
+ if (!header) return void 0;
58
+ const asInt = Number(header);
59
+ if (!Number.isNaN(asInt)) return asInt * 1e3;
60
+ const date = Date.parse(header);
61
+ if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
62
+ return void 0;
63
+ }
64
+
65
+ // src/router/options.ts
66
+ function assertNoConflictingModelFields(o, where) {
67
+ if (o.model !== void 0 && o.primary !== void 0) {
68
+ throw new BadRequest(
69
+ `Both "model" and "primary" are set in the same ${where} \u2014 "model" always takes precedence, so "primary" would be silently ignored. Set only one.`
70
+ );
71
+ }
72
+ }
73
+ function mergeOptions(global, route, call) {
74
+ const out = {
75
+ ...pickShared(global),
76
+ ...pickShared(route ?? {}),
77
+ ...pickShared(call ?? {})
78
+ };
79
+ const retry = { ...global.retry, ...route?.retry, ...call?.retry };
80
+ if (Object.keys(retry).length > 0) out.retry = retry;
81
+ if (call?.cache !== void 0) out.cache = call.cache;
82
+ else if (route?.cache !== void 0) out.cache = route.cache;
83
+ else if (global.cache !== void 0) out.cache = global.cache;
84
+ const fallbacks = call?.fallbacks ?? route?.fallbacks ?? global.fallbacks;
85
+ if (fallbacks !== void 0) out.fallbacks = fallbacks;
86
+ const raw = { ...global.raw, ...route?.raw, ...call?.raw };
87
+ if (Object.keys(raw).length > 0) out.raw = raw;
88
+ return out;
89
+ }
90
+ function pickShared(o) {
91
+ const out = {};
92
+ if (o.model !== void 0) out.model = o.model;
93
+ if (o.primary !== void 0) out.primary = o.primary;
94
+ if (o.fallbacks !== void 0) out.fallbacks = o.fallbacks;
95
+ if (o.retry !== void 0) out.retry = o.retry;
96
+ if (o.timeout !== void 0) out.timeout = o.timeout;
97
+ if (o.cache !== void 0) out.cache = o.cache;
98
+ if (o.temperature !== void 0) out.temperature = o.temperature;
99
+ if (o.maxTokens !== void 0) out.maxTokens = o.maxTokens;
100
+ if (o.system !== void 0) out.system = o.system;
101
+ if (o.messages !== void 0) out.messages = o.messages;
102
+ if (o.raw !== void 0) out.raw = o.raw;
103
+ if (o.tools !== void 0) out.tools = o.tools;
104
+ if (o.allowContentFilterFailover !== void 0) {
105
+ out.allowContentFilterFailover = o.allowContentFilterFailover;
106
+ }
107
+ return out;
108
+ }
109
+ function resolveModelChain(options) {
110
+ if (options.model) return [options.model, ...options.fallbacks ?? []];
111
+ if (!options.primary) return options.fallbacks ?? [];
112
+ return [options.primary, ...options.fallbacks ?? []];
113
+ }
114
+
115
+ // src/cache/memory.ts
116
+ var DEFAULT_MAX_ENTRIES = 1e3;
117
+ var MemoryCache = class {
118
+ constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
119
+ this.maxEntries = maxEntries;
120
+ }
121
+ maxEntries;
122
+ store = /* @__PURE__ */ new Map();
123
+ get(key) {
124
+ const entry = this.store.get(key);
125
+ if (!entry) return void 0;
126
+ if (Date.now() > entry.expiresAt) {
127
+ this.store.delete(key);
128
+ return void 0;
129
+ }
130
+ this.store.delete(key);
131
+ this.store.set(key, entry);
132
+ return entry.value;
133
+ }
134
+ set(key, value, ttlMs) {
135
+ this.store.delete(key);
136
+ this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
137
+ if (this.store.size > this.maxEntries) {
138
+ const oldest = this.store.keys().next().value;
139
+ if (oldest !== void 0) this.store.delete(oldest);
140
+ }
141
+ }
142
+ };
143
+
144
+ // src/adapters/zod-schema.ts
145
+ function zodInfo(schema) {
146
+ const s = schema;
147
+ if (s?._def && typeof s._def.typeName === "string") {
148
+ return { kind: s._def.typeName.replace(/^Zod/, "").toLowerCase(), def: s._def, legacy: true };
149
+ }
150
+ if (s?.def && typeof s.def.type === "string") {
151
+ return { kind: s.def.type.toLowerCase(), def: s.def, legacy: false };
152
+ }
153
+ return void 0;
154
+ }
155
+ function isZodLike(schema) {
156
+ return zodInfo(schema) !== void 0;
157
+ }
158
+ function withDescription(schema, description) {
159
+ return description ? { ...schema, description } : schema;
160
+ }
161
+ function isOptionalField(field) {
162
+ const info = zodInfo(field);
163
+ return info?.kind === "optional" || info?.kind === "default";
164
+ }
165
+ function zodToJsonSchema(schema) {
166
+ const s = schema;
167
+ const info = zodInfo(schema);
168
+ if (!info) return withDescription({}, s?.description);
169
+ const { kind, def, legacy } = info;
170
+ switch (kind) {
171
+ case "string":
172
+ return withDescription({ type: "string" }, s.description);
173
+ case "number":
174
+ return withDescription({ type: "number" }, s.description);
175
+ case "boolean":
176
+ return withDescription({ type: "boolean" }, s.description);
177
+ case "bigint":
178
+ case "int":
179
+ return withDescription({ type: "integer" }, s.description);
180
+ case "date":
181
+ return withDescription({ type: "string", format: "date-time" }, s.description);
182
+ case "literal": {
183
+ const value = legacy ? def.value : def.values?.[0];
184
+ return withDescription({ const: value }, s.description);
185
+ }
186
+ case "enum": {
187
+ const raw = legacy ? def.values : def.entries ?? def.options;
188
+ const list = Array.isArray(raw) ? raw : Object.values(raw ?? {});
189
+ return withDescription({ enum: list }, s.description);
190
+ }
191
+ case "array": {
192
+ const item = legacy ? def.type : def.element;
193
+ return withDescription({ type: "array", items: zodToJsonSchema(item) }, s.description);
194
+ }
195
+ case "object": {
196
+ const shape = s.shape ?? {};
197
+ const properties = {};
198
+ const required = [];
199
+ for (const [key, field] of Object.entries(shape)) {
200
+ properties[key] = zodToJsonSchema(field);
201
+ if (!isOptionalField(field)) required.push(key);
202
+ }
203
+ return withDescription(
204
+ { type: "object", properties, ...required.length > 0 ? { required } : {} },
205
+ s.description
206
+ );
207
+ }
208
+ case "optional":
209
+ case "nullable":
210
+ case "default":
211
+ case "catch":
212
+ return zodToJsonSchema(def.innerType);
213
+ default:
214
+ return withDescription({}, s.description);
215
+ }
216
+ }
217
+ function toJsonSchema(schema) {
218
+ if (schema === void 0) return { type: "object", properties: {} };
219
+ return isZodLike(schema) ? zodToJsonSchema(schema) : schema;
220
+ }
221
+
222
+ // src/cache/key.ts
223
+ function normalize(value) {
224
+ if (Array.isArray(value)) return value.map(normalize);
225
+ if (value !== null && typeof value === "object") {
226
+ const sorted = {};
227
+ for (const key of Object.keys(value).sort()) {
228
+ sorted[key] = normalize(value[key]);
229
+ }
230
+ return sorted;
231
+ }
232
+ return value;
233
+ }
234
+ function cacheKey(modelChain, messages, opts = {}) {
235
+ const payload = JSON.stringify(
236
+ normalize({
237
+ models: modelChain,
238
+ messages,
239
+ temperature: opts.temperature ?? null,
240
+ maxTokens: opts.maxTokens ?? null,
241
+ tools: opts.tools ? opts.tools.map((t) => ({
242
+ name: t.name,
243
+ description: t.description ?? null,
244
+ schema: toJsonSchema(t.schema)
245
+ })) : null,
246
+ raw: opts.raw ?? null
247
+ })
248
+ );
249
+ return crypto.createHash("sha256").update(payload).digest("hex");
250
+ }
251
+
252
+ // src/cache/index.ts
253
+ function parseTtl(ttl) {
254
+ if (typeof ttl === "number") return ttl;
255
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i.exec(ttl.trim());
256
+ if (!match) {
257
+ throw new Error(`Invalid TTL "${ttl}". Use e.g. "30m", "1h", or milliseconds.`);
258
+ }
259
+ const n = Number(match[1]);
260
+ const unit = match[2].toLowerCase();
261
+ switch (unit) {
262
+ case "ms":
263
+ return n;
264
+ case "s":
265
+ return n * 1e3;
266
+ case "m":
267
+ return n * 6e4;
268
+ case "h":
269
+ return n * 36e5;
270
+ case "d":
271
+ return n * 864e5;
272
+ default:
273
+ return n;
274
+ }
275
+ }
276
+
277
+ // src/extract.ts
278
+ function zodFieldKind(field) {
279
+ const f = field;
280
+ const name = f._def?.typeName ?? f.def?.type;
281
+ return name ? String(name).replace(/^Zod/, "").toLowerCase() : void 0;
282
+ }
283
+ function describeSchema(schema) {
284
+ const s = schema;
285
+ if (!s || typeof s !== "object" || !s.shape || typeof s.shape !== "object") return void 0;
286
+ const fields = Object.entries(s.shape).map(([key, field]) => {
287
+ const kind = zodFieldKind(field);
288
+ return kind ? `${key}: ${kind}` : key;
289
+ });
290
+ return fields.length > 0 ? `{ ${fields.join(", ")} }` : void 0;
291
+ }
292
+ async function extractWithSchema(complete, input, call = {}) {
293
+ const schema = input.schema;
294
+ if (!schema || typeof schema.parse !== "function" && typeof schema.safeParse !== "function") {
295
+ throw new BadRequest("extract() requires a Zod schema (or compatible parse/safeParse)");
296
+ }
297
+ const shape = input.schemaDescription ?? describeSchema(input.schema);
298
+ const instruction = shape ? `Respond with JSON only matching this shape: ${shape}. No markdown.` : "Respond with JSON only that matches the requested schema. No markdown.";
299
+ const basePrompt = [input.prompt, "", instruction].join("\n");
300
+ const maxSchemaRetries = 2;
301
+ let lastErr;
302
+ for (let i = 0; i < maxSchemaRetries; i++) {
303
+ const prompt = i === 0 ? basePrompt : `${basePrompt}
304
+
305
+ Previous JSON was invalid:
306
+ ${String(lastErr)}
307
+
308
+ Fix it.`;
309
+ const completeInput = {
310
+ prompt,
311
+ ...input.system !== void 0 ? { system: input.system } : {}
312
+ };
313
+ const res = await complete(completeInput, {
314
+ ...call,
315
+ temperature: call.temperature ?? 0
316
+ });
317
+ try {
318
+ const jsonText = stripJsonFence(res.text);
319
+ const parsed = JSON.parse(jsonText);
320
+ const data = typeof schema.parse === "function" ? schema.parse(parsed) : (() => {
321
+ const r = schema.safeParse(parsed);
322
+ if (!r.success) throw r.error;
323
+ return r.data;
324
+ })();
325
+ return {
326
+ data,
327
+ text: res.text,
328
+ provider: res.provider,
329
+ model: res.model,
330
+ usage: res.usage,
331
+ cost: res.cost,
332
+ unknownModel: res.unknownModel,
333
+ cached: res.cached,
334
+ latencyMs: res.latencyMs,
335
+ attempts: res.attempts
336
+ };
337
+ } catch (err) {
338
+ lastErr = err;
339
+ }
340
+ }
341
+ throw new BadRequest(`extract() failed to produce valid data: ${String(lastErr)}`);
342
+ }
343
+ function stripJsonFence(text) {
344
+ const trimmed = text.trim();
345
+ const fence = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(trimmed);
346
+ return fence?.[1]?.trim() ?? trimmed;
347
+ }
348
+
349
+ // src/policy/cache.ts
350
+ function cacheAllowed(cache, temperature) {
351
+ if (!cache) return false;
352
+ if (temperature !== void 0 && temperature > 0 && !cache.includeNonDeterministic) return false;
353
+ return true;
354
+ }
355
+
356
+ // src/policy/fallback.ts
357
+ function errorLabel(error) {
358
+ if (error instanceof ProviderError) return error.kind;
359
+ if (error instanceof Error) return error.message;
360
+ return "unknown";
361
+ }
362
+ function reclassifyFatal(err, options, attempts) {
363
+ if (err instanceof BadRequest) throw err;
364
+ if (err instanceof ProviderError && err.kind === "bad_request") {
365
+ throw new BadRequest(err.message, { cause: err });
366
+ }
367
+ if (err instanceof ProviderError && err.kind === "content_filter" && !options.allowContentFilterFailover) {
368
+ throw new AllProvidersFailed(attempts, "Content was filtered by the provider");
369
+ }
370
+ }
371
+ function skipsSameProviderRetry(err) {
372
+ return err instanceof ProviderError && (err.kind === "overloaded" || err.kind === "content_filter" || err.kind === "auth" || err.kind === "unknown");
373
+ }
374
+
375
+ // src/policy/retry.ts
376
+ function computeBackoff(attemptIndex, options = {}, retryAfterMs) {
377
+ if (retryAfterMs !== void 0 && retryAfterMs > 0) {
378
+ return Math.min(options.maxDelay ?? 1e4, retryAfterMs);
379
+ }
380
+ const base = options.baseDelay ?? 500;
381
+ const max = options.maxDelay ?? 1e4;
382
+ const exp = Math.min(max, base * 2 ** attemptIndex);
383
+ return Math.floor(Math.random() * exp);
384
+ }
385
+ function sleep(ms, signal) {
386
+ return new Promise((resolve, reject) => {
387
+ if (signal?.aborted) {
388
+ reject(new ProviderError("Timed out", { kind: "timeout", provider: "router" }));
389
+ return;
390
+ }
391
+ const t = setTimeout(resolve, ms);
392
+ signal?.addEventListener(
393
+ "abort",
394
+ () => {
395
+ clearTimeout(t);
396
+ reject(new ProviderError("Timed out", { kind: "timeout", provider: "router" }));
397
+ },
398
+ { once: true }
399
+ );
400
+ });
401
+ }
402
+
403
+ // src/policy/timeout.ts
404
+ var TimeoutBudget = class {
405
+ deadline;
406
+ constructor(totalMs) {
407
+ this.deadline = Date.now() + totalMs;
408
+ }
409
+ remaining() {
410
+ return Math.max(0, this.deadline - Date.now());
411
+ }
412
+ exhausted() {
413
+ return this.remaining() <= 0;
414
+ }
415
+ /** AbortSignal that fires when the remaining budget elapses. */
416
+ signalForAttempt() {
417
+ const controller = new AbortController();
418
+ const remaining = this.remaining();
419
+ if (remaining <= 0) {
420
+ controller.abort();
421
+ return { signal: controller.signal, clear: () => void 0 };
422
+ }
423
+ const timer = setTimeout(() => controller.abort(), remaining);
424
+ return {
425
+ signal: controller.signal,
426
+ clear: () => clearTimeout(timer)
427
+ };
428
+ }
429
+ };
430
+
431
+ // src/pricing/prices.json
432
+ var prices_default = {
433
+ "gpt-4o": { input: 2.5, output: 10 },
434
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
435
+ "claude-sonnet-4-5": { input: 3, output: 15 },
436
+ "claude-haiku-4-5": { input: 1, output: 5 },
437
+ "llama-3.3-70b": { input: 0.59, output: 0.79 },
438
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 }
439
+ };
440
+
441
+ // src/pricing/index.ts
442
+ var PRICES = prices_default;
443
+ function isKnownModel(model) {
444
+ return Object.hasOwn(PRICES, model);
445
+ }
446
+ var warnedModels = /* @__PURE__ */ new Set();
447
+ function cost(usage, model) {
448
+ const row = PRICES[model];
449
+ if (!row) {
450
+ if (!warnedModels.has(model)) {
451
+ warnedModels.add(model);
452
+ console.warn(
453
+ `[llm-sdk] No pricing data for model "${model}" \u2014 cost will report as 0, which means "unpriced," not "free." Extend PRICES or check CompleteResult.unknownModel.`
454
+ );
455
+ }
456
+ return 0;
457
+ }
458
+ return usage.input / 1e6 * row.input + usage.output / 1e6 * row.output;
459
+ }
460
+ function estimateCost(model, usage) {
461
+ return cost(usage, model);
462
+ }
463
+
464
+ // src/router/messages.ts
465
+ function toMessages(input, options) {
466
+ const messages = [];
467
+ const system = typeof input === "object" ? input.system ?? options.system : options.system;
468
+ if (system) messages.push({ role: "system", content: system });
469
+ if (typeof input === "string") {
470
+ messages.push({ role: "user", content: input });
471
+ return messages;
472
+ }
473
+ if (input.messages?.length) messages.push(...input.messages);
474
+ else if (input.prompt) messages.push({ role: "user", content: input.prompt });
475
+ if (messages.filter((m) => m.role !== "system").length === 0) {
476
+ throw new BadRequest("complete() requires a string prompt or messages");
477
+ }
478
+ return messages;
479
+ }
480
+
481
+ // src/router/model-ref.ts
482
+ function parseModelRef(ref) {
483
+ const slash = ref.indexOf("/");
484
+ if (slash <= 0 || slash === ref.length - 1) {
485
+ throw new BadRequest(`Invalid model ref "${ref}". Expected "provider/model".`);
486
+ }
487
+ return {
488
+ provider: ref.slice(0, slash),
489
+ model: ref.slice(slash + 1),
490
+ ref
491
+ };
492
+ }
493
+
494
+ // src/router/request.ts
495
+ function buildAdapterRequest(parsed, prepared, signal) {
496
+ return {
497
+ model: parsed.model,
498
+ messages: prepared.messages,
499
+ ...prepared.temperature !== void 0 ? { temperature: prepared.temperature } : {},
500
+ ...prepared.maxTokens !== void 0 ? { maxTokens: prepared.maxTokens } : {},
501
+ ...prepared.tools !== void 0 ? { tools: prepared.tools } : {},
502
+ ...prepared.options.raw?.[parsed.provider] ? { raw: prepared.options.raw[parsed.provider] } : {},
503
+ signal
504
+ };
505
+ }
506
+
507
+ // src/router/router-impl.ts
508
+ var STREAM_BUFFER_CHARS = 40;
509
+ function replayFromCache(hit) {
510
+ async function* run() {
511
+ if (hit.text) yield { text: hit.text };
512
+ yield { text: "", done: true };
513
+ }
514
+ const iterator = run();
515
+ const result = { ...hit, cached: true, latencyMs: 0, attempts: [] };
516
+ return {
517
+ [Symbol.asyncIterator]: () => iterator,
518
+ async result() {
519
+ for await (const _ of iterator) {
520
+ }
521
+ return result;
522
+ }
523
+ };
524
+ }
525
+ var RouterImpl = class _RouterImpl {
526
+ constructor(state, routeName) {
527
+ this.state = state;
528
+ this.routeName = routeName;
529
+ }
530
+ state;
531
+ routeName;
532
+ get config() {
533
+ return this.state.config;
534
+ }
535
+ route(name) {
536
+ if (!this.config.routes?.[name]) {
537
+ throw new BadRequest(`Unknown route "${name}"`);
538
+ }
539
+ return new _RouterImpl(this.state, name);
540
+ }
541
+ prepareCall(input, call) {
542
+ assertNoConflictingModelFields(call, "call options");
543
+ const routeConfig = this.activeRoute();
544
+ const options = mergeOptions(this.config, routeConfig, call);
545
+ const modelChain = resolveModelChain(options);
546
+ if (modelChain.length === 0) {
547
+ throw new BadRequest("No primary model configured");
548
+ }
549
+ const messages = toMessages(input, options);
550
+ const temperature = call.temperature ?? (typeof input === "object" ? input.temperature : void 0) ?? options.temperature;
551
+ const maxTokens = call.maxTokens ?? (typeof input === "object" ? input.maxTokens : void 0) ?? options.maxTokens;
552
+ const tools = call.tools ?? (typeof input === "object" ? input.tools : void 0) ?? options.tools;
553
+ return {
554
+ options,
555
+ modelChain,
556
+ messages,
557
+ ...temperature !== void 0 ? { temperature } : {},
558
+ ...maxTokens !== void 0 ? { maxTokens } : {},
559
+ ...tools !== void 0 ? { tools } : {}
560
+ };
561
+ }
562
+ async complete(input, call = {}) {
563
+ const prepared = this.prepareCall(input, call);
564
+ const { options, modelChain, messages, temperature, maxTokens, tools } = prepared;
565
+ const keyOpts = {
566
+ ...temperature !== void 0 ? { temperature } : {},
567
+ ...maxTokens !== void 0 ? { maxTokens } : {},
568
+ ...tools !== void 0 ? { tools } : {},
569
+ ...options.raw !== void 0 ? { raw: options.raw } : {}
570
+ };
571
+ const cacheEnabled = cacheAllowed(options.cache, temperature);
572
+ if (cacheEnabled) {
573
+ const hit = this.state.cache.get(cacheKey(modelChain, messages, keyOpts));
574
+ if (hit) {
575
+ return { ...hit, cached: true, latencyMs: 0, attempts: [] };
576
+ }
577
+ }
578
+ const budget = new TimeoutBudget(options.timeout ?? 6e4);
579
+ const attempts = [];
580
+ const retries = options.retry?.attempts ?? 1;
581
+ for (let i = 0; i < modelChain.length; i++) {
582
+ const ref = modelChain[i];
583
+ const parsed = parseModelRef(ref);
584
+ const adapter = this.state.getAdapter(parsed.provider);
585
+ for (let retry = 0; retry < retries; retry++) {
586
+ if (budget.exhausted()) {
587
+ attempts.push({
588
+ provider: parsed.provider,
589
+ model: parsed.model,
590
+ error: "timeout",
591
+ ms: 0
592
+ });
593
+ break;
594
+ }
595
+ const started = Date.now();
596
+ const { signal, clear } = budget.signalForAttempt();
597
+ try {
598
+ const response = await adapter.complete(buildAdapterRequest(parsed, prepared, signal));
599
+ clear();
600
+ const ms = Date.now() - started;
601
+ attempts.push({ provider: parsed.provider, model: parsed.model, ms });
602
+ const estimated = cost(response.usage, parsed.model);
603
+ const result = {
604
+ text: response.text,
605
+ provider: parsed.provider,
606
+ model: parsed.model,
607
+ usage: response.usage,
608
+ cost: estimated,
609
+ unknownModel: !isKnownModel(parsed.model),
610
+ cached: false,
611
+ latencyMs: ms,
612
+ attempts,
613
+ toolCalls: response.toolCalls
614
+ };
615
+ if (cacheEnabled && options.cache) {
616
+ const entry = {
617
+ text: result.text,
618
+ provider: result.provider,
619
+ model: result.model,
620
+ usage: result.usage,
621
+ cost: result.cost,
622
+ unknownModel: result.unknownModel,
623
+ toolCalls: result.toolCalls
624
+ };
625
+ this.state.cache.set(
626
+ cacheKey(modelChain, messages, keyOpts),
627
+ entry,
628
+ parseTtl(options.cache.ttl ?? "1h")
629
+ );
630
+ }
631
+ return result;
632
+ } catch (err) {
633
+ clear();
634
+ const ms = Date.now() - started;
635
+ attempts.push({
636
+ provider: parsed.provider,
637
+ model: parsed.model,
638
+ error: errorLabel(err),
639
+ ms
640
+ });
641
+ reclassifyFatal(err, options, attempts);
642
+ const nextRef = modelChain[i + 1];
643
+ const isLastRetry = skipsSameProviderRetry(err) || retry >= retries - 1;
644
+ if (isLastRetry) {
645
+ if (nextRef) this.config.onFallback?.(ref, nextRef, err);
646
+ break;
647
+ }
648
+ const retryAfterMs = err instanceof ProviderError ? err.retryAfterMs : void 0;
649
+ const delay = computeBackoff(retry, options.retry ?? {}, retryAfterMs);
650
+ const wait = Math.min(delay, budget.remaining());
651
+ if (wait > 0) {
652
+ await sleep(wait, AbortSignal.timeout(budget.remaining()));
653
+ }
654
+ }
655
+ }
656
+ }
657
+ throw new AllProvidersFailed(attempts);
658
+ }
659
+ extract(input, call = {}) {
660
+ return extractWithSchema((i, o) => this.complete(i, o), input, call);
661
+ }
662
+ // No retry.attempts loop here, unlike complete() — once a chunk has been yielded to the
663
+ // caller, retrying the same provider would mean sending conflicting text for the same
664
+ // span, so a mid-stream failure fails over (pre-flush) or ends the stream (post-flush)
665
+ // rather than retrying in place. See "Streaming vs complete()" in the fallback guide.
666
+ stream(input, call = {}) {
667
+ const prepared = this.prepareCall(input, call);
668
+ const { options, modelChain, messages, temperature, maxTokens, tools } = prepared;
669
+ const keyOpts = {
670
+ ...temperature !== void 0 ? { temperature } : {},
671
+ ...maxTokens !== void 0 ? { maxTokens } : {},
672
+ ...tools !== void 0 ? { tools } : {},
673
+ ...options.raw !== void 0 ? { raw: options.raw } : {}
674
+ };
675
+ const cacheEnabled = cacheAllowed(options.cache, temperature);
676
+ if (cacheEnabled) {
677
+ const hit = this.state.cache.get(cacheKey(modelChain, messages, keyOpts));
678
+ if (hit) return replayFromCache(hit);
679
+ }
680
+ const budget = new TimeoutBudget(options.timeout ?? 6e4);
681
+ const attempts = [];
682
+ let finalResult;
683
+ let finalError;
684
+ const config = this.config;
685
+ const state = this.state;
686
+ async function* run() {
687
+ try {
688
+ for (let i = 0; i < modelChain.length; i++) {
689
+ const ref = modelChain[i];
690
+ const parsed = parseModelRef(ref);
691
+ const adapter = state.getAdapter(parsed.provider);
692
+ const started = Date.now();
693
+ const { signal, clear } = budget.signalForAttempt();
694
+ let fullText = "";
695
+ let buffer = "";
696
+ let flushed = false;
697
+ let usage = { input: 0, output: 0 };
698
+ let toolCalls = [];
699
+ try {
700
+ for await (const evt of adapter.stream(buildAdapterRequest(parsed, prepared, signal))) {
701
+ if (evt.type === "delta") {
702
+ fullText += evt.text;
703
+ if (!flushed) {
704
+ buffer += evt.text;
705
+ if (buffer.length >= STREAM_BUFFER_CHARS) {
706
+ flushed = true;
707
+ yield { text: buffer };
708
+ buffer = "";
709
+ }
710
+ } else {
711
+ yield { text: evt.text };
712
+ }
713
+ } else {
714
+ usage = evt.usage;
715
+ toolCalls = evt.toolCalls;
716
+ }
717
+ }
718
+ clear();
719
+ const ms = Date.now() - started;
720
+ attempts.push({ provider: parsed.provider, model: parsed.model, ms });
721
+ if (buffer.length > 0) yield { text: buffer };
722
+ yield { text: "", done: true };
723
+ const estimated = cost(usage, parsed.model);
724
+ const unknownModel = !isKnownModel(parsed.model);
725
+ if (cacheEnabled && options.cache) {
726
+ const entry = {
727
+ text: fullText,
728
+ provider: parsed.provider,
729
+ model: parsed.model,
730
+ usage,
731
+ cost: estimated,
732
+ unknownModel,
733
+ toolCalls
734
+ };
735
+ state.cache.set(cacheKey(modelChain, messages, keyOpts), entry, parseTtl(options.cache.ttl ?? "1h"));
736
+ }
737
+ finalResult = {
738
+ text: fullText,
739
+ provider: parsed.provider,
740
+ model: parsed.model,
741
+ usage,
742
+ cost: estimated,
743
+ unknownModel,
744
+ cached: false,
745
+ latencyMs: ms,
746
+ attempts,
747
+ toolCalls
748
+ };
749
+ return;
750
+ } catch (err) {
751
+ clear();
752
+ const ms = Date.now() - started;
753
+ attempts.push({ provider: parsed.provider, model: parsed.model, error: errorLabel(err), ms });
754
+ reclassifyFatal(err, options, attempts);
755
+ if (flushed) {
756
+ throw err;
757
+ }
758
+ const nextRef = modelChain[i + 1];
759
+ if (nextRef) {
760
+ config.onFallback?.(ref, nextRef, err);
761
+ continue;
762
+ }
763
+ throw new AllProvidersFailed(attempts);
764
+ }
765
+ }
766
+ throw new AllProvidersFailed(attempts);
767
+ } catch (err) {
768
+ finalError = err;
769
+ throw err;
770
+ }
771
+ }
772
+ const iterator = run();
773
+ return {
774
+ [Symbol.asyncIterator]: () => iterator,
775
+ async result() {
776
+ for await (const _ of iterator) {
777
+ }
778
+ if (finalResult) return finalResult;
779
+ throw finalError ?? new Error("stream ended without a result");
780
+ }
781
+ };
782
+ }
783
+ activeRoute() {
784
+ if (this.routeName) return this.config.routes?.[this.routeName];
785
+ if (this.config.default && this.config.routes?.[this.config.default]) {
786
+ return this.config.routes[this.config.default];
787
+ }
788
+ return void 0;
789
+ }
790
+ };
791
+
792
+ // src/adapters/sse.ts
793
+ async function* readSseEvents(body) {
794
+ const reader = body.getReader();
795
+ const decoder = new TextDecoder();
796
+ let buffer = "";
797
+ try {
798
+ while (true) {
799
+ const { done, value } = await reader.read();
800
+ if (done) break;
801
+ buffer += decoder.decode(value, { stream: true });
802
+ let sep;
803
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
804
+ const rawEvent = buffer.slice(0, sep);
805
+ buffer = buffer.slice(sep + 2);
806
+ yield parseFrame(rawEvent);
807
+ }
808
+ }
809
+ if (buffer.trim().length > 0) yield parseFrame(buffer);
810
+ } finally {
811
+ reader.releaseLock();
812
+ }
813
+ }
814
+ function parseFrame(raw) {
815
+ let event;
816
+ const dataLines = [];
817
+ for (const line of raw.split("\n")) {
818
+ if (line.startsWith("event:")) event = line.slice(6).trim();
819
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
820
+ }
821
+ return { ...event !== void 0 ? { event } : {}, data: dataLines.join("\n") };
822
+ }
823
+
824
+ // src/adapters/openai-compatible.ts
825
+ function envKey(name) {
826
+ return typeof process !== "undefined" ? process.env[name] : void 0;
827
+ }
828
+ async function postJson(adapter, path, body, signal) {
829
+ const url = `${adapter.baseUrl.replace(/\/$/, "")}${path}`;
830
+ let response;
831
+ try {
832
+ response = await fetch(url, {
833
+ method: "POST",
834
+ headers: {
835
+ Authorization: `Bearer ${adapter.apiKey}`,
836
+ "Content-Type": "application/json"
837
+ },
838
+ body: JSON.stringify(body),
839
+ ...signal ? { signal } : {}
840
+ });
841
+ } catch (err) {
842
+ if (err instanceof Error && err.name === "AbortError") {
843
+ throw new ProviderError("Timed out", { kind: "timeout", provider: adapter.name, cause: err });
844
+ }
845
+ throw new ProviderError("Network error", {
846
+ kind: "network",
847
+ provider: adapter.name,
848
+ cause: err
849
+ });
850
+ }
851
+ if (!response.ok) {
852
+ const responseBody = await response.text().catch(() => "");
853
+ throw providerErrorFromHttp(
854
+ adapter.name,
855
+ response.status,
856
+ responseBody,
857
+ response.headers.get("retry-after")
858
+ );
859
+ }
860
+ return response;
861
+ }
862
+ function toOpenAiMessages(messages) {
863
+ return messages.map((m) => {
864
+ if (m.role === "assistant" && m.toolCalls?.length) {
865
+ return {
866
+ role: "assistant",
867
+ content: m.content || null,
868
+ tool_calls: m.toolCalls.map((c) => ({
869
+ id: c.id,
870
+ type: "function",
871
+ function: { name: c.name, arguments: JSON.stringify(c.args) }
872
+ }))
873
+ };
874
+ }
875
+ if (m.role === "tool") {
876
+ return { role: "tool", tool_call_id: m.toolCallId, content: m.content };
877
+ }
878
+ return { role: m.role, content: m.content, ...m.name ? { name: m.name } : {} };
879
+ });
880
+ }
881
+ function toOpenAiTools(request) {
882
+ if (!request.tools?.length) return void 0;
883
+ return request.tools.map((t) => ({
884
+ type: "function",
885
+ function: {
886
+ name: t.name,
887
+ description: t.description,
888
+ parameters: toJsonSchema(t.schema)
889
+ }
890
+ }));
891
+ }
892
+ function parseOpenAiToolCalls(raw) {
893
+ if (!raw?.length) return [];
894
+ return raw.map((c, i) => {
895
+ const name = c.function?.name ?? "";
896
+ const argsText = c.function?.arguments ?? "{}";
897
+ let args;
898
+ try {
899
+ args = JSON.parse(argsText);
900
+ } catch {
901
+ args = argsText;
902
+ }
903
+ return { id: c.id ?? `call_${i}`, name, args };
904
+ });
905
+ }
906
+ async function openAiCompatibleComplete(adapter, request) {
907
+ const response = await postJson(
908
+ adapter,
909
+ "/chat/completions",
910
+ {
911
+ model: request.model,
912
+ messages: toOpenAiMessages(request.messages),
913
+ temperature: request.temperature,
914
+ max_tokens: request.maxTokens,
915
+ tools: toOpenAiTools(request),
916
+ ...request.raw
917
+ },
918
+ request.signal
919
+ );
920
+ const data = await response.json();
921
+ const choice = data.choices?.[0];
922
+ if (choice?.finish_reason === "content_filter") {
923
+ throw new ProviderError("Content was filtered by the provider", {
924
+ kind: "content_filter",
925
+ provider: adapter.name,
926
+ retryable: false
927
+ });
928
+ }
929
+ return {
930
+ text: choice?.message?.content ?? "",
931
+ usage: {
932
+ input: data.usage?.prompt_tokens ?? 0,
933
+ output: data.usage?.completion_tokens ?? 0
934
+ },
935
+ toolCalls: parseOpenAiToolCalls(choice?.message?.tool_calls),
936
+ ...choice?.finish_reason ? { finishReason: choice.finish_reason } : {}
937
+ };
938
+ }
939
+ function accumulateToolCallDelta(acc, deltas) {
940
+ for (const d of deltas) {
941
+ const index = d.index ?? 0;
942
+ const entry = acc.get(index) ?? { args: "" };
943
+ if (d.id) entry.id = d.id;
944
+ if (d.function?.name) entry.name = d.function.name;
945
+ if (d.function?.arguments) entry.args += d.function.arguments;
946
+ acc.set(index, entry);
947
+ }
948
+ }
949
+ function finalizeToolCalls(acc) {
950
+ return [...acc.entries()].sort(([a], [b]) => a - b).map(([, c], i) => {
951
+ let args;
952
+ try {
953
+ args = c.args ? JSON.parse(c.args) : {};
954
+ } catch {
955
+ args = c.args;
956
+ }
957
+ return { id: c.id ?? `call_${i}`, name: c.name ?? "", args };
958
+ });
959
+ }
960
+ async function* openAiCompatibleStream(adapter, request) {
961
+ const response = await postJson(
962
+ adapter,
963
+ "/chat/completions",
964
+ {
965
+ model: request.model,
966
+ messages: toOpenAiMessages(request.messages),
967
+ temperature: request.temperature,
968
+ max_tokens: request.maxTokens,
969
+ tools: toOpenAiTools(request),
970
+ stream: true,
971
+ stream_options: { include_usage: true },
972
+ ...request.raw
973
+ },
974
+ request.signal
975
+ );
976
+ if (!response.body) {
977
+ throw new ProviderError("Empty stream body", { kind: "network", provider: adapter.name });
978
+ }
979
+ let usage = { input: 0, output: 0 };
980
+ let finishReason;
981
+ const toolCallAcc = /* @__PURE__ */ new Map();
982
+ try {
983
+ for await (const evt of readSseEvents(response.body)) {
984
+ if (!evt.data || evt.data === "[DONE]") continue;
985
+ let parsed;
986
+ try {
987
+ parsed = JSON.parse(evt.data);
988
+ } catch {
989
+ continue;
990
+ }
991
+ const choice = parsed.choices?.[0];
992
+ if (typeof choice?.delta?.content === "string" && choice.delta.content.length > 0) {
993
+ yield { type: "delta", text: choice.delta.content };
994
+ }
995
+ if (choice?.delta?.tool_calls?.length) {
996
+ accumulateToolCallDelta(toolCallAcc, choice.delta.tool_calls);
997
+ }
998
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
999
+ if (parsed.usage) {
1000
+ usage = {
1001
+ input: parsed.usage.prompt_tokens ?? 0,
1002
+ output: parsed.usage.completion_tokens ?? 0
1003
+ };
1004
+ }
1005
+ }
1006
+ } catch (err) {
1007
+ if (err instanceof ProviderError) throw err;
1008
+ if (err instanceof Error && err.name === "AbortError") {
1009
+ throw new ProviderError("Timed out", { kind: "timeout", provider: adapter.name, cause: err });
1010
+ }
1011
+ throw new ProviderError("Network error", { kind: "network", provider: adapter.name, cause: err });
1012
+ }
1013
+ if (finishReason === "content_filter") {
1014
+ throw new ProviderError("Content was filtered by the provider", {
1015
+ kind: "content_filter",
1016
+ provider: adapter.name,
1017
+ retryable: false
1018
+ });
1019
+ }
1020
+ yield {
1021
+ type: "done",
1022
+ usage,
1023
+ toolCalls: finalizeToolCalls(toolCallAcc),
1024
+ ...finishReason ? { finishReason } : {}
1025
+ };
1026
+ }
1027
+ function requireApiKey(provider, envName, explicit) {
1028
+ const key = explicit ?? envKey(envName);
1029
+ if (!key) {
1030
+ throw new ProviderError(`Missing API key (${envName})`, {
1031
+ kind: "auth",
1032
+ provider,
1033
+ retryable: false
1034
+ });
1035
+ }
1036
+ return key;
1037
+ }
1038
+
1039
+ // src/adapters/anthropic.ts
1040
+ function toAnthropicTools(request) {
1041
+ if (!request.tools?.length) return void 0;
1042
+ return request.tools.map((t) => ({
1043
+ name: t.name,
1044
+ description: t.description,
1045
+ input_schema: toJsonSchema(t.schema)
1046
+ }));
1047
+ }
1048
+ function parseAnthropicToolCalls(content) {
1049
+ if (!content?.length) return [];
1050
+ return content.filter((c) => c.type === "tool_use").map((c, i) => ({ id: c.id ?? `call_${i}`, name: c.name ?? "", args: c.input }));
1051
+ }
1052
+ function isToolResultMessage(m) {
1053
+ return Array.isArray(m.content) && m.content.every((b) => b.type === "tool_result");
1054
+ }
1055
+ function toAnthropicMessages(messages) {
1056
+ const system = messages.filter((m) => m.role === "system").map((m) => m.content).join("\n");
1057
+ const rest = [];
1058
+ for (const m of messages) {
1059
+ if (m.role === "system") continue;
1060
+ if (m.role === "assistant" && m.toolCalls?.length) {
1061
+ const blocks = [];
1062
+ if (m.content) blocks.push({ type: "text", text: m.content });
1063
+ for (const call of m.toolCalls) {
1064
+ blocks.push({ type: "tool_use", id: call.id, name: call.name, input: call.args });
1065
+ }
1066
+ rest.push({ role: "assistant", content: blocks });
1067
+ continue;
1068
+ }
1069
+ if (m.role === "tool") {
1070
+ const block = {
1071
+ type: "tool_result",
1072
+ tool_use_id: m.toolCallId ?? "",
1073
+ content: m.content
1074
+ };
1075
+ const prev = rest.at(-1);
1076
+ if (prev?.role === "user" && isToolResultMessage(prev)) {
1077
+ prev.content.push(block);
1078
+ } else {
1079
+ rest.push({ role: "user", content: [block] });
1080
+ }
1081
+ continue;
1082
+ }
1083
+ rest.push({ role: m.role === "assistant" ? "assistant" : "user", content: m.content });
1084
+ }
1085
+ return { system, messages: rest };
1086
+ }
1087
+ function classifyAnthropicErrorType(type) {
1088
+ switch (type) {
1089
+ case "rate_limit_error":
1090
+ return "rate_limit";
1091
+ case "overloaded_error":
1092
+ return "overloaded";
1093
+ case "authentication_error":
1094
+ case "permission_error":
1095
+ return "auth";
1096
+ case "invalid_request_error":
1097
+ case "not_found_error":
1098
+ return "bad_request";
1099
+ case "timeout_error":
1100
+ return "timeout";
1101
+ case "api_error":
1102
+ return "server_error";
1103
+ default:
1104
+ return "unknown";
1105
+ }
1106
+ }
1107
+ var AnthropicAdapter = class {
1108
+ constructor(ctx = {}) {
1109
+ this.ctx = ctx;
1110
+ }
1111
+ ctx;
1112
+ name = "anthropic";
1113
+ headers() {
1114
+ return {
1115
+ "x-api-key": requireApiKey(this.name, "ANTHROPIC_API_KEY", this.ctx.apiKey),
1116
+ "anthropic-version": "2023-06-01",
1117
+ "Content-Type": "application/json"
1118
+ };
1119
+ }
1120
+ url() {
1121
+ return `${(this.ctx.baseUrl ?? "https://api.anthropic.com").replace(/\/$/, "")}/v1/messages`;
1122
+ }
1123
+ async post(request, extra = {}) {
1124
+ const { system, messages } = toAnthropicMessages(request.messages);
1125
+ const headers = this.headers();
1126
+ let response;
1127
+ try {
1128
+ response = await fetch(this.url(), {
1129
+ method: "POST",
1130
+ headers,
1131
+ body: JSON.stringify({
1132
+ model: request.model,
1133
+ max_tokens: request.maxTokens ?? 1024,
1134
+ temperature: request.temperature,
1135
+ system: system || void 0,
1136
+ messages,
1137
+ tools: toAnthropicTools(request),
1138
+ ...extra,
1139
+ ...request.raw
1140
+ }),
1141
+ ...request.signal ? { signal: request.signal } : {}
1142
+ });
1143
+ } catch (err) {
1144
+ if (err instanceof Error && err.name === "AbortError") {
1145
+ throw new ProviderError("Timed out", { kind: "timeout", provider: this.name, cause: err });
1146
+ }
1147
+ throw new ProviderError("Network error", { kind: "network", provider: this.name, cause: err });
1148
+ }
1149
+ if (!response.ok) {
1150
+ const body = await response.text().catch(() => "");
1151
+ throw providerErrorFromHttp(
1152
+ this.name,
1153
+ response.status,
1154
+ body,
1155
+ response.headers.get("retry-after")
1156
+ );
1157
+ }
1158
+ return response;
1159
+ }
1160
+ async complete(request) {
1161
+ const response = await this.post(request);
1162
+ const data = await response.json();
1163
+ if (data.stop_reason === "refusal") {
1164
+ throw new ProviderError("Content was filtered by the provider", {
1165
+ kind: "content_filter",
1166
+ provider: this.name,
1167
+ retryable: false
1168
+ });
1169
+ }
1170
+ const text = data.content?.filter((c) => c.type === "text").map((c) => c.text ?? "").join("") ?? "";
1171
+ return {
1172
+ text,
1173
+ usage: {
1174
+ input: data.usage?.input_tokens ?? 0,
1175
+ output: data.usage?.output_tokens ?? 0
1176
+ },
1177
+ toolCalls: parseAnthropicToolCalls(data.content),
1178
+ ...data.stop_reason ? { finishReason: data.stop_reason } : {}
1179
+ };
1180
+ }
1181
+ /** Streaming accumulates `tool_use` blocks from `input_json_delta` events; surfaced on `done`. */
1182
+ async *stream(request) {
1183
+ const response = await this.post(request, { stream: true });
1184
+ if (!response.body) {
1185
+ throw new ProviderError("Empty stream body", { kind: "network", provider: this.name });
1186
+ }
1187
+ let usage = { input: 0, output: 0 };
1188
+ let stopReason;
1189
+ const toolBlocks = /* @__PURE__ */ new Map();
1190
+ try {
1191
+ for await (const evt of readSseEvents(response.body)) {
1192
+ if (!evt.data) continue;
1193
+ let parsed;
1194
+ try {
1195
+ parsed = JSON.parse(evt.data);
1196
+ } catch {
1197
+ continue;
1198
+ }
1199
+ if (evt.event === "message_start") {
1200
+ usage.input = parsed.message?.usage?.input_tokens ?? 0;
1201
+ } else if (evt.event === "content_block_start" && parsed.content_block?.type === "tool_use") {
1202
+ toolBlocks.set(parsed.index ?? 0, {
1203
+ ...parsed.content_block.id ? { id: parsed.content_block.id } : {},
1204
+ ...parsed.content_block.name ? { name: parsed.content_block.name } : {},
1205
+ json: ""
1206
+ });
1207
+ } else if (evt.event === "content_block_delta" && parsed.delta?.type === "text_delta") {
1208
+ if (parsed.delta.text) yield { type: "delta", text: parsed.delta.text };
1209
+ } else if (evt.event === "content_block_delta" && parsed.delta?.type === "input_json_delta") {
1210
+ const block = toolBlocks.get(parsed.index ?? 0);
1211
+ if (block && parsed.delta.partial_json) block.json += parsed.delta.partial_json;
1212
+ } else if (evt.event === "message_delta") {
1213
+ if (parsed.usage?.output_tokens !== void 0) usage.output = parsed.usage.output_tokens;
1214
+ if (parsed.delta?.stop_reason) stopReason = parsed.delta.stop_reason;
1215
+ } else if (evt.event === "error") {
1216
+ throw new ProviderError(parsed.error?.message ?? "Anthropic stream error", {
1217
+ kind: classifyAnthropicErrorType(parsed.error?.type),
1218
+ provider: this.name
1219
+ });
1220
+ }
1221
+ }
1222
+ } catch (err) {
1223
+ if (err instanceof ProviderError) throw err;
1224
+ if (err instanceof Error && err.name === "AbortError") {
1225
+ throw new ProviderError("Timed out", { kind: "timeout", provider: this.name, cause: err });
1226
+ }
1227
+ throw new ProviderError("Network error", { kind: "network", provider: this.name, cause: err });
1228
+ }
1229
+ if (stopReason === "refusal") {
1230
+ throw new ProviderError("Content was filtered by the provider", {
1231
+ kind: "content_filter",
1232
+ provider: this.name,
1233
+ retryable: false
1234
+ });
1235
+ }
1236
+ const toolCalls = [...toolBlocks.entries()].sort(([a], [b]) => a - b).map(([, b], i) => {
1237
+ let args;
1238
+ try {
1239
+ args = b.json ? JSON.parse(b.json) : {};
1240
+ } catch {
1241
+ args = b.json;
1242
+ }
1243
+ return { id: b.id ?? `call_${i}`, name: b.name ?? "", args };
1244
+ });
1245
+ yield { type: "done", usage, toolCalls, ...stopReason ? { finishReason: stopReason } : {} };
1246
+ }
1247
+ };
1248
+
1249
+ // src/adapters/groq.ts
1250
+ var GroqAdapter = class {
1251
+ constructor(ctx = {}) {
1252
+ this.ctx = ctx;
1253
+ }
1254
+ ctx;
1255
+ name = "groq";
1256
+ target() {
1257
+ return {
1258
+ name: this.name,
1259
+ baseUrl: this.ctx.baseUrl ?? "https://api.groq.com/openai/v1",
1260
+ apiKey: requireApiKey(this.name, "GROQ_API_KEY", this.ctx.apiKey)
1261
+ };
1262
+ }
1263
+ async complete(request) {
1264
+ return openAiCompatibleComplete(this.target(), request);
1265
+ }
1266
+ stream(request) {
1267
+ return openAiCompatibleStream(this.target(), request);
1268
+ }
1269
+ };
1270
+
1271
+ // src/adapters/ollama.ts
1272
+ var OllamaAdapter = class {
1273
+ constructor(ctx = {}, name = "ollama") {
1274
+ this.ctx = ctx;
1275
+ this.name = name;
1276
+ }
1277
+ ctx;
1278
+ name;
1279
+ target() {
1280
+ return {
1281
+ name: this.name,
1282
+ baseUrl: this.ctx.baseUrl ?? "http://127.0.0.1:11434/v1",
1283
+ apiKey: this.ctx.apiKey ?? envKey("LOCAL_API_KEY") ?? envKey("OLLAMA_API_KEY") ?? "ollama"
1284
+ };
1285
+ }
1286
+ async complete(request) {
1287
+ return openAiCompatibleComplete(this.target(), request);
1288
+ }
1289
+ stream(request) {
1290
+ return openAiCompatibleStream(this.target(), request);
1291
+ }
1292
+ };
1293
+
1294
+ // src/adapters/openai.ts
1295
+ var OpenAIAdapter = class {
1296
+ constructor(ctx = {}) {
1297
+ this.ctx = ctx;
1298
+ }
1299
+ ctx;
1300
+ name = "openai";
1301
+ target() {
1302
+ return {
1303
+ name: this.name,
1304
+ baseUrl: this.ctx.baseUrl ?? "https://api.openai.com/v1",
1305
+ apiKey: requireApiKey(this.name, "OPENAI_API_KEY", this.ctx.apiKey)
1306
+ };
1307
+ }
1308
+ async complete(request) {
1309
+ return openAiCompatibleComplete(this.target(), request);
1310
+ }
1311
+ stream(request) {
1312
+ return openAiCompatibleStream(this.target(), request);
1313
+ }
1314
+ };
1315
+
1316
+ // src/adapters/index.ts
1317
+ function createAdapter(provider, ctx = {}) {
1318
+ const override = ctx.providers?.[provider];
1319
+ const adapterCtx = {
1320
+ ...override?.apiKey !== void 0 ? { apiKey: override.apiKey } : {},
1321
+ ...override?.baseUrl !== void 0 ? { baseUrl: override.baseUrl } : {},
1322
+ ...override?.timeoutMs !== void 0 ? { timeoutMs: override.timeoutMs } : {},
1323
+ ...ctx.apiKey !== void 0 ? { apiKey: ctx.apiKey } : {},
1324
+ ...ctx.baseUrl !== void 0 ? { baseUrl: ctx.baseUrl } : {}
1325
+ };
1326
+ switch (provider) {
1327
+ case "openai":
1328
+ return new OpenAIAdapter(adapterCtx);
1329
+ case "anthropic":
1330
+ return new AnthropicAdapter(adapterCtx);
1331
+ case "groq":
1332
+ return new GroqAdapter(adapterCtx);
1333
+ case "ollama":
1334
+ return new OllamaAdapter(adapterCtx, "ollama");
1335
+ case "local":
1336
+ return new OllamaAdapter(adapterCtx, "local");
1337
+ default:
1338
+ throw new Error(
1339
+ `Unknown provider "${provider}". Register a custom Adapter for it via createRouter({ adapters: { "${provider}": ... } }).`
1340
+ );
1341
+ }
1342
+ }
1343
+
1344
+ // src/router/state.ts
1345
+ var RouterState = class {
1346
+ constructor(config) {
1347
+ this.config = config;
1348
+ }
1349
+ config;
1350
+ cache = new MemoryCache();
1351
+ adapters = /* @__PURE__ */ new Map();
1352
+ getAdapter(provider) {
1353
+ let adapter = this.adapters.get(provider);
1354
+ if (!adapter) {
1355
+ adapter = this.config.adapters?.[provider] ?? createAdapter(provider, {
1356
+ ...this.config.providers !== void 0 ? { providers: this.config.providers } : {}
1357
+ });
1358
+ this.adapters.set(provider, adapter);
1359
+ }
1360
+ return adapter;
1361
+ }
1362
+ };
1363
+
1364
+ // src/router/create-router.ts
1365
+ function createRouter(config) {
1366
+ if (!config.primary && !config.routes) {
1367
+ throw new BadRequest("createRouter() requires primary or routes");
1368
+ }
1369
+ assertNoConflictingModelFields(config, "createRouter() config");
1370
+ for (const [name, route] of Object.entries(config.routes ?? {})) {
1371
+ assertNoConflictingModelFields(route, `route "${name}"`);
1372
+ }
1373
+ const state = new RouterState(config);
1374
+ const router = new RouterImpl(state);
1375
+ if (config.default && config.routes) {
1376
+ return router.route(config.default);
1377
+ }
1378
+ return router;
1379
+ }
1380
+
1381
+ exports.AllProvidersFailed = AllProvidersFailed;
1382
+ exports.BadRequest = BadRequest;
1383
+ exports.PRICES = PRICES;
1384
+ exports.PRICING = PRICES;
1385
+ exports.ProviderError = ProviderError;
1386
+ exports.cost = cost;
1387
+ exports.createRouter = createRouter;
1388
+ exports.estimateCost = estimateCost;
1389
+ exports.isKnownModel = isKnownModel;
1390
+ //# sourceMappingURL=index.cjs.map
1391
+ //# sourceMappingURL=index.cjs.map