github-router 0.3.263 → 0.3.270

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.
Files changed (77) hide show
  1. package/dist/{attribution-settings-DFIVIRG8.js → attribution-settings-of5gJM5a.js} +4 -4
  2. package/dist/{attribution-settings-DFIVIRG8.js.map → attribution-settings-of5gJM5a.js.map} +1 -1
  3. package/dist/{auth-WA88cE9q.js → auth-DIFbHOu0.js} +3 -3
  4. package/dist/{auth-WA88cE9q.js.map → auth-DIFbHOu0.js.map} +1 -1
  5. package/dist/browser-bridge/index.js +67 -27
  6. package/dist/browser-ext/manifest.json +1 -1
  7. package/dist/{check-usage-v2apGKz7.js → check-usage-DfnrR-d_.js} +4 -4
  8. package/dist/{check-usage-v2apGKz7.js.map → check-usage-DfnrR-d_.js.map} +1 -1
  9. package/dist/{claude-BCI3Sx6_.js → claude-BlzCUbVq.js} +15 -14
  10. package/dist/{claude-BCI3Sx6_.js.map → claude-BlzCUbVq.js.map} +1 -1
  11. package/dist/{codex-DyJIrbJt.js → codex-BaFcXOyg.js} +5 -5
  12. package/dist/{codex-DyJIrbJt.js.map → codex-BaFcXOyg.js.map} +1 -1
  13. package/dist/{debug-vshv4nIv.js → debug-CuuxCk_J.js} +2 -2
  14. package/dist/{debug-vshv4nIv.js.map → debug-CuuxCk_J.js.map} +1 -1
  15. package/dist/engine-mwIgHJoi.js +2 -0
  16. package/dist/{exec-DTO2oin0.js → exec-y8C_MU8A.js} +89 -18
  17. package/dist/exec-y8C_MU8A.js.map +1 -0
  18. package/dist/{gate-discovery-cMbt8Ct2.js → gate-discovery-AUAjqSpV.js} +6 -6
  19. package/dist/{gate-discovery-cMbt8Ct2.js.map → gate-discovery-AUAjqSpV.js.map} +1 -1
  20. package/dist/{get-copilot-usage-DOFJWJaQ.js → get-copilot-usage-Ds09xSmH.js} +2 -2
  21. package/dist/{get-copilot-usage-DOFJWJaQ.js.map → get-copilot-usage-Ds09xSmH.js.map} +1 -1
  22. package/dist/{internal-artifact-open-CZGO831M.js → internal-artifact-open-CDqPfegE.js} +2 -2
  23. package/dist/{internal-artifact-open-CZGO831M.js.map → internal-artifact-open-CDqPfegE.js.map} +1 -1
  24. package/dist/{internal-plan-review-6gkcOAjT.js → internal-plan-review-CH9qTHoZ.js} +3 -3
  25. package/dist/{internal-plan-review-6gkcOAjT.js.map → internal-plan-review-CH9qTHoZ.js.map} +1 -1
  26. package/dist/{internal-prompt-submit-C0XOPgcP.js → internal-prompt-submit-Bve1s4z_.js} +5 -5
  27. package/dist/{internal-prompt-submit-C0XOPgcP.js.map → internal-prompt-submit-Bve1s4z_.js.map} +1 -1
  28. package/dist/{internal-session-bind-DtSBTkQe.js → internal-session-bind-Cc8bMXHK.js} +2 -2
  29. package/dist/{internal-session-bind-DtSBTkQe.js.map → internal-session-bind-Cc8bMXHK.js.map} +1 -1
  30. package/dist/{internal-stop-hook-CBcE2Js8.js → internal-stop-hook-B1MIwmTR.js} +6 -6
  31. package/dist/{internal-stop-hook-CBcE2Js8.js.map → internal-stop-hook-B1MIwmTR.js.map} +1 -1
  32. package/dist/{internal-stop-review-_1Mqz7eT.js → internal-stop-review-CWE7_QVv.js} +2 -2
  33. package/dist/{internal-stop-review-_1Mqz7eT.js.map → internal-stop-review-CWE7_QVv.js.map} +1 -1
  34. package/dist/{lifecycle-DjC9tC1A.js → lifecycle-0XrazT05.js} +2 -2
  35. package/dist/{lifecycle-DjC9tC1A.js.map → lifecycle-0XrazT05.js.map} +1 -1
  36. package/dist/lifecycle-2M0IThRr.js +2 -0
  37. package/dist/{lifecycle-Bd1Fyf4B.js → lifecycle-AgNOtFRv.js} +3 -3
  38. package/dist/{lifecycle-Bd1Fyf4B.js.map → lifecycle-AgNOtFRv.js.map} +1 -1
  39. package/dist/lifecycle-C8kFsAj-.js +2 -0
  40. package/dist/main.js +14 -14
  41. package/dist/{models-Cv6gprpy.js → models-BFQUp0ov.js} +35 -30
  42. package/dist/models-BFQUp0ov.js.map +1 -0
  43. package/dist/{orchestration-B6qidQAd.js → orchestration-B_Q3ncXI.js} +3 -3
  44. package/dist/{orchestration-B6qidQAd.js.map → orchestration-B_Q3ncXI.js.map} +1 -1
  45. package/dist/paths-BbHtQAOs.js +2 -0
  46. package/dist/{paths-BXrbboAS.js → paths-Bg1_DWhi.js} +3 -3
  47. package/dist/{paths-BXrbboAS.js.map → paths-Bg1_DWhi.js.map} +1 -1
  48. package/dist/{peer-mcp-personas-B22ncnbQ.js → peer-mcp-personas-pWP3Eyt7.js} +235 -108
  49. package/dist/peer-mcp-personas-pWP3Eyt7.js.map +1 -0
  50. package/dist/{plan-review-hook-Bo4y1r3j.js → plan-review-hook-BKOcW5s4.js} +3 -3
  51. package/dist/{plan-review-hook-Bo4y1r3j.js.map → plan-review-hook-BKOcW5s4.js.map} +1 -1
  52. package/dist/{prompt-submit-hook-BFRFLSk5.js → prompt-submit-hook-F7Zih_au.js} +2 -2
  53. package/dist/{prompt-submit-hook-BFRFLSk5.js.map → prompt-submit-hook-F7Zih_au.js.map} +1 -1
  54. package/dist/{provision-DSpLQHC3.js → provision-CsYUVdMT.js} +5 -5
  55. package/dist/{provision-DSpLQHC3.js.map → provision-CsYUVdMT.js.map} +1 -1
  56. package/dist/{serve-D9tBVVoK.js → serve--c9n9hk4.js} +11 -11
  57. package/dist/{serve-D9tBVVoK.js.map → serve--c9n9hk4.js.map} +1 -1
  58. package/dist/{server-setup-BM9Smfek.js → server-setup-BmyWS4WU.js} +559 -158
  59. package/dist/server-setup-BmyWS4WU.js.map +1 -0
  60. package/dist/{start-D7Hx2T_T.js → start-CXnLBouJ.js} +3 -3
  61. package/dist/{start-D7Hx2T_T.js.map → start-CXnLBouJ.js.map} +1 -1
  62. package/dist/{stop-gate-hook-Bmwla_2u.js → stop-gate-hook-BqpumJtl.js} +3 -3
  63. package/dist/{stop-gate-hook-Bmwla_2u.js.map → stop-gate-hook-BqpumJtl.js.map} +1 -1
  64. package/dist/{stop-gate-policy-CoMKWSsS.js → stop-gate-policy-C0XOBZhB.js} +3 -3
  65. package/dist/{stop-gate-policy-CoMKWSsS.js.map → stop-gate-policy-C0XOBZhB.js.map} +1 -1
  66. package/dist/{token-C8Uh7aUe.js → token-iaSGp76k.js} +356 -147
  67. package/dist/token-iaSGp76k.js.map +1 -0
  68. package/package.json +2 -2
  69. package/dist/engine-D8dlcSW6.js +0 -2
  70. package/dist/exec-DTO2oin0.js.map +0 -1
  71. package/dist/lifecycle-CRTWEbiP.js +0 -2
  72. package/dist/lifecycle-DUPXAOQS.js +0 -2
  73. package/dist/models-Cv6gprpy.js.map +0 -1
  74. package/dist/paths-Dz1WyRJG.js +0 -2
  75. package/dist/peer-mcp-personas-B22ncnbQ.js.map +0 -1
  76. package/dist/server-setup-BM9Smfek.js.map +0 -1
  77. package/dist/token-C8Uh7aUe.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as PATHS } from "./paths-BXrbboAS.js";
1
+ import { t as PATHS } from "./paths-Bg1_DWhi.js";
2
2
  import consola from "consola";
3
3
  import path from "node:path";
4
4
  import { randomBytes, randomUUID } from "node:crypto";
@@ -83,123 +83,6 @@ const githubAgentGraphQLHeaders = (state, features) => ({
83
83
  ...features ? { "GraphQL-Features": features } : {}
84
84
  });
85
85
  //#endregion
86
- //#region src/lib/error.ts
87
- var HTTPError = class extends Error {
88
- response;
89
- constructor(message, response) {
90
- super(message);
91
- this.response = response;
92
- }
93
- };
94
- async function forwardError(c, error) {
95
- consola.error(`Error occurred at ${c.req.path}:`, error);
96
- if (error instanceof HTTPError) {
97
- const errorText = await error.response.text().catch(() => "");
98
- let errorJson;
99
- try {
100
- errorJson = JSON.parse(errorText);
101
- } catch {
102
- errorJson = void 0;
103
- }
104
- if (isContextOverflow(error.response.status, errorJson, errorText)) {
105
- const upstream = resolveErrorMessage(errorJson, errorText);
106
- consola.error("HTTP error (mapped to overflow):", errorJson ?? errorText);
107
- return c.json({
108
- type: "error",
109
- error: {
110
- type: "invalid_request_error",
111
- message: `prompt is too long: ${upstream}`
112
- }
113
- }, 400);
114
- }
115
- const responseStatus = error.response.status === 401 ? 503 : error.response.status;
116
- if (isAnthropicError(errorJson)) {
117
- consola.error("HTTP error:", errorJson);
118
- return c.json(errorJson, responseStatus);
119
- }
120
- const message = resolveErrorMessage(errorJson, errorText);
121
- consola.error("HTTP error:", errorJson ?? errorText);
122
- return c.json({
123
- type: "error",
124
- error: {
125
- type: resolveErrorType(responseStatus),
126
- message
127
- }
128
- }, responseStatus);
129
- }
130
- return c.json({
131
- type: "error",
132
- error: {
133
- type: "api_error",
134
- message: error instanceof Error ? error.message : String(error)
135
- }
136
- }, 500);
137
- }
138
- function resolveErrorMessage(errorJson, fallback) {
139
- if (typeof errorJson !== "object" || errorJson === null) return fallback;
140
- const errorRecord = errorJson;
141
- if (errorRecord.message !== void 0) return String(errorRecord.message);
142
- if (typeof errorRecord.error === "object" && errorRecord.error !== null) {
143
- const nestedRecord = errorRecord.error;
144
- if (nestedRecord.message !== void 0) return String(nestedRecord.message);
145
- }
146
- return fallback;
147
- }
148
- /**
149
- * Check if a parsed JSON body is already in Anthropic error format:
150
- * { type: "error", error: { type: "...", message: "..." } }
151
- */
152
- function isAnthropicError(json) {
153
- if (typeof json !== "object" || json === null) return false;
154
- const record = json;
155
- if (record.type !== "error") return false;
156
- if (typeof record.error !== "object" || record.error === null) return false;
157
- const inner = record.error;
158
- return typeof inner.type === "string" && typeof inner.message === "string";
159
- }
160
- const CONTEXT_OVERFLOW_SUBSTRINGS = [
161
- "prompt is too long",
162
- "context_length_exceeded",
163
- "context length exceeded",
164
- "input is too long",
165
- "maximum context length",
166
- "too many tokens"
167
- ];
168
- /**
169
- * Detect upstream context-overflow errors so we can remap them to a 400
170
- * "prompt is too long" shape that triggers Claude Code self-compaction.
171
- *
172
- * Always remaps 413 (treated as a hard payload-size signal regardless of
173
- * body wording). Remaps 400 only when the error text contains one of the
174
- * known overflow substrings — a regular 400 (e.g. "model not found") must
175
- * NOT remap.
176
- */
177
- function isContextOverflow(status, errorJson, errorText) {
178
- if (status === 413) return true;
179
- if (status !== 400) return false;
180
- const haystack = (errorText + " " + (typeof errorJson === "object" && errorJson !== null ? JSON.stringify(errorJson) : "")).toLowerCase();
181
- return CONTEXT_OVERFLOW_SUBSTRINGS.some((s) => haystack.includes(s));
182
- }
183
- /**
184
- * Map HTTP status to Anthropic error type.
185
- *
186
- * Note: a 401 from upstream is remapped to 503 in `forwardError` BEFORE
187
- * this function is called (no-401 invariant — see comment there). The
188
- * 401 → "authentication_error" mapping below is preserved for
189
- * defensive coverage in case any code path calls `resolveErrorType`
190
- * directly with an unsanitized status.
191
- */
192
- function resolveErrorType(status) {
193
- if (status === 400) return "invalid_request_error";
194
- if (status === 401) return "authentication_error";
195
- if (status === 403) return "permission_error";
196
- if (status === 404) return "not_found_error";
197
- if (status === 429) return "rate_limit_error";
198
- if (status === 503) return "overloaded_error";
199
- if (status === 529) return "overloaded_error";
200
- return "api_error";
201
- }
202
- //#endregion
203
86
  //#region src/lib/upstream-retry.ts
204
87
  /**
205
88
  * Shared transient-failure retry for upstream calls (`src/lib/upstream-retry.ts`).
@@ -234,6 +117,154 @@ const DEFAULT_RETRY_STATUSES = [
234
117
  503,
235
118
  504
236
119
  ];
120
+ const TRANSPORT_MESSAGE_MAX = 512;
121
+ const TRANSIENT_CODES = /* @__PURE__ */ new Set([
122
+ "ECONNRESET",
123
+ "ETIMEDOUT",
124
+ "EPIPE",
125
+ "EAI_AGAIN",
126
+ "UND_ERR_CONNECT_TIMEOUT",
127
+ "UND_ERR_HEADERS_TIMEOUT",
128
+ "UND_ERR_SOCKET"
129
+ ]);
130
+ const DETERMINISTIC_CODES = /* @__PURE__ */ new Set([
131
+ "ENOTFOUND",
132
+ "ECONNREFUSED",
133
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
134
+ "SELF_SIGNED_CERT_IN_CHAIN",
135
+ "UNABLE_TO_GET_ISSUER_CERT",
136
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
137
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
138
+ "CERT_HAS_EXPIRED",
139
+ "CERT_NOT_YET_VALID",
140
+ "ERR_TLS_CERT_ALTNAME_INVALID",
141
+ "ERR_TLS_CERT_SIGNATURE_ALGORITHM_UNSUPPORTED",
142
+ "ERR_SSL_WRONG_VERSION_NUMBER"
143
+ ]);
144
+ const CREDENTIAL_RE = /\b(eyJ[A-Za-z0-9_-]{20,}(?:\.[A-Za-z0-9_-]+){0,2}|gh[opsu]_[A-Za-z0-9_]{20,}|Bearer\s+\S{20,})\b/gi;
145
+ const SECRET_ASSIGNMENT_RE = /\b(authorization|api[_-]?key|access[_-]?token|token|password)\s*[:=]\s*([^\s,;]+)/gi;
146
+ const REQUEST_BODY_RE = /\b(request\s+body|body)\s*[:=]\s*.*$/gi;
147
+ var TransportExhaustionError = class extends Error {
148
+ endpoint;
149
+ label;
150
+ attempts;
151
+ classification;
152
+ lastError;
153
+ constructor(metadata, cause) {
154
+ const target = metadata.endpoint ?? metadata.label ?? "upstream";
155
+ const lastError = {
156
+ name: sanitizeTransportText(metadata.lastError.name),
157
+ message: sanitizeTransportText(metadata.lastError.message),
158
+ code: metadata.lastError.code ? sanitizeTransportText(metadata.lastError.code) : void 0,
159
+ causeCode: metadata.lastError.causeCode ? sanitizeTransportText(metadata.lastError.causeCode) : void 0
160
+ };
161
+ const code = lastError.causeCode ?? lastError.code;
162
+ const suffix = code ? ` (${code})` : "";
163
+ super(`Upstream transport ${metadata.classification === "transient" ? "failed" : "is unreachable"} at ${sanitizeTransportText(target)} after ${metadata.attempts} attempt${metadata.attempts === 1 ? "" : "s"}: ${lastError.name}: ${lastError.message}${suffix}`, { cause });
164
+ this.name = "TransportExhaustionError";
165
+ this.endpoint = metadata.endpoint ? sanitizeTransportText(metadata.endpoint) : void 0;
166
+ this.label = metadata.label ? sanitizeTransportText(metadata.label) : void 0;
167
+ this.attempts = metadata.attempts;
168
+ this.classification = metadata.classification;
169
+ this.lastError = lastError;
170
+ }
171
+ };
172
+ function readStringProperty(value, key) {
173
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return;
174
+ try {
175
+ const property = value[key];
176
+ return typeof property === "string" ? property : void 0;
177
+ } catch {
178
+ return;
179
+ }
180
+ }
181
+ function readCause(value) {
182
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return;
183
+ try {
184
+ return value.cause;
185
+ } catch {
186
+ return;
187
+ }
188
+ }
189
+ function safeString(value) {
190
+ try {
191
+ return String(value);
192
+ } catch {
193
+ return "[unserializable error]";
194
+ }
195
+ }
196
+ function sanitizeTransportText(value) {
197
+ const sanitized = value.replace(/\r\n|\r|\n/g, " ").replace(CREDENTIAL_RE, "[REDACTED]").replace(SECRET_ASSIGNMENT_RE, "$1=[REDACTED]").replace(REQUEST_BODY_RE, "$1=[REDACTED]");
198
+ return sanitized.length > TRANSPORT_MESSAGE_MAX ? `${sanitized.slice(0, TRANSPORT_MESSAGE_MAX)}…` : sanitized;
199
+ }
200
+ /**
201
+ * Classify errors thrown before the first response byte. Deterministic
202
+ * connectivity/configuration signals win over generic runtime wording such as
203
+ * TypeError("fetch failed"), because the nested cause is more specific.
204
+ */
205
+ function classifyTransportError(error, opts = {}) {
206
+ const name = readStringProperty(error, "name") ?? "Error";
207
+ const rawMessage = readStringProperty(error, "message") ?? (typeof error === "string" ? error : safeString(error));
208
+ const message = sanitizeTransportText(rawMessage);
209
+ const code = readStringProperty(error, "code");
210
+ const causes = [];
211
+ const seen = /* @__PURE__ */ new Set();
212
+ let current = readCause(error);
213
+ for (let depth = 0; current !== void 0 && depth < 4; depth++) {
214
+ if ((typeof current === "object" || typeof current === "function") && current !== null) {
215
+ if (seen.has(current)) break;
216
+ seen.add(current);
217
+ }
218
+ causes.push(current);
219
+ current = readCause(current);
220
+ }
221
+ const causeCode = causes.map((cause) => readStringProperty(cause, "code")).find((value) => value !== void 0);
222
+ const codes = [code, ...causes.map((cause) => readStringProperty(cause, "code"))].filter((value) => value !== void 0).map((value) => value.toUpperCase());
223
+ const messages = [rawMessage, ...causes.map((cause) => readStringProperty(cause, "message") ?? "")].join(" ").toLowerCase();
224
+ if (opts.callerCancelled) return {
225
+ classification: "cancelled",
226
+ name,
227
+ message,
228
+ code,
229
+ causeCode
230
+ };
231
+ if (codes.some((value) => DETERMINISTIC_CODES.has(value)) || messages.includes("self signed certificate") || messages.includes("certificate has expired") || messages.includes("unable to verify the first certificate") || messages.includes("unable to get local issuer certificate") || messages.includes("hostname/ip does not match certificate") || messages.includes("tls handshake") || messages.includes("ssl routines") || messages.includes("econnrefused") || messages.includes("enotfound")) return {
232
+ classification: "deterministic",
233
+ name,
234
+ message,
235
+ code,
236
+ causeCode
237
+ };
238
+ if (name === "AbortError" || name === "TimeoutError" || name === "InactivityTimeout" || codes.some((value) => TRANSIENT_CODES.has(value)) || messages.includes("terminated") || messages.includes("fetch failed") || messages.includes("network error") || messages.includes("socket hang up") || messages.includes("socket closed") || messages.includes("econnreset") || messages.includes("etimedout") || messages.includes("epipe") || messages.includes("eai_again")) return {
239
+ classification: "transient",
240
+ name,
241
+ message,
242
+ code,
243
+ causeCode
244
+ };
245
+ return {
246
+ classification: "non_transport",
247
+ name,
248
+ message,
249
+ code,
250
+ causeCode
251
+ };
252
+ }
253
+ function transportExhaustion(error, attempts, opts, details) {
254
+ if (details.classification !== "transient" && details.classification !== "deterministic") throw new TypeError("transportExhaustion requires a transport error");
255
+ return new TransportExhaustionError({
256
+ endpoint: opts.endpoint ?? opts.label,
257
+ label: opts.label,
258
+ attempts,
259
+ classification: details.classification,
260
+ lastError: {
261
+ name: details.name,
262
+ message: details.message,
263
+ code: details.code,
264
+ causeCode: details.causeCode
265
+ }
266
+ }, error);
267
+ }
237
268
  function parseRetryAfter(headerValue) {
238
269
  if (!headerValue) return void 0;
239
270
  const secs = Number(headerValue);
@@ -241,27 +272,6 @@ function parseRetryAfter(headerValue) {
241
272
  const dateMs = Date.parse(headerValue);
242
273
  if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
243
274
  }
244
- /**
245
- * A thrown fetch error that is worth retrying — network resets, broken
246
- * pipes, DNS hiccups, and timeout aborts. NOTE: the caller must already
247
- * have ruled out a user cancel (caller signal aborted) before calling
248
- * this, since a timeout abort and a user-cancel abort look identical.
249
- */
250
- function isTransientNetworkError(err) {
251
- const e = err;
252
- if (!e) return false;
253
- if (e.name === "AbortError" || e.name === "TimeoutError") return true;
254
- const msg = (e.message ?? "").toLowerCase();
255
- if (msg.includes("terminated") || msg.includes("fetch failed") || msg.includes("network") || msg.includes("socket") || msg.includes("econnreset") || msg.includes("etimedout") || msg.includes("econnrefused") || msg.includes("epipe") || msg.includes("enotfound")) return true;
256
- const code = e.code ?? e.cause?.code;
257
- return code !== void 0 && [
258
- "ECONNRESET",
259
- "ETIMEDOUT",
260
- "ECONNREFUSED",
261
- "EPIPE",
262
- "ENOTFOUND"
263
- ].includes(code);
264
- }
265
275
  /** Sleep that resolves early (does not reject) when `signal` aborts — the
266
276
  * retry loop re-checks `signal.aborted` at the top and throws there. */
267
277
  function abortableSleep(ms, signal) {
@@ -310,11 +320,14 @@ async function fetchWithTransientRetry(doFetch, opts = {}) {
310
320
  if (res && !retryStatuses.includes(res.status)) return res;
311
321
  if (caught !== void 0) {
312
322
  if (signal?.aborted) throw caught;
313
- if (!isTransientNetworkError(caught)) throw caught;
323
+ const details = classifyTransportError(caught);
324
+ if (details.classification === "deterministic") throw transportExhaustion(caught, attempt, opts, details);
325
+ if (details.classification !== "transient") throw caught;
314
326
  }
315
327
  if (attempt >= attempts) {
316
328
  if (res) return res;
317
- throw caught;
329
+ const details = classifyTransportError(caught);
330
+ throw transportExhaustion(caught, attempt, opts, details);
318
331
  }
319
332
  const retryAfterMs = res ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
320
333
  if (res?.body) try {
@@ -364,7 +377,14 @@ async function withTransientRetry(fn, opts = {}) {
364
377
  } catch (err) {
365
378
  if (signal?.aborted) throw err;
366
379
  const status = errorStatus(err);
367
- if (!(status !== void 0 && retryStatuses.includes(status) || isTransientNetworkError(err)) || attempt >= attempts) throw err;
380
+ const details = classifyTransportError(err);
381
+ if (status !== void 0) {
382
+ if (!retryStatuses.includes(status) || attempt >= attempts) throw err;
383
+ } else {
384
+ if (details.classification === "deterministic") throw transportExhaustion(err, attempt, opts, details);
385
+ if (details.classification !== "transient") throw err;
386
+ if (attempt >= attempts) throw transportExhaustion(err, attempt, opts, details);
387
+ }
368
388
  const retryAfterMs = parseRetryAfter(err?.response?.headers?.get?.("retry-after") ?? null);
369
389
  const expCap = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
370
390
  const delay = Math.min(maxDelayMs, retryAfterMs ?? Math.round(Math.random() * expCap));
@@ -374,6 +394,146 @@ async function withTransientRetry(fn, opts = {}) {
374
394
  }
375
395
  }
376
396
  //#endregion
397
+ //#region src/lib/error.ts
398
+ var HTTPError = class extends Error {
399
+ response;
400
+ constructor(message, response) {
401
+ super(message);
402
+ this.response = response;
403
+ }
404
+ };
405
+ async function forwardError(c, error) {
406
+ consola.error(`Error occurred at ${c.req.path}:`, error);
407
+ if (error instanceof TransportExhaustionError) {
408
+ const target = sanitizeTransportText(error.endpoint ?? error.label ?? c.req.path);
409
+ const diagnostic = formatTransportDiagnostic(error);
410
+ if (error.classification === "transient") return c.json({
411
+ type: "error",
412
+ error: {
413
+ type: "overloaded_error",
414
+ message: `Upstream transport was interrupted at ${target} after ${error.attempts} attempts; retry later. ${diagnostic}`
415
+ }
416
+ }, 503);
417
+ return c.json({
418
+ type: "error",
419
+ error: {
420
+ type: "api_error",
421
+ message: `Could not connect to upstream at ${target}. Check DNS, proxy, firewall, and TLS configuration. ${diagnostic}`
422
+ }
423
+ }, 502);
424
+ }
425
+ if (error instanceof HTTPError) {
426
+ const errorText = await error.response.text().catch(() => "");
427
+ let errorJson;
428
+ try {
429
+ errorJson = JSON.parse(errorText);
430
+ } catch {
431
+ errorJson = void 0;
432
+ }
433
+ if (isContextOverflow(error.response.status, errorJson, errorText)) {
434
+ const upstream = resolveErrorMessage(errorJson, errorText);
435
+ consola.error("HTTP error (mapped to overflow):", errorJson ?? errorText);
436
+ return c.json({
437
+ type: "error",
438
+ error: {
439
+ type: "invalid_request_error",
440
+ message: `prompt is too long: ${upstream}`
441
+ }
442
+ }, 400);
443
+ }
444
+ const responseStatus = error.response.status === 401 ? 503 : error.response.status;
445
+ if (isAnthropicError(errorJson)) {
446
+ consola.error("HTTP error:", errorJson);
447
+ return c.json(errorJson, responseStatus);
448
+ }
449
+ const message = resolveErrorMessage(errorJson, errorText);
450
+ consola.error("HTTP error:", errorJson ?? errorText);
451
+ return c.json({
452
+ type: "error",
453
+ error: {
454
+ type: resolveErrorType(responseStatus),
455
+ message
456
+ }
457
+ }, responseStatus);
458
+ }
459
+ return c.json({
460
+ type: "error",
461
+ error: {
462
+ type: "api_error",
463
+ message: error instanceof Error ? error.message : String(error)
464
+ }
465
+ }, 500);
466
+ }
467
+ function formatTransportDiagnostic(error) {
468
+ const code = error.lastError.causeCode ?? error.lastError.code;
469
+ const codeText = code ? ` (${sanitizeTransportText(code)})` : "";
470
+ return `${sanitizeTransportText(error.lastError.name)}: ${sanitizeTransportText(error.lastError.message)}${codeText}`;
471
+ }
472
+ function resolveErrorMessage(errorJson, fallback) {
473
+ if (typeof errorJson !== "object" || errorJson === null) return fallback;
474
+ const errorRecord = errorJson;
475
+ if (errorRecord.message !== void 0) return String(errorRecord.message);
476
+ if (typeof errorRecord.error === "object" && errorRecord.error !== null) {
477
+ const nestedRecord = errorRecord.error;
478
+ if (nestedRecord.message !== void 0) return String(nestedRecord.message);
479
+ }
480
+ return fallback;
481
+ }
482
+ /**
483
+ * Check if a parsed JSON body is already in Anthropic error format:
484
+ * { type: "error", error: { type: "...", message: "..." } }
485
+ */
486
+ function isAnthropicError(json) {
487
+ if (typeof json !== "object" || json === null) return false;
488
+ const record = json;
489
+ if (record.type !== "error") return false;
490
+ if (typeof record.error !== "object" || record.error === null) return false;
491
+ const inner = record.error;
492
+ return typeof inner.type === "string" && typeof inner.message === "string";
493
+ }
494
+ const CONTEXT_OVERFLOW_SUBSTRINGS = [
495
+ "prompt is too long",
496
+ "context_length_exceeded",
497
+ "context length exceeded",
498
+ "input is too long",
499
+ "maximum context length",
500
+ "too many tokens"
501
+ ];
502
+ /**
503
+ * Detect upstream context-overflow errors so we can remap them to a 400
504
+ * "prompt is too long" shape that triggers Claude Code self-compaction.
505
+ *
506
+ * Always remaps 413 (treated as a hard payload-size signal regardless of
507
+ * body wording). Remaps 400 only when the error text contains one of the
508
+ * known overflow substrings — a regular 400 (e.g. "model not found") must
509
+ * NOT remap.
510
+ */
511
+ function isContextOverflow(status, errorJson, errorText) {
512
+ if (status === 413) return true;
513
+ if (status !== 400) return false;
514
+ const haystack = (errorText + " " + (typeof errorJson === "object" && errorJson !== null ? JSON.stringify(errorJson) : "")).toLowerCase();
515
+ return CONTEXT_OVERFLOW_SUBSTRINGS.some((s) => haystack.includes(s));
516
+ }
517
+ /**
518
+ * Map HTTP status to Anthropic error type.
519
+ *
520
+ * Note: a 401 from upstream is remapped to 503 in `forwardError` BEFORE
521
+ * this function is called (no-401 invariant — see comment there). The
522
+ * 401 → "authentication_error" mapping below is preserved for
523
+ * defensive coverage in case any code path calls `resolveErrorType`
524
+ * directly with an unsanitized status.
525
+ */
526
+ function resolveErrorType(status) {
527
+ if (status === 400) return "invalid_request_error";
528
+ if (status === 401) return "authentication_error";
529
+ if (status === 403) return "permission_error";
530
+ if (status === 404) return "not_found_error";
531
+ if (status === 429) return "rate_limit_error";
532
+ if (status === 503) return "overloaded_error";
533
+ if (status === 529) return "overloaded_error";
534
+ return "api_error";
535
+ }
536
+ //#endregion
377
537
  //#region src/services/github/get-copilot-token.ts
378
538
  /**
379
539
  * Allowlist of hosts the router will trust as the Copilot API base URL.
@@ -936,7 +1096,22 @@ async function pollAccessToken(deviceCode, clientId = GITHUB_CLIENT_ID) {
936
1096
  //#region src/lib/token.ts
937
1097
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
938
1098
  /**
939
- * Replace a credential file atomically: temp + rename.
1099
+ * Backoff for a transient Windows rename failure. On Windows `fs.rename` over
1100
+ * an existing destination transiently fails with EPERM / EBUSY / EACCES when
1101
+ * anything else holds the target open for a moment (antivirus, the search
1102
+ * indexer, a backup agent). Nothing is wrong and the handle closes microseconds
1103
+ * later — but without a retry, a scan landing on the wrong millisecond throws
1104
+ * out of a credential write and costs the user a re-auth. Same delays as
1105
+ * `renameWithRetry` in `~/lib/claude-md-injection`, which documents the
1106
+ * underlying behavior at length.
1107
+ */
1108
+ const RENAME_RETRY_DELAYS_MS = [
1109
+ 50,
1110
+ 200,
1111
+ 500
1112
+ ];
1113
+ /**
1114
+ * Replace a credential file atomically: temp + rename, with bounded retry.
940
1115
  *
941
1116
  * A plain `fs.writeFile` truncates the destination before it writes, so a
942
1117
  * crash, a full disk, or a kill mid-write leaves a truncated token on disk and
@@ -963,7 +1138,17 @@ async function writeTokenFileAtomic(filePath, token) {
963
1138
  mode: 384,
964
1139
  flag: "wx"
965
1140
  });
966
- await fs.rename(tmp, filePath);
1141
+ let lastErr;
1142
+ let renamed = false;
1143
+ for (let attempt = 0; attempt <= RENAME_RETRY_DELAYS_MS.length; attempt++) try {
1144
+ await fs.rename(tmp, filePath);
1145
+ renamed = true;
1146
+ break;
1147
+ } catch (err) {
1148
+ lastErr = err;
1149
+ if (attempt < RENAME_RETRY_DELAYS_MS.length) await new Promise((resolve) => setTimeout(resolve, RENAME_RETRY_DELAYS_MS[attempt]));
1150
+ }
1151
+ if (!renamed) throw lastErr;
967
1152
  } catch (err) {
968
1153
  await fs.unlink(tmp).catch(() => {});
969
1154
  throw err;
@@ -972,15 +1157,39 @@ async function writeTokenFileAtomic(filePath, token) {
972
1157
  const writeGithubToken = (token) => writeTokenFileAtomic(PATHS.GITHUB_TOKEN_PATH, token);
973
1158
  const readGithubAgentToken = () => fs.readFile(PATHS.GITHUB_AGENT_TOKEN_PATH, "utf8");
974
1159
  const writeGithubAgentToken = (token) => writeTokenFileAtomic(PATHS.GITHUB_AGENT_TOKEN_PATH, token);
1160
+ /**
1161
+ * Fetch the Copilot token and keep it fresh in the background.
1162
+ *
1163
+ * Returns a disposer that stops the refresh loop. **Long-lived callers**
1164
+ * (`start`/`claude`/`codex`, via `setupAndServe`) can ignore it — the interval
1165
+ * is `unref()`d, so it never holds the event loop open on its own. The one-shot
1166
+ * `models` command calls it, so ownership of the timer is explicit rather than
1167
+ * implied by a runtime flag. (`check-usage` does not call this function at all;
1168
+ * it only needs `setupGitHubToken`.)
1169
+ *
1170
+ * Both halves are load-bearing, for different failure modes. Without the
1171
+ * `unref()`, `github-router models` printed its full correct output and then
1172
+ * hung forever: its success path just returns (only the failure branches call
1173
+ * `process.exit`), and the un-unref'd interval pinned the event loop. Without
1174
+ * the disposer, that fix would depend on a property no caller can see, so the
1175
+ * next one-shot command would inherit the same trap.
1176
+ */
975
1177
  const setupCopilotToken = async () => {
976
1178
  const { token, refresh_in } = await getCopilotToken();
977
1179
  state.copilotToken = token;
978
1180
  consola.debug("GitHub Copilot Token fetched successfully!");
979
1181
  if (state.showToken) consola.info("Copilot token:", token);
980
1182
  const refreshInterval = Math.max((refresh_in - 60) * 1e3, 1e3);
981
- setInterval(() => {
1183
+ const handle = setInterval(() => {
982
1184
  refreshCopilotToken("interval");
983
1185
  }, refreshInterval);
1186
+ handle.unref?.();
1187
+ let stopped = false;
1188
+ return () => {
1189
+ if (stopped) return;
1190
+ stopped = true;
1191
+ clearInterval(handle);
1192
+ };
984
1193
  };
985
1194
  let inflightRefresh;
986
1195
  let lastRefreshSuccess = 0;
@@ -1131,6 +1340,6 @@ async function warnIfAgentScopesInsufficient() {
1131
1340
  }
1132
1341
  }
1133
1342
  //#endregion
1134
- export { copilotHeaders as C, githubHeaders as D, githubAgentHeaders as E, state as O, copilotBaseUrl as S, githubAgentGraphQLHeaders as T, withTransientRetry as _, cacheCopilotVersion as a, GITHUB_API_BASE_URL as b, filterBetaHeader as c, resolveModel as d, sleep as f, fetchWithTransientRetry as g, isAllowedCopilotHost as h, tryRefreshAndRetry as i, isNullish as l, getGitHubUser as m, setupGitHubAgentToken as n, cacheModels as o, getModels as p, setupGitHubToken as r, cacheVSCodeVersion as s, setupCopilotToken as t, resolveCodexModel as u, HTTPError as v, copilotVersion as w, GITHUB_GRAPHQL_URL as x, forwardError as y };
1343
+ export { state as A, GITHUB_GRAPHQL_URL as C, githubAgentGraphQLHeaders as D, copilotVersion as E, githubAgentHeaders as O, GITHUB_API_BASE_URL as S, copilotHeaders as T, forwardError as _, cacheCopilotVersion as a, sanitizeTransportText as b, filterBetaHeader as c, resolveModel as d, sleep as f, HTTPError as g, isAllowedCopilotHost as h, tryRefreshAndRetry as i, githubHeaders as k, isNullish as l, getGitHubUser as m, setupGitHubAgentToken as n, cacheModels as o, getModels as p, setupGitHubToken as r, cacheVSCodeVersion as s, setupCopilotToken as t, resolveCodexModel as u, classifyTransportError as v, copilotBaseUrl as w, withTransientRetry as x, fetchWithTransientRetry as y };
1135
1344
 
1136
- //# sourceMappingURL=token-C8Uh7aUe.js.map
1345
+ //# sourceMappingURL=token-iaSGp76k.js.map