@absolutejs/ai 0.0.49 → 0.0.51

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 (39) hide show
  1. package/README.md +127 -0
  2. package/dist/ai/client/index.js +30 -1
  3. package/dist/ai/client/index.js.map +5 -5
  4. package/dist/ai/index.js +1174 -92
  5. package/dist/ai/index.js.map +18 -16
  6. package/dist/ai/providers/anthropic.js +105 -26
  7. package/dist/ai/providers/anthropic.js.map +5 -5
  8. package/dist/ai/providers/gemini.js +37 -4
  9. package/dist/ai/providers/gemini.js.map +5 -5
  10. package/dist/ai/providers/ollama.js +7 -3
  11. package/dist/ai/providers/ollama.js.map +4 -4
  12. package/dist/ai/providers/openai.js +177 -36
  13. package/dist/ai/providers/openai.js.map +5 -5
  14. package/dist/ai/providers/openaiCompatible.js +177 -36
  15. package/dist/ai/providers/openaiCompatible.js.map +5 -5
  16. package/dist/ai/providers/openaiResponses.js +147 -33
  17. package/dist/ai/providers/openaiResponses.js.map +5 -5
  18. package/dist/ai/providers/openrouter.js +2607 -0
  19. package/dist/ai/providers/openrouter.js.map +18 -0
  20. package/dist/angular/ai/index.js +30 -1
  21. package/dist/angular/ai/index.js.map +5 -5
  22. package/dist/react/ai/index.js +30 -1
  23. package/dist/react/ai/index.js.map +5 -5
  24. package/dist/src/ai/client/actions.d.ts +53 -0
  25. package/dist/src/ai/errors/providerError.d.ts +3 -0
  26. package/dist/src/ai/generateAI.d.ts +4 -1
  27. package/dist/src/ai/index.d.ts +2 -0
  28. package/dist/src/ai/providers/openai.d.ts +7 -3
  29. package/dist/src/ai/providers/openaiResponses.d.ts +9 -4
  30. package/dist/src/ai/providers/openrouter.d.ts +157 -0
  31. package/dist/src/ai/providers/openrouterClient.d.ts +276 -0
  32. package/dist/src/ai/streamAIWithTools.d.ts +6 -0
  33. package/dist/svelte/ai/index.js +30 -1
  34. package/dist/svelte/ai/index.js.map +5 -5
  35. package/dist/types/ai.d.ts +116 -8
  36. package/dist/types/anthropic.d.ts +13 -5
  37. package/dist/vue/ai/index.js +30 -1
  38. package/dist/vue/ai/index.js.map +5 -5
  39. package/package.json +9 -2
@@ -0,0 +1,2607 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/ai/errors/providerError.ts
5
+ var PROVIDER_STATUS_PAGES = {
6
+ anthropic: "https://status.claude.com",
7
+ gemini: "https://status.cloud.google.com",
8
+ google: "https://status.cloud.google.com",
9
+ openai: "https://status.openai.com",
10
+ openrouter: "https://status.openrouter.ai"
11
+ };
12
+ var RETRYABLE_STATUSES = new Set([
13
+ 408,
14
+ 409,
15
+ 425,
16
+ 429,
17
+ 500,
18
+ 502,
19
+ 503,
20
+ 504,
21
+ 529
22
+ ]);
23
+ var CONNECTION_ERROR_PATTERNS = [
24
+ "econnreset",
25
+ "econnrefused",
26
+ "etimedout",
27
+ "enotfound",
28
+ "eai_again",
29
+ "socket hang up",
30
+ "fetch failed",
31
+ "network",
32
+ "terminated",
33
+ "timed out",
34
+ "timeout",
35
+ "and not retryable",
36
+ "no response body"
37
+ ];
38
+ var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
39
+ var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
40
+
41
+ class ProviderError extends Error {
42
+ provider;
43
+ status;
44
+ type;
45
+ retryable;
46
+ metadata;
47
+ statusPageUrl;
48
+ constructor(init) {
49
+ super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
50
+ this.name = "ProviderError";
51
+ this.provider = init.provider;
52
+ this.status = init.status ?? null;
53
+ this.type = init.type ?? null;
54
+ this.retryable = init.retryable;
55
+ this.metadata = init.metadata;
56
+ this.statusPageUrl = providerStatusPage(init.provider);
57
+ }
58
+ static fromResponse(provider, status, body, type) {
59
+ return new ProviderError({
60
+ message: `${capitalize(provider)} API error ${status}: ${body}`,
61
+ provider,
62
+ retryable: RETRYABLE_STATUSES.has(status),
63
+ status,
64
+ type: type ?? null
65
+ });
66
+ }
67
+ static from(err, provider) {
68
+ if (err instanceof ProviderError)
69
+ return err;
70
+ if (isAbortError(err))
71
+ throw err;
72
+ const rawMessage = err instanceof Error ? err.message : String(err ?? "");
73
+ const lower = rawMessage.toLowerCase();
74
+ const statusMatch = lower.match(/api error\s+(\d{3})/);
75
+ const status = statusMatch ? Number(statusMatch[1]) : null;
76
+ const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
77
+ return new ProviderError({
78
+ cause: err,
79
+ message: rawMessage || `${capitalize(provider)} request failed`,
80
+ provider,
81
+ retryable,
82
+ status
83
+ });
84
+ }
85
+ }
86
+ var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
87
+
88
+ // src/ai/resilience.ts
89
+ var DEFAULT_CONFIG = {
90
+ baseDelayMs: 500,
91
+ failureThreshold: 4,
92
+ maxDelayMs: 8000,
93
+ maxRetries: 2,
94
+ openMs: 30000
95
+ };
96
+ var config = { ...DEFAULT_CONFIG };
97
+ var configureProviderResilience = (partial) => {
98
+ config = { ...config, ...partial };
99
+ };
100
+ var health = new Map;
101
+ var recordFor = (provider) => {
102
+ const existing = health.get(provider);
103
+ if (existing)
104
+ return existing;
105
+ const fresh = {
106
+ consecutiveFailures: 0,
107
+ external: null,
108
+ lastError: null,
109
+ lastFailureAt: null,
110
+ lastSuccessAt: null,
111
+ nextProbeAt: null,
112
+ state: "closed"
113
+ };
114
+ health.set(provider, fresh);
115
+ return fresh;
116
+ };
117
+ var setProviderAvailability = (provider, status) => {
118
+ const record = recordFor(provider);
119
+ record.external = {
120
+ available: status.available,
121
+ checkedAt: Date.now(),
122
+ indicator: status.indicator ?? (status.available ? "operational" : "unknown"),
123
+ reason: status.reason ?? ""
124
+ };
125
+ };
126
+ var noteSuccess = (provider) => {
127
+ const record = recordFor(provider);
128
+ record.state = "closed";
129
+ record.consecutiveFailures = 0;
130
+ record.nextProbeAt = null;
131
+ record.lastError = null;
132
+ record.lastSuccessAt = Date.now();
133
+ };
134
+ var noteFailure = (provider, error) => {
135
+ const record = recordFor(provider);
136
+ record.consecutiveFailures += 1;
137
+ record.lastFailureAt = Date.now();
138
+ record.lastError = {
139
+ message: error.message,
140
+ status: error.status,
141
+ type: error.type
142
+ };
143
+ if (record.consecutiveFailures >= config.failureThreshold) {
144
+ record.state = "open";
145
+ record.nextProbeAt = Date.now() + config.openMs;
146
+ }
147
+ };
148
+ var snapshot = (provider, record) => ({
149
+ consecutiveFailures: record.consecutiveFailures,
150
+ external: record.external,
151
+ healthy: record.state === "closed" && record.external?.available !== false,
152
+ lastError: record.lastError,
153
+ lastFailureAt: record.lastFailureAt,
154
+ lastSuccessAt: record.lastSuccessAt,
155
+ nextRetryAt: record.nextProbeAt,
156
+ provider,
157
+ state: record.state,
158
+ statusPageUrl: providerStatusPage(provider)
159
+ });
160
+ function getProviderHealth(provider) {
161
+ if (provider !== undefined)
162
+ return snapshot(provider, recordFor(provider));
163
+ return [...health.entries()].map(([name, record]) => snapshot(name, record));
164
+ }
165
+ var backoffDelay = (attempt) => {
166
+ const exponential = config.baseDelayMs * 2 ** attempt;
167
+ const capped = Math.min(exponential, config.maxDelayMs);
168
+ return Math.round(capped / 2 + Math.random() * capped / 2);
169
+ };
170
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
171
+ if (signal?.aborted) {
172
+ reject(signal.reason ?? new Error("aborted"));
173
+ return;
174
+ }
175
+ const timer = setTimeout(() => {
176
+ signal?.removeEventListener("abort", onAbort);
177
+ resolve();
178
+ }, ms);
179
+ const onAbort = () => {
180
+ clearTimeout(timer);
181
+ reject(signal?.reason ?? new Error("aborted"));
182
+ };
183
+ signal?.addEventListener("abort", onAbort, { once: true });
184
+ });
185
+ var circuitOpen = (provider) => {
186
+ const record = recordFor(provider);
187
+ if (record.external && !record.external.available) {
188
+ return new ProviderError({
189
+ message: `${provider} API is reported down${record.external.reason ? `: ${record.external.reason}` : ""}`,
190
+ provider,
191
+ retryable: true,
192
+ status: null,
193
+ type: record.external.indicator
194
+ });
195
+ }
196
+ if (record.state !== "open")
197
+ return null;
198
+ if (record.nextProbeAt !== null && Date.now() >= record.nextProbeAt) {
199
+ record.state = "half-open";
200
+ return null;
201
+ }
202
+ const detail = record.lastError?.message ?? "recent failures";
203
+ return new ProviderError({
204
+ message: `${provider} is temporarily unavailable (circuit open after ${record.consecutiveFailures} failures): ${detail}`,
205
+ provider,
206
+ retryable: true,
207
+ status: record.lastError?.status ?? null,
208
+ type: record.lastError?.type ?? null
209
+ });
210
+ };
211
+ var withResilience = (provider, providerName = "unknown") => {
212
+ const attempt = async function* (params, attemptNo) {
213
+ let yielded = false;
214
+ try {
215
+ for await (const chunk of provider.stream(params)) {
216
+ yielded = true;
217
+ yield chunk;
218
+ }
219
+ noteSuccess(providerName);
220
+ } catch (err) {
221
+ const providerError = ProviderError.from(err, providerName);
222
+ const canRetry = !yielded && providerError.retryable && attemptNo < config.maxRetries && !params.signal?.aborted;
223
+ if (canRetry) {
224
+ await sleep(backoffDelay(attemptNo), params.signal);
225
+ yield* attempt(params, attemptNo + 1);
226
+ return;
227
+ }
228
+ if (providerError.retryable)
229
+ noteFailure(providerName, providerError);
230
+ throw providerError;
231
+ }
232
+ };
233
+ return {
234
+ stream: (params) => {
235
+ const tripped = circuitOpen(providerName);
236
+ if (tripped) {
237
+ return async function* () {
238
+ throw tripped;
239
+ }();
240
+ }
241
+ return attempt(params, 0);
242
+ }
243
+ };
244
+ };
245
+
246
+ // src/ai/providers/instrumentation.ts
247
+ var instrumentAIProvider = (provider, providerName) => {
248
+ const resilient = withResilience(provider, providerName);
249
+ return {
250
+ stream: (params) => {
251
+ if (!params.onUsage && !params.onSpan) {
252
+ return resilient.stream(params);
253
+ }
254
+ return tapStream(resilient.stream(params), params, providerName);
255
+ }
256
+ };
257
+ };
258
+ async function* tapStream(source, params, providerName) {
259
+ const startedAt = Date.now();
260
+ let lastUsage;
261
+ try {
262
+ for await (const chunk of source) {
263
+ if (chunk.type === "done" && chunk.usage) {
264
+ lastUsage = chunk.usage;
265
+ }
266
+ yield chunk;
267
+ }
268
+ } finally {
269
+ if (lastUsage && params.onUsage) {
270
+ try {
271
+ params.onUsage({
272
+ ...lastUsage,
273
+ model: params.model,
274
+ provider: providerName
275
+ });
276
+ } catch {}
277
+ }
278
+ if (params.onSpan) {
279
+ try {
280
+ params.onSpan({
281
+ durationMs: Date.now() - startedAt,
282
+ model: params.model,
283
+ provider: providerName,
284
+ usage: lastUsage
285
+ });
286
+ } catch {}
287
+ }
288
+ }
289
+ }
290
+
291
+ // src/ai/providers/reasoning.ts
292
+ var EFFORT_ORDER = [
293
+ "minimal",
294
+ "low",
295
+ "medium",
296
+ "high",
297
+ "max"
298
+ ];
299
+ var ANTHROPIC_EFFORT = [/opus-4-[5-8]/, /sonnet-4-6/, /fable-5/, /mythos-5/];
300
+ var ANTHROPIC_ADAPTIVE_ONLY = [];
301
+ var ANTHROPIC_LEGACY_THINKING = [
302
+ /sonnet-4-5/,
303
+ /sonnet-4-0/,
304
+ /sonnet-4-2025/,
305
+ /opus-4-0/,
306
+ /opus-4-1/,
307
+ /opus-4-2025/,
308
+ /3-7-sonnet/
309
+ ];
310
+ var ANTHROPIC_NO_SAMPLING = [/opus-4-[78]/, /fable-5/, /mythos-5/];
311
+ var ANTHROPIC_MAX_EFFORT = [
312
+ /opus-4-[678]/,
313
+ /sonnet-4-6/,
314
+ /fable-5/,
315
+ /mythos-5/
316
+ ];
317
+ var OPENAI_REASONING = [/(^|[^a-z])o[1345](-|$)/, /gpt-5/];
318
+ var OPENAI_MINIMAL_EFFORT = [/gpt-5/];
319
+ var matches = (model, patterns) => patterns.some((pattern) => pattern.test(model));
320
+ var anthropicReasoningMode = (model) => {
321
+ if (matches(model, ANTHROPIC_EFFORT))
322
+ return "effort";
323
+ if (matches(model, ANTHROPIC_ADAPTIVE_ONLY))
324
+ return "adaptive";
325
+ if (matches(model, ANTHROPIC_LEGACY_THINKING))
326
+ return "legacy";
327
+ return "none";
328
+ };
329
+ var anthropicSupportsSampling = (model) => !matches(model, ANTHROPIC_NO_SAMPLING);
330
+ var isOpenAIReasoningModel = (model) => matches(model, OPENAI_REASONING);
331
+ var EFFORT_BUDGET = {
332
+ high: 16384,
333
+ low: 2048,
334
+ max: 32768,
335
+ medium: 8192,
336
+ minimal: 1024
337
+ };
338
+ var budgetToEffort = (budget) => {
339
+ if (budget <= 2048)
340
+ return "low";
341
+ if (budget <= 8192)
342
+ return "medium";
343
+ if (budget <= 16384)
344
+ return "high";
345
+ return "max";
346
+ };
347
+ var resolveEffort = (reasoning) => {
348
+ if (reasoning.effort)
349
+ return reasoning.effort;
350
+ if (typeof reasoning.budgetTokens === "number") {
351
+ return budgetToEffort(reasoning.budgetTokens);
352
+ }
353
+ return null;
354
+ };
355
+ var resolveBudgetTokens = (reasoning) => {
356
+ if (typeof reasoning.budgetTokens === "number")
357
+ return reasoning.budgetTokens;
358
+ if (reasoning.effort)
359
+ return EFFORT_BUDGET[reasoning.effort];
360
+ return null;
361
+ };
362
+ var clampEffort = (effort, allowed) => {
363
+ if (allowed.includes(effort))
364
+ return effort;
365
+ const idx = EFFORT_ORDER.indexOf(effort);
366
+ for (let lower = idx - 1;lower >= 0; lower -= 1) {
367
+ const candidate = EFFORT_ORDER[lower];
368
+ if (candidate && allowed.includes(candidate))
369
+ return candidate;
370
+ }
371
+ return allowed[0] ?? effort;
372
+ };
373
+ var anthropicEffortValue = (model, reasoning) => {
374
+ const effort = resolveEffort(reasoning);
375
+ if (!effort)
376
+ return null;
377
+ const allowed = matches(model, ANTHROPIC_MAX_EFFORT) ? ["low", "medium", "high", "max"] : ["low", "medium", "high"];
378
+ return clampEffort(effort, allowed);
379
+ };
380
+ var openaiEffortValue = (model, reasoning) => {
381
+ if (!isOpenAIReasoningModel(model))
382
+ return null;
383
+ const effort = resolveEffort(reasoning);
384
+ if (!effort)
385
+ return null;
386
+ const allowed = matches(model, OPENAI_MINIMAL_EFFORT) ? ["minimal", "low", "medium", "high"] : ["low", "medium", "high"];
387
+ const requested = effort === "max" ? "high" : effort;
388
+ return clampEffort(requested, allowed);
389
+ };
390
+
391
+ // src/ai/providers/openai.ts
392
+ var h2IfHttps = (url) => url.startsWith("https://") ? { protocol: "http2" } : {};
393
+ var DEFAULT_BASE_URL = "https://api.openai.com";
394
+ var SSE_DATA_PREFIX_LENGTH = 6;
395
+ var DONE_SENTINEL = "[DONE]";
396
+ var NOT_FOUND = -1;
397
+ var isRecord = (value) => typeof value === "object" && value !== null;
398
+ var isRecordArray = (value) => Array.isArray(value) && value.length > 0 && isRecord(value[0]);
399
+ var hasArrayContent = (msg) => typeof msg.content !== "string" && Array.isArray(msg.content);
400
+ var buildToolMessages = (blocks) => {
401
+ const toolUseBlocks = blocks.filter((block) => block.type === "tool_use");
402
+ const toolResultBlocks = blocks.filter((block) => block.type === "tool_result");
403
+ const messages = [];
404
+ if (toolUseBlocks.length > 0) {
405
+ messages.push({
406
+ content: null,
407
+ role: "assistant",
408
+ tool_calls: toolUseBlocks.map((block) => ({
409
+ function: {
410
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
411
+ name: block.name
412
+ },
413
+ id: block.id,
414
+ type: "function"
415
+ }))
416
+ });
417
+ }
418
+ for (const result of toolResultBlocks) {
419
+ messages.push({
420
+ content: typeof result.content === "string" ? result.content : "",
421
+ role: "tool",
422
+ tool_call_id: result.tool_use_id
423
+ });
424
+ }
425
+ return messages;
426
+ };
427
+ var processMessageAtIndex = (result, msg, idx) => {
428
+ if (!hasArrayContent(msg)) {
429
+ return;
430
+ }
431
+ const hasToolBlocks = msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result");
432
+ if (!hasToolBlocks) {
433
+ return;
434
+ }
435
+ const toolMessages = buildToolMessages(msg.content);
436
+ result.splice(idx, 1, ...toolMessages);
437
+ };
438
+ var convertSingleMessage = (result, msg, idx) => {
439
+ if (!msg) {
440
+ return;
441
+ }
442
+ processMessageAtIndex(result, msg, idx);
443
+ };
444
+ var convertToolResultMessages = (messages, params) => {
445
+ const result = [...messages];
446
+ for (let idx = 0;idx < params.messages.length; idx++) {
447
+ convertSingleMessage(result, params.messages[idx], idx);
448
+ }
449
+ return result;
450
+ };
451
+ var mapToolDefinitions = (tools) => tools.map((tool) => ({
452
+ function: {
453
+ description: tool.description,
454
+ name: tool.name,
455
+ parameters: tool.input_schema
456
+ },
457
+ type: "function"
458
+ }));
459
+ var mapContentBlockToOpenAI = (block) => {
460
+ if (block.type === "image") {
461
+ return {
462
+ image_url: {
463
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
464
+ },
465
+ type: "image_url"
466
+ };
467
+ }
468
+ if (block.type === "document") {
469
+ return {
470
+ file: {
471
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
472
+ filename: block.name ?? "document.pdf"
473
+ },
474
+ type: "file"
475
+ };
476
+ }
477
+ if (block.type === "audio") {
478
+ return {
479
+ input_audio: { data: block.source.data, format: block.source.format },
480
+ type: "input_audio"
481
+ };
482
+ }
483
+ if (block.type === "video") {
484
+ return {
485
+ type: "video_url",
486
+ video_url: {
487
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
488
+ }
489
+ };
490
+ }
491
+ if (block.type === "text") {
492
+ return { text: block.content, type: "text" };
493
+ }
494
+ return null;
495
+ };
496
+ var mapOpenAIContent = (msg) => {
497
+ if (typeof msg.content === "string") {
498
+ return msg.content;
499
+ }
500
+ const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document" || block.type === "audio" || block.type === "video");
501
+ if (!hasMedia) {
502
+ return null;
503
+ }
504
+ return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
505
+ };
506
+ var buildRequestBody = (params, capabilityModel = params.model) => {
507
+ const messages = convertToolResultMessages(params.messages.map((msg) => ({
508
+ content: mapOpenAIContent(msg),
509
+ role: msg.role
510
+ })), params);
511
+ if (params.systemPrompt) {
512
+ messages.unshift({ content: params.systemPrompt, role: "system" });
513
+ }
514
+ const body = {
515
+ messages,
516
+ model: params.model,
517
+ stream: true,
518
+ stream_options: { include_usage: true }
519
+ };
520
+ if (params.tools && params.tools.length > 0) {
521
+ body.tools = mapToolDefinitions(params.tools);
522
+ if (params.toolChoice === "auto" || params.toolChoice === "none" || params.toolChoice === "required") {
523
+ body.tool_choice = params.toolChoice;
524
+ } else if (params.toolChoice && typeof params.toolChoice === "object") {
525
+ body.tool_choice = {
526
+ function: { name: params.toolChoice.name },
527
+ type: "function"
528
+ };
529
+ }
530
+ if (typeof params.parallelToolCalls === "boolean") {
531
+ body.parallel_tool_calls = params.parallelToolCalls;
532
+ }
533
+ }
534
+ if (isOpenAIReasoningModel(capabilityModel)) {
535
+ if (typeof params.maxTokens === "number") {
536
+ body.max_completion_tokens = params.maxTokens;
537
+ }
538
+ if (params.reasoning) {
539
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
540
+ if (effort)
541
+ body.reasoning_effort = effort;
542
+ }
543
+ } else {
544
+ if (typeof params.temperature === "number") {
545
+ body.temperature = params.temperature;
546
+ }
547
+ if (typeof params.topP === "number")
548
+ body.top_p = params.topP;
549
+ if (typeof params.maxTokens === "number")
550
+ body.max_tokens = params.maxTokens;
551
+ }
552
+ if (params.stopSequences && params.stopSequences.length > 0)
553
+ body.stop = params.stopSequences;
554
+ if (typeof params.seed === "number")
555
+ body.seed = params.seed;
556
+ if (typeof params.frequencyPenalty === "number")
557
+ body.frequency_penalty = params.frequencyPenalty;
558
+ if (typeof params.presencePenalty === "number")
559
+ body.presence_penalty = params.presencePenalty;
560
+ if (params.responseFormat) {
561
+ if (params.responseFormat.type === "text" || params.responseFormat.type === "json_object") {
562
+ body.response_format = { type: params.responseFormat.type };
563
+ } else if (params.responseFormat.type === "json_schema") {
564
+ body.response_format = {
565
+ json_schema: {
566
+ name: params.responseFormat.name,
567
+ schema: params.responseFormat.schema,
568
+ strict: params.responseFormat.strict ?? true
569
+ },
570
+ type: "json_schema"
571
+ };
572
+ }
573
+ }
574
+ return body;
575
+ };
576
+ var parseToolInput = (rawArguments) => {
577
+ try {
578
+ return JSON.parse(rawArguments);
579
+ } catch {
580
+ return rawArguments;
581
+ }
582
+ };
583
+ var flushPendingToolCalls = function* (pendingToolCalls) {
584
+ for (const [, tool] of pendingToolCalls) {
585
+ const input = parseToolInput(tool.arguments);
586
+ yield {
587
+ id: tool.id,
588
+ input,
589
+ name: tool.name,
590
+ type: "tool_use"
591
+ };
592
+ }
593
+ pendingToolCalls.clear();
594
+ };
595
+ var extractUsage = (parsedUsage) => {
596
+ const prompt = parsedUsage.prompt_tokens ?? 0;
597
+ const cached = parsedUsage.cached_tokens ?? 0;
598
+ return {
599
+ cacheReadInputTokens: cached,
600
+ cacheWriteInputTokens: parsedUsage.cache_write_tokens || undefined,
601
+ costCredits: parsedUsage.cost,
602
+ inputTokens: Math.max(0, prompt - cached),
603
+ outputTokens: parsedUsage.completion_tokens ?? 0,
604
+ reasoningTokens: parsedUsage.reasoning_tokens || undefined,
605
+ upstreamInferenceCostCredits: parsedUsage.upstream_inference_cost || undefined
606
+ };
607
+ };
608
+ var resolveToolCallIndex = (toolCall) => {
609
+ const raw = typeof toolCall.index === "number" ? toolCall.index : NOT_FOUND;
610
+ return raw < 0 ? undefined : raw;
611
+ };
612
+ var initPendingToolCall = (toolCall, func, index, pendingToolCalls) => {
613
+ if (pendingToolCalls.has(index)) {
614
+ return;
615
+ }
616
+ const toolId = typeof toolCall.id === "string" ? toolCall.id : "";
617
+ const toolName = func && typeof func.name === "string" ? func.name : "";
618
+ pendingToolCalls.set(index, {
619
+ arguments: "",
620
+ id: toolId,
621
+ name: toolName
622
+ });
623
+ };
624
+ var updatePendingToolCall = (toolCall, func, pending) => {
625
+ if (typeof toolCall.id === "string") {
626
+ pending.id = toolCall.id;
627
+ }
628
+ if (func && typeof func.name === "string") {
629
+ pending.name = func.name;
630
+ }
631
+ if (func && typeof func.arguments === "string") {
632
+ pending.arguments += func.arguments;
633
+ }
634
+ };
635
+ var processToolCallDelta = (toolCall, pendingToolCalls) => {
636
+ const index = resolveToolCallIndex(toolCall);
637
+ if (index === undefined) {
638
+ return;
639
+ }
640
+ const func = isRecord(toolCall.function) ? toolCall.function : null;
641
+ initPendingToolCall(toolCall, func, index, pendingToolCalls);
642
+ const pending = pendingToolCalls.get(index);
643
+ if (!pending) {
644
+ return;
645
+ }
646
+ updatePendingToolCall(toolCall, func, pending);
647
+ };
648
+ var processToolCallDeltas = (toolCalls, pendingToolCalls) => {
649
+ for (const toolCall of toolCalls) {
650
+ processToolCallDelta(toolCall, pendingToolCalls);
651
+ }
652
+ };
653
+ var processDelta = function* (delta, pendingToolCalls) {
654
+ if (typeof delta.content === "string") {
655
+ yield { content: delta.content, type: "text" };
656
+ }
657
+ if (isRecord(delta.audio)) {
658
+ const audio = delta.audio;
659
+ if (typeof audio.data === "string" || typeof audio.transcript === "string") {
660
+ yield {
661
+ audioId: typeof audio.id === "string" ? audio.id : undefined,
662
+ data: typeof audio.data === "string" ? audio.data : "",
663
+ format: typeof audio.format === "string" ? audio.format : "pcm16",
664
+ transcript: typeof audio.transcript === "string" ? audio.transcript : undefined,
665
+ type: "audio"
666
+ };
667
+ }
668
+ }
669
+ if (isRecordArray(delta.tool_calls)) {
670
+ processToolCallDeltas(delta.tool_calls, pendingToolCalls);
671
+ }
672
+ if (Array.isArray(delta.annotations)) {
673
+ for (const annotation of delta.annotations) {
674
+ if (!isRecord(annotation) || annotation.type !== "url_citation")
675
+ continue;
676
+ const citation = isRecord(annotation.url_citation) ? annotation.url_citation : annotation;
677
+ if (typeof citation.url !== "string")
678
+ continue;
679
+ yield {
680
+ content: typeof citation.content === "string" ? citation.content : undefined,
681
+ endIndex: typeof citation.end_index === "number" ? citation.end_index : undefined,
682
+ startIndex: typeof citation.start_index === "number" ? citation.start_index : undefined,
683
+ title: typeof citation.title === "string" ? citation.title : undefined,
684
+ type: "citation",
685
+ url: citation.url
686
+ };
687
+ }
688
+ }
689
+ };
690
+ var narrowResponseMetadata = (parsed) => {
691
+ const providerMetadata = isRecord(parsed.openrouter_metadata) ? parsed.openrouter_metadata : undefined;
692
+ const generationId = typeof parsed.id === "string" ? parsed.id : undefined;
693
+ const model = typeof parsed.model === "string" ? parsed.model : undefined;
694
+ const serviceTier = typeof parsed.service_tier === "string" ? parsed.service_tier : undefined;
695
+ const selected = providerMetadata && isRecord(providerMetadata.endpoints) ? providerMetadata.endpoints.available : undefined;
696
+ const selectedEndpoint = Array.isArray(selected) ? selected.find((entry) => isRecord(entry) && entry.selected === true) : undefined;
697
+ const provider = isRecord(selectedEndpoint) && typeof selectedEndpoint.provider === "string" ? selectedEndpoint.provider : undefined;
698
+ if (!providerMetadata && !generationId && !model && !serviceTier)
699
+ return;
700
+ return { generationId, model, provider, providerMetadata, serviceTier };
701
+ };
702
+ var processChoice = function* (choice, pendingToolCalls) {
703
+ const delta = isRecord(choice.delta) ? choice.delta : null;
704
+ if (delta) {
705
+ yield* processDelta(delta, pendingToolCalls);
706
+ }
707
+ if (choice.finish_reason === "tool_calls") {
708
+ yield* flushPendingToolCalls(pendingToolCalls);
709
+ }
710
+ };
711
+ var narrowUsageRecord = (parsed) => {
712
+ if (!isRecord(parsed.usage)) {
713
+ return;
714
+ }
715
+ const { usage } = parsed;
716
+ const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
717
+ const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
718
+ const cachedTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cached_tokens === "number" ? usage.prompt_tokens_details.cached_tokens : 0;
719
+ const cacheWriteTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cache_write_tokens === "number" ? usage.prompt_tokens_details.cache_write_tokens : 0;
720
+ const reasoningTokens = isRecord(usage.completion_tokens_details) && typeof usage.completion_tokens_details.reasoning_tokens === "number" ? usage.completion_tokens_details.reasoning_tokens : 0;
721
+ const cost = typeof usage.cost === "number" ? usage.cost : undefined;
722
+ const upstreamInferenceCost = isRecord(usage.cost_details) && typeof usage.cost_details.upstream_inference_cost === "number" ? usage.cost_details.upstream_inference_cost : undefined;
723
+ const normalized = extractUsage({
724
+ cache_write_tokens: cacheWriteTokens,
725
+ cached_tokens: cachedTokens,
726
+ completion_tokens: completionTokens,
727
+ cost,
728
+ prompt_tokens: promptTokens,
729
+ reasoning_tokens: reasoningTokens,
730
+ upstream_inference_cost: upstreamInferenceCost
731
+ });
732
+ if (isRecord(usage.server_tool_use)) {
733
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
734
+ }
735
+ return normalized;
736
+ };
737
+ var processSSELine = function* (line, pendingToolCalls, providerName) {
738
+ const trimmed = line.trim();
739
+ if (!trimmed || !trimmed.startsWith("data: ")) {
740
+ return;
741
+ }
742
+ const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
743
+ if (data === DONE_SENTINEL) {
744
+ yield* flushPendingToolCalls(pendingToolCalls);
745
+ return;
746
+ }
747
+ let parsed;
748
+ try {
749
+ parsed = JSON.parse(data);
750
+ } catch {
751
+ return;
752
+ }
753
+ if (isRecord(parsed.error)) {
754
+ const error = parsed.error;
755
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
756
+ const status = typeof error.code === "number" ? error.code : null;
757
+ const type = metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : typeof error.error_type === "string" ? error.error_type : null;
758
+ throw new ProviderError({
759
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
760
+ metadata: metadata2,
761
+ provider: providerName,
762
+ retryable: status === 408 || status === 409 || status === 425 || status === 429 || status !== null && status >= 500,
763
+ status,
764
+ type
765
+ });
766
+ }
767
+ const usageUpdate = narrowUsageRecord(parsed);
768
+ if (usageUpdate) {
769
+ yield { type: "usage_update", usage: usageUpdate };
770
+ }
771
+ const metadata = narrowResponseMetadata(parsed);
772
+ if (metadata) {
773
+ yield { metadata, type: "response_metadata" };
774
+ }
775
+ const { choices } = parsed;
776
+ if (!isRecordArray(choices)) {
777
+ return;
778
+ }
779
+ const [firstChoice] = choices;
780
+ if (!firstChoice) {
781
+ return;
782
+ }
783
+ if (isRecord(firstChoice.error)) {
784
+ const error = firstChoice.error;
785
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
786
+ const status = typeof error.code === "number" ? error.code : null;
787
+ throw new ProviderError({
788
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
789
+ metadata: metadata2,
790
+ provider: providerName,
791
+ retryable: status === 429 || status !== null && status >= 500,
792
+ status,
793
+ type: metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : null
794
+ });
795
+ }
796
+ yield* processChoice(firstChoice, pendingToolCalls);
797
+ };
798
+ var isUsageUpdate = (chunk) => chunk.type === "usage_update";
799
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef, metadataRef, providerName) => {
800
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls, providerName));
801
+ const usageChunks = allChunks.filter(isUsageUpdate);
802
+ const lastUsage = usageChunks.at(NOT_FOUND);
803
+ if (lastUsage) {
804
+ usageRef.current = lastUsage.usage;
805
+ }
806
+ const metadataChunks = allChunks.filter((chunk) => chunk.type === "response_metadata");
807
+ const lastMetadata = metadataChunks.at(NOT_FOUND);
808
+ if (lastMetadata && "metadata" in lastMetadata) {
809
+ metadataRef.current = {
810
+ ...metadataRef.current,
811
+ ...lastMetadata.metadata,
812
+ providerMetadata: {
813
+ ...metadataRef.current?.providerMetadata,
814
+ ...lastMetadata.metadata.providerMetadata
815
+ }
816
+ };
817
+ }
818
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk) && chunk.type !== "response_metadata");
819
+ };
820
+ var processSSELines = function* (lines, pendingToolCalls, usageRef, metadataRef, providerName) {
821
+ for (const line of lines) {
822
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef, metadataRef, providerName);
823
+ }
824
+ };
825
+ var processStreamValue = (value, decoder, state) => {
826
+ state.buffer += decoder.decode(value, { stream: true });
827
+ const lines = state.buffer.split(`
828
+ `);
829
+ state.buffer = lines.pop() ?? "";
830
+ return lines;
831
+ };
832
+ var drainReader = async function* (reader, decoder, state, signal) {
833
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
834
+ const lines = processStreamValue(result.value, decoder, state);
835
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef, state.metadataRef, state.providerName);
836
+ }
837
+ };
838
+ var parseSSEStream = async function* (body, initialMetadata, providerName = "openai", signal) {
839
+ const reader = body.getReader();
840
+ const decoder = new TextDecoder;
841
+ const state = {
842
+ buffer: "",
843
+ metadataRef: { current: initialMetadata },
844
+ pendingToolCalls: new Map,
845
+ providerName,
846
+ usageRef: { current: undefined }
847
+ };
848
+ try {
849
+ yield* drainReader(reader, decoder, state, signal);
850
+ yield {
851
+ metadata: state.metadataRef.current,
852
+ type: "done",
853
+ usage: state.usageRef.current
854
+ };
855
+ } finally {
856
+ reader.releaseLock();
857
+ }
858
+ };
859
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
860
+ const target = `${baseUrl}/v1/chat/completions`;
861
+ const requestHeaders = new Headers(headers);
862
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
863
+ requestHeaders.set("Content-Type", "application/json");
864
+ const response = await fetchImpl(target, {
865
+ ...h2IfHttps(target),
866
+ body: JSON.stringify(body),
867
+ headers: requestHeaders,
868
+ method: "POST",
869
+ signal
870
+ });
871
+ if (!response.ok) {
872
+ const errorText = await response.text();
873
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
874
+ }
875
+ if (!response.body) {
876
+ throw new ProviderError({
877
+ message: `${providerName} API returned no response body`,
878
+ provider: providerName,
879
+ retryable: true
880
+ });
881
+ }
882
+ yield* parseSSEStream(response.body, {
883
+ generationId: response.headers.get("X-Generation-Id") ?? undefined,
884
+ providerMetadata: {
885
+ cacheAge: response.headers.get("X-OpenRouter-Cache-Age") ?? undefined,
886
+ cacheSourceId: response.headers.get("X-OpenRouter-Cache-Source-Id") ?? undefined,
887
+ cacheStatus: response.headers.get("X-OpenRouter-Cache-Status") ?? undefined,
888
+ cacheTtl: response.headers.get("X-OpenRouter-Cache-TTL") ?? undefined
889
+ }
890
+ }, providerName, signal);
891
+ };
892
+ var openai = (config2) => {
893
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
894
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
895
+ const providerName = config2.providerName ?? "openai";
896
+ if (!config2.apiKey && !config2.tokenSource) {
897
+ throw new Error("openai() requires either apiKey or tokenSource");
898
+ }
899
+ const resolveKey = async () => {
900
+ if (config2.tokenSource) {
901
+ return await Promise.resolve(config2.tokenSource());
902
+ }
903
+ return config2.apiKey;
904
+ };
905
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
906
+ return instrumentAIProvider({
907
+ stream: (params) => {
908
+ const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model);
909
+ const body = config2.transformRequestBody ? config2.transformRequestBody(openaiBody, params) : openaiBody;
910
+ return async function* () {
911
+ const [apiKey, headers] = await Promise.all([
912
+ resolveKey(),
913
+ resolveHeaders(params)
914
+ ]);
915
+ yield* fetchOpenAIStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
916
+ }();
917
+ }
918
+ }, providerName);
919
+ };
920
+
921
+ // src/ai/providers/openaiResponses.ts
922
+ var h2IfHttps2 = (url) => url.startsWith("https://") ? { protocol: "http2" } : {};
923
+ var DEFAULT_BASE_URL2 = "https://api.openai.com";
924
+ var EVENT_PREFIX_LENGTH = 7;
925
+ var DATA_PREFIX_LENGTH = 6;
926
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
927
+ var isRecordArray2 = (value) => Array.isArray(value) && value.length > 0 && isRecord2(value[0]);
928
+ var mapBlockToResponsesFormat = (block) => {
929
+ if (block.type === "text") {
930
+ return { text: block.content, type: "input_text" };
931
+ }
932
+ if (block.type === "image") {
933
+ return {
934
+ image_url: {
935
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
936
+ },
937
+ type: "input_image"
938
+ };
939
+ }
940
+ if (block.type === "document") {
941
+ return {
942
+ file: {
943
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
944
+ filename: block.name ?? "document.pdf"
945
+ },
946
+ type: "input_file"
947
+ };
948
+ }
949
+ if (block.type === "audio") {
950
+ return {
951
+ input_audio: { data: block.source.data, format: block.source.format },
952
+ type: "input_audio"
953
+ };
954
+ }
955
+ if (block.type === "video") {
956
+ return {
957
+ type: "input_video",
958
+ video_url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
959
+ };
960
+ }
961
+ return null;
962
+ };
963
+ var mapContentToResponsesFormat = (content) => {
964
+ if (typeof content === "string") {
965
+ return content;
966
+ }
967
+ const parts = content.map(mapBlockToResponsesFormat).filter((mapped) => mapped !== null);
968
+ return parts.length > 0 ? parts : "";
969
+ };
970
+ var hasToolBlocks = (content) => content.some((block) => block.type === "tool_use" || block.type === "tool_result");
971
+ var convertToolBlock = (block) => {
972
+ if (block.type === "provider_data" && block.provider === "openrouter") {
973
+ return { ...block.data };
974
+ }
975
+ if (block.type === "tool_use") {
976
+ if (block.providerData)
977
+ return { ...block.providerData };
978
+ return {
979
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
980
+ call_id: block.id,
981
+ name: block.name,
982
+ type: "function_call"
983
+ };
984
+ }
985
+ if (block.type === "tool_result") {
986
+ return {
987
+ call_id: block.tool_use_id,
988
+ output: typeof block.content === "string" ? block.content : "",
989
+ type: "function_call_output"
990
+ };
991
+ }
992
+ return null;
993
+ };
994
+ var convertToolBlocks = (content) => content.map(convertToolBlock).filter((converted) => converted !== null);
995
+ var convertMessage = (msg) => {
996
+ if (typeof msg.content !== "string" && Array.isArray(msg.content) && hasToolBlocks(msg.content)) {
997
+ return convertToolBlocks(msg.content);
998
+ }
999
+ const content = mapContentToResponsesFormat(msg.content);
1000
+ return [
1001
+ {
1002
+ content,
1003
+ role: msg.role === "system" ? "developer" : msg.role,
1004
+ type: "message"
1005
+ }
1006
+ ];
1007
+ };
1008
+ var buildInput = (messages) => {
1009
+ const input = [];
1010
+ for (const msg of messages) {
1011
+ input.push(...convertMessage(msg));
1012
+ }
1013
+ return input;
1014
+ };
1015
+ var mapToolDefinition = (tool) => ({
1016
+ description: tool.description,
1017
+ name: tool.name,
1018
+ parameters: tool.input_schema,
1019
+ type: "function"
1020
+ });
1021
+ var buildTools = (tools, isImageModel) => {
1022
+ const mapped = tools ? tools.map(mapToolDefinition) : [];
1023
+ const result = [...mapped];
1024
+ if (isImageModel) {
1025
+ result.push({ type: "image_generation" });
1026
+ }
1027
+ return result.length > 0 ? result : undefined;
1028
+ };
1029
+ var buildRequestBody2 = (params, isImageModel, capabilityModel = params.model) => {
1030
+ const body = {
1031
+ input: buildInput(params.messages),
1032
+ model: params.model,
1033
+ stream: true
1034
+ };
1035
+ if (params.systemPrompt) {
1036
+ body.instructions = params.systemPrompt;
1037
+ }
1038
+ const tools = buildTools(params.tools, isImageModel);
1039
+ if (tools) {
1040
+ body.tools = tools;
1041
+ if (params.toolChoice === "auto" || params.toolChoice === "none" || params.toolChoice === "required") {
1042
+ body.tool_choice = params.toolChoice;
1043
+ } else if (params.toolChoice && typeof params.toolChoice === "object") {
1044
+ body.tool_choice = {
1045
+ name: params.toolChoice.name,
1046
+ type: "function"
1047
+ };
1048
+ }
1049
+ if (typeof params.parallelToolCalls === "boolean") {
1050
+ body.parallel_tool_calls = params.parallelToolCalls;
1051
+ }
1052
+ }
1053
+ if (typeof params.temperature === "number")
1054
+ body.temperature = params.temperature;
1055
+ if (typeof params.topP === "number")
1056
+ body.top_p = params.topP;
1057
+ if (typeof params.maxTokens === "number")
1058
+ body.max_output_tokens = params.maxTokens;
1059
+ if (params.stopSequences && params.stopSequences.length > 0)
1060
+ body.stop = params.stopSequences;
1061
+ if (typeof params.seed === "number")
1062
+ body.seed = params.seed;
1063
+ if (typeof params.frequencyPenalty === "number")
1064
+ body.frequency_penalty = params.frequencyPenalty;
1065
+ if (typeof params.presencePenalty === "number")
1066
+ body.presence_penalty = params.presencePenalty;
1067
+ if (params.responseFormat) {
1068
+ if (params.responseFormat.type === "text" || params.responseFormat.type === "json_object") {
1069
+ body.text = { format: { type: params.responseFormat.type } };
1070
+ } else if (params.responseFormat.type === "json_schema") {
1071
+ body.text = {
1072
+ format: {
1073
+ name: params.responseFormat.name,
1074
+ schema: params.responseFormat.schema,
1075
+ strict: params.responseFormat.strict ?? true,
1076
+ type: "json_schema"
1077
+ }
1078
+ };
1079
+ }
1080
+ }
1081
+ if (params.reasoning && isOpenAIReasoningModel(capabilityModel)) {
1082
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
1083
+ if (effort) {
1084
+ body.reasoning = {
1085
+ effort,
1086
+ summary: "auto"
1087
+ };
1088
+ }
1089
+ }
1090
+ return body;
1091
+ };
1092
+ var parseJSON = (data) => {
1093
+ try {
1094
+ return JSON.parse(data);
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ };
1099
+ var parseToolInput2 = (rawArguments) => {
1100
+ try {
1101
+ return JSON.parse(rawArguments);
1102
+ } catch {
1103
+ return rawArguments;
1104
+ }
1105
+ };
1106
+ var extractUsage2 = (response) => {
1107
+ if (!isRecord2(response.usage)) {
1108
+ return;
1109
+ }
1110
+ const { usage } = response;
1111
+ const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
1112
+ const cached = isRecord2(usage.input_tokens_details) && typeof usage.input_tokens_details.cached_tokens === "number" ? usage.input_tokens_details.cached_tokens : 0;
1113
+ const outputDetails = isRecord2(usage.output_tokens_details) ? usage.output_tokens_details : undefined;
1114
+ const inputDetails = isRecord2(usage.input_tokens_details) ? usage.input_tokens_details : undefined;
1115
+ const costDetails = isRecord2(usage.cost_details) ? usage.cost_details : undefined;
1116
+ const normalized = {
1117
+ cacheReadInputTokens: cached,
1118
+ cacheWriteInputTokens: inputDetails && typeof inputDetails.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : undefined,
1119
+ costCredits: typeof usage.cost === "number" ? usage.cost : undefined,
1120
+ inputTokens: Math.max(0, input - cached),
1121
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
1122
+ reasoningTokens: outputDetails && typeof outputDetails.reasoning_tokens === "number" ? outputDetails.reasoning_tokens : undefined,
1123
+ upstreamInferenceCostCredits: costDetails && typeof costDetails.upstream_inference_cost === "number" ? costDetails.upstream_inference_cost : undefined
1124
+ };
1125
+ if (isRecord2(usage.server_tool_use)) {
1126
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
1127
+ }
1128
+ return normalized;
1129
+ };
1130
+ var extractResponseMetadata = (response) => {
1131
+ const providerMetadata = isRecord2(response.openrouter_metadata) ? response.openrouter_metadata : undefined;
1132
+ const generationId = typeof response.id === "string" ? response.id : undefined;
1133
+ const model = typeof response.model === "string" ? response.model : undefined;
1134
+ const provider = typeof response.provider === "string" ? response.provider : undefined;
1135
+ const serviceTier = typeof response.service_tier === "string" ? response.service_tier : undefined;
1136
+ if (!providerMetadata && !generationId && !model && !provider && !serviceTier)
1137
+ return;
1138
+ return { generationId, model, provider, providerMetadata, serviceTier };
1139
+ };
1140
+ var extractMimeFormat = (mimeType) => {
1141
+ if (typeof mimeType !== "string") {
1142
+ return "png";
1143
+ }
1144
+ if (mimeType.includes("jpeg"))
1145
+ return "jpeg";
1146
+ if (mimeType.includes("webp"))
1147
+ return "webp";
1148
+ return "png";
1149
+ };
1150
+ var processTextDelta = function* (parsed) {
1151
+ if (typeof parsed.delta === "string") {
1152
+ yield { content: parsed.delta, type: "text" };
1153
+ }
1154
+ };
1155
+ var processPartialImage = function* (parsed) {
1156
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : undefined;
1157
+ const b64 = typeof parsed.partial_image_b64 === "string" ? parsed.partial_image_b64 : undefined;
1158
+ if (b64) {
1159
+ yield {
1160
+ data: b64,
1161
+ format: "png",
1162
+ imageId: itemId,
1163
+ isPartial: true,
1164
+ type: "image"
1165
+ };
1166
+ }
1167
+ };
1168
+ var processFunctionCallArgumentsDelta = (parsed, pendingCalls) => {
1169
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : "";
1170
+ const callId = typeof parsed.call_id === "string" ? parsed.call_id : "";
1171
+ const delta = typeof parsed.arguments_delta === "string" ? parsed.arguments_delta : "";
1172
+ const existing = pendingCalls.get(itemId);
1173
+ if (existing) {
1174
+ existing.arguments += delta;
1175
+ } else {
1176
+ pendingCalls.set(itemId, {
1177
+ arguments: delta,
1178
+ callId,
1179
+ name: ""
1180
+ });
1181
+ }
1182
+ };
1183
+ var processFunctionCallArgumentsDone = function* (parsed, pendingCalls) {
1184
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : "";
1185
+ const callId = typeof parsed.call_id === "string" ? parsed.call_id : "";
1186
+ const fullArgs = typeof parsed.arguments === "string" ? parsed.arguments : "";
1187
+ const pending = pendingCalls.get(itemId);
1188
+ const name = pending?.name ?? "";
1189
+ const args = fullArgs || pending?.arguments || "";
1190
+ pendingCalls.delete(itemId);
1191
+ yield {
1192
+ id: callId || pending?.callId || itemId,
1193
+ input: parseToolInput2(args),
1194
+ name,
1195
+ providerData: pending?.providerData ? { ...pending.providerData, arguments: args } : undefined,
1196
+ type: "tool_use"
1197
+ };
1198
+ };
1199
+ var processOutputItemAdded = (parsed, pendingCalls) => {
1200
+ if (!isRecord2(parsed.item)) {
1201
+ return;
1202
+ }
1203
+ const { item } = parsed;
1204
+ const itemId = typeof item.id === "string" ? item.id : "";
1205
+ const itemType = typeof item.type === "string" ? item.type : "";
1206
+ if (itemType !== "function_call") {
1207
+ return;
1208
+ }
1209
+ const callId = typeof item.call_id === "string" ? item.call_id : "";
1210
+ const name = typeof item.name === "string" ? item.name : "";
1211
+ pendingCalls.set(itemId, {
1212
+ arguments: "",
1213
+ callId,
1214
+ name,
1215
+ providerData: { ...item }
1216
+ });
1217
+ };
1218
+ var processOutputItemDone = function* (parsed) {
1219
+ if (!isRecord2(parsed.item) || typeof parsed.item.type !== "string")
1220
+ return;
1221
+ if (!parsed.item.type.startsWith("openrouter:"))
1222
+ return;
1223
+ yield {
1224
+ data: { ...parsed.item },
1225
+ provider: "openrouter",
1226
+ type: "provider_event"
1227
+ };
1228
+ };
1229
+ var isCompletedImageGeneration = (item) => item.type === "image_generation_call" && item.status === "completed" && typeof item.result === "string" && item.result !== "";
1230
+ var buildImageChunk = (item) => ({
1231
+ data: typeof item.result === "string" ? item.result : "",
1232
+ format: extractMimeFormat(item.output_format),
1233
+ imageId: typeof item.id === "string" ? item.id : undefined,
1234
+ isPartial: false,
1235
+ revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : undefined,
1236
+ type: "image"
1237
+ });
1238
+ var extractImageFromOutput = function* (output) {
1239
+ const completedImages = output.filter(isCompletedImageGeneration);
1240
+ for (const item of completedImages) {
1241
+ yield buildImageChunk(item);
1242
+ }
1243
+ };
1244
+ var extractCitationsFromOutput = function* (output) {
1245
+ for (const item of output) {
1246
+ if (!isRecordArray2(item.content))
1247
+ continue;
1248
+ for (const content of item.content) {
1249
+ if (!Array.isArray(content.annotations))
1250
+ continue;
1251
+ for (const annotation of content.annotations) {
1252
+ if (!isRecord2(annotation) || annotation.type !== "url_citation")
1253
+ continue;
1254
+ if (typeof annotation.url !== "string")
1255
+ continue;
1256
+ yield {
1257
+ content: typeof annotation.content === "string" ? annotation.content : undefined,
1258
+ endIndex: typeof annotation.end_index === "number" ? annotation.end_index : undefined,
1259
+ startIndex: typeof annotation.start_index === "number" ? annotation.start_index : undefined,
1260
+ title: typeof annotation.title === "string" ? annotation.title : undefined,
1261
+ type: "citation",
1262
+ url: annotation.url
1263
+ };
1264
+ }
1265
+ }
1266
+ }
1267
+ };
1268
+ var processCompleted = function* (parsed) {
1269
+ if (!isRecord2(parsed.response)) {
1270
+ yield { type: "done", usage: undefined };
1271
+ return;
1272
+ }
1273
+ const { response } = parsed;
1274
+ const usage = extractUsage2(response);
1275
+ const metadata = extractResponseMetadata(response);
1276
+ if (isRecordArray2(response.output)) {
1277
+ yield* extractCitationsFromOutput(response.output);
1278
+ yield* extractImageFromOutput(response.output);
1279
+ }
1280
+ yield { metadata, type: "done", usage };
1281
+ };
1282
+ var responseFailure = (eventType, parsed, providerName) => {
1283
+ const response = isRecord2(parsed.response) ? parsed.response : parsed;
1284
+ const error = isRecord2(response.error) ? response.error : undefined;
1285
+ const type = typeof response.error_type === "string" ? response.error_type : error && typeof error.code === "string" ? error.code : eventType;
1286
+ return new ProviderError({
1287
+ message: error && typeof error.message === "string" ? error.message : `OpenRouter Responses API: ${eventType}`,
1288
+ metadata: response,
1289
+ provider: providerName,
1290
+ retryable: type === "rate_limit_exceeded" || type === "provider_overloaded" || type === "provider_unavailable" || type === "server",
1291
+ type
1292
+ });
1293
+ };
1294
+ var processSSEEvent = function* (eventType, parsed, pendingCalls, providerName) {
1295
+ switch (eventType) {
1296
+ case "response.reasoning_summary_text.delta": {
1297
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
1298
+ if (!delta)
1299
+ break;
1300
+ yield {
1301
+ content: delta,
1302
+ type: "thinking"
1303
+ };
1304
+ break;
1305
+ }
1306
+ case "response.output_text.delta":
1307
+ yield* processTextDelta(parsed);
1308
+ break;
1309
+ case "response.image_generation_call.partial_image":
1310
+ yield* processPartialImage(parsed);
1311
+ break;
1312
+ case "response.output_item.added":
1313
+ processOutputItemAdded(parsed, pendingCalls);
1314
+ break;
1315
+ case "response.output_item.done":
1316
+ yield* processOutputItemDone(parsed);
1317
+ break;
1318
+ case "response.function_call_arguments.delta":
1319
+ processFunctionCallArgumentsDelta(parsed, pendingCalls);
1320
+ break;
1321
+ case "response.function_call_arguments.done":
1322
+ yield* processFunctionCallArgumentsDone(parsed, pendingCalls);
1323
+ break;
1324
+ case "response.completed":
1325
+ yield* processCompleted(parsed);
1326
+ break;
1327
+ case "response.failed":
1328
+ case "response.incomplete":
1329
+ case "response.error":
1330
+ case "error":
1331
+ throw responseFailure(eventType, parsed, providerName);
1332
+ }
1333
+ };
1334
+ var flushSSEBuffer = function* (state) {
1335
+ if (!state.currentEvent || !state.buffer) {
1336
+ return;
1337
+ }
1338
+ const parsed = parseJSON(state.buffer);
1339
+ if (parsed) {
1340
+ yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls, state.providerName);
1341
+ }
1342
+ state.currentEvent = "";
1343
+ state.buffer = "";
1344
+ };
1345
+ var parseSSELine = (trimmed, state) => {
1346
+ if (trimmed.startsWith("event: ")) {
1347
+ state.currentEvent = trimmed.slice(EVENT_PREFIX_LENGTH);
1348
+ } else if (trimmed.startsWith("data: ")) {
1349
+ state.buffer = trimmed.slice(DATA_PREFIX_LENGTH);
1350
+ }
1351
+ };
1352
+ var processSSELine2 = function* (line, state) {
1353
+ const trimmed = line.trim();
1354
+ if (trimmed) {
1355
+ parseSSELine(trimmed, state);
1356
+ return;
1357
+ }
1358
+ yield* flushSSEBuffer(state);
1359
+ };
1360
+ var processSSELines2 = function* (lines, state) {
1361
+ for (const line of lines) {
1362
+ yield* processSSELine2(line, state);
1363
+ }
1364
+ };
1365
+ var drainReader2 = async function* (reader, decoder, state, signal) {
1366
+ let textBuffer = "";
1367
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
1368
+ textBuffer += decoder.decode(result.value, { stream: true });
1369
+ const lines = textBuffer.split(`
1370
+ `);
1371
+ textBuffer = lines.pop() ?? "";
1372
+ yield* processSSELines2(lines, state);
1373
+ }
1374
+ if (textBuffer.trim()) {
1375
+ yield* processSSELines2([textBuffer, ""], state);
1376
+ }
1377
+ };
1378
+ var parseSSEStream2 = async function* (body, providerName, signal) {
1379
+ const reader = body.getReader();
1380
+ const decoder = new TextDecoder;
1381
+ const state = {
1382
+ buffer: "",
1383
+ currentEvent: "",
1384
+ pendingCalls: new Map,
1385
+ usage: undefined,
1386
+ providerName
1387
+ };
1388
+ try {
1389
+ yield* drainReader2(reader, decoder, state, signal);
1390
+ yield* flushSSEBuffer(state);
1391
+ } finally {
1392
+ reader.releaseLock();
1393
+ }
1394
+ };
1395
+ var fetchResponsesStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
1396
+ const target = `${baseUrl}/v1/responses`;
1397
+ const requestHeaders = new Headers(headers);
1398
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
1399
+ requestHeaders.set("Content-Type", "application/json");
1400
+ const response = await fetchImpl(target, {
1401
+ ...h2IfHttps2(target),
1402
+ body: JSON.stringify(body),
1403
+ headers: requestHeaders,
1404
+ method: "POST",
1405
+ signal
1406
+ });
1407
+ if (!response.ok) {
1408
+ const errorText = await response.text();
1409
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
1410
+ }
1411
+ if (!response.body) {
1412
+ throw new ProviderError({
1413
+ message: `${providerName} Responses API returned no response body`,
1414
+ provider: providerName,
1415
+ retryable: true
1416
+ });
1417
+ }
1418
+ yield* parseSSEStream2(response.body, providerName, signal);
1419
+ };
1420
+ var resolveImageModels = (imageModels) => {
1421
+ if (!imageModels) {
1422
+ return new Set;
1423
+ }
1424
+ if (imageModels instanceof Set) {
1425
+ return imageModels;
1426
+ }
1427
+ return new Set(imageModels);
1428
+ };
1429
+ var openaiResponses = (config2) => {
1430
+ if (!config2.apiKey && !config2.tokenSource)
1431
+ throw new Error("openaiResponses() requires either apiKey or tokenSource");
1432
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL2;
1433
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
1434
+ const imageModels = resolveImageModels(config2.imageModels);
1435
+ const providerName = config2.providerName ?? "openai-responses";
1436
+ const resolveKey = async () => config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
1437
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
1438
+ return instrumentAIProvider({
1439
+ stream: (params) => {
1440
+ const isImageModel = imageModels.has(params.model);
1441
+ const builtBody = buildRequestBody2(params, isImageModel, config2.modelForCapabilities?.(params.model) ?? params.model);
1442
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
1443
+ return async function* () {
1444
+ const [apiKey, headers] = await Promise.all([
1445
+ resolveKey(),
1446
+ resolveHeaders(params)
1447
+ ]);
1448
+ yield* fetchResponsesStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
1449
+ }();
1450
+ }
1451
+ }, providerName);
1452
+ };
1453
+
1454
+ // src/ai/providers/anthropic.ts
1455
+ var h2IfHttps3 = (url) => url.startsWith("https://") ? { protocol: "http2" } : {};
1456
+ var DEFAULT_BASE_URL3 = "https://api.anthropic.com";
1457
+ var API_VERSION = "2023-06-01";
1458
+ var DEFAULT_MAX_TOKENS = 32000;
1459
+ var EVENT_PREFIX_LENGTH2 = 7;
1460
+ var DATA_PREFIX_LENGTH2 = 6;
1461
+ var EMPTY_CHUNKS = [];
1462
+ var isRecord3 = (val) => typeof val === "object" && val !== null;
1463
+ var mapContentBlock = (block) => {
1464
+ if (block.type === "thinking") {
1465
+ return {
1466
+ signature: block.signature,
1467
+ thinking: block.thinking,
1468
+ type: "thinking"
1469
+ };
1470
+ }
1471
+ if (block.type === "image") {
1472
+ return {
1473
+ source: block.source,
1474
+ type: "image"
1475
+ };
1476
+ }
1477
+ if (block.type === "document") {
1478
+ return {
1479
+ source: block.source,
1480
+ type: "document"
1481
+ };
1482
+ }
1483
+ if (block.type === "tool_result") {
1484
+ return {
1485
+ content: block.content,
1486
+ tool_use_id: block.tool_use_id,
1487
+ type: "tool_result"
1488
+ };
1489
+ }
1490
+ if (block.type === "tool_use") {
1491
+ if (block.providerData)
1492
+ return { ...block.providerData };
1493
+ return {
1494
+ id: block.id,
1495
+ input: block.input,
1496
+ name: block.name,
1497
+ type: "tool_use"
1498
+ };
1499
+ }
1500
+ if (block.type === "audio" || block.type === "video") {
1501
+ throw new Error(`Anthropic does not support ${block.type} content blocks`);
1502
+ }
1503
+ if (block.type === "provider_data") {
1504
+ return { ...block.data };
1505
+ }
1506
+ return { text: block.content, type: "text" };
1507
+ };
1508
+ var mapMessage = (msg) => ({
1509
+ content: typeof msg.content === "string" ? msg.content : msg.content.map(mapContentBlock),
1510
+ role: msg.role === "system" ? "user" : msg.role
1511
+ });
1512
+ var mapToolDefinition2 = (tool) => ({
1513
+ description: tool.description,
1514
+ input_schema: tool.input_schema,
1515
+ name: tool.name
1516
+ });
1517
+ var cacheLastContentBlock = (msg) => {
1518
+ const cacheControl = { type: "ephemeral" };
1519
+ if (typeof msg.content === "string") {
1520
+ return {
1521
+ content: [
1522
+ { cache_control: cacheControl, text: msg.content, type: "text" }
1523
+ ],
1524
+ role: msg.role
1525
+ };
1526
+ }
1527
+ if (msg.content.length === 0)
1528
+ return msg;
1529
+ const blocks = [...msg.content];
1530
+ blocks[blocks.length - 1] = {
1531
+ ...blocks[blocks.length - 1],
1532
+ cache_control: cacheControl
1533
+ };
1534
+ return { content: blocks, role: msg.role };
1535
+ };
1536
+ var buildRequestBody3 = (params, configuredMax, configCaching) => {
1537
+ const caching = params.promptCaching ?? configCaching;
1538
+ const cacheSystem = params.cacheSystemPrompt ?? caching;
1539
+ const messages = params.messages.filter((msg) => msg.role !== "system").map(mapMessage);
1540
+ if (caching && messages.length > 1) {
1541
+ const last = messages[messages.length - 1];
1542
+ if (last) {
1543
+ messages[messages.length - 1] = cacheLastContentBlock(last);
1544
+ }
1545
+ }
1546
+ const max = typeof params.maxTokens === "number" ? params.maxTokens : configuredMax;
1547
+ const body = {
1548
+ max_tokens: max,
1549
+ messages,
1550
+ model: params.model,
1551
+ stream: true
1552
+ };
1553
+ if (params.systemPrompt) {
1554
+ body.system = cacheSystem ? [
1555
+ {
1556
+ cache_control: { type: "ephemeral" },
1557
+ text: params.systemPrompt,
1558
+ type: "text"
1559
+ }
1560
+ ] : params.systemPrompt;
1561
+ }
1562
+ if (params.tools && params.tools.length > 0) {
1563
+ const tools = params.tools.map(mapToolDefinition2);
1564
+ if (caching) {
1565
+ tools[tools.length - 1] = {
1566
+ ...tools[tools.length - 1],
1567
+ cache_control: { type: "ephemeral" }
1568
+ };
1569
+ }
1570
+ body.tools = tools;
1571
+ if (params.toolChoice === "auto" || params.toolChoice === "none") {
1572
+ body.tool_choice = { type: params.toolChoice };
1573
+ } else if (params.toolChoice === "required") {
1574
+ body.tool_choice = { type: "any" };
1575
+ } else if (params.toolChoice && typeof params.toolChoice === "object") {
1576
+ body.tool_choice = { name: params.toolChoice.name, type: "tool" };
1577
+ }
1578
+ }
1579
+ if (params.stopSequences && params.stopSequences.length > 0) {
1580
+ body.stop_sequences = params.stopSequences;
1581
+ }
1582
+ const mode = params.reasoning ? anthropicReasoningMode(params.model) : "none";
1583
+ const thinkingActive = mode !== "none";
1584
+ if (!thinkingActive && anthropicSupportsSampling(params.model)) {
1585
+ if (typeof params.temperature === "number") {
1586
+ body.temperature = params.temperature;
1587
+ }
1588
+ if (typeof params.topP === "number")
1589
+ body.top_p = params.topP;
1590
+ }
1591
+ if (mode === "effort" || mode === "adaptive") {
1592
+ body.thinking = { type: "adaptive" };
1593
+ if (mode === "effort" && params.reasoning) {
1594
+ const effort = anthropicEffortValue(params.model, params.reasoning);
1595
+ if (effort)
1596
+ body.output_config = { effort };
1597
+ }
1598
+ } else if (mode === "legacy" && params.reasoning) {
1599
+ const budget = resolveBudgetTokens(params.reasoning);
1600
+ if (budget) {
1601
+ body.thinking = { budget_tokens: budget, type: "enabled" };
1602
+ body.max_tokens = Math.max(max, budget + max);
1603
+ }
1604
+ }
1605
+ return body;
1606
+ };
1607
+ var classifyLine = (line) => {
1608
+ if (line.startsWith("event: ")) {
1609
+ return {
1610
+ field: "event",
1611
+ value: line.slice(EVENT_PREFIX_LENGTH2)
1612
+ };
1613
+ }
1614
+ if (line.startsWith("data: ")) {
1615
+ return {
1616
+ field: "data",
1617
+ value: line.slice(DATA_PREFIX_LENGTH2)
1618
+ };
1619
+ }
1620
+ return;
1621
+ };
1622
+ var applyClassified = (acc, classified) => {
1623
+ if (!classified) {
1624
+ return acc;
1625
+ }
1626
+ if (classified.field === "event") {
1627
+ return { eventData: acc.eventData, eventType: classified.value };
1628
+ }
1629
+ return { eventData: classified.value, eventType: acc.eventType };
1630
+ };
1631
+ var parseEventLines = (event) => event.split(`
1632
+ `).reduce((acc, line) => applyClassified(acc, classifyLine(line)), {
1633
+ eventData: "",
1634
+ eventType: ""
1635
+ });
1636
+ var safeParse = (text) => {
1637
+ try {
1638
+ const result = JSON.parse(text);
1639
+ return result;
1640
+ } catch {
1641
+ return;
1642
+ }
1643
+ };
1644
+ var tryParseJson = (text) => {
1645
+ const result = safeParse(text);
1646
+ if (isRecord3(result)) {
1647
+ return result;
1648
+ }
1649
+ return;
1650
+ };
1651
+ var getRecord = (obj, key) => {
1652
+ const val = obj[key];
1653
+ if (isRecord3(val)) {
1654
+ return val;
1655
+ }
1656
+ return;
1657
+ };
1658
+ var getString = (obj, key) => {
1659
+ const val = obj[key];
1660
+ if (typeof val === "string") {
1661
+ return val;
1662
+ }
1663
+ return "";
1664
+ };
1665
+ var getNumber = (obj, key) => {
1666
+ const val = obj[key];
1667
+ if (typeof val === "number") {
1668
+ return val;
1669
+ }
1670
+ return 0;
1671
+ };
1672
+ var handleContentBlockStart = (parsed, state) => {
1673
+ const block = getRecord(parsed, "content_block");
1674
+ if (block && block.type === "tool_use") {
1675
+ state.currentToolId = getString(block, "id");
1676
+ state.currentToolName = getString(block, "name");
1677
+ state.toolInputJson = "";
1678
+ state.isThinkingBlock = false;
1679
+ state.currentProviderBlock = undefined;
1680
+ } else if (block && block.type === "thinking") {
1681
+ state.isThinkingBlock = true;
1682
+ state.thinkingSignature = "";
1683
+ state.currentProviderBlock = undefined;
1684
+ } else {
1685
+ state.isThinkingBlock = false;
1686
+ state.currentProviderBlock = block && block.type !== "text" ? { ...block } : undefined;
1687
+ state.providerBlockInputJson = "";
1688
+ }
1689
+ };
1690
+ var handleContentBlockDelta = (parsed, state) => {
1691
+ const delta = getRecord(parsed, "delta");
1692
+ if (!delta) {
1693
+ return;
1694
+ }
1695
+ if (delta.type === "thinking_delta") {
1696
+ return {
1697
+ content: getString(delta, "thinking"),
1698
+ type: "thinking"
1699
+ };
1700
+ }
1701
+ if (delta.type === "text_delta") {
1702
+ return {
1703
+ content: getString(delta, "text"),
1704
+ type: "text"
1705
+ };
1706
+ }
1707
+ if (delta.type === "input_json_delta") {
1708
+ if (state.currentProviderBlock) {
1709
+ state.providerBlockInputJson += getString(delta, "partial_json");
1710
+ } else {
1711
+ state.toolInputJson += getString(delta, "partial_json");
1712
+ }
1713
+ }
1714
+ if (delta.type === "signature_delta") {
1715
+ state.thinkingSignature += getString(delta, "signature");
1716
+ }
1717
+ return;
1718
+ };
1719
+ var handleContentBlockStop = (state) => {
1720
+ if (state.isThinkingBlock && state.thinkingSignature) {
1721
+ state.isThinkingBlock = false;
1722
+ const signature = state.thinkingSignature;
1723
+ state.thinkingSignature = "";
1724
+ return {
1725
+ content: "",
1726
+ signature,
1727
+ type: "thinking"
1728
+ };
1729
+ }
1730
+ if (state.currentProviderBlock) {
1731
+ const data = { ...state.currentProviderBlock };
1732
+ if (state.providerBlockInputJson) {
1733
+ data.input = tryParseJson(state.providerBlockInputJson) ?? state.providerBlockInputJson;
1734
+ }
1735
+ state.currentProviderBlock = undefined;
1736
+ state.providerBlockInputJson = "";
1737
+ return {
1738
+ data,
1739
+ provider: state.providerName,
1740
+ type: "provider_event"
1741
+ };
1742
+ }
1743
+ if (!state.currentToolId) {
1744
+ return;
1745
+ }
1746
+ const input = tryParseJson(state.toolInputJson) ?? state.toolInputJson;
1747
+ const chunk = {
1748
+ id: state.currentToolId,
1749
+ input,
1750
+ name: state.currentToolName,
1751
+ type: "tool_use"
1752
+ };
1753
+ state.currentToolId = "";
1754
+ state.currentToolName = "";
1755
+ state.toolInputJson = "";
1756
+ return chunk;
1757
+ };
1758
+ var extractUsage3 = (usageRecord, existingUsage) => {
1759
+ if (!usageRecord) {
1760
+ return existingUsage;
1761
+ }
1762
+ const normalized = {
1763
+ cacheReadInputTokens: getNumber(usageRecord, "cache_read_input_tokens") || existingUsage?.cacheReadInputTokens || 0,
1764
+ cacheWriteInputTokens: getNumber(usageRecord, "cache_creation_input_tokens") || existingUsage?.cacheWriteInputTokens || 0,
1765
+ inputTokens: getNumber(usageRecord, "input_tokens") || existingUsage?.inputTokens || 0,
1766
+ outputTokens: getNumber(usageRecord, "output_tokens") || existingUsage?.outputTokens || 0,
1767
+ costCredits: getNumber(usageRecord, "cost") || existingUsage?.costCredits,
1768
+ reasoningTokens: getNumber(usageRecord, "reasoning_tokens") || existingUsage?.reasoningTokens,
1769
+ upstreamInferenceCostCredits: getNumber(getRecord(usageRecord, "cost_details") ?? {}, "upstream_inference_cost") || existingUsage?.upstreamInferenceCostCredits
1770
+ };
1771
+ const serverToolUse = getRecord(usageRecord, "server_tool_use");
1772
+ if (serverToolUse) {
1773
+ normalized.serverToolUse = Object.fromEntries(Object.entries(serverToolUse).filter((entry) => typeof entry[1] === "number"));
1774
+ }
1775
+ return normalized;
1776
+ };
1777
+ var mergeMetadata = (source, state) => {
1778
+ const providerMetadata = getRecord(source, "openrouter_metadata");
1779
+ const generationId = getString(source, "id") || undefined;
1780
+ const model = getString(source, "model") || undefined;
1781
+ const provider = getString(source, "provider") || undefined;
1782
+ const serviceTier = getString(source, "service_tier") || undefined;
1783
+ if (!providerMetadata && !generationId && !model && !provider && !serviceTier)
1784
+ return;
1785
+ state.metadata = {
1786
+ ...state.metadata,
1787
+ generationId: generationId ?? state.metadata?.generationId,
1788
+ model: model ?? state.metadata?.model,
1789
+ provider: provider ?? state.metadata?.provider,
1790
+ providerMetadata: {
1791
+ ...state.metadata?.providerMetadata,
1792
+ ...providerMetadata
1793
+ },
1794
+ serviceTier: serviceTier ?? state.metadata?.serviceTier
1795
+ };
1796
+ };
1797
+ var handleMessageDelta = (parsed, state) => {
1798
+ const deltaUsage = getRecord(parsed, "usage");
1799
+ state.usage = extractUsage3(deltaUsage, state.usage);
1800
+ const delta = getRecord(parsed, "delta");
1801
+ const stopReason = delta ? getString(delta, "stop_reason") : "";
1802
+ if (stopReason)
1803
+ state.stopReason = stopReason;
1804
+ };
1805
+ var handleMessageStart = (parsed, state) => {
1806
+ const message = getRecord(parsed, "message");
1807
+ if (!message) {
1808
+ return;
1809
+ }
1810
+ const startUsage = getRecord(message, "usage");
1811
+ state.usage = extractUsage3(startUsage, state.usage);
1812
+ mergeMetadata(message, state);
1813
+ };
1814
+ var handleError = (parsed, state) => {
1815
+ const error = getRecord(parsed, "error");
1816
+ const errorMessage = error ? getString(error, "message") : "";
1817
+ const nativeErrorType = error ? getString(error, "type") : "";
1818
+ const errorType = error ? getString(error, "error_type") || nativeErrorType : "";
1819
+ const retryable = errorType === "provider_overloaded" || errorType === "rate_limit_exceeded" || errorType === "provider_unavailable" || errorType === "server" || nativeErrorType === "overloaded_error" || nativeErrorType === "rate_limit_error" || nativeErrorType === "api_error";
1820
+ throw new ProviderError({
1821
+ message: errorMessage || "Anthropic API error",
1822
+ metadata: error,
1823
+ provider: state.providerName,
1824
+ retryable,
1825
+ type: errorType || null
1826
+ });
1827
+ };
1828
+ var processEvent = (eventType, parsed, state) => {
1829
+ switch (eventType) {
1830
+ case "content_block_start": {
1831
+ handleContentBlockStart(parsed, state);
1832
+ return;
1833
+ }
1834
+ case "content_block_delta": {
1835
+ return handleContentBlockDelta(parsed, state);
1836
+ }
1837
+ case "content_block_stop": {
1838
+ return handleContentBlockStop(state);
1839
+ }
1840
+ case "message_delta": {
1841
+ handleMessageDelta(parsed, state);
1842
+ mergeMetadata(parsed, state);
1843
+ return;
1844
+ }
1845
+ case "message_start": {
1846
+ handleMessageStart(parsed, state);
1847
+ return;
1848
+ }
1849
+ case "message_stop": {
1850
+ mergeMetadata(parsed, state);
1851
+ return {
1852
+ stopReason: state.stopReason,
1853
+ metadata: state.metadata,
1854
+ type: "done",
1855
+ usage: state.usage
1856
+ };
1857
+ }
1858
+ case "error": {
1859
+ handleError(parsed, state);
1860
+ return;
1861
+ }
1862
+ default: {
1863
+ return;
1864
+ }
1865
+ }
1866
+ };
1867
+ var processSingleEvent = (event, state) => {
1868
+ if (!event.trim()) {
1869
+ return;
1870
+ }
1871
+ const { eventData, eventType } = parseEventLines(event);
1872
+ if (!eventData) {
1873
+ return;
1874
+ }
1875
+ const parsed = tryParseJson(eventData);
1876
+ if (!parsed) {
1877
+ return;
1878
+ }
1879
+ return processEvent(eventType, parsed, state);
1880
+ };
1881
+ var collectChunk = (event, state) => {
1882
+ const chunk = processSingleEvent(event, state);
1883
+ return chunk ? [chunk] : [];
1884
+ };
1885
+ var processBufferedEvents = (eventsText, state) => {
1886
+ const events = eventsText.split(`
1887
+
1888
+ `);
1889
+ state.buffer = events.pop() ?? "";
1890
+ return events.flatMap((event) => collectChunk(event, state));
1891
+ };
1892
+ var readNextChunks = async (reader, decoder, state, signal) => {
1893
+ if (signal?.aborted) {
1894
+ return { chunks: EMPTY_CHUNKS, done: true };
1895
+ }
1896
+ const { done, value } = await reader.read();
1897
+ if (done) {
1898
+ return { chunks: EMPTY_CHUNKS, done: true };
1899
+ }
1900
+ const rawText = state.buffer + decoder.decode(value, { stream: true });
1901
+ const chunks = processBufferedEvents(rawText, state);
1902
+ return { chunks, done: false };
1903
+ };
1904
+ var findDoneChunk = (chunks) => chunks.findIndex((c) => c.type === "done");
1905
+ var sseStreamLoop = async (reader, decoder, state, signal) => {
1906
+ const result = await readNextChunks(reader, decoder, state, signal);
1907
+ if (result.done) {
1908
+ return { chunks: result.chunks, finished: true };
1909
+ }
1910
+ const doneIdx = findDoneChunk(result.chunks);
1911
+ if (doneIdx >= 0) {
1912
+ return { chunks: result.chunks.slice(0, doneIdx + 1), finished: true };
1913
+ }
1914
+ return { chunks: result.chunks, finished: false };
1915
+ };
1916
+ async function* streamChunks(reader, decoder, state, signal) {
1917
+ let finished = false;
1918
+ while (!finished) {
1919
+ const result = await sseStreamLoop(reader, decoder, state, signal);
1920
+ ({ finished } = result);
1921
+ yield* result.chunks;
1922
+ }
1923
+ }
1924
+ async function* parseSSEStream3(body, providerName, signal) {
1925
+ const reader = body.getReader();
1926
+ const decoder = new TextDecoder;
1927
+ const state = {
1928
+ buffer: "",
1929
+ currentToolId: "",
1930
+ currentToolName: "",
1931
+ isThinkingBlock: false,
1932
+ stopReason: "",
1933
+ thinkingSignature: "",
1934
+ toolInputJson: "",
1935
+ usage: undefined,
1936
+ providerName,
1937
+ providerBlockInputJson: ""
1938
+ };
1939
+ try {
1940
+ yield* streamChunks(reader, decoder, state, signal);
1941
+ } finally {
1942
+ reader.releaseLock();
1943
+ }
1944
+ }
1945
+ var fetchAndStream = async function* (baseUrl, config2, params, configuredMax, promptCaching, providerName) {
1946
+ const builtBody = buildRequestBody3(params, configuredMax, promptCaching);
1947
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
1948
+ const target = `${baseUrl}/v1/messages`;
1949
+ const fetchImpl = config2.fetch ?? fetch;
1950
+ const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
1951
+ const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
1952
+ const requestHeaders = new Headers(suppliedHeaders);
1953
+ requestHeaders.set("Content-Type", "application/json");
1954
+ if (config2.authStyle === "bearer") {
1955
+ requestHeaders.set("Authorization", `Bearer ${token}`);
1956
+ } else {
1957
+ requestHeaders.set("anthropic-version", API_VERSION);
1958
+ requestHeaders.set("x-api-key", token);
1959
+ }
1960
+ const response = await fetchImpl(target, {
1961
+ ...h2IfHttps3(target),
1962
+ body: JSON.stringify(body),
1963
+ headers: requestHeaders,
1964
+ method: "POST",
1965
+ signal: params.signal
1966
+ });
1967
+ if (!response.ok) {
1968
+ const errorText = await response.text();
1969
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
1970
+ }
1971
+ if (!response.body) {
1972
+ throw new ProviderError({
1973
+ message: `${providerName} Messages API returned no response body`,
1974
+ provider: providerName,
1975
+ retryable: true
1976
+ });
1977
+ }
1978
+ yield* parseSSEStream3(response.body, providerName, params.signal);
1979
+ };
1980
+ var anthropic = (config2) => {
1981
+ if (!config2.apiKey && !config2.tokenSource)
1982
+ throw new Error("anthropic() requires either apiKey or tokenSource");
1983
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL3;
1984
+ const configuredMax = config2.maxTokens ?? DEFAULT_MAX_TOKENS;
1985
+ const promptCaching = config2.promptCaching ?? true;
1986
+ const providerName = config2.providerName ?? "anthropic";
1987
+ return instrumentAIProvider({
1988
+ stream: (params) => fetchAndStream(baseUrl, config2, params, configuredMax, promptCaching, providerName)
1989
+ }, providerName);
1990
+ };
1991
+
1992
+ // src/ai/providers/openrouterClient.ts
1993
+ var DEFAULT_BASE_URL4 = "https://openrouter.ai/api/v1";
1994
+ var withoutLatestPrefix = (model) => model.startsWith("~") ? model.slice(1) : model;
1995
+ var openRouterModelMatchesRule = (model, rule) => {
1996
+ const normalizedModel = withoutLatestPrefix(model);
1997
+ const normalizedRule = withoutLatestPrefix(rule);
1998
+ return normalizedRule.endsWith("/*") ? normalizedModel.startsWith(normalizedRule.slice(0, -1)) : normalizedModel === normalizedRule;
1999
+ };
2000
+ var assertAllowedModel = (model, allowedModels) => {
2001
+ if (!allowedModels)
2002
+ return;
2003
+ if (allowedModels.some((rule) => openRouterModelMatchesRule(model, rule)))
2004
+ return;
2005
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
2006
+ };
2007
+ var normalizePath = (path) => path.startsWith("/") ? path : `/${path}`;
2008
+ var encodeModelPath = (model) => model.split("/").map(encodeURIComponent).join("/");
2009
+ var withQuery = (url, query) => {
2010
+ if (!query)
2011
+ return url;
2012
+ const result = new URL(url);
2013
+ for (const [key, value] of Object.entries(query)) {
2014
+ if (value !== undefined)
2015
+ result.searchParams.set(key, String(value));
2016
+ }
2017
+ return result.toString();
2018
+ };
2019
+ var parseImageSSE = async function* (response) {
2020
+ if (!response.body)
2021
+ throw new Error("OpenRouter image stream has no body");
2022
+ const reader = response.body.getReader();
2023
+ const decoder = new TextDecoder;
2024
+ let buffer = "";
2025
+ try {
2026
+ for (;; ) {
2027
+ const result = await reader.read();
2028
+ buffer += decoder.decode(result.value, { stream: !result.done });
2029
+ const lines = buffer.split(`
2030
+ `);
2031
+ buffer = lines.pop() ?? "";
2032
+ for (const line of lines) {
2033
+ if (!line.startsWith("data: "))
2034
+ continue;
2035
+ const data = line.slice(6);
2036
+ if (data === "[DONE]")
2037
+ return;
2038
+ try {
2039
+ const parsed = JSON.parse(data);
2040
+ if (parsed && typeof parsed === "object" && "type" in parsed)
2041
+ yield parsed;
2042
+ } catch {}
2043
+ }
2044
+ if (result.done)
2045
+ break;
2046
+ }
2047
+ if (buffer.startsWith("data: ")) {
2048
+ const parsed = JSON.parse(buffer.slice(6));
2049
+ if (parsed && typeof parsed === "object" && "type" in parsed)
2050
+ yield parsed;
2051
+ }
2052
+ } finally {
2053
+ reader.releaseLock();
2054
+ }
2055
+ };
2056
+ var toBytes = (value) => typeof value === "string" ? new TextEncoder().encode(value) : value;
2057
+ var hexToBytes = (hex) => {
2058
+ if (!/^[0-9a-f]+$/iu.test(hex) || hex.length % 2 !== 0)
2059
+ return;
2060
+ const bytes = new Uint8Array(hex.length / 2);
2061
+ for (let index = 0;index < bytes.length; index += 1) {
2062
+ bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
2063
+ }
2064
+ return bytes;
2065
+ };
2066
+ var constantTimeEqual = (left, right) => {
2067
+ if (left.length !== right.length)
2068
+ return false;
2069
+ let mismatch = 0;
2070
+ for (let index = 0;index < left.length; index += 1)
2071
+ mismatch |= (left[index] ?? 0) ^ (right[index] ?? 0);
2072
+ return mismatch === 0;
2073
+ };
2074
+ var verifyOpenRouterWebhookSignature = async (options) => {
2075
+ const fields = new Map(options.header.split(",").map((part) => {
2076
+ const [key2, ...rest] = part.trim().split("=");
2077
+ return [key2, rest.join("=")];
2078
+ }));
2079
+ const timestamp = fields.get("t");
2080
+ const supplied = fields.get("v1");
2081
+ if (!timestamp || !supplied)
2082
+ return false;
2083
+ const timestampNumber = Number(timestamp);
2084
+ const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
2085
+ const tolerance = options.toleranceSeconds ?? 300;
2086
+ if (!Number.isFinite(timestampNumber) || Math.abs(now - timestampNumber) > tolerance)
2087
+ return false;
2088
+ const key = await crypto.subtle.importKey("raw", Uint8Array.from(toBytes(options.secret)).buffer, { hash: "SHA-256", name: "HMAC" }, false, ["sign"]);
2089
+ const prefix = new TextEncoder().encode(`${timestamp},`);
2090
+ const body = toBytes(options.body);
2091
+ const payload = new Uint8Array(prefix.length + body.length);
2092
+ payload.set(prefix);
2093
+ payload.set(body, prefix.length);
2094
+ const expected = new Uint8Array(await crypto.subtle.sign("HMAC", key, payload.buffer));
2095
+ const suppliedBytes = hexToBytes(supplied);
2096
+ return suppliedBytes ? constantTimeEqual(expected, suppliedBytes) : false;
2097
+ };
2098
+ var createOpenRouterClient = (config2) => {
2099
+ if (!config2.apiKey && !config2.tokenSource)
2100
+ throw new Error("createOpenRouterClient() requires either apiKey or tokenSource");
2101
+ const baseUrl = (config2.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
2102
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
2103
+ const allowedModels = config2.allowedModels ? [...config2.allowedModels] : undefined;
2104
+ const requestRaw = async (path, options = {}) => {
2105
+ const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
2106
+ const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2107
+ const headers = new Headers(suppliedHeaders);
2108
+ new Headers(options.headers).forEach((value, key) => headers.set(key, value));
2109
+ headers.set("Authorization", `Bearer ${token}`);
2110
+ let body;
2111
+ if (options.body instanceof FormData || options.body instanceof Blob) {
2112
+ body = options.body;
2113
+ } else if (options.body !== undefined) {
2114
+ headers.set("Content-Type", "application/json");
2115
+ body = JSON.stringify(options.body);
2116
+ }
2117
+ const { query, ...requestInit } = options;
2118
+ const response = await fetchImpl(withQuery(`${baseUrl}${normalizePath(path)}`, options.query), { ...requestInit, body, headers });
2119
+ if (!response.ok) {
2120
+ throw ProviderError.fromResponse("openrouter", response.status, await response.text());
2121
+ }
2122
+ return response;
2123
+ };
2124
+ const request = async (path, options = {}) => (await requestRaw(path, options)).json();
2125
+ const listModels = async (query) => {
2126
+ const result = await request("/models", { query });
2127
+ if (!allowedModels)
2128
+ return result;
2129
+ return {
2130
+ ...result,
2131
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2132
+ };
2133
+ };
2134
+ const filterModelList = (result) => {
2135
+ if (!allowedModels)
2136
+ return result;
2137
+ return {
2138
+ ...result,
2139
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2140
+ };
2141
+ };
2142
+ return {
2143
+ cancelBatch: (id) => request(`/batches/${encodeURIComponent(id)}/cancel`, {
2144
+ method: "POST"
2145
+ }),
2146
+ createBatch: (body) => request("/batches", { body, method: "POST" }),
2147
+ createEmbedding: (body) => {
2148
+ assertAllowedModel(body.model, allowedModels);
2149
+ return request("/embeddings", {
2150
+ body,
2151
+ method: "POST"
2152
+ });
2153
+ },
2154
+ generateImage: (body) => {
2155
+ assertAllowedModel(body.model, allowedModels);
2156
+ return request("/images", {
2157
+ body,
2158
+ method: "POST"
2159
+ });
2160
+ },
2161
+ deleteFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, { method: "DELETE", query: { workspace_id: workspaceId } }),
2162
+ downloadFile: (id, workspaceId) => requestRaw(`/files/${encodeURIComponent(id)}/content`, {
2163
+ query: { workspace_id: workspaceId }
2164
+ }),
2165
+ downloadVideo: (id, index = 0) => requestRaw(`/videos/${encodeURIComponent(id)}/content`, {
2166
+ query: { index }
2167
+ }),
2168
+ generateVideo: (body) => {
2169
+ assertAllowedModel(body.model, allowedModels);
2170
+ return request("/videos", {
2171
+ body,
2172
+ method: "POST"
2173
+ });
2174
+ },
2175
+ getBatch: (id) => request(`/batches/${encodeURIComponent(id)}`),
2176
+ getCredits: () => request("/credits"),
2177
+ getCurrentKey: () => request("/key"),
2178
+ getFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, {
2179
+ query: { workspace_id: workspaceId }
2180
+ }),
2181
+ getGeneration: (id) => request("/generation", {
2182
+ query: { id }
2183
+ }),
2184
+ getModelEndpoints: (model) => {
2185
+ assertAllowedModel(model, allowedModels);
2186
+ return request(`/models/${encodeModelPath(model)}/endpoints`);
2187
+ },
2188
+ getModel: (model) => {
2189
+ assertAllowedModel(model, allowedModels);
2190
+ return request(`/model/${encodeModelPath(model)}`);
2191
+ },
2192
+ getImageModelEndpoints: (model) => {
2193
+ assertAllowedModel(model, allowedModels);
2194
+ return request(`/images/models/${encodeModelPath(model)}/endpoints`);
2195
+ },
2196
+ getVideo: (id) => request(`/videos/${encodeURIComponent(id)}`),
2197
+ listImageModels: async () => filterModelList(await request("/images/models")),
2198
+ listFiles: (query) => request("/files", { query }),
2199
+ listModels,
2200
+ listUserModels: async () => filterModelList(await request("/models/user")),
2201
+ listZdrEndpoints: async () => {
2202
+ const result = await request("/endpoints/zdr");
2203
+ if (!allowedModels)
2204
+ return result;
2205
+ return {
2206
+ ...result,
2207
+ data: result.data.filter((endpoint) => allowedModels.some((rule) => openRouterModelMatchesRule(endpoint.model_id, rule)))
2208
+ };
2209
+ },
2210
+ countModels: (outputModalities) => request("/models/count", {
2211
+ query: { output_modalities: outputModalities }
2212
+ }),
2213
+ listPresets: (offset = 0, limit = 100) => request("/presets", { query: { limit, offset } }),
2214
+ listPresetVersions: (slug, offset = 0, limit = 100) => request(`/presets/${encodeURIComponent(slug)}/versions`, { query: { limit, offset } }),
2215
+ listProviders: () => request("/providers"),
2216
+ listRerankModels: async () => filterModelList(await request("/rerank/models")),
2217
+ listVideoModels: async () => filterModelList(await request("/videos/models")),
2218
+ request,
2219
+ requestRaw,
2220
+ streamImage: async function* (body, options = {}) {
2221
+ assertAllowedModel(body.model, allowedModels);
2222
+ const response = await requestRaw("/images", {
2223
+ ...options,
2224
+ body: { ...body, stream: true },
2225
+ method: "POST"
2226
+ });
2227
+ yield* parseImageSSE(response);
2228
+ },
2229
+ respond: (body) => {
2230
+ assertAllowedModel(body.model, allowedModels);
2231
+ return body.stream ? requestRaw("/responses", { body, method: "POST" }) : request("/responses", {
2232
+ body,
2233
+ method: "POST"
2234
+ });
2235
+ },
2236
+ rerank: (body) => {
2237
+ assertAllowedModel(body.model, allowedModels);
2238
+ return request("/rerank", {
2239
+ body,
2240
+ method: "POST"
2241
+ });
2242
+ },
2243
+ speak: (body) => {
2244
+ assertAllowedModel(body.model, allowedModels);
2245
+ return requestRaw("/audio/speech", { body, method: "POST" });
2246
+ },
2247
+ transcribe: (body) => {
2248
+ if (body instanceof FormData) {
2249
+ const model = body.get("model");
2250
+ if (typeof model !== "string")
2251
+ throw new Error("OpenRouter transcription FormData requires model");
2252
+ assertAllowedModel(model, allowedModels);
2253
+ } else {
2254
+ assertAllowedModel(body.model, allowedModels);
2255
+ }
2256
+ return request("/audio/transcriptions", {
2257
+ body,
2258
+ method: "POST"
2259
+ });
2260
+ },
2261
+ uploadFile: (file, options = {}) => {
2262
+ const body = new FormData;
2263
+ if (options.filename)
2264
+ body.append("file", file, options.filename);
2265
+ else
2266
+ body.append("file", file);
2267
+ return request("/files", {
2268
+ body,
2269
+ method: "POST",
2270
+ query: { workspace_id: options.workspaceId }
2271
+ });
2272
+ }
2273
+ };
2274
+ };
2275
+
2276
+ // src/ai/providers/openrouter.ts
2277
+ var DEFAULT_BASE_URL5 = "https://openrouter.ai/api";
2278
+ var MAX_APP_CATEGORIES = 2;
2279
+ var withoutLatestPrefix2 = (model) => model.startsWith("~") ? model.slice(1) : model;
2280
+ var modelForOpenAICapabilities = (model) => {
2281
+ const normalized = withoutLatestPrefix2(model);
2282
+ return normalized.startsWith("openai/") ? normalized.slice("openai/".length) : normalized;
2283
+ };
2284
+ var modelMatchesRule = (model, rule) => {
2285
+ const normalizedModel = withoutLatestPrefix2(model);
2286
+ const normalizedRule = withoutLatestPrefix2(rule);
2287
+ if (normalizedRule.endsWith("/*")) {
2288
+ return normalizedModel.startsWith(normalizedRule.slice(0, -1));
2289
+ }
2290
+ return normalizedModel === normalizedRule;
2291
+ };
2292
+ var providerMatchesRule = (provider, rule) => provider === rule || provider.startsWith(`${rule}/`);
2293
+ var assertNonEmptyPolicy = (label, value) => {
2294
+ if (value && value.length === 0) {
2295
+ throw new Error(`openrouter() ${label} must not be empty`);
2296
+ }
2297
+ };
2298
+ var assertRoutingPolicy = (config2) => {
2299
+ assertNonEmptyPolicy("allowedProviders", config2.allowedProviders);
2300
+ assertNonEmptyPolicy("routing.only", config2.routing?.only);
2301
+ assertNonEmptyPolicy("allowedPresets", config2.allowedPresets);
2302
+ if (config2.appCategories && config2.appCategories.length > MAX_APP_CATEGORIES) {
2303
+ throw new Error("openrouter() appCategories supports at most 2 entries");
2304
+ }
2305
+ if (!config2.allowedProviders)
2306
+ return;
2307
+ const selected = [
2308
+ ...config2.routing?.only ?? [],
2309
+ ...config2.routing?.order ?? []
2310
+ ];
2311
+ const denied = selected.find((provider) => !config2.allowedProviders.some((rule) => providerMatchesRule(provider, rule)));
2312
+ if (denied) {
2313
+ throw new Error(`openrouter() provider "${denied}" is outside allowedProviders`);
2314
+ }
2315
+ };
2316
+ var assertRequestRoutingPolicy = (routing, allowedProviders) => {
2317
+ assertRoutingPolicy({
2318
+ allowedProviders,
2319
+ apiKey: "policy-validation",
2320
+ routing
2321
+ });
2322
+ };
2323
+ var mapRouting = (routing, allowedProviders) => {
2324
+ const only = routing?.only ?? allowedProviders;
2325
+ const wire = {};
2326
+ if (typeof routing?.allowFallbacks === "boolean")
2327
+ wire.allow_fallbacks = routing.allowFallbacks;
2328
+ if (routing?.dataCollection)
2329
+ wire.data_collection = routing.dataCollection;
2330
+ if (typeof routing?.enforceDistillableText === "boolean")
2331
+ wire.enforce_distillable_text = routing.enforceDistillableText;
2332
+ if (routing?.ignore)
2333
+ wire.ignore = [...routing.ignore];
2334
+ if (routing?.maxPrice)
2335
+ wire.max_price = { ...routing.maxPrice };
2336
+ if (only)
2337
+ wire.only = [...only];
2338
+ if (routing?.order)
2339
+ wire.order = [...routing.order];
2340
+ if (routing?.preferredMaxLatency !== undefined)
2341
+ wire.preferred_max_latency = routing.preferredMaxLatency;
2342
+ if (routing?.preferredMinThroughput !== undefined)
2343
+ wire.preferred_min_throughput = routing.preferredMinThroughput;
2344
+ if (routing?.quantizations)
2345
+ wire.quantizations = [...routing.quantizations];
2346
+ if (typeof routing?.requireParameters === "boolean")
2347
+ wire.require_parameters = routing.requireParameters;
2348
+ if (routing?.sort)
2349
+ wire.sort = routing.sort;
2350
+ if (typeof routing?.zdr === "boolean")
2351
+ wire.zdr = routing.zdr;
2352
+ return wire;
2353
+ };
2354
+ var requestOptionsFor = (params, defaults) => {
2355
+ const supplied = params.providerOptions?.openrouter;
2356
+ if (supplied !== undefined && (typeof supplied !== "object" || !supplied)) {
2357
+ throw new Error("providerOptions.openrouter must be an object");
2358
+ }
2359
+ return {
2360
+ ...defaults,
2361
+ ...supplied
2362
+ };
2363
+ };
2364
+ var resolveAttributionHeaders = async (config2, params) => {
2365
+ const supplied = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2366
+ const headers = new Headers(supplied);
2367
+ if (config2.appUrl)
2368
+ headers.set("HTTP-Referer", config2.appUrl);
2369
+ if (config2.appName)
2370
+ headers.set("X-OpenRouter-Title", config2.appName);
2371
+ if (config2.appCategories?.length) {
2372
+ headers.set("X-OpenRouter-Categories", config2.appCategories.join(","));
2373
+ }
2374
+ const options = requestOptionsFor(params, config2.requestOptions);
2375
+ if (options.routerMetadata ?? true)
2376
+ headers.set("X-OpenRouter-Metadata", "enabled");
2377
+ if (options.sessionId)
2378
+ headers.set("X-Session-Id", options.sessionId);
2379
+ if (options.responseCache) {
2380
+ headers.set("X-OpenRouter-Cache", options.responseCache.enabled ? "true" : "false");
2381
+ if (options.responseCache.ttlSeconds !== undefined)
2382
+ headers.set("X-OpenRouter-Cache-TTL", String(options.responseCache.ttlSeconds));
2383
+ if (options.responseCache.clear)
2384
+ headers.set("X-OpenRouter-Cache-Clear", "true");
2385
+ }
2386
+ return headers;
2387
+ };
2388
+ var SECURITY_SENSITIVE_EXTRA_BODY_FIELDS = new Set([
2389
+ "messages",
2390
+ "model",
2391
+ "models",
2392
+ "plugins",
2393
+ "preset",
2394
+ "provider",
2395
+ "stream",
2396
+ "tools"
2397
+ ]);
2398
+ var assertAllowedPreset = (preset, allowedPresets) => {
2399
+ if (!preset)
2400
+ return;
2401
+ if (allowedPresets?.includes(preset))
2402
+ return;
2403
+ throw new Error(`OpenRouter preset "${preset}" is not allowed`);
2404
+ };
2405
+ var assertIndirectModels = (value, allowedModels, key = "") => {
2406
+ if (key === "model" && typeof value === "string")
2407
+ assertAllowedModel2(value, allowedModels);
2408
+ if ((key === "models" || key === "analysis_models") && Array.isArray(value)) {
2409
+ for (const model of value) {
2410
+ if (typeof model === "string")
2411
+ assertAllowedModel2(model, allowedModels);
2412
+ }
2413
+ }
2414
+ if (Array.isArray(value)) {
2415
+ for (const item of value)
2416
+ assertIndirectModels(item, allowedModels);
2417
+ } else if (value && typeof value === "object") {
2418
+ for (const [childKey, child] of Object.entries(value))
2419
+ assertIndirectModels(child, allowedModels, childKey);
2420
+ }
2421
+ };
2422
+ var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProviders) => {
2423
+ assertAllowedPreset(options.preset, allowedPresets);
2424
+ assertRequestRoutingPolicy(options.routing, allowedProviders);
2425
+ if (options.sessionId && options.sessionId.length > 256)
2426
+ throw new Error("OpenRouter sessionId must be at most 256 characters");
2427
+ const ttl = options.responseCache?.ttlSeconds;
2428
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 1 || ttl > 86400))
2429
+ throw new Error("OpenRouter response-cache TTL must be 1-86400 seconds");
2430
+ if (options.responseCache?.clear && !options.responseCache.enabled)
2431
+ throw new Error("OpenRouter cache clear requires response caching enabled");
2432
+ if (options.fallbackModels) {
2433
+ if (options.fallbackModels.length === 0)
2434
+ throw new Error("OpenRouter fallbackModels must not be empty");
2435
+ for (const model of options.fallbackModels)
2436
+ assertAllowedModel2(model, allowedModels);
2437
+ }
2438
+ assertIndirectModels(options.serverTools, allowedModels);
2439
+ assertIndirectModels(options.messagesTools, allowedModels);
2440
+ assertIndirectModels(options.plugins, allowedModels);
2441
+ if (options.extraBody) {
2442
+ const unsafe = Object.keys(options.extraBody).find((key) => SECURITY_SENSITIVE_EXTRA_BODY_FIELDS.has(key));
2443
+ if (unsafe)
2444
+ throw new Error(`OpenRouter extraBody cannot override "${unsafe}"`);
2445
+ }
2446
+ };
2447
+ var assertAllowedModel2 = (model, allowedModels) => {
2448
+ if (!allowedModels)
2449
+ return;
2450
+ if (allowedModels.some((rule) => modelMatchesRule(model, rule)))
2451
+ return;
2452
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
2453
+ };
2454
+ var snapshotPolicy = (config2) => ({
2455
+ allowedModels: config2.allowedModels ? [...config2.allowedModels] : undefined,
2456
+ allowedPresets: config2.allowedPresets ? [...config2.allowedPresets] : undefined
2457
+ });
2458
+ var transformOpenRouterRequest = (config2, allowedModels, allowedPresets, body, params, skin = "openai") => {
2459
+ const options = requestOptionsFor(params, config2.requestOptions);
2460
+ assertRequestOptions(options, allowedModels, allowedPresets, config2.allowedProviders);
2461
+ const transformed = { ...body, ...options.extraBody };
2462
+ if (options.audioOutput) {
2463
+ transformed.audio = options.audioOutput;
2464
+ transformed.modalities = ["text", "audio"];
2465
+ }
2466
+ if (skin === "openai") {
2467
+ const requestedReasoning = {};
2468
+ if (params.reasoning?.budgetTokens !== undefined) {
2469
+ requestedReasoning.max_tokens = params.reasoning.budgetTokens;
2470
+ delete transformed.reasoning_effort;
2471
+ } else if (params.reasoning?.effort) {
2472
+ requestedReasoning.effort = params.reasoning.effort;
2473
+ delete transformed.reasoning_effort;
2474
+ }
2475
+ if (options.reasoning) {
2476
+ Object.assign(requestedReasoning, options.reasoning);
2477
+ if (options.reasoning.maxTokens !== undefined) {
2478
+ requestedReasoning.max_tokens = options.reasoning.maxTokens;
2479
+ delete requestedReasoning.maxTokens;
2480
+ }
2481
+ }
2482
+ if (Object.keys(requestedReasoning).length > 0)
2483
+ transformed.reasoning = requestedReasoning;
2484
+ }
2485
+ const automaticCacheControl = params.promptCaching === true || params.cacheSystemPrompt === true ? { type: "ephemeral" } : undefined;
2486
+ const cacheControl = options.cacheControl ?? automaticCacheControl;
2487
+ if (cacheControl)
2488
+ transformed.cache_control = cacheControl;
2489
+ if (options.promptCacheKey)
2490
+ transformed.prompt_cache_key = options.promptCacheKey;
2491
+ if (options.promptCacheOptions)
2492
+ transformed.prompt_cache_options = options.promptCacheOptions;
2493
+ const routing = mapRouting({ ...config2.routing, ...options.routing }, config2.allowedProviders);
2494
+ if (Object.keys(routing).length > 0)
2495
+ transformed.provider = routing;
2496
+ if (options.fallbackModels)
2497
+ transformed.models = [...options.fallbackModels];
2498
+ if (options.includeReasoning !== undefined)
2499
+ transformed.include_reasoning = options.includeReasoning;
2500
+ if (options.maxToolCalls !== undefined)
2501
+ transformed.max_tool_calls = options.maxToolCalls;
2502
+ if (options.plugins)
2503
+ transformed.plugins = [...options.plugins];
2504
+ if (options.preset)
2505
+ transformed.preset = options.preset;
2506
+ if (options.serverTools) {
2507
+ transformed.tools = [
2508
+ ...Array.isArray(transformed.tools) ? transformed.tools : [],
2509
+ ...options.serverTools
2510
+ ];
2511
+ }
2512
+ if (options.messagesTools) {
2513
+ if (skin !== "messages")
2514
+ throw new Error("OpenRouter messagesTools requires openrouterMessages()");
2515
+ transformed.tools = [
2516
+ ...Array.isArray(transformed.tools) ? transformed.tools : [],
2517
+ ...options.messagesTools
2518
+ ];
2519
+ }
2520
+ if (options.serviceTier)
2521
+ transformed.service_tier = options.serviceTier;
2522
+ if (options.sessionId)
2523
+ transformed.session_id = options.sessionId;
2524
+ if (options.stopServerToolsWhen)
2525
+ transformed.stop_server_tools_when = options.stopServerToolsWhen;
2526
+ if (options.trace)
2527
+ transformed.trace = options.trace;
2528
+ if (options.transforms)
2529
+ transformed.transforms = [...options.transforms];
2530
+ if (options.user)
2531
+ transformed.user = options.user;
2532
+ if (options.verbosity)
2533
+ transformed.verbosity = options.verbosity;
2534
+ return transformed;
2535
+ };
2536
+ var withOpenRouterPolicy = (provider, allowedModels, allowedPresets) => ({
2537
+ stream: (params) => {
2538
+ if (params.model.startsWith("@preset/")) {
2539
+ assertAllowedPreset(params.model.slice("@preset/".length), allowedPresets);
2540
+ } else {
2541
+ const presetSeparator = params.model.indexOf("@preset/");
2542
+ if (presetSeparator >= 0) {
2543
+ assertAllowedModel2(params.model.slice(0, presetSeparator), allowedModels);
2544
+ assertAllowedPreset(params.model.slice(presetSeparator + "@preset/".length), allowedPresets);
2545
+ } else {
2546
+ assertAllowedModel2(params.model, allowedModels);
2547
+ }
2548
+ }
2549
+ return provider.stream(params);
2550
+ }
2551
+ });
2552
+ var openrouter = (config2) => {
2553
+ assertRoutingPolicy(config2);
2554
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
2555
+ const provider = openai({
2556
+ apiKey: config2.apiKey,
2557
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL5,
2558
+ fetch: config2.fetch,
2559
+ headers: (params) => resolveAttributionHeaders(config2, params),
2560
+ modelForCapabilities: modelForOpenAICapabilities,
2561
+ providerName: "openrouter",
2562
+ tokenSource: config2.tokenSource,
2563
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
2564
+ });
2565
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
2566
+ };
2567
+ var openrouterResponses = (config2) => {
2568
+ assertRoutingPolicy(config2);
2569
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
2570
+ const provider = openaiResponses({
2571
+ apiKey: config2.apiKey,
2572
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL5,
2573
+ fetch: config2.fetch,
2574
+ headers: (params) => resolveAttributionHeaders(config2, params),
2575
+ modelForCapabilities: modelForOpenAICapabilities,
2576
+ providerName: "openrouter",
2577
+ tokenSource: config2.tokenSource,
2578
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
2579
+ });
2580
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
2581
+ };
2582
+ var openrouterMessages = (config2) => {
2583
+ assertRoutingPolicy(config2);
2584
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
2585
+ const provider = anthropic({
2586
+ apiKey: config2.apiKey,
2587
+ authStyle: "bearer",
2588
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL5,
2589
+ fetch: config2.fetch,
2590
+ headers: (params) => resolveAttributionHeaders(config2, params),
2591
+ providerName: "openrouter",
2592
+ tokenSource: config2.tokenSource,
2593
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params, "messages")
2594
+ });
2595
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
2596
+ };
2597
+ export {
2598
+ verifyOpenRouterWebhookSignature,
2599
+ openrouterResponses,
2600
+ openrouterMessages,
2601
+ openrouter,
2602
+ openRouterModelMatchesRule,
2603
+ createOpenRouterClient
2604
+ };
2605
+
2606
+ //# debugId=29E9DB40EA495C0E64756E2164756E21
2607
+ //# sourceMappingURL=openrouter.js.map