@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 CHANGED
@@ -32,6 +32,7 @@ module.exports = __toCommonJS(index_exports);
32
32
  function resolveRuntimeOptions(options) {
33
33
  return {
34
34
  ...options,
35
+ apiKey: options.apiKey ?? readOpenRouterApiKeyFromEnv() ?? "",
35
36
  baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1",
36
37
  defaultHeaders: options.defaultHeaders ?? {},
37
38
  fetch: options.fetch ?? globalThis.fetch
@@ -59,6 +60,10 @@ function resolveDefaults(options) {
59
60
  }
60
61
  };
61
62
  }
63
+ function readOpenRouterApiKeyFromEnv() {
64
+ const env = typeof process === "undefined" ? void 0 : process.env;
65
+ return env?.OPENROUTER_API_KEY || env?.OPEN_ROUTER_KEY;
66
+ }
62
67
 
63
68
  // src/openrouter/errors.ts
64
69
  var RuntimeConfigError = class extends Error {
@@ -72,18 +77,20 @@ var RuntimeConfigError = class extends Error {
72
77
  details;
73
78
  };
74
79
  var OpenRouterHttpError = class extends Error {
75
- constructor(status, code, message, body, retryable = false) {
80
+ constructor(status, code, message, body, retryable = false, providerAttempts = []) {
76
81
  super(message);
77
82
  this.status = status;
78
83
  this.code = code;
79
84
  this.body = body;
80
85
  this.retryable = retryable;
86
+ this.providerAttempts = providerAttempts;
81
87
  this.name = "OpenRouterHttpError";
82
88
  }
83
89
  status;
84
90
  code;
85
91
  body;
86
92
  retryable;
93
+ providerAttempts;
87
94
  };
88
95
 
89
96
  // src/utils/delay.ts
@@ -100,13 +107,35 @@ function retryDelay(attempt, baseDelayMs, maxDelayMs) {
100
107
  async function sendOpenRouterRequest(compiled, options, defaults, timeoutMs) {
101
108
  const attempts = defaults.retry.enabled ? defaults.retry.maxAttempts : 1;
102
109
  let lastError;
110
+ const providerAttempts = [];
103
111
  for (let attempt = 1; attempt <= attempts; attempt++) {
104
112
  try {
105
113
  return await sendOnce(compiled, options, timeoutMs);
106
114
  } catch (error) {
107
115
  lastError = error;
116
+ if (error instanceof OpenRouterHttpError) {
117
+ const providerAttempt = toProviderAttempt(error, compiled);
118
+ providerAttempts.push(providerAttempt);
119
+ options.logger?.warn?.("openrouter.provider_attempt.failed", {
120
+ ...providerAttempt,
121
+ attempt,
122
+ maxAttempts: attempts
123
+ });
124
+ }
108
125
  const retryable = error instanceof OpenRouterHttpError ? error.retryable : true;
109
- if (!retryable || attempt >= attempts) throw error;
126
+ if (!retryable || attempt >= attempts) {
127
+ if (error instanceof OpenRouterHttpError) {
128
+ throw new OpenRouterHttpError(
129
+ error.status,
130
+ error.code,
131
+ error.message,
132
+ error.body,
133
+ error.retryable,
134
+ providerAttempts
135
+ );
136
+ }
137
+ throw error;
138
+ }
110
139
  await delay(retryDelay(attempt, defaults.retry.baseDelayMs, defaults.retry.maxDelayMs));
111
140
  }
112
141
  }
@@ -125,10 +154,11 @@ async function sendOnce(compiled, options, timeoutMs) {
125
154
  const text = await response.text();
126
155
  const body = text ? parseBody(text) : void 0;
127
156
  if (!response.ok) {
157
+ const message = extractMessage(body) ?? `OpenRouter request failed with status ${response.status}.`;
128
158
  throw new OpenRouterHttpError(
129
159
  response.status,
130
- statusToCode(response.status),
131
- extractMessage(body) ?? `OpenRouter request failed with status ${response.status}.`,
160
+ classifyProviderFailure(response.status, message, body),
161
+ message,
132
162
  body,
133
163
  isRetryableStatus(response.status)
134
164
  );
@@ -137,9 +167,15 @@ async function sendOnce(compiled, options, timeoutMs) {
137
167
  } catch (error) {
138
168
  if (error instanceof OpenRouterHttpError) throw error;
139
169
  if (error instanceof Error && error.name === "AbortError") {
140
- throw new OpenRouterHttpError(408, "OPENROUTER_TIMEOUT", "OpenRouter request timed out.", void 0, true);
170
+ throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter request timed out.", void 0, true);
141
171
  }
142
- throw error;
172
+ throw new OpenRouterHttpError(
173
+ 503,
174
+ "PROVIDER_GATEWAY_UNREACHABLE",
175
+ error instanceof Error ? error.message : "OpenRouter request failed before receiving a response.",
176
+ void 0,
177
+ true
178
+ );
143
179
  } finally {
144
180
  clearTimeout(timeout);
145
181
  }
@@ -160,10 +196,31 @@ function extractMessage(body) {
160
196
  }
161
197
  return typeof record.message === "string" ? record.message : void 0;
162
198
  }
163
- function statusToCode(status) {
164
- if (status === 401) return "OPENROUTER_AUTH_FAILED";
165
- if (status === 429) return "OPENROUTER_RATE_LIMITED";
166
- return "OPENROUTER_REQUEST_FAILED";
199
+ function toProviderAttempt(error, compiled) {
200
+ const modelId = typeof compiled.body.model === "string" ? compiled.body.model : void 0;
201
+ return {
202
+ provider: "openrouter",
203
+ ...modelId ? { modelId } : {},
204
+ httpStatus: error.status,
205
+ code: error.code,
206
+ message: error.message,
207
+ retryable: error.retryable
208
+ };
209
+ }
210
+ function classifyProviderFailure(status, message, body) {
211
+ const haystack = `${message} ${JSON.stringify(body ?? "")}`.toLowerCase();
212
+ if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
213
+ if (status === 402 || mentionsQuota(haystack)) return "PROVIDER_QUOTA_EXCEEDED";
214
+ if (status === 404 || mentionsMissingModel(haystack)) return "PROVIDER_MODEL_NOT_FOUND";
215
+ if (status === 429) return "PROVIDER_RATE_LIMITED";
216
+ if (status === 408 || status >= 500) return "PROVIDER_GATEWAY_UNREACHABLE";
217
+ return "PROVIDER_REQUEST_FAILED";
218
+ }
219
+ function mentionsQuota(text) {
220
+ return /\b(quota|credit|credits|billing|balance|insufficient funds|insufficient credit)\b/.test(text);
221
+ }
222
+ function mentionsMissingModel(text) {
223
+ 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);
167
224
  }
168
225
  function isRetryableStatus(status) {
169
226
  return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
@@ -1304,6 +1361,7 @@ function normalizeError(error) {
1304
1361
  code: error.code,
1305
1362
  message: error.message,
1306
1363
  source: "validation",
1364
+ ...optionalErrorField("operatorHint", operatorHintFor(error.code)),
1307
1365
  details: error.details
1308
1366
  };
1309
1367
  }
@@ -1313,7 +1371,11 @@ function normalizeError(error) {
1313
1371
  message: error.message,
1314
1372
  source: "openrouter",
1315
1373
  retryable: error.retryable,
1316
- details: error.body
1374
+ ...optionalErrorField("operatorHint", operatorHintFor(error.code)),
1375
+ details: {
1376
+ providerAttempts: error.providerAttempts,
1377
+ ...error.body === void 0 ? {} : { response: error.body }
1378
+ }
1317
1379
  };
1318
1380
  }
1319
1381
  return {
@@ -1322,6 +1384,27 @@ function normalizeError(error) {
1322
1384
  source: "runtime"
1323
1385
  };
1324
1386
  }
1387
+ function optionalErrorField(key, value) {
1388
+ return value === void 0 ? {} : { [key]: value };
1389
+ }
1390
+ function operatorHintFor(code) {
1391
+ switch (code) {
1392
+ case "OPENROUTER_API_KEY_MISSING":
1393
+ return "Set OPENROUTER_API_KEY or OPEN_ROUTER_KEY, or pass apiKey when creating the runtime.";
1394
+ case "PROVIDER_AUTH_FAILED":
1395
+ return "Check that the OpenRouter API key is present, valid, and authorized for this route.";
1396
+ case "PROVIDER_RATE_LIMITED":
1397
+ return "Retry after the provider rate limit resets or reduce request concurrency.";
1398
+ case "PROVIDER_QUOTA_EXCEEDED":
1399
+ return "Check provider billing, credits, quota, and project limits.";
1400
+ case "PROVIDER_MODEL_NOT_FOUND":
1401
+ return "Verify the model id and whether it is available through OpenRouter for this account.";
1402
+ case "PROVIDER_GATEWAY_UNREACHABLE":
1403
+ return "Retry later; the provider gateway was unreachable or timed out.";
1404
+ default:
1405
+ return void 0;
1406
+ }
1407
+ }
1325
1408
  // Annotate the CommonJS export names for ESM import in node:
1326
1409
  0 && (module.exports = {
1327
1410
  OpenRouterHttpError,