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.
- package/dist/{attribution-settings-DFIVIRG8.js → attribution-settings-of5gJM5a.js} +4 -4
- package/dist/{attribution-settings-DFIVIRG8.js.map → attribution-settings-of5gJM5a.js.map} +1 -1
- package/dist/{auth-WA88cE9q.js → auth-DIFbHOu0.js} +3 -3
- package/dist/{auth-WA88cE9q.js.map → auth-DIFbHOu0.js.map} +1 -1
- package/dist/browser-bridge/index.js +67 -27
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-v2apGKz7.js → check-usage-DfnrR-d_.js} +4 -4
- package/dist/{check-usage-v2apGKz7.js.map → check-usage-DfnrR-d_.js.map} +1 -1
- package/dist/{claude-BCI3Sx6_.js → claude-BlzCUbVq.js} +15 -14
- package/dist/{claude-BCI3Sx6_.js.map → claude-BlzCUbVq.js.map} +1 -1
- package/dist/{codex-DyJIrbJt.js → codex-BaFcXOyg.js} +5 -5
- package/dist/{codex-DyJIrbJt.js.map → codex-BaFcXOyg.js.map} +1 -1
- package/dist/{debug-vshv4nIv.js → debug-CuuxCk_J.js} +2 -2
- package/dist/{debug-vshv4nIv.js.map → debug-CuuxCk_J.js.map} +1 -1
- package/dist/engine-mwIgHJoi.js +2 -0
- package/dist/{exec-DTO2oin0.js → exec-y8C_MU8A.js} +89 -18
- package/dist/exec-y8C_MU8A.js.map +1 -0
- package/dist/{gate-discovery-cMbt8Ct2.js → gate-discovery-AUAjqSpV.js} +6 -6
- package/dist/{gate-discovery-cMbt8Ct2.js.map → gate-discovery-AUAjqSpV.js.map} +1 -1
- package/dist/{get-copilot-usage-DOFJWJaQ.js → get-copilot-usage-Ds09xSmH.js} +2 -2
- package/dist/{get-copilot-usage-DOFJWJaQ.js.map → get-copilot-usage-Ds09xSmH.js.map} +1 -1
- package/dist/{internal-artifact-open-CZGO831M.js → internal-artifact-open-CDqPfegE.js} +2 -2
- package/dist/{internal-artifact-open-CZGO831M.js.map → internal-artifact-open-CDqPfegE.js.map} +1 -1
- package/dist/{internal-plan-review-6gkcOAjT.js → internal-plan-review-CH9qTHoZ.js} +3 -3
- package/dist/{internal-plan-review-6gkcOAjT.js.map → internal-plan-review-CH9qTHoZ.js.map} +1 -1
- package/dist/{internal-prompt-submit-C0XOPgcP.js → internal-prompt-submit-Bve1s4z_.js} +5 -5
- package/dist/{internal-prompt-submit-C0XOPgcP.js.map → internal-prompt-submit-Bve1s4z_.js.map} +1 -1
- package/dist/{internal-session-bind-DtSBTkQe.js → internal-session-bind-Cc8bMXHK.js} +2 -2
- package/dist/{internal-session-bind-DtSBTkQe.js.map → internal-session-bind-Cc8bMXHK.js.map} +1 -1
- package/dist/{internal-stop-hook-CBcE2Js8.js → internal-stop-hook-B1MIwmTR.js} +6 -6
- package/dist/{internal-stop-hook-CBcE2Js8.js.map → internal-stop-hook-B1MIwmTR.js.map} +1 -1
- package/dist/{internal-stop-review-_1Mqz7eT.js → internal-stop-review-CWE7_QVv.js} +2 -2
- package/dist/{internal-stop-review-_1Mqz7eT.js.map → internal-stop-review-CWE7_QVv.js.map} +1 -1
- package/dist/{lifecycle-DjC9tC1A.js → lifecycle-0XrazT05.js} +2 -2
- package/dist/{lifecycle-DjC9tC1A.js.map → lifecycle-0XrazT05.js.map} +1 -1
- package/dist/lifecycle-2M0IThRr.js +2 -0
- package/dist/{lifecycle-Bd1Fyf4B.js → lifecycle-AgNOtFRv.js} +3 -3
- package/dist/{lifecycle-Bd1Fyf4B.js.map → lifecycle-AgNOtFRv.js.map} +1 -1
- package/dist/lifecycle-C8kFsAj-.js +2 -0
- package/dist/main.js +14 -14
- package/dist/{models-Cv6gprpy.js → models-BFQUp0ov.js} +35 -30
- package/dist/models-BFQUp0ov.js.map +1 -0
- package/dist/{orchestration-B6qidQAd.js → orchestration-B_Q3ncXI.js} +3 -3
- package/dist/{orchestration-B6qidQAd.js.map → orchestration-B_Q3ncXI.js.map} +1 -1
- package/dist/paths-BbHtQAOs.js +2 -0
- package/dist/{paths-BXrbboAS.js → paths-Bg1_DWhi.js} +3 -3
- package/dist/{paths-BXrbboAS.js.map → paths-Bg1_DWhi.js.map} +1 -1
- package/dist/{peer-mcp-personas-B22ncnbQ.js → peer-mcp-personas-pWP3Eyt7.js} +235 -108
- package/dist/peer-mcp-personas-pWP3Eyt7.js.map +1 -0
- package/dist/{plan-review-hook-Bo4y1r3j.js → plan-review-hook-BKOcW5s4.js} +3 -3
- package/dist/{plan-review-hook-Bo4y1r3j.js.map → plan-review-hook-BKOcW5s4.js.map} +1 -1
- package/dist/{prompt-submit-hook-BFRFLSk5.js → prompt-submit-hook-F7Zih_au.js} +2 -2
- package/dist/{prompt-submit-hook-BFRFLSk5.js.map → prompt-submit-hook-F7Zih_au.js.map} +1 -1
- package/dist/{provision-DSpLQHC3.js → provision-CsYUVdMT.js} +5 -5
- package/dist/{provision-DSpLQHC3.js.map → provision-CsYUVdMT.js.map} +1 -1
- package/dist/{serve-D9tBVVoK.js → serve--c9n9hk4.js} +11 -11
- package/dist/{serve-D9tBVVoK.js.map → serve--c9n9hk4.js.map} +1 -1
- package/dist/{server-setup-BM9Smfek.js → server-setup-BmyWS4WU.js} +559 -158
- package/dist/server-setup-BmyWS4WU.js.map +1 -0
- package/dist/{start-D7Hx2T_T.js → start-CXnLBouJ.js} +3 -3
- package/dist/{start-D7Hx2T_T.js.map → start-CXnLBouJ.js.map} +1 -1
- package/dist/{stop-gate-hook-Bmwla_2u.js → stop-gate-hook-BqpumJtl.js} +3 -3
- package/dist/{stop-gate-hook-Bmwla_2u.js.map → stop-gate-hook-BqpumJtl.js.map} +1 -1
- package/dist/{stop-gate-policy-CoMKWSsS.js → stop-gate-policy-C0XOBZhB.js} +3 -3
- package/dist/{stop-gate-policy-CoMKWSsS.js.map → stop-gate-policy-C0XOBZhB.js.map} +1 -1
- package/dist/{token-C8Uh7aUe.js → token-iaSGp76k.js} +356 -147
- package/dist/token-iaSGp76k.js.map +1 -0
- package/package.json +2 -2
- package/dist/engine-D8dlcSW6.js +0 -2
- package/dist/exec-DTO2oin0.js.map +0 -1
- package/dist/lifecycle-CRTWEbiP.js +0 -2
- package/dist/lifecycle-DUPXAOQS.js +0 -2
- package/dist/models-Cv6gprpy.js.map +0 -1
- package/dist/paths-Dz1WyRJG.js +0 -2
- package/dist/peer-mcp-personas-B22ncnbQ.js.map +0 -1
- package/dist/server-setup-BM9Smfek.js.map +0 -1
- package/dist/token-C8Uh7aUe.js.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
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-
|
|
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-pWP3Eyt7.js";
|
|
2
2
|
import { t as getPackageVersion } from "./version-_Q1WpsQp.js";
|
|
3
|
-
import { i as ensurePaths, t as PATHS } from "./paths-
|
|
4
|
-
import {
|
|
5
|
-
import { t as getCopilotUsage } from "./get-copilot-usage-
|
|
6
|
-
import { a as resolveExecutable, n as killManagedTree, o as runCommandCapture, r as parseBoolEnv, s as runCommandVoid } from "./exec-
|
|
3
|
+
import { i as ensurePaths, t as PATHS } from "./paths-Bg1_DWhi.js";
|
|
4
|
+
import { A as state, T as copilotHeaders, _ as forwardError, a as cacheCopilotVersion, c as filterBetaHeader, d as resolveModel, f as sleep, g as HTTPError, i as tryRefreshAndRetry, l as isNullish, n as setupGitHubAgentToken, o as cacheModels, r as setupGitHubToken, s as cacheVSCodeVersion, t as setupCopilotToken, w as copilotBaseUrl, y as fetchWithTransientRetry } from "./token-iaSGp76k.js";
|
|
5
|
+
import { t as getCopilotUsage } from "./get-copilot-usage-Ds09xSmH.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
9
|
import fs, { existsSync } from "node:fs";
|
|
10
10
|
import * as nodePath from "node:path";
|
|
11
11
|
import path from "node:path";
|
|
12
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
13
13
|
import fs$1 from "node:fs/promises";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import process$1 from "node:process";
|
|
@@ -357,9 +357,12 @@ async function runSelfUpdate(opts) {
|
|
|
357
357
|
//#region src/lib/file-log-reporter.ts
|
|
358
358
|
const MAX_LOG_BYTES = 1048576;
|
|
359
359
|
const DEDUP_MAX = 1e3;
|
|
360
|
+
const DEDUP_WINDOW_MS = 3e5;
|
|
360
361
|
const ARG_MAX_LEN = 2048;
|
|
361
|
-
const
|
|
362
|
+
const ERROR_CAUSE_MAX_DEPTH = 4;
|
|
363
|
+
const ERROR_FIELD_MAX_LEN = 512;
|
|
362
364
|
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;
|
|
365
|
+
const ERROR_BODY_RE = /\b(request\s+body|body)\s*[:=]\s*.*$/i;
|
|
363
366
|
const ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
364
367
|
"fatal",
|
|
365
368
|
"error",
|
|
@@ -368,25 +371,87 @@ const ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
|
368
371
|
function sanitize(line) {
|
|
369
372
|
return line.replace(CREDENTIAL_RE, "[REDACTED]");
|
|
370
373
|
}
|
|
374
|
+
function errorProperty(error, key) {
|
|
375
|
+
if (typeof error !== "object" && typeof error !== "function" || error === null) return;
|
|
376
|
+
try {
|
|
377
|
+
const value = error[key];
|
|
378
|
+
return typeof value === "string" ? value : void 0;
|
|
379
|
+
} catch {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function errorCause(error) {
|
|
384
|
+
if (typeof error !== "object" && typeof error !== "function" || error === null) return;
|
|
385
|
+
try {
|
|
386
|
+
return error.cause;
|
|
387
|
+
} catch {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function safeString(value) {
|
|
392
|
+
try {
|
|
393
|
+
return String(value);
|
|
394
|
+
} catch {
|
|
395
|
+
return "[unserializable]";
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function boundErrorField(value) {
|
|
399
|
+
return value.length > ERROR_FIELD_MAX_LEN ? `${value.slice(0, ERROR_FIELD_MAX_LEN)}…` : value;
|
|
400
|
+
}
|
|
401
|
+
function sanitizeErrorMessage(value) {
|
|
402
|
+
return sanitize(value).replace(ERROR_BODY_RE, "$1=[REDACTED]");
|
|
403
|
+
}
|
|
404
|
+
function serializeError(error) {
|
|
405
|
+
const chain = [];
|
|
406
|
+
const seen = /* @__PURE__ */ new Set();
|
|
407
|
+
let current = error;
|
|
408
|
+
for (let depth = 0; current !== void 0 && depth <= ERROR_CAUSE_MAX_DEPTH; depth++) {
|
|
409
|
+
if ((typeof current === "object" || typeof current === "function") && current !== null) {
|
|
410
|
+
if (seen.has(current)) {
|
|
411
|
+
chain.push("[circular cause]");
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
seen.add(current);
|
|
415
|
+
}
|
|
416
|
+
const name = boundErrorField(errorProperty(current, "name") ?? "Error");
|
|
417
|
+
const message = boundErrorField(sanitizeErrorMessage(errorProperty(current, "message") ?? (typeof current === "string" ? current : safeString(current))));
|
|
418
|
+
const rawCode = errorProperty(current, "code");
|
|
419
|
+
const code = rawCode ? boundErrorField(rawCode) : void 0;
|
|
420
|
+
chain.push(`${name}: ${message}${code ? ` code=${code}` : ""}`);
|
|
421
|
+
const cause = errorCause(current);
|
|
422
|
+
if (cause === void 0) break;
|
|
423
|
+
if (depth === ERROR_CAUSE_MAX_DEPTH) {
|
|
424
|
+
chain.push("[cause chain truncated]");
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
current = cause;
|
|
428
|
+
}
|
|
429
|
+
return chain.join(" <- cause: ");
|
|
430
|
+
}
|
|
371
431
|
function serializeArg(arg) {
|
|
372
432
|
if (typeof arg === "string") return arg;
|
|
373
|
-
if (arg instanceof Error)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
433
|
+
if (arg instanceof Error) return serializeError(arg);
|
|
434
|
+
return safeString(arg);
|
|
435
|
+
}
|
|
436
|
+
function serializeArgs(logObj) {
|
|
437
|
+
return logObj.args.map((arg) => {
|
|
438
|
+
const serialized = sanitize(serializeArg(arg));
|
|
439
|
+
return serialized.length > ARG_MAX_LEN ? `${serialized.slice(0, ARG_MAX_LEN)}…` : serialized;
|
|
440
|
+
});
|
|
379
441
|
}
|
|
380
|
-
function formatLogLine(logObj) {
|
|
381
|
-
return
|
|
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`);
|
|
442
|
+
function formatLogLine(logObj, suppressed = 0) {
|
|
443
|
+
return `${logObj.date.toISOString()} [${(logObj.type ?? "error").toUpperCase()}] ${serializeArgs(logObj).join(" ").replace(/\r\n|\r|\n/g, "\\n")}${suppressed > 0 ? ` [${suppressed} identical occurrences suppressed]` : ""}\n`;
|
|
385
444
|
}
|
|
386
445
|
function makeDedupeKey(logObj) {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
446
|
+
const hash = createHash("sha256");
|
|
447
|
+
hash.update(logObj.type);
|
|
448
|
+
for (const arg of serializeArgs(logObj)) {
|
|
449
|
+
hash.update("\0");
|
|
450
|
+
hash.update(String(arg.length));
|
|
451
|
+
hash.update(":");
|
|
452
|
+
hash.update(arg);
|
|
453
|
+
}
|
|
454
|
+
return hash.digest("base64url");
|
|
390
455
|
}
|
|
391
456
|
/**
|
|
392
457
|
* Construction-time rotation: rename the log aside if it's already over the
|
|
@@ -408,7 +473,7 @@ function rotateAtStartup(filePath) {
|
|
|
408
473
|
}
|
|
409
474
|
var FileLogReporter = class FileLogReporter {
|
|
410
475
|
filePath;
|
|
411
|
-
seen = /* @__PURE__ */ new
|
|
476
|
+
seen = /* @__PURE__ */ new Map();
|
|
412
477
|
bytesSinceCheck = 0;
|
|
413
478
|
static ROTATE_CHECK_BYTES = MAX_LOG_BYTES / 2;
|
|
414
479
|
fd;
|
|
@@ -466,10 +531,25 @@ var FileLogReporter = class FileLogReporter {
|
|
|
466
531
|
log(logObj, _ctx) {
|
|
467
532
|
if (!ALLOWED_TYPES.has(logObj.type)) return;
|
|
468
533
|
const key = makeDedupeKey(logObj);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
534
|
+
const timestamp = logObj.date.getTime();
|
|
535
|
+
const previous = this.seen.get(key);
|
|
536
|
+
let suppressed = 0;
|
|
537
|
+
if (previous) {
|
|
538
|
+
if (timestamp - previous.lastWrittenAt < DEDUP_WINDOW_MS) {
|
|
539
|
+
previous.suppressed++;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
suppressed = previous.suppressed;
|
|
543
|
+
previous.lastWrittenAt = timestamp;
|
|
544
|
+
previous.suppressed = 0;
|
|
545
|
+
} else {
|
|
546
|
+
if (this.seen.size >= DEDUP_MAX) this.seen.clear();
|
|
547
|
+
this.seen.set(key, {
|
|
548
|
+
lastWrittenAt: timestamp,
|
|
549
|
+
suppressed: 0
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
const line = formatLogLine(logObj, suppressed);
|
|
473
553
|
this.bytesSinceCheck += line.length;
|
|
474
554
|
if (this.bytesSinceCheck >= FileLogReporter.ROTATE_CHECK_BYTES) {
|
|
475
555
|
this.rotateIfNeeded();
|
|
@@ -1045,60 +1125,6 @@ function initProxyFromEnv() {
|
|
|
1045
1125
|
}
|
|
1046
1126
|
}
|
|
1047
1127
|
//#endregion
|
|
1048
|
-
//#region package.json
|
|
1049
|
-
var name = "github-router";
|
|
1050
|
-
var version = "0.3.263";
|
|
1051
|
-
//#endregion
|
|
1052
|
-
//#region src/lib/approval.ts
|
|
1053
|
-
const awaitApproval = async () => {
|
|
1054
|
-
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 }));
|
|
1055
|
-
};
|
|
1056
|
-
//#endregion
|
|
1057
|
-
//#region src/lib/rate-limit.ts
|
|
1058
|
-
const RATE_LIMIT_QUEUE_TIMEOUT_MS = 5e3;
|
|
1059
|
-
let rateLimitChain = Promise.resolve();
|
|
1060
|
-
async function checkRateLimit(state) {
|
|
1061
|
-
if (state.rateLimitSeconds === void 0) return;
|
|
1062
|
-
const ticket = { aborted: false };
|
|
1063
|
-
const myTurn = rateLimitChain.then(() => doCheck(state, ticket));
|
|
1064
|
-
rateLimitChain = myTurn.catch(() => {});
|
|
1065
|
-
return Promise.race([myTurn, sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {
|
|
1066
|
-
ticket.aborted = true;
|
|
1067
|
-
throw new HTTPError("Rate limit queue wait exceeded", Response.json({
|
|
1068
|
-
type: "error",
|
|
1069
|
-
error: {
|
|
1070
|
-
type: "rate_limit_error",
|
|
1071
|
-
message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`
|
|
1072
|
-
}
|
|
1073
|
-
}, { status: 429 }));
|
|
1074
|
-
})]);
|
|
1075
|
-
}
|
|
1076
|
-
async function doCheck(state, ticket) {
|
|
1077
|
-
if (state.rateLimitSeconds === void 0) return;
|
|
1078
|
-
if (ticket.aborted) return;
|
|
1079
|
-
const now = Date.now();
|
|
1080
|
-
if (!state.lastRequestTimestamp) {
|
|
1081
|
-
state.lastRequestTimestamp = now;
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
const elapsedSeconds = (now - state.lastRequestTimestamp) / 1e3;
|
|
1085
|
-
if (elapsedSeconds > state.rateLimitSeconds) {
|
|
1086
|
-
state.lastRequestTimestamp = now;
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds);
|
|
1090
|
-
if (!state.rateLimitWait) {
|
|
1091
|
-
consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
|
|
1092
|
-
throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
|
|
1093
|
-
}
|
|
1094
|
-
const waitTimeMs = waitTimeSeconds * 1e3;
|
|
1095
|
-
consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
|
|
1096
|
-
await sleep(waitTimeMs);
|
|
1097
|
-
if (ticket.aborted) return;
|
|
1098
|
-
state.lastRequestTimestamp = Date.now();
|
|
1099
|
-
consola.info("Rate limit wait completed, proceeding with request");
|
|
1100
|
-
}
|
|
1101
|
-
//#endregion
|
|
1102
1128
|
//#region src/lib/request-log.ts
|
|
1103
1129
|
/**
|
|
1104
1130
|
* Rolling request-body size distribution.
|
|
@@ -1122,6 +1148,79 @@ function recordBodySize(bytes) {
|
|
|
1122
1148
|
bodySizeIdx = (bodySizeIdx + 1) % BODY_SIZE_RING;
|
|
1123
1149
|
if (bodySizeCount < BODY_SIZE_RING) bodySizeCount++;
|
|
1124
1150
|
}
|
|
1151
|
+
/** Percentile snapshot of observed body sizes, or undefined if none seen. */
|
|
1152
|
+
function bodySizeStats() {
|
|
1153
|
+
if (bodySizeCount === 0) return void 0;
|
|
1154
|
+
const s = Array.from(bodySizes.slice(0, bodySizeCount)).sort((a, b) => a - b);
|
|
1155
|
+
const at = (q) => s[Math.min(s.length - 1, Math.floor(s.length * q))];
|
|
1156
|
+
return {
|
|
1157
|
+
count: bodySizeCount,
|
|
1158
|
+
p50: at(.5),
|
|
1159
|
+
p95: at(.95),
|
|
1160
|
+
p99: at(.99),
|
|
1161
|
+
max: s[s.length - 1]
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Print the observed body-size distribution, if any requests were seen.
|
|
1166
|
+
*
|
|
1167
|
+
* Without this the ring was WRITE-ONLY: `bodySizeStats()` had no caller
|
|
1168
|
+
* anywhere in `src/`, so the one number its own doc comment says the decision
|
|
1169
|
+
* needs ("benchmarks at 4.5 MiB mean nothing if the real p50 is 40 KiB") was
|
|
1170
|
+
* collected every request and readable by nobody.
|
|
1171
|
+
*
|
|
1172
|
+
* At `consola.info` because it is a once-per-session line answering a question
|
|
1173
|
+
* an operator or contributor actually asks; anything noisier would be a reason
|
|
1174
|
+
* to switch it off.
|
|
1175
|
+
*/
|
|
1176
|
+
function logBodySizeStats() {
|
|
1177
|
+
const stats = bodySizeStats();
|
|
1178
|
+
if (!stats) return void 0;
|
|
1179
|
+
const line = `request body sizes (n=${stats.count}): p50 ${formatBytes(stats.p50)}, p95 ${formatBytes(stats.p95)}, p99 ${formatBytes(stats.p99)}, max ${formatBytes(stats.max)}`;
|
|
1180
|
+
consola.info(line);
|
|
1181
|
+
return line;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Report the distribution once, on the way out.
|
|
1185
|
+
*
|
|
1186
|
+
* Self-registered rather than wired into each subcommand's shutdown chain
|
|
1187
|
+
* because `start` has NO `launchChild`/`onShutdown` to hang it on (see the
|
|
1188
|
+
* comment at its `startKeepAwake()` call), and a stat that only appeared under
|
|
1189
|
+
* `claude` would answer the question for one of three entry points. Same
|
|
1190
|
+
* pattern, and same reasoning, as `~/lib/keep-awake`'s own reaper.
|
|
1191
|
+
*
|
|
1192
|
+
* **Windows caveat, measured rather than assumed.** This reports on a clean
|
|
1193
|
+
* exit and on a signal the runtime actually delivers to JS (Ctrl-C in an
|
|
1194
|
+
* interactive console). It does NOT report when the process is killed by
|
|
1195
|
+
* another process on Windows: `child.kill(...)` and `taskkill` terminate
|
|
1196
|
+
* abruptly via `TerminateProcess`, dispatching no JS signal event, so NO
|
|
1197
|
+
* handler runs — not this one, and not `process.on("exit")` either. Verified
|
|
1198
|
+
* on a real win32 host: a child registering both handlers and killed by its
|
|
1199
|
+
* parent produced no output and exited with a null code.
|
|
1200
|
+
*
|
|
1201
|
+
* That is acceptable for a diagnostic — it is a measurement aid, not a
|
|
1202
|
+
* durability guarantee, and the interactive case it exists to serve (run a
|
|
1203
|
+
* session, Ctrl-C, read the distribution) does work. It is NOT acceptable for
|
|
1204
|
+
* anything durable: do not move credential, ledger, or cleanup work onto this
|
|
1205
|
+
* hook. The existing teardown paths that must survive a hard kill are built
|
|
1206
|
+
* differently on purpose (see `~/lib/keep-awake`, which relies on the OS
|
|
1207
|
+
* releasing a thread-scoped assertion rather than on a handler running, and
|
|
1208
|
+
* `~/lib/process-guard`, which uses a separate reaper process).
|
|
1209
|
+
*
|
|
1210
|
+
* Idempotent: `exit` can follow a signal handler, and both call this.
|
|
1211
|
+
*/
|
|
1212
|
+
let statsReported = false;
|
|
1213
|
+
function reportOnce() {
|
|
1214
|
+
if (statsReported) return;
|
|
1215
|
+
statsReported = true;
|
|
1216
|
+
logBodySizeStats();
|
|
1217
|
+
}
|
|
1218
|
+
let exitHooksInstalled = false;
|
|
1219
|
+
function installBodySizeStatsExitHook() {
|
|
1220
|
+
if (exitHooksInstalled) return;
|
|
1221
|
+
exitHooksInstalled = true;
|
|
1222
|
+
process.once("exit", reportOnce);
|
|
1223
|
+
}
|
|
1125
1224
|
/** Format a byte count compactly (1.2M / 15.3K / 900B). */
|
|
1126
1225
|
function formatBytes(n) {
|
|
1127
1226
|
if (n >= 1048576) return `${(n / 1048576).toFixed(1)}M`;
|
|
@@ -1256,6 +1355,60 @@ function collectToolFieldKeys(body) {
|
|
|
1256
1355
|
return [...seen].sort();
|
|
1257
1356
|
}
|
|
1258
1357
|
//#endregion
|
|
1358
|
+
//#region package.json
|
|
1359
|
+
var name = "github-router";
|
|
1360
|
+
var version = "0.3.270";
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/lib/approval.ts
|
|
1363
|
+
const awaitApproval = async () => {
|
|
1364
|
+
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 }));
|
|
1365
|
+
};
|
|
1366
|
+
//#endregion
|
|
1367
|
+
//#region src/lib/rate-limit.ts
|
|
1368
|
+
const RATE_LIMIT_QUEUE_TIMEOUT_MS = 5e3;
|
|
1369
|
+
let rateLimitChain = Promise.resolve();
|
|
1370
|
+
async function checkRateLimit(state) {
|
|
1371
|
+
if (state.rateLimitSeconds === void 0) return;
|
|
1372
|
+
const ticket = { aborted: false };
|
|
1373
|
+
const myTurn = rateLimitChain.then(() => doCheck(state, ticket));
|
|
1374
|
+
rateLimitChain = myTurn.catch(() => {});
|
|
1375
|
+
return Promise.race([myTurn, sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {
|
|
1376
|
+
ticket.aborted = true;
|
|
1377
|
+
throw new HTTPError("Rate limit queue wait exceeded", Response.json({
|
|
1378
|
+
type: "error",
|
|
1379
|
+
error: {
|
|
1380
|
+
type: "rate_limit_error",
|
|
1381
|
+
message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`
|
|
1382
|
+
}
|
|
1383
|
+
}, { status: 429 }));
|
|
1384
|
+
})]);
|
|
1385
|
+
}
|
|
1386
|
+
async function doCheck(state, ticket) {
|
|
1387
|
+
if (state.rateLimitSeconds === void 0) return;
|
|
1388
|
+
if (ticket.aborted) return;
|
|
1389
|
+
const now = Date.now();
|
|
1390
|
+
if (!state.lastRequestTimestamp) {
|
|
1391
|
+
state.lastRequestTimestamp = now;
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const elapsedSeconds = (now - state.lastRequestTimestamp) / 1e3;
|
|
1395
|
+
if (elapsedSeconds > state.rateLimitSeconds) {
|
|
1396
|
+
state.lastRequestTimestamp = now;
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds);
|
|
1400
|
+
if (!state.rateLimitWait) {
|
|
1401
|
+
consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
|
|
1402
|
+
throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
|
|
1403
|
+
}
|
|
1404
|
+
const waitTimeMs = waitTimeSeconds * 1e3;
|
|
1405
|
+
consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
|
|
1406
|
+
await sleep(waitTimeMs);
|
|
1407
|
+
if (ticket.aborted) return;
|
|
1408
|
+
state.lastRequestTimestamp = Date.now();
|
|
1409
|
+
consola.info("Rate limit wait completed, proceeding with request");
|
|
1410
|
+
}
|
|
1411
|
+
//#endregion
|
|
1259
1412
|
//#region src/routes/chat-completions/handler.ts
|
|
1260
1413
|
const ENCODER$1 = new TextEncoder();
|
|
1261
1414
|
function formatSSE$1(chunk) {
|
|
@@ -1552,12 +1705,21 @@ mcpRoutes.delete("/:group", (c) => {
|
|
|
1552
1705
|
* any other input shape, fall back to a synthesized `toolu_advisor_N`
|
|
1553
1706
|
* id.
|
|
1554
1707
|
*/
|
|
1555
|
-
function toCopilotToolUseId(srvId
|
|
1708
|
+
function toCopilotToolUseId(srvId) {
|
|
1556
1709
|
if (srvId.startsWith("srvtoolu_")) {
|
|
1557
1710
|
const suffix = srvId.slice(9);
|
|
1558
1711
|
if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `toolu_${suffix}`;
|
|
1559
1712
|
}
|
|
1560
|
-
return
|
|
1713
|
+
if (/^toolu_[a-zA-Z0-9_]+$/.test(srvId)) return srvId;
|
|
1714
|
+
}
|
|
1715
|
+
function invalidAdvisorHistory(message) {
|
|
1716
|
+
throw new HTTPError("Invalid advisor history", Response.json({
|
|
1717
|
+
type: "error",
|
|
1718
|
+
error: {
|
|
1719
|
+
type: "invalid_request_error",
|
|
1720
|
+
message: `Invalid advisor history: ${message}`
|
|
1721
|
+
}
|
|
1722
|
+
}, { status: 400 }));
|
|
1561
1723
|
}
|
|
1562
1724
|
/**
|
|
1563
1725
|
* Fast-path detector: returns true if the raw body has any chance of
|
|
@@ -1572,85 +1734,148 @@ function bodyMightNeedSanitize(rawBody) {
|
|
|
1572
1734
|
return rawBody.includes("\"server_tool_use\"") || rawBody.includes("\"advisor_tool_result\"") || /"type":"advisor_\d+"/.test(rawBody);
|
|
1573
1735
|
}
|
|
1574
1736
|
/**
|
|
1575
|
-
* Translate one assistant turn
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
* Output: array of {role, content[]} message objects, alternating
|
|
1582
|
-
* assistant→user→assistant for each advisor pair encountered.
|
|
1737
|
+
* Translate one client-visible assistant turn into the internal messages
|
|
1738
|
+
* Copilot originally saw. Parallel advisor calls are replayed as one assistant
|
|
1739
|
+
* turn followed by one user turn containing every matching result. Sequential
|
|
1740
|
+
* advisor groups remain separate assistant/user pairs. When a client tool and
|
|
1741
|
+
* advisor coexist, the advisor results and the client's following tool results
|
|
1742
|
+
* are merged into the one immediate user message required by Anthropic.
|
|
1583
1743
|
*/
|
|
1584
|
-
function splitAssistantTurnAtAdvisorPairs(originalContent,
|
|
1744
|
+
function splitAssistantTurnAtAdvisorPairs(originalMessage, originalContent, followingMessage) {
|
|
1585
1745
|
const messages = [];
|
|
1586
1746
|
let currentAssistantContent = [];
|
|
1587
1747
|
let translated = false;
|
|
1748
|
+
let emittedFirstAssistant = false;
|
|
1749
|
+
const pushAssistant = () => {
|
|
1750
|
+
if (currentAssistantContent.length === 0) invalidAdvisorHistory("advisor pair has no assistant content");
|
|
1751
|
+
messages.push(emittedFirstAssistant ? {
|
|
1752
|
+
role: "assistant",
|
|
1753
|
+
content: currentAssistantContent
|
|
1754
|
+
} : {
|
|
1755
|
+
...originalMessage,
|
|
1756
|
+
content: currentAssistantContent
|
|
1757
|
+
});
|
|
1758
|
+
emittedFirstAssistant = true;
|
|
1759
|
+
};
|
|
1588
1760
|
let i = 0;
|
|
1589
1761
|
while (i < originalContent.length) {
|
|
1590
1762
|
const block = originalContent[i];
|
|
1591
1763
|
const b = typeof block === "object" && block !== null ? block : null;
|
|
1592
|
-
if (b
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1764
|
+
if (b?.type === "server_tool_use" && b.name === "__anthropic_advisor".replace(/^__anthropic_/, "")) {
|
|
1765
|
+
const uses = [];
|
|
1766
|
+
const results = /* @__PURE__ */ new Map();
|
|
1767
|
+
let readingResults = false;
|
|
1768
|
+
while (i < originalContent.length) {
|
|
1769
|
+
const candidate = originalContent[i];
|
|
1770
|
+
const record = typeof candidate === "object" && candidate !== null ? candidate : null;
|
|
1771
|
+
if (record?.type === "server_tool_use" && record.name === "__anthropic_advisor".replace(/^__anthropic_/, "")) {
|
|
1772
|
+
if (readingResults) {
|
|
1773
|
+
if (results.size === uses.length) break;
|
|
1774
|
+
invalidAdvisorHistory("advisor blocks overlap or are ambiguously nested");
|
|
1775
|
+
}
|
|
1776
|
+
const serverId = typeof record.id === "string" ? record.id : "";
|
|
1777
|
+
const copilotId = toCopilotToolUseId(serverId);
|
|
1778
|
+
if (!copilotId) invalidAdvisorHistory("advisor server_tool_use id is not round-trippable");
|
|
1779
|
+
if (uses.some((use) => use.serverId === serverId)) invalidAdvisorHistory("duplicate advisor server_tool_use id");
|
|
1780
|
+
uses.push({
|
|
1781
|
+
serverId,
|
|
1782
|
+
copilotId
|
|
1783
|
+
});
|
|
1784
|
+
currentAssistantContent.push({
|
|
1785
|
+
type: "tool_use",
|
|
1786
|
+
id: copilotId,
|
|
1787
|
+
name: ADVISOR_INTERNAL_TOOL_NAME,
|
|
1788
|
+
input: {}
|
|
1789
|
+
});
|
|
1790
|
+
i++;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (record?.type === "advisor_tool_result") {
|
|
1794
|
+
readingResults = true;
|
|
1795
|
+
const serverId = typeof record.tool_use_id === "string" ? record.tool_use_id : "";
|
|
1796
|
+
if (!uses.some((use) => use.serverId === serverId)) invalidAdvisorHistory("advisor_tool_result has no matching advisor server_tool_use");
|
|
1797
|
+
if (results.has(serverId)) invalidAdvisorHistory("duplicate advisor_tool_result");
|
|
1798
|
+
const resultContent = record.content;
|
|
1799
|
+
let resultText;
|
|
1800
|
+
if (typeof resultContent === "string") resultText = resultContent;
|
|
1801
|
+
else if (typeof resultContent === "object" && resultContent !== null) {
|
|
1802
|
+
const text = resultContent.text;
|
|
1803
|
+
if (typeof text === "string") resultText = text;
|
|
1804
|
+
}
|
|
1805
|
+
if (resultText === void 0) invalidAdvisorHistory("advisor_tool_result has no replayable text");
|
|
1806
|
+
results.set(serverId, resultText);
|
|
1807
|
+
i++;
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
if (readingResults) {
|
|
1811
|
+
if (results.size !== uses.length) invalidAdvisorHistory("advisor server_tool_use has no matching advisor_tool_result in the same assistant turn");
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
currentAssistantContent.push(candidate);
|
|
1815
|
+
i++;
|
|
1816
|
+
}
|
|
1817
|
+
if (results.size !== uses.length) invalidAdvisorHistory("advisor server_tool_use has no matching advisor_tool_result in the same assistant turn");
|
|
1818
|
+
const ordinaryToolIds = currentAssistantContent.filter((entry) => typeof entry === "object" && entry !== null && entry.type === "tool_use" && entry.name !== "__anthropic_advisor").map((entry) => entry.id);
|
|
1819
|
+
if (ordinaryToolIds.some((id) => typeof id !== "string" || id.length === 0)) invalidAdvisorHistory("client tool_use has no matchable id");
|
|
1820
|
+
pushAssistant();
|
|
1607
1821
|
translated = true;
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1822
|
+
const advisorResults = uses.map((use) => ({
|
|
1823
|
+
type: "tool_result",
|
|
1824
|
+
tool_use_id: use.copilotId,
|
|
1825
|
+
content: results.get(use.serverId)
|
|
1826
|
+
}));
|
|
1827
|
+
if (ordinaryToolIds.length > 0) {
|
|
1828
|
+
if (i < originalContent.length) invalidAdvisorHistory("assistant continuation followed an unresolved client tool in an advisor turn");
|
|
1829
|
+
if (typeof followingMessage !== "object" || followingMessage === null || followingMessage.role !== "user" || !Array.isArray(followingMessage.content)) invalidAdvisorHistory("mixed advisor/client-tool turn has no immediate user tool results");
|
|
1830
|
+
const following = followingMessage;
|
|
1831
|
+
const followingContent = following.content;
|
|
1832
|
+
let sawNonResult = false;
|
|
1833
|
+
const resultIds = [];
|
|
1834
|
+
for (const entry of followingContent) {
|
|
1835
|
+
const record = typeof entry === "object" && entry !== null ? entry : null;
|
|
1836
|
+
if (record?.type === "tool_result") {
|
|
1837
|
+
if (sawNonResult) invalidAdvisorHistory("tool_result appears after user text/content");
|
|
1838
|
+
if (typeof record.tool_use_id !== "string") invalidAdvisorHistory("tool_result has no matchable tool_use_id");
|
|
1839
|
+
resultIds.push(record.tool_use_id);
|
|
1840
|
+
} else sawNonResult = true;
|
|
1615
1841
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1842
|
+
const expected = [...ordinaryToolIds].sort();
|
|
1843
|
+
const actual = [...resultIds].sort();
|
|
1844
|
+
if (expected.length !== actual.length || expected.some((id, index) => id !== actual[index])) invalidAdvisorHistory("mixed advisor/client-tool turn does not have exactly one result for every client tool");
|
|
1845
|
+
messages.push({
|
|
1846
|
+
...following,
|
|
1847
|
+
content: [...advisorResults, ...followingContent]
|
|
1848
|
+
});
|
|
1849
|
+
return {
|
|
1850
|
+
messages,
|
|
1851
|
+
translated: true,
|
|
1852
|
+
consumedFollowingMessage: true
|
|
1853
|
+
};
|
|
1620
1854
|
}
|
|
1621
1855
|
messages.push({
|
|
1622
1856
|
role: "user",
|
|
1623
|
-
content:
|
|
1624
|
-
type: "tool_result",
|
|
1625
|
-
tool_use_id: copilotId,
|
|
1626
|
-
content: resultText
|
|
1627
|
-
}]
|
|
1857
|
+
content: advisorResults
|
|
1628
1858
|
});
|
|
1629
1859
|
currentAssistantContent = [];
|
|
1630
1860
|
continue;
|
|
1631
1861
|
}
|
|
1632
|
-
if (b
|
|
1633
|
-
translated = true;
|
|
1634
|
-
i += 1;
|
|
1635
|
-
continue;
|
|
1636
|
-
}
|
|
1862
|
+
if (b?.type === "advisor_tool_result") invalidAdvisorHistory("advisor_tool_result has no immediately preceding advisor server_tool_use");
|
|
1637
1863
|
currentAssistantContent.push(block);
|
|
1638
|
-
i
|
|
1864
|
+
i++;
|
|
1639
1865
|
}
|
|
1640
1866
|
if (currentAssistantContent.length > 0) messages.push({
|
|
1641
1867
|
role: "assistant",
|
|
1642
1868
|
content: currentAssistantContent
|
|
1643
1869
|
});
|
|
1644
1870
|
if (!translated) return {
|
|
1645
|
-
messages: [
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
}],
|
|
1649
|
-
translated: false
|
|
1871
|
+
messages: [originalMessage],
|
|
1872
|
+
translated: false,
|
|
1873
|
+
consumedFollowingMessage: false
|
|
1650
1874
|
};
|
|
1651
1875
|
return {
|
|
1652
1876
|
messages,
|
|
1653
|
-
translated: true
|
|
1877
|
+
translated: true,
|
|
1878
|
+
consumedFollowingMessage: false
|
|
1654
1879
|
};
|
|
1655
1880
|
}
|
|
1656
1881
|
function sanitizeAnthropicBody(rawBody) {
|
|
@@ -1679,8 +1904,8 @@ function sanitizeAnthropicBody(rawBody) {
|
|
|
1679
1904
|
const original = parsed.messages;
|
|
1680
1905
|
const rebuilt = [];
|
|
1681
1906
|
let anyTranslated = false;
|
|
1682
|
-
|
|
1683
|
-
|
|
1907
|
+
for (let messageIndex = 0; messageIndex < original.length; messageIndex++) {
|
|
1908
|
+
const msg = original[messageIndex];
|
|
1684
1909
|
if (typeof msg !== "object" || msg === null || msg.role !== "assistant") {
|
|
1685
1910
|
rebuilt.push(msg);
|
|
1686
1911
|
continue;
|
|
@@ -1699,10 +1924,11 @@ function sanitizeAnthropicBody(rawBody) {
|
|
|
1699
1924
|
rebuilt.push(msg);
|
|
1700
1925
|
continue;
|
|
1701
1926
|
}
|
|
1702
|
-
const { messages: split, translated } = splitAssistantTurnAtAdvisorPairs(content,
|
|
1927
|
+
const { messages: split, translated, consumedFollowingMessage } = splitAssistantTurnAtAdvisorPairs(msg, content, original[messageIndex + 1]);
|
|
1703
1928
|
if (translated) {
|
|
1704
1929
|
anyTranslated = true;
|
|
1705
1930
|
for (const m of split) rebuilt.push(m);
|
|
1931
|
+
if (consumedFollowingMessage) messageIndex++;
|
|
1706
1932
|
} else rebuilt.push(msg);
|
|
1707
1933
|
}
|
|
1708
1934
|
if (anyTranslated) {
|
|
@@ -1900,6 +2126,40 @@ function anthropicSseStreamFromEvents(events, opts) {
|
|
|
1900
2126
|
controller.close();
|
|
1901
2127
|
} catch {}
|
|
1902
2128
|
};
|
|
2129
|
+
/**
|
|
2130
|
+
* Tear down the upstream, exactly once, on a PREMATURE end of stream.
|
|
2131
|
+
*
|
|
2132
|
+
* Called from the three places a stream can end early — the stall branch,
|
|
2133
|
+
* the error branch, and consumer `cancel()` — because all three leave the
|
|
2134
|
+
* upstream fetch running and the generator suspended. Both halves matter:
|
|
2135
|
+
* `onCancel` aborts the fetch, and `return()` drives the generator's
|
|
2136
|
+
* `finally`; `return()` alone cannot release a generator parked on a
|
|
2137
|
+
* never-settling await, since it queues behind that await.
|
|
2138
|
+
*
|
|
2139
|
+
* Two things this is deliberately NOT:
|
|
2140
|
+
*
|
|
2141
|
+
* - **Not a `finally` around `pull`.** `pull` re-enters per chunk, so a
|
|
2142
|
+
* `finally` there would fire on every successful chunk.
|
|
2143
|
+
* - **Not called on normal completion** (`res.done`). Aborting an
|
|
2144
|
+
* AbortController whose fetch has already completed DESTROYS the socket
|
|
2145
|
+
* instead of returning it to the keep-alive pool, so every successful
|
|
2146
|
+
* turn would pay a fresh TLS handshake. Premature-only is the whole
|
|
2147
|
+
* correctness condition, and `tornDown` only makes it idempotent — it
|
|
2148
|
+
* does not make an unconditional call safe.
|
|
2149
|
+
*/
|
|
2150
|
+
let tornDown = false;
|
|
2151
|
+
const teardownUpstream = () => {
|
|
2152
|
+
if (tornDown) return;
|
|
2153
|
+
tornDown = true;
|
|
2154
|
+
try {
|
|
2155
|
+
opts.onCancel?.();
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
consola.warn(`Anthropic-translate onCancel threw during teardown at ${opts.routePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
events.return?.(void 0)?.catch?.(() => void 0);
|
|
2161
|
+
} catch {}
|
|
2162
|
+
};
|
|
1903
2163
|
return new ReadableStream({
|
|
1904
2164
|
async pull(controller) {
|
|
1905
2165
|
if (consumerCancelled || finished) {
|
|
@@ -1918,8 +2178,7 @@ function anthropicSseStreamFromEvents(events, opts) {
|
|
|
1918
2178
|
}
|
|
1919
2179
|
const message = `Upstream stalled: no stream activity for ${timeoutMs}ms`;
|
|
1920
2180
|
consola.error(`Anthropic-translate stream inactivity timeout at ${opts.routePath}: ${message}`);
|
|
1921
|
-
|
|
1922
|
-
events.return?.(void 0)?.catch?.(() => void 0);
|
|
2181
|
+
teardownUpstream();
|
|
1923
2182
|
try {
|
|
1924
2183
|
controller.enqueue(enc.encode(buildAnthropicErrorEvent("timeout_error", message)));
|
|
1925
2184
|
} catch (enqueueError) {
|
|
@@ -1938,6 +2197,7 @@ function anthropicSseStreamFromEvents(events, opts) {
|
|
|
1938
2197
|
const name = err instanceof Error ? err.name : "Error";
|
|
1939
2198
|
const message = err instanceof Error ? err.message : String(err);
|
|
1940
2199
|
consola.error(`Anthropic-translate stream interrupted at ${opts.routePath}: ${name}: ${JSON.stringify(message)}`);
|
|
2200
|
+
teardownUpstream();
|
|
1941
2201
|
try {
|
|
1942
2202
|
controller.enqueue(enc.encode(buildAnthropicErrorEvent(name, message)));
|
|
1943
2203
|
} catch (enqueueError) {
|
|
@@ -1968,8 +2228,7 @@ function anthropicSseStreamFromEvents(events, opts) {
|
|
|
1968
2228
|
cancel() {
|
|
1969
2229
|
consumerCancelled = true;
|
|
1970
2230
|
finished = true;
|
|
1971
|
-
|
|
1972
|
-
events.return?.(void 0)?.catch?.(() => void 0);
|
|
2231
|
+
teardownUpstream();
|
|
1973
2232
|
}
|
|
1974
2233
|
});
|
|
1975
2234
|
}
|
|
@@ -3395,7 +3654,119 @@ async function handleNonClaudeChat(c, opts) {
|
|
|
3395
3654
|
return c.json(anthropic, 200);
|
|
3396
3655
|
}
|
|
3397
3656
|
//#endregion
|
|
3657
|
+
//#region src/lib/thinking-history-repair.ts
|
|
3658
|
+
const THINKING_INTEGRITY_ERROR_RES = [/messages\.(\d+)\.content\.\d+:\s*`thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified/, /messages\.(\d+)\.content\.\d+:\s*Invalid `signature` in `thinking` block/];
|
|
3659
|
+
const MAX_REMEMBERED_REPAIRS = 1e3;
|
|
3660
|
+
const OMITTED_THINKING_PLACEHOLDER = "[prior reasoning omitted]";
|
|
3661
|
+
const repairSalt = randomBytes(32);
|
|
3662
|
+
const rememberedRepairs = /* @__PURE__ */ new Set();
|
|
3663
|
+
function isRecord(value) {
|
|
3664
|
+
return typeof value === "object" && value !== null;
|
|
3665
|
+
}
|
|
3666
|
+
function signedBlockFingerprint(_messageIndex, content) {
|
|
3667
|
+
const hash = createHash("sha256");
|
|
3668
|
+
hash.update(repairSalt);
|
|
3669
|
+
let signedBlocks = 0;
|
|
3670
|
+
for (let index = 0; index < content.length; index++) {
|
|
3671
|
+
const block = content[index];
|
|
3672
|
+
if (!isRecord(block)) continue;
|
|
3673
|
+
if (block.type !== "thinking" && block.type !== "redacted_thinking") continue;
|
|
3674
|
+
signedBlocks++;
|
|
3675
|
+
hash.update(String(index));
|
|
3676
|
+
hash.update(String(block.type));
|
|
3677
|
+
if (typeof block.signature === "string") hash.update(block.signature);
|
|
3678
|
+
if (typeof block.data === "string") hash.update(block.data);
|
|
3679
|
+
}
|
|
3680
|
+
return signedBlocks > 0 ? hash.digest("hex") : void 0;
|
|
3681
|
+
}
|
|
3682
|
+
function repairMessageAt(parsed, messageIndex) {
|
|
3683
|
+
if (!Array.isArray(parsed.messages)) return void 0;
|
|
3684
|
+
const messages = [...parsed.messages];
|
|
3685
|
+
const message = messages[messageIndex];
|
|
3686
|
+
if (!isRecord(message) || message.role !== "assistant") return void 0;
|
|
3687
|
+
if (!Array.isArray(message.content)) return void 0;
|
|
3688
|
+
const fingerprint = signedBlockFingerprint(messageIndex, message.content);
|
|
3689
|
+
if (!fingerprint) return void 0;
|
|
3690
|
+
const repairedContent = message.content.filter((block) => !isRecord(block) || block.type !== "thinking" && block.type !== "redacted_thinking");
|
|
3691
|
+
const removedBlocks = message.content.length - repairedContent.length;
|
|
3692
|
+
if (removedBlocks === 0) return void 0;
|
|
3693
|
+
if (repairedContent.length === 0) repairedContent.push({
|
|
3694
|
+
type: "text",
|
|
3695
|
+
text: OMITTED_THINKING_PLACEHOLDER
|
|
3696
|
+
});
|
|
3697
|
+
messages[messageIndex] = {
|
|
3698
|
+
...message,
|
|
3699
|
+
content: repairedContent
|
|
3700
|
+
};
|
|
3701
|
+
parsed.messages = messages;
|
|
3702
|
+
return {
|
|
3703
|
+
body: JSON.stringify(parsed),
|
|
3704
|
+
fingerprint,
|
|
3705
|
+
messageIndex,
|
|
3706
|
+
removedBlocks
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Build a one-shot repair only for Copilot's known signed-thinking integrity
|
|
3711
|
+
* rejections. The upstream error is the oracle: valid omitted-display thinking
|
|
3712
|
+
* also has an empty `thinking` string, so request shape alone cannot identify a
|
|
3713
|
+
* corrupt signature safely.
|
|
3714
|
+
*/
|
|
3715
|
+
function repairRejectedThinkingHistory(rawBody, upstreamErrorText) {
|
|
3716
|
+
const match = THINKING_INTEGRITY_ERROR_RES.map((pattern) => pattern.exec(upstreamErrorText)).find((candidate) => candidate !== null);
|
|
3717
|
+
if (!match) return void 0;
|
|
3718
|
+
const messageIndex = Number(match[1]);
|
|
3719
|
+
if (!Number.isSafeInteger(messageIndex) || messageIndex < 0) return void 0;
|
|
3720
|
+
let parsed;
|
|
3721
|
+
try {
|
|
3722
|
+
parsed = JSON.parse(rawBody);
|
|
3723
|
+
} catch {
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
return repairMessageAt(parsed, messageIndex);
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Reapply a repair already proven successful in this process. Only salted
|
|
3730
|
+
* digests are retained; no message, thinking, signature, or tool content is
|
|
3731
|
+
* stored.
|
|
3732
|
+
*/
|
|
3733
|
+
function repairKnownThinkingHistory(rawBody) {
|
|
3734
|
+
if (rememberedRepairs.size === 0) return void 0;
|
|
3735
|
+
let parsed;
|
|
3736
|
+
try {
|
|
3737
|
+
parsed = JSON.parse(rawBody);
|
|
3738
|
+
} catch {
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
if (!Array.isArray(parsed.messages)) return void 0;
|
|
3742
|
+
let repaired;
|
|
3743
|
+
let totalRemovedBlocks = 0;
|
|
3744
|
+
for (let index = parsed.messages.length - 1; index >= 0; index--) {
|
|
3745
|
+
const message = parsed.messages[index];
|
|
3746
|
+
if (!isRecord(message) || !Array.isArray(message.content)) continue;
|
|
3747
|
+
const fingerprint = signedBlockFingerprint(index, message.content);
|
|
3748
|
+
if (!fingerprint || !rememberedRepairs.has(fingerprint)) continue;
|
|
3749
|
+
const attempt = repairMessageAt(parsed, index);
|
|
3750
|
+
if (!attempt) continue;
|
|
3751
|
+
totalRemovedBlocks += attempt.removedBlocks;
|
|
3752
|
+
repaired = {
|
|
3753
|
+
...attempt,
|
|
3754
|
+
removedBlocks: totalRemovedBlocks
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
return repaired;
|
|
3758
|
+
}
|
|
3759
|
+
function rememberThinkingHistoryRepair(fingerprint) {
|
|
3760
|
+
if (rememberedRepairs.has(fingerprint)) return;
|
|
3761
|
+
if (rememberedRepairs.size >= MAX_REMEMBERED_REPAIRS) {
|
|
3762
|
+
const oldest = rememberedRepairs.values().next().value;
|
|
3763
|
+
if (typeof oldest === "string") rememberedRepairs.delete(oldest);
|
|
3764
|
+
}
|
|
3765
|
+
rememberedRepairs.add(fingerprint);
|
|
3766
|
+
}
|
|
3767
|
+
//#endregion
|
|
3398
3768
|
//#region src/routes/messages/handler.ts
|
|
3769
|
+
const MAX_THINKING_REPAIR_ATTEMPTS = 5;
|
|
3399
3770
|
const isWebSearchTool$1 = (tool) => !!tool && typeof tool === "object" && (typeof tool.type === "string" && tool.type.startsWith("web_search") || tool.name === "web_search");
|
|
3400
3771
|
/**
|
|
3401
3772
|
* Extract whitelisted beta headers from the incoming request to forward
|
|
@@ -3592,15 +3963,30 @@ async function handleCompletion(c) {
|
|
|
3592
3963
|
if (modelId) logEndpointMismatch(modelId, "/v1/messages");
|
|
3593
3964
|
const effectiveBetas = applyDefaultBetas(betaHeaders, resolvedModel ?? originalModel);
|
|
3594
3965
|
const advisorAborter = advisorEnabled ? new AbortController() : void 0;
|
|
3966
|
+
const requestHeaders = {
|
|
3967
|
+
...selectedModel?.requestHeaders,
|
|
3968
|
+
...effectiveBetas
|
|
3969
|
+
};
|
|
3970
|
+
let nativeBody = resolvedBody;
|
|
3971
|
+
const knownThinkingRepair = repairKnownThinkingHistory(nativeBody);
|
|
3972
|
+
if (knownThinkingRepair) {
|
|
3973
|
+
nativeBody = knownThinkingRepair.body;
|
|
3974
|
+
consola.warn(`Reapplying proven thinking-history repair: message=${knownThinkingRepair.messageIndex} removed_blocks=${knownThinkingRepair.removedBlocks}`);
|
|
3975
|
+
}
|
|
3595
3976
|
let response;
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3977
|
+
let acceptedRepair;
|
|
3978
|
+
const repairedMessageIndices = /* @__PURE__ */ new Set();
|
|
3979
|
+
for (let attempt = 0; response === void 0; attempt++) try {
|
|
3980
|
+
response = await createMessages(nativeBody, requestHeaders, advisorAborter?.signal, true);
|
|
3981
|
+
if (acceptedRepair) {
|
|
3982
|
+
rememberThinkingHistoryRepair(acceptedRepair.fingerprint);
|
|
3983
|
+
consola.warn(`Thinking-history repair succeeded: message=${acceptedRepair.messageIndex} removed_blocks=${acceptedRepair.removedBlocks} attempts=${attempt}`);
|
|
3984
|
+
}
|
|
3601
3985
|
} catch (error) {
|
|
3602
|
-
if (error instanceof HTTPError)
|
|
3603
|
-
|
|
3986
|
+
if (!(error instanceof HTTPError)) throw error;
|
|
3987
|
+
const errorBody = await error.response.clone().text().catch(() => "");
|
|
3988
|
+
const thinkingRepair = attempt < MAX_THINKING_REPAIR_ATTEMPTS ? repairRejectedThinkingHistory(nativeBody, errorBody) : void 0;
|
|
3989
|
+
if (!thinkingRepair || repairedMessageIndices.has(thinkingRepair.messageIndex)) {
|
|
3604
3990
|
logRequest({
|
|
3605
3991
|
method: "POST",
|
|
3606
3992
|
path: c.req.path,
|
|
@@ -3609,8 +3995,13 @@ async function handleCompletion(c) {
|
|
|
3609
3995
|
status: error.response.status,
|
|
3610
3996
|
errorBody
|
|
3611
3997
|
}, selectedModel, startTime);
|
|
3998
|
+
throw error;
|
|
3612
3999
|
}
|
|
3613
|
-
|
|
4000
|
+
if (acceptedRepair) rememberThinkingHistoryRepair(acceptedRepair.fingerprint);
|
|
4001
|
+
consola.warn(`Copilot rejected signed thinking history; retrying without rejected historical blocks: message=${thinkingRepair.messageIndex} removed_blocks=${thinkingRepair.removedBlocks} attempt=${attempt + 1}`);
|
|
4002
|
+
nativeBody = thinkingRepair.body;
|
|
4003
|
+
acceptedRepair = thinkingRepair;
|
|
4004
|
+
repairedMessageIndices.add(thinkingRepair.messageIndex);
|
|
3614
4005
|
}
|
|
3615
4006
|
const contentType = response.headers.get("content-type") ?? "";
|
|
3616
4007
|
const clientAcceptsSSE = (c.req.header("accept") ?? "").includes("text/event-stream");
|
|
@@ -3645,17 +4036,14 @@ async function handleCompletion(c) {
|
|
|
3645
4036
|
if (advisorEnabled && response.body) {
|
|
3646
4037
|
let parsedBase = {};
|
|
3647
4038
|
try {
|
|
3648
|
-
parsedBase = JSON.parse(
|
|
4039
|
+
parsedBase = JSON.parse(nativeBody);
|
|
3649
4040
|
} catch {}
|
|
3650
4041
|
const initialConversation = Array.isArray(parsedBase.messages) ? parsedBase.messages : [];
|
|
3651
4042
|
return new Response(buildAdvisorStream({
|
|
3652
4043
|
firstResponse: response,
|
|
3653
4044
|
initialConversation,
|
|
3654
4045
|
baseBody: parsedBase,
|
|
3655
|
-
requestHeaders
|
|
3656
|
-
...selectedModel?.requestHeaders,
|
|
3657
|
-
...effectiveBetas
|
|
3658
|
-
},
|
|
4046
|
+
requestHeaders,
|
|
3659
4047
|
externalAborter: advisorAborter
|
|
3660
4048
|
}), {
|
|
3661
4049
|
status: response.status,
|
|
@@ -3715,7 +4103,7 @@ function resolveModelInBody$1(rawBody) {
|
|
|
3715
4103
|
const selectedModel = resolvedModel ? state.models?.data.find((m) => m.id === resolvedModel) : void 0;
|
|
3716
4104
|
if (translateThinking(parsed, selectedModel)) modified = true;
|
|
3717
4105
|
if (clampOutputConfigEffortInPlace(parsed, selectedModel)) modified = true;
|
|
3718
|
-
if (rawBody.includes("\"scope\"") && sanitizeCacheControl$1(parsed)) modified = true;
|
|
4106
|
+
if ((rawBody.includes("\"scope\"") || rawBody.includes("\"cache_control\"") && (rawBody.includes("\"thinking\"") || rawBody.includes("\"redacted_thinking\""))) && sanitizeCacheControl$1(parsed)) modified = true;
|
|
3719
4107
|
if ((rawBody.includes("\"budget\"") || rawBody.includes("\"output_config\"") || rawBody.includes("\"betas\"") || rawBody.includes("\"eager_input_streaming\"")) && stripAnthropicOnlyFields$1(parsed)) modified = true;
|
|
3720
4108
|
return {
|
|
3721
4109
|
body: modified ? JSON.stringify(parsed) : rawBody,
|
|
@@ -3797,7 +4185,9 @@ function translateThinking(body, model) {
|
|
|
3797
4185
|
return true;
|
|
3798
4186
|
}
|
|
3799
4187
|
/**
|
|
3800
|
-
* Strip the `scope` field from
|
|
4188
|
+
* Strip the `scope` field from ordinary `cache_control` objects. Copilot
|
|
4189
|
+
* rejects the entire cache_control object on signed thinking blocks, so remove
|
|
4190
|
+
* that unsigned transport metadata without touching thinking/signature/data.
|
|
3801
4191
|
* Claude CLI 2.1.88+ sends {"type":"ephemeral","scope":"global"} which
|
|
3802
4192
|
* Copilot rejects. Mutates the parsed object in place.
|
|
3803
4193
|
*
|
|
@@ -3808,6 +4198,11 @@ function sanitizeCacheControl$1(body) {
|
|
|
3808
4198
|
let stripped = false;
|
|
3809
4199
|
function stripScope(block) {
|
|
3810
4200
|
const cc = block.cache_control;
|
|
4201
|
+
if (cc && (block.type === "thinking" || block.type === "redacted_thinking")) {
|
|
4202
|
+
delete block.cache_control;
|
|
4203
|
+
stripped = true;
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
3811
4206
|
if (cc?.scope !== void 0) {
|
|
3812
4207
|
delete cc.scope;
|
|
3813
4208
|
if (Object.keys(cc).length === 0) delete block.cache_control;
|
|
@@ -4014,7 +4409,7 @@ function resolveModelInBody(rawBody) {
|
|
|
4014
4409
|
modified = true;
|
|
4015
4410
|
}
|
|
4016
4411
|
}
|
|
4017
|
-
if (rawBody.includes("\"scope\"") && sanitizeCacheControl(parsed)) modified = true;
|
|
4412
|
+
if ((rawBody.includes("\"scope\"") || rawBody.includes("\"cache_control\"") && (rawBody.includes("\"thinking\"") || rawBody.includes("\"redacted_thinking\""))) && sanitizeCacheControl(parsed)) modified = true;
|
|
4018
4413
|
if ((rawBody.includes("\"budget\"") || rawBody.includes("\"output_config\"") || rawBody.includes("\"betas\"") || rawBody.includes("\"eager_input_streaming\"")) && stripAnthropicOnlyFields(parsed)) modified = true;
|
|
4019
4414
|
const resolvedModel = typeof parsed.model === "string" ? parsed.model : originalModel;
|
|
4020
4415
|
const selectedModel = resolvedModel ? state.models?.data.find((m) => m.id === resolvedModel) : void 0;
|
|
@@ -4028,6 +4423,11 @@ function resolveModelInBody(rawBody) {
|
|
|
4028
4423
|
function sanitizeCacheControl(body) {
|
|
4029
4424
|
let stripped = false;
|
|
4030
4425
|
function stripScope(block) {
|
|
4426
|
+
if (block.cache_control && (block.type === "thinking" || block.type === "redacted_thinking")) {
|
|
4427
|
+
delete block.cache_control;
|
|
4428
|
+
stripped = true;
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4031
4431
|
if (block.cache_control?.scope !== void 0) {
|
|
4032
4432
|
delete block.cache_control.scope;
|
|
4033
4433
|
if (Object.keys(block.cache_control).length === 0) delete block.cache_control;
|
|
@@ -4751,6 +5151,7 @@ async function setupAndServe(options) {
|
|
|
4751
5151
|
await cacheModels();
|
|
4752
5152
|
consola.debug(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
|
|
4753
5153
|
const serveOptions = buildServeOptions(server.fetch, options.silent);
|
|
5154
|
+
installBodySizeStatsExitHook();
|
|
4754
5155
|
let srvxServer;
|
|
4755
5156
|
if (options.port !== void 0) srvxServer = serve({
|
|
4756
5157
|
...serveOptions,
|
|
@@ -5156,4 +5557,4 @@ function getCodexEnvVars(serverUrl) {
|
|
|
5156
5557
|
//#endregion
|
|
5157
5558
|
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 };
|
|
5158
5559
|
|
|
5159
|
-
//# sourceMappingURL=server-setup-
|
|
5560
|
+
//# sourceMappingURL=server-setup-BmyWS4WU.js.map
|