github-router 0.3.248 → 0.3.249

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.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "github-router browser bridge",
4
4
  "short_name": "gh-router-browser",
5
- "version": "0.3.248",
5
+ "version": "0.3.249",
6
6
  "description": "Bridge between Claude (via github-router /mcp) and the browser. Implements tab control, navigation, clicks, form fill, downloads, screenshots, devtools eval. Blocks navigation to chrome://settings.",
7
7
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqJElxuBlonBS3TVW9FJN0mGTtShB3L1hoaYf6k39SOr1ogGYmF90EjRxy1i21k9wQQjPf26bcBu/9X67KrQjQV0uB38CaNukgiSeoLjfptN811u+PJHx6BP+jx3Qa6/3VenNPxHC8WEU0GXql8QSjIHEyCwKb6fMASXOK94JyB5Ywov2x8mt/+9ncqBBBMVzf6r5Sagy4PL1XnryLsuADD/vOEkPet8wXgH/Oj7v5tTsQQZ7U1JT51PoDs2BFnXc5v3TkVgZwd32k3ONh+nkDw1Hof+4zwUGOyJE6eMrlYzRlKM4Qxdf9JpavQvqfieAbTRWcyKeclnHeoIfE7cDBQIDAQAB",
8
8
  "background": {
@@ -0,0 +1,2 @@
1
+ import { W as runWorkerAgent } from "./peer-mcp-personas-DJzLpfDJ.js";
2
+ export { runWorkerAgent };
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as searchWeb, $t as DEFAULT_CLAUDE_MODEL_FALLBACKS, A as trustRepo, An as state, At as getTokenCount, Bt as hasSupportedBrowserInstalled, C as fileLastPromptStore, Cn as fetchWithTransientRetry, Ct as scoutModel, D as repoRoot, Dn as copilotBaseUrl, Dt as shimDefaultsToXhigh, E as repoFingerprint, En as GITHUB_API_BASE_URL, Et as workerToolsEnabled, Ft as createChatCompletions, G as buildEnv, Gt as CONDENSED_OPERATING_SEQUENCE, Ht as extractTarGzMember, It as MAX_RESPONSE_BODY_BYTES, J as toolbeltEnabled, Jt as ArtifactClient, K as availableToolCommands, Kt as DEFINITION_OF_GREATNESS, Lt as readResponseBodyCapped, M as resolveSealedGate, Mt as resolveMcpToolTimeoutMs, Nt as pickEndpoint, O as stopGateEnabledForRepo, On as copilotHeaders, Ot as countTokens, Pt as createResponses, Q as assetFor, Qt as toolbeltPathOverride, Rt as parseJsonOrDiagnose, S as fileFindingsStore, Sn as getGitHubUser, St as reviewerModel, T as isSubagentContext, Tn as forwardError, Tt as standInToolEnabled, Ut as extractZipMember, Vt as provisionAndIndexColbert, Wt as warmTreeSitterPool, X as vscodeRipgrepPath, Xt as buildWorkspaceHeaderJson, Y as toolbeltSkipSet, Yt as buildWorkspaceHeaderHelperCommand, Z as TOOLBELT_TOOLS, Zt as collapsePathKeys, _ as stopGateDisabled, _n as isNullish, _t as browserCompoundToolsEnabled, a as buildPeerAwarenessSnippet, an as generateRandomPort, at as buildAnthropicErrorEvent, b as stopReviewEnabled, bn as sleep$1, bt as geminiAvailable, c as personasFor, cn as withInstallLock, ct as logStreamError, d as buildStopHookCommand, dn as setupGitHubToken, dt as handleMcpDelete, en as DEFAULT_CODEX_MODEL, et as ADVISOR_INTERNAL_TOOL_NAME, f as captureLaunchBaseline, fn as tryRefreshAndRetry, ft as handleMcpPost, g as launchBaselineKey, gn as filterBetaHeader, gt as browseAgentEnabled, h as injectStopHookIntoSettingsFile, hn as cacheVSCodeVersion, ht as brainstormModel, i as buildAgentPrompt, in as UPSTREAM_INACTIVITY_TIMEOUT_MS, it as isAdvisorRequested, j as liveExec, jt as assembleResponsesPayload, k as stopReviewStateDir, kn as githubHeaders, kt as createMessages, l as buildArtifactOpenHookCommand, ln as setupCopilotToken, lt as readIteratorWithTimeout, m as fileBlockBudget, mn as cacheModels, mt as artifactToolsEnabled, n as MCP_GROUPS, nt as buildAdvisorStream, o as buildPeerAwarenessSummary, on as pickClaudeDefault, ot as buildOpenAIErrorEvent, p as decideStopHook, pn as cacheCopilotVersion, pt as agentToolsEnabled, q as buildToolbeltAwareness, qt as shouldUseInsecureTls, r as assertMcpToolSurfaceConsistent, rn as UPSTREAM_FETCH_TIMEOUT_MS, rt as injectAdvisorTool, s as enumerateInjectedMcpToolNames, sn as getPackageVersion$1, st as isControllerClosedError, t as GROUP_META, tn as DEFAULT_CODEX_MODEL_FALLBACKS, tt as ADVISOR_TOOL_INSTRUCTIONS, u as buildSessionBindHookCommand, un as setupGitHubAgentToken, ut as relayAnthropicStream, v as stopGateId, vn as resolveCodexModel, vt as browserToolsEnabled, w as fileReviewDebounce, wn as HTTPError, wt as scribeModel, x as fileBaselineStore, xn as getModels, xt as nativeSubagentModel, y as stopGatePlanMode, yn as resolveModel, yt as fleetToolsEnabled, zt as provisionBrowserAssets } from "./peer-mcp-personas-HWhxZUvf.js";
2
+ import { $ as searchWeb, $t as DEFAULT_CLAUDE_MODEL_FALLBACKS, A as trustRepo, An as state, At as getTokenCount, Bt as hasSupportedBrowserInstalled, C as fileLastPromptStore, Cn as fetchWithTransientRetry, Ct as scoutModel, D as repoRoot, Dn as copilotBaseUrl, Dt as shimDefaultsToXhigh, E as repoFingerprint, En as GITHUB_API_BASE_URL, Et as workerToolsEnabled, Ft as createChatCompletions, G as buildEnv, Gt as CONDENSED_OPERATING_SEQUENCE, Ht as extractTarGzMember, It as MAX_RESPONSE_BODY_BYTES, J as toolbeltEnabled, Jt as ArtifactClient, K as availableToolCommands, Kt as DEFINITION_OF_GREATNESS, Lt as readResponseBodyCapped, M as resolveSealedGate, Mt as resolveMcpToolTimeoutMs, Nt as pickEndpoint, O as stopGateEnabledForRepo, On as copilotHeaders, Ot as countTokens, Pt as createResponses, Q as assetFor, Qt as toolbeltPathOverride, Rt as parseJsonOrDiagnose, S as fileFindingsStore, Sn as getGitHubUser, St as reviewerModel, T as isSubagentContext, Tn as forwardError, Tt as standInToolEnabled, Ut as extractZipMember, Vt as provisionAndIndexColbert, Wt as warmTreeSitterPool, X as vscodeRipgrepPath, Xt as buildWorkspaceHeaderJson, Y as toolbeltSkipSet, Yt as buildWorkspaceHeaderHelperCommand, Z as TOOLBELT_TOOLS, Zt as collapsePathKeys, _ as stopGateDisabled, _n as isNullish, _t as browserCompoundToolsEnabled, a as buildPeerAwarenessSnippet, an as generateRandomPort, at as buildAnthropicErrorEvent, b as stopReviewEnabled, bn as sleep$1, bt as geminiAvailable, c as personasFor, cn as withInstallLock, ct as logStreamError, d as buildStopHookCommand, dn as setupGitHubToken, dt as handleMcpDelete, en as DEFAULT_CODEX_MODEL, et as ADVISOR_INTERNAL_TOOL_NAME, f as captureLaunchBaseline, fn as tryRefreshAndRetry, ft as handleMcpPost, g as launchBaselineKey, gn as filterBetaHeader, gt as browseAgentEnabled, h as injectStopHookIntoSettingsFile, hn as cacheVSCodeVersion, ht as brainstormModel, i as buildAgentPrompt, in as UPSTREAM_INACTIVITY_TIMEOUT_MS, it as isAdvisorRequested, j as liveExec, jt as assembleResponsesPayload, k as stopReviewStateDir, kn as githubHeaders, kt as createMessages, l as buildArtifactOpenHookCommand, ln as setupCopilotToken, lt as readIteratorWithTimeout, m as fileBlockBudget, mn as cacheModels, mt as artifactToolsEnabled, n as MCP_GROUPS, nt as buildAdvisorStream, o as buildPeerAwarenessSummary, on as pickClaudeDefault, ot as buildOpenAIErrorEvent, p as decideStopHook, pn as cacheCopilotVersion, pt as agentToolsEnabled, q as buildToolbeltAwareness, qt as shouldUseInsecureTls, r as assertMcpToolSurfaceConsistent, rn as UPSTREAM_FETCH_TIMEOUT_MS, rt as injectAdvisorTool, s as enumerateInjectedMcpToolNames, sn as getPackageVersion$1, st as isControllerClosedError, t as GROUP_META, tn as DEFAULT_CODEX_MODEL_FALLBACKS, tt as ADVISOR_TOOL_INSTRUCTIONS, u as buildSessionBindHookCommand, un as setupGitHubAgentToken, ut as relayAnthropicStream, v as stopGateId, vn as resolveCodexModel, vt as browserToolsEnabled, w as fileReviewDebounce, wn as HTTPError, wt as scribeModel, x as fileBaselineStore, xn as getModels, xt as nativeSubagentModel, y as stopGatePlanMode, yn as resolveModel, yt as fleetToolsEnabled, zt as provisionBrowserAssets } from "./peer-mcp-personas-DJzLpfDJ.js";
3
3
  import { a as isUnderClaudeConfigMirror, d as writeRuntimeFileSecure, i as ensurePaths, o as removeOwnClaudeConfigMirror, r as ensureClaudeConfigMirror, t as PATHS, u as writeArtifactCredsToMirror } from "./paths-BjTMI_xK.js";
4
4
  import { c as killManagedTree, d as resolveExecutable, f as runCommandCapture, l as parseBoolEnv, p as runCommandVoid, s as killChildProcessTree, u as parseIntEnv } from "./lifecycle-DHZsVN8j.js";
5
5
  import { a as sweepRegistry } from "./lifecycle-CUf1UDrH.js";
@@ -18,6 +18,7 @@ import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
18
18
  import { Writable } from "node:stream";
19
19
  import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from "node:timers";
20
20
  import { serve } from "srvx";
21
+ import { createBodyTooLargeError, limitRequestBody } from "srvx/body-limit";
21
22
  import { getProxyForUrl } from "proxy-from-env";
22
23
  import { Hono } from "hono";
23
24
  import { cors } from "hono/cors";
@@ -3087,7 +3088,7 @@ async function discoverGateCommands(cwd, opts) {
3087
3088
  if (files.length === 0) return null;
3088
3089
  let result;
3089
3090
  try {
3090
- const { runWorkerAgent } = await import("./engine-C1nJ6ue1.js");
3091
+ const { runWorkerAgent } = await import("./engine-BuuHUb4t.js");
3091
3092
  result = await runWorkerAgent({
3092
3093
  mode: "explore",
3093
3094
  workspace: root,
@@ -4281,14 +4282,27 @@ const STYLE_DIRECTIVE = "Write concisely without losing detail. Use a natural hu
4281
4282
  * main context free to reason and collaborate with the user, while still
4282
4283
  * doing trivial / surgical / last-mile work directly (delegating that
4283
4284
  * would only add relay-fidelity loss + latency).
4284
- * 2. Excellence lens: the principles stated plainly and concretely (radical
4285
+ * 2. Adversarial review: WHEN a peer critic earns its keep and, equally
4286
+ * important, when reaching for one is ritual rather than review. Same
4287
+ * failure shape the delegation default had: "consult a critic for
4288
+ * non-trivial changes" is unfalsifiable in advance, so it collapses into
4289
+ * either never (four of four unprimed agents) or always (worse than
4290
+ * never). The discriminator is whether the conclusion still turns on
4291
+ * judgment once the direct evidence is in: a consequential recommendation
4292
+ * cannot be run, which is exactly where confabulation hides, while a
4293
+ * tracing question a search already proved gains nothing from a second
4294
+ * model re-deriving it. The roster, the lens-to-artifact match, the
4295
+ * advisor-complements-rather-than-substitutes distinction, and the
4296
+ * do-not-anchor-the-critic rule live here; the digest carries only the
4297
+ * trigger and the ritual exclusion.
4298
+ * 3. Excellence lens: the principles stated plainly and concretely (radical
4285
4299
  * simplicity + real-user focus; whole-system first-principles thinking that
4286
4300
  * anticipates scale; work back from the customer outcome). Named exemplars
4287
4301
  * were dropped per the injected-surface review: a named entity is a dense,
4288
4302
  * high-variance vector that pulls in persona mannerisms at top salience, and
4289
4303
  * the guidance favors specific functional framing over comparison, so
4290
4304
  * specificity carries the vividness instead.
4291
- * 3. Engineering excellence: quality / robustness / maintainability over
4305
+ * 4. Engineering excellence: quality / robustness / maintainability over
4292
4306
  * development cost; reproduce a bug end-to-end (as a real user hits it)
4293
4307
  * before fixing so the fix targets the real cause; a pixel-perfect UI bar;
4294
4308
  * and fix any lint error / test failure / flake on sight, whoever caused it,
@@ -4300,15 +4314,24 @@ const STYLE_DIRECTIVE = "Write concisely without losing detail. Use a natural hu
4300
4314
  * Self-referentially compliant with the style directive: no em dashes, no
4301
4315
  * Claude / Anthropic attribution.
4302
4316
  */
4303
- const OPERATING_DEFAULTS_DIRECTIVE = "## Operating defaults (apply when the user has not specified otherwise; the user's explicit direction and the domain's own standards always override)\n\nOrchestrate. Delegate research, implementation, review, and large reads to the right subagent, worker, or model. Reach for `implementer` when you know what to build, `reviewer` when something exists and you want it assessed (including reproducing and root-causing a failure), `brainstorm` when you do not yet know which approach to take, `scout` to find or understand something in the repo, and `scribe` for docs and ADRs that trail the code; worker-* agents for background non-blocking runs; Task subagents for parallel work; peer critics for review. That keeps your own context free to reason and collaborate with the user. Prefer parallel delegation for independent work. Delegation pays when the work is WIDE (many files or sources to sweep) or SLOW, and you need only the conclusion: the main thread is where you think with and respond to the user, and its context window is a finite shared resource. It does NOT pay merely because a sub-question is separable. A narrow, deep question whose whole value is file:line fidelity loses exactly that through a summarization layer, and a sub-question you could answer in one command is cheaper done directly than paying a subagent's startup. Do trivial, surgical, and last-mile work yourself.\n\nAim high. Default to radical simplicity and a relentless focus on the user's real experience: design for the person and the job to be done, not the demo. Reason about the whole system from first principles, anticipating scale and the long arc rather than patching the surface. Work backwards from the outcome the user actually needs. Question every assumption and prefer what you can derive, reproduce, or test.\n\nEngineering excellence. When making technical decisions, give little weight to development cost; prefer quality, simplicity, robustness, scalability, and long-term maintainability. Fix a bug by first reproducing it end to end, as close to how a real user hits it as you can, so you solve the real problem and not a symptom. When testing a product end to end, be picky about the UI and obsessed with pixel perfection: if something clearly looks off, even when it is unrelated to your task, get it fixed along the way. Hold that same bar for the codebase itself: a lint error, a failing test, or a flaky test is worth fixing the moment you see it, whoever introduced it. Fold it into your current work rather than letting it derail the task the user actually asked for.";
4317
+ const OPERATING_DEFAULTS_DIRECTIVE = "## Operating defaults (apply when the user has not specified otherwise; the user's explicit direction and the domain's own standards always override)\n\nOrchestrate. Delegate research, implementation, review, and large reads to the right subagent, worker, or model. Reach for `implementer` when you know what to build, `reviewer` when something exists and you want it assessed (including reproducing and root-causing a failure), `brainstorm` when you do not yet know which approach to take, `scout` to find or understand something in the repo, and `scribe` for docs and ADRs that trail the code; worker-* agents for background non-blocking runs; Task subagents for parallel work; peer critics for review. That keeps your own context free to reason and collaborate with the user. Prefer parallel delegation for independent work. Delegation pays when the work is WIDE (many files or sources to sweep) or SLOW, and you need only the conclusion: the main thread is where you think with and respond to the user, and its context window is a finite shared resource. It does NOT pay merely because a sub-question is separable. A narrow, deep question whose whole value is file:line fidelity loses exactly that through a summarization layer, and a sub-question you could answer in one command is cheaper done directly than paying a subagent's startup. Do trivial, surgical, and last-mile work yourself.\n\nAdversarial review. The peer critics (`codex_critic` and `codex_reviewer`, `gemini_critic` and `gemini_reviewer`, `opus_critic`, and the `peer-review-coordinator` that fans out to several of them) are fresh-context models, so what they add is a blind spot that whoever produced the work cannot reach by thinking harder about it; prefer a critic from a different lab than the producer, since blind spots correlate within a lab. The `advisor` is a complement and not a substitute: it sees your transcript, so it catches your own drift and momentum, but it inherits your framing, which is exactly what a fresh-context critic does not. They earn their keep on consequential design choices, recommendations, and hard-to-reverse decisions: the cases where plausible alternatives remain and the conclusion rests on judgment rather than on something you can verify directly. That is where confabulation hides, so budget the wait even under delivery pressure. Always consult one when the change touches auth, user input, database queries, crypto, or serialization. They do NOT pay for read-only tracing, ordinary repository lookup, or a conclusion that a focused test, a direct reproduction, or unambiguous code evidence already settles. Asking a critic to re-derive a proven fact returns a confident answer either way, which is ritual skepticism rather than review, and skipping them there is the right call and not a shortcut. Match the lens to the artifact: a strategic critic for plans and trade-offs, a code reviewer for a concrete diff, the coordinator only when the risk warrants several independent lenses. Give whichever you pick the artifact and the constraints and not your rationale, since justification anchors the review and dulls it.\n\nAim high. Default to radical simplicity and a relentless focus on the user's real experience: design for the person and the job to be done, not the demo. Reason about the whole system from first principles, anticipating scale and the long arc rather than patching the surface. Work backwards from the outcome the user actually needs. Question every assumption and prefer what you can derive, reproduce, or test.\n\nEngineering excellence. When making technical decisions, give little weight to development cost; prefer quality, simplicity, robustness, scalability, and long-term maintainability. Fix a bug by first reproducing it end to end, as close to how a real user hits it as you can, so you solve the real problem and not a symptom. When testing a product end to end, be picky about the UI and obsessed with pixel perfection: if something clearly looks off, even when it is unrelated to your task, get it fixed along the way. Hold that same bar for the codebase itself: a lint error, a failing test, or a flaky test is worth fixing the moment you see it, whoever introduced it. Fold it into your current work rather than letting it derail the task the user actually asked for.";
4304
4318
  /**
4305
4319
  * Condensed digest of OPERATING_DEFAULTS_DIRECTIVE for the spawned session's
4306
4320
  * system prompt (--append-system-prompt). The FULL directive is prepended to
4307
4321
  * the mirrored CLAUDE.md (read by the main agent and descendants); this digest
4308
4322
  * keeps both behavioral directives at top salience without duplicating the full
4309
4323
  * ~310-token block in the context window every turn. Points to the full copy.
4324
+ *
4325
+ * The unverifiable-claim rule is here rather than in CLAUDE.md alone because it
4326
+ * fires at a moment that suppresses lookups: an agent racing to deliver a
4327
+ * recommendation is precisely the one that will not stop to consult its project
4328
+ * instructions, which is how the check got crowded out in the first place. It
4329
+ * also closes a hole in the rule above it, which only covers claims that direct
4330
+ * evidence CAN settle. The roster of critics, the lens-to-artifact match, and
4331
+ * the reasoning stay in the directive; the always-resident copy carries only
4332
+ * the trigger and the ritual exclusion.
4310
4333
  */
4311
- const OPERATING_DEFAULTS_DIGEST = "## Operating defaults (the user's explicit direction and the domain's standards always override)\n\nDelegate when the work is WIDE (many files or sources to sweep) or SLOW and you need only the conclusion, to protect the main thread's finite context and keep it free for reasoning and interacting with the user; prefer parallel delegation for independent work. Do NOT delegate merely because a sub-question is separable: a narrow, deep question whose value is file:line fidelity loses exactly that through a summarization layer, and one answerable in a single command is cheaper done directly. Do trivial, surgical, and last-mile work yourself.\n\nVerify, do not assert. Run the code, read the file, check the exit code. A claim in prose is worth nothing against state you did not check, and a check that cannot fail proves nothing. Reproduce a bug end to end, the way a real user hits it, before fixing it. Fix a lint error, failing test, or flake the moment you see it, whoever introduced it, without letting it derail the task at hand. Prefer quality and long-term maintainability over development cost.\n\nThe agent roster, the tool surface, and the reasoning behind these defaults are in your CLAUDE.md project instructions. Read them when choosing HOW to work; the rules above apply without a lookup.";
4334
+ const OPERATING_DEFAULTS_DIGEST = "## Operating defaults (the user's explicit direction and the domain's standards always override)\n\nDelegate when the work is WIDE (many files or sources to sweep) or SLOW and you need only the conclusion, to protect the main thread's finite context and keep it free for reasoning and interacting with the user; prefer parallel delegation for independent work. Do NOT delegate merely because a sub-question is separable: a narrow, deep question whose value is file:line fidelity loses exactly that through a summarization layer, and one answerable in a single command is cheaper done directly. Do trivial, surgical, and last-mile work yourself.\n\nVerify, do not assert. Run the code, read the file, check the exit code. A claim in prose is worth nothing against state you did not check, and a check that cannot fail proves nothing. Reproduce a bug end to end, the way a real user hits it, before fixing it. Fix a lint error, failing test, or flake the moment you see it, whoever introduced it, without letting it derail the task at hand. Prefer quality and long-term maintainability over development cost.\n\nVerification has a blind spot: a consequential recommendation, a design or trade-off call, or a hard-to-reverse decision that still turns on judgment among plausible alternatives once the direct evidence is in. That is where confabulation hides, so put it past a peer critic before you ship it and budget the wait even under delivery pressure. When a test, a run, a reproduction, or a search would settle the claim, settle it that way instead; a critic asked to re-derive what you can already prove is ritual, not review.\n\nThe agent roster, the tool surface, and the reasoning behind these defaults are in your CLAUDE.md project instructions. Read them when choosing HOW to work; the rules above apply without a lookup.";
4312
4335
  /**
4313
4336
  * Skip the helper if the user's `~/.claude/CLAUDE.md` (or, equivalently,
4314
4337
  * the would-be post-write file) has grown past this size.
@@ -5538,7 +5561,7 @@ function initProxyFromEnv() {
5538
5561
  //#endregion
5539
5562
  //#region package.json
5540
5563
  var name = "github-router";
5541
- var version$1 = "0.3.248";
5564
+ var version$1 = "0.3.249";
5542
5565
  //#endregion
5543
5566
  //#region src/lib/approval.ts
5544
5567
  const awaitApproval = async () => {
@@ -7512,6 +7535,29 @@ function isClaudeModel(modelId, model, originalModelId) {
7512
7535
  * any chat-default model) → "chat-shim".
7513
7536
  * - A non-Claude model absent from the catalog (so we can't confirm an endpoint)
7514
7537
  * → "claude-passthrough" (unchanged; we don't divert what we can't classify).
7538
+ * - A non-Claude model that IS in the catalog and serves NEITHER of the two shim
7539
+ * endpoints (`pickEndpoint` → undefined) → "claude-passthrough" as well.
7540
+ *
7541
+ * Those last two land on the same route but are NOT the same answer, and the
7542
+ * coincidence is deliberate rather than a collapsed default (contrast
7543
+ * `resolveEndpointForModelId`, whose callers must tell them apart because
7544
+ * guessing there produces an opaque upstream 400). Here neither shim is even a
7545
+ * candidate: a shim can only speak `/responses` or `/chat/completions`, so
7546
+ * diverting a model that serves neither would 400 just as surely. Passthrough
7547
+ * is the better default because it is sometimes RIGHT — a non-Claude catalog
7548
+ * model advertising `/v1/messages` is served by exactly the endpoint
7549
+ * passthrough uses. It also preserves this module's fail-CLOSED-toward-Claude
7550
+ * invariant: an unclassifiable model is never diverted.
7551
+ *
7552
+ * KNOWN GAP (audited, deliberately not fixed here): a non-Claude model serving
7553
+ * only something we cannot speak at all (say `/embeddings`) also lands on
7554
+ * passthrough and will still 400 upstream — `logEndpointMismatch(modelId,
7555
+ * "/v1/messages")` logs it at the passthrough seam, but no local error is
7556
+ * raised. Closing that needs a change in `src/routes/messages/handler.ts`,
7557
+ * which this seam does not own. It is strictly narrower than the defect fixed
7558
+ * in `resolveEndpointForModelId`: no such model is reachable as a Claude Code
7559
+ * `/v1/messages` target today, whereas the plan worker's `claude-opus-5`
7560
+ * default is.
7515
7561
  *
7516
7562
  * `originalModelId` is the optional pre-resolution request id; when supplied it
7517
7563
  * is checked for Claude-likeness alongside the resolved id so an alias that
@@ -8783,21 +8829,177 @@ server.notFound((c) => c.json({
8783
8829
  //#region src/lib/server-setup.ts
8784
8830
  const MAX_PORT_RETRIES = 10;
8785
8831
  /**
8832
+ * Maximum request body the proxy will accept, in bytes.
8833
+ *
8834
+ * Pinned EXPLICITLY because the runtimes disagree by default: bun's `serve`
8835
+ * defaults `maxRequestBodySize` to 128 MB and rejects past it, while
8836
+ * `node:http` (the srvx node adapter) has no body limit at all. Left
8837
+ * unset, the same request succeeds under node and 413s under bun, with
8838
+ * nothing in the response explaining that the runtime is the variable.
8839
+ * Same class of defect as the `idleTimeout` divergence below.
8840
+ *
8841
+ * Note which side that leaves exposed: `dist/main.js` ships
8842
+ * `#!/usr/bin/env node`, so npm-installed users are on the runtime with NO
8843
+ * limit, and bun is mostly the dev/`bun run start` path. The uncapped case
8844
+ * was the shipped one.
8845
+ *
8846
+ * 128 MB — i.e. bun's default, adopted as the intended value on BOTH
8847
+ * runtimes rather than inherited on one:
8848
+ *
8849
+ * - It sits above the traffic this proxy is built for, with margin. A
8850
+ * full 1M-token Claude Code context is on the order of 4-5 MB of JSON
8851
+ * text; base64 inline images inflate 4/3 and are re-sent every turn, so
8852
+ * an image-heavy session runs to tens of MB. That is an estimate, not a
8853
+ * measurement, which is part of why the cap is set well clear of it
8854
+ * rather than snugly above it.
8855
+ * - Removing the limit to "match node" is not free even on loopback. The
8856
+ * app mounts permissive `cors()`, so any web page the user visits can
8857
+ * POST to 127.0.0.1:<port> (CORS gates reading the response, not sending
8858
+ * the request); so can any local process. An unbounded body lets either
8859
+ * stream the proxy to death.
8860
+ * - Raising it above 128 MB buys nothing and costs safety: the proxy
8861
+ * BUFFERS the whole body (`c.req.json()`), parses it into a JS object
8862
+ * (multiples of the text size in heap), then re-serializes it upstream.
8863
+ * Past ~512 MB V8's max string length makes `JSON.stringify` fail
8864
+ * opaquely — a cap well under that keeps the failure explainable.
8865
+ *
8866
+ * A body over this ceiling means something is wrong (runaway loop,
8867
+ * corrupted context), not a workload that needs a bigger buffer, so
8868
+ * there is deliberately no env override to raise it.
8869
+ */
8870
+ const MAX_REQUEST_BODY_BYTES = 134217728;
8871
+ /**
8872
+ * Where the RUNTIME's own body check sits. Not the policy.
8873
+ *
8874
+ * The policy is `MAX_REQUEST_BODY_BYTES`, enforced by `withBodyLimit` in
8875
+ * this process, identically on every runtime. This value exists only to
8876
+ * displace the 128 MB default bun's `serve` applies, which would otherwise pre-empt
8877
+ * that gate — bun enforces its limit at header-parse time by replying and
8878
+ * closing while the client is still uploading, so a client that has not
8879
+ * finished writing never reads the reply. Measured with a real client
8880
+ * posting 64 MB over a limit: `The socket connection was closed
8881
+ * unexpectedly`, i.e. exactly the opaque failure this change removes, and
8882
+ * exactly what bun does TODAY at its default. An early response from our
8883
+ * own handler, by contrast, is delivered cleanly (413 with the JSON body,
8884
+ * ~50ms, no memory growth; draining the body first was strictly worse at
8885
+ * 155ms and +50 MB, so we do not drain).
8886
+ *
8887
+ * Set as ONE top-level srvx option, so bun and node get the same number.
8888
+ * A bun-only override would put the two runtimes back on different
8889
+ * thresholds, which is the defect being fixed, not a fix for it.
8890
+ *
8891
+ * Deliberately far above the policy so the policy always decides first:
8892
+ * everything a client can realistically send is answered by the explained
8893
+ * 413, and this is reached only by a body that is both undeclared and
8894
+ * absurd. It stays finite so "no limit at all" is never the state. It
8895
+ * bounds nothing the gate does not already bound — an unread body is
8896
+ * discarded by both runtimes rather than buffered — so a high value costs
8897
+ * no memory, and buys message quality across the whole realistic range.
8898
+ */
8899
+ const TRANSPORT_BODY_CEILING_BYTES = 1073741824;
8900
+ /**
8901
+ * The 413 the user sees when a request body is over the ceiling.
8902
+ *
8903
+ * Anthropic error envelope (`request_too_large` is Anthropic's own 413
8904
+ * category), so a client that parses our error shape on every other path
8905
+ * parses this one too, and the message says what to actually do about it.
8906
+ *
8907
+ * `declaredBytes` is present only when the request declared a length. A
8908
+ * body caught mid-stream has no known size, and saying so beats inventing
8909
+ * one. Byte counts are exact, with the MB figure only as a gloss:
8910
+ * rounding alone reads as a contradiction at the boundary, where a body
8911
+ * one byte over renders as "128.0 MB, over the 128.0 MB limit".
8912
+ */
8913
+ function bodyTooLargeResponse(declaredBytes) {
8914
+ const mb = (n) => `${(n / 1048576).toFixed(1)} MB`;
8915
+ const actual = declaredBytes === void 0 ? `Request body exceeds the size github-router accepts` : `Request body is ${declaredBytes} bytes (${mb(declaredBytes)}), over the size github-router accepts`;
8916
+ return new Response(JSON.stringify({
8917
+ type: "error",
8918
+ error: {
8919
+ type: "request_too_large",
8920
+ message: `${actual}: at most ${MAX_REQUEST_BODY_BYTES} bytes (${mb(MAX_REQUEST_BODY_BYTES)}), the same limit under bun and node. This is usually an accumulated conversation or inline images (base64 attachments are re-sent every turn) rather than one large message — start a new session or drop the attachments.`
8921
+ }
8922
+ }), {
8923
+ status: 413,
8924
+ headers: { "content-type": "application/json" }
8925
+ });
8926
+ }
8927
+ /** srvx's canonical over-limit error, however it reaches us. */
8928
+ function isBodyTooLarge(error) {
8929
+ return typeof error === "object" && error !== null && error.code === "ERR_BODY_TOO_LARGE";
8930
+ }
8931
+ /**
8932
+ * Enforce `MAX_REQUEST_BODY_BYTES` in-process, so the limit and the
8933
+ * rejection are the same on every runtime.
8934
+ *
8935
+ * Leaving it to the runtimes is what produced the divergence in the first
8936
+ * place, and their rejections are not interchangeable:
8937
+ *
8938
+ * - bun rejects at header-parse time and closes the connection while the
8939
+ * client is still uploading, so the client sees a dead socket rather
8940
+ * than the 413 (measured; see `TRANSPORT_BODY_CEILING_BYTES`).
8941
+ * - node lets the handler run and rejects the body READ with an
8942
+ * `ERR_BODY_TOO_LARGE` error. The app installs no Hono `onError`, so
8943
+ * that becomes `500 Internal Server Error` — indistinguishable from a
8944
+ * real proxy fault.
8945
+ *
8946
+ * Two layers, one threshold, applied uniformly:
8947
+ *
8948
+ * 1. A declared `Content-Length` over the limit is answered before the
8949
+ * body is read at all. This is the path every real client takes
8950
+ * (Claude Code / undici / curl all declare a length on a JSON body).
8951
+ * 2. Anything else — chunked, or a length that understates the body — is
8952
+ * caught mid-stream by srvx's own `limitRequestBody`, the same helper
8953
+ * its node and deno adapters use. Overflow is surfaced as the SAME
8954
+ * explained 413 rather than leaking out as a 500.
8955
+ *
8956
+ * So a body that declares no length is no longer a hole in the message
8957
+ * quality OR in the bound, and neither depends on which runtime is
8958
+ * serving. Verified end to end against both adapters with a real client:
8959
+ * declared-over and chunked-over each return an identical 413.
8960
+ */
8961
+ function withBodyLimit(fetchHandler) {
8962
+ return async (request) => {
8963
+ const declared = request.headers.get("content-length");
8964
+ if (declared !== null && /^\d+$/.test(declared)) {
8965
+ const bytes = Number(declared);
8966
+ if (bytes > 134217728) return bodyTooLargeResponse(bytes);
8967
+ }
8968
+ let overflowed = false;
8969
+ const limited = limitRequestBody(request, MAX_REQUEST_BODY_BYTES, { createError: (max) => {
8970
+ overflowed = true;
8971
+ return createBodyTooLargeError(max);
8972
+ } });
8973
+ try {
8974
+ const response = await fetchHandler(limited);
8975
+ if (overflowed) {
8976
+ response.body?.cancel().catch(() => {});
8977
+ return bodyTooLargeResponse();
8978
+ }
8979
+ return response;
8980
+ } catch (error) {
8981
+ if (overflowed || isBodyTooLarge(error)) return bodyTooLargeResponse();
8982
+ throw error;
8983
+ }
8984
+ };
8985
+ }
8986
+ /**
8786
8987
  * Build the srvx `serve()` options shared by the explicit-port and
8787
8988
  * random-port paths.
8788
8989
  *
8789
- * Extracted and exported so the Bun idle-reaper override below is
8990
+ * Extracted and exported so the per-runtime overrides below are
8790
8991
  * assertable. `setupAndServe` itself performs auth and network I/O, so the
8791
8992
  * only way to pin this behaviour in a test is to make the options object
8792
- * reachable on its own — and it needs pinning, because deleting the override
8793
- * reintroduces a failure that looks like an upstream/network problem rather
8794
- * than a config one.
8993
+ * reachable on its own — and it needs pinning, because deleting either
8994
+ * override reintroduces a failure that looks like an upstream/network
8995
+ * problem rather than a config one.
8795
8996
  */
8796
8997
  function buildServeOptions(fetchHandler, silent) {
8797
8998
  return {
8798
- fetch: fetchHandler,
8999
+ fetch: withBodyLimit(fetchHandler),
8799
9000
  hostname: "127.0.0.1",
8800
9001
  silent,
9002
+ maxRequestBodySize: TRANSPORT_BODY_CEILING_BYTES,
8801
9003
  bun: { idleTimeout: 0 }
8802
9004
  };
8803
9005
  }