@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.
- package/README.md +127 -0
- package/dist/ai/client/index.js +30 -1
- package/dist/ai/client/index.js.map +5 -5
- package/dist/ai/index.js +1174 -92
- package/dist/ai/index.js.map +18 -16
- package/dist/ai/providers/anthropic.js +105 -26
- package/dist/ai/providers/anthropic.js.map +5 -5
- package/dist/ai/providers/gemini.js +37 -4
- package/dist/ai/providers/gemini.js.map +5 -5
- package/dist/ai/providers/ollama.js +7 -3
- package/dist/ai/providers/ollama.js.map +4 -4
- package/dist/ai/providers/openai.js +177 -36
- package/dist/ai/providers/openai.js.map +5 -5
- package/dist/ai/providers/openaiCompatible.js +177 -36
- package/dist/ai/providers/openaiCompatible.js.map +5 -5
- package/dist/ai/providers/openaiResponses.js +147 -33
- package/dist/ai/providers/openaiResponses.js.map +5 -5
- package/dist/ai/providers/openrouter.js +2607 -0
- package/dist/ai/providers/openrouter.js.map +18 -0
- package/dist/angular/ai/index.js +30 -1
- package/dist/angular/ai/index.js.map +5 -5
- package/dist/react/ai/index.js +30 -1
- package/dist/react/ai/index.js.map +5 -5
- package/dist/src/ai/client/actions.d.ts +53 -0
- package/dist/src/ai/errors/providerError.d.ts +3 -0
- package/dist/src/ai/generateAI.d.ts +4 -1
- package/dist/src/ai/index.d.ts +2 -0
- package/dist/src/ai/providers/openai.d.ts +7 -3
- package/dist/src/ai/providers/openaiResponses.d.ts +9 -4
- package/dist/src/ai/providers/openrouter.d.ts +157 -0
- package/dist/src/ai/providers/openrouterClient.d.ts +276 -0
- package/dist/src/ai/streamAIWithTools.d.ts +6 -0
- package/dist/svelte/ai/index.js +30 -1
- package/dist/svelte/ai/index.js.map +5 -5
- package/dist/types/ai.d.ts +116 -8
- package/dist/types/anthropic.d.ts +13 -5
- package/dist/vue/ai/index.js +30 -1
- package/dist/vue/ai/index.js.map +5 -5
- 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
|