github-router 0.3.247 → 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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/engine-BuuHUb4t.js +2 -0
- package/dist/main.js +214 -12
- package/dist/main.js.map +1 -1
- package/dist/paths-BjTMI_xK.js.map +1 -1
- package/dist/{peer-mcp-personas-EM3cQqlR.js → peer-mcp-personas-DJzLpfDJ.js} +186 -31
- package/dist/{peer-mcp-personas-EM3cQqlR.js.map → peer-mcp-personas-DJzLpfDJ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/engine-BZ3Ml3rs.js +0 -2
|
@@ -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.
|
|
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": {
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
*
|
|
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\
|
|
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.
|
|
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
|
|
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
|
|
8793
|
-
* reintroduces a failure that looks like an upstream/network
|
|
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
|
}
|