@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.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) throw error;
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
- statusToCode(response.status),
101
- extractMessage(body) ?? `OpenRouter request failed with status ${response.status}.`,
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, "OPENROUTER_TIMEOUT", "OpenRouter request timed out.", void 0, true);
140
+ throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter request timed out.", void 0, true);
111
141
  }
112
- throw error;
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 statusToCode(status) {
134
- if (status === 401) return "OPENROUTER_AUTH_FAILED";
135
- if (status === 429) return "OPENROUTER_RATE_LIMITED";
136
- return "OPENROUTER_REQUEST_FAILED";
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
- details: error.body
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,