@x12i/openrouter-runtime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1212 @@
1
+ // src/config/defaults.ts
2
+ function resolveRuntimeOptions(options) {
3
+ return {
4
+ ...options,
5
+ baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1",
6
+ defaultHeaders: options.defaultHeaders ?? {},
7
+ fetch: options.fetch ?? globalThis.fetch
8
+ };
9
+ }
10
+ function resolveDefaults(options) {
11
+ const retry = options.defaults?.retry;
12
+ return {
13
+ ...options.defaults,
14
+ apiMode: options.defaults?.apiMode ?? "auto",
15
+ timeoutMs: options.defaults?.timeoutMs ?? 12e4,
16
+ retry: {
17
+ enabled: retry?.enabled ?? true,
18
+ maxAttempts: retry?.maxAttempts ?? options.defaults?.maxRetries ?? 3,
19
+ baseDelayMs: retry?.baseDelayMs ?? 500,
20
+ maxDelayMs: retry?.maxDelayMs ?? 5e3
21
+ },
22
+ requireCitationsWhenSearchUsed: options.defaults?.requireCitationsWhenSearchUsed ?? true,
23
+ onPolicyViolation: options.defaults?.onPolicyViolation ?? "return_error",
24
+ serverTools: options.defaults?.serverTools ?? {
25
+ datetime: {
26
+ mode: "allowed",
27
+ timezone: "UTC"
28
+ }
29
+ }
30
+ };
31
+ }
32
+
33
+ // src/openrouter/errors.ts
34
+ var RuntimeConfigError = class extends Error {
35
+ constructor(code, message, details) {
36
+ super(message);
37
+ this.code = code;
38
+ this.details = details;
39
+ this.name = "RuntimeConfigError";
40
+ }
41
+ code;
42
+ details;
43
+ };
44
+ var OpenRouterHttpError = class extends Error {
45
+ constructor(status, code, message, body, retryable = false) {
46
+ super(message);
47
+ this.status = status;
48
+ this.code = code;
49
+ this.body = body;
50
+ this.retryable = retryable;
51
+ this.name = "OpenRouterHttpError";
52
+ }
53
+ status;
54
+ code;
55
+ body;
56
+ retryable;
57
+ };
58
+
59
+ // src/utils/delay.ts
60
+ function delay(ms) {
61
+ return new Promise((resolve) => setTimeout(resolve, ms));
62
+ }
63
+ function retryDelay(attempt, baseDelayMs, maxDelayMs) {
64
+ const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** Math.max(0, attempt - 1));
65
+ const jitter = Math.floor(Math.random() * Math.min(250, exponential));
66
+ return exponential + jitter;
67
+ }
68
+
69
+ // src/openrouter/http-client.ts
70
+ async function sendOpenRouterRequest(compiled, options, defaults, timeoutMs) {
71
+ const attempts = defaults.retry.enabled ? defaults.retry.maxAttempts : 1;
72
+ let lastError;
73
+ for (let attempt = 1; attempt <= attempts; attempt++) {
74
+ try {
75
+ return await sendOnce(compiled, options, timeoutMs);
76
+ } catch (error) {
77
+ lastError = error;
78
+ const retryable = error instanceof OpenRouterHttpError ? error.retryable : true;
79
+ if (!retryable || attempt >= attempts) throw error;
80
+ await delay(retryDelay(attempt, defaults.retry.baseDelayMs, defaults.retry.maxDelayMs));
81
+ }
82
+ }
83
+ throw lastError;
84
+ }
85
+ async function sendOnce(compiled, options, timeoutMs) {
86
+ const controller = new AbortController();
87
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
88
+ try {
89
+ const response = await options.fetch(compiled.url, {
90
+ method: "POST",
91
+ headers: compiled.headers,
92
+ body: JSON.stringify(compiled.body),
93
+ signal: controller.signal
94
+ });
95
+ const text = await response.text();
96
+ const body = text ? parseBody(text) : void 0;
97
+ if (!response.ok) {
98
+ throw new OpenRouterHttpError(
99
+ response.status,
100
+ statusToCode(response.status),
101
+ extractMessage(body) ?? `OpenRouter request failed with status ${response.status}.`,
102
+ body,
103
+ isRetryableStatus(response.status)
104
+ );
105
+ }
106
+ return body;
107
+ } catch (error) {
108
+ if (error instanceof OpenRouterHttpError) throw error;
109
+ if (error instanceof Error && error.name === "AbortError") {
110
+ throw new OpenRouterHttpError(408, "OPENROUTER_TIMEOUT", "OpenRouter request timed out.", void 0, true);
111
+ }
112
+ throw error;
113
+ } finally {
114
+ clearTimeout(timeout);
115
+ }
116
+ }
117
+ function parseBody(text) {
118
+ try {
119
+ return JSON.parse(text);
120
+ } catch {
121
+ return text;
122
+ }
123
+ }
124
+ function extractMessage(body) {
125
+ if (typeof body !== "object" || body === null) return void 0;
126
+ const record = body;
127
+ const error = record.error;
128
+ if (typeof error === "object" && error !== null && typeof error.message === "string") {
129
+ return error.message;
130
+ }
131
+ return typeof record.message === "string" ? record.message : void 0;
132
+ }
133
+ function statusToCode(status) {
134
+ if (status === 401) return "OPENROUTER_AUTH_FAILED";
135
+ if (status === 429) return "OPENROUTER_RATE_LIMITED";
136
+ return "OPENROUTER_REQUEST_FAILED";
137
+ }
138
+ function isRetryableStatus(status) {
139
+ return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
140
+ }
141
+
142
+ // src/utils/json.ts
143
+ function parseJsonMaybe(value) {
144
+ if (typeof value !== "string") return value;
145
+ if (value.trim() === "") return {};
146
+ try {
147
+ return JSON.parse(value);
148
+ } catch {
149
+ return value;
150
+ }
151
+ }
152
+ function stringifyToolResult(value) {
153
+ if (typeof value === "string") return value;
154
+ return JSON.stringify(value);
155
+ }
156
+ function isRecord(value) {
157
+ return typeof value === "object" && value !== null && !Array.isArray(value);
158
+ }
159
+
160
+ // src/function-tools/tool-messages.ts
161
+ function resultToChatToolMessage(result) {
162
+ return {
163
+ role: "tool",
164
+ toolCallId: result.callId,
165
+ name: result.name,
166
+ content: stringifyToolResult(result.status === "completed" ? result.result : { error: result.error })
167
+ };
168
+ }
169
+ function resultToResponsesInput(result) {
170
+ return {
171
+ type: "function_call_output",
172
+ call_id: result.callId,
173
+ output: stringifyToolResult(result.status === "completed" ? result.result : { error: result.error })
174
+ };
175
+ }
176
+
177
+ // src/normalize/citations.ts
178
+ function extractCitations(value) {
179
+ const citations = [];
180
+ visit(value, (item) => {
181
+ const url = pickString(item, ["url", "uri"]);
182
+ if (!url) return;
183
+ citations.push({
184
+ type: "url",
185
+ url,
186
+ sourceProvider: "openrouter",
187
+ raw: item,
188
+ ...optional("title", pickString(item, ["title", "name"])),
189
+ ...optional("excerpt", pickString(item, ["excerpt", "content", "text"])),
190
+ ...optional("startIndex", pickNumber(item, ["start_index", "startIndex"])),
191
+ ...optional("endIndex", pickNumber(item, ["end_index", "endIndex"]))
192
+ });
193
+ });
194
+ return dedupe(citations);
195
+ }
196
+ function optional(key, value) {
197
+ return value === void 0 ? {} : { [key]: value };
198
+ }
199
+ function visit(value, onCitation) {
200
+ if (Array.isArray(value)) {
201
+ for (const item of value) visit(item, onCitation);
202
+ return;
203
+ }
204
+ if (!isRecord(value)) return;
205
+ if (value.type === "url_citation" || value.type === "citation" || value.url || value.uri) onCitation(value);
206
+ for (const key of ["annotations", "citations"]) visit(value[key], onCitation);
207
+ }
208
+ function pickString(record, keys) {
209
+ for (const key of keys) if (typeof record[key] === "string") return record[key];
210
+ return void 0;
211
+ }
212
+ function pickNumber(record, keys) {
213
+ for (const key of keys) if (typeof record[key] === "number") return record[key];
214
+ return void 0;
215
+ }
216
+ function dedupe(citations) {
217
+ const seen = /* @__PURE__ */ new Set();
218
+ return citations.filter((citation) => {
219
+ const key = `${citation.url}:${citation.startIndex ?? ""}:${citation.endIndex ?? ""}`;
220
+ if (seen.has(key)) return false;
221
+ seen.add(key);
222
+ return true;
223
+ });
224
+ }
225
+
226
+ // src/normalize/images.ts
227
+ function extractImages(value) {
228
+ const images = [];
229
+ visit2(value, (record) => {
230
+ const imageUrl = stringValue(record.image_url) ?? stringValue(record.url);
231
+ if (imageUrl) images.push({ imageUrl, status: "ok", raw: record });
232
+ });
233
+ return images;
234
+ }
235
+ function visit2(value, onImage) {
236
+ if (Array.isArray(value)) {
237
+ for (const item of value) visit2(item, onImage);
238
+ return;
239
+ }
240
+ if (!isRecord(value)) return;
241
+ if (value.type === "image" || value.type === "image_generation_call" || value.image_url || value.url) {
242
+ if (value.image_url || value.type === "image_generation_call") onImage(value);
243
+ }
244
+ for (const nested of Object.values(value)) visit2(nested, onImage);
245
+ }
246
+ function stringValue(value) {
247
+ if (typeof value === "string") return value;
248
+ if (isRecord(value) && typeof value.url === "string") return value.url;
249
+ return void 0;
250
+ }
251
+
252
+ // src/normalize/patches.ts
253
+ function extractPatches(value) {
254
+ const patches = [];
255
+ visit3(value, (record) => {
256
+ const operation = normalizeOperation(record);
257
+ if (!operation) return;
258
+ patches.push({
259
+ callId: stringValue2(record.call_id) ?? stringValue2(record.id) ?? "patch_call",
260
+ status: record.status === "failed" ? "failed" : "completed",
261
+ operation,
262
+ raw: record
263
+ });
264
+ });
265
+ return patches;
266
+ }
267
+ function visit3(value, onPatch) {
268
+ if (Array.isArray(value)) {
269
+ for (const item of value) visit3(item, onPatch);
270
+ return;
271
+ }
272
+ if (!isRecord(value)) return;
273
+ if (value.type === "apply_patch_call" || value.type === "patch" || value.operation) onPatch(value);
274
+ for (const nested of Object.values(value)) visit3(nested, onPatch);
275
+ }
276
+ function normalizeOperation(record) {
277
+ const operation = isRecord(record.operation) ? record.operation : record;
278
+ const type = stringValue2(operation.type) ?? stringValue2(operation.operation);
279
+ const path = stringValue2(operation.path) ?? stringValue2(operation.file);
280
+ if (!type || !path) return void 0;
281
+ if (type === "delete_file") return { type, path };
282
+ if (type === "create_file" || type === "update_file") {
283
+ return { type, path, diff: stringValue2(operation.diff) ?? stringValue2(operation.patch) ?? "" };
284
+ }
285
+ return void 0;
286
+ }
287
+ function stringValue2(value) {
288
+ return typeof value === "string" ? value : void 0;
289
+ }
290
+
291
+ // src/normalize/tool-usage.ts
292
+ var serverToolKeys = [
293
+ "webSearch",
294
+ "webFetch",
295
+ "datetime",
296
+ "imageGeneration",
297
+ "applyPatch",
298
+ "fusion",
299
+ "advisor",
300
+ "subagent"
301
+ ];
302
+ var rawTypeToKey = {
303
+ "openrouter:web_search": "webSearch",
304
+ web_search: "webSearch",
305
+ webSearch: "webSearch",
306
+ "openrouter:web_fetch": "webFetch",
307
+ web_fetch: "webFetch",
308
+ webFetch: "webFetch",
309
+ "openrouter:datetime": "datetime",
310
+ datetime: "datetime",
311
+ "openrouter:image_generation": "imageGeneration",
312
+ image_generation: "imageGeneration",
313
+ imageGeneration: "imageGeneration",
314
+ "openrouter:apply_patch": "applyPatch",
315
+ apply_patch: "applyPatch",
316
+ applyPatch: "applyPatch",
317
+ "openrouter:fusion": "fusion",
318
+ fusion: "fusion",
319
+ "openrouter:advisor": "advisor",
320
+ advisor: "advisor",
321
+ "openrouter:subagent": "subagent",
322
+ subagent: "subagent"
323
+ };
324
+ function createToolUsage(policy, raw, functionTools = []) {
325
+ const counts = detectServerToolCounts(raw);
326
+ const serverTools = Object.fromEntries(
327
+ serverToolKeys.map((key) => {
328
+ const item = policyValue(policy, key);
329
+ const requested = Array.isArray(item) ? item.some((x) => x.mode !== "disabled") : !!item && item.mode !== "disabled";
330
+ const required = Array.isArray(item) ? item.some((x) => x.mode === "required") : !!item && item.mode === "required";
331
+ return [
332
+ key,
333
+ {
334
+ requested,
335
+ required,
336
+ used: (counts[key] ?? 0) > 0,
337
+ callCount: counts[key] ?? 0
338
+ }
339
+ ];
340
+ })
341
+ );
342
+ return { serverTools, functionTools };
343
+ }
344
+ function policyValue(policy, key) {
345
+ return policy?.[key];
346
+ }
347
+ function detectServerToolCounts(raw) {
348
+ const counts = {};
349
+ visit4(raw, (type) => {
350
+ const key = rawTypeToKey[type];
351
+ if (key) counts[key] = (counts[key] ?? 0) + 1;
352
+ });
353
+ return counts;
354
+ }
355
+ function visit4(value, onType) {
356
+ if (Array.isArray(value)) {
357
+ for (const item of value) visit4(item, onType);
358
+ return;
359
+ }
360
+ if (typeof value !== "object" || value === null) return;
361
+ const record = value;
362
+ if (typeof record.type === "string") onType(record.type);
363
+ if (typeof record.name === "string") onType(record.name);
364
+ for (const nested of Object.values(record)) visit4(nested, onType);
365
+ }
366
+
367
+ // src/normalize/usage.ts
368
+ function normalizeUsage(usage) {
369
+ if (!isRecord(usage)) return void 0;
370
+ const details = isRecord(usage.completion_tokens_details) ? usage.completion_tokens_details : void 0;
371
+ return {
372
+ raw: usage,
373
+ ...optional2("inputTokens", numberValue(usage.prompt_tokens) ?? numberValue(usage.input_tokens)),
374
+ ...optional2("outputTokens", numberValue(usage.completion_tokens) ?? numberValue(usage.output_tokens)),
375
+ ...optional2("totalTokens", numberValue(usage.total_tokens)),
376
+ ...optional2("reasoningTokens", numberValue(details?.reasoning_tokens) ?? numberValue(usage.reasoning_tokens)),
377
+ ...optional2("costUsd", numberValue(usage.cost))
378
+ };
379
+ }
380
+ function numberValue(value) {
381
+ return typeof value === "number" ? value : void 0;
382
+ }
383
+ function optional2(key, value) {
384
+ return value === void 0 ? {} : { [key]: value };
385
+ }
386
+
387
+ // src/utils/ids.ts
388
+ function createRuntimeId(prefix = "orun") {
389
+ const cryptoObject = globalThis.crypto;
390
+ if (cryptoObject?.randomUUID) return `${prefix}_${cryptoObject.randomUUID()}`;
391
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
392
+ }
393
+
394
+ // src/normalize/normalize-chat-response.ts
395
+ function normalizeChatResponse(params) {
396
+ const responseRecord = isRecord(params.response) ? params.response : {};
397
+ const choice = Array.isArray(responseRecord.choices) && isRecord(responseRecord.choices[0]) ? responseRecord.choices[0] : {};
398
+ const message = isRecord(choice.message) ? choice.message : {};
399
+ const text = normalizeContentToText(message.content);
400
+ return {
401
+ id: typeof responseRecord.id === "string" ? responseRecord.id : createRuntimeId("orresp"),
402
+ status: "completed",
403
+ apiMode: params.apiMode,
404
+ model: typeof responseRecord.model === "string" ? responseRecord.model : params.model,
405
+ text,
406
+ citations: extractCitations([message.annotations, responseRecord.citations, params.response]),
407
+ images: extractImages(params.response),
408
+ patches: extractPatches(params.response),
409
+ toolUsage: createToolUsage(params.serverTools, params.response, params.functionToolUsage),
410
+ warnings: params.warnings,
411
+ errors: [],
412
+ raw: {
413
+ request: params.requestBody,
414
+ response: params.response
415
+ },
416
+ ...optional3("usage", normalizeUsage(responseRecord.usage)),
417
+ ...optional3("finishReason", typeof choice.finish_reason === "string" ? choice.finish_reason : void 0),
418
+ ...optional3("metadata", params.metadata)
419
+ };
420
+ }
421
+ function normalizeContentToText(content) {
422
+ if (typeof content === "string") return content;
423
+ if (!Array.isArray(content)) return "";
424
+ return content.map((part) => {
425
+ if (typeof part === "string") return part;
426
+ if (isRecord(part) && typeof part.text === "string") return part.text;
427
+ if (isRecord(part) && typeof part.content === "string") return part.content;
428
+ return "";
429
+ }).filter(Boolean).join("");
430
+ }
431
+ function optional3(key, value) {
432
+ return value === void 0 ? {} : { [key]: value };
433
+ }
434
+
435
+ // src/policies/post-response-validation.ts
436
+ async function validatePostResponse(response, defaults) {
437
+ const errors = [...response.errors];
438
+ const warnings = [...response.warnings];
439
+ for (const [key, usage] of Object.entries(response.toolUsage.serverTools)) {
440
+ if (usage.required && !usage.used) {
441
+ const code = `${toScreamingSnake(key)}_REQUIRED_BUT_NOT_USED`;
442
+ usage.policyViolation = code;
443
+ const violation = { code, message: `${key} was required but no usage evidence was found.` };
444
+ if (defaults.onPolicyViolation === "throw") {
445
+ errors.push({ ...violation, source: "policy" });
446
+ } else {
447
+ warnings.push(violation);
448
+ }
449
+ }
450
+ }
451
+ if (defaults.requireCitationsWhenSearchUsed && response.toolUsage.serverTools.webSearch.requested && response.citations.length === 0) {
452
+ warnings.push({
453
+ code: "CITATIONS_REQUIRED_BUT_MISSING",
454
+ message: "Web search was requested but no citations were extracted."
455
+ });
456
+ }
457
+ return {
458
+ ...response,
459
+ status: errors.some((error) => error.source === "policy") ? "policy_violation" : response.status,
460
+ errors,
461
+ warnings
462
+ };
463
+ }
464
+ function toScreamingSnake(value) {
465
+ return value.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
466
+ }
467
+
468
+ // src/config/normalize-model.ts
469
+ function normalizeModel(model) {
470
+ if (!model.endsWith(":online")) {
471
+ return { model, warnings: [] };
472
+ }
473
+ return {
474
+ model: model.slice(0, -":online".length),
475
+ warnings: [
476
+ {
477
+ code: "OPENROUTER_ONLINE_VARIANT_DEPRECATED",
478
+ message: "The :online model suffix is deprecated. Use serverTools.webSearch instead."
479
+ }
480
+ ],
481
+ impliedServerTools: {
482
+ webSearch: { mode: "allowed" }
483
+ }
484
+ };
485
+ }
486
+
487
+ // src/config/validate-runtime-request.ts
488
+ function validateRuntimeRequest(request, options, apiMode, model, serverTools) {
489
+ if (!options.apiKey) throw new RuntimeConfigError("OPENROUTER_API_KEY_MISSING", "OpenRouter API key is required.");
490
+ if (!model) throw new RuntimeConfigError("MODEL_MISSING", "Runtime request must include a model.");
491
+ if (!request.messages?.length && !request.input && !request.prompt) {
492
+ throw new RuntimeConfigError("INPUT_MISSING", "Runtime request must include messages, input, or prompt.");
493
+ }
494
+ if (serverTools?.applyPatch?.mode && serverTools.applyPatch.mode !== "disabled" && apiMode !== "responses") {
495
+ throw new RuntimeConfigError("APPLY_PATCH_REQUIRES_RESPONSES_API", "apply_patch requires the Responses API.");
496
+ }
497
+ if (serverTools?.applyPatch?.behavior === "apply_with_callback" && !options.patchApplier) {
498
+ throw new RuntimeConfigError("PATCH_APPLIER_REQUIRED", "apply_with_callback requires options.patchApplier.");
499
+ }
500
+ validateWebSearch(serverTools);
501
+ validateWebFetch(serverTools);
502
+ validateDomains(serverTools?.webSearch?.allowedDomains, "allowedDomains");
503
+ validateDomains(serverTools?.webSearch?.excludedDomains, "excludedDomains");
504
+ validateDomains(serverTools?.webFetch?.allowedDomains, "allowedDomains");
505
+ validateDomains(serverTools?.webFetch?.blockedDomains, "blockedDomains");
506
+ validateFunctionTools(request.functionTools);
507
+ validateAdvisorNames(serverTools);
508
+ validateToolChoice(request, serverTools);
509
+ }
510
+ function validateWebSearch(policy) {
511
+ const search = policy?.webSearch;
512
+ if (!search || search.mode === "disabled") return;
513
+ const maxResults = search.maxResults ?? 5;
514
+ const maxTotalResults = search.maxTotalResults ?? 15;
515
+ if (maxResults < 1 || maxResults > 25) {
516
+ throw new RuntimeConfigError("INVALID_WEB_SEARCH_MAX_RESULTS", "webSearch.maxResults must be between 1 and 25.");
517
+ }
518
+ if (search.engine === "perplexity" && maxResults > 20) {
519
+ throw new RuntimeConfigError("PERPLEXITY_MAX_RESULTS_EXCEEDED", "Perplexity search supports at most 20 results.");
520
+ }
521
+ if (maxTotalResults < maxResults) {
522
+ throw new RuntimeConfigError("MAX_TOTAL_RESULTS_LT_MAX_RESULTS", "maxTotalResults must be >= maxResults.");
523
+ }
524
+ }
525
+ function validateWebFetch(policy) {
526
+ const fetch = policy?.webFetch;
527
+ if (!fetch || fetch.mode === "disabled") return;
528
+ if (fetch.maxUses !== void 0 && fetch.maxUses < 1) {
529
+ throw new RuntimeConfigError("INVALID_WEB_FETCH_MAX_USES", "webFetch.maxUses must be >= 1.");
530
+ }
531
+ }
532
+ function validateDomains(domains, field) {
533
+ for (const domain of domains ?? []) {
534
+ if (/^https?:\/\//i.test(domain) || domain.includes("/") || domain.trim() !== domain || domain === "") {
535
+ throw new RuntimeConfigError("INVALID_DOMAIN", `${field} must contain domains, not URLs.`, { domain });
536
+ }
537
+ }
538
+ }
539
+ function validateFunctionTools(tools) {
540
+ const names = /* @__PURE__ */ new Set();
541
+ for (const tool of tools ?? []) {
542
+ if (names.has(tool.name)) {
543
+ throw new RuntimeConfigError("DUPLICATE_FUNCTION_TOOL", `Function tool ${tool.name} is defined more than once.`);
544
+ }
545
+ names.add(tool.name);
546
+ }
547
+ }
548
+ function validateAdvisorNames(policy) {
549
+ const advisors = Array.isArray(policy?.advisor) ? policy.advisor : policy?.advisor ? [policy.advisor] : [];
550
+ const names = /* @__PURE__ */ new Set();
551
+ for (const advisor of advisors) {
552
+ if (!advisor.name) continue;
553
+ if (names.has(advisor.name)) {
554
+ throw new RuntimeConfigError("DUPLICATE_ADVISOR_NAME", `Advisor ${advisor.name} is defined more than once.`);
555
+ }
556
+ names.add(advisor.name);
557
+ }
558
+ }
559
+ function validateToolChoice(request, policy) {
560
+ const choice = request.toolChoice;
561
+ if (!choice || typeof choice === "string") return;
562
+ if (choice.type === "function") {
563
+ if (!request.functionTools?.some((tool) => tool.name === choice.functionName)) {
564
+ throw new RuntimeConfigError("TOOL_CHOICE_FUNCTION_NOT_ENABLED", `Function tool ${choice.functionName} is not enabled.`);
565
+ }
566
+ return;
567
+ }
568
+ const map = {
569
+ web_search: policy?.webSearch,
570
+ web_fetch: policy?.webFetch,
571
+ datetime: policy?.datetime,
572
+ image_generation: policy?.imageGeneration,
573
+ apply_patch: policy?.applyPatch,
574
+ fusion: policy?.fusion,
575
+ advisor: policy?.advisor,
576
+ subagent: policy?.subagent
577
+ };
578
+ const selected = map[choice.serverTool];
579
+ const enabled2 = Array.isArray(selected) ? selected.some((item) => item.mode !== "disabled") : !!selected && selected.mode !== "disabled";
580
+ if (!enabled2) {
581
+ throw new RuntimeConfigError("TOOL_CHOICE_SERVER_TOOL_NOT_ENABLED", `Server tool ${choice.serverTool} is not enabled.`);
582
+ }
583
+ }
584
+
585
+ // src/openrouter/endpoints.ts
586
+ function endpointFor(baseUrl, apiMode) {
587
+ const trimmed = baseUrl.replace(/\/+$/, "");
588
+ return apiMode === "chat" ? `${trimmed}/chat/completions` : `${trimmed}/responses`;
589
+ }
590
+
591
+ // src/openrouter/headers.ts
592
+ function buildHeaders(options) {
593
+ return {
594
+ "content-type": "application/json",
595
+ authorization: `Bearer ${options.apiKey}`,
596
+ ...options.appAttribution?.siteUrl ? { "HTTP-Referer": options.appAttribution.siteUrl } : {},
597
+ ...options.appAttribution?.appName ? { "X-Title": options.appAttribution.appName } : {},
598
+ ...options.defaultHeaders ?? {}
599
+ };
600
+ }
601
+
602
+ // src/tools/nested-tools.ts
603
+ var allowedNestedTypes = /* @__PURE__ */ new Set([
604
+ "openrouter:web_search",
605
+ "openrouter:web_fetch",
606
+ "openrouter:datetime",
607
+ "openrouter:image_generation"
608
+ ]);
609
+ function validateNestedTools(tools) {
610
+ for (const tool of tools ?? []) {
611
+ if (!allowedNestedTypes.has(tool.type)) {
612
+ throw new RuntimeConfigError(
613
+ "INVALID_NESTED_SERVER_TOOL",
614
+ `Nested server tool ${tool.type} is not allowed.`
615
+ );
616
+ }
617
+ }
618
+ }
619
+
620
+ // src/tools/server-tools.ts
621
+ function cleanParameters(parameters) {
622
+ return Object.fromEntries(Object.entries(parameters).filter(([, value]) => value !== void 0));
623
+ }
624
+ function enabled(policy) {
625
+ return !!policy && policy.mode !== "disabled";
626
+ }
627
+ function buildOpenRouterServerTools(policy) {
628
+ const tools = [];
629
+ const webSearch = buildWebSearchTool(policy?.webSearch);
630
+ if (webSearch) tools.push(webSearch);
631
+ const webFetch = buildWebFetchTool(policy?.webFetch);
632
+ if (webFetch) tools.push(webFetch);
633
+ const datetime = buildDatetimeTool(policy?.datetime);
634
+ if (datetime) tools.push(datetime);
635
+ const image = buildImageGenerationTool(policy?.imageGeneration);
636
+ if (image) tools.push(image);
637
+ const patch = buildApplyPatchTool(policy?.applyPatch);
638
+ if (patch) tools.push(patch);
639
+ const fusion = buildFusionTool(policy?.fusion);
640
+ if (fusion) tools.push(fusion);
641
+ tools.push(...buildAdvisorTools(policy?.advisor));
642
+ const subagent = buildSubagentTool(policy?.subagent);
643
+ if (subagent) tools.push(subagent);
644
+ return tools;
645
+ }
646
+ function buildWebSearchTool(policy) {
647
+ if (!enabled(policy)) return void 0;
648
+ return {
649
+ type: "openrouter:web_search",
650
+ parameters: cleanParameters({
651
+ engine: policy?.engine ?? "auto",
652
+ max_results: policy?.maxResults ?? 5,
653
+ max_total_results: policy?.maxTotalResults ?? 15,
654
+ search_context_size: policy?.searchContextSize ?? "medium",
655
+ allowed_domains: policy?.allowedDomains,
656
+ excluded_domains: policy?.excludedDomains,
657
+ user_location: policy?.userLocation
658
+ })
659
+ };
660
+ }
661
+ function buildWebFetchTool(policy) {
662
+ if (!enabled(policy)) return void 0;
663
+ return {
664
+ type: "openrouter:web_fetch",
665
+ parameters: cleanParameters({
666
+ engine: policy?.engine ?? "auto",
667
+ max_uses: policy?.maxUses ?? 10,
668
+ max_content_tokens: policy?.maxContentTokens ?? 5e4,
669
+ allowed_domains: policy?.allowedDomains,
670
+ blocked_domains: policy?.blockedDomains
671
+ })
672
+ };
673
+ }
674
+ function buildDatetimeTool(policy) {
675
+ if (!enabled(policy)) return void 0;
676
+ return {
677
+ type: "openrouter:datetime",
678
+ parameters: cleanParameters({ timezone: policy?.timezone ?? "UTC" })
679
+ };
680
+ }
681
+ function buildImageGenerationTool(policy) {
682
+ if (!enabled(policy)) return void 0;
683
+ return {
684
+ type: "openrouter:image_generation",
685
+ parameters: cleanParameters({
686
+ model: policy?.model ?? "openai/gpt-5-image",
687
+ quality: policy?.quality,
688
+ size: policy?.size,
689
+ aspect_ratio: policy?.aspectRatio,
690
+ background: policy?.background,
691
+ output_format: policy?.outputFormat,
692
+ output_compression: policy?.outputCompression,
693
+ moderation: policy?.moderation
694
+ })
695
+ };
696
+ }
697
+ function buildApplyPatchTool(policy) {
698
+ if (!enabled(policy)) return void 0;
699
+ return { type: "openrouter:apply_patch" };
700
+ }
701
+ function buildFusionTool(policy) {
702
+ if (!enabled(policy)) return void 0;
703
+ return {
704
+ type: "openrouter:fusion",
705
+ parameters: cleanParameters({
706
+ analysis_models: policy?.analysisModels,
707
+ model: policy?.judgeModel,
708
+ max_tool_calls: policy?.maxToolCalls ?? 8,
709
+ max_completion_tokens: policy?.maxCompletionTokens,
710
+ reasoning: policy?.reasoning,
711
+ temperature: policy?.temperature
712
+ })
713
+ };
714
+ }
715
+ function buildAdvisorTools(policy) {
716
+ const advisors = Array.isArray(policy) ? policy : policy ? [policy] : [];
717
+ return advisors.filter(enabled).map((advisor) => {
718
+ validateNestedTools(advisor.tools);
719
+ return {
720
+ type: "openrouter:advisor",
721
+ parameters: cleanParameters({
722
+ name: advisor.name,
723
+ model: advisor.model,
724
+ instructions: advisor.instructions,
725
+ tools: advisor.tools,
726
+ forward_transcript: advisor.forwardTranscript ?? false,
727
+ stream: advisor.stream,
728
+ max_tool_calls: advisor.maxToolCalls,
729
+ max_completion_tokens: advisor.maxCompletionTokens,
730
+ reasoning: advisor.reasoning,
731
+ temperature: advisor.temperature
732
+ })
733
+ };
734
+ });
735
+ }
736
+ function buildSubagentTool(policy) {
737
+ if (!policy || policy.mode === "disabled") return void 0;
738
+ const subagent = policy;
739
+ validateNestedTools(subagent.tools);
740
+ return {
741
+ type: "openrouter:subagent",
742
+ parameters: cleanParameters({
743
+ model: subagent.model,
744
+ instructions: subagent.instructions,
745
+ tools: subagent.tools,
746
+ max_tool_calls: subagent.maxToolCalls,
747
+ max_completion_tokens: subagent.maxCompletionTokens,
748
+ reasoning: subagent.reasoning,
749
+ temperature: subagent.temperature
750
+ })
751
+ };
752
+ }
753
+
754
+ // src/runtime/runtime.ts
755
+ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }) {
756
+ const warnings = [];
757
+ const requestedModel = request.model ?? options.defaultModel ?? "";
758
+ const normalized = normalizeModel(requestedModel);
759
+ warnings.push(...normalized.warnings);
760
+ const serverTools = mergeServerTools(defaults.serverTools, normalized.impliedServerTools, request.serverTools);
761
+ const apiMode = resolveApiMode(request, defaults.apiMode, serverTools);
762
+ validateRuntimeRequest(request, options, apiMode, normalized.model, serverTools);
763
+ const tools = [
764
+ ...buildOpenRouterServerTools(serverTools),
765
+ ...(request.functionTools ?? []).map((tool) => ({
766
+ type: "function",
767
+ function: {
768
+ name: tool.name,
769
+ description: tool.description,
770
+ parameters: tool.parameters
771
+ }
772
+ }))
773
+ ];
774
+ const headers = buildHeaders(options);
775
+ const baseBody = buildBaseBody(request, normalized.model, defaults, tools, serverTools);
776
+ const body = apiMode === "chat" ? buildChatBody(request, baseBody) : buildResponsesBody(request, baseBody);
777
+ return {
778
+ apiMode,
779
+ url: endpointFor(options.baseUrl ?? "https://openrouter.ai/api/v1", apiMode),
780
+ headers,
781
+ body,
782
+ warnings
783
+ };
784
+ }
785
+ function resolveApiMode(request, defaultMode = "auto", serverTools) {
786
+ const mode = request.apiMode ?? defaultMode;
787
+ if (mode === "chat") return "chat";
788
+ if (mode === "responses") return "responses";
789
+ if (serverTools?.applyPatch?.mode && serverTools.applyPatch.mode !== "disabled") return "responses";
790
+ if (request.input) return "responses";
791
+ return "chat";
792
+ }
793
+ function mergeServerTools(...policies) {
794
+ const merged = {};
795
+ for (const policy of policies) {
796
+ if (!policy) continue;
797
+ Object.assign(merged, policy);
798
+ }
799
+ return Object.keys(merged).length ? merged : void 0;
800
+ }
801
+ function buildBaseBody(request, model, defaults, tools, serverTools) {
802
+ const body = {
803
+ model,
804
+ temperature: request.temperature ?? defaults.temperature,
805
+ reasoning: request.reasoning,
806
+ tools: tools.length ? tools : void 0,
807
+ tool_choice: compileToolChoice(request.toolChoice, serverTools),
808
+ ...request.rawOpenRouterOverrides
809
+ };
810
+ return clean(body);
811
+ }
812
+ function buildChatBody(request, baseBody) {
813
+ const messages = compileMessages(request);
814
+ return clean({
815
+ ...baseBody,
816
+ messages,
817
+ max_tokens: request.maxTokens,
818
+ response_format: request.responseFormat
819
+ });
820
+ }
821
+ function buildResponsesBody(request, baseBody) {
822
+ return clean({
823
+ ...baseBody,
824
+ input: request.input ?? compilePromptInput(request),
825
+ instructions: request.instructions ?? request.system,
826
+ max_output_tokens: request.maxTokens,
827
+ text: request.responseFormat
828
+ });
829
+ }
830
+ function compileMessages(request) {
831
+ const messages = [];
832
+ if (request.system) messages.push({ role: "system", content: request.system });
833
+ if (request.messages) messages.push(...request.messages);
834
+ if (request.prompt) messages.push({ role: "user", content: request.prompt });
835
+ return messages;
836
+ }
837
+ function compilePromptInput(request) {
838
+ if (request.prompt) return request.prompt;
839
+ return compileMessages(request).map((message) => ({
840
+ role: message.role,
841
+ content: message.content
842
+ }));
843
+ }
844
+ function compileToolChoice(choice, serverTools) {
845
+ if (!choice || choice === "auto") return void 0;
846
+ if (choice === "none" || choice === "required") return choice;
847
+ if (choice.type === "function") {
848
+ return { type: "function", function: { name: choice.functionName } };
849
+ }
850
+ const toolType = `openrouter:${choice.serverTool}`;
851
+ const matchingPolicy = serverToolPolicyForChoice(choice.serverTool, serverTools);
852
+ return matchingPolicy === 1 ? { type: toolType } : "required";
853
+ }
854
+ function serverToolPolicyForChoice(name, policy) {
855
+ const value = {
856
+ web_search: policy?.webSearch,
857
+ web_fetch: policy?.webFetch,
858
+ datetime: policy?.datetime,
859
+ image_generation: policy?.imageGeneration,
860
+ apply_patch: policy?.applyPatch,
861
+ fusion: policy?.fusion,
862
+ advisor: policy?.advisor,
863
+ subagent: policy?.subagent
864
+ }[name];
865
+ if (!value) return 0;
866
+ if (Array.isArray(value)) return value.filter((item) => item.mode !== "disabled").length;
867
+ return value.mode === "disabled" ? 0 : 1;
868
+ }
869
+ function clean(record) {
870
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== void 0));
871
+ }
872
+
873
+ // src/function-tools/registry.ts
874
+ function resolveExecutor(name, definitions, options) {
875
+ return definitions?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
876
+ }
877
+
878
+ // src/function-tools/parse-tool-args.ts
879
+ function parseToolArgs(args) {
880
+ return parseJsonMaybe(args);
881
+ }
882
+
883
+ // src/function-tools/execute-function-tool.ts
884
+ async function executeFunctionTool(call, request, definitions, options) {
885
+ const executor = resolveExecutor(call.name, definitions, options);
886
+ if (!executor) {
887
+ throw new RuntimeConfigError("FUNCTION_TOOL_EXECUTOR_MISSING", `No executor registered for ${call.name}.`);
888
+ }
889
+ const started = Date.now();
890
+ const args = parseToolArgs(call.args);
891
+ try {
892
+ options.logger?.debug?.("runtime.function_tool.started", { name: call.name, callId: call.callId });
893
+ await options.hooks?.beforeFunctionToolExecution?.({ ...call, args });
894
+ const context = {
895
+ requestId: request.id ?? "",
896
+ toolName: call.name,
897
+ callId: call.callId,
898
+ ...request.metadata ? { metadata: request.metadata } : {}
899
+ };
900
+ const result = await executor(args, context);
901
+ const normalized = {
902
+ name: call.name,
903
+ callId: call.callId,
904
+ status: "completed",
905
+ result,
906
+ durationMs: Date.now() - started
907
+ };
908
+ await options.hooks?.afterFunctionToolExecution?.(normalized);
909
+ options.logger?.debug?.("runtime.function_tool.completed", { name: call.name, callId: call.callId });
910
+ return normalized;
911
+ } catch (error) {
912
+ const normalized = {
913
+ name: call.name,
914
+ callId: call.callId,
915
+ status: "failed",
916
+ error: error instanceof Error ? error.message : String(error),
917
+ durationMs: Date.now() - started
918
+ };
919
+ await options.hooks?.afterFunctionToolExecution?.(normalized);
920
+ options.logger?.warn?.("runtime.function_tool.failed", normalized);
921
+ return normalized;
922
+ }
923
+ }
924
+
925
+ // src/runtime/tool-loop.ts
926
+ function extractChatFunctionCalls(response) {
927
+ const record = isRecord(response) ? response : {};
928
+ const choice = Array.isArray(record.choices) && isRecord(record.choices[0]) ? record.choices[0] : {};
929
+ const message = isRecord(choice.message) ? choice.message : {};
930
+ const calls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
931
+ return calls.flatMap((call) => {
932
+ if (!isRecord(call) || call.type !== "function" || !isRecord(call.function)) return [];
933
+ const name = typeof call.function.name === "string" ? call.function.name : void 0;
934
+ if (!name) return [];
935
+ return [
936
+ {
937
+ name,
938
+ callId: typeof call.id === "string" ? call.id : name,
939
+ args: call.function.arguments
940
+ }
941
+ ];
942
+ });
943
+ }
944
+ function extractResponsesFunctionCalls(response) {
945
+ const record = isRecord(response) ? response : {};
946
+ const output = Array.isArray(record.output) ? record.output : [];
947
+ return output.flatMap((item) => {
948
+ if (!isRecord(item) || item.type !== "function_call") return [];
949
+ const name = typeof item.name === "string" ? item.name : void 0;
950
+ if (!name) return [];
951
+ return [
952
+ {
953
+ name,
954
+ callId: typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : name,
955
+ args: item.arguments
956
+ }
957
+ ];
958
+ });
959
+ }
960
+ async function executeFunctionCalls(params) {
961
+ if (params.previousUsage.length + params.calls.length > params.maxFunctionToolCalls) {
962
+ throw new Error("maxFunctionToolCalls exceeded.");
963
+ }
964
+ const results = [];
965
+ for (const call of params.calls) {
966
+ const result = await executeFunctionTool(call, params.request, params.definitions, params.options);
967
+ results.push({
968
+ name: result.name,
969
+ callId: result.callId,
970
+ status: result.status,
971
+ args: call.args,
972
+ ...result.result !== void 0 ? { result: result.result } : {},
973
+ ...result.error !== void 0 ? { error: result.error } : {},
974
+ ...result.durationMs !== void 0 ? { durationMs: result.durationMs } : {}
975
+ });
976
+ }
977
+ return results;
978
+ }
979
+
980
+ // src/runtime/run-chat.ts
981
+ async function runChat(request, compiled, context) {
982
+ const maxIterations = request.execution?.maxToolIterations ?? 8;
983
+ const maxFunctionToolCalls = request.execution?.maxFunctionToolCalls ?? 20;
984
+ const functionUsage = [];
985
+ let activeCompiled = compiled;
986
+ let response;
987
+ for (let iteration = 0; iteration <= maxIterations; iteration++) {
988
+ response = await sendOpenRouterRequest(
989
+ activeCompiled,
990
+ context.options,
991
+ context.defaults,
992
+ request.execution?.timeoutMs ?? context.defaults.timeoutMs
993
+ );
994
+ const calls = extractChatFunctionCalls(response);
995
+ if (!calls.length) break;
996
+ const results = await executeFunctionCalls({
997
+ calls,
998
+ request,
999
+ definitions: request.functionTools,
1000
+ options: context.options,
1001
+ previousUsage: functionUsage,
1002
+ maxFunctionToolCalls
1003
+ });
1004
+ functionUsage.push(...results);
1005
+ const body = activeCompiled.body;
1006
+ const messages = Array.isArray(body.messages) ? [...body.messages] : [];
1007
+ messages.push(...results.map(resultToChatToolMessage));
1008
+ activeCompiled = { ...activeCompiled, body: { ...body, messages } };
1009
+ }
1010
+ const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
1011
+ const normalized = normalizeChatResponse({
1012
+ requestBody: activeCompiled.body,
1013
+ response,
1014
+ apiMode: "chat",
1015
+ model,
1016
+ warnings: compiled.warnings,
1017
+ functionToolUsage: functionUsage,
1018
+ ...optional4("serverTools", mergeServerTools(context.defaults.serverTools, request.serverTools)),
1019
+ ...optional4("metadata", request.metadata)
1020
+ });
1021
+ return validatePostResponse(normalized, context.defaults);
1022
+ }
1023
+ function optional4(key, value) {
1024
+ return value === void 0 ? {} : { [key]: value };
1025
+ }
1026
+
1027
+ // src/normalize/normalize-responses-response.ts
1028
+ function normalizeResponsesResponse(params) {
1029
+ const record = isRecord(params.response) ? params.response : {};
1030
+ return {
1031
+ id: typeof record.id === "string" ? record.id : createRuntimeId("orresp"),
1032
+ status: record.status === "requires_action" ? "requires_action" : "completed",
1033
+ apiMode: "responses",
1034
+ model: typeof record.model === "string" ? record.model : params.model,
1035
+ text: extractOutputText(record),
1036
+ citations: extractCitations(params.response),
1037
+ images: extractImages(params.response),
1038
+ patches: extractPatches(params.response),
1039
+ toolUsage: createToolUsage(params.serverTools, params.response, params.functionToolUsage),
1040
+ warnings: params.warnings,
1041
+ errors: [],
1042
+ raw: {
1043
+ request: params.requestBody,
1044
+ response: params.response
1045
+ },
1046
+ ...optional5("usage", normalizeUsage(record.usage)),
1047
+ ...optional5("finishReason", typeof record.status === "string" ? record.status : void 0),
1048
+ ...optional5("metadata", params.metadata)
1049
+ };
1050
+ }
1051
+ function extractOutputText(record) {
1052
+ if (typeof record.output_text === "string") return record.output_text;
1053
+ const output = Array.isArray(record.output) ? record.output : [];
1054
+ const chunks = [];
1055
+ for (const item of output) {
1056
+ if (!isRecord(item)) continue;
1057
+ if (typeof item.text === "string") chunks.push(item.text);
1058
+ const content = Array.isArray(item.content) ? item.content : [];
1059
+ for (const part of content) {
1060
+ if (isRecord(part) && typeof part.text === "string") chunks.push(part.text);
1061
+ }
1062
+ }
1063
+ return chunks.join("");
1064
+ }
1065
+ function optional5(key, value) {
1066
+ return value === void 0 ? {} : { [key]: value };
1067
+ }
1068
+
1069
+ // src/runtime/run-responses.ts
1070
+ async function runResponses(request, compiled, context) {
1071
+ const maxIterations = request.execution?.maxToolIterations ?? 8;
1072
+ const maxFunctionToolCalls = request.execution?.maxFunctionToolCalls ?? 20;
1073
+ const functionUsage = [];
1074
+ let activeCompiled = compiled;
1075
+ let response;
1076
+ for (let iteration = 0; iteration <= maxIterations; iteration++) {
1077
+ response = await sendOpenRouterRequest(
1078
+ activeCompiled,
1079
+ context.options,
1080
+ context.defaults,
1081
+ request.execution?.timeoutMs ?? context.defaults.timeoutMs
1082
+ );
1083
+ const calls = extractResponsesFunctionCalls(response);
1084
+ if (!calls.length) break;
1085
+ const results = await executeFunctionCalls({
1086
+ calls,
1087
+ request,
1088
+ definitions: request.functionTools,
1089
+ options: context.options,
1090
+ previousUsage: functionUsage,
1091
+ maxFunctionToolCalls
1092
+ });
1093
+ functionUsage.push(...results);
1094
+ activeCompiled = {
1095
+ ...activeCompiled,
1096
+ body: {
1097
+ ...activeCompiled.body,
1098
+ previous_response_id: isRecord(response) && typeof response.id === "string" ? response.id : void 0,
1099
+ input: results.map(resultToResponsesInput)
1100
+ }
1101
+ };
1102
+ }
1103
+ const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
1104
+ const normalized = normalizeResponsesResponse({
1105
+ requestBody: activeCompiled.body,
1106
+ response,
1107
+ model,
1108
+ warnings: compiled.warnings,
1109
+ functionToolUsage: functionUsage,
1110
+ ...optional6("serverTools", mergeServerTools(context.defaults.serverTools, request.serverTools)),
1111
+ ...optional6("metadata", request.metadata)
1112
+ });
1113
+ return validatePostResponse(normalized, context.defaults);
1114
+ }
1115
+ function optional6(key, value) {
1116
+ return value === void 0 ? {} : { [key]: value };
1117
+ }
1118
+
1119
+ // src/runtime/create-runtime.ts
1120
+ function createOpenRouterRuntime(options) {
1121
+ const resolvedOptions = resolveRuntimeOptions(options);
1122
+ const defaults = resolveDefaults(options);
1123
+ const context = { options: resolvedOptions, defaults };
1124
+ return {
1125
+ async run(request) {
1126
+ const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
1127
+ try {
1128
+ resolvedOptions.logger?.info?.("runtime.request.started", { requestId: requestWithId.id });
1129
+ await resolvedOptions.hooks?.beforeCompile?.(requestWithId);
1130
+ const compiled = compileRuntimeRequest(requestWithId, defaults, resolvedOptions);
1131
+ await resolvedOptions.hooks?.afterCompile?.(compiled);
1132
+ resolvedOptions.logger?.debug?.("runtime.request.compiled", {
1133
+ requestId: requestWithId.id,
1134
+ apiMode: compiled.apiMode
1135
+ });
1136
+ await resolvedOptions.hooks?.beforeOpenRouterRequest?.(compiled);
1137
+ const response = compiled.apiMode === "chat" ? await runChat(requestWithId, compiled, context) : await runResponses(requestWithId, compiled, context);
1138
+ await resolvedOptions.hooks?.afterOpenRouterResponse?.(response.raw.response);
1139
+ resolvedOptions.logger?.info?.("runtime.response.normalized", { requestId: requestWithId.id, status: response.status });
1140
+ return response;
1141
+ } catch (error) {
1142
+ return errorResponse(requestWithId, error);
1143
+ }
1144
+ }
1145
+ };
1146
+ function errorResponse(request, error) {
1147
+ const runtimeError = normalizeError(error);
1148
+ if (error instanceof RuntimeConfigError && defaults.onPolicyViolation === "throw") throw error;
1149
+ return {
1150
+ id: request.id ?? createRuntimeId("orerr"),
1151
+ status: "failed",
1152
+ apiMode: request.apiMode === "responses" ? "responses" : "chat",
1153
+ model: request.model ?? resolvedOptions.defaultModel ?? "",
1154
+ text: "",
1155
+ citations: [],
1156
+ images: [],
1157
+ patches: [],
1158
+ toolUsage: {
1159
+ serverTools: {
1160
+ webSearch: { requested: false, required: false, used: false },
1161
+ webFetch: { requested: false, required: false, used: false },
1162
+ datetime: { requested: false, required: false, used: false },
1163
+ imageGeneration: { requested: false, required: false, used: false },
1164
+ applyPatch: { requested: false, required: false, used: false },
1165
+ fusion: { requested: false, required: false, used: false },
1166
+ advisor: { requested: false, required: false, used: false },
1167
+ subagent: { requested: false, required: false, used: false }
1168
+ },
1169
+ functionTools: []
1170
+ },
1171
+ warnings: [],
1172
+ errors: [runtimeError],
1173
+ raw: {
1174
+ request,
1175
+ response: error instanceof OpenRouterHttpError ? error.body : void 0
1176
+ },
1177
+ ...request.metadata ? { metadata: request.metadata } : {}
1178
+ };
1179
+ }
1180
+ }
1181
+ function normalizeError(error) {
1182
+ if (error instanceof RuntimeConfigError) {
1183
+ return {
1184
+ code: error.code,
1185
+ message: error.message,
1186
+ source: "validation",
1187
+ details: error.details
1188
+ };
1189
+ }
1190
+ if (error instanceof OpenRouterHttpError) {
1191
+ return {
1192
+ code: error.code,
1193
+ message: error.message,
1194
+ source: "openrouter",
1195
+ retryable: error.retryable,
1196
+ details: error.body
1197
+ };
1198
+ }
1199
+ return {
1200
+ code: "RUNTIME_ERROR",
1201
+ message: error instanceof Error ? error.message : String(error),
1202
+ source: "runtime"
1203
+ };
1204
+ }
1205
+ export {
1206
+ OpenRouterHttpError,
1207
+ RuntimeConfigError,
1208
+ buildOpenRouterServerTools,
1209
+ compileRuntimeRequest,
1210
+ createOpenRouterRuntime
1211
+ };
1212
+ //# sourceMappingURL=index.js.map