github-router 0.3.261 → 0.3.269

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 (78) hide show
  1. package/dist/{attribution-settings-Bl0HHJQF.js → attribution-settings-B1txv_Lk.js} +4 -4
  2. package/dist/{attribution-settings-Bl0HHJQF.js.map → attribution-settings-B1txv_Lk.js.map} +1 -1
  3. package/dist/{auth-I8k2OOvA.js → auth-r-my7efo.js} +3 -3
  4. package/dist/{auth-I8k2OOvA.js.map → auth-r-my7efo.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-SOn7-cWc.js → check-usage-CZr2ZkG0.js} +4 -4
  8. package/dist/{check-usage-SOn7-cWc.js.map → check-usage-CZr2ZkG0.js.map} +1 -1
  9. package/dist/{claude-CEaI47j7.js → claude-BjNY0J6e.js} +15 -15
  10. package/dist/{claude-CEaI47j7.js.map → claude-BjNY0J6e.js.map} +1 -1
  11. package/dist/{codex-C6Eyz3EV.js → codex-CoRWpkHL.js} +5 -6
  12. package/dist/{codex-C6Eyz3EV.js.map → codex-CoRWpkHL.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-DpVC7HRL.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-BnPhH_AC.js → gate-discovery-D-7oua8b.js} +6 -6
  19. package/dist/{gate-discovery-BnPhH_AC.js.map → gate-discovery-D-7oua8b.js.map} +1 -1
  20. package/dist/{get-copilot-usage-CYpAsYrM.js → get-copilot-usage-CFEpeA7E.js} +2 -2
  21. package/dist/{get-copilot-usage-CYpAsYrM.js.map → get-copilot-usage-CFEpeA7E.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-CSJR24-y.js → internal-prompt-submit-Bve1s4z_.js} +5 -5
  27. package/dist/{internal-prompt-submit-CSJR24-y.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-CiflCJO-.js → internal-stop-hook-Db5o9RcZ.js} +6 -6
  31. package/dist/{internal-stop-hook-CiflCJO-.js.map → internal-stop-hook-Db5o9RcZ.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-uQgsKNNC.js → models-CJLt4XjP.js} +35 -30
  42. package/dist/models-CJLt4XjP.js.map +1 -0
  43. package/dist/{orchestration-BiZviyam.js → orchestration-B_Q3ncXI.js} +3 -3
  44. package/dist/{orchestration-BiZviyam.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-V4AXhm9B.js → peer-mcp-personas-DSbBlgFt.js} +119 -45
  49. package/dist/peer-mcp-personas-DSbBlgFt.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-BLwsOFCI.js → provision-CA-xwnFu.js} +5 -5
  55. package/dist/{provision-BLwsOFCI.js.map → provision-CA-xwnFu.js.map} +1 -1
  56. package/dist/{serve-De-kqWXM.js → serve-DHny4ewF.js} +20 -15
  57. package/dist/{serve-De-kqWXM.js.map → serve-DHny4ewF.js.map} +1 -1
  58. package/dist/{server-setup-BeeT8m-w.js → server-setup-Dd293T1O.js} +479 -75
  59. package/dist/server-setup-Dd293T1O.js.map +1 -0
  60. package/dist/{start-DEC0M8UH.js → start-P2rmHBkL.js} +3 -3
  61. package/dist/{start-DEC0M8UH.js.map → start-P2rmHBkL.js.map} +1 -1
  62. package/dist/{stop-gate-hook-DoYD9Wt8.js → stop-gate-hook-BqpumJtl.js} +12 -7
  63. package/dist/{stop-gate-hook-DoYD9Wt8.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-DWknHe9M.js → token-C_q95GF7.js} +124 -14
  67. package/dist/{token-DWknHe9M.js.map → token-C_q95GF7.js.map} +1 -1
  68. package/package.json +2 -2
  69. package/dist/engine-B4Ohhv5i.js +0 -2
  70. package/dist/exec-DTO2oin0.js.map +0 -1
  71. package/dist/file-log-reporter-vI8qCFNM.js +0 -158
  72. package/dist/file-log-reporter-vI8qCFNM.js.map +0 -1
  73. package/dist/lifecycle-CRTWEbiP.js +0 -2
  74. package/dist/lifecycle-DUPXAOQS.js +0 -2
  75. package/dist/models-uQgsKNNC.js.map +0 -1
  76. package/dist/paths-Dz1WyRJG.js +0 -2
  77. package/dist/peer-mcp-personas-V4AXhm9B.js.map +0 -1
  78. package/dist/server-setup-BeeT8m-w.js.map +0 -1
@@ -1,12 +1,12 @@
1
- import { A as searchWeb, B as readIteratorWithTimeout, Dt as toolbeltPathOverride, F as isAdvisorRequested, H as handleMcpDelete, I as buildAnthropicErrorEvent, It as withOneMSuffix, L as buildOpenAIErrorEvent, Lt as withInstallLock, M as ADVISOR_TOOL_INSTRUCTIONS, Mt as UPSTREAM_FETCH_TIMEOUT_MS, N as buildAdvisorStream, Nt as UPSTREAM_INACTIVITY_TIMEOUT_MS, P as injectAdvisorTool, Pt as generateRandomPort, R as isControllerClosedError, U as handleMcpPost, V as relayAnthropicStream, W as agentToolsEnabled, at as countTokens, ct as assembleResponsesPayload, dt as createResponses, ft as createChatCompletions, ht as parseJsonOrDiagnose, it as shimDefaultsToXhigh, j as ADVISOR_INTERNAL_TOOL_NAME, lt as resolveMcpToolTimeoutMs, mt as readResponseBodyCapped, ot as createMessages, pt as MAX_RESPONSE_BODY_BYTES, r as assertMcpToolSurfaceConsistent, st as getTokenCount, ut as pickEndpoint, w as toolbeltEnabled, z as logStreamError } from "./peer-mcp-personas-V4AXhm9B.js";
1
+ import { A as searchWeb, B as readIteratorWithTimeout, Dt as toolbeltPathOverride, F as isAdvisorRequested, H as handleMcpDelete, I as buildAnthropicErrorEvent, It as withOneMSuffix, L as buildOpenAIErrorEvent, Lt as withInstallLock, M as ADVISOR_TOOL_INSTRUCTIONS, Mt as UPSTREAM_FETCH_TIMEOUT_MS, N as buildAdvisorStream, Nt as UPSTREAM_INACTIVITY_TIMEOUT_MS, P as injectAdvisorTool, Pt as generateRandomPort, R as isControllerClosedError, U as handleMcpPost, V as relayAnthropicStream, W as agentToolsEnabled, at as countTokens, ct as assembleResponsesPayload, dt as createResponses, ft as createChatCompletions, ht as parseJsonOrDiagnose, it as shimDefaultsToXhigh, j as ADVISOR_INTERNAL_TOOL_NAME, lt as resolveMcpToolTimeoutMs, mt as readResponseBodyCapped, ot as createMessages, pt as MAX_RESPONSE_BODY_BYTES, r as assertMcpToolSurfaceConsistent, st as getTokenCount, ut as pickEndpoint, w as toolbeltEnabled, z as logStreamError } from "./peer-mcp-personas-DSbBlgFt.js";
2
2
  import { t as getPackageVersion } from "./version-_Q1WpsQp.js";
3
- import { i as ensurePaths, t as PATHS } from "./paths-BXrbboAS.js";
4
- import { C as copilotHeaders, O as state, S as copilotBaseUrl, a as cacheCopilotVersion, c as filterBetaHeader, d as resolveModel, f as sleep, g as fetchWithTransientRetry, i as tryRefreshAndRetry, l as isNullish, n as setupGitHubAgentToken, o as cacheModels, r as setupGitHubToken, s as cacheVSCodeVersion, t as setupCopilotToken, v as HTTPError, y as forwardError } from "./token-DWknHe9M.js";
5
- import { t as getCopilotUsage } from "./get-copilot-usage-CYpAsYrM.js";
6
- import { a as resolveExecutable, n as killManagedTree, o as runCommandCapture, r as parseBoolEnv, s as runCommandVoid } from "./exec-DTO2oin0.js";
3
+ import { i as ensurePaths, t as PATHS } from "./paths-Bg1_DWhi.js";
4
+ import { C as copilotHeaders, O as state, S as copilotBaseUrl, a as cacheCopilotVersion, c as filterBetaHeader, d as resolveModel, f as sleep, g as fetchWithTransientRetry, i as tryRefreshAndRetry, l as isNullish, n as setupGitHubAgentToken, o as cacheModels, r as setupGitHubToken, s as cacheVSCodeVersion, t as setupCopilotToken, v as HTTPError, y as forwardError } from "./token-C_q95GF7.js";
5
+ import { t as getCopilotUsage } from "./get-copilot-usage-CFEpeA7E.js";
6
+ import { a as resolveExecutable, n as killManagedTree, o as runCommandCapture, r as parseBoolEnv, s as runCommandVoid } from "./exec-y8C_MU8A.js";
7
7
  import consola from "consola";
8
8
  import * as fs$2 from "node:fs";
9
- import { existsSync } from "node:fs";
9
+ import fs, { existsSync } from "node:fs";
10
10
  import * as nodePath from "node:path";
11
11
  import path from "node:path";
12
12
  import { randomUUID } from "node:crypto";
@@ -15,6 +15,7 @@ import os from "node:os";
15
15
  import process$1 from "node:process";
16
16
  import { spawn } from "node:child_process";
17
17
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
18
+ import { Writable } from "node:stream";
18
19
  import { serve } from "srvx";
19
20
  import { createBodyTooLargeError, limitRequestBody } from "srvx/body-limit";
20
21
  import { getProxyForUrl } from "proxy-from-env";
@@ -353,6 +354,170 @@ async function runSelfUpdate(opts) {
353
354
  }
354
355
  }
355
356
  //#endregion
357
+ //#region src/lib/file-log-reporter.ts
358
+ const MAX_LOG_BYTES = 1048576;
359
+ const DEDUP_MAX = 1e3;
360
+ const ARG_MAX_LEN = 2048;
361
+ const DEDUP_KEY_MAX_LEN = 200;
362
+ 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/g;
363
+ const ALLOWED_TYPES = /* @__PURE__ */ new Set([
364
+ "fatal",
365
+ "error",
366
+ "warn"
367
+ ]);
368
+ function sanitize(line) {
369
+ return line.replace(CREDENTIAL_RE, "[REDACTED]");
370
+ }
371
+ function serializeArg(arg) {
372
+ if (typeof arg === "string") return arg;
373
+ if (arg instanceof Error) {
374
+ const parts = [arg.message];
375
+ if (arg.stack) parts.push(arg.stack);
376
+ return parts.join("\n");
377
+ }
378
+ return String(arg);
379
+ }
380
+ function formatLogLine(logObj) {
381
+ return sanitize(`${logObj.date.toISOString()} [${(logObj.type ?? "error").toUpperCase()}] ${logObj.args.map((a) => {
382
+ const s = serializeArg(a);
383
+ return s.length > ARG_MAX_LEN ? s.slice(0, ARG_MAX_LEN) + "…" : s;
384
+ }).join(" ").replace(/\r\n|\r|\n/g, "\\n")}\n`);
385
+ }
386
+ function makeDedupeKey(logObj) {
387
+ const firstArg = logObj.args.length > 0 ? serializeArg(logObj.args[0]) : "";
388
+ const key = `${logObj.type}:${firstArg}`;
389
+ return key.length > DEDUP_KEY_MAX_LEN ? key.slice(0, DEDUP_KEY_MAX_LEN) : key;
390
+ }
391
+ /**
392
+ * Construction-time rotation: rename the log aside if it's already over the
393
+ * cap before we start appending. Runs with no descriptor held (the instance
394
+ * fd is opened lazily on the first `log()`), so a plain path stat + rename is
395
+ * correct here. The per-`log()` ceiling check lives in `rotateIfNeeded()`.
396
+ */
397
+ function rotateAtStartup(filePath) {
398
+ let size;
399
+ try {
400
+ size = fs.statSync(filePath).size;
401
+ } catch {
402
+ return;
403
+ }
404
+ if (size <= MAX_LOG_BYTES) return;
405
+ try {
406
+ fs.renameSync(filePath, filePath + ".1");
407
+ } catch {}
408
+ }
409
+ var FileLogReporter = class FileLogReporter {
410
+ filePath;
411
+ seen = /* @__PURE__ */ new Set();
412
+ bytesSinceCheck = 0;
413
+ static ROTATE_CHECK_BYTES = MAX_LOG_BYTES / 2;
414
+ fd;
415
+ constructor(filePath) {
416
+ this.filePath = filePath;
417
+ rotateAtStartup(filePath);
418
+ }
419
+ ensureFd() {
420
+ if (this.fd !== void 0) return this.fd;
421
+ try {
422
+ this.fd = fs.openSync(this.filePath, "a", 384);
423
+ } catch {
424
+ this.fd = void 0;
425
+ }
426
+ return this.fd;
427
+ }
428
+ closeFd() {
429
+ if (this.fd === void 0) return;
430
+ try {
431
+ fs.closeSync(this.fd);
432
+ } catch {}
433
+ this.fd = void 0;
434
+ }
435
+ /**
436
+ * Enforce the MAX_LOG_BYTES ceiling. Sizes the LIVE file (fstat on the open
437
+ * fd when we hold one — no path race — else a path stat), and on overflow
438
+ * CLOSES the fd before renaming. Closing first is load-bearing on Windows
439
+ * (renaming a file with an open handle fails with EBUSY/EPERM) and correct
440
+ * on POSIX too (a held append-fd would otherwise keep writing into the
441
+ * renamed `.1` inode). The fd is left closed so the next write reopens the
442
+ * freshly-created file.
443
+ */
444
+ rotateIfNeeded() {
445
+ let size;
446
+ try {
447
+ size = this.fd !== void 0 ? fs.fstatSync(this.fd).size : fs.statSync(this.filePath).size;
448
+ } catch {
449
+ return;
450
+ }
451
+ if (size <= MAX_LOG_BYTES) return;
452
+ this.closeFd();
453
+ try {
454
+ fs.renameSync(this.filePath, this.filePath + ".1");
455
+ } catch {}
456
+ }
457
+ /**
458
+ * Close the held descriptor. Safe to call repeatedly and after a write
459
+ * failure. Optional for correctness (writeSync flushes immediately and the
460
+ * OS closes fds at process exit), but lets a long-lived host release the
461
+ * handle deterministically on shutdown.
462
+ */
463
+ close() {
464
+ this.closeFd();
465
+ }
466
+ log(logObj, _ctx) {
467
+ if (!ALLOWED_TYPES.has(logObj.type)) return;
468
+ const key = makeDedupeKey(logObj);
469
+ if (this.seen.has(key)) return;
470
+ if (this.seen.size >= DEDUP_MAX) this.seen.clear();
471
+ this.seen.add(key);
472
+ const line = formatLogLine(logObj);
473
+ this.bytesSinceCheck += line.length;
474
+ if (this.bytesSinceCheck >= FileLogReporter.ROTATE_CHECK_BYTES) {
475
+ this.rotateIfNeeded();
476
+ this.bytesSinceCheck = 0;
477
+ }
478
+ const fd = this.ensureFd();
479
+ if (fd === void 0) return;
480
+ try {
481
+ fs.writeSync(fd, line);
482
+ } catch {
483
+ this.closeFd();
484
+ }
485
+ }
486
+ };
487
+ const nullStream = new Writable({ write(_chunk, _encoding, cb) {
488
+ cb();
489
+ } });
490
+ /**
491
+ * Switch consola to file-only mode for TUI sessions.
492
+ * Removes the terminal reporter and installs a file reporter that
493
+ * persists errors and warnings to disk with dedup and credential scrubbing.
494
+ *
495
+ * Also sinks consola's stdout/stderr streams as belt-and-suspenders:
496
+ * even if a terminal reporter is re-added, it cannot write to the terminal.
497
+ * Crash handlers that call process.stderr.write() directly are unaffected.
498
+ * FileLogReporter uses fs.writeSync() directly and is also unaffected.
499
+ */
500
+ function enableFileLogging() {
501
+ const reporter = new FileLogReporter(PATHS.ERROR_LOG_PATH);
502
+ consola.options.throttle = 0;
503
+ consola.setReporters([reporter]);
504
+ consola.options.stdout = nullStream;
505
+ consola.options.stderr = nullStream;
506
+ fileLoggingActive = true;
507
+ }
508
+ /**
509
+ * True once `enableFileLogging` has run. The file reporter accepts only
510
+ * fatal/error/warn (`ALLOWED_TYPES`), so an `info`-level line — which is what
511
+ * the per-request summary is — reaches no consumer at all in this mode.
512
+ *
513
+ * Exposed so callers can skip work whose ONLY purpose is to populate that
514
+ * line. See `requestLogVisible` in `~/lib/request-log`.
515
+ */
516
+ let fileLoggingActive = false;
517
+ function isFileLoggingEnabled() {
518
+ return fileLoggingActive;
519
+ }
520
+ //#endregion
356
521
  //#region src/lib/model-validation.ts
357
522
  const ENDPOINT_ALIASES = {
358
523
  "/chat/completions": "/chat/completions",
@@ -880,61 +1045,108 @@ function initProxyFromEnv() {
880
1045
  }
881
1046
  }
882
1047
  //#endregion
883
- //#region package.json
884
- var name = "github-router";
885
- var version = "0.3.261";
886
- //#endregion
887
- //#region src/lib/approval.ts
888
- const awaitApproval = async () => {
889
- if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected by user", Response.json({ message: "Request rejected by user" }, { status: 403 }));
890
- };
891
- //#endregion
892
- //#region src/lib/rate-limit.ts
893
- const RATE_LIMIT_QUEUE_TIMEOUT_MS = 5e3;
894
- let rateLimitChain = Promise.resolve();
895
- async function checkRateLimit(state) {
896
- if (state.rateLimitSeconds === void 0) return;
897
- const ticket = { aborted: false };
898
- const myTurn = rateLimitChain.then(() => doCheck(state, ticket));
899
- rateLimitChain = myTurn.catch(() => {});
900
- return Promise.race([myTurn, sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {
901
- ticket.aborted = true;
902
- throw new HTTPError("Rate limit queue wait exceeded", Response.json({
903
- type: "error",
904
- error: {
905
- type: "rate_limit_error",
906
- message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`
907
- }
908
- }, { status: 429 }));
909
- })]);
1048
+ //#region src/lib/request-log.ts
1049
+ /**
1050
+ * Rolling request-body size distribution.
1051
+ *
1052
+ * Motivation: the cost of the `/v1/messages` prologue (substring guards, JSON
1053
+ * parses, re-serialization) scales with body size, but the proxy read
1054
+ * `content-length` and never recorded it so there was no way to tell whether
1055
+ * a prologue optimization was worth anything. Benchmarks at 4.5 MiB mean
1056
+ * nothing if the real p50 is 40 KiB.
1057
+ *
1058
+ * Bounded by construction: a fixed-size ring, not a growing array.
1059
+ */
1060
+ const BODY_SIZE_RING = 512;
1061
+ const bodySizes = new Float64Array(BODY_SIZE_RING);
1062
+ let bodySizeCount = 0;
1063
+ let bodySizeIdx = 0;
1064
+ /** Record one request body size (bytes). Cheap; safe to call per request. */
1065
+ function recordBodySize(bytes) {
1066
+ if (!Number.isFinite(bytes) || bytes < 0) return;
1067
+ bodySizes[bodySizeIdx] = bytes;
1068
+ bodySizeIdx = (bodySizeIdx + 1) % BODY_SIZE_RING;
1069
+ if (bodySizeCount < BODY_SIZE_RING) bodySizeCount++;
1070
+ }
1071
+ /** Percentile snapshot of observed body sizes, or undefined if none seen. */
1072
+ function bodySizeStats() {
1073
+ if (bodySizeCount === 0) return void 0;
1074
+ const s = Array.from(bodySizes.slice(0, bodySizeCount)).sort((a, b) => a - b);
1075
+ const at = (q) => s[Math.min(s.length - 1, Math.floor(s.length * q))];
1076
+ return {
1077
+ count: bodySizeCount,
1078
+ p50: at(.5),
1079
+ p95: at(.95),
1080
+ p99: at(.99),
1081
+ max: s[s.length - 1]
1082
+ };
910
1083
  }
911
- async function doCheck(state, ticket) {
912
- if (state.rateLimitSeconds === void 0) return;
913
- if (ticket.aborted) return;
914
- const now = Date.now();
915
- if (!state.lastRequestTimestamp) {
916
- state.lastRequestTimestamp = now;
917
- return;
918
- }
919
- const elapsedSeconds = (now - state.lastRequestTimestamp) / 1e3;
920
- if (elapsedSeconds > state.rateLimitSeconds) {
921
- state.lastRequestTimestamp = now;
922
- return;
923
- }
924
- const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds);
925
- if (!state.rateLimitWait) {
926
- consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
927
- throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
928
- }
929
- const waitTimeMs = waitTimeSeconds * 1e3;
930
- consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
931
- await sleep(waitTimeMs);
932
- if (ticket.aborted) return;
933
- state.lastRequestTimestamp = Date.now();
934
- consola.info("Rate limit wait completed, proceeding with request");
1084
+ /**
1085
+ * Print the observed body-size distribution, if any requests were seen.
1086
+ *
1087
+ * Without this the ring was WRITE-ONLY: `bodySizeStats()` had no caller
1088
+ * anywhere in `src/`, so the one number its own doc comment says the decision
1089
+ * needs ("benchmarks at 4.5 MiB mean nothing if the real p50 is 40 KiB") was
1090
+ * collected every request and readable by nobody.
1091
+ *
1092
+ * At `consola.info` because it is a once-per-session line answering a question
1093
+ * an operator or contributor actually asks; anything noisier would be a reason
1094
+ * to switch it off.
1095
+ */
1096
+ function logBodySizeStats() {
1097
+ const stats = bodySizeStats();
1098
+ if (!stats) return void 0;
1099
+ const line = `request body sizes (n=${stats.count}): p50 ${formatBytes(stats.p50)}, p95 ${formatBytes(stats.p95)}, p99 ${formatBytes(stats.p99)}, max ${formatBytes(stats.max)}`;
1100
+ consola.info(line);
1101
+ return line;
1102
+ }
1103
+ /**
1104
+ * Report the distribution once, on the way out.
1105
+ *
1106
+ * Self-registered rather than wired into each subcommand's shutdown chain
1107
+ * because `start` has NO `launchChild`/`onShutdown` to hang it on (see the
1108
+ * comment at its `startKeepAwake()` call), and a stat that only appeared under
1109
+ * `claude` would answer the question for one of three entry points. Same
1110
+ * pattern, and same reasoning, as `~/lib/keep-awake`'s own reaper.
1111
+ *
1112
+ * **Windows caveat, measured rather than assumed.** This reports on a clean
1113
+ * exit and on a signal the runtime actually delivers to JS (Ctrl-C in an
1114
+ * interactive console). It does NOT report when the process is killed by
1115
+ * another process on Windows: `child.kill(...)` and `taskkill` terminate
1116
+ * abruptly via `TerminateProcess`, dispatching no JS signal event, so NO
1117
+ * handler runs — not this one, and not `process.on("exit")` either. Verified
1118
+ * on a real win32 host: a child registering both handlers and killed by its
1119
+ * parent produced no output and exited with a null code.
1120
+ *
1121
+ * That is acceptable for a diagnostic — it is a measurement aid, not a
1122
+ * durability guarantee, and the interactive case it exists to serve (run a
1123
+ * session, Ctrl-C, read the distribution) does work. It is NOT acceptable for
1124
+ * anything durable: do not move credential, ledger, or cleanup work onto this
1125
+ * hook. The existing teardown paths that must survive a hard kill are built
1126
+ * differently on purpose (see `~/lib/keep-awake`, which relies on the OS
1127
+ * releasing a thread-scoped assertion rather than on a handler running, and
1128
+ * `~/lib/process-guard`, which uses a separate reaper process).
1129
+ *
1130
+ * Idempotent: `exit` can follow a signal handler, and both call this.
1131
+ */
1132
+ let statsReported = false;
1133
+ function reportOnce() {
1134
+ if (statsReported) return;
1135
+ statsReported = true;
1136
+ logBodySizeStats();
1137
+ }
1138
+ let exitHooksInstalled = false;
1139
+ function installBodySizeStatsExitHook() {
1140
+ if (exitHooksInstalled) return;
1141
+ exitHooksInstalled = true;
1142
+ process.once("exit", reportOnce);
1143
+ }
1144
+ /** Format a byte count compactly (1.2M / 15.3K / 900B). */
1145
+ function formatBytes(n) {
1146
+ if (n >= 1048576) return `${(n / 1048576).toFixed(1)}M`;
1147
+ if (n >= 1024) return `${(n / 1024).toFixed(1)}K`;
1148
+ return `${n}B`;
935
1149
  }
936
- //#endregion
937
- //#region src/lib/request-log.ts
938
1150
  /**
939
1151
  * Format a number with K/M suffix for compact display.
940
1152
  */
@@ -958,6 +1170,26 @@ function formatTokenInfo(inputTokens, outputTokens, model) {
958
1170
  return parts.join(" ");
959
1171
  }
960
1172
  /**
1173
+ * Will a per-request summary line actually reach a consumer?
1174
+ *
1175
+ * Two ways it will not:
1176
+ * - consola's level is below `info` (3), so the line is filtered out; or
1177
+ * - file-logging mode is active, whose reporter accepts only
1178
+ * fatal/error/warn and drops `info` entirely.
1179
+ *
1180
+ * Callers use this to skip work that exists ONLY to populate that line — most
1181
+ * notably the BPE tokenization on the `/chat/completions` path, which is
1182
+ * synchronous, scales with prompt size, and sits ahead of the upstream call.
1183
+ *
1184
+ * Deliberately conservative: when in doubt this returns true, so the failure
1185
+ * mode is "did unnecessary work", never "silently dropped a field the operator
1186
+ * was looking at".
1187
+ */
1188
+ function requestLogVisible() {
1189
+ if (isFileLoggingEnabled()) return false;
1190
+ return consola.level >= 3;
1191
+ }
1192
+ /**
961
1193
  * Print a single summary line for a completed request.
962
1194
  *
963
1195
  * Examples:
@@ -972,10 +1204,12 @@ function logRequest(info, model, startTime) {
972
1204
  else if (info.resolvedModel ?? info.model) parts.push(info.resolvedModel ?? info.model);
973
1205
  const tokenInfo = formatTokenInfo(info.inputTokens, info.outputTokens, model);
974
1206
  if (tokenInfo) parts.push(tokenInfo);
1207
+ if (info.bodyBytes !== void 0) parts.push(`body:${formatBytes(info.bodyBytes)}`);
975
1208
  if (info.status !== void 0) parts.push(String(info.status));
976
- const elapsed = Date.now() - startTime;
977
- const duration = elapsed >= 1e3 ? `${(elapsed / 1e3).toFixed(1)}s` : `${elapsed}ms`;
978
- parts.push(info.streaming ? `${duration} stream` : duration);
1209
+ const elapsed = info.streamMs ?? Date.now() - startTime;
1210
+ const fmt = (ms) => ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${Math.round(ms)}ms`;
1211
+ if (info.ttfbMs !== void 0) parts.push(`ttfb:${fmt(info.ttfbMs)}`);
1212
+ parts.push(info.streaming ? `${fmt(elapsed)} stream` : fmt(elapsed));
979
1213
  const line = parts.join(" ");
980
1214
  if (detectCapabilityMismatch(info, model)) consola.error(`[MISMATCH] ${line}`);
981
1215
  else consola.info(line);
@@ -1041,6 +1275,60 @@ function collectToolFieldKeys(body) {
1041
1275
  return [...seen].sort();
1042
1276
  }
1043
1277
  //#endregion
1278
+ //#region package.json
1279
+ var name = "github-router";
1280
+ var version = "0.3.269";
1281
+ //#endregion
1282
+ //#region src/lib/approval.ts
1283
+ const awaitApproval = async () => {
1284
+ if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected by user", Response.json({ message: "Request rejected by user" }, { status: 403 }));
1285
+ };
1286
+ //#endregion
1287
+ //#region src/lib/rate-limit.ts
1288
+ const RATE_LIMIT_QUEUE_TIMEOUT_MS = 5e3;
1289
+ let rateLimitChain = Promise.resolve();
1290
+ async function checkRateLimit(state) {
1291
+ if (state.rateLimitSeconds === void 0) return;
1292
+ const ticket = { aborted: false };
1293
+ const myTurn = rateLimitChain.then(() => doCheck(state, ticket));
1294
+ rateLimitChain = myTurn.catch(() => {});
1295
+ return Promise.race([myTurn, sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {
1296
+ ticket.aborted = true;
1297
+ throw new HTTPError("Rate limit queue wait exceeded", Response.json({
1298
+ type: "error",
1299
+ error: {
1300
+ type: "rate_limit_error",
1301
+ message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`
1302
+ }
1303
+ }, { status: 429 }));
1304
+ })]);
1305
+ }
1306
+ async function doCheck(state, ticket) {
1307
+ if (state.rateLimitSeconds === void 0) return;
1308
+ if (ticket.aborted) return;
1309
+ const now = Date.now();
1310
+ if (!state.lastRequestTimestamp) {
1311
+ state.lastRequestTimestamp = now;
1312
+ return;
1313
+ }
1314
+ const elapsedSeconds = (now - state.lastRequestTimestamp) / 1e3;
1315
+ if (elapsedSeconds > state.rateLimitSeconds) {
1316
+ state.lastRequestTimestamp = now;
1317
+ return;
1318
+ }
1319
+ const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds);
1320
+ if (!state.rateLimitWait) {
1321
+ consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
1322
+ throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
1323
+ }
1324
+ const waitTimeMs = waitTimeSeconds * 1e3;
1325
+ consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
1326
+ await sleep(waitTimeMs);
1327
+ if (ticket.aborted) return;
1328
+ state.lastRequestTimestamp = Date.now();
1329
+ consola.info("Rate limit wait completed, proceeding with request");
1330
+ }
1331
+ //#endregion
1044
1332
  //#region src/routes/chat-completions/handler.ts
1045
1333
  const ENCODER$1 = new TextEncoder();
1046
1334
  function formatSSE$1(chunk) {
@@ -1065,7 +1353,7 @@ async function handleCompletion$1(c) {
1065
1353
  logEndpointMismatch(payload.model, "/chat/completions");
1066
1354
  let inputTokens;
1067
1355
  try {
1068
- if (selectedModel) inputTokens = (await getTokenCount(payload, selectedModel)).input;
1356
+ if (selectedModel && requestLogVisible()) inputTokens = (await getTokenCount(payload, selectedModel)).input;
1069
1357
  } catch {}
1070
1358
  if (isNullish(payload.max_tokens)) {
1071
1359
  payload = {
@@ -1242,11 +1530,13 @@ completionRoutes.post("/", async (c) => {
1242
1530
  //#region src/services/copilot/create-embeddings.ts
1243
1531
  const createEmbeddings = async (payload) => {
1244
1532
  if (!state.copilotToken) throw new Error("Copilot token not found");
1245
- const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
1533
+ const body = JSON.stringify(payload);
1534
+ const response = await fetchWithTransientRetry(() => tryRefreshAndRetry(() => fetch(`${copilotBaseUrl(state)}/embeddings`, {
1246
1535
  method: "POST",
1247
1536
  headers: copilotHeaders(state),
1248
- body: JSON.stringify(payload)
1249
- });
1537
+ body,
1538
+ ...UPSTREAM_FETCH_TIMEOUT_MS > 0 ? { signal: AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS) } : {}
1539
+ }), "/embeddings"), { label: "/embeddings" });
1250
1540
  if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
1251
1541
  return await response.json();
1252
1542
  };
@@ -1631,16 +1921,92 @@ function serializeAnthropicEvent(ev) {
1631
1921
  * converts a mid-stream generator throw into a terminal `event: error` frame.
1632
1922
  * On consumer cancel it invokes `onCancel` (abort the upstream fetch) and
1633
1923
  * `return()`s the generator so its `finally` tears down the upstream reader.
1924
+ *
1925
+ * `inactivityTimeoutMs` bounds how long a single `events.next()` may stall.
1926
+ * Without it a Copilot upstream that holds the socket open but stops emitting
1927
+ * hangs the caller FOREVER: `UPSTREAM_FETCH_TIMEOUT_MS` defaults to 0 (see
1928
+ * `~/lib/port`), so inactivity detection is the only stall defense, and every
1929
+ * sibling streaming path already has it (`readIteratorWithTimeout`). The clock
1930
+ * is per-read, NOT an absolute budget — a long-reasoning model legitimately
1931
+ * streams for many minutes, and only a gap between events is a stall.
1932
+ *
1933
+ * Three properties this must preserve, each pinned by a test in
1934
+ * `tests/anthropic-translate-inactivity.test.ts`:
1935
+ *
1936
+ * - NO LEAK: on timeout we take the SAME teardown path as consumer cancel
1937
+ * (`onCancel` aborts the upstream fetch, `events.return()` drives the
1938
+ * generator's `finally`). Timing out without that leaves the fetch alive
1939
+ * for the life of the process.
1940
+ * - NO CRASH: the abandoned `events.next()` promise keeps running. If the
1941
+ * upstream later errors, that rejection has no handler and would kill the
1942
+ * process under Node's `--unhandled-rejections=throw`, so it is swallowed
1943
+ * explicitly. (Same reasoning as the noop `.catch` in `stream-relay.ts`.)
1944
+ * - NO TRUNCATION: a stream that keeps producing inside the deadline is
1945
+ * never cut short, however long it runs in total.
1634
1946
  */
1635
1947
  function anthropicSseStreamFromEvents(events, opts) {
1636
1948
  const enc = new TextEncoder();
1637
1949
  let consumerCancelled = false;
1638
1950
  let finished = false;
1951
+ /** Sentinel identity — never a legitimate iterator result. */
1952
+ const STALLED = Symbol("stalled");
1953
+ /**
1954
+ * `events.next()` bounded by an inactivity deadline. Resolves to `STALLED`
1955
+ * rather than throwing so the caller can distinguish a stall (terminate with
1956
+ * a stall-specific message) from a genuine upstream error.
1957
+ */
1958
+ const nextWithTimeout = async (timeoutMs) => {
1959
+ const pending = events.next();
1960
+ let timer;
1961
+ try {
1962
+ return await Promise.race([pending, new Promise((resolve) => {
1963
+ timer = setTimeout(() => resolve(STALLED), timeoutMs);
1964
+ timer.unref?.();
1965
+ })]);
1966
+ } finally {
1967
+ if (timer !== void 0) clearTimeout(timer);
1968
+ pending.catch(() => void 0);
1969
+ }
1970
+ };
1639
1971
  const safeClose = (controller) => {
1640
1972
  try {
1641
1973
  controller.close();
1642
1974
  } catch {}
1643
1975
  };
1976
+ /**
1977
+ * Tear down the upstream, exactly once, on a PREMATURE end of stream.
1978
+ *
1979
+ * Called from the three places a stream can end early — the stall branch,
1980
+ * the error branch, and consumer `cancel()` — because all three leave the
1981
+ * upstream fetch running and the generator suspended. Both halves matter:
1982
+ * `onCancel` aborts the fetch, and `return()` drives the generator's
1983
+ * `finally`; `return()` alone cannot release a generator parked on a
1984
+ * never-settling await, since it queues behind that await.
1985
+ *
1986
+ * Two things this is deliberately NOT:
1987
+ *
1988
+ * - **Not a `finally` around `pull`.** `pull` re-enters per chunk, so a
1989
+ * `finally` there would fire on every successful chunk.
1990
+ * - **Not called on normal completion** (`res.done`). Aborting an
1991
+ * AbortController whose fetch has already completed DESTROYS the socket
1992
+ * instead of returning it to the keep-alive pool, so every successful
1993
+ * turn would pay a fresh TLS handshake. Premature-only is the whole
1994
+ * correctness condition, and `tornDown` only makes it idempotent — it
1995
+ * does not make an unconditional call safe.
1996
+ */
1997
+ let tornDown = false;
1998
+ const teardownUpstream = () => {
1999
+ if (tornDown) return;
2000
+ tornDown = true;
2001
+ try {
2002
+ opts.onCancel?.();
2003
+ } catch (err) {
2004
+ consola.warn(`Anthropic-translate onCancel threw during teardown at ${opts.routePath}: ${err instanceof Error ? err.message : String(err)}`);
2005
+ }
2006
+ try {
2007
+ events.return?.(void 0)?.catch?.(() => void 0);
2008
+ } catch {}
2009
+ };
1644
2010
  return new ReadableStream({
1645
2011
  async pull(controller) {
1646
2012
  if (consumerCancelled || finished) {
@@ -1649,7 +2015,26 @@ function anthropicSseStreamFromEvents(events, opts) {
1649
2015
  }
1650
2016
  let res;
1651
2017
  try {
1652
- res = await events.next();
2018
+ const timeoutMs = opts.inactivityTimeoutMs;
2019
+ const settled = timeoutMs !== void 0 && timeoutMs > 0 ? await nextWithTimeout(timeoutMs) : await events.next();
2020
+ if (settled === STALLED) {
2021
+ finished = true;
2022
+ if (consumerCancelled) {
2023
+ safeClose(controller);
2024
+ return;
2025
+ }
2026
+ const message = `Upstream stalled: no stream activity for ${timeoutMs}ms`;
2027
+ consola.error(`Anthropic-translate stream inactivity timeout at ${opts.routePath}: ${message}`);
2028
+ teardownUpstream();
2029
+ try {
2030
+ controller.enqueue(enc.encode(buildAnthropicErrorEvent("timeout_error", message)));
2031
+ } catch (enqueueError) {
2032
+ if (!isControllerClosedError(enqueueError)) consola.warn(`Could not deliver error event to consumer at ${opts.routePath}: ${enqueueError instanceof Error ? enqueueError.message : String(enqueueError)}`);
2033
+ }
2034
+ safeClose(controller);
2035
+ return;
2036
+ }
2037
+ res = settled;
1653
2038
  } catch (err) {
1654
2039
  finished = true;
1655
2040
  if (consumerCancelled) {
@@ -1659,6 +2044,7 @@ function anthropicSseStreamFromEvents(events, opts) {
1659
2044
  const name = err instanceof Error ? err.name : "Error";
1660
2045
  const message = err instanceof Error ? err.message : String(err);
1661
2046
  consola.error(`Anthropic-translate stream interrupted at ${opts.routePath}: ${name}: ${JSON.stringify(message)}`);
2047
+ teardownUpstream();
1662
2048
  try {
1663
2049
  controller.enqueue(enc.encode(buildAnthropicErrorEvent(name, message)));
1664
2050
  } catch (enqueueError) {
@@ -1689,8 +2075,7 @@ function anthropicSseStreamFromEvents(events, opts) {
1689
2075
  cancel() {
1690
2076
  consumerCancelled = true;
1691
2077
  finished = true;
1692
- opts.onCancel?.();
1693
- events.return?.(void 0);
2078
+ teardownUpstream();
1694
2079
  }
1695
2080
  });
1696
2081
  }
@@ -3038,7 +3423,8 @@ async function handleNonClaudeResponses(c, opts) {
3038
3423
  }, opts.model, opts.startTime);
3039
3424
  const stream = anthropicSseStreamFromEvents(synthAnthropicFromResponses(result, { modelId: opts.modelId }), {
3040
3425
  routePath,
3041
- onCancel: () => aborter.abort()
3426
+ onCancel: () => aborter.abort(),
3427
+ inactivityTimeoutMs: UPSTREAM_INACTIVITY_TIMEOUT_MS
3042
3428
  });
3043
3429
  return new Response(stream, {
3044
3430
  status: 200,
@@ -3094,7 +3480,8 @@ async function handleNonClaudeChat(c, opts) {
3094
3480
  }, opts.model, opts.startTime);
3095
3481
  const stream = anthropicSseStreamFromEvents(synthAnthropicFromChat(result, { modelId: opts.modelId }), {
3096
3482
  routePath,
3097
- onCancel: () => aborter.abort()
3483
+ onCancel: () => aborter.abort(),
3484
+ inactivityTimeoutMs: UPSTREAM_INACTIVITY_TIMEOUT_MS
3098
3485
  });
3099
3486
  return new Response(stream, {
3100
3487
  status: 200,
@@ -3258,6 +3645,7 @@ async function handleCompletion(c) {
3258
3645
  const startTime = Date.now();
3259
3646
  await checkRateLimit(state);
3260
3647
  const rawBody = await c.req.text();
3648
+ recordBodySize(rawBody.length);
3261
3649
  const debugEnabled = consola.level >= 4;
3262
3650
  if (debugEnabled) consola.debug("Anthropic request body:", rawBody.slice(0, 2e3));
3263
3651
  if (process.env.GH_ROUTER_LOG_FIELDS === "1") {
@@ -3860,6 +4248,14 @@ modelRoutes.get("/", async (c) => {
3860
4248
  //#endregion
3861
4249
  //#region src/routes/responses/handler.ts
3862
4250
  const ENCODER = new TextEncoder();
4251
+ /**
4252
+ * Cap on CONSECUTIVE empty frames tolerated while discovering the first real
4253
+ * SSE event. Reset by any meaningful frame, so a legitimately slow upstream
4254
+ * that interleaves keepalives with progress is never cut off — only a source
4255
+ * that emits nothing BUT empty frames trips it. Generous by design: this is a
4256
+ * runaway backstop, not a policy knob.
4257
+ */
4258
+ const MAX_CONSECUTIVE_EMPTY_DISCOVERY_FRAMES = 1e4;
3863
4259
  function formatSSE(chunk) {
3864
4260
  const parts = [];
3865
4261
  if (chunk.event) parts.push(`event: ${chunk.event}`);
@@ -3910,18 +4306,25 @@ async function handleResponses(c) {
3910
4306
  const iterator = response[Symbol.asyncIterator]();
3911
4307
  let firstChunk;
3912
4308
  let upstreamFinished = false;
4309
+ let consecutiveEmpty = 0;
3913
4310
  while (true) {
3914
4311
  const r = await readIteratorWithTimeout(iterator, UPSTREAM_INACTIVITY_TIMEOUT_MS);
3915
4312
  if (r.done) {
3916
4313
  upstreamFinished = true;
3917
4314
  break;
3918
4315
  }
3919
- if (r.value === void 0 || r.value === null) continue;
4316
+ if (r.value === void 0 || r.value === null) {
4317
+ if (++consecutiveEmpty > MAX_CONSECUTIVE_EMPTY_DISCOVERY_FRAMES) break;
4318
+ continue;
4319
+ }
3920
4320
  if (r.value.data === "[DONE]") {
3921
4321
  upstreamFinished = true;
3922
4322
  break;
3923
4323
  }
3924
- if (!r.value.data) continue;
4324
+ if (!r.value.data) {
4325
+ if (++consecutiveEmpty > MAX_CONSECUTIVE_EMPTY_DISCOVERY_FRAMES) break;
4326
+ continue;
4327
+ }
3925
4328
  firstChunk = r.value;
3926
4329
  break;
3927
4330
  }
@@ -4454,6 +4857,7 @@ async function setupAndServe(options) {
4454
4857
  await cacheModels();
4455
4858
  consola.debug(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
4456
4859
  const serveOptions = buildServeOptions(server.fetch, options.silent);
4860
+ installBodySizeStatsExitHook();
4457
4861
  let srvxServer;
4458
4862
  if (options.port !== void 0) srvxServer = serve({
4459
4863
  ...serveOptions,
@@ -4857,6 +5261,6 @@ function getCodexEnvVars(serverUrl) {
4857
5261
  return vars;
4858
5262
  }
4859
5263
  //#endregion
4860
- export { sharedServerArgs as a, listModelsForEndpoint as c, updateClaude as d, setupAndServe as i, runSelfUpdate as l, getCodexEnvVars as n, startKeepAwake as o, parseSharedArgs as r, stopKeepAwake as s, getClaudeCodeEnvVars as t, checkClaudeVersion as u };
5264
+ export { sharedServerArgs as a, listModelsForEndpoint as c, checkClaudeVersion as d, updateClaude as f, setupAndServe as i, enableFileLogging as l, getCodexEnvVars as n, startKeepAwake as o, parseSharedArgs as r, stopKeepAwake as s, getClaudeCodeEnvVars as t, runSelfUpdate as u };
4861
5265
 
4862
- //# sourceMappingURL=server-setup-BeeT8m-w.js.map
5266
+ //# sourceMappingURL=server-setup-Dd293T1O.js.map