@x12i/openrouter-runtime 1.0.3 → 1.0.4
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.cjs +94 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +94 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
function resolveRuntimeOptions(options) {
|
|
3
3
|
return {
|
|
4
4
|
...options,
|
|
5
|
+
apiKey: options.apiKey ?? readOpenRouterApiKeyFromEnv() ?? "",
|
|
5
6
|
baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1",
|
|
6
7
|
defaultHeaders: options.defaultHeaders ?? {},
|
|
7
8
|
fetch: options.fetch ?? globalThis.fetch
|
|
@@ -29,6 +30,10 @@ function resolveDefaults(options) {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
function readOpenRouterApiKeyFromEnv() {
|
|
34
|
+
const env = typeof process === "undefined" ? void 0 : process.env;
|
|
35
|
+
return env?.OPENROUTER_API_KEY || env?.OPEN_ROUTER_KEY;
|
|
36
|
+
}
|
|
32
37
|
|
|
33
38
|
// src/openrouter/errors.ts
|
|
34
39
|
var RuntimeConfigError = class extends Error {
|
|
@@ -42,18 +47,20 @@ var RuntimeConfigError = class extends Error {
|
|
|
42
47
|
details;
|
|
43
48
|
};
|
|
44
49
|
var OpenRouterHttpError = class extends Error {
|
|
45
|
-
constructor(status, code, message, body, retryable = false) {
|
|
50
|
+
constructor(status, code, message, body, retryable = false, providerAttempts = []) {
|
|
46
51
|
super(message);
|
|
47
52
|
this.status = status;
|
|
48
53
|
this.code = code;
|
|
49
54
|
this.body = body;
|
|
50
55
|
this.retryable = retryable;
|
|
56
|
+
this.providerAttempts = providerAttempts;
|
|
51
57
|
this.name = "OpenRouterHttpError";
|
|
52
58
|
}
|
|
53
59
|
status;
|
|
54
60
|
code;
|
|
55
61
|
body;
|
|
56
62
|
retryable;
|
|
63
|
+
providerAttempts;
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
// src/utils/delay.ts
|
|
@@ -70,13 +77,35 @@ function retryDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
|
70
77
|
async function sendOpenRouterRequest(compiled, options, defaults, timeoutMs) {
|
|
71
78
|
const attempts = defaults.retry.enabled ? defaults.retry.maxAttempts : 1;
|
|
72
79
|
let lastError;
|
|
80
|
+
const providerAttempts = [];
|
|
73
81
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
74
82
|
try {
|
|
75
83
|
return await sendOnce(compiled, options, timeoutMs);
|
|
76
84
|
} catch (error) {
|
|
77
85
|
lastError = error;
|
|
86
|
+
if (error instanceof OpenRouterHttpError) {
|
|
87
|
+
const providerAttempt = toProviderAttempt(error, compiled);
|
|
88
|
+
providerAttempts.push(providerAttempt);
|
|
89
|
+
options.logger?.warn?.("openrouter.provider_attempt.failed", {
|
|
90
|
+
...providerAttempt,
|
|
91
|
+
attempt,
|
|
92
|
+
maxAttempts: attempts
|
|
93
|
+
});
|
|
94
|
+
}
|
|
78
95
|
const retryable = error instanceof OpenRouterHttpError ? error.retryable : true;
|
|
79
|
-
if (!retryable || attempt >= attempts)
|
|
96
|
+
if (!retryable || attempt >= attempts) {
|
|
97
|
+
if (error instanceof OpenRouterHttpError) {
|
|
98
|
+
throw new OpenRouterHttpError(
|
|
99
|
+
error.status,
|
|
100
|
+
error.code,
|
|
101
|
+
error.message,
|
|
102
|
+
error.body,
|
|
103
|
+
error.retryable,
|
|
104
|
+
providerAttempts
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
80
109
|
await delay(retryDelay(attempt, defaults.retry.baseDelayMs, defaults.retry.maxDelayMs));
|
|
81
110
|
}
|
|
82
111
|
}
|
|
@@ -95,10 +124,11 @@ async function sendOnce(compiled, options, timeoutMs) {
|
|
|
95
124
|
const text = await response.text();
|
|
96
125
|
const body = text ? parseBody(text) : void 0;
|
|
97
126
|
if (!response.ok) {
|
|
127
|
+
const message = extractMessage(body) ?? `OpenRouter request failed with status ${response.status}.`;
|
|
98
128
|
throw new OpenRouterHttpError(
|
|
99
129
|
response.status,
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
classifyProviderFailure(response.status, message, body),
|
|
131
|
+
message,
|
|
102
132
|
body,
|
|
103
133
|
isRetryableStatus(response.status)
|
|
104
134
|
);
|
|
@@ -107,9 +137,15 @@ async function sendOnce(compiled, options, timeoutMs) {
|
|
|
107
137
|
} catch (error) {
|
|
108
138
|
if (error instanceof OpenRouterHttpError) throw error;
|
|
109
139
|
if (error instanceof Error && error.name === "AbortError") {
|
|
110
|
-
throw new OpenRouterHttpError(408, "
|
|
140
|
+
throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter request timed out.", void 0, true);
|
|
111
141
|
}
|
|
112
|
-
throw
|
|
142
|
+
throw new OpenRouterHttpError(
|
|
143
|
+
503,
|
|
144
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
145
|
+
error instanceof Error ? error.message : "OpenRouter request failed before receiving a response.",
|
|
146
|
+
void 0,
|
|
147
|
+
true
|
|
148
|
+
);
|
|
113
149
|
} finally {
|
|
114
150
|
clearTimeout(timeout);
|
|
115
151
|
}
|
|
@@ -130,10 +166,31 @@ function extractMessage(body) {
|
|
|
130
166
|
}
|
|
131
167
|
return typeof record.message === "string" ? record.message : void 0;
|
|
132
168
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
function toProviderAttempt(error, compiled) {
|
|
170
|
+
const modelId = typeof compiled.body.model === "string" ? compiled.body.model : void 0;
|
|
171
|
+
return {
|
|
172
|
+
provider: "openrouter",
|
|
173
|
+
...modelId ? { modelId } : {},
|
|
174
|
+
httpStatus: error.status,
|
|
175
|
+
code: error.code,
|
|
176
|
+
message: error.message,
|
|
177
|
+
retryable: error.retryable
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function classifyProviderFailure(status, message, body) {
|
|
181
|
+
const haystack = `${message} ${JSON.stringify(body ?? "")}`.toLowerCase();
|
|
182
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
183
|
+
if (status === 402 || mentionsQuota(haystack)) return "PROVIDER_QUOTA_EXCEEDED";
|
|
184
|
+
if (status === 404 || mentionsMissingModel(haystack)) return "PROVIDER_MODEL_NOT_FOUND";
|
|
185
|
+
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
186
|
+
if (status === 408 || status >= 500) return "PROVIDER_GATEWAY_UNREACHABLE";
|
|
187
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
188
|
+
}
|
|
189
|
+
function mentionsQuota(text) {
|
|
190
|
+
return /\b(quota|credit|credits|billing|balance|insufficient funds|insufficient credit)\b/.test(text);
|
|
191
|
+
}
|
|
192
|
+
function mentionsMissingModel(text) {
|
|
193
|
+
return /\bmodel\b.*\b(not found|not a valid|invalid|unavailable)\b/.test(text) || /\b(no endpoints found|not a valid model id)\b/.test(text);
|
|
137
194
|
}
|
|
138
195
|
function isRetryableStatus(status) {
|
|
139
196
|
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
@@ -1274,6 +1331,7 @@ function normalizeError(error) {
|
|
|
1274
1331
|
code: error.code,
|
|
1275
1332
|
message: error.message,
|
|
1276
1333
|
source: "validation",
|
|
1334
|
+
...optionalErrorField("operatorHint", operatorHintFor(error.code)),
|
|
1277
1335
|
details: error.details
|
|
1278
1336
|
};
|
|
1279
1337
|
}
|
|
@@ -1283,7 +1341,11 @@ function normalizeError(error) {
|
|
|
1283
1341
|
message: error.message,
|
|
1284
1342
|
source: "openrouter",
|
|
1285
1343
|
retryable: error.retryable,
|
|
1286
|
-
|
|
1344
|
+
...optionalErrorField("operatorHint", operatorHintFor(error.code)),
|
|
1345
|
+
details: {
|
|
1346
|
+
providerAttempts: error.providerAttempts,
|
|
1347
|
+
...error.body === void 0 ? {} : { response: error.body }
|
|
1348
|
+
}
|
|
1287
1349
|
};
|
|
1288
1350
|
}
|
|
1289
1351
|
return {
|
|
@@ -1292,6 +1354,27 @@ function normalizeError(error) {
|
|
|
1292
1354
|
source: "runtime"
|
|
1293
1355
|
};
|
|
1294
1356
|
}
|
|
1357
|
+
function optionalErrorField(key, value) {
|
|
1358
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1359
|
+
}
|
|
1360
|
+
function operatorHintFor(code) {
|
|
1361
|
+
switch (code) {
|
|
1362
|
+
case "OPENROUTER_API_KEY_MISSING":
|
|
1363
|
+
return "Set OPENROUTER_API_KEY or OPEN_ROUTER_KEY, or pass apiKey when creating the runtime.";
|
|
1364
|
+
case "PROVIDER_AUTH_FAILED":
|
|
1365
|
+
return "Check that the OpenRouter API key is present, valid, and authorized for this route.";
|
|
1366
|
+
case "PROVIDER_RATE_LIMITED":
|
|
1367
|
+
return "Retry after the provider rate limit resets or reduce request concurrency.";
|
|
1368
|
+
case "PROVIDER_QUOTA_EXCEEDED":
|
|
1369
|
+
return "Check provider billing, credits, quota, and project limits.";
|
|
1370
|
+
case "PROVIDER_MODEL_NOT_FOUND":
|
|
1371
|
+
return "Verify the model id and whether it is available through OpenRouter for this account.";
|
|
1372
|
+
case "PROVIDER_GATEWAY_UNREACHABLE":
|
|
1373
|
+
return "Retry later; the provider gateway was unreachable or timed out.";
|
|
1374
|
+
default:
|
|
1375
|
+
return void 0;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1295
1378
|
export {
|
|
1296
1379
|
OpenRouterHttpError,
|
|
1297
1380
|
RuntimeConfigError,
|