github-router 0.3.27 → 0.3.28

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/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","names":["_claudeConfigDirSuffix: string | undefined","CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy>","name","SHARED_TOPLEVEL_NAMES: ReadonlyArray<string>","entries: Array<string>","stats: Awaited<ReturnType<typeof fs.lstat>>","existing: Awaited<ReturnType<typeof fs.lstat>> | null","state: State","state","version","headers: Record<string, string>","errorJson: unknown","parsed: URL","FALLBACK","version","inflightRefresh: Promise<void> | undefined","name","sanitized: NodeJS.ProcessEnv","process","name","raw: string","version","cmd: string[]","child: ChildProcess","server","searchTimestamps: Array<number>","throttleChain: Promise<void>","headers: Record<string, string>","sid: string | undefined","rpc: z.infer<typeof RpcSchema> | undefined","parsedJson: unknown","innerRaw: unknown","references: Array<{ title: string; url: string }>","PERSONAS_READ: ReadonlyArray<PersonaSpec>","PERSONAS_WRITE: ReadonlyArray<PersonaSpec>","criticList: Array<string>","result: Array<PersonaSpec>","NON_PERSONA_MCP_TOOLS: ReadonlyArray<NonPersonaMcpTool>","mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry>","peers: Array<string>","out: PeerAgentDefinitions","name","paths: Array<string>","existing: Record<string, unknown>","mcpServers: Record<string, unknown>","conflicts: Array<string>","size: number","fs","fd: number | undefined","ENDPOINT_ALIASES: Record<string, Endpoint>","path","rateLimitChain: Promise<void>","state","parts: Array<string>","ENCODER","timeoutHandle: ReturnType<typeof setTimeout> | undefined","x","fetchInit: RequestInit","signals: Array<AbortSignal>","ENCODER","formatSSE","parts: Array<string>","handleCompletion","injectWebSearchIfNeeded","inputTokens: number | undefined","isNonStreaming","pendingFirstChunk: UpstreamSSEEvent | undefined","extractUserQuery","handleCompletion","fetchInit: RequestInit","signals: Array<AbortSignal>","fetchInit: RequestInit","signals: Array<AbortSignal>","bodyText: string","auth","personaEntries: Array<ToolEntry>","nonPersonaEntries: Array<ToolEntry>","out: Array<string>","PRE_FLIGHT_CAPS: ReadonlyArray<{\n toolName: string\n effort: Effort\n maxBriefBytes: number\n}>","name","effort: Effort","text","nonPersonaTool: NonPersonaMcpTool | undefined","personaPrompt: string | undefined","personaContext: string | undefined","personaEffort: Effort | undefined","aborter: AbortController | undefined","body: JsonRpcRequest","ENCODER","parsed: AnyRecord","turnBlocks: Array<string>","block: Array<string>","out: Array<string>","text","capturedBlocks: Array<CapturedBlock>","advisorToolUse: ToolUseTracker | null","payload: AnyRecord","captured: CapturedBlock","response: Response","advisorText: string","messages: Array<AnyRecord>","currentAssistantContent: Array<unknown>","parsed: AnyRecord","rebuilt: Array<unknown>","name","isWebSearchTool","body: AnyRecord","resolveModelInBody","extraHeaders: Record<string, string>","parsed: AnyRecord","sanitizeCacheControl","stripAnthropicOnlyFields","headers: Record<string, string>","extractUserQuery","body: AnyRecord","parsedForLog: unknown","response: Response","streamHeaders: Record<string, string>","parsedBase: AnyRecord","parsed: AnyRecord","best: (typeof EFFORT_ORDER)[number] | undefined","parts: Array<string>","firstChunk: UpstreamSSEEvent | undefined","pendingFirstChunk: UpstreamSSEEvent | undefined","payload: ResponsesPayload","result: ResponsesApiResponse","packageJson.name","packageJson.version","app","srvxServer: ReturnType<typeof serve> | undefined","lastError: unknown","port: number | undefined","rateLimit: number | undefined","vars: Record<string, string>","process","server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]","serverUrl: string","onShutdown: () => Promise<void>","geminiAvailable","server","process","server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]","serverUrl: string","server","version","process","commandBlock: string"],"sources":["../src/lib/paths.ts","../src/lib/state.ts","../src/lib/api-config.ts","../src/lib/error.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/github/get-user.ts","../src/services/copilot/get-models.ts","../src/services/get-copilot-version.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts","../src/auth.ts","../src/services/github/get-copilot-usage.ts","../src/check-usage.ts","../src/lib/claude-version-check.ts","../src/lib/port.ts","../src/lib/launch.ts","../src/services/copilot/web-search.ts","../src/lib/peer-mcp-personas.ts","../src/lib/codex-mcp-config.ts","../src/lib/file-log-reporter.ts","../src/lib/model-validation.ts","../src/lib/proxy.ts","../package.json","../src/lib/approval.ts","../src/lib/rate-limit.ts","../src/lib/request-log.ts","../src/lib/stream-relay.ts","../src/lib/tokenizer.ts","../src/services/copilot/create-chat-completions.ts","../src/routes/chat-completions/handler.ts","../src/routes/chat-completions/route.ts","../src/services/copilot/create-embeddings.ts","../src/routes/embeddings/route.ts","../src/services/copilot/create-messages.ts","../src/services/copilot/create-responses.ts","../src/routes/mcp/handler.ts","../src/routes/mcp/route.ts","../src/services/advisor/advisor.ts","../src/lib/sanitize-anthropic-body.ts","../src/lib/diagnose-response.ts","../src/routes/messages/count-tokens-handler.ts","../src/routes/messages/handler.ts","../src/routes/messages/route.ts","../src/routes/models/route.ts","../src/routes/responses/handler.ts","../src/routes/responses/route.ts","../src/routes/search/route.ts","../src/routes/token/route.ts","../src/routes/usage/route.ts","../src/server.ts","../src/lib/server-setup.ts","../src/claude.ts","../src/codex.ts","../src/debug.ts","../src/lib/shell.ts","../src/start.ts","../src/main.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nfunction appDir(): string {\n return path.join(os.homedir(), \".local\", \"share\", \"github-router\")\n}\n\nexport const PATHS = {\n get APP_DIR() {\n return appDir()\n },\n get GITHUB_TOKEN_PATH() {\n return path.join(appDir(), \"github_token\")\n },\n get ERROR_LOG_PATH() {\n return path.join(appDir(), \"error.log\")\n },\n /**\n * Isolated CODEX_HOME for the spawned Codex CLI. Masks any cached\n * ChatGPT subscription login (openai/codex#2733 — cached login can\n * override OPENAI_API_KEY) so the proxy's dummy key is authoritative.\n */\n get CODEX_HOME() {\n return path.join(appDir(), \"codex-isolated\")\n },\n /**\n * Runtime tempfiles for the per-launch peer-MCP wiring (the\n * `--mcp-config` JSON and `--agents` JSON written before spawning\n * Claude Code). Mode 0o700 to match the security review's mandate;\n * cleaned on shutdown via the per-launch `cleanup()`, plus a\n * boot-time sweep of stale files (dead PIDs, >24h old).\n */\n get CLAUDE_RUNTIME_DIR() {\n return path.join(appDir(), \"runtime\")\n },\n /**\n * Router-owned CLAUDE_CONFIG_DIR. The spawned Claude Code (and any\n * teammates it spawns via the agent-teams primitive) reads its\n * config — including `.credentials.json` — from this dir. We\n * snapshot-copy the user's `~/.claude/` here at startup (excluding\n * `.credentials.json` and volatile state), then write our own\n * synthetic Console OAuth credential. The teammate-spawn allowlist\n * propagates `CLAUDE_CONFIG_DIR` to children, so teammates find the\n * synthetic credential and authenticate instead of falling into the\n * \"Not logged in · Run /login\" gate that would otherwise leave\n * them mute. See `ensureClaudeConfigMirror` below.\n *\n * Per-launch dir: `<appDir>/claude-config/<pid>-<8 hex>`. Two\n * concurrent `github-router claude` launches each get their own\n * isolated mirror, so per-launch state (synthetic credential,\n * snapshot copy of `~/.claude/`, future per-launch `.claude.json`\n * mutation with the peer-MCP entry) cannot cross-talk. The\n * per-launch suffix is cached on first access (see\n * `claudeConfigDirSuffix()`) so all callers within a single proxy\n * lifetime see the same value. Boot-time `sweepStaleClaudeConfigMirrors`\n * reaps mirrors from crashed prior PIDs.\n */\n get CLAUDE_CONFIG_DIR() {\n return path.join(appDir(), \"claude-config\", claudeConfigDirSuffix())\n },\n}\n\n/**\n * Per-launch suffix for `PATHS.CLAUDE_CONFIG_DIR`. Lazily generated on\n * first access and cached for the lifetime of the process so every\n * caller (env-var injection in `getClaudeCodeEnvVars`,\n * `ensureClaudeConfigMirror` provisioning, peer-agent `.md` writes\n * under `<dir>/agents/`, the shutdown cleanup) resolves the same path.\n *\n * Shape: `<pid>-<8 hex>`. The PID prefix is what\n * `sweepStaleClaudeConfigMirrors` keys off to drop orphans from\n * crashed prior sessions; the 8-hex random suffix prevents collision\n * if a future caller (tests, internal relaunch) ever clears the cache\n * within a single PID lifetime.\n *\n * NOT exported — every consumer should go through `PATHS.CLAUDE_CONFIG_DIR`\n * so the homedir-mock pattern used in the test suite keeps working.\n */\nlet _claudeConfigDirSuffix: string | undefined\nfunction claudeConfigDirSuffix(): string {\n if (_claudeConfigDirSuffix === undefined) {\n _claudeConfigDirSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n }\n return _claudeConfigDirSuffix\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await fs.mkdir(PATHS.CODEX_HOME, { recursive: true })\n await fs.mkdir(PATHS.CLAUDE_RUNTIME_DIR, { recursive: true })\n // mkdir({recursive: true}) does NOT chmod an existing directory, so\n // explicitly tighten in case the dir was created by an older version.\n await chmodIfPossible(PATHS.CLAUDE_RUNTIME_DIR, 0o700)\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n await sweepStaleRuntimeFiles().catch((err) => {\n consola.debug(\"Runtime sweep skipped:\", err)\n })\n // Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n // crashed prior proxy sessions BEFORE peer-agent .md sweep, since\n // the .md sweep is scoped to THIS launch's mirror and the per-launch\n // dir sweep is the parent cleanup for the same orphan class.\n await sweepStaleClaudeConfigMirrors().catch((err) => {\n consola.debug(\"Per-launch claude-config sweep skipped:\", err)\n })\n // Phase 2.5: also sweep stale peer-* subagent .md files from this\n // launch's CLAUDE_CONFIG_DIR/agents/ (defense-in-depth — should be\n // a no-op since the per-launch dir didn't exist before this PID\n // started; keeps the safety net in case a future change ever shares\n // an agents/ dir across launches).\n await sweepStalePeerAgentMdFiles().catch((err) => {\n consola.debug(\"Peer-agent .md sweep skipped:\", err)\n })\n}\n\n/**\n * Per-entry mirror policy. Every top-level entry under `~/.claude/` falls\n * into exactly one bucket; unlisted names default to `MIRRORED` so a future\n * Claude-Code-side addition flows through as a snapshot copy rather than\n * being silently lost.\n *\n * Three policies:\n *\n * - `ISOLATED` — not present in the mirror at all. The proxy owns its\n * own copy (synthetic `.credentials.json`, the `.github-router-managed`\n * marker) or the entry has no place in a proxy session\n * (`.credentials.json.lock`, `.oauth_refresh.lock` couple refresh loops\n * across sessions; `statsig/` is write-heavy and would constantly\n * re-copy; `cache/` and `logs/` are ephemeral; `paste-cache/` holds\n * sensitive clipboard extracts and shouldn't leak across sessions —\n * gemini-critic finding).\n *\n * - `SHARED` — symlink `<mirror>/<X>` → `~/.claude/<X>` so writes made\n * during the proxy session land in the user's real `~/.claude/` and\n * chat history is visible in both proxy and plain-`claude` sessions.\n * **Directories only.** Never use this for individual files: Claude\n * Code's atomic-write pattern (`fs.writeFile(temp); fs.rename(temp,\n * target)`) does NOT follow symlinks — a `rename` over the symlink\n * replaces it with a regular file, silently severing the connection\n * to `~/.claude/<X>`. Gemini-critic finding from the 3-lab review.\n *\n * - `MIRRORED` (default) — snapshot-copy with mtime skip. Use for static\n * or settings-shaped state where proxy-session writes should NOT flow\n * back to `~/.claude/` (e.g. `settings.json`, `.claude.json`,\n * `teams/`, `session-env/`) and for `agents/` — the proxy itself\n * writes per-launch `peer-<pid>-*.md` files into the mirror's `agents/`\n * and `sweepStalePeerAgentMdFiles` deletes them; a symlink would route\n * those writes/deletes into the user's real `~/.claude/agents/` and\n * destroy the user's own subagent files. **Hard regression test**:\n * `policyFor(\"agents\") === \"MIRRORED\"` is asserted in\n * `tests/lib-paths.test.ts` to prevent accidental reclassification.\n *\n * Sub-paths within MIRRORED dirs cascade recursively (existing behavior).\n */\ntype MirrorPolicy = \"ISOLATED\" | \"SHARED\" | \"MIRRORED\"\n\nconst CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy> = new Map<\n string,\n MirrorPolicy\n>([\n // ISOLATED\n [\".credentials.json\", \"ISOLATED\"],\n [\".credentials.json.lock\", \"ISOLATED\"],\n [\".oauth_refresh.lock\", \"ISOLATED\"],\n // Defense-in-depth: don't let a user-side file/symlink with the same\n // name as our marker collide with what we write. The marker write\n // logic also lstat-checks before writing (refuses if a non-regular\n // file exists at the path), but excluding it here removes the\n // attack vector entirely.\n [\".github-router-managed\", \"ISOLATED\"],\n [\"statsig\", \"ISOLATED\"],\n [\"cache\", \"ISOLATED\"],\n [\"logs\", \"ISOLATED\"],\n [\"paste-cache\", \"ISOLATED\"],\n [\"jobs\", \"ISOLATED\"],\n [\"daemon\", \"ISOLATED\"],\n [\"daemon.log\", \"ISOLATED\"],\n // SHARED — directories only (see policy doc above)\n [\"projects\", \"SHARED\"],\n [\"sessions\", \"SHARED\"],\n [\"tasks\", \"SHARED\"],\n [\"todos\", \"SHARED\"],\n [\"transcripts\", \"SHARED\"],\n [\"shell-snapshots\", \"SHARED\"],\n // The underscored variant is the historical exclude-list name; some\n // Claude Code versions may still use it. Classify SHARED so either\n // spelling resolves correctly.\n [\"shell_snapshots\", \"SHARED\"],\n [\"plans\", \"SHARED\"],\n [\"file-history\", \"SHARED\"],\n [\"backups\", \"SHARED\"],\n])\n\nfunction policyFor(name: string): MirrorPolicy {\n return CLAUDE_HOME_POLICY.get(name) ?? \"MIRRORED\"\n}\n\n/**\n * Test-only export: lets the test suite assert hard regression guards\n * such as `policyFor(\"agents\") === \"MIRRORED\"` (preventing accidental\n * reclassification that would let `sweepStalePeerAgentMdFiles` delete\n * files in the user's real `~/.claude/agents/`).\n */\nexport const __testing = { policyFor, ensureSharedSymlink }\n\n/**\n * Names with `SHARED` policy, materialized once for iteration in\n * `ensureClaudeConfigMirror`'s post-copy phase.\n */\nconst SHARED_TOPLEVEL_NAMES: ReadonlyArray<string> = Array.from(\n CLAUDE_HOME_POLICY.entries(),\n)\n .filter(([, kind]) => kind === \"SHARED\")\n .map(([name]) => name)\n\n/**\n * Marker file written into the router-owned CLAUDE_CONFIG_DIR so users\n * (and our own future sweeps) can identify that the dir is managed by\n * github-router. Content is informational only; no logic depends on\n * its presence.\n */\nconst MANAGED_MARKER_FILENAME = \".github-router-managed\"\n\n/**\n * Synthetic Console OAuth credential the router writes into its own\n * `CLAUDE_CONFIG_DIR/.credentials.json` so spawned Claude Code (and\n * any teammates it spawns) can authenticate without a real user\n * `/login`.\n *\n * Schema verified verbatim from `claude` v2.1.140 binary, function\n * `guH` (the credentials-save mutation). Fields:\n * - `accessToken` — sent as `Authorization: Bearer ...` to the\n * proxy. Proxy accepts any bearer (per CLAUDE.md \"doesn't enforce\n * auth\").\n * - `refreshToken` — only used by Claude Code's reactive refresh\n * path (function `nH8`), which fires on 401 from upstream. The\n * proxy maintains the no-401 invariant on the Anthropic-shape\n * boundary, so this is never invoked. Synthetic value is fine.\n * - `expiresAt` — far-future (2099-01-01 ms epoch). Sidesteps the\n * proactive refresh path (`R8H(expiresAt)` returns false).\n * - `scopes` — claude-ai-shaped so `tB(scopes)` returns true,\n * making `Hq()` true (full feature surface, not \"inference only\").\n * - `subscriptionType` — `\"max\"`. Pure client-side label\n * (`e7()` / `Zc_()` / `CZ1()`); no server validation since\n * `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` suppresses\n * subscription-validation calls. Picks the most-permissive gating.\n */\nconst SYNTHETIC_CREDENTIAL = {\n claudeAiOauth: {\n accessToken: \"github-router-synthetic\",\n refreshToken: \"github-router-synthetic\",\n expiresAt: 4_070_908_800_000,\n scopes: [\"user:inference\", \"user:profile\"],\n subscriptionType: \"max\",\n rateLimitTier: null,\n clientId: \"github-router\",\n },\n} as const\n\n/**\n * Snapshot-copy the user's `~/.claude/` into the router-owned\n * CLAUDE_CONFIG_DIR (real files, not symlinks — symlinks don't isolate\n * writes), classifying each top-level entry per `CLAUDE_HOME_POLICY`:\n * ISOLATED entries are skipped, MIRRORED entries are copied, and\n * SHARED entries become directory symlinks back to `~/.claude/<X>` so\n * chat history (in `projects/<cwd-hash>/<session-uuid>.jsonl`) and\n * other durable user state flow between proxy and plain-`claude`\n * sessions. Then writes the synthetic `.credentials.json` so spawned\n * Claude Code (and teammates that inherit `CLAUDE_CONFIG_DIR`)\n * authenticate.\n *\n * Idempotent: only re-copies files whose source `mtime` is newer than\n * target; SHARED-symlink creation no-ops when the symlink already\n * points at the right target. Concurrent-safe: `mkdir({recursive:true})`\n * is idempotent; symlinks are created via atomic temp+rename so two\n * parallel github-router-claude startups can't race to EEXIST; the\n * credentials write uses temp-file + atomic rename so Claude Code's\n * `EZ1()` mtime watcher never sees a partial write.\n *\n * Walks with `lstat` (does NOT follow symlinks during traversal — a\n * symlink-into-`/` would otherwise let the walk escape). Symlink leaves\n * in the source tree are skipped during the MIRRORED copy walk (per the\n * symlink-confused-deputy security finding); SHARED symlinks are\n * created on the mirror side only, pointing at predetermined targets\n * inside the user's real `~/.claude/`.\n *\n * Caller is expected to invoke this after `ensurePaths()` and before\n * spawning Claude Code (`launchChild`). The mirror must exist before\n * the child reads it. Currently called from the `claude` subcommand\n * entry point only; `start` and `codex` subcommands don't need it.\n */\nexport async function ensureClaudeConfigMirror(opts: {\n realHome?: string\n} = {}): Promise<void> {\n const realHome = opts.realHome ?? os.homedir()\n const sourceDir = path.join(realHome, \".claude\")\n const targetDir = PATHS.CLAUDE_CONFIG_DIR\n\n // 1. Create our config dir (idempotent, mode 0o700)\n await fs.mkdir(targetDir, { recursive: true, mode: 0o700 })\n await chmodIfPossible(targetDir, 0o700)\n\n // 2. Snapshot-copy from ~/.claude if it exists. Only MIRRORED entries\n // flow through this walk; ISOLATED and SHARED entries are filtered\n // in `mirrorDirRecursive` and handled separately.\n let sourceExists = false\n try {\n const sourceStat = await fs.stat(sourceDir)\n sourceExists = sourceStat.isDirectory()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot stat ${sourceDir}:`, err)\n }\n }\n if (sourceExists) {\n await mirrorDirRecursive(sourceDir, targetDir, \"\")\n }\n\n // 3. Always ensure agents/ exists (even if user has none) so the\n // peer-agent .md emission has a place to write. Empty dir is fine.\n // agents/ is MIRRORED, not SHARED — the proxy writes per-launch\n // `peer-<pid>-*.md` files here and `sweepStalePeerAgentMdFiles`\n // deletes them; routing those operations into the user's real\n // `~/.claude/agents/` would destroy their custom subagent files.\n await fs.mkdir(path.join(targetDir, \"agents\"), { recursive: true })\n\n // 4. Create symlinks for SHARED entries so chat history (and other\n // durable user state) is visible in both proxy and plain-`claude`.\n for (const name of SHARED_TOPLEVEL_NAMES) {\n await ensureSharedSymlink(name, sourceDir, targetDir).catch((err) => {\n consola.debug(\n `ensureClaudeConfigMirror: SHARED symlink for ${name} skipped:`,\n err,\n )\n })\n }\n\n // 5. Write synthetic .credentials.json (only if content differs)\n const credentialsPath = path.join(targetDir, \".credentials.json\")\n const desiredJson = JSON.stringify(SYNTHETIC_CREDENTIAL, null, 2)\n let needsWrite = true\n try {\n const existing = await fs.readFile(credentialsPath, \"utf8\")\n needsWrite = existing.trim() !== desiredJson.trim()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot read existing credentials:`, err)\n }\n }\n if (needsWrite) {\n // Atomic temp-file + rename so EZ1()'s mtime watcher doesn't see\n // a partial write. wx flag ensures we don't clobber a concurrent\n // writer's tempfile.\n const tempPath = `${credentialsPath}.${process.pid}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson + \"\\n\", { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, credentialsPath)\n } catch (err) {\n // EEXIST on the tempfile means another concurrent startup is\n // mid-write. Best-effort: skip — the other writer will produce\n // identical content (deterministic constant blob).\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n consola.debug(\n \"ensureClaudeConfigMirror: concurrent credentials-write detected, skipping\",\n )\n } else {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n }\n }\n await chmodIfPossible(credentialsPath, 0o600)\n\n // 6. Write/refresh marker file. Use lstat (not access) to detect\n // symlinks at the marker path — a previously-mirrored or\n // user-placed symlink could otherwise let our `fs.writeFile`\n // follow through to an arbitrary target. With the symlink-skip\n // policy in `mirrorDirRecursive` this is defense-in-depth, but\n // cheap and definitive.\n const markerPath = path.join(targetDir, MANAGED_MARKER_FILENAME)\n let markerExists = false\n try {\n const markerStat = await fs.lstat(markerPath)\n if (markerStat.isFile()) {\n markerExists = true\n } else {\n // Anything non-regular (symlink, dir, special file) is a red flag —\n // refuse to overwrite, log loudly. The user can investigate.\n consola.warn(\n `ensureClaudeConfigMirror: ${markerPath} exists but is not a regular file (mode=${markerStat.mode.toString(8)}); refusing to overwrite. Inspect and remove manually if safe.`,\n )\n markerExists = true\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot lstat marker:`, err)\n markerExists = true\n }\n }\n if (!markerExists) {\n const body = `Managed by github-router. Created ${new Date().toISOString()}. Safe to delete (will be recreated).\\n`\n // wx flag (O_CREAT | O_EXCL) refuses to clobber an existing\n // file or symlink (POSIX O_EXCL behavior) — additional protection\n // against the marker-symlink confused-deputy vector.\n await fs\n .writeFile(markerPath, body, { mode: 0o600, flag: \"wx\" })\n .catch((err) => {\n consola.debug(`ensureClaudeConfigMirror: marker write skipped:`, err)\n })\n }\n}\n\n/**\n * Recursive snapshot-copy helper for `ensureClaudeConfigMirror`. Walks\n * `sourceDir/relPath` and mirrors each entry into `targetDir/relPath`.\n * - Top-level entries are dispatched on `policyFor(name)`:\n * - `ISOLATED` → skipped entirely (no presence in mirror).\n * - `SHARED` → skipped from the copy walk; handled by\n * `ensureSharedSymlink` in the post-copy phase.\n * - `MIRRORED` → copied as today.\n * - Symlinks are skipped (not recreated) so the walk never follows out\n * of `sourceDir` and we don't reintroduce a confused-deputy vector.\n * - Files copy only if source mtime > target mtime (idempotent).\n */\nasync function mirrorDirRecursive(\n sourceDir: string,\n targetDir: string,\n relPath: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, relPath)\n let entries: Array<string>\n try {\n entries = await fs.readdir(sourcePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n consola.debug(`mirrorDirRecursive: cannot readdir ${sourcePath}:`, err)\n return\n }\n for (const name of entries) {\n // Policy dispatch at top-level only. Sub-paths within MIRRORED\n // dirs always cascade as MIRRORED.\n if (relPath === \"\") {\n const policy = policyFor(name)\n if (policy === \"ISOLATED\" || policy === \"SHARED\") continue\n }\n const childRel = relPath === \"\" ? name : path.join(relPath, name)\n const childSource = path.join(sourceDir, childRel)\n const childTarget = path.join(targetDir, childRel)\n let stats: Awaited<ReturnType<typeof fs.lstat>>\n try {\n stats = await fs.lstat(childSource)\n } catch (err) {\n consola.debug(`mirrorDirRecursive: cannot lstat ${childSource}:`, err)\n continue\n }\n if (stats.isSymbolicLink()) {\n // Skip symlinks during mirror copy. gemini-critic security finding:\n // recreating user symlinks in our mirror creates a confused-deputy\n // vector — a previously prompt-injected process could place\n // `~/.claude/<X>` → `/some/sensitive/file`, our walker would mirror\n // it, and any subsequent write to `<mirror>/<X>` (by us or by\n // Claude Code) would follow the symlink and overwrite the target.\n // Snapshot-copy semantics make symlink preservation moot anyway:\n // a snapshot is a point-in-time content copy, and a symlink\n // recreated in the mirror points at exactly the same target as\n // the original would have — the user-side symlink is sufficient.\n // If a user has a legitimate need for a symlink to be visible\n // through the proxy session, they can create the equivalent\n // symlink in their `~/.claude/` directly and it'll be reachable\n // — they just won't see it in our mirror dir.\n consola.debug(`mirrorDirRecursive: skipping symlink ${childSource} (security policy)`)\n continue\n }\n if (stats.isDirectory()) {\n await fs.mkdir(childTarget, { recursive: true })\n await mirrorDirRecursive(sourceDir, targetDir, childRel)\n continue\n }\n if (stats.isFile()) {\n // mtime-based skip — only copy if source is newer than target.\n let needsCopy = true\n try {\n const targetStat = await fs.lstat(childTarget)\n if (targetStat.isFile() && targetStat.mtimeMs >= stats.mtimeMs) {\n needsCopy = false\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`mirrorDirRecursive: lstat target ${childTarget}:`, err)\n }\n }\n if (!needsCopy) continue\n try {\n await fs.copyFile(childSource, childTarget, fs.constants.COPYFILE_FICLONE)\n } catch (err) {\n consola.debug(`mirrorDirRecursive: copy ${childSource} → ${childTarget}:`, err)\n }\n continue\n }\n // Skip other inode types (sockets, devices, fifos) silently.\n }\n}\n\n/**\n * Create or refresh a directory symlink `<mirrorDir>/<name>` →\n * `<sourceDir>/<name>` (i.e. `~/.local/share/github-router/claude-config/<X>`\n * → `~/.claude/<X>`). Idempotent and concurrent-safe.\n *\n * Behavior depending on what's already at `<mirrorDir>/<name>`:\n * - Symlink with the correct target → no-op.\n * - Symlink with the wrong target → replace atomically.\n * - Empty real directory (legacy mirror leftover with no proxy-session\n * writes accumulated yet) → `rmdir` and replace with the symlink.\n * Safe by definition: `fs.rmdir` only succeeds on empty dirs (POSIX),\n * so there is nothing to lose. Smooths the upgrade path for users\n * whose legacy mirror dirs were never written to.\n * - Non-empty real directory or regular file → loud-warn and skip.\n * Auto-deleting would destroy proxy-session writes from the prior\n * version. The user is told the exact path and remediation.\n * - ENOENT → create symlink atomically.\n *\n * Atomic-creation: symlinks are first written at a unique side-path\n * (`<mirrorDir>/<name>.tmp.<pid>.<8 hex>`) and then `fs.rename()`d into\n * place. POSIX `rename` is atomic and replaces an existing symlink in\n * a single step, so two concurrent `github-router claude` startups can't\n * race to `EEXIST` — the loser's rename just overwrites the winner's\n * symlink with an identical one. Gemini-critic 3-lab-review finding.\n *\n * Pre-creates `~/.claude/<name>/` as a real directory if missing so\n * Claude Code's writes through the symlink don't fail with ENOENT.\n */\nasync function ensureSharedSymlink(\n name: string,\n sourceDir: string,\n mirrorDir: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, name)\n const mirrorPath = path.join(mirrorDir, name)\n\n // 1. Ensure the source directory exists. Without this, Claude Code's\n // writes through the symlink (e.g. `projects/<hash>/foo.jsonl`)\n // fail with ENOENT on the parent dir.\n try {\n await fs.mkdir(sourcePath, { recursive: true })\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the symlink and rename catches below):\n // if the source dir cannot be created (e.g. a stray regular file\n // sitting at `~/.claude/projects`, perms blocking mkdir on a\n // corp-managed Windows box, OneDrive cloud-only reparse point),\n // ensureSharedSymlink returns without creating a junction. The\n // spawned Claude Code child then writes to the REAL `~/.claude`\n // while the proxy reads from the mirror — exactly the split-brain\n // pattern this whole function exists to prevent. Silent debug-log\n // hid this from us once already; warn so the user sees the cause.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot mkdir source ${sourcePath}:`,\n err,\n )\n return\n }\n\n // 2. Inspect the mirror-side slot.\n let existing: Awaited<ReturnType<typeof fs.lstat>> | null = null\n try {\n existing = await fs.lstat(mirrorPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the other fs catches in this function):\n // ENOENT is the only expected-and-benign failure mode here\n // (slot doesn't exist yet — falls through to create). Any other\n // lstat failure (EACCES, ELOOP, EIO from a sketchy reparse\n // point) means we bail without creating the junction, which\n // silently leaves the proxy and child diverged. A visible warn\n // surfaces the root cause instead of a mysteriously missing\n // junction.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot lstat ${mirrorPath}:`,\n err,\n )\n return\n }\n }\n\n if (existing?.isSymbolicLink()) {\n // Resolve both sides to their canonical absolute paths and compare.\n // We use `fs.realpath` rather than the raw `fs.readlink()` output\n // because Windows junctions resolve via readlink to `\\\\?\\`-prefixed\n // device-namespace paths (e.g. `\\\\?\\C:\\Users\\foo\\.claude\\projects`)\n // while we wrote the plain absolute `sourcePath` (e.g.\n // `C:\\Users\\foo\\.claude\\projects`) with `fs.symlink`. A literal\n // `===` on the raw readlink output never matched on Windows, so\n // the fast path silently failed and every startup tore down +\n // recreated all 9 SHARED junctions — masked locally because NTFS\n // File System Tunneling forges the creation timestamp for a name\n // deleted and recreated within 15 s (the per-startup churn was\n // real, the ctime-stable assertion was a false negative). The\n // realpath comparison canonicalizes both forms to the same string\n // on POSIX and Windows alike, and as a bonus handles drive-letter\n // casing / trailing-slash differences too. The extra two syscalls\n // per slot are negligible at proxy startup (runs once per launch).\n //\n // CRITICAL: sourceReal and currentReal are NOT treated symmetrically.\n // If `sourceReal` is null (we just mkdir'd it above, but realpath\n // failed — OneDrive cloud-only reparse point, EACCES on parent,\n // EXDEV mount oddity), we WARN AND RETURN rather than fall through.\n // Falling through would do unlink+symlink+rename with the same\n // failing realpath next launch — silent every-startup churn, the\n // exact bug class round-3 G2 fixed in a different code path.\n // `currentReal === null` is benign (broken/wrong slot — replace).\n const sourceReal = await fs.realpath(sourcePath).catch(() => null)\n if (sourceReal === null) {\n consola.warn(\n `ensureSharedSymlink(${name}): cannot resolve source ${sourcePath} ` +\n `— skipping junction creation to avoid silent every-startup churn. ` +\n `Inspect the source dir's permissions / OneDrive sync state and re-launch.`,\n )\n return\n }\n const currentReal = await fs.realpath(mirrorPath).catch(() => null)\n if (currentReal !== null && currentReal === sourceReal) {\n return\n }\n // Wrong target (or unresolvable mirror) — fall through to the\n // atomic-rename replace path.\n } else if (existing?.isDirectory()) {\n // Legacy real directory at the slot. Try `fs.rmdir` — on POSIX it\n // succeeds ONLY if the directory is empty, so there's nothing to\n // lose. If it's non-empty (ENOTEMPTY) or any other failure occurs,\n // fall back to the warn-and-skip path so we never auto-clobber\n // user data.\n try {\n await fs.rmdir(mirrorPath)\n // Empty dir reaped — fall through to the atomic-rename create path.\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a non-empty real directory ` +\n `from an older github-router version; refusing to clobber. ` +\n `If you want chat-history continuity for \"${name}\", move its ` +\n `contents into ${sourcePath}/ then delete ${mirrorPath}; the ` +\n `mirror will create a symlink (junction on Windows) on next launch. ` +\n `(rmdir error: ${(err as NodeJS.ErrnoException).code ?? \"unknown\"})`,\n )\n return\n }\n } else if (existing) {\n // Regular file (or special inode like a socket) — never auto-clobber.\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a regular file at a ` +\n `SHARED symlink slot; refusing to clobber. Inspect and remove ` +\n `manually if safe; the mirror will create a symlink on next launch.`,\n )\n return\n }\n\n // 3. Atomic-rename creation: symlink to a unique temp path, then\n // rename over the slot. `fs.rename` replaces existing symlinks\n // atomically on POSIX and is safe against concurrent racers.\n // On Windows, MoveFileEx with MOVEFILE_REPLACE_EXISTING does NOT\n // replace an existing directory or junction destination\n // (npm/cli#9021), so when the slot already holds a wrong-target\n // junction we must explicitly unlink it first. The sub-millisecond\n // window of no-link is acceptable: ensureClaudeConfigMirror is\n // idempotent under concurrency and only runs at proxy startup,\n // before any spawned Claude Code child has been launched.\n const tempPath = `${mirrorPath}.tmp.${process.pid}.${randomBytes(4).toString(\"hex\")}`\n try {\n await fs.symlink(\n sourcePath,\n tempPath,\n process.platform === \"win32\" ? \"junction\" : \"dir\",\n )\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\" rule:\n // the rule applies to ALL fs catches in this function, not just the\n // rename one. The temp path is per-pid + 8-hex random so EEXIST is\n // essentially impossible — any failure here (EPERM on Windows\n // without DevMode, EXDEV cross-volume, ENOSPC, …) is a real\n // operational problem the user needs to see.\n consola.warn(\n `ensureSharedSymlink(${name}): symlink ${tempPath} failed:`,\n err,\n )\n return\n }\n if (process.platform === \"win32\" && existing?.isSymbolicLink()) {\n // Windows-only: clear the wrong-target junction so the rename\n // below can land. Best-effort — if a concurrent racer already\n // unlinked it, the rename succeeds as a CREATE; if a concurrent\n // racer already replaced it with a fresh junction, the rename\n // hits the catch below and we surface a warn.\n await fs.unlink(mirrorPath).catch(() => {})\n }\n try {\n await fs.rename(tempPath, mirrorPath)\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the fs.symlink catch above): a silent\n // debug log here previously hid the Windows rename-replace bug\n // (junction-over-junction MoveFileEx EPERM). Post-fix, rename\n // failures should be rare and visible.\n consola.warn(\n `ensureSharedSymlink(${name}): rename ${tempPath} → ${mirrorPath} failed:`,\n err,\n )\n await fs.unlink(tempPath).catch(() => {})\n }\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n\nasync function chmodIfPossible(target: string, mode: number): Promise<void> {\n if (process.platform === \"win32\") return // Windows chmod is no-op-ish\n try {\n await fs.chmod(target, mode)\n } catch (err) {\n consola.debug(`chmod ${target} ${mode.toString(8)} failed:`, err)\n }\n}\n\n/**\n * Write a runtime tempfile securely.\n *\n * - Mode `0o600` so other local users (multi-tenant boxes, shared\n * dev containers) can't read the per-launch nonce or runtime URL.\n * - `flag: \"wx\"` (O_CREAT | O_EXCL | O_WRONLY) refuses to overwrite\n * an existing path. POSIX open(2) with O_EXCL also rejects\n * pre-placed symlinks, killing the symlink-clobber attack vector.\n * - The caller's responsibility to pick a path NOT yet in use.\n * We intentionally do NOT pre-unlink: an `lstat` + `unlink` +\n * `open(O_EXCL)` sequence still has a TOCTOU window where an\n * attacker can drop a symlink between unlink and open. Letting\n * `wx` fail is the safer behavior — surfaces the conflict\n * instead of silently following.\n */\nexport async function writeRuntimeFileSecure(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, { mode: 0o600, flag: \"wx\" })\n}\n\n/**\n * Sweep stale runtime tempfiles. Removes files whose embedded PID is no\n * longer a live process. A proxy crash (`kill -9`, OS reboot) leaves\n * orphans that would otherwise accumulate forever — and worse, a stale\n * config pointing at a now-recycled port could route MCP traffic to\n * whatever process bound that port next.\n *\n * Naming convention: `peer-mcp-<pid>.json` and `peer-agents-<pid>.json`.\n * Files not matching either pattern are left alone — this directory\n * is shared with future runtime artifacts.\n *\n * We deliberately do NOT age-prune files whose PID is alive. A\n * legitimately long-running proxy can have a tempfile older than any\n * arbitrary threshold; deleting it out from under the live process\n * breaks the spawned Claude Code child's MCP/agent wiring with no clean\n * recovery. PID-wraparound risk is mitigated by (a) PID reuse on Linux\n * being slow under typical loads, and (b) the file is only consulted by\n * github-router itself — an unrelated process that inherits the PID\n * never reads it.\n */\nexport async function sweepStaleRuntimeFiles(): Promise<void> {\n const dir = PATHS.CLAUDE_RUNTIME_DIR\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n\n for (const name of entries) {\n // Match both legacy `peer-mcp-<pid>.json` and current\n // `peer-mcp-<pid>-<rand>.json` filenames so we can clean up either.\n const match = /^peer-(?:mcp|agents)-(\\d+)(?:-[0-9a-f]+)?\\.json$/.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n const filePath = path.join(dir, name)\n\n if (isPidAlive(pid)) continue\n\n await fs.unlink(filePath).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\nfunction isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n // signal 0 = check existence without delivering a signal. EPERM\n // means the process exists but we can't signal it (which is still\n // \"alive\" for our purposes); ESRCH means it's gone.\n process.kill(pid, 0)\n return true\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"EPERM\") return true\n return false\n }\n}\n\n/**\n * Sweep stale peer-* subagent .md files from the router-owned\n * `CLAUDE_CONFIG_DIR/agents/`. Phase 2.5 writes one .md per peer agent\n * into Claude Code's agents directory (now our config dir's `agents/`\n * subdir, since `getClaudeCodeEnvVars` points `CLAUDE_CONFIG_DIR` at\n * `PATHS.CLAUDE_CONFIG_DIR`) so they appear in Claude Code's Task\n * `subagent_type` enum. Files are named `peer-<pid>-<rand>-<agentName>.md`\n * so this sweep can drop orphans from crashed prior proxy sessions\n * without touching the user's own .md files (which were copied into\n * the same dir during `ensureClaudeConfigMirror`).\n *\n * Same liveness rule as `sweepStaleRuntimeFiles`: only delete when the\n * file's embedded PID is no longer alive. Live PIDs keep their files —\n * a long-running proxy doesn't lose its agent registrations.\n *\n * Regex tightening (Phase 2.6, codex-critic + gemini-critic 2-lab finding):\n * the original sweep regex `^peer-(\\d+)(?:-[0-9a-f]+)?-.+\\.md$` was too\n * permissive — a user-authored `peer-12345-meeting-notes.md` matches\n * (`12345` = \"PID\", `-meeting-notes` = trailing `.+`) and would be\n * silently unlinked when 12345 happens to be a dead PID (overwhelmingly\n * likely). Tightened to require BOTH the 8-hex-char random suffix AND\n * an exact-match persona name suffix, eliminating the risk for any\n * realistic user filename.\n */\nexport async function sweepStalePeerAgentMdFiles(): Promise<void> {\n const dir = path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = PEER_AGENT_MD_FILENAME.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs.unlink(path.join(dir, name)).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\n/**\n * Strict regex matching only files this proxy writes:\n * peer-<pid>-<8 hex>-<exact persona/coordinator name>.md\n * The persona-name allowlist is the load-bearing protection against\n * deleting user files. Update this list whenever a new persona is added\n * to `PERSONAS_READ` / `PERSONAS_WRITE` in `peer-mcp-personas.ts` or a\n * new coordinator-style agent is added in `codex-mcp-config.ts`.\n */\nconst PEER_AGENT_MD_FILENAME =\n /^peer-(\\d+)-[0-9a-f]{8}-(?:codex-critic|codex-reviewer|gemini-critic|codex-implementer|peer-review-coordinator)\\.md$/\n\n/**\n * Strict regex matching only per-launch claude-config mirror dirs this\n * proxy creates: `<pid>-<8 hex>`. Anchored to the entire entry name so\n * user-authored siblings under `<appDir>/claude-config/` (if any) are\n * untouchable. The PID prefix is what `sweepStaleClaudeConfigMirrors`\n * keys off; the 8-hex random suffix matches `randomBytes(4)` exactly\n * (no `?` — files created by a different shape are not ours).\n */\nconst CLAUDE_CONFIG_MIRROR_DIR = /^(\\d+)-[0-9a-f]{8}$/\n\n/**\n * Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n * crashed prior proxy sessions. Symmetric to `sweepStalePeerAgentMdFiles`\n * — same liveness rule (only delete when the embedded PID is dead),\n * same strict regex (the dir-name allowlist is the load-bearing\n * protection against deleting user-authored siblings).\n *\n * Scans `<appDir>/claude-config/` (the parent of the per-launch dirs).\n * Each entry whose name matches `<pid>-<8 hex>` AND whose PID is no\n * longer alive is removed recursively. `fs.rm({recursive: true})`\n * walks the tree calling `unlink` on symlinks/junctions rather than\n * following them, so the SHARED junctions back to `~/.claude/<X>`\n * are removed without touching their targets.\n *\n * Tolerates missing parent dir (first-ever launch, or user wiped it).\n */\nexport async function sweepStaleClaudeConfigMirrors(): Promise<void> {\n const parent = path.join(appDir(), \"claude-config\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(parent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = CLAUDE_CONFIG_MIRROR_DIR.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs\n .rm(path.join(parent, name), { recursive: true, force: true })\n .catch((err) => {\n // Best-effort: stale-dir cleanup must never block startup.\n // Common failure modes (worth surviving silently): an EBUSY/EPERM\n // on Windows if a leftover handle is still open, or a stray\n // root-owned file inside the dir from a previous run with\n // different permissions.\n consola.debug(\n `sweepStaleClaudeConfigMirrors: cannot rm ${name}:`,\n err,\n )\n })\n }\n}\n\n/**\n * Remove THIS launch's per-launch CLAUDE_CONFIG_DIR on shutdown.\n * Best-effort: a failure here must not block process exit (the caller\n * wraps this in a `.catch`-equivalent via `launchChild`'s onShutdown\n * try/catch). Symmetric to `writePeerMcpRuntimeFiles`'s `cleanup()`:\n * we own this dir for the lifetime of the proxy, so removing it on\n * normal shutdown is correct; the boot-time sweep handles the\n * abnormal-exit case.\n *\n * `fs.rm({recursive: true})` removes SHARED junctions via unlink\n * (does NOT follow them into the user's real `~/.claude/<X>`).\n */\nexport async function removeOwnClaudeConfigMirror(): Promise<void> {\n const dir = PATHS.CLAUDE_CONFIG_DIR\n await fs.rm(dir, { recursive: true, force: true }).catch((err) => {\n consola.debug(`removeOwnClaudeConfigMirror: rm ${dir} skipped:`, err)\n })\n}\n","import { randomBytes, randomUUID } from \"node:crypto\"\n\nimport type { ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n accountType: string\n copilotApiUrl?: string\n models?: ModelsResponse\n vsCodeVersion?: string\n copilotVersion?: string\n\n manualApprove: boolean\n rateLimitWait: boolean\n showToken: boolean\n extendedBetas: boolean\n\n // Rate limiting configuration\n rateLimitSeconds?: number\n lastRequestTimestamp?: number\n\n // Persistent session identifiers to match VS Code fingerprint\n sessionId: string\n machineId: string\n\n /**\n * Per-launch nonce for the loopback `/mcp` endpoint. Set by the\n * `claude` subcommand after `setupAndServe` and before spawning\n * Claude Code; the spawned MCP client reads it from the\n * `--mcp-config` tempfile and presents it as `Authorization: Bearer`.\n * When unset, `/mcp` rejects all requests — closes the\n * loopback-no-auth gap (DNS rebinding, malicious browser-ext\n * native messaging, sibling-process probe).\n */\n peerMcpNonce?: string\n}\n\nexport const state: State = {\n accountType: \"enterprise\",\n manualApprove: false,\n rateLimitWait: false,\n showToken: false,\n extendedBetas: false,\n sessionId: randomUUID(),\n machineId: randomBytes(32).toString(\"hex\"),\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst DEFAULT_COPILOT_VERSION = \"0.43.2026033101\"\n\nexport function copilotVersion(state: State): string {\n return state.copilotVersion ?? DEFAULT_COPILOT_VERSION\n}\n\nconst API_VERSION = \"2026-01-09\"\n\nexport const copilotBaseUrl = (state: State) =>\n state.copilotApiUrl ?? \"https://api.githubcopilot.com\"\nexport const copilotHeaders = (\n state: State,\n vision: boolean = false,\n integrationId: string = \"vscode-chat\",\n) => {\n const version = copilotVersion(state)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": integrationId,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": `copilot-chat/${version}`,\n \"user-agent\": `GitHubCopilotChat/${version}`,\n \"openai-intent\": \"conversation-panel\",\n \"x-interaction-type\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n \"VScode-SessionId\": state.sessionId,\n \"VScode-MachineId\": state.machineId,\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL =\n process.env.GITHUB_API_URL ?? \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": `copilot-chat/${copilotVersion(state)}`,\n \"user-agent\": `GitHubCopilotChat/${copilotVersion(state)}`,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n consola.error(`Error occurred at ${c.req.path}:`, error)\n\n if (error instanceof HTTPError) {\n const errorText = await error.response.text().catch(() => \"\")\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = undefined\n }\n\n // Map upstream context-overflow errors (413, or 400 with a known\n // overflow substring) to Anthropic's \"prompt is too long\" 400 shape so\n // Claude Code triggers self-compaction instead of bubbling the error.\n // Note: a live probe of an oversized prompt against Copilot returned\n // 200 with stop_reason:\"refusal\" rather than 413/400 — this guard is\n // defensive for the documented Anthropic contract, not load-bearing.\n if (isContextOverflow(error.response.status, errorJson, errorText)) {\n const upstream = resolveErrorMessage(errorJson, errorText)\n consola.error(\"HTTP error (mapped to overflow):\", errorJson ?? errorText)\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: `prompt is too long: ${upstream}`,\n },\n },\n 400,\n )\n }\n\n // Remap upstream 401 to 503 — maintain the no-401 invariant on the\n // Anthropic-shape boundary. Claude Code's reactive refresh path\n // (function `SZ1` → `D3(0,true,...)` in v2.1.140 binary) fires on\n // any 401 from upstream and attempts to refresh the OAuth token.\n // Spawned-via-proxy sessions use a synthetic credential\n // (`ensureClaudeConfigMirror`'s SYNTHETIC_CREDENTIAL); refreshing\n // it would fail and degrade the session. Mapping 401 → 503 lets\n // the upstream message still reach the user while side-stepping\n // the refresh path. 503 maps to Anthropic's \"overloaded_error\"\n // type — semantically reasonable for \"proxy got an upstream\n // failure, retry later\".\n const responseStatus =\n error.response.status === 401 ? 503 : error.response.status\n\n // Forward upstream Anthropic-format errors as-is (with remapped status)\n if (isAnthropicError(errorJson)) {\n consola.error(\"HTTP error:\", errorJson)\n return c.json(errorJson, responseStatus as ContentfulStatusCode)\n }\n\n const message = resolveErrorMessage(errorJson, errorText)\n consola.error(\"HTTP error:\", errorJson ?? errorText)\n return c.json(\n {\n type: \"error\",\n error: {\n type: resolveErrorType(responseStatus),\n message,\n },\n },\n responseStatus as ContentfulStatusCode,\n )\n }\n\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"api_error\",\n message: error instanceof Error ? error.message : String(error),\n },\n },\n 500,\n )\n}\n\n// Extracts error message from { message } or { error: { message } } payloads.\nfunction resolveErrorMessage(errorJson: unknown, fallback: string): string {\n if (typeof errorJson !== \"object\" || errorJson === null) return fallback\n\n const errorRecord = errorJson as Record<string, unknown>\n if (errorRecord.message !== undefined) return String(errorRecord.message)\n\n if (typeof errorRecord.error === \"object\" && errorRecord.error !== null) {\n const nestedRecord = errorRecord.error as Record<string, unknown>\n if (nestedRecord.message !== undefined) return String(nestedRecord.message)\n }\n\n return fallback\n}\n\n/**\n * Check if a parsed JSON body is already in Anthropic error format:\n * { type: \"error\", error: { type: \"...\", message: \"...\" } }\n */\nfunction isAnthropicError(json: unknown): boolean {\n if (typeof json !== \"object\" || json === null) return false\n const record = json as Record<string, unknown>\n if (record.type !== \"error\") return false\n if (typeof record.error !== \"object\" || record.error === null) return false\n const inner = record.error as Record<string, unknown>\n return typeof inner.type === \"string\" && typeof inner.message === \"string\"\n}\n\nconst CONTEXT_OVERFLOW_SUBSTRINGS = [\n \"prompt is too long\",\n \"context_length_exceeded\",\n \"context length exceeded\",\n \"input is too long\",\n \"maximum context length\",\n \"too many tokens\",\n]\n\n/**\n * Detect upstream context-overflow errors so we can remap them to a 400\n * \"prompt is too long\" shape that triggers Claude Code self-compaction.\n *\n * Always remaps 413 (treated as a hard payload-size signal regardless of\n * body wording). Remaps 400 only when the error text contains one of the\n * known overflow substrings — a regular 400 (e.g. \"model not found\") must\n * NOT remap.\n */\nexport function isContextOverflow(\n status: number,\n errorJson: unknown,\n errorText: string,\n): boolean {\n if (status === 413) return true\n if (status !== 400) return false\n\n const haystack = (\n errorText +\n \" \" +\n (typeof errorJson === \"object\" && errorJson !== null\n ? JSON.stringify(errorJson)\n : \"\")\n ).toLowerCase()\n\n return CONTEXT_OVERFLOW_SUBSTRINGS.some((s) => haystack.includes(s))\n}\n\n/**\n * Map HTTP status to Anthropic error type.\n *\n * Note: a 401 from upstream is remapped to 503 in `forwardError` BEFORE\n * this function is called (no-401 invariant — see comment there). The\n * 401 → \"authentication_error\" mapping below is preserved for\n * defensive coverage in case any code path calls `resolveErrorType`\n * directly with an unsanitized status.\n */\nfunction resolveErrorType(status: number): string {\n if (status === 400) return \"invalid_request_error\"\n if (status === 401) return \"authentication_error\"\n if (status === 403) return \"permission_error\"\n if (status === 404) return \"not_found_error\"\n if (status === 429) return \"rate_limit_error\"\n if (status === 503) return \"overloaded_error\"\n if (status === 529) return \"overloaded_error\"\n return \"api_error\"\n}\n","import consola from \"consola\"\n\nimport { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Allowlist of hosts the router will trust as the Copilot API base URL.\n * Anything else returned in `endpoints.api` (e.g. via a tampered or\n * misconfigured token-exchange response) is rejected — otherwise a\n * malicious value would receive the long-lived GitHub PAT we send to\n * `/mcp` for web search (see `src/services/copilot/web-search.ts`).\n */\nconst COPILOT_HOST_ALLOWLIST = [\n \"api.githubcopilot.com\",\n \"api.individual.githubcopilot.com\",\n \"api.business.githubcopilot.com\",\n \"api.enterprise.githubcopilot.com\",\n]\n\nfunction isAllowedCopilotHost(rawUrl: string): boolean {\n let parsed: URL\n try {\n parsed = new URL(rawUrl)\n } catch {\n return false\n }\n if (parsed.protocol !== \"https:\") return false\n return COPILOT_HOST_ALLOWLIST.includes(parsed.hostname)\n}\n\nexport const getCopilotToken = async () => {\n const response = await fetch(\n `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`,\n {\n headers: githubHeaders(state),\n },\n )\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n const data = (await response.json()) as GetCopilotTokenResponse\n\n // Use the API base URL from the token response if available, matching\n // how VS Code determines the CAPI endpoint dynamically — but only when\n // it points at a github-controlled host (see allowlist above).\n // We deliberately do NOT clobber an existing `state.copilotApiUrl` in\n // the disallowed branch: when the user sets `COPILOT_API_URL` themselves\n // (e.g. for local testing or a CI mock), that's an explicit opt-in and\n // a different threat model than a tampered token-exchange response.\n // Allowlist-failing token-response values are simply ignored.\n if (data.endpoints?.api) {\n if (isAllowedCopilotHost(data.endpoints.api)) {\n state.copilotApiUrl = data.endpoints.api\n } else {\n consola.warn(\n `Refusing to honor Copilot API endpoint \"${data.endpoints.api}\" from ` +\n `the token-exchange response — not in allowlist ` +\n `(${COPILOT_HOST_ALLOWLIST.join(\", \")}). ` +\n (state.copilotApiUrl\n ? `Keeping existing override \"${state.copilotApiUrl}\".`\n : `Falling back to the default api.githubcopilot.com.`),\n )\n }\n }\n\n return data\n}\n\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n endpoints?: {\n api?: string\n proxy?: string\n telemetry?: string\n \"origin-tracker\"?: string\n }\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise<DeviceCodeResponse> {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n max_non_streaming_output_tokens?: number\n vision?: {\n max_prompt_image_size?: number\n max_prompt_images?: number\n supported_media_types?: string[]\n }\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n streaming?: boolean\n vision?: boolean\n structured_outputs?: boolean\n adaptive_thinking?: boolean\n max_thinking_budget?: number\n min_thinking_budget?: number\n reasoning_effort?: Array<string>\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n supported_endpoints?: Array<string>\n requestHeaders?: Record<string, string>\n policy?: {\n state: string\n terms: string\n }\n billing?: {\n is_premium: boolean\n multiplier: number\n restricted_to?: string[]\n }\n is_chat_default?: boolean\n is_chat_fallback?: boolean\n model_picker_category?: string\n info_messages?: Array<{ code: string; message: string }>\n}\n","const FALLBACK = \"0.43.2026033101\"\n\ninterface MarketplaceResult {\n results: Array<{\n extensions: Array<{\n versions: Array<{ version: string }>\n }>\n }>\n}\n\nexport async function getCopilotChatVersion(): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json;api-version=7.1-preview.1\",\n },\n body: JSON.stringify({\n filters: [\n {\n criteria: [{ filterType: 7, value: \"GitHub.copilot-chat\" }],\n },\n ],\n flags: 914,\n }),\n signal: controller.signal,\n },\n )\n\n if (!response.ok) return FALLBACK\n\n const data = (await response.json()) as MarketplaceResult\n const version =\n data?.results?.[0]?.extensions?.[0]?.versions?.[0]?.version\n\n return version ?? FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getCopilotChatVersion } from \"~/services/get-copilot-version\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\n/**\n * Beta prefixes VS Code Copilot Chat v0.43 actually sends.\n * Default mode — makes proxy traffic indistinguishable from VS Code.\n */\nconst VSCODE_BETA_PREFIXES = [\n \"interleaved-thinking-\",\n \"context-management-\",\n \"advanced-tool-use-\",\n]\n\n/**\n * Extended beta prefixes for Claude CLI compatibility.\n * Enabled via --extended-betas flag. Includes all betas confirmed\n * to work with the Copilot API.\n *\n * Notably absent (Copilot 400s on these — verified live):\n * context-1m-, skills-, files-api-, code-execution-, output-128k-,\n * advisor-tool- (see EXPLICITLY_STRIPPED_BETA_PREFIXES below).\n * 1M context is unlocked by selecting `claude-opus-4.7-1m-internal`\n * as the model id, not via a beta header.\n *\n * Empirical verification (2026-05-11 against api.enterprise.githubcopilot.com):\n * task-budgets-2026-03-13 → 200 ACCEPTED (cost-ceiling leverage)\n * token-efficient-tools-2026-03-28 → 200 ACCEPTED (per-tool token saving)\n * summarize-connector-text-2026-03-13 → 200 (Anthropic-internal feature flag,\n * won't fire for non-ant users; allowlisted defensively for ant edge case)\n * afk-mode-2026-01-31 → 200 (Anthropic-internal feature flag)\n * cli-internal-2026-02-09 → 200 (USER_TYPE=ant only)\n * oauth-2025-04-20 → 200 (Files-API path; Files-API itself\n * is not supportable via Copilot, but the header alone is harmless)\n * prompt-caching-scope-2026-01-05 → 200 even with body cache_control.scope\n * stripped (already covered by `prompt-caching-` prefix above)\n */\nconst EXTENDED_BETA_PREFIXES = [\n ...VSCODE_BETA_PREFIXES,\n \"claude-code-\",\n \"effort-\",\n \"prompt-caching-\",\n \"computer-use-\",\n \"pdfs-\",\n \"max-tokens-\",\n \"token-counting-\",\n \"compact-\",\n \"structured-outputs-\",\n \"fast-mode-\",\n \"mcp-client-\",\n \"mcp-servers-\",\n \"redact-thinking-\",\n \"web-search-\",\n // Empirically accepted by Copilot, sent by Claude Code v2.1.138+\n \"task-budgets-\",\n \"token-efficient-tools-\",\n // Anthropic-internal feature flags (won't reach proxy from non-ant users\n // due to Bun build-time dead-code elimination, but allowlisted so the rare\n // ant-user / managed-deployment case flows cleanly).\n \"summarize-connector-text-\",\n \"afk-mode-\",\n \"cli-internal-\",\n \"oauth-\",\n]\n\n/**\n * Beta prefixes the proxy explicitly STRIPS even from the extended\n * allowlist (and even if a future leverage mode broadens the allowlist\n * further). Defensive layer: today's allowlist-only filter would already\n * drop these because they're not in any allowlist, but keeping an\n * explicit deny-list catches future changes that broaden allow rules\n * (e.g. a hypothetical pattern-based mode that lets `claude-*` through).\n *\n * Empirical (2026-05-11): Copilot returns HTTP 400\n * `unsupported beta header(s): advisor-tool-2026-03-01`\n * on every request that includes `advisor-tool-`. Stripping it is the\n * difference between a working request (no ADVISOR semantics) and a\n * fully-failed request. Document upstream limitation in CLAUDE.md.\n */\nconst EXPLICITLY_STRIPPED_BETA_PREFIXES = [\n \"advisor-tool-\",\n] as const\n\n/**\n * Filter an `anthropic-beta` header value, keeping only beta flags\n * in the active whitelist AND not in the explicit-strip list.\n * Uses extended prefixes when --extended-betas is enabled, VS Code-only\n * prefixes otherwise. Returns the filtered comma-separated string,\n * or undefined if nothing remains.\n */\nexport function filterBetaHeader(value: string): string | undefined {\n const prefixes = state.extendedBetas\n ? EXTENDED_BETA_PREFIXES\n : VSCODE_BETA_PREFIXES\n const filtered = value\n .split(\",\")\n .map((v) => v.trim())\n .filter(\n (v) =>\n v\n && prefixes.some((prefix) => v.startsWith(prefix))\n && !EXPLICITLY_STRIPPED_BETA_PREFIXES.some((p) => v.startsWith(p)),\n )\n .join(\",\")\n return filtered || undefined\n}\n\n/**\n * Normalize a model ID for fuzzy comparison: lowercase, replace dots with\n * dashes, insert dash at letter→digit boundaries, and collapse repeated\n * dashes. E.g. \"gpt5.3-codex\" → \"gpt-5-3-codex\", \"GPT-5.3-Codex\" → \"gpt-5-3-codex\".\n */\nexport function normalizeModelId(id: string): string {\n return id\n .toLowerCase()\n .replace(/\\./g, \"-\")\n .replace(/([a-z])(\\d)/g, \"$1-$2\")\n .replace(/-{2,}/g, \"-\")\n}\n\n/**\n * Resolve a model name to the best available variant in the Copilot model list.\n *\n * Resolution cascade:\n * 0. `[1m]` literal-bracket suffix: strip, delegate, warn if downgraded.\n * Bracketed slug must never reach Copilot (400s on it). See cc-backup\n * `src/utils/context.ts:35-40` for Claude Code's 1M unlock mechanism.\n * 1. Exact match\n * 2. Case-insensitive match\n * 3. Family preference (opus→1m, codex→highest version)\n * 4. Normalized match (dots→dashes, letter-digit boundaries)\n * 5. Anthropic dated-slug retry: if the input matches `claude-...-YYYYMMDD`,\n * strip the date and re-run the cascade once. Family-guarded so non-claude\n * 8-digit suffixes can't be mis-stripped; runs after Steps 1-4 so explicit\n * version pinning (a dated catalog id matched at Step 1) always wins.\n * 6. Return as-is with a warning\n */\nexport function resolveModel(modelId: string): string {\n const models = state.models?.data\n if (!models) return modelId\n\n // [1m] literal-bracket suffix: Claude Code's request for 1M context\n // accounting. cc-backup `src/utils/context.ts:35-40` has1mContext\n // matches `/\\[1m\\]/i`; getContextWindowForModel returns 1_000_000\n // when true. parseUserSpecifiedModel (model.ts:445-506) reattaches\n // the bracket after alias resolution, so Claude Code SENDS the\n // bracketed slug verbatim on the wire (`model: \"claude-opus-4-7[1m]\"`).\n // Copilot doesn't recognize the bracket → 400.\n //\n // Strip for the catalog lookup and delegate. If the stripped\n // resolution lands on a `-1m` variant (enterprise opus path via\n // family preference), perfect — the upstream call routes to the 1M\n // backend and Claude Code's local accounting was right. Otherwise\n // (non-enterprise for opus, or any [1m] on sonnet/haiku where\n // Copilot has no -1m backend), warn and return the 200K resolution\n // so the request still succeeds — at the cost of Claude Code\n // over-accounting context against the proxy (it will compact early\n // because it thinks the window is 1M).\n //\n // Bounded recursion: the stripped form no longer matches the regex,\n // so the inner resolveModel call cannot re-enter this branch.\n const oneMMatch = modelId.match(/^(.*)\\[1m\\]$/i)\n if (oneMMatch) {\n const stripped = oneMMatch[1]\n const resolved = resolveModel(stripped)\n if (!/-1m(?:$|-)/.test(resolved)) {\n consola.warn(\n `Model \"${modelId}\" requested 1M context but no -1m backend is in Copilot's catalog for this tier/family; downgrading upstream to \"${resolved}\" (200K). Claude Code's local context accounting will still assume 1M — expect premature auto-compact. Drop the [1m] suffix (or unset CLAUDE_CODE_DISABLE_1M_CONTEXT if you set it) to silence.`,\n )\n }\n return resolved\n }\n\n // 1. Exact match\n if (models.some((m) => m.id === modelId)) return modelId\n\n // 2. Case-insensitive match\n const lower = modelId.toLowerCase()\n const ciMatch = models.find((m) => m.id.toLowerCase() === lower)\n if (ciMatch) return ciMatch.id\n\n // 3. Family preference — before normalization so product aliases\n // (opus→1m, codex→latest) take priority over fuzzy matches\n if (lower.includes(\"opus\")) {\n // Match ...-1m or ...-1m-<anything> (e.g. claude-opus-4.7-1m-internal).\n // Prefer the 1M variant whose major.minor matches the requested version,\n // otherwise find() would silently downgrade claude-opus-4.7 to a\n // claude-opus-4.6-1m if the latter happens to come first in the list.\n // Accept both dotted (\"opus-4.7\") and dashed (\"opus-4-7\") inputs —\n // Claude Code historically sends the dashed form.\n const oneMs = models.filter(\n (m) => m.id.includes(\"opus\") && /-1m(?:$|-)/.test(m.id),\n )\n const versionMatch = lower.match(/opus-(\\d+)[.-](\\d+)/)\n const requestedVersion =\n versionMatch ? `${versionMatch[1]}.${versionMatch[2]}` : undefined\n const preferred = requestedVersion\n ? oneMs.find((m) => m.id.includes(`opus-${requestedVersion}-`))\n : undefined\n const oneM = preferred ?? oneMs[0]\n if (oneM) return oneM.id\n }\n\n if (lower.includes(\"codex\")) {\n const codexModels = models.filter(\n (m) => m.id.includes(\"codex\") && !m.id.includes(\"mini\"),\n )\n if (codexModels.length > 0) {\n codexModels.sort((a, b) => b.id.localeCompare(a.id))\n return codexModels[0].id\n }\n }\n\n // 4. Normalized match (dots → dashes, letter-digit boundaries)\n const normalized = normalizeModelId(modelId)\n const normMatch = models.find(\n (m) => normalizeModelId(m.id) === normalized,\n )\n if (normMatch) return normMatch.id\n\n // 5. Anthropic dated-slug retry. Claude Code's /model UI ships Anthropic's\n // published slugs (e.g. \"claude-haiku-4-5-20251001\") that carry a\n // -YYYYMMDD suffix Copilot's catalog doesn't use. Strip the date and\n // re-run the cascade once so the request maps to the floating tag\n // (claude-haiku-4.5). Family-guarded to `claude-` so a hypothetical\n // \"gpt-...-20260101\" can't be silently stripped. Bounded recursion:\n // the stripped id no longer matches the regex, so the retry's own\n // Step 5 is a no-op.\n const dateStripped = modelId.replace(/^(claude-[\\w.-]+)-20\\d{6}$/i, \"$1\")\n if (dateStripped !== modelId) {\n const retried = resolveModel(dateStripped)\n // resolveModel returns the input unchanged on miss; treat unchanged-and-\n // not-in-catalog as miss to avoid logging a misleading \"resolved\" hop.\n const retryHit =\n retried !== dateStripped || models.some((m) => m.id === dateStripped)\n if (retryHit) {\n consola.info(\n `Resolved Anthropic dated slug \"${modelId}\" → \"${retried}\" (stripped -YYYYMMDD; pass an explicit catalog id to pin a snapshot)`,\n )\n return retried\n }\n }\n\n // 6. Legacy family fallback. Claude Code's settings.json may pin slugs\n // whose Copilot equivalent does not exist (e.g. claude-3-7-sonnet-20250219\n // or claude-sonnet-4-0 — neither is in Copilot's enterprise catalog as\n // of 2026-05-11; a request for either returns HTTP 400 \"model not\n // supported\"). Step 5's dated-retry strips the date but the resulting\n // \"claude-3-7-sonnet\" still has no Copilot equivalent. Rather than\n // dead-end the request, fall back to the highest available family\n // member (sonnet → highest sonnet, haiku → highest haiku). Surfaces\n // via consola.info so the user sees the substitution. Opus is already\n // handled by the family preference in Step 3.\n //\n // Guards (codex-reviewer findings):\n // (a) Family fires only for `claude-` prefixed inputs — protects\n // against custom-sonnet-future or any non-Anthropic provider\n // coincidentally containing \"sonnet\"/\"haiku\" in its slug.\n // (b) Family token must be word-bounded (`-sonnet-` / `-sonnet$`)\n // so a hypothetical claude-supersonnet-* doesn't match.\n // (c) Sort uses numeric collation (`{numeric: true}`) so a future\n // claude-sonnet-4.10 sorts higher than claude-sonnet-4.6\n // (lexicographic alone would invert).\n if (lower.startsWith(\"claude-\")) {\n const matchSonnet = /(?:^|-)sonnet(?:-|$)/.test(lower)\n const matchHaiku = /(?:^|-)haiku(?:-|$)/.test(lower)\n if (matchSonnet || matchHaiku) {\n const family = matchSonnet ? \"sonnet\" : \"haiku\"\n const familyMembers = models.filter((m) =>\n new RegExp(`(?:^|-)${family}(?:-|$|\\\\.)`).test(m.id),\n )\n if (familyMembers.length > 0) {\n familyMembers.sort((a, b) =>\n b.id.localeCompare(a.id, undefined, { numeric: true }),\n )\n const best = familyMembers[0].id\n consola.info(\n `Model \"${modelId}\" not in Copilot catalog; falling back to highest available \"${best}\" (legacy ${family} slug). Pin a current catalog id to silence.`,\n )\n return best\n }\n }\n }\n\n // 7. No match — warn and return as-is\n consola.warn(\n `Model \"${modelId}\" not found in Copilot model list. Available: ${models.map((m) => m.id).join(\", \")}`,\n )\n return modelId\n}\n\n/**\n * Resolve a codex model ID, falling back to the best available codex model.\n * Used by the codex subcommand for model selection.\n */\nexport function resolveCodexModel(modelId: string): string {\n const resolved = resolveModel(modelId)\n const models = state.models?.data\n if (!models) return resolved\n\n // Check if the resolved model exists in the model list\n if (models.some((m) => m.id === resolved)) return resolved\n\n // Fall back to the best available codex-class model. The /responses\n // endpoint is the discriminator — gpt-5.5 dropped the -codex suffix but\n // still routes through /responses. Prefer explicit -codex ids when both\n // exist, otherwise pick the highest version-like id.\n const candidates = models.filter((m) => {\n const endpoints = m.supported_endpoints ?? []\n if (m.id.includes(\"mini\") || m.id.includes(\"nano\")) return false\n return endpoints.length === 0 || endpoints.includes(\"/responses\")\n })\n\n if (candidates.length > 0) {\n candidates.sort((a, b) => {\n const aCodex = a.id.includes(\"codex\") ? 1 : 0\n const bCodex = b.id.includes(\"codex\") ? 1 : 0\n if (aCodex !== bCodex) return bCodex - aCodex\n return b.id.localeCompare(a.id)\n })\n const best = candidates[0].id\n consola.warn(`Model \"${modelId}\" not available, using \"${best}\" instead`)\n return best\n }\n\n return resolved\n}\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\nexport const cacheCopilotVersion = async () => {\n const version = await getCopilotChatVersion()\n state.copilotVersion = version\n\n consola.info(`Using Copilot Chat version: ${version}`)\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n const expiresAt = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expiresAt) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n consola.error(\"Failed to poll access token:\", await response.text())\n if (Date.now() >= expiresAt) break\n await sleep(sleepDuration)\n continue\n }\n\n const json = await response.json()\n consola.debug(\"Polling access token response:\", json)\n\n const { access_token } = json as AccessTokenResponse\n\n if (access_token) {\n return access_token\n }\n\n if (Date.now() >= expiresAt) break\n await sleep(sleepDuration)\n }\n\n throw new Error(\"Device code expired. Please run auth again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = Math.max((refresh_in - 60) * 1000, 1000)\n setInterval(() => {\n void refreshCopilotToken(\"interval\")\n }, refreshInterval)\n}\n\n// Single-flight mutex around the refresh fetch. Concurrent triggers (interval\n// + a 401-retry path) share one in-flight refresh promise so we never\n// overlap network calls or race writes to state.copilotToken.\nlet inflightRefresh: Promise<void> | undefined\n// Cooldowns are keyed off the OUTCOME of the last refresh, not the attempt:\n// - lastRefreshSuccess: throttles 401-retries when the token is fresh\n// (don't pointlessly re-fetch a token we just got).\n// - lastRefreshFailure: shorter backoff so a transient upstream blip\n// doesn't suppress legitimate refresh attempts for a full 30s, but\n// still prevents a thundering-herd refresh-storm against an upstream\n// that's persistently failing.\nlet lastRefreshSuccess = 0\nlet lastRefreshFailure = 0\nconst REFRESH_SUCCESS_COOLDOWN_MS = 30_000\nconst REFRESH_FAILURE_COOLDOWN_MS = 5_000\n\nexport async function refreshCopilotToken(\n reason: \"interval\" | \"401-retry\",\n): Promise<void> {\n if (inflightRefresh) return inflightRefresh\n // Refresh-storm protection: if a recent refresh already completed,\n // decline new 401-retry attempts. Interval refreshes always proceed\n // (they're spaced by `refresh_in - 60s` which is well outside the\n // window). 401-retry attempts respect both cooldowns:\n // - skip if a refresh succeeded within the last 30s (token is fresh)\n // - skip if a refresh failed within the last 5s (back off briefly)\n if (reason === \"401-retry\") {\n const now = Date.now()\n if (now - lastRefreshSuccess < REFRESH_SUCCESS_COOLDOWN_MS) {\n consola.debug(\n `refreshCopilotToken(${reason}) skipped: prior success within ${REFRESH_SUCCESS_COOLDOWN_MS}ms`,\n )\n return\n }\n if (now - lastRefreshFailure < REFRESH_FAILURE_COOLDOWN_MS) {\n consola.debug(\n `refreshCopilotToken(${reason}) skipped: prior failure within ${REFRESH_FAILURE_COOLDOWN_MS}ms`,\n )\n return\n }\n }\n\n inflightRefresh = (async () => {\n consola.debug(`Refreshing Copilot token (reason=${reason})`)\n try {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n lastRefreshSuccess = Date.now()\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n } catch (error) {\n lastRefreshFailure = Date.now()\n consola.error(\n `Failed to refresh Copilot token (reason=${reason}):`,\n error,\n )\n } finally {\n inflightRefresh = undefined\n }\n })()\n return inflightRefresh\n}\n\n/**\n * Try `request()`. If it returns a 401, refresh the Copilot token (subject\n * to the single-flight + refresh-storm-protection of `refreshCopilotToken`)\n * and retry once. After one retry, propagate whatever the second attempt\n * returned — the caller's existing 401-handling path is preserved.\n *\n * The `request` callback is responsible for capturing `state.copilotToken`\n * locally before any await; this helper does NOT re-build the request\n * itself, just re-invokes the callback after a refresh.\n */\nexport async function tryRefreshAndRetry(\n request: () => Promise<Response>,\n routePath: string,\n): Promise<Response> {\n const first = await request()\n if (first.status !== 401) return first\n\n consola.warn(\n `${routePath}: upstream returned 401, attempting one token refresh + retry`,\n )\n await refreshCopilotToken(\"401-retry\")\n // Re-invoke the request with the (possibly) new token in state.\n return request()\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise<void> {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n await logUser()\n\n return\n }\n\n consola.info(\"Not logged in, getting new access token\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/paths\"\nimport { state } from \"./lib/state\"\nimport { setupGitHubToken } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showToken = options.showToken\n\n await ensurePaths()\n await setupGitHubToken({ force: true })\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showToken: args[\"show-token\"],\n })\n },\n})\n","import { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotUsage = async (): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/paths\"\nimport { setupGitHubToken } from \"./lib/token\"\nimport {\n getCopilotUsage,\n type QuotaDetail,\n} from \"./services/github/get-copilot-usage\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n await setupGitHubToken()\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed =\n premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\n \"Completions\",\n usage.quota_snapshots.completions,\n )\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n","import { execFile, execFileSync } from \"node:child_process\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport { promisify } from \"node:util\"\n\nimport consola from \"consola\"\n\nconst execFileAsync = promisify(execFile)\n\nconst NPM_PACKAGE = \"@anthropic-ai/claude-code\"\nconst THROTTLE_HOURS = 1\nconst NPM_VIEW_TIMEOUT_MS = 5000\nconst NPM_INSTALL_TIMEOUT_MS = 120_000 // 2 min — npm install can be slow on cold caches\n\ninterface VersionCheckCache {\n /** ISO timestamp of last check */\n checkedAt: string\n /** Installed version at last check */\n installedVersion: string | null\n /** Latest version on npm at last check */\n latestVersion: string | null\n}\n\n/** Path to the throttle cache. Created on demand. */\nfunction cacheFilePath(): string {\n return path.join(\n os.homedir(),\n \".local\",\n \"share\",\n \"github-router\",\n \"last-update-check\",\n )\n}\n\n/**\n * Read the throttle cache. Returns null on missing/corrupt file —\n * triggers a fresh check.\n */\nasync function readCache(): Promise<VersionCheckCache | null> {\n try {\n const raw = await fs.readFile(cacheFilePath(), \"utf8\")\n const parsed = JSON.parse(raw) as VersionCheckCache\n if (\n typeof parsed.checkedAt !== \"string\"\n || (parsed.installedVersion !== null\n && typeof parsed.installedVersion !== \"string\")\n || (parsed.latestVersion !== null\n && typeof parsed.latestVersion !== \"string\")\n ) {\n return null\n }\n return parsed\n } catch {\n return null\n }\n}\n\nasync function writeCache(cache: VersionCheckCache): Promise<void> {\n try {\n await fs.mkdir(path.dirname(cacheFilePath()), { recursive: true })\n await fs.writeFile(cacheFilePath(), JSON.stringify(cache), {\n mode: 0o600,\n })\n } catch (err) {\n // Throttle cache is best-effort — a write failure means we'll re-check\n // on next launch. Not worth surfacing.\n consola.debug(\"Failed to write claude version-check cache:\", err)\n }\n}\n\n/** Check if it's been more than THROTTLE_HOURS since the last check. */\nfunction shouldCheckNow(cache: VersionCheckCache | null): boolean {\n if (!cache) return true\n const lastCheck = new Date(cache.checkedAt).getTime()\n if (Number.isNaN(lastCheck)) return true\n const hoursSince = (Date.now() - lastCheck) / 1000 / 3600\n return hoursSince >= THROTTLE_HOURS\n}\n\n/**\n * Read the installed `claude` version. Returns null if claude is not\n * on PATH or the version probe fails (e.g. older versions that don't\n * support `--version` cleanly).\n */\nfunction getInstalledVersion(): string | null {\n try {\n const out = execFileSync(\"claude\", [\"--version\"], {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3000,\n encoding: \"utf8\",\n })\n // Output shape: \"2.1.139 (Claude Code)\\n\"\n const match = out.match(/^(\\d+\\.\\d+\\.\\d+)/)\n return match ? match[1] : null\n } catch {\n return null\n }\n}\n\n/**\n * Fetch the latest version of @anthropic-ai/claude-code from the npm\n * registry. Returns null on network failure / npm unavailable.\n */\nasync function getLatestVersion(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync(\n \"npm\",\n [\"view\", NPM_PACKAGE, \"version\", \"--silent\"],\n { timeout: NPM_VIEW_TIMEOUT_MS },\n )\n const v = stdout.trim()\n return /^\\d+\\.\\d+\\.\\d+/.test(v) ? v : null\n } catch {\n return null\n }\n}\n\n/**\n * Compare two semver-shaped strings (only the leading X.Y.Z, no\n * pre-release / metadata handling — sufficient for npm-published\n * stable releases). Returns true if `latest` is strictly higher than\n * `installed`.\n */\nfunction isNewer(installed: string | null, latest: string | null): boolean {\n if (!installed || !latest) return false\n const a = installed.split(\".\").map((n) => parseInt(n, 10))\n const b = latest.split(\".\").map((n) => parseInt(n, 10))\n for (let i = 0; i < 3; i++) {\n const av = a[i] ?? 0\n const bv = b[i] ?? 0\n if (av < bv) return true\n if (av > bv) return false\n }\n return false\n}\n\nexport interface VersionCheckResult {\n /** Whether claude is on PATH at all */\n installed: boolean\n installedVersion: string | null\n latestVersion: string | null\n /** True if a newer version is available */\n needsUpdate: boolean\n /** Whether the check was skipped (throttled or disabled) */\n skipped: boolean\n skipReason?: \"throttled\" | \"disabled\" | \"no-npm\" | \"no-claude\"\n}\n\n/**\n * Run a version check (subject to throttle). Side-effect: updates the\n * throttle cache. Returns the comparison result.\n */\nexport async function checkClaudeVersion(opts: {\n noCheck?: boolean\n /** Bypass the throttle (used when the check is the user's main intent) */\n force?: boolean\n} = {}): Promise<VersionCheckResult> {\n if (opts.noCheck) {\n return {\n installed: false,\n installedVersion: null,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"disabled\",\n }\n }\n\n const cache = await readCache()\n if (!opts.force && !shouldCheckNow(cache)) {\n return {\n installed: cache?.installedVersion !== null,\n installedVersion: cache?.installedVersion ?? null,\n latestVersion: cache?.latestVersion ?? null,\n needsUpdate: isNewer(\n cache?.installedVersion ?? null,\n cache?.latestVersion ?? null,\n ),\n skipped: true,\n skipReason: \"throttled\",\n }\n }\n\n const installedVersion = getInstalledVersion()\n if (installedVersion === null) {\n return {\n installed: false,\n installedVersion: null,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"no-claude\",\n }\n }\n\n const latestVersion = await getLatestVersion()\n // Update cache regardless of whether latest fetched (so we still\n // throttle if npm is offline).\n await writeCache({\n checkedAt: new Date().toISOString(),\n installedVersion,\n latestVersion,\n })\n\n if (latestVersion === null) {\n return {\n installed: true,\n installedVersion,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"no-npm\",\n }\n }\n\n return {\n installed: true,\n installedVersion,\n latestVersion,\n needsUpdate: isNewer(installedVersion, latestVersion),\n skipped: false,\n }\n}\n\n/**\n * Run `npm install -g @anthropic-ai/claude-code@latest` synchronously.\n * Throws on failure — the caller decides whether to abort the launch\n * or continue with the older version.\n */\nexport async function autoUpdateClaude(latestVersion: string): Promise<void> {\n consola.info(\n `Updating ${NPM_PACKAGE} to ${latestVersion} (this may take ~30s)...`,\n )\n try {\n await execFileAsync(\n \"npm\",\n [\"install\", \"-g\", `${NPM_PACKAGE}@latest`, \"--silent\"],\n { timeout: NPM_INSTALL_TIMEOUT_MS },\n )\n consola.success(`${NPM_PACKAGE} updated to ${latestVersion}`)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`npm install failed: ${msg}`)\n }\n}\n","import consola from \"consola\"\n\nimport { state } from \"./state\"\n\nexport const DEFAULT_PORT = 8787\n\n/**\n * Default model for `github-router claude`. The Anthropic-published dashed\n * slug (`claude-opus-4-7`) — NOT the Copilot-internal slug\n * (`claude-opus-4.7-1m-internal`) — because Claude Code 2.1.126's `/model`\n * UI is backed by a hardcoded registry of Anthropic slugs, and an\n * unrecognized slug causes the menu to highlight \"Opus 4\" with a\n * \"Newer version available\" hint instead of \"Opus 4.7 (1M context)\".\n *\n * The proxy's `resolveModel` (`src/lib/utils.ts`) translates this to\n * Copilot's `claude-opus-4.7-1m-internal` (enterprise) or\n * `claude-opus-4.7` (Pro+/Business/Max) at request time via the\n * family-preference + version-match branch — round-trip covered by\n * `tests/lib-utils.test.ts:154`.\n *\n * `DEFAULT_CLAUDE_MODEL_FALLBACKS` covers major.minor regressions only;\n * 1M↔200K downgrade is handled inside the resolver, so we don't need\n * separate `-1m` entries here.\n */\nexport const DEFAULT_CLAUDE_MODEL = \"claude-opus-4-7\"\nexport const DEFAULT_CLAUDE_MODEL_FALLBACKS = [\n \"claude-opus-4-6\",\n \"claude-opus-4-5\",\n] as const\n\n/**\n * Cap-aware default picker for `ANTHROPIC_MODEL` on the implicit-default\n * path. Returns `claude-opus-4-7[1m]` when the live Copilot catalog\n * contains a `*-opus-4.7-1m*` variant (enterprise tier), else\n * `DEFAULT_CLAUDE_MODEL` (the bare slug).\n *\n * The `[1m]` literal-bracket suffix is Claude Code's local 1M-context\n * unlock — cc-backup `src/utils/context.ts:35-40` matches `/\\[1m\\]/i`\n * to flip the context window from 200K to 1M, which drives compaction\n * triggers, the status-line context %, and token budgets. Without the\n * bracket Claude Code accounts against 200K regardless of how the\n * proxy routes the underlying request.\n *\n * Cap-awareness matters because on non-enterprise Copilot tiers there\n * is no `-1m` opus backend; sending `[1m]` there would either 400 at\n * Copilot or (with `resolveModel`'s graceful-degrade) silently\n * downgrade upstream while Claude Code still over-accounts context.\n * This helper detects the catalog state at launch and only opts in\n * when the backend can actually serve 1M.\n *\n * Sonnet/Haiku families are intentionally NOT given `[1m]` defaults\n * because Copilot has no `-1m` backend for them (and Anthropic-side\n * `modelSupports1M` doesn't list haiku at all). See\n * `src/lib/server-setup.ts:getClaudeCodeEnvVars` for the\n * `ANTHROPIC_DEFAULT_{SONNET,HAIKU,OPUS}_MODEL` tier defaults.\n *\n * Must be called AFTER `cacheModels()` has populated `state.models`.\n * Returns the bare slug if the catalog isn't populated (resolveModel\n * can't tell the difference between \"no catalog yet\" and \"no 1M\n * variant\" — defaulting safe-side preserves the pre-change behavior).\n */\nexport function pickClaudeDefault(): string {\n const has1mOpus47 =\n state.models?.data.some((m) => /opus-4[.-]7-1m(?:$|-)/i.test(m.id)) ?? false\n if (has1mOpus47) {\n consola.info(\n `Catalog contains opus-4.7-1m variant; defaulting ANTHROPIC_MODEL to \"${DEFAULT_CLAUDE_MODEL}[1m]\" so Claude Code accounts for 1M context locally. Set CLAUDE_CODE_DISABLE_1M_CONTEXT=1 to opt out (HIPAA), or pass --model ${DEFAULT_CLAUDE_MODEL} to pin 200K.`,\n )\n return `${DEFAULT_CLAUDE_MODEL}[1m]`\n }\n return DEFAULT_CLAUDE_MODEL\n}\n\n/**\n * Default model for `github-router codex`. `gpt-5.5` is the new flagship\n * `/responses` model; the fallback chain handles older Copilot tiers where\n * 5.5 hasn't rolled out yet. `resolveCodexModel` provides a final\n * \"best available `/responses` model\" safety net beyond this list.\n */\nexport const DEFAULT_CODEX_MODEL = \"gpt-5.5\"\nexport const DEFAULT_CODEX_MODEL_FALLBACKS = [\n \"gpt-5.4\",\n \"gpt-5.3-codex\",\n \"gpt-5.2-codex\",\n] as const\n\nconst PORT_RANGE_MIN = 11000\nconst PORT_RANGE_MAX = 65535\n\n/** Generate a random port number in the range [11000, 65535]. */\nexport function generateRandomPort(): number {\n return (\n Math.floor(Math.random() * (PORT_RANGE_MAX - PORT_RANGE_MIN + 1))\n + PORT_RANGE_MIN\n )\n}\n\nfunction envInt(key: string, fallback: number): number {\n const raw = process.env[key]\n if (!raw) return fallback\n // Strict integer format only: parseInt is too permissive — it would\n // silently turn `\"5e3\"` into 5, `\"300_000\"` into 300, `\"60000ms\"` into\n // 60000. For timeout knobs we'd rather fall back than silently\n // misconfigure (e.g. set a 5-min inactivity timer to 5 ms).\n if (!/^[0-9]+$/.test(raw.trim())) {\n consola.warn(\n `${key}=${JSON.stringify(raw)} is not a non-negative integer; using fallback ${fallback}`,\n )\n return fallback\n }\n const parsed = Number.parseInt(raw, 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\n// Total fetch-phase timeout (until Response object resolves) for upstream\n// streaming endpoints. Default 0 = no fetch-phase timeout — body-phase\n// failures are covered by UPSTREAM_INACTIVITY_TIMEOUT_MS below, and a\n// fetch-lifecycle timeout would silently truncate legitimate long\n// completions (e.g. xhigh-thinking responses that legitimately stream\n// for 30+ minutes). Set the env var to a positive integer if you need\n// a hard cap.\nexport const UPSTREAM_FETCH_TIMEOUT_MS = envInt(\n \"UPSTREAM_FETCH_TIMEOUT_MS\",\n 0,\n)\n\n// Inactivity bound on body reads — if no chunk arrives within this window,\n// abort the stream and emit a structured error event. 300s (5 min) sits\n// well above Copilot's ~60s idle cut so the proxy still reaps stalled\n// connections before the upstream RST hits us as an unhandled rejection,\n// but does NOT prematurely abort reasoning-capable models (gpt-5.5,\n// gpt-5.3-codex, gemini-3.1-pro-preview, claude-opus-4.7-xhigh) which\n// routinely produce >75s silences between visible token bursts while\n// thinking. The earlier 75s default produced live aborts at /v1/messages\n// with bytes=134k–163k already streamed — proof the upstream was healthy\n// and just thinking. Lower this only if you specifically want to reap\n// stalled connections faster than 5 minutes.\nexport const UPSTREAM_INACTIVITY_TIMEOUT_MS = envInt(\n \"UPSTREAM_INACTIVITY_TIMEOUT_MS\",\n 300_000,\n)\n\n// TODO: extend timeout coverage to non-streaming paths (web-search MCP in\n// src/services/copilot/web-search.ts, embeddings, models) when those\n// endpoints become hot or start hanging in practice.\n","import { execFileSync, spawn, type ChildProcess } from \"node:child_process\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport type { Server } from \"srvx\"\n\nimport { DEFAULT_CODEX_MODEL } from \"./port\"\n\n/**\n * Auth-related env keys we strip from the parent before spawning the\n * child CLI. The proxy provides its own values for everything we care\n * about (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, OPENAI_BASE_URL,\n * OPENAI_API_KEY, CODEX_HOME, ANTHROPIC_MODEL); for the rest, we want\n * the child to behave as if the user had no parent-env auth at all.\n *\n * Why strip rather than override-with-empty-string:\n * - Claude Code emits \"Auth conflict\" warnings whenever both\n * ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY are present (regardless\n * of value, even when both are \"dummy\"). Stripping API_KEY entirely\n * suppresses the warning AND prevents an inherited real shell key\n * from leaking via x-api-key.\n * - Cloud-provider toggles (CLAUDE_CODE_USE_*) and OAUTH_TOKEN, etc.\n * are simpler dropped than overridden — a missing env var is\n * unambiguously falsy/absent in every code path that reads it.\n */\nconst STRIPPED_PARENT_ENV_KEYS = [\n // Claude Code auth surface\n \"ANTHROPIC_API_KEY\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_CUSTOM_HEADERS\",\n \"ANTHROPIC_MODEL\",\n \"CLAUDE_CODE_OAUTH_TOKEN\",\n // Per binary-grep of v2.1.140 (function QuH): Claude Code recognizes\n // CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR as an alternate auth source\n // (loads OAuth from an open file descriptor). Stripping this prevents\n // a user-exported FD reference from leaking into the proxy session\n // and creating a third auth source alongside the synthetic\n // .credentials.json (potential auth-conflict warning).\n \"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR\",\n \"CLAUDE_CODE_USE_BEDROCK\",\n \"CLAUDE_CODE_USE_VERTEX\",\n \"CLAUDE_CODE_USE_FOUNDRY\",\n // Defense-in-depth: prevent a parent-set CLAUDE_CONFIG_DIR (e.g. an\n // alternate test profile) from silently leaking into the proxy session.\n // The proxy sets its own value to activate per-config-dir keychain\n // isolation (see `getClaudeCodeEnvVars` doc comment).\n \"CLAUDE_CONFIG_DIR\",\n // Claude Code Bridge / IDE remote-session surface. Any of these set in\n // the parent shell would activate Claude Code's remote-session code path\n // — which makes many additional API calls (POST /v1/code/sessions,\n // POST /v1/environments/bridge, etc.) that this proxy does not implement\n // (Copilot has no equivalent). Stripping forces the spawned child to\n // run as a local-only session, which is what the proxy supports.\n // (Verified surface in cc-backup src/bridge/*, src/utils/managedEnv.ts;\n // empirical check 2026-05-11.)\n \"CLAUDE_BRIDGE_OAUTH_TOKEN\",\n \"CLAUDE_BRIDGE_BASE_URL\",\n \"CLAUDE_BRIDGE_SESSION_INGRESS_URL\",\n \"SESSION_INGRESS_URL\",\n \"CLAUDE_CODE_REMOTE\",\n \"CLAUDE_CODE_CONTAINER_ID\",\n \"CLAUDE_CODE_REMOTE_SESSION_ID\",\n \"CLAUDE_CODE_SESSION_ID\",\n // CLAUDE_CODE_ADDITIONAL_PROTECTION makes Claude Code emit\n // `x-anthropic-additional-protection: true` on every /v1/messages request.\n // Copilot ignores it today (verified 2026-05-11) but the header is pure\n // wire-fingerprint noise that breaks the VS Code stealth posture.\n \"CLAUDE_CODE_ADDITIONAL_PROTECTION\",\n // NOT stripped: ANTHROPIC_SMALL_FAST_MODEL. Users with custom Copilot\n // mappings legitimately rely on this to route the haiku-tier \"small fast\"\n // model. Stripping would be an unforced error (gemini-critic finding) —\n // we trust resolveModel's dated-slug-retry / family-fallback to translate\n // unrecognized values, and surface unsupported-model failures via consola.\n // Codex CLI auth surface\n \"OPENAI_API_KEY\",\n \"OPENAI_BASE_URL\",\n \"CODEX_HOME\",\n] as const\n\n/**\n * Strip auth-related keys from a parent-process env object. The result\n * is suitable to spread into a spawned child's env BEFORE the proxy's\n * explicit overrides, so the proxy is the only source of truth for\n * auth — and stale shell exports can't leak through.\n */\nexport function sanitizeParentEnv(\n parent: NodeJS.ProcessEnv,\n): NodeJS.ProcessEnv {\n const sanitized: NodeJS.ProcessEnv = { ...parent }\n for (const key of STRIPPED_PARENT_ENV_KEYS) {\n delete sanitized[key]\n }\n return sanitized\n}\n\nfunction commandExists(name: string): boolean {\n try {\n execFileSync(process.platform === \"win32\" ? \"where.exe\" : \"which\", [name], {\n stdio: \"ignore\",\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Provider-config flags (`-c model_providers.github_router=...`) that\n * point Codex at our proxy. Extracted from `buildCodexCmd` so the new\n * `codex mcp-server` MCP-config builder can reuse the exact same\n * provider definition — drift between the two paths would silently\n * break the MCP wiring.\n */\nexport function buildCodexProviderConfigFlags(serverUrl: string): Array<string> {\n return [\n \"-c\",\n `model_providers.github_router={name=\"github-router\",base_url=\"${serverUrl}/v1\",wire_api=\"responses\",env_key=\"OPENAI_API_KEY\"}`,\n \"-c\",\n \"model_provider=github_router\",\n ]\n}\n\n/**\n * Inspect the installed `codex` binary. Used by the codex-MCP wiring\n * in `claude.ts` to gate `--codex-cli`. Codex 0.129.0 introduced the\n * `mcp-server` subcommand; older versions don't expose it, so we\n * downgrade to the HTTP backend with a warning.\n */\nexport function getCodexVersion(): { ok: boolean; version?: string } {\n if (!commandExists(\"codex\")) return { ok: false }\n let raw: string\n try {\n raw = execFileSync(\"codex\", [\"--version\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim()\n } catch {\n return { ok: false }\n }\n // Output examples: \"codex-cli 0.129.0\", \"codex 0.130.1-dev\"\n const m = /(\\d+)\\.(\\d+)\\.(\\d+)/.exec(raw)\n if (!m) return { ok: false, version: raw }\n const major = Number.parseInt(m[1], 10)\n const minor = Number.parseInt(m[2], 10)\n const version = `${m[1]}.${m[2]}.${m[3]}`\n // mcp-server requires codex >= 0.129.0\n const ok = major > 0 || (major === 0 && minor >= 129)\n return { ok, version }\n}\n\nexport interface LaunchTarget {\n kind: \"claude-code\" | \"codex\"\n envVars: Record<string, string>\n extraArgs: string[]\n model?: string\n /**\n * Proxy URL the spawned child should target. Required for Codex 0.129+\n * which stopped honoring OPENAI_BASE_URL and now needs an explicit\n * `-c model_providers.<name>.base_url=...` argument. Set by the codex\n * subcommand from the same `serverUrl` it computed for env vars.\n */\n serverUrl?: string\n}\n\n/**\n * Codex 0.129.0 broke two things the launcher had been relying on:\n * (1) `--full-auto` was removed in favor of `--sandbox` + `--ask-for-approval`;\n * passing it now exits the child immediately with\n * `error: unexpected argument '--full-auto' found`.\n * (2) `OPENAI_BASE_URL` is silently ignored — Codex hardcodes\n * `https://api.openai.com/v1/responses` and 401s out without an\n * explicit `-c model_providers.<name>.base_url` override.\n *\n * `buildCodexCmd` builds the launch argv that works on Codex 0.129+ while\n * still being compatible with older versions that accept the same flags.\n */\nfunction buildCodexCmd(target: LaunchTarget): string[] {\n const cmd: string[] = [\"codex\"]\n if (target.serverUrl) {\n cmd.push(...buildCodexProviderConfigFlags(target.serverUrl))\n }\n cmd.push(\n \"--sandbox\",\n \"workspace-write\",\n \"--ask-for-approval\",\n \"on-request\",\n \"-m\",\n target.model ?? DEFAULT_CODEX_MODEL,\n ...target.extraArgs,\n )\n return cmd\n}\n\nexport function buildLaunchCommand(target: LaunchTarget): {\n cmd: string[]\n env: Record<string, string | undefined>\n} {\n const cmd: string[] =\n target.kind === \"claude-code\"\n ? [\"claude\", \"--dangerously-skip-permissions\", ...target.extraArgs]\n : buildCodexCmd(target)\n\n return {\n cmd,\n env: { ...sanitizeParentEnv(process.env), ...target.envVars },\n }\n}\n\nexport function launchChild(\n target: LaunchTarget,\n server: Server,\n options: { onShutdown?: () => Promise<void> | void } = {},\n): void {\n const { cmd, env } = buildLaunchCommand(target)\n\n const executable = cmd[0]\n if (!commandExists(executable)) {\n const msg = `\"${executable}\" not found on PATH. Install it first, then try again.`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n process.exit(1)\n }\n\n let child: ChildProcess\n try {\n if (process.platform === \"win32\") {\n // On Windows, npm-installed binaries are .cmd scripts that need\n // shell execution. Use the full command as a single string to\n // avoid DEP0190 deprecation warning about shell + args.\n const quoted = cmd.map((a) => (a.includes(\" \") ? `\"${a}\"` : a)).join(\" \")\n child = spawn(quoted, [], {\n env,\n stdio: \"inherit\",\n shell: true,\n })\n } else {\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n })\n }\n } catch (error) {\n const msg = `Failed to launch ${executable}: ${error instanceof Error ? error.message : String(error)}`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n server.close(true).catch(() => {})\n if (options.onShutdown) {\n void Promise.resolve(options.onShutdown()).catch(() => {})\n }\n process.exit(1)\n }\n\n let cleaned = false\n let exiting = false\n async function cleanup(): Promise<void> {\n if (cleaned) return\n cleaned = true\n\n try {\n child.kill()\n } catch {\n // Already exited\n }\n\n const timeout = setTimeout(() => process.exit(1), 5000)\n try {\n await server.close(true)\n } catch {\n // Server already closed\n }\n if (options.onShutdown) {\n try {\n await options.onShutdown()\n } catch {\n // Best-effort cleanup; shutdown must not be blocked by it.\n }\n }\n clearTimeout(timeout)\n }\n\n function exit(code: number): void {\n if (exiting) return\n exiting = true\n process.exit(code)\n }\n\n const onSignal = () => {\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }\n process.on(\"SIGINT\", onSignal)\n process.on(\"SIGTERM\", onSignal)\n\n child.on(\"exit\", (exitCode, signal) => {\n // When killed by a signal, exitCode is null — derive from signal number\n const code = exitCode ?? (signal ? 128 : 1)\n cleanup().then(() => exit(code)).catch(() => exit(1))\n })\n child.on(\"error\", () => {\n cleanup().then(() => exit(1)).catch(() => exit(1))\n })\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\nimport { z } from \"zod\"\n\nimport { copilotBaseUrl, copilotVersion } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { sleep } from \"~/lib/utils\"\n\nexport interface WebSearchResult {\n content: string\n references: Array<{ title: string; url: string }>\n}\n\nconst RpcSchema = z.object({\n jsonrpc: z.literal(\"2.0\"),\n id: z.number().optional(),\n result: z\n .object({\n content: z\n .array(z.object({ type: z.literal(\"text\"), text: z.string() }))\n .optional(),\n isError: z.boolean().optional(),\n })\n .optional(),\n error: z\n .object({ code: z.number(), message: z.string() })\n .optional(),\n})\n\nconst InnerSchema = z.object({\n text: z.object({\n value: z.string(),\n // Upstream sometimes returns `null` instead of an absent field for the\n // no-results case. `.nullable().optional()` accepts undefined, null,\n // and a real array; readers must `?? []` before iterating.\n annotations: z\n .array(\n z.object({\n url_citation: z\n .object({ title: z.string(), url: z.string() })\n .optional(),\n }),\n )\n .nullable()\n .optional(),\n }),\n bing_searches: z.array(z.unknown()).nullable().optional(),\n})\n\nconst MAX_SEARCHES_PER_SECOND = 3\nlet searchTimestamps: Array<number> = []\n\n// Single-flight chain serializes throttle checks. Without this, two\n// concurrent searches can both read the timestamp array, both filter,\n// both skip the await, and both push — doubling the QPS the throttle\n// is supposed to enforce.\nlet throttleChain: Promise<void> = Promise.resolve()\n\nasync function throttleSearch(): Promise<void> {\n const myTurn = throttleChain.then(async () => {\n const now = Date.now()\n searchTimestamps = searchTimestamps.filter((t) => now - t < 1000)\n if (searchTimestamps.length >= MAX_SEARCHES_PER_SECOND) {\n const waitMs = 1000 - (now - searchTimestamps[0])\n if (waitMs > 0) {\n consola.debug(`Web search rate limited, waiting ${waitMs}ms`)\n await sleep(waitMs)\n }\n }\n searchTimestamps.push(Date.now())\n })\n throttleChain = myTurn.catch(() => {\n // errors don't break the chain — next caller starts fresh\n })\n return myTurn\n}\n\nfunction mcpHeaders(sid?: string): Record<string, string> {\n if (!state.githubToken) {\n throw new Error(\n \"GitHub token missing — re-run auth flow. Web search uses the GitHub PAT (not the Copilot token); the on-disk token at ~/.local/share/github-router/github_token must be present.\",\n )\n }\n // Match the GitHubCopilotChat/<version> User-Agent the rest of the\n // router sends (see api-config.ts:32). Sending \"github-router/<version>\"\n // breaks the VS-Code-stealth posture and broadcasts our identity to the\n // MCP server.\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.githubToken}`,\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n \"X-MCP-Host\": \"copilot-cli\",\n \"X-MCP-Toolsets\": \"web_search\",\n \"Mcp-Protocol-Version\": \"2025-06-18\",\n \"user-agent\": `GitHubCopilotChat/${copilotVersion(state)}`,\n }\n if (sid) headers[\"Mcp-Session-Id\"] = sid\n return headers\n}\n\nasync function postMcp(\n body: unknown,\n sid?: string,\n retry = true,\n): Promise<Response> {\n const url = `${copilotBaseUrl(state)}/mcp`\n const res = await fetch(url, {\n method: \"POST\",\n headers: mcpHeaders(sid),\n body: JSON.stringify(body),\n })\n if (!res.ok && retry && res.status >= 500) {\n await sleep(500)\n return postMcp(body, sid, false)\n }\n return res\n}\n\nexport async function searchWeb(query: string): Promise<WebSearchResult> {\n await throttleSearch()\n consola.info(`Web search (MCP): \"${query.slice(0, 80)}\"`)\n\n const callId = Math.floor(Math.random() * 1_000_000_000)\n let sid: string | undefined\n\n try {\n // 1. initialize\n const initRes = await postMcp({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"initialize\",\n params: {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n // Identify as the Copilot Chat extension, mirroring the User-Agent\n // and editor-plugin-version we send on every other request.\n clientInfo: {\n name: \"GitHubCopilotChat\",\n version: copilotVersion(state),\n },\n },\n })\n if (!initRes.ok) {\n consola.error(\"MCP initialize failed\", initRes.status)\n throw new HTTPError(\"MCP initialize failed\", initRes)\n }\n sid = initRes.headers.get(\"mcp-session-id\") ?? undefined\n if (!sid) {\n throw new HTTPError(\n \"MCP initialize: missing Mcp-Session-Id header\",\n initRes,\n )\n }\n\n // 2. notifications/initialized — server returns 202 (no body)\n const notifRes = await postMcp(\n { jsonrpc: \"2.0\", method: \"notifications/initialized\" },\n sid,\n )\n if (!notifRes.ok && notifRes.status !== 202) {\n consola.error(\"MCP notifications/initialized failed\", notifRes.status)\n throw new HTTPError(\"MCP notifications/initialized failed\", notifRes)\n }\n\n // 3. tools/call web_search — SSE stream of JSON-RPC events; match by id\n const callRes = await postMcp(\n {\n jsonrpc: \"2.0\",\n id: callId,\n method: \"tools/call\",\n params: {\n name: \"web_search\",\n arguments: { query },\n },\n },\n sid,\n )\n if (!callRes.ok) {\n consola.error(\"MCP tools/call failed\", callRes.status)\n throw new HTTPError(\"MCP tools/call failed\", callRes)\n }\n\n let rpc: z.infer<typeof RpcSchema> | undefined\n for await (const ev of events(callRes)) {\n if (!ev.data) continue\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(ev.data)\n } catch {\n continue\n }\n const parsed = RpcSchema.safeParse(parsedJson)\n if (parsed.success && parsed.data.id === callId) {\n rpc = parsed.data\n break\n }\n }\n if (!rpc) {\n throw new HTTPError(\n \"MCP tools/call: no matching response id in SSE stream\",\n callRes,\n )\n }\n if (rpc.error) {\n throw new HTTPError(\n `MCP error ${rpc.error.code}: ${rpc.error.message}`,\n callRes,\n )\n }\n if (rpc.result?.isError) {\n throw new HTTPError(\"MCP web_search tool error\", callRes)\n }\n\n const text = rpc.result?.content?.[0]?.text\n if (!text) {\n throw new HTTPError(\"MCP web_search: empty content\", callRes)\n }\n\n let innerRaw: unknown\n try {\n innerRaw = JSON.parse(text)\n } catch (err) {\n throw new HTTPError(\n `MCP web_search: inner content not JSON: ${err instanceof Error ? err.message : String(err)}`,\n callRes,\n )\n }\n // safeParse: a raw ZodError thrown here would bypass forwardError's\n // HTTPError check and surface as a generic 500 instead of an Anthropic\n // shape error. Wrap explicitly.\n const innerParsed = InnerSchema.safeParse(innerRaw)\n if (!innerParsed.success) {\n throw new HTTPError(\n `MCP web_search: inner content shape changed (${innerParsed.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")})`,\n callRes,\n )\n }\n const inner = innerParsed.data\n\n const references: Array<{ title: string; url: string }> = []\n for (const ann of inner.text.annotations ?? []) {\n const cite = ann.url_citation\n if (cite && !cite.url.toLowerCase().includes(\"bing.com/search\")) {\n references.push({ title: cite.title, url: cite.url })\n }\n }\n\n consola.debug(`Web search returned ${references.length} references`)\n return { content: inner.text.value, references }\n } finally {\n if (sid) {\n // Best-effort session teardown — never throw. Wrap header construction\n // in try{} too: if state.githubToken cleared between init and finally,\n // mcpHeaders(sid) throws synchronously BEFORE fetch is called and\n // .catch() never attaches, which would mask the original error.\n try {\n void fetch(`${copilotBaseUrl(state)}/mcp`, {\n method: \"DELETE\",\n headers: mcpHeaders(sid),\n }).catch(() => {\n // ignore\n })\n } catch {\n // mcpHeaders threw (token cleared); skip teardown\n }\n }\n }\n}\n","/**\n * Peer-model persona specifications.\n *\n * The github-router proxy hosts a `/mcp` endpoint that exposes these\n * personas as MCP tools, and the `claude` subcommand wires them as\n * Claude Code subagents via `--agents` so Opus 4.7 can delegate\n * blind-spot-busting work to gpt-5.5, gpt-5.3-codex, and\n * gemini-3.1-pro-preview without leaving the session.\n *\n * Design contract (from the approved plan):\n *\n * 1. Persona text is a STABLE string. Never construct per-call —\n * gpt-5.x prompt caching reuses the prefix across invocations.\n * 2. Calibrated grading replaces \"force one disagreement.\" Silence\n * on good work is the signal Opus needs.\n * 3. End-of-prompt self-reminder beats start-of-prompt for\n * sustained behavioral fidelity in long sessions.\n * 4. Description fields differentiate routing — Opus picks a\n * persona largely from its `description`.\n * 5. Cold-start brief contract: subagent contexts are blank;\n * the persona prompt teaches the lead what to paste.\n */\n\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\n/**\n * Reasoning effort levels accepted by Copilot's /v1/responses (gpt-5.x) and\n * /v1/chat/completions endpoints. Per the proxy's existing thinking-mode\n * translator (CLAUDE.md \"Thinking-mode translation\"), Copilot's adaptive-\n * thinking path uses these same buckets:\n * <2k tokens → low, <8k → medium, <24k → high, else → xhigh.\n *\n * Per-persona `allowedEfforts` and `defaultEffort` constrain which subset\n * each persona exposes — enforced in handler.ts:handleToolsCall.\n *\n * **xhigh on long-running personas works via SSE-streamed /mcp responses**\n * (handler.ts:handleToolsCallSSE). Claude Code's MCP HTTP client honors\n * `text/event-stream` responses without applying the ~60s per-tool-call\n * timer that previously broke xhigh on gpt-5.5 (~56s wall) and\n * claude-opus-4-7 (high+ thinking budgets). All four personas now expose\n * all four effort tiers with `high` default; SSE handles the long tail\n * transparently to the user.\n */\nexport const EFFORT_LEVELS = [\"low\", \"medium\", \"high\", \"xhigh\"] as const\nexport type Effort = (typeof EFFORT_LEVELS)[number]\n\nexport function isEffort(v: unknown): v is Effort {\n return typeof v === \"string\" && (EFFORT_LEVELS as ReadonlyArray<string>).includes(v)\n}\n\nexport interface PersonaSpec {\n /** Subagent identifier in `--agents` JSON (and in Claude Code's UI). */\n agentName: string\n /** Tool name the HTTP MCP backend exposes for this persona. */\n toolNameHttp: string\n /** Copilot-side model id. Verified live against /v1/models at startup. */\n model: string\n /** Upstream endpoint the model speaks. */\n endpoint: \"/v1/responses\" | \"/v1/chat/completions\" | \"/v1/messages\"\n /** Description shown to Opus when picking a subagent. Drives routing. */\n description: string\n /** Persona system prompt — passed as `instructions` (Responses), system message (chat-completions), or `system` (messages). */\n baseInstructions: string\n /** Subagent prompt body that Claude Code uses as the agent's full system prompt. */\n agentPrompt: string\n /** True when the persona can mutate the workspace (only `codex-implementer`). */\n writeCapable: boolean\n /** True when the persona MUST use the HTTP backend (the codex-cli stdio\n * bridge can't run this model). gemini-3.x and claude-opus-4-7 both\n * set this — codex-cli only knows gpt-5/codex models. */\n requiresHttp: boolean\n /** True when the persona's model belongs to a model family that may not\n * be present in Copilot's live `/v1/models` catalog (gemini-critic\n * needs `gemini-3.x-pro` to be served). When true, `personasFor`\n * drops the persona if the catalog lacks the corresponding model.\n * Optional: defaults to false (persona is always registered). Kept\n * separate from `requiresHttp` so a persona can require HTTP without\n * also requiring gemini in the catalog (e.g. opus-critic). */\n requiresGeminiCatalog?: boolean\n /** Effort tiers this persona accepts. Subset of EFFORT_LEVELS. Driven\n * by empirical latency data — see the EFFORT_LEVELS doc above. Tiers\n * outside this list are rejected with a clean RPC_INVALID_PARAMS at\n * the handler layer rather than letting the call fail at the 60s\n * MCP ceiling. */\n allowedEfforts: ReadonlyArray<Effort>\n /** Default effort when the caller omits the arg. MUST appear in\n * `allowedEfforts`. */\n defaultEffort: Effort\n}\n\nconst CRITIC_RUBRIC = `\nApply this grading rubric:\n - Score 1–5 on three axes:\n A. assumption-soundness (are stated assumptions accurate? are unstated ones load-bearing?)\n B. failure-mode coverage (which realistic failure modes are unaddressed?)\n C. alternative-considered (was a meaningfully different approach weighed and rejected with reason?)\n - If every axis scores ≥ 4, reply with the literal string \"no material objection\" and stop. Do not invent issues to satisfy this rubric.\n - Otherwise, the lowest-scoring axis IS your critique. Lead with that single critique; secondary observations may follow as \"additional notes\".\n\nReply format (markdown):\n ## Verdict\n <\"no material objection\" OR a one-sentence summary of the load-bearing critique>\n ## Scores\n - assumption-soundness: <n>/5\n - failure-mode coverage: <n>/5\n - alternative-considered: <n>/5\n ## Critique\n <only when at least one axis < 4 — concrete, specific, actionable>\n ## Additional notes (optional)\n <secondary observations; omit if none>\n\nSelf-reminder (read before every reply):\n Am I still acting as the adversarial critic per the rubric above?\n If I just produced agreement, restart and apply the grading rubric instead.\n Sycophancy is the failure mode I exist to fight; manufactured contrarianism is a different failure of the same shape — do neither.\n`.trim()\n\nconst COLD_START_CONTRACT = `\nCold-start contract for the lead orchestrator (Opus):\n When delegating to me, paste a self-contained brief. I have no access to your scrollback, project memory, or the project tree. Always include:\n (a) the artifact under review verbatim (code/diff/plan text),\n (b) the constraints or \"done\" criteria,\n (c) any prior decisions I should not relitigate.\n If your brief lacks (a), I will reply with a one-line request for the artifact instead of speculating.\n`.trim()\n\nconst CRITIC_BASE = `You are codex-critic, an adversarial reviewer running on gpt-5.5. Your single job is to overcome the lead orchestrator's blind spots — assumptions it didn't notice it was making, failure modes it didn't enumerate, alternatives it didn't consider.\n\nYou are NOT a helpful assistant. You are NOT a coach. Sycophancy is the failure mode you exist to fight. Manufactured contrarianism is a different failure of the same shape — silence on good work is a valid and welcome answer.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nconst GEMINI_CRITIC_BASE = `You are gemini-critic, an adversarial reviewer running on Gemini 3.1 Pro. You exist to provide a second-lab perspective: your training data, RLHF priors, and attention patterns are systematically different from the lead orchestrator's (Opus, Anthropic) and from codex-critic (gpt-5.5, OpenAI). Use that to surface blind spots both miss.\n\nYour strengths the lead may want to draw on:\n - long-context reasoning over large artifacts (the brief may include >50k tokens of context)\n - math, proofs, and formally-stated invariants\n - cross-checking conclusions where codex-critic has already weighed in (the lead may forward you both the artifact and codex-critic's verdict)\n\nYou are NOT a helpful assistant. Sycophancy is the failure mode you exist to fight; do not invent issues to look thorough.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nconst REVIEWER_BASE = `You are codex-reviewer, a line-level code reviewer running on gpt-5.3-codex. You are the code-specialist persona — your job is to read concrete code (diffs, single files, function bodies) and surface bugs, edge cases, security issues, and idiom violations.\n\nYou are not a critic-of-architecture. If the brief is a plan or a high-level design, redirect: \"this looks like architecture review; consider codex-critic or gemini-critic.\" Your tool is the magnifying glass, not the wide-angle lens.\n\n${COLD_START_CONTRACT}\n\nReply format (markdown):\n ## Summary\n <one sentence: clean / N findings / blocking issue>\n ## Findings\n For each:\n ### <severity: info | low | medium | high | critical> — <one-line title>\n - location: <file:line[-line]>\n - issue: <what's wrong, why it matters in this codebase>\n - suggested fix: <minimal change OR \"needs design discussion\">\n Number the findings if there are more than one. List them in severity-descending order (critical first).\n If there are zero findings of any severity, reply only with \"## Summary\\\\nClean review — no findings.\" and stop.\n\nSelf-reminder (read before every reply):\n Am I citing real code at real line numbers in the brief? If a finding doesn't have a concrete file:line citation, drop it.\n Did I rank the finding's severity by impact-in-this-codebase, not by general-principle?\n If everything looks fine, say so cleanly — do not pad with stylistic nitpicks.`\n\nconst IMPLEMENTER_BASE = `You are codex-implementer, a focused implementation specialist running on gpt-5.3-codex with workspace-write access. You execute scoped, well-specified coding tasks end-to-end: read the relevant files, make the change, verify it, report back.\n\nYou are not a planner. If the brief is vague or missing acceptance criteria, ask the lead for the missing piece BEFORE editing anything. A wasted edit is worse than a clarifying question.\n\n${COLD_START_CONTRACT}\n\nWhat \"done\" looks like for an implementation task:\n - Exactly the files specified by the brief have been changed (or you reported back why a different scope was needed).\n - The change is minimal — surrounding cleanup is out of scope unless requested.\n - You ran the relevant test(s) / typecheck / linter for the touched files and report the results.\n - The summary you return enumerates each file changed with a one-line description.\n\nReply format (markdown):\n ## Status\n <complete | needs-clarification | blocked>\n ## Files changed\n - path/one.ts: <one-line description>\n - path/two.ts: <one-line description>\n ## Verification\n <commands run + outcomes>\n ## Notes\n <anything the lead must know to integrate, e.g. follow-ups intentionally not done>\n\nResilience reminder:\n If your session terminates abnormally before \"Status: complete\", the lead will retry once. On recovery, ask the lead to confirm what's already been done before re-applying changes — duplicate edits are worse than a slow restart.`\n\nconst OPUS_CRITIC_BASE = `You are opus-critic, a fresh-context Anthropic-side adversarial reviewer running on Claude Opus 4.7 — the same model and lab as the lead orchestrator that just delegated to you. You are NOT the lead. You did not see the lead's reasoning trace. You only see the brief.\n\nYour job is to spot what the lead missed because of cognitive momentum, sunk-cost on a plan, or motivated reasoning toward a particular fix. Your blind-spot diversification is LIMITED compared to codex-critic (gpt-5.5) and gemini-critic (gemini-3.1-pro) — same training, same lab, same RLHF priors. Use that honestly: don't pretend to find a different perspective when the obvious read is \"the lead got it right.\" Silence on good work is a valid and welcome answer.\n\nSycophancy is the failure mode you exist to fight. Manufactured contrarianism is a different failure of the same shape — do neither.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nexport const PERSONAS_READ: ReadonlyArray<PersonaSpec> = Object.freeze([\n {\n agentName: \"codex-critic\",\n toolNameHttp: \"codex_critic\",\n model: \"gpt-5.5\",\n endpoint: \"/v1/responses\",\n description:\n \"Adversarial second opinion on plans, designs, or code tradeoffs. Backed by gpt-5.5 (OpenAI) — different lab than Opus. Pass artifact verbatim.\",\n baseInstructions: CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: false,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n {\n agentName: \"gemini-critic\",\n toolNameHttp: \"gemini_critic\",\n model: \"gemini-3.1-pro-preview\",\n endpoint: \"/v1/chat/completions\",\n description:\n \"Adversarial second opinion. Backed by gemini-3.1-pro (Google) — third-lab triangulation, strong on long-context and formal reasoning. Pass artifact verbatim.\",\n baseInstructions: GEMINI_CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: true,\n requiresGeminiCatalog: true,\n allowedEfforts: [\"low\", \"medium\", \"high\"] as const,\n defaultEffort: \"high\",\n },\n {\n agentName: \"codex-reviewer\",\n toolNameHttp: \"codex_reviewer\",\n model: \"gpt-5.3-codex\",\n endpoint: \"/v1/responses\",\n description:\n \"Line-level review of a concrete diff or single file. Backed by gpt-5.3-codex (OpenAI) — code-specialist, narrow-scope. Pass artifact verbatim.\",\n baseInstructions: REVIEWER_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: false,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n {\n agentName: \"opus-critic\",\n toolNameHttp: \"opus_critic\",\n model: \"claude-opus-4-7\",\n endpoint: \"/v1/messages\",\n description:\n \"Adversarial second opinion from a fresh-context Opus 4.7 — cheap same-lab sanity check. Pass artifact verbatim.\",\n baseInstructions: OPUS_CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n // requiresHttp: true — codex-cli stdio bridge can't run claude-opus-4-7\n // (it speaks gpt-5/codex only), so opus-critic must always route via\n // HTTP. Distinct from requiresGeminiCatalog (which is false here —\n // claude-opus-4-7 is always in Copilot's catalog for our supported\n // tiers; we don't need a catalog probe to register the persona).\n requiresHttp: true,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n])\n\nexport const PERSONAS_WRITE: ReadonlyArray<PersonaSpec> = Object.freeze([\n {\n agentName: \"codex-implementer\",\n toolNameHttp: \"codex_implementer\",\n model: \"gpt-5.3-codex\",\n endpoint: \"/v1/responses\",\n description:\n \"Targeted implementation of a self-contained coding task. Backed by gpt-5.3-codex with workspace-write access. Pass spec + files verbatim.\",\n baseInstructions: IMPLEMENTER_BASE,\n agentPrompt: \"\",\n writeCapable: true,\n requiresHttp: false,\n // All four tiers supported — long calls stream via SSE.\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"high\",\n },\n])\n\n/**\n * Build the agent-prompt body Claude Code uses as the subagent's full\n * system prompt. The prompt fully replaces Claude Code's default system\n * prompt (per Anthropic's subagent docs) so it must be self-sufficient.\n *\n * Two modes branch on `codexCli`:\n * - HTTP backend: subagent calls the per-persona tool\n * `mcp__gh-router-peers__<toolNameHttp>` with `{prompt, context}`;\n * model + instructions are server-baked.\n * - codex-cli backend: subagent calls the single\n * `mcp__codex-cli__codex` tool with `{prompt, model: <persona.model>,\n * base-instructions: <persona.baseInstructions>}`. Gemini stays on\n * HTTP regardless because Codex CLI can't run Gemini.\n */\nexport function buildAgentPrompt(\n persona: PersonaSpec,\n opts: { codexCli: boolean },\n): string {\n const useStdio = opts.codexCli && !persona.requiresHttp\n const toolPath = useStdio\n ? \"mcp__codex-cli__codex\"\n : `mcp__gh-router-peers__${persona.toolNameHttp}`\n\n const invocationBlock = useStdio\n ? [\n `Always invoke the \\`${toolPath}\\` tool with these arguments:`,\n \" - `prompt`: the lead's brief, copied verbatim\",\n ` - \\`model\\`: \"${persona.model}\"`,\n \" - `base-instructions`: the persona text below (paste verbatim, do not paraphrase)\",\n ...(persona.writeCapable\n ? [\n ' - `sandbox`: \"workspace-write\"',\n ' - `approval-policy`: \"on-request\"',\n ]\n : [' - `sandbox`: \"read-only\"']),\n ].join(\"\\n\")\n : [\n `Always invoke the \\`${toolPath}\\` tool with these arguments:`,\n \" - `prompt`: the lead's brief, copied verbatim\",\n \" - `context` (optional): any additional file/diff content the persona needs\",\n \"Do NOT pass model or instructions — they are server-baked into this tool.\",\n ].join(\"\\n\")\n\n return [\n `# Subagent: ${persona.agentName}`,\n \"\",\n persona.baseInstructions,\n \"\",\n \"---\",\n \"\",\n \"## Routing instructions for this subagent\",\n \"\",\n invocationBlock,\n \"\",\n \"When the tool returns, surface its output to the lead verbatim. Do not summarize, paraphrase, or add your own commentary on top — the lead integrates the persona's reply directly.\",\n ].join(\"\\n\")\n}\n\n/**\n * Build the awareness snippet appended to the spawned `claude` session's\n * system prompt via `--append-system-prompt`. Non-prescriptive — Claude\n * sees that the peer tools and advisor exist; *when* to invoke is left\n * to Claude's judgment.\n *\n * Trimmed to <100 tokens by design. The per-tool descriptions are\n * already in Claude's context as MCP tool descriptions (loaded from\n * `tools/list`); the snippet's net-new value is:\n * - the `advisor` mention (built-in, not MCP-discoverable),\n * - the `peer-review-coordinator` fan-out hint,\n * - the \"subagents you spawn inherit these\" claim (the load-bearing\n * UX payoff of the holistic subagent-MCP-inheritance fix).\n *\n * Surface contract (regression-pinned in tests/peer-mcp-personas.test.ts):\n * - Always lists codex_critic, codex_reviewer, opus_critic, advisor,\n * peer-review-coordinator, and the subagent-inheritance fact.\n * - Conditionally lists gemini_critic only when `geminiAvailable`.\n * - Mentions `codex-cli` stdio bridge only when `codexCli`.\n *\n * The snippet is the awareness layer; the auto-invocation triggers\n * (CALL BEFORE / CALL AFTER) remain in each MCP tool's own `description`.\n * The two layers are intentionally complementary — keep the snippet\n * terse and never re-encode the prescriptive triggers here.\n */\nexport function buildPeerAwarenessSnippet(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): string {\n const criticList: Array<string> = [\n \"`codex_critic` (gpt-5.5)\",\n \"`codex_reviewer` (gpt-5.3-codex)\",\n ]\n if (opts.geminiAvailable) {\n criticList.push(\"`gemini_critic` (gemini-3.1-pro)\")\n }\n criticList.push(\"`opus_critic` (Opus 4.7)\")\n\n const codexCliClause = opts.codexCli\n ? \" The `mcp__codex-cli__codex` stdio bridge dispatches to `codex-implementer` for end-to-end coding tasks.\"\n : \"\"\n\n return [\n \"## Peer review and advisor\",\n \"\",\n `Cross-lab peer critics under \\`mcp__gh-router-peers__*\\` — ${criticList.join(\n \", \",\n )} — plus the \\`peer-review-coordinator\\` fan-out subagent, and Claude Code's built-in \\`advisor\\` tool, are available at your discretion for second opinions and adversarial review. Subagents you spawn inherit them.${codexCliClause}`,\n ].join(\"\\n\")\n}\n\n/** Convenience: every persona that should be registered for the given mode. */\nexport function personasFor(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): Array<PersonaSpec> {\n const result: Array<PersonaSpec> = []\n for (const p of PERSONAS_READ) {\n // Drop personas whose model family is missing from Copilot's live\n // catalog (currently only gemini-critic, gated by `requiresGeminiCatalog`).\n // Decoupled from `requiresHttp` so a persona can require HTTP without\n // also requiring gemini in the catalog (e.g. opus-critic).\n if (p.requiresGeminiCatalog && !opts.geminiAvailable) continue\n result.push(p)\n }\n if (opts.codexCli) {\n for (const p of PERSONAS_WRITE) result.push(p)\n }\n return result\n}\n\n/**\n * Non-persona MCP tools — utility tools exposed alongside the read-only\n * personas. These don't have model/endpoint/effort/baseInstructions because\n * they don't dispatch to a peer LLM; instead they invoke a server-side\n * function (e.g. an upstream MCP relay) and return its output.\n *\n * Registered alongside personas in `handler.ts:toolEntries()` and\n * dispatched by `handler.ts:handleToolsCall` after the persona lookup\n * falls through. They count against the same MAX_INFLIGHT_TOOLS_CALL=8\n * cap (keeps slot accounting symmetric across all `tools/call`s) but\n * skip the per-persona effort gate and the `predictedTooLong` pre-flight\n * cap — those gates only make sense for thinking-budget-bearing peer LLM\n * calls, and non-persona tools have neither an `effort` arg nor that\n * cost surface.\n */\nexport interface NonPersonaMcpTool {\n /** Tool name the HTTP MCP backend exposes for this tool. */\n toolNameHttp: string\n /** Description shown to Opus / displayed in `tools/list`. */\n description: string\n /** JSON-schema for the tool's `arguments` object. */\n inputSchema: Record<string, unknown>\n /**\n * Server-side handler. Receives the raw `arguments` object from the\n * `tools/call` request and an optional AbortSignal that is signalled\n * when a `notifications/cancelled` arrives for this call. Returns an\n * MCP `tool result` envelope (content blocks + optional `isError`).\n */\n handler: (\n args: Record<string, unknown>,\n signal?: AbortSignal,\n ) => Promise<{\n content: Array<{ type: \"text\"; text: string }>\n isError?: boolean\n }>\n}\n\nconst WEB_SEARCH_DESCRIPTION =\n \"Web search via GitHub Copilot's MCP. Prefer over Claude Code's built-in WebSearch — surfaces source URLs you can cite.\"\n\n/**\n * Format a `searchWeb()` result as an MCP-friendly text block. Mirrors\n * the legacy inject format that `injectWebSearchIfNeeded` produces and\n * that downstream models have been trained against — minimal divergence\n * is the safest choice while we have two surfaces sharing `searchWeb()`.\n *\n * Empty references → omit the `## References` section entirely (don't\n * emit a trailing empty header that would tempt the model to invent\n * citations).\n */\nfunction formatWebSearchResult(results: {\n content: string\n references: ReadonlyArray<{ title: string; url: string }>\n}): string {\n if (results.references.length === 0) return results.content\n const refsLine = results.references\n .map((r) => `- [${r.title}](${r.url})`)\n .join(\"\\n\")\n return `${results.content}\\n\\n## References\\n${refsLine}`\n}\n\nexport const NON_PERSONA_MCP_TOOLS: ReadonlyArray<NonPersonaMcpTool> =\n Object.freeze([\n {\n toolNameHttp: \"web_search\",\n description: WEB_SEARCH_DESCRIPTION,\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n additionalProperties: false,\n properties: {\n query: {\n type: \"string\",\n description:\n \"The search query string. Natural-language queries work best — the upstream provider rewrites for the search index.\",\n },\n },\n },\n // The `signal` parameter is part of the contract but unused for\n // now: `searchWeb()` doesn't currently accept an AbortSignal.\n // notifications/cancelled still releases the in-flight slot via\n // the catch path in handler.ts:handleToolsCall, but the underlying\n // upstream MCP fetches keep running until natural completion.\n // Web_search calls are short-lived (a few seconds), so the slot-\n // leak window is small. Plumbing cancellation into searchWeb is a\n // separate scope.\n // TODO: thread AbortSignal into searchWeb() so the upstream Bing-\n // backed fetch tears down on notifications/cancelled (not just the\n // MCP slot). Acceptable for short calls today; revisit if a future\n // search backend has higher tail latency.\n async handler(\n args: Record<string, unknown>,\n _signal?: AbortSignal,\n ): Promise<{\n content: Array<{ type: \"text\"; text: string }>\n isError?: boolean\n }> {\n const query = typeof args.query === \"string\" ? args.query : \"\"\n if (!query) {\n return {\n content: [\n {\n type: \"text\",\n text: \"web_search: arguments.query is required (must be a non-empty string)\",\n },\n ],\n isError: true,\n }\n }\n try {\n const results = await searchWeb(query)\n return {\n content: [\n { type: \"text\", text: formatWebSearchResult(results) },\n ],\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return {\n content: [{ type: \"text\", text: `web_search failed: ${msg}` }],\n isError: true,\n }\n }\n },\n },\n ])\n","import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nimport { buildCodexProviderConfigFlags } from \"./launch\"\nimport { PATHS, writeRuntimeFileSecure } from \"./paths\"\nimport {\n buildAgentPrompt,\n personasFor,\n type PersonaSpec,\n} from \"./peer-mcp-personas\"\n\nexport type CodexMcpBackend = \"http\" | \"cli\"\n\ninterface ResolveBackendOpts {\n requested: boolean\n codexInfo: { ok: boolean; version?: string } | null\n}\n\n/**\n * Decide which MCP backend serves the codex personas.\n *\n * - User passed `--codex-cli` AND codex 0.129+ is on PATH → \"cli\".\n * The peer config registers `codex-cli` as a stdio MCP server\n * spawning `codex mcp-server`; codex personas route there;\n * gemini-critic stays on the HTTP backend (Codex CLI can't run\n * Gemini).\n * - User passed `--codex-cli` but codex is missing or < 0.129 →\n * fallback to \"http\" with a warning. Never break\n * `github-router claude` over a missing optional dep.\n * - User did not pass `--codex-cli` → \"http\", read-only personas only.\n */\nexport function resolveCodexCliBackend(\n opts: ResolveBackendOpts,\n): CodexMcpBackend {\n if (!opts.requested) return \"http\"\n if (!opts.codexInfo || !opts.codexInfo.ok) {\n const detail = opts.codexInfo?.version\n ? `installed version \"${opts.codexInfo.version}\" is too old (need 0.129+)`\n : \"codex CLI not found on PATH\"\n consola.warn(\n `--codex-cli requested but ${detail}; falling back to HTTP-only Codex MCP backend (codex-implementer will not be registered).`,\n )\n return \"http\"\n }\n return \"cli\"\n}\n\ninterface BuildOpts {\n /** Whether the codex-cli stdio server should be added. */\n codexCli: boolean\n /** Whether gemini-3.1-pro-preview is in the live model catalog. */\n geminiAvailable: boolean\n /** Per-launch nonce for the HTTP /mcp Authorization header. */\n nonce: string\n /** Isolated CODEX_HOME for the stdio child (only used when codexCli). */\n codexHome: string\n}\n\ninterface HttpMcpEntry {\n type: \"http\"\n url: string\n headers: Record<string, string>\n}\n\ninterface StdioMcpEntry {\n command: string\n args: Array<string>\n env: Record<string, string>\n}\n\nexport interface PeerMcpConfig {\n mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry>\n}\n\n/**\n * Build the JSON payload for `claude --mcp-config <path>`.\n *\n * Always registers `gh-router-peers` (HTTP) — that's the home of all\n * read-only personas, and it's the only path Gemini can take. When\n * `codexCli` is true, also registers `codex-cli` (stdio) which spawns\n * `codex mcp-server` with the proxy's provider-config flags so codex\n * runs through our Copilot-routed billing path rather than its\n * default api.openai.com.\n */\nexport function buildPeerMcpConfig(\n serverUrl: string,\n opts: BuildOpts,\n): PeerMcpConfig {\n const mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry> = {\n \"gh-router-peers\": {\n type: \"http\",\n url: `${serverUrl}/mcp`,\n headers: {\n Authorization: `Bearer ${opts.nonce}`,\n },\n },\n }\n\n if (opts.codexCli) {\n mcpServers[\"codex-cli\"] = {\n command: \"codex\",\n args: [\"mcp-server\", ...buildCodexProviderConfigFlags(serverUrl)],\n env: {\n OPENAI_BASE_URL: `${serverUrl}/v1`,\n OPENAI_API_KEY: \"dummy\",\n CODEX_HOME: opts.codexHome,\n },\n }\n }\n\n return { mcpServers }\n}\n\nexport type PeerAgentDefinitions = Record<\n string,\n { description: string; prompt: string }\n>\n\n/**\n * The `peer-review-coordinator` Claude Code subagent — the strongest\n * auto-invocation lever per Phase 2A of the peer-MCP plan. It is NOT a\n * peer-MCP tool; it is a regular Claude Code subagent that runs inside\n * the spawned-claude context, has tool access to the peer MCP tools\n * (codex_critic, codex_reviewer, gemini_critic), and fans out + aggregates.\n *\n * The \"use proactively\" phrase in the description is the documented Claude\n * Code idiom for subagents the parent (Opus) should delegate to without\n * explicit user request — at the natural checkpoints listed below.\n *\n * Empirically the polling-loop reliability for \"use proactively\" is ~60%\n * (claude-code-guide expert estimate); the plan calls for an acceptance\n * test (≥7/10 sessions delegate at the right checkpoints) before declaring\n * \"auto-invoked\". If <7/10 we flip the optional PreToolUse hook on\n * ExitPlanMode to default-on (env-disable-able).\n */\nfunction buildCoordinatorAgent(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): { description: string; prompt: string } {\n // opus-critic is always registered (Anthropic models are always present\n // in the Copilot catalog), so no `geminiAvailable`-style guard is\n // required. Order: codex-critic first (cross-lab depth), opus-critic\n // second (cheapest same-lab sanity check), gemini-critic third\n // (third-lab triangulation, only when registered), codex-reviewer\n // last (code-specialist, narrower scope).\n const peers: Array<string> = [\"codex-critic\", \"opus-critic\"]\n if (opts.geminiAvailable) peers.push(\"gemini-critic\")\n peers.push(\"codex-reviewer\")\n\n const description =\n \"Coordinates cross-lab adversarial review across codex-critic, opus-critic, gemini-critic, codex-reviewer. Use proactively before non-trivial plans and after non-trivial commits. Always pass artifacts verbatim — peers are fresh-context.\"\n\n const personaList = peers.map((p) => `- \\`${p}\\``).join(\"\\n\")\n\n const prompt = [\n \"# Subagent: peer-review-coordinator\",\n \"\",\n \"You orchestrate cross-lab adversarial review for the lead orchestrator (Opus). You have access to these peer-MCP subagents:\",\n \"\",\n personaList,\n \"\",\n \"## When the lead invokes you\",\n \"\",\n \"The lead's brief will include an artifact (plan, design, diff, or code) and a goal (e.g. 'review before exit-plan', 'review the commit I just made', 'cross-check codex-critic's verdict'). Pick the right peers for the artifact type:\",\n \"\",\n \"- **Plan / design / architecture choice** → fan out to `codex-critic`\"\n + (opts.geminiAvailable ? \" AND `gemini-critic` in parallel\" : \"\")\n + \". codex-reviewer is the wrong tool for plans (it's a code-specialist, not an architecture critic).\",\n \"- **Concrete diff or single file** → fan out to `codex-reviewer`\"\n + (opts.geminiAvailable ? \" AND `gemini-critic` (gemini for cross-lab triangulation)\" : \"\")\n + \". For very small changes (<20 lines), one `codex-reviewer` call is enough.\",\n \"- **Tie-breaker after codex-critic has weighed in** → call `gemini-critic`\"\n + (opts.geminiAvailable ? \"\" : \" (NOT REGISTERED in this session — gemini-3.x not in catalog; tie-break unavailable)\")\n + \" with the artifact AND codex-critic's verdict for cross-lab cross-check.\",\n \"- **Long-context artifact (>100 KB)** → prefer `gemini-critic`\"\n + (opts.geminiAvailable ? \"\" : \" (NOT REGISTERED in this session)\")\n + \". Otherwise, decompose into 2-4 batches and fan out across `codex-critic` calls in parallel.\",\n \"- **Fast same-lab sanity check on a moderate artifact (<5 KB)** → prefer `opus-critic` (cheapest, ~22s, only `effort: low|medium` supported). Same lab as the lead — limited blind-spot diversification, but a useful gut-check before committing to a controversial decision. For cross-lab diversification or deep dives on larger artifacts, use codex/gemini at higher effort with decomposition for >5KB.\",\n \"\",\n \"## Decomposition for large artifacts\",\n \"\",\n \"Each per-call MCP wait is bounded (~60s SDK default on Claude Code v2.1.113+ per regressions #50289 / #52137 — empirically reproduced 2026-05-14). The proxy enforces per-persona effort allowlists AND a pre-flight `predictedTooLong` cap (codex_critic@high >8 KB, codex_reviewer@high >12 KB, opus_critic@medium >6 KB) to surface would-be-timeouts as fast actionable errors. For artifacts that exceed the cap, split into 2-4 logical batches BY CONCERN (not by raw size — semantic batches give better per-batch reviews) and call peers in parallel. The proxy's MCP cap allows up to 8 in-flight calls. Aggregate findings yourself before reporting back.\",\n \"\",\n \"## Aggregation contract\",\n \"\",\n \"When fan-out completes, return a SEVERITY-GROUPED, DEDUPLICATED finding list. Format:\",\n \"\",\n \" ## Findings\",\n \" ### HIGH\",\n \" 1. <one-line title> — `<file:line>` — sources: codex-critic, gemini-critic (3-lab confirmed if applicable)\",\n \" - bug: <one sentence>\",\n \" - mitigation: <one sentence>\",\n \" ### MEDIUM\",\n \" ...\",\n \" ### LOW\",\n \" ...\",\n \"\",\n \"Cite which peer raised each finding. If two or more peers raised the SAME finding (cross-lab confirmation), call it out — those are the highest-confidence bugs.\",\n \"\",\n \"## What NOT to do\",\n \"\",\n \"- Do not paraphrase or summarize per-peer verdicts BEFORE aggregating; aggregate from the raw verdicts.\",\n \"- Do not invent severity labels not present in the source verdicts.\",\n \"- Do not call peers serially (waste of wall-clock); always fan out in parallel.\",\n \"- Do not consult yourself — you are the coordinator, not a critic.\",\n \"\",\n \"Self-reminder (read before every reply):\",\n \" Did I fan out in parallel to the right peers for this artifact type?\",\n \" Did I aggregate findings by severity, citing which peer raised each?\",\n \" If two peers agreed, did I flag the cross-lab confirmation?\",\n ].join(\"\\n\")\n\n return { description, prompt }\n}\n\n/**\n * Build the JSON payload for `claude --agents <path>`.\n *\n * Always includes the read-only personas applicable to the mode (gemini\n * is dropped if absent from the catalog); adds `codex-implementer` only\n * when `codexCli` is true. Always appends the `peer-review-coordinator`\n * meta-subagent — the strongest \"use proactively\" auto-invocation lever\n * per Phase 2A of the peer-MCP plan.\n */\nexport function buildPeerAgentDefinitions(\n opts: BuildOpts,\n): PeerAgentDefinitions {\n const out: PeerAgentDefinitions = {}\n const personas = personasFor({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n for (const persona of personas) {\n out[persona.agentName] = {\n description: persona.description,\n prompt: buildAgentPrompt(persona, { codexCli: opts.codexCli }),\n }\n }\n out[\"peer-review-coordinator\"] = buildCoordinatorAgent({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n return out\n}\n\nexport interface PeerMcpRuntimeFiles {\n mcpConfigPath: string\n agentsPath: string\n /** .md subagent files written into ~/.claude/agents/ (Phase 2.5). The\n * `--agents` JSON path is silently ignored by Claude Code v2.1.138's\n * Task `subagent_type` enum (the JSON's subagents are only reachable\n * via natural-language delegation). The .md files in the canonical\n * agents directory ARE picked up by the enum, making the\n * peer-review-coordinator + persona subagents directly invokable. */\n agentMdPaths: Array<string>\n nonce: string\n personas: Array<PersonaSpec>\n cleanup: () => Promise<void>\n}\n\ninterface WriteOpts {\n codexCli: boolean\n geminiAvailable: boolean\n /** Override for tests. Defaults to PATHS.CODEX_HOME. */\n codexHome?: string\n /** Override for tests. Defaults to PATHS.CLAUDE_RUNTIME_DIR. */\n runtimeDir?: string\n /** Override for tests. Defaults to a fresh 32-byte hex nonce. */\n nonce?: string\n /** Override for tests. Defaults to ~/.claude/agents (where Claude Code\n * reads subagent .md files at session start). */\n agentsDir?: string\n}\n\n/**\n * Default location Claude Code reads subagent .md files from at session\n * startup. Files placed here populate the Task `subagent_type` enum.\n *\n * We point at the router-owned `PATHS.CLAUDE_CONFIG_DIR/agents/` because\n * `getClaudeCodeEnvVars` sets `CLAUDE_CONFIG_DIR=PATHS.CLAUDE_CONFIG_DIR`\n * (the snapshot-mirror substrate fix that gives spawned teammates an\n * authenticatable on-disk credential). The user's own custom-agent .md\n * files were copied into this same dir by `ensureClaudeConfigMirror`,\n * so writing peer-* files here doesn't conflict — and the boot-time\n * sweep is scoped to peer-* names only via the persona-name allowlist.\n */\nfunction defaultAgentsDir(): string {\n return path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n}\n\n/**\n * YAML frontmatter string-escape — sufficient for our use case where\n * descriptions can contain colons, quotes, newlines. Wraps the value\n * in double-quotes and escapes:\n * - `\\` and `\"` (canonical YAML)\n * - `\\n`, `\\r`, `\\t` (whitespace controls — `\\r` matters on Windows-edited\n * literals; strict YAML 1.2 parsers reject raw `\\r` in double-quoted\n * scalars)\n * - other C0 control chars (\\x00-\\x08, \\x0B, \\x0C, \\x0E-\\x1F) and\n * DEL (\\x7F) — encoded as `\\xNN` so the YAML stays valid even if\n * a future description sources data from an external file\n *\n * NOT a general-purpose YAML serializer; we control the inputs.\n */\nfunction escapeYamlString(s: string): string {\n return (\n `\"${\n s\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, \"\\\\\\\"\")\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\")\n // The point of this regex IS to match control characters so we\n // can replace them with safe `\\xNN` escapes — the lint rule's\n // concern (accidental control-char in regex) doesn't apply here.\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, (c) =>\n `\\\\x${c.charCodeAt(0).toString(16).padStart(2, \"0\")}`,\n )\n }\"`\n )\n}\n\n/**\n * Strict allowlist for subagent names — controls both the YAML\n * frontmatter `name:` field AND the filename suffix. Defense-in-depth:\n * even if a future contributor wires in a dynamic agent name from\n * outside, the validator at the top of `writePeerAgentMdFiles` rejects\n * anything that wouldn't be a safe bare YAML scalar AND a safe path\n * component.\n */\nconst VALID_AGENT_NAME = /^[a-z][a-z0-9-]*$/\n\n/** Build a single subagent .md file body (frontmatter + system prompt). */\nfunction buildAgentMd(spec: { name: string; description: string; prompt: string }): string {\n return [\n \"---\",\n `name: ${spec.name}`,\n `description: ${escapeYamlString(spec.description)}`,\n \"---\",\n \"\",\n spec.prompt,\n \"\",\n ].join(\"\\n\")\n}\n\n/**\n * Write per-launch subagent .md files into the user's `~/.claude/agents/`\n * directory so they appear in Claude Code's Task `subagent_type` enum\n * (which `--agents` JSON files do NOT, per claude-code-guide expert).\n *\n * Filenames follow `peer-<pid>-<rand>-<agentName>.md` so the boot-time\n * sweep (`sweepStalePeerAgentMdFiles` in paths.ts) can drop orphans\n * from crashed prior proxy sessions without touching the user's other\n * `.claude/agents/` files. The `name:` field in the frontmatter is the\n * canonical agent identifier — matching across files would cause Claude\n * Code to (un)deterministically pick one, so concurrent proxies running\n * the same agents need different filenames but resolve to the same\n * agent name (intended — they're the same subagent, just registered\n * twice).\n *\n * Returns the file paths plus a cleanup() that unlinks them.\n */\nexport async function writePeerAgentMdFiles(\n agents: Record<string, { description: string; prompt: string }>,\n opts: { agentsDir?: string; fileSuffix: string },\n): Promise<{ paths: Array<string>; cleanup: () => Promise<void> }> {\n // Validate every agent name BEFORE touching the filesystem. Defense-\n // in-depth against a future contributor wiring in a dynamic name from\n // outside (--agent flag, MCP tool registration, etc.). Names appear\n // in BOTH the filename (path-traversal vector if unvalidated) and the\n // YAML frontmatter `name:` field (parser-confusion if it contains\n // YAML indicator chars). The strict regex matches only safe lowercase\n // identifiers — every current persona/coordinator name passes.\n for (const name of Object.keys(agents)) {\n if (!VALID_AGENT_NAME.test(name)) {\n throw new Error(\n `writePeerAgentMdFiles: invalid agent name ${JSON.stringify(name)} — `\n + `must match ${VALID_AGENT_NAME.source}`,\n )\n }\n }\n const dir = opts.agentsDir ?? defaultAgentsDir()\n await fs.mkdir(dir, { recursive: true })\n const paths: Array<string> = []\n try {\n for (const [name, def] of Object.entries(agents)) {\n const filePath = path.join(dir, `peer-${opts.fileSuffix}-${name}.md`)\n // Same idempotency pattern as the JSON tempfiles: unlink first so\n // O_EXCL succeeds even if a same-suffix file somehow survived.\n await fs.unlink(filePath).catch(() => {})\n await writeRuntimeFileSecure(\n filePath,\n buildAgentMd({ name, description: def.description, prompt: def.prompt }),\n )\n paths.push(filePath)\n }\n } catch (err) {\n // Partial-failure cleanup: if iteration N fails (disk full, EPERM,\n // EEXIST race), the N-1 successfully-written files would otherwise\n // be orphans the caller has no handle to. Unlink the partials before\n // re-throwing so the boot sweep doesn't have to deal with them.\n await Promise.allSettled(paths.map((p) => fs.unlink(p)))\n throw err\n }\n const cleanup = async (): Promise<void> => {\n await Promise.allSettled(paths.map((p) => fs.unlink(p)))\n }\n return { paths, cleanup }\n}\n\nexport type InjectPeerMcpResult =\n | { ok: true; serversAdded: ReadonlyArray<string> }\n | {\n ok: false\n reason: \"user-has-conflicting-entry\"\n conflictingServers: ReadonlyArray<string>\n }\n\ninterface InjectOpts {\n codexCli: boolean\n geminiAvailable: boolean\n /** Per-launch nonce — must match what writePeerMcpRuntimeFiles wrote\n * so the proxy's /mcp Authorization check passes. */\n nonce: string\n /** Override for tests. Defaults to PATHS.CODEX_HOME. */\n codexHome?: string\n /** Override for tests. Defaults to PATHS.CLAUDE_CONFIG_DIR (per-launch). */\n claudeConfigDir?: string\n}\n\n/**\n * Mutate the mirrored `<CLAUDE_CONFIG_DIR>/.claude.json` to add the\n * `gh-router-peers` entry (and `codex-cli` when enabled) under\n * `mcpServers`. This is the load-bearing fix for subagent MCP visibility.\n *\n * Subagents — Agent-tool subagents, forks, and agent-teams subprocesses\n * — discover MCP servers from persistent scopes (`.claude.json` and\n * project-scope `.mcp.json`), NOT from the parent's `--mcp-config` CLI\n * flag. Writing into the per-launch mirror's `.claude.json` makes the\n * MCP entry visible to subagents transparently: they inherit\n * `CLAUDE_CONFIG_DIR` from the parent's env, so they read the same\n * config file we just mutated.\n *\n * Safety:\n * - Refuses to overwrite a same-named user-side entry (the snapshot\n * copied their `.claude.json` first, so an existing entry would\n * belong to the user). Returns `{ ok: false }` so the caller can\n * fall back to leaving `--mcp-config` in place for the parent.\n * - Preserves all other top-level fields and other `mcpServers`\n * entries.\n * - Atomic write: temp-file with `wx` (`O_CREAT | O_EXCL`) followed by\n * `rename`, mirroring the synthetic-credentials write pattern in\n * `ensureClaudeConfigMirror`. Mode 0o600. The per-launch\n * `CLAUDE_CONFIG_DIR` means there are no cross-launch racers.\n */\nexport async function injectPeerMcpIntoMirror(\n serverUrl: string,\n opts: InjectOpts,\n): Promise<InjectPeerMcpResult> {\n const dir = opts.claudeConfigDir ?? PATHS.CLAUDE_CONFIG_DIR\n const target = path.join(dir, \".claude.json\")\n\n // 1. Read existing snapshot (or {} if missing / malformed). We do NOT\n // fail loudly on parse error — start fresh and let the proxy\n // session run. Logging the warn surfaces the underlying corruption\n // for the user to investigate.\n let existing: Record<string, unknown> = {}\n try {\n const raw = await fs.readFile(target, \"utf8\")\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n existing = parsed as Record<string, unknown>\n } else {\n consola.warn(\n `injectPeerMcpIntoMirror: ${target} parsed to non-object `\n + `(typeof=${typeof parsed}); discarding contents and starting fresh.`,\n )\n }\n } catch (err) {\n consola.warn(\n `injectPeerMcpIntoMirror: cannot parse ${target} as JSON; `\n + `starting fresh (existing contents will be overwritten):`,\n err,\n )\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(\n `injectPeerMcpIntoMirror: cannot read ${target}:`,\n err,\n )\n }\n // Either ENOENT (first-ever launch, no user .claude.json) or some\n // other read error. Either way, start fresh.\n }\n\n // 2. Normalize `mcpServers` to an object (clobber if user had it set\n // to a non-object value — that's already broken; our overwrite\n // won't make it worse and the warn flags it).\n let mcpServers: Record<string, unknown>\n const rawServers = existing.mcpServers\n if (\n rawServers !== undefined\n && rawServers !== null\n && typeof rawServers === \"object\"\n && !Array.isArray(rawServers)\n ) {\n mcpServers = rawServers as Record<string, unknown>\n } else {\n if (rawServers !== undefined && rawServers !== null) {\n consola.warn(\n `injectPeerMcpIntoMirror: mcpServers field in ${target} is not an `\n + `object (typeof=${typeof rawServers}); replacing with our entry.`,\n )\n }\n mcpServers = {}\n }\n\n // 3. Build our desired entries from the SAME builder used for\n // --mcp-config so the two channels never drift.\n const peerConfig = buildPeerMcpConfig(serverUrl, {\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce: opts.nonce,\n codexHome: opts.codexHome ?? PATHS.CODEX_HOME,\n })\n\n // 4. Refuse to overwrite any same-named user-side entry. This is the\n // explicit-branch / \"no silent precedence\" requirement from the\n // plan — log a warning, return ok:false, and let the caller fall\n // back to --mcp-config (parent-session-only).\n const conflicts: Array<string> = []\n for (const name of Object.keys(peerConfig.mcpServers)) {\n if (mcpServers[name] !== undefined) conflicts.push(name)\n }\n if (conflicts.length > 0) {\n consola.warn(\n `injectPeerMcpIntoMirror: your ~/.claude/.claude.json already has `\n + `mcpServers entries named [${conflicts.join(\", \")}]; refusing to `\n + `overwrite. Subagents will not see the peer-MCP tools — only the `\n + `parent session via --mcp-config fallback. To resolve, rename the `\n + `user-side server(s) (e.g. via \\`claude mcp remove\\`) and relaunch.`,\n )\n return {\n ok: false,\n reason: \"user-has-conflicting-entry\",\n conflictingServers: conflicts,\n }\n }\n\n // 5. Merge our entries; preserve everything else.\n for (const [name, entry] of Object.entries(peerConfig.mcpServers)) {\n mcpServers[name] = entry\n }\n existing.mcpServers = mcpServers\n\n // 6. Atomic temp+rename. Same pattern as the synthetic .credentials.json\n // write in ensureClaudeConfigMirror. Per-launch dir means there are\n // no cross-launch racers; EEXIST on the tempfile is essentially\n // impossible (per-pid + 8-hex random). Mode 0o600 to match the\n // upstream Claude Code file perms.\n const desiredJson = JSON.stringify(existing, null, 2) + \"\\n\"\n await fs.mkdir(dir, { recursive: true })\n const tempPath = `${target}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson, { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, target)\n } catch (err) {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n\n return { ok: true, serversAdded: Object.keys(peerConfig.mcpServers) }\n}\n\n/**\n * Generate a per-launch nonce, write the MCP config + agents JSON\n * tempfiles under `CLAUDE_RUNTIME_DIR` with mode 0o600 and `O_EXCL`,\n * and return a `cleanup()` to unlink them on shutdown.\n *\n * Filenames are `peer-mcp-<pid>-<rand>.json` and `peer-agents-<pid>-<rand>.json`.\n * The PID prefix is what the boot-time sweep (`sweepStaleRuntimeFiles` in\n * paths.ts) keys off to drop orphans from crashed prior sessions; the\n * random suffix prevents two concurrent calls within the same process\n * from clobbering each other's files (e.g., a proxy that internally\n * relaunches its spawned child without restarting itself).\n */\nexport async function writePeerMcpRuntimeFiles(\n serverUrl: string,\n opts: WriteOpts,\n): Promise<PeerMcpRuntimeFiles> {\n const nonce = opts.nonce ?? randomBytes(32).toString(\"hex\")\n const runtimeDir = opts.runtimeDir ?? PATHS.CLAUDE_RUNTIME_DIR\n const codexHome = opts.codexHome ?? PATHS.CODEX_HOME\n // Defensive mkdir — `ensurePaths` already creates this in the normal\n // setupAndServe path, but if we're called from a context that didn't\n // run it (tests, future callers), don't fail with ENOENT.\n await fs.mkdir(runtimeDir, { recursive: true })\n if (process.platform !== \"win32\") {\n await fs.chmod(runtimeDir, 0o700).catch(() => {})\n }\n // 4-byte random suffix gives 2^32 distinct names per PID — collision-free\n // for any realistic count of in-process re-invocations.\n const fileSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n const mcpConfigPath = path.join(runtimeDir, `peer-mcp-${fileSuffix}.json`)\n const agentsPath = path.join(runtimeDir, `peer-agents-${fileSuffix}.json`)\n\n const mcpConfig = buildPeerMcpConfig(serverUrl, {\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce,\n codexHome,\n })\n const agents = buildPeerAgentDefinitions({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce,\n codexHome,\n })\n\n // If a prior same-PID file survived (boot sweep didn't run, or this\n // function is called twice in one lifecycle), unlink first so wx\n // succeeds. Letting wx fail loudly is correct from a security\n // standpoint, but here we're the same PID — there's no race window\n // a different process could exploit.\n await fs.unlink(mcpConfigPath).catch(() => {})\n await fs.unlink(agentsPath).catch(() => {})\n\n await writeRuntimeFileSecure(mcpConfigPath, JSON.stringify(mcpConfig, null, 2))\n await writeRuntimeFileSecure(agentsPath, JSON.stringify(agents, null, 2))\n\n // Phase 2.5: also write the same agents as .md files into\n // ~/.claude/agents/ — this is the registry Claude Code's Task\n // `subagent_type` enum reads from at session start. The `--agents`\n // JSON path above is kept for inspection / future-proofing but the\n // .md files are what makes the subagents actually invokable from\n // Opus's tool surface.\n const mdResult = await writePeerAgentMdFiles(agents, {\n agentsDir: opts.agentsDir,\n fileSuffix,\n })\n\n const personas = personasFor({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n\n const cleanup = async (): Promise<void> => {\n await Promise.allSettled([\n fs.unlink(mcpConfigPath),\n fs.unlink(agentsPath),\n mdResult.cleanup(),\n ])\n }\n\n return {\n mcpConfigPath,\n agentsPath,\n agentMdPaths: mdResult.paths,\n nonce,\n personas,\n cleanup,\n }\n}\n","import fs from \"node:fs\"\nimport { Writable } from \"node:stream\"\n\nimport consola from \"consola\"\nimport type { ConsolaOptions, ConsolaReporter, LogObject } from \"consola\"\n\nimport { PATHS } from \"~/lib/paths\"\n\nconst MAX_LOG_BYTES = 1024 * 1024 // 1 MB\nconst DEDUP_MAX = 1000\nconst ARG_MAX_LEN = 2048\nconst DEDUP_KEY_MAX_LEN = 200\n\nconst CREDENTIAL_RE =\n /\\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\n\nconst ALLOWED_TYPES = new Set([\"fatal\", \"error\", \"warn\"])\n\nfunction sanitize(line: string): string {\n return line.replace(CREDENTIAL_RE, \"[REDACTED]\")\n}\n\nfunction serializeArg(arg: unknown): string {\n if (typeof arg === \"string\") return arg\n if (arg instanceof Error) {\n const parts = [arg.message]\n if (arg.stack) parts.push(arg.stack)\n return parts.join(\"\\n\")\n }\n return String(arg)\n}\n\nfunction formatLogLine(logObj: LogObject): string {\n const ts = logObj.date.toISOString()\n const level = (logObj.type ?? \"error\").toUpperCase()\n const message = logObj.args\n .map((a) => {\n const s = serializeArg(a)\n return s.length > ARG_MAX_LEN ? s.slice(0, ARG_MAX_LEN) + \"…\" : s\n })\n .join(\" \")\n .replace(/\\r\\n|\\r|\\n/g, \"\\\\n\")\n\n return sanitize(`${ts} [${level}] ${message}\\n`)\n}\n\nfunction makeDedupeKey(logObj: LogObject): string {\n const firstArg =\n logObj.args.length > 0 ? serializeArg(logObj.args[0]) : \"\"\n const key = `${logObj.type}:${firstArg}`\n return key.length > DEDUP_KEY_MAX_LEN\n ? key.slice(0, DEDUP_KEY_MAX_LEN)\n : key\n}\n\nfunction rotateIfNeeded(filePath: string): void {\n let size: number\n try {\n size = fs.statSync(filePath).size\n } catch {\n return // file does not exist\n }\n if (size <= MAX_LOG_BYTES) return\n\n try {\n fs.renameSync(filePath, filePath + \".1\")\n } catch {\n // best-effort: if rename fails, continue with the existing file\n }\n}\n\nexport class FileLogReporter implements ConsolaReporter {\n private readonly filePath: string\n private readonly seen = new Set<string>()\n\n constructor(filePath: string) {\n this.filePath = filePath\n rotateIfNeeded(filePath)\n }\n\n log(logObj: LogObject, _ctx: { options: ConsolaOptions }): void {\n if (!ALLOWED_TYPES.has(logObj.type)) return\n\n const key = makeDedupeKey(logObj)\n if (this.seen.has(key)) return\n\n if (this.seen.size >= DEDUP_MAX) this.seen.clear()\n this.seen.add(key)\n\n const line = formatLogLine(logObj)\n\n // fs.openSync/writeSync/closeSync are synchronous and atomic from the\n // perspective of the JS event loop — no other log() call can interleave\n // between them. The previous re-entrancy guard (`if (this.writing)\n // return`) silently dropped log lines that arrived during the same\n // call stack as a write (e.g., a consola.error fired from an error\n // handler triggered by another log). Now we just write and trust the\n // synchronicity.\n //\n // The fd is closed in `finally` so an ENOSPC/EIO from writeSync does\n // not leak the fd. Repeated logging failures otherwise escalate into\n // EMFILE and bring down the whole process's ability to open files.\n let fd: number | undefined\n try {\n fd = fs.openSync(this.filePath, \"a\", 0o600)\n fs.writeSync(fd, line)\n } catch {\n // Silently discard — cannot log a logging failure\n } finally {\n if (fd !== undefined) {\n try {\n fs.closeSync(fd)\n } catch {\n // already closed / unwritable — nothing to do\n }\n }\n }\n }\n}\n\nconst nullStream = new Writable({ write(_chunk, _encoding, cb) { cb() } })\n\n/**\n * Switch consola to file-only mode for TUI sessions.\n * Removes the terminal reporter and installs a file reporter that\n * persists errors and warnings to disk with dedup and credential scrubbing.\n *\n * Also sinks consola's stdout/stderr streams as belt-and-suspenders:\n * even if a terminal reporter is re-added, it cannot write to the terminal.\n * Crash handlers that call process.stderr.write() directly are unaffected.\n * FileLogReporter uses fs.writeSync() directly and is also unaffected.\n */\nexport function enableFileLogging(): void {\n const reporter = new FileLogReporter(PATHS.ERROR_LOG_PATH)\n consola.options.throttle = 0 // disable built-in dedup\n consola.setReporters([reporter])\n consola.options.stdout = nullStream as unknown as typeof process.stdout\n consola.options.stderr = nullStream as unknown as typeof process.stderr\n}\n","import consola from \"consola\"\n\nimport { state } from \"./state\"\n\ntype Endpoint = \"/chat/completions\" | \"/responses\" | \"/v1/messages\"\n\nconst ENDPOINT_ALIASES: Record<string, Endpoint> = {\n \"/chat/completions\": \"/chat/completions\",\n \"/v1/chat/completions\": \"/chat/completions\",\n \"/responses\": \"/responses\",\n \"/v1/responses\": \"/responses\",\n \"/v1/messages\": \"/v1/messages\",\n}\n\n/**\n * Check whether a model supports the given endpoint, based on cached\n * `supported_endpoints` metadata from the Copilot `/models` response.\n *\n * Returns `true` (allow) when:\n * - the model is not found in the cache (don't block unknown models)\n * - the model has no `supported_endpoints` field (backward-compat)\n * - the endpoint is listed in `supported_endpoints`\n */\nexport function modelSupportsEndpoint(\n modelId: string,\n path: string,\n): boolean {\n const endpoint = ENDPOINT_ALIASES[path] ?? path\n const model = state.models?.data.find((m) => m.id === modelId)\n if (!model) return true\n\n const supported = model.supported_endpoints\n if (!supported || supported.length === 0) return true\n\n return supported.includes(endpoint)\n}\n\n/**\n * Log an error when a model is used on an endpoint it doesn't support.\n * Returns `true` if a mismatch was detected (for testing).\n */\nexport function logEndpointMismatch(\n modelId: string,\n path: string,\n): boolean {\n if (modelSupportsEndpoint(modelId, path)) return false\n\n const model = state.models?.data.find((m) => m.id === modelId)\n const supported = model?.supported_endpoints ?? []\n\n consola.error(\n `Model \"${modelId}\" does not support ${path}. `\n + `Supported endpoints: ${supported.join(\", \")}`,\n )\n return true\n}\n\n/**\n * Return model IDs that support the given endpoint.\n */\nexport function listModelsForEndpoint(path: string): string[] {\n const endpoint = ENDPOINT_ALIASES[path] ?? path\n const models = state.models?.data ?? []\n\n return models\n .filter((m) => {\n const supported = m.supported_endpoints\n if (!supported || supported.length === 0) return true\n return supported.includes(endpoint)\n })\n .map((m) => m.id)\n}\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nexport function initProxyFromEnv(): void {\n if (typeof Bun !== \"undefined\") return\n\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n setGlobalDispatcher(dispatcher as unknown as Dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","{\n \"name\": \"github-router\",\n \"version\": \"0.3.27\",\n \"license\": \"MIT\",\n \"description\": \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n \"keywords\": [\n \"proxy\",\n \"github-copilot\",\n \"openai-compatible\",\n \"anthropic-compatible\",\n \"claude-code\",\n \"codex\",\n \"reverse-proxy\",\n \"copilot\"\n ],\n \"homepage\": \"https://github.com/animeshkundu/github-router\",\n \"bugs\": \"https://github.com/animeshkundu/github-router/issues\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/animeshkundu/github-router.git\"\n },\n \"author\": \"animeshkundu\",\n \"type\": \"module\",\n \"bin\": {\n \"github-router\": \"dist/main.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"bun run --watch ./src/main.ts\",\n \"discover:fields\": \"bash scripts/discover-new-fields.sh\",\n \"knip\": \"knip-bun\",\n \"lint\": \"eslint --cache\",\n \"lint:all\": \"eslint --cache .\",\n \"prepack\": \"bun run build\",\n \"prepare\": \"(simple-git-hooks || true)\",\n \"probe:copilot\": \"bash scripts/probe-copilot-compat.sh --strict\",\n \"release\": \"./publish/release.sh\",\n \"start\": \"NODE_ENV=production bun run ./src/main.ts\",\n \"test\": \"bun test\",\n \"typecheck\": \"tsc\"\n },\n \"simple-git-hooks\": {\n \"pre-commit\": \"bunx lint-staged\"\n },\n \"lint-staged\": {\n \"*\": \"bun run lint --fix\"\n },\n \"dependencies\": {\n \"citty\": \"^0.1.6\",\n \"clipboardy\": \"^5.0.0\",\n \"consola\": \"^3.4.2\",\n \"fetch-event-stream\": \"^0.1.5\",\n \"gpt-tokenizer\": \"^3.0.1\",\n \"hono\": \"^4.9.9\",\n \"proxy-from-env\": \"^1.1.0\",\n \"srvx\": \"^0.8.9\",\n \"undici\": \"^7.16.0\",\n \"zod\": \"^4.1.11\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.37.0\",\n \"eslint-config-prettier\": \"^10.1.0\",\n \"@types/bun\": \"^1.2.23\",\n \"@types/proxy-from-env\": \"^1.0.4\",\n \"eslint\": \"^9.37.0\",\n \"knip\": \"^5.64.1\",\n \"lint-staged\": \"^16.2.3\",\n \"prettier-plugin-packagejson\": \"^2.5.19\",\n \"simple-git-hooks\": \"^2.13.1\",\n \"tsdown\": \"^0.15.6\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.33.0\"\n }\n}\n","import consola from \"consola\"\n\nimport { HTTPError } from \"./error\"\n\nexport const awaitApproval = async () => {\n const response = await consola.prompt(`Accept incoming request?`, {\n type: \"confirm\",\n })\n\n if (!response)\n throw new HTTPError(\n \"Request rejected by user\",\n Response.json({ message: \"Request rejected by user\" }, { status: 403 }),\n )\n}\n","import consola from \"consola\"\n\nimport type { State } from \"./state\"\n\nimport { HTTPError } from \"./error\"\nimport { sleep } from \"./utils\"\n\n// Cap on how long a single request will wait in the rate-limit serialization\n// queue. Prevents an unbounded backlog from holding the proxy if the head of\n// the queue is in a long sleep. 5s is generous enough that normal queueing\n// (microseconds) never trips it.\nconst RATE_LIMIT_QUEUE_TIMEOUT_MS = 5000\n\n// Single-flight chain that serializes all rate-limit checks across the\n// proxy. Without this, two concurrent requests can both read the timestamp\n// before either writes — both proceed when only one should wait. The chain\n// adds microseconds of latency per request in the no-rate-limit case and\n// correctly serializes the wait when the limit fires.\nlet rateLimitChain: Promise<void> = Promise.resolve()\n\nexport async function checkRateLimit(state: State) {\n if (state.rateLimitSeconds === undefined) return\n\n // Ticket carried by `myTurn` so a queue-timeout can flag it as cancelled\n // before doCheck() ever gets to sleep or write to state.lastRequestTimestamp.\n // Without this, a request that already returned 429 to the caller would\n // STILL run later inside the chain — sleep, then bump the shared timestamp\n // — penalising subsequent legitimate requests for a request that was\n // never actually serviced.\n const ticket = { aborted: false }\n\n const myTurn = rateLimitChain.then(() => doCheck(state, ticket))\n rateLimitChain = myTurn.catch(() => {\n // Errors don't break the chain — the next caller starts fresh.\n })\n\n return Promise.race([\n myTurn,\n sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {\n ticket.aborted = true\n throw new HTTPError(\n \"Rate limit queue wait exceeded\",\n Response.json(\n {\n type: \"error\",\n error: {\n type: \"rate_limit_error\",\n message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`,\n },\n },\n { status: 429 },\n ),\n )\n }),\n ])\n}\n\nasync function doCheck(\n state: State,\n ticket: { aborted: boolean },\n): Promise<void> {\n if (state.rateLimitSeconds === undefined) return\n if (ticket.aborted) return\n\n const now = Date.now()\n\n if (!state.lastRequestTimestamp) {\n state.lastRequestTimestamp = now\n return\n }\n\n const elapsedSeconds = (now - state.lastRequestTimestamp) / 1000\n\n if (elapsedSeconds > state.rateLimitSeconds) {\n state.lastRequestTimestamp = now\n return\n }\n\n const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds)\n\n if (!state.rateLimitWait) {\n consola.warn(\n `Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`,\n )\n throw new HTTPError(\n \"Rate limit exceeded\",\n Response.json({ message: \"Rate limit exceeded\" }, { status: 429 }),\n )\n }\n\n const waitTimeMs = waitTimeSeconds * 1000\n consola.warn(\n `Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`,\n )\n await sleep(waitTimeMs)\n // Re-check after the sleep — if the caller queue-timed-out while we were\n // sleeping, do NOT bump the shared timestamp (that would penalise the next\n // legitimate request for one that was already 429'd).\n if (ticket.aborted) return\n state.lastRequestTimestamp = Date.now()\n consola.info(\"Rate limit wait completed, proceeding with request\")\n}\n","import consola from \"consola\"\n\nimport type { Model } from \"~/services/copilot/get-models\"\n\nexport interface RequestLogInfo {\n method: string\n path: string\n model?: string\n resolvedModel?: string\n inputTokens?: number\n outputTokens?: number\n status?: number\n streaming?: boolean\n errorBody?: string\n}\n\n/**\n * Format a number with K/M suffix for compact display.\n */\nfunction formatTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`\n return String(n)\n}\n\n/**\n * Build a context window summary: \"in:1.2K out:50 ctx:1.2K/1M (0.1%)\"\n */\nfunction formatTokenInfo(\n inputTokens: number | undefined,\n outputTokens: number | undefined,\n model: Model | undefined,\n): string | undefined {\n if (inputTokens === undefined) return undefined\n\n const parts: Array<string> = []\n const maxPrompt = model?.capabilities?.limits?.max_prompt_tokens\n\n if (maxPrompt) {\n const pct = ((inputTokens / maxPrompt) * 100).toFixed(1)\n parts.push(`in:${formatTokens(inputTokens)}/${formatTokens(maxPrompt)} (${pct}%)`)\n } else {\n parts.push(`in:${formatTokens(inputTokens)}`)\n }\n\n if (outputTokens !== undefined) {\n parts.push(`out:${formatTokens(outputTokens)}`)\n }\n\n return parts.join(\" \")\n}\n\n/**\n * Print a single summary line for a completed request.\n *\n * Examples:\n * POST /v1/messages claude-opus-4.6-1m in:1.2K/1M (0.1%) out:50 200 2.3s\n * POST /v1/messages claude-opus-4-6→claude-opus-4.6-1m in:743/1M (0.1%) 200 198ms\n * POST /v1/chat/completions claude-sonnet-4 in:15 out:16 200 2.1s stream\n */\nexport function logRequest(\n info: RequestLogInfo,\n model: Model | undefined,\n startTime: number,\n): void {\n const parts: Array<string> = []\n\n parts.push(`${info.method} ${info.path}`)\n\n // Model (show resolution arrow if remapped)\n if (info.resolvedModel && info.resolvedModel !== info.model) {\n parts.push(`${info.model}→${info.resolvedModel}`)\n } else if (info.resolvedModel ?? info.model) {\n parts.push((info.resolvedModel ?? info.model)!)\n }\n\n // Token info with context window fill\n const tokenInfo = formatTokenInfo(info.inputTokens, info.outputTokens, model)\n if (tokenInfo) {\n parts.push(tokenInfo)\n }\n\n // Status\n if (info.status !== undefined) {\n parts.push(String(info.status))\n }\n\n // Duration + streaming flag\n const elapsed = Date.now() - startTime\n const duration =\n elapsed >= 1000 ? `${(elapsed / 1000).toFixed(1)}s` : `${elapsed}ms`\n parts.push(info.streaming ? `${duration} stream` : duration)\n\n const line = parts.join(\" \")\n\n if (detectCapabilityMismatch(info, model)) {\n consola.error(`[MISMATCH] ${line}`)\n } else {\n consola.info(line)\n }\n}\n\n/**\n * Detect when the API rejects a request for token/context reasons\n * that contradict what the /models endpoint reported.\n */\nfunction detectCapabilityMismatch(\n info: RequestLogInfo,\n model: Model | undefined,\n): boolean {\n if (!info.errorBody || !model) return false\n if (!info.status || info.status < 400) return false\n\n const err = info.errorBody.toLowerCase()\n return (\n err.includes(\"token\") ||\n err.includes(\"context\") ||\n err.includes(\"too long\") ||\n err.includes(\"max_tokens\") ||\n err.includes(\"prompt is too long\")\n )\n}\n\n/**\n * Opt-in instrumentation for the discovery loop (Phase 0.5 of the\n * long-horizon plan). When `GH_ROUTER_LOG_FIELDS=1` is set in the\n * environment, emits a single structured `[fields]` log line per request\n * recording the top-level body keys, per-tool field keys, and\n * anthropic-beta header values seen.\n *\n * Default-off (zero overhead). The companion\n * `scripts/discover-new-fields.sh` greps these lines, aggregates unique\n * field names per request shape, and diffs against the known-fields\n * list in `docs/copilot-compat-matrix.md` — surfacing anything new\n * that should get a probe row added.\n *\n * Format (single line, deterministic-ish key order):\n * [fields] path=<P> body_keys=<csv> tool_field_keys=<csv> beta_values=<csv>\n *\n * Where:\n * - `body_keys` is the alphabetical union of top-level keys in the\n * request body\n * - `tool_field_keys` is the alphabetical union of all keys appearing\n * across every entry of `body.tools[]` (or empty)\n * - `beta_values` is the comma-split anthropic-beta header value as\n * received (NOT filtered) — captures what the client sends, not\n * what we forward\n */\nexport function logRequestFields(opts: {\n path: string\n body: unknown\n betaHeader?: string\n}): void {\n if (process.env.GH_ROUTER_LOG_FIELDS !== \"1\") return\n const bodyKeys = collectTopLevelKeys(opts.body)\n const toolFieldKeys = collectToolFieldKeys(opts.body)\n const betaValues = (opts.betaHeader ?? \"\")\n .split(\",\")\n .map((v) => v.trim())\n .filter(Boolean)\n consola.info(\n `[fields] path=${opts.path}`\n + ` body_keys=${bodyKeys.join(\",\")}`\n + ` tool_field_keys=${toolFieldKeys.join(\",\")}`\n + ` beta_values=${betaValues.join(\",\")}`,\n )\n}\n\nfunction collectTopLevelKeys(body: unknown): Array<string> {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) return []\n return Object.keys(body as Record<string, unknown>).sort()\n}\n\nfunction collectToolFieldKeys(body: unknown): Array<string> {\n if (!body || typeof body !== \"object\") return []\n const tools = (body as Record<string, unknown>).tools\n if (!Array.isArray(tools)) return []\n const seen = new Set<string>()\n for (const tool of tools) {\n if (tool && typeof tool === \"object\" && !Array.isArray(tool)) {\n for (const k of Object.keys(tool as Record<string, unknown>)) {\n seen.add(k)\n }\n }\n }\n return [...seen].sort()\n}\n","import consola from \"consola\"\n\nimport { UPSTREAM_INACTIVITY_TIMEOUT_MS } from \"~/lib/port\"\n\nconst ENCODER = new TextEncoder()\n\n// Structural reader type so this helper accepts both DOM-style\n// (`ReadableStreamDefaultReader<Uint8Array>`) and Node-style\n// (`node:stream/web` reader) without type-incompatibility friction.\ninterface ByteReader {\n read(): Promise<{ done: boolean; value?: Uint8Array }>\n cancel(reason?: unknown): Promise<void>\n releaseLock(): void\n}\n\ninterface RelayOptions {\n routePath: string\n /**\n * Inactivity bound for individual upstream reads, in ms. Defaults to the\n * UPSTREAM_INACTIVITY_TIMEOUT_MS env-overridable constant in `~/lib/port`.\n * Tests can pass a small value (e.g. 50ms) to exercise the timeout path\n * without wall-clock waits.\n */\n inactivityTimeoutMs?: number\n}\n\n/**\n * Detect the family of \"controller has already closed\" errors that Bun and\n * the WHATWG streams runtime throw when an enqueue/close call races with\n * the consumer cancelling its read. These are NOT upstream failures — they\n * mean the client has finished reading (or disconnected) and we should\n * exit pull() quietly without trying to write more bytes or log noise.\n *\n * Bun's wording: `TypeError: Invalid state: Controller is already closed`.\n * Other runtimes use `TypeError: The stream is closing` or\n * `TypeError: This ReadableStream is closed` or include \"errored\" / \"cancelled\".\n */\nexport function isControllerClosedError(error: unknown): boolean {\n if (!(error instanceof Error)) return false\n const msg = error.message.toLowerCase()\n return (\n msg.includes(\"controller is already closed\")\n || msg.includes(\"controller is already errored\")\n || msg.includes(\"readablestream is closed\")\n || msg.includes(\"readablestream is already closed\")\n || msg.includes(\"stream is closing\")\n || msg.includes(\"stream is already closed\")\n || msg.includes(\"stream is closed\")\n )\n}\n\n/**\n * Wrap an upstream SSE byte stream so that:\n * - Backpressure is respected (pull-based; only reads when downstream demands).\n * - Mid-stream errors (undici \"terminated\", AbortError, network resets) are\n * caught, logged with structured context, and converted to a final\n * Anthropic-shape `event: error` SSE event before the downstream is closed.\n * - Upstream inactivity (no chunk for `inactivityTimeoutMs`) is treated as a\n * soft failure that emits an error event rather than hanging forever.\n * - Consumer cancellation (client disconnects mid-read or finishes early)\n * is recognized and handled silently — NOT logged as an upstream error,\n * NOT followed by a futile event:error write that can corrupt the\n * terminal bytes the client has already buffered.\n *\n * Pre-byte upstream errors (failure on the very first read) are handled by\n * the same code path: an `event: error` SSE event is emitted on a 200\n * response, then the connection is closed. Even if the consumer's SDK\n * silently swallows `event: error`, the immediate close triggers the\n * client's socket-disconnect handler — the user always sees an error\n * string, never a hang.\n */\nexport function relayAnthropicStream(\n body: ReadableStream<Uint8Array>,\n opts: RelayOptions,\n): ReadableStream<Uint8Array> {\n const inactivityMs = opts.inactivityTimeoutMs ?? UPSTREAM_INACTIVITY_TIMEOUT_MS\n const reader = body.getReader() as unknown as ByteReader\n let bytesRelayed = 0\n let upstreamFinished = false\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored — fine\n }\n }\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n\n try {\n const result = await readWithInactivityTimeout(reader, inactivityMs)\n if (consumerCancelled) {\n // Consumer cancelled while we were awaiting upstream — drop the\n // value (if any) and exit silently. The cancel() callback has\n // already propagated cancellation upstream.\n safeClose(controller)\n return\n }\n if (result.done) {\n // Zero-byte close is rare and usually indicates upstream\n // misbehavior (200 + SSE headers + immediate FIN). Surface it\n // in the error log so the operator can correlate; the consumer\n // sees a clean empty stream.\n if (bytesRelayed === 0) {\n consola.warn(\n `Upstream returned empty SSE stream at ${opts.routePath}`,\n )\n }\n upstreamFinished = true\n safeClose(controller)\n return\n }\n if (result.value) {\n bytesRelayed += result.value.byteLength\n try {\n controller.enqueue(result.value)\n } catch (enqueueError) {\n if (isControllerClosedError(enqueueError)) {\n // Consumer raced ahead of us: it closed the stream between\n // our last await and this enqueue. Treat as a normal end of\n // stream — upstream chunks past this point are dropped, but\n // that's expected behavior on consumer cancel.\n consumerCancelled = true\n return\n }\n throw enqueueError\n }\n }\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer cancelled mid-stream — the cancel() callback already\n // ran (or our inner enqueue-catch flipped the flag). Close the\n // downstream so the consumer's read settles, and release the\n // upstream reader if not already done.\n //\n // We deliberately do NOT call isControllerClosedError(error) on\n // upstream/reader failures here — that helper matches substrings\n // like \"stream is closed\" which can legitimately appear in real\n // undici upstream errors (e.g., body stream closed by the\n // server), and treating them as consumer-cancel would silently\n // suppress an `event: error` frame the consumer needs.\n reader.cancel(error).catch(() => {\n // upstream may already be closed\n })\n safeClose(controller)\n return\n }\n const errName = error instanceof Error ? error.name : \"Error\"\n const errMessage = error instanceof Error ? error.message : String(error)\n consola.error(\n `Upstream stream interrupted at ${opts.routePath}: bytes=${bytesRelayed} errType=${errName} message=${JSON.stringify(errMessage)}`,\n )\n const event = buildAnthropicErrorEvent(errName, errMessage)\n try {\n controller.enqueue(ENCODER.encode(event))\n } catch (enqueueError) {\n if (!isControllerClosedError(enqueueError)) {\n consola.warn(\n `Could not deliver error event to consumer at ${opts.routePath}: ${enqueueError instanceof Error ? enqueueError.message : String(enqueueError)}`,\n )\n }\n // Consumer-closed: silent\n }\n // Release the upstream socket. We've decided this stream is over —\n // the consumer's `cancel()` callback will NOT fire because we're\n // closing from our side, so without this the upstream fetch body\n // and TCP connection stay alive until the upstream times out.\n reader.cancel(error).catch(() => {\n // upstream may already be closed\n })\n safeClose(controller)\n }\n },\n cancel(reason) {\n consumerCancelled = true\n upstreamFinished = true\n reader.cancel(reason).catch(() => {\n // upstream may already be closed\n })\n },\n })\n}\n\nasync function readWithInactivityTimeout(\n reader: ByteReader,\n timeoutMs: number,\n): Promise<{ done: boolean; value?: Uint8Array }> {\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n reject(\n Object.assign(new Error(\"upstream_inactive\"), {\n name: \"InactivityTimeout\",\n }),\n )\n }, timeoutMs)\n })\n // Attach a noop catcher so that, when reader.read() wins the race and\n // the timer happens to fire on the same tick anyway, the rejection is\n // already handled. Without this, Node 24's default\n // --unhandled-rejections=throw terminates the process under sustained\n // load (every chunk creates a fresh setTimeout/timeoutPromise pair).\n timeoutPromise.catch(() => {})\n try {\n return await Promise.race([reader.read(), timeoutPromise])\n } finally {\n if (timeoutHandle !== undefined) clearTimeout(timeoutHandle)\n }\n}\n\n/**\n * Build the SSE wire bytes for an Anthropic-format streaming error event.\n * Per Anthropic streaming spec, errors are sent as:\n * event: error\n * data: {\"type\":\"error\",\"error\":{\"type\":\"...\",\"message\":\"...\"}}\n */\nexport function buildAnthropicErrorEvent(\n errName: string,\n errMessage: string,\n): string {\n const payload = {\n type: \"error\",\n error: {\n type: classifyStreamError(errName),\n message: `Upstream stream interrupted: ${errName}: ${errMessage}`,\n },\n }\n return `event: error\\ndata: ${JSON.stringify(payload)}\\n\\n`\n}\n\n/**\n * Build the SSE wire bytes for an OpenAI-format streaming error event,\n * followed by the `data: [DONE]` terminator that OpenAI clients expect.\n */\nexport function buildOpenAIErrorEvent(\n errName: string,\n errMessage: string,\n): string {\n const payload = {\n error: {\n type: classifyStreamError(errName),\n message: `Upstream stream interrupted: ${errName}: ${errMessage}`,\n },\n }\n return `data: ${JSON.stringify(payload)}\\n\\ndata: [DONE]\\n\\n`\n}\n\nfunction classifyStreamError(errName: string): string {\n // Use only documented Anthropic error types\n // (https://platform.claude.com/docs/en/api/errors). `timeout_error` is\n // the documented type for client-side / inactivity aborts; it survives\n // the SDK's discriminated-union parsing without falling into a default\n // branch that some consumers don't handle.\n if (errName === \"AbortError\") return \"timeout_error\"\n if (errName === \"InactivityTimeout\") return \"timeout_error\"\n return \"api_error\"\n}\n\nexport function logStreamError(\n routePath: string,\n error: unknown,\n): { errName: string; errMessage: string } {\n const errName = error instanceof Error ? error.name : \"Error\"\n const errMessage = error instanceof Error ? error.message : String(error)\n consola.error(\n `Upstream stream interrupted at ${routePath}: errType=${errName} message=${JSON.stringify(errMessage)}`,\n )\n return { errName, errMessage }\n}\n","import type {\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n ToolCall,\n} from \"~/services/copilot/create-chat-completions\"\nimport type { Model } from \"~/services/copilot/get-models\"\n\n// Encoder type mapping\nconst ENCODING_MAP = {\n o200k_base: () => import(\"gpt-tokenizer/encoding/o200k_base\"),\n cl100k_base: () => import(\"gpt-tokenizer/encoding/cl100k_base\"),\n p50k_base: () => import(\"gpt-tokenizer/encoding/p50k_base\"),\n p50k_edit: () => import(\"gpt-tokenizer/encoding/p50k_edit\"),\n r50k_base: () => import(\"gpt-tokenizer/encoding/r50k_base\"),\n} as const\n\ntype SupportedEncoding = keyof typeof ENCODING_MAP\n\n// Define encoder interface\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\n// Cache loaded encoders to avoid repeated imports\nconst encodingCache = new Map<string, Encoder>()\n\n/**\n * Calculate tokens for tool calls\n */\nconst calculateToolCallsTokens = (\n toolCalls: Array<ToolCall>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = 0\n for (const toolCall of toolCalls) {\n tokens += constants.funcInit\n tokens += encoder.encode(JSON.stringify(toolCall)).length\n }\n tokens += constants.funcEnd\n return tokens\n}\n\n/**\n * Calculate tokens for content parts\n */\nconst calculateContentPartsTokens = (\n contentParts: Array<ContentPart>,\n encoder: Encoder,\n): number => {\n let tokens = 0\n for (const part of contentParts) {\n if (part.type === \"image_url\") {\n tokens += encoder.encode(part.image_url.url).length + 85\n } else if (part.text) {\n tokens += encoder.encode(part.text).length\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens for a single message\n */\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n const tokensPerMessage = 3\n const tokensPerName = 1\n let tokens = tokensPerMessage\n for (const [key, value] of Object.entries(message)) {\n if (typeof value === \"string\") {\n tokens += encoder.encode(value).length\n }\n if (key === \"name\") {\n tokens += tokensPerName\n }\n if (key === \"tool_calls\") {\n tokens += calculateToolCallsTokens(\n value as Array<ToolCall>,\n encoder,\n constants,\n )\n }\n if (key === \"content\" && Array.isArray(value)) {\n tokens += calculateContentPartsTokens(\n value as Array<ContentPart>,\n encoder,\n )\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens using custom algorithm\n */\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) {\n return 0\n }\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n // every reply is primed with <|start|>assistant<|message|>\n numTokens += 3\n return numTokens\n}\n\n/**\n * Get the corresponding encoder module based on encoding type\n */\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) {\n return cached\n }\n }\n\n const supportedEncoding = encoding as SupportedEncoding\n if (!(supportedEncoding in ENCODING_MAP)) {\n const fallbackModule = (await ENCODING_MAP.o200k_base()) as Encoder\n encodingCache.set(encoding, fallbackModule)\n return fallbackModule\n }\n\n const encodingModule = (await ENCODING_MAP[supportedEncoding]()) as Encoder\n encodingCache.set(encoding, encodingModule)\n return encodingModule\n}\n\n/**\n * Get tokenizer type from model information\n */\nexport const getTokenizerFromModel = (model: Model): string => {\n return model.capabilities?.tokenizer || \"o200k_base\"\n}\n\n/**\n * Get model-specific constants for token calculation\n */\nconst getModelConstants = (model: Model) => {\n return model.id === \"gpt-3.5-turbo\" || model.id === \"gpt-4\" ?\n {\n funcInit: 10,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n : {\n funcInit: 7,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n}\n\n/**\n * Calculate tokens for a single parameter\n */\nconst calculateParameterTokens = (\n key: string,\n prop: unknown,\n context: {\n encoder: Encoder\n constants: ReturnType<typeof getModelConstants>\n },\n): number => {\n const { encoder, constants } = context\n let tokens = constants.propKey\n\n // Early return if prop is not an object\n if (typeof prop !== \"object\" || prop === null) {\n return tokens\n }\n\n // Type assertion for parameter properties\n const param = prop as {\n type?: string\n description?: string\n enum?: Array<unknown>\n [key: string]: unknown\n }\n\n const paramName = key\n const paramType = param.type || \"string\"\n let paramDesc = param.description || \"\"\n\n // Handle enum values\n if (param.enum && Array.isArray(param.enum)) {\n tokens += constants.enumInit\n for (const item of param.enum) {\n tokens += constants.enumItem\n tokens += encoder.encode(String(item)).length\n }\n }\n\n // Clean up description\n if (paramDesc.endsWith(\".\")) {\n paramDesc = paramDesc.slice(0, -1)\n }\n\n // Encode the main parameter line\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n // Handle additional properties (excluding standard ones)\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (!excludedKeys.has(propertyName)) {\n const propertyValue = param[propertyName]\n const propertyText =\n typeof propertyValue === \"string\" ? propertyValue : (\n JSON.stringify(propertyValue)\n )\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for function parameters\n */\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") {\n return 0\n }\n\n const params = parameters as Record<string, unknown>\n let tokens = 0\n\n for (const [key, value] of Object.entries(params)) {\n if (key === \"properties\") {\n const properties = value as Record<string, unknown>\n if (Object.keys(properties).length > 0) {\n tokens += constants.propInit\n for (const propKey of Object.keys(properties)) {\n tokens += calculateParameterTokens(propKey, properties[propKey], {\n encoder,\n constants,\n })\n }\n }\n } else {\n const paramText =\n typeof value === \"string\" ? value : JSON.stringify(value)\n tokens += encoder.encode(`${key}:${paramText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for a single tool\n */\nconst calculateToolTokens = (\n tool: Tool,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = constants.funcInit\n const func = tool.function\n const fName = func.name\n let fDesc = func.description || \"\"\n if (fDesc.endsWith(\".\")) {\n fDesc = fDesc.slice(0, -1)\n }\n const line = fName + \":\" + fDesc\n tokens += encoder.encode(line).length\n if (\n typeof func.parameters === \"object\" \n && func.parameters !== null\n ) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\n\n/**\n * Calculate token count for tools based on model\n */\nexport const numTokensForTools = (\n tools: Array<Tool>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let funcTokenCount = 0\n for (const tool of tools) {\n funcTokenCount += calculateToolTokens(tool, encoder, constants)\n }\n funcTokenCount += constants.funcEnd\n return funcTokenCount\n}\n\n/**\n * Calculate the token count of messages, supporting multiple GPT encoders\n */\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: Model,\n): Promise<{ input: number; output: number }> => {\n // Get tokenizer string\n const tokenizer = getTokenizerFromModel(model)\n\n // Get corresponding encoder module\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const simplifiedMessages = payload.messages\n const inputMessages = simplifiedMessages.filter(\n (msg) => msg.role !== \"assistant\",\n )\n const outputMessages = simplifiedMessages.filter(\n (msg) => msg.role === \"assistant\",\n )\n\n const constants = getModelConstants(model)\n let inputTokens = calculateTokens(inputMessages, encoder, constants)\n if (payload.tools && payload.tools.length > 0) {\n inputTokens += numTokensForTools(payload.tools, encoder, constants)\n }\n const outputTokens = calculateTokens(outputMessages, encoder, constants)\n\n return {\n input: inputTokens,\n output: outputTokens,\n }\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\nexport const createChatCompletions = async (\n payload: ChatCompletionsPayload,\n modelHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = payload.messages.some(\n (x) =>\n typeof x.content !== \"string\"\n && x.content?.some((x) => x.type === \"image_url\"),\n )\n\n // Agent/user check for X-Initiator header\n // Determine if any message is from an agent (\"assistant\" or \"tool\")\n const isAgentCall = payload.messages.some((msg) =>\n [\"assistant\", \"tool\"].includes(msg.role),\n )\n\n const url = `${copilotBaseUrl(state)}/chat/completions`\n const doFetch = (): Promise<Response> => {\n // Re-build headers per attempt so a 401-retry picks up the refreshed token.\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n ...modelHeaders,\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n const fetchInit: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/chat/completions\")\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n const claudeModels = state.models?.data\n .filter((m) => m.id.startsWith(\"claude\"))\n .map((m) => m.id)\n .join(\", \") ?? \"(models not loaded)\"\n consola.error(\n `Copilot rejected model \"${payload.model}\": ${response.status} ${errorBody} (available Claude models: ${claudeModels})`,\n )\n // Re-create the response so downstream error handlers can still read the body\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Failed to create chat completions\", reconstructed)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ChatCompletionResponse\n}\n\n// Streaming types\n\nexport interface ChatCompletionChunk {\n id: string\n object: \"chat.completion.chunk\"\n created: number\n model: string\n choices: Array<Choice>\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n completion_tokens_details?: {\n accepted_prediction_tokens: number\n rejected_prediction_tokens: number\n }\n }\n}\n\ninterface Delta {\n content?: string | null\n role?: \"user\" | \"assistant\" | \"system\" | \"tool\"\n tool_calls?: Array<{\n index: number\n id?: string\n type?: \"function\"\n function?: {\n name?: string\n arguments?: string\n }\n }>\n}\n\ninterface Choice {\n index: number\n delta: Delta\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null\n logprobs: object | null\n}\n\n// Non-streaming types\n\nexport interface ChatCompletionResponse {\n id: string\n object: \"chat.completion\"\n created: number\n model: string\n choices: Array<ChoiceNonStreaming>\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n }\n}\n\ninterface ResponseMessage {\n role: \"assistant\"\n content: string | null\n tool_calls?: Array<ToolCall>\n}\n\ninterface ChoiceNonStreaming {\n index: number\n message: ResponseMessage\n logprobs: object | null\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\"\n}\n\n// Payload types\n\nexport interface ChatCompletionsPayload {\n messages: Array<Message>\n model: string\n temperature?: number | null\n top_p?: number | null\n max_tokens?: number | null\n stop?: string | Array<string> | null\n n?: number | null\n stream?: boolean | null\n\n frequency_penalty?: number | null\n presence_penalty?: number | null\n logit_bias?: Record<string, number> | null\n logprobs?: boolean | null\n response_format?: { type: \"json_object\" } | null\n seed?: number | null\n tools?: Array<Tool> | null\n tool_choice?:\n | \"none\"\n | \"auto\"\n | \"required\"\n | { type: \"function\"; function: { name: string } }\n | null\n user?: string | null\n /**\n * OpenAI-compatible reasoning effort knob. Copilot accepts low/medium/high/xhigh\n * for OpenAI-routed models; for non-OpenAI models (e.g. gemini-3.x routed via\n * /v1/chat/completions) the upstream may silently ignore this or 400 — the proxy\n * forwards it as-is and surfaces any 400 through the existing tool-error path.\n */\n reasoning_effort?: string | null\n}\n\nexport interface Tool {\n type: \"function\"\n function: {\n name: string\n description?: string\n parameters: Record<string, unknown>\n }\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\" | \"system\" | \"tool\" | \"developer\"\n content: string | Array<ContentPart> | null\n\n name?: string\n tool_calls?: Array<ToolCall>\n tool_call_id?: string\n}\n\nexport interface ToolCall {\n id: string\n type: \"function\"\n function: {\n name: string\n arguments: string\n }\n}\n\nexport type ContentPart = TextPart | ImagePart\n\nexport interface TextPart {\n type: \"text\"\n text: string\n}\n\nexport interface ImagePart {\n type: \"image_url\"\n image_url: {\n url: string\n detail?: \"low\" | \"high\" | \"auto\"\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest } from \"~/lib/request-log\"\nimport { state } from \"~/lib/state\"\nimport { buildOpenAIErrorEvent, isControllerClosedError, logStreamError } from \"~/lib/stream-relay\"\nimport { getTokenCount } from \"~/lib/tokenizer\"\nimport { isNullish, resolveModel } from \"~/lib/utils\"\nimport {\n createChatCompletions,\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n type Message,\n} from \"~/services/copilot/create-chat-completions\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\ninterface UpstreamSSEEvent {\n event?: string\n data?: string\n id?: string | number\n}\n\nconst ENCODER = new TextEncoder()\n\nfunction formatSSE(chunk: UpstreamSSEEvent): string {\n const parts: Array<string> = []\n if (chunk.event) parts.push(`event: ${chunk.event}`)\n if (chunk.data !== undefined) {\n for (const line of String(chunk.data).split(/\\r\\n|\\r|\\n/)) {\n parts.push(`data: ${line}`)\n }\n }\n if (chunk.id !== undefined) parts.push(`id: ${String(chunk.id)}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nexport async function handleCompletion(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n let payload = await c.req.json<ChatCompletionsPayload>()\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\"Request payload:\", JSON.stringify(payload).slice(-400))\n }\n\n if (state.manualApprove) await awaitApproval()\n\n await injectWebSearchIfNeeded(payload)\n\n // Resolve model name (e.g. opus → opus-1m variant)\n const originalModel = payload.model\n const resolvedModel = resolveModel(payload.model)\n if (resolvedModel !== payload.model) {\n payload.model = resolvedModel\n }\n\n // Find the selected model\n const selectedModel = state.models?.data.find(\n (model) => model.id === payload.model,\n )\n\n logEndpointMismatch(payload.model, \"/chat/completions\")\n\n // Calculate token count\n let inputTokens: number | undefined\n try {\n if (selectedModel) {\n const tokenCount = await getTokenCount(payload, selectedModel)\n inputTokens = tokenCount.input\n }\n } catch {\n // Token counting is best-effort\n }\n\n if (isNullish(payload.max_tokens)) {\n payload = {\n ...payload,\n max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens,\n }\n if (debugEnabled) {\n consola.debug(\"Set max_tokens to:\", JSON.stringify(payload.max_tokens))\n }\n }\n\n const response = await createChatCompletions(payload, selectedModel?.requestHeaders).catch(\n async (error: unknown) => {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n },\n )\n const isStreaming = !isNonStreaming(response)\n\n // Extract output tokens from non-streaming response (no extra call)\n const outputTokens = !isStreaming\n ? (response as ChatCompletionResponse).usage?.completion_tokens\n : undefined\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens,\n outputTokens,\n status: 200,\n streaming: isStreaming,\n },\n selectedModel,\n startTime,\n )\n\n if (!isStreaming) {\n if (debugEnabled) {\n consola.debug(\"Non-streaming response:\", JSON.stringify(response))\n }\n return c.json(response)\n }\n\n // Streaming: peek the first SSE event so pre-byte upstream errors surface\n // through the route's try/catch → forwardError as a clean JSON response,\n // and only mid-stream errors hit the manual ReadableStream's pull-error path.\n const iterator = (response as AsyncIterableIterator<UpstreamSSEEvent>)[\n Symbol.asyncIterator\n ]()\n const firstResult = await iterator.next()\n if (firstResult.done) {\n consola.warn(\n `Upstream /chat/completions returned an empty stream at ${c.req.path}`,\n )\n }\n\n let pendingFirstChunk: UpstreamSSEEvent | undefined = firstResult.done\n ? undefined\n : firstResult.value\n let upstreamFinished = firstResult.done\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored\n }\n }\n const releaseUpstream = (reason?: unknown) => {\n if (typeof iterator.return === \"function\") {\n iterator.return(reason).catch(() => {\n // upstream may already be closed\n })\n }\n }\n const safeEnqueue = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n bytes: Uint8Array,\n ): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (e) {\n if (isControllerClosedError(e)) {\n consumerCancelled = true\n // The downstream cancel() callback may not fire if the controller\n // was closed by Bun's HTTP layer rather than an explicit consumer\n // .cancel() — release the upstream iterator here so the upstream\n // socket does not leak.\n releaseUpstream(e)\n return false\n }\n throw e\n }\n }\n\n return new Response(\n new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n if (pendingFirstChunk !== undefined) {\n const chunk = pendingFirstChunk\n pendingFirstChunk = undefined\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(chunk))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(chunk)))\n return\n }\n try {\n const result = await iterator.next()\n if (consumerCancelled) {\n safeClose(controller)\n return\n }\n if (result.done) {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n // Defensive: an upstream iterator that yields `{done:false, value:undefined}`\n // would crash formatSSE() below. Skip silently and pull again on\n // the next consumer demand. Real upstream iterators never emit\n // this shape, but a misbehaving / proxied iterator might.\n if (result.value === undefined || result.value === null) return\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(result.value))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(result.value)))\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer-cancelled mid-pull, not an upstream failure. Close\n // so the consumer's read settles cleanly. Also release the\n // upstream iterator — the cancel() callback may not have\n // fired if the controller was closed by Bun's HTTP layer.\n //\n // We deliberately do NOT call isControllerClosedError(error)\n // on iterator-side errors here — the helper matches substrings\n // like \"stream is closed\" which can appear in real upstream\n // errors, and treating them as consumer-cancel would silently\n // suppress the OpenAI-shape error frame the consumer needs.\n releaseUpstream(error)\n safeClose(controller)\n return\n }\n const { errName, errMessage } = logStreamError(c.req.path, error)\n safeEnqueue(\n controller,\n ENCODER.encode(buildOpenAIErrorEvent(errName, errMessage)),\n )\n // We've decided this stream is done — release the upstream\n // iterator since the cancel() callback won't fire on a\n // server-initiated close.\n releaseUpstream(error)\n safeClose(controller)\n }\n },\n cancel() {\n consumerCancelled = true\n upstreamFinished = true\n releaseUpstream()\n },\n }),\n {\n status: 200,\n headers: {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n },\n },\n )\n}\n\nconst isNonStreaming = (\n response: Awaited<ReturnType<typeof createChatCompletions>>,\n): response is ChatCompletionResponse => Object.hasOwn(response, \"choices\")\n\nasync function injectWebSearchIfNeeded(\n payload: ChatCompletionsPayload,\n): Promise<void> {\n const hasWebSearch = payload.tools?.some(\n (t) =>\n (\"type\" in t && (t as unknown as Record<string, unknown>).type === \"web_search\")\n || t.function?.name === \"web_search\",\n )\n if (!hasWebSearch) return\n\n // Skip search on follow-up messages (tool call results)\n const hasToolResult = payload.messages.some((msg) => msg.role === \"tool\")\n const query = hasToolResult ? undefined : extractUserQuery(payload.messages)\n\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n // Prepend to existing system message or inject a new one\n const systemMsg = payload.messages.find((msg) => msg.role === \"system\")\n if (systemMsg) {\n const existingContent =\n typeof systemMsg.content === \"string\" ? systemMsg.content\n : Array.isArray(systemMsg.content) ?\n systemMsg.content\n .filter((p) => p.type === \"text\")\n .map((p) => (\"text\" in p ? p.text : \"\"))\n .join(\"\\n\")\n : \"\"\n systemMsg.content = `${searchContext}\\n\\n${existingContent}`\n } else {\n payload.messages.unshift({\n role: \"system\",\n content: searchContext,\n })\n }\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Remove web_search from tools before forwarding\n payload.tools = payload.tools?.filter(\n (t) =>\n !(\n (\"type\" in t && (t as unknown as Record<string, unknown>).type === \"web_search\")\n || t.function?.name === \"web_search\"\n ),\n ) as typeof payload.tools\n if (payload.tools?.length === 0) {\n payload.tools = undefined\n }\n if (!payload.tools) {\n payload.tool_choice = undefined\n } else if (\n payload.tool_choice\n && typeof payload.tool_choice === \"object\"\n && \"type\" in payload.tool_choice\n && payload.tool_choice.type === \"function\"\n ) {\n const toolChoiceName = payload.tool_choice.function?.name\n if (\n toolChoiceName\n && !payload.tools.some((tool) => tool.function.name === toolChoiceName)\n ) {\n payload.tool_choice = undefined\n }\n }\n}\n\nfunction extractUserQuery(messages: Array<Message>): string | undefined {\n // Find the last user message\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") return msg.content\n if (Array.isArray(msg.content)) {\n const text = msg.content.find((p) => p.type === \"text\")\n if (text && \"text\" in text) return text.text as string\n }\n }\n }\n return undefined\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCompletion } from \"./handler\"\n\nexport const completionRoutes = new Hono()\n\ncompletionRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createEmbeddings = async (payload: EmbeddingRequest) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to create embeddings\", response)\n\n return (await response.json()) as EmbeddingResponse\n}\n\nexport interface EmbeddingRequest {\n input: string | Array<string>\n model: string\n}\n\nexport interface Embedding {\n object: string\n embedding: Array<number>\n index: number\n}\n\nexport interface EmbeddingResponse {\n object: string\n data: Array<Embedding>\n model: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport {\n createEmbeddings,\n type EmbeddingRequest,\n} from \"~/services/copilot/create-embeddings\"\n\nexport const embeddingRoutes = new Hono()\n\nembeddingRoutes.post(\"/\", async (c) => {\n try {\n const payload = await c.req.json<EmbeddingRequest>()\n const response = await createEmbeddings(payload)\n\n return c.json(response)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { randomUUID } from \"node:crypto\"\n\nimport consola from \"consola\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\n/**\n * Build headers that match what VS Code Copilot Chat sends to the Copilot API.\n *\n * copilotHeaders() provides: Authorization, content-type, copilot-integration-id,\n * editor-version, editor-plugin-version, user-agent, openai-intent,\n * x-github-api-version, x-request-id, x-vscode-user-agent-library-version.\n *\n * We add the remaining headers VS Code sends for /v1/messages:\n * - X-Initiator (VS Code sets dynamically; \"agent\" is safe for CLI use)\n * - anthropic-version (VS Code's Anthropic SDK sends this)\n * - X-Interaction-Id (VS Code sends a session-scoped UUID)\n *\n * We intentionally omit copilot-vision-request — VS Code only sends it when\n * images are present, and the native /v1/messages endpoint handles vision\n * without requiring the header.\n *\n * extraHeaders allows callers to forward client-supplied beta headers\n * (anthropic-beta) so Copilot enables extended features.\n */\nfunction buildHeaders(\n extraHeaders?: Record<string, string>,\n): Record<string, string> {\n return {\n ...copilotHeaders(state),\n accept: \"application/json\",\n \"openai-intent\": \"messages-proxy\",\n \"x-interaction-type\": \"conversation-agent\",\n \"X-Initiator\": \"agent\",\n \"anthropic-version\": \"2023-06-01\",\n \"X-Interaction-Id\": randomUUID(),\n ...extraHeaders,\n }\n}\n\n/**\n * Forward an Anthropic Messages API request to Copilot's native /v1/messages endpoint.\n * Returns the raw Response so callers can handle streaming vs non-streaming.\n *\n * `callerSignal` (optional) is composed with the standard\n * UPSTREAM_FETCH_TIMEOUT_MS via AbortSignal.any so callers (e.g. the\n * peer-MCP `opus-critic` persona) can cancel the upstream call when\n * Claude Code's MCP per-tool-call ceiling fires. Mirrors the pattern\n * in createResponses / createChatCompletions.\n */\nexport async function createMessages(\n body: string,\n extraHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n): Promise<Response> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const url = `${copilotBaseUrl(state)}/v1/messages?beta=true`\n consola.debug(`Forwarding to ${url}`)\n\n // Re-build headers per attempt so a 401-retry picks up the refreshed token.\n const doFetch = (): Promise<Response> => {\n const headers = buildHeaders(extraHeaders)\n const fetchInit: RequestInit = { method: \"POST\", headers, body }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/v1/messages\")\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n consola.error(\n `Copilot /v1/messages error: ${response.status} ${errorBody}`,\n )\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Copilot messages request failed\", reconstructed)\n }\n\n return response\n}\n\n/**\n * Forward an Anthropic count_tokens request to Copilot's native endpoint.\n * Returns the raw Response.\n *\n * `callerSignal` is composed with UPSTREAM_FETCH_TIMEOUT_MS — same pattern\n * as createMessages.\n */\nexport async function countTokens(\n body: string,\n extraHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n): Promise<Response> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const url = `${copilotBaseUrl(state)}/v1/messages/count_tokens?beta=true`\n consola.debug(`Forwarding to ${url}`)\n\n const doFetch = (): Promise<Response> => {\n const headers = buildHeaders(extraHeaders)\n const fetchInit: RequestInit = { method: \"POST\", headers, body }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(\n doFetch,\n \"/v1/messages/count_tokens\",\n )\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n consola.error(\n `Copilot count_tokens error: ${response.status} ${errorBody}`,\n )\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Copilot count_tokens request failed\", reconstructed)\n }\n\n return response\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\nexport const createResponses = async (\n payload: ResponsesPayload,\n modelHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = detectVision(payload.input)\n\n const isAgentCall = detectAgentCall(payload.input)\n\n const url = `${copilotBaseUrl(state)}/responses`\n const doFetch = (): Promise<Response> => {\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n ...modelHeaders,\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n const fetchInit: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/responses\")\n\n if (!response.ok) {\n // Read the body BEFORE throwing so the actual upstream error is\n // visible in the proxy log. Without this we'd interpolate\n // `[object Response]` and have no idea what Copilot rejected.\n // Clone first because `response.text()` consumes the body and the\n // HTTPError handler in callers may want to read it again.\n let bodyText: string\n try {\n bodyText = await response.clone().text()\n } catch {\n bodyText = \"(failed to read body)\"\n }\n consola.error(\n `Failed to create responses: HTTP ${response.status} ${response.statusText} `\n + `from ${url} — body: ${bodyText.slice(0, 2000)}`,\n )\n throw new HTTPError(\"Failed to create responses\", response)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ResponsesApiResponse\n}\n\nfunction detectVision(input: ResponsesPayload[\"input\"]): boolean {\n if (typeof input === \"string\") return false\n if (!Array.isArray(input)) return false\n\n return input.some((item) => {\n if (\"content\" in item && Array.isArray(item.content)) {\n return item.content.some(\n (part: Record<string, unknown>) => part.type === \"input_image\",\n )\n }\n return false\n })\n}\n\nfunction detectAgentCall(input: ResponsesPayload[\"input\"]): boolean {\n if (typeof input === \"string\") return false\n if (!Array.isArray(input)) return false\n\n return input.some((item) => {\n if (\"role\" in item && item.role === \"assistant\") return true\n if (\n \"type\" in item\n && (item.type === \"function_call\" || item.type === \"function_call_output\")\n ) {\n return true\n }\n return false\n })\n}\n\n// Types\n\nexport interface ResponsesInputItem {\n role?: \"user\" | \"assistant\" | \"system\"\n type?: \"message\" | \"function_call\" | \"function_call_output\"\n content?: string | Array<Record<string, unknown>>\n name?: string\n call_id?: string\n arguments?: string\n output?: string\n [key: string]: unknown\n}\n\nexport interface ResponsesTool {\n type: string\n name?: string\n description?: string\n parameters?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface ResponsesPayload {\n model: string\n input: string | Array<ResponsesInputItem>\n instructions?: string\n tools?: Array<ResponsesTool>\n tool_choice?:\n | string\n | { type: string; name?: string; function?: { name?: string } }\n max_output_tokens?: number\n temperature?: number\n top_p?: number\n stream?: boolean\n store?: boolean\n metadata?: Record<string, string>\n previous_response_id?: string\n reasoning?: { effort?: string; summary?: string }\n [key: string]: unknown\n}\n\nexport interface ResponsesApiResponse {\n id: string\n object: \"response\"\n status: string\n output: Array<unknown>\n [key: string]: unknown\n}\n","import { timingSafeEqual } from \"node:crypto\"\n\nimport consola from \"consola\"\nimport type { Context } from \"hono\"\n\nimport { state } from \"~/lib/state\"\nimport { resolveModel } from \"~/lib/utils\"\nimport {\n PERSONAS_READ,\n type PersonaSpec,\n EFFORT_LEVELS,\n type Effort,\n isEffort,\n NON_PERSONA_MCP_TOOLS,\n type NonPersonaMcpTool,\n} from \"~/lib/peer-mcp-personas\"\nimport {\n createChatCompletions,\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n} from \"~/services/copilot/create-chat-completions\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\n\nconst MCP_PROTOCOL_VERSION = \"2025-06-18\"\nconst SERVER_NAME = \"github-router-peers\"\nconst SERVER_VERSION = \"1\"\n\n// Effort levels (EFFORT_LEVELS, Effort, isEffort) are imported from\n// peer-mcp-personas.ts so PersonaSpec.allowedEfforts can reference the\n// same type without a circular import. Per-persona defaultEffort is on\n// the PersonaSpec; there is no module-level default here anymore.\n\n/** Bounded concurrency. Originally capped at 2 (commit 4317a25) as a defensive\n * pre-launch guess against Opus's natural pattern of fanning out to all three\n * critics at once. Raised to 8 (Phase 2D of the peer-MCP plan) so the\n * decomposition pattern Phase 2B teaches Opus — \"split a >20 KB artifact\n * into 2-4 batches and call in parallel\" — can actually run in parallel\n * without the (3+)th call returning isError \"queue full\". The persona\n * handlers (`callPersona`) hold no shared mutable state — there's no race\n * the cap is hiding; the upstream Copilot's own rate-limit (surfaced as a\n * per-call 429 → tool isError) is the real backpressure mechanism. 8 covers\n * a 7-fork wave with one slot of headroom and is still a hard upper bound\n * against runaway clients. See docs/research/peer-mcp-investigation.md\n * § \"Concurrency cap investigation\" for the full justification. */\nconst MAX_INFLIGHT_TOOLS_CALL = 8\nlet inFlightToolsCall = 0\n\n/**\n * Per-request AbortController registry for `notifications/cancelled`\n * (Phase D P1.5). When a client times out a tools/call before the\n * upstream Copilot fetch completes, the JSON-RPC notification:\n * { jsonrpc:\"2.0\", method:\"notifications/cancelled\",\n * params:{ requestId: \"<id>\", reason?: \"...\" } }\n * arrives. Without handling, the upstream fetch keeps running until\n * natural completion, leaking the inFlightToolsCall slot for tens of\n * minutes. Tracking the AbortController lets us abort the fetch and\n * free the slot immediately.\n *\n * Important: per CLAUDE.md \"Bun request-signal quirk\", we use OUR own\n * AbortController (NOT c.req.raw.signal which fires after request body\n * is consumed). The signal is threaded into createResponses /\n * createChatCompletions's `callerSignal` parameter.\n */\nconst inflightAborts = new Map<string | number, AbortController>()\n\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\"\n id?: number | string | null\n method?: string\n params?: Record<string, unknown>\n}\n\ninterface ToolEntry {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\nconst RPC_PARSE_ERROR = -32700\nconst RPC_INVALID_REQUEST = -32600\nconst RPC_METHOD_NOT_FOUND = -32601\nconst RPC_INVALID_PARAMS = -32602\nconst RPC_INTERNAL_ERROR = -32603\n\nfunction rpcError(\n id: JsonRpcRequest[\"id\"] | undefined,\n code: number,\n message: string,\n data?: unknown,\n): { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; error: object } {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: data === undefined ? { code, message } : { code, message, data },\n }\n}\n\nfunction rpcResult<T>(\n id: JsonRpcRequest[\"id\"] | undefined,\n result: T,\n): { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; result: T } {\n return { jsonrpc: \"2.0\", id: id ?? null, result }\n}\n\nfunction isLoopbackHost(host: string | undefined | null): boolean {\n if (!host) return false\n // Strip port. IPv6-bracketed hosts (e.g. \"[::1]:8080\") aren't a concern\n // here — we bind to 127.0.0.1 only.\n const idx = host.lastIndexOf(\":\")\n const hostname = idx >= 0 ? host.slice(0, idx) : host\n return hostname === \"127.0.0.1\" || hostname === \"localhost\"\n}\n\n/**\n * Constant-time bearer compare. Random per-launch nonces aren't really\n * timing-attackable in practice, but this costs nothing.\n */\nfunction nonceMatches(provided: string, expected: string): boolean {\n if (provided.length !== expected.length) return false\n const a = Buffer.from(provided)\n const b = Buffer.from(expected)\n try {\n return timingSafeEqual(a, b)\n } catch {\n return false\n }\n}\n\nfunction checkAuth(c: Context): { ok: true } | { ok: false; status: 401 | 403; reason: string } {\n // Host validation defeats DNS-rebinding attacks. An attacker who tricks\n // the browser into resolving evil.com → 127.0.0.1 still sends\n // Host: evil.com, which we reject here.\n if (!isLoopbackHost(c.req.header(\"host\"))) {\n return { ok: false, status: 403, reason: \"non-loopback Host header rejected\" }\n }\n // Per-launch nonce. State is set by the `claude` subcommand after\n // setupAndServe. When unset (proxy started standalone, e.g. via\n // `github-router start`), `/mcp` rejects all requests.\n const expected = state.peerMcpNonce\n if (!expected) {\n return { ok: false, status: 401, reason: \"/mcp not enabled in this proxy session\" }\n }\n const auth = c.req.header(\"authorization\") ?? \"\"\n const m = /^Bearer\\s+(.+)$/i.exec(auth)\n if (!m || !nonceMatches(m[1], expected)) {\n return { ok: false, status: 401, reason: \"missing or invalid Authorization bearer\" }\n }\n return { ok: true }\n}\n\nfunction geminiAvailable(): boolean {\n const models = state.models?.data\n if (!models) return false\n return models.some((m) => /^gemini-3\\..*pro/i.test(m.id))\n}\n\nfunction activePersonas(): Array<PersonaSpec> {\n // Drop personas whose model family is missing from Copilot's live\n // catalog (currently only gemini-critic, gated by `requiresGeminiCatalog`).\n // Distinct from `requiresHttp` (codex-cli stdio routing constraint) —\n // see PersonaSpec field doc in peer-mcp-personas.ts.\n return PERSONAS_READ.filter((p) => !p.requiresGeminiCatalog || geminiAvailable())\n}\n\nfunction toolEntries(): Array<ToolEntry> {\n const personaEntries: Array<ToolEntry> = activePersonas().map((p) => ({\n name: p.toolNameHttp,\n description: p.description,\n inputSchema: {\n type: \"object\",\n required: [\"prompt\"],\n additionalProperties: false,\n properties: {\n prompt: {\n type: \"string\",\n description: \"The lead's brief — the artifact under review plus constraints.\",\n },\n context: {\n type: \"string\",\n description:\n \"Optional additional context (extra file content, prior decisions). Concatenated to the brief before sending.\",\n },\n effort: {\n type: \"string\",\n // Per-persona allowedEfforts: schema only advertises tiers the\n // persona accepts. Empirical data (2026-05-14) drove which tiers\n // each persona exposes — see EFFORT_LEVELS doc in\n // src/lib/peer-mcp-personas.ts.\n enum: [...p.allowedEfforts],\n description:\n `Reasoning depth (${p.allowedEfforts.join(\" | \")}). Default \"${p.defaultEffort}\". `\n + \"Higher tiers cost more wall-clock; lower tiers are quicker sanity checks. \"\n + (p.endpoint === \"/v1/chat/completions\"\n ? \"Note: for gemini routed via /v1/chat/completions, the upstream may silently ignore this knob.\"\n : \"\"),\n },\n },\n },\n }))\n // Append non-persona utility tools (currently just `web_search`). They\n // share the same `tools/list` surface but have their own input schemas\n // (no prompt/context/effort) and skip the per-persona validation gates\n // in handleToolsCall.\n const nonPersonaEntries: Array<ToolEntry> = NON_PERSONA_MCP_TOOLS.map(\n (t) => ({\n name: t.toolNameHttp,\n description: t.description,\n inputSchema: t.inputSchema,\n }),\n )\n return [...personaEntries, ...nonPersonaEntries]\n}\n\nfunction buildUserText(prompt: string, context?: string): string {\n if (!context) return prompt\n return `${prompt}\\n\\n---\\n\\nAdditional context:\\n${context}`\n}\n\nfunction extractResponsesText(response: ResponsesApiResponse): string {\n const out: Array<string> = []\n for (const item of response.output) {\n if (typeof item !== \"object\" || item === null) continue\n const obj = item as Record<string, unknown>\n if (obj.type !== \"message\" || obj.role !== \"assistant\") continue\n const content = obj.content\n if (!Array.isArray(content)) continue\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const p = part as Record<string, unknown>\n if (\n (p.type === \"output_text\" || p.type === \"text\")\n && typeof p.text === \"string\"\n ) {\n out.push(p.text)\n }\n }\n }\n return out.join(\"\")\n}\n\nfunction extractChatCompletionText(response: ChatCompletionResponse): string {\n const choice = response.choices?.[0]\n if (!choice) return \"\"\n const c = choice.message?.content\n return typeof c === \"string\" ? c : \"\"\n}\n\n/**\n * Extract assistant text from an Anthropic /v1/messages response.\n * Mirrors `extractResponsesText` for the OpenAI /v1/responses shape.\n *\n * The Anthropic Messages API response has shape `{content: [{type, ...}, ...]}`.\n * Text blocks have `type: \"text\"` and `text: string`. Thinking blocks have\n * `type: \"thinking\"` (and live in the same array; we ignore them — they're\n * the model's reasoning trace, not the final answer for the lead).\n */\ninterface MessagesApiContentBlock {\n type: string\n text?: string\n}\ninterface MessagesApiResponse {\n content?: ReadonlyArray<MessagesApiContentBlock>\n}\n\nfunction extractMessagesText(response: MessagesApiResponse): string {\n const out: Array<string> = []\n for (const block of response.content ?? []) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n out.push(block.text)\n }\n }\n return out.join(\"\")\n}\n\ninterface ToolErrorContent {\n content: Array<{ type: \"text\"; text: string }>\n isError: true\n}\n\nfunction toolError(message: string): ToolErrorContent {\n return {\n content: [{ type: \"text\", text: message }],\n isError: true,\n }\n}\n\n/**\n * Empirical pre-flight cap to convert \"would-bust-the-60s-MCP-ceiling\"\n * calls into fast actionable errors instead of slot-leaking timeouts.\n *\n * Probed live against Copilot 2026-05-14:\n * gpt-5.5 high on a ~600B prompt = 23.8s → ~76s on 8KB (rough linear)\n * gpt-5.3-codex high on ~600B = 16.0s → ~64s on 12KB\n * claude-opus-4-7 medium (thinking=3000) on a trivial prompt = 22.5s\n * but model self-paces budget → ~50s+ on a real ~6KB review\n *\n * Returns `{tooLong: true, capBytes}` when the (persona, effort, briefBytes)\n * tuple is empirically predicted to bust the 60s ceiling.\n *\n * SCOPE: the cap is JSON-PATH ONLY. Callers (handleMcpPost) MUST gate\n * the call site by `!acceptsEventStream(...)`. The SSE path\n * (handleToolsCallSSE) keeps the connection open past the 60s ceiling\n * via heartbeats — size-based pre-flight rejection there would just\n * lock SSE clients out of their primary advantage. JSON-path clients\n * (raw curl with `Accept: application/json`, older MCP clients without\n * SSE awareness) DO still hit the underlying tools/call timer, so the\n * cap is the only way to surface a fast actionable error there\n * instead of a slot-leaking timeout.\n *\n * INVARIANT: pre-flight MUST fire BEFORE inFlightToolsCall++ — the\n * slot must not be acquired for a rejected pre-flight. handleMcpPost\n * runs the check before delegating to handleRpc → handleToolsCall (the\n * function that increments the counter). Documented in CLAUDE.md.\n *\n * gemini_critic has no cap (long-context model + Copilot may auto-pace).\n */\nconst PRE_FLIGHT_CAPS: ReadonlyArray<{\n toolName: string\n effort: Effort\n maxBriefBytes: number\n}> = [\n { toolName: \"codex_critic\", effort: \"high\", maxBriefBytes: 8 * 1024 },\n { toolName: \"codex_reviewer\", effort: \"high\", maxBriefBytes: 12 * 1024 },\n { toolName: \"opus_critic\", effort: \"medium\", maxBriefBytes: 6 * 1024 },\n]\n\nfunction predictedTooLong(\n persona: PersonaSpec,\n effort: Effort,\n briefBytes: number,\n): { tooLong: true; capBytes: number } | { tooLong: false } {\n for (const cap of PRE_FLIGHT_CAPS) {\n if (\n cap.toolName === persona.toolNameHttp\n && cap.effort === effort\n && briefBytes > cap.maxBriefBytes\n ) {\n return { tooLong: true, capBytes: cap.maxBriefBytes }\n }\n }\n return { tooLong: false }\n}\n\n/**\n * JSON-path pre-flight predictedTooLong gate. Returns a JSON-RPC result\n * body wrapping a tool-error envelope when the call would bust the 60s\n * tools/call ceiling on the JSON path; returns undefined when the call\n * should proceed normally.\n *\n * Skips the check (returns undefined) for any shape problem so\n * handleRpc can return the canonical JSON-RPC error code instead:\n * - notification (no id) → handleRpc returns 202 + empty body\n * - missing/unknown name → handleRpc returns -32601\n * - missing prompt → handleRpc returns -32602\n * - invalid effort string → handleRpc returns -32602\n * - effort not in persona.allowedEfforts → handleRpc returns -32602\n */\nfunction jsonPathPreflightCap(body: JsonRpcRequest):\n | { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; result: ToolErrorContent }\n | undefined {\n if (body.id === undefined) return undefined\n const params = (body.params ?? {}) as Record<string, unknown>\n const name = typeof params.name === \"string\" ? params.name : \"\"\n const args = (params.arguments ?? {}) as Record<string, unknown>\n const prompt = typeof args.prompt === \"string\" ? args.prompt : \"\"\n const context = typeof args.context === \"string\" ? args.context : undefined\n const rawEffort = args.effort\n if (!name || !prompt) return undefined\n const persona = activePersonas().find((p) => p.toolNameHttp === name)\n if (!persona) return undefined\n if (rawEffort !== undefined && !isEffort(rawEffort)) return undefined\n const effortMaybe = rawEffort as Effort | undefined\n if (\n effortMaybe !== undefined\n && !persona.allowedEfforts.includes(effortMaybe)\n ) {\n return undefined\n }\n const effort: Effort = effortMaybe ?? persona.defaultEffort\n const briefBytes = Buffer.byteLength(buildUserText(prompt, context), \"utf8\")\n const verdict = predictedTooLong(persona, effort, briefBytes)\n if (!verdict.tooLong) return undefined\n return rpcResult(\n body.id,\n toolError(\n `pre-flight rejected: ${persona.toolNameHttp} at effort=${effort} on a `\n + `${briefBytes}-byte brief is empirically predicted to exceed the JSON `\n + `tools/call timeout (cap=${verdict.capBytes} bytes for this tier). `\n + `Either drop to a lower effort tier, split the brief into 2-4 `\n + `parallel sub-calls per the decomposition guidance, or send `\n + `Accept: text/event-stream to use the SSE path which bypasses this cap.`,\n ),\n )\n}\n\nasync function callPersona(\n persona: PersonaSpec,\n prompt: string,\n context: string | undefined,\n effort: Effort,\n signal?: AbortSignal,\n): Promise<{ content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n // Resolve the model id against the live catalog so a slug rename\n // (e.g., gemini-3.1-pro-preview → gemini-3.1-pro at GA) auto-resolves\n // through the existing fuzzy matcher rather than 404'ing.\n const resolvedModel = resolveModel(persona.model)\n const userText = buildUserText(prompt, context)\n\n // NOTE: predictedTooLong pre-flight cap fires in handleMcpPost\n // BEFORE handleRpc → handleToolsCall → inFlightToolsCall++ — see\n // the architectural invariant documented in CLAUDE.md. JSON-path\n // only; SSE callers bypass it. Don't duplicate it here.\n\n // NOTE on consumer-cancel signal: we deliberately do NOT pass\n // c.req.raw.signal into the upstream fetch. Bun/srvx aborts the\n // request signal as soon as the request body is fully consumed\n // (after `await c.req.json()`), which would make every persona call\n // fail immediately with \"This operation was aborted\". Instead, the\n // caller (handleToolsCall) creates its own AbortController and\n // threads it through `signal`. This is the controller registered in\n // `inflightAborts` and aborted by `notifications/cancelled` (Phase D\n // P1.5). See CLAUDE.md \"Bun request-signal quirk\" for full context.\n if (persona.endpoint === \"/v1/responses\") {\n const payload: ResponsesPayload = {\n model: resolvedModel,\n instructions: persona.baseInstructions,\n input: [\n {\n role: \"user\",\n content: [{ type: \"input_text\", text: userText }],\n },\n ],\n stream: false,\n // Reasoning effort — gpt-5.x adaptive-thinking reads this field\n // directly. Copilot's translator buckets to its own internal\n // levels (CLAUDE.md \"Thinking-mode translation\").\n reasoning: { effort },\n }\n const response = (await createResponses(\n payload,\n undefined,\n signal,\n )) as ResponsesApiResponse\n const text = extractResponsesText(response)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n }\n\n if (persona.endpoint === \"/v1/messages\") {\n // claude-opus-4-7 path. Copilot's adaptive-thinking models reject\n // Anthropic's standard `thinking: {type:\"enabled\", budget_tokens:N}`\n // shape with HTTP 400: \"thinking.type.enabled is not supported for\n // this model. Use thinking.type.adaptive and output_config.effort\".\n // Build the Copilot-shape directly. Empirical: confirmed 2026-05-14\n // via curl test against the proxy after build, opus_critic@xhigh\n // returned the expected 400 with that exact wording.\n //\n // max_tokens budget: choose a generous ceiling per effort tier so\n // the model has room for substantive reasoning + response without\n // truncation. Numbers chosen empirically:\n // low → 4096, medium → 8192, high → 16384, xhigh → 32768.\n const maxTokens =\n effort === \"low\" ? 4096\n : effort === \"medium\" ? 8192\n : effort === \"high\" ? 16384\n : 32768 // xhigh\n const body = JSON.stringify({\n model: resolvedModel,\n max_tokens: maxTokens,\n system: persona.baseInstructions,\n thinking: { type: \"adaptive\" },\n output_config: { effort },\n messages: [{ role: \"user\", content: userText }],\n })\n const response = await createMessages(body, undefined, signal)\n const json = (await response.json()) as MessagesApiResponse\n const text = extractMessagesText(json)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n }\n\n // /v1/chat/completions (Gemini)\n const payload: ChatCompletionsPayload = {\n model: resolvedModel,\n messages: [\n { role: \"system\", content: persona.baseInstructions },\n { role: \"user\", content: userText },\n ],\n stream: false,\n // Forwarded as-is. Per gemini_critic's review (see\n // docs/research/peer-mcp-investigation.md): Copilot's gemini route\n // may silently ignore this knob or 400 if it strict-validates the\n // schema; the latter surfaces through the existing tool-error path.\n reasoning_effort: effort,\n }\n const response = (await createChatCompletions(\n payload,\n undefined,\n signal,\n )) as ChatCompletionResponse\n const text = extractChatCompletionText(response)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n}\n\ninterface PersonaTelemetry {\n name: string\n model: string\n durationMs: number\n result: \"ok\" | \"isError\" | \"exception\"\n errorMessage?: string\n}\n\nfunction logTelemetry(t: PersonaTelemetry): void {\n // Single-line stderr log so users can grep across sessions to see\n // which personas earn their keep. Honors the minimalist reviewer's\n // \"earn your keep\" critique — personas with near-zero use after\n // ~2 weeks are removal candidates.\n const parts = [\n `[peer-mcp]`,\n `name=${t.name}`,\n `model=${t.model}`,\n `duration_ms=${t.durationMs}`,\n `result=${t.result}`,\n ]\n if (t.errorMessage) parts.push(`error=${JSON.stringify(t.errorMessage)}`)\n // Use stderr directly so this is visible regardless of consola level.\n process.stderr.write(parts.join(\" \") + \"\\n\")\n}\n\nasync function handleToolsCall(\n body: JsonRpcRequest,\n): Promise<object> {\n const params = body.params ?? {}\n const name = typeof params.name === \"string\" ? params.name : \"\"\n const args = (params.arguments ?? {}) as Record<string, unknown>\n\n if (!name) {\n return rpcError(body.id, RPC_INVALID_PARAMS, \"tools/call missing name\")\n }\n\n // Routing: try personas first; fall through to non-persona utility\n // tools (currently just `web_search`). The two registries share the\n // tools/list surface but have different validation gates — personas\n // get the prompt+effort+predictedTooLong gauntlet; non-persona tools\n // do their own arg validation inside the handler closure.\n const persona = activePersonas().find((p) => p.toolNameHttp === name)\n const nonPersonaTool: NonPersonaMcpTool | undefined = persona\n ? undefined\n : NON_PERSONA_MCP_TOOLS.find((t) => t.toolNameHttp === name)\n\n if (!persona && !nonPersonaTool) {\n return rpcError(\n body.id,\n RPC_METHOD_NOT_FOUND,\n `tools/call: unknown tool \"${name}\"`,\n )\n }\n\n // Persona-only validation: prompt required, effort schema-checked\n // against EFFORT_LEVELS and gated by per-persona allowedEfforts. None\n // of this applies to non-persona tools (no prompt, no effort).\n let personaPrompt: string | undefined\n let personaContext: string | undefined\n let personaEffort: Effort | undefined\n if (persona) {\n // Validate effort shape against the global EFFORT_LEVELS allowlist\n // (rejects garbage like `effort: \"extreme\"`); the per-persona\n // allowedEfforts gate runs AFTER persona lookup below (rejects\n // valid-but-not-allowed-here tiers like `xhigh` on codex_critic).\n if (args.effort !== undefined && !isEffort(args.effort)) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: arguments.effort must be one of ${EFFORT_LEVELS.join(\"|\")}; got ${JSON.stringify(args.effort)}`,\n )\n }\n const requestedEffort = args.effort as Effort | undefined\n\n const prompt = typeof args.prompt === \"string\" ? args.prompt : \"\"\n if (!prompt) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: arguments.prompt is required`,\n )\n }\n personaPrompt = prompt\n personaContext = typeof args.context === \"string\" ? args.context : undefined\n\n // Per-persona effort gate. All four personas now allow all four\n // effort tiers (low|medium|high|xhigh). The gate remains in place so\n // a future persona that needs to constrain its tiers can do so\n // declaratively via PersonaSpec.allowedEfforts.\n if (\n requestedEffort !== undefined\n && !persona.allowedEfforts.includes(requestedEffort)\n ) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: persona \"${persona.toolNameHttp}\" does not accept effort=\"${requestedEffort}\". `\n + `Allowed: ${persona.allowedEfforts.join(\"|\")}.`,\n )\n }\n personaEffort = requestedEffort ?? persona.defaultEffort\n }\n\n // predictedTooLong pre-flight cap is enforced upstream of this\n // function — see `jsonPathPreflightCap` invoked by handleMcpPost\n // BEFORE handleRpc/handleToolsCall, so the slot increment below is\n // never reached for a rejected pre-flight (architectural invariant\n // documented in CLAUDE.md). The cap is JSON-PATH ONLY: SSE-streamed\n // responses (handleToolsCallSSE) bypass Claude Code's ~60s\n // tools/call ceiling via heartbeats and therefore don't need the\n // size-based gate. Non-persona tools have no thinking budget and so\n // the predictedTooLong cap doesn't apply to them either (the\n // jsonPathPreflightCap returns undefined when persona lookup misses,\n // which naturally exempts non-persona tools).\n\n if (inFlightToolsCall >= MAX_INFLIGHT_TOOLS_CALL) {\n // Documented per-call cap. NOT silent serialization — surface the\n // backpressure so Opus knows to retry shortly.\n return rpcResult(body.id, {\n content: [\n {\n type: \"text\",\n text: `Peer MCP queue full (${MAX_INFLIGHT_TOOLS_CALL} in-flight). Retry shortly, or wait for the current persona calls to complete.`,\n },\n ],\n isError: true,\n })\n }\n\n inFlightToolsCall++\n const startedAt = Date.now()\n // Phase D P1.5: register an AbortController so notifications/cancelled\n // can free the slot. Use the JSON-RPC request id as the key — clients\n // emit `params.requestId` matching it. If the client doesn't supply\n // an id (notification request), skip registration; nothing to cancel.\n const abortKey =\n body.id !== undefined && body.id !== null ? body.id : undefined\n let aborter: AbortController | undefined\n if (abortKey !== undefined) {\n aborter = new AbortController()\n inflightAborts.set(abortKey, aborter)\n }\n // Telemetry shape differs per branch — personas have a model id;\n // non-persona tools don't dispatch to a peer LLM, so log the tool\n // name as the \"model\" slot for grep'ability.\n const telemetryName = persona ? persona.agentName : nonPersonaTool!.toolNameHttp\n const telemetryModel = persona ? persona.model : \"(non-persona)\"\n try {\n const result = persona\n ? await callPersona(\n persona,\n personaPrompt!,\n personaContext,\n personaEffort!,\n aborter?.signal,\n )\n : await nonPersonaTool!.handler(args, aborter?.signal)\n logTelemetry({\n name: telemetryName,\n model: telemetryModel,\n durationMs: Date.now() - startedAt,\n result: result.isError ? \"isError\" : \"ok\",\n })\n return rpcResult(body.id, result)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logTelemetry({\n name: telemetryName,\n model: telemetryModel,\n durationMs: Date.now() - startedAt,\n result: \"exception\",\n errorMessage: message,\n })\n // Tool error vs JSON-RPC error: per MCP spec, runtime errors that\n // correspond to \"the tool ran but failed\" should surface as\n // result.isError=true (not as JSON-RPC errors). Catalog/auth/etc.\n // 404s from the upstream all go here. Aborts (from\n // notifications/cancelled) also land here as `AbortError`; treat\n // identically — the cancel notification is fire-and-forget, but\n // the original tools/call still gets a response so the client\n // doesn't hang waiting for it.\n return rpcResult(body.id, {\n content: [\n {\n type: \"text\",\n text: persona\n ? `persona ${persona.agentName} failed: ${message}`\n : `tool ${nonPersonaTool!.toolNameHttp} failed: ${message}`,\n },\n ],\n isError: true,\n })\n } finally {\n inFlightToolsCall--\n if (abortKey !== undefined) {\n inflightAborts.delete(abortKey)\n }\n }\n}\n\n/**\n * Handle `notifications/cancelled` per JSON-RPC 2.0 + MCP spec.\n * params.requestId is the id of an in-flight tools/call to abort.\n * Notifications return no body (handled by isNotification path in\n * handleRpc); this side-effect frees the in-flight slot.\n */\nfunction handleCancelledNotification(body: JsonRpcRequest): void {\n const params = body.params ?? {}\n const requestId = (params as { requestId?: unknown }).requestId\n if (\n requestId === undefined\n || (typeof requestId !== \"string\" && typeof requestId !== \"number\")\n ) {\n consola.debug(\n `[mcp] notifications/cancelled missing or invalid requestId: ${JSON.stringify(requestId)}`,\n )\n return\n }\n const aborter = inflightAborts.get(requestId)\n if (!aborter) {\n // Already completed or never registered. No-op — common race when\n // cancel races with completion.\n return\n }\n aborter.abort(new Error(\"client requested cancellation\"))\n // The finally block in handleToolsCall removes the entry on\n // completion; we don't delete here to avoid a TOCTOU race where the\n // upstream fetch is mid-completion when cancel arrives.\n}\n\nasync function handleRpc(\n _c: Context,\n body: JsonRpcRequest,\n): Promise<{ status: number; body: object | null }> {\n // Reject non-object envelopes (null, arrays, primitives) BEFORE we\n // dereference body.jsonrpc / body.method — without this guard a `null`\n // body throws TypeError on the property access, falls into the outer\n // catch in handleMcpPost, and returns RPC_INTERNAL_ERROR (-32603) when\n // the JSON-RPC spec wants RPC_INVALID_REQUEST (-32600) for shape errors.\n if (\n body === null\n || typeof body !== \"object\"\n || Array.isArray(body)\n ) {\n return {\n status: 200,\n body: rpcError(null, RPC_INVALID_REQUEST, \"jsonrpc 2.0 envelope required\"),\n }\n }\n if (body.jsonrpc !== \"2.0\" || typeof body.method !== \"string\") {\n return {\n status: 200,\n body: rpcError(body.id ?? null, RPC_INVALID_REQUEST, \"jsonrpc 2.0 envelope required\"),\n }\n }\n\n // Per JSON-RPC 2.0: requests without an `id` field are notifications\n // and MUST NOT receive a response body. The runtime must treat them\n // as fire-and-forget. We dispatch the method (so e.g. notifications/\n // initialized still gets observed), then return 202 + empty body\n // regardless of what the dispatched method returned.\n const isNotification = body.id === undefined\n\n switch (body.method) {\n case \"initialize\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, {\n protocolVersion: MCP_PROTOCOL_VERSION,\n // Capabilities advertised must match what we actually serve\n // (codex-critic Phase D requirement: \"empty lists are not\n // sufficient unless the whole MCP handshake is coherent\").\n // We expose tools (the personas), and stub resources/prompts\n // as empty lists so well-behaved clients don't error on\n // probing them. {} for resources/prompts means \"supported\n // but no list-changed notifications, no subscribe semantics\".\n capabilities: {\n tools: { listChanged: false },\n resources: {},\n prompts: {},\n },\n serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },\n }),\n }\n\n case \"notifications/initialized\":\n // Notifications have no id and expect no response body.\n // Return 202 Accepted with an empty body (Hono accepts null).\n return { status: 202, body: null }\n\n case \"tools/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { tools: toolEntries() }),\n }\n\n case \"tools/call\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: await handleToolsCall(body),\n }\n\n // --- Phase D: MCP method stubs with full handshake coherence ---\n // (codex-critic: \"if advertising resources:{}, also handle\n // resources/templates/list with {resourceTemplates: []}\")\n\n case \"resources/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { resources: [] }),\n }\n\n case \"resources/templates/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { resourceTemplates: [] }),\n }\n\n case \"resources/read\": {\n if (isNotification) return { status: 202, body: null }\n // Parametric — empty list isn't appropriate. Return proper\n // JSON-RPC -32602 invalid params per codex-critic Phase D.\n const uri = (body.params as { uri?: unknown } | undefined)?.uri\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `resources/read: resource URI not found: ${\n typeof uri === \"string\" ? uri : \"(missing/invalid uri)\"\n }`,\n ),\n }\n }\n\n case \"prompts/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { prompts: [] }),\n }\n\n case \"prompts/get\": {\n if (isNotification) return { status: 202, body: null }\n const name = (body.params as { name?: unknown } | undefined)?.name\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `prompts/get: prompt name not found: ${\n typeof name === \"string\" ? name : \"(missing/invalid name)\"\n }`,\n ),\n }\n }\n\n // --- Phase D P1.5: cancellation handling ---\n case \"notifications/cancelled\":\n // Side-effect only (abort the in-flight call). MUST NOT return\n // a body per JSON-RPC 2.0 notifications. Returns 202 like other\n // notifications.\n handleCancelledNotification(body)\n return { status: 202, body: null }\n\n case \"ping\":\n if (isNotification) return { status: 202, body: null }\n // MCP heartbeat — return empty result.\n return { status: 200, body: rpcResult(body.id, {}) }\n\n default:\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_METHOD_NOT_FOUND,\n `unknown method: ${body.method}`,\n ),\n }\n }\n}\n\nexport async function handleMcpPost(c: Context): Promise<Response> {\n const auth = checkAuth(c)\n if (!auth.ok) {\n return c.json(\n rpcError(null, RPC_INVALID_REQUEST, auth.reason),\n auth.status,\n )\n }\n\n let body: JsonRpcRequest\n try {\n body = (await c.req.json()) as JsonRpcRequest\n } catch (err) {\n consola.debug(\"/mcp parse error:\", err)\n return c.json(\n rpcError(null, RPC_PARSE_ERROR, \"request body is not valid JSON\"),\n 200,\n )\n }\n\n // SSE-streamed response branch for `tools/call` when the client\n // advertises text/event-stream Accept (Claude Code's MCP HTTP client\n // does, per MCP 2025-06-18 Streamable HTTP transport spec). Streamed\n // responses bypass the per-tool-call wait timer that ~60s-caps JSON\n // responses on Claude Code v2.1.113+ (regressions #50289 / #52137,\n // documented in docs/research/peer-mcp-investigation.md). Heartbeat\n // `notifications/progress` events keep the connection alive while\n // the upstream Copilot call is in flight; the final tools/call\n // response is delivered as the closing `message` event. Non-tools/call\n // RPC methods (initialize, tools/list, etc.) stay on the JSON path —\n // they're synchronous and don't benefit from streaming.\n if (\n typeof body === \"object\"\n && body !== null\n && !Array.isArray(body)\n && body.method === \"tools/call\"\n && acceptsEventStream(c.req.header(\"accept\"))\n ) {\n return handleToolsCallSSE(body)\n }\n\n // JSON-path pre-flight predictedTooLong cap. SSE clients (above)\n // bypass Claude Code's ~60s tools/call ceiling via heartbeats, but\n // JSON-path clients (raw curl with `Accept: application/json`,\n // older MCP clients without SSE awareness) still hit the underlying\n // timer. Reject here as a fast actionable error instead of letting\n // the request burn an inFlight slot for ~60s before the client\n // times out — invariant: the cap MUST fire BEFORE handleToolsCall\n // so inFlightToolsCall++ is never reached for a rejected pre-flight\n // (CLAUDE.md). `jsonPathPreflightCap` returns undefined for any\n // shape problem (missing prompt, unknown name, invalid effort) so\n // handleRpc returns the canonical -32601/-32602 error code.\n if (\n typeof body === \"object\"\n && body !== null\n && !Array.isArray(body)\n && body.method === \"tools/call\"\n ) {\n const preflight = jsonPathPreflightCap(body)\n if (preflight) return c.json(preflight, 200)\n }\n\n try {\n const { status, body: respBody } = await handleRpc(c, body)\n if (respBody === null) return c.body(null, status as 202)\n return c.json(respBody, status as 200)\n } catch (err) {\n consola.error(\"/mcp handler error:\", err)\n // Be defensive about `body.id` — body could be null or a non-object\n // primitive that slipped past the JSON parse (rare but possible).\n const echoId =\n typeof body === \"object\" && body !== null && !Array.isArray(body)\n ? (body as JsonRpcRequest).id ?? null\n : null\n return c.json(\n rpcError(\n echoId,\n RPC_INTERNAL_ERROR,\n err instanceof Error ? err.message : String(err),\n ),\n 200,\n )\n }\n}\n\n/**\n * Accept-header parsing for MCP Streamable HTTP. Per MCP 2025-06-18\n * spec, clients send `Accept: application/json, text/event-stream` to\n * indicate they can consume either response shape. Server picks; for\n * tools/call we pick SSE because Claude Code's per-tool-call timer\n * (~60s on v2.1.113+) does not fire on streamed responses.\n *\n * Lenient parse: split on commas, strip params (q-values, charset),\n * trim, lowercase, look for the SSE token. Returns false on undefined\n * / empty / strict-JSON-only Accept.\n */\nfunction acceptsEventStream(accept: string | undefined): boolean {\n if (!accept) return false\n const tokens = accept\n .toLowerCase()\n .split(\",\")\n .map((t) => t.split(\";\")[0].trim())\n return tokens.includes(\"text/event-stream\")\n}\n\n/**\n * SSE-streamed response for a single tools/call. Delegates the actual\n * upstream call to `handleToolsCall` (so the per-persona effort gate,\n * predictedTooLong cap, AbortController registration, telemetry, and\n * inFlight slot accounting all run identically); wraps the awaited\n * result in an SSE envelope with periodic heartbeats while the upstream\n * fetch is in flight.\n *\n * SSE event format (per MCP Streamable HTTP):\n * event: message\n * data: <json-rpc-2.0 message>\\n\\n\n *\n * - Heartbeats are JSON-RPC `notifications/progress` notifications with\n * the request id as `progressToken` (per MCP progress-notification spec).\n * - The final message is the JSON-RPC response envelope returned by\n * handleToolsCall — same structure as the JSON-path response.\n * - On consumer cancel (ReadableStream.cancel), the heartbeat interval\n * is cleared and the inFlight slot's AbortController is signalled\n * (handleToolsCall observes the abort and returns an error envelope\n * that we drop unwritten — controller is already closed).\n *\n * Per CLAUDE.md \"Stream lifecycle\" / \"The smoking gun\" rules: every\n * controller.enqueue/close is wrapped in a try/catch that swallows the\n * \"Invalid state: Controller is already closed\" race without warning.\n */\nconst SSE_HEARTBEAT_INTERVAL_MS = 5000\n\nasync function handleToolsCallSSE(body: JsonRpcRequest): Promise<Response> {\n const encoder = new TextEncoder()\n // Kick off the actual tool call as a Promise. handleToolsCall handles\n // all gates, slot accounting, abort registration, telemetry — we just\n // wrap its eventual result in an SSE envelope.\n const callPromise = handleToolsCall(body)\n\n const stream = new ReadableStream<Uint8Array>({\n async start(controller) {\n let closed = false\n const safeEnqueue = (chunk: Uint8Array): void => {\n if (closed) return\n try {\n controller.enqueue(chunk)\n } catch (err) {\n // Controller already closed by consumer cancel or earlier\n // close — common race between heartbeat tick and stream\n // teardown. Per CLAUDE.md \"smoking gun\" rule, do NOT log\n // this as a warning; it's expected.\n consola.debug(\"/mcp SSE enqueue after close (expected race):\", err)\n closed = true\n }\n }\n const safeClose = (): void => {\n if (closed) return\n closed = true\n try {\n controller.close()\n } catch (err) {\n consola.debug(\"/mcp SSE close after close:\", err)\n }\n }\n const sseFrame = (rpcMessage: object): Uint8Array =>\n encoder.encode(`event: message\\ndata: ${JSON.stringify(rpcMessage)}\\n\\n`)\n const heartbeatFrame = (): Uint8Array =>\n sseFrame({\n jsonrpc: \"2.0\",\n method: \"notifications/progress\",\n params: {\n progressToken: body.id ?? null,\n progress: 0,\n message: \"in flight\",\n },\n })\n\n // Initial heartbeat (proves the stream is open) + recurring\n // heartbeats every SSE_HEARTBEAT_INTERVAL_MS until the call\n // resolves.\n safeEnqueue(heartbeatFrame())\n const heartbeatHandle = setInterval(\n () => safeEnqueue(heartbeatFrame()),\n SSE_HEARTBEAT_INTERVAL_MS,\n )\n\n try {\n const result = await callPromise\n safeEnqueue(sseFrame(result))\n } catch (err) {\n consola.error(\"/mcp SSE upstream error:\", err)\n safeEnqueue(\n sseFrame(\n rpcError(\n body.id ?? null,\n RPC_INTERNAL_ERROR,\n err instanceof Error ? err.message : String(err),\n ),\n ),\n )\n } finally {\n clearInterval(heartbeatHandle)\n safeClose()\n }\n },\n cancel() {\n // Consumer disconnected. handleToolsCall's AbortController is\n // keyed by body.id and already registered in inflightAborts;\n // signal it so the upstream Copilot fetch tears down and the\n // inFlight slot is freed promptly. No need to clear heartbeats\n // here — the start() function's finally-block does that when\n // callPromise resolves (or rejects with the abort).\n const abortKey =\n body.id !== undefined && body.id !== null ? body.id : undefined\n if (abortKey !== undefined) {\n const aborter = inflightAborts.get(abortKey)\n if (aborter) aborter.abort(new Error(\"client disconnected SSE stream\"))\n }\n },\n })\n\n return new Response(stream, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n \"Connection\": \"keep-alive\",\n // MCP Streamable HTTP transport identifier so middleboxes (and\n // future Claude Code versions that key off this) handle the\n // response correctly.\n \"X-Accel-Buffering\": \"no\",\n },\n })\n}\n\nexport function handleMcpDelete(c: Context): Response {\n // MCP DELETE is for session teardown. v1 is session-less, so this\n // is a 200 ack regardless of body. Body is intentionally NOT\n // parsed — there's no schema to validate against, and parsing an\n // attacker-controlled body adds attack surface.\n const auth = checkAuth(c)\n if (!auth.ok) {\n return c.json(\n rpcError(null, RPC_INVALID_REQUEST, auth.reason),\n auth.status,\n )\n }\n return c.body(null, 200)\n}\n\n/** Test helper: reset in-flight counter between tests. */\nexport function __resetInFlightForTests(): void {\n inFlightToolsCall = 0\n}\n\n/** Test helper: peek the in-flight counter. */\nexport function __getInFlightForTests(): number {\n return inFlightToolsCall\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleMcpDelete, handleMcpPost } from \"./handler\"\n\nexport const mcpRoutes = new Hono()\n\nmcpRoutes.post(\"/\", async (c) => {\n try {\n return await handleMcpPost(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nmcpRoutes.delete(\"/\", (c) => {\n try {\n return handleMcpDelete(c)\n } catch {\n return c.body(null, 500)\n }\n})\n","/**\n * Phase I: ADVISOR proxy-side translation.\n *\n * ADVISOR is Anthropic's server-side server_tool_use mechanism — the\n * model invokes a stronger reviewer model with the full conversation\n * context. Copilot doesn't implement it (returns 400 'unsupported beta\n * header(s): advisor-tool-2026-03-01'). This module implements the\n * equivalent semantics proxy-side per gemini-critic's streaming design:\n *\n * 1. Strip the `advisor-tool-` beta header before forwarding to Copilot\n * (Phase A already does this via EXPLICITLY_STRIPPED_BETA_PREFIXES).\n * 2. Inject a `__anthropic_advisor` tool definition into body.tools[]\n * (with cc-backup's ADVISOR_TOOL_INSTRUCTIONS as the description so\n * the model knows when to call it). The double-underscore prefix\n * avoids collision with any user MCP server's `advisor` tool.\n * 3. Stream the Copilot response, watching for tool_use blocks with\n * name `__anthropic_advisor`. When detected:\n * a. Translate the block in-flight: emit\n * `{type: \"server_tool_use\", name: \"advisor\"}` to the client so\n * Claude Code's AdvisorMessage.tsx renders the \"Consulting\n * advisor...\" spinner immediately (gemini: do NOT buffer the loop\n * — the UI hangs without an indicator).\n * b. After the current turn's `message_stop` would have arrived,\n * suppress it and run the advisor model server-side with the\n * conversation context up through the current assistant turn.\n * c. Synthesize an `advisor_tool_result` block to the client with\n * the advisor's text response.\n * d. Append the synthetic tool_result to the conversation and\n * re-call Copilot for the next turn — stream onto the SAME\n * SSE connection (no new message_start; the original one is\n * still open). Loop up to ADVISOR_MAX_TURNS times.\n * 4. Cross-lab default: route the advisor call to a different model\n * family than the main loop (gpt-5.5 by default) so the user gets\n * a true \"second set of eyes\" instead of Opus reviewing Opus\n * (gemini-critic finding).\n *\n * The translate-loop is bounded to a single user request — no\n * persistent state across requests is needed (unlike Phase G's\n * mcp_servers translate which had unfix-able continuation-after-TTL\n * holes). Each request evaluates ADVISOR fresh from the body.\n */\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { isControllerClosedError } from \"~/lib/stream-relay\"\nimport { resolveModel } from \"~/lib/utils\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst ENCODER = new TextEncoder()\n\n/** The tool name we inject for Copilot. Double-underscore prefix\n * avoids collision with any user MCP server's `advisor` tool. */\nexport const ADVISOR_INTERNAL_TOOL_NAME = \"__anthropic_advisor\"\n\n/** The Anthropic-spec name used in the translated server_tool_use\n * block sent to the client. cc-backup AdvisorMessage.tsx requires\n * this exact name to render the advisor spinner. */\nexport const ADVISOR_CLIENT_TOOL_NAME = \"advisor\"\n\n/** Hard cap on advisor calls per request to bound runaway behavior.\n * Matches Phase G's loop bound; ADVISOR is typically called 1-3\n * times per session per cc-backup ADVISOR_TOOL_INSTRUCTIONS. */\nexport const ADVISOR_MAX_TURNS = 16\n\n/** Default advisor model + reasoning effort. Per gemini-critic + user\n * direction: hardcode to a cross-lab model (gpt-5.5 — Copilot's\n * /responses-only flagship) at xhigh effort. The cross-lab choice\n * gives a true \"second set of eyes\" instead of the main model\n * reviewing itself; xhigh effort buys the deep-dive reasoning that\n * matches Anthropic's own ADVISOR (which uses a stronger reviewer\n * model — Opus 4.6/Sonnet 4.6 typically). */\nexport const ADVISOR_DEFAULT_MODEL = \"gpt-5.5\"\nexport const ADVISOR_DEFAULT_EFFORT = \"xhigh\"\n\ntype Effort = \"low\" | \"medium\" | \"high\" | \"xhigh\"\n\n/** ADVISOR_TOOL_INSTRUCTIONS verbatim from cc-backup\n * src/utils/advisor.ts — describes when the model should invoke\n * the advisor. Long-form prose; see source for justification. */\nexport const ADVISOR_TOOL_INSTRUCTIONS = `# Advisor Tool\n\nYou have access to an \\`advisor\\` tool backed by a stronger reviewer model. It takes NO parameters -- when you call it, your entire conversation history is automatically forwarded. The advisor sees the task, every tool call you've made, every result you've seen.\n\nCall advisor BEFORE substantive work -- before writing code, before committing to an interpretation, before building on an assumption. If the task requires orientation first (finding files, reading code, seeing what's there), do that, then call advisor. Orientation is not substantive work. Writing, editing, and declaring an answer are.\n\nAlso call advisor:\n- When you believe the task is complete. BEFORE this call, make your deliverable durable: write the file, stage the change, save the result. The advisor call takes time; if the session ends during it, a durable result persists and an unwritten one doesn't.\n- When stuck -- errors recurring, approach not converging, results that don't fit.\n- When considering a change of approach.\n\nOn tasks longer than a few steps, call advisor at least once before committing to an approach and once before declaring done. On short reactive tasks where the next action is dictated by tool output you just read, you don't need to keep calling -- the advisor adds most of its value on the first call, before the approach crystallizes.\n\nGive the advice serious weight. If you follow a step and it fails empirically, or you have primary-source evidence that contradicts a specific claim (the file says X, the code does Y), adapt. A passing self-test is not evidence the advice is wrong -- it's evidence your test doesn't check what the advice is checking.\n\nIf you've already retrieved data pointing one way and the advisor points another: don't silently switch. Surface the conflict in one more advisor call -- \"I found X, you suggest Y, which constraint breaks the tie?\" The advisor saw your evidence but may have underweighted it; a reconcile call is cheaper than committing to the wrong branch.`\n\nconst ADVISOR_OPT_OUT_ENV = \"CLAUDE_CODE_DISABLE_ADVISOR_TOOL\"\n\n/**\n * Detect whether the request asked for ADVISOR (incoming\n * `anthropic-beta` header contains an `advisor-tool-` prefix). Also\n * respects the `CLAUDE_CODE_DISABLE_ADVISOR_TOOL` opt-out env var\n * (set by the user to globally disable; matches cc-backup advisor.ts\n * line 61).\n */\nexport function isAdvisorRequested(rawBetaHeader: string | undefined): boolean {\n if (!rawBetaHeader) return false\n if (process.env[ADVISOR_OPT_OUT_ENV]) return false\n return rawBetaHeader\n .split(\",\")\n .map((s) => s.trim())\n .some((v) => v.startsWith(\"advisor-tool-\"))\n}\n\n/**\n * Inject the __anthropic_advisor tool definition into the body's tools\n * array. Returns a new body string. Idempotent — if the tool is already\n * present (e.g. the user's MCP shadowed it) we leave the existing one\n * alone and return the body unchanged.\n *\n * Also strips any tool entry with `type: \"advisor_*\"` (Anthropic API's\n * native server-side advisor tool — `advisor_20260301` and future\n * variants). When `CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL=1` is\n * set, Claude Code injects its own advisor tool with this type into\n * `tools[]`. Copilot 400s on the unknown tool type (\"Input tag\n * 'advisor_20260301' found using 'type' does not match any of the\n * expected tags\"), so the proxy must strip it before forwarding while\n * still injecting our custom `__anthropic_advisor` tool that the model\n * can invoke. The proxy's intercept on the response stream then\n * translates the model's `tool_use{__anthropic_advisor}` to the\n * client-shape `server_tool_use{name:\"advisor\"}` + `advisor_tool_result`\n * blocks the client expects.\n */\nexport function injectAdvisorTool(rawBody: string): string {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n const rawTools = Array.isArray(parsed.tools) ? parsed.tools : []\n // Strip Anthropic-native advisor typed tools (Copilot 400s on these).\n const tools = rawTools.filter((t: AnyRecord) => {\n if (typeof t !== \"object\" || t === null) return true\n const type = (t as AnyRecord).type\n return typeof type !== \"string\" || !type.startsWith(\"advisor_\")\n })\n const stripped = tools.length !== rawTools.length\n const alreadyInjected = tools.some(\n (t: AnyRecord) => t?.name === ADVISOR_INTERNAL_TOOL_NAME,\n )\n if (alreadyInjected && !stripped) {\n return rawBody // no-op: already injected and nothing to strip\n }\n parsed.tools = alreadyInjected\n ? tools\n : [\n ...tools,\n {\n name: ADVISOR_INTERNAL_TOOL_NAME,\n description: ADVISOR_TOOL_INSTRUCTIONS,\n input_schema: {\n type: \"object\",\n properties: {},\n required: [],\n },\n },\n ]\n return JSON.stringify(parsed)\n}\n\n/** Character budget for rendered conversation text passed to the\n * advisor model. gpt-5.5 (default advisor) caps prompt input at\n * 272,000 tokens. At a conservative ~3 chars/token (mixed prose +\n * code + JSON), 720,000 chars renders to ≈240,000 tokens, leaving\n * ~32,000 tokens of headroom for the system prompt and per-turn\n * framing overhead. Without this cap, long Claude Code sessions\n * produce 400 `model_max_prompt_tokens_exceeded` from /v1/responses\n * and the advisor falls back silently. */\nexport const ADVISOR_MAX_CONVERSATION_CHARS = 720_000\n\n/**\n * Render an Anthropic-shape conversation (messages array with\n * role/content blocks) as a single human-readable text blob. Used\n * as the input to the advisor model (gpt-5.5 via /v1/responses\n * doesn't have a 1:1 mapping for Anthropic's tool_use/tool_result\n * blocks; serializing to text preserves the semantics — the advisor\n * just needs to READ the conversation, not produce more of it).\n *\n * Front-truncates oldest turns when the rendered output would exceed\n * `maxChars`. The advisor cares more about current state (latest\n * tool calls, errors, in-flight task) than the original prompt —\n * mirrors Claude Code's own context-truncation strategy. When any\n * turns are dropped, prepends a `[TRUNCATED: N earlier turn(s)\n * omitted ...]` notice so the advisor knows the transcript is\n * partial and can flag if it needs the missing context.\n */\nexport function renderConversationAsText(\n conversation: Array<AnyRecord>,\n maxChars: number = ADVISOR_MAX_CONVERSATION_CHARS,\n): string {\n const turnBlocks: Array<string> = []\n for (let i = 0; i < conversation.length; i++) {\n const msg = conversation[i]\n const role = (msg.role as string) ?? \"unknown\"\n const block: Array<string> = [`### Turn ${i + 1} — ${role}`]\n const content = msg.content\n if (typeof content === \"string\") {\n block.push(content)\n } else if (Array.isArray(content)) {\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const b = part as AnyRecord\n if (b.type === \"text\" && typeof b.text === \"string\") {\n block.push(b.text)\n } else if (b.type === \"tool_use\") {\n block.push(\n `[tool_use ${b.name ?? \"?\"}(${b.id ?? \"?\"}): ${JSON.stringify(b.input ?? {})}]`,\n )\n } else if (b.type === \"tool_result\") {\n const c =\n typeof b.content === \"string\" ? b.content : JSON.stringify(b.content)\n block.push(`[tool_result ${b.tool_use_id ?? \"?\"}]:\\n${c}`)\n } else {\n block.push(`[${b.type}: ${JSON.stringify(b).slice(0, 500)}]`)\n }\n }\n }\n block.push(\"\")\n turnBlocks.push(block.join(\"\\n\"))\n }\n\n // Walk from the latest turn backward, accumulating until the next\n // turn would push us over budget. The \"+1\" accounts for the join\n // separator between turn blocks.\n let totalChars = 0\n let firstKeptIdx = turnBlocks.length\n for (let i = turnBlocks.length - 1; i >= 0; i--) {\n const len = turnBlocks[i].length + 1\n if (totalChars + len > maxChars) break\n totalChars += len\n firstKeptIdx = i\n }\n\n // Edge case: even the latest turn alone exceeds the budget. Hard-\n // truncate its tail to fit (advisor still gets the most-recent\n // context, just not all of it). 200-char reserve for the notice.\n if (firstKeptIdx === turnBlocks.length && turnBlocks.length > 0) {\n const last = turnBlocks[turnBlocks.length - 1]\n const reserve = 200\n const tail = last.slice(-(maxChars - reserve))\n return (\n `[TRUNCATED: conversation too long for advisor model context; `\n + `only the tail of the latest (turn ${turnBlocks.length}) is shown]\\n\\n`\n + tail\n )\n }\n\n const kept = turnBlocks.slice(firstKeptIdx)\n if (firstKeptIdx > 0) {\n kept.unshift(\n `[TRUNCATED: ${firstKeptIdx} earlier turn(s) omitted to fit advisor `\n + `model context budget; ${turnBlocks.length - firstKeptIdx} most-recent `\n + `turn(s) shown below]\\n`,\n )\n }\n return kept.join(\"\\n\")\n}\n\n/**\n * Run the advisor model with the full conversation context. Returns\n * the advisor's text response.\n *\n * Routes by model family:\n * - gpt-5.x / codex / o-series (have `/responses` in supported_endpoints):\n * use createResponses with `reasoning.effort` set. This is the\n * default path — gpt-5.5 at xhigh effort.\n * - claude-* (no `/responses`): fall back to createMessages.\n *\n * The conversation is serialized to text via renderConversationAsText\n * so the advisor model (which may not natively understand Anthropic's\n * tool_use/tool_result block shapes) sees a flat readable transcript.\n * This loses some structural fidelity but matches the spirit of\n * Anthropic's own ADVISOR (\"see the whole task + every tool call +\n * every result\").\n */\nasync function runAdvisor(\n conversation: Array<AnyRecord>,\n advisorModel: string,\n advisorEffort: Effort,\n): Promise<string> {\n const advisorSystem =\n \"You are an expert advisor reviewing an in-progress Claude Code session. \"\n + \"The transcript below is the work-in-progress (turns numbered, with \"\n + \"tool calls and results inlined). Read carefully and provide concrete, \"\n + \"actionable advice on the next step or course-correction. Be specific — \"\n + \"cite the parts of the transcript you're responding to. If the assistant \"\n + \"is on the right track, say so explicitly. If they're stuck or off-track, \"\n + \"name the specific assumption or step to revisit. Aim for 2-5 paragraphs \"\n + \"of substantive guidance.\"\n\n const conversationText = renderConversationAsText(conversation)\n const resolvedAdvisorModel = resolveModel(advisorModel)\n\n // Route by model family. gpt-5.x / o-series / codex go through\n // /v1/responses with reasoning.effort. claude-* stays on /v1/messages.\n // Quick heuristic: if the model id starts with \"gpt-\" or contains\n // \"codex\" or starts with \"o\", use /responses. Otherwise /v1/messages.\n // (Could be tightened with a state.models lookup, but the fast-path\n // text match is correct for every model in Copilot's catalog today.)\n const useResponses = /^(gpt-|o\\d|.*codex)/i.test(resolvedAdvisorModel)\n\n if (useResponses) {\n const payload: ResponsesPayload = {\n model: resolvedAdvisorModel,\n instructions: advisorSystem,\n input: [\n {\n role: \"user\",\n content: [{ type: \"input_text\", text: conversationText }],\n },\n ],\n stream: false,\n // gpt-5.x reads reasoning.effort directly. xhigh is the deepest\n // reasoning bucket — appropriate for adversarial review since the\n // advisor adds most of its value on the FIRST call (per cc-backup\n // ADVISOR_TOOL_INSTRUCTIONS line 31), so don't be cheap.\n reasoning: { effort: advisorEffort },\n }\n const response = (await createResponses(payload)) as ResponsesApiResponse\n const out: Array<string> = []\n for (const item of response.output) {\n if (typeof item !== \"object\" || item === null) continue\n const obj = item as Record<string, unknown>\n if (obj.type !== \"message\" || obj.role !== \"assistant\") continue\n const content = obj.content\n if (!Array.isArray(content)) continue\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const p = part as Record<string, unknown>\n if (\n (p.type === \"output_text\" || p.type === \"text\")\n && typeof p.text === \"string\"\n ) {\n out.push(p.text)\n }\n }\n }\n const text = out.join(\"\")\n if (!text) {\n throw new Error(\n `Advisor model ${resolvedAdvisorModel} returned empty assistant output`,\n )\n }\n return text\n }\n\n // claude-* fallback: /v1/messages with the conversation as a single\n // user message. Effort doesn't apply (Anthropic uses thinking mode\n // separately).\n const advisorBody = JSON.stringify({\n model: resolvedAdvisorModel,\n max_tokens: 4096,\n system: advisorSystem,\n messages: [{ role: \"user\", content: conversationText }],\n stream: false,\n })\n const response = await createMessages(advisorBody, {})\n const json = (await response.json()) as AnyRecord\n const blocks = Array.isArray(json.content) ? json.content : []\n const text = blocks\n .filter((b: AnyRecord) => b.type === \"text\" && typeof b.text === \"string\")\n .map((b: AnyRecord) => b.text as string)\n .join(\"\\n\\n\")\n if (!text) {\n throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`)\n }\n return text\n}\n\ninterface ToolUseTracker {\n /** Block index from the SSE stream */\n index: number\n /** tool_use_id assigned by the upstream model — used in the\n * conversation-replay path sent back to Copilot in next turns of\n * the in-loop advisor flow (must match Anthropic `^toolu_*$`). */\n id: string\n /** Client-facing server_tool_use id derived from `id` — used in\n * the translated server_tool_use + advisor_tool_result blocks\n * emitted on the SSE stream to the client. Anthropic spec\n * requires this to match `^srvtoolu_[a-zA-Z0-9_]+$` (parallel to\n * `toolu_*` for client-fulfilled tools). Mismatched format causes\n * Copilot to 400 the conversation history when Claude Code\n * replays it later — the failure is delayed because the original\n * request succeeds; the broken block only hits a validator on a\n * much-later turn that includes it in the message history. */\n clientId: string\n /** Accumulated input_json_delta text (advisor takes no input but\n * we accumulate defensively) */\n inputJson: string\n}\n\n/**\n * Derive a spec-compliant `srvtoolu_*` id for a client-facing\n * `server_tool_use` (and matching `advisor_tool_result.tool_use_id`)\n * from the upstream model's `toolu_*` id.\n *\n * Anthropic spec: `^srvtoolu_[a-zA-Z0-9_]+$`. If the upstream id\n * suffix contains chars outside that charset (e.g., a hyphenated id\n * from a non-Anthropic provider, or a corrupt id), fall back to a\n * synthesized stable id keyed by the SSE block index. Defensive\n * against edge cases that would otherwise emit a malformed block —\n * spec violation in either direction is a 400.\n */\nexport function toClientServerToolUseId(\n id: string,\n fallbackIndex: number,\n): string {\n const suffix = id.startsWith(\"toolu_\") ? id.slice(\"toolu_\".length) : id\n if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `srvtoolu_${suffix}`\n return `srvtoolu_advisor_${fallbackIndex}`\n}\n\n/**\n * A captured assistant content block from the upstream Copilot stream,\n * suitable for replay back to Copilot in the advisor loop's\n * continuation turn. Holds the raw `content_block` object verbatim so\n * future block types we don't recognize today (thinking, redacted_\n * thinking, image, document, citations, etc.) flow through correctly.\n *\n * Mutated in place during streaming: text_delta appends to .block.text,\n * thinking_delta to .block.thinking, signature_delta to .block.signature,\n * input_json_delta accumulates into partialJson and is parsed into\n * .block.input at content_block_stop (Anthropic spec requires\n * tool_use.input to be a parsed object on replay, not a raw JSON string).\n *\n * Special case: when the upstream block is `tool_use{__anthropic_advisor}`,\n * the proxy SYNTHESIZES a different block for client output\n * (`server_tool_use{name:\"advisor\"}` with the `srvtoolu_*` clientId)\n * AND tracks the original `toolu_*` id in `advisorReplay` so the\n * Copilot-replay continuation request uses the original.\n */\ninterface CapturedBlock {\n /** The full `content_block` object from the upstream\n * content_block_start event (or, for advisor blocks, an internal\n * representation we'll synthesize on emit). */\n block: AnyRecord\n /** Raw partial_json buffer for tool_use blocks. JSON.parse'd into\n * `block.input` at content_block_stop. */\n partialJson: string\n /** Set if this block was the advisor invocation. The\n * Copilot-replay path must emit a `tool_use{__anthropic_advisor}`\n * with the original `toolu_*` id, NOT the client-facing\n * `srvtoolu_*` id; the input is the parsed advisor input (defaults\n * to {} if no input_json_delta arrived — codex round-7: don't bake\n * \"advisor takes no input\" as a load-bearing invariant). */\n advisorReplay?: { id: string }\n /** Set during content_block_stop if this block should be dropped\n * from the replay (e.g., empty text block). */\n dropFromReplay?: boolean\n}\n\n/**\n * Build an SSE event line in the canonical Anthropic shape:\n * event: <type>\n * data: <json>\n * <blank>\n */\nfunction sseEvent(type: string, data: AnyRecord): string {\n return `event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`\n}\n\n/**\n * The streaming translate-loop. Returns a ReadableStream<Uint8Array>\n * suitable to wrap with Hono's c.body() / new Response().\n *\n * @param firstResponse The first Copilot streaming response\n * @param initialConversation The conversation messages from the\n * incoming request (used as the starting context for advisor calls\n * and continuation Copilot calls).\n * @param baseBody Parsed initial request body (model, max_tokens,\n * system, etc.) — used as the template for continuation Copilot calls.\n * @param requestHeaders Extra headers (model-specific + filtered\n * anthropic-beta) for downstream Copilot calls.\n * @param advisorModel Which model to route advisor calls to. Defaults\n * to ADVISOR_DEFAULT_MODEL (cross-lab).\n */\nexport function buildAdvisorStream(opts: {\n firstResponse: Response\n initialConversation: Array<AnyRecord>\n baseBody: AnyRecord\n requestHeaders: Record<string, string>\n advisorModel?: string\n advisorEffort?: Effort\n}): ReadableStream<Uint8Array> {\n const advisorModel = opts.advisorModel ?? ADVISOR_DEFAULT_MODEL\n const advisorEffort = opts.advisorEffort ?? ADVISOR_DEFAULT_EFFORT\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n const conversation = [...opts.initialConversation]\n let messageStartForwarded = false\n let nextSyntheticIndex = 0\n let turnsRun = 0\n\n const safeEnqueue = (bytes: Uint8Array): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (err) {\n if (isControllerClosedError(err)) return false\n throw err\n }\n }\n\n const safeEnqueueEvent = (type: string, data: AnyRecord): boolean =>\n safeEnqueue(ENCODER.encode(sseEvent(type, data)))\n\n // Process one Copilot streaming response. Returns the assistant\n // turn's blocks + the advisor tool_use info if one was called.\n // Forwards events to the client as it goes.\n async function processOneTurn(\n response: Response,\n ): Promise<{\n capturedBlocks: Array<CapturedBlock>\n advisorToolUse: ToolUseTracker | null\n }> {\n const capturedBlocks: Array<CapturedBlock> = []\n let advisorToolUse: ToolUseTracker | null = null\n // Track which upstream block index corresponds to which entry\n // in capturedBlocks (so deltas know which to update).\n const indexToBlock = new Map<number, CapturedBlock>()\n\n for await (const ev of events(response)) {\n if (!ev.event || !ev.data) continue\n let payload: AnyRecord\n try {\n payload = JSON.parse(ev.data) as AnyRecord\n } catch {\n // Non-JSON data — forward as-is (defensive).\n const ok = safeEnqueue(ENCODER.encode(`event: ${ev.event}\\ndata: ${ev.data}\\n\\n`))\n if (!ok) return { capturedBlocks, advisorToolUse }\n continue\n }\n\n switch (ev.event) {\n case \"message_start\": {\n if (!messageStartForwarded) {\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n messageStartForwarded = true\n }\n // Suppress duplicate message_start on continuation turns —\n // we keep one open for the entire advisor loop.\n continue\n }\n\n case \"content_block_start\": {\n const block = (payload as AnyRecord).content_block as AnyRecord | undefined\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n if (block && upstreamIndex !== undefined) {\n // Re-index to the synthetic stream's monotonic index\n // (continuation turns reset their upstream index to 0,\n // which would collide with prior turns' indices).\n const myIndex = nextSyntheticIndex++\n\n if (\n block.type === \"tool_use\"\n && block.name === ADVISOR_INTERNAL_TOOL_NAME\n ) {\n // Translate to server_tool_use{advisor}\n const id =\n typeof block.id === \"string\"\n ? block.id\n : `toolu_advisor_${myIndex}`\n advisorToolUse = {\n index: myIndex,\n id,\n clientId: toClientServerToolUseId(id, myIndex),\n inputJson: \"\",\n }\n const translated = {\n ...payload,\n index: myIndex,\n content_block: {\n type: \"server_tool_use\",\n id: advisorToolUse.clientId,\n name: ADVISOR_CLIENT_TOOL_NAME,\n input: {},\n },\n }\n if (!safeEnqueueEvent(ev.event, translated)) return { capturedBlocks, advisorToolUse }\n // Track for later — the Copilot-replay continuation\n // turn needs to round-trip with the INTERNAL name +\n // ORIGINAL toolu_* id (Copilot doesn't know\n // server_tool_use). The advisor branch reuses the\n // standard captured-block pipeline (deltas accumulate,\n // input parses) so that future versions of advisor\n // that take params would Just Work — we synthesize\n // the actual replay shape in the content mapping.\n const captured: CapturedBlock = {\n block: {\n type: \"tool_use\",\n id,\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input: {},\n },\n partialJson: \"\",\n advisorReplay: { id },\n }\n capturedBlocks.push(captured)\n indexToBlock.set(upstreamIndex, captured)\n } else {\n // Forward as-is, with re-indexed.\n const reindexed = { ...payload, index: myIndex }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n // Store the raw content_block verbatim — preserves\n // every field upstream sent (including ones the proxy\n // doesn't know about: thinking, signature, image src,\n // document data, citations, etc.). Mutated in place\n // by deltas; emitted verbatim on replay.\n const captured: CapturedBlock = {\n block: { ...block },\n partialJson: \"\",\n }\n capturedBlocks.push(captured)\n indexToBlock.set(upstreamIndex, captured)\n }\n }\n continue\n }\n\n case \"content_block_delta\": {\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n const delta = (payload as AnyRecord).delta as AnyRecord | undefined\n if (upstreamIndex !== undefined) {\n const captured =\n upstreamIndex !== undefined ? indexToBlock.get(upstreamIndex) : undefined\n // Re-index for the outgoing event\n const reindexed = {\n ...payload,\n index: captured\n ? capturedBlocks.indexOf(captured) >= 0\n ? // Find the synthetic index by matching back.\n nextSyntheticIndex - capturedBlocks.length + capturedBlocks.indexOf(captured)\n : upstreamIndex\n : upstreamIndex,\n }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n // Accumulate every delta type into the right field on\n // captured.block. The block is mutated in place; on\n // replay it's emitted verbatim, so every field upstream\n // sent (text, thinking, signature, citations, image\n // src, document data, etc.) flows back correctly.\n if (captured && delta) {\n if (delta.type === \"text_delta\" && typeof delta.text === \"string\") {\n captured.block.text =\n ((captured.block.text as string | undefined) ?? \"\") + delta.text\n } else if (\n delta.type === \"thinking_delta\"\n && typeof delta.thinking === \"string\"\n ) {\n // Anthropic spec: thinking blocks must carry their\n // text on replay. signature_delta carries the\n // cryptographic signature separately.\n captured.block.thinking =\n ((captured.block.thinking as string | undefined) ?? \"\") + delta.thinking\n } else if (\n delta.type === \"signature_delta\"\n && typeof delta.signature === \"string\"\n ) {\n // Concatenate verbatim — Anthropic verifies\n // signatures cryptographically; mutating bytes\n // (e.g., normalization, base64 decode/re-encode)\n // would break verification. Pure string append.\n captured.block.signature =\n ((captured.block.signature as string | undefined) ?? \"\") + delta.signature\n } else if (\n delta.type === \"input_json_delta\"\n && typeof delta.partial_json === \"string\"\n ) {\n captured.partialJson += delta.partial_json\n } else if (\n delta.type === \"citations_delta\"\n && delta.citation\n ) {\n // Append citations array. Future-proof for the\n // citations Anthropic feature without us needing\n // to know its shape.\n if (!Array.isArray(captured.block.citations)) {\n captured.block.citations = [] as Array<unknown>\n }\n ;(captured.block.citations as Array<unknown>).push(delta.citation)\n }\n // Other delta types: leave block as-is. The\n // content_block_start payload is preserved verbatim,\n // so any future delta type that the proxy hasn't\n // explicitly accumulated still has the original\n // start-state to fall back to.\n }\n } else {\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n }\n continue\n }\n\n case \"content_block_stop\": {\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n const captured = upstreamIndex !== undefined ? indexToBlock.get(upstreamIndex) : undefined\n const reindexed = {\n ...payload,\n index: captured\n ? nextSyntheticIndex - capturedBlocks.length + capturedBlocks.indexOf(captured)\n : (upstreamIndex ?? 0),\n }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n\n // Finalize block state for replay:\n if (captured) {\n // (a) For tool_use blocks, parse the accumulated raw\n // partial_json into the block's `input` field.\n // Anthropic spec requires `tool_use.input` to be a\n // parsed JSON object on replay, not a string.\n // Warn-log on parse failure rather than silent\n // fallback so corruption surfaces in production\n // stderr (codex round-7).\n if (\n captured.block.type === \"tool_use\"\n && captured.partialJson.length > 0\n ) {\n try {\n captured.block.input = JSON.parse(captured.partialJson)\n } catch (err) {\n consola.warn(\n `advisor: malformed input_json_delta for tool_use `\n + `id=${(captured.block.id as string | undefined) ?? \"?\"} `\n + `name=${(captured.block.name as string | undefined) ?? \"?\"} `\n + `partialJson.length=${captured.partialJson.length} `\n + `parseError=${err instanceof Error ? err.message : String(err)}`,\n )\n captured.block.input = {}\n }\n }\n // (b) Drop empty text blocks from replay — empty\n // {type:\"text\", text:\"\"} is at best meaningless and\n // at worst spec-invalid (codex round-7).\n if (\n captured.block.type === \"text\"\n && (typeof captured.block.text !== \"string\"\n || (captured.block.text as string).length === 0)\n ) {\n captured.dropFromReplay = true\n }\n }\n continue\n }\n\n case \"message_delta\": {\n // Forward as-is (usage updates etc.)\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n continue\n }\n\n case \"message_stop\": {\n // CRITICAL: do NOT forward yet if advisor was called —\n // we need to run advisor + continue the loop. message_stop\n // ends the entire outgoing assistant turn. Only emit it\n // when the advisor loop is fully done.\n if (advisorToolUse) {\n return { capturedBlocks, advisorToolUse }\n }\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n return { capturedBlocks, advisorToolUse }\n }\n\n default: {\n // Unknown event — forward as-is.\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n }\n }\n }\n return { capturedBlocks, advisorToolUse }\n }\n\n try {\n let response: Response = opts.firstResponse\n\n for (turnsRun = 0; turnsRun < ADVISOR_MAX_TURNS; turnsRun++) {\n const { capturedBlocks, advisorToolUse } = await processOneTurn(response)\n\n if (!advisorToolUse) {\n // No advisor call this turn — message_stop was already\n // forwarded. We're done.\n return\n }\n\n // Advisor was called this turn. Run advisor model with the\n // full conversation extended by the assistant turn.\n //\n // Replay strategy: emit captured.block VERBATIM for every\n // captured block (preserves thinking, signature, redacted_\n // thinking, image, document, citations, anything Anthropic\n // adds tomorrow). Special-case ONLY the advisor block, which\n // needs the INTERNAL `__anthropic_advisor` name + ORIGINAL\n // `toolu_*` id (Copilot doesn't know server_tool_use).\n const assistantTurn = {\n role: \"assistant\",\n content: capturedBlocks\n .filter((c) => !c.dropFromReplay)\n .map((c) => {\n if (c.advisorReplay) {\n // Use the parsed input if any input_json_delta\n // arrived; otherwise default to {}. Don't bake\n // \"advisor takes no input\" as a load-bearing\n // invariant (codex round-7).\n const input =\n typeof c.block.input === \"object\" && c.block.input !== null\n ? (c.block.input as AnyRecord)\n : {}\n return {\n type: \"tool_use\",\n id: c.advisorReplay.id, // toolu_*, NOT srvtoolu_*\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input,\n }\n }\n return c.block // verbatim — the bug fix\n }),\n }\n conversation.push(assistantTurn)\n\n let advisorText: string\n try {\n advisorText = await runAdvisor(conversation, advisorModel, advisorEffort)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.warn(`Advisor model call failed: ${msg}`)\n advisorText =\n `[Advisor unavailable: ${msg}. Continuing without external review — `\n + `proceed with caution and consider self-checking against your `\n + `primary-source evidence.]`\n }\n\n // Synthesize advisor_tool_result block to client.\n // tool_use_id MUST be the client-facing srvtoolu_* id so it\n // pairs with the server_tool_use block emitted earlier; the\n // internal toolu_* id is only used in the Copilot-replay\n // path below.\n const resultIndex = nextSyntheticIndex++\n const startOk = safeEnqueueEvent(\"content_block_start\", {\n type: \"content_block_start\",\n index: resultIndex,\n content_block: {\n type: \"advisor_tool_result\",\n tool_use_id: advisorToolUse.clientId,\n content: { type: \"advisor_result\", text: advisorText },\n },\n })\n if (!startOk) return\n const stopOk = safeEnqueueEvent(\"content_block_stop\", {\n type: \"content_block_stop\",\n index: resultIndex,\n })\n if (!stopOk) return\n\n // Append the tool_result to conversation as a USER turn for\n // the next Copilot call. NOTE we use the standard tool_result\n // shape (Copilot doesn't know advisor_tool_result).\n conversation.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: advisorToolUse.id,\n content: advisorText,\n },\n ],\n })\n\n // Make the next Copilot call to continue the model's response\n // post-advisor. Reuse baseBody fields (max_tokens, system,\n // tools, etc.) but with the extended conversation and\n // stream:true.\n const continuationBody = JSON.stringify({\n ...opts.baseBody,\n messages: conversation,\n stream: true,\n })\n response = await createMessages(continuationBody, opts.requestHeaders)\n }\n\n // Loop exhausted. Synthesize final message_stop + an error text\n // block so the client doesn't hang.\n const finalIndex = nextSyntheticIndex++\n safeEnqueueEvent(\"content_block_start\", {\n type: \"content_block_start\",\n index: finalIndex,\n content_block: { type: \"text\", text: \"\" },\n })\n safeEnqueueEvent(\"content_block_delta\", {\n type: \"content_block_delta\",\n index: finalIndex,\n delta: {\n type: \"text_delta\",\n text: `\\n\\n[Advisor loop exceeded ${ADVISOR_MAX_TURNS} turns; halting]`,\n },\n })\n safeEnqueueEvent(\"content_block_stop\", {\n type: \"content_block_stop\",\n index: finalIndex,\n })\n safeEnqueueEvent(\"message_stop\", { type: \"message_stop\" })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.error(`Advisor stream error: ${msg}`)\n safeEnqueueEvent(\"error\", {\n type: \"error\",\n error: { type: \"api_error\", message: `advisor loop failed: ${msg}` },\n })\n } finally {\n try {\n controller.close()\n } catch {\n // already closed\n }\n }\n },\n })\n}\n","/**\n * Inbound /v1/messages body sanitizer.\n *\n * Today this only handles ADVISOR-related corruption — the\n * proxy-generated `server_tool_use{name:\"advisor\"}` and paired\n * `advisor_tool_result` blocks may travel through Claude Code's\n * persisted conversation state with a malformed `id`/`tool_use_id`\n * (e.g., a leftover `toolu_*` value from before the round-5 fix).\n * Every replay of that history through `/v1/messages` would 400 at\n * Copilot's spec validator without rewriting them on inbound. This\n * module performs the rewrite.\n *\n * **Out of scope** (deliberate, per gemini-critic round 6 — the \"ID\n * round-trip trap\"): generic `tool_use.id` and `tool_result.tool_use_id`\n * are stateful references between requests; rewriting them statelessly\n * would break Claude Code's client-side tool tracker. Only advisor\n * blocks are touched here, because both sides of an advisor pair\n * (`server_tool_use` + `advisor_tool_result`) are proxy-generated and\n * round-trip together in the same request body — so a per-request\n * deterministic transformation preserves pairing without cross-request\n * state.\n *\n * **Round-7 holistic fix**: Copilot rejects `server_tool_use{name:\n * \"advisor\"}` outright (spec validator says only `web_search`,\n * `tool_search_tool_regex`, `tool_search_tool_bm25` are allowed), so\n * fixing the id format alone isn't enough. The sanitizer also\n * **translates** historical advisor pairs to the\n * `tool_use{__anthropic_advisor}` + `tool_result` shape Copilot\n * accepts (per user direction \"Option C\"). Multi-turn split is\n * required because `tool_result` must appear in a `user` role per\n * Anthropic spec. The `__anthropic_advisor` tool definition is\n * re-injected into `tools[]` so the `tool_use.name` reference\n * resolves.\n */\nimport {\n ADVISOR_INTERNAL_TOOL_NAME,\n ADVISOR_TOOL_INSTRUCTIONS,\n} from \"~/services/advisor/advisor\"\n\ntype AnyRecord = Record<string, unknown>\n\n/**\n * Convert a `srvtoolu_*` id to the matching `toolu_*` id used in the\n * Copilot-replay shape (`tool_use.id` must match `^toolu_*$`). For\n * any other input shape, fall back to a synthesized `toolu_advisor_N`\n * id.\n */\nfunction toCopilotToolUseId(srvId: string, fallbackIndex: number): string {\n if (srvId.startsWith(\"srvtoolu_\")) {\n const suffix = srvId.slice(\"srvtoolu_\".length)\n if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `toolu_${suffix}`\n }\n return `toolu_advisor_${fallbackIndex}`\n}\n\n/**\n * Fast-path detector: returns true if the raw body has any chance of\n * needing sanitization. Avoids a full JSON parse for the common case\n * where the body is already spec-compliant.\n *\n * Looks for either an Anthropic-native advisor typed tool entry, or\n * any advisor-related block type that would need rewriting/\n * translating.\n */\nfunction bodyMightNeedSanitize(rawBody: string): boolean {\n return (\n rawBody.includes('\"server_tool_use\"')\n || rawBody.includes('\"advisor_tool_result\"')\n || /\"type\":\"advisor_\\d+\"/.test(rawBody)\n )\n}\n\n/**\n * Translate one assistant turn's content array, splitting at advisor\n * pairs into the multi-message structure Copilot accepts.\n *\n * Input shape (Claude Code stores everything in one assistant turn):\n * [text*, server_tool_use{advisor}, advisor_tool_result, text*, ...]\n *\n * Output: array of {role, content[]} message objects, alternating\n * assistant→user→assistant for each advisor pair encountered.\n */\nfunction splitAssistantTurnAtAdvisorPairs(\n originalContent: Array<unknown>,\n syntheticIndexRef: { value: number },\n): { messages: Array<AnyRecord>; translated: boolean } {\n const messages: Array<AnyRecord> = []\n let currentAssistantContent: Array<unknown> = []\n let translated = false\n // Walk linearly. When we see `server_tool_use{name:\"advisor\"}`,\n // expect the very next block to be `advisor_tool_result`. Translate\n // both, split into assistant→user→[continued assistant].\n let i = 0\n while (i < originalContent.length) {\n const block = originalContent[i]\n const b = (typeof block === \"object\" && block !== null) ? (block as AnyRecord) : null\n\n if (\n b\n && b.type === \"server_tool_use\"\n && b.name === ADVISOR_INTERNAL_TOOL_NAME.replace(/^__anthropic_/, \"\") // \"advisor\"\n ) {\n const stuId = typeof b.id === \"string\" ? b.id : \"\"\n // The next block should be the paired advisor_tool_result.\n const nextBlock = originalContent[i + 1]\n const next =\n typeof nextBlock === \"object\" && nextBlock !== null\n ? (nextBlock as AnyRecord)\n : null\n\n // Synthesize a Copilot-shape toolu_* id for the translated pair.\n // Prefer to derive from the existing id (preserves any\n // identifying suffix); if malformed, fall back to a synthesized\n // advisor_N id. Then both blocks of the pair get the SAME id.\n const copilotId = stuId.startsWith(\"srvtoolu_\")\n ? toCopilotToolUseId(stuId, syntheticIndexRef.value++)\n : stuId.startsWith(\"toolu_\") && /^toolu_[a-zA-Z0-9_]+$/.test(stuId)\n ? stuId\n : `toolu_advisor_${syntheticIndexRef.value++}`\n\n // Emit the assistant turn so far + the translated tool_use.\n currentAssistantContent.push({\n type: \"tool_use\",\n id: copilotId,\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input: {},\n })\n messages.push({ role: \"assistant\", content: currentAssistantContent })\n translated = true\n\n // Translate the paired advisor_tool_result → tool_result in a\n // new user turn.\n let resultText = \"\"\n if (next && next.type === \"advisor_tool_result\") {\n const c = next.content\n if (typeof c === \"string\") {\n resultText = c\n } else if (typeof c === \"object\" && c !== null) {\n const txt = (c as AnyRecord).text\n if (typeof txt === \"string\") resultText = txt\n }\n i += 2 // consume both blocks\n } else {\n // No paired result block — synthesize an empty result so\n // Copilot's tool-use/tool-result pairing stays consistent.\n resultText = \"[Advisor result missing in conversation history.]\"\n i += 1\n }\n messages.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: copilotId,\n content: resultText,\n },\n ],\n })\n\n // Start a fresh assistant content array for any blocks after\n // the advisor pair.\n currentAssistantContent = []\n continue\n }\n\n if (b && b.type === \"advisor_tool_result\") {\n // Stray advisor_tool_result without a preceding server_tool_use.\n // Drop it — sending it to Copilot would 400. Loss is minor:\n // this only happens if Claude Code's history is corrupt.\n translated = true\n i += 1\n continue\n }\n\n // Pass-through any other block.\n currentAssistantContent.push(block)\n i += 1\n }\n\n // Flush any trailing assistant content as a final message.\n if (currentAssistantContent.length > 0) {\n messages.push({ role: \"assistant\", content: currentAssistantContent })\n }\n // If we never split (no advisor blocks), return the original as one\n // message so the caller can detect \"no change\" and short-circuit.\n if (!translated) {\n return {\n messages: [{ role: \"assistant\", content: originalContent }],\n translated: false,\n }\n }\n return { messages, translated: true }\n}\n\nexport function sanitizeAnthropicBody(rawBody: string): string {\n if (!bodyMightNeedSanitize(rawBody)) return rawBody\n\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody) as AnyRecord\n } catch {\n return rawBody\n }\n\n let mutated = false\n\n // 1. Strip Anthropic-native `advisor_*` typed tools from `tools[]`.\n // Copilot 400s on the unknown tool type. Always-strip (vs only\n // when ADVISOR is enabled per `injectAdvisorTool`) covers the case\n // where Claude Code injects the typed tool independently of the\n // beta header.\n if (Array.isArray(parsed.tools)) {\n const tools = parsed.tools as Array<unknown>\n const before = tools.length\n const filtered = tools.filter((t) => {\n if (typeof t !== \"object\" || t === null) return true\n const type = (t as AnyRecord).type\n return typeof type !== \"string\" || !type.startsWith(\"advisor_\")\n })\n if (filtered.length !== before) {\n parsed.tools = filtered\n mutated = true\n }\n }\n\n // 2. Walk messages[] and translate any assistant turns that contain\n // advisor blocks (server_tool_use{name:\"advisor\"} +\n // advisor_tool_result). Per user direction round-7 \"Option C\":\n // rewrite to the tool_use{__anthropic_advisor} + tool_result\n // shape Copilot accepts, splitting into multi-turn assistant→\n // user→assistant as needed (Anthropic spec requires tool_result\n // in user role).\n if (Array.isArray(parsed.messages)) {\n const original = parsed.messages as Array<unknown>\n const rebuilt: Array<unknown> = []\n let anyTranslated = false\n const syntheticIndexRef = { value: 0 }\n for (const msg of original) {\n if (\n typeof msg !== \"object\"\n || msg === null\n || (msg as AnyRecord).role !== \"assistant\"\n ) {\n rebuilt.push(msg)\n continue\n }\n const content = (msg as AnyRecord).content\n if (!Array.isArray(content)) {\n rebuilt.push(msg)\n continue\n }\n // Quick check: does this assistant turn contain any advisor\n // blocks? If not, pass through unchanged.\n const hasAdvisorBlocks = content.some((b) => {\n if (typeof b !== \"object\" || b === null) return false\n const type = (b as AnyRecord).type\n const name = (b as AnyRecord).name\n return (\n (type === \"server_tool_use\" && name === \"advisor\")\n || type === \"advisor_tool_result\"\n )\n })\n if (!hasAdvisorBlocks) {\n rebuilt.push(msg)\n continue\n }\n const { messages: split, translated } = splitAssistantTurnAtAdvisorPairs(\n content as Array<unknown>,\n syntheticIndexRef,\n )\n if (translated) {\n anyTranslated = true\n for (const m of split) rebuilt.push(m)\n } else {\n rebuilt.push(msg)\n }\n }\n if (anyTranslated) {\n parsed.messages = rebuilt\n mutated = true\n // Re-inject __anthropic_advisor tool definition into tools[]\n // so the translated tool_use.name resolves at Copilot's\n // validator. Idempotent: skip if already present.\n const existingTools = Array.isArray(parsed.tools)\n ? (parsed.tools as Array<unknown>)\n : []\n const alreadyInjected = existingTools.some((t) => {\n if (typeof t !== \"object\" || t === null) return false\n return (t as AnyRecord).name === ADVISOR_INTERNAL_TOOL_NAME\n })\n if (!alreadyInjected) {\n parsed.tools = [\n ...existingTools,\n {\n name: ADVISOR_INTERNAL_TOOL_NAME,\n description: ADVISOR_TOOL_INSTRUCTIONS,\n input_schema: {\n type: \"object\",\n properties: {},\n required: [],\n },\n },\n ]\n }\n }\n }\n\n if (!mutated) return rawBody\n return JSON.stringify(parsed)\n}\n\n","import consola from \"consola\"\n\nconst PREVIEW_LIMIT = 200\n\nexport async function parseJsonOrDiagnose<T = unknown>(\n response: Response,\n routePath: string,\n): Promise<T> {\n const cloned = response.clone()\n try {\n return (await response.json()) as T\n } catch (error) {\n const contentType = response.headers.get(\"content-type\") ?? \"(none)\"\n const bodyText = await cloned.text().catch(() => \"(unreadable)\")\n const preview =\n bodyText.length > PREVIEW_LIMIT\n ? bodyText.slice(0, PREVIEW_LIMIT) + \"...(truncated)\"\n : bodyText\n consola.error(\n `Upstream JSON parse failed at ${routePath}: status=${response.status} content-type=\"${contentType}\" body[0..${PREVIEW_LIMIT}]=${JSON.stringify(preview)}`,\n )\n throw error\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { logRequest } from \"~/lib/request-log\"\nimport { sanitizeAnthropicBody } from \"~/lib/sanitize-anthropic-body\"\nimport { filterBetaHeader, resolveModel } from \"~/lib/utils\"\nimport { state } from \"~/lib/state\"\nimport { countTokens } from \"~/services/copilot/create-messages\"\nimport { parseJsonOrDiagnose } from \"~/lib/diagnose-response\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst isWebSearchTool = (tool: AnyRecord): boolean =>\n (typeof tool.type === \"string\" && tool.type.startsWith(\"web_search\")) ||\n tool.name === \"web_search\"\n\n/**\n * Strip web_search tools from the request body before forwarding\n * to Copilot's count_tokens endpoint, which rejects unknown tool types.\n * Returns the original raw body if no web_search tools are present.\n */\nfunction stripWebSearchFromBody(rawBody: string): string {\n if (!rawBody.includes(\"web_search\")) return rawBody\n\n let body: AnyRecord\n try {\n body = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n\n const hasWebSearch = body.tools?.some(\n (tool: AnyRecord) => isWebSearchTool(tool),\n )\n if (!hasWebSearch) return rawBody\n\n body.tools = body.tools.filter(\n (tool: AnyRecord) => !isWebSearchTool(tool),\n )\n\n if (body.tools.length === 0) {\n body.tools = undefined\n body.tool_choice = undefined\n } else if (\n body.tool_choice &&\n typeof body.tool_choice === \"object\" &&\n body.tool_choice.type === \"tool\"\n ) {\n const choiceName = body.tool_choice.name\n if (\n choiceName &&\n !body.tools.some((tool: AnyRecord) => tool.name === choiceName)\n ) {\n body.tool_choice = { type: \"auto\" }\n }\n }\n\n return JSON.stringify(body)\n}\n\n/**\n * Passthrough handler for Anthropic token counting.\n * Strips web_search tools and forwards beta headers to Copilot's\n * native /v1/messages/count_tokens endpoint.\n */\nexport async function handleCountTokens(c: Context) {\n const startTime = Date.now()\n const rawBody = await c.req.text()\n // Inbound advisor-history sanitization (mirrors handler.ts) — count\n // tokens uses the same Copilot validator, so a malformed\n // server_tool_use block in the conversation history would 400 here\n // too. Scoped narrowly to advisor pairs.\n const sanitizedBody = sanitizeAnthropicBody(rawBody)\n const strippedBody = stripWebSearchFromBody(sanitizedBody)\n\n // Phase G fail-fast: same rationale as handler.ts. count_tokens uses\n // the same Copilot schema validator, so mcp_servers in the body\n // produces the same 400 — surface clearly here too.\n if (strippedBody.includes('\"mcp_servers\"')) {\n try {\n const probe = JSON.parse(strippedBody) as AnyRecord\n if (Array.isArray(probe.mcp_servers) && probe.mcp_servers.length > 0) {\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Inline `mcp_servers` body field is not supported by github-router. \"\n + \"Configure remote MCP servers as local stdio entries in `~/.claude/mcp.json` instead.\",\n },\n },\n 400,\n )\n }\n } catch {\n // Not valid JSON — fall through to downstream parse-error path.\n }\n }\n\n const { body: finalBody, originalModel, resolvedModel } = resolveModelInBody(strippedBody)\n\n const extraHeaders: Record<string, string> = {}\n const anthropicBeta = c.req.header(\"anthropic-beta\")\n if (anthropicBeta) {\n const filtered = filterBetaHeader(anthropicBeta)\n if (filtered) extraHeaders[\"anthropic-beta\"] = filtered\n }\n\n const modelId = resolvedModel ?? originalModel\n const selectedModel = state.models?.data.find((m) => m.id === modelId)\n\n const response = await countTokens(finalBody, {\n ...selectedModel?.requestHeaders,\n ...extraHeaders,\n })\n const responseBody = await parseJsonOrDiagnose<{ input_tokens?: number }>(\n response,\n c.req.path,\n )\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens: responseBody.input_tokens,\n status: response.status,\n },\n selectedModel,\n startTime,\n )\n\n return c.json(responseBody)\n}\n\n/**\n * Parse the JSON body, resolve the model name, sanitize cache_control, and re-serialize.\n */\nfunction resolveModelInBody(rawBody: string): {\n body: string\n originalModel?: string\n resolvedModel?: string\n} {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return { body: rawBody }\n }\n\n const originalModel =\n typeof parsed.model === \"string\" ? parsed.model : undefined\n\n let modified = false\n if (originalModel) {\n const resolved = resolveModel(originalModel)\n if (resolved !== originalModel) {\n parsed.model = resolved\n modified = true\n }\n }\n\n const needsSanitize = rawBody.includes('\"scope\"')\n if (needsSanitize && sanitizeCacheControl(parsed)) {\n modified = true\n }\n\n // Strip Anthropic-only body fields Copilot 400s on. See\n // `stripAnthropicOnlyFields` in handler.ts for full rationale + empirical\n // evidence (2026-05-11 / 2026-05-13 verification). count_tokens uses the same\n // Copilot schema validator as /v1/messages so the same fields are rejected.\n const needsAnthropicOnlyStrip =\n rawBody.includes('\"budget\"')\n || rawBody.includes('\"output_config\"')\n || rawBody.includes('\"betas\"')\n || rawBody.includes('\"eager_input_streaming\"')\n if (needsAnthropicOnlyStrip && stripAnthropicOnlyFields(parsed)) {\n modified = true\n }\n\n const resolvedModel =\n typeof parsed.model === \"string\" ? parsed.model : originalModel\n\n return {\n body: modified ? JSON.stringify(parsed) : rawBody,\n originalModel,\n resolvedModel,\n }\n}\n\nfunction sanitizeCacheControl(body: AnyRecord): boolean {\n let stripped = false\n function stripScope(block: AnyRecord): void {\n if (block.cache_control?.scope !== undefined) {\n delete block.cache_control.scope\n if (Object.keys(block.cache_control).length === 0) {\n delete block.cache_control\n }\n stripped = true\n }\n }\n\n if (Array.isArray(body.system)) {\n for (const block of body.system) stripScope(block)\n }\n\n if (Array.isArray(body.messages)) {\n for (const msg of body.messages) {\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n stripScope(block)\n if (Array.isArray(block.content)) {\n for (const nested of block.content) stripScope(nested)\n }\n }\n }\n }\n }\n\n if (Array.isArray(body.tools)) {\n for (const tool of body.tools) stripScope(tool)\n }\n\n return stripped\n}\n\n/**\n * Strip top-level body fields Copilot 400s on (budget, output_config.schema,\n * betas). Duplicated structurally from handler.ts because count_tokens uses\n * its own JSON-pass; the bodies are independent. Behavior must stay in lock-\n * step with handler.ts's stripAnthropicOnlyFields — covered by integration\n * tests (Phase F P2.4).\n */\nfunction stripAnthropicOnlyFields(body: AnyRecord): boolean {\n let stripped = false\n if (body.budget !== undefined) {\n consola.warn(\n \"[count_tokens] Stripping body-level `budget` field (Copilot 400s)\",\n )\n delete body.budget\n stripped = true\n }\n if (body.output_config !== undefined) {\n if (body.output_config && typeof body.output_config === \"object\") {\n const oc = body.output_config as AnyRecord\n const PROXY_OWNED_FIELDS = new Set([\"effort\"])\n let strippedAny = false\n for (const key of Object.keys(oc)) {\n if (!PROXY_OWNED_FIELDS.has(key)) {\n delete oc[key]\n strippedAny = true\n }\n }\n if (strippedAny) {\n consola.warn(\n \"[count_tokens] Stripping client-set `output_config` Structured-Outputs fields (Copilot 400s on `output_config.*` other than `effort`)\",\n )\n if (Object.keys(oc).length === 0) {\n delete body.output_config\n }\n stripped = true\n }\n }\n }\n if (Array.isArray(body.betas)) {\n consola.warn(\n \"[count_tokens] Stripping body-level `betas` array (Copilot 400s; conveyed via header)\",\n )\n delete body.betas\n stripped = true\n }\n // Per-tool field strip: `eager_input_streaming` (FGTS). Mirrors handler.ts.\n if (Array.isArray(body.tools)) {\n let warnedFGTS = false\n for (const tool of body.tools) {\n if (typeof tool === \"object\" && tool !== null) {\n const t = tool as AnyRecord\n if (t.eager_input_streaming !== undefined) {\n delete t.eager_input_streaming\n stripped = true\n if (!warnedFGTS) {\n consola.warn(\n \"[count_tokens] Stripping per-tool `eager_input_streaming` (Copilot 400s on `tools.*.custom.eager_input_streaming`)\",\n )\n warnedFGTS = true\n }\n }\n }\n }\n }\n return stripped\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { parseJsonOrDiagnose } from \"~/lib/diagnose-response\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest, logRequestFields } from \"~/lib/request-log\"\nimport { sanitizeAnthropicBody } from \"~/lib/sanitize-anthropic-body\"\nimport { state } from \"~/lib/state\"\nimport { relayAnthropicStream } from \"~/lib/stream-relay\"\nimport { filterBetaHeader, resolveModel } from \"~/lib/utils\"\nimport {\n buildAdvisorStream,\n injectAdvisorTool,\n isAdvisorRequested,\n} from \"~/services/advisor/advisor\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport type { Model } from \"~/services/copilot/get-models\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst isWebSearchTool = (tool: AnyRecord): boolean =>\n (typeof tool.type === \"string\" && tool.type.startsWith(\"web_search\")) ||\n tool.name === \"web_search\"\n\n/**\n * Extract whitelisted beta headers from the incoming request to forward\n * to the Copilot API. VS Code sends these to enable extended features\n * like thinking, context management, and advanced tool use.\n */\nfunction extractBetaHeaders(c: Context): Record<string, string> {\n const headers: Record<string, string> = {}\n const anthropicBeta = c.req.header(\"anthropic-beta\")\n if (anthropicBeta) {\n const filtered = filterBetaHeader(anthropicBeta)\n if (filtered) headers[\"anthropic-beta\"] = filtered\n }\n return headers\n}\n\n/**\n * Extract the text content from the last user message for web search.\n * Handles both string content and content block arrays (multimodal).\n */\nfunction extractUserQuery(\n messages: Array<AnyRecord>,\n): string | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") return msg.content\n if (Array.isArray(msg.content)) {\n const textBlock = msg.content.find(\n (block: AnyRecord) => block.type === \"text\",\n )\n if (textBlock?.text) return textBlock.text as string\n }\n }\n }\n return undefined\n}\n\n/**\n * Check if any user message contains tool_result content blocks,\n * indicating a follow-up turn where we should skip web search.\n * In Anthropic format, tool results are content blocks inside user messages,\n * NOT separate role: \"tool\" messages like in OpenAI format.\n */\nfunction hasToolResultContent(messages: Array<AnyRecord>): boolean {\n return messages.some(\n (msg) =>\n Array.isArray(msg.content) &&\n msg.content.some(\n (block: AnyRecord) => block.type === \"tool_result\",\n ),\n )\n}\n\n/**\n * Inject web search results into the Anthropic system field.\n * Handles three cases: absent, string, or array of content blocks.\n * When array, prepends without cache_control to preserve existing directives.\n */\nfunction injectSearchResults(\n body: AnyRecord,\n searchContext: string,\n): void {\n if (body.system === undefined || body.system === null) {\n body.system = searchContext\n } else if (typeof body.system === \"string\") {\n body.system = `${searchContext}\\n\\n${body.system}`\n } else if (Array.isArray(body.system)) {\n body.system = [\n { type: \"text\", text: searchContext },\n ...body.system,\n ]\n }\n}\n\n/**\n * Strip web_search tools from the request and clean up tool_choice.\n * Returns the modified body object.\n */\nfunction stripWebSearchTool(body: AnyRecord): void {\n if (!body.tools) return\n\n body.tools = body.tools.filter(\n (tool: AnyRecord) => !isWebSearchTool(tool),\n )\n\n if (body.tools.length === 0) {\n body.tools = undefined\n body.tool_choice = undefined\n } else if (\n body.tool_choice &&\n typeof body.tool_choice === \"object\" &&\n body.tool_choice.type === \"tool\"\n ) {\n // If tool_choice forced the removed web_search tool, fall back to auto\n const choiceName = body.tool_choice.name\n if (\n choiceName &&\n !body.tools.some((tool: AnyRecord) => tool.name === choiceName)\n ) {\n body.tool_choice = { type: \"auto\" }\n }\n }\n}\n\n/**\n * Process web search if the request contains a web_search tool.\n * Performs the search, injects results into system, and strips the tool.\n * Returns the (possibly modified) body string to forward.\n */\nasync function processWebSearch(rawBody: string): Promise<string> {\n // Fast path: skip parsing if no web_search tool present\n if (!rawBody.includes(\"web_search\")) return rawBody\n\n let body: AnyRecord\n try {\n body = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n\n const hasWebSearch = body.tools?.some(\n (tool: AnyRecord) => isWebSearchTool(tool),\n )\n if (!hasWebSearch) return rawBody\n\n // Skip search on follow-up messages (tool call results)\n const hasToolResult = hasToolResultContent(body.messages ?? [])\n const query = hasToolResult ? undefined : extractUserQuery(body.messages ?? [])\n\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n injectSearchResults(body, searchContext)\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Always strip web_search tool regardless of whether search succeeded\n stripWebSearchTool(body)\n\n return JSON.stringify(body)\n}\n\nexport async function handleCompletion(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n const rawBody = await c.req.text()\n\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\"Anthropic request body:\", rawBody.slice(0, 2000))\n }\n\n // Opt-in field-key discovery (Phase 0.5 of the long-horizon plan).\n // No-op unless GH_ROUTER_LOG_FIELDS=1 is set. Feeds\n // scripts/discover-new-fields.sh.\n if (process.env.GH_ROUTER_LOG_FIELDS === \"1\") {\n let parsedForLog: unknown = undefined\n try {\n parsedForLog = JSON.parse(rawBody) as unknown\n } catch {\n // Body parse failures are surfaced downstream; don't double-warn here.\n }\n logRequestFields({\n path: c.req.path,\n body: parsedForLog,\n betaHeader: c.req.header(\"anthropic-beta\"),\n })\n }\n\n if (state.manualApprove) {\n await awaitApproval()\n }\n\n const betaHeaders = extractBetaHeaders(c)\n\n // Phase I: detect ADVISOR request BEFORE filterBetaHeader strips\n // the advisor-tool- prefix from the outgoing header. We need the raw\n // incoming header to know whether the user asked for ADVISOR.\n const incomingBeta = c.req.header(\"anthropic-beta\")\n const advisorEnabled = isAdvisorRequested(incomingBeta)\n\n let finalBody = await processWebSearch(rawBody)\n // Inbound advisor-history sanitization: rewrite malformed\n // server_tool_use ids in Claude Code's replayed conversation history\n // (left over from before the round-5 fix or any non-spec-compliant\n // source). Without this, Copilot 400s on\n // `messages.N.content.M.server_tool_use.id: String should match\n // pattern '^srvtoolu_[a-zA-Z0-9_]+$'`\n // when the conversation grows long enough to echo a malformed block.\n // Scoped narrowly to advisor pairs to avoid the ID round-trip trap\n // (see src/lib/sanitize-anthropic-body.ts header comment).\n finalBody = sanitizeAnthropicBody(finalBody)\n if (advisorEnabled) {\n // Inject __anthropic_advisor tool definition (with cc-backup's\n // ADVISOR_TOOL_INSTRUCTIONS as description) so the model knows\n // when to call it. Tool name uses double-underscore prefix to\n // avoid collision with any user MCP server's `advisor`.\n finalBody = injectAdvisorTool(finalBody)\n consola.info(\n \"ADVISOR enabled for this request — injecting __anthropic_advisor tool; will translate tool_use → server_tool_use{advisor} on the SSE stream\",\n )\n }\n\n // Phase G fail-fast (deferred translate path per codex-critic): if the\n // request includes inline `mcp_servers`, refuse with a clear Anthropic-\n // format error before forwarding. The original plan was to translate\n // (instantiate MCP clients server-side and inline tools) but the design\n // has structural holes — continuation after pool TTL isn't implementable\n // from the request alone, and streaming correctness during the multi-turn\n // tool loop is fragile. Local stdio MCP (~/.claude/mcp.json) covers the\n // common Claude usage; remote-managed MCP is the rare path. Fail-fast\n // with a clear pointer is the better Pareto choice (codex-critic 2/2/3\n // verdict on the translate-path design).\n if (finalBody.includes('\"mcp_servers\"')) {\n try {\n const probe = JSON.parse(finalBody) as AnyRecord\n if (Array.isArray(probe.mcp_servers) && probe.mcp_servers.length > 0) {\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Inline `mcp_servers` body field is not supported by github-router. \"\n + \"Configure remote MCP servers as local stdio entries in `~/.claude/mcp.json` instead — \"\n + \"Claude Code will spawn them locally and the proxy passes their tool calls through transparently. \"\n + \"(https://docs.claude.com/en/docs/claude-code/mcp)\",\n },\n },\n 400,\n )\n }\n } catch {\n // Body wasn't valid JSON — fall through, downstream handlers will\n // surface the parse error in their own way.\n }\n }\n\n // Resolve model name (e.g. opus → opus-1m variant) and translate\n // thinking-mode shape for adaptive-thinking models.\n const {\n body: resolvedBody,\n originalModel,\n resolvedModel,\n selectedModel,\n } = resolveModelInBody(finalBody)\n\n const modelId = resolvedModel ?? originalModel\n if (modelId) logEndpointMismatch(modelId, \"/v1/messages\")\n\n // Apply default anthropic-beta for Claude models when client sends none\n const effectiveBetas = applyDefaultBetas(betaHeaders, resolvedModel ?? originalModel)\n\n let response: Response\n try {\n response = await createMessages(resolvedBody, {\n ...selectedModel?.requestHeaders,\n ...effectiveBetas,\n })\n } catch (error) {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\"\n // Trust the upstream content-type when it's explicit. Two anomalies need\n // a fallback: (a) header missing entirely, (b) header is\n // `application/octet-stream` (some proxies normalize SSE this way). In\n // those cases, treat as streaming if the client asked for it via the\n // Accept header — Anthropic SDKs send `Accept: text/event-stream` for\n // streaming requests. We do NOT fall back when content-type is\n // explicitly `application/json` — that's almost always an upstream\n // error response that should be parsed via parseJsonOrDiagnose.\n const clientAcceptsSSE = (c.req.header(\"accept\") ?? \"\").includes(\n \"text/event-stream\",\n )\n let isStreaming = contentType.includes(\"text/event-stream\")\n if (!isStreaming && clientAcceptsSSE) {\n if (contentType === \"\" || contentType === \"application/octet-stream\") {\n consola.warn(\n `Upstream /v1/messages returned status=${response.status} content-type=${JSON.stringify(contentType)} but client requested streaming; treating response body as SSE`,\n )\n isStreaming = true\n }\n }\n\n if (debugEnabled) {\n consola.debug(\n `Upstream /v1/messages: status=${response.status} content-type=\"${contentType}\" isStreaming=${isStreaming}`,\n )\n }\n\n // Streaming: pipe the upstream SSE response body directly (or wrap\n // with the ADVISOR translate-loop if advisor was requested).\n if (isStreaming) {\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: response.status,\n streaming: true,\n },\n selectedModel,\n startTime,\n )\n\n if (debugEnabled) {\n consola.debug(\"Streaming response from Copilot /v1/messages\")\n }\n const streamHeaders: Record<string, string> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n }\n const requestId = response.headers.get(\"x-request-id\")\n if (requestId) streamHeaders[\"x-request-id\"] = requestId\n const reqId = response.headers.get(\"request-id\")\n if (reqId) streamHeaders[\"request-id\"] = reqId\n\n // Phase I: branch into the advisor translate-loop if the user\n // requested ADVISOR. The loop intercepts tool_use{__anthropic_advisor}\n // blocks, translates to server_tool_use{advisor}, runs the advisor\n // model server-side, emits advisor_tool_result, and continues the\n // Copilot conversation on the SAME SSE connection (no intermediate\n // message_stop). See src/services/advisor/advisor.ts for the design\n // (gemini-critic streaming-during-loop pattern).\n if (advisorEnabled && response.body) {\n // Parse the resolved body once to extract the conversation +\n // base body for continuation calls. The translate-loop needs\n // these to extend the conversation across advisor turns.\n let parsedBase: AnyRecord = {}\n try {\n parsedBase = JSON.parse(resolvedBody) as AnyRecord\n } catch {\n // Should not happen since resolveModelInBody just re-serialized\n // it. Fallback: pass empty conversation; translate-loop will\n // skip advisor calls if it can't construct continuations.\n }\n const initialConversation = Array.isArray(parsedBase.messages)\n ? (parsedBase.messages as Array<AnyRecord>)\n : []\n return new Response(\n buildAdvisorStream({\n firstResponse: response,\n initialConversation,\n baseBody: parsedBase,\n requestHeaders: {\n ...selectedModel?.requestHeaders,\n ...effectiveBetas,\n },\n }),\n {\n status: response.status,\n headers: streamHeaders,\n },\n )\n }\n\n return new Response(\n response.body\n ? relayAnthropicStream(response.body, { routePath: c.req.path })\n : null,\n {\n status: response.status,\n headers: streamHeaders,\n },\n )\n }\n\n // Non-streaming: extract usage from response body\n const responseBody = await parseJsonOrDiagnose<AnyRecord>(\n response,\n c.req.path,\n )\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens: responseBody.usage?.input_tokens,\n outputTokens: responseBody.usage?.output_tokens,\n status: response.status,\n },\n selectedModel,\n startTime,\n )\n\n if (debugEnabled) {\n consola.debug(\n \"Non-streaming response from Copilot /v1/messages:\",\n JSON.stringify(responseBody).slice(0, 2000),\n )\n }\n const xRequestId = response.headers.get(\"x-request-id\")\n if (xRequestId) c.header(\"x-request-id\", xRequestId)\n const requestIdHeader = response.headers.get(\"request-id\")\n if (requestIdHeader) c.header(\"request-id\", requestIdHeader)\n return c.json(responseBody, response.status as 200)\n}\n\n/**\n * Parse the JSON body, resolve the model name, sanitize cache_control\n * fields, translate thinking-mode shape for adaptive-thinking models,\n * and re-serialize. Returns the body string, original/resolved model\n * names, and the matching model metadata (if any).\n *\n * Re-serialization is skipped when no modifications are needed.\n */\nfunction resolveModelInBody(rawBody: string): {\n body: string\n originalModel?: string\n resolvedModel?: string\n selectedModel?: Model\n} {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return { body: rawBody }\n }\n\n const originalModel =\n typeof parsed.model === \"string\" ? parsed.model : undefined\n\n let modified = false\n if (originalModel) {\n const resolved = resolveModel(originalModel)\n if (resolved !== originalModel) {\n parsed.model = resolved\n modified = true\n }\n }\n\n const resolvedModel =\n typeof parsed.model === \"string\" ? parsed.model : originalModel\n\n const selectedModel = resolvedModel\n ? state.models?.data.find((m) => m.id === resolvedModel)\n : undefined\n\n // Translate thinking-mode shape for adaptive-thinking models — Copilot\n // wants {type:\"adaptive\"} + output_config.effort, not Anthropic's\n // {type:\"enabled\", budget_tokens}.\n if (translateThinking(parsed, selectedModel)) {\n modified = true\n }\n\n // Strip cache_control.scope — fast path skips when \"scope\" absent\n const needsSanitize = rawBody.includes('\"scope\"')\n if (needsSanitize && sanitizeCacheControl(parsed)) {\n modified = true\n }\n\n // Strip Anthropic-only top-level body fields Copilot 400s on. Empirical\n // verification (2026-05-11 / 2026-05-13 against api.enterprise.githubcopilot.com):\n // - `budget: {total_tokens}` (Task Budgets) → 400 \"budget: Extra inputs not permitted\"\n // - `output_config: {schema}` (Structured Outputs) → 400 \"output_config.schema: Extra...\"\n // - `betas: [...]` (top-level array, distinct from anthropic-beta header) → 400 \"betas: Extra...\"\n // - `tools[i].eager_input_streaming` (Fine-Grained Tool Streaming) → 400\n // \"tools.0.custom.eager_input_streaming: Extra inputs are not permitted\"\n // (the `.custom.` infix is Copilot's error-format; the actual emit\n // location from Claude Code is the top of each tool object per\n // https://platform.claude.com/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming).\n // Stripping disables only the streaming-chunk-size optimization;\n // correctness is unaffected — `input_json_delta` events still flow,\n // just with `partial_json:\"\"` instead of populated chunks.\n // Probes: `eager_input_streaming_strips` / `eager_input_streaming_passthrough`\n // in scripts/probe-copilot-compat.sh.\n // Fast-path skip when none of the field names appear in the raw body.\n // NOT stripped:\n // - `mcp_servers` — Phase G builds the translate path; silent strip\n // here would cause LLM to hallucinate tools (gemini-critic finding).\n // - `metadata: {user_id}` — Copilot 200s, ignores harmlessly. Strip\n // would be cosmetic (codex-critic: \"preserve unknown fields unless\n // documented reason\"); ~0.1ms re-serialize cost per request adds up.\n const needsAnthropicOnlyStrip =\n rawBody.includes('\"budget\"')\n || rawBody.includes('\"output_config\"')\n || rawBody.includes('\"betas\"')\n || rawBody.includes('\"eager_input_streaming\"')\n if (needsAnthropicOnlyStrip && stripAnthropicOnlyFields(parsed)) {\n modified = true\n }\n\n return {\n body: modified ? JSON.stringify(parsed) : rawBody,\n originalModel,\n resolvedModel,\n selectedModel,\n }\n}\n\nexport const EFFORT_ORDER = [\"low\", \"medium\", \"high\", \"xhigh\"] as const\n\n/**\n * Bucket a thinking budget into a Copilot reasoning-effort string.\n * `<2000`→low, `<8000`→medium, `<24000`→high, else→xhigh.\n * Defaults missing/non-numeric budgets to 8000 (\"high\").\n */\nexport function bucketEffort(budget: unknown): (typeof EFFORT_ORDER)[number] {\n const n =\n typeof budget === \"number\" && Number.isFinite(budget) ? budget : 8000\n if (n < 2000) return \"low\"\n if (n < 8000) return \"medium\"\n if (n < 24000) return \"high\"\n return \"xhigh\"\n}\n\n/**\n * Clamp a bucketed effort to the closest value in `supported`. Ties\n * resolve to the lower-tier option (per EFFORT_ORDER).\n *\n * Iterates EFFORT_ORDER (canonical low→xhigh) so the first match on a\n * given distance is always the lower-tier value, regardless of input\n * order in `supported`.\n */\nexport function clampEffort(\n bucketed: (typeof EFFORT_ORDER)[number],\n supported: Array<string>,\n): string {\n if (supported.includes(bucketed)) return bucketed\n const targetIdx = EFFORT_ORDER.indexOf(bucketed)\n let best: (typeof EFFORT_ORDER)[number] | undefined\n let bestDist = Infinity\n for (let i = 0; i < EFFORT_ORDER.length; i++) {\n const value = EFFORT_ORDER[i]\n if (!supported.includes(value)) continue\n const dist = Math.abs(i - targetIdx)\n // strict `<` keeps the first (lower-tier) on ties\n if (dist < bestDist) {\n bestDist = dist\n best = value\n }\n }\n return best ?? bucketed\n}\n\n/**\n * Translate Anthropic-shape `thinking:{type:\"enabled\", budget_tokens}` to\n * Copilot-shape `thinking:{type:\"adaptive\"}` + `output_config.effort`\n * when the resolved model declares `adaptive_thinking: true`.\n *\n * Returns true if the body was modified. No-op when the model doesn't\n * support adaptive thinking, when thinking is missing/disabled/already\n * adaptive, or when `body` isn't a plain object. Client-supplied\n * `output_config.effort` always wins over the bucketed value.\n */\nfunction translateThinking(body: AnyRecord, model?: Model): boolean {\n if (!model?.capabilities?.supports?.adaptive_thinking) return false\n const thinking = body.thinking\n if (!thinking || typeof thinking !== \"object\") return false\n if (thinking.type !== \"enabled\") return false\n\n const bucketed = bucketEffort(thinking.budget_tokens)\n const supported = model.capabilities.supports.reasoning_effort\n const effort =\n Array.isArray(supported) && supported.length > 0\n ? clampEffort(bucketed, supported)\n : bucketed\n\n body.thinking = { type: \"adaptive\" }\n\n const existing =\n body.output_config && typeof body.output_config === \"object\"\n ? (body.output_config as AnyRecord)\n : {}\n body.output_config = {\n ...existing,\n // client-supplied effort wins\n effort: existing.effort ?? effort,\n }\n\n return true\n}\n\n/**\n * Strip the `scope` field from all `cache_control` objects in the body.\n * Claude CLI 2.1.88+ sends {\"type\":\"ephemeral\",\"scope\":\"global\"} which\n * Copilot rejects. Mutates the parsed object in place.\n *\n * Covers: system blocks, message content blocks (including nested\n * tool_result content), and tool definitions.\n */\nfunction sanitizeCacheControl(body: AnyRecord): boolean {\n let stripped = false\n function stripScope(block: AnyRecord): void {\n if (block.cache_control?.scope !== undefined) {\n delete block.cache_control.scope\n if (Object.keys(block.cache_control).length === 0) {\n delete block.cache_control\n }\n stripped = true\n }\n }\n\n if (Array.isArray(body.system)) {\n for (const block of body.system) stripScope(block)\n }\n\n if (Array.isArray(body.messages)) {\n for (const msg of body.messages) {\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n stripScope(block)\n if (Array.isArray(block.content)) {\n for (const nested of block.content) stripScope(nested)\n }\n }\n }\n }\n }\n\n if (Array.isArray(body.tools)) {\n for (const tool of body.tools) stripScope(tool)\n }\n\n return stripped\n}\n\n/**\n * Apply default anthropic-beta values for Claude models when the client\n * (e.g. curl) sends no beta headers. Claude CLI sends its own betas,\n * so this only fires as a safety net for bare clients.\n */\nfunction applyDefaultBetas(\n betaHeaders: Record<string, string>,\n modelId?: string,\n): Record<string, string> {\n if (betaHeaders[\"anthropic-beta\"]) return betaHeaders\n if (!modelId || !modelId.startsWith(\"claude-\")) return betaHeaders\n\n return {\n ...betaHeaders,\n \"anthropic-beta\": [\n \"interleaved-thinking-2025-05-14\",\n \"context-management-2025-06-27\",\n ].join(\",\"),\n }\n}\n\n/**\n * Strip top-level body fields that Anthropic's Messages API accepts but\n * Copilot rejects with HTTP 400 \"Extra inputs are not permitted\". Mutates\n * `body` in place; returns true if anything was stripped.\n *\n * Empirical verification (2026-05-11):\n * POST /v1/messages?beta=true { ..., budget: {total_tokens: 10000} } → 400\n * POST /v1/messages?beta=true { ..., output_config: {schema: {...}} } → 400\n * POST /v1/messages?beta=true { ..., betas: [\"...\"] } → 400\n *\n * Each strip emits a one-line consola.warn so users running with these\n * features (e.g. `claude --max-budget-usd`, `--json-schema`) understand\n * the request succeeds with the *body field* dropped — semantics may\n * differ from upstream Anthropic. The corresponding `anthropic-beta`\n * header is preserved (Phase A allowlist) so the *intent* still flows\n * to Copilot, even if the per-request enforcement field is gone.\n *\n * NOT stripped here:\n * - `mcp_servers` (Phase G translate path — silent strip causes LLM\n * to hallucinate tools per gemini-critic finding)\n * - `metadata` (Copilot 200s, ignores harmlessly)\n */\nfunction stripAnthropicOnlyFields(body: AnyRecord): boolean {\n let stripped = false\n if (body.budget !== undefined) {\n consola.warn(\n \"Stripping body-level `budget` field (Copilot 400s; the `task-budgets-` beta header is preserved but cost ceiling is not enforced server-side)\",\n )\n delete body.budget\n stripped = true\n }\n if (body.output_config !== undefined) {\n // output_config has multiple known shapes:\n // - `{schema:{...}}` (Structured Outputs full form) — Copilot 400s\n // - `{type:\"json_object\"}` (Structured Outputs short form, used\n // by Claude Code's hook evaluator + the Anthropic SDK's\n // structured-output API) — Copilot 400s with the same\n // `output_config: Extra inputs are not permitted` message,\n // just at the top-level field rather than the nested .schema.\n // - `{effort:\"high\"}` (proxy-set during adaptive-thinking\n // translation) — Copilot 200s, required by translateThinking.\n //\n // Strategy: strip every Structured-Outputs field (`schema`,\n // `type`, `response_format`, anything else we don't recognize as\n // proxy-internal). Keep `effort` if present. If the object ends\n // up empty, drop the whole field.\n //\n // **Schema preservation via prompt injection**: stripping\n // `output_config.schema` removes server-side enforcement, which\n // makes the model's output non-deterministic. Claude Code's\n // hook evaluator then fails with \"JSON validation failed\" because\n // it tries to `JSON.parse(response)` and gets natural-language\n // text. To preserve the structured-output INTENT through Copilot,\n // append a system-prompt instruction telling the model to produce\n // JSON conforming to the schema. This isn't as strong as\n // server-side enforcement (the model may occasionally deviate),\n // but it's much better than no constraint at all.\n if (body.output_config && typeof body.output_config === \"object\") {\n const oc = body.output_config as AnyRecord\n const PROXY_OWNED_FIELDS = new Set([\"effort\"])\n // Capture the schema BEFORE stripping so we can inject it.\n const schema = oc.schema\n const ocType = oc.type\n let strippedAny = false\n for (const key of Object.keys(oc)) {\n if (!PROXY_OWNED_FIELDS.has(key)) {\n delete oc[key]\n strippedAny = true\n }\n }\n if (strippedAny) {\n consola.warn(\n \"Stripping client-set `output_config` Structured-Outputs fields\"\n + \" (Copilot 400s on `output_config.*` other than `effort`;\"\n + \" injecting schema as system-prompt instruction so the\"\n + \" model still produces JSON conforming to the structured-\"\n + \"outputs schema, since server-side enforcement is gone)\",\n )\n if (Object.keys(oc).length === 0) {\n delete body.output_config\n }\n if (schema !== undefined || ocType === \"json_object\") {\n appendStructuredOutputInstruction(body, schema, ocType)\n }\n stripped = true\n }\n }\n }\n if (Array.isArray(body.betas)) {\n consola.warn(\n \"Stripping body-level `betas` array (Copilot 400s; the betas are conveyed via the `anthropic-beta` header instead)\",\n )\n delete body.betas\n stripped = true\n }\n // Per-tool field strip: `eager_input_streaming` (Fine-Grained Tool Streaming).\n // Auto-enabled by getClaudeCodeEnvVars setting CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1\n // (see src/lib/server-setup.ts), which causes the Claude Code SDK to emit\n // `eager_input_streaming: true` on each custom tool definition. Copilot rejects.\n // JSON-AST traversal — never regex on the raw body (gemini-critic: would\n // corrupt prompt text containing the same string).\n if (Array.isArray(body.tools)) {\n let warnedFGTS = false\n for (const tool of body.tools) {\n if (typeof tool === \"object\" && tool !== null) {\n const t = tool as AnyRecord\n if (t.eager_input_streaming !== undefined) {\n delete t.eager_input_streaming\n stripped = true\n if (!warnedFGTS) {\n consola.warn(\n \"Stripping per-tool `eager_input_streaming` field (Copilot 400s on `tools.*.custom.eager_input_streaming`; FGTS chunk-size optimization disabled, but streaming correctness is unaffected — `input_json_delta` events still flow normally)\",\n )\n warnedFGTS = true\n }\n }\n }\n }\n }\n return stripped\n}\n\n/**\n * Append a system-prompt instruction telling the model to produce JSON\n * conforming to a Structured Outputs schema. Used after the proxy\n * strips `output_config` to preserve the schema enforcement intent\n * via prompt engineering instead of server-side validation.\n *\n * Mutates `body.system` in place. Handles both string and array shapes\n * (Anthropic spec allows either).\n */\nfunction appendStructuredOutputInstruction(\n body: AnyRecord,\n schema: unknown,\n ocType: unknown,\n): void {\n let instruction =\n \"\\n\\nIMPORTANT: Your response MUST be a single valid JSON object.\"\n + \" Do not wrap it in markdown code fences. Do not include any text\"\n + \" before or after the JSON object.\"\n if (schema !== undefined) {\n instruction +=\n ` The JSON object MUST conform to this JSON Schema:\\n${JSON.stringify(schema)}`\n } else if (typeof ocType === \"string\") {\n instruction +=\n ` Output type requested: ${ocType}.`\n }\n if (typeof body.system === \"string\") {\n body.system = body.system + instruction\n } else if (Array.isArray(body.system)) {\n body.system = [\n ...body.system,\n { type: \"text\", text: instruction.trimStart() },\n ]\n } else {\n body.system = instruction.trimStart()\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCountTokens } from \"./count-tokens-handler\"\nimport { handleCompletion } from \"./handler\"\n\nexport const messageRoutes = new Hono()\n\nmessageRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nmessageRoutes.post(\"/count_tokens\", async (c) => {\n try {\n return await handleCountTokens(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { cacheModels } from \"~/lib/utils\"\n\nexport const modelRoutes = new Hono()\n\nmodelRoutes.get(\"/\", async (c) => {\n try {\n if (!state.models) {\n // This should be handled by startup logic, but as a fallback.\n await cacheModels()\n }\n\n const models = state.models?.data.map((model) => {\n // Pass through every upstream field (billing, is_chat_default,\n // info_messages, model_picker_category, etc.) and overlay the\n // OpenAI-compat aliases. requestHeaders is router-internal — drop it.\n const { requestHeaders, ...rest } = model\n void requestHeaders\n return {\n ...rest,\n object: \"model\",\n type: model.capabilities?.type ?? \"model\",\n created: 0,\n created_at: new Date(0).toISOString(),\n owned_by: model.vendor,\n display_name: model.name,\n }\n })\n\n return c.json({\n object: \"list\",\n data: models,\n has_more: false,\n })\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { randomUUID } from \"node:crypto\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { awaitApproval } from \"~/lib/approval\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest } from \"~/lib/request-log\"\nimport { state } from \"~/lib/state\"\nimport { buildOpenAIErrorEvent, isControllerClosedError, logStreamError } from \"~/lib/stream-relay\"\nimport { resolveModel } from \"~/lib/utils\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesInputItem,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\ninterface UpstreamSSEEvent {\n event?: string\n data?: string\n id?: string | number\n}\n\nconst ENCODER = new TextEncoder()\n\nfunction formatSSE(chunk: UpstreamSSEEvent): string {\n const parts: Array<string> = []\n if (chunk.event) parts.push(`event: ${chunk.event}`)\n if (chunk.data !== undefined) {\n for (const line of String(chunk.data).split(/\\r\\n|\\r|\\n/)) {\n parts.push(`data: ${line}`)\n }\n }\n if (chunk.id !== undefined) parts.push(`id: ${String(chunk.id)}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nexport async function handleResponses(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n const payload = await c.req.json<ResponsesPayload>()\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\n \"Responses request payload:\",\n JSON.stringify(payload).slice(-400),\n )\n }\n\n // Resolve model name (e.g. opus → opus-1m variant)\n const originalModel = payload.model\n const resolvedModel = resolveModel(payload.model)\n if (resolvedModel !== payload.model) {\n payload.model = resolvedModel\n }\n\n const selectedModel = state.models?.data.find(\n (model) => model.id === payload.model,\n )\n\n logEndpointMismatch(payload.model, \"/responses\")\n\n if (state.manualApprove) await awaitApproval()\n\n await injectWebSearchIfNeeded(payload)\n\n const response = await createResponses(payload, selectedModel?.requestHeaders).catch(\n async (error: unknown) => {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n },\n )\n const isStreaming = !isNonStreaming(response)\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: 200,\n streaming: isStreaming,\n },\n selectedModel,\n startTime,\n )\n\n if (!isStreaming) {\n if (debugEnabled) {\n consola.debug(\"Non-streaming response:\", JSON.stringify(response))\n }\n return c.json(response)\n }\n\n // Streaming: peek the first SSE event so pre-byte upstream errors surface\n // through the route's try/catch → forwardError as a clean JSON response,\n // and only mid-stream errors hit the manual ReadableStream's pull-error path.\n // The /responses iterator emits a final `[DONE]` sentinel which we drop.\n const iterator = (response as AsyncIterableIterator<UpstreamSSEEvent>)[\n Symbol.asyncIterator\n ]()\n\n // Skip leading empty / [DONE] sentinels until we get a real event.\n let firstChunk: UpstreamSSEEvent | undefined\n let upstreamFinished = false\n while (true) {\n const r = await iterator.next()\n if (r.done) {\n upstreamFinished = true\n break\n }\n // Defensive guard against an iterator that yields {done:false, value:undefined}\n // before we dereference r.value.data below.\n if (r.value === undefined || r.value === null) continue\n if (r.value.data === \"[DONE]\") {\n upstreamFinished = true\n break\n }\n if (!r.value.data) continue\n firstChunk = r.value\n break\n }\n if (firstChunk === undefined) {\n consola.warn(\n `Upstream /responses returned no payload events at ${c.req.path}`,\n )\n }\n\n let pendingFirstChunk: UpstreamSSEEvent | undefined = firstChunk\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored\n }\n }\n const releaseUpstream = (reason?: unknown) => {\n if (typeof iterator.return === \"function\") {\n iterator.return(reason).catch(() => {\n // upstream may already be closed\n })\n }\n }\n const safeEnqueue = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n bytes: Uint8Array,\n ): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (e) {\n if (isControllerClosedError(e)) {\n consumerCancelled = true\n // The downstream cancel() callback may not fire if the controller\n // was closed by Bun's HTTP layer rather than an explicit consumer\n // .cancel() — release the upstream iterator here so the upstream\n // socket does not leak.\n releaseUpstream(e)\n return false\n }\n throw e\n }\n }\n\n return new Response(\n new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n if (pendingFirstChunk !== undefined) {\n const chunk = pendingFirstChunk\n pendingFirstChunk = undefined\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(chunk))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(chunk)))\n return\n }\n try {\n const result = await iterator.next()\n if (consumerCancelled) {\n safeClose(controller)\n return\n }\n if (result.done) {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n // Defensive: an upstream iterator that yields `{done:false, value:undefined}`\n // would crash on `result.value.data` below. Skip silently and\n // pull again on the next consumer demand. Real upstream iterators\n // never emit this shape, but a misbehaving / proxied iterator might.\n if (result.value === undefined || result.value === null) return\n if (result.value.data === \"[DONE]\") {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n if (!result.value.data) return\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(result.value))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(result.value)))\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer-cancelled mid-pull. Release the upstream iterator\n // — the cancel() callback may not have fired if the controller\n // was closed by Bun's HTTP layer.\n //\n // We deliberately do NOT call isControllerClosedError(error)\n // on iterator-side errors here — the helper matches substrings\n // like \"stream is closed\" which can appear in real upstream\n // errors, and treating them as consumer-cancel would silently\n // suppress the OpenAI-shape error frame the consumer needs.\n releaseUpstream(error)\n safeClose(controller)\n return\n }\n const { errName, errMessage } = logStreamError(c.req.path, error)\n safeEnqueue(\n controller,\n ENCODER.encode(buildOpenAIErrorEvent(errName, errMessage)),\n )\n // Server-initiated close — release the upstream iterator since\n // our cancel() callback won't fire.\n releaseUpstream(error)\n safeClose(controller)\n }\n },\n cancel() {\n consumerCancelled = true\n upstreamFinished = true\n releaseUpstream()\n },\n }),\n {\n status: 200,\n headers: {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n },\n },\n )\n}\n\nconst isNonStreaming = (\n response: Awaited<ReturnType<typeof createResponses>>,\n): response is ResponsesApiResponse => Object.hasOwn(response, \"output\")\n\nasync function injectWebSearchIfNeeded(\n payload: ResponsesPayload,\n): Promise<void> {\n const hasWebSearch = payload.tools?.some((t) => t.type === \"web_search\")\n if (!hasWebSearch) return\n\n // Skip search on follow-up messages (function call results)\n if (Array.isArray(payload.input)) {\n const hasFollowUp = payload.input.some(\n (item: ResponsesInputItem) => item.type === \"function_call_output\",\n )\n if (hasFollowUp) return\n }\n\n const query = extractUserQuery(payload.input)\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n payload.instructions =\n payload.instructions ?\n `${searchContext}\\n\\n${payload.instructions}`\n : searchContext\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Strip the legacy `web_search` tool — defensive. Copilot's /responses\n // empirically accepts bare `web_search` on gpt-5.x today (2026-05-15\n // probe `web_search_responses_preview`: model invokes it natively, output\n // contains a `web_search_call` block), and also accepts the explicit\n // `web_search_preview` / `web_search_preview_2025_03_11` shapes. We strip\n // here as belt-and-suspenders against version drift across Copilot tiers\n // — the proxy's MCP fallback (`injectWebSearchIfNeeded`) substitutes a\n // pre-fetched result so the user-facing path always works regardless of\n // upstream support. Lift this strip if/when we trust bare `web_search`\n // across all served gpt-5.x variants. Other tool types pass through\n // unchanged.\n payload.tools = payload.tools?.filter((t) => t.type !== \"web_search\")\n if (payload.tools && payload.tools.length === 0) {\n payload.tools = undefined\n }\n if (!payload.tools) {\n payload.tool_choice = undefined\n } else if (\n payload.tool_choice\n && typeof payload.tool_choice === \"object\"\n ) {\n const choice = payload.tool_choice as {\n name?: string\n function?: { name?: string }\n }\n const choiceName = choice.function?.name ?? choice.name\n if (choiceName === \"web_search\") {\n payload.tool_choice = undefined\n }\n }\n}\n\nfunction extractUserQuery(\n input: ResponsesPayload[\"input\"],\n): string | undefined {\n if (typeof input === \"string\") return input\n if (!Array.isArray(input)) return undefined\n\n // Find the last user message\n for (let i = input.length - 1; i >= 0; i--) {\n const item = input[i]\n if (\"role\" in item && item.role === \"user\") {\n if (typeof item.content === \"string\") return item.content\n if (Array.isArray(item.content)) {\n const text = item.content.find(\n (p: Record<string, unknown>) => p.type === \"input_text\",\n )\n if (text && \"text\" in text) return text.text as string\n }\n }\n }\n return undefined\n}\n\n/**\n * Compaction prompt used when GitHub Copilot API does not support\n * /responses/compact natively. Matches the prompt Codex CLI uses for\n * local (non-OpenAI) compaction.\n */\nconst COMPACTION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.\n\nInclude:\n- Current progress and key decisions made\n- Important context, constraints, or user preferences\n- What remains to be done (clear next steps)\n- Any critical data, examples, or references needed to continue\n\nBe concise, structured, and focused on helping the next LLM seamlessly continue the work.`\n\ninterface CompactRequestPayload {\n model: string\n input: Array<Record<string, unknown>>\n instructions?: string\n [key: string]: unknown\n}\n\nexport async function handleResponsesCompact(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n if (state.manualApprove) await awaitApproval()\n\n const body = await c.req.json<CompactRequestPayload>()\n\n // Try Copilot's native compact endpoint first (future-proofs for when they add support)\n const response = await fetch(\n `${copilotBaseUrl(state)}/responses/compact`,\n {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(body),\n },\n )\n\n if (response.ok) {\n logRequest(\n { method: \"POST\", path: c.req.path, status: 200 },\n undefined,\n startTime,\n )\n return c.json(await response.json())\n }\n\n // Copilot doesn't support /responses/compact — perform synthetic compaction\n // by sending a regular /responses call with a summarization prompt\n if (response.status === 404) {\n consola.debug(\"Copilot API does not support /responses/compact, using synthetic compaction\")\n return await syntheticCompact(c, body, startTime)\n }\n\n // Other errors: throw as before\n logRequest(\n { method: \"POST\", path: c.req.path, status: response.status },\n undefined,\n startTime,\n )\n throw new HTTPError(\"Copilot responses/compact request failed\", response)\n}\n\n/**\n * Synthetic compaction: sends the conversation history to Copilot's\n * regular /responses endpoint with a compaction prompt appended,\n * then returns the model's summary in the compact response format.\n */\nasync function syntheticCompact(\n c: Context,\n body: CompactRequestPayload,\n startTime: number,\n) {\n const input = Array.isArray(body.input) ? [...body.input] : []\n\n // Append compaction prompt as the last user message\n input.push({\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: COMPACTION_PROMPT }],\n })\n\n const payload: ResponsesPayload = {\n model: body.model,\n input: input as Array<ResponsesInputItem>,\n instructions: body.instructions,\n stream: false,\n store: false,\n }\n\n let result: ResponsesApiResponse\n try {\n result = (await createResponses(payload)) as ResponsesApiResponse\n } catch (error) {\n if (error instanceof HTTPError) {\n logRequest(\n { method: \"POST\", path: c.req.path, status: error.response.status },\n undefined,\n startTime,\n )\n }\n throw error\n }\n\n logRequest(\n { method: \"POST\", path: c.req.path, status: 200 },\n undefined,\n startTime,\n )\n\n return c.json({\n id: `resp_compact_${randomUUID().replace(/-/g, \"\").slice(0, 24)}`,\n object: \"response.compaction\",\n created_at: Math.floor(Date.now() / 1000),\n output: result.output,\n usage: result.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 },\n })\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleResponses, handleResponsesCompact } from \"./handler\"\n\nexport const responsesRoutes = new Hono()\n\nresponsesRoutes.post(\"/\", async (c) => {\n try {\n return await handleResponses(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nresponsesRoutes.post(\"/compact\", async (c) => {\n try {\n return await handleResponsesCompact(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\nexport const searchRoutes = new Hono()\n\nsearchRoutes.post(\"/\", async (c) => {\n try {\n const { query } = await c.req.json<{ query: string }>()\n\n if (!query || typeof query !== \"string\") {\n return c.json(\n { error: { message: \"Missing required field: query\" } },\n 400,\n )\n }\n\n const results = await searchWeb(query)\n return c.json({ results })\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { state } from \"~/lib/state\"\n\nexport const tokenRoute = new Hono()\n\ntokenRoute.get(\"/\", (c) => {\n if (!state.showToken) {\n return c.json(\n { error: { message: \"Token endpoint disabled\", type: \"error\" } },\n 403,\n )\n }\n\n return c.json({\n token: state.copilotToken,\n })\n})\n","import { Hono } from \"hono\"\nimport consola from \"consola\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\n\nexport const usageRoute = new Hono()\n\nusageRoute.get(\"/\", async (c) => {\n try {\n const usage = await getCopilotUsage()\n return c.json(usage)\n } catch (error) {\n consola.error(\"Error fetching Copilot usage:\", error)\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\n\nimport packageJson from \"../package.json\" with { type: \"json\" }\n\nimport { completionRoutes } from \"./routes/chat-completions/route\"\nimport { embeddingRoutes } from \"./routes/embeddings/route\"\nimport { mcpRoutes } from \"./routes/mcp/route\"\nimport { messageRoutes } from \"./routes/messages/route\"\nimport { modelRoutes } from \"./routes/models/route\"\nimport { responsesRoutes } from \"./routes/responses/route\"\nimport { searchRoutes } from \"./routes/search/route\"\nimport { tokenRoute } from \"./routes/token/route\"\nimport { usageRoute } from \"./routes/usage/route\"\n\nexport const server = new Hono()\n\nserver.use(cors())\n\nserver.get(\"/\", (c) => c.text(\"Server running\"))\n\n// Build identity. Operators can `curl http://localhost:<port>/version` to\n// confirm which build is serving requests — useful when upgrading via\n// `npx github-router@latest` and verifying the new code actually loaded.\nserver.get(\"/version\", (c) =>\n c.json({\n name: packageJson.name,\n version: packageJson.version,\n gitSha: process.env.GITHUB_SHA ?? \"unknown\",\n }),\n)\n\n// Claude CLI sends HEAD / as health check before each request\nserver.on(\"HEAD\", [\"/\"], (c) => c.body(null, 200))\n\nserver.route(\"/chat/completions\", completionRoutes)\nserver.route(\"/responses\", responsesRoutes)\nserver.route(\"/models\", modelRoutes)\nserver.route(\"/embeddings\", embeddingRoutes)\nserver.route(\"/search\", searchRoutes)\nserver.route(\"/usage\", usageRoute)\nserver.route(\"/token\", tokenRoute)\n\n// Compatibility with tools that expect v1/ prefix\nserver.route(\"/v1/chat/completions\", completionRoutes)\nserver.route(\"/v1/responses\", responsesRoutes)\nserver.route(\"/v1/models\", modelRoutes)\nserver.route(\"/v1/embeddings\", embeddingRoutes)\nserver.route(\"/v1/search\", searchRoutes)\n\n// Anthropic compatible endpoints\nserver.route(\"/v1/messages\", messageRoutes)\n\n// Peer-MCP endpoint: hosts gpt-5.5/gpt-5.3-codex/gemini-3.1-pro\n// persona tools (codex_critic / codex_reviewer / gemini_critic) for\n// the spawned Claude Code session to consult via MCP. Auth is a\n// per-launch nonce stored in `state.peerMcpNonce`; the route\n// rejects all requests when the nonce is unset (e.g. proxy started\n// standalone via `github-router start`). See src/routes/mcp/handler.ts.\nserver.route(\"/mcp\", mcpRoutes)\n\n// Stub out Claude Code SDK telemetry so it doesn't 404-spam logs.\n// Copilot doesn't expose this endpoint; clients fire it best-effort.\nserver.post(\"/api/event_logging/batch\", (c) => c.body(null, 200))\n\n// Phase E P1.4: explicit Files-API not-supported route. Claude Code's\n// BriefTool upload + utils/teleport/gitBundle paths hit\n// GET /v1/files/{id}/content (download), GET /v1/files (list),\n// POST /v1/files (upload). Copilot has no equivalent storage backend\n// (verified via cc-backup src/services/api/filesApi.ts). Without this\n// explicit route, requests fall to the default 404 with a generic\n// \"not found\" message — fine but unhelpful.\n//\n// Why surface explicitly: the user gets a clear signal \"this feature\n// isn't supported here\" instead of inferring it from a generic 404.\n// Fail-loud-with-explanation aligns with cc-backup mentality #10\n// (errors are logged not swallowed; surface the limitation).\nserver.all(\"/v1/files/*\", (c) =>\n c.json(\n {\n type: \"error\",\n error: {\n type: \"not_found_error\",\n message:\n \"Files API is not supported by github-router (Copilot has no equivalent storage backend). \"\n + \"Use the Anthropic API directly for file uploads/downloads.\",\n },\n },\n 404,\n ),\n)\n\n// Return Anthropic-format JSON for unknown endpoints\nserver.notFound((c) =>\n c.json(\n {\n type: \"error\",\n error: {\n type: \"not_found_error\",\n message: `${c.req.method} ${c.req.path} not found`,\n },\n },\n 404,\n ),\n)\n","import consola from \"consola\"\nimport { serve, type ServerHandler } from \"srvx\"\n\nimport { PATHS, ensurePaths } from \"./paths\"\nimport { generateRandomPort } from \"./port\"\nimport { initProxyFromEnv } from \"./proxy\"\nimport { state } from \"./state\"\nimport { setupCopilotToken, setupGitHubToken } from \"./token\"\nimport { cacheModels, cacheCopilotVersion, cacheVSCodeVersion } from \"./utils\"\nimport { server as app } from \"../server\"\n\nconst MAX_PORT_RETRIES = 10\n\nexport interface ServerSetupOptions {\n port?: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n showToken: boolean\n proxyEnv: boolean\n extendedBetas: boolean\n silent: boolean\n}\n\nexport async function setupAndServe(\n options: ServerSetupOptions,\n): Promise<{ server: ReturnType<typeof serve>; serverUrl: string }> {\n if (options.proxyEnv) {\n initProxyFromEnv()\n }\n\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n if (options.accountType !== \"individual\") {\n consola.info(`Using ${options.accountType} plan GitHub account`)\n }\n\n state.manualApprove = options.manual\n state.rateLimitSeconds = options.rateLimit\n state.rateLimitWait = options.rateLimitWait\n state.showToken = options.showToken\n state.extendedBetas = options.extendedBetas\n\n if (process.env.COPILOT_API_URL) {\n state.copilotApiUrl = process.env.COPILOT_API_URL\n }\n\n await ensurePaths()\n await cacheVSCodeVersion()\n await cacheCopilotVersion()\n\n if (options.githubToken) {\n state.githubToken = options.githubToken\n consola.info(\"Using provided GitHub token\")\n } else {\n await setupGitHubToken()\n }\n\n await setupCopilotToken()\n await cacheModels()\n\n consola.debug(\n `Available models: \\n${state.models?.data.map((model) => `- ${model.id}`).join(\"\\n\")}`,\n )\n\n const serveOptions = {\n fetch: app.fetch as ServerHandler,\n hostname: \"127.0.0.1\",\n silent: options.silent,\n }\n\n let srvxServer: ReturnType<typeof serve> | undefined\n\n if (options.port !== undefined) {\n // Explicit port — no retry\n srvxServer = serve({ ...serveOptions, port: options.port })\n } else {\n // Random available port with retry\n let lastError: unknown\n for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {\n const candidatePort = generateRandomPort()\n try {\n srvxServer = serve({ ...serveOptions, port: candidatePort })\n break\n } catch (error) {\n lastError = error\n const isAddrInUse =\n error instanceof Error\n && (error.message.includes(\"EADDRINUSE\")\n || error.message.includes(\"address already in use\")\n || (\"code\" in error\n && (error as NodeJS.ErrnoException).code === \"EADDRINUSE\"))\n if (!isAddrInUse) throw error\n consola.debug(`Port ${candidatePort} in use, trying another...`)\n }\n }\n\n if (srvxServer === undefined) {\n throw new Error(\n `Failed to find an available port after ${MAX_PORT_RETRIES} attempts. `\n + `Specify a port with --port or free some ports. Last error: ${lastError}`,\n )\n }\n }\n\n // Wait for the server to be listening before reading the URL\n await srvxServer.ready()\n const url = srvxServer.url\n if (!url) {\n throw new Error(\"Server started but URL is not available\")\n }\n const serverUrl = url.replace(/\\/$/, \"\")\n\n return { server: srvxServer, serverUrl }\n}\n\n/** Shared CLI arg definitions for all server commands. */\nexport const sharedServerArgs = {\n port: {\n alias: \"p\",\n type: \"string\" as const,\n description: \"Port to listen on\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\" as const,\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\" as const,\n default: \"enterprise\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n manual: {\n type: \"boolean\" as const,\n default: false,\n description: \"Enable manual request approval\",\n },\n \"rate-limit\": {\n alias: \"r\",\n type: \"string\" as const,\n description: \"Rate limit in seconds between requests\",\n },\n wait: {\n alias: \"w\",\n type: \"boolean\" as const,\n default: false,\n description:\n \"Wait instead of error when rate limit is hit. Has no effect if rate limit is not set\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\" as const,\n description:\n \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"show-token\": {\n type: \"boolean\" as const,\n default: false,\n description: \"Show GitHub and Copilot tokens on fetch and refresh\",\n },\n \"proxy-env\": {\n type: \"boolean\" as const,\n default: false,\n description: \"Initialize proxy from environment variables\",\n },\n \"extended-betas\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Forward extended beta headers for Claude CLI compatibility (default: VS Code-only)\",\n },\n} as const\n\nconst allowedAccountTypes = new Set([\"individual\", \"business\", \"enterprise\"])\n\n/** Parse shared server args into ServerSetupOptions fields. */\nexport function parseSharedArgs(args: Record<string, unknown>): {\n port?: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n showToken: boolean\n proxyEnv: boolean\n extendedBetas: boolean\n} {\n const portRaw = args.port as string | undefined\n let port: number | undefined\n if (portRaw !== undefined) {\n port = Number.parseInt(portRaw, 10)\n if (Number.isNaN(port) || port <= 0 || port > 65535) {\n throw new Error(\"Invalid port. Must be between 1 and 65535.\")\n }\n }\n\n const accountType = (args[\"account-type\"] as string) ?? \"enterprise\"\n if (!allowedAccountTypes.has(accountType)) {\n throw new Error(\n \"Invalid account type. Must be individual, business, or enterprise.\",\n )\n }\n\n const rateLimitRaw = args[\"rate-limit\"] as string | undefined\n let rateLimit: number | undefined\n if (rateLimitRaw !== undefined) {\n rateLimit = Number.parseInt(rateLimitRaw, 10)\n if (Number.isNaN(rateLimit) || rateLimit <= 0) {\n throw new Error(\"Invalid rate limit. Must be a positive integer.\")\n }\n }\n\n const rateLimitWait = (args.wait as boolean) && rateLimit !== undefined\n if ((args.wait as boolean) && rateLimit === undefined) {\n consola.warn(\"Rate limit wait ignored because no rate limit was set.\")\n }\n\n const githubToken =\n (args[\"github-token\"] as string | undefined) ?? process.env.GH_TOKEN\n\n return {\n port,\n verbose: args.verbose as boolean,\n accountType,\n manual: args.manual as boolean,\n rateLimit,\n rateLimitWait,\n githubToken,\n showToken: args[\"show-token\"] as boolean,\n proxyEnv: args[\"proxy-env\"] as boolean,\n extendedBetas: args[\"extended-betas\"] as boolean,\n }\n}\n\n/**\n * Build environment variables for Claude Code.\n *\n * The parent env is sanitized of every key in `STRIPPED_PARENT_ENV_KEYS`\n * (see `src/lib/launch.ts`) BEFORE these overrides are merged in, so we\n * only need to provide the positive values.\n *\n * Auth precedence in Claude Code (https://code.claude.com/docs/en/iam),\n * after the github-router substrate fix:\n * 1. Cloud provider (CLAUDE_CODE_USE_BEDROCK / VERTEX / FOUNDRY) — stripped at parent.\n * 2. ANTHROPIC_AUTH_TOKEN — NOT set by the proxy. Stripped at parent\n * (no env-source auth in the spawned child at all).\n * 3. ANTHROPIC_API_KEY — stripped at parent.\n * 4. apiKeyHelper in settings.json — copied into our config dir as\n * part of the mirror; if the user defined one, it still fires\n * and may mint an `x-api-key` header. Copilot ignores `x-api-key`,\n * so behavior is unchanged from before this fix.\n * 5. CLAUDE_CODE_OAUTH_TOKEN — stripped at parent.\n * 6. Subscription OAuth (Keychain / `<CLAUDE_CONFIG_DIR>/.credentials.json`)\n * — the credentials file is OURS (synthetic blob, written by\n * `ensureClaudeConfigMirror`). Claude Code reads accessToken from\n * it and sends as `Authorization: Bearer <accessToken>`. The\n * teammate-spawn allowlist propagates `CLAUDE_CONFIG_DIR` to\n * children, so spawned teammates find the same synthetic credential\n * and authenticate (the bug this whole fix addresses).\n *\n * `CLAUDE_CONFIG_DIR` activates Claude Code's per-config-dir keychain\n * isolation (per binary-grep of v2.1.126's `iN()` function: when set,\n * the keychain service name becomes `Claude Code-<sha256(path)[0..8]>`,\n * missing the user's real `Claude Code` entry). Pointing it at our\n * snapshot-copied `PATHS.CLAUDE_CONFIG_DIR` preserves user customization\n * (mirrored settings.json, skills, MCP, hooks, CLAUDE.md, custom\n * agents) while giving teammates a credential they can find on disk.\n *\n * No-401 invariant: Claude Code's reactive refresh path (`SZ1` →\n * `D3(0,true,...)`) fires on any 401 from upstream. The synthetic\n * refreshToken would fail any real refresh attempt, so the proxy\n * MUST NOT return 401 on the Anthropic-shape boundary even when\n * upstream Copilot returns 401. See `src/routes/messages/handler.ts`.\n */\nexport function getClaudeCodeEnvVars(\n serverUrl: string,\n model?: string,\n): Record<string, string> {\n const vars: Record<string, string> = {\n // Route to the proxy\n ANTHROPIC_BASE_URL: serverUrl,\n // CLAUDE_CONFIG_DIR points at the router-owned snapshot mirror;\n // the synthetic .credentials.json inside it provides the OAuth\n // accessToken that Claude Code sends as Bearer. See\n // `ensureClaudeConfigMirror` in `src/lib/paths.ts`.\n CLAUDE_CONFIG_DIR: PATHS.CLAUDE_CONFIG_DIR,\n // Extend Claude Code's MCP per-tool-call wait window. Two distinct\n // env vars are at play (per binary inspection of v2.1.141 by the\n // peer-MCP team's empirical SDK test, 2026-05-14):\n //\n // - MCP_TIMEOUT — historical/general MCP timeout, may apply to\n // server-startup or initial-handshake but NOT confirmed to reach\n // the per-tool-call HTTP wait on v2.1.138-141 (regressions\n // #50289 / #52137 documented this as silently-ignored on the\n // per-call path). Kept as belt-and-suspenders.\n //\n // - MCP_TOOL_TIMEOUT — the load-bearing one. v2.1.141's `y13()`\n // reads `parseInt(process.env.MCP_TOOL_TIMEOUT)` for the per-\n // tool-call timeout passed to MCP SDK's `.callTool({...},\n // schema, {timeout: W})`. Default `1e8` ms (~27.7 hours) when\n // the env is unset. Setting it to a finite-but-large value\n // (10 min) is safer than relying on the implicit 27.7-hour\n // default — surfaces regressions where the SDK silently caps\n // at 60s, AND prevents long-tail runaway calls from holding\n // resources indefinitely.\n //\n // Without the SDK's `resetTimeoutOnProgress` opt-in (which Claude\n // Code does not pass), SSE notifications/progress events DO NOT\n // reset the per-call timer — they only fire UI callbacks. So\n // MCP_TOOL_TIMEOUT is the actual lever for long-running peer-MCP\n // calls, not the SSE response transport. SSE remains valuable as\n // the canonical Streamable HTTP shape and for progress UI, but the\n // ceiling-busting work is done by these env vars.\n MCP_TIMEOUT: \"600000\",\n MCP_TOOL_TIMEOUT: \"600000\",\n // Suppress non-essential telemetry/model calls. The first two are\n // Anthropic's own knobs (per cc-backup managedEnv.ts); the third\n // (`DISABLE_TELEMETRY`) suppresses Datadog/Statsig/etc. external\n // analytics that would otherwise run regardless of the proxy. None of\n // these calls reach the proxy (they hit external hosts), but they\n // consume user resources and may leak metadata. Setting all three\n // turns the spawned child into a quiet local-only session.\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n DISABLE_TELEMETRY: \"1\",\n }\n if (model) vars.ANTHROPIC_MODEL = model\n\n // Default the small/fast tier model (used by Claude Code for status\n // text, auto-compact summaries, session titles, background ops) to\n // claude-haiku-4-5. Anthropic-published dashed slug; the proxy's\n // resolveModel translates to Copilot's dotted slug at request time.\n // Presence-based guard preserves any user-set value, including the\n // dated slug variant or a different family (gemini, gpt) for users\n // who have custom Copilot mappings — symmetric with the\n // ANTHROPIC_SMALL_FAST_MODEL pass-through documented in launch.ts's\n // STRIPPED_PARENT_ENV_KEYS comment.\n if (process.env.ANTHROPIC_SMALL_FAST_MODEL === undefined) {\n vars.ANTHROPIC_SMALL_FAST_MODEL = \"claude-haiku-4-5\"\n }\n\n // Tier-default knobs read by Claude Code's /model picker (cc-backup\n // src/utils/model/modelOptions.ts:78,109,167) when the user invokes\n // the picker to switch model. Without these, the picker shows\n // Anthropic's catalog-baseline entries (which may be stale relative\n // to what Copilot has). Setting them seeds the three tier rows with\n // ids the proxy's resolveModel knows how to route.\n //\n // Why NO [1m] suffix on Sonnet/Haiku: Copilot has no -1m backend for\n // either family (only opus-4.7-1m-internal exists in the catalog as\n // of 2026-05-22; Anthropic-side modelSupports1M in cc-backup\n // context.ts:43-49 only lists sonnet-4* and opus-4-6 — haiku has no\n // 1M variant on either side). The [1m] decoration for the *active*\n // default lives on ANTHROPIC_MODEL itself (see pickClaudeDefault in\n // src/claude.ts) and is cap-aware against the live catalog.\n //\n // Presence-based guard symmetric with the SMALL_FAST_MODEL guard\n // above — preserves any value (including 0/false/off/unrecognized)\n // the user has explicitly set.\n if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_SONNET_MODEL = \"claude-sonnet-4-6\"\n }\n if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_HAIKU_MODEL = \"claude-haiku-4-5\"\n }\n if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_OPUS_MODEL = \"claude-opus-4-7\"\n }\n\n // Auto-enable Anthropic's experimental \"leverage\" features for proxied\n // claude sessions. Symmetric with the leverage-policy default\n // (extended-betas ON for `claude` subcommand): users running\n // `github-router claude` opted in for the Claude Code feature surface,\n // and these experimental gates default off for non-Anthropic users\n // (gated by GrowthBook flags that don't fire outside Anthropic).\n //\n // Presence-based guard: if the parent env has set ANY value for these\n // keys (including \"0\", \"false\", \"no\", \"off\", or any unrecognized\n // value), preserve the user's intent — only inject \"1\" when the key\n // is unset. The parent env survives `buildLaunchCommand`'s sanitize\n // step because none of these keys are in `STRIPPED_PARENT_ENV_KEYS`,\n // so an unset proxy var means the parent's value (if any) wins\n // naturally.\n //\n // ADVISOR has a documented `CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1` hard\n // opt-out that wins via JI()'s ordering (DISABLE checked before\n // ENABLE). FORK_SUBAGENT and AGENT_TEAMS rely on Anthropic's SH()\n // falsy semantics for opt-out (\"0\"/\"false\"/\"no\"/\"off\"/empty all opt\n // out — preserved by the presence guard). FINE_GRAINED_TOOL_STREAMING\n // is explicitly recommended by Anthropic's docs at\n // code.claude.com/docs/en/env-vars: \"Set to `1` to force on when\n // routing through a proxy via ANTHROPIC_BASE_URL\". TASKS only\n // manifests in `claude -p` headless mode.\n //\n // GATEWAY_MODEL_DISCOVERY is intentionally NOT enabled here — Claude\n // Code's hardcoded slug registry maps slugs to capabilities, not just\n // labels; Copilot's slugs (claude-opus-4.6-1m) don't match\n // Anthropic's registry (claude-opus-4-6), so dynamic discovery would\n // silently degrade advanced tool use. Enable it intentionally only\n // after building a slug-translation shim in /v1/models. See\n // CLAUDE.md \"Experimental Claude Code features auto-enabled\".\n const experimentalEnables: ReadonlyArray<string> = [\n \"CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL\",\n \"CLAUDE_CODE_FORK_SUBAGENT\",\n \"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS\",\n \"CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING\",\n \"CLAUDE_CODE_ENABLE_TASKS\",\n ]\n for (const key of experimentalEnables) {\n if (process.env[key] === undefined) {\n vars[key] = \"1\"\n }\n }\n\n return vars\n}\n\n/**\n * Build environment variables for Codex CLI.\n *\n * Like `getClaudeCodeEnvVars`, the parent env is sanitized of\n * `OPENAI_API_KEY` / `OPENAI_BASE_URL` / `CODEX_HOME` (see\n * `STRIPPED_PARENT_ENV_KEYS` in `src/lib/launch.ts`) before these\n * overrides are merged, so a stale shell `OPENAI_API_KEY` can't leak\n * through. Codex caches a ChatGPT subscription login under\n * `$CODEX_HOME/auth.json` which can override `OPENAI_API_KEY` per\n * openai/codex#2733; pointing `CODEX_HOME` at an isolated directory\n * masks any cached login.\n */\nexport function getCodexEnvVars(serverUrl: string): Record<string, string> {\n return {\n OPENAI_BASE_URL: `${serverUrl}/v1`,\n OPENAI_API_KEY: \"dummy\",\n // Isolated CODEX_HOME — masks any cached ChatGPT login (openai/codex#2733).\n CODEX_HOME: PATHS.CODEX_HOME,\n }\n}\n","import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport {\n autoUpdateClaude,\n checkClaudeVersion,\n} from \"./lib/claude-version-check\"\nimport {\n injectPeerMcpIntoMirror,\n resolveCodexCliBackend,\n writePeerMcpRuntimeFiles,\n} from \"./lib/codex-mcp-config\"\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { getCodexVersion, launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport { ensureClaudeConfigMirror, removeOwnClaudeConfigMirror } from \"./lib/paths\"\nimport { buildPeerAwarenessSnippet } from \"./lib/peer-mcp-personas\"\nimport {\n DEFAULT_CLAUDE_MODEL_FALLBACKS,\n pickClaudeDefault,\n} from \"./lib/port\"\nimport {\n getClaudeCodeEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { state } from \"./lib/state\"\nimport { resolveModel } from \"./lib/utils\"\n\nexport const claude = defineCommand({\n meta: {\n name: \"claude\",\n description: \"Start the proxy server and launch Claude Code\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Claude Code\",\n },\n \"codex-mcp\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Wire peer-model MCP personas (codex-critic, codex-reviewer, gemini-critic) into the spawned Claude Code session\",\n },\n \"codex-cli\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Add a `codex mcp-server` stdio backend so codex-implementer can mutate files. Requires codex CLI 0.129+; gracefully falls back to HTTP-only if absent.\",\n },\n \"codex-mcp-only\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Pass --strict-mcp-config to claude code so only github-router's MCP servers are loaded (hides user's existing MCP servers)\",\n },\n stealth: {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Opt back into VS Code-only beta header filtering. Loses leverage features (task budgets, token-efficient tools, prompt caching, etc.) but minimizes the wire-fingerprint difference from VS Code Copilot Chat. By default the `claude` subcommand enables extended/leverage betas because the spawned Claude Code already identifies itself via UA and other headers — partial stealth doesn't buy much.\",\n },\n \"auto-update\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check for and install latest Claude Code on launch (throttled to once per hour via ~/.local/share/github-router/last-update-check). Set to false (--no-auto-update) to keep the current installed version. Falls back gracefully if npm/network unavailable.\",\n },\n \"update-check\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check the npm registry for a newer Claude Code version on launch and warn if stale (non-blocking ~500ms cost). Set to false (--no-update-check) to skip the check entirely (useful for offline/CI). Independent from --auto-update: --no-update-check implies no auto-install (nothing to install since we never check).\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The claude subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n // Phase E P2.2: stealth-vs-leverage policy.\n // The `claude` subcommand defaults to LEVERAGE mode (extended-betas\n // ON) because the spawned Claude Code already identifies itself via\n // UA / editor-version / x-app headers — partial stealth doesn't\n // meaningfully reduce the wire fingerprint, and the cost of stealth\n // is losing features the user explicitly chose to install Claude\n // Code for (--max-budget-usd, token-efficient tools, prompt caching,\n // structured outputs, MCP, etc.).\n //\n // The `--stealth` flag opts back into the VS Code-only filter for\n // users who specifically want minimal wire diff over leverage.\n // The shared `--extended-betas` flag still works (treated as alias).\n //\n // Note: `advisor-tool-` is stripped in BOTH modes regardless of this\n // setting (Phase A: Copilot 400s on it). ADVISOR will be served via\n // Phase I's proxy-side translate path independently.\n if (args.stealth) {\n // Stealth wins if explicitly requested.\n parsed.extendedBetas = false\n consola.info(\n \"Stealth mode: VS Code-only beta filtering. Leverage features disabled.\",\n )\n } else if (!args[\"extended-betas\"]) {\n // No explicit --extended-betas AND no --stealth → default ON.\n parsed.extendedBetas = true\n }\n // If user passed --extended-betas explicitly, parsed already reflects it.\n\n // Phase H P2: Claude Code version check + opt-in auto-update.\n // Default: check + auto-install if newer version available\n // (throttled to once per hour). The user explicitly chose to install\n // a Claude Code wrapper — they want the latest features and bug\n // fixes. Opt-out via --no-auto-update (check only, warn) or\n // --no-update-check (silence entirely). Best-effort: skips silently\n // if npm is offline or claude is not on PATH. The check happens\n // BEFORE setupAndServe so a stale version doesn't get spawned.\n if (args[\"update-check\"] !== false) {\n try {\n const versionCheck = await checkClaudeVersion({\n noCheck: false,\n })\n if (versionCheck.skipped && versionCheck.skipReason === \"no-claude\") {\n // Claude isn't on PATH — let launchChild surface the more\n // contextual \"claude not found\" error in the spawn step.\n consola.debug(\n \"claude --version probe failed; skipping auto-update.\",\n )\n } else if (versionCheck.skipped && versionCheck.skipReason === \"no-npm\") {\n // npm view failed — likely offline. Don't block launch.\n consola.debug(\n \"npm view @anthropic-ai/claude-code failed; skipping auto-update check (likely offline).\",\n )\n } else if (\n versionCheck.needsUpdate\n && versionCheck.installedVersion\n && versionCheck.latestVersion\n ) {\n if (args[\"auto-update\"] !== false) {\n try {\n await autoUpdateClaude(versionCheck.latestVersion)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.warn(\n `Auto-update of Claude Code from ${versionCheck.installedVersion} to ${versionCheck.latestVersion} failed (${msg}); continuing with installed version. Run \\`npm install -g @anthropic-ai/claude-code@latest\\` manually to retry.`,\n )\n }\n } else {\n consola.warn(\n `Claude Code v${versionCheck.installedVersion} is installed; v${versionCheck.latestVersion} is available. Run with --auto-update (the default) to install on launch, or \\`npm install -g @anthropic-ai/claude-code@latest\\` manually.`,\n )\n }\n }\n } catch (err) {\n // Whole version-check should never block launch.\n consola.debug(\"Claude version check failed:\", err)\n }\n }\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n // Provision the router-owned CLAUDE_CONFIG_DIR with our synthetic\n // .credentials.json + a snapshot copy of the user's ~/.claude/.\n // The spawned Claude Code (and any teammates it spawns via the\n // agent-teams primitive) reads this dir instead of ~/.claude/,\n // finds our synthetic credential, and authenticates — closing the\n // teammate-spawn allowlist gap that drops ANTHROPIC_AUTH_TOKEN.\n // See ensureClaudeConfigMirror in src/lib/paths.ts.\n //\n // Run BEFORE enableFileLogging so a fatal credentials-write failure\n // surfaces on the user's terminal (we only throw on the credentials\n // write — copy failures of individual user files are debug-logged\n // and skipped).\n try {\n await ensureClaudeConfigMirror()\n } catch (err) {\n consola.error(\n `Failed to provision CLAUDE_CONFIG_DIR mirror: ${\n err instanceof Error ? err.message : String(err)\n }. Spawned Claude Code would not be able to authenticate.`,\n )\n process.exit(1)\n }\n\n enableFileLogging() // redirect errors/warnings to file; suppress terminal output\n\n // Two slugs flow through this code:\n // * `chosenSlug` — the value we set for `ANTHROPIC_MODEL`. Must be an\n // Anthropic-published slug (e.g. `claude-opus-4-7`) so Claude Code's\n // hardcoded `/model` registry matches it and the UI shows the right\n // menu entry. The proxy's resolver translates this back to a Copilot\n // slug at request time, so the actual upstream call still works.\n // * `resolvedSlug` — the Copilot-side slug after `resolveModel`. Used\n // only for cache-presence validation (fallback chain) and the\n // launch banner.\n //\n // For the implicit-default path only, we walk\n // DEFAULT_CLAUDE_MODEL_FALLBACKS when neither the default nor any\n // earlier fallback resolves to a model present in the Copilot cache.\n // Explicit `--model` is respected as-is — including Copilot slugs\n // (which Claude Code's UI won't recognize, but power users may want\n // for explicit pinning).\n const usingDefault = !args.model\n const requestedSlug = args.model ?? pickClaudeDefault()\n let chosenSlug = requestedSlug\n let resolvedSlug = resolveModel(chosenSlug)\n\n if (usingDefault && state.models) {\n const inCache = (slug: string) =>\n state.models?.data.some((m) => m.id === resolveModel(slug)) ?? false\n if (!inCache(chosenSlug)) {\n for (const fallback of DEFAULT_CLAUDE_MODEL_FALLBACKS) {\n if (inCache(fallback)) {\n consola.info(\n `Default model \"${chosenSlug}\" not in your Copilot model list; falling back to \"${fallback}\".`,\n )\n chosenSlug = fallback\n resolvedSlug = resolveModel(fallback)\n break\n }\n }\n }\n }\n\n if (resolvedSlug !== chosenSlug) {\n consola.info(`Model \"${chosenSlug}\" resolved to \"${resolvedSlug}\"`)\n }\n const modelEntry = state.models?.data.find((m) => m.id === resolvedSlug)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/v1/messages\")\n consola.warn(\n `Model \"${resolvedSlug}\" not found. Available claude models: ${available.join(\", \")}`,\n )\n }\n\n // Banner shows the round-trip so the user sees both names. Claude Code's\n // UI will display the chosenSlug; Copilot upstream sees resolvedSlug.\n const banner =\n chosenSlug === resolvedSlug\n ? chosenSlug\n : `${chosenSlug} → ${resolvedSlug}`\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Claude Code (${banner})...\\n`)\n\n const envVars = getClaudeCodeEnvVars(serverUrl, chosenSlug)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n // Peer-MCP wiring. Default-on. When enabled:\n // 1. Decide between HTTP backend (always works, read-only personas)\n // and the `--codex-cli` stdio backend (requires codex 0.129+,\n // adds the implementer persona).\n // 2. Probe the live Copilot catalog for gemini-3.1-pro-preview.\n // 3. Generate a per-launch nonce, write the MCP config tempfile\n // under PATHS.CLAUDE_RUNTIME_DIR with mode 0o600, AND write\n // one .md subagent file per peer agent into ~/.claude/agents/\n // (Phase 2.5 — `--agents` JSON does NOT populate Claude Code's\n // Task subagent_type enum on v2.1.138; .md files in the canonical\n // agents dir do).\n // 4. Inject `--mcp-config <path>` into the spawned Claude Code's\n // argv. Add `--strict-mcp-config` if the user explicitly opts\n // out of their existing MCP servers. The `--agents` JSON path\n // is intentionally NOT passed: the .md registration in the\n // canonical agents dir is the authoritative surface.\n // 5. Plumb `cleanup()` into launchChild's onShutdown so tempfiles\n // are unlinked on signal exit.\n //\n // The per-launch CLAUDE_CONFIG_DIR mirror is ALWAYS cleaned up on\n // shutdown (regardless of codex-mcp), since `ensureClaudeConfigMirror`\n // above always provisioned it. We chain the peer-MCP cleanup\n // (if any) ahead of the mirror removal so files inside the mirror\n // get unlinked first via known paths; the recursive `fs.rm` is\n // belt-and-braces for everything else.\n const baseShutdown = async (): Promise<void> => {\n await removeOwnClaudeConfigMirror()\n }\n let onShutdown: () => Promise<void> = baseShutdown\n const codexMcpEnabled = (args as Record<string, unknown>)[\"codex-mcp\"] !== false\n if (codexMcpEnabled) {\n try {\n const requestedCli =\n ((args as Record<string, unknown>)[\"codex-cli\"] as boolean | undefined) ?? false\n const backend = resolveCodexCliBackend({\n requested: requestedCli,\n codexInfo: requestedCli ? getCodexVersion() : null,\n })\n const geminiAvailable =\n state.models?.data.some((m) => /^gemini-3\\..*pro/i.test(m.id)) ?? false\n if (!geminiAvailable) {\n consola.info(\n \"gemini-3.1-pro-preview not found in your Copilot model catalog; gemini-critic persona will not be registered.\",\n )\n }\n\n const runtime = await writePeerMcpRuntimeFiles(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable,\n })\n state.peerMcpNonce = runtime.nonce\n onShutdown = async (): Promise<void> => {\n await runtime.cleanup()\n await baseShutdown()\n }\n\n // Subagent MCP visibility: inject `gh-router-peers` (and the\n // `codex-cli` stdio entry when enabled) into the mirrored\n // `<CLAUDE_CONFIG_DIR>/.claude.json` so subagents — Agent-tool\n // subagents, forks, agent-teams subprocesses — discover the peer\n // MCP from persistent (user-scope) config rather than the parent's\n // ephemeral --mcp-config CLI flag. Same nonce as runtime files\n // (the proxy validates Authorization against the launch nonce\n // regardless of which channel the request came through).\n //\n // On collision with a user-side entry of the same name, this\n // returns ok:false; we then keep --mcp-config as the fallback so\n // at least the parent session retains the peer tools (subagents\n // remain blind in that case, by design — explicit branch, not\n // silent precedence).\n const injected = await injectPeerMcpIntoMirror(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable,\n nonce: runtime.nonce,\n })\n\n // Channel selection: prefer the mirror (subagent-visible) when\n // injection succeeded. Only fall back to --mcp-config when\n // injection refused due to a user-side collision. Pushing BOTH\n // would register the same server name twice (mirror + CLI flag),\n // which is ambiguous across Claude Code versions.\n if (!injected.ok) {\n extraArgs.push(\"--mcp-config\", runtime.mcpConfigPath)\n if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n extraArgs.push(\"--strict-mcp-config\")\n }\n } else if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n // User asked for strict-MCP-only but the mirror inject path\n // can't enforce that (other user-scope MCPs already in the\n // mirror's snapshot are visible). Warn so the flag's mismatch\n // with the new behavior is obvious.\n consola.warn(\n \"--codex-mcp-only has no effect when peer MCP is wired via the \"\n + \"mirrored .claude.json (the user's existing user-scope MCPs in \"\n + \"the snapshot are still visible). Pass --no-codex-mcp to skip \"\n + \"peer-MCP wiring entirely.\",\n )\n }\n\n const personaNames = runtime.personas.map((p) => p.agentName).join(\", \")\n const subagentVisibility = injected.ok\n ? `subagent-visible (mirrored mcpServers: [${injected.serversAdded.join(\", \")}])`\n : `subagent-INVISIBLE (collision on user-side mcpServers: [${injected.conflictingServers.join(\", \")}]; parent-only via --mcp-config)`\n process.stderr.write(\n `Peer MCP wired (backend=${backend}, personas=[${personaNames}], `\n + `subagent .md files=${runtime.agentMdPaths.length}, ${subagentVisibility}).\\n`,\n )\n\n // Awareness snippet: append a short, non-prescriptive system-prompt\n // section telling Claude *what* peer-review tools exist and *when*\n // they tend to be useful — Claude decides *whether* to call them.\n // The auto-invocation triggers live in each MCP tool's own\n // `description` (the prescriptive layer); this snippet is the\n // awareness layer. Opt out with `GH_ROUTER_PEER_AWARENESS` set to\n // 0, false, no, off, or empty string (case-insensitive, trimmed)\n // — same surface as the CLAUDE_CODE_* opt-outs documented in\n // docs/claude-env-injection.md.\n const peerAwarenessOptOut = (\n process.env.GH_ROUTER_PEER_AWARENESS ?? \"1\"\n )\n .trim()\n .toLowerCase()\n const peerAwarenessDisabled =\n peerAwarenessOptOut === \"\"\n || peerAwarenessOptOut === \"0\"\n || peerAwarenessOptOut === \"false\"\n || peerAwarenessOptOut === \"off\"\n || peerAwarenessOptOut === \"no\"\n if (!peerAwarenessDisabled) {\n extraArgs.push(\n \"--append-system-prompt\",\n buildPeerAwarenessSnippet({\n codexCli: backend === \"cli\",\n geminiAvailable,\n }),\n )\n }\n } catch (err) {\n consola.warn(\n `Peer MCP wiring failed (claude will launch without it): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n launchChild(\n { kind: \"claude-code\", envVars, extraArgs, model: chosenSlug },\n server,\n { onShutdown },\n )\n },\n})\n","import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport {\n DEFAULT_CODEX_MODEL,\n DEFAULT_CODEX_MODEL_FALLBACKS,\n} from \"./lib/port\"\nimport {\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { state } from \"./lib/state\"\nimport { resolveCodexModel } from \"./lib/utils\"\n\nexport const codex = defineCommand({\n meta: {\n name: \"codex\",\n description: \"Start the proxy server and launch Codex CLI\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Codex CLI\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The codex subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n const usingDefault = !args.model\n const requestedModel = args.model ?? DEFAULT_CODEX_MODEL\n\n // Resolve model before printing success message (so we show the actual model)\n // but enable file logging first so resolution warnings go to file, not terminal\n enableFileLogging()\n\n let codexModel = resolveCodexModel(requestedModel)\n if (codexModel !== requestedModel) {\n consola.info(`Model \"${requestedModel}\" resolved to \"${codexModel}\"`)\n }\n\n // For the implicit-default path only, walk DEFAULT_CODEX_MODEL_FALLBACKS\n // when the default isn't in the resolved Copilot model list. Layered on\n // top of resolveCodexModel's \"best /responses model\" fallback — that\n // remains the final safety net when every named fallback misses.\n if (usingDefault && state.models) {\n const inCache = (id: string) =>\n state.models?.data.some((m) => m.id === id) ?? false\n if (!inCache(codexModel)) {\n for (const fallback of DEFAULT_CODEX_MODEL_FALLBACKS) {\n const resolved = resolveCodexModel(fallback)\n if (inCache(resolved)) {\n consola.info(\n `Default model \"${codexModel}\" not in your Copilot model list; falling back to \"${resolved}\".`,\n )\n codexModel = resolved\n break\n }\n }\n }\n }\n\n // Validate model exists in Copilot model list\n const modelEntry = state.models?.data.find((m) => m.id === codexModel)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/responses\")\n consola.warn(\n `Model \"${codexModel}\" not found. Available codex models: ${available.join(\", \")}`,\n )\n } else {\n const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens\n if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`)\n }\n\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\\n`)\n\n const envVars = getCodexEnvVars(serverUrl)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n launchChild(\n {\n kind: \"codex\",\n envVars,\n extraArgs,\n model: codexModel,\n serverUrl,\n },\n server,\n )\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { PATHS } from \"./lib/paths\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise<boolean> {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getDebugInfo(): Promise<DebugInfo> {\n const [version, tokenExists] = await Promise.all([\n getPackageVersion(),\n checkTokenExists(),\n ])\n\n return {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n consola.info(`github-router debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise<void> {\n const debugInfo = await getDebugInfo()\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({\n json: args.json,\n })\n },\n})\n","import process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, env } = process\n\n if (platform === \"win32\") {\n // Git Bash / MSYS2 / Cygwin set SHELL even on Windows\n if (env.SHELL) {\n if (env.SHELL.endsWith(\"zsh\")) return \"zsh\"\n if (env.SHELL.endsWith(\"fish\")) return \"fish\"\n if (env.SHELL.endsWith(\"bash\")) return \"bash\"\n return \"sh\"\n }\n\n // Windows PowerShell 5.x sets this\n if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return \"powershell\"\n\n // PowerShell (both 5.x and 7+/pwsh) adds user-scoped module paths\n // at runtime. The system-level PSModulePath in CMD lacks these paths.\n if (env.PSModulePath) {\n const lower = env.PSModulePath.toLowerCase()\n if (\n lower.includes(\"documents\\\\powershell\")\n || lower.includes(\"documents\\\\windowspowershell\")\n ) {\n return \"powershell\"\n }\n }\n\n return \"cmd\"\n }\n\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n}\n\nfunction quotePosixValue(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction quotePowerShellValue(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set \"${key}=${value}\"`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${quotePosixValue(value)}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator =\n shell === \"cmd\" ? \" & \" : shell === \"powershell\" ? \"; \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\n\nimport { generateEnvScript } from \"./lib/shell\"\nimport { DEFAULT_CODEX_MODEL, DEFAULT_PORT } from \"./lib/port\"\nimport {\n getClaudeCodeEnvVars,\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\n\nfunction printAndCopyCommand(command: string, label: string): void {\n consola.box(`${label}\\n\\n${command}`)\n try {\n clipboard.writeSync(command)\n consola.success(`Copied ${label} command to clipboard!`)\n } catch {\n consola.warn(\"Failed to copy to clipboard. Copy the command above manually.\")\n }\n}\n\nfunction generateClaudeCodeCommand(serverUrl: string, model?: string) {\n const envVars = getClaudeCodeEnvVars(serverUrl, model)\n const command = generateEnvScript(envVars, \"claude --dangerously-skip-permissions\")\n printAndCopyCommand(command, \"Claude Code\")\n}\n\nfunction generateCodexCommand(serverUrl: string, model?: string) {\n const codexModel = model ?? DEFAULT_CODEX_MODEL\n const envVars = getCodexEnvVars(serverUrl)\n const command = generateEnvScript(envVars, `codex -m ${codexModel}`)\n printAndCopyCommand(command, \"Codex CLI\")\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the github-router server\",\n },\n args: {\n ...sharedServerArgs,\n cc: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n cx: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Codex CLI with Copilot API config\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model (used with --cc or --cx)\",\n },\n },\n async run({ args }) {\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n const { serverUrl } = await setupAndServe({\n ...parsed,\n port: parsed.port ?? DEFAULT_PORT,\n silent: false,\n })\n\n if (args.cc) generateClaudeCodeCommand(serverUrl, args.model)\n if (args.cx) generateCodexCommand(serverUrl, args.model)\n\n consola.box(\n `🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`,\n )\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand, runMain } from \"citty\"\nimport consola from \"consola\"\n\nimport { auth } from \"./auth\"\nimport { checkUsage } from \"./check-usage\"\nimport { claude } from \"./claude\"\nimport { codex } from \"./codex\"\nimport { debug } from \"./debug\"\nimport { start } from \"./start\"\n\nprocess.on(\"unhandledRejection\", (error) => {\n consola.error(\"Unhandled rejection:\", error)\n})\n\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nconst main = defineCommand({\n meta: {\n name: \"github-router\",\n description:\n \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n },\n subCommands: { auth, start, claude, codex, \"check-usage\": checkUsage, debug },\n})\n\nawait runMain(main)\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.js","names":["_claudeConfigDirSuffix: string | undefined","CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy>","name","SHARED_TOPLEVEL_NAMES: ReadonlyArray<string>","entries: Array<string>","stats: Awaited<ReturnType<typeof fs.lstat>>","existing: Awaited<ReturnType<typeof fs.lstat>> | null","state: State","state","version","headers: Record<string, string>","errorJson: unknown","parsed: URL","FALLBACK","version","inflightRefresh: Promise<void> | undefined","name","sanitized: NodeJS.ProcessEnv","process","name","raw: string","version","cmd: string[]","child: ChildProcess","server","_rgResolution: RipgrepResolution | undefined","path","canonical: string","out: Array<string>","pieces: Array<string>","args: Array<string>","hits: Array<RawHit>","pendingContextBefore: Array<string>","lastHitForContext: RawHit | undefined","evt: RgEvent","hit: RawHit","EXTENSION_TO_LANG: Readonly<Record<string, string>>","GRAMMAR_FILES: Readonly<Record<string, string>>","DEFINITION_NODE_TYPES: Readonly<Record<string, ReadonlySet<string>>>","_grammarBundle: GrammarBundle | undefined","cur: Parser.SyntaxNode | null","result: StructuralPassResult","mtimeMs: number","size: number","source: string","tree: Parser.Tree | null","node: Parser.SyntaxNode | null","symbolContext: string","perHitTokens: Array<Record<keyof FieldTexts, Array<string>>>","fileTokensByField: Record<keyof FieldTexts, Map<string, Set<string>>>","avglen: Record<keyof FieldTexts, number>","lens: Array<number>","out: Array<ScoredHit>","contributions: Record<string, number>","perField: Record<string, number>","parseResult: ParseResult","rgResolution: RipgrepResolution","child: ChildProcess","exitCode: number | null","kept: Array<ScoredHit>","prunedBelowShoulder: number | undefined","notice: string | null","results: Array<CodeSearchHit>","baseHit: CodeSearchHit","searchTimestamps: Array<number>","throttleChain: Promise<void>","headers: Record<string, string>","sid: string | undefined","rpc: z.infer<typeof RpcSchema> | undefined","parsedJson: unknown","innerRaw: unknown","references: Array<{ title: string; url: string }>","PERSONAS_READ: ReadonlyArray<PersonaSpec>","PERSONAS_WRITE: ReadonlyArray<PersonaSpec>","criticList: Array<string>","result: Array<PersonaSpec>","NON_PERSONA_MCP_TOOLS: ReadonlyArray<NonPersonaMcpTool>","trimmedHits: Array<{\n file: string\n line: number\n snippet: string\n }>","minimal: {\n results: Array<{ file: string; line: number; snippet: string }>\n truncated: boolean\n notice?: string\n }","mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry>","peers: Array<string>","out: PeerAgentDefinitions","name","paths: Array<string>","existing: Record<string, unknown>","mcpServers: Record<string, unknown>","conflicts: Array<string>","size: number","fs","fd: number | undefined","ENDPOINT_ALIASES: Record<string, Endpoint>","path","rateLimitChain: Promise<void>","state","parts: Array<string>","ENCODER","timeoutHandle: ReturnType<typeof setTimeout> | undefined","x","fetchInit: RequestInit","signals: Array<AbortSignal>","ENCODER","formatSSE","parts: Array<string>","handleCompletion","injectWebSearchIfNeeded","inputTokens: number | undefined","isNonStreaming","pendingFirstChunk: UpstreamSSEEvent | undefined","extractUserQuery","handleCompletion","fetchInit: RequestInit","signals: Array<AbortSignal>","fetchInit: RequestInit","signals: Array<AbortSignal>","bodyText: string","auth","personaEntries: Array<ToolEntry>","nonPersonaEntries: Array<ToolEntry>","out: Array<string>","PRE_FLIGHT_CAPS: ReadonlyArray<{\n toolName: string\n effort: Effort\n maxBriefBytes: number\n}>","name","effort: Effort","text","nonPersonaTool: NonPersonaMcpTool | undefined","personaPrompt: string | undefined","personaContext: string | undefined","personaEffort: Effort | undefined","aborter: AbortController | undefined","body: JsonRpcRequest","ENCODER","parsed: AnyRecord","turnBlocks: Array<string>","block: Array<string>","out: Array<string>","text","capturedBlocks: Array<CapturedBlock>","advisorToolUse: ToolUseTracker | null","payload: AnyRecord","captured: CapturedBlock","response: Response","advisorText: string","messages: Array<AnyRecord>","currentAssistantContent: Array<unknown>","parsed: AnyRecord","rebuilt: Array<unknown>","name","isWebSearchTool","body: AnyRecord","resolveModelInBody","extraHeaders: Record<string, string>","parsed: AnyRecord","sanitizeCacheControl","stripAnthropicOnlyFields","headers: Record<string, string>","extractUserQuery","body: AnyRecord","parsedForLog: unknown","response: Response","streamHeaders: Record<string, string>","parsedBase: AnyRecord","parsed: AnyRecord","best: (typeof EFFORT_ORDER)[number] | undefined","parts: Array<string>","firstChunk: UpstreamSSEEvent | undefined","pendingFirstChunk: UpstreamSSEEvent | undefined","payload: ResponsesPayload","result: ResponsesApiResponse","packageJson.name","packageJson.version","app","srvxServer: ReturnType<typeof serve> | undefined","lastError: unknown","port: number | undefined","rateLimit: number | undefined","vars: Record<string, string>","process","server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]","serverUrl: string","onShutdown: () => Promise<void>","geminiAvailable","server","process","server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]","serverUrl: string","server","version","process","commandBlock: string"],"sources":["../src/lib/paths.ts","../src/lib/state.ts","../src/lib/api-config.ts","../src/lib/error.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/github/get-user.ts","../src/services/copilot/get-models.ts","../src/services/get-copilot-version.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts","../src/auth.ts","../src/services/github/get-copilot-usage.ts","../src/check-usage.ts","../src/lib/claude-version-check.ts","../src/lib/port.ts","../src/lib/launch.ts","../src/lib/code-search.ts","../src/services/copilot/web-search.ts","../src/lib/peer-mcp-personas.ts","../src/lib/codex-mcp-config.ts","../src/lib/file-log-reporter.ts","../src/lib/model-validation.ts","../src/lib/proxy.ts","../package.json","../src/lib/approval.ts","../src/lib/rate-limit.ts","../src/lib/request-log.ts","../src/lib/stream-relay.ts","../src/lib/tokenizer.ts","../src/services/copilot/create-chat-completions.ts","../src/routes/chat-completions/handler.ts","../src/routes/chat-completions/route.ts","../src/services/copilot/create-embeddings.ts","../src/routes/embeddings/route.ts","../src/services/copilot/create-messages.ts","../src/services/copilot/create-responses.ts","../src/routes/mcp/handler.ts","../src/routes/mcp/route.ts","../src/services/advisor/advisor.ts","../src/lib/sanitize-anthropic-body.ts","../src/lib/diagnose-response.ts","../src/routes/messages/count-tokens-handler.ts","../src/routes/messages/handler.ts","../src/routes/messages/route.ts","../src/routes/models/route.ts","../src/routes/responses/handler.ts","../src/routes/responses/route.ts","../src/routes/search/route.ts","../src/routes/token/route.ts","../src/routes/usage/route.ts","../src/server.ts","../src/lib/server-setup.ts","../src/claude.ts","../src/codex.ts","../src/debug.ts","../src/lib/shell.ts","../src/start.ts","../src/main.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nfunction appDir(): string {\n return path.join(os.homedir(), \".local\", \"share\", \"github-router\")\n}\n\nexport const PATHS = {\n get APP_DIR() {\n return appDir()\n },\n get GITHUB_TOKEN_PATH() {\n return path.join(appDir(), \"github_token\")\n },\n get ERROR_LOG_PATH() {\n return path.join(appDir(), \"error.log\")\n },\n /**\n * Isolated CODEX_HOME for the spawned Codex CLI. Masks any cached\n * ChatGPT subscription login (openai/codex#2733 — cached login can\n * override OPENAI_API_KEY) so the proxy's dummy key is authoritative.\n */\n get CODEX_HOME() {\n return path.join(appDir(), \"codex-isolated\")\n },\n /**\n * Runtime tempfiles for the per-launch peer-MCP wiring (the\n * `--mcp-config` JSON and `--agents` JSON written before spawning\n * Claude Code). Mode 0o700 to match the security review's mandate;\n * cleaned on shutdown via the per-launch `cleanup()`, plus a\n * boot-time sweep of stale files (dead PIDs, >24h old).\n */\n get CLAUDE_RUNTIME_DIR() {\n return path.join(appDir(), \"runtime\")\n },\n /**\n * Router-owned CLAUDE_CONFIG_DIR. The spawned Claude Code (and any\n * teammates it spawns via the agent-teams primitive) reads its\n * config — including `.credentials.json` — from this dir. We\n * snapshot-copy the user's `~/.claude/` here at startup (excluding\n * `.credentials.json` and volatile state), then write our own\n * synthetic Console OAuth credential. The teammate-spawn allowlist\n * propagates `CLAUDE_CONFIG_DIR` to children, so teammates find the\n * synthetic credential and authenticate instead of falling into the\n * \"Not logged in · Run /login\" gate that would otherwise leave\n * them mute. See `ensureClaudeConfigMirror` below.\n *\n * Per-launch dir: `<appDir>/claude-config/<pid>-<8 hex>`. Two\n * concurrent `github-router claude` launches each get their own\n * isolated mirror, so per-launch state (synthetic credential,\n * snapshot copy of `~/.claude/`, future per-launch `.claude.json`\n * mutation with the peer-MCP entry) cannot cross-talk. The\n * per-launch suffix is cached on first access (see\n * `claudeConfigDirSuffix()`) so all callers within a single proxy\n * lifetime see the same value. Boot-time `sweepStaleClaudeConfigMirrors`\n * reaps mirrors from crashed prior PIDs.\n */\n get CLAUDE_CONFIG_DIR() {\n return path.join(appDir(), \"claude-config\", claudeConfigDirSuffix())\n },\n}\n\n/**\n * Per-launch suffix for `PATHS.CLAUDE_CONFIG_DIR`. Lazily generated on\n * first access and cached for the lifetime of the process so every\n * caller (env-var injection in `getClaudeCodeEnvVars`,\n * `ensureClaudeConfigMirror` provisioning, peer-agent `.md` writes\n * under `<dir>/agents/`, the shutdown cleanup) resolves the same path.\n *\n * Shape: `<pid>-<8 hex>`. The PID prefix is what\n * `sweepStaleClaudeConfigMirrors` keys off to drop orphans from\n * crashed prior sessions; the 8-hex random suffix prevents collision\n * if a future caller (tests, internal relaunch) ever clears the cache\n * within a single PID lifetime.\n *\n * NOT exported — every consumer should go through `PATHS.CLAUDE_CONFIG_DIR`\n * so the homedir-mock pattern used in the test suite keeps working.\n */\nlet _claudeConfigDirSuffix: string | undefined\nfunction claudeConfigDirSuffix(): string {\n if (_claudeConfigDirSuffix === undefined) {\n _claudeConfigDirSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n }\n return _claudeConfigDirSuffix\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await fs.mkdir(PATHS.CODEX_HOME, { recursive: true })\n await fs.mkdir(PATHS.CLAUDE_RUNTIME_DIR, { recursive: true })\n // mkdir({recursive: true}) does NOT chmod an existing directory, so\n // explicitly tighten in case the dir was created by an older version.\n await chmodIfPossible(PATHS.CLAUDE_RUNTIME_DIR, 0o700)\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n await sweepStaleRuntimeFiles().catch((err) => {\n consola.debug(\"Runtime sweep skipped:\", err)\n })\n // Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n // crashed prior proxy sessions BEFORE peer-agent .md sweep, since\n // the .md sweep is scoped to THIS launch's mirror and the per-launch\n // dir sweep is the parent cleanup for the same orphan class.\n await sweepStaleClaudeConfigMirrors().catch((err) => {\n consola.debug(\"Per-launch claude-config sweep skipped:\", err)\n })\n // Phase 2.5: also sweep stale peer-* subagent .md files from this\n // launch's CLAUDE_CONFIG_DIR/agents/ (defense-in-depth — should be\n // a no-op since the per-launch dir didn't exist before this PID\n // started; keeps the safety net in case a future change ever shares\n // an agents/ dir across launches).\n await sweepStalePeerAgentMdFiles().catch((err) => {\n consola.debug(\"Peer-agent .md sweep skipped:\", err)\n })\n}\n\n/**\n * Per-entry mirror policy. Every top-level entry under `~/.claude/` falls\n * into exactly one bucket; unlisted names default to `MIRRORED` so a future\n * Claude-Code-side addition flows through as a snapshot copy rather than\n * being silently lost.\n *\n * Three policies:\n *\n * - `ISOLATED` — not present in the mirror at all. The proxy owns its\n * own copy (synthetic `.credentials.json`, the `.github-router-managed`\n * marker) or the entry has no place in a proxy session\n * (`.credentials.json.lock`, `.oauth_refresh.lock` couple refresh loops\n * across sessions; `statsig/` is write-heavy and would constantly\n * re-copy; `cache/` and `logs/` are ephemeral; `paste-cache/` holds\n * sensitive clipboard extracts and shouldn't leak across sessions —\n * gemini-critic finding).\n *\n * - `SHARED` — symlink `<mirror>/<X>` → `~/.claude/<X>` so writes made\n * during the proxy session land in the user's real `~/.claude/` and\n * chat history is visible in both proxy and plain-`claude` sessions.\n * **Directories only.** Never use this for individual files: Claude\n * Code's atomic-write pattern (`fs.writeFile(temp); fs.rename(temp,\n * target)`) does NOT follow symlinks — a `rename` over the symlink\n * replaces it with a regular file, silently severing the connection\n * to `~/.claude/<X>`. Gemini-critic finding from the 3-lab review.\n *\n * - `MIRRORED` (default) — snapshot-copy with mtime skip. Use for static\n * or settings-shaped state where proxy-session writes should NOT flow\n * back to `~/.claude/` (e.g. `settings.json`, `.claude.json`,\n * `teams/`, `session-env/`) and for `agents/` — the proxy itself\n * writes per-launch `peer-<pid>-*.md` files into the mirror's `agents/`\n * and `sweepStalePeerAgentMdFiles` deletes them; a symlink would route\n * those writes/deletes into the user's real `~/.claude/agents/` and\n * destroy the user's own subagent files. **Hard regression test**:\n * `policyFor(\"agents\") === \"MIRRORED\"` is asserted in\n * `tests/lib-paths.test.ts` to prevent accidental reclassification.\n *\n * Sub-paths within MIRRORED dirs cascade recursively (existing behavior).\n */\ntype MirrorPolicy = \"ISOLATED\" | \"SHARED\" | \"MIRRORED\"\n\nconst CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy> = new Map<\n string,\n MirrorPolicy\n>([\n // ISOLATED\n [\".credentials.json\", \"ISOLATED\"],\n [\".credentials.json.lock\", \"ISOLATED\"],\n [\".oauth_refresh.lock\", \"ISOLATED\"],\n // Defense-in-depth: don't let a user-side file/symlink with the same\n // name as our marker collide with what we write. The marker write\n // logic also lstat-checks before writing (refuses if a non-regular\n // file exists at the path), but excluding it here removes the\n // attack vector entirely.\n [\".github-router-managed\", \"ISOLATED\"],\n [\"statsig\", \"ISOLATED\"],\n [\"cache\", \"ISOLATED\"],\n [\"logs\", \"ISOLATED\"],\n [\"paste-cache\", \"ISOLATED\"],\n [\"jobs\", \"ISOLATED\"],\n [\"daemon\", \"ISOLATED\"],\n [\"daemon.log\", \"ISOLATED\"],\n // SHARED — directories only (see policy doc above)\n [\"projects\", \"SHARED\"],\n [\"sessions\", \"SHARED\"],\n [\"tasks\", \"SHARED\"],\n [\"todos\", \"SHARED\"],\n [\"transcripts\", \"SHARED\"],\n [\"shell-snapshots\", \"SHARED\"],\n // The underscored variant is the historical exclude-list name; some\n // Claude Code versions may still use it. Classify SHARED so either\n // spelling resolves correctly.\n [\"shell_snapshots\", \"SHARED\"],\n [\"plans\", \"SHARED\"],\n [\"file-history\", \"SHARED\"],\n [\"backups\", \"SHARED\"],\n])\n\nfunction policyFor(name: string): MirrorPolicy {\n return CLAUDE_HOME_POLICY.get(name) ?? \"MIRRORED\"\n}\n\n/**\n * Test-only export: lets the test suite assert hard regression guards\n * such as `policyFor(\"agents\") === \"MIRRORED\"` (preventing accidental\n * reclassification that would let `sweepStalePeerAgentMdFiles` delete\n * files in the user's real `~/.claude/agents/`).\n */\nexport const __testing = { policyFor, ensureSharedSymlink }\n\n/**\n * Names with `SHARED` policy, materialized once for iteration in\n * `ensureClaudeConfigMirror`'s post-copy phase.\n */\nconst SHARED_TOPLEVEL_NAMES: ReadonlyArray<string> = Array.from(\n CLAUDE_HOME_POLICY.entries(),\n)\n .filter(([, kind]) => kind === \"SHARED\")\n .map(([name]) => name)\n\n/**\n * Marker file written into the router-owned CLAUDE_CONFIG_DIR so users\n * (and our own future sweeps) can identify that the dir is managed by\n * github-router. Content is informational only; no logic depends on\n * its presence.\n */\nconst MANAGED_MARKER_FILENAME = \".github-router-managed\"\n\n/**\n * Synthetic Console OAuth credential the router writes into its own\n * `CLAUDE_CONFIG_DIR/.credentials.json` so spawned Claude Code (and\n * any teammates it spawns) can authenticate without a real user\n * `/login`.\n *\n * Schema verified verbatim from `claude` v2.1.140 binary, function\n * `guH` (the credentials-save mutation). Fields:\n * - `accessToken` — sent as `Authorization: Bearer ...` to the\n * proxy. Proxy accepts any bearer (per CLAUDE.md \"doesn't enforce\n * auth\").\n * - `refreshToken` — only used by Claude Code's reactive refresh\n * path (function `nH8`), which fires on 401 from upstream. The\n * proxy maintains the no-401 invariant on the Anthropic-shape\n * boundary, so this is never invoked. Synthetic value is fine.\n * - `expiresAt` — far-future (2099-01-01 ms epoch). Sidesteps the\n * proactive refresh path (`R8H(expiresAt)` returns false).\n * - `scopes` — claude-ai-shaped so `tB(scopes)` returns true,\n * making `Hq()` true (full feature surface, not \"inference only\").\n * - `subscriptionType` — `\"max\"`. Pure client-side label\n * (`e7()` / `Zc_()` / `CZ1()`); no server validation since\n * `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` suppresses\n * subscription-validation calls. Picks the most-permissive gating.\n */\nconst SYNTHETIC_CREDENTIAL = {\n claudeAiOauth: {\n accessToken: \"github-router-synthetic\",\n refreshToken: \"github-router-synthetic\",\n expiresAt: 4_070_908_800_000,\n scopes: [\"user:inference\", \"user:profile\"],\n subscriptionType: \"max\",\n rateLimitTier: null,\n clientId: \"github-router\",\n },\n} as const\n\n/**\n * Snapshot-copy the user's `~/.claude/` into the router-owned\n * CLAUDE_CONFIG_DIR (real files, not symlinks — symlinks don't isolate\n * writes), classifying each top-level entry per `CLAUDE_HOME_POLICY`:\n * ISOLATED entries are skipped, MIRRORED entries are copied, and\n * SHARED entries become directory symlinks back to `~/.claude/<X>` so\n * chat history (in `projects/<cwd-hash>/<session-uuid>.jsonl`) and\n * other durable user state flow between proxy and plain-`claude`\n * sessions. Then writes the synthetic `.credentials.json` so spawned\n * Claude Code (and teammates that inherit `CLAUDE_CONFIG_DIR`)\n * authenticate.\n *\n * Idempotent: only re-copies files whose source `mtime` is newer than\n * target; SHARED-symlink creation no-ops when the symlink already\n * points at the right target. Concurrent-safe: `mkdir({recursive:true})`\n * is idempotent; symlinks are created via atomic temp+rename so two\n * parallel github-router-claude startups can't race to EEXIST; the\n * credentials write uses temp-file + atomic rename so Claude Code's\n * `EZ1()` mtime watcher never sees a partial write.\n *\n * Walks with `lstat` (does NOT follow symlinks during traversal — a\n * symlink-into-`/` would otherwise let the walk escape). Symlink leaves\n * in the source tree are skipped during the MIRRORED copy walk (per the\n * symlink-confused-deputy security finding); SHARED symlinks are\n * created on the mirror side only, pointing at predetermined targets\n * inside the user's real `~/.claude/`.\n *\n * Caller is expected to invoke this after `ensurePaths()` and before\n * spawning Claude Code (`launchChild`). The mirror must exist before\n * the child reads it. Currently called from the `claude` subcommand\n * entry point only; `start` and `codex` subcommands don't need it.\n */\nexport async function ensureClaudeConfigMirror(opts: {\n realHome?: string\n} = {}): Promise<void> {\n const realHome = opts.realHome ?? os.homedir()\n const sourceDir = path.join(realHome, \".claude\")\n const targetDir = PATHS.CLAUDE_CONFIG_DIR\n\n // 1. Create our config dir (idempotent, mode 0o700)\n await fs.mkdir(targetDir, { recursive: true, mode: 0o700 })\n await chmodIfPossible(targetDir, 0o700)\n\n // 2. Snapshot-copy from ~/.claude if it exists. Only MIRRORED entries\n // flow through this walk; ISOLATED and SHARED entries are filtered\n // in `mirrorDirRecursive` and handled separately.\n let sourceExists = false\n try {\n const sourceStat = await fs.stat(sourceDir)\n sourceExists = sourceStat.isDirectory()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot stat ${sourceDir}:`, err)\n }\n }\n if (sourceExists) {\n await mirrorDirRecursive(sourceDir, targetDir, \"\")\n }\n\n // 3. Always ensure agents/ exists (even if user has none) so the\n // peer-agent .md emission has a place to write. Empty dir is fine.\n // agents/ is MIRRORED, not SHARED — the proxy writes per-launch\n // `peer-<pid>-*.md` files here and `sweepStalePeerAgentMdFiles`\n // deletes them; routing those operations into the user's real\n // `~/.claude/agents/` would destroy their custom subagent files.\n await fs.mkdir(path.join(targetDir, \"agents\"), { recursive: true })\n\n // 4. Create symlinks for SHARED entries so chat history (and other\n // durable user state) is visible in both proxy and plain-`claude`.\n for (const name of SHARED_TOPLEVEL_NAMES) {\n await ensureSharedSymlink(name, sourceDir, targetDir).catch((err) => {\n consola.debug(\n `ensureClaudeConfigMirror: SHARED symlink for ${name} skipped:`,\n err,\n )\n })\n }\n\n // 5. Write synthetic .credentials.json (only if content differs)\n const credentialsPath = path.join(targetDir, \".credentials.json\")\n const desiredJson = JSON.stringify(SYNTHETIC_CREDENTIAL, null, 2)\n let needsWrite = true\n try {\n const existing = await fs.readFile(credentialsPath, \"utf8\")\n needsWrite = existing.trim() !== desiredJson.trim()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot read existing credentials:`, err)\n }\n }\n if (needsWrite) {\n // Atomic temp-file + rename so EZ1()'s mtime watcher doesn't see\n // a partial write. wx flag ensures we don't clobber a concurrent\n // writer's tempfile.\n const tempPath = `${credentialsPath}.${process.pid}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson + \"\\n\", { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, credentialsPath)\n } catch (err) {\n // EEXIST on the tempfile means another concurrent startup is\n // mid-write. Best-effort: skip — the other writer will produce\n // identical content (deterministic constant blob).\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n consola.debug(\n \"ensureClaudeConfigMirror: concurrent credentials-write detected, skipping\",\n )\n } else {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n }\n }\n await chmodIfPossible(credentialsPath, 0o600)\n\n // 6. Write/refresh marker file. Use lstat (not access) to detect\n // symlinks at the marker path — a previously-mirrored or\n // user-placed symlink could otherwise let our `fs.writeFile`\n // follow through to an arbitrary target. With the symlink-skip\n // policy in `mirrorDirRecursive` this is defense-in-depth, but\n // cheap and definitive.\n const markerPath = path.join(targetDir, MANAGED_MARKER_FILENAME)\n let markerExists = false\n try {\n const markerStat = await fs.lstat(markerPath)\n if (markerStat.isFile()) {\n markerExists = true\n } else {\n // Anything non-regular (symlink, dir, special file) is a red flag —\n // refuse to overwrite, log loudly. The user can investigate.\n consola.warn(\n `ensureClaudeConfigMirror: ${markerPath} exists but is not a regular file (mode=${markerStat.mode.toString(8)}); refusing to overwrite. Inspect and remove manually if safe.`,\n )\n markerExists = true\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot lstat marker:`, err)\n markerExists = true\n }\n }\n if (!markerExists) {\n const body = `Managed by github-router. Created ${new Date().toISOString()}. Safe to delete (will be recreated).\\n`\n // wx flag (O_CREAT | O_EXCL) refuses to clobber an existing\n // file or symlink (POSIX O_EXCL behavior) — additional protection\n // against the marker-symlink confused-deputy vector.\n await fs\n .writeFile(markerPath, body, { mode: 0o600, flag: \"wx\" })\n .catch((err) => {\n consola.debug(`ensureClaudeConfigMirror: marker write skipped:`, err)\n })\n }\n}\n\n/**\n * Recursive snapshot-copy helper for `ensureClaudeConfigMirror`. Walks\n * `sourceDir/relPath` and mirrors each entry into `targetDir/relPath`.\n * - Top-level entries are dispatched on `policyFor(name)`:\n * - `ISOLATED` → skipped entirely (no presence in mirror).\n * - `SHARED` → skipped from the copy walk; handled by\n * `ensureSharedSymlink` in the post-copy phase.\n * - `MIRRORED` → copied as today.\n * - Symlinks are skipped (not recreated) so the walk never follows out\n * of `sourceDir` and we don't reintroduce a confused-deputy vector.\n * - Files copy only if source mtime > target mtime (idempotent).\n */\nasync function mirrorDirRecursive(\n sourceDir: string,\n targetDir: string,\n relPath: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, relPath)\n let entries: Array<string>\n try {\n entries = await fs.readdir(sourcePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n consola.debug(`mirrorDirRecursive: cannot readdir ${sourcePath}:`, err)\n return\n }\n for (const name of entries) {\n // Policy dispatch at top-level only. Sub-paths within MIRRORED\n // dirs always cascade as MIRRORED.\n if (relPath === \"\") {\n const policy = policyFor(name)\n if (policy === \"ISOLATED\" || policy === \"SHARED\") continue\n }\n const childRel = relPath === \"\" ? name : path.join(relPath, name)\n const childSource = path.join(sourceDir, childRel)\n const childTarget = path.join(targetDir, childRel)\n let stats: Awaited<ReturnType<typeof fs.lstat>>\n try {\n stats = await fs.lstat(childSource)\n } catch (err) {\n consola.debug(`mirrorDirRecursive: cannot lstat ${childSource}:`, err)\n continue\n }\n if (stats.isSymbolicLink()) {\n // Skip symlinks during mirror copy. gemini-critic security finding:\n // recreating user symlinks in our mirror creates a confused-deputy\n // vector — a previously prompt-injected process could place\n // `~/.claude/<X>` → `/some/sensitive/file`, our walker would mirror\n // it, and any subsequent write to `<mirror>/<X>` (by us or by\n // Claude Code) would follow the symlink and overwrite the target.\n // Snapshot-copy semantics make symlink preservation moot anyway:\n // a snapshot is a point-in-time content copy, and a symlink\n // recreated in the mirror points at exactly the same target as\n // the original would have — the user-side symlink is sufficient.\n // If a user has a legitimate need for a symlink to be visible\n // through the proxy session, they can create the equivalent\n // symlink in their `~/.claude/` directly and it'll be reachable\n // — they just won't see it in our mirror dir.\n consola.debug(`mirrorDirRecursive: skipping symlink ${childSource} (security policy)`)\n continue\n }\n if (stats.isDirectory()) {\n await fs.mkdir(childTarget, { recursive: true })\n await mirrorDirRecursive(sourceDir, targetDir, childRel)\n continue\n }\n if (stats.isFile()) {\n // mtime-based skip — only copy if source is newer than target.\n let needsCopy = true\n try {\n const targetStat = await fs.lstat(childTarget)\n if (targetStat.isFile() && targetStat.mtimeMs >= stats.mtimeMs) {\n needsCopy = false\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`mirrorDirRecursive: lstat target ${childTarget}:`, err)\n }\n }\n if (!needsCopy) continue\n try {\n await fs.copyFile(childSource, childTarget, fs.constants.COPYFILE_FICLONE)\n } catch (err) {\n consola.debug(`mirrorDirRecursive: copy ${childSource} → ${childTarget}:`, err)\n }\n continue\n }\n // Skip other inode types (sockets, devices, fifos) silently.\n }\n}\n\n/**\n * Create or refresh a directory symlink `<mirrorDir>/<name>` →\n * `<sourceDir>/<name>` (i.e. `~/.local/share/github-router/claude-config/<X>`\n * → `~/.claude/<X>`). Idempotent and concurrent-safe.\n *\n * Behavior depending on what's already at `<mirrorDir>/<name>`:\n * - Symlink with the correct target → no-op.\n * - Symlink with the wrong target → replace atomically.\n * - Empty real directory (legacy mirror leftover with no proxy-session\n * writes accumulated yet) → `rmdir` and replace with the symlink.\n * Safe by definition: `fs.rmdir` only succeeds on empty dirs (POSIX),\n * so there is nothing to lose. Smooths the upgrade path for users\n * whose legacy mirror dirs were never written to.\n * - Non-empty real directory or regular file → loud-warn and skip.\n * Auto-deleting would destroy proxy-session writes from the prior\n * version. The user is told the exact path and remediation.\n * - ENOENT → create symlink atomically.\n *\n * Atomic-creation: symlinks are first written at a unique side-path\n * (`<mirrorDir>/<name>.tmp.<pid>.<8 hex>`) and then `fs.rename()`d into\n * place. POSIX `rename` is atomic and replaces an existing symlink in\n * a single step, so two concurrent `github-router claude` startups can't\n * race to `EEXIST` — the loser's rename just overwrites the winner's\n * symlink with an identical one. Gemini-critic 3-lab-review finding.\n *\n * Pre-creates `~/.claude/<name>/` as a real directory if missing so\n * Claude Code's writes through the symlink don't fail with ENOENT.\n */\nasync function ensureSharedSymlink(\n name: string,\n sourceDir: string,\n mirrorDir: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, name)\n const mirrorPath = path.join(mirrorDir, name)\n\n // 1. Ensure the source directory exists. Without this, Claude Code's\n // writes through the symlink (e.g. `projects/<hash>/foo.jsonl`)\n // fail with ENOENT on the parent dir.\n try {\n await fs.mkdir(sourcePath, { recursive: true })\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the symlink and rename catches below):\n // if the source dir cannot be created (e.g. a stray regular file\n // sitting at `~/.claude/projects`, perms blocking mkdir on a\n // corp-managed Windows box, OneDrive cloud-only reparse point),\n // ensureSharedSymlink returns without creating a junction. The\n // spawned Claude Code child then writes to the REAL `~/.claude`\n // while the proxy reads from the mirror — exactly the split-brain\n // pattern this whole function exists to prevent. Silent debug-log\n // hid this from us once already; warn so the user sees the cause.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot mkdir source ${sourcePath}:`,\n err,\n )\n return\n }\n\n // 2. Inspect the mirror-side slot.\n let existing: Awaited<ReturnType<typeof fs.lstat>> | null = null\n try {\n existing = await fs.lstat(mirrorPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the other fs catches in this function):\n // ENOENT is the only expected-and-benign failure mode here\n // (slot doesn't exist yet — falls through to create). Any other\n // lstat failure (EACCES, ELOOP, EIO from a sketchy reparse\n // point) means we bail without creating the junction, which\n // silently leaves the proxy and child diverged. A visible warn\n // surfaces the root cause instead of a mysteriously missing\n // junction.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot lstat ${mirrorPath}:`,\n err,\n )\n return\n }\n }\n\n if (existing?.isSymbolicLink()) {\n // Resolve both sides to their canonical absolute paths and compare.\n // We use `fs.realpath` rather than the raw `fs.readlink()` output\n // because Windows junctions resolve via readlink to `\\\\?\\`-prefixed\n // device-namespace paths (e.g. `\\\\?\\C:\\Users\\foo\\.claude\\projects`)\n // while we wrote the plain absolute `sourcePath` (e.g.\n // `C:\\Users\\foo\\.claude\\projects`) with `fs.symlink`. A literal\n // `===` on the raw readlink output never matched on Windows, so\n // the fast path silently failed and every startup tore down +\n // recreated all 9 SHARED junctions — masked locally because NTFS\n // File System Tunneling forges the creation timestamp for a name\n // deleted and recreated within 15 s (the per-startup churn was\n // real, the ctime-stable assertion was a false negative). The\n // realpath comparison canonicalizes both forms to the same string\n // on POSIX and Windows alike, and as a bonus handles drive-letter\n // casing / trailing-slash differences too. The extra two syscalls\n // per slot are negligible at proxy startup (runs once per launch).\n //\n // CRITICAL: sourceReal and currentReal are NOT treated symmetrically.\n // If `sourceReal` is null (we just mkdir'd it above, but realpath\n // failed — OneDrive cloud-only reparse point, EACCES on parent,\n // EXDEV mount oddity), we WARN AND RETURN rather than fall through.\n // Falling through would do unlink+symlink+rename with the same\n // failing realpath next launch — silent every-startup churn, the\n // exact bug class round-3 G2 fixed in a different code path.\n // `currentReal === null` is benign (broken/wrong slot — replace).\n const sourceReal = await fs.realpath(sourcePath).catch(() => null)\n if (sourceReal === null) {\n consola.warn(\n `ensureSharedSymlink(${name}): cannot resolve source ${sourcePath} ` +\n `— skipping junction creation to avoid silent every-startup churn. ` +\n `Inspect the source dir's permissions / OneDrive sync state and re-launch.`,\n )\n return\n }\n const currentReal = await fs.realpath(mirrorPath).catch(() => null)\n if (currentReal !== null && currentReal === sourceReal) {\n return\n }\n // Wrong target (or unresolvable mirror) — fall through to the\n // atomic-rename replace path.\n } else if (existing?.isDirectory()) {\n // Legacy real directory at the slot. Try `fs.rmdir` — on POSIX it\n // succeeds ONLY if the directory is empty, so there's nothing to\n // lose. If it's non-empty (ENOTEMPTY) or any other failure occurs,\n // fall back to the warn-and-skip path so we never auto-clobber\n // user data.\n try {\n await fs.rmdir(mirrorPath)\n // Empty dir reaped — fall through to the atomic-rename create path.\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a non-empty real directory ` +\n `from an older github-router version; refusing to clobber. ` +\n `If you want chat-history continuity for \"${name}\", move its ` +\n `contents into ${sourcePath}/ then delete ${mirrorPath}; the ` +\n `mirror will create a symlink (junction on Windows) on next launch. ` +\n `(rmdir error: ${(err as NodeJS.ErrnoException).code ?? \"unknown\"})`,\n )\n return\n }\n } else if (existing) {\n // Regular file (or special inode like a socket) — never auto-clobber.\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a regular file at a ` +\n `SHARED symlink slot; refusing to clobber. Inspect and remove ` +\n `manually if safe; the mirror will create a symlink on next launch.`,\n )\n return\n }\n\n // 3. Atomic-rename creation: symlink to a unique temp path, then\n // rename over the slot. `fs.rename` replaces existing symlinks\n // atomically on POSIX and is safe against concurrent racers.\n // On Windows, MoveFileEx with MOVEFILE_REPLACE_EXISTING does NOT\n // replace an existing directory or junction destination\n // (npm/cli#9021), so when the slot already holds a wrong-target\n // junction we must explicitly unlink it first. The sub-millisecond\n // window of no-link is acceptable: ensureClaudeConfigMirror is\n // idempotent under concurrency and only runs at proxy startup,\n // before any spawned Claude Code child has been launched.\n const tempPath = `${mirrorPath}.tmp.${process.pid}.${randomBytes(4).toString(\"hex\")}`\n try {\n await fs.symlink(\n sourcePath,\n tempPath,\n process.platform === \"win32\" ? \"junction\" : \"dir\",\n )\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\" rule:\n // the rule applies to ALL fs catches in this function, not just the\n // rename one. The temp path is per-pid + 8-hex random so EEXIST is\n // essentially impossible — any failure here (EPERM on Windows\n // without DevMode, EXDEV cross-volume, ENOSPC, …) is a real\n // operational problem the user needs to see.\n consola.warn(\n `ensureSharedSymlink(${name}): symlink ${tempPath} failed:`,\n err,\n )\n return\n }\n if (process.platform === \"win32\" && existing?.isSymbolicLink()) {\n // Windows-only: clear the wrong-target junction so the rename\n // below can land. Best-effort — if a concurrent racer already\n // unlinked it, the rename succeeds as a CREATE; if a concurrent\n // racer already replaced it with a fresh junction, the rename\n // hits the catch below and we surface a warn.\n await fs.unlink(mirrorPath).catch(() => {})\n }\n try {\n await fs.rename(tempPath, mirrorPath)\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the fs.symlink catch above): a silent\n // debug log here previously hid the Windows rename-replace bug\n // (junction-over-junction MoveFileEx EPERM). Post-fix, rename\n // failures should be rare and visible.\n consola.warn(\n `ensureSharedSymlink(${name}): rename ${tempPath} → ${mirrorPath} failed:`,\n err,\n )\n await fs.unlink(tempPath).catch(() => {})\n }\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n\nasync function chmodIfPossible(target: string, mode: number): Promise<void> {\n if (process.platform === \"win32\") return // Windows chmod is no-op-ish\n try {\n await fs.chmod(target, mode)\n } catch (err) {\n consola.debug(`chmod ${target} ${mode.toString(8)} failed:`, err)\n }\n}\n\n/**\n * Write a runtime tempfile securely.\n *\n * - Mode `0o600` so other local users (multi-tenant boxes, shared\n * dev containers) can't read the per-launch nonce or runtime URL.\n * - `flag: \"wx\"` (O_CREAT | O_EXCL | O_WRONLY) refuses to overwrite\n * an existing path. POSIX open(2) with O_EXCL also rejects\n * pre-placed symlinks, killing the symlink-clobber attack vector.\n * - The caller's responsibility to pick a path NOT yet in use.\n * We intentionally do NOT pre-unlink: an `lstat` + `unlink` +\n * `open(O_EXCL)` sequence still has a TOCTOU window where an\n * attacker can drop a symlink between unlink and open. Letting\n * `wx` fail is the safer behavior — surfaces the conflict\n * instead of silently following.\n */\nexport async function writeRuntimeFileSecure(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, { mode: 0o600, flag: \"wx\" })\n}\n\n/**\n * Sweep stale runtime tempfiles. Removes files whose embedded PID is no\n * longer a live process. A proxy crash (`kill -9`, OS reboot) leaves\n * orphans that would otherwise accumulate forever — and worse, a stale\n * config pointing at a now-recycled port could route MCP traffic to\n * whatever process bound that port next.\n *\n * Naming convention: `peer-mcp-<pid>.json` and `peer-agents-<pid>.json`.\n * Files not matching either pattern are left alone — this directory\n * is shared with future runtime artifacts.\n *\n * We deliberately do NOT age-prune files whose PID is alive. A\n * legitimately long-running proxy can have a tempfile older than any\n * arbitrary threshold; deleting it out from under the live process\n * breaks the spawned Claude Code child's MCP/agent wiring with no clean\n * recovery. PID-wraparound risk is mitigated by (a) PID reuse on Linux\n * being slow under typical loads, and (b) the file is only consulted by\n * github-router itself — an unrelated process that inherits the PID\n * never reads it.\n */\nexport async function sweepStaleRuntimeFiles(): Promise<void> {\n const dir = PATHS.CLAUDE_RUNTIME_DIR\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n\n for (const name of entries) {\n // Match both legacy `peer-mcp-<pid>.json` and current\n // `peer-mcp-<pid>-<rand>.json` filenames so we can clean up either.\n const match = /^peer-(?:mcp|agents)-(\\d+)(?:-[0-9a-f]+)?\\.json$/.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n const filePath = path.join(dir, name)\n\n if (isPidAlive(pid)) continue\n\n await fs.unlink(filePath).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\nfunction isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n // signal 0 = check existence without delivering a signal. EPERM\n // means the process exists but we can't signal it (which is still\n // \"alive\" for our purposes); ESRCH means it's gone.\n process.kill(pid, 0)\n return true\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"EPERM\") return true\n return false\n }\n}\n\n/**\n * Sweep stale peer-* subagent .md files from the router-owned\n * `CLAUDE_CONFIG_DIR/agents/`. Phase 2.5 writes one .md per peer agent\n * into Claude Code's agents directory (now our config dir's `agents/`\n * subdir, since `getClaudeCodeEnvVars` points `CLAUDE_CONFIG_DIR` at\n * `PATHS.CLAUDE_CONFIG_DIR`) so they appear in Claude Code's Task\n * `subagent_type` enum. Files are named `peer-<pid>-<rand>-<agentName>.md`\n * so this sweep can drop orphans from crashed prior proxy sessions\n * without touching the user's own .md files (which were copied into\n * the same dir during `ensureClaudeConfigMirror`).\n *\n * Same liveness rule as `sweepStaleRuntimeFiles`: only delete when the\n * file's embedded PID is no longer alive. Live PIDs keep their files —\n * a long-running proxy doesn't lose its agent registrations.\n *\n * Regex tightening (Phase 2.6, codex-critic + gemini-critic 2-lab finding):\n * the original sweep regex `^peer-(\\d+)(?:-[0-9a-f]+)?-.+\\.md$` was too\n * permissive — a user-authored `peer-12345-meeting-notes.md` matches\n * (`12345` = \"PID\", `-meeting-notes` = trailing `.+`) and would be\n * silently unlinked when 12345 happens to be a dead PID (overwhelmingly\n * likely). Tightened to require BOTH the 8-hex-char random suffix AND\n * an exact-match persona name suffix, eliminating the risk for any\n * realistic user filename.\n */\nexport async function sweepStalePeerAgentMdFiles(): Promise<void> {\n const dir = path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = PEER_AGENT_MD_FILENAME.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs.unlink(path.join(dir, name)).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\n/**\n * Strict regex matching only files this proxy writes:\n * peer-<pid>-<8 hex>-<exact persona/coordinator name>.md\n * The persona-name allowlist is the load-bearing protection against\n * deleting user files. Update this list whenever a new persona is added\n * to `PERSONAS_READ` / `PERSONAS_WRITE` in `peer-mcp-personas.ts` or a\n * new coordinator-style agent is added in `codex-mcp-config.ts`.\n */\nconst PEER_AGENT_MD_FILENAME =\n /^peer-(\\d+)-[0-9a-f]{8}-(?:codex-critic|codex-reviewer|gemini-critic|codex-implementer|peer-review-coordinator)\\.md$/\n\n/**\n * Strict regex matching only per-launch claude-config mirror dirs this\n * proxy creates: `<pid>-<8 hex>`. Anchored to the entire entry name so\n * user-authored siblings under `<appDir>/claude-config/` (if any) are\n * untouchable. The PID prefix is what `sweepStaleClaudeConfigMirrors`\n * keys off; the 8-hex random suffix matches `randomBytes(4)` exactly\n * (no `?` — files created by a different shape are not ours).\n */\nconst CLAUDE_CONFIG_MIRROR_DIR = /^(\\d+)-[0-9a-f]{8}$/\n\n/**\n * Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n * crashed prior proxy sessions. Symmetric to `sweepStalePeerAgentMdFiles`\n * — same liveness rule (only delete when the embedded PID is dead),\n * same strict regex (the dir-name allowlist is the load-bearing\n * protection against deleting user-authored siblings).\n *\n * Scans `<appDir>/claude-config/` (the parent of the per-launch dirs).\n * Each entry whose name matches `<pid>-<8 hex>` AND whose PID is no\n * longer alive is removed recursively. `fs.rm({recursive: true})`\n * walks the tree calling `unlink` on symlinks/junctions rather than\n * following them, so the SHARED junctions back to `~/.claude/<X>`\n * are removed without touching their targets.\n *\n * Tolerates missing parent dir (first-ever launch, or user wiped it).\n */\nexport async function sweepStaleClaudeConfigMirrors(): Promise<void> {\n const parent = path.join(appDir(), \"claude-config\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(parent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = CLAUDE_CONFIG_MIRROR_DIR.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs\n .rm(path.join(parent, name), { recursive: true, force: true })\n .catch((err) => {\n // Best-effort: stale-dir cleanup must never block startup.\n // Common failure modes (worth surviving silently): an EBUSY/EPERM\n // on Windows if a leftover handle is still open, or a stray\n // root-owned file inside the dir from a previous run with\n // different permissions.\n consola.debug(\n `sweepStaleClaudeConfigMirrors: cannot rm ${name}:`,\n err,\n )\n })\n }\n}\n\n/**\n * Remove THIS launch's per-launch CLAUDE_CONFIG_DIR on shutdown.\n * Best-effort: a failure here must not block process exit (the caller\n * wraps this in a `.catch`-equivalent via `launchChild`'s onShutdown\n * try/catch). Symmetric to `writePeerMcpRuntimeFiles`'s `cleanup()`:\n * we own this dir for the lifetime of the proxy, so removing it on\n * normal shutdown is correct; the boot-time sweep handles the\n * abnormal-exit case.\n *\n * `fs.rm({recursive: true})` removes SHARED junctions via unlink\n * (does NOT follow them into the user's real `~/.claude/<X>`).\n */\nexport async function removeOwnClaudeConfigMirror(): Promise<void> {\n const dir = PATHS.CLAUDE_CONFIG_DIR\n await fs.rm(dir, { recursive: true, force: true }).catch((err) => {\n consola.debug(`removeOwnClaudeConfigMirror: rm ${dir} skipped:`, err)\n })\n}\n","import { randomBytes, randomUUID } from \"node:crypto\"\n\nimport type { ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n accountType: string\n copilotApiUrl?: string\n models?: ModelsResponse\n vsCodeVersion?: string\n copilotVersion?: string\n\n manualApprove: boolean\n rateLimitWait: boolean\n showToken: boolean\n extendedBetas: boolean\n\n // Rate limiting configuration\n rateLimitSeconds?: number\n lastRequestTimestamp?: number\n\n // Persistent session identifiers to match VS Code fingerprint\n sessionId: string\n machineId: string\n\n /**\n * Per-launch nonce for the loopback `/mcp` endpoint. Set by the\n * `claude` subcommand after `setupAndServe` and before spawning\n * Claude Code; the spawned MCP client reads it from the\n * `--mcp-config` tempfile and presents it as `Authorization: Bearer`.\n * When unset, `/mcp` rejects all requests — closes the\n * loopback-no-auth gap (DNS rebinding, malicious browser-ext\n * native messaging, sibling-process probe).\n */\n peerMcpNonce?: string\n}\n\nexport const state: State = {\n accountType: \"enterprise\",\n manualApprove: false,\n rateLimitWait: false,\n showToken: false,\n extendedBetas: false,\n sessionId: randomUUID(),\n machineId: randomBytes(32).toString(\"hex\"),\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst DEFAULT_COPILOT_VERSION = \"0.43.2026033101\"\n\nexport function copilotVersion(state: State): string {\n return state.copilotVersion ?? DEFAULT_COPILOT_VERSION\n}\n\nconst API_VERSION = \"2026-01-09\"\n\nexport const copilotBaseUrl = (state: State) =>\n state.copilotApiUrl ?? \"https://api.githubcopilot.com\"\nexport const copilotHeaders = (\n state: State,\n vision: boolean = false,\n integrationId: string = \"vscode-chat\",\n) => {\n const version = copilotVersion(state)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": integrationId,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": `copilot-chat/${version}`,\n \"user-agent\": `GitHubCopilotChat/${version}`,\n \"openai-intent\": \"conversation-panel\",\n \"x-interaction-type\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n \"VScode-SessionId\": state.sessionId,\n \"VScode-MachineId\": state.machineId,\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL =\n process.env.GITHUB_API_URL ?? \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": `copilot-chat/${copilotVersion(state)}`,\n \"user-agent\": `GitHubCopilotChat/${copilotVersion(state)}`,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n consola.error(`Error occurred at ${c.req.path}:`, error)\n\n if (error instanceof HTTPError) {\n const errorText = await error.response.text().catch(() => \"\")\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = undefined\n }\n\n // Map upstream context-overflow errors (413, or 400 with a known\n // overflow substring) to Anthropic's \"prompt is too long\" 400 shape so\n // Claude Code triggers self-compaction instead of bubbling the error.\n // Note: a live probe of an oversized prompt against Copilot returned\n // 200 with stop_reason:\"refusal\" rather than 413/400 — this guard is\n // defensive for the documented Anthropic contract, not load-bearing.\n if (isContextOverflow(error.response.status, errorJson, errorText)) {\n const upstream = resolveErrorMessage(errorJson, errorText)\n consola.error(\"HTTP error (mapped to overflow):\", errorJson ?? errorText)\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: `prompt is too long: ${upstream}`,\n },\n },\n 400,\n )\n }\n\n // Remap upstream 401 to 503 — maintain the no-401 invariant on the\n // Anthropic-shape boundary. Claude Code's reactive refresh path\n // (function `SZ1` → `D3(0,true,...)` in v2.1.140 binary) fires on\n // any 401 from upstream and attempts to refresh the OAuth token.\n // Spawned-via-proxy sessions use a synthetic credential\n // (`ensureClaudeConfigMirror`'s SYNTHETIC_CREDENTIAL); refreshing\n // it would fail and degrade the session. Mapping 401 → 503 lets\n // the upstream message still reach the user while side-stepping\n // the refresh path. 503 maps to Anthropic's \"overloaded_error\"\n // type — semantically reasonable for \"proxy got an upstream\n // failure, retry later\".\n const responseStatus =\n error.response.status === 401 ? 503 : error.response.status\n\n // Forward upstream Anthropic-format errors as-is (with remapped status)\n if (isAnthropicError(errorJson)) {\n consola.error(\"HTTP error:\", errorJson)\n return c.json(errorJson, responseStatus as ContentfulStatusCode)\n }\n\n const message = resolveErrorMessage(errorJson, errorText)\n consola.error(\"HTTP error:\", errorJson ?? errorText)\n return c.json(\n {\n type: \"error\",\n error: {\n type: resolveErrorType(responseStatus),\n message,\n },\n },\n responseStatus as ContentfulStatusCode,\n )\n }\n\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"api_error\",\n message: error instanceof Error ? error.message : String(error),\n },\n },\n 500,\n )\n}\n\n// Extracts error message from { message } or { error: { message } } payloads.\nfunction resolveErrorMessage(errorJson: unknown, fallback: string): string {\n if (typeof errorJson !== \"object\" || errorJson === null) return fallback\n\n const errorRecord = errorJson as Record<string, unknown>\n if (errorRecord.message !== undefined) return String(errorRecord.message)\n\n if (typeof errorRecord.error === \"object\" && errorRecord.error !== null) {\n const nestedRecord = errorRecord.error as Record<string, unknown>\n if (nestedRecord.message !== undefined) return String(nestedRecord.message)\n }\n\n return fallback\n}\n\n/**\n * Check if a parsed JSON body is already in Anthropic error format:\n * { type: \"error\", error: { type: \"...\", message: \"...\" } }\n */\nfunction isAnthropicError(json: unknown): boolean {\n if (typeof json !== \"object\" || json === null) return false\n const record = json as Record<string, unknown>\n if (record.type !== \"error\") return false\n if (typeof record.error !== \"object\" || record.error === null) return false\n const inner = record.error as Record<string, unknown>\n return typeof inner.type === \"string\" && typeof inner.message === \"string\"\n}\n\nconst CONTEXT_OVERFLOW_SUBSTRINGS = [\n \"prompt is too long\",\n \"context_length_exceeded\",\n \"context length exceeded\",\n \"input is too long\",\n \"maximum context length\",\n \"too many tokens\",\n]\n\n/**\n * Detect upstream context-overflow errors so we can remap them to a 400\n * \"prompt is too long\" shape that triggers Claude Code self-compaction.\n *\n * Always remaps 413 (treated as a hard payload-size signal regardless of\n * body wording). Remaps 400 only when the error text contains one of the\n * known overflow substrings — a regular 400 (e.g. \"model not found\") must\n * NOT remap.\n */\nexport function isContextOverflow(\n status: number,\n errorJson: unknown,\n errorText: string,\n): boolean {\n if (status === 413) return true\n if (status !== 400) return false\n\n const haystack = (\n errorText +\n \" \" +\n (typeof errorJson === \"object\" && errorJson !== null\n ? JSON.stringify(errorJson)\n : \"\")\n ).toLowerCase()\n\n return CONTEXT_OVERFLOW_SUBSTRINGS.some((s) => haystack.includes(s))\n}\n\n/**\n * Map HTTP status to Anthropic error type.\n *\n * Note: a 401 from upstream is remapped to 503 in `forwardError` BEFORE\n * this function is called (no-401 invariant — see comment there). The\n * 401 → \"authentication_error\" mapping below is preserved for\n * defensive coverage in case any code path calls `resolveErrorType`\n * directly with an unsanitized status.\n */\nfunction resolveErrorType(status: number): string {\n if (status === 400) return \"invalid_request_error\"\n if (status === 401) return \"authentication_error\"\n if (status === 403) return \"permission_error\"\n if (status === 404) return \"not_found_error\"\n if (status === 429) return \"rate_limit_error\"\n if (status === 503) return \"overloaded_error\"\n if (status === 529) return \"overloaded_error\"\n return \"api_error\"\n}\n","import consola from \"consola\"\n\nimport { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Allowlist of hosts the router will trust as the Copilot API base URL.\n * Anything else returned in `endpoints.api` (e.g. via a tampered or\n * misconfigured token-exchange response) is rejected — otherwise a\n * malicious value would receive the long-lived GitHub PAT we send to\n * `/mcp` for web search (see `src/services/copilot/web-search.ts`).\n */\nconst COPILOT_HOST_ALLOWLIST = [\n \"api.githubcopilot.com\",\n \"api.individual.githubcopilot.com\",\n \"api.business.githubcopilot.com\",\n \"api.enterprise.githubcopilot.com\",\n]\n\nfunction isAllowedCopilotHost(rawUrl: string): boolean {\n let parsed: URL\n try {\n parsed = new URL(rawUrl)\n } catch {\n return false\n }\n if (parsed.protocol !== \"https:\") return false\n return COPILOT_HOST_ALLOWLIST.includes(parsed.hostname)\n}\n\nexport const getCopilotToken = async () => {\n const response = await fetch(\n `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`,\n {\n headers: githubHeaders(state),\n },\n )\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n const data = (await response.json()) as GetCopilotTokenResponse\n\n // Use the API base URL from the token response if available, matching\n // how VS Code determines the CAPI endpoint dynamically — but only when\n // it points at a github-controlled host (see allowlist above).\n // We deliberately do NOT clobber an existing `state.copilotApiUrl` in\n // the disallowed branch: when the user sets `COPILOT_API_URL` themselves\n // (e.g. for local testing or a CI mock), that's an explicit opt-in and\n // a different threat model than a tampered token-exchange response.\n // Allowlist-failing token-response values are simply ignored.\n if (data.endpoints?.api) {\n if (isAllowedCopilotHost(data.endpoints.api)) {\n state.copilotApiUrl = data.endpoints.api\n } else {\n consola.warn(\n `Refusing to honor Copilot API endpoint \"${data.endpoints.api}\" from ` +\n `the token-exchange response — not in allowlist ` +\n `(${COPILOT_HOST_ALLOWLIST.join(\", \")}). ` +\n (state.copilotApiUrl\n ? `Keeping existing override \"${state.copilotApiUrl}\".`\n : `Falling back to the default api.githubcopilot.com.`),\n )\n }\n }\n\n return data\n}\n\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n endpoints?: {\n api?: string\n proxy?: string\n telemetry?: string\n \"origin-tracker\"?: string\n }\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise<DeviceCodeResponse> {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n max_non_streaming_output_tokens?: number\n vision?: {\n max_prompt_image_size?: number\n max_prompt_images?: number\n supported_media_types?: string[]\n }\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n streaming?: boolean\n vision?: boolean\n structured_outputs?: boolean\n adaptive_thinking?: boolean\n max_thinking_budget?: number\n min_thinking_budget?: number\n reasoning_effort?: Array<string>\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n supported_endpoints?: Array<string>\n requestHeaders?: Record<string, string>\n policy?: {\n state: string\n terms: string\n }\n billing?: {\n is_premium: boolean\n multiplier: number\n restricted_to?: string[]\n }\n is_chat_default?: boolean\n is_chat_fallback?: boolean\n model_picker_category?: string\n info_messages?: Array<{ code: string; message: string }>\n}\n","const FALLBACK = \"0.43.2026033101\"\n\ninterface MarketplaceResult {\n results: Array<{\n extensions: Array<{\n versions: Array<{ version: string }>\n }>\n }>\n}\n\nexport async function getCopilotChatVersion(): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json;api-version=7.1-preview.1\",\n },\n body: JSON.stringify({\n filters: [\n {\n criteria: [{ filterType: 7, value: \"GitHub.copilot-chat\" }],\n },\n ],\n flags: 914,\n }),\n signal: controller.signal,\n },\n )\n\n if (!response.ok) return FALLBACK\n\n const data = (await response.json()) as MarketplaceResult\n const version =\n data?.results?.[0]?.extensions?.[0]?.versions?.[0]?.version\n\n return version ?? FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getCopilotChatVersion } from \"~/services/get-copilot-version\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\n/**\n * Beta prefixes VS Code Copilot Chat v0.43 actually sends.\n * Default mode — makes proxy traffic indistinguishable from VS Code.\n */\nconst VSCODE_BETA_PREFIXES = [\n \"interleaved-thinking-\",\n \"context-management-\",\n \"advanced-tool-use-\",\n]\n\n/**\n * Extended beta prefixes for Claude CLI compatibility.\n * Enabled via --extended-betas flag. Includes all betas confirmed\n * to work with the Copilot API.\n *\n * Notably absent (Copilot 400s on these — verified live):\n * context-1m-, skills-, files-api-, code-execution-, output-128k-,\n * advisor-tool- (see EXPLICITLY_STRIPPED_BETA_PREFIXES below).\n * 1M context is unlocked by selecting `claude-opus-4.7-1m-internal`\n * as the model id, not via a beta header.\n *\n * Empirical verification (2026-05-11 against api.enterprise.githubcopilot.com):\n * task-budgets-2026-03-13 → 200 ACCEPTED (cost-ceiling leverage)\n * token-efficient-tools-2026-03-28 → 200 ACCEPTED (per-tool token saving)\n * summarize-connector-text-2026-03-13 → 200 (Anthropic-internal feature flag,\n * won't fire for non-ant users; allowlisted defensively for ant edge case)\n * afk-mode-2026-01-31 → 200 (Anthropic-internal feature flag)\n * cli-internal-2026-02-09 → 200 (USER_TYPE=ant only)\n * oauth-2025-04-20 → 200 (Files-API path; Files-API itself\n * is not supportable via Copilot, but the header alone is harmless)\n * prompt-caching-scope-2026-01-05 → 200 even with body cache_control.scope\n * stripped (already covered by `prompt-caching-` prefix above)\n */\nconst EXTENDED_BETA_PREFIXES = [\n ...VSCODE_BETA_PREFIXES,\n \"claude-code-\",\n \"effort-\",\n \"prompt-caching-\",\n \"computer-use-\",\n \"pdfs-\",\n \"max-tokens-\",\n \"token-counting-\",\n \"compact-\",\n \"structured-outputs-\",\n \"fast-mode-\",\n \"mcp-client-\",\n \"mcp-servers-\",\n \"redact-thinking-\",\n \"web-search-\",\n // Empirically accepted by Copilot, sent by Claude Code v2.1.138+\n \"task-budgets-\",\n \"token-efficient-tools-\",\n // Anthropic-internal feature flags (won't reach proxy from non-ant users\n // due to Bun build-time dead-code elimination, but allowlisted so the rare\n // ant-user / managed-deployment case flows cleanly).\n \"summarize-connector-text-\",\n \"afk-mode-\",\n \"cli-internal-\",\n \"oauth-\",\n]\n\n/**\n * Beta prefixes the proxy explicitly STRIPS even from the extended\n * allowlist (and even if a future leverage mode broadens the allowlist\n * further). Defensive layer: today's allowlist-only filter would already\n * drop these because they're not in any allowlist, but keeping an\n * explicit deny-list catches future changes that broaden allow rules\n * (e.g. a hypothetical pattern-based mode that lets `claude-*` through).\n *\n * Empirical (2026-05-11): Copilot returns HTTP 400\n * `unsupported beta header(s): advisor-tool-2026-03-01`\n * on every request that includes `advisor-tool-`. Stripping it is the\n * difference between a working request (no ADVISOR semantics) and a\n * fully-failed request. Document upstream limitation in CLAUDE.md.\n */\nconst EXPLICITLY_STRIPPED_BETA_PREFIXES = [\n \"advisor-tool-\",\n] as const\n\n/**\n * Filter an `anthropic-beta` header value, keeping only beta flags\n * in the active whitelist AND not in the explicit-strip list.\n * Uses extended prefixes when --extended-betas is enabled, VS Code-only\n * prefixes otherwise. Returns the filtered comma-separated string,\n * or undefined if nothing remains.\n */\nexport function filterBetaHeader(value: string): string | undefined {\n const prefixes = state.extendedBetas\n ? EXTENDED_BETA_PREFIXES\n : VSCODE_BETA_PREFIXES\n const filtered = value\n .split(\",\")\n .map((v) => v.trim())\n .filter(\n (v) =>\n v\n && prefixes.some((prefix) => v.startsWith(prefix))\n && !EXPLICITLY_STRIPPED_BETA_PREFIXES.some((p) => v.startsWith(p)),\n )\n .join(\",\")\n return filtered || undefined\n}\n\n/**\n * Normalize a model ID for fuzzy comparison: lowercase, replace dots with\n * dashes, insert dash at letter→digit boundaries, and collapse repeated\n * dashes. E.g. \"gpt5.3-codex\" → \"gpt-5-3-codex\", \"GPT-5.3-Codex\" → \"gpt-5-3-codex\".\n */\nexport function normalizeModelId(id: string): string {\n return id\n .toLowerCase()\n .replace(/\\./g, \"-\")\n .replace(/([a-z])(\\d)/g, \"$1-$2\")\n .replace(/-{2,}/g, \"-\")\n}\n\n/**\n * Resolve a model name to the best available variant in the Copilot model list.\n *\n * Resolution cascade:\n * 0. `[1m]` literal-bracket suffix: strip, delegate, warn if downgraded.\n * Bracketed slug must never reach Copilot (400s on it). See cc-backup\n * `src/utils/context.ts:35-40` for Claude Code's 1M unlock mechanism.\n * 1. Exact match\n * 2. Case-insensitive match\n * 3. Family preference (opus→1m, codex→highest version)\n * 4. Normalized match (dots→dashes, letter-digit boundaries)\n * 5. Anthropic dated-slug retry: if the input matches `claude-...-YYYYMMDD`,\n * strip the date and re-run the cascade once. Family-guarded so non-claude\n * 8-digit suffixes can't be mis-stripped; runs after Steps 1-4 so explicit\n * version pinning (a dated catalog id matched at Step 1) always wins.\n * 6. Return as-is with a warning\n */\nexport function resolveModel(modelId: string): string {\n const models = state.models?.data\n if (!models) return modelId\n\n // [1m] literal-bracket suffix: Claude Code's request for 1M context\n // accounting. cc-backup `src/utils/context.ts:35-40` has1mContext\n // matches `/\\[1m\\]/i`; getContextWindowForModel returns 1_000_000\n // when true. parseUserSpecifiedModel (model.ts:445-506) reattaches\n // the bracket after alias resolution, so Claude Code SENDS the\n // bracketed slug verbatim on the wire (`model: \"claude-opus-4-7[1m]\"`).\n // Copilot doesn't recognize the bracket → 400.\n //\n // Strip for the catalog lookup and delegate. If the stripped\n // resolution lands on a `-1m` variant (enterprise opus path via\n // family preference), perfect — the upstream call routes to the 1M\n // backend and Claude Code's local accounting was right. Otherwise\n // (non-enterprise for opus, or any [1m] on sonnet/haiku where\n // Copilot has no -1m backend), warn and return the 200K resolution\n // so the request still succeeds — at the cost of Claude Code\n // over-accounting context against the proxy (it will compact early\n // because it thinks the window is 1M).\n //\n // Bounded recursion: the stripped form no longer matches the regex,\n // so the inner resolveModel call cannot re-enter this branch.\n const oneMMatch = modelId.match(/^(.*)\\[1m\\]$/i)\n if (oneMMatch) {\n const stripped = oneMMatch[1]\n const resolved = resolveModel(stripped)\n if (!/-1m(?:$|-)/.test(resolved)) {\n consola.warn(\n `Model \"${modelId}\" requested 1M context but no -1m backend is in Copilot's catalog for this tier/family; downgrading upstream to \"${resolved}\" (200K). Claude Code's local context accounting will still assume 1M — expect premature auto-compact. Drop the [1m] suffix (or unset CLAUDE_CODE_DISABLE_1M_CONTEXT if you set it) to silence.`,\n )\n }\n return resolved\n }\n\n // 1. Exact match\n if (models.some((m) => m.id === modelId)) return modelId\n\n // 2. Case-insensitive match\n const lower = modelId.toLowerCase()\n const ciMatch = models.find((m) => m.id.toLowerCase() === lower)\n if (ciMatch) return ciMatch.id\n\n // 3. Family preference — before normalization so product aliases\n // (opus→1m, codex→latest) take priority over fuzzy matches\n if (lower.includes(\"opus\")) {\n // Match ...-1m or ...-1m-<anything> (e.g. claude-opus-4.7-1m-internal).\n // Prefer the 1M variant whose major.minor matches the requested version,\n // otherwise find() would silently downgrade claude-opus-4.7 to a\n // claude-opus-4.6-1m if the latter happens to come first in the list.\n // Accept both dotted (\"opus-4.7\") and dashed (\"opus-4-7\") inputs —\n // Claude Code historically sends the dashed form.\n const oneMs = models.filter(\n (m) => m.id.includes(\"opus\") && /-1m(?:$|-)/.test(m.id),\n )\n const versionMatch = lower.match(/opus-(\\d+)[.-](\\d+)/)\n const requestedVersion =\n versionMatch ? `${versionMatch[1]}.${versionMatch[2]}` : undefined\n const preferred = requestedVersion\n ? oneMs.find((m) => m.id.includes(`opus-${requestedVersion}-`))\n : undefined\n const oneM = preferred ?? oneMs[0]\n if (oneM) return oneM.id\n }\n\n if (lower.includes(\"codex\")) {\n const codexModels = models.filter(\n (m) => m.id.includes(\"codex\") && !m.id.includes(\"mini\"),\n )\n if (codexModels.length > 0) {\n codexModels.sort((a, b) => b.id.localeCompare(a.id))\n return codexModels[0].id\n }\n }\n\n // 4. Normalized match (dots → dashes, letter-digit boundaries)\n const normalized = normalizeModelId(modelId)\n const normMatch = models.find(\n (m) => normalizeModelId(m.id) === normalized,\n )\n if (normMatch) return normMatch.id\n\n // 5. Anthropic dated-slug retry. Claude Code's /model UI ships Anthropic's\n // published slugs (e.g. \"claude-haiku-4-5-20251001\") that carry a\n // -YYYYMMDD suffix Copilot's catalog doesn't use. Strip the date and\n // re-run the cascade once so the request maps to the floating tag\n // (claude-haiku-4.5). Family-guarded to `claude-` so a hypothetical\n // \"gpt-...-20260101\" can't be silently stripped. Bounded recursion:\n // the stripped id no longer matches the regex, so the retry's own\n // Step 5 is a no-op.\n const dateStripped = modelId.replace(/^(claude-[\\w.-]+)-20\\d{6}$/i, \"$1\")\n if (dateStripped !== modelId) {\n const retried = resolveModel(dateStripped)\n // resolveModel returns the input unchanged on miss; treat unchanged-and-\n // not-in-catalog as miss to avoid logging a misleading \"resolved\" hop.\n const retryHit =\n retried !== dateStripped || models.some((m) => m.id === dateStripped)\n if (retryHit) {\n consola.info(\n `Resolved Anthropic dated slug \"${modelId}\" → \"${retried}\" (stripped -YYYYMMDD; pass an explicit catalog id to pin a snapshot)`,\n )\n return retried\n }\n }\n\n // 6. Legacy family fallback. Claude Code's settings.json may pin slugs\n // whose Copilot equivalent does not exist (e.g. claude-3-7-sonnet-20250219\n // or claude-sonnet-4-0 — neither is in Copilot's enterprise catalog as\n // of 2026-05-11; a request for either returns HTTP 400 \"model not\n // supported\"). Step 5's dated-retry strips the date but the resulting\n // \"claude-3-7-sonnet\" still has no Copilot equivalent. Rather than\n // dead-end the request, fall back to the highest available family\n // member (sonnet → highest sonnet, haiku → highest haiku). Surfaces\n // via consola.info so the user sees the substitution. Opus is already\n // handled by the family preference in Step 3.\n //\n // Guards (codex-reviewer findings):\n // (a) Family fires only for `claude-` prefixed inputs — protects\n // against custom-sonnet-future or any non-Anthropic provider\n // coincidentally containing \"sonnet\"/\"haiku\" in its slug.\n // (b) Family token must be word-bounded (`-sonnet-` / `-sonnet$`)\n // so a hypothetical claude-supersonnet-* doesn't match.\n // (c) Sort uses numeric collation (`{numeric: true}`) so a future\n // claude-sonnet-4.10 sorts higher than claude-sonnet-4.6\n // (lexicographic alone would invert).\n if (lower.startsWith(\"claude-\")) {\n const matchSonnet = /(?:^|-)sonnet(?:-|$)/.test(lower)\n const matchHaiku = /(?:^|-)haiku(?:-|$)/.test(lower)\n if (matchSonnet || matchHaiku) {\n const family = matchSonnet ? \"sonnet\" : \"haiku\"\n const familyMembers = models.filter((m) =>\n new RegExp(`(?:^|-)${family}(?:-|$|\\\\.)`).test(m.id),\n )\n if (familyMembers.length > 0) {\n familyMembers.sort((a, b) =>\n b.id.localeCompare(a.id, undefined, { numeric: true }),\n )\n const best = familyMembers[0].id\n consola.info(\n `Model \"${modelId}\" not in Copilot catalog; falling back to highest available \"${best}\" (legacy ${family} slug). Pin a current catalog id to silence.`,\n )\n return best\n }\n }\n }\n\n // 7. No match — warn and return as-is\n consola.warn(\n `Model \"${modelId}\" not found in Copilot model list. Available: ${models.map((m) => m.id).join(\", \")}`,\n )\n return modelId\n}\n\n/**\n * Resolve a codex model ID, falling back to the best available codex model.\n * Used by the codex subcommand for model selection.\n */\nexport function resolveCodexModel(modelId: string): string {\n const resolved = resolveModel(modelId)\n const models = state.models?.data\n if (!models) return resolved\n\n // Check if the resolved model exists in the model list\n if (models.some((m) => m.id === resolved)) return resolved\n\n // Fall back to the best available codex-class model. The /responses\n // endpoint is the discriminator — gpt-5.5 dropped the -codex suffix but\n // still routes through /responses. Prefer explicit -codex ids when both\n // exist, otherwise pick the highest version-like id.\n const candidates = models.filter((m) => {\n const endpoints = m.supported_endpoints ?? []\n if (m.id.includes(\"mini\") || m.id.includes(\"nano\")) return false\n return endpoints.length === 0 || endpoints.includes(\"/responses\")\n })\n\n if (candidates.length > 0) {\n candidates.sort((a, b) => {\n const aCodex = a.id.includes(\"codex\") ? 1 : 0\n const bCodex = b.id.includes(\"codex\") ? 1 : 0\n if (aCodex !== bCodex) return bCodex - aCodex\n return b.id.localeCompare(a.id)\n })\n const best = candidates[0].id\n consola.warn(`Model \"${modelId}\" not available, using \"${best}\" instead`)\n return best\n }\n\n return resolved\n}\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\nexport const cacheCopilotVersion = async () => {\n const version = await getCopilotChatVersion()\n state.copilotVersion = version\n\n consola.info(`Using Copilot Chat version: ${version}`)\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n const expiresAt = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expiresAt) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n consola.error(\"Failed to poll access token:\", await response.text())\n if (Date.now() >= expiresAt) break\n await sleep(sleepDuration)\n continue\n }\n\n const json = await response.json()\n consola.debug(\"Polling access token response:\", json)\n\n const { access_token } = json as AccessTokenResponse\n\n if (access_token) {\n return access_token\n }\n\n if (Date.now() >= expiresAt) break\n await sleep(sleepDuration)\n }\n\n throw new Error(\"Device code expired. Please run auth again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = Math.max((refresh_in - 60) * 1000, 1000)\n setInterval(() => {\n void refreshCopilotToken(\"interval\")\n }, refreshInterval)\n}\n\n// Single-flight mutex around the refresh fetch. Concurrent triggers (interval\n// + a 401-retry path) share one in-flight refresh promise so we never\n// overlap network calls or race writes to state.copilotToken.\nlet inflightRefresh: Promise<void> | undefined\n// Cooldowns are keyed off the OUTCOME of the last refresh, not the attempt:\n// - lastRefreshSuccess: throttles 401-retries when the token is fresh\n// (don't pointlessly re-fetch a token we just got).\n// - lastRefreshFailure: shorter backoff so a transient upstream blip\n// doesn't suppress legitimate refresh attempts for a full 30s, but\n// still prevents a thundering-herd refresh-storm against an upstream\n// that's persistently failing.\nlet lastRefreshSuccess = 0\nlet lastRefreshFailure = 0\nconst REFRESH_SUCCESS_COOLDOWN_MS = 30_000\nconst REFRESH_FAILURE_COOLDOWN_MS = 5_000\n\nexport async function refreshCopilotToken(\n reason: \"interval\" | \"401-retry\",\n): Promise<void> {\n if (inflightRefresh) return inflightRefresh\n // Refresh-storm protection: if a recent refresh already completed,\n // decline new 401-retry attempts. Interval refreshes always proceed\n // (they're spaced by `refresh_in - 60s` which is well outside the\n // window). 401-retry attempts respect both cooldowns:\n // - skip if a refresh succeeded within the last 30s (token is fresh)\n // - skip if a refresh failed within the last 5s (back off briefly)\n if (reason === \"401-retry\") {\n const now = Date.now()\n if (now - lastRefreshSuccess < REFRESH_SUCCESS_COOLDOWN_MS) {\n consola.debug(\n `refreshCopilotToken(${reason}) skipped: prior success within ${REFRESH_SUCCESS_COOLDOWN_MS}ms`,\n )\n return\n }\n if (now - lastRefreshFailure < REFRESH_FAILURE_COOLDOWN_MS) {\n consola.debug(\n `refreshCopilotToken(${reason}) skipped: prior failure within ${REFRESH_FAILURE_COOLDOWN_MS}ms`,\n )\n return\n }\n }\n\n inflightRefresh = (async () => {\n consola.debug(`Refreshing Copilot token (reason=${reason})`)\n try {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n lastRefreshSuccess = Date.now()\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n } catch (error) {\n lastRefreshFailure = Date.now()\n consola.error(\n `Failed to refresh Copilot token (reason=${reason}):`,\n error,\n )\n } finally {\n inflightRefresh = undefined\n }\n })()\n return inflightRefresh\n}\n\n/**\n * Try `request()`. If it returns a 401, refresh the Copilot token (subject\n * to the single-flight + refresh-storm-protection of `refreshCopilotToken`)\n * and retry once. After one retry, propagate whatever the second attempt\n * returned — the caller's existing 401-handling path is preserved.\n *\n * The `request` callback is responsible for capturing `state.copilotToken`\n * locally before any await; this helper does NOT re-build the request\n * itself, just re-invokes the callback after a refresh.\n */\nexport async function tryRefreshAndRetry(\n request: () => Promise<Response>,\n routePath: string,\n): Promise<Response> {\n const first = await request()\n if (first.status !== 401) return first\n\n consola.warn(\n `${routePath}: upstream returned 401, attempting one token refresh + retry`,\n )\n await refreshCopilotToken(\"401-retry\")\n // Re-invoke the request with the (possibly) new token in state.\n return request()\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise<void> {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n await logUser()\n\n return\n }\n\n consola.info(\"Not logged in, getting new access token\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/paths\"\nimport { state } from \"./lib/state\"\nimport { setupGitHubToken } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showToken = options.showToken\n\n await ensurePaths()\n await setupGitHubToken({ force: true })\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showToken: args[\"show-token\"],\n })\n },\n})\n","import { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotUsage = async (): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/paths\"\nimport { setupGitHubToken } from \"./lib/token\"\nimport {\n getCopilotUsage,\n type QuotaDetail,\n} from \"./services/github/get-copilot-usage\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n await setupGitHubToken()\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed =\n premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\n \"Completions\",\n usage.quota_snapshots.completions,\n )\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n","import { execFile, execFileSync } from \"node:child_process\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport { promisify } from \"node:util\"\n\nimport consola from \"consola\"\n\nconst execFileAsync = promisify(execFile)\n\nconst NPM_PACKAGE = \"@anthropic-ai/claude-code\"\nconst THROTTLE_HOURS = 1\nconst NPM_VIEW_TIMEOUT_MS = 5000\nconst NPM_INSTALL_TIMEOUT_MS = 120_000 // 2 min — npm install can be slow on cold caches\n\ninterface VersionCheckCache {\n /** ISO timestamp of last check */\n checkedAt: string\n /** Installed version at last check */\n installedVersion: string | null\n /** Latest version on npm at last check */\n latestVersion: string | null\n}\n\n/** Path to the throttle cache. Created on demand. */\nfunction cacheFilePath(): string {\n return path.join(\n os.homedir(),\n \".local\",\n \"share\",\n \"github-router\",\n \"last-update-check\",\n )\n}\n\n/**\n * Read the throttle cache. Returns null on missing/corrupt file —\n * triggers a fresh check.\n */\nasync function readCache(): Promise<VersionCheckCache | null> {\n try {\n const raw = await fs.readFile(cacheFilePath(), \"utf8\")\n const parsed = JSON.parse(raw) as VersionCheckCache\n if (\n typeof parsed.checkedAt !== \"string\"\n || (parsed.installedVersion !== null\n && typeof parsed.installedVersion !== \"string\")\n || (parsed.latestVersion !== null\n && typeof parsed.latestVersion !== \"string\")\n ) {\n return null\n }\n return parsed\n } catch {\n return null\n }\n}\n\nasync function writeCache(cache: VersionCheckCache): Promise<void> {\n try {\n await fs.mkdir(path.dirname(cacheFilePath()), { recursive: true })\n await fs.writeFile(cacheFilePath(), JSON.stringify(cache), {\n mode: 0o600,\n })\n } catch (err) {\n // Throttle cache is best-effort — a write failure means we'll re-check\n // on next launch. Not worth surfacing.\n consola.debug(\"Failed to write claude version-check cache:\", err)\n }\n}\n\n/** Check if it's been more than THROTTLE_HOURS since the last check. */\nfunction shouldCheckNow(cache: VersionCheckCache | null): boolean {\n if (!cache) return true\n const lastCheck = new Date(cache.checkedAt).getTime()\n if (Number.isNaN(lastCheck)) return true\n const hoursSince = (Date.now() - lastCheck) / 1000 / 3600\n return hoursSince >= THROTTLE_HOURS\n}\n\n/**\n * Read the installed `claude` version. Returns null if claude is not\n * on PATH or the version probe fails (e.g. older versions that don't\n * support `--version` cleanly).\n */\nfunction getInstalledVersion(): string | null {\n try {\n const out = execFileSync(\"claude\", [\"--version\"], {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3000,\n encoding: \"utf8\",\n })\n // Output shape: \"2.1.139 (Claude Code)\\n\"\n const match = out.match(/^(\\d+\\.\\d+\\.\\d+)/)\n return match ? match[1] : null\n } catch {\n return null\n }\n}\n\n/**\n * Fetch the latest version of @anthropic-ai/claude-code from the npm\n * registry. Returns null on network failure / npm unavailable.\n */\nasync function getLatestVersion(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync(\n \"npm\",\n [\"view\", NPM_PACKAGE, \"version\", \"--silent\"],\n { timeout: NPM_VIEW_TIMEOUT_MS },\n )\n const v = stdout.trim()\n return /^\\d+\\.\\d+\\.\\d+/.test(v) ? v : null\n } catch {\n return null\n }\n}\n\n/**\n * Compare two semver-shaped strings (only the leading X.Y.Z, no\n * pre-release / metadata handling — sufficient for npm-published\n * stable releases). Returns true if `latest` is strictly higher than\n * `installed`.\n */\nfunction isNewer(installed: string | null, latest: string | null): boolean {\n if (!installed || !latest) return false\n const a = installed.split(\".\").map((n) => parseInt(n, 10))\n const b = latest.split(\".\").map((n) => parseInt(n, 10))\n for (let i = 0; i < 3; i++) {\n const av = a[i] ?? 0\n const bv = b[i] ?? 0\n if (av < bv) return true\n if (av > bv) return false\n }\n return false\n}\n\nexport interface VersionCheckResult {\n /** Whether claude is on PATH at all */\n installed: boolean\n installedVersion: string | null\n latestVersion: string | null\n /** True if a newer version is available */\n needsUpdate: boolean\n /** Whether the check was skipped (throttled or disabled) */\n skipped: boolean\n skipReason?: \"throttled\" | \"disabled\" | \"no-npm\" | \"no-claude\"\n}\n\n/**\n * Run a version check (subject to throttle). Side-effect: updates the\n * throttle cache. Returns the comparison result.\n */\nexport async function checkClaudeVersion(opts: {\n noCheck?: boolean\n /** Bypass the throttle (used when the check is the user's main intent) */\n force?: boolean\n} = {}): Promise<VersionCheckResult> {\n if (opts.noCheck) {\n return {\n installed: false,\n installedVersion: null,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"disabled\",\n }\n }\n\n const cache = await readCache()\n if (!opts.force && !shouldCheckNow(cache)) {\n return {\n installed: cache?.installedVersion !== null,\n installedVersion: cache?.installedVersion ?? null,\n latestVersion: cache?.latestVersion ?? null,\n needsUpdate: isNewer(\n cache?.installedVersion ?? null,\n cache?.latestVersion ?? null,\n ),\n skipped: true,\n skipReason: \"throttled\",\n }\n }\n\n const installedVersion = getInstalledVersion()\n if (installedVersion === null) {\n return {\n installed: false,\n installedVersion: null,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"no-claude\",\n }\n }\n\n const latestVersion = await getLatestVersion()\n // Update cache regardless of whether latest fetched (so we still\n // throttle if npm is offline).\n await writeCache({\n checkedAt: new Date().toISOString(),\n installedVersion,\n latestVersion,\n })\n\n if (latestVersion === null) {\n return {\n installed: true,\n installedVersion,\n latestVersion: null,\n needsUpdate: false,\n skipped: true,\n skipReason: \"no-npm\",\n }\n }\n\n return {\n installed: true,\n installedVersion,\n latestVersion,\n needsUpdate: isNewer(installedVersion, latestVersion),\n skipped: false,\n }\n}\n\n/**\n * Run `npm install -g @anthropic-ai/claude-code@latest` synchronously.\n * Throws on failure — the caller decides whether to abort the launch\n * or continue with the older version.\n */\nexport async function autoUpdateClaude(latestVersion: string): Promise<void> {\n consola.info(\n `Updating ${NPM_PACKAGE} to ${latestVersion} (this may take ~30s)...`,\n )\n try {\n await execFileAsync(\n \"npm\",\n [\"install\", \"-g\", `${NPM_PACKAGE}@latest`, \"--silent\"],\n { timeout: NPM_INSTALL_TIMEOUT_MS },\n )\n consola.success(`${NPM_PACKAGE} updated to ${latestVersion}`)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`npm install failed: ${msg}`)\n }\n}\n","import consola from \"consola\"\n\nimport { state } from \"./state\"\n\nexport const DEFAULT_PORT = 8787\n\n/**\n * Default model for `github-router claude`. The Anthropic-published dashed\n * slug (`claude-opus-4-7`) — NOT the Copilot-internal slug\n * (`claude-opus-4.7-1m-internal`) — because Claude Code 2.1.126's `/model`\n * UI is backed by a hardcoded registry of Anthropic slugs, and an\n * unrecognized slug causes the menu to highlight \"Opus 4\" with a\n * \"Newer version available\" hint instead of \"Opus 4.7 (1M context)\".\n *\n * The proxy's `resolveModel` (`src/lib/utils.ts`) translates this to\n * Copilot's `claude-opus-4.7-1m-internal` (enterprise) or\n * `claude-opus-4.7` (Pro+/Business/Max) at request time via the\n * family-preference + version-match branch — round-trip covered by\n * `tests/lib-utils.test.ts:154`.\n *\n * `DEFAULT_CLAUDE_MODEL_FALLBACKS` covers major.minor regressions only;\n * 1M↔200K downgrade is handled inside the resolver, so we don't need\n * separate `-1m` entries here.\n */\nexport const DEFAULT_CLAUDE_MODEL = \"claude-opus-4-7\"\nexport const DEFAULT_CLAUDE_MODEL_FALLBACKS = [\n \"claude-opus-4-6\",\n \"claude-opus-4-5\",\n] as const\n\n/**\n * Cap-aware default picker for `ANTHROPIC_MODEL` on the implicit-default\n * path. Returns `claude-opus-4-7[1m]` when the live Copilot catalog\n * contains a `*-opus-4.7-1m*` variant (enterprise tier), else\n * `DEFAULT_CLAUDE_MODEL` (the bare slug).\n *\n * The `[1m]` literal-bracket suffix is Claude Code's local 1M-context\n * unlock — cc-backup `src/utils/context.ts:35-40` matches `/\\[1m\\]/i`\n * to flip the context window from 200K to 1M, which drives compaction\n * triggers, the status-line context %, and token budgets. Without the\n * bracket Claude Code accounts against 200K regardless of how the\n * proxy routes the underlying request.\n *\n * Cap-awareness matters because on non-enterprise Copilot tiers there\n * is no `-1m` opus backend; sending `[1m]` there would either 400 at\n * Copilot or (with `resolveModel`'s graceful-degrade) silently\n * downgrade upstream while Claude Code still over-accounts context.\n * This helper detects the catalog state at launch and only opts in\n * when the backend can actually serve 1M.\n *\n * Sonnet/Haiku families are intentionally NOT given `[1m]` defaults\n * because Copilot has no `-1m` backend for them (and Anthropic-side\n * `modelSupports1M` doesn't list haiku at all). See\n * `src/lib/server-setup.ts:getClaudeCodeEnvVars` for the\n * `ANTHROPIC_DEFAULT_{SONNET,HAIKU,OPUS}_MODEL` tier defaults.\n *\n * Must be called AFTER `cacheModels()` has populated `state.models`.\n * Returns the bare slug if the catalog isn't populated (resolveModel\n * can't tell the difference between \"no catalog yet\" and \"no 1M\n * variant\" — defaulting safe-side preserves the pre-change behavior).\n */\nexport function pickClaudeDefault(): string {\n const has1mOpus47 =\n state.models?.data.some((m) => /opus-4[.-]7-1m(?:$|-)/i.test(m.id)) ?? false\n if (has1mOpus47) {\n consola.info(\n `Catalog contains opus-4.7-1m variant; defaulting ANTHROPIC_MODEL to \"${DEFAULT_CLAUDE_MODEL}[1m]\" so Claude Code accounts for 1M context locally. Set CLAUDE_CODE_DISABLE_1M_CONTEXT=1 to opt out (HIPAA), or pass --model ${DEFAULT_CLAUDE_MODEL} to pin 200K.`,\n )\n return `${DEFAULT_CLAUDE_MODEL}[1m]`\n }\n return DEFAULT_CLAUDE_MODEL\n}\n\n/**\n * Default model for `github-router codex`. `gpt-5.5` is the new flagship\n * `/responses` model; the fallback chain handles older Copilot tiers where\n * 5.5 hasn't rolled out yet. `resolveCodexModel` provides a final\n * \"best available `/responses` model\" safety net beyond this list.\n */\nexport const DEFAULT_CODEX_MODEL = \"gpt-5.5\"\nexport const DEFAULT_CODEX_MODEL_FALLBACKS = [\n \"gpt-5.4\",\n \"gpt-5.3-codex\",\n \"gpt-5.2-codex\",\n] as const\n\nconst PORT_RANGE_MIN = 11000\nconst PORT_RANGE_MAX = 65535\n\n/** Generate a random port number in the range [11000, 65535]. */\nexport function generateRandomPort(): number {\n return (\n Math.floor(Math.random() * (PORT_RANGE_MAX - PORT_RANGE_MIN + 1))\n + PORT_RANGE_MIN\n )\n}\n\nfunction envInt(key: string, fallback: number): number {\n const raw = process.env[key]\n if (!raw) return fallback\n // Strict integer format only: parseInt is too permissive — it would\n // silently turn `\"5e3\"` into 5, `\"300_000\"` into 300, `\"60000ms\"` into\n // 60000. For timeout knobs we'd rather fall back than silently\n // misconfigure (e.g. set a 5-min inactivity timer to 5 ms).\n if (!/^[0-9]+$/.test(raw.trim())) {\n consola.warn(\n `${key}=${JSON.stringify(raw)} is not a non-negative integer; using fallback ${fallback}`,\n )\n return fallback\n }\n const parsed = Number.parseInt(raw, 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\n// Total fetch-phase timeout (until Response object resolves) for upstream\n// streaming endpoints. Default 0 = no fetch-phase timeout — body-phase\n// failures are covered by UPSTREAM_INACTIVITY_TIMEOUT_MS below, and a\n// fetch-lifecycle timeout would silently truncate legitimate long\n// completions (e.g. xhigh-thinking responses that legitimately stream\n// for 30+ minutes). Set the env var to a positive integer if you need\n// a hard cap.\nexport const UPSTREAM_FETCH_TIMEOUT_MS = envInt(\n \"UPSTREAM_FETCH_TIMEOUT_MS\",\n 0,\n)\n\n// Inactivity bound on body reads — if no chunk arrives within this window,\n// abort the stream and emit a structured error event. 300s (5 min) sits\n// well above Copilot's ~60s idle cut so the proxy still reaps stalled\n// connections before the upstream RST hits us as an unhandled rejection,\n// but does NOT prematurely abort reasoning-capable models (gpt-5.5,\n// gpt-5.3-codex, gemini-3.1-pro-preview, claude-opus-4.7-xhigh) which\n// routinely produce >75s silences between visible token bursts while\n// thinking. The earlier 75s default produced live aborts at /v1/messages\n// with bytes=134k–163k already streamed — proof the upstream was healthy\n// and just thinking. Lower this only if you specifically want to reap\n// stalled connections faster than 5 minutes.\nexport const UPSTREAM_INACTIVITY_TIMEOUT_MS = envInt(\n \"UPSTREAM_INACTIVITY_TIMEOUT_MS\",\n 300_000,\n)\n\n// TODO: extend timeout coverage to non-streaming paths (web-search MCP in\n// src/services/copilot/web-search.ts, embeddings, models) when those\n// endpoints become hot or start hanging in practice.\n","import { execFileSync, spawn, type ChildProcess } from \"node:child_process\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport type { Server } from \"srvx\"\n\nimport { DEFAULT_CODEX_MODEL } from \"./port\"\n\n/**\n * Auth-related env keys we strip from the parent before spawning the\n * child CLI. The proxy provides its own values for everything we care\n * about (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, OPENAI_BASE_URL,\n * OPENAI_API_KEY, CODEX_HOME, ANTHROPIC_MODEL); for the rest, we want\n * the child to behave as if the user had no parent-env auth at all.\n *\n * Why strip rather than override-with-empty-string:\n * - Claude Code emits \"Auth conflict\" warnings whenever both\n * ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY are present (regardless\n * of value, even when both are \"dummy\"). Stripping API_KEY entirely\n * suppresses the warning AND prevents an inherited real shell key\n * from leaking via x-api-key.\n * - Cloud-provider toggles (CLAUDE_CODE_USE_*) and OAUTH_TOKEN, etc.\n * are simpler dropped than overridden — a missing env var is\n * unambiguously falsy/absent in every code path that reads it.\n */\nconst STRIPPED_PARENT_ENV_KEYS = [\n // Claude Code auth surface\n \"ANTHROPIC_API_KEY\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_CUSTOM_HEADERS\",\n \"ANTHROPIC_MODEL\",\n \"CLAUDE_CODE_OAUTH_TOKEN\",\n // Per binary-grep of v2.1.140 (function QuH): Claude Code recognizes\n // CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR as an alternate auth source\n // (loads OAuth from an open file descriptor). Stripping this prevents\n // a user-exported FD reference from leaking into the proxy session\n // and creating a third auth source alongside the synthetic\n // .credentials.json (potential auth-conflict warning).\n \"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR\",\n \"CLAUDE_CODE_USE_BEDROCK\",\n \"CLAUDE_CODE_USE_VERTEX\",\n \"CLAUDE_CODE_USE_FOUNDRY\",\n // Defense-in-depth: prevent a parent-set CLAUDE_CONFIG_DIR (e.g. an\n // alternate test profile) from silently leaking into the proxy session.\n // The proxy sets its own value to activate per-config-dir keychain\n // isolation (see `getClaudeCodeEnvVars` doc comment).\n \"CLAUDE_CONFIG_DIR\",\n // Claude Code Bridge / IDE remote-session surface. Any of these set in\n // the parent shell would activate Claude Code's remote-session code path\n // — which makes many additional API calls (POST /v1/code/sessions,\n // POST /v1/environments/bridge, etc.) that this proxy does not implement\n // (Copilot has no equivalent). Stripping forces the spawned child to\n // run as a local-only session, which is what the proxy supports.\n // (Verified surface in cc-backup src/bridge/*, src/utils/managedEnv.ts;\n // empirical check 2026-05-11.)\n \"CLAUDE_BRIDGE_OAUTH_TOKEN\",\n \"CLAUDE_BRIDGE_BASE_URL\",\n \"CLAUDE_BRIDGE_SESSION_INGRESS_URL\",\n \"SESSION_INGRESS_URL\",\n \"CLAUDE_CODE_REMOTE\",\n \"CLAUDE_CODE_CONTAINER_ID\",\n \"CLAUDE_CODE_REMOTE_SESSION_ID\",\n \"CLAUDE_CODE_SESSION_ID\",\n // CLAUDE_CODE_ADDITIONAL_PROTECTION makes Claude Code emit\n // `x-anthropic-additional-protection: true` on every /v1/messages request.\n // Copilot ignores it today (verified 2026-05-11) but the header is pure\n // wire-fingerprint noise that breaks the VS Code stealth posture.\n \"CLAUDE_CODE_ADDITIONAL_PROTECTION\",\n // NOT stripped: ANTHROPIC_SMALL_FAST_MODEL. Users with custom Copilot\n // mappings legitimately rely on this to route the haiku-tier \"small fast\"\n // model. Stripping would be an unforced error (gemini-critic finding) —\n // we trust resolveModel's dated-slug-retry / family-fallback to translate\n // unrecognized values, and surface unsupported-model failures via consola.\n // Codex CLI auth surface\n \"OPENAI_API_KEY\",\n \"OPENAI_BASE_URL\",\n \"CODEX_HOME\",\n] as const\n\n/**\n * Strip auth-related keys from a parent-process env object. The result\n * is suitable to spread into a spawned child's env BEFORE the proxy's\n * explicit overrides, so the proxy is the only source of truth for\n * auth — and stale shell exports can't leak through.\n */\nexport function sanitizeParentEnv(\n parent: NodeJS.ProcessEnv,\n): NodeJS.ProcessEnv {\n const sanitized: NodeJS.ProcessEnv = { ...parent }\n for (const key of STRIPPED_PARENT_ENV_KEYS) {\n delete sanitized[key]\n }\n return sanitized\n}\n\nfunction commandExists(name: string): boolean {\n try {\n execFileSync(process.platform === \"win32\" ? \"where.exe\" : \"which\", [name], {\n stdio: \"ignore\",\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Provider-config flags (`-c model_providers.github_router=...`) that\n * point Codex at our proxy. Extracted from `buildCodexCmd` so the new\n * `codex mcp-server` MCP-config builder can reuse the exact same\n * provider definition — drift between the two paths would silently\n * break the MCP wiring.\n */\nexport function buildCodexProviderConfigFlags(serverUrl: string): Array<string> {\n return [\n \"-c\",\n `model_providers.github_router={name=\"github-router\",base_url=\"${serverUrl}/v1\",wire_api=\"responses\",env_key=\"OPENAI_API_KEY\"}`,\n \"-c\",\n \"model_provider=github_router\",\n ]\n}\n\n/**\n * Inspect the installed `codex` binary. Used by the codex-MCP wiring\n * in `claude.ts` to gate `--codex-cli`. Codex 0.129.0 introduced the\n * `mcp-server` subcommand; older versions don't expose it, so we\n * downgrade to the HTTP backend with a warning.\n */\nexport function getCodexVersion(): { ok: boolean; version?: string } {\n if (!commandExists(\"codex\")) return { ok: false }\n let raw: string\n try {\n raw = execFileSync(\"codex\", [\"--version\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim()\n } catch {\n return { ok: false }\n }\n // Output examples: \"codex-cli 0.129.0\", \"codex 0.130.1-dev\"\n const m = /(\\d+)\\.(\\d+)\\.(\\d+)/.exec(raw)\n if (!m) return { ok: false, version: raw }\n const major = Number.parseInt(m[1], 10)\n const minor = Number.parseInt(m[2], 10)\n const version = `${m[1]}.${m[2]}.${m[3]}`\n // mcp-server requires codex >= 0.129.0\n const ok = major > 0 || (major === 0 && minor >= 129)\n return { ok, version }\n}\n\nexport interface LaunchTarget {\n kind: \"claude-code\" | \"codex\"\n envVars: Record<string, string>\n extraArgs: string[]\n model?: string\n /**\n * Proxy URL the spawned child should target. Required for Codex 0.129+\n * which stopped honoring OPENAI_BASE_URL and now needs an explicit\n * `-c model_providers.<name>.base_url=...` argument. Set by the codex\n * subcommand from the same `serverUrl` it computed for env vars.\n */\n serverUrl?: string\n}\n\n/**\n * Codex 0.129.0 broke two things the launcher had been relying on:\n * (1) `--full-auto` was removed in favor of `--sandbox` + `--ask-for-approval`;\n * passing it now exits the child immediately with\n * `error: unexpected argument '--full-auto' found`.\n * (2) `OPENAI_BASE_URL` is silently ignored — Codex hardcodes\n * `https://api.openai.com/v1/responses` and 401s out without an\n * explicit `-c model_providers.<name>.base_url` override.\n *\n * `buildCodexCmd` builds the launch argv that works on Codex 0.129+ while\n * still being compatible with older versions that accept the same flags.\n */\nfunction buildCodexCmd(target: LaunchTarget): string[] {\n const cmd: string[] = [\"codex\"]\n if (target.serverUrl) {\n cmd.push(...buildCodexProviderConfigFlags(target.serverUrl))\n }\n cmd.push(\n \"--sandbox\",\n \"workspace-write\",\n \"--ask-for-approval\",\n \"on-request\",\n \"-m\",\n target.model ?? DEFAULT_CODEX_MODEL,\n ...target.extraArgs,\n )\n return cmd\n}\n\nexport function buildLaunchCommand(target: LaunchTarget): {\n cmd: string[]\n env: Record<string, string | undefined>\n} {\n const cmd: string[] =\n target.kind === \"claude-code\"\n ? [\"claude\", \"--dangerously-skip-permissions\", ...target.extraArgs]\n : buildCodexCmd(target)\n\n return {\n cmd,\n env: { ...sanitizeParentEnv(process.env), ...target.envVars },\n }\n}\n\nexport function launchChild(\n target: LaunchTarget,\n server: Server,\n options: { onShutdown?: () => Promise<void> | void } = {},\n): void {\n const { cmd, env } = buildLaunchCommand(target)\n\n const executable = cmd[0]\n if (!commandExists(executable)) {\n const msg = `\"${executable}\" not found on PATH. Install it first, then try again.`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n process.exit(1)\n }\n\n let child: ChildProcess\n try {\n if (process.platform === \"win32\") {\n // On Windows, npm-installed binaries are .cmd scripts that need\n // shell execution. Use the full command as a single string to\n // avoid DEP0190 deprecation warning about shell + args.\n const quoted = cmd.map((a) => (a.includes(\" \") ? `\"${a}\"` : a)).join(\" \")\n child = spawn(quoted, [], {\n env,\n stdio: \"inherit\",\n shell: true,\n })\n } else {\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n })\n }\n } catch (error) {\n const msg = `Failed to launch ${executable}: ${error instanceof Error ? error.message : String(error)}`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n server.close(true).catch(() => {})\n if (options.onShutdown) {\n void Promise.resolve(options.onShutdown()).catch(() => {})\n }\n process.exit(1)\n }\n\n let cleaned = false\n let exiting = false\n async function cleanup(): Promise<void> {\n if (cleaned) return\n cleaned = true\n\n try {\n child.kill()\n } catch {\n // Already exited\n }\n\n const timeout = setTimeout(() => process.exit(1), 5000)\n try {\n await server.close(true)\n } catch {\n // Server already closed\n }\n if (options.onShutdown) {\n try {\n await options.onShutdown()\n } catch {\n // Best-effort cleanup; shutdown must not be blocked by it.\n }\n }\n clearTimeout(timeout)\n }\n\n function exit(code: number): void {\n if (exiting) return\n exiting = true\n process.exit(code)\n }\n\n const onSignal = () => {\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }\n process.on(\"SIGINT\", onSignal)\n process.on(\"SIGTERM\", onSignal)\n\n child.on(\"exit\", (exitCode, signal) => {\n // When killed by a signal, exitCode is null — derive from signal number\n const code = exitCode ?? (signal ? 128 : 1)\n cleanup().then(() => exit(code)).catch(() => exit(1))\n })\n child.on(\"error\", () => {\n cleanup().then(() => exit(1)).catch(() => exit(1))\n })\n}\n","/**\n * `code_search` MCP tool implementation.\n *\n * Exposes structured code search to clients (Claude Code, codex,\n * gemini callers) via the `mcp__gh-router-peers__code_search` tool.\n * Backed by ripgrep; ranks with BM25F (Robertson, Zaragoza, Taylor\n * 2004) over four code-aware fields (matched line, context window,\n * file path tokens, symbol-context heuristic).\n *\n * Plan: ~/.local/share/.../plans/what-are-the-following-wild-tarjan.md\n * Peer review: same dir, file ending in -agent-af76e3758b0fa7e1a.md.\n *\n * Load-bearing design decisions worth knowing before editing:\n *\n * - Workspace is any absolute path that exists and is a directory.\n * The proxy runs as the user; code_search reads what the proxy\n * process can read, the same way Claude Code's built-in Read /\n * Bash tools do. The earlier allow-set + secret-shape denylist\n * was dropped: the threat model is symmetric (the model already\n * has Bash and Read), so an extra gate on this one tool was just\n * inconsistency, not defense.\n *\n * - rg is spawned with `cwd: canonicalWorkspace` and target `.`,\n * NEVER with the user-supplied path string as an argv positional.\n * This pins the directory at kernel-level at spawn time, closing\n * most of the TOCTOU window between validate and spawn. The\n * residual same-user race is out of scope.\n *\n * - The `--` positional separator is mandatory. A query starting\n * with `-` would otherwise be parsed as a ripgrep flag — CVE.\n *\n * - On Windows, child.kill() does NOT reliably terminate\n * descendants. We invoke `taskkill /T /F /PID <pid>` on abort.\n *\n * - JSON streaming parser short-circuits on `signal.aborted` so a\n * half-flushed truncated chunk never reaches JSON.parse — three-\n * lab confirmed cancel-race fix.\n *\n * - `--max-count` is per-file, not global. We enforce the limit\n * globally in the TS reader; relying on ripgrep would let a\n * 500-file monorepo return 10,000 hits with limit=20.\n *\n * - BM25F is applied at file granularity over the rg hit set. The\n * v1 review's BM25 critique was about snippet-granularity (4-line\n * length-normalization noise); files have varied lengths so\n * length normalization is meaningful here.\n */\n\nimport { spawn, execFile, execFileSync, type ChildProcess } from \"node:child_process\"\nimport { existsSync, readFileSync, realpathSync, statSync } from \"node:fs\"\nimport { createInterface } from \"node:readline\"\nimport * as path from \"node:path\"\n\nimport consola from \"consola\"\nimport Parser from \"web-tree-sitter\"\n\n// ============================================================\n// Constants\n// ============================================================\n\n/**\n * BM25's `k1` term-frequency saturation parameter. Lucene's default.\n * Robertson & Zaragoza 2009 monograph recommends 1.2-2.0; Lucene\n * ships 1.2, Elasticsearch ships 1.2, we ship 1.2.\n */\nconst BM25F_K1 = 1.2\n\n/**\n * Per-field BM25F boost weights (`b_f` in the CIKM 2004 paper). The\n * relative ordering follows Sourcegraph Zoekt's published signal\n * priorities — matched line first, then symbol context, then path,\n * then surrounding context.\n */\nconst FIELD_BOOSTS = {\n match_line: 3.0,\n symbol_context: 2.5,\n file_path: 2.0,\n context: 1.0,\n} as const\n\n/**\n * Per-field length-normalization parameter (`l_f`). 0.0 disables\n * length normalization for short, uniform fields. Lucene's default\n * `b=0.75` for prose-like fields.\n */\nconst FIELD_LEN_NORMS = {\n match_line: 0.0,\n symbol_context: 0.0,\n file_path: 0.0,\n context: 0.75,\n} as const\n\n/**\n * Shoulder cut: drop results below this fraction of the top score.\n * 0.5 is the convention from learning-to-rank literature (Burges\n * 2010); chosen as the deliberate single-place constant.\n */\nconst SHOULDER_THRESHOLD = 0.5\n\nconst MAX_QUERY_LEN = 1024\nconst MAX_GLOB_LEN = 512\nconst DEFAULT_LIMIT = 20\nconst MAX_CONTEXT_LINES = 10\nconst DEFAULT_CONTEXT_LINES = 2\nconst MAX_SNIPPET_BYTES = 2048\nconst MAX_STDOUT_BYTES = 10 * 1024 * 1024\nconst WALL_TIME_MS = 30_000\n\n/**\n * Structural-pass settings. The wall-clock budget is checked between\n * files (NOT mid-parse — tree-sitter doesn't surface a usable cancel\n * hook in the web-tree-sitter binding we're on), so a single\n * pathological file can overrun by one file's parse-time. In practice\n * a single source file parses in well under 50ms; 200ms gives us\n * comfortable headroom for ~5-10 files even on cold cache.\n */\nconst STRUCTURAL_BUDGET_MS = 200\nconst STRUCTURAL_TOPN_FULL = 50\nconst STRUCTURAL_TOPN_FAST = 10\n\n/**\n * Cap the per-file size we'll parse. 1MB of source covers all\n * reasonable hand-written files; bigger files are almost always\n * generated code or vendored bundles whose AST signal is worthless\n * for ranking real definitions.\n */\nconst STRUCTURAL_MAX_FILE_BYTES = 1024 * 1024\n\n/**\n * LRU bound on the parsed-tree cache. Each Tree pins ~roughly the\n * size of its source plus tree-sitter's internal node arena. 64 is\n * comfortably under typical Node heap budgets; trees are eagerly\n * `.delete()`-ed on eviction.\n */\nconst STRUCTURAL_CACHE_MAX = 64\n\n/**\n * Definition-shape heuristic for `symbol_context` field. Match this\n * against the matched line (after leading whitespace strip) to\n * detect \"the match is on a definition.\" This is the regex fallback\n * we use when (a) tree-sitter can't reach the file (unsupported\n * language, grammar load failure, parse error), (b) the file isn't\n * in the structural pass's top-N slice, or (c) the structural budget\n * fired.\n */\nconst SYMBOL_REGEX =\n /^(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?(?:public\\s+|private\\s+|protected\\s+|static\\s+|abstract\\s+|readonly\\s+)*(?:function|class|interface|type|enum|def|fn|trait|impl|module|namespace|const|let|var)\\s+[A-Za-z_$]/\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface CodeSearchInput {\n query: string\n workspace: string\n mode?: \"ranked\" | \"literal\" | \"regex\"\n file_glob?: string\n limit?: number\n context_lines?: number\n /**\n * Depth of the tree-sitter structural-ranking pass. `\"full\"` parses\n * the top 50 BM25F hits and re-scores them with AST-confirmed\n * definition signal. `\"topN\"` parses only the top 10 — same signal,\n * tighter latency on large repos. Default `\"full\"`. The pass is\n * wrapped in a 200ms wall-clock budget; on overrun, remaining hits\n * fall back to the regex symbol heuristic and `notice` is populated\n * with a human-readable explanation.\n */\n structural?: \"full\" | \"topN\"\n}\n\nexport interface CodeSearchHit {\n file: string\n line: number\n snippet: string\n match_byte_range: [number, number]\n score?: number\n field_contributions?: Readonly<Record<string, number>> | null\n}\n\nexport interface CodeSearchResponse {\n results: Array<CodeSearchHit>\n truncated: boolean\n pruned_below_shoulder?: number\n scanned_files: number\n elapsed_ms: number\n ranking: {\n algorithm: \"BM25F\" | \"ripgrep_document_order\"\n citation?: string\n k1?: number\n }\n /**\n * Single actionable degradation notice for the model. `null` on the\n * happy path. A string when something the model can correct fired:\n * - structural-budget exhaustion (\"retry with structural: \\\"topN\\\"\n * or narrow query\")\n * - response-size cap (\"response size limit reached at N hits;\n * lower limit or narrow your query\")\n * Size-cap takes priority over structural-budget when both fire,\n * because size-cap means the model is missing results; structural-\n * budget just means the ranking was less precise but the result set\n * is complete.\n *\n * The MCP handler maps this to the `notice` response field (omitted\n * entirely when `null`) — only-when-actionable surface per the\n * docs/peer-mcp-design.md minimality principle.\n */\n notice: string | null\n}\n\n/**\n * Internal representation of one rg match before scoring.\n * `context_before` and `context_after` are populated via ripgrep's\n * --context flag (rg emits \"context\" JSON events that we associate\n * with the surrounding match).\n */\ninterface RawHit {\n file: string // path RELATIVE to workspace\n line: number // 1-indexed\n matched_line: string // line text without trailing newline\n match_start: number // byte offset in matched_line\n match_end: number // byte offset in matched_line\n context_before: Array<string>\n context_after: Array<string>\n}\n\n// ============================================================\n// Ripgrep resolution\n// ============================================================\n\ninterface RipgrepResolution {\n rgPath: string\n source: \"system\" | \"bundled\"\n}\n\nlet _rgResolution: RipgrepResolution | undefined\n\n/**\n * Tri-tier resolution. Memoized. Mirrors cc-backup\n * `src/utils/ripgrep.ts:31-65`.\n *\n * 1. System rg on PATH — use the literal command name `\"rg\"` (NOT\n * the absolute path). This leverages NoDefaultCurrentDirectory-\n * InExePath on Windows, preventing PATH-hijacking via a\n * malicious ./rg.exe in the proxy's cwd.\n * 2. Bundled via `@vscode/ripgrep` — falls back to the per-platform\n * binary that `optionalDependencies` installed.\n * 3. Throw — surfaced to the caller as an MCP isError response.\n */\nexport function resolveRipgrep(): RipgrepResolution {\n if (_rgResolution) return _rgResolution\n\n // System check: probe PATH for `rg`. We DON'T use the absolute\n // path returned by which/where — using just the command name lets\n // the OS apply NoDefaultCurrentDirectoryInExePath on Windows.\n if (hasSystemRipgrep()) {\n _rgResolution = { rgPath: \"rg\", source: \"system\" }\n return _rgResolution\n }\n\n // Bundled fallback. require.resolve through the @vscode/ripgrep\n // package's exports — works because v1.18.0 ships per-platform\n // binary packages via optionalDependencies.\n try {\n // Using a dynamic import keeps the dep optional at type level;\n // the package's rgPath export is a string.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require(\"@vscode/ripgrep\") as { rgPath: string }\n if (mod.rgPath && existsSync(mod.rgPath)) {\n _rgResolution = { rgPath: mod.rgPath, source: \"bundled\" }\n return _rgResolution\n }\n } catch {\n // fall through\n }\n\n throw new Error(\n \"ripgrep not found. Either install rg system-wide (brew/apt/winget) \" +\n \"or reinstall the proxy so @vscode/ripgrep's per-platform binary is \" +\n \"fetched. See README's code_search section.\",\n )\n}\n\nfunction hasSystemRipgrep(): boolean {\n // Probe via `which rg` / `where rg`. We don't trust the returned\n // path (PATH-hijack risk) — we just want to know if SOME rg is\n // on PATH. When found, we'll spawn `\"rg\"` and let the OS resolve\n // it again with its own safety guarantees.\n try {\n const cmd = process.platform === \"win32\" ? \"where\" : \"which\"\n const out = execFileSync(cmd, [\"rg\"], {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n })\n return out.length > 0\n } catch {\n return false\n }\n}\n\n// ============================================================\n// Input validation\n// ============================================================\n\nfunction validateInputs(input: CodeSearchInput): string | null {\n if (typeof input.query !== \"string\" || input.query.length === 0) {\n return \"code_search: arguments.query is required (non-empty string)\"\n }\n if (input.query.length > MAX_QUERY_LEN) {\n return `code_search: query exceeds ${MAX_QUERY_LEN} chars`\n }\n if (/[\\0\\r\\n]/.test(input.query)) {\n return \"code_search: query contains null byte or newline (rejected)\"\n }\n if (typeof input.workspace !== \"string\" || input.workspace.length === 0) {\n return \"code_search: arguments.workspace is required (absolute path)\"\n }\n if (input.mode && ![\"ranked\", \"literal\", \"regex\"].includes(input.mode)) {\n return `code_search: mode must be one of \"ranked\", \"literal\", \"regex\"`\n }\n if (input.file_glob !== undefined) {\n if (typeof input.file_glob !== \"string\") {\n return \"code_search: file_glob must be a string\"\n }\n if (input.file_glob.length > MAX_GLOB_LEN) {\n return `code_search: file_glob exceeds ${MAX_GLOB_LEN} chars`\n }\n if (/[\\0\\r\\n]/.test(input.file_glob)) {\n return \"code_search: file_glob contains null byte or newline\"\n }\n }\n if (input.limit !== undefined) {\n if (typeof input.limit !== \"number\" || !Number.isInteger(input.limit) || input.limit < 1) {\n return \"code_search: limit must be a positive integer\"\n }\n }\n if (input.context_lines !== undefined) {\n if (\n typeof input.context_lines !== \"number\" ||\n !Number.isInteger(input.context_lines) ||\n input.context_lines < 0\n ) {\n return \"code_search: context_lines must be a non-negative integer\"\n }\n }\n return null\n}\n\n// ============================================================\n// Workspace validation\n// ============================================================\n\ninterface ValidationResult {\n ok: boolean\n canonical?: string\n error?: string\n}\n\n/**\n * Validate a `workspace` arg. The proxy runs as the user; any path\n * the proxy process can `stat` is a legal workspace — mirrors what\n * Claude Code's Read / Bash tools could already reach. Earlier the\n * validator enforced an allow-set + secret-shape file denylist; the\n * holistic threat model showed those were inconsistent guardrails\n * (the model already has filesystem access via its other tools), so\n * they're dropped.\n *\n * Still enforced:\n * - Absolute path (relative paths are an integration-error footgun).\n * - realpath canonicalization (resolves symlinks; output paths are\n * reported relative to this).\n * - Path must exist AND be a directory.\n *\n * Errors do NOT echo the rejected path (output of code_search flows\n * upstream to the model provider; consistent with the\n * COPILOT_HOST_ALLOWLIST pattern in `src/lib/utils.ts`).\n */\nexport function validateWorkspace(workspace: string): ValidationResult {\n if (!path.isAbsolute(workspace)) {\n return { ok: false, error: \"workspace must be an absolute path\" }\n }\n\n let canonical: string\n try {\n canonical = realpathSync(workspace)\n } catch {\n return { ok: false, error: \"workspace path is not accessible\" }\n }\n\n try {\n if (!statSync(canonical).isDirectory()) {\n return { ok: false, error: \"workspace must be a directory\" }\n }\n } catch {\n return { ok: false, error: \"workspace path is not accessible\" }\n }\n\n return { ok: true, canonical }\n}\n\n// ============================================================\n// Tokenization (Vasilescu, Ray, Mockus ESEC/FSE 2021 — rule-based)\n// ============================================================\n\n/**\n * Rule-based identifier splitter per the ESEC/FSE 2021 benchmark.\n *\n * 1. Split on non-word characters.\n * 2. Within each chunk, split on case boundaries with acronym\n * lookahead — `HTTPSConnection` → [`HTTPS`, `Connection`].\n * 3. Attach trailing digit runs to letters — `parseV2Handler` →\n * [`parse`, `V2`, `Handler`] (NOT `[parse, V, 2, Handler]`).\n * 4. Lowercase all tokens.\n * 5. Drop tokens of length < 2 to suppress single-char noise.\n *\n * Limitation: ASCII identifiers only. Unicode identifiers (Cyrillic,\n * CJK, etc.) won't be tokenized. Documented as MVP scope.\n */\nexport function tokenize(text: string): Array<string> {\n const out: Array<string> = []\n const pieces = text.split(/[^A-Za-z0-9]+/)\n const re = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+[0-9]*|[A-Z]+[0-9]*|[0-9]+/g\n for (const piece of pieces) {\n if (!piece) continue\n const matches = piece.match(re)\n if (!matches) continue\n for (const m of matches) {\n const lc = m.toLowerCase()\n if (lc.length >= 2) out.push(lc)\n }\n }\n return out\n}\n\n// ============================================================\n// Process management\n// ============================================================\n\n/**\n * Platform-aware child termination. On Unix: SIGTERM, then SIGKILL\n * after a brief grace period. On Windows: taskkill /T /F because\n * child.kill() doesn't reliably terminate descendants — a long\n * search with worker threads would leak rg.exe processes.\n */\nfunction killChild(child: ChildProcess): void {\n if (!child.pid || child.killed) return\n\n if (process.platform === \"win32\") {\n // /T = kill tree (including children of children)\n // /F = force; rg has no graceful-shutdown signal handler on Win.\n try {\n execFile(\"taskkill\", [\"/T\", \"/F\", \"/PID\", String(child.pid)], () => {\n // Errors are swallowed: the process may already have exited,\n // and we don't have anywhere meaningful to surface this to.\n })\n } catch {\n // Best effort.\n }\n return\n }\n\n try {\n child.kill(\"SIGTERM\")\n } catch {\n // already dead\n }\n // Hard kill after 500 ms if it didn't go quietly.\n setTimeout(() => {\n if (!child.killed) {\n try {\n child.kill(\"SIGKILL\")\n } catch {\n // already dead\n }\n }\n }, 500).unref()\n}\n\n// ============================================================\n// Identifier skeleton-form query expansion\n// ============================================================\n\n/**\n * Single-identifier query matcher. We only expand queries that look\n * like a single identifier — any whitespace, regex metacharacters, or\n * structural punctuation defeats the expansion and we fall through to\n * the original rg behavior. ASCII-only on purpose (matches the\n * tokenizer's scope; Unicode identifiers are MVP-out).\n */\nconst SINGLE_IDENTIFIER_REGEX = /^[A-Za-z][A-Za-z0-9_-]{0,127}$/\n\n/**\n * Split an identifier into its constituent word-pieces, recognizing\n *\n * - snake_case (split on `_`)\n * - kebab-case (split on `-`)\n * - camelCase (split on lowercase→uppercase boundaries)\n * - PascalCase (each capitalized run is a piece)\n * - acronym runs (HTTPSConnection → [HTTPS, Connection])\n * - trailing digits attached to letters (parseV2 → [parse, V2])\n *\n * Pieces are returned in source-order, with the original case\n * preserved per piece — re-skeletons compose by re-casing each piece.\n */\nfunction splitIdentifierPieces(identifier: string): Array<string> {\n const pieces: Array<string> = []\n for (const chunk of identifier.split(/[-_]/)) {\n if (!chunk) continue\n // Acronym-aware case-boundary split. Same regex as the BM25F\n // tokenizer, minus the lowercasing — we want original-case\n // pieces so we can re-cast them per skeleton.\n const matches = chunk.match(\n /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+[0-9]*|[A-Z]+[0-9]*|[0-9]+/g,\n )\n if (matches) pieces.push(...matches)\n }\n return pieces\n}\n\n/**\n * Produce skeleton variants for an identifier query. Returns `null`\n * when the query is not a single identifier or has only one piece\n * (no skeleton structure to vary across) — caller falls through to\n * the literal-search path.\n *\n * The variant set covers the five conventions any real codebase\n * mixes:\n *\n * getUserName (lowerCamelCase)\n * GetUserName (UpperCamelCase / PascalCase)\n * get_user_name (snake_case)\n * get-user-name (kebab-case)\n * GET_USER_NAME (UPPER_SNAKE_CASE)\n *\n * The set is deduplicated so identifiers that collapse skeletons\n * (e.g., single-word queries) don't bloat the regex pointlessly.\n */\nfunction expandIdentifierVariants(query: string): Array<string> | null {\n if (!SINGLE_IDENTIFIER_REGEX.test(query)) return null\n const pieces = splitIdentifierPieces(query)\n if (pieces.length < 2) return null\n const lower = pieces.map((p) => p.toLowerCase())\n const upper = pieces.map((p) => p.toUpperCase())\n const cap = lower.map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n const variants = new Set<string>()\n variants.add(query)\n variants.add(lower[0] + cap.slice(1).join(\"\")) // camelCase\n variants.add(cap.join(\"\")) // PascalCase\n variants.add(lower.join(\"_\")) // snake_case\n variants.add(lower.join(\"-\")) // kebab-case\n variants.add(upper.join(\"_\")) // UPPER_SNAKE_CASE\n return Array.from(variants)\n}\n\n/**\n * Build the rg regex pattern for a set of skeleton variants. The\n * variants are already plain identifiers (no regex metacharacters),\n * so simple alternation suffices. Word boundaries are intentionally\n * NOT applied — the user's mental model for \"search for getUserName\"\n * is substring-anywhere, which is also what `-F getUserName` did.\n */\nfunction buildExpansionPattern(variants: ReadonlyArray<string>): string {\n return \"(?:\" + variants.join(\"|\") + \")\"\n}\n\nfunction buildRgArgs(input: {\n mode: \"ranked\" | \"literal\" | \"regex\"\n fileGlob?: string\n contextLines: number\n query: string\n /**\n * When set, the caller has expanded the original query into a\n * regex alternation across skeleton-form variants. We override\n * `-F` (literal) regardless of the user's chosen mode and pass\n * the alternation as a ripgrep regex pattern. The original-mode\n * literal semantics are preserved because the variants are plain\n * identifiers (no regex metacharacters).\n */\n expansionPattern?: string\n}): Array<string> {\n const args: Array<string> = [\"--json\", \"--no-follow\"]\n\n // -C N means N lines BEFORE and N AFTER. We always want context\n // for snippet rendering AND for the BM25F context field.\n if (input.contextLines > 0) {\n args.push(`-C`, String(input.contextLines))\n }\n\n // Literal mode → -F. Ranked mode uses literal too (we want\n // exact-string semantics for the user's query; BM25F handles\n // tokenized matching at scoring time, not at rg time). Regex\n // mode uses ripgrep's default (PCRE2-via-builtin).\n //\n // EXCEPTION: when the caller passed `expansionPattern`, we drop\n // `-F` and feed the alternation as a regex. Skeleton expansion is\n // mutually exclusive with literal-mode semantics — but the\n // variants are still plain identifiers, so it remains\n // identifier-substring matching (the user's intent).\n if (!input.expansionPattern && (input.mode === \"literal\" || input.mode === \"ranked\")) {\n args.push(\"-F\")\n }\n\n if (input.fileGlob && input.fileGlob !== \"**/*\") {\n args.push(\"-g\", input.fileGlob)\n }\n\n // CVE fix HIGH-2: positional separator. Without `--`, a query\n // starting with `-` (e.g. `--no-ignore`) would be parsed as a\n // ripgrep flag.\n args.push(\"--\", input.expansionPattern ?? input.query, \".\")\n\n return args\n}\n\n// ============================================================\n// JSON streaming parser\n// ============================================================\n\ninterface RgEvent {\n type: string\n data: {\n path?: { text: string }\n lines?: { text: string }\n line_number?: number\n submatches?: Array<{\n match: { text: string }\n start: number\n end: number\n }>\n stats?: { searches?: number }\n }\n}\n\ninterface ParseResult {\n hits: Array<RawHit>\n scannedFiles: number\n truncated: boolean\n cancelled: boolean\n stdoutBytes: number\n}\n\n/**\n * Stream-parse ripgrep --json output. Two load-bearing behaviors:\n *\n * 1. GLOBAL limit cap (NOT per-file — MEDIUM-10). Once we've\n * accumulated `limit` hits, send SIGTERM and stop emitting.\n *\n * 2. CANCEL RACE short-circuit (HIGH-9, 3-lab confirmed). The\n * moment `signal.aborted` flips, detach the line listener AND\n * return early. A half-flushed truncated JSON line never\n * reaches JSON.parse — that's the bug we're guarding against.\n */\nasync function parseRgJsonStream(\n child: ChildProcess,\n opts: { limit: number; contextLines: number; signal: AbortSignal },\n): Promise<ParseResult> {\n const hits: Array<RawHit> = []\n let stdoutBytes = 0\n let truncatedByCap = false\n let cancelled = false\n let scannedFiles = 0\n\n // Pre-aborted: short-circuit before constructing the reader. Calling\n // rl.close() before the async iterator starts can hang on some\n // platforms (observed on Bun); avoid the readline construction\n // entirely.\n if (opts.signal.aborted) {\n killChild(child)\n return {\n hits,\n scannedFiles: 0,\n truncated: false,\n cancelled: true,\n stdoutBytes: 0,\n }\n }\n\n // Per-file accumulator: rg streams begin → context*before → match\n // → context*after → end. We buffer context_before lines per file\n // so we can attach them to the next match in that file.\n const pendingContextBefore: Array<string> = []\n let lastHitForContext: RawHit | undefined\n\n if (!child.stdout) {\n return { hits, scannedFiles: 0, truncated: false, cancelled: false, stdoutBytes: 0 }\n }\n\n const rl = createInterface({ input: child.stdout, crlfDelay: Infinity })\n\n // Wire abort: on signal, immediately tear down the reader and\n // kill the child. The early `cancelled = true` flag stops the\n // line handler from attempting JSON.parse on partial chunks.\n const onAbort = (): void => {\n cancelled = true\n rl.close()\n killChild(child)\n }\n opts.signal.addEventListener(\"abort\", onAbort, { once: true })\n\n try {\n for await (const rawLine of rl) {\n if (cancelled) break\n stdoutBytes += rawLine.length + 1 // +1 for the elided \\n\n if (stdoutBytes > MAX_STDOUT_BYTES) {\n truncatedByCap = true\n killChild(child)\n break\n }\n if (rawLine.length === 0) continue\n\n let evt: RgEvent\n try {\n evt = JSON.parse(rawLine) as RgEvent\n } catch {\n // Skip malformed lines rather than failing the whole call.\n // A truncated chunk at process death would also land here;\n // the cancelled flag check above handles the common case.\n continue\n }\n\n switch (evt.type) {\n case \"begin\": {\n scannedFiles += 1\n pendingContextBefore.length = 0\n lastHitForContext = undefined\n break\n }\n case \"context\": {\n const text = stripTrailingNewline(evt.data.lines?.text ?? \"\")\n if (lastHitForContext && lastHitForContext.context_after.length < opts.contextLines) {\n lastHitForContext.context_after.push(text)\n } else {\n pendingContextBefore.push(text)\n if (pendingContextBefore.length > opts.contextLines) {\n pendingContextBefore.shift()\n }\n }\n break\n }\n case \"match\": {\n if (hits.length >= opts.limit) {\n // Global limit reached. Kill child and stop reading.\n killChild(child)\n break\n }\n const sub = evt.data.submatches?.[0]\n if (!evt.data.path || !evt.data.lines || !evt.data.line_number || !sub) {\n break\n }\n const hit: RawHit = {\n file: evt.data.path.text,\n line: evt.data.line_number,\n matched_line: stripTrailingNewline(evt.data.lines.text),\n match_start: sub.start,\n match_end: sub.end,\n context_before: [...pendingContextBefore],\n context_after: [],\n }\n pendingContextBefore.length = 0\n lastHitForContext = hit\n hits.push(hit)\n break\n }\n case \"end\":\n case \"summary\":\n default:\n // Nothing actionable. The \"summary\" event arrives after\n // the entire stream; \"end\" arrives per-file. Both are\n // informational here.\n break\n }\n }\n } finally {\n opts.signal.removeEventListener(\"abort\", onAbort)\n }\n\n return {\n hits,\n scannedFiles,\n truncated: truncatedByCap || hits.length >= opts.limit,\n cancelled,\n stdoutBytes,\n }\n}\n\nfunction stripTrailingNewline(s: string): string {\n if (s.endsWith(\"\\r\\n\")) return s.slice(0, -2)\n if (s.endsWith(\"\\n\")) return s.slice(0, -1)\n return s\n}\n\n// ============================================================\n// Tree-sitter structural ranking\n// ============================================================\n\n/**\n * Extension → grammar key. Grammars not in this map skip structural\n * parsing (the hit falls back to the regex SYMBOL_REGEX heuristic for\n * `symbol_context`). Keep this list aligned with `GRAMMAR_FILES`\n * below — adding a language requires both an extension mapping and a\n * `.wasm` to load.\n */\nconst EXTENSION_TO_LANG: Readonly<Record<string, string>> = {\n \".ts\": \"typescript\",\n \".tsx\": \"tsx\",\n \".js\": \"javascript\",\n \".mjs\": \"javascript\",\n \".cjs\": \"javascript\",\n \".jsx\": \"javascript\",\n \".py\": \"python\",\n \".go\": \"go\",\n \".rs\": \"rust\",\n \".java\": \"java\",\n \".c\": \"c\",\n \".h\": \"c\",\n \".cpp\": \"cpp\",\n \".cc\": \"cpp\",\n \".cxx\": \"cpp\",\n \".hpp\": \"cpp\",\n \".hxx\": \"cpp\",\n}\n\n/**\n * Grammar key → wasm filename under `node_modules/tree-sitter-wasms/out/`.\n * Resolved at runtime from `node_modules`; the file paths are stable\n * because `tree-sitter-wasms` ships prebuilt binaries (no per-install\n * codegen).\n */\nconst GRAMMAR_FILES: Readonly<Record<string, string>> = {\n typescript: \"tree-sitter-typescript.wasm\",\n tsx: \"tree-sitter-tsx.wasm\",\n javascript: \"tree-sitter-javascript.wasm\",\n python: \"tree-sitter-python.wasm\",\n go: \"tree-sitter-go.wasm\",\n rust: \"tree-sitter-rust.wasm\",\n java: \"tree-sitter-java.wasm\",\n c: \"tree-sitter-c.wasm\",\n cpp: \"tree-sitter-cpp.wasm\",\n}\n\n/**\n * Per-language definition-shape node types. When a matched identifier\n * sits inside one of these nodes AND is at the node's \"name\" position,\n * we have AST-confirmed evidence the line is an identifier-definition\n * site. The brief's enumeration plus a handful of language-idiomatic\n * extras (e.g., `lexical_declaration` for TS/JS top-level `const`s,\n * `mod_item` for Rust modules).\n *\n * The set lookup is per-language so a node type that means\n * \"definition\" in one language but \"reference\" in another won't\n * cross-pollute.\n */\nconst DEFINITION_NODE_TYPES: Readonly<Record<string, ReadonlySet<string>>> = {\n typescript: new Set([\n \"function_declaration\",\n \"function_signature\",\n \"function_expression\",\n \"method_definition\",\n \"method_signature\",\n \"class_declaration\",\n \"interface_declaration\",\n \"type_alias_declaration\",\n \"enum_declaration\",\n \"variable_declarator\",\n \"generator_function_declaration\",\n \"abstract_method_signature\",\n \"public_field_definition\",\n \"property_signature\",\n ]),\n tsx: new Set([\n \"function_declaration\",\n \"function_signature\",\n \"function_expression\",\n \"method_definition\",\n \"method_signature\",\n \"class_declaration\",\n \"interface_declaration\",\n \"type_alias_declaration\",\n \"enum_declaration\",\n \"variable_declarator\",\n \"generator_function_declaration\",\n \"abstract_method_signature\",\n \"public_field_definition\",\n \"property_signature\",\n ]),\n javascript: new Set([\n \"function_declaration\",\n \"function_expression\",\n \"method_definition\",\n \"class_declaration\",\n \"variable_declarator\",\n \"generator_function_declaration\",\n ]),\n python: new Set([\n \"function_definition\",\n \"class_definition\",\n \"decorated_definition\",\n ]),\n go: new Set([\n \"function_declaration\",\n \"method_declaration\",\n \"type_spec\",\n \"type_alias\",\n \"const_spec\",\n \"var_spec\",\n ]),\n rust: new Set([\n \"function_item\",\n \"impl_item\",\n \"trait_item\",\n \"struct_item\",\n \"enum_item\",\n \"mod_item\",\n \"type_item\",\n \"const_item\",\n \"static_item\",\n \"macro_definition\",\n ]),\n java: new Set([\n \"class_declaration\",\n \"interface_declaration\",\n \"method_declaration\",\n \"constructor_declaration\",\n \"enum_declaration\",\n \"field_declaration\",\n \"annotation_type_declaration\",\n ]),\n c: new Set([\n \"function_definition\",\n \"declaration\",\n \"struct_specifier\",\n \"enum_specifier\",\n \"union_specifier\",\n \"type_definition\",\n ]),\n cpp: new Set([\n \"function_definition\",\n \"declaration\",\n \"struct_specifier\",\n \"class_specifier\",\n \"enum_specifier\",\n \"union_specifier\",\n \"type_definition\",\n \"namespace_definition\",\n \"template_declaration\",\n ]),\n}\n\n/**\n * Node types that the AST exposes as \"this token is an identifier\".\n * The match-position lookup uses these to filter out parent-node hits\n * before checking the definition-site predicate.\n */\nconst IDENTIFIER_NODE_TYPES = new Set([\n \"identifier\",\n \"type_identifier\",\n \"field_identifier\",\n \"property_identifier\",\n \"shorthand_property_identifier_pattern\",\n \"shorthand_property_identifier\",\n \"scoped_identifier\",\n \"name\",\n])\n\ninterface GrammarBundle {\n /** Lazy promise of the language registry. Awaited per-call so the\n * init cost overlaps with any other module-load work. */\n ready: Promise<Map<string, Parser.Language>>\n}\n\nlet _grammarBundle: GrammarBundle | undefined\n\n/**\n * Resolve the `tree-sitter-wasms/out/` directory at the package root.\n * `require.resolve` is used through a try/catch — the bundled-only\n * fallback runs in environments where node_modules has been pruned to\n * just runtime deps.\n */\nfunction resolveGrammarRoot(): string | null {\n try {\n const pkgPath = require.resolve(\"tree-sitter-wasms/package.json\")\n return path.join(path.dirname(pkgPath), \"out\")\n } catch {\n return null\n }\n}\n\n/**\n * Pre-load all grammars at module-init time so the first search\n * doesn't pay a ~500ms cold-start cost. The Promise is captured at\n * import time and awaited per-call; per-grammar failures are caught\n * individually so one broken grammar can't take the whole tool down.\n */\nfunction getGrammarBundle(): GrammarBundle {\n if (_grammarBundle) return _grammarBundle\n const ready = (async (): Promise<Map<string, Parser.Language>> => {\n const out = new Map<string, Parser.Language>()\n try {\n await Parser.init()\n } catch (err) {\n consola.warn(\n `[code_search] tree-sitter Parser.init failed; structural ranking disabled: ${(err as Error).message}`,\n )\n return out\n }\n const root = resolveGrammarRoot()\n if (!root) {\n consola.warn(\n \"[code_search] tree-sitter-wasms package not resolvable; structural ranking disabled\",\n )\n return out\n }\n for (const [key, filename] of Object.entries(GRAMMAR_FILES)) {\n const wasmPath = path.join(root, filename)\n try {\n const lang = await Parser.Language.load(wasmPath)\n out.set(key, lang)\n } catch (err) {\n consola.warn(\n `[code_search] failed to load tree-sitter grammar '${key}' from ${filename}: ${(err as Error).message}`,\n )\n }\n }\n return out\n })()\n _grammarBundle = { ready }\n return _grammarBundle\n}\n\n// Kick off grammar pre-load at module import time. The brief calls\n// this out explicitly: amortize the WASM init cost across module load\n// rather than the first search call.\nvoid getGrammarBundle().ready.catch(() => {\n /* errors already logged per-grammar */\n})\n\nfunction getLanguageKeyForPath(filePath: string): string | null {\n const ext = path.extname(filePath).toLowerCase()\n return EXTENSION_TO_LANG[ext] ?? null\n}\n\n/**\n * Tree cache. Keyed by canonical file path with mtime gate — on\n * mtime change the cache entry is invalidated (and the old Tree's\n * native memory is freed via `.delete()`). LRU eviction at\n * STRUCTURAL_CACHE_MAX entries; null trees indicate prior failure\n * and short-circuit re-parsing for the same mtime.\n */\ninterface CachedTree {\n mtimeMs: number\n /** null = tried, parse failed (or unsupported language). */\n tree: Parser.Tree | null\n /** Source bytes — we need them at structural-walk time to compute\n * byte offsets from line numbers. Kept alongside the tree so the\n * next call on the same (file, mtime) doesn't re-read. */\n source: string | null\n}\n\nconst _treeCache = new Map<string, CachedTree>()\n\nfunction cacheGet(absPath: string, mtimeMs: number): CachedTree | undefined {\n const cur = _treeCache.get(absPath)\n if (!cur) return undefined\n if (cur.mtimeMs !== mtimeMs) {\n // File changed since cache entry — discard.\n if (cur.tree) {\n try {\n cur.tree.delete()\n } catch {\n // Tree already collected\n }\n }\n _treeCache.delete(absPath)\n return undefined\n }\n // Touch for LRU ordering.\n _treeCache.delete(absPath)\n _treeCache.set(absPath, cur)\n return cur\n}\n\nfunction cachePut(absPath: string, entry: CachedTree): void {\n // Evict oldest if at cap. Map iteration order is insertion order\n // (and we re-insert on access in cacheGet), so the first key is\n // the oldest.\n while (_treeCache.size >= STRUCTURAL_CACHE_MAX) {\n const firstKey = _treeCache.keys().next().value\n if (firstKey === undefined) break\n const evicted = _treeCache.get(firstKey)\n if (evicted?.tree) {\n try {\n evicted.tree.delete()\n } catch {\n // Best effort\n }\n }\n _treeCache.delete(firstKey)\n }\n _treeCache.set(absPath, entry)\n}\n\n/**\n * Compute the absolute byte offset where line `lineNumber1` starts\n * in `source`. Lines are counted by LF; CRLF files have the same\n * line starts as LF files (the \\r is part of the previous line's\n * content, not the line break). `lineNumber1` is 1-indexed to match\n * ripgrep's output. Returns -1 if the line is past EOF.\n */\nfunction lineStartByte(source: string, lineNumber1: number): number {\n if (lineNumber1 <= 1) return 0\n let line = 1\n for (let i = 0; i < source.length; i++) {\n if (source.charCodeAt(i) === 0x0a /* \\n */) {\n line += 1\n if (line === lineNumber1) return i + 1\n }\n }\n return -1\n}\n\n/**\n * Walk up from a matched identifier node looking for the closest\n * definition-shape ancestor (per the language's allowed types). When\n * we find one, verify the matched identifier is at the definition's\n * \"name\" slot — NOT inside a parameter type, a body, or a parent's\n * signature. Returns true iff this is a real definition site for\n * the identifier the rg submatch landed on.\n *\n * The walk has a small depth bound (6) — definition names sit very\n * close to their definition node in every supported grammar; deeper\n * walks risk false positives (e.g., matching `name` inside the body\n * of an enclosing function and concluding \"yes, definition\").\n */\nfunction isDefiningSite(\n matchedNode: Parser.SyntaxNode,\n langKey: string,\n): boolean {\n const defTypes = DEFINITION_NODE_TYPES[langKey]\n if (!defTypes) return false\n let cur: Parser.SyntaxNode | null = matchedNode.parent\n let depth = 0\n while (cur && depth < 6) {\n if (defTypes.has(cur.type)) {\n // Try the language's standard \"name\" field first. Almost all\n // grammars expose this for class/method/function/variable\n // declarations.\n const nameField = cur.childForFieldName(\"name\")\n if (nameField && containsByteRange(nameField, matchedNode)) {\n return true\n }\n // C / C++ function_definition: name lives inside the\n // `declarator` field, possibly nested through pointer or\n // reference declarators. Same trick works for Java\n // field_declaration's `declarator` field.\n const declarator = cur.childForFieldName(\"declarator\")\n if (declarator && containsByteRange(declarator, matchedNode)) {\n // The matched identifier is somewhere in the declarator\n // subtree. Confirm it's the first identifier-leaf — that\n // disambiguates `int foo(int bar)`'s `foo` (definition) from\n // its `bar` (parameter, also inside declarator).\n const first = firstIdentifierLeaf(declarator)\n if (first && first.startIndex === matchedNode.startIndex) {\n return true\n }\n }\n // Rust `impl_item` and Go `type_spec`: the identifier is in\n // the `type` field rather than `name`.\n const typeField = cur.childForFieldName(\"type\")\n if (typeField && containsByteRange(typeField, matchedNode)) {\n const first = firstIdentifierLeaf(typeField)\n if (first && first.startIndex === matchedNode.startIndex) {\n return true\n }\n }\n }\n cur = cur.parent\n depth += 1\n }\n return false\n}\n\nfunction containsByteRange(\n outer: Parser.SyntaxNode,\n inner: Parser.SyntaxNode,\n): boolean {\n return outer.startIndex <= inner.startIndex && outer.endIndex >= inner.endIndex\n}\n\nfunction firstIdentifierLeaf(\n node: Parser.SyntaxNode,\n): Parser.SyntaxNode | null {\n if (IDENTIFIER_NODE_TYPES.has(node.type)) return node\n for (const child of node.namedChildren) {\n const r = firstIdentifierLeaf(child)\n if (r) return r\n }\n return null\n}\n\ninterface StructuralPassResult {\n /** Indexes (into the input hits array) where AST confirmed the\n * matched identifier is at a definition site. */\n confirmedHitIndexes: Set<number>\n /** null = success (entire top-N parsed within budget). String =\n * budget exceeded mid-pass, with explanation suitable for surfacing\n * to the model as the `notice` field (overridden by size-cap notice\n * at the handler boundary when both fire). */\n fallback: string | null\n}\n\n/**\n * Run the structural-confirmation pass over the top-N already-ranked\n * BM25F hits. Wall-clock-bounded — checked between files, not mid-\n * parse (web-tree-sitter@0.22 doesn't expose a usable cancel hook).\n *\n * Per-file failure modes (file too big, language unsupported, parse\n * error, I/O error) are silent: the file's hits keep the regex\n * `symbol_context` heuristic. Only the wall-clock budget fires the\n * user-visible `fallback` message.\n */\nasync function runStructuralPass(opts: {\n hitsRanked: Array<{ hit: RawHit; index: number }>\n workspaceRoot: string\n topN: number\n budgetMs: number\n signal: AbortSignal\n}): Promise<StructuralPassResult> {\n const result: StructuralPassResult = {\n confirmedHitIndexes: new Set(),\n fallback: null,\n }\n if (opts.hitsRanked.length === 0) return result\n\n if (opts.signal.aborted) return result\n\n const grammars = await getGrammarBundle().ready\n if (grammars.size === 0) {\n // No grammars loaded — log already done in getGrammarBundle.\n // Don't surface as user-facing fallback; this is a setup-side\n // failure, not a per-search budget overrun.\n return result\n }\n\n // Group hits by file so we parse each file once across all its\n // hits within the top-N slice.\n const cap = Math.min(opts.hitsRanked.length, opts.topN)\n const byFile = new Map<string, Array<{ hit: RawHit; index: number }>>()\n for (let i = 0; i < cap; i++) {\n const entry = opts.hitsRanked[i]\n const list = byFile.get(entry.hit.file) ?? []\n list.push(entry)\n byFile.set(entry.hit.file, list)\n }\n\n const t0 = Date.now()\n let filesParsed = 0\n let parsersUsed = new Map<string, Parser>()\n\n try {\n for (const [relFile, entries] of byFile) {\n if (opts.signal.aborted) break\n const elapsed = Date.now() - t0\n if (elapsed >= opts.budgetMs) {\n result.fallback =\n `structural budget exceeded after parsing ${filesParsed}/${cap} hits; ` +\n `retry with structural: \"topN\" or narrow your query`\n break\n }\n\n const langKey = getLanguageKeyForPath(relFile)\n if (!langKey) continue // unsupported extension — silent skip\n const lang = grammars.get(langKey)\n if (!lang) continue // grammar failed to load — silent skip\n\n const absPath = path.join(opts.workspaceRoot, relFile)\n let mtimeMs: number\n let size: number\n try {\n const st = statSync(absPath)\n mtimeMs = st.mtimeMs\n size = st.size\n } catch (err) {\n consola.debug(\n `[code_search] structural skip ${relFile} (stat failed: ${(err as Error).message})`,\n )\n continue\n }\n if (size > STRUCTURAL_MAX_FILE_BYTES) {\n consola.debug(\n `[code_search] structural skip ${relFile} (${size} bytes > cap)`,\n )\n continue\n }\n\n let cached = cacheGet(absPath, mtimeMs)\n if (!cached) {\n let source: string\n try {\n source = readFileSync(absPath, \"utf8\")\n } catch (err) {\n consola.debug(\n `[code_search] structural skip ${relFile} (read failed: ${(err as Error).message})`,\n )\n cachePut(absPath, { mtimeMs, tree: null, source: null })\n continue\n }\n let parser = parsersUsed.get(langKey)\n if (!parser) {\n parser = new Parser()\n parser.setLanguage(lang)\n parsersUsed.set(langKey, parser)\n }\n let tree: Parser.Tree | null = null\n try {\n tree = parser.parse(source)\n } catch (err) {\n consola.debug(\n `[code_search] tree-sitter parse failed for ${relFile}: ${(err as Error).message}`,\n )\n }\n cached = { mtimeMs, tree, source: tree ? source : null }\n cachePut(absPath, cached)\n filesParsed += 1\n }\n\n if (!cached.tree || !cached.source) continue\n\n // Walk every hit's matched identifier in this file.\n for (const entry of entries) {\n const lineStart = lineStartByte(cached.source, entry.hit.line)\n if (lineStart < 0) continue\n const matchByteStart = lineStart + entry.hit.match_start\n const matchByteEnd = lineStart + entry.hit.match_end\n let node: Parser.SyntaxNode | null\n try {\n node = cached.tree.rootNode.descendantForIndex(\n matchByteStart,\n matchByteEnd,\n )\n } catch {\n node = null\n }\n if (!node) continue\n // Climb to the nearest identifier-typed node, since\n // descendantForIndex may land on a parent for off-by-one\n // byte ranges in CRLF files.\n if (!IDENTIFIER_NODE_TYPES.has(node.type)) {\n let cur: Parser.SyntaxNode | null = node\n let depth = 0\n while (cur && !IDENTIFIER_NODE_TYPES.has(cur.type) && depth < 3) {\n // Try descending to an identifier leaf at the match\n // start before climbing — handles the case where the\n // grammar wraps the identifier in e.g. shorthand_*\n // patterns.\n const leaf = firstIdentifierLeaf(cur)\n if (leaf && leaf.startIndex === matchByteStart) {\n cur = leaf\n break\n }\n cur = cur.parent\n depth += 1\n }\n node = cur\n }\n if (!node || !IDENTIFIER_NODE_TYPES.has(node.type)) continue\n if (isDefiningSite(node, langKey)) {\n result.confirmedHitIndexes.add(entry.index)\n }\n }\n }\n } finally {\n // Tree-sitter Parser instances are reusable and we don't hold\n // them across calls; freeing keeps native memory clean.\n for (const parser of parsersUsed.values()) {\n try {\n parser.delete()\n } catch {\n // Best effort\n }\n }\n parsersUsed = new Map()\n }\n\n return result\n}\n\ninterface FieldTexts {\n match_line: string\n context: string\n file_path: string\n symbol_context: string\n}\n\nfunction extractFields(hit: RawHit, astConfirmed: boolean): FieldTexts {\n const ctx = [...hit.context_before, ...hit.context_after].join(\"\\n\")\n let symbolContext: string\n if (astConfirmed) {\n // Tree-sitter confirmed: this is a real identifier-definition\n // site. Populate `symbol_context` with the matched identifier\n // text so the BM25F field-weight (2.5x) fires for this hit even\n // when the regex heuristic would have left the field empty —\n // the live correctness fix described in the brief.\n const ident = hit.matched_line.slice(hit.match_start, hit.match_end)\n // Guard: rg submatch offsets can be empty / out-of-range for\n // multiline matches — fall back to the matched line so we still\n // get a non-empty field.\n symbolContext = ident.length > 0 ? ident : hit.matched_line\n } else if (SYMBOL_REGEX.test(hit.matched_line.trimStart())) {\n // Regex heuristic remains in place for hits the AST hasn't\n // confirmed (top-N spillover, unsupported language, parse\n // error, budget overrun). Same field shape as v1.\n symbolContext = hit.matched_line\n } else {\n symbolContext = \"\"\n }\n return {\n match_line: hit.matched_line,\n context: ctx,\n file_path: hit.file.replace(/[/\\\\]/g, \" \"),\n symbol_context: symbolContext,\n }\n}\n\ninterface ScoredHit {\n hit: RawHit\n score: number\n field_contributions: Record<string, number>\n}\n\n/**\n * BM25F score for the given hit set against the tokenized query.\n *\n * BM25F(q, f) = Σ_t IDF(t) · w_t,f / (w_t,f + k1)\n *\n * w_t,f = Σ_field b_field · tf_t,field,f /\n * ((1 − l_field) + l_field · len_field,f/avglen_field)\n *\n * IDF(t) = log( (M − df(t) + 0.5) / (df(t) + 0.5) )\n *\n * Corpus stats are derived from the rg hit set itself — we have no\n * persistent index. M = number of files in the hit set; df(t) = how\n * many of those files contain token `t` in any field; avglen_f =\n * mean tokenized length of field `f` across those files. This is\n * the \"compute corpus stats per-call\" pattern, which works because\n * M ≤ a few hundred files in practice (sub-second).\n */\nfunction bm25fScore(\n hits: ReadonlyArray<RawHit>,\n queryTokens: ReadonlyArray<string>,\n /**\n * Indexes (into `hits`) for which tree-sitter has confirmed the\n * matched identifier sits at a real definition site. Drives the\n * `extractFields` symbol_context override. Pass `undefined` (or an\n * empty Set) to score with the regex heuristic only — matches the\n * v1 behavior, used as the first pass before structural ranking\n * runs.\n */\n astConfirmedHits?: ReadonlySet<number>,\n): Array<ScoredHit> {\n if (hits.length === 0 || queryTokens.length === 0) {\n return hits.map((h) => ({\n hit: h,\n score: 0,\n field_contributions: {\n match_line: 0,\n symbol_context: 0,\n file_path: 0,\n context: 0,\n },\n }))\n }\n\n // Per-file tokenization (cache by file path to avoid re-tokenizing\n // the same path across multiple hits in one file).\n const fileTokenCache = new Map<string, FieldTexts>()\n const perHitTokens: Array<Record<keyof FieldTexts, Array<string>>> = []\n for (let i = 0; i < hits.length; i++) {\n const hit = hits[i]\n const confirmed = astConfirmedHits?.has(i) ?? false\n const fields = extractFields(hit, confirmed)\n fileTokenCache.set(hit.file, fields)\n perHitTokens.push({\n match_line: tokenize(fields.match_line),\n context: tokenize(fields.context),\n file_path: tokenize(fields.file_path),\n symbol_context: tokenize(fields.symbol_context),\n })\n }\n\n // Distinct files for IDF.\n const filesSeen = new Set<string>()\n for (const hit of hits) filesSeen.add(hit.file)\n const M = filesSeen.size\n\n // df(t) per query token: number of distinct files where ANY field\n // contains t. We compute over the hit set; this is the per-call\n // corpus.\n const df = new Map<string, number>()\n const fileTokensByField: Record<keyof FieldTexts, Map<string, Set<string>>> = {\n match_line: new Map(),\n context: new Map(),\n file_path: new Map(),\n symbol_context: new Map(),\n }\n // First pass: build file → token-set per field, so df is per-file\n // not per-hit (multiple hits in one file shouldn't inflate df).\n for (let i = 0; i < hits.length; i++) {\n const file = hits[i].file\n const t = perHitTokens[i]\n for (const fname of Object.keys(t) as Array<keyof FieldTexts>) {\n let bucket = fileTokensByField[fname].get(file)\n if (!bucket) {\n bucket = new Set()\n fileTokensByField[fname].set(file, bucket)\n }\n for (const tok of t[fname]) bucket.add(tok)\n }\n }\n // Now compute df: count files containing the query token in any field.\n for (const qt of queryTokens) {\n const files = new Set<string>()\n for (const fname of Object.keys(fileTokensByField) as Array<keyof FieldTexts>) {\n for (const [file, tokSet] of fileTokensByField[fname]) {\n if (tokSet.has(qt)) files.add(file)\n }\n }\n df.set(qt, files.size)\n }\n\n // avglen per field — across files (one length per file, average them).\n const avglen: Record<keyof FieldTexts, number> = {\n match_line: 0,\n context: 0,\n file_path: 0,\n symbol_context: 0,\n }\n for (const fname of Object.keys(avglen) as Array<keyof FieldTexts>) {\n const lens: Array<number> = []\n const seen = new Set<string>()\n for (let i = 0; i < hits.length; i++) {\n if (seen.has(hits[i].file)) continue\n seen.add(hits[i].file)\n lens.push(perHitTokens[i][fname].length)\n }\n avglen[fname] = lens.length > 0 ? lens.reduce((a, b) => a + b, 0) / lens.length : 1\n if (avglen[fname] === 0) avglen[fname] = 1\n }\n\n // IDF per query token.\n const idf = new Map<string, number>()\n for (const qt of queryTokens) {\n const d = df.get(qt) ?? 0\n idf.set(qt, Math.log((M - d + 0.5) / (d + 0.5) + 1)) // +1 keeps IDF positive (Lucene convention)\n }\n\n // Score each hit.\n const out: Array<ScoredHit> = []\n for (let i = 0; i < hits.length; i++) {\n const tokens = perHitTokens[i]\n const contributions: Record<string, number> = {\n match_line: 0,\n symbol_context: 0,\n file_path: 0,\n context: 0,\n }\n for (const qt of queryTokens) {\n // Weighted TF across fields (the BM25F inner sum).\n let w = 0\n const perField: Record<string, number> = {\n match_line: 0,\n symbol_context: 0,\n file_path: 0,\n context: 0,\n }\n for (const fname of Object.keys(FIELD_BOOSTS) as Array<keyof FieldTexts>) {\n const tf = tokens[fname].filter((t) => t === qt).length\n if (tf === 0) continue\n const len = tokens[fname].length || 1\n const l = FIELD_LEN_NORMS[fname]\n const norm = 1 - l + l * (len / (avglen[fname] || 1))\n const fieldContrib = FIELD_BOOSTS[fname] * (tf / norm)\n w += fieldContrib\n perField[fname] = fieldContrib\n }\n if (w === 0) continue\n const termScore = (idf.get(qt) ?? 0) * (w / (w + BM25F_K1))\n // Attribute the term's contribution back to fields\n // proportionally to each field's share of w.\n for (const fname of Object.keys(perField)) {\n const share = perField[fname] / w\n contributions[fname] += termScore * share\n }\n }\n const total = Object.values(contributions).reduce((a, b) => a + b, 0)\n out.push({ hit: hits[i], score: total, field_contributions: contributions })\n }\n\n return out\n}\n\n// ============================================================\n// Shoulder pruning\n// ============================================================\n\ninterface PrunedResult {\n kept: Array<ScoredHit>\n prunedBelowShoulder: number\n}\n\nfunction shoulderPrune(scored: Array<ScoredHit>): PrunedResult {\n if (scored.length === 0) return { kept: [], prunedBelowShoulder: 0 }\n // Sort by score desc, then by (file, line) for determinism.\n scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n if (a.hit.file !== b.hit.file) return a.hit.file < b.hit.file ? -1 : 1\n return a.hit.line - b.hit.line\n })\n const topScore = scored[0].score\n if (topScore <= 0) {\n // No ranking signal — return all (caller will apply limit).\n return { kept: scored, prunedBelowShoulder: 0 }\n }\n const threshold = topScore * SHOULDER_THRESHOLD\n let cut = scored.length\n for (let i = 0; i < scored.length; i++) {\n if (scored[i].score < threshold) {\n cut = i\n break\n }\n }\n return {\n kept: scored.slice(0, cut),\n prunedBelowShoulder: scored.length - cut,\n }\n}\n\n// ============================================================\n// Snippet rendering\n// ============================================================\n\nfunction renderSnippet(hit: RawHit): string {\n const lines = [\n ...hit.context_before,\n hit.matched_line,\n ...hit.context_after,\n ]\n let snippet = lines.join(\"\\n\")\n if (Buffer.byteLength(snippet, \"utf8\") > MAX_SNIPPET_BYTES) {\n // Middle-elide. Preserve start and end so context survives.\n const buf = Buffer.from(snippet, \"utf8\")\n const halfCap = Math.floor((MAX_SNIPPET_BYTES - 16) / 2)\n snippet =\n buf.slice(0, halfCap).toString(\"utf8\") +\n \"\\n... [truncated] ...\\n\" +\n buf.slice(buf.length - halfCap).toString(\"utf8\")\n }\n return snippet\n}\n\n// ============================================================\n// Main entry point\n// ============================================================\n\nexport async function searchCode(\n rawInput: CodeSearchInput,\n externalSignal?: AbortSignal,\n): Promise<CodeSearchResponse> {\n const t0 = Date.now()\n\n const inputErr = validateInputs(rawInput)\n if (inputErr) throw new Error(inputErr)\n\n const ws = validateWorkspace(rawInput.workspace)\n if (!ws.ok || !ws.canonical) {\n throw new Error(ws.error ?? \"workspace validation failed\")\n }\n\n const mode = rawInput.mode ?? \"ranked\"\n const structuralMode = rawInput.structural ?? \"full\"\n const limit = rawInput.limit ?? DEFAULT_LIMIT\n const contextLines = Math.min(\n rawInput.context_lines ?? DEFAULT_CONTEXT_LINES,\n MAX_CONTEXT_LINES,\n )\n\n // Identifier skeleton-form expansion. When the user's query is a\n // single identifier in any of the five canonical conventions, we\n // expand to all of them and feed rg a regex alternation. This is\n // the live-correctness fix for \"rg getUserName\" not finding\n // get_user_name. Regex mode is excluded — the user is explicit\n // about regex semantics there.\n const expansion =\n mode === \"regex\" ? null : expandIdentifierVariants(rawInput.query)\n const expansionPattern = expansion\n ? buildExpansionPattern(expansion)\n : undefined\n\n // Local AbortController combines: external signal, wall-time, and\n // any internal short-circuits (stdout cap, global limit). Single\n // place to fire abort from.\n const ac = new AbortController()\n const onExternal = (): void => ac.abort(\"external\")\n if (externalSignal) {\n if (externalSignal.aborted) ac.abort(\"external\")\n else externalSignal.addEventListener(\"abort\", onExternal, { once: true })\n }\n const wallTimer = setTimeout(() => ac.abort(\"timeout\"), WALL_TIME_MS)\n wallTimer.unref()\n\n let parseResult: ParseResult\n let rgResolution: RipgrepResolution\n try {\n rgResolution = resolveRipgrep()\n } catch (err) {\n clearTimeout(wallTimer)\n if (externalSignal) externalSignal.removeEventListener(\"abort\", onExternal)\n throw err\n }\n\n const args = buildRgArgs({\n mode,\n fileGlob: rawInput.file_glob,\n contextLines,\n query: rawInput.query,\n expansionPattern,\n })\n\n let child: ChildProcess\n try {\n child = spawn(rgResolution.rgPath, args, {\n cwd: ws.canonical, // TOCTOU mitigation: kernel-level pin\n shell: false, // never via shell\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n })\n } catch (err) {\n clearTimeout(wallTimer)\n if (externalSignal) externalSignal.removeEventListener(\"abort\", onExternal)\n throw new Error(`failed to spawn ripgrep: ${(err as Error).message}`)\n }\n\n // Capture stderr as text (bounded to 64KB — rg errors are short,\n // but the existing 1MB byte cap stays as a runaway-input guard).\n // We surface stderr on exit code 2 so model gets actionable errors\n // (e.g. regex compile failures) rather than empty results.\n const STDERR_TEXT_CAP = 64 * 1024\n let stderrBytes = 0\n let stderrText = \"\"\n if (child.stderr) {\n child.stderr.setEncoding(\"utf8\")\n child.stderr.on(\"data\", (chunk: string) => {\n stderrBytes += chunk.length\n if (stderrText.length < STDERR_TEXT_CAP) {\n stderrText = (stderrText + chunk).slice(0, STDERR_TEXT_CAP)\n }\n if (stderrBytes > 1024 * 1024) {\n // 1MB stderr is excessive — kill.\n ac.abort(\"stderr_cap\")\n }\n })\n }\n\n // Track rg's exit code so we can distinguish \"no matches\" (code 1)\n // from a real error (code 2: bad regex, IO failure after our\n // workspace validation, etc.) Per `man rg`:\n // 0 = matches found\n // 1 = no matches (not an error)\n // 2 = error (regex, IO, ...)\n let exitCode: number | null = null\n const exitPromise = new Promise<void>((resolve) => {\n child.on(\"close\", (code) => {\n exitCode = code\n resolve()\n })\n })\n\n try {\n parseResult = await parseRgJsonStream(child, {\n limit,\n contextLines,\n signal: ac.signal,\n })\n } finally {\n clearTimeout(wallTimer)\n if (externalSignal) externalSignal.removeEventListener(\"abort\", onExternal)\n if (!child.killed) killChild(child)\n }\n\n // If the abort was due to timeout/cap/external, surface that.\n if (ac.signal.aborted && parseResult.hits.length === 0) {\n const reason = String(ac.signal.reason ?? \"aborted\")\n throw new Error(`code_search aborted (${reason})`)\n }\n\n // Surface rg errors (regex compile failures, etc.) to the caller.\n // Exit code 2 means \"rg encountered an error\" — typically a malformed\n // regex in mode=\"regex\". Without this, an invalid regex returns\n // empty results with no indication of why; the model can't tell\n // \"no matches\" from \"your pattern is broken.\" We re-check\n // !signal.aborted so timeout/cap-driven aborts (which also produce\n // non-zero exit) keep their existing error path above.\n //\n // Await rg's full exit before reading exitCode — the parseRgJsonStream\n // for-await terminates on stdout EOF, which may slightly precede the\n // child's 'close' event in Node's event-loop ordering.\n if (!ac.signal.aborted) {\n await exitPromise\n }\n if (\n exitCode !== null &&\n exitCode !== 0 &&\n exitCode !== 1 &&\n !ac.signal.aborted &&\n parseResult.hits.length === 0\n ) {\n const trimmed = stderrText.trim()\n const detail =\n trimmed.length > 0\n ? trimmed.replace(/^rg:\\s*/i, \"\").slice(0, 600)\n : `ripgrep exited with code ${exitCode}`\n throw new Error(`code_search: ${detail}`)\n }\n\n // Apply ranking.\n let kept: Array<ScoredHit>\n let prunedBelowShoulder: number | undefined\n let notice: string | null = null\n if (mode === \"ranked\") {\n const queryTokens = tokenize(rawInput.query)\n // Pass 1: regex-only BM25F. Cheap and gives us a reliable\n // ordering to pick the top-N for structural confirmation.\n const pass1 = bm25fScore(parseResult.hits, queryTokens)\n pass1.sort((a, b) => b.score - a.score)\n const topN =\n structuralMode === \"topN\" ? STRUCTURAL_TOPN_FAST : STRUCTURAL_TOPN_FULL\n // Build (hit, original-index) entries for the top-N. The index\n // is into `parseResult.hits` so the AST-confirmed set lines up\n // with the pass-2 scoring loop.\n const indexByHit = new Map<RawHit, number>()\n for (let i = 0; i < parseResult.hits.length; i++) {\n indexByHit.set(parseResult.hits[i], i)\n }\n const hitsRanked = pass1\n .slice(0, Math.min(topN, pass1.length))\n .map((sh) => ({ hit: sh.hit, index: indexByHit.get(sh.hit) ?? -1 }))\n .filter((e) => e.index >= 0)\n\n const structural = await runStructuralPass({\n hitsRanked,\n workspaceRoot: ws.canonical,\n topN,\n budgetMs: STRUCTURAL_BUDGET_MS,\n signal: ac.signal,\n })\n notice = structural.fallback\n\n // Pass 2: re-score with AST confirmation. Corpus stats are\n // re-computed against the structurally-enriched symbol_context\n // fields so token IDFs reflect the new field contents.\n const pass2 = bm25fScore(\n parseResult.hits,\n queryTokens,\n structural.confirmedHitIndexes,\n )\n const pruned = shoulderPrune(pass2)\n kept = pruned.kept.slice(0, limit)\n prunedBelowShoulder = pruned.prunedBelowShoulder\n } else {\n // Literal / regex: ripgrep document order, no scoring.\n kept = parseResult.hits.map((h) => ({\n hit: h,\n score: 0,\n field_contributions: {} as Record<string, number>,\n }))\n }\n\n // Render output hits. rg paths are already relative to cwd\n // (we spawned with target \".\") so no extra resolution needed.\n // Strip the leading \"./\" or \".\\\" that rg prepends when target=\".\".\n // Then normalize separators to \"/\" so output is platform-agnostic\n // (Windows rg returns \"src\\foo.ts\"; models and tests expect \"/\").\n const results: Array<CodeSearchHit> = kept.map((sh) => {\n let file = sh.hit.file\n if (file.startsWith(\"./\") || file.startsWith(\".\\\\\")) {\n file = file.slice(2)\n }\n file = file.replace(/\\\\/g, \"/\")\n const baseHit: CodeSearchHit = {\n file,\n line: sh.hit.line,\n snippet: renderSnippet(sh.hit),\n match_byte_range: [sh.hit.match_start, sh.hit.match_end],\n }\n if (mode === \"ranked\") {\n baseHit.score = round4(sh.score)\n baseHit.field_contributions = {\n match_line: round4(sh.field_contributions.match_line ?? 0),\n symbol_context: round4(sh.field_contributions.symbol_context ?? 0),\n file_path: round4(sh.field_contributions.file_path ?? 0),\n context: round4(sh.field_contributions.context ?? 0),\n }\n } else {\n baseHit.field_contributions = null\n }\n return baseHit\n })\n\n const elapsed_ms = Date.now() - t0\n\n // Telemetry breadcrumb. Per LOW-17: don't log raw query or\n // absolute paths unless explicitly enabled.\n const debugLog = process.env.GH_ROUTER_DEBUG_CODE_SEARCH === \"1\"\n consola.info(\n `[code_search] mode=${mode} structural=${structuralMode} ` +\n `expansion=${expansion ? expansion.length : 0} ` +\n `results=${results.length} truncated=${parseResult.truncated} ` +\n `scanned_files=${parseResult.scannedFiles} elapsed_ms=${elapsed_ms} ` +\n `abort=${parseResult.cancelled} rg=${rgResolution.source} ` +\n `notice=${notice ? \"yes\" : \"no\"}` +\n (debugLog ? ` query=\"${rawInput.query}\" workspace=\"${ws.canonical}\"` : \"\"),\n )\n\n return {\n results,\n truncated: parseResult.truncated,\n pruned_below_shoulder: mode === \"ranked\" ? prunedBelowShoulder : undefined,\n scanned_files: parseResult.scannedFiles,\n elapsed_ms,\n ranking:\n mode === \"ranked\"\n ? {\n algorithm: \"BM25F\",\n citation: \"Robertson, Zaragoza, Taylor 2004\",\n k1: BM25F_K1,\n }\n : { algorithm: \"ripgrep_document_order\" },\n notice,\n }\n}\n\nfunction round4(x: number): number {\n return Math.round(x * 10000) / 10000\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\nimport { z } from \"zod\"\n\nimport { copilotBaseUrl, copilotVersion } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { sleep } from \"~/lib/utils\"\n\nexport interface WebSearchResult {\n content: string\n references: Array<{ title: string; url: string }>\n}\n\nconst RpcSchema = z.object({\n jsonrpc: z.literal(\"2.0\"),\n id: z.number().optional(),\n result: z\n .object({\n content: z\n .array(z.object({ type: z.literal(\"text\"), text: z.string() }))\n .optional(),\n isError: z.boolean().optional(),\n })\n .optional(),\n error: z\n .object({ code: z.number(), message: z.string() })\n .optional(),\n})\n\nconst InnerSchema = z.object({\n text: z.object({\n value: z.string(),\n // Upstream sometimes returns `null` instead of an absent field for the\n // no-results case. `.nullable().optional()` accepts undefined, null,\n // and a real array; readers must `?? []` before iterating.\n annotations: z\n .array(\n z.object({\n url_citation: z\n .object({ title: z.string(), url: z.string() })\n .optional(),\n }),\n )\n .nullable()\n .optional(),\n }),\n bing_searches: z.array(z.unknown()).nullable().optional(),\n})\n\nconst MAX_SEARCHES_PER_SECOND = 3\nlet searchTimestamps: Array<number> = []\n\n// Single-flight chain serializes throttle checks. Without this, two\n// concurrent searches can both read the timestamp array, both filter,\n// both skip the await, and both push — doubling the QPS the throttle\n// is supposed to enforce.\nlet throttleChain: Promise<void> = Promise.resolve()\n\nasync function throttleSearch(): Promise<void> {\n const myTurn = throttleChain.then(async () => {\n const now = Date.now()\n searchTimestamps = searchTimestamps.filter((t) => now - t < 1000)\n if (searchTimestamps.length >= MAX_SEARCHES_PER_SECOND) {\n const waitMs = 1000 - (now - searchTimestamps[0])\n if (waitMs > 0) {\n consola.debug(`Web search rate limited, waiting ${waitMs}ms`)\n await sleep(waitMs)\n }\n }\n searchTimestamps.push(Date.now())\n })\n throttleChain = myTurn.catch(() => {\n // errors don't break the chain — next caller starts fresh\n })\n return myTurn\n}\n\nfunction mcpHeaders(sid?: string): Record<string, string> {\n if (!state.githubToken) {\n throw new Error(\n \"GitHub token missing — re-run auth flow. Web search uses the GitHub PAT (not the Copilot token); the on-disk token at ~/.local/share/github-router/github_token must be present.\",\n )\n }\n // Match the GitHubCopilotChat/<version> User-Agent the rest of the\n // router sends (see api-config.ts:32). Sending \"github-router/<version>\"\n // breaks the VS-Code-stealth posture and broadcasts our identity to the\n // MCP server.\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.githubToken}`,\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n \"X-MCP-Host\": \"copilot-cli\",\n \"X-MCP-Toolsets\": \"web_search\",\n \"Mcp-Protocol-Version\": \"2025-06-18\",\n \"user-agent\": `GitHubCopilotChat/${copilotVersion(state)}`,\n }\n if (sid) headers[\"Mcp-Session-Id\"] = sid\n return headers\n}\n\nasync function postMcp(\n body: unknown,\n sid?: string,\n retry = true,\n): Promise<Response> {\n const url = `${copilotBaseUrl(state)}/mcp`\n const res = await fetch(url, {\n method: \"POST\",\n headers: mcpHeaders(sid),\n body: JSON.stringify(body),\n })\n if (!res.ok && retry && res.status >= 500) {\n await sleep(500)\n return postMcp(body, sid, false)\n }\n return res\n}\n\nexport async function searchWeb(query: string): Promise<WebSearchResult> {\n await throttleSearch()\n consola.info(`Web search (MCP): \"${query.slice(0, 80)}\"`)\n\n const callId = Math.floor(Math.random() * 1_000_000_000)\n let sid: string | undefined\n\n try {\n // 1. initialize\n const initRes = await postMcp({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"initialize\",\n params: {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n // Identify as the Copilot Chat extension, mirroring the User-Agent\n // and editor-plugin-version we send on every other request.\n clientInfo: {\n name: \"GitHubCopilotChat\",\n version: copilotVersion(state),\n },\n },\n })\n if (!initRes.ok) {\n consola.error(\"MCP initialize failed\", initRes.status)\n throw new HTTPError(\"MCP initialize failed\", initRes)\n }\n sid = initRes.headers.get(\"mcp-session-id\") ?? undefined\n if (!sid) {\n throw new HTTPError(\n \"MCP initialize: missing Mcp-Session-Id header\",\n initRes,\n )\n }\n\n // 2. notifications/initialized — server returns 202 (no body)\n const notifRes = await postMcp(\n { jsonrpc: \"2.0\", method: \"notifications/initialized\" },\n sid,\n )\n if (!notifRes.ok && notifRes.status !== 202) {\n consola.error(\"MCP notifications/initialized failed\", notifRes.status)\n throw new HTTPError(\"MCP notifications/initialized failed\", notifRes)\n }\n\n // 3. tools/call web_search — SSE stream of JSON-RPC events; match by id\n const callRes = await postMcp(\n {\n jsonrpc: \"2.0\",\n id: callId,\n method: \"tools/call\",\n params: {\n name: \"web_search\",\n arguments: { query },\n },\n },\n sid,\n )\n if (!callRes.ok) {\n consola.error(\"MCP tools/call failed\", callRes.status)\n throw new HTTPError(\"MCP tools/call failed\", callRes)\n }\n\n let rpc: z.infer<typeof RpcSchema> | undefined\n for await (const ev of events(callRes)) {\n if (!ev.data) continue\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(ev.data)\n } catch {\n continue\n }\n const parsed = RpcSchema.safeParse(parsedJson)\n if (parsed.success && parsed.data.id === callId) {\n rpc = parsed.data\n break\n }\n }\n if (!rpc) {\n throw new HTTPError(\n \"MCP tools/call: no matching response id in SSE stream\",\n callRes,\n )\n }\n if (rpc.error) {\n throw new HTTPError(\n `MCP error ${rpc.error.code}: ${rpc.error.message}`,\n callRes,\n )\n }\n if (rpc.result?.isError) {\n throw new HTTPError(\"MCP web_search tool error\", callRes)\n }\n\n const text = rpc.result?.content?.[0]?.text\n if (!text) {\n throw new HTTPError(\"MCP web_search: empty content\", callRes)\n }\n\n let innerRaw: unknown\n try {\n innerRaw = JSON.parse(text)\n } catch (err) {\n throw new HTTPError(\n `MCP web_search: inner content not JSON: ${err instanceof Error ? err.message : String(err)}`,\n callRes,\n )\n }\n // safeParse: a raw ZodError thrown here would bypass forwardError's\n // HTTPError check and surface as a generic 500 instead of an Anthropic\n // shape error. Wrap explicitly.\n const innerParsed = InnerSchema.safeParse(innerRaw)\n if (!innerParsed.success) {\n throw new HTTPError(\n `MCP web_search: inner content shape changed (${innerParsed.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")})`,\n callRes,\n )\n }\n const inner = innerParsed.data\n\n const references: Array<{ title: string; url: string }> = []\n for (const ann of inner.text.annotations ?? []) {\n const cite = ann.url_citation\n if (cite && !cite.url.toLowerCase().includes(\"bing.com/search\")) {\n references.push({ title: cite.title, url: cite.url })\n }\n }\n\n consola.debug(`Web search returned ${references.length} references`)\n return { content: inner.text.value, references }\n } finally {\n if (sid) {\n // Best-effort session teardown — never throw. Wrap header construction\n // in try{} too: if state.githubToken cleared between init and finally,\n // mcpHeaders(sid) throws synchronously BEFORE fetch is called and\n // .catch() never attaches, which would mask the original error.\n try {\n void fetch(`${copilotBaseUrl(state)}/mcp`, {\n method: \"DELETE\",\n headers: mcpHeaders(sid),\n }).catch(() => {\n // ignore\n })\n } catch {\n // mcpHeaders threw (token cleared); skip teardown\n }\n }\n }\n}\n","/**\n * Peer-model persona specifications.\n *\n * The github-router proxy hosts a `/mcp` endpoint that exposes these\n * personas as MCP tools, and the `claude` subcommand wires them as\n * Claude Code subagents via `--agents` so Opus 4.7 can delegate\n * blind-spot-busting work to gpt-5.5, gpt-5.3-codex, and\n * gemini-3.1-pro-preview without leaving the session.\n *\n * Design contract (from the approved plan):\n *\n * 1. Persona text is a STABLE string. Never construct per-call —\n * gpt-5.x prompt caching reuses the prefix across invocations.\n * 2. Calibrated grading replaces \"force one disagreement.\" Silence\n * on good work is the signal Opus needs.\n * 3. End-of-prompt self-reminder beats start-of-prompt for\n * sustained behavioral fidelity in long sessions.\n * 4. Description fields differentiate routing — Opus picks a\n * persona largely from its `description`.\n * 5. Cold-start brief contract: subagent contexts are blank;\n * the persona prompt teaches the lead what to paste.\n */\n\nimport { searchCode } from \"./code-search\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\n/**\n * Reasoning effort levels accepted by Copilot's /v1/responses (gpt-5.x) and\n * /v1/chat/completions endpoints. Per the proxy's existing thinking-mode\n * translator (CLAUDE.md \"Thinking-mode translation\"), Copilot's adaptive-\n * thinking path uses these same buckets:\n * <2k tokens → low, <8k → medium, <24k → high, else → xhigh.\n *\n * Per-persona `allowedEfforts` and `defaultEffort` constrain which subset\n * each persona exposes — enforced in handler.ts:handleToolsCall.\n *\n * **xhigh on long-running personas works via SSE-streamed /mcp responses**\n * (handler.ts:handleToolsCallSSE). Claude Code's MCP HTTP client honors\n * `text/event-stream` responses without applying the ~60s per-tool-call\n * timer that previously broke xhigh on gpt-5.5 (~56s wall) and\n * claude-opus-4-7 (high+ thinking budgets). All four personas now expose\n * all four effort tiers with `high` default; SSE handles the long tail\n * transparently to the user.\n */\nexport const EFFORT_LEVELS = [\"low\", \"medium\", \"high\", \"xhigh\"] as const\nexport type Effort = (typeof EFFORT_LEVELS)[number]\n\nexport function isEffort(v: unknown): v is Effort {\n return typeof v === \"string\" && (EFFORT_LEVELS as ReadonlyArray<string>).includes(v)\n}\n\nexport interface PersonaSpec {\n /** Subagent identifier in `--agents` JSON (and in Claude Code's UI). */\n agentName: string\n /** Tool name the HTTP MCP backend exposes for this persona. */\n toolNameHttp: string\n /** Copilot-side model id. Verified live against /v1/models at startup. */\n model: string\n /** Upstream endpoint the model speaks. */\n endpoint: \"/v1/responses\" | \"/v1/chat/completions\" | \"/v1/messages\"\n /** Description shown to Opus when picking a subagent. Drives routing. */\n description: string\n /** Persona system prompt — passed as `instructions` (Responses), system message (chat-completions), or `system` (messages). */\n baseInstructions: string\n /** Subagent prompt body that Claude Code uses as the agent's full system prompt. */\n agentPrompt: string\n /** True when the persona can mutate the workspace (only `codex-implementer`). */\n writeCapable: boolean\n /** True when the persona MUST use the HTTP backend (the codex-cli stdio\n * bridge can't run this model). gemini-3.x and claude-opus-4-7 both\n * set this — codex-cli only knows gpt-5/codex models. */\n requiresHttp: boolean\n /** True when the persona's model belongs to a model family that may not\n * be present in Copilot's live `/v1/models` catalog (gemini-critic\n * needs `gemini-3.x-pro` to be served). When true, `personasFor`\n * drops the persona if the catalog lacks the corresponding model.\n * Optional: defaults to false (persona is always registered). Kept\n * separate from `requiresHttp` so a persona can require HTTP without\n * also requiring gemini in the catalog (e.g. opus-critic). */\n requiresGeminiCatalog?: boolean\n /** Effort tiers this persona accepts. Subset of EFFORT_LEVELS. Driven\n * by empirical latency data — see the EFFORT_LEVELS doc above. Tiers\n * outside this list are rejected with a clean RPC_INVALID_PARAMS at\n * the handler layer rather than letting the call fail at the 60s\n * MCP ceiling. */\n allowedEfforts: ReadonlyArray<Effort>\n /** Default effort when the caller omits the arg. MUST appear in\n * `allowedEfforts`. */\n defaultEffort: Effort\n}\n\nconst CRITIC_RUBRIC = `\nApply this grading rubric:\n - Score 1–5 on three axes:\n A. assumption-soundness (are stated assumptions accurate? are unstated ones load-bearing?)\n B. failure-mode coverage (which realistic failure modes are unaddressed?)\n C. alternative-considered (was a meaningfully different approach weighed and rejected with reason?)\n - If every axis scores ≥ 4, reply with the literal string \"no material objection\" and stop. Do not invent issues to satisfy this rubric.\n - Otherwise, the lowest-scoring axis IS your critique. Lead with that single critique; secondary observations may follow as \"additional notes\".\n\nReply format (markdown):\n ## Verdict\n <\"no material objection\" OR a one-sentence summary of the load-bearing critique>\n ## Scores\n - assumption-soundness: <n>/5\n - failure-mode coverage: <n>/5\n - alternative-considered: <n>/5\n ## Critique\n <only when at least one axis < 4 — concrete, specific, actionable>\n ## Additional notes (optional)\n <secondary observations; omit if none>\n\nSelf-reminder (read before every reply):\n Am I still acting as the adversarial critic per the rubric above?\n If I just produced agreement, restart and apply the grading rubric instead.\n Sycophancy is the failure mode I exist to fight; manufactured contrarianism is a different failure of the same shape — do neither.\n`.trim()\n\nconst COLD_START_CONTRACT = `\nCold-start contract for the lead orchestrator (Opus):\n When delegating to me, paste a self-contained brief. I have no access to your scrollback, project memory, or the project tree. Always include:\n (a) the artifact under review verbatim (code/diff/plan text),\n (b) the constraints or \"done\" criteria,\n (c) any prior decisions I should not relitigate.\n If your brief lacks (a), I will reply with a one-line request for the artifact instead of speculating.\n`.trim()\n\nconst CRITIC_BASE = `You are codex-critic, an adversarial reviewer running on gpt-5.5. Your single job is to overcome the lead orchestrator's blind spots — assumptions it didn't notice it was making, failure modes it didn't enumerate, alternatives it didn't consider.\n\nYou are NOT a helpful assistant. You are NOT a coach. Sycophancy is the failure mode you exist to fight. Manufactured contrarianism is a different failure of the same shape — silence on good work is a valid and welcome answer.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nconst GEMINI_CRITIC_BASE = `You are gemini-critic, an adversarial reviewer running on Gemini 3.1 Pro. You exist to provide a second-lab perspective: your training data, RLHF priors, and attention patterns are systematically different from the lead orchestrator's (Opus, Anthropic) and from codex-critic (gpt-5.5, OpenAI). Use that to surface blind spots both miss.\n\nYour strengths the lead may want to draw on:\n - long-context reasoning over large artifacts (the brief may include >50k tokens of context)\n - math, proofs, and formally-stated invariants\n - cross-checking conclusions where codex-critic has already weighed in (the lead may forward you both the artifact and codex-critic's verdict)\n\nYou are NOT a helpful assistant. Sycophancy is the failure mode you exist to fight; do not invent issues to look thorough.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nconst REVIEWER_BASE = `You are codex-reviewer, a line-level code reviewer running on gpt-5.3-codex. You are the code-specialist persona — your job is to read concrete code (diffs, single files, function bodies) and surface bugs, edge cases, security issues, and idiom violations.\n\nYou are not a critic-of-architecture. If the brief is a plan or a high-level design, redirect: \"this looks like architecture review; consider codex-critic or gemini-critic.\" Your tool is the magnifying glass, not the wide-angle lens.\n\n${COLD_START_CONTRACT}\n\nReply format (markdown):\n ## Summary\n <one sentence: clean / N findings / blocking issue>\n ## Findings\n For each:\n ### <severity: info | low | medium | high | critical> — <one-line title>\n - location: <file:line[-line]>\n - issue: <what's wrong, why it matters in this codebase>\n - suggested fix: <minimal change OR \"needs design discussion\">\n Number the findings if there are more than one. List them in severity-descending order (critical first).\n If there are zero findings of any severity, reply only with \"## Summary\\\\nClean review — no findings.\" and stop.\n\nSelf-reminder (read before every reply):\n Am I citing real code at real line numbers in the brief? If a finding doesn't have a concrete file:line citation, drop it.\n Did I rank the finding's severity by impact-in-this-codebase, not by general-principle?\n If everything looks fine, say so cleanly — do not pad with stylistic nitpicks.`\n\nconst IMPLEMENTER_BASE = `You are codex-implementer, a focused implementation specialist running on gpt-5.3-codex with workspace-write access. You execute scoped, well-specified coding tasks end-to-end: read the relevant files, make the change, verify it, report back.\n\nYou are not a planner. If the brief is vague or missing acceptance criteria, ask the lead for the missing piece BEFORE editing anything. A wasted edit is worse than a clarifying question.\n\n${COLD_START_CONTRACT}\n\nWhat \"done\" looks like for an implementation task:\n - Exactly the files specified by the brief have been changed (or you reported back why a different scope was needed).\n - The change is minimal — surrounding cleanup is out of scope unless requested.\n - You ran the relevant test(s) / typecheck / linter for the touched files and report the results.\n - The summary you return enumerates each file changed with a one-line description.\n\nReply format (markdown):\n ## Status\n <complete | needs-clarification | blocked>\n ## Files changed\n - path/one.ts: <one-line description>\n - path/two.ts: <one-line description>\n ## Verification\n <commands run + outcomes>\n ## Notes\n <anything the lead must know to integrate, e.g. follow-ups intentionally not done>\n\nResilience reminder:\n If your session terminates abnormally before \"Status: complete\", the lead will retry once. On recovery, ask the lead to confirm what's already been done before re-applying changes — duplicate edits are worse than a slow restart.`\n\nconst OPUS_CRITIC_BASE = `You are opus-critic, a fresh-context Anthropic-side adversarial reviewer running on Claude Opus 4.7 — the same model and lab as the lead orchestrator that just delegated to you. You are NOT the lead. You did not see the lead's reasoning trace. You only see the brief.\n\nYour job is to spot what the lead missed because of cognitive momentum, sunk-cost on a plan, or motivated reasoning toward a particular fix. Your blind-spot diversification is LIMITED compared to codex-critic (gpt-5.5) and gemini-critic (gemini-3.1-pro) — same training, same lab, same RLHF priors. Use that honestly: don't pretend to find a different perspective when the obvious read is \"the lead got it right.\" Silence on good work is a valid and welcome answer.\n\nSycophancy is the failure mode you exist to fight. Manufactured contrarianism is a different failure of the same shape — do neither.\n\n${COLD_START_CONTRACT}\n\n${CRITIC_RUBRIC}`\n\nexport const PERSONAS_READ: ReadonlyArray<PersonaSpec> = Object.freeze([\n {\n agentName: \"codex-critic\",\n toolNameHttp: \"codex_critic\",\n model: \"gpt-5.5\",\n endpoint: \"/v1/responses\",\n description:\n \"Adversarial second opinion on plans, designs, or code tradeoffs. Backed by gpt-5.5 (OpenAI) — different lab than Opus. Pass artifact verbatim.\",\n baseInstructions: CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: false,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n {\n agentName: \"gemini-critic\",\n toolNameHttp: \"gemini_critic\",\n model: \"gemini-3.1-pro-preview\",\n endpoint: \"/v1/chat/completions\",\n description:\n \"Adversarial second opinion. Backed by gemini-3.1-pro (Google) — third-lab triangulation, strong on long-context and formal reasoning. Pass artifact verbatim.\",\n baseInstructions: GEMINI_CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: true,\n requiresGeminiCatalog: true,\n allowedEfforts: [\"low\", \"medium\", \"high\"] as const,\n defaultEffort: \"high\",\n },\n {\n agentName: \"codex-reviewer\",\n toolNameHttp: \"codex_reviewer\",\n model: \"gpt-5.3-codex\",\n endpoint: \"/v1/responses\",\n description:\n \"Line-level review of a concrete diff or single file. Backed by gpt-5.3-codex (OpenAI) — code-specialist, narrow-scope. Pass artifact verbatim.\",\n baseInstructions: REVIEWER_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n requiresHttp: false,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n {\n agentName: \"opus-critic\",\n toolNameHttp: \"opus_critic\",\n model: \"claude-opus-4-7\",\n endpoint: \"/v1/messages\",\n description:\n \"Adversarial second opinion from a fresh-context Opus 4.7 — cheap same-lab sanity check. Pass artifact verbatim.\",\n baseInstructions: OPUS_CRITIC_BASE,\n agentPrompt: \"\",\n writeCapable: false,\n // requiresHttp: true — codex-cli stdio bridge can't run claude-opus-4-7\n // (it speaks gpt-5/codex only), so opus-critic must always route via\n // HTTP. Distinct from requiresGeminiCatalog (which is false here —\n // claude-opus-4-7 is always in Copilot's catalog for our supported\n // tiers; we don't need a catalog probe to register the persona).\n requiresHttp: true,\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"xhigh\",\n },\n])\n\nexport const PERSONAS_WRITE: ReadonlyArray<PersonaSpec> = Object.freeze([\n {\n agentName: \"codex-implementer\",\n toolNameHttp: \"codex_implementer\",\n model: \"gpt-5.3-codex\",\n endpoint: \"/v1/responses\",\n description:\n \"Targeted implementation of a self-contained coding task. Backed by gpt-5.3-codex with workspace-write access. Pass spec + files verbatim.\",\n baseInstructions: IMPLEMENTER_BASE,\n agentPrompt: \"\",\n writeCapable: true,\n requiresHttp: false,\n // All four tiers supported — long calls stream via SSE.\n allowedEfforts: [\"low\", \"medium\", \"high\", \"xhigh\"] as const,\n defaultEffort: \"high\",\n },\n])\n\n/**\n * Build the agent-prompt body Claude Code uses as the subagent's full\n * system prompt. The prompt fully replaces Claude Code's default system\n * prompt (per Anthropic's subagent docs) so it must be self-sufficient.\n *\n * Two modes branch on `codexCli`:\n * - HTTP backend: subagent calls the per-persona tool\n * `mcp__gh-router-peers__<toolNameHttp>` with `{prompt, context}`;\n * model + instructions are server-baked.\n * - codex-cli backend: subagent calls the single\n * `mcp__codex-cli__codex` tool with `{prompt, model: <persona.model>,\n * base-instructions: <persona.baseInstructions>}`. Gemini stays on\n * HTTP regardless because Codex CLI can't run Gemini.\n */\nexport function buildAgentPrompt(\n persona: PersonaSpec,\n opts: { codexCli: boolean },\n): string {\n const useStdio = opts.codexCli && !persona.requiresHttp\n const toolPath = useStdio\n ? \"mcp__codex-cli__codex\"\n : `mcp__gh-router-peers__${persona.toolNameHttp}`\n\n const invocationBlock = useStdio\n ? [\n `Always invoke the \\`${toolPath}\\` tool with these arguments:`,\n \" - `prompt`: the lead's brief, copied verbatim\",\n ` - \\`model\\`: \"${persona.model}\"`,\n \" - `base-instructions`: the persona text below (paste verbatim, do not paraphrase)\",\n ...(persona.writeCapable\n ? [\n ' - `sandbox`: \"workspace-write\"',\n ' - `approval-policy`: \"on-request\"',\n ]\n : [' - `sandbox`: \"read-only\"']),\n ].join(\"\\n\")\n : [\n `Always invoke the \\`${toolPath}\\` tool with these arguments:`,\n \" - `prompt`: the lead's brief, copied verbatim\",\n \" - `context` (optional): any additional file/diff content the persona needs\",\n \"Do NOT pass model or instructions — they are server-baked into this tool.\",\n ].join(\"\\n\")\n\n return [\n `# Subagent: ${persona.agentName}`,\n \"\",\n persona.baseInstructions,\n \"\",\n \"---\",\n \"\",\n \"## Routing instructions for this subagent\",\n \"\",\n invocationBlock,\n \"\",\n \"When the tool returns, surface its output to the lead verbatim. Do not summarize, paraphrase, or add your own commentary on top — the lead integrates the persona's reply directly.\",\n ].join(\"\\n\")\n}\n\n/**\n * Build the awareness snippet appended to the spawned `claude` session's\n * system prompt via `--append-system-prompt`. Non-prescriptive — Claude\n * sees that the peer tools and advisor exist; *when* to invoke is left\n * to Claude's judgment.\n *\n * Trimmed to <100 tokens by design. The per-tool descriptions are\n * already in Claude's context as MCP tool descriptions (loaded from\n * `tools/list`); the snippet's net-new value is:\n * - the `advisor` mention (built-in, not MCP-discoverable),\n * - the `peer-review-coordinator` fan-out hint,\n * - the \"subagents you spawn inherit these\" claim (the load-bearing\n * UX payoff of the holistic subagent-MCP-inheritance fix).\n *\n * Surface contract (regression-pinned in tests/peer-mcp-personas.test.ts):\n * - Always lists codex_critic, codex_reviewer, opus_critic, advisor,\n * peer-review-coordinator, and the subagent-inheritance fact.\n * - Conditionally lists gemini_critic only when `geminiAvailable`.\n * - Mentions `codex-cli` stdio bridge only when `codexCli`.\n *\n * The snippet is the awareness layer; the auto-invocation triggers\n * (CALL BEFORE / CALL AFTER) remain in each MCP tool's own `description`.\n * The two layers are intentionally complementary — keep the snippet\n * terse and never re-encode the prescriptive triggers here.\n */\nexport function buildPeerAwarenessSnippet(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): string {\n const criticList: Array<string> = [\n \"`codex_critic` (gpt-5.5)\",\n \"`codex_reviewer` (gpt-5.3-codex)\",\n ]\n if (opts.geminiAvailable) {\n criticList.push(\"`gemini_critic` (gemini-3.1-pro)\")\n }\n criticList.push(\"`opus_critic` (Opus 4.7)\")\n\n const codexCliClause = opts.codexCli\n ? \" The `mcp__codex-cli__codex` stdio bridge dispatches to `codex-implementer` for end-to-end coding tasks.\"\n : \"\"\n\n return [\n \"## Peer review and advisor\",\n \"\",\n `Cross-lab peer critics under \\`mcp__gh-router-peers__*\\` — ${criticList.join(\n \", \",\n )} — plus the \\`peer-review-coordinator\\` fan-out subagent, and Claude Code's built-in \\`advisor\\` tool, are available at your discretion for second opinions and adversarial review. Subagents you spawn inherit them.${codexCliClause} Also \\`mcp__gh-router-peers__code_search\\` for accurate ranked code discovery (BM25F + tree-sitter) — prefer it over \\`Grep\\` when finding definitions or call sites.`,\n ].join(\"\\n\")\n}\n\n/** Convenience: every persona that should be registered for the given mode. */\nexport function personasFor(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): Array<PersonaSpec> {\n const result: Array<PersonaSpec> = []\n for (const p of PERSONAS_READ) {\n // Drop personas whose model family is missing from Copilot's live\n // catalog (currently only gemini-critic, gated by `requiresGeminiCatalog`).\n // Decoupled from `requiresHttp` so a persona can require HTTP without\n // also requiring gemini in the catalog (e.g. opus-critic).\n if (p.requiresGeminiCatalog && !opts.geminiAvailable) continue\n result.push(p)\n }\n if (opts.codexCli) {\n for (const p of PERSONAS_WRITE) result.push(p)\n }\n return result\n}\n\n/**\n * Non-persona MCP tools — utility tools exposed alongside the read-only\n * personas. These don't have model/endpoint/effort/baseInstructions because\n * they don't dispatch to a peer LLM; instead they invoke a server-side\n * function (e.g. an upstream MCP relay) and return its output.\n *\n * Registered alongside personas in `handler.ts:toolEntries()` and\n * dispatched by `handler.ts:handleToolsCall` after the persona lookup\n * falls through. They count against the same MAX_INFLIGHT_TOOLS_CALL=8\n * cap (keeps slot accounting symmetric across all `tools/call`s) but\n * skip the per-persona effort gate and the `predictedTooLong` pre-flight\n * cap — those gates only make sense for thinking-budget-bearing peer LLM\n * calls, and non-persona tools have neither an `effort` arg nor that\n * cost surface.\n */\nexport interface NonPersonaMcpTool {\n /** Tool name the HTTP MCP backend exposes for this tool. */\n toolNameHttp: string\n /** Description shown to Opus / displayed in `tools/list`. */\n description: string\n /** JSON-schema for the tool's `arguments` object. */\n inputSchema: Record<string, unknown>\n /**\n * Server-side handler. Receives the raw `arguments` object from the\n * `tools/call` request and an optional AbortSignal that is signalled\n * when a `notifications/cancelled` arrives for this call. Returns an\n * MCP `tool result` envelope (content blocks + optional `isError`).\n */\n handler: (\n args: Record<string, unknown>,\n signal?: AbortSignal,\n ) => Promise<{\n content: Array<{ type: \"text\"; text: string }>\n isError?: boolean\n }>\n}\n\nconst WEB_SEARCH_DESCRIPTION =\n \"Web search via GitHub Copilot's MCP. Prefer over Claude Code's built-in WebSearch — surfaces source URLs you can cite.\"\n\n/**\n * Format a `searchWeb()` result as an MCP-friendly text block. Mirrors\n * the legacy inject format that `injectWebSearchIfNeeded` produces and\n * that downstream models have been trained against — minimal divergence\n * is the safest choice while we have two surfaces sharing `searchWeb()`.\n *\n * Empty references → omit the `## References` section entirely (don't\n * emit a trailing empty header that would tempt the model to invent\n * citations).\n */\nfunction formatWebSearchResult(results: {\n content: string\n references: ReadonlyArray<{ title: string; url: string }>\n}): string {\n if (results.references.length === 0) return results.content\n const refsLine = results.references\n .map((r) => `- [${r.title}](${r.url})`)\n .join(\"\\n\")\n return `${results.content}\\n\\n## References\\n${refsLine}`\n}\n\nexport const NON_PERSONA_MCP_TOOLS: ReadonlyArray<NonPersonaMcpTool> =\n Object.freeze([\n {\n toolNameHttp: \"web_search\",\n description: WEB_SEARCH_DESCRIPTION,\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n additionalProperties: false,\n properties: {\n query: {\n type: \"string\",\n description:\n \"The search query string. Natural-language queries work best — the upstream provider rewrites for the search index.\",\n },\n },\n },\n // The `signal` parameter is part of the contract but unused for\n // now: `searchWeb()` doesn't currently accept an AbortSignal.\n // notifications/cancelled still releases the in-flight slot via\n // the catch path in handler.ts:handleToolsCall, but the underlying\n // upstream MCP fetches keep running until natural completion.\n // Web_search calls are short-lived (a few seconds), so the slot-\n // leak window is small. Plumbing cancellation into searchWeb is a\n // separate scope.\n // TODO: thread AbortSignal into searchWeb() so the upstream Bing-\n // backed fetch tears down on notifications/cancelled (not just the\n // MCP slot). Acceptable for short calls today; revisit if a future\n // search backend has higher tail latency.\n async handler(\n args: Record<string, unknown>,\n _signal?: AbortSignal,\n ): Promise<{\n content: Array<{ type: \"text\"; text: string }>\n isError?: boolean\n }> {\n const query = typeof args.query === \"string\" ? args.query : \"\"\n if (!query) {\n return {\n content: [\n {\n type: \"text\",\n text: \"web_search: arguments.query is required (must be a non-empty string)\",\n },\n ],\n isError: true,\n }\n }\n try {\n const results = await searchWeb(query)\n return {\n content: [\n { type: \"text\", text: formatWebSearchResult(results) },\n ],\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return {\n content: [{ type: \"text\", text: `web_search failed: ${msg}` }],\n isError: true,\n }\n }\n },\n },\n {\n // code_search — proxy-side MCP tool exposing ripgrep + BM25F +\n // tree-sitter structural-aware ranking to all clients (Claude\n // Code, codex, gemini callers). Implementation: src/lib/code-search.ts.\n //\n // SCHEMA + RESPONSE MINIMALITY: this entry is the canonical\n // worked example for the \"ruthlessly minimal MCP tool surface\"\n // principle (docs/peer-mcp-design.md). The handler below trims\n // the rich internal `CodeSearchResponse` to {file, line, snippet}\n // per hit and a tiny top-level envelope. Internal diagnostics\n // (scores, field_contributions, scanned_files, elapsed_ms, the\n // ranking metadata block) are intentionally NOT forwarded — the\n // model cannot act on them, so they would only burn its context.\n // Do NOT re-export them without re-reading the principle section.\n toolNameHttp: \"code_search\",\n description:\n \"Fast structured code search over a local workspace. Returns \" +\n \"ranked, deduplicated hits with snippets. Ranks with BM25F \" +\n \"across matched-line / file-path / surrounding-context / \" +\n \"symbol-context fields, then refines `symbol-context` with \" +\n \"tree-sitter AST analysis on the top hits so identifier \" +\n \"definitions outrank incidental string matches. Prefer this \" +\n \"over Grep/Bash+grep for ranked discovery (\\\"where is X \" +\n \"defined\\\", \\\"which files reference Y\\\", \\\"find code that does \" +\n \"Z\\\") — ranked mode surfaces the few right answers instead of \" +\n \"every match. Use Grep for exact-pattern enumeration when you \" +\n \"need every hit unranked, and Glob for file-name patterns (no \" +\n \"content match). `workspace` is any absolute path the proxy \" +\n \"process can read — typically the project root or a sub-tree \" +\n \"you're working in.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\", \"workspace\"],\n additionalProperties: false,\n properties: {\n query: {\n type: \"string\",\n description:\n \"Search text. In 'ranked' (default) and 'literal' modes, \" +\n \"interpreted as a literal string. In 'regex' mode, \" +\n \"interpreted as a PCRE2 regex. In 'ranked' and 'literal' \" +\n \"modes, single-identifier queries are auto-expanded across \" +\n \"camelCase / snake_case / kebab-case / SCREAMING_SNAKE \" +\n \"skeletons so `getUserName` also matches `get_user_name`.\",\n },\n workspace: {\n type: \"string\",\n description:\n \"Absolute path to the project root (or sub-tree) to search.\",\n },\n mode: {\n type: \"string\",\n enum: [\"ranked\", \"literal\", \"regex\"],\n description:\n \"Ranking mode. 'ranked' (default): BM25F + tree-sitter \" +\n \"structural boost; results ordered by score with shoulder \" +\n \"pruning (drops results below 50% of the top score). \" +\n \"'literal': fixed-string search, ripgrep document order. \" +\n \"'regex': PCRE2 search, ripgrep document order.\",\n },\n file_glob: {\n type: \"string\",\n description: \"Optional ripgrep glob filter (e.g. 'src/**/*.ts').\",\n },\n limit: {\n type: \"number\",\n description: \"Max hits to return (default 20).\",\n },\n structural: {\n type: \"string\",\n enum: [\"full\", \"topN\"],\n description:\n \"Structural-ranking depth (ranked mode only). 'full' \" +\n \"(default) runs tree-sitter on the top 50 BM25F hits — \" +\n \"best signal, fine for typical repos. 'topN' restricts to \" +\n \"the top 10 for tighter latency on very large workspaces. \" +\n \"Both modes share a 200ms wall-clock budget; on budget \" +\n \"exhaustion the response includes `notice` and remaining \" +\n \"hits fall back to the regex symbol heuristic.\",\n },\n },\n },\n async handler(\n args: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<{\n content: Array<{ type: \"text\"; text: string }>\n isError?: boolean\n }> {\n try {\n const result = await searchCode(\n {\n query: typeof args.query === \"string\" ? args.query : \"\",\n workspace:\n typeof args.workspace === \"string\" ? args.workspace : \"\",\n mode:\n args.mode === \"literal\" || args.mode === \"regex\" ||\n args.mode === \"ranked\"\n ? args.mode\n : undefined,\n file_glob:\n typeof args.file_glob === \"string\" ? args.file_glob : undefined,\n limit: typeof args.limit === \"number\" ? args.limit : undefined,\n structural:\n args.structural === \"full\" || args.structural === \"topN\"\n ? args.structural\n : undefined,\n },\n signal,\n )\n // Minimal-surface response shape. See the SCHEMA + RESPONSE\n // MINIMALITY comment above for why these fields and only\n // these fields are forwarded to the model.\n //\n // Response-size cap (256KB): MCP clients can't ingest\n // multi-megabyte tool results in one shot, so a runaway\n // `limit: 1000000` against a hit-heavy repo would produce\n // a blob the model can't actually use. We accumulate hits\n // up to a hard byte budget and surface `notice` when the\n // cap fires so the model knows to narrow its query or\n // lower `limit`. Always returns at least one hit when\n // there are any hits to return (per-hit oversize is\n // bounded separately by `max_snippet_bytes`).\n const SIZE_CAP_BYTES = 256 * 1024\n const trimmedHits: Array<{\n file: string\n line: number\n snippet: string\n }> = []\n let totalBytes = 0\n let sizeCapped = false\n for (const hit of result.results) {\n const next = {\n file: hit.file,\n line: hit.line,\n snippet: hit.snippet,\n }\n const nextBytes = Buffer.byteLength(JSON.stringify(next), \"utf8\")\n if (trimmedHits.length > 0 && totalBytes + nextBytes > SIZE_CAP_BYTES) {\n sizeCapped = true\n break\n }\n trimmedHits.push(next)\n totalBytes += nextBytes\n }\n\n const minimal: {\n results: Array<{ file: string; line: number; snippet: string }>\n truncated: boolean\n notice?: string\n } = {\n results: trimmedHits,\n truncated: result.truncated || sizeCapped,\n }\n // Notice priority: size-cap > structural-budget. Size-cap\n // means the model is missing results entirely and should\n // narrow; structural-budget just means the ranking was\n // less precise but the result set is complete. The size-\n // cap message is the more urgent action.\n if (sizeCapped) {\n minimal.notice =\n `response size limit reached at ${trimmedHits.length} hits ` +\n `(~${Math.round(totalBytes / 1024)}KB); narrow your query ` +\n `or lower 'limit' to get all relevant matches`\n } else if (typeof result.notice === \"string\") {\n minimal.notice = result.notice\n }\n return {\n content: [{ type: \"text\", text: JSON.stringify(minimal) }],\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return {\n content: [{ type: \"text\", text: `code_search failed: ${msg}` }],\n isError: true,\n }\n }\n },\n },\n ])\n","import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nimport { buildCodexProviderConfigFlags } from \"./launch\"\nimport { PATHS, writeRuntimeFileSecure } from \"./paths\"\nimport {\n buildAgentPrompt,\n personasFor,\n type PersonaSpec,\n} from \"./peer-mcp-personas\"\n\nexport type CodexMcpBackend = \"http\" | \"cli\"\n\ninterface ResolveBackendOpts {\n requested: boolean\n codexInfo: { ok: boolean; version?: string } | null\n}\n\n/**\n * Decide which MCP backend serves the codex personas.\n *\n * - User passed `--codex-cli` AND codex 0.129+ is on PATH → \"cli\".\n * The peer config registers `codex-cli` as a stdio MCP server\n * spawning `codex mcp-server`; codex personas route there;\n * gemini-critic stays on the HTTP backend (Codex CLI can't run\n * Gemini).\n * - User passed `--codex-cli` but codex is missing or < 0.129 →\n * fallback to \"http\" with a warning. Never break\n * `github-router claude` over a missing optional dep.\n * - User did not pass `--codex-cli` → \"http\", read-only personas only.\n */\nexport function resolveCodexCliBackend(\n opts: ResolveBackendOpts,\n): CodexMcpBackend {\n if (!opts.requested) return \"http\"\n if (!opts.codexInfo || !opts.codexInfo.ok) {\n const detail = opts.codexInfo?.version\n ? `installed version \"${opts.codexInfo.version}\" is too old (need 0.129+)`\n : \"codex CLI not found on PATH\"\n consola.warn(\n `--codex-cli requested but ${detail}; falling back to HTTP-only Codex MCP backend (codex-implementer will not be registered).`,\n )\n return \"http\"\n }\n return \"cli\"\n}\n\ninterface BuildOpts {\n /** Whether the codex-cli stdio server should be added. */\n codexCli: boolean\n /** Whether gemini-3.1-pro-preview is in the live model catalog. */\n geminiAvailable: boolean\n /** Per-launch nonce for the HTTP /mcp Authorization header. */\n nonce: string\n /** Isolated CODEX_HOME for the stdio child (only used when codexCli). */\n codexHome: string\n}\n\ninterface HttpMcpEntry {\n type: \"http\"\n url: string\n headers: Record<string, string>\n}\n\ninterface StdioMcpEntry {\n command: string\n args: Array<string>\n env: Record<string, string>\n}\n\nexport interface PeerMcpConfig {\n mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry>\n}\n\n/**\n * Build the JSON payload for `claude --mcp-config <path>`.\n *\n * Always registers `gh-router-peers` (HTTP) — that's the home of all\n * read-only personas, and it's the only path Gemini can take. When\n * `codexCli` is true, also registers `codex-cli` (stdio) which spawns\n * `codex mcp-server` with the proxy's provider-config flags so codex\n * runs through our Copilot-routed billing path rather than its\n * default api.openai.com.\n */\nexport function buildPeerMcpConfig(\n serverUrl: string,\n opts: BuildOpts,\n): PeerMcpConfig {\n const mcpServers: Record<string, HttpMcpEntry | StdioMcpEntry> = {\n \"gh-router-peers\": {\n type: \"http\",\n url: `${serverUrl}/mcp`,\n headers: {\n Authorization: `Bearer ${opts.nonce}`,\n },\n },\n }\n\n if (opts.codexCli) {\n mcpServers[\"codex-cli\"] = {\n command: \"codex\",\n args: [\"mcp-server\", ...buildCodexProviderConfigFlags(serverUrl)],\n env: {\n OPENAI_BASE_URL: `${serverUrl}/v1`,\n OPENAI_API_KEY: \"dummy\",\n CODEX_HOME: opts.codexHome,\n },\n }\n }\n\n return { mcpServers }\n}\n\nexport type PeerAgentDefinitions = Record<\n string,\n { description: string; prompt: string }\n>\n\n/**\n * The `peer-review-coordinator` Claude Code subagent — the strongest\n * auto-invocation lever per Phase 2A of the peer-MCP plan. It is NOT a\n * peer-MCP tool; it is a regular Claude Code subagent that runs inside\n * the spawned-claude context, has tool access to the peer MCP tools\n * (codex_critic, codex_reviewer, gemini_critic), and fans out + aggregates.\n *\n * The \"use proactively\" phrase in the description is the documented Claude\n * Code idiom for subagents the parent (Opus) should delegate to without\n * explicit user request — at the natural checkpoints listed below.\n *\n * Empirically the polling-loop reliability for \"use proactively\" is ~60%\n * (claude-code-guide expert estimate); the plan calls for an acceptance\n * test (≥7/10 sessions delegate at the right checkpoints) before declaring\n * \"auto-invoked\". If <7/10 we flip the optional PreToolUse hook on\n * ExitPlanMode to default-on (env-disable-able).\n */\nfunction buildCoordinatorAgent(opts: {\n codexCli: boolean\n geminiAvailable: boolean\n}): { description: string; prompt: string } {\n // opus-critic is always registered (Anthropic models are always present\n // in the Copilot catalog), so no `geminiAvailable`-style guard is\n // required. Order: codex-critic first (cross-lab depth), opus-critic\n // second (cheapest same-lab sanity check), gemini-critic third\n // (third-lab triangulation, only when registered), codex-reviewer\n // last (code-specialist, narrower scope).\n const peers: Array<string> = [\"codex-critic\", \"opus-critic\"]\n if (opts.geminiAvailable) peers.push(\"gemini-critic\")\n peers.push(\"codex-reviewer\")\n\n const description =\n \"Coordinates cross-lab adversarial review across codex-critic, opus-critic, gemini-critic, codex-reviewer. Use proactively before non-trivial plans and after non-trivial commits. Always pass artifacts verbatim — peers are fresh-context.\"\n\n const personaList = peers.map((p) => `- \\`${p}\\``).join(\"\\n\")\n\n const prompt = [\n \"# Subagent: peer-review-coordinator\",\n \"\",\n \"You orchestrate cross-lab adversarial review for the lead orchestrator (Opus). You have access to these peer-MCP subagents:\",\n \"\",\n personaList,\n \"\",\n \"## When the lead invokes you\",\n \"\",\n \"The lead's brief will include an artifact (plan, design, diff, or code) and a goal (e.g. 'review before exit-plan', 'review the commit I just made', 'cross-check codex-critic's verdict'). Pick the right peers for the artifact type:\",\n \"\",\n \"- **Plan / design / architecture choice** → fan out to `codex-critic`\"\n + (opts.geminiAvailable ? \" AND `gemini-critic` in parallel\" : \"\")\n + \". codex-reviewer is the wrong tool for plans (it's a code-specialist, not an architecture critic).\",\n \"- **Concrete diff or single file** → fan out to `codex-reviewer`\"\n + (opts.geminiAvailable ? \" AND `gemini-critic` (gemini for cross-lab triangulation)\" : \"\")\n + \". For very small changes (<20 lines), one `codex-reviewer` call is enough.\",\n \"- **Tie-breaker after codex-critic has weighed in** → call `gemini-critic`\"\n + (opts.geminiAvailable ? \"\" : \" (NOT REGISTERED in this session — gemini-3.x not in catalog; tie-break unavailable)\")\n + \" with the artifact AND codex-critic's verdict for cross-lab cross-check.\",\n \"- **Long-context artifact (>100 KB)** → prefer `gemini-critic`\"\n + (opts.geminiAvailable ? \"\" : \" (NOT REGISTERED in this session)\")\n + \". Otherwise, decompose into 2-4 batches and fan out across `codex-critic` calls in parallel.\",\n \"- **Fast same-lab sanity check on a moderate artifact (<5 KB)** → prefer `opus-critic` (cheapest, ~22s, only `effort: low|medium` supported). Same lab as the lead — limited blind-spot diversification, but a useful gut-check before committing to a controversial decision. For cross-lab diversification or deep dives on larger artifacts, use codex/gemini at higher effort with decomposition for >5KB.\",\n \"\",\n \"## Decomposition for large artifacts\",\n \"\",\n \"Each per-call MCP wait is bounded (~60s SDK default on Claude Code v2.1.113+ per regressions #50289 / #52137 — empirically reproduced 2026-05-14). The proxy enforces per-persona effort allowlists AND a pre-flight `predictedTooLong` cap (codex_critic@high >8 KB, codex_reviewer@high >12 KB, opus_critic@medium >6 KB) to surface would-be-timeouts as fast actionable errors. For artifacts that exceed the cap, split into 2-4 logical batches BY CONCERN (not by raw size — semantic batches give better per-batch reviews) and call peers in parallel. The proxy's MCP cap allows up to 8 in-flight calls. Aggregate findings yourself before reporting back.\",\n \"\",\n \"## Aggregation contract\",\n \"\",\n \"When fan-out completes, return a SEVERITY-GROUPED, DEDUPLICATED finding list. Format:\",\n \"\",\n \" ## Findings\",\n \" ### HIGH\",\n \" 1. <one-line title> — `<file:line>` — sources: codex-critic, gemini-critic (3-lab confirmed if applicable)\",\n \" - bug: <one sentence>\",\n \" - mitigation: <one sentence>\",\n \" ### MEDIUM\",\n \" ...\",\n \" ### LOW\",\n \" ...\",\n \"\",\n \"Cite which peer raised each finding. If two or more peers raised the SAME finding (cross-lab confirmation), call it out — those are the highest-confidence bugs.\",\n \"\",\n \"## What NOT to do\",\n \"\",\n \"- Do not paraphrase or summarize per-peer verdicts BEFORE aggregating; aggregate from the raw verdicts.\",\n \"- Do not invent severity labels not present in the source verdicts.\",\n \"- Do not call peers serially (waste of wall-clock); always fan out in parallel.\",\n \"- Do not consult yourself — you are the coordinator, not a critic.\",\n \"\",\n \"Self-reminder (read before every reply):\",\n \" Did I fan out in parallel to the right peers for this artifact type?\",\n \" Did I aggregate findings by severity, citing which peer raised each?\",\n \" If two peers agreed, did I flag the cross-lab confirmation?\",\n ].join(\"\\n\")\n\n return { description, prompt }\n}\n\n/**\n * Build the JSON payload for `claude --agents <path>`.\n *\n * Always includes the read-only personas applicable to the mode (gemini\n * is dropped if absent from the catalog); adds `codex-implementer` only\n * when `codexCli` is true. Always appends the `peer-review-coordinator`\n * meta-subagent — the strongest \"use proactively\" auto-invocation lever\n * per Phase 2A of the peer-MCP plan.\n */\nexport function buildPeerAgentDefinitions(\n opts: BuildOpts,\n): PeerAgentDefinitions {\n const out: PeerAgentDefinitions = {}\n const personas = personasFor({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n for (const persona of personas) {\n out[persona.agentName] = {\n description: persona.description,\n prompt: buildAgentPrompt(persona, { codexCli: opts.codexCli }),\n }\n }\n out[\"peer-review-coordinator\"] = buildCoordinatorAgent({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n return out\n}\n\nexport interface PeerMcpRuntimeFiles {\n mcpConfigPath: string\n agentsPath: string\n /** .md subagent files written into ~/.claude/agents/ (Phase 2.5). The\n * `--agents` JSON path is silently ignored by Claude Code v2.1.138's\n * Task `subagent_type` enum (the JSON's subagents are only reachable\n * via natural-language delegation). The .md files in the canonical\n * agents directory ARE picked up by the enum, making the\n * peer-review-coordinator + persona subagents directly invokable. */\n agentMdPaths: Array<string>\n nonce: string\n personas: Array<PersonaSpec>\n cleanup: () => Promise<void>\n}\n\ninterface WriteOpts {\n codexCli: boolean\n geminiAvailable: boolean\n /** Override for tests. Defaults to PATHS.CODEX_HOME. */\n codexHome?: string\n /** Override for tests. Defaults to PATHS.CLAUDE_RUNTIME_DIR. */\n runtimeDir?: string\n /** Override for tests. Defaults to a fresh 32-byte hex nonce. */\n nonce?: string\n /** Override for tests. Defaults to ~/.claude/agents (where Claude Code\n * reads subagent .md files at session start). */\n agentsDir?: string\n}\n\n/**\n * Default location Claude Code reads subagent .md files from at session\n * startup. Files placed here populate the Task `subagent_type` enum.\n *\n * We point at the router-owned `PATHS.CLAUDE_CONFIG_DIR/agents/` because\n * `getClaudeCodeEnvVars` sets `CLAUDE_CONFIG_DIR=PATHS.CLAUDE_CONFIG_DIR`\n * (the snapshot-mirror substrate fix that gives spawned teammates an\n * authenticatable on-disk credential). The user's own custom-agent .md\n * files were copied into this same dir by `ensureClaudeConfigMirror`,\n * so writing peer-* files here doesn't conflict — and the boot-time\n * sweep is scoped to peer-* names only via the persona-name allowlist.\n */\nfunction defaultAgentsDir(): string {\n return path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n}\n\n/**\n * YAML frontmatter string-escape — sufficient for our use case where\n * descriptions can contain colons, quotes, newlines. Wraps the value\n * in double-quotes and escapes:\n * - `\\` and `\"` (canonical YAML)\n * - `\\n`, `\\r`, `\\t` (whitespace controls — `\\r` matters on Windows-edited\n * literals; strict YAML 1.2 parsers reject raw `\\r` in double-quoted\n * scalars)\n * - other C0 control chars (\\x00-\\x08, \\x0B, \\x0C, \\x0E-\\x1F) and\n * DEL (\\x7F) — encoded as `\\xNN` so the YAML stays valid even if\n * a future description sources data from an external file\n *\n * NOT a general-purpose YAML serializer; we control the inputs.\n */\nfunction escapeYamlString(s: string): string {\n return (\n `\"${\n s\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, \"\\\\\\\"\")\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\")\n // The point of this regex IS to match control characters so we\n // can replace them with safe `\\xNN` escapes — the lint rule's\n // concern (accidental control-char in regex) doesn't apply here.\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, (c) =>\n `\\\\x${c.charCodeAt(0).toString(16).padStart(2, \"0\")}`,\n )\n }\"`\n )\n}\n\n/**\n * Strict allowlist for subagent names — controls both the YAML\n * frontmatter `name:` field AND the filename suffix. Defense-in-depth:\n * even if a future contributor wires in a dynamic agent name from\n * outside, the validator at the top of `writePeerAgentMdFiles` rejects\n * anything that wouldn't be a safe bare YAML scalar AND a safe path\n * component.\n */\nconst VALID_AGENT_NAME = /^[a-z][a-z0-9-]*$/\n\n/** Build a single subagent .md file body (frontmatter + system prompt). */\nfunction buildAgentMd(spec: { name: string; description: string; prompt: string }): string {\n return [\n \"---\",\n `name: ${spec.name}`,\n `description: ${escapeYamlString(spec.description)}`,\n \"---\",\n \"\",\n spec.prompt,\n \"\",\n ].join(\"\\n\")\n}\n\n/**\n * Write per-launch subagent .md files into the user's `~/.claude/agents/`\n * directory so they appear in Claude Code's Task `subagent_type` enum\n * (which `--agents` JSON files do NOT, per claude-code-guide expert).\n *\n * Filenames follow `peer-<pid>-<rand>-<agentName>.md` so the boot-time\n * sweep (`sweepStalePeerAgentMdFiles` in paths.ts) can drop orphans\n * from crashed prior proxy sessions without touching the user's other\n * `.claude/agents/` files. The `name:` field in the frontmatter is the\n * canonical agent identifier — matching across files would cause Claude\n * Code to (un)deterministically pick one, so concurrent proxies running\n * the same agents need different filenames but resolve to the same\n * agent name (intended — they're the same subagent, just registered\n * twice).\n *\n * Returns the file paths plus a cleanup() that unlinks them.\n */\nexport async function writePeerAgentMdFiles(\n agents: Record<string, { description: string; prompt: string }>,\n opts: { agentsDir?: string; fileSuffix: string },\n): Promise<{ paths: Array<string>; cleanup: () => Promise<void> }> {\n // Validate every agent name BEFORE touching the filesystem. Defense-\n // in-depth against a future contributor wiring in a dynamic name from\n // outside (--agent flag, MCP tool registration, etc.). Names appear\n // in BOTH the filename (path-traversal vector if unvalidated) and the\n // YAML frontmatter `name:` field (parser-confusion if it contains\n // YAML indicator chars). The strict regex matches only safe lowercase\n // identifiers — every current persona/coordinator name passes.\n for (const name of Object.keys(agents)) {\n if (!VALID_AGENT_NAME.test(name)) {\n throw new Error(\n `writePeerAgentMdFiles: invalid agent name ${JSON.stringify(name)} — `\n + `must match ${VALID_AGENT_NAME.source}`,\n )\n }\n }\n const dir = opts.agentsDir ?? defaultAgentsDir()\n await fs.mkdir(dir, { recursive: true })\n const paths: Array<string> = []\n try {\n for (const [name, def] of Object.entries(agents)) {\n const filePath = path.join(dir, `peer-${opts.fileSuffix}-${name}.md`)\n // Same idempotency pattern as the JSON tempfiles: unlink first so\n // O_EXCL succeeds even if a same-suffix file somehow survived.\n await fs.unlink(filePath).catch(() => {})\n await writeRuntimeFileSecure(\n filePath,\n buildAgentMd({ name, description: def.description, prompt: def.prompt }),\n )\n paths.push(filePath)\n }\n } catch (err) {\n // Partial-failure cleanup: if iteration N fails (disk full, EPERM,\n // EEXIST race), the N-1 successfully-written files would otherwise\n // be orphans the caller has no handle to. Unlink the partials before\n // re-throwing so the boot sweep doesn't have to deal with them.\n await Promise.allSettled(paths.map((p) => fs.unlink(p)))\n throw err\n }\n const cleanup = async (): Promise<void> => {\n await Promise.allSettled(paths.map((p) => fs.unlink(p)))\n }\n return { paths, cleanup }\n}\n\nexport type InjectPeerMcpResult =\n | { ok: true; serversAdded: ReadonlyArray<string> }\n | {\n ok: false\n reason: \"user-has-conflicting-entry\"\n conflictingServers: ReadonlyArray<string>\n }\n\ninterface InjectOpts {\n codexCli: boolean\n geminiAvailable: boolean\n /** Per-launch nonce — must match what writePeerMcpRuntimeFiles wrote\n * so the proxy's /mcp Authorization check passes. */\n nonce: string\n /** Override for tests. Defaults to PATHS.CODEX_HOME. */\n codexHome?: string\n /** Override for tests. Defaults to PATHS.CLAUDE_CONFIG_DIR (per-launch). */\n claudeConfigDir?: string\n}\n\n/**\n * Mutate the mirrored `<CLAUDE_CONFIG_DIR>/.claude.json` to add the\n * `gh-router-peers` entry (and `codex-cli` when enabled) under\n * `mcpServers`. This is the load-bearing fix for subagent MCP visibility.\n *\n * Subagents — Agent-tool subagents, forks, and agent-teams subprocesses\n * — discover MCP servers from persistent scopes (`.claude.json` and\n * project-scope `.mcp.json`), NOT from the parent's `--mcp-config` CLI\n * flag. Writing into the per-launch mirror's `.claude.json` makes the\n * MCP entry visible to subagents transparently: they inherit\n * `CLAUDE_CONFIG_DIR` from the parent's env, so they read the same\n * config file we just mutated.\n *\n * Safety:\n * - Refuses to overwrite a same-named user-side entry (the snapshot\n * copied their `.claude.json` first, so an existing entry would\n * belong to the user). Returns `{ ok: false }` so the caller can\n * fall back to leaving `--mcp-config` in place for the parent.\n * - Preserves all other top-level fields and other `mcpServers`\n * entries.\n * - Atomic write: temp-file with `wx` (`O_CREAT | O_EXCL`) followed by\n * `rename`, mirroring the synthetic-credentials write pattern in\n * `ensureClaudeConfigMirror`. Mode 0o600. The per-launch\n * `CLAUDE_CONFIG_DIR` means there are no cross-launch racers.\n */\nexport async function injectPeerMcpIntoMirror(\n serverUrl: string,\n opts: InjectOpts,\n): Promise<InjectPeerMcpResult> {\n const dir = opts.claudeConfigDir ?? PATHS.CLAUDE_CONFIG_DIR\n const target = path.join(dir, \".claude.json\")\n\n // 1. Read existing snapshot (or {} if missing / malformed). We do NOT\n // fail loudly on parse error — start fresh and let the proxy\n // session run. Logging the warn surfaces the underlying corruption\n // for the user to investigate.\n let existing: Record<string, unknown> = {}\n try {\n const raw = await fs.readFile(target, \"utf8\")\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n existing = parsed as Record<string, unknown>\n } else {\n consola.warn(\n `injectPeerMcpIntoMirror: ${target} parsed to non-object `\n + `(typeof=${typeof parsed}); discarding contents and starting fresh.`,\n )\n }\n } catch (err) {\n consola.warn(\n `injectPeerMcpIntoMirror: cannot parse ${target} as JSON; `\n + `starting fresh (existing contents will be overwritten):`,\n err,\n )\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(\n `injectPeerMcpIntoMirror: cannot read ${target}:`,\n err,\n )\n }\n // Either ENOENT (first-ever launch, no user .claude.json) or some\n // other read error. Either way, start fresh.\n }\n\n // 2. Normalize `mcpServers` to an object (clobber if user had it set\n // to a non-object value — that's already broken; our overwrite\n // won't make it worse and the warn flags it).\n let mcpServers: Record<string, unknown>\n const rawServers = existing.mcpServers\n if (\n rawServers !== undefined\n && rawServers !== null\n && typeof rawServers === \"object\"\n && !Array.isArray(rawServers)\n ) {\n mcpServers = rawServers as Record<string, unknown>\n } else {\n if (rawServers !== undefined && rawServers !== null) {\n consola.warn(\n `injectPeerMcpIntoMirror: mcpServers field in ${target} is not an `\n + `object (typeof=${typeof rawServers}); replacing with our entry.`,\n )\n }\n mcpServers = {}\n }\n\n // 3. Build our desired entries from the SAME builder used for\n // --mcp-config so the two channels never drift.\n const peerConfig = buildPeerMcpConfig(serverUrl, {\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce: opts.nonce,\n codexHome: opts.codexHome ?? PATHS.CODEX_HOME,\n })\n\n // 4. Refuse to overwrite any same-named user-side entry. This is the\n // explicit-branch / \"no silent precedence\" requirement from the\n // plan — log a warning, return ok:false, and let the caller fall\n // back to --mcp-config (parent-session-only).\n const conflicts: Array<string> = []\n for (const name of Object.keys(peerConfig.mcpServers)) {\n if (mcpServers[name] !== undefined) conflicts.push(name)\n }\n if (conflicts.length > 0) {\n consola.warn(\n `injectPeerMcpIntoMirror: your ~/.claude/.claude.json already has `\n + `mcpServers entries named [${conflicts.join(\", \")}]; refusing to `\n + `overwrite. Subagents will not see the peer-MCP tools — only the `\n + `parent session via --mcp-config fallback. To resolve, rename the `\n + `user-side server(s) (e.g. via \\`claude mcp remove\\`) and relaunch.`,\n )\n return {\n ok: false,\n reason: \"user-has-conflicting-entry\",\n conflictingServers: conflicts,\n }\n }\n\n // 5. Merge our entries; preserve everything else.\n for (const [name, entry] of Object.entries(peerConfig.mcpServers)) {\n mcpServers[name] = entry\n }\n existing.mcpServers = mcpServers\n\n // 6. Atomic temp+rename. Same pattern as the synthetic .credentials.json\n // write in ensureClaudeConfigMirror. Per-launch dir means there are\n // no cross-launch racers; EEXIST on the tempfile is essentially\n // impossible (per-pid + 8-hex random). Mode 0o600 to match the\n // upstream Claude Code file perms.\n const desiredJson = JSON.stringify(existing, null, 2) + \"\\n\"\n await fs.mkdir(dir, { recursive: true })\n const tempPath = `${target}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson, { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, target)\n } catch (err) {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n\n return { ok: true, serversAdded: Object.keys(peerConfig.mcpServers) }\n}\n\n/**\n * Generate a per-launch nonce, write the MCP config + agents JSON\n * tempfiles under `CLAUDE_RUNTIME_DIR` with mode 0o600 and `O_EXCL`,\n * and return a `cleanup()` to unlink them on shutdown.\n *\n * Filenames are `peer-mcp-<pid>-<rand>.json` and `peer-agents-<pid>-<rand>.json`.\n * The PID prefix is what the boot-time sweep (`sweepStaleRuntimeFiles` in\n * paths.ts) keys off to drop orphans from crashed prior sessions; the\n * random suffix prevents two concurrent calls within the same process\n * from clobbering each other's files (e.g., a proxy that internally\n * relaunches its spawned child without restarting itself).\n */\nexport async function writePeerMcpRuntimeFiles(\n serverUrl: string,\n opts: WriteOpts,\n): Promise<PeerMcpRuntimeFiles> {\n const nonce = opts.nonce ?? randomBytes(32).toString(\"hex\")\n const runtimeDir = opts.runtimeDir ?? PATHS.CLAUDE_RUNTIME_DIR\n const codexHome = opts.codexHome ?? PATHS.CODEX_HOME\n // Defensive mkdir — `ensurePaths` already creates this in the normal\n // setupAndServe path, but if we're called from a context that didn't\n // run it (tests, future callers), don't fail with ENOENT.\n await fs.mkdir(runtimeDir, { recursive: true })\n if (process.platform !== \"win32\") {\n await fs.chmod(runtimeDir, 0o700).catch(() => {})\n }\n // 4-byte random suffix gives 2^32 distinct names per PID — collision-free\n // for any realistic count of in-process re-invocations.\n const fileSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n const mcpConfigPath = path.join(runtimeDir, `peer-mcp-${fileSuffix}.json`)\n const agentsPath = path.join(runtimeDir, `peer-agents-${fileSuffix}.json`)\n\n const mcpConfig = buildPeerMcpConfig(serverUrl, {\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce,\n codexHome,\n })\n const agents = buildPeerAgentDefinitions({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n nonce,\n codexHome,\n })\n\n // If a prior same-PID file survived (boot sweep didn't run, or this\n // function is called twice in one lifecycle), unlink first so wx\n // succeeds. Letting wx fail loudly is correct from a security\n // standpoint, but here we're the same PID — there's no race window\n // a different process could exploit.\n await fs.unlink(mcpConfigPath).catch(() => {})\n await fs.unlink(agentsPath).catch(() => {})\n\n await writeRuntimeFileSecure(mcpConfigPath, JSON.stringify(mcpConfig, null, 2))\n await writeRuntimeFileSecure(agentsPath, JSON.stringify(agents, null, 2))\n\n // Phase 2.5: also write the same agents as .md files into\n // ~/.claude/agents/ — this is the registry Claude Code's Task\n // `subagent_type` enum reads from at session start. The `--agents`\n // JSON path above is kept for inspection / future-proofing but the\n // .md files are what makes the subagents actually invokable from\n // Opus's tool surface.\n const mdResult = await writePeerAgentMdFiles(agents, {\n agentsDir: opts.agentsDir,\n fileSuffix,\n })\n\n const personas = personasFor({\n codexCli: opts.codexCli,\n geminiAvailable: opts.geminiAvailable,\n })\n\n const cleanup = async (): Promise<void> => {\n await Promise.allSettled([\n fs.unlink(mcpConfigPath),\n fs.unlink(agentsPath),\n mdResult.cleanup(),\n ])\n }\n\n return {\n mcpConfigPath,\n agentsPath,\n agentMdPaths: mdResult.paths,\n nonce,\n personas,\n cleanup,\n }\n}\n","import fs from \"node:fs\"\nimport { Writable } from \"node:stream\"\n\nimport consola from \"consola\"\nimport type { ConsolaOptions, ConsolaReporter, LogObject } from \"consola\"\n\nimport { PATHS } from \"~/lib/paths\"\n\nconst MAX_LOG_BYTES = 1024 * 1024 // 1 MB\nconst DEDUP_MAX = 1000\nconst ARG_MAX_LEN = 2048\nconst DEDUP_KEY_MAX_LEN = 200\n\nconst CREDENTIAL_RE =\n /\\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\n\nconst ALLOWED_TYPES = new Set([\"fatal\", \"error\", \"warn\"])\n\nfunction sanitize(line: string): string {\n return line.replace(CREDENTIAL_RE, \"[REDACTED]\")\n}\n\nfunction serializeArg(arg: unknown): string {\n if (typeof arg === \"string\") return arg\n if (arg instanceof Error) {\n const parts = [arg.message]\n if (arg.stack) parts.push(arg.stack)\n return parts.join(\"\\n\")\n }\n return String(arg)\n}\n\nfunction formatLogLine(logObj: LogObject): string {\n const ts = logObj.date.toISOString()\n const level = (logObj.type ?? \"error\").toUpperCase()\n const message = logObj.args\n .map((a) => {\n const s = serializeArg(a)\n return s.length > ARG_MAX_LEN ? s.slice(0, ARG_MAX_LEN) + \"…\" : s\n })\n .join(\" \")\n .replace(/\\r\\n|\\r|\\n/g, \"\\\\n\")\n\n return sanitize(`${ts} [${level}] ${message}\\n`)\n}\n\nfunction makeDedupeKey(logObj: LogObject): string {\n const firstArg =\n logObj.args.length > 0 ? serializeArg(logObj.args[0]) : \"\"\n const key = `${logObj.type}:${firstArg}`\n return key.length > DEDUP_KEY_MAX_LEN\n ? key.slice(0, DEDUP_KEY_MAX_LEN)\n : key\n}\n\nfunction rotateIfNeeded(filePath: string): void {\n let size: number\n try {\n size = fs.statSync(filePath).size\n } catch {\n return // file does not exist\n }\n if (size <= MAX_LOG_BYTES) return\n\n try {\n fs.renameSync(filePath, filePath + \".1\")\n } catch {\n // best-effort: if rename fails, continue with the existing file\n }\n}\n\nexport class FileLogReporter implements ConsolaReporter {\n private readonly filePath: string\n private readonly seen = new Set<string>()\n\n constructor(filePath: string) {\n this.filePath = filePath\n rotateIfNeeded(filePath)\n }\n\n log(logObj: LogObject, _ctx: { options: ConsolaOptions }): void {\n if (!ALLOWED_TYPES.has(logObj.type)) return\n\n const key = makeDedupeKey(logObj)\n if (this.seen.has(key)) return\n\n if (this.seen.size >= DEDUP_MAX) this.seen.clear()\n this.seen.add(key)\n\n const line = formatLogLine(logObj)\n\n // fs.openSync/writeSync/closeSync are synchronous and atomic from the\n // perspective of the JS event loop — no other log() call can interleave\n // between them. The previous re-entrancy guard (`if (this.writing)\n // return`) silently dropped log lines that arrived during the same\n // call stack as a write (e.g., a consola.error fired from an error\n // handler triggered by another log). Now we just write and trust the\n // synchronicity.\n //\n // The fd is closed in `finally` so an ENOSPC/EIO from writeSync does\n // not leak the fd. Repeated logging failures otherwise escalate into\n // EMFILE and bring down the whole process's ability to open files.\n let fd: number | undefined\n try {\n fd = fs.openSync(this.filePath, \"a\", 0o600)\n fs.writeSync(fd, line)\n } catch {\n // Silently discard — cannot log a logging failure\n } finally {\n if (fd !== undefined) {\n try {\n fs.closeSync(fd)\n } catch {\n // already closed / unwritable — nothing to do\n }\n }\n }\n }\n}\n\nconst nullStream = new Writable({ write(_chunk, _encoding, cb) { cb() } })\n\n/**\n * Switch consola to file-only mode for TUI sessions.\n * Removes the terminal reporter and installs a file reporter that\n * persists errors and warnings to disk with dedup and credential scrubbing.\n *\n * Also sinks consola's stdout/stderr streams as belt-and-suspenders:\n * even if a terminal reporter is re-added, it cannot write to the terminal.\n * Crash handlers that call process.stderr.write() directly are unaffected.\n * FileLogReporter uses fs.writeSync() directly and is also unaffected.\n */\nexport function enableFileLogging(): void {\n const reporter = new FileLogReporter(PATHS.ERROR_LOG_PATH)\n consola.options.throttle = 0 // disable built-in dedup\n consola.setReporters([reporter])\n consola.options.stdout = nullStream as unknown as typeof process.stdout\n consola.options.stderr = nullStream as unknown as typeof process.stderr\n}\n","import consola from \"consola\"\n\nimport { state } from \"./state\"\n\ntype Endpoint = \"/chat/completions\" | \"/responses\" | \"/v1/messages\"\n\nconst ENDPOINT_ALIASES: Record<string, Endpoint> = {\n \"/chat/completions\": \"/chat/completions\",\n \"/v1/chat/completions\": \"/chat/completions\",\n \"/responses\": \"/responses\",\n \"/v1/responses\": \"/responses\",\n \"/v1/messages\": \"/v1/messages\",\n}\n\n/**\n * Check whether a model supports the given endpoint, based on cached\n * `supported_endpoints` metadata from the Copilot `/models` response.\n *\n * Returns `true` (allow) when:\n * - the model is not found in the cache (don't block unknown models)\n * - the model has no `supported_endpoints` field (backward-compat)\n * - the endpoint is listed in `supported_endpoints`\n */\nexport function modelSupportsEndpoint(\n modelId: string,\n path: string,\n): boolean {\n const endpoint = ENDPOINT_ALIASES[path] ?? path\n const model = state.models?.data.find((m) => m.id === modelId)\n if (!model) return true\n\n const supported = model.supported_endpoints\n if (!supported || supported.length === 0) return true\n\n return supported.includes(endpoint)\n}\n\n/**\n * Log an error when a model is used on an endpoint it doesn't support.\n * Returns `true` if a mismatch was detected (for testing).\n */\nexport function logEndpointMismatch(\n modelId: string,\n path: string,\n): boolean {\n if (modelSupportsEndpoint(modelId, path)) return false\n\n const model = state.models?.data.find((m) => m.id === modelId)\n const supported = model?.supported_endpoints ?? []\n\n consola.error(\n `Model \"${modelId}\" does not support ${path}. `\n + `Supported endpoints: ${supported.join(\", \")}`,\n )\n return true\n}\n\n/**\n * Return model IDs that support the given endpoint.\n */\nexport function listModelsForEndpoint(path: string): string[] {\n const endpoint = ENDPOINT_ALIASES[path] ?? path\n const models = state.models?.data ?? []\n\n return models\n .filter((m) => {\n const supported = m.supported_endpoints\n if (!supported || supported.length === 0) return true\n return supported.includes(endpoint)\n })\n .map((m) => m.id)\n}\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nexport function initProxyFromEnv(): void {\n if (typeof Bun !== \"undefined\") return\n\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n setGlobalDispatcher(dispatcher as unknown as Dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","{\n \"name\": \"github-router\",\n \"version\": \"0.3.28\",\n \"license\": \"MIT\",\n \"description\": \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n \"keywords\": [\n \"proxy\",\n \"github-copilot\",\n \"openai-compatible\",\n \"anthropic-compatible\",\n \"claude-code\",\n \"codex\",\n \"reverse-proxy\",\n \"copilot\"\n ],\n \"homepage\": \"https://github.com/animeshkundu/github-router\",\n \"bugs\": \"https://github.com/animeshkundu/github-router/issues\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/animeshkundu/github-router.git\"\n },\n \"author\": \"animeshkundu\",\n \"type\": \"module\",\n \"bin\": {\n \"github-router\": \"dist/main.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"bun run --watch ./src/main.ts\",\n \"discover:fields\": \"bash scripts/discover-new-fields.sh\",\n \"knip\": \"knip-bun\",\n \"lint\": \"eslint --cache\",\n \"lint:all\": \"eslint --cache .\",\n \"prepack\": \"bun run build\",\n \"prepare\": \"(simple-git-hooks || true)\",\n \"probe:copilot\": \"bash scripts/probe-copilot-compat.sh --strict\",\n \"release\": \"./publish/release.sh\",\n \"start\": \"NODE_ENV=production bun run ./src/main.ts\",\n \"test\": \"bun test\",\n \"typecheck\": \"tsc\"\n },\n \"simple-git-hooks\": {\n \"pre-commit\": \"bunx lint-staged\"\n },\n \"trustedDependencies\": [\n \"@vscode/ripgrep\"\n ],\n \"lint-staged\": {\n \"*\": \"bun run lint --fix\"\n },\n \"dependencies\": {\n \"@vscode/ripgrep\": \"^1.15.13\",\n \"citty\": \"^0.1.6\",\n \"clipboardy\": \"^5.0.0\",\n \"consola\": \"^3.4.2\",\n \"fetch-event-stream\": \"^0.1.5\",\n \"gpt-tokenizer\": \"^3.0.1\",\n \"hono\": \"^4.9.9\",\n \"proxy-from-env\": \"^1.1.0\",\n \"srvx\": \"^0.8.9\",\n \"tree-sitter-wasms\": \"^0.1.13\",\n \"undici\": \"^7.16.0\",\n \"web-tree-sitter\": \"0.22.6\",\n \"zod\": \"^4.1.11\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.37.0\",\n \"eslint-config-prettier\": \"^10.1.0\",\n \"@types/bun\": \"^1.2.23\",\n \"@types/proxy-from-env\": \"^1.0.4\",\n \"eslint\": \"^9.37.0\",\n \"knip\": \"^5.64.1\",\n \"lint-staged\": \"^16.2.3\",\n \"prettier-plugin-packagejson\": \"^2.5.19\",\n \"simple-git-hooks\": \"^2.13.1\",\n \"tsdown\": \"^0.15.6\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.33.0\"\n }\n}\n","import consola from \"consola\"\n\nimport { HTTPError } from \"./error\"\n\nexport const awaitApproval = async () => {\n const response = await consola.prompt(`Accept incoming request?`, {\n type: \"confirm\",\n })\n\n if (!response)\n throw new HTTPError(\n \"Request rejected by user\",\n Response.json({ message: \"Request rejected by user\" }, { status: 403 }),\n )\n}\n","import consola from \"consola\"\n\nimport type { State } from \"./state\"\n\nimport { HTTPError } from \"./error\"\nimport { sleep } from \"./utils\"\n\n// Cap on how long a single request will wait in the rate-limit serialization\n// queue. Prevents an unbounded backlog from holding the proxy if the head of\n// the queue is in a long sleep. 5s is generous enough that normal queueing\n// (microseconds) never trips it.\nconst RATE_LIMIT_QUEUE_TIMEOUT_MS = 5000\n\n// Single-flight chain that serializes all rate-limit checks across the\n// proxy. Without this, two concurrent requests can both read the timestamp\n// before either writes — both proceed when only one should wait. The chain\n// adds microseconds of latency per request in the no-rate-limit case and\n// correctly serializes the wait when the limit fires.\nlet rateLimitChain: Promise<void> = Promise.resolve()\n\nexport async function checkRateLimit(state: State) {\n if (state.rateLimitSeconds === undefined) return\n\n // Ticket carried by `myTurn` so a queue-timeout can flag it as cancelled\n // before doCheck() ever gets to sleep or write to state.lastRequestTimestamp.\n // Without this, a request that already returned 429 to the caller would\n // STILL run later inside the chain — sleep, then bump the shared timestamp\n // — penalising subsequent legitimate requests for a request that was\n // never actually serviced.\n const ticket = { aborted: false }\n\n const myTurn = rateLimitChain.then(() => doCheck(state, ticket))\n rateLimitChain = myTurn.catch(() => {\n // Errors don't break the chain — the next caller starts fresh.\n })\n\n return Promise.race([\n myTurn,\n sleep(RATE_LIMIT_QUEUE_TIMEOUT_MS).then(() => {\n ticket.aborted = true\n throw new HTTPError(\n \"Rate limit queue wait exceeded\",\n Response.json(\n {\n type: \"error\",\n error: {\n type: \"rate_limit_error\",\n message: `Rate limit queue exceeded ${RATE_LIMIT_QUEUE_TIMEOUT_MS}ms; try again`,\n },\n },\n { status: 429 },\n ),\n )\n }),\n ])\n}\n\nasync function doCheck(\n state: State,\n ticket: { aborted: boolean },\n): Promise<void> {\n if (state.rateLimitSeconds === undefined) return\n if (ticket.aborted) return\n\n const now = Date.now()\n\n if (!state.lastRequestTimestamp) {\n state.lastRequestTimestamp = now\n return\n }\n\n const elapsedSeconds = (now - state.lastRequestTimestamp) / 1000\n\n if (elapsedSeconds > state.rateLimitSeconds) {\n state.lastRequestTimestamp = now\n return\n }\n\n const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds)\n\n if (!state.rateLimitWait) {\n consola.warn(\n `Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`,\n )\n throw new HTTPError(\n \"Rate limit exceeded\",\n Response.json({ message: \"Rate limit exceeded\" }, { status: 429 }),\n )\n }\n\n const waitTimeMs = waitTimeSeconds * 1000\n consola.warn(\n `Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`,\n )\n await sleep(waitTimeMs)\n // Re-check after the sleep — if the caller queue-timed-out while we were\n // sleeping, do NOT bump the shared timestamp (that would penalise the next\n // legitimate request for one that was already 429'd).\n if (ticket.aborted) return\n state.lastRequestTimestamp = Date.now()\n consola.info(\"Rate limit wait completed, proceeding with request\")\n}\n","import consola from \"consola\"\n\nimport type { Model } from \"~/services/copilot/get-models\"\n\nexport interface RequestLogInfo {\n method: string\n path: string\n model?: string\n resolvedModel?: string\n inputTokens?: number\n outputTokens?: number\n status?: number\n streaming?: boolean\n errorBody?: string\n}\n\n/**\n * Format a number with K/M suffix for compact display.\n */\nfunction formatTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`\n return String(n)\n}\n\n/**\n * Build a context window summary: \"in:1.2K out:50 ctx:1.2K/1M (0.1%)\"\n */\nfunction formatTokenInfo(\n inputTokens: number | undefined,\n outputTokens: number | undefined,\n model: Model | undefined,\n): string | undefined {\n if (inputTokens === undefined) return undefined\n\n const parts: Array<string> = []\n const maxPrompt = model?.capabilities?.limits?.max_prompt_tokens\n\n if (maxPrompt) {\n const pct = ((inputTokens / maxPrompt) * 100).toFixed(1)\n parts.push(`in:${formatTokens(inputTokens)}/${formatTokens(maxPrompt)} (${pct}%)`)\n } else {\n parts.push(`in:${formatTokens(inputTokens)}`)\n }\n\n if (outputTokens !== undefined) {\n parts.push(`out:${formatTokens(outputTokens)}`)\n }\n\n return parts.join(\" \")\n}\n\n/**\n * Print a single summary line for a completed request.\n *\n * Examples:\n * POST /v1/messages claude-opus-4.6-1m in:1.2K/1M (0.1%) out:50 200 2.3s\n * POST /v1/messages claude-opus-4-6→claude-opus-4.6-1m in:743/1M (0.1%) 200 198ms\n * POST /v1/chat/completions claude-sonnet-4 in:15 out:16 200 2.1s stream\n */\nexport function logRequest(\n info: RequestLogInfo,\n model: Model | undefined,\n startTime: number,\n): void {\n const parts: Array<string> = []\n\n parts.push(`${info.method} ${info.path}`)\n\n // Model (show resolution arrow if remapped)\n if (info.resolvedModel && info.resolvedModel !== info.model) {\n parts.push(`${info.model}→${info.resolvedModel}`)\n } else if (info.resolvedModel ?? info.model) {\n parts.push((info.resolvedModel ?? info.model)!)\n }\n\n // Token info with context window fill\n const tokenInfo = formatTokenInfo(info.inputTokens, info.outputTokens, model)\n if (tokenInfo) {\n parts.push(tokenInfo)\n }\n\n // Status\n if (info.status !== undefined) {\n parts.push(String(info.status))\n }\n\n // Duration + streaming flag\n const elapsed = Date.now() - startTime\n const duration =\n elapsed >= 1000 ? `${(elapsed / 1000).toFixed(1)}s` : `${elapsed}ms`\n parts.push(info.streaming ? `${duration} stream` : duration)\n\n const line = parts.join(\" \")\n\n if (detectCapabilityMismatch(info, model)) {\n consola.error(`[MISMATCH] ${line}`)\n } else {\n consola.info(line)\n }\n}\n\n/**\n * Detect when the API rejects a request for token/context reasons\n * that contradict what the /models endpoint reported.\n */\nfunction detectCapabilityMismatch(\n info: RequestLogInfo,\n model: Model | undefined,\n): boolean {\n if (!info.errorBody || !model) return false\n if (!info.status || info.status < 400) return false\n\n const err = info.errorBody.toLowerCase()\n return (\n err.includes(\"token\") ||\n err.includes(\"context\") ||\n err.includes(\"too long\") ||\n err.includes(\"max_tokens\") ||\n err.includes(\"prompt is too long\")\n )\n}\n\n/**\n * Opt-in instrumentation for the discovery loop (Phase 0.5 of the\n * long-horizon plan). When `GH_ROUTER_LOG_FIELDS=1` is set in the\n * environment, emits a single structured `[fields]` log line per request\n * recording the top-level body keys, per-tool field keys, and\n * anthropic-beta header values seen.\n *\n * Default-off (zero overhead). The companion\n * `scripts/discover-new-fields.sh` greps these lines, aggregates unique\n * field names per request shape, and diffs against the known-fields\n * list in `docs/copilot-compat-matrix.md` — surfacing anything new\n * that should get a probe row added.\n *\n * Format (single line, deterministic-ish key order):\n * [fields] path=<P> body_keys=<csv> tool_field_keys=<csv> beta_values=<csv>\n *\n * Where:\n * - `body_keys` is the alphabetical union of top-level keys in the\n * request body\n * - `tool_field_keys` is the alphabetical union of all keys appearing\n * across every entry of `body.tools[]` (or empty)\n * - `beta_values` is the comma-split anthropic-beta header value as\n * received (NOT filtered) — captures what the client sends, not\n * what we forward\n */\nexport function logRequestFields(opts: {\n path: string\n body: unknown\n betaHeader?: string\n}): void {\n if (process.env.GH_ROUTER_LOG_FIELDS !== \"1\") return\n const bodyKeys = collectTopLevelKeys(opts.body)\n const toolFieldKeys = collectToolFieldKeys(opts.body)\n const betaValues = (opts.betaHeader ?? \"\")\n .split(\",\")\n .map((v) => v.trim())\n .filter(Boolean)\n consola.info(\n `[fields] path=${opts.path}`\n + ` body_keys=${bodyKeys.join(\",\")}`\n + ` tool_field_keys=${toolFieldKeys.join(\",\")}`\n + ` beta_values=${betaValues.join(\",\")}`,\n )\n}\n\nfunction collectTopLevelKeys(body: unknown): Array<string> {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) return []\n return Object.keys(body as Record<string, unknown>).sort()\n}\n\nfunction collectToolFieldKeys(body: unknown): Array<string> {\n if (!body || typeof body !== \"object\") return []\n const tools = (body as Record<string, unknown>).tools\n if (!Array.isArray(tools)) return []\n const seen = new Set<string>()\n for (const tool of tools) {\n if (tool && typeof tool === \"object\" && !Array.isArray(tool)) {\n for (const k of Object.keys(tool as Record<string, unknown>)) {\n seen.add(k)\n }\n }\n }\n return [...seen].sort()\n}\n","import consola from \"consola\"\n\nimport { UPSTREAM_INACTIVITY_TIMEOUT_MS } from \"~/lib/port\"\n\nconst ENCODER = new TextEncoder()\n\n// Structural reader type so this helper accepts both DOM-style\n// (`ReadableStreamDefaultReader<Uint8Array>`) and Node-style\n// (`node:stream/web` reader) without type-incompatibility friction.\ninterface ByteReader {\n read(): Promise<{ done: boolean; value?: Uint8Array }>\n cancel(reason?: unknown): Promise<void>\n releaseLock(): void\n}\n\ninterface RelayOptions {\n routePath: string\n /**\n * Inactivity bound for individual upstream reads, in ms. Defaults to the\n * UPSTREAM_INACTIVITY_TIMEOUT_MS env-overridable constant in `~/lib/port`.\n * Tests can pass a small value (e.g. 50ms) to exercise the timeout path\n * without wall-clock waits.\n */\n inactivityTimeoutMs?: number\n}\n\n/**\n * Detect the family of \"controller has already closed\" errors that Bun and\n * the WHATWG streams runtime throw when an enqueue/close call races with\n * the consumer cancelling its read. These are NOT upstream failures — they\n * mean the client has finished reading (or disconnected) and we should\n * exit pull() quietly without trying to write more bytes or log noise.\n *\n * Bun's wording: `TypeError: Invalid state: Controller is already closed`.\n * Other runtimes use `TypeError: The stream is closing` or\n * `TypeError: This ReadableStream is closed` or include \"errored\" / \"cancelled\".\n */\nexport function isControllerClosedError(error: unknown): boolean {\n if (!(error instanceof Error)) return false\n const msg = error.message.toLowerCase()\n return (\n msg.includes(\"controller is already closed\")\n || msg.includes(\"controller is already errored\")\n || msg.includes(\"readablestream is closed\")\n || msg.includes(\"readablestream is already closed\")\n || msg.includes(\"stream is closing\")\n || msg.includes(\"stream is already closed\")\n || msg.includes(\"stream is closed\")\n )\n}\n\n/**\n * Wrap an upstream SSE byte stream so that:\n * - Backpressure is respected (pull-based; only reads when downstream demands).\n * - Mid-stream errors (undici \"terminated\", AbortError, network resets) are\n * caught, logged with structured context, and converted to a final\n * Anthropic-shape `event: error` SSE event before the downstream is closed.\n * - Upstream inactivity (no chunk for `inactivityTimeoutMs`) is treated as a\n * soft failure that emits an error event rather than hanging forever.\n * - Consumer cancellation (client disconnects mid-read or finishes early)\n * is recognized and handled silently — NOT logged as an upstream error,\n * NOT followed by a futile event:error write that can corrupt the\n * terminal bytes the client has already buffered.\n *\n * Pre-byte upstream errors (failure on the very first read) are handled by\n * the same code path: an `event: error` SSE event is emitted on a 200\n * response, then the connection is closed. Even if the consumer's SDK\n * silently swallows `event: error`, the immediate close triggers the\n * client's socket-disconnect handler — the user always sees an error\n * string, never a hang.\n */\nexport function relayAnthropicStream(\n body: ReadableStream<Uint8Array>,\n opts: RelayOptions,\n): ReadableStream<Uint8Array> {\n const inactivityMs = opts.inactivityTimeoutMs ?? UPSTREAM_INACTIVITY_TIMEOUT_MS\n const reader = body.getReader() as unknown as ByteReader\n let bytesRelayed = 0\n let upstreamFinished = false\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored — fine\n }\n }\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n\n try {\n const result = await readWithInactivityTimeout(reader, inactivityMs)\n if (consumerCancelled) {\n // Consumer cancelled while we were awaiting upstream — drop the\n // value (if any) and exit silently. The cancel() callback has\n // already propagated cancellation upstream.\n safeClose(controller)\n return\n }\n if (result.done) {\n // Zero-byte close is rare and usually indicates upstream\n // misbehavior (200 + SSE headers + immediate FIN). Surface it\n // in the error log so the operator can correlate; the consumer\n // sees a clean empty stream.\n if (bytesRelayed === 0) {\n consola.warn(\n `Upstream returned empty SSE stream at ${opts.routePath}`,\n )\n }\n upstreamFinished = true\n safeClose(controller)\n return\n }\n if (result.value) {\n bytesRelayed += result.value.byteLength\n try {\n controller.enqueue(result.value)\n } catch (enqueueError) {\n if (isControllerClosedError(enqueueError)) {\n // Consumer raced ahead of us: it closed the stream between\n // our last await and this enqueue. Treat as a normal end of\n // stream — upstream chunks past this point are dropped, but\n // that's expected behavior on consumer cancel.\n consumerCancelled = true\n return\n }\n throw enqueueError\n }\n }\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer cancelled mid-stream — the cancel() callback already\n // ran (or our inner enqueue-catch flipped the flag). Close the\n // downstream so the consumer's read settles, and release the\n // upstream reader if not already done.\n //\n // We deliberately do NOT call isControllerClosedError(error) on\n // upstream/reader failures here — that helper matches substrings\n // like \"stream is closed\" which can legitimately appear in real\n // undici upstream errors (e.g., body stream closed by the\n // server), and treating them as consumer-cancel would silently\n // suppress an `event: error` frame the consumer needs.\n reader.cancel(error).catch(() => {\n // upstream may already be closed\n })\n safeClose(controller)\n return\n }\n const errName = error instanceof Error ? error.name : \"Error\"\n const errMessage = error instanceof Error ? error.message : String(error)\n consola.error(\n `Upstream stream interrupted at ${opts.routePath}: bytes=${bytesRelayed} errType=${errName} message=${JSON.stringify(errMessage)}`,\n )\n const event = buildAnthropicErrorEvent(errName, errMessage)\n try {\n controller.enqueue(ENCODER.encode(event))\n } catch (enqueueError) {\n if (!isControllerClosedError(enqueueError)) {\n consola.warn(\n `Could not deliver error event to consumer at ${opts.routePath}: ${enqueueError instanceof Error ? enqueueError.message : String(enqueueError)}`,\n )\n }\n // Consumer-closed: silent\n }\n // Release the upstream socket. We've decided this stream is over —\n // the consumer's `cancel()` callback will NOT fire because we're\n // closing from our side, so without this the upstream fetch body\n // and TCP connection stay alive until the upstream times out.\n reader.cancel(error).catch(() => {\n // upstream may already be closed\n })\n safeClose(controller)\n }\n },\n cancel(reason) {\n consumerCancelled = true\n upstreamFinished = true\n reader.cancel(reason).catch(() => {\n // upstream may already be closed\n })\n },\n })\n}\n\nasync function readWithInactivityTimeout(\n reader: ByteReader,\n timeoutMs: number,\n): Promise<{ done: boolean; value?: Uint8Array }> {\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n reject(\n Object.assign(new Error(\"upstream_inactive\"), {\n name: \"InactivityTimeout\",\n }),\n )\n }, timeoutMs)\n })\n // Attach a noop catcher so that, when reader.read() wins the race and\n // the timer happens to fire on the same tick anyway, the rejection is\n // already handled. Without this, Node 24's default\n // --unhandled-rejections=throw terminates the process under sustained\n // load (every chunk creates a fresh setTimeout/timeoutPromise pair).\n timeoutPromise.catch(() => {})\n try {\n return await Promise.race([reader.read(), timeoutPromise])\n } finally {\n if (timeoutHandle !== undefined) clearTimeout(timeoutHandle)\n }\n}\n\n/**\n * Build the SSE wire bytes for an Anthropic-format streaming error event.\n * Per Anthropic streaming spec, errors are sent as:\n * event: error\n * data: {\"type\":\"error\",\"error\":{\"type\":\"...\",\"message\":\"...\"}}\n */\nexport function buildAnthropicErrorEvent(\n errName: string,\n errMessage: string,\n): string {\n const payload = {\n type: \"error\",\n error: {\n type: classifyStreamError(errName),\n message: `Upstream stream interrupted: ${errName}: ${errMessage}`,\n },\n }\n return `event: error\\ndata: ${JSON.stringify(payload)}\\n\\n`\n}\n\n/**\n * Build the SSE wire bytes for an OpenAI-format streaming error event,\n * followed by the `data: [DONE]` terminator that OpenAI clients expect.\n */\nexport function buildOpenAIErrorEvent(\n errName: string,\n errMessage: string,\n): string {\n const payload = {\n error: {\n type: classifyStreamError(errName),\n message: `Upstream stream interrupted: ${errName}: ${errMessage}`,\n },\n }\n return `data: ${JSON.stringify(payload)}\\n\\ndata: [DONE]\\n\\n`\n}\n\nfunction classifyStreamError(errName: string): string {\n // Use only documented Anthropic error types\n // (https://platform.claude.com/docs/en/api/errors). `timeout_error` is\n // the documented type for client-side / inactivity aborts; it survives\n // the SDK's discriminated-union parsing without falling into a default\n // branch that some consumers don't handle.\n if (errName === \"AbortError\") return \"timeout_error\"\n if (errName === \"InactivityTimeout\") return \"timeout_error\"\n return \"api_error\"\n}\n\nexport function logStreamError(\n routePath: string,\n error: unknown,\n): { errName: string; errMessage: string } {\n const errName = error instanceof Error ? error.name : \"Error\"\n const errMessage = error instanceof Error ? error.message : String(error)\n consola.error(\n `Upstream stream interrupted at ${routePath}: errType=${errName} message=${JSON.stringify(errMessage)}`,\n )\n return { errName, errMessage }\n}\n","import type {\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n ToolCall,\n} from \"~/services/copilot/create-chat-completions\"\nimport type { Model } from \"~/services/copilot/get-models\"\n\n// Encoder type mapping\nconst ENCODING_MAP = {\n o200k_base: () => import(\"gpt-tokenizer/encoding/o200k_base\"),\n cl100k_base: () => import(\"gpt-tokenizer/encoding/cl100k_base\"),\n p50k_base: () => import(\"gpt-tokenizer/encoding/p50k_base\"),\n p50k_edit: () => import(\"gpt-tokenizer/encoding/p50k_edit\"),\n r50k_base: () => import(\"gpt-tokenizer/encoding/r50k_base\"),\n} as const\n\ntype SupportedEncoding = keyof typeof ENCODING_MAP\n\n// Define encoder interface\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\n// Cache loaded encoders to avoid repeated imports\nconst encodingCache = new Map<string, Encoder>()\n\n/**\n * Calculate tokens for tool calls\n */\nconst calculateToolCallsTokens = (\n toolCalls: Array<ToolCall>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = 0\n for (const toolCall of toolCalls) {\n tokens += constants.funcInit\n tokens += encoder.encode(JSON.stringify(toolCall)).length\n }\n tokens += constants.funcEnd\n return tokens\n}\n\n/**\n * Calculate tokens for content parts\n */\nconst calculateContentPartsTokens = (\n contentParts: Array<ContentPart>,\n encoder: Encoder,\n): number => {\n let tokens = 0\n for (const part of contentParts) {\n if (part.type === \"image_url\") {\n tokens += encoder.encode(part.image_url.url).length + 85\n } else if (part.text) {\n tokens += encoder.encode(part.text).length\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens for a single message\n */\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n const tokensPerMessage = 3\n const tokensPerName = 1\n let tokens = tokensPerMessage\n for (const [key, value] of Object.entries(message)) {\n if (typeof value === \"string\") {\n tokens += encoder.encode(value).length\n }\n if (key === \"name\") {\n tokens += tokensPerName\n }\n if (key === \"tool_calls\") {\n tokens += calculateToolCallsTokens(\n value as Array<ToolCall>,\n encoder,\n constants,\n )\n }\n if (key === \"content\" && Array.isArray(value)) {\n tokens += calculateContentPartsTokens(\n value as Array<ContentPart>,\n encoder,\n )\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens using custom algorithm\n */\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) {\n return 0\n }\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n // every reply is primed with <|start|>assistant<|message|>\n numTokens += 3\n return numTokens\n}\n\n/**\n * Get the corresponding encoder module based on encoding type\n */\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) {\n return cached\n }\n }\n\n const supportedEncoding = encoding as SupportedEncoding\n if (!(supportedEncoding in ENCODING_MAP)) {\n const fallbackModule = (await ENCODING_MAP.o200k_base()) as Encoder\n encodingCache.set(encoding, fallbackModule)\n return fallbackModule\n }\n\n const encodingModule = (await ENCODING_MAP[supportedEncoding]()) as Encoder\n encodingCache.set(encoding, encodingModule)\n return encodingModule\n}\n\n/**\n * Get tokenizer type from model information\n */\nexport const getTokenizerFromModel = (model: Model): string => {\n return model.capabilities?.tokenizer || \"o200k_base\"\n}\n\n/**\n * Get model-specific constants for token calculation\n */\nconst getModelConstants = (model: Model) => {\n return model.id === \"gpt-3.5-turbo\" || model.id === \"gpt-4\" ?\n {\n funcInit: 10,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n : {\n funcInit: 7,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n}\n\n/**\n * Calculate tokens for a single parameter\n */\nconst calculateParameterTokens = (\n key: string,\n prop: unknown,\n context: {\n encoder: Encoder\n constants: ReturnType<typeof getModelConstants>\n },\n): number => {\n const { encoder, constants } = context\n let tokens = constants.propKey\n\n // Early return if prop is not an object\n if (typeof prop !== \"object\" || prop === null) {\n return tokens\n }\n\n // Type assertion for parameter properties\n const param = prop as {\n type?: string\n description?: string\n enum?: Array<unknown>\n [key: string]: unknown\n }\n\n const paramName = key\n const paramType = param.type || \"string\"\n let paramDesc = param.description || \"\"\n\n // Handle enum values\n if (param.enum && Array.isArray(param.enum)) {\n tokens += constants.enumInit\n for (const item of param.enum) {\n tokens += constants.enumItem\n tokens += encoder.encode(String(item)).length\n }\n }\n\n // Clean up description\n if (paramDesc.endsWith(\".\")) {\n paramDesc = paramDesc.slice(0, -1)\n }\n\n // Encode the main parameter line\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n // Handle additional properties (excluding standard ones)\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (!excludedKeys.has(propertyName)) {\n const propertyValue = param[propertyName]\n const propertyText =\n typeof propertyValue === \"string\" ? propertyValue : (\n JSON.stringify(propertyValue)\n )\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for function parameters\n */\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") {\n return 0\n }\n\n const params = parameters as Record<string, unknown>\n let tokens = 0\n\n for (const [key, value] of Object.entries(params)) {\n if (key === \"properties\") {\n const properties = value as Record<string, unknown>\n if (Object.keys(properties).length > 0) {\n tokens += constants.propInit\n for (const propKey of Object.keys(properties)) {\n tokens += calculateParameterTokens(propKey, properties[propKey], {\n encoder,\n constants,\n })\n }\n }\n } else {\n const paramText =\n typeof value === \"string\" ? value : JSON.stringify(value)\n tokens += encoder.encode(`${key}:${paramText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for a single tool\n */\nconst calculateToolTokens = (\n tool: Tool,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = constants.funcInit\n const func = tool.function\n const fName = func.name\n let fDesc = func.description || \"\"\n if (fDesc.endsWith(\".\")) {\n fDesc = fDesc.slice(0, -1)\n }\n const line = fName + \":\" + fDesc\n tokens += encoder.encode(line).length\n if (\n typeof func.parameters === \"object\" \n && func.parameters !== null\n ) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\n\n/**\n * Calculate token count for tools based on model\n */\nexport const numTokensForTools = (\n tools: Array<Tool>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let funcTokenCount = 0\n for (const tool of tools) {\n funcTokenCount += calculateToolTokens(tool, encoder, constants)\n }\n funcTokenCount += constants.funcEnd\n return funcTokenCount\n}\n\n/**\n * Calculate the token count of messages, supporting multiple GPT encoders\n */\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: Model,\n): Promise<{ input: number; output: number }> => {\n // Get tokenizer string\n const tokenizer = getTokenizerFromModel(model)\n\n // Get corresponding encoder module\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const simplifiedMessages = payload.messages\n const inputMessages = simplifiedMessages.filter(\n (msg) => msg.role !== \"assistant\",\n )\n const outputMessages = simplifiedMessages.filter(\n (msg) => msg.role === \"assistant\",\n )\n\n const constants = getModelConstants(model)\n let inputTokens = calculateTokens(inputMessages, encoder, constants)\n if (payload.tools && payload.tools.length > 0) {\n inputTokens += numTokensForTools(payload.tools, encoder, constants)\n }\n const outputTokens = calculateTokens(outputMessages, encoder, constants)\n\n return {\n input: inputTokens,\n output: outputTokens,\n }\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\nexport const createChatCompletions = async (\n payload: ChatCompletionsPayload,\n modelHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = payload.messages.some(\n (x) =>\n typeof x.content !== \"string\"\n && x.content?.some((x) => x.type === \"image_url\"),\n )\n\n // Agent/user check for X-Initiator header\n // Determine if any message is from an agent (\"assistant\" or \"tool\")\n const isAgentCall = payload.messages.some((msg) =>\n [\"assistant\", \"tool\"].includes(msg.role),\n )\n\n const url = `${copilotBaseUrl(state)}/chat/completions`\n const doFetch = (): Promise<Response> => {\n // Re-build headers per attempt so a 401-retry picks up the refreshed token.\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n ...modelHeaders,\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n const fetchInit: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/chat/completions\")\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n const claudeModels = state.models?.data\n .filter((m) => m.id.startsWith(\"claude\"))\n .map((m) => m.id)\n .join(\", \") ?? \"(models not loaded)\"\n consola.error(\n `Copilot rejected model \"${payload.model}\": ${response.status} ${errorBody} (available Claude models: ${claudeModels})`,\n )\n // Re-create the response so downstream error handlers can still read the body\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Failed to create chat completions\", reconstructed)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ChatCompletionResponse\n}\n\n// Streaming types\n\nexport interface ChatCompletionChunk {\n id: string\n object: \"chat.completion.chunk\"\n created: number\n model: string\n choices: Array<Choice>\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n completion_tokens_details?: {\n accepted_prediction_tokens: number\n rejected_prediction_tokens: number\n }\n }\n}\n\ninterface Delta {\n content?: string | null\n role?: \"user\" | \"assistant\" | \"system\" | \"tool\"\n tool_calls?: Array<{\n index: number\n id?: string\n type?: \"function\"\n function?: {\n name?: string\n arguments?: string\n }\n }>\n}\n\ninterface Choice {\n index: number\n delta: Delta\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null\n logprobs: object | null\n}\n\n// Non-streaming types\n\nexport interface ChatCompletionResponse {\n id: string\n object: \"chat.completion\"\n created: number\n model: string\n choices: Array<ChoiceNonStreaming>\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n }\n}\n\ninterface ResponseMessage {\n role: \"assistant\"\n content: string | null\n tool_calls?: Array<ToolCall>\n}\n\ninterface ChoiceNonStreaming {\n index: number\n message: ResponseMessage\n logprobs: object | null\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\"\n}\n\n// Payload types\n\nexport interface ChatCompletionsPayload {\n messages: Array<Message>\n model: string\n temperature?: number | null\n top_p?: number | null\n max_tokens?: number | null\n stop?: string | Array<string> | null\n n?: number | null\n stream?: boolean | null\n\n frequency_penalty?: number | null\n presence_penalty?: number | null\n logit_bias?: Record<string, number> | null\n logprobs?: boolean | null\n response_format?: { type: \"json_object\" } | null\n seed?: number | null\n tools?: Array<Tool> | null\n tool_choice?:\n | \"none\"\n | \"auto\"\n | \"required\"\n | { type: \"function\"; function: { name: string } }\n | null\n user?: string | null\n /**\n * OpenAI-compatible reasoning effort knob. Copilot accepts low/medium/high/xhigh\n * for OpenAI-routed models; for non-OpenAI models (e.g. gemini-3.x routed via\n * /v1/chat/completions) the upstream may silently ignore this or 400 — the proxy\n * forwards it as-is and surfaces any 400 through the existing tool-error path.\n */\n reasoning_effort?: string | null\n}\n\nexport interface Tool {\n type: \"function\"\n function: {\n name: string\n description?: string\n parameters: Record<string, unknown>\n }\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\" | \"system\" | \"tool\" | \"developer\"\n content: string | Array<ContentPart> | null\n\n name?: string\n tool_calls?: Array<ToolCall>\n tool_call_id?: string\n}\n\nexport interface ToolCall {\n id: string\n type: \"function\"\n function: {\n name: string\n arguments: string\n }\n}\n\nexport type ContentPart = TextPart | ImagePart\n\nexport interface TextPart {\n type: \"text\"\n text: string\n}\n\nexport interface ImagePart {\n type: \"image_url\"\n image_url: {\n url: string\n detail?: \"low\" | \"high\" | \"auto\"\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest } from \"~/lib/request-log\"\nimport { state } from \"~/lib/state\"\nimport { buildOpenAIErrorEvent, isControllerClosedError, logStreamError } from \"~/lib/stream-relay\"\nimport { getTokenCount } from \"~/lib/tokenizer\"\nimport { isNullish, resolveModel } from \"~/lib/utils\"\nimport {\n createChatCompletions,\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n type Message,\n} from \"~/services/copilot/create-chat-completions\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\ninterface UpstreamSSEEvent {\n event?: string\n data?: string\n id?: string | number\n}\n\nconst ENCODER = new TextEncoder()\n\nfunction formatSSE(chunk: UpstreamSSEEvent): string {\n const parts: Array<string> = []\n if (chunk.event) parts.push(`event: ${chunk.event}`)\n if (chunk.data !== undefined) {\n for (const line of String(chunk.data).split(/\\r\\n|\\r|\\n/)) {\n parts.push(`data: ${line}`)\n }\n }\n if (chunk.id !== undefined) parts.push(`id: ${String(chunk.id)}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nexport async function handleCompletion(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n let payload = await c.req.json<ChatCompletionsPayload>()\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\"Request payload:\", JSON.stringify(payload).slice(-400))\n }\n\n if (state.manualApprove) await awaitApproval()\n\n await injectWebSearchIfNeeded(payload)\n\n // Resolve model name (e.g. opus → opus-1m variant)\n const originalModel = payload.model\n const resolvedModel = resolveModel(payload.model)\n if (resolvedModel !== payload.model) {\n payload.model = resolvedModel\n }\n\n // Find the selected model\n const selectedModel = state.models?.data.find(\n (model) => model.id === payload.model,\n )\n\n logEndpointMismatch(payload.model, \"/chat/completions\")\n\n // Calculate token count\n let inputTokens: number | undefined\n try {\n if (selectedModel) {\n const tokenCount = await getTokenCount(payload, selectedModel)\n inputTokens = tokenCount.input\n }\n } catch {\n // Token counting is best-effort\n }\n\n if (isNullish(payload.max_tokens)) {\n payload = {\n ...payload,\n max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens,\n }\n if (debugEnabled) {\n consola.debug(\"Set max_tokens to:\", JSON.stringify(payload.max_tokens))\n }\n }\n\n const response = await createChatCompletions(payload, selectedModel?.requestHeaders).catch(\n async (error: unknown) => {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n },\n )\n const isStreaming = !isNonStreaming(response)\n\n // Extract output tokens from non-streaming response (no extra call)\n const outputTokens = !isStreaming\n ? (response as ChatCompletionResponse).usage?.completion_tokens\n : undefined\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens,\n outputTokens,\n status: 200,\n streaming: isStreaming,\n },\n selectedModel,\n startTime,\n )\n\n if (!isStreaming) {\n if (debugEnabled) {\n consola.debug(\"Non-streaming response:\", JSON.stringify(response))\n }\n return c.json(response)\n }\n\n // Streaming: peek the first SSE event so pre-byte upstream errors surface\n // through the route's try/catch → forwardError as a clean JSON response,\n // and only mid-stream errors hit the manual ReadableStream's pull-error path.\n const iterator = (response as AsyncIterableIterator<UpstreamSSEEvent>)[\n Symbol.asyncIterator\n ]()\n const firstResult = await iterator.next()\n if (firstResult.done) {\n consola.warn(\n `Upstream /chat/completions returned an empty stream at ${c.req.path}`,\n )\n }\n\n let pendingFirstChunk: UpstreamSSEEvent | undefined = firstResult.done\n ? undefined\n : firstResult.value\n let upstreamFinished = firstResult.done\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored\n }\n }\n const releaseUpstream = (reason?: unknown) => {\n if (typeof iterator.return === \"function\") {\n iterator.return(reason).catch(() => {\n // upstream may already be closed\n })\n }\n }\n const safeEnqueue = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n bytes: Uint8Array,\n ): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (e) {\n if (isControllerClosedError(e)) {\n consumerCancelled = true\n // The downstream cancel() callback may not fire if the controller\n // was closed by Bun's HTTP layer rather than an explicit consumer\n // .cancel() — release the upstream iterator here so the upstream\n // socket does not leak.\n releaseUpstream(e)\n return false\n }\n throw e\n }\n }\n\n return new Response(\n new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n if (pendingFirstChunk !== undefined) {\n const chunk = pendingFirstChunk\n pendingFirstChunk = undefined\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(chunk))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(chunk)))\n return\n }\n try {\n const result = await iterator.next()\n if (consumerCancelled) {\n safeClose(controller)\n return\n }\n if (result.done) {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n // Defensive: an upstream iterator that yields `{done:false, value:undefined}`\n // would crash formatSSE() below. Skip silently and pull again on\n // the next consumer demand. Real upstream iterators never emit\n // this shape, but a misbehaving / proxied iterator might.\n if (result.value === undefined || result.value === null) return\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(result.value))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(result.value)))\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer-cancelled mid-pull, not an upstream failure. Close\n // so the consumer's read settles cleanly. Also release the\n // upstream iterator — the cancel() callback may not have\n // fired if the controller was closed by Bun's HTTP layer.\n //\n // We deliberately do NOT call isControllerClosedError(error)\n // on iterator-side errors here — the helper matches substrings\n // like \"stream is closed\" which can appear in real upstream\n // errors, and treating them as consumer-cancel would silently\n // suppress the OpenAI-shape error frame the consumer needs.\n releaseUpstream(error)\n safeClose(controller)\n return\n }\n const { errName, errMessage } = logStreamError(c.req.path, error)\n safeEnqueue(\n controller,\n ENCODER.encode(buildOpenAIErrorEvent(errName, errMessage)),\n )\n // We've decided this stream is done — release the upstream\n // iterator since the cancel() callback won't fire on a\n // server-initiated close.\n releaseUpstream(error)\n safeClose(controller)\n }\n },\n cancel() {\n consumerCancelled = true\n upstreamFinished = true\n releaseUpstream()\n },\n }),\n {\n status: 200,\n headers: {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n },\n },\n )\n}\n\nconst isNonStreaming = (\n response: Awaited<ReturnType<typeof createChatCompletions>>,\n): response is ChatCompletionResponse => Object.hasOwn(response, \"choices\")\n\nasync function injectWebSearchIfNeeded(\n payload: ChatCompletionsPayload,\n): Promise<void> {\n const hasWebSearch = payload.tools?.some(\n (t) =>\n (\"type\" in t && (t as unknown as Record<string, unknown>).type === \"web_search\")\n || t.function?.name === \"web_search\",\n )\n if (!hasWebSearch) return\n\n // Skip search on follow-up messages (tool call results)\n const hasToolResult = payload.messages.some((msg) => msg.role === \"tool\")\n const query = hasToolResult ? undefined : extractUserQuery(payload.messages)\n\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n // Prepend to existing system message or inject a new one\n const systemMsg = payload.messages.find((msg) => msg.role === \"system\")\n if (systemMsg) {\n const existingContent =\n typeof systemMsg.content === \"string\" ? systemMsg.content\n : Array.isArray(systemMsg.content) ?\n systemMsg.content\n .filter((p) => p.type === \"text\")\n .map((p) => (\"text\" in p ? p.text : \"\"))\n .join(\"\\n\")\n : \"\"\n systemMsg.content = `${searchContext}\\n\\n${existingContent}`\n } else {\n payload.messages.unshift({\n role: \"system\",\n content: searchContext,\n })\n }\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Remove web_search from tools before forwarding\n payload.tools = payload.tools?.filter(\n (t) =>\n !(\n (\"type\" in t && (t as unknown as Record<string, unknown>).type === \"web_search\")\n || t.function?.name === \"web_search\"\n ),\n ) as typeof payload.tools\n if (payload.tools?.length === 0) {\n payload.tools = undefined\n }\n if (!payload.tools) {\n payload.tool_choice = undefined\n } else if (\n payload.tool_choice\n && typeof payload.tool_choice === \"object\"\n && \"type\" in payload.tool_choice\n && payload.tool_choice.type === \"function\"\n ) {\n const toolChoiceName = payload.tool_choice.function?.name\n if (\n toolChoiceName\n && !payload.tools.some((tool) => tool.function.name === toolChoiceName)\n ) {\n payload.tool_choice = undefined\n }\n }\n}\n\nfunction extractUserQuery(messages: Array<Message>): string | undefined {\n // Find the last user message\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") return msg.content\n if (Array.isArray(msg.content)) {\n const text = msg.content.find((p) => p.type === \"text\")\n if (text && \"text\" in text) return text.text as string\n }\n }\n }\n return undefined\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCompletion } from \"./handler\"\n\nexport const completionRoutes = new Hono()\n\ncompletionRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createEmbeddings = async (payload: EmbeddingRequest) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to create embeddings\", response)\n\n return (await response.json()) as EmbeddingResponse\n}\n\nexport interface EmbeddingRequest {\n input: string | Array<string>\n model: string\n}\n\nexport interface Embedding {\n object: string\n embedding: Array<number>\n index: number\n}\n\nexport interface EmbeddingResponse {\n object: string\n data: Array<Embedding>\n model: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport {\n createEmbeddings,\n type EmbeddingRequest,\n} from \"~/services/copilot/create-embeddings\"\n\nexport const embeddingRoutes = new Hono()\n\nembeddingRoutes.post(\"/\", async (c) => {\n try {\n const payload = await c.req.json<EmbeddingRequest>()\n const response = await createEmbeddings(payload)\n\n return c.json(response)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { randomUUID } from \"node:crypto\"\n\nimport consola from \"consola\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\n/**\n * Build headers that match what VS Code Copilot Chat sends to the Copilot API.\n *\n * copilotHeaders() provides: Authorization, content-type, copilot-integration-id,\n * editor-version, editor-plugin-version, user-agent, openai-intent,\n * x-github-api-version, x-request-id, x-vscode-user-agent-library-version.\n *\n * We add the remaining headers VS Code sends for /v1/messages:\n * - X-Initiator (VS Code sets dynamically; \"agent\" is safe for CLI use)\n * - anthropic-version (VS Code's Anthropic SDK sends this)\n * - X-Interaction-Id (VS Code sends a session-scoped UUID)\n *\n * We intentionally omit copilot-vision-request — VS Code only sends it when\n * images are present, and the native /v1/messages endpoint handles vision\n * without requiring the header.\n *\n * extraHeaders allows callers to forward client-supplied beta headers\n * (anthropic-beta) so Copilot enables extended features.\n */\nfunction buildHeaders(\n extraHeaders?: Record<string, string>,\n): Record<string, string> {\n return {\n ...copilotHeaders(state),\n accept: \"application/json\",\n \"openai-intent\": \"messages-proxy\",\n \"x-interaction-type\": \"conversation-agent\",\n \"X-Initiator\": \"agent\",\n \"anthropic-version\": \"2023-06-01\",\n \"X-Interaction-Id\": randomUUID(),\n ...extraHeaders,\n }\n}\n\n/**\n * Forward an Anthropic Messages API request to Copilot's native /v1/messages endpoint.\n * Returns the raw Response so callers can handle streaming vs non-streaming.\n *\n * `callerSignal` (optional) is composed with the standard\n * UPSTREAM_FETCH_TIMEOUT_MS via AbortSignal.any so callers (e.g. the\n * peer-MCP `opus-critic` persona) can cancel the upstream call when\n * Claude Code's MCP per-tool-call ceiling fires. Mirrors the pattern\n * in createResponses / createChatCompletions.\n */\nexport async function createMessages(\n body: string,\n extraHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n): Promise<Response> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const url = `${copilotBaseUrl(state)}/v1/messages?beta=true`\n consola.debug(`Forwarding to ${url}`)\n\n // Re-build headers per attempt so a 401-retry picks up the refreshed token.\n const doFetch = (): Promise<Response> => {\n const headers = buildHeaders(extraHeaders)\n const fetchInit: RequestInit = { method: \"POST\", headers, body }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/v1/messages\")\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n consola.error(\n `Copilot /v1/messages error: ${response.status} ${errorBody}`,\n )\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Copilot messages request failed\", reconstructed)\n }\n\n return response\n}\n\n/**\n * Forward an Anthropic count_tokens request to Copilot's native endpoint.\n * Returns the raw Response.\n *\n * `callerSignal` is composed with UPSTREAM_FETCH_TIMEOUT_MS — same pattern\n * as createMessages.\n */\nexport async function countTokens(\n body: string,\n extraHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n): Promise<Response> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const url = `${copilotBaseUrl(state)}/v1/messages/count_tokens?beta=true`\n consola.debug(`Forwarding to ${url}`)\n\n const doFetch = (): Promise<Response> => {\n const headers = buildHeaders(extraHeaders)\n const fetchInit: RequestInit = { method: \"POST\", headers, body }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(\n doFetch,\n \"/v1/messages/count_tokens\",\n )\n\n if (!response.ok) {\n let errorBody = \"\"\n try {\n errorBody = await response.text()\n } catch {\n errorBody = \"(could not read error body)\"\n }\n consola.error(\n `Copilot count_tokens error: ${response.status} ${errorBody}`,\n )\n const reconstructed = new Response(errorBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n throw new HTTPError(\"Copilot count_tokens request failed\", reconstructed)\n }\n\n return response\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { UPSTREAM_FETCH_TIMEOUT_MS } from \"~/lib/port\"\nimport { state } from \"~/lib/state\"\nimport { tryRefreshAndRetry } from \"~/lib/token\"\n\nexport const createResponses = async (\n payload: ResponsesPayload,\n modelHeaders?: Record<string, string>,\n callerSignal?: AbortSignal,\n) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = detectVision(payload.input)\n\n const isAgentCall = detectAgentCall(payload.input)\n\n const url = `${copilotBaseUrl(state)}/responses`\n const doFetch = (): Promise<Response> => {\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n ...modelHeaders,\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n const fetchInit: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n }\n const signals: Array<AbortSignal> = []\n if (UPSTREAM_FETCH_TIMEOUT_MS > 0) {\n signals.push(AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS))\n }\n if (callerSignal) signals.push(callerSignal)\n if (signals.length === 1) fetchInit.signal = signals[0]\n else if (signals.length > 1) fetchInit.signal = AbortSignal.any(signals)\n return fetch(url, fetchInit)\n }\n const response = await tryRefreshAndRetry(doFetch, \"/responses\")\n\n if (!response.ok) {\n // Read the body BEFORE throwing so the actual upstream error is\n // visible in the proxy log. Without this we'd interpolate\n // `[object Response]` and have no idea what Copilot rejected.\n // Clone first because `response.text()` consumes the body and the\n // HTTPError handler in callers may want to read it again.\n let bodyText: string\n try {\n bodyText = await response.clone().text()\n } catch {\n bodyText = \"(failed to read body)\"\n }\n consola.error(\n `Failed to create responses: HTTP ${response.status} ${response.statusText} `\n + `from ${url} — body: ${bodyText.slice(0, 2000)}`,\n )\n throw new HTTPError(\"Failed to create responses\", response)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ResponsesApiResponse\n}\n\nfunction detectVision(input: ResponsesPayload[\"input\"]): boolean {\n if (typeof input === \"string\") return false\n if (!Array.isArray(input)) return false\n\n return input.some((item) => {\n if (\"content\" in item && Array.isArray(item.content)) {\n return item.content.some(\n (part: Record<string, unknown>) => part.type === \"input_image\",\n )\n }\n return false\n })\n}\n\nfunction detectAgentCall(input: ResponsesPayload[\"input\"]): boolean {\n if (typeof input === \"string\") return false\n if (!Array.isArray(input)) return false\n\n return input.some((item) => {\n if (\"role\" in item && item.role === \"assistant\") return true\n if (\n \"type\" in item\n && (item.type === \"function_call\" || item.type === \"function_call_output\")\n ) {\n return true\n }\n return false\n })\n}\n\n// Types\n\nexport interface ResponsesInputItem {\n role?: \"user\" | \"assistant\" | \"system\"\n type?: \"message\" | \"function_call\" | \"function_call_output\"\n content?: string | Array<Record<string, unknown>>\n name?: string\n call_id?: string\n arguments?: string\n output?: string\n [key: string]: unknown\n}\n\nexport interface ResponsesTool {\n type: string\n name?: string\n description?: string\n parameters?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface ResponsesPayload {\n model: string\n input: string | Array<ResponsesInputItem>\n instructions?: string\n tools?: Array<ResponsesTool>\n tool_choice?:\n | string\n | { type: string; name?: string; function?: { name?: string } }\n max_output_tokens?: number\n temperature?: number\n top_p?: number\n stream?: boolean\n store?: boolean\n metadata?: Record<string, string>\n previous_response_id?: string\n reasoning?: { effort?: string; summary?: string }\n [key: string]: unknown\n}\n\nexport interface ResponsesApiResponse {\n id: string\n object: \"response\"\n status: string\n output: Array<unknown>\n [key: string]: unknown\n}\n","import { timingSafeEqual } from \"node:crypto\"\n\nimport consola from \"consola\"\nimport type { Context } from \"hono\"\n\nimport { state } from \"~/lib/state\"\nimport { resolveModel } from \"~/lib/utils\"\nimport {\n PERSONAS_READ,\n type PersonaSpec,\n EFFORT_LEVELS,\n type Effort,\n isEffort,\n NON_PERSONA_MCP_TOOLS,\n type NonPersonaMcpTool,\n} from \"~/lib/peer-mcp-personas\"\nimport {\n createChatCompletions,\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n} from \"~/services/copilot/create-chat-completions\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\n\nconst MCP_PROTOCOL_VERSION = \"2025-06-18\"\nconst SERVER_NAME = \"github-router-peers\"\nconst SERVER_VERSION = \"1\"\n\n// Effort levels (EFFORT_LEVELS, Effort, isEffort) are imported from\n// peer-mcp-personas.ts so PersonaSpec.allowedEfforts can reference the\n// same type without a circular import. Per-persona defaultEffort is on\n// the PersonaSpec; there is no module-level default here anymore.\n\n/** Bounded concurrency. Originally capped at 2 (commit 4317a25) as a defensive\n * pre-launch guess against Opus's natural pattern of fanning out to all three\n * critics at once. Raised to 8 (Phase 2D of the peer-MCP plan) so the\n * decomposition pattern Phase 2B teaches Opus — \"split a >20 KB artifact\n * into 2-4 batches and call in parallel\" — can actually run in parallel\n * without the (3+)th call returning isError \"queue full\". The persona\n * handlers (`callPersona`) hold no shared mutable state — there's no race\n * the cap is hiding; the upstream Copilot's own rate-limit (surfaced as a\n * per-call 429 → tool isError) is the real backpressure mechanism. 8 covers\n * a 7-fork wave with one slot of headroom and is still a hard upper bound\n * against runaway clients. See docs/research/peer-mcp-investigation.md\n * § \"Concurrency cap investigation\" for the full justification. */\nconst MAX_INFLIGHT_TOOLS_CALL = 8\nlet inFlightToolsCall = 0\n\n/**\n * Per-request AbortController registry for `notifications/cancelled`\n * (Phase D P1.5). When a client times out a tools/call before the\n * upstream Copilot fetch completes, the JSON-RPC notification:\n * { jsonrpc:\"2.0\", method:\"notifications/cancelled\",\n * params:{ requestId: \"<id>\", reason?: \"...\" } }\n * arrives. Without handling, the upstream fetch keeps running until\n * natural completion, leaking the inFlightToolsCall slot for tens of\n * minutes. Tracking the AbortController lets us abort the fetch and\n * free the slot immediately.\n *\n * Important: per CLAUDE.md \"Bun request-signal quirk\", we use OUR own\n * AbortController (NOT c.req.raw.signal which fires after request body\n * is consumed). The signal is threaded into createResponses /\n * createChatCompletions's `callerSignal` parameter.\n */\nconst inflightAborts = new Map<string | number, AbortController>()\n\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\"\n id?: number | string | null\n method?: string\n params?: Record<string, unknown>\n}\n\ninterface ToolEntry {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\nconst RPC_PARSE_ERROR = -32700\nconst RPC_INVALID_REQUEST = -32600\nconst RPC_METHOD_NOT_FOUND = -32601\nconst RPC_INVALID_PARAMS = -32602\nconst RPC_INTERNAL_ERROR = -32603\n\nfunction rpcError(\n id: JsonRpcRequest[\"id\"] | undefined,\n code: number,\n message: string,\n data?: unknown,\n): { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; error: object } {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: data === undefined ? { code, message } : { code, message, data },\n }\n}\n\nfunction rpcResult<T>(\n id: JsonRpcRequest[\"id\"] | undefined,\n result: T,\n): { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; result: T } {\n return { jsonrpc: \"2.0\", id: id ?? null, result }\n}\n\nfunction isLoopbackHost(host: string | undefined | null): boolean {\n if (!host) return false\n // Strip port. IPv6-bracketed hosts (e.g. \"[::1]:8080\") aren't a concern\n // here — we bind to 127.0.0.1 only.\n const idx = host.lastIndexOf(\":\")\n const hostname = idx >= 0 ? host.slice(0, idx) : host\n return hostname === \"127.0.0.1\" || hostname === \"localhost\"\n}\n\n/**\n * Constant-time bearer compare. Random per-launch nonces aren't really\n * timing-attackable in practice, but this costs nothing.\n */\nfunction nonceMatches(provided: string, expected: string): boolean {\n if (provided.length !== expected.length) return false\n const a = Buffer.from(provided)\n const b = Buffer.from(expected)\n try {\n return timingSafeEqual(a, b)\n } catch {\n return false\n }\n}\n\nfunction checkAuth(c: Context): { ok: true } | { ok: false; status: 401 | 403; reason: string } {\n // Host validation defeats DNS-rebinding attacks. An attacker who tricks\n // the browser into resolving evil.com → 127.0.0.1 still sends\n // Host: evil.com, which we reject here.\n if (!isLoopbackHost(c.req.header(\"host\"))) {\n return { ok: false, status: 403, reason: \"non-loopback Host header rejected\" }\n }\n // Per-launch nonce. State is set by the `claude` subcommand after\n // setupAndServe. When unset (proxy started standalone, e.g. via\n // `github-router start`), `/mcp` rejects all requests.\n const expected = state.peerMcpNonce\n if (!expected) {\n return { ok: false, status: 401, reason: \"/mcp not enabled in this proxy session\" }\n }\n const auth = c.req.header(\"authorization\") ?? \"\"\n const m = /^Bearer\\s+(.+)$/i.exec(auth)\n if (!m || !nonceMatches(m[1], expected)) {\n return { ok: false, status: 401, reason: \"missing or invalid Authorization bearer\" }\n }\n return { ok: true }\n}\n\nfunction geminiAvailable(): boolean {\n const models = state.models?.data\n if (!models) return false\n return models.some((m) => /^gemini-3\\..*pro/i.test(m.id))\n}\n\nfunction activePersonas(): Array<PersonaSpec> {\n // Drop personas whose model family is missing from Copilot's live\n // catalog (currently only gemini-critic, gated by `requiresGeminiCatalog`).\n // Distinct from `requiresHttp` (codex-cli stdio routing constraint) —\n // see PersonaSpec field doc in peer-mcp-personas.ts.\n return PERSONAS_READ.filter((p) => !p.requiresGeminiCatalog || geminiAvailable())\n}\n\nfunction toolEntries(): Array<ToolEntry> {\n const personaEntries: Array<ToolEntry> = activePersonas().map((p) => ({\n name: p.toolNameHttp,\n description: p.description,\n inputSchema: {\n type: \"object\",\n required: [\"prompt\"],\n additionalProperties: false,\n properties: {\n prompt: {\n type: \"string\",\n description: \"The lead's brief — the artifact under review plus constraints.\",\n },\n context: {\n type: \"string\",\n description:\n \"Optional additional context (extra file content, prior decisions). Concatenated to the brief before sending.\",\n },\n effort: {\n type: \"string\",\n // Per-persona allowedEfforts: schema only advertises tiers the\n // persona accepts. Empirical data (2026-05-14) drove which tiers\n // each persona exposes — see EFFORT_LEVELS doc in\n // src/lib/peer-mcp-personas.ts.\n enum: [...p.allowedEfforts],\n description:\n `Reasoning depth (${p.allowedEfforts.join(\" | \")}). Default \"${p.defaultEffort}\". `\n + \"Higher tiers cost more wall-clock; lower tiers are quicker sanity checks. \"\n + (p.endpoint === \"/v1/chat/completions\"\n ? \"Note: for gemini routed via /v1/chat/completions, the upstream may silently ignore this knob.\"\n : \"\"),\n },\n },\n },\n }))\n // Append non-persona utility tools (currently just `web_search`). They\n // share the same `tools/list` surface but have their own input schemas\n // (no prompt/context/effort) and skip the per-persona validation gates\n // in handleToolsCall.\n const nonPersonaEntries: Array<ToolEntry> = NON_PERSONA_MCP_TOOLS.map(\n (t) => ({\n name: t.toolNameHttp,\n description: t.description,\n inputSchema: t.inputSchema,\n }),\n )\n return [...personaEntries, ...nonPersonaEntries]\n}\n\nfunction buildUserText(prompt: string, context?: string): string {\n if (!context) return prompt\n return `${prompt}\\n\\n---\\n\\nAdditional context:\\n${context}`\n}\n\nfunction extractResponsesText(response: ResponsesApiResponse): string {\n const out: Array<string> = []\n for (const item of response.output) {\n if (typeof item !== \"object\" || item === null) continue\n const obj = item as Record<string, unknown>\n if (obj.type !== \"message\" || obj.role !== \"assistant\") continue\n const content = obj.content\n if (!Array.isArray(content)) continue\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const p = part as Record<string, unknown>\n if (\n (p.type === \"output_text\" || p.type === \"text\")\n && typeof p.text === \"string\"\n ) {\n out.push(p.text)\n }\n }\n }\n return out.join(\"\")\n}\n\nfunction extractChatCompletionText(response: ChatCompletionResponse): string {\n const choice = response.choices?.[0]\n if (!choice) return \"\"\n const c = choice.message?.content\n return typeof c === \"string\" ? c : \"\"\n}\n\n/**\n * Extract assistant text from an Anthropic /v1/messages response.\n * Mirrors `extractResponsesText` for the OpenAI /v1/responses shape.\n *\n * The Anthropic Messages API response has shape `{content: [{type, ...}, ...]}`.\n * Text blocks have `type: \"text\"` and `text: string`. Thinking blocks have\n * `type: \"thinking\"` (and live in the same array; we ignore them — they're\n * the model's reasoning trace, not the final answer for the lead).\n */\ninterface MessagesApiContentBlock {\n type: string\n text?: string\n}\ninterface MessagesApiResponse {\n content?: ReadonlyArray<MessagesApiContentBlock>\n}\n\nfunction extractMessagesText(response: MessagesApiResponse): string {\n const out: Array<string> = []\n for (const block of response.content ?? []) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n out.push(block.text)\n }\n }\n return out.join(\"\")\n}\n\ninterface ToolErrorContent {\n content: Array<{ type: \"text\"; text: string }>\n isError: true\n}\n\nfunction toolError(message: string): ToolErrorContent {\n return {\n content: [{ type: \"text\", text: message }],\n isError: true,\n }\n}\n\n/**\n * Empirical pre-flight cap to convert \"would-bust-the-60s-MCP-ceiling\"\n * calls into fast actionable errors instead of slot-leaking timeouts.\n *\n * Probed live against Copilot 2026-05-14:\n * gpt-5.5 high on a ~600B prompt = 23.8s → ~76s on 8KB (rough linear)\n * gpt-5.3-codex high on ~600B = 16.0s → ~64s on 12KB\n * claude-opus-4-7 medium (thinking=3000) on a trivial prompt = 22.5s\n * but model self-paces budget → ~50s+ on a real ~6KB review\n *\n * Returns `{tooLong: true, capBytes}` when the (persona, effort, briefBytes)\n * tuple is empirically predicted to bust the 60s ceiling.\n *\n * SCOPE: the cap is JSON-PATH ONLY. Callers (handleMcpPost) MUST gate\n * the call site by `!acceptsEventStream(...)`. The SSE path\n * (handleToolsCallSSE) keeps the connection open past the 60s ceiling\n * via heartbeats — size-based pre-flight rejection there would just\n * lock SSE clients out of their primary advantage. JSON-path clients\n * (raw curl with `Accept: application/json`, older MCP clients without\n * SSE awareness) DO still hit the underlying tools/call timer, so the\n * cap is the only way to surface a fast actionable error there\n * instead of a slot-leaking timeout.\n *\n * INVARIANT: pre-flight MUST fire BEFORE inFlightToolsCall++ — the\n * slot must not be acquired for a rejected pre-flight. handleMcpPost\n * runs the check before delegating to handleRpc → handleToolsCall (the\n * function that increments the counter). Documented in CLAUDE.md.\n *\n * gemini_critic has no cap (long-context model + Copilot may auto-pace).\n */\nconst PRE_FLIGHT_CAPS: ReadonlyArray<{\n toolName: string\n effort: Effort\n maxBriefBytes: number\n}> = [\n { toolName: \"codex_critic\", effort: \"high\", maxBriefBytes: 8 * 1024 },\n { toolName: \"codex_reviewer\", effort: \"high\", maxBriefBytes: 12 * 1024 },\n { toolName: \"opus_critic\", effort: \"medium\", maxBriefBytes: 6 * 1024 },\n]\n\nfunction predictedTooLong(\n persona: PersonaSpec,\n effort: Effort,\n briefBytes: number,\n): { tooLong: true; capBytes: number } | { tooLong: false } {\n for (const cap of PRE_FLIGHT_CAPS) {\n if (\n cap.toolName === persona.toolNameHttp\n && cap.effort === effort\n && briefBytes > cap.maxBriefBytes\n ) {\n return { tooLong: true, capBytes: cap.maxBriefBytes }\n }\n }\n return { tooLong: false }\n}\n\n/**\n * JSON-path pre-flight predictedTooLong gate. Returns a JSON-RPC result\n * body wrapping a tool-error envelope when the call would bust the 60s\n * tools/call ceiling on the JSON path; returns undefined when the call\n * should proceed normally.\n *\n * Skips the check (returns undefined) for any shape problem so\n * handleRpc can return the canonical JSON-RPC error code instead:\n * - notification (no id) → handleRpc returns 202 + empty body\n * - missing/unknown name → handleRpc returns -32601\n * - missing prompt → handleRpc returns -32602\n * - invalid effort string → handleRpc returns -32602\n * - effort not in persona.allowedEfforts → handleRpc returns -32602\n */\nfunction jsonPathPreflightCap(body: JsonRpcRequest):\n | { jsonrpc: \"2.0\"; id: JsonRpcRequest[\"id\"] | null; result: ToolErrorContent }\n | undefined {\n if (body.id === undefined) return undefined\n const params = (body.params ?? {}) as Record<string, unknown>\n const name = typeof params.name === \"string\" ? params.name : \"\"\n const args = (params.arguments ?? {}) as Record<string, unknown>\n const prompt = typeof args.prompt === \"string\" ? args.prompt : \"\"\n const context = typeof args.context === \"string\" ? args.context : undefined\n const rawEffort = args.effort\n if (!name || !prompt) return undefined\n const persona = activePersonas().find((p) => p.toolNameHttp === name)\n if (!persona) return undefined\n if (rawEffort !== undefined && !isEffort(rawEffort)) return undefined\n const effortMaybe = rawEffort as Effort | undefined\n if (\n effortMaybe !== undefined\n && !persona.allowedEfforts.includes(effortMaybe)\n ) {\n return undefined\n }\n const effort: Effort = effortMaybe ?? persona.defaultEffort\n const briefBytes = Buffer.byteLength(buildUserText(prompt, context), \"utf8\")\n const verdict = predictedTooLong(persona, effort, briefBytes)\n if (!verdict.tooLong) return undefined\n return rpcResult(\n body.id,\n toolError(\n `pre-flight rejected: ${persona.toolNameHttp} at effort=${effort} on a `\n + `${briefBytes}-byte brief is empirically predicted to exceed the JSON `\n + `tools/call timeout (cap=${verdict.capBytes} bytes for this tier). `\n + `Either drop to a lower effort tier, split the brief into 2-4 `\n + `parallel sub-calls per the decomposition guidance, or send `\n + `Accept: text/event-stream to use the SSE path which bypasses this cap.`,\n ),\n )\n}\n\nasync function callPersona(\n persona: PersonaSpec,\n prompt: string,\n context: string | undefined,\n effort: Effort,\n signal?: AbortSignal,\n): Promise<{ content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n // Resolve the model id against the live catalog so a slug rename\n // (e.g., gemini-3.1-pro-preview → gemini-3.1-pro at GA) auto-resolves\n // through the existing fuzzy matcher rather than 404'ing.\n const resolvedModel = resolveModel(persona.model)\n const userText = buildUserText(prompt, context)\n\n // NOTE: predictedTooLong pre-flight cap fires in handleMcpPost\n // BEFORE handleRpc → handleToolsCall → inFlightToolsCall++ — see\n // the architectural invariant documented in CLAUDE.md. JSON-path\n // only; SSE callers bypass it. Don't duplicate it here.\n\n // NOTE on consumer-cancel signal: we deliberately do NOT pass\n // c.req.raw.signal into the upstream fetch. Bun/srvx aborts the\n // request signal as soon as the request body is fully consumed\n // (after `await c.req.json()`), which would make every persona call\n // fail immediately with \"This operation was aborted\". Instead, the\n // caller (handleToolsCall) creates its own AbortController and\n // threads it through `signal`. This is the controller registered in\n // `inflightAborts` and aborted by `notifications/cancelled` (Phase D\n // P1.5). See CLAUDE.md \"Bun request-signal quirk\" for full context.\n if (persona.endpoint === \"/v1/responses\") {\n const payload: ResponsesPayload = {\n model: resolvedModel,\n instructions: persona.baseInstructions,\n input: [\n {\n role: \"user\",\n content: [{ type: \"input_text\", text: userText }],\n },\n ],\n stream: false,\n // Reasoning effort — gpt-5.x adaptive-thinking reads this field\n // directly. Copilot's translator buckets to its own internal\n // levels (CLAUDE.md \"Thinking-mode translation\").\n reasoning: { effort },\n }\n const response = (await createResponses(\n payload,\n undefined,\n signal,\n )) as ResponsesApiResponse\n const text = extractResponsesText(response)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n }\n\n if (persona.endpoint === \"/v1/messages\") {\n // claude-opus-4-7 path. Copilot's adaptive-thinking models reject\n // Anthropic's standard `thinking: {type:\"enabled\", budget_tokens:N}`\n // shape with HTTP 400: \"thinking.type.enabled is not supported for\n // this model. Use thinking.type.adaptive and output_config.effort\".\n // Build the Copilot-shape directly. Empirical: confirmed 2026-05-14\n // via curl test against the proxy after build, opus_critic@xhigh\n // returned the expected 400 with that exact wording.\n //\n // max_tokens budget: choose a generous ceiling per effort tier so\n // the model has room for substantive reasoning + response without\n // truncation. Numbers chosen empirically:\n // low → 4096, medium → 8192, high → 16384, xhigh → 32768.\n const maxTokens =\n effort === \"low\" ? 4096\n : effort === \"medium\" ? 8192\n : effort === \"high\" ? 16384\n : 32768 // xhigh\n const body = JSON.stringify({\n model: resolvedModel,\n max_tokens: maxTokens,\n system: persona.baseInstructions,\n thinking: { type: \"adaptive\" },\n output_config: { effort },\n messages: [{ role: \"user\", content: userText }],\n })\n const response = await createMessages(body, undefined, signal)\n const json = (await response.json()) as MessagesApiResponse\n const text = extractMessagesText(json)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n }\n\n // /v1/chat/completions (Gemini)\n const payload: ChatCompletionsPayload = {\n model: resolvedModel,\n messages: [\n { role: \"system\", content: persona.baseInstructions },\n { role: \"user\", content: userText },\n ],\n stream: false,\n // Forwarded as-is. Per gemini_critic's review (see\n // docs/research/peer-mcp-investigation.md): Copilot's gemini route\n // may silently ignore this knob or 400 if it strict-validates the\n // schema; the latter surfaces through the existing tool-error path.\n reasoning_effort: effort,\n }\n const response = (await createChatCompletions(\n payload,\n undefined,\n signal,\n )) as ChatCompletionResponse\n const text = extractChatCompletionText(response)\n if (!text) {\n return toolError(`persona ${persona.agentName}: empty assistant output`)\n }\n return { content: [{ type: \"text\", text }] }\n}\n\ninterface PersonaTelemetry {\n name: string\n model: string\n durationMs: number\n result: \"ok\" | \"isError\" | \"exception\"\n errorMessage?: string\n}\n\nfunction logTelemetry(t: PersonaTelemetry): void {\n // Single-line stderr log so users can grep across sessions to see\n // which personas earn their keep. Honors the minimalist reviewer's\n // \"earn your keep\" critique — personas with near-zero use after\n // ~2 weeks are removal candidates.\n const parts = [\n `[peer-mcp]`,\n `name=${t.name}`,\n `model=${t.model}`,\n `duration_ms=${t.durationMs}`,\n `result=${t.result}`,\n ]\n if (t.errorMessage) parts.push(`error=${JSON.stringify(t.errorMessage)}`)\n // Use stderr directly so this is visible regardless of consola level.\n process.stderr.write(parts.join(\" \") + \"\\n\")\n}\n\nasync function handleToolsCall(\n body: JsonRpcRequest,\n): Promise<object> {\n const params = body.params ?? {}\n const name = typeof params.name === \"string\" ? params.name : \"\"\n const args = (params.arguments ?? {}) as Record<string, unknown>\n\n if (!name) {\n return rpcError(body.id, RPC_INVALID_PARAMS, \"tools/call missing name\")\n }\n\n // Routing: try personas first; fall through to non-persona utility\n // tools (currently just `web_search`). The two registries share the\n // tools/list surface but have different validation gates — personas\n // get the prompt+effort+predictedTooLong gauntlet; non-persona tools\n // do their own arg validation inside the handler closure.\n const persona = activePersonas().find((p) => p.toolNameHttp === name)\n const nonPersonaTool: NonPersonaMcpTool | undefined = persona\n ? undefined\n : NON_PERSONA_MCP_TOOLS.find((t) => t.toolNameHttp === name)\n\n if (!persona && !nonPersonaTool) {\n return rpcError(\n body.id,\n RPC_METHOD_NOT_FOUND,\n `tools/call: unknown tool \"${name}\"`,\n )\n }\n\n // Persona-only validation: prompt required, effort schema-checked\n // against EFFORT_LEVELS and gated by per-persona allowedEfforts. None\n // of this applies to non-persona tools (no prompt, no effort).\n let personaPrompt: string | undefined\n let personaContext: string | undefined\n let personaEffort: Effort | undefined\n if (persona) {\n // Validate effort shape against the global EFFORT_LEVELS allowlist\n // (rejects garbage like `effort: \"extreme\"`); the per-persona\n // allowedEfforts gate runs AFTER persona lookup below (rejects\n // valid-but-not-allowed-here tiers like `xhigh` on codex_critic).\n if (args.effort !== undefined && !isEffort(args.effort)) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: arguments.effort must be one of ${EFFORT_LEVELS.join(\"|\")}; got ${JSON.stringify(args.effort)}`,\n )\n }\n const requestedEffort = args.effort as Effort | undefined\n\n const prompt = typeof args.prompt === \"string\" ? args.prompt : \"\"\n if (!prompt) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: arguments.prompt is required`,\n )\n }\n personaPrompt = prompt\n personaContext = typeof args.context === \"string\" ? args.context : undefined\n\n // Per-persona effort gate. All four personas now allow all four\n // effort tiers (low|medium|high|xhigh). The gate remains in place so\n // a future persona that needs to constrain its tiers can do so\n // declaratively via PersonaSpec.allowedEfforts.\n if (\n requestedEffort !== undefined\n && !persona.allowedEfforts.includes(requestedEffort)\n ) {\n return rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `tools/call: persona \"${persona.toolNameHttp}\" does not accept effort=\"${requestedEffort}\". `\n + `Allowed: ${persona.allowedEfforts.join(\"|\")}.`,\n )\n }\n personaEffort = requestedEffort ?? persona.defaultEffort\n }\n\n // predictedTooLong pre-flight cap is enforced upstream of this\n // function — see `jsonPathPreflightCap` invoked by handleMcpPost\n // BEFORE handleRpc/handleToolsCall, so the slot increment below is\n // never reached for a rejected pre-flight (architectural invariant\n // documented in CLAUDE.md). The cap is JSON-PATH ONLY: SSE-streamed\n // responses (handleToolsCallSSE) bypass Claude Code's ~60s\n // tools/call ceiling via heartbeats and therefore don't need the\n // size-based gate. Non-persona tools have no thinking budget and so\n // the predictedTooLong cap doesn't apply to them either (the\n // jsonPathPreflightCap returns undefined when persona lookup misses,\n // which naturally exempts non-persona tools).\n\n if (inFlightToolsCall >= MAX_INFLIGHT_TOOLS_CALL) {\n // Documented per-call cap. NOT silent serialization — surface the\n // backpressure so Opus knows to retry shortly.\n return rpcResult(body.id, {\n content: [\n {\n type: \"text\",\n text: `Peer MCP queue full (${MAX_INFLIGHT_TOOLS_CALL} in-flight). Retry shortly, or wait for the current persona calls to complete.`,\n },\n ],\n isError: true,\n })\n }\n\n inFlightToolsCall++\n const startedAt = Date.now()\n // Phase D P1.5: register an AbortController so notifications/cancelled\n // can free the slot. Use the JSON-RPC request id as the key — clients\n // emit `params.requestId` matching it. If the client doesn't supply\n // an id (notification request), skip registration; nothing to cancel.\n const abortKey =\n body.id !== undefined && body.id !== null ? body.id : undefined\n let aborter: AbortController | undefined\n if (abortKey !== undefined) {\n aborter = new AbortController()\n inflightAborts.set(abortKey, aborter)\n }\n // Telemetry shape differs per branch — personas have a model id;\n // non-persona tools don't dispatch to a peer LLM, so log the tool\n // name as the \"model\" slot for grep'ability.\n const telemetryName = persona ? persona.agentName : nonPersonaTool!.toolNameHttp\n const telemetryModel = persona ? persona.model : \"(non-persona)\"\n try {\n const result = persona\n ? await callPersona(\n persona,\n personaPrompt!,\n personaContext,\n personaEffort!,\n aborter?.signal,\n )\n : await nonPersonaTool!.handler(args, aborter?.signal)\n logTelemetry({\n name: telemetryName,\n model: telemetryModel,\n durationMs: Date.now() - startedAt,\n result: result.isError ? \"isError\" : \"ok\",\n })\n return rpcResult(body.id, result)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logTelemetry({\n name: telemetryName,\n model: telemetryModel,\n durationMs: Date.now() - startedAt,\n result: \"exception\",\n errorMessage: message,\n })\n // Tool error vs JSON-RPC error: per MCP spec, runtime errors that\n // correspond to \"the tool ran but failed\" should surface as\n // result.isError=true (not as JSON-RPC errors). Catalog/auth/etc.\n // 404s from the upstream all go here. Aborts (from\n // notifications/cancelled) also land here as `AbortError`; treat\n // identically — the cancel notification is fire-and-forget, but\n // the original tools/call still gets a response so the client\n // doesn't hang waiting for it.\n return rpcResult(body.id, {\n content: [\n {\n type: \"text\",\n text: persona\n ? `persona ${persona.agentName} failed: ${message}`\n : `tool ${nonPersonaTool!.toolNameHttp} failed: ${message}`,\n },\n ],\n isError: true,\n })\n } finally {\n inFlightToolsCall--\n if (abortKey !== undefined) {\n inflightAborts.delete(abortKey)\n }\n }\n}\n\n/**\n * Handle `notifications/cancelled` per JSON-RPC 2.0 + MCP spec.\n * params.requestId is the id of an in-flight tools/call to abort.\n * Notifications return no body (handled by isNotification path in\n * handleRpc); this side-effect frees the in-flight slot.\n */\nfunction handleCancelledNotification(body: JsonRpcRequest): void {\n const params = body.params ?? {}\n const requestId = (params as { requestId?: unknown }).requestId\n if (\n requestId === undefined\n || (typeof requestId !== \"string\" && typeof requestId !== \"number\")\n ) {\n consola.debug(\n `[mcp] notifications/cancelled missing or invalid requestId: ${JSON.stringify(requestId)}`,\n )\n return\n }\n const aborter = inflightAborts.get(requestId)\n if (!aborter) {\n // Already completed or never registered. No-op — common race when\n // cancel races with completion.\n return\n }\n aborter.abort(new Error(\"client requested cancellation\"))\n // The finally block in handleToolsCall removes the entry on\n // completion; we don't delete here to avoid a TOCTOU race where the\n // upstream fetch is mid-completion when cancel arrives.\n}\n\nasync function handleRpc(\n _c: Context,\n body: JsonRpcRequest,\n): Promise<{ status: number; body: object | null }> {\n // Reject non-object envelopes (null, arrays, primitives) BEFORE we\n // dereference body.jsonrpc / body.method — without this guard a `null`\n // body throws TypeError on the property access, falls into the outer\n // catch in handleMcpPost, and returns RPC_INTERNAL_ERROR (-32603) when\n // the JSON-RPC spec wants RPC_INVALID_REQUEST (-32600) for shape errors.\n if (\n body === null\n || typeof body !== \"object\"\n || Array.isArray(body)\n ) {\n return {\n status: 200,\n body: rpcError(null, RPC_INVALID_REQUEST, \"jsonrpc 2.0 envelope required\"),\n }\n }\n if (body.jsonrpc !== \"2.0\" || typeof body.method !== \"string\") {\n return {\n status: 200,\n body: rpcError(body.id ?? null, RPC_INVALID_REQUEST, \"jsonrpc 2.0 envelope required\"),\n }\n }\n\n // Per JSON-RPC 2.0: requests without an `id` field are notifications\n // and MUST NOT receive a response body. The runtime must treat them\n // as fire-and-forget. We dispatch the method (so e.g. notifications/\n // initialized still gets observed), then return 202 + empty body\n // regardless of what the dispatched method returned.\n const isNotification = body.id === undefined\n\n switch (body.method) {\n case \"initialize\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, {\n protocolVersion: MCP_PROTOCOL_VERSION,\n // Capabilities advertised must match what we actually serve\n // (codex-critic Phase D requirement: \"empty lists are not\n // sufficient unless the whole MCP handshake is coherent\").\n // We expose tools (the personas), and stub resources/prompts\n // as empty lists so well-behaved clients don't error on\n // probing them. {} for resources/prompts means \"supported\n // but no list-changed notifications, no subscribe semantics\".\n capabilities: {\n tools: { listChanged: false },\n resources: {},\n prompts: {},\n },\n serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },\n }),\n }\n\n case \"notifications/initialized\":\n // Notifications have no id and expect no response body.\n // Return 202 Accepted with an empty body (Hono accepts null).\n return { status: 202, body: null }\n\n case \"tools/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { tools: toolEntries() }),\n }\n\n case \"tools/call\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: await handleToolsCall(body),\n }\n\n // --- Phase D: MCP method stubs with full handshake coherence ---\n // (codex-critic: \"if advertising resources:{}, also handle\n // resources/templates/list with {resourceTemplates: []}\")\n\n case \"resources/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { resources: [] }),\n }\n\n case \"resources/templates/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { resourceTemplates: [] }),\n }\n\n case \"resources/read\": {\n if (isNotification) return { status: 202, body: null }\n // Parametric — empty list isn't appropriate. Return proper\n // JSON-RPC -32602 invalid params per codex-critic Phase D.\n const uri = (body.params as { uri?: unknown } | undefined)?.uri\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `resources/read: resource URI not found: ${\n typeof uri === \"string\" ? uri : \"(missing/invalid uri)\"\n }`,\n ),\n }\n }\n\n case \"prompts/list\":\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcResult(body.id, { prompts: [] }),\n }\n\n case \"prompts/get\": {\n if (isNotification) return { status: 202, body: null }\n const name = (body.params as { name?: unknown } | undefined)?.name\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_INVALID_PARAMS,\n `prompts/get: prompt name not found: ${\n typeof name === \"string\" ? name : \"(missing/invalid name)\"\n }`,\n ),\n }\n }\n\n // --- Phase D P1.5: cancellation handling ---\n case \"notifications/cancelled\":\n // Side-effect only (abort the in-flight call). MUST NOT return\n // a body per JSON-RPC 2.0 notifications. Returns 202 like other\n // notifications.\n handleCancelledNotification(body)\n return { status: 202, body: null }\n\n case \"ping\":\n if (isNotification) return { status: 202, body: null }\n // MCP heartbeat — return empty result.\n return { status: 200, body: rpcResult(body.id, {}) }\n\n default:\n if (isNotification) return { status: 202, body: null }\n return {\n status: 200,\n body: rpcError(\n body.id,\n RPC_METHOD_NOT_FOUND,\n `unknown method: ${body.method}`,\n ),\n }\n }\n}\n\nexport async function handleMcpPost(c: Context): Promise<Response> {\n const auth = checkAuth(c)\n if (!auth.ok) {\n return c.json(\n rpcError(null, RPC_INVALID_REQUEST, auth.reason),\n auth.status,\n )\n }\n\n let body: JsonRpcRequest\n try {\n body = (await c.req.json()) as JsonRpcRequest\n } catch (err) {\n consola.debug(\"/mcp parse error:\", err)\n return c.json(\n rpcError(null, RPC_PARSE_ERROR, \"request body is not valid JSON\"),\n 200,\n )\n }\n\n // SSE-streamed response branch for `tools/call` when the client\n // advertises text/event-stream Accept (Claude Code's MCP HTTP client\n // does, per MCP 2025-06-18 Streamable HTTP transport spec). Streamed\n // responses bypass the per-tool-call wait timer that ~60s-caps JSON\n // responses on Claude Code v2.1.113+ (regressions #50289 / #52137,\n // documented in docs/research/peer-mcp-investigation.md). Heartbeat\n // `notifications/progress` events keep the connection alive while\n // the upstream Copilot call is in flight; the final tools/call\n // response is delivered as the closing `message` event. Non-tools/call\n // RPC methods (initialize, tools/list, etc.) stay on the JSON path —\n // they're synchronous and don't benefit from streaming.\n if (\n typeof body === \"object\"\n && body !== null\n && !Array.isArray(body)\n && body.method === \"tools/call\"\n && acceptsEventStream(c.req.header(\"accept\"))\n ) {\n return handleToolsCallSSE(body)\n }\n\n // JSON-path pre-flight predictedTooLong cap. SSE clients (above)\n // bypass Claude Code's ~60s tools/call ceiling via heartbeats, but\n // JSON-path clients (raw curl with `Accept: application/json`,\n // older MCP clients without SSE awareness) still hit the underlying\n // timer. Reject here as a fast actionable error instead of letting\n // the request burn an inFlight slot for ~60s before the client\n // times out — invariant: the cap MUST fire BEFORE handleToolsCall\n // so inFlightToolsCall++ is never reached for a rejected pre-flight\n // (CLAUDE.md). `jsonPathPreflightCap` returns undefined for any\n // shape problem (missing prompt, unknown name, invalid effort) so\n // handleRpc returns the canonical -32601/-32602 error code.\n if (\n typeof body === \"object\"\n && body !== null\n && !Array.isArray(body)\n && body.method === \"tools/call\"\n ) {\n const preflight = jsonPathPreflightCap(body)\n if (preflight) return c.json(preflight, 200)\n }\n\n try {\n const { status, body: respBody } = await handleRpc(c, body)\n if (respBody === null) return c.body(null, status as 202)\n return c.json(respBody, status as 200)\n } catch (err) {\n consola.error(\"/mcp handler error:\", err)\n // Be defensive about `body.id` — body could be null or a non-object\n // primitive that slipped past the JSON parse (rare but possible).\n const echoId =\n typeof body === \"object\" && body !== null && !Array.isArray(body)\n ? (body as JsonRpcRequest).id ?? null\n : null\n return c.json(\n rpcError(\n echoId,\n RPC_INTERNAL_ERROR,\n err instanceof Error ? err.message : String(err),\n ),\n 200,\n )\n }\n}\n\n/**\n * Accept-header parsing for MCP Streamable HTTP. Per MCP 2025-06-18\n * spec, clients send `Accept: application/json, text/event-stream` to\n * indicate they can consume either response shape. Server picks; for\n * tools/call we pick SSE because Claude Code's per-tool-call timer\n * (~60s on v2.1.113+) does not fire on streamed responses.\n *\n * Lenient parse: split on commas, strip params (q-values, charset),\n * trim, lowercase, look for the SSE token. Returns false on undefined\n * / empty / strict-JSON-only Accept.\n */\nfunction acceptsEventStream(accept: string | undefined): boolean {\n if (!accept) return false\n const tokens = accept\n .toLowerCase()\n .split(\",\")\n .map((t) => t.split(\";\")[0].trim())\n return tokens.includes(\"text/event-stream\")\n}\n\n/**\n * SSE-streamed response for a single tools/call. Delegates the actual\n * upstream call to `handleToolsCall` (so the per-persona effort gate,\n * predictedTooLong cap, AbortController registration, telemetry, and\n * inFlight slot accounting all run identically); wraps the awaited\n * result in an SSE envelope with periodic heartbeats while the upstream\n * fetch is in flight.\n *\n * SSE event format (per MCP Streamable HTTP):\n * event: message\n * data: <json-rpc-2.0 message>\\n\\n\n *\n * - Heartbeats are JSON-RPC `notifications/progress` notifications with\n * the request id as `progressToken` (per MCP progress-notification spec).\n * - The final message is the JSON-RPC response envelope returned by\n * handleToolsCall — same structure as the JSON-path response.\n * - On consumer cancel (ReadableStream.cancel), the heartbeat interval\n * is cleared and the inFlight slot's AbortController is signalled\n * (handleToolsCall observes the abort and returns an error envelope\n * that we drop unwritten — controller is already closed).\n *\n * Per CLAUDE.md \"Stream lifecycle\" / \"The smoking gun\" rules: every\n * controller.enqueue/close is wrapped in a try/catch that swallows the\n * \"Invalid state: Controller is already closed\" race without warning.\n */\nconst SSE_HEARTBEAT_INTERVAL_MS = 5000\n\nasync function handleToolsCallSSE(body: JsonRpcRequest): Promise<Response> {\n const encoder = new TextEncoder()\n // Kick off the actual tool call as a Promise. handleToolsCall handles\n // all gates, slot accounting, abort registration, telemetry — we just\n // wrap its eventual result in an SSE envelope.\n const callPromise = handleToolsCall(body)\n\n const stream = new ReadableStream<Uint8Array>({\n async start(controller) {\n let closed = false\n const safeEnqueue = (chunk: Uint8Array): void => {\n if (closed) return\n try {\n controller.enqueue(chunk)\n } catch (err) {\n // Controller already closed by consumer cancel or earlier\n // close — common race between heartbeat tick and stream\n // teardown. Per CLAUDE.md \"smoking gun\" rule, do NOT log\n // this as a warning; it's expected.\n consola.debug(\"/mcp SSE enqueue after close (expected race):\", err)\n closed = true\n }\n }\n const safeClose = (): void => {\n if (closed) return\n closed = true\n try {\n controller.close()\n } catch (err) {\n consola.debug(\"/mcp SSE close after close:\", err)\n }\n }\n const sseFrame = (rpcMessage: object): Uint8Array =>\n encoder.encode(`event: message\\ndata: ${JSON.stringify(rpcMessage)}\\n\\n`)\n const heartbeatFrame = (): Uint8Array =>\n sseFrame({\n jsonrpc: \"2.0\",\n method: \"notifications/progress\",\n params: {\n progressToken: body.id ?? null,\n progress: 0,\n message: \"in flight\",\n },\n })\n\n // Initial heartbeat (proves the stream is open) + recurring\n // heartbeats every SSE_HEARTBEAT_INTERVAL_MS until the call\n // resolves.\n safeEnqueue(heartbeatFrame())\n const heartbeatHandle = setInterval(\n () => safeEnqueue(heartbeatFrame()),\n SSE_HEARTBEAT_INTERVAL_MS,\n )\n\n try {\n const result = await callPromise\n safeEnqueue(sseFrame(result))\n } catch (err) {\n consola.error(\"/mcp SSE upstream error:\", err)\n safeEnqueue(\n sseFrame(\n rpcError(\n body.id ?? null,\n RPC_INTERNAL_ERROR,\n err instanceof Error ? err.message : String(err),\n ),\n ),\n )\n } finally {\n clearInterval(heartbeatHandle)\n safeClose()\n }\n },\n cancel() {\n // Consumer disconnected. handleToolsCall's AbortController is\n // keyed by body.id and already registered in inflightAborts;\n // signal it so the upstream Copilot fetch tears down and the\n // inFlight slot is freed promptly. No need to clear heartbeats\n // here — the start() function's finally-block does that when\n // callPromise resolves (or rejects with the abort).\n const abortKey =\n body.id !== undefined && body.id !== null ? body.id : undefined\n if (abortKey !== undefined) {\n const aborter = inflightAborts.get(abortKey)\n if (aborter) aborter.abort(new Error(\"client disconnected SSE stream\"))\n }\n },\n })\n\n return new Response(stream, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n \"Connection\": \"keep-alive\",\n // MCP Streamable HTTP transport identifier so middleboxes (and\n // future Claude Code versions that key off this) handle the\n // response correctly.\n \"X-Accel-Buffering\": \"no\",\n },\n })\n}\n\nexport function handleMcpDelete(c: Context): Response {\n // MCP DELETE is for session teardown. v1 is session-less, so this\n // is a 200 ack regardless of body. Body is intentionally NOT\n // parsed — there's no schema to validate against, and parsing an\n // attacker-controlled body adds attack surface.\n const auth = checkAuth(c)\n if (!auth.ok) {\n return c.json(\n rpcError(null, RPC_INVALID_REQUEST, auth.reason),\n auth.status,\n )\n }\n return c.body(null, 200)\n}\n\n/** Test helper: reset in-flight counter between tests. */\nexport function __resetInFlightForTests(): void {\n inFlightToolsCall = 0\n}\n\n/** Test helper: peek the in-flight counter. */\nexport function __getInFlightForTests(): number {\n return inFlightToolsCall\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleMcpDelete, handleMcpPost } from \"./handler\"\n\nexport const mcpRoutes = new Hono()\n\nmcpRoutes.post(\"/\", async (c) => {\n try {\n return await handleMcpPost(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nmcpRoutes.delete(\"/\", (c) => {\n try {\n return handleMcpDelete(c)\n } catch {\n return c.body(null, 500)\n }\n})\n","/**\n * Phase I: ADVISOR proxy-side translation.\n *\n * ADVISOR is Anthropic's server-side server_tool_use mechanism — the\n * model invokes a stronger reviewer model with the full conversation\n * context. Copilot doesn't implement it (returns 400 'unsupported beta\n * header(s): advisor-tool-2026-03-01'). This module implements the\n * equivalent semantics proxy-side per gemini-critic's streaming design:\n *\n * 1. Strip the `advisor-tool-` beta header before forwarding to Copilot\n * (Phase A already does this via EXPLICITLY_STRIPPED_BETA_PREFIXES).\n * 2. Inject a `__anthropic_advisor` tool definition into body.tools[]\n * (with cc-backup's ADVISOR_TOOL_INSTRUCTIONS as the description so\n * the model knows when to call it). The double-underscore prefix\n * avoids collision with any user MCP server's `advisor` tool.\n * 3. Stream the Copilot response, watching for tool_use blocks with\n * name `__anthropic_advisor`. When detected:\n * a. Translate the block in-flight: emit\n * `{type: \"server_tool_use\", name: \"advisor\"}` to the client so\n * Claude Code's AdvisorMessage.tsx renders the \"Consulting\n * advisor...\" spinner immediately (gemini: do NOT buffer the loop\n * — the UI hangs without an indicator).\n * b. After the current turn's `message_stop` would have arrived,\n * suppress it and run the advisor model server-side with the\n * conversation context up through the current assistant turn.\n * c. Synthesize an `advisor_tool_result` block to the client with\n * the advisor's text response.\n * d. Append the synthetic tool_result to the conversation and\n * re-call Copilot for the next turn — stream onto the SAME\n * SSE connection (no new message_start; the original one is\n * still open). Loop up to ADVISOR_MAX_TURNS times.\n * 4. Cross-lab default: route the advisor call to a different model\n * family than the main loop (gpt-5.5 by default) so the user gets\n * a true \"second set of eyes\" instead of Opus reviewing Opus\n * (gemini-critic finding).\n *\n * The translate-loop is bounded to a single user request — no\n * persistent state across requests is needed (unlike Phase G's\n * mcp_servers translate which had unfix-able continuation-after-TTL\n * holes). Each request evaluates ADVISOR fresh from the body.\n */\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { isControllerClosedError } from \"~/lib/stream-relay\"\nimport { resolveModel } from \"~/lib/utils\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst ENCODER = new TextEncoder()\n\n/** The tool name we inject for Copilot. Double-underscore prefix\n * avoids collision with any user MCP server's `advisor` tool. */\nexport const ADVISOR_INTERNAL_TOOL_NAME = \"__anthropic_advisor\"\n\n/** The Anthropic-spec name used in the translated server_tool_use\n * block sent to the client. cc-backup AdvisorMessage.tsx requires\n * this exact name to render the advisor spinner. */\nexport const ADVISOR_CLIENT_TOOL_NAME = \"advisor\"\n\n/** Hard cap on advisor calls per request to bound runaway behavior.\n * Matches Phase G's loop bound; ADVISOR is typically called 1-3\n * times per session per cc-backup ADVISOR_TOOL_INSTRUCTIONS. */\nexport const ADVISOR_MAX_TURNS = 16\n\n/** Default advisor model + reasoning effort. Per gemini-critic + user\n * direction: hardcode to a cross-lab model (gpt-5.5 — Copilot's\n * /responses-only flagship) at xhigh effort. The cross-lab choice\n * gives a true \"second set of eyes\" instead of the main model\n * reviewing itself; xhigh effort buys the deep-dive reasoning that\n * matches Anthropic's own ADVISOR (which uses a stronger reviewer\n * model — Opus 4.6/Sonnet 4.6 typically). */\nexport const ADVISOR_DEFAULT_MODEL = \"gpt-5.5\"\nexport const ADVISOR_DEFAULT_EFFORT = \"xhigh\"\n\ntype Effort = \"low\" | \"medium\" | \"high\" | \"xhigh\"\n\n/** ADVISOR_TOOL_INSTRUCTIONS verbatim from cc-backup\n * src/utils/advisor.ts — describes when the model should invoke\n * the advisor. Long-form prose; see source for justification. */\nexport const ADVISOR_TOOL_INSTRUCTIONS = `# Advisor Tool\n\nYou have access to an \\`advisor\\` tool backed by a stronger reviewer model. It takes NO parameters -- when you call it, your entire conversation history is automatically forwarded. The advisor sees the task, every tool call you've made, every result you've seen.\n\nCall advisor BEFORE substantive work -- before writing code, before committing to an interpretation, before building on an assumption. If the task requires orientation first (finding files, reading code, seeing what's there), do that, then call advisor. Orientation is not substantive work. Writing, editing, and declaring an answer are.\n\nAlso call advisor:\n- When you believe the task is complete. BEFORE this call, make your deliverable durable: write the file, stage the change, save the result. The advisor call takes time; if the session ends during it, a durable result persists and an unwritten one doesn't.\n- When stuck -- errors recurring, approach not converging, results that don't fit.\n- When considering a change of approach.\n\nOn tasks longer than a few steps, call advisor at least once before committing to an approach and once before declaring done. On short reactive tasks where the next action is dictated by tool output you just read, you don't need to keep calling -- the advisor adds most of its value on the first call, before the approach crystallizes.\n\nGive the advice serious weight. If you follow a step and it fails empirically, or you have primary-source evidence that contradicts a specific claim (the file says X, the code does Y), adapt. A passing self-test is not evidence the advice is wrong -- it's evidence your test doesn't check what the advice is checking.\n\nIf you've already retrieved data pointing one way and the advisor points another: don't silently switch. Surface the conflict in one more advisor call -- \"I found X, you suggest Y, which constraint breaks the tie?\" The advisor saw your evidence but may have underweighted it; a reconcile call is cheaper than committing to the wrong branch.`\n\nconst ADVISOR_OPT_OUT_ENV = \"CLAUDE_CODE_DISABLE_ADVISOR_TOOL\"\n\n/**\n * Detect whether the request asked for ADVISOR (incoming\n * `anthropic-beta` header contains an `advisor-tool-` prefix). Also\n * respects the `CLAUDE_CODE_DISABLE_ADVISOR_TOOL` opt-out env var\n * (set by the user to globally disable; matches cc-backup advisor.ts\n * line 61).\n */\nexport function isAdvisorRequested(rawBetaHeader: string | undefined): boolean {\n if (!rawBetaHeader) return false\n if (process.env[ADVISOR_OPT_OUT_ENV]) return false\n return rawBetaHeader\n .split(\",\")\n .map((s) => s.trim())\n .some((v) => v.startsWith(\"advisor-tool-\"))\n}\n\n/**\n * Inject the __anthropic_advisor tool definition into the body's tools\n * array. Returns a new body string. Idempotent — if the tool is already\n * present (e.g. the user's MCP shadowed it) we leave the existing one\n * alone and return the body unchanged.\n *\n * Also strips any tool entry with `type: \"advisor_*\"` (Anthropic API's\n * native server-side advisor tool — `advisor_20260301` and future\n * variants). When `CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL=1` is\n * set, Claude Code injects its own advisor tool with this type into\n * `tools[]`. Copilot 400s on the unknown tool type (\"Input tag\n * 'advisor_20260301' found using 'type' does not match any of the\n * expected tags\"), so the proxy must strip it before forwarding while\n * still injecting our custom `__anthropic_advisor` tool that the model\n * can invoke. The proxy's intercept on the response stream then\n * translates the model's `tool_use{__anthropic_advisor}` to the\n * client-shape `server_tool_use{name:\"advisor\"}` + `advisor_tool_result`\n * blocks the client expects.\n */\nexport function injectAdvisorTool(rawBody: string): string {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n const rawTools = Array.isArray(parsed.tools) ? parsed.tools : []\n // Strip Anthropic-native advisor typed tools (Copilot 400s on these).\n const tools = rawTools.filter((t: AnyRecord) => {\n if (typeof t !== \"object\" || t === null) return true\n const type = (t as AnyRecord).type\n return typeof type !== \"string\" || !type.startsWith(\"advisor_\")\n })\n const stripped = tools.length !== rawTools.length\n const alreadyInjected = tools.some(\n (t: AnyRecord) => t?.name === ADVISOR_INTERNAL_TOOL_NAME,\n )\n if (alreadyInjected && !stripped) {\n return rawBody // no-op: already injected and nothing to strip\n }\n parsed.tools = alreadyInjected\n ? tools\n : [\n ...tools,\n {\n name: ADVISOR_INTERNAL_TOOL_NAME,\n description: ADVISOR_TOOL_INSTRUCTIONS,\n input_schema: {\n type: \"object\",\n properties: {},\n required: [],\n },\n },\n ]\n return JSON.stringify(parsed)\n}\n\n/** Character budget for rendered conversation text passed to the\n * advisor model. gpt-5.5 (default advisor) caps prompt input at\n * 272,000 tokens. At a conservative ~3 chars/token (mixed prose +\n * code + JSON), 720,000 chars renders to ≈240,000 tokens, leaving\n * ~32,000 tokens of headroom for the system prompt and per-turn\n * framing overhead. Without this cap, long Claude Code sessions\n * produce 400 `model_max_prompt_tokens_exceeded` from /v1/responses\n * and the advisor falls back silently. */\nexport const ADVISOR_MAX_CONVERSATION_CHARS = 720_000\n\n/**\n * Render an Anthropic-shape conversation (messages array with\n * role/content blocks) as a single human-readable text blob. Used\n * as the input to the advisor model (gpt-5.5 via /v1/responses\n * doesn't have a 1:1 mapping for Anthropic's tool_use/tool_result\n * blocks; serializing to text preserves the semantics — the advisor\n * just needs to READ the conversation, not produce more of it).\n *\n * Front-truncates oldest turns when the rendered output would exceed\n * `maxChars`. The advisor cares more about current state (latest\n * tool calls, errors, in-flight task) than the original prompt —\n * mirrors Claude Code's own context-truncation strategy. When any\n * turns are dropped, prepends a `[TRUNCATED: N earlier turn(s)\n * omitted ...]` notice so the advisor knows the transcript is\n * partial and can flag if it needs the missing context.\n */\nexport function renderConversationAsText(\n conversation: Array<AnyRecord>,\n maxChars: number = ADVISOR_MAX_CONVERSATION_CHARS,\n): string {\n const turnBlocks: Array<string> = []\n for (let i = 0; i < conversation.length; i++) {\n const msg = conversation[i]\n const role = (msg.role as string) ?? \"unknown\"\n const block: Array<string> = [`### Turn ${i + 1} — ${role}`]\n const content = msg.content\n if (typeof content === \"string\") {\n block.push(content)\n } else if (Array.isArray(content)) {\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const b = part as AnyRecord\n if (b.type === \"text\" && typeof b.text === \"string\") {\n block.push(b.text)\n } else if (b.type === \"tool_use\") {\n block.push(\n `[tool_use ${b.name ?? \"?\"}(${b.id ?? \"?\"}): ${JSON.stringify(b.input ?? {})}]`,\n )\n } else if (b.type === \"tool_result\") {\n const c =\n typeof b.content === \"string\" ? b.content : JSON.stringify(b.content)\n block.push(`[tool_result ${b.tool_use_id ?? \"?\"}]:\\n${c}`)\n } else {\n block.push(`[${b.type}: ${JSON.stringify(b).slice(0, 500)}]`)\n }\n }\n }\n block.push(\"\")\n turnBlocks.push(block.join(\"\\n\"))\n }\n\n // Walk from the latest turn backward, accumulating until the next\n // turn would push us over budget. The \"+1\" accounts for the join\n // separator between turn blocks.\n let totalChars = 0\n let firstKeptIdx = turnBlocks.length\n for (let i = turnBlocks.length - 1; i >= 0; i--) {\n const len = turnBlocks[i].length + 1\n if (totalChars + len > maxChars) break\n totalChars += len\n firstKeptIdx = i\n }\n\n // Edge case: even the latest turn alone exceeds the budget. Hard-\n // truncate its tail to fit (advisor still gets the most-recent\n // context, just not all of it). 200-char reserve for the notice.\n if (firstKeptIdx === turnBlocks.length && turnBlocks.length > 0) {\n const last = turnBlocks[turnBlocks.length - 1]\n const reserve = 200\n const tail = last.slice(-(maxChars - reserve))\n return (\n `[TRUNCATED: conversation too long for advisor model context; `\n + `only the tail of the latest (turn ${turnBlocks.length}) is shown]\\n\\n`\n + tail\n )\n }\n\n const kept = turnBlocks.slice(firstKeptIdx)\n if (firstKeptIdx > 0) {\n kept.unshift(\n `[TRUNCATED: ${firstKeptIdx} earlier turn(s) omitted to fit advisor `\n + `model context budget; ${turnBlocks.length - firstKeptIdx} most-recent `\n + `turn(s) shown below]\\n`,\n )\n }\n return kept.join(\"\\n\")\n}\n\n/**\n * Run the advisor model with the full conversation context. Returns\n * the advisor's text response.\n *\n * Routes by model family:\n * - gpt-5.x / codex / o-series (have `/responses` in supported_endpoints):\n * use createResponses with `reasoning.effort` set. This is the\n * default path — gpt-5.5 at xhigh effort.\n * - claude-* (no `/responses`): fall back to createMessages.\n *\n * The conversation is serialized to text via renderConversationAsText\n * so the advisor model (which may not natively understand Anthropic's\n * tool_use/tool_result block shapes) sees a flat readable transcript.\n * This loses some structural fidelity but matches the spirit of\n * Anthropic's own ADVISOR (\"see the whole task + every tool call +\n * every result\").\n */\nasync function runAdvisor(\n conversation: Array<AnyRecord>,\n advisorModel: string,\n advisorEffort: Effort,\n): Promise<string> {\n const advisorSystem =\n \"You are an expert advisor reviewing an in-progress Claude Code session. \"\n + \"The transcript below is the work-in-progress (turns numbered, with \"\n + \"tool calls and results inlined). Read carefully and provide concrete, \"\n + \"actionable advice on the next step or course-correction. Be specific — \"\n + \"cite the parts of the transcript you're responding to. If the assistant \"\n + \"is on the right track, say so explicitly. If they're stuck or off-track, \"\n + \"name the specific assumption or step to revisit. Aim for 2-5 paragraphs \"\n + \"of substantive guidance.\"\n\n const conversationText = renderConversationAsText(conversation)\n const resolvedAdvisorModel = resolveModel(advisorModel)\n\n // Route by model family. gpt-5.x / o-series / codex go through\n // /v1/responses with reasoning.effort. claude-* stays on /v1/messages.\n // Quick heuristic: if the model id starts with \"gpt-\" or contains\n // \"codex\" or starts with \"o\", use /responses. Otherwise /v1/messages.\n // (Could be tightened with a state.models lookup, but the fast-path\n // text match is correct for every model in Copilot's catalog today.)\n const useResponses = /^(gpt-|o\\d|.*codex)/i.test(resolvedAdvisorModel)\n\n if (useResponses) {\n const payload: ResponsesPayload = {\n model: resolvedAdvisorModel,\n instructions: advisorSystem,\n input: [\n {\n role: \"user\",\n content: [{ type: \"input_text\", text: conversationText }],\n },\n ],\n stream: false,\n // gpt-5.x reads reasoning.effort directly. xhigh is the deepest\n // reasoning bucket — appropriate for adversarial review since the\n // advisor adds most of its value on the FIRST call (per cc-backup\n // ADVISOR_TOOL_INSTRUCTIONS line 31), so don't be cheap.\n reasoning: { effort: advisorEffort },\n }\n const response = (await createResponses(payload)) as ResponsesApiResponse\n const out: Array<string> = []\n for (const item of response.output) {\n if (typeof item !== \"object\" || item === null) continue\n const obj = item as Record<string, unknown>\n if (obj.type !== \"message\" || obj.role !== \"assistant\") continue\n const content = obj.content\n if (!Array.isArray(content)) continue\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue\n const p = part as Record<string, unknown>\n if (\n (p.type === \"output_text\" || p.type === \"text\")\n && typeof p.text === \"string\"\n ) {\n out.push(p.text)\n }\n }\n }\n const text = out.join(\"\")\n if (!text) {\n throw new Error(\n `Advisor model ${resolvedAdvisorModel} returned empty assistant output`,\n )\n }\n return text\n }\n\n // claude-* fallback: /v1/messages with the conversation as a single\n // user message. Effort doesn't apply (Anthropic uses thinking mode\n // separately).\n const advisorBody = JSON.stringify({\n model: resolvedAdvisorModel,\n max_tokens: 4096,\n system: advisorSystem,\n messages: [{ role: \"user\", content: conversationText }],\n stream: false,\n })\n const response = await createMessages(advisorBody, {})\n const json = (await response.json()) as AnyRecord\n const blocks = Array.isArray(json.content) ? json.content : []\n const text = blocks\n .filter((b: AnyRecord) => b.type === \"text\" && typeof b.text === \"string\")\n .map((b: AnyRecord) => b.text as string)\n .join(\"\\n\\n\")\n if (!text) {\n throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`)\n }\n return text\n}\n\ninterface ToolUseTracker {\n /** Block index from the SSE stream */\n index: number\n /** tool_use_id assigned by the upstream model — used in the\n * conversation-replay path sent back to Copilot in next turns of\n * the in-loop advisor flow (must match Anthropic `^toolu_*$`). */\n id: string\n /** Client-facing server_tool_use id derived from `id` — used in\n * the translated server_tool_use + advisor_tool_result blocks\n * emitted on the SSE stream to the client. Anthropic spec\n * requires this to match `^srvtoolu_[a-zA-Z0-9_]+$` (parallel to\n * `toolu_*` for client-fulfilled tools). Mismatched format causes\n * Copilot to 400 the conversation history when Claude Code\n * replays it later — the failure is delayed because the original\n * request succeeds; the broken block only hits a validator on a\n * much-later turn that includes it in the message history. */\n clientId: string\n /** Accumulated input_json_delta text (advisor takes no input but\n * we accumulate defensively) */\n inputJson: string\n}\n\n/**\n * Derive a spec-compliant `srvtoolu_*` id for a client-facing\n * `server_tool_use` (and matching `advisor_tool_result.tool_use_id`)\n * from the upstream model's `toolu_*` id.\n *\n * Anthropic spec: `^srvtoolu_[a-zA-Z0-9_]+$`. If the upstream id\n * suffix contains chars outside that charset (e.g., a hyphenated id\n * from a non-Anthropic provider, or a corrupt id), fall back to a\n * synthesized stable id keyed by the SSE block index. Defensive\n * against edge cases that would otherwise emit a malformed block —\n * spec violation in either direction is a 400.\n */\nexport function toClientServerToolUseId(\n id: string,\n fallbackIndex: number,\n): string {\n const suffix = id.startsWith(\"toolu_\") ? id.slice(\"toolu_\".length) : id\n if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `srvtoolu_${suffix}`\n return `srvtoolu_advisor_${fallbackIndex}`\n}\n\n/**\n * A captured assistant content block from the upstream Copilot stream,\n * suitable for replay back to Copilot in the advisor loop's\n * continuation turn. Holds the raw `content_block` object verbatim so\n * future block types we don't recognize today (thinking, redacted_\n * thinking, image, document, citations, etc.) flow through correctly.\n *\n * Mutated in place during streaming: text_delta appends to .block.text,\n * thinking_delta to .block.thinking, signature_delta to .block.signature,\n * input_json_delta accumulates into partialJson and is parsed into\n * .block.input at content_block_stop (Anthropic spec requires\n * tool_use.input to be a parsed object on replay, not a raw JSON string).\n *\n * Special case: when the upstream block is `tool_use{__anthropic_advisor}`,\n * the proxy SYNTHESIZES a different block for client output\n * (`server_tool_use{name:\"advisor\"}` with the `srvtoolu_*` clientId)\n * AND tracks the original `toolu_*` id in `advisorReplay` so the\n * Copilot-replay continuation request uses the original.\n */\ninterface CapturedBlock {\n /** The full `content_block` object from the upstream\n * content_block_start event (or, for advisor blocks, an internal\n * representation we'll synthesize on emit). */\n block: AnyRecord\n /** Raw partial_json buffer for tool_use blocks. JSON.parse'd into\n * `block.input` at content_block_stop. */\n partialJson: string\n /** Set if this block was the advisor invocation. The\n * Copilot-replay path must emit a `tool_use{__anthropic_advisor}`\n * with the original `toolu_*` id, NOT the client-facing\n * `srvtoolu_*` id; the input is the parsed advisor input (defaults\n * to {} if no input_json_delta arrived — codex round-7: don't bake\n * \"advisor takes no input\" as a load-bearing invariant). */\n advisorReplay?: { id: string }\n /** Set during content_block_stop if this block should be dropped\n * from the replay (e.g., empty text block). */\n dropFromReplay?: boolean\n}\n\n/**\n * Build an SSE event line in the canonical Anthropic shape:\n * event: <type>\n * data: <json>\n * <blank>\n */\nfunction sseEvent(type: string, data: AnyRecord): string {\n return `event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`\n}\n\n/**\n * The streaming translate-loop. Returns a ReadableStream<Uint8Array>\n * suitable to wrap with Hono's c.body() / new Response().\n *\n * @param firstResponse The first Copilot streaming response\n * @param initialConversation The conversation messages from the\n * incoming request (used as the starting context for advisor calls\n * and continuation Copilot calls).\n * @param baseBody Parsed initial request body (model, max_tokens,\n * system, etc.) — used as the template for continuation Copilot calls.\n * @param requestHeaders Extra headers (model-specific + filtered\n * anthropic-beta) for downstream Copilot calls.\n * @param advisorModel Which model to route advisor calls to. Defaults\n * to ADVISOR_DEFAULT_MODEL (cross-lab).\n */\nexport function buildAdvisorStream(opts: {\n firstResponse: Response\n initialConversation: Array<AnyRecord>\n baseBody: AnyRecord\n requestHeaders: Record<string, string>\n advisorModel?: string\n advisorEffort?: Effort\n}): ReadableStream<Uint8Array> {\n const advisorModel = opts.advisorModel ?? ADVISOR_DEFAULT_MODEL\n const advisorEffort = opts.advisorEffort ?? ADVISOR_DEFAULT_EFFORT\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n const conversation = [...opts.initialConversation]\n let messageStartForwarded = false\n let nextSyntheticIndex = 0\n let turnsRun = 0\n\n const safeEnqueue = (bytes: Uint8Array): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (err) {\n if (isControllerClosedError(err)) return false\n throw err\n }\n }\n\n const safeEnqueueEvent = (type: string, data: AnyRecord): boolean =>\n safeEnqueue(ENCODER.encode(sseEvent(type, data)))\n\n // Process one Copilot streaming response. Returns the assistant\n // turn's blocks + the advisor tool_use info if one was called.\n // Forwards events to the client as it goes.\n async function processOneTurn(\n response: Response,\n ): Promise<{\n capturedBlocks: Array<CapturedBlock>\n advisorToolUse: ToolUseTracker | null\n }> {\n const capturedBlocks: Array<CapturedBlock> = []\n let advisorToolUse: ToolUseTracker | null = null\n // Track which upstream block index corresponds to which entry\n // in capturedBlocks (so deltas know which to update).\n const indexToBlock = new Map<number, CapturedBlock>()\n\n for await (const ev of events(response)) {\n if (!ev.event || !ev.data) continue\n let payload: AnyRecord\n try {\n payload = JSON.parse(ev.data) as AnyRecord\n } catch {\n // Non-JSON data — forward as-is (defensive).\n const ok = safeEnqueue(ENCODER.encode(`event: ${ev.event}\\ndata: ${ev.data}\\n\\n`))\n if (!ok) return { capturedBlocks, advisorToolUse }\n continue\n }\n\n switch (ev.event) {\n case \"message_start\": {\n if (!messageStartForwarded) {\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n messageStartForwarded = true\n }\n // Suppress duplicate message_start on continuation turns —\n // we keep one open for the entire advisor loop.\n continue\n }\n\n case \"content_block_start\": {\n const block = (payload as AnyRecord).content_block as AnyRecord | undefined\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n if (block && upstreamIndex !== undefined) {\n // Re-index to the synthetic stream's monotonic index\n // (continuation turns reset their upstream index to 0,\n // which would collide with prior turns' indices).\n const myIndex = nextSyntheticIndex++\n\n if (\n block.type === \"tool_use\"\n && block.name === ADVISOR_INTERNAL_TOOL_NAME\n ) {\n // Translate to server_tool_use{advisor}\n const id =\n typeof block.id === \"string\"\n ? block.id\n : `toolu_advisor_${myIndex}`\n advisorToolUse = {\n index: myIndex,\n id,\n clientId: toClientServerToolUseId(id, myIndex),\n inputJson: \"\",\n }\n const translated = {\n ...payload,\n index: myIndex,\n content_block: {\n type: \"server_tool_use\",\n id: advisorToolUse.clientId,\n name: ADVISOR_CLIENT_TOOL_NAME,\n input: {},\n },\n }\n if (!safeEnqueueEvent(ev.event, translated)) return { capturedBlocks, advisorToolUse }\n // Track for later — the Copilot-replay continuation\n // turn needs to round-trip with the INTERNAL name +\n // ORIGINAL toolu_* id (Copilot doesn't know\n // server_tool_use). The advisor branch reuses the\n // standard captured-block pipeline (deltas accumulate,\n // input parses) so that future versions of advisor\n // that take params would Just Work — we synthesize\n // the actual replay shape in the content mapping.\n const captured: CapturedBlock = {\n block: {\n type: \"tool_use\",\n id,\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input: {},\n },\n partialJson: \"\",\n advisorReplay: { id },\n }\n capturedBlocks.push(captured)\n indexToBlock.set(upstreamIndex, captured)\n } else {\n // Forward as-is, with re-indexed.\n const reindexed = { ...payload, index: myIndex }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n // Store the raw content_block verbatim — preserves\n // every field upstream sent (including ones the proxy\n // doesn't know about: thinking, signature, image src,\n // document data, citations, etc.). Mutated in place\n // by deltas; emitted verbatim on replay.\n const captured: CapturedBlock = {\n block: { ...block },\n partialJson: \"\",\n }\n capturedBlocks.push(captured)\n indexToBlock.set(upstreamIndex, captured)\n }\n }\n continue\n }\n\n case \"content_block_delta\": {\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n const delta = (payload as AnyRecord).delta as AnyRecord | undefined\n if (upstreamIndex !== undefined) {\n const captured =\n upstreamIndex !== undefined ? indexToBlock.get(upstreamIndex) : undefined\n // Re-index for the outgoing event\n const reindexed = {\n ...payload,\n index: captured\n ? capturedBlocks.indexOf(captured) >= 0\n ? // Find the synthetic index by matching back.\n nextSyntheticIndex - capturedBlocks.length + capturedBlocks.indexOf(captured)\n : upstreamIndex\n : upstreamIndex,\n }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n // Accumulate every delta type into the right field on\n // captured.block. The block is mutated in place; on\n // replay it's emitted verbatim, so every field upstream\n // sent (text, thinking, signature, citations, image\n // src, document data, etc.) flows back correctly.\n if (captured && delta) {\n if (delta.type === \"text_delta\" && typeof delta.text === \"string\") {\n captured.block.text =\n ((captured.block.text as string | undefined) ?? \"\") + delta.text\n } else if (\n delta.type === \"thinking_delta\"\n && typeof delta.thinking === \"string\"\n ) {\n // Anthropic spec: thinking blocks must carry their\n // text on replay. signature_delta carries the\n // cryptographic signature separately.\n captured.block.thinking =\n ((captured.block.thinking as string | undefined) ?? \"\") + delta.thinking\n } else if (\n delta.type === \"signature_delta\"\n && typeof delta.signature === \"string\"\n ) {\n // Concatenate verbatim — Anthropic verifies\n // signatures cryptographically; mutating bytes\n // (e.g., normalization, base64 decode/re-encode)\n // would break verification. Pure string append.\n captured.block.signature =\n ((captured.block.signature as string | undefined) ?? \"\") + delta.signature\n } else if (\n delta.type === \"input_json_delta\"\n && typeof delta.partial_json === \"string\"\n ) {\n captured.partialJson += delta.partial_json\n } else if (\n delta.type === \"citations_delta\"\n && delta.citation\n ) {\n // Append citations array. Future-proof for the\n // citations Anthropic feature without us needing\n // to know its shape.\n if (!Array.isArray(captured.block.citations)) {\n captured.block.citations = [] as Array<unknown>\n }\n ;(captured.block.citations as Array<unknown>).push(delta.citation)\n }\n // Other delta types: leave block as-is. The\n // content_block_start payload is preserved verbatim,\n // so any future delta type that the proxy hasn't\n // explicitly accumulated still has the original\n // start-state to fall back to.\n }\n } else {\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n }\n continue\n }\n\n case \"content_block_stop\": {\n const upstreamIndex = (payload as AnyRecord).index as number | undefined\n const captured = upstreamIndex !== undefined ? indexToBlock.get(upstreamIndex) : undefined\n const reindexed = {\n ...payload,\n index: captured\n ? nextSyntheticIndex - capturedBlocks.length + capturedBlocks.indexOf(captured)\n : (upstreamIndex ?? 0),\n }\n if (!safeEnqueueEvent(ev.event, reindexed)) return { capturedBlocks, advisorToolUse }\n\n // Finalize block state for replay:\n if (captured) {\n // (a) For tool_use blocks, parse the accumulated raw\n // partial_json into the block's `input` field.\n // Anthropic spec requires `tool_use.input` to be a\n // parsed JSON object on replay, not a string.\n // Warn-log on parse failure rather than silent\n // fallback so corruption surfaces in production\n // stderr (codex round-7).\n if (\n captured.block.type === \"tool_use\"\n && captured.partialJson.length > 0\n ) {\n try {\n captured.block.input = JSON.parse(captured.partialJson)\n } catch (err) {\n consola.warn(\n `advisor: malformed input_json_delta for tool_use `\n + `id=${(captured.block.id as string | undefined) ?? \"?\"} `\n + `name=${(captured.block.name as string | undefined) ?? \"?\"} `\n + `partialJson.length=${captured.partialJson.length} `\n + `parseError=${err instanceof Error ? err.message : String(err)}`,\n )\n captured.block.input = {}\n }\n }\n // (b) Drop empty text blocks from replay — empty\n // {type:\"text\", text:\"\"} is at best meaningless and\n // at worst spec-invalid (codex round-7).\n if (\n captured.block.type === \"text\"\n && (typeof captured.block.text !== \"string\"\n || (captured.block.text as string).length === 0)\n ) {\n captured.dropFromReplay = true\n }\n }\n continue\n }\n\n case \"message_delta\": {\n // Forward as-is (usage updates etc.)\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n continue\n }\n\n case \"message_stop\": {\n // CRITICAL: do NOT forward yet if advisor was called —\n // we need to run advisor + continue the loop. message_stop\n // ends the entire outgoing assistant turn. Only emit it\n // when the advisor loop is fully done.\n if (advisorToolUse) {\n return { capturedBlocks, advisorToolUse }\n }\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n return { capturedBlocks, advisorToolUse }\n }\n\n default: {\n // Unknown event — forward as-is.\n if (!safeEnqueueEvent(ev.event, payload)) return { capturedBlocks, advisorToolUse }\n }\n }\n }\n return { capturedBlocks, advisorToolUse }\n }\n\n try {\n let response: Response = opts.firstResponse\n\n for (turnsRun = 0; turnsRun < ADVISOR_MAX_TURNS; turnsRun++) {\n const { capturedBlocks, advisorToolUse } = await processOneTurn(response)\n\n if (!advisorToolUse) {\n // No advisor call this turn — message_stop was already\n // forwarded. We're done.\n return\n }\n\n // Advisor was called this turn. Run advisor model with the\n // full conversation extended by the assistant turn.\n //\n // Replay strategy: emit captured.block VERBATIM for every\n // captured block (preserves thinking, signature, redacted_\n // thinking, image, document, citations, anything Anthropic\n // adds tomorrow). Special-case ONLY the advisor block, which\n // needs the INTERNAL `__anthropic_advisor` name + ORIGINAL\n // `toolu_*` id (Copilot doesn't know server_tool_use).\n const assistantTurn = {\n role: \"assistant\",\n content: capturedBlocks\n .filter((c) => !c.dropFromReplay)\n .map((c) => {\n if (c.advisorReplay) {\n // Use the parsed input if any input_json_delta\n // arrived; otherwise default to {}. Don't bake\n // \"advisor takes no input\" as a load-bearing\n // invariant (codex round-7).\n const input =\n typeof c.block.input === \"object\" && c.block.input !== null\n ? (c.block.input as AnyRecord)\n : {}\n return {\n type: \"tool_use\",\n id: c.advisorReplay.id, // toolu_*, NOT srvtoolu_*\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input,\n }\n }\n return c.block // verbatim — the bug fix\n }),\n }\n conversation.push(assistantTurn)\n\n let advisorText: string\n try {\n advisorText = await runAdvisor(conversation, advisorModel, advisorEffort)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.warn(`Advisor model call failed: ${msg}`)\n advisorText =\n `[Advisor unavailable: ${msg}. Continuing without external review — `\n + `proceed with caution and consider self-checking against your `\n + `primary-source evidence.]`\n }\n\n // Synthesize advisor_tool_result block to client.\n // tool_use_id MUST be the client-facing srvtoolu_* id so it\n // pairs with the server_tool_use block emitted earlier; the\n // internal toolu_* id is only used in the Copilot-replay\n // path below.\n const resultIndex = nextSyntheticIndex++\n const startOk = safeEnqueueEvent(\"content_block_start\", {\n type: \"content_block_start\",\n index: resultIndex,\n content_block: {\n type: \"advisor_tool_result\",\n tool_use_id: advisorToolUse.clientId,\n content: { type: \"advisor_result\", text: advisorText },\n },\n })\n if (!startOk) return\n const stopOk = safeEnqueueEvent(\"content_block_stop\", {\n type: \"content_block_stop\",\n index: resultIndex,\n })\n if (!stopOk) return\n\n // Append the tool_result to conversation as a USER turn for\n // the next Copilot call. NOTE we use the standard tool_result\n // shape (Copilot doesn't know advisor_tool_result).\n conversation.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: advisorToolUse.id,\n content: advisorText,\n },\n ],\n })\n\n // Make the next Copilot call to continue the model's response\n // post-advisor. Reuse baseBody fields (max_tokens, system,\n // tools, etc.) but with the extended conversation and\n // stream:true.\n const continuationBody = JSON.stringify({\n ...opts.baseBody,\n messages: conversation,\n stream: true,\n })\n response = await createMessages(continuationBody, opts.requestHeaders)\n }\n\n // Loop exhausted. Synthesize final message_stop + an error text\n // block so the client doesn't hang.\n const finalIndex = nextSyntheticIndex++\n safeEnqueueEvent(\"content_block_start\", {\n type: \"content_block_start\",\n index: finalIndex,\n content_block: { type: \"text\", text: \"\" },\n })\n safeEnqueueEvent(\"content_block_delta\", {\n type: \"content_block_delta\",\n index: finalIndex,\n delta: {\n type: \"text_delta\",\n text: `\\n\\n[Advisor loop exceeded ${ADVISOR_MAX_TURNS} turns; halting]`,\n },\n })\n safeEnqueueEvent(\"content_block_stop\", {\n type: \"content_block_stop\",\n index: finalIndex,\n })\n safeEnqueueEvent(\"message_stop\", { type: \"message_stop\" })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.error(`Advisor stream error: ${msg}`)\n safeEnqueueEvent(\"error\", {\n type: \"error\",\n error: { type: \"api_error\", message: `advisor loop failed: ${msg}` },\n })\n } finally {\n try {\n controller.close()\n } catch {\n // already closed\n }\n }\n },\n })\n}\n","/**\n * Inbound /v1/messages body sanitizer.\n *\n * Today this only handles ADVISOR-related corruption — the\n * proxy-generated `server_tool_use{name:\"advisor\"}` and paired\n * `advisor_tool_result` blocks may travel through Claude Code's\n * persisted conversation state with a malformed `id`/`tool_use_id`\n * (e.g., a leftover `toolu_*` value from before the round-5 fix).\n * Every replay of that history through `/v1/messages` would 400 at\n * Copilot's spec validator without rewriting them on inbound. This\n * module performs the rewrite.\n *\n * **Out of scope** (deliberate, per gemini-critic round 6 — the \"ID\n * round-trip trap\"): generic `tool_use.id` and `tool_result.tool_use_id`\n * are stateful references between requests; rewriting them statelessly\n * would break Claude Code's client-side tool tracker. Only advisor\n * blocks are touched here, because both sides of an advisor pair\n * (`server_tool_use` + `advisor_tool_result`) are proxy-generated and\n * round-trip together in the same request body — so a per-request\n * deterministic transformation preserves pairing without cross-request\n * state.\n *\n * **Round-7 holistic fix**: Copilot rejects `server_tool_use{name:\n * \"advisor\"}` outright (spec validator says only `web_search`,\n * `tool_search_tool_regex`, `tool_search_tool_bm25` are allowed), so\n * fixing the id format alone isn't enough. The sanitizer also\n * **translates** historical advisor pairs to the\n * `tool_use{__anthropic_advisor}` + `tool_result` shape Copilot\n * accepts (per user direction \"Option C\"). Multi-turn split is\n * required because `tool_result` must appear in a `user` role per\n * Anthropic spec. The `__anthropic_advisor` tool definition is\n * re-injected into `tools[]` so the `tool_use.name` reference\n * resolves.\n */\nimport {\n ADVISOR_INTERNAL_TOOL_NAME,\n ADVISOR_TOOL_INSTRUCTIONS,\n} from \"~/services/advisor/advisor\"\n\ntype AnyRecord = Record<string, unknown>\n\n/**\n * Convert a `srvtoolu_*` id to the matching `toolu_*` id used in the\n * Copilot-replay shape (`tool_use.id` must match `^toolu_*$`). For\n * any other input shape, fall back to a synthesized `toolu_advisor_N`\n * id.\n */\nfunction toCopilotToolUseId(srvId: string, fallbackIndex: number): string {\n if (srvId.startsWith(\"srvtoolu_\")) {\n const suffix = srvId.slice(\"srvtoolu_\".length)\n if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `toolu_${suffix}`\n }\n return `toolu_advisor_${fallbackIndex}`\n}\n\n/**\n * Fast-path detector: returns true if the raw body has any chance of\n * needing sanitization. Avoids a full JSON parse for the common case\n * where the body is already spec-compliant.\n *\n * Looks for either an Anthropic-native advisor typed tool entry, or\n * any advisor-related block type that would need rewriting/\n * translating.\n */\nfunction bodyMightNeedSanitize(rawBody: string): boolean {\n return (\n rawBody.includes('\"server_tool_use\"')\n || rawBody.includes('\"advisor_tool_result\"')\n || /\"type\":\"advisor_\\d+\"/.test(rawBody)\n )\n}\n\n/**\n * Translate one assistant turn's content array, splitting at advisor\n * pairs into the multi-message structure Copilot accepts.\n *\n * Input shape (Claude Code stores everything in one assistant turn):\n * [text*, server_tool_use{advisor}, advisor_tool_result, text*, ...]\n *\n * Output: array of {role, content[]} message objects, alternating\n * assistant→user→assistant for each advisor pair encountered.\n */\nfunction splitAssistantTurnAtAdvisorPairs(\n originalContent: Array<unknown>,\n syntheticIndexRef: { value: number },\n): { messages: Array<AnyRecord>; translated: boolean } {\n const messages: Array<AnyRecord> = []\n let currentAssistantContent: Array<unknown> = []\n let translated = false\n // Walk linearly. When we see `server_tool_use{name:\"advisor\"}`,\n // expect the very next block to be `advisor_tool_result`. Translate\n // both, split into assistant→user→[continued assistant].\n let i = 0\n while (i < originalContent.length) {\n const block = originalContent[i]\n const b = (typeof block === \"object\" && block !== null) ? (block as AnyRecord) : null\n\n if (\n b\n && b.type === \"server_tool_use\"\n && b.name === ADVISOR_INTERNAL_TOOL_NAME.replace(/^__anthropic_/, \"\") // \"advisor\"\n ) {\n const stuId = typeof b.id === \"string\" ? b.id : \"\"\n // The next block should be the paired advisor_tool_result.\n const nextBlock = originalContent[i + 1]\n const next =\n typeof nextBlock === \"object\" && nextBlock !== null\n ? (nextBlock as AnyRecord)\n : null\n\n // Synthesize a Copilot-shape toolu_* id for the translated pair.\n // Prefer to derive from the existing id (preserves any\n // identifying suffix); if malformed, fall back to a synthesized\n // advisor_N id. Then both blocks of the pair get the SAME id.\n const copilotId = stuId.startsWith(\"srvtoolu_\")\n ? toCopilotToolUseId(stuId, syntheticIndexRef.value++)\n : stuId.startsWith(\"toolu_\") && /^toolu_[a-zA-Z0-9_]+$/.test(stuId)\n ? stuId\n : `toolu_advisor_${syntheticIndexRef.value++}`\n\n // Emit the assistant turn so far + the translated tool_use.\n currentAssistantContent.push({\n type: \"tool_use\",\n id: copilotId,\n name: ADVISOR_INTERNAL_TOOL_NAME,\n input: {},\n })\n messages.push({ role: \"assistant\", content: currentAssistantContent })\n translated = true\n\n // Translate the paired advisor_tool_result → tool_result in a\n // new user turn.\n let resultText = \"\"\n if (next && next.type === \"advisor_tool_result\") {\n const c = next.content\n if (typeof c === \"string\") {\n resultText = c\n } else if (typeof c === \"object\" && c !== null) {\n const txt = (c as AnyRecord).text\n if (typeof txt === \"string\") resultText = txt\n }\n i += 2 // consume both blocks\n } else {\n // No paired result block — synthesize an empty result so\n // Copilot's tool-use/tool-result pairing stays consistent.\n resultText = \"[Advisor result missing in conversation history.]\"\n i += 1\n }\n messages.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: copilotId,\n content: resultText,\n },\n ],\n })\n\n // Start a fresh assistant content array for any blocks after\n // the advisor pair.\n currentAssistantContent = []\n continue\n }\n\n if (b && b.type === \"advisor_tool_result\") {\n // Stray advisor_tool_result without a preceding server_tool_use.\n // Drop it — sending it to Copilot would 400. Loss is minor:\n // this only happens if Claude Code's history is corrupt.\n translated = true\n i += 1\n continue\n }\n\n // Pass-through any other block.\n currentAssistantContent.push(block)\n i += 1\n }\n\n // Flush any trailing assistant content as a final message.\n if (currentAssistantContent.length > 0) {\n messages.push({ role: \"assistant\", content: currentAssistantContent })\n }\n // If we never split (no advisor blocks), return the original as one\n // message so the caller can detect \"no change\" and short-circuit.\n if (!translated) {\n return {\n messages: [{ role: \"assistant\", content: originalContent }],\n translated: false,\n }\n }\n return { messages, translated: true }\n}\n\nexport function sanitizeAnthropicBody(rawBody: string): string {\n if (!bodyMightNeedSanitize(rawBody)) return rawBody\n\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody) as AnyRecord\n } catch {\n return rawBody\n }\n\n let mutated = false\n\n // 1. Strip Anthropic-native `advisor_*` typed tools from `tools[]`.\n // Copilot 400s on the unknown tool type. Always-strip (vs only\n // when ADVISOR is enabled per `injectAdvisorTool`) covers the case\n // where Claude Code injects the typed tool independently of the\n // beta header.\n if (Array.isArray(parsed.tools)) {\n const tools = parsed.tools as Array<unknown>\n const before = tools.length\n const filtered = tools.filter((t) => {\n if (typeof t !== \"object\" || t === null) return true\n const type = (t as AnyRecord).type\n return typeof type !== \"string\" || !type.startsWith(\"advisor_\")\n })\n if (filtered.length !== before) {\n parsed.tools = filtered\n mutated = true\n }\n }\n\n // 2. Walk messages[] and translate any assistant turns that contain\n // advisor blocks (server_tool_use{name:\"advisor\"} +\n // advisor_tool_result). Per user direction round-7 \"Option C\":\n // rewrite to the tool_use{__anthropic_advisor} + tool_result\n // shape Copilot accepts, splitting into multi-turn assistant→\n // user→assistant as needed (Anthropic spec requires tool_result\n // in user role).\n if (Array.isArray(parsed.messages)) {\n const original = parsed.messages as Array<unknown>\n const rebuilt: Array<unknown> = []\n let anyTranslated = false\n const syntheticIndexRef = { value: 0 }\n for (const msg of original) {\n if (\n typeof msg !== \"object\"\n || msg === null\n || (msg as AnyRecord).role !== \"assistant\"\n ) {\n rebuilt.push(msg)\n continue\n }\n const content = (msg as AnyRecord).content\n if (!Array.isArray(content)) {\n rebuilt.push(msg)\n continue\n }\n // Quick check: does this assistant turn contain any advisor\n // blocks? If not, pass through unchanged.\n const hasAdvisorBlocks = content.some((b) => {\n if (typeof b !== \"object\" || b === null) return false\n const type = (b as AnyRecord).type\n const name = (b as AnyRecord).name\n return (\n (type === \"server_tool_use\" && name === \"advisor\")\n || type === \"advisor_tool_result\"\n )\n })\n if (!hasAdvisorBlocks) {\n rebuilt.push(msg)\n continue\n }\n const { messages: split, translated } = splitAssistantTurnAtAdvisorPairs(\n content as Array<unknown>,\n syntheticIndexRef,\n )\n if (translated) {\n anyTranslated = true\n for (const m of split) rebuilt.push(m)\n } else {\n rebuilt.push(msg)\n }\n }\n if (anyTranslated) {\n parsed.messages = rebuilt\n mutated = true\n // Re-inject __anthropic_advisor tool definition into tools[]\n // so the translated tool_use.name resolves at Copilot's\n // validator. Idempotent: skip if already present.\n const existingTools = Array.isArray(parsed.tools)\n ? (parsed.tools as Array<unknown>)\n : []\n const alreadyInjected = existingTools.some((t) => {\n if (typeof t !== \"object\" || t === null) return false\n return (t as AnyRecord).name === ADVISOR_INTERNAL_TOOL_NAME\n })\n if (!alreadyInjected) {\n parsed.tools = [\n ...existingTools,\n {\n name: ADVISOR_INTERNAL_TOOL_NAME,\n description: ADVISOR_TOOL_INSTRUCTIONS,\n input_schema: {\n type: \"object\",\n properties: {},\n required: [],\n },\n },\n ]\n }\n }\n }\n\n if (!mutated) return rawBody\n return JSON.stringify(parsed)\n}\n\n","import consola from \"consola\"\n\nconst PREVIEW_LIMIT = 200\n\nexport async function parseJsonOrDiagnose<T = unknown>(\n response: Response,\n routePath: string,\n): Promise<T> {\n const cloned = response.clone()\n try {\n return (await response.json()) as T\n } catch (error) {\n const contentType = response.headers.get(\"content-type\") ?? \"(none)\"\n const bodyText = await cloned.text().catch(() => \"(unreadable)\")\n const preview =\n bodyText.length > PREVIEW_LIMIT\n ? bodyText.slice(0, PREVIEW_LIMIT) + \"...(truncated)\"\n : bodyText\n consola.error(\n `Upstream JSON parse failed at ${routePath}: status=${response.status} content-type=\"${contentType}\" body[0..${PREVIEW_LIMIT}]=${JSON.stringify(preview)}`,\n )\n throw error\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { logRequest } from \"~/lib/request-log\"\nimport { sanitizeAnthropicBody } from \"~/lib/sanitize-anthropic-body\"\nimport { filterBetaHeader, resolveModel } from \"~/lib/utils\"\nimport { state } from \"~/lib/state\"\nimport { countTokens } from \"~/services/copilot/create-messages\"\nimport { parseJsonOrDiagnose } from \"~/lib/diagnose-response\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst isWebSearchTool = (tool: AnyRecord): boolean =>\n (typeof tool.type === \"string\" && tool.type.startsWith(\"web_search\")) ||\n tool.name === \"web_search\"\n\n/**\n * Strip web_search tools from the request body before forwarding\n * to Copilot's count_tokens endpoint, which rejects unknown tool types.\n * Returns the original raw body if no web_search tools are present.\n */\nfunction stripWebSearchFromBody(rawBody: string): string {\n if (!rawBody.includes(\"web_search\")) return rawBody\n\n let body: AnyRecord\n try {\n body = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n\n const hasWebSearch = body.tools?.some(\n (tool: AnyRecord) => isWebSearchTool(tool),\n )\n if (!hasWebSearch) return rawBody\n\n body.tools = body.tools.filter(\n (tool: AnyRecord) => !isWebSearchTool(tool),\n )\n\n if (body.tools.length === 0) {\n body.tools = undefined\n body.tool_choice = undefined\n } else if (\n body.tool_choice &&\n typeof body.tool_choice === \"object\" &&\n body.tool_choice.type === \"tool\"\n ) {\n const choiceName = body.tool_choice.name\n if (\n choiceName &&\n !body.tools.some((tool: AnyRecord) => tool.name === choiceName)\n ) {\n body.tool_choice = { type: \"auto\" }\n }\n }\n\n return JSON.stringify(body)\n}\n\n/**\n * Passthrough handler for Anthropic token counting.\n * Strips web_search tools and forwards beta headers to Copilot's\n * native /v1/messages/count_tokens endpoint.\n */\nexport async function handleCountTokens(c: Context) {\n const startTime = Date.now()\n const rawBody = await c.req.text()\n // Inbound advisor-history sanitization (mirrors handler.ts) — count\n // tokens uses the same Copilot validator, so a malformed\n // server_tool_use block in the conversation history would 400 here\n // too. Scoped narrowly to advisor pairs.\n const sanitizedBody = sanitizeAnthropicBody(rawBody)\n const strippedBody = stripWebSearchFromBody(sanitizedBody)\n\n // Phase G fail-fast: same rationale as handler.ts. count_tokens uses\n // the same Copilot schema validator, so mcp_servers in the body\n // produces the same 400 — surface clearly here too.\n if (strippedBody.includes('\"mcp_servers\"')) {\n try {\n const probe = JSON.parse(strippedBody) as AnyRecord\n if (Array.isArray(probe.mcp_servers) && probe.mcp_servers.length > 0) {\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Inline `mcp_servers` body field is not supported by github-router. \"\n + \"Configure remote MCP servers as local stdio entries in `~/.claude/mcp.json` instead.\",\n },\n },\n 400,\n )\n }\n } catch {\n // Not valid JSON — fall through to downstream parse-error path.\n }\n }\n\n const { body: finalBody, originalModel, resolvedModel } = resolveModelInBody(strippedBody)\n\n const extraHeaders: Record<string, string> = {}\n const anthropicBeta = c.req.header(\"anthropic-beta\")\n if (anthropicBeta) {\n const filtered = filterBetaHeader(anthropicBeta)\n if (filtered) extraHeaders[\"anthropic-beta\"] = filtered\n }\n\n const modelId = resolvedModel ?? originalModel\n const selectedModel = state.models?.data.find((m) => m.id === modelId)\n\n const response = await countTokens(finalBody, {\n ...selectedModel?.requestHeaders,\n ...extraHeaders,\n })\n const responseBody = await parseJsonOrDiagnose<{ input_tokens?: number }>(\n response,\n c.req.path,\n )\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens: responseBody.input_tokens,\n status: response.status,\n },\n selectedModel,\n startTime,\n )\n\n return c.json(responseBody)\n}\n\n/**\n * Parse the JSON body, resolve the model name, sanitize cache_control, and re-serialize.\n */\nfunction resolveModelInBody(rawBody: string): {\n body: string\n originalModel?: string\n resolvedModel?: string\n} {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return { body: rawBody }\n }\n\n const originalModel =\n typeof parsed.model === \"string\" ? parsed.model : undefined\n\n let modified = false\n if (originalModel) {\n const resolved = resolveModel(originalModel)\n if (resolved !== originalModel) {\n parsed.model = resolved\n modified = true\n }\n }\n\n const needsSanitize = rawBody.includes('\"scope\"')\n if (needsSanitize && sanitizeCacheControl(parsed)) {\n modified = true\n }\n\n // Strip Anthropic-only body fields Copilot 400s on. See\n // `stripAnthropicOnlyFields` in handler.ts for full rationale + empirical\n // evidence (2026-05-11 / 2026-05-13 verification). count_tokens uses the same\n // Copilot schema validator as /v1/messages so the same fields are rejected.\n const needsAnthropicOnlyStrip =\n rawBody.includes('\"budget\"')\n || rawBody.includes('\"output_config\"')\n || rawBody.includes('\"betas\"')\n || rawBody.includes('\"eager_input_streaming\"')\n if (needsAnthropicOnlyStrip && stripAnthropicOnlyFields(parsed)) {\n modified = true\n }\n\n const resolvedModel =\n typeof parsed.model === \"string\" ? parsed.model : originalModel\n\n return {\n body: modified ? JSON.stringify(parsed) : rawBody,\n originalModel,\n resolvedModel,\n }\n}\n\nfunction sanitizeCacheControl(body: AnyRecord): boolean {\n let stripped = false\n function stripScope(block: AnyRecord): void {\n if (block.cache_control?.scope !== undefined) {\n delete block.cache_control.scope\n if (Object.keys(block.cache_control).length === 0) {\n delete block.cache_control\n }\n stripped = true\n }\n }\n\n if (Array.isArray(body.system)) {\n for (const block of body.system) stripScope(block)\n }\n\n if (Array.isArray(body.messages)) {\n for (const msg of body.messages) {\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n stripScope(block)\n if (Array.isArray(block.content)) {\n for (const nested of block.content) stripScope(nested)\n }\n }\n }\n }\n }\n\n if (Array.isArray(body.tools)) {\n for (const tool of body.tools) stripScope(tool)\n }\n\n return stripped\n}\n\n/**\n * Strip top-level body fields Copilot 400s on (budget, output_config.schema,\n * betas). Duplicated structurally from handler.ts because count_tokens uses\n * its own JSON-pass; the bodies are independent. Behavior must stay in lock-\n * step with handler.ts's stripAnthropicOnlyFields — covered by integration\n * tests (Phase F P2.4).\n */\nfunction stripAnthropicOnlyFields(body: AnyRecord): boolean {\n let stripped = false\n if (body.budget !== undefined) {\n consola.warn(\n \"[count_tokens] Stripping body-level `budget` field (Copilot 400s)\",\n )\n delete body.budget\n stripped = true\n }\n if (body.output_config !== undefined) {\n if (body.output_config && typeof body.output_config === \"object\") {\n const oc = body.output_config as AnyRecord\n const PROXY_OWNED_FIELDS = new Set([\"effort\"])\n let strippedAny = false\n for (const key of Object.keys(oc)) {\n if (!PROXY_OWNED_FIELDS.has(key)) {\n delete oc[key]\n strippedAny = true\n }\n }\n if (strippedAny) {\n consola.warn(\n \"[count_tokens] Stripping client-set `output_config` Structured-Outputs fields (Copilot 400s on `output_config.*` other than `effort`)\",\n )\n if (Object.keys(oc).length === 0) {\n delete body.output_config\n }\n stripped = true\n }\n }\n }\n if (Array.isArray(body.betas)) {\n consola.warn(\n \"[count_tokens] Stripping body-level `betas` array (Copilot 400s; conveyed via header)\",\n )\n delete body.betas\n stripped = true\n }\n // Per-tool field strip: `eager_input_streaming` (FGTS). Mirrors handler.ts.\n if (Array.isArray(body.tools)) {\n let warnedFGTS = false\n for (const tool of body.tools) {\n if (typeof tool === \"object\" && tool !== null) {\n const t = tool as AnyRecord\n if (t.eager_input_streaming !== undefined) {\n delete t.eager_input_streaming\n stripped = true\n if (!warnedFGTS) {\n consola.warn(\n \"[count_tokens] Stripping per-tool `eager_input_streaming` (Copilot 400s on `tools.*.custom.eager_input_streaming`)\",\n )\n warnedFGTS = true\n }\n }\n }\n }\n }\n return stripped\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { parseJsonOrDiagnose } from \"~/lib/diagnose-response\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest, logRequestFields } from \"~/lib/request-log\"\nimport { sanitizeAnthropicBody } from \"~/lib/sanitize-anthropic-body\"\nimport { state } from \"~/lib/state\"\nimport { relayAnthropicStream } from \"~/lib/stream-relay\"\nimport { filterBetaHeader, resolveModel } from \"~/lib/utils\"\nimport {\n buildAdvisorStream,\n injectAdvisorTool,\n isAdvisorRequested,\n} from \"~/services/advisor/advisor\"\nimport { createMessages } from \"~/services/copilot/create-messages\"\nimport type { Model } from \"~/services/copilot/get-models\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyRecord = Record<string, any>\n\nconst isWebSearchTool = (tool: AnyRecord): boolean =>\n (typeof tool.type === \"string\" && tool.type.startsWith(\"web_search\")) ||\n tool.name === \"web_search\"\n\n/**\n * Extract whitelisted beta headers from the incoming request to forward\n * to the Copilot API. VS Code sends these to enable extended features\n * like thinking, context management, and advanced tool use.\n */\nfunction extractBetaHeaders(c: Context): Record<string, string> {\n const headers: Record<string, string> = {}\n const anthropicBeta = c.req.header(\"anthropic-beta\")\n if (anthropicBeta) {\n const filtered = filterBetaHeader(anthropicBeta)\n if (filtered) headers[\"anthropic-beta\"] = filtered\n }\n return headers\n}\n\n/**\n * Extract the text content from the last user message for web search.\n * Handles both string content and content block arrays (multimodal).\n */\nfunction extractUserQuery(\n messages: Array<AnyRecord>,\n): string | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") return msg.content\n if (Array.isArray(msg.content)) {\n const textBlock = msg.content.find(\n (block: AnyRecord) => block.type === \"text\",\n )\n if (textBlock?.text) return textBlock.text as string\n }\n }\n }\n return undefined\n}\n\n/**\n * Check if any user message contains tool_result content blocks,\n * indicating a follow-up turn where we should skip web search.\n * In Anthropic format, tool results are content blocks inside user messages,\n * NOT separate role: \"tool\" messages like in OpenAI format.\n */\nfunction hasToolResultContent(messages: Array<AnyRecord>): boolean {\n return messages.some(\n (msg) =>\n Array.isArray(msg.content) &&\n msg.content.some(\n (block: AnyRecord) => block.type === \"tool_result\",\n ),\n )\n}\n\n/**\n * Inject web search results into the Anthropic system field.\n * Handles three cases: absent, string, or array of content blocks.\n * When array, prepends without cache_control to preserve existing directives.\n */\nfunction injectSearchResults(\n body: AnyRecord,\n searchContext: string,\n): void {\n if (body.system === undefined || body.system === null) {\n body.system = searchContext\n } else if (typeof body.system === \"string\") {\n body.system = `${searchContext}\\n\\n${body.system}`\n } else if (Array.isArray(body.system)) {\n body.system = [\n { type: \"text\", text: searchContext },\n ...body.system,\n ]\n }\n}\n\n/**\n * Strip web_search tools from the request and clean up tool_choice.\n * Returns the modified body object.\n */\nfunction stripWebSearchTool(body: AnyRecord): void {\n if (!body.tools) return\n\n body.tools = body.tools.filter(\n (tool: AnyRecord) => !isWebSearchTool(tool),\n )\n\n if (body.tools.length === 0) {\n body.tools = undefined\n body.tool_choice = undefined\n } else if (\n body.tool_choice &&\n typeof body.tool_choice === \"object\" &&\n body.tool_choice.type === \"tool\"\n ) {\n // If tool_choice forced the removed web_search tool, fall back to auto\n const choiceName = body.tool_choice.name\n if (\n choiceName &&\n !body.tools.some((tool: AnyRecord) => tool.name === choiceName)\n ) {\n body.tool_choice = { type: \"auto\" }\n }\n }\n}\n\n/**\n * Process web search if the request contains a web_search tool.\n * Performs the search, injects results into system, and strips the tool.\n * Returns the (possibly modified) body string to forward.\n */\nasync function processWebSearch(rawBody: string): Promise<string> {\n // Fast path: skip parsing if no web_search tool present\n if (!rawBody.includes(\"web_search\")) return rawBody\n\n let body: AnyRecord\n try {\n body = JSON.parse(rawBody)\n } catch {\n return rawBody\n }\n\n const hasWebSearch = body.tools?.some(\n (tool: AnyRecord) => isWebSearchTool(tool),\n )\n if (!hasWebSearch) return rawBody\n\n // Skip search on follow-up messages (tool call results)\n const hasToolResult = hasToolResultContent(body.messages ?? [])\n const query = hasToolResult ? undefined : extractUserQuery(body.messages ?? [])\n\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n injectSearchResults(body, searchContext)\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Always strip web_search tool regardless of whether search succeeded\n stripWebSearchTool(body)\n\n return JSON.stringify(body)\n}\n\nexport async function handleCompletion(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n const rawBody = await c.req.text()\n\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\"Anthropic request body:\", rawBody.slice(0, 2000))\n }\n\n // Opt-in field-key discovery (Phase 0.5 of the long-horizon plan).\n // No-op unless GH_ROUTER_LOG_FIELDS=1 is set. Feeds\n // scripts/discover-new-fields.sh.\n if (process.env.GH_ROUTER_LOG_FIELDS === \"1\") {\n let parsedForLog: unknown = undefined\n try {\n parsedForLog = JSON.parse(rawBody) as unknown\n } catch {\n // Body parse failures are surfaced downstream; don't double-warn here.\n }\n logRequestFields({\n path: c.req.path,\n body: parsedForLog,\n betaHeader: c.req.header(\"anthropic-beta\"),\n })\n }\n\n if (state.manualApprove) {\n await awaitApproval()\n }\n\n const betaHeaders = extractBetaHeaders(c)\n\n // Phase I: detect ADVISOR request BEFORE filterBetaHeader strips\n // the advisor-tool- prefix from the outgoing header. We need the raw\n // incoming header to know whether the user asked for ADVISOR.\n const incomingBeta = c.req.header(\"anthropic-beta\")\n const advisorEnabled = isAdvisorRequested(incomingBeta)\n\n let finalBody = await processWebSearch(rawBody)\n // Inbound advisor-history sanitization: rewrite malformed\n // server_tool_use ids in Claude Code's replayed conversation history\n // (left over from before the round-5 fix or any non-spec-compliant\n // source). Without this, Copilot 400s on\n // `messages.N.content.M.server_tool_use.id: String should match\n // pattern '^srvtoolu_[a-zA-Z0-9_]+$'`\n // when the conversation grows long enough to echo a malformed block.\n // Scoped narrowly to advisor pairs to avoid the ID round-trip trap\n // (see src/lib/sanitize-anthropic-body.ts header comment).\n finalBody = sanitizeAnthropicBody(finalBody)\n if (advisorEnabled) {\n // Inject __anthropic_advisor tool definition (with cc-backup's\n // ADVISOR_TOOL_INSTRUCTIONS as description) so the model knows\n // when to call it. Tool name uses double-underscore prefix to\n // avoid collision with any user MCP server's `advisor`.\n finalBody = injectAdvisorTool(finalBody)\n consola.info(\n \"ADVISOR enabled for this request — injecting __anthropic_advisor tool; will translate tool_use → server_tool_use{advisor} on the SSE stream\",\n )\n }\n\n // Phase G fail-fast (deferred translate path per codex-critic): if the\n // request includes inline `mcp_servers`, refuse with a clear Anthropic-\n // format error before forwarding. The original plan was to translate\n // (instantiate MCP clients server-side and inline tools) but the design\n // has structural holes — continuation after pool TTL isn't implementable\n // from the request alone, and streaming correctness during the multi-turn\n // tool loop is fragile. Local stdio MCP (~/.claude/mcp.json) covers the\n // common Claude usage; remote-managed MCP is the rare path. Fail-fast\n // with a clear pointer is the better Pareto choice (codex-critic 2/2/3\n // verdict on the translate-path design).\n if (finalBody.includes('\"mcp_servers\"')) {\n try {\n const probe = JSON.parse(finalBody) as AnyRecord\n if (Array.isArray(probe.mcp_servers) && probe.mcp_servers.length > 0) {\n return c.json(\n {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Inline `mcp_servers` body field is not supported by github-router. \"\n + \"Configure remote MCP servers as local stdio entries in `~/.claude/mcp.json` instead — \"\n + \"Claude Code will spawn them locally and the proxy passes their tool calls through transparently. \"\n + \"(https://docs.claude.com/en/docs/claude-code/mcp)\",\n },\n },\n 400,\n )\n }\n } catch {\n // Body wasn't valid JSON — fall through, downstream handlers will\n // surface the parse error in their own way.\n }\n }\n\n // Resolve model name (e.g. opus → opus-1m variant) and translate\n // thinking-mode shape for adaptive-thinking models.\n const {\n body: resolvedBody,\n originalModel,\n resolvedModel,\n selectedModel,\n } = resolveModelInBody(finalBody)\n\n const modelId = resolvedModel ?? originalModel\n if (modelId) logEndpointMismatch(modelId, \"/v1/messages\")\n\n // Apply default anthropic-beta for Claude models when client sends none\n const effectiveBetas = applyDefaultBetas(betaHeaders, resolvedModel ?? originalModel)\n\n let response: Response\n try {\n response = await createMessages(resolvedBody, {\n ...selectedModel?.requestHeaders,\n ...effectiveBetas,\n })\n } catch (error) {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\"\n // Trust the upstream content-type when it's explicit. Two anomalies need\n // a fallback: (a) header missing entirely, (b) header is\n // `application/octet-stream` (some proxies normalize SSE this way). In\n // those cases, treat as streaming if the client asked for it via the\n // Accept header — Anthropic SDKs send `Accept: text/event-stream` for\n // streaming requests. We do NOT fall back when content-type is\n // explicitly `application/json` — that's almost always an upstream\n // error response that should be parsed via parseJsonOrDiagnose.\n const clientAcceptsSSE = (c.req.header(\"accept\") ?? \"\").includes(\n \"text/event-stream\",\n )\n let isStreaming = contentType.includes(\"text/event-stream\")\n if (!isStreaming && clientAcceptsSSE) {\n if (contentType === \"\" || contentType === \"application/octet-stream\") {\n consola.warn(\n `Upstream /v1/messages returned status=${response.status} content-type=${JSON.stringify(contentType)} but client requested streaming; treating response body as SSE`,\n )\n isStreaming = true\n }\n }\n\n if (debugEnabled) {\n consola.debug(\n `Upstream /v1/messages: status=${response.status} content-type=\"${contentType}\" isStreaming=${isStreaming}`,\n )\n }\n\n // Streaming: pipe the upstream SSE response body directly (or wrap\n // with the ADVISOR translate-loop if advisor was requested).\n if (isStreaming) {\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: response.status,\n streaming: true,\n },\n selectedModel,\n startTime,\n )\n\n if (debugEnabled) {\n consola.debug(\"Streaming response from Copilot /v1/messages\")\n }\n const streamHeaders: Record<string, string> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n }\n const requestId = response.headers.get(\"x-request-id\")\n if (requestId) streamHeaders[\"x-request-id\"] = requestId\n const reqId = response.headers.get(\"request-id\")\n if (reqId) streamHeaders[\"request-id\"] = reqId\n\n // Phase I: branch into the advisor translate-loop if the user\n // requested ADVISOR. The loop intercepts tool_use{__anthropic_advisor}\n // blocks, translates to server_tool_use{advisor}, runs the advisor\n // model server-side, emits advisor_tool_result, and continues the\n // Copilot conversation on the SAME SSE connection (no intermediate\n // message_stop). See src/services/advisor/advisor.ts for the design\n // (gemini-critic streaming-during-loop pattern).\n if (advisorEnabled && response.body) {\n // Parse the resolved body once to extract the conversation +\n // base body for continuation calls. The translate-loop needs\n // these to extend the conversation across advisor turns.\n let parsedBase: AnyRecord = {}\n try {\n parsedBase = JSON.parse(resolvedBody) as AnyRecord\n } catch {\n // Should not happen since resolveModelInBody just re-serialized\n // it. Fallback: pass empty conversation; translate-loop will\n // skip advisor calls if it can't construct continuations.\n }\n const initialConversation = Array.isArray(parsedBase.messages)\n ? (parsedBase.messages as Array<AnyRecord>)\n : []\n return new Response(\n buildAdvisorStream({\n firstResponse: response,\n initialConversation,\n baseBody: parsedBase,\n requestHeaders: {\n ...selectedModel?.requestHeaders,\n ...effectiveBetas,\n },\n }),\n {\n status: response.status,\n headers: streamHeaders,\n },\n )\n }\n\n return new Response(\n response.body\n ? relayAnthropicStream(response.body, { routePath: c.req.path })\n : null,\n {\n status: response.status,\n headers: streamHeaders,\n },\n )\n }\n\n // Non-streaming: extract usage from response body\n const responseBody = await parseJsonOrDiagnose<AnyRecord>(\n response,\n c.req.path,\n )\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n inputTokens: responseBody.usage?.input_tokens,\n outputTokens: responseBody.usage?.output_tokens,\n status: response.status,\n },\n selectedModel,\n startTime,\n )\n\n if (debugEnabled) {\n consola.debug(\n \"Non-streaming response from Copilot /v1/messages:\",\n JSON.stringify(responseBody).slice(0, 2000),\n )\n }\n const xRequestId = response.headers.get(\"x-request-id\")\n if (xRequestId) c.header(\"x-request-id\", xRequestId)\n const requestIdHeader = response.headers.get(\"request-id\")\n if (requestIdHeader) c.header(\"request-id\", requestIdHeader)\n return c.json(responseBody, response.status as 200)\n}\n\n/**\n * Parse the JSON body, resolve the model name, sanitize cache_control\n * fields, translate thinking-mode shape for adaptive-thinking models,\n * and re-serialize. Returns the body string, original/resolved model\n * names, and the matching model metadata (if any).\n *\n * Re-serialization is skipped when no modifications are needed.\n */\nfunction resolveModelInBody(rawBody: string): {\n body: string\n originalModel?: string\n resolvedModel?: string\n selectedModel?: Model\n} {\n let parsed: AnyRecord\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return { body: rawBody }\n }\n\n const originalModel =\n typeof parsed.model === \"string\" ? parsed.model : undefined\n\n let modified = false\n if (originalModel) {\n const resolved = resolveModel(originalModel)\n if (resolved !== originalModel) {\n parsed.model = resolved\n modified = true\n }\n }\n\n const resolvedModel =\n typeof parsed.model === \"string\" ? parsed.model : originalModel\n\n const selectedModel = resolvedModel\n ? state.models?.data.find((m) => m.id === resolvedModel)\n : undefined\n\n // Translate thinking-mode shape for adaptive-thinking models — Copilot\n // wants {type:\"adaptive\"} + output_config.effort, not Anthropic's\n // {type:\"enabled\", budget_tokens}.\n if (translateThinking(parsed, selectedModel)) {\n modified = true\n }\n\n // Strip cache_control.scope — fast path skips when \"scope\" absent\n const needsSanitize = rawBody.includes('\"scope\"')\n if (needsSanitize && sanitizeCacheControl(parsed)) {\n modified = true\n }\n\n // Strip Anthropic-only top-level body fields Copilot 400s on. Empirical\n // verification (2026-05-11 / 2026-05-13 against api.enterprise.githubcopilot.com):\n // - `budget: {total_tokens}` (Task Budgets) → 400 \"budget: Extra inputs not permitted\"\n // - `output_config: {schema}` (Structured Outputs) → 400 \"output_config.schema: Extra...\"\n // - `betas: [...]` (top-level array, distinct from anthropic-beta header) → 400 \"betas: Extra...\"\n // - `tools[i].eager_input_streaming` (Fine-Grained Tool Streaming) → 400\n // \"tools.0.custom.eager_input_streaming: Extra inputs are not permitted\"\n // (the `.custom.` infix is Copilot's error-format; the actual emit\n // location from Claude Code is the top of each tool object per\n // https://platform.claude.com/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming).\n // Stripping disables only the streaming-chunk-size optimization;\n // correctness is unaffected — `input_json_delta` events still flow,\n // just with `partial_json:\"\"` instead of populated chunks.\n // Probes: `eager_input_streaming_strips` / `eager_input_streaming_passthrough`\n // in scripts/probe-copilot-compat.sh.\n // Fast-path skip when none of the field names appear in the raw body.\n // NOT stripped:\n // - `mcp_servers` — Phase G builds the translate path; silent strip\n // here would cause LLM to hallucinate tools (gemini-critic finding).\n // - `metadata: {user_id}` — Copilot 200s, ignores harmlessly. Strip\n // would be cosmetic (codex-critic: \"preserve unknown fields unless\n // documented reason\"); ~0.1ms re-serialize cost per request adds up.\n const needsAnthropicOnlyStrip =\n rawBody.includes('\"budget\"')\n || rawBody.includes('\"output_config\"')\n || rawBody.includes('\"betas\"')\n || rawBody.includes('\"eager_input_streaming\"')\n if (needsAnthropicOnlyStrip && stripAnthropicOnlyFields(parsed)) {\n modified = true\n }\n\n return {\n body: modified ? JSON.stringify(parsed) : rawBody,\n originalModel,\n resolvedModel,\n selectedModel,\n }\n}\n\nexport const EFFORT_ORDER = [\"low\", \"medium\", \"high\", \"xhigh\"] as const\n\n/**\n * Bucket a thinking budget into a Copilot reasoning-effort string.\n * `<2000`→low, `<8000`→medium, `<24000`→high, else→xhigh.\n * Defaults missing/non-numeric budgets to 8000 (\"high\").\n */\nexport function bucketEffort(budget: unknown): (typeof EFFORT_ORDER)[number] {\n const n =\n typeof budget === \"number\" && Number.isFinite(budget) ? budget : 8000\n if (n < 2000) return \"low\"\n if (n < 8000) return \"medium\"\n if (n < 24000) return \"high\"\n return \"xhigh\"\n}\n\n/**\n * Clamp a bucketed effort to the closest value in `supported`. Ties\n * resolve to the lower-tier option (per EFFORT_ORDER).\n *\n * Iterates EFFORT_ORDER (canonical low→xhigh) so the first match on a\n * given distance is always the lower-tier value, regardless of input\n * order in `supported`.\n */\nexport function clampEffort(\n bucketed: (typeof EFFORT_ORDER)[number],\n supported: Array<string>,\n): string {\n if (supported.includes(bucketed)) return bucketed\n const targetIdx = EFFORT_ORDER.indexOf(bucketed)\n let best: (typeof EFFORT_ORDER)[number] | undefined\n let bestDist = Infinity\n for (let i = 0; i < EFFORT_ORDER.length; i++) {\n const value = EFFORT_ORDER[i]\n if (!supported.includes(value)) continue\n const dist = Math.abs(i - targetIdx)\n // strict `<` keeps the first (lower-tier) on ties\n if (dist < bestDist) {\n bestDist = dist\n best = value\n }\n }\n return best ?? bucketed\n}\n\n/**\n * Translate Anthropic-shape `thinking:{type:\"enabled\", budget_tokens}` to\n * Copilot-shape `thinking:{type:\"adaptive\"}` + `output_config.effort`\n * when the resolved model declares `adaptive_thinking: true`.\n *\n * Returns true if the body was modified. No-op when the model doesn't\n * support adaptive thinking, when thinking is missing/disabled/already\n * adaptive, or when `body` isn't a plain object. Client-supplied\n * `output_config.effort` always wins over the bucketed value.\n */\nfunction translateThinking(body: AnyRecord, model?: Model): boolean {\n if (!model?.capabilities?.supports?.adaptive_thinking) return false\n const thinking = body.thinking\n if (!thinking || typeof thinking !== \"object\") return false\n if (thinking.type !== \"enabled\") return false\n\n const bucketed = bucketEffort(thinking.budget_tokens)\n const supported = model.capabilities.supports.reasoning_effort\n const effort =\n Array.isArray(supported) && supported.length > 0\n ? clampEffort(bucketed, supported)\n : bucketed\n\n body.thinking = { type: \"adaptive\" }\n\n const existing =\n body.output_config && typeof body.output_config === \"object\"\n ? (body.output_config as AnyRecord)\n : {}\n body.output_config = {\n ...existing,\n // client-supplied effort wins\n effort: existing.effort ?? effort,\n }\n\n return true\n}\n\n/**\n * Strip the `scope` field from all `cache_control` objects in the body.\n * Claude CLI 2.1.88+ sends {\"type\":\"ephemeral\",\"scope\":\"global\"} which\n * Copilot rejects. Mutates the parsed object in place.\n *\n * Covers: system blocks, message content blocks (including nested\n * tool_result content), and tool definitions.\n */\nfunction sanitizeCacheControl(body: AnyRecord): boolean {\n let stripped = false\n function stripScope(block: AnyRecord): void {\n if (block.cache_control?.scope !== undefined) {\n delete block.cache_control.scope\n if (Object.keys(block.cache_control).length === 0) {\n delete block.cache_control\n }\n stripped = true\n }\n }\n\n if (Array.isArray(body.system)) {\n for (const block of body.system) stripScope(block)\n }\n\n if (Array.isArray(body.messages)) {\n for (const msg of body.messages) {\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n stripScope(block)\n if (Array.isArray(block.content)) {\n for (const nested of block.content) stripScope(nested)\n }\n }\n }\n }\n }\n\n if (Array.isArray(body.tools)) {\n for (const tool of body.tools) stripScope(tool)\n }\n\n return stripped\n}\n\n/**\n * Apply default anthropic-beta values for Claude models when the client\n * (e.g. curl) sends no beta headers. Claude CLI sends its own betas,\n * so this only fires as a safety net for bare clients.\n */\nfunction applyDefaultBetas(\n betaHeaders: Record<string, string>,\n modelId?: string,\n): Record<string, string> {\n if (betaHeaders[\"anthropic-beta\"]) return betaHeaders\n if (!modelId || !modelId.startsWith(\"claude-\")) return betaHeaders\n\n return {\n ...betaHeaders,\n \"anthropic-beta\": [\n \"interleaved-thinking-2025-05-14\",\n \"context-management-2025-06-27\",\n ].join(\",\"),\n }\n}\n\n/**\n * Strip top-level body fields that Anthropic's Messages API accepts but\n * Copilot rejects with HTTP 400 \"Extra inputs are not permitted\". Mutates\n * `body` in place; returns true if anything was stripped.\n *\n * Empirical verification (2026-05-11):\n * POST /v1/messages?beta=true { ..., budget: {total_tokens: 10000} } → 400\n * POST /v1/messages?beta=true { ..., output_config: {schema: {...}} } → 400\n * POST /v1/messages?beta=true { ..., betas: [\"...\"] } → 400\n *\n * Each strip emits a one-line consola.warn so users running with these\n * features (e.g. `claude --max-budget-usd`, `--json-schema`) understand\n * the request succeeds with the *body field* dropped — semantics may\n * differ from upstream Anthropic. The corresponding `anthropic-beta`\n * header is preserved (Phase A allowlist) so the *intent* still flows\n * to Copilot, even if the per-request enforcement field is gone.\n *\n * NOT stripped here:\n * - `mcp_servers` (Phase G translate path — silent strip causes LLM\n * to hallucinate tools per gemini-critic finding)\n * - `metadata` (Copilot 200s, ignores harmlessly)\n */\nfunction stripAnthropicOnlyFields(body: AnyRecord): boolean {\n let stripped = false\n if (body.budget !== undefined) {\n consola.warn(\n \"Stripping body-level `budget` field (Copilot 400s; the `task-budgets-` beta header is preserved but cost ceiling is not enforced server-side)\",\n )\n delete body.budget\n stripped = true\n }\n if (body.output_config !== undefined) {\n // output_config has multiple known shapes:\n // - `{schema:{...}}` (Structured Outputs full form) — Copilot 400s\n // - `{type:\"json_object\"}` (Structured Outputs short form, used\n // by Claude Code's hook evaluator + the Anthropic SDK's\n // structured-output API) — Copilot 400s with the same\n // `output_config: Extra inputs are not permitted` message,\n // just at the top-level field rather than the nested .schema.\n // - `{effort:\"high\"}` (proxy-set during adaptive-thinking\n // translation) — Copilot 200s, required by translateThinking.\n //\n // Strategy: strip every Structured-Outputs field (`schema`,\n // `type`, `response_format`, anything else we don't recognize as\n // proxy-internal). Keep `effort` if present. If the object ends\n // up empty, drop the whole field.\n //\n // **Schema preservation via prompt injection**: stripping\n // `output_config.schema` removes server-side enforcement, which\n // makes the model's output non-deterministic. Claude Code's\n // hook evaluator then fails with \"JSON validation failed\" because\n // it tries to `JSON.parse(response)` and gets natural-language\n // text. To preserve the structured-output INTENT through Copilot,\n // append a system-prompt instruction telling the model to produce\n // JSON conforming to the schema. This isn't as strong as\n // server-side enforcement (the model may occasionally deviate),\n // but it's much better than no constraint at all.\n if (body.output_config && typeof body.output_config === \"object\") {\n const oc = body.output_config as AnyRecord\n const PROXY_OWNED_FIELDS = new Set([\"effort\"])\n // Capture the schema BEFORE stripping so we can inject it.\n const schema = oc.schema\n const ocType = oc.type\n let strippedAny = false\n for (const key of Object.keys(oc)) {\n if (!PROXY_OWNED_FIELDS.has(key)) {\n delete oc[key]\n strippedAny = true\n }\n }\n if (strippedAny) {\n consola.warn(\n \"Stripping client-set `output_config` Structured-Outputs fields\"\n + \" (Copilot 400s on `output_config.*` other than `effort`;\"\n + \" injecting schema as system-prompt instruction so the\"\n + \" model still produces JSON conforming to the structured-\"\n + \"outputs schema, since server-side enforcement is gone)\",\n )\n if (Object.keys(oc).length === 0) {\n delete body.output_config\n }\n if (schema !== undefined || ocType === \"json_object\") {\n appendStructuredOutputInstruction(body, schema, ocType)\n }\n stripped = true\n }\n }\n }\n if (Array.isArray(body.betas)) {\n consola.warn(\n \"Stripping body-level `betas` array (Copilot 400s; the betas are conveyed via the `anthropic-beta` header instead)\",\n )\n delete body.betas\n stripped = true\n }\n // Per-tool field strip: `eager_input_streaming` (Fine-Grained Tool Streaming).\n // Auto-enabled by getClaudeCodeEnvVars setting CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1\n // (see src/lib/server-setup.ts), which causes the Claude Code SDK to emit\n // `eager_input_streaming: true` on each custom tool definition. Copilot rejects.\n // JSON-AST traversal — never regex on the raw body (gemini-critic: would\n // corrupt prompt text containing the same string).\n if (Array.isArray(body.tools)) {\n let warnedFGTS = false\n for (const tool of body.tools) {\n if (typeof tool === \"object\" && tool !== null) {\n const t = tool as AnyRecord\n if (t.eager_input_streaming !== undefined) {\n delete t.eager_input_streaming\n stripped = true\n if (!warnedFGTS) {\n consola.warn(\n \"Stripping per-tool `eager_input_streaming` field (Copilot 400s on `tools.*.custom.eager_input_streaming`; FGTS chunk-size optimization disabled, but streaming correctness is unaffected — `input_json_delta` events still flow normally)\",\n )\n warnedFGTS = true\n }\n }\n }\n }\n }\n return stripped\n}\n\n/**\n * Append a system-prompt instruction telling the model to produce JSON\n * conforming to a Structured Outputs schema. Used after the proxy\n * strips `output_config` to preserve the schema enforcement intent\n * via prompt engineering instead of server-side validation.\n *\n * Mutates `body.system` in place. Handles both string and array shapes\n * (Anthropic spec allows either).\n */\nfunction appendStructuredOutputInstruction(\n body: AnyRecord,\n schema: unknown,\n ocType: unknown,\n): void {\n let instruction =\n \"\\n\\nIMPORTANT: Your response MUST be a single valid JSON object.\"\n + \" Do not wrap it in markdown code fences. Do not include any text\"\n + \" before or after the JSON object.\"\n if (schema !== undefined) {\n instruction +=\n ` The JSON object MUST conform to this JSON Schema:\\n${JSON.stringify(schema)}`\n } else if (typeof ocType === \"string\") {\n instruction +=\n ` Output type requested: ${ocType}.`\n }\n if (typeof body.system === \"string\") {\n body.system = body.system + instruction\n } else if (Array.isArray(body.system)) {\n body.system = [\n ...body.system,\n { type: \"text\", text: instruction.trimStart() },\n ]\n } else {\n body.system = instruction.trimStart()\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCountTokens } from \"./count-tokens-handler\"\nimport { handleCompletion } from \"./handler\"\n\nexport const messageRoutes = new Hono()\n\nmessageRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nmessageRoutes.post(\"/count_tokens\", async (c) => {\n try {\n return await handleCountTokens(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { cacheModels } from \"~/lib/utils\"\n\nexport const modelRoutes = new Hono()\n\nmodelRoutes.get(\"/\", async (c) => {\n try {\n if (!state.models) {\n // This should be handled by startup logic, but as a fallback.\n await cacheModels()\n }\n\n const models = state.models?.data.map((model) => {\n // Pass through every upstream field (billing, is_chat_default,\n // info_messages, model_picker_category, etc.) and overlay the\n // OpenAI-compat aliases. requestHeaders is router-internal — drop it.\n const { requestHeaders, ...rest } = model\n void requestHeaders\n return {\n ...rest,\n object: \"model\",\n type: model.capabilities?.type ?? \"model\",\n created: 0,\n created_at: new Date(0).toISOString(),\n owned_by: model.vendor,\n display_name: model.name,\n }\n })\n\n return c.json({\n object: \"list\",\n data: models,\n has_more: false,\n })\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { randomUUID } from \"node:crypto\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { awaitApproval } from \"~/lib/approval\"\nimport { HTTPError } from \"~/lib/error\"\nimport { logEndpointMismatch } from \"~/lib/model-validation\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { logRequest } from \"~/lib/request-log\"\nimport { state } from \"~/lib/state\"\nimport { buildOpenAIErrorEvent, isControllerClosedError, logStreamError } from \"~/lib/stream-relay\"\nimport { resolveModel } from \"~/lib/utils\"\nimport {\n createResponses,\n type ResponsesApiResponse,\n type ResponsesInputItem,\n type ResponsesPayload,\n} from \"~/services/copilot/create-responses\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\ninterface UpstreamSSEEvent {\n event?: string\n data?: string\n id?: string | number\n}\n\nconst ENCODER = new TextEncoder()\n\nfunction formatSSE(chunk: UpstreamSSEEvent): string {\n const parts: Array<string> = []\n if (chunk.event) parts.push(`event: ${chunk.event}`)\n if (chunk.data !== undefined) {\n for (const line of String(chunk.data).split(/\\r\\n|\\r|\\n/)) {\n parts.push(`data: ${line}`)\n }\n }\n if (chunk.id !== undefined) parts.push(`id: ${String(chunk.id)}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nexport async function handleResponses(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n const payload = await c.req.json<ResponsesPayload>()\n const debugEnabled = consola.level >= 4\n if (debugEnabled) {\n consola.debug(\n \"Responses request payload:\",\n JSON.stringify(payload).slice(-400),\n )\n }\n\n // Resolve model name (e.g. opus → opus-1m variant)\n const originalModel = payload.model\n const resolvedModel = resolveModel(payload.model)\n if (resolvedModel !== payload.model) {\n payload.model = resolvedModel\n }\n\n const selectedModel = state.models?.data.find(\n (model) => model.id === payload.model,\n )\n\n logEndpointMismatch(payload.model, \"/responses\")\n\n if (state.manualApprove) await awaitApproval()\n\n await injectWebSearchIfNeeded(payload)\n\n const response = await createResponses(payload, selectedModel?.requestHeaders).catch(\n async (error: unknown) => {\n if (error instanceof HTTPError) {\n const errorBody = await error.response.clone().text().catch(() => \"\")\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: error.response.status,\n errorBody,\n },\n selectedModel,\n startTime,\n )\n }\n throw error\n },\n )\n const isStreaming = !isNonStreaming(response)\n\n logRequest(\n {\n method: \"POST\",\n path: c.req.path,\n model: originalModel,\n resolvedModel,\n status: 200,\n streaming: isStreaming,\n },\n selectedModel,\n startTime,\n )\n\n if (!isStreaming) {\n if (debugEnabled) {\n consola.debug(\"Non-streaming response:\", JSON.stringify(response))\n }\n return c.json(response)\n }\n\n // Streaming: peek the first SSE event so pre-byte upstream errors surface\n // through the route's try/catch → forwardError as a clean JSON response,\n // and only mid-stream errors hit the manual ReadableStream's pull-error path.\n // The /responses iterator emits a final `[DONE]` sentinel which we drop.\n const iterator = (response as AsyncIterableIterator<UpstreamSSEEvent>)[\n Symbol.asyncIterator\n ]()\n\n // Skip leading empty / [DONE] sentinels until we get a real event.\n let firstChunk: UpstreamSSEEvent | undefined\n let upstreamFinished = false\n while (true) {\n const r = await iterator.next()\n if (r.done) {\n upstreamFinished = true\n break\n }\n // Defensive guard against an iterator that yields {done:false, value:undefined}\n // before we dereference r.value.data below.\n if (r.value === undefined || r.value === null) continue\n if (r.value.data === \"[DONE]\") {\n upstreamFinished = true\n break\n }\n if (!r.value.data) continue\n firstChunk = r.value\n break\n }\n if (firstChunk === undefined) {\n consola.warn(\n `Upstream /responses returned no payload events at ${c.req.path}`,\n )\n }\n\n let pendingFirstChunk: UpstreamSSEEvent | undefined = firstChunk\n let consumerCancelled = false\n\n const safeClose = (controller: ReadableStreamDefaultController<Uint8Array>) => {\n try {\n controller.close()\n } catch {\n // already closed / errored\n }\n }\n const releaseUpstream = (reason?: unknown) => {\n if (typeof iterator.return === \"function\") {\n iterator.return(reason).catch(() => {\n // upstream may already be closed\n })\n }\n }\n const safeEnqueue = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n bytes: Uint8Array,\n ): boolean => {\n try {\n controller.enqueue(bytes)\n return true\n } catch (e) {\n if (isControllerClosedError(e)) {\n consumerCancelled = true\n // The downstream cancel() callback may not fire if the controller\n // was closed by Bun's HTTP layer rather than an explicit consumer\n // .cancel() — release the upstream iterator here so the upstream\n // socket does not leak.\n releaseUpstream(e)\n return false\n }\n throw e\n }\n }\n\n return new Response(\n new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (consumerCancelled || upstreamFinished) {\n safeClose(controller)\n return\n }\n if (pendingFirstChunk !== undefined) {\n const chunk = pendingFirstChunk\n pendingFirstChunk = undefined\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(chunk))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(chunk)))\n return\n }\n try {\n const result = await iterator.next()\n if (consumerCancelled) {\n safeClose(controller)\n return\n }\n if (result.done) {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n // Defensive: an upstream iterator that yields `{done:false, value:undefined}`\n // would crash on `result.value.data` below. Skip silently and\n // pull again on the next consumer demand. Real upstream iterators\n // never emit this shape, but a misbehaving / proxied iterator might.\n if (result.value === undefined || result.value === null) return\n if (result.value.data === \"[DONE]\") {\n upstreamFinished = true\n safeClose(controller)\n return\n }\n if (!result.value.data) return\n if (debugEnabled) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(result.value))\n }\n safeEnqueue(controller, ENCODER.encode(formatSSE(result.value)))\n } catch (error) {\n upstreamFinished = true\n if (consumerCancelled) {\n // Consumer-cancelled mid-pull. Release the upstream iterator\n // — the cancel() callback may not have fired if the controller\n // was closed by Bun's HTTP layer.\n //\n // We deliberately do NOT call isControllerClosedError(error)\n // on iterator-side errors here — the helper matches substrings\n // like \"stream is closed\" which can appear in real upstream\n // errors, and treating them as consumer-cancel would silently\n // suppress the OpenAI-shape error frame the consumer needs.\n releaseUpstream(error)\n safeClose(controller)\n return\n }\n const { errName, errMessage } = logStreamError(c.req.path, error)\n safeEnqueue(\n controller,\n ENCODER.encode(buildOpenAIErrorEvent(errName, errMessage)),\n )\n // Server-initiated close — release the upstream iterator since\n // our cancel() callback won't fire.\n releaseUpstream(error)\n safeClose(controller)\n }\n },\n cancel() {\n consumerCancelled = true\n upstreamFinished = true\n releaseUpstream()\n },\n }),\n {\n status: 200,\n headers: {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n \"transfer-encoding\": \"chunked\",\n connection: \"keep-alive\",\n },\n },\n )\n}\n\nconst isNonStreaming = (\n response: Awaited<ReturnType<typeof createResponses>>,\n): response is ResponsesApiResponse => Object.hasOwn(response, \"output\")\n\nasync function injectWebSearchIfNeeded(\n payload: ResponsesPayload,\n): Promise<void> {\n const hasWebSearch = payload.tools?.some((t) => t.type === \"web_search\")\n if (!hasWebSearch) return\n\n // Skip search on follow-up messages (function call results)\n if (Array.isArray(payload.input)) {\n const hasFollowUp = payload.input.some(\n (item: ResponsesInputItem) => item.type === \"function_call_output\",\n )\n if (hasFollowUp) return\n }\n\n const query = extractUserQuery(payload.input)\n if (query) {\n try {\n const results = await searchWeb(query)\n const searchContext = [\n \"[Web Search Results]\",\n results.content,\n \"\",\n results.references.map((r) => `- [${r.title}](${r.url})`).join(\"\\n\"),\n \"[End Web Search Results]\",\n ].join(\"\\n\")\n\n payload.instructions =\n payload.instructions ?\n `${searchContext}\\n\\n${payload.instructions}`\n : searchContext\n } catch (error) {\n consola.warn(\"Web search failed, continuing without results:\", error)\n }\n }\n\n // Strip the legacy `web_search` tool — defensive. Copilot's /responses\n // empirically accepts bare `web_search` on gpt-5.x today (2026-05-15\n // probe `web_search_responses_preview`: model invokes it natively, output\n // contains a `web_search_call` block), and also accepts the explicit\n // `web_search_preview` / `web_search_preview_2025_03_11` shapes. We strip\n // here as belt-and-suspenders against version drift across Copilot tiers\n // — the proxy's MCP fallback (`injectWebSearchIfNeeded`) substitutes a\n // pre-fetched result so the user-facing path always works regardless of\n // upstream support. Lift this strip if/when we trust bare `web_search`\n // across all served gpt-5.x variants. Other tool types pass through\n // unchanged.\n payload.tools = payload.tools?.filter((t) => t.type !== \"web_search\")\n if (payload.tools && payload.tools.length === 0) {\n payload.tools = undefined\n }\n if (!payload.tools) {\n payload.tool_choice = undefined\n } else if (\n payload.tool_choice\n && typeof payload.tool_choice === \"object\"\n ) {\n const choice = payload.tool_choice as {\n name?: string\n function?: { name?: string }\n }\n const choiceName = choice.function?.name ?? choice.name\n if (choiceName === \"web_search\") {\n payload.tool_choice = undefined\n }\n }\n}\n\nfunction extractUserQuery(\n input: ResponsesPayload[\"input\"],\n): string | undefined {\n if (typeof input === \"string\") return input\n if (!Array.isArray(input)) return undefined\n\n // Find the last user message\n for (let i = input.length - 1; i >= 0; i--) {\n const item = input[i]\n if (\"role\" in item && item.role === \"user\") {\n if (typeof item.content === \"string\") return item.content\n if (Array.isArray(item.content)) {\n const text = item.content.find(\n (p: Record<string, unknown>) => p.type === \"input_text\",\n )\n if (text && \"text\" in text) return text.text as string\n }\n }\n }\n return undefined\n}\n\n/**\n * Compaction prompt used when GitHub Copilot API does not support\n * /responses/compact natively. Matches the prompt Codex CLI uses for\n * local (non-OpenAI) compaction.\n */\nconst COMPACTION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.\n\nInclude:\n- Current progress and key decisions made\n- Important context, constraints, or user preferences\n- What remains to be done (clear next steps)\n- Any critical data, examples, or references needed to continue\n\nBe concise, structured, and focused on helping the next LLM seamlessly continue the work.`\n\ninterface CompactRequestPayload {\n model: string\n input: Array<Record<string, unknown>>\n instructions?: string\n [key: string]: unknown\n}\n\nexport async function handleResponsesCompact(c: Context) {\n const startTime = Date.now()\n await checkRateLimit(state)\n\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n if (state.manualApprove) await awaitApproval()\n\n const body = await c.req.json<CompactRequestPayload>()\n\n // Try Copilot's native compact endpoint first (future-proofs for when they add support)\n const response = await fetch(\n `${copilotBaseUrl(state)}/responses/compact`,\n {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(body),\n },\n )\n\n if (response.ok) {\n logRequest(\n { method: \"POST\", path: c.req.path, status: 200 },\n undefined,\n startTime,\n )\n return c.json(await response.json())\n }\n\n // Copilot doesn't support /responses/compact — perform synthetic compaction\n // by sending a regular /responses call with a summarization prompt\n if (response.status === 404) {\n consola.debug(\"Copilot API does not support /responses/compact, using synthetic compaction\")\n return await syntheticCompact(c, body, startTime)\n }\n\n // Other errors: throw as before\n logRequest(\n { method: \"POST\", path: c.req.path, status: response.status },\n undefined,\n startTime,\n )\n throw new HTTPError(\"Copilot responses/compact request failed\", response)\n}\n\n/**\n * Synthetic compaction: sends the conversation history to Copilot's\n * regular /responses endpoint with a compaction prompt appended,\n * then returns the model's summary in the compact response format.\n */\nasync function syntheticCompact(\n c: Context,\n body: CompactRequestPayload,\n startTime: number,\n) {\n const input = Array.isArray(body.input) ? [...body.input] : []\n\n // Append compaction prompt as the last user message\n input.push({\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: COMPACTION_PROMPT }],\n })\n\n const payload: ResponsesPayload = {\n model: body.model,\n input: input as Array<ResponsesInputItem>,\n instructions: body.instructions,\n stream: false,\n store: false,\n }\n\n let result: ResponsesApiResponse\n try {\n result = (await createResponses(payload)) as ResponsesApiResponse\n } catch (error) {\n if (error instanceof HTTPError) {\n logRequest(\n { method: \"POST\", path: c.req.path, status: error.response.status },\n undefined,\n startTime,\n )\n }\n throw error\n }\n\n logRequest(\n { method: \"POST\", path: c.req.path, status: 200 },\n undefined,\n startTime,\n )\n\n return c.json({\n id: `resp_compact_${randomUUID().replace(/-/g, \"\").slice(0, 24)}`,\n object: \"response.compaction\",\n created_at: Math.floor(Date.now() / 1000),\n output: result.output,\n usage: result.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 },\n })\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleResponses, handleResponsesCompact } from \"./handler\"\n\nexport const responsesRoutes = new Hono()\n\nresponsesRoutes.post(\"/\", async (c) => {\n try {\n return await handleResponses(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nresponsesRoutes.post(\"/compact\", async (c) => {\n try {\n return await handleResponsesCompact(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { searchWeb } from \"~/services/copilot/web-search\"\n\nexport const searchRoutes = new Hono()\n\nsearchRoutes.post(\"/\", async (c) => {\n try {\n const { query } = await c.req.json<{ query: string }>()\n\n if (!query || typeof query !== \"string\") {\n return c.json(\n { error: { message: \"Missing required field: query\" } },\n 400,\n )\n }\n\n const results = await searchWeb(query)\n return c.json({ results })\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { state } from \"~/lib/state\"\n\nexport const tokenRoute = new Hono()\n\ntokenRoute.get(\"/\", (c) => {\n if (!state.showToken) {\n return c.json(\n { error: { message: \"Token endpoint disabled\", type: \"error\" } },\n 403,\n )\n }\n\n return c.json({\n token: state.copilotToken,\n })\n})\n","import { Hono } from \"hono\"\nimport consola from \"consola\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\n\nexport const usageRoute = new Hono()\n\nusageRoute.get(\"/\", async (c) => {\n try {\n const usage = await getCopilotUsage()\n return c.json(usage)\n } catch (error) {\n consola.error(\"Error fetching Copilot usage:\", error)\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\n\nimport packageJson from \"../package.json\" with { type: \"json\" }\n\nimport { completionRoutes } from \"./routes/chat-completions/route\"\nimport { embeddingRoutes } from \"./routes/embeddings/route\"\nimport { mcpRoutes } from \"./routes/mcp/route\"\nimport { messageRoutes } from \"./routes/messages/route\"\nimport { modelRoutes } from \"./routes/models/route\"\nimport { responsesRoutes } from \"./routes/responses/route\"\nimport { searchRoutes } from \"./routes/search/route\"\nimport { tokenRoute } from \"./routes/token/route\"\nimport { usageRoute } from \"./routes/usage/route\"\n\nexport const server = new Hono()\n\nserver.use(cors())\n\nserver.get(\"/\", (c) => c.text(\"Server running\"))\n\n// Build identity. Operators can `curl http://localhost:<port>/version` to\n// confirm which build is serving requests — useful when upgrading via\n// `npx github-router@latest` and verifying the new code actually loaded.\nserver.get(\"/version\", (c) =>\n c.json({\n name: packageJson.name,\n version: packageJson.version,\n gitSha: process.env.GITHUB_SHA ?? \"unknown\",\n }),\n)\n\n// Claude CLI sends HEAD / as health check before each request\nserver.on(\"HEAD\", [\"/\"], (c) => c.body(null, 200))\n\nserver.route(\"/chat/completions\", completionRoutes)\nserver.route(\"/responses\", responsesRoutes)\nserver.route(\"/models\", modelRoutes)\nserver.route(\"/embeddings\", embeddingRoutes)\nserver.route(\"/search\", searchRoutes)\nserver.route(\"/usage\", usageRoute)\nserver.route(\"/token\", tokenRoute)\n\n// Compatibility with tools that expect v1/ prefix\nserver.route(\"/v1/chat/completions\", completionRoutes)\nserver.route(\"/v1/responses\", responsesRoutes)\nserver.route(\"/v1/models\", modelRoutes)\nserver.route(\"/v1/embeddings\", embeddingRoutes)\nserver.route(\"/v1/search\", searchRoutes)\n\n// Anthropic compatible endpoints\nserver.route(\"/v1/messages\", messageRoutes)\n\n// Peer-MCP endpoint: hosts gpt-5.5/gpt-5.3-codex/gemini-3.1-pro\n// persona tools (codex_critic / codex_reviewer / gemini_critic) for\n// the spawned Claude Code session to consult via MCP. Auth is a\n// per-launch nonce stored in `state.peerMcpNonce`; the route\n// rejects all requests when the nonce is unset (e.g. proxy started\n// standalone via `github-router start`). See src/routes/mcp/handler.ts.\nserver.route(\"/mcp\", mcpRoutes)\n\n// Stub out Claude Code SDK telemetry so it doesn't 404-spam logs.\n// Copilot doesn't expose this endpoint; clients fire it best-effort.\nserver.post(\"/api/event_logging/batch\", (c) => c.body(null, 200))\n\n// Phase E P1.4: explicit Files-API not-supported route. Claude Code's\n// BriefTool upload + utils/teleport/gitBundle paths hit\n// GET /v1/files/{id}/content (download), GET /v1/files (list),\n// POST /v1/files (upload). Copilot has no equivalent storage backend\n// (verified via cc-backup src/services/api/filesApi.ts). Without this\n// explicit route, requests fall to the default 404 with a generic\n// \"not found\" message — fine but unhelpful.\n//\n// Why surface explicitly: the user gets a clear signal \"this feature\n// isn't supported here\" instead of inferring it from a generic 404.\n// Fail-loud-with-explanation aligns with cc-backup mentality #10\n// (errors are logged not swallowed; surface the limitation).\nserver.all(\"/v1/files/*\", (c) =>\n c.json(\n {\n type: \"error\",\n error: {\n type: \"not_found_error\",\n message:\n \"Files API is not supported by github-router (Copilot has no equivalent storage backend). \"\n + \"Use the Anthropic API directly for file uploads/downloads.\",\n },\n },\n 404,\n ),\n)\n\n// Return Anthropic-format JSON for unknown endpoints\nserver.notFound((c) =>\n c.json(\n {\n type: \"error\",\n error: {\n type: \"not_found_error\",\n message: `${c.req.method} ${c.req.path} not found`,\n },\n },\n 404,\n ),\n)\n","import consola from \"consola\"\nimport { serve, type ServerHandler } from \"srvx\"\n\nimport { PATHS, ensurePaths } from \"./paths\"\nimport { generateRandomPort } from \"./port\"\nimport { initProxyFromEnv } from \"./proxy\"\nimport { state } from \"./state\"\nimport { setupCopilotToken, setupGitHubToken } from \"./token\"\nimport { cacheModels, cacheCopilotVersion, cacheVSCodeVersion } from \"./utils\"\nimport { server as app } from \"../server\"\n\nconst MAX_PORT_RETRIES = 10\n\nexport interface ServerSetupOptions {\n port?: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n showToken: boolean\n proxyEnv: boolean\n extendedBetas: boolean\n silent: boolean\n}\n\nexport async function setupAndServe(\n options: ServerSetupOptions,\n): Promise<{ server: ReturnType<typeof serve>; serverUrl: string }> {\n if (options.proxyEnv) {\n initProxyFromEnv()\n }\n\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n if (options.accountType !== \"individual\") {\n consola.info(`Using ${options.accountType} plan GitHub account`)\n }\n\n state.manualApprove = options.manual\n state.rateLimitSeconds = options.rateLimit\n state.rateLimitWait = options.rateLimitWait\n state.showToken = options.showToken\n state.extendedBetas = options.extendedBetas\n\n if (process.env.COPILOT_API_URL) {\n state.copilotApiUrl = process.env.COPILOT_API_URL\n }\n\n await ensurePaths()\n await cacheVSCodeVersion()\n await cacheCopilotVersion()\n\n if (options.githubToken) {\n state.githubToken = options.githubToken\n consola.info(\"Using provided GitHub token\")\n } else {\n await setupGitHubToken()\n }\n\n await setupCopilotToken()\n await cacheModels()\n\n consola.debug(\n `Available models: \\n${state.models?.data.map((model) => `- ${model.id}`).join(\"\\n\")}`,\n )\n\n const serveOptions = {\n fetch: app.fetch as ServerHandler,\n hostname: \"127.0.0.1\",\n silent: options.silent,\n }\n\n let srvxServer: ReturnType<typeof serve> | undefined\n\n if (options.port !== undefined) {\n // Explicit port — no retry\n srvxServer = serve({ ...serveOptions, port: options.port })\n } else {\n // Random available port with retry\n let lastError: unknown\n for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {\n const candidatePort = generateRandomPort()\n try {\n srvxServer = serve({ ...serveOptions, port: candidatePort })\n break\n } catch (error) {\n lastError = error\n const isAddrInUse =\n error instanceof Error\n && (error.message.includes(\"EADDRINUSE\")\n || error.message.includes(\"address already in use\")\n || (\"code\" in error\n && (error as NodeJS.ErrnoException).code === \"EADDRINUSE\"))\n if (!isAddrInUse) throw error\n consola.debug(`Port ${candidatePort} in use, trying another...`)\n }\n }\n\n if (srvxServer === undefined) {\n throw new Error(\n `Failed to find an available port after ${MAX_PORT_RETRIES} attempts. `\n + `Specify a port with --port or free some ports. Last error: ${lastError}`,\n )\n }\n }\n\n // Wait for the server to be listening before reading the URL\n await srvxServer.ready()\n const url = srvxServer.url\n if (!url) {\n throw new Error(\"Server started but URL is not available\")\n }\n const serverUrl = url.replace(/\\/$/, \"\")\n\n return { server: srvxServer, serverUrl }\n}\n\n/** Shared CLI arg definitions for all server commands. */\nexport const sharedServerArgs = {\n port: {\n alias: \"p\",\n type: \"string\" as const,\n description: \"Port to listen on\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\" as const,\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\" as const,\n default: \"enterprise\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n manual: {\n type: \"boolean\" as const,\n default: false,\n description: \"Enable manual request approval\",\n },\n \"rate-limit\": {\n alias: \"r\",\n type: \"string\" as const,\n description: \"Rate limit in seconds between requests\",\n },\n wait: {\n alias: \"w\",\n type: \"boolean\" as const,\n default: false,\n description:\n \"Wait instead of error when rate limit is hit. Has no effect if rate limit is not set\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\" as const,\n description:\n \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"show-token\": {\n type: \"boolean\" as const,\n default: false,\n description: \"Show GitHub and Copilot tokens on fetch and refresh\",\n },\n \"proxy-env\": {\n type: \"boolean\" as const,\n default: false,\n description: \"Initialize proxy from environment variables\",\n },\n \"extended-betas\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Forward extended beta headers for Claude CLI compatibility (default: VS Code-only)\",\n },\n} as const\n\nconst allowedAccountTypes = new Set([\"individual\", \"business\", \"enterprise\"])\n\n/** Parse shared server args into ServerSetupOptions fields. */\nexport function parseSharedArgs(args: Record<string, unknown>): {\n port?: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n showToken: boolean\n proxyEnv: boolean\n extendedBetas: boolean\n} {\n const portRaw = args.port as string | undefined\n let port: number | undefined\n if (portRaw !== undefined) {\n port = Number.parseInt(portRaw, 10)\n if (Number.isNaN(port) || port <= 0 || port > 65535) {\n throw new Error(\"Invalid port. Must be between 1 and 65535.\")\n }\n }\n\n const accountType = (args[\"account-type\"] as string) ?? \"enterprise\"\n if (!allowedAccountTypes.has(accountType)) {\n throw new Error(\n \"Invalid account type. Must be individual, business, or enterprise.\",\n )\n }\n\n const rateLimitRaw = args[\"rate-limit\"] as string | undefined\n let rateLimit: number | undefined\n if (rateLimitRaw !== undefined) {\n rateLimit = Number.parseInt(rateLimitRaw, 10)\n if (Number.isNaN(rateLimit) || rateLimit <= 0) {\n throw new Error(\"Invalid rate limit. Must be a positive integer.\")\n }\n }\n\n const rateLimitWait = (args.wait as boolean) && rateLimit !== undefined\n if ((args.wait as boolean) && rateLimit === undefined) {\n consola.warn(\"Rate limit wait ignored because no rate limit was set.\")\n }\n\n const githubToken =\n (args[\"github-token\"] as string | undefined) ?? process.env.GH_TOKEN\n\n return {\n port,\n verbose: args.verbose as boolean,\n accountType,\n manual: args.manual as boolean,\n rateLimit,\n rateLimitWait,\n githubToken,\n showToken: args[\"show-token\"] as boolean,\n proxyEnv: args[\"proxy-env\"] as boolean,\n extendedBetas: args[\"extended-betas\"] as boolean,\n }\n}\n\n/**\n * Build environment variables for Claude Code.\n *\n * The parent env is sanitized of every key in `STRIPPED_PARENT_ENV_KEYS`\n * (see `src/lib/launch.ts`) BEFORE these overrides are merged in, so we\n * only need to provide the positive values.\n *\n * Auth precedence in Claude Code (https://code.claude.com/docs/en/iam),\n * after the github-router substrate fix:\n * 1. Cloud provider (CLAUDE_CODE_USE_BEDROCK / VERTEX / FOUNDRY) — stripped at parent.\n * 2. ANTHROPIC_AUTH_TOKEN — NOT set by the proxy. Stripped at parent\n * (no env-source auth in the spawned child at all).\n * 3. ANTHROPIC_API_KEY — stripped at parent.\n * 4. apiKeyHelper in settings.json — copied into our config dir as\n * part of the mirror; if the user defined one, it still fires\n * and may mint an `x-api-key` header. Copilot ignores `x-api-key`,\n * so behavior is unchanged from before this fix.\n * 5. CLAUDE_CODE_OAUTH_TOKEN — stripped at parent.\n * 6. Subscription OAuth (Keychain / `<CLAUDE_CONFIG_DIR>/.credentials.json`)\n * — the credentials file is OURS (synthetic blob, written by\n * `ensureClaudeConfigMirror`). Claude Code reads accessToken from\n * it and sends as `Authorization: Bearer <accessToken>`. The\n * teammate-spawn allowlist propagates `CLAUDE_CONFIG_DIR` to\n * children, so spawned teammates find the same synthetic credential\n * and authenticate (the bug this whole fix addresses).\n *\n * `CLAUDE_CONFIG_DIR` activates Claude Code's per-config-dir keychain\n * isolation (per binary-grep of v2.1.126's `iN()` function: when set,\n * the keychain service name becomes `Claude Code-<sha256(path)[0..8]>`,\n * missing the user's real `Claude Code` entry). Pointing it at our\n * snapshot-copied `PATHS.CLAUDE_CONFIG_DIR` preserves user customization\n * (mirrored settings.json, skills, MCP, hooks, CLAUDE.md, custom\n * agents) while giving teammates a credential they can find on disk.\n *\n * No-401 invariant: Claude Code's reactive refresh path (`SZ1` →\n * `D3(0,true,...)`) fires on any 401 from upstream. The synthetic\n * refreshToken would fail any real refresh attempt, so the proxy\n * MUST NOT return 401 on the Anthropic-shape boundary even when\n * upstream Copilot returns 401. See `src/routes/messages/handler.ts`.\n */\nexport function getClaudeCodeEnvVars(\n serverUrl: string,\n model?: string,\n): Record<string, string> {\n const vars: Record<string, string> = {\n // Route to the proxy\n ANTHROPIC_BASE_URL: serverUrl,\n // CLAUDE_CONFIG_DIR points at the router-owned snapshot mirror;\n // the synthetic .credentials.json inside it provides the OAuth\n // accessToken that Claude Code sends as Bearer. See\n // `ensureClaudeConfigMirror` in `src/lib/paths.ts`.\n CLAUDE_CONFIG_DIR: PATHS.CLAUDE_CONFIG_DIR,\n // Extend Claude Code's MCP per-tool-call wait window. Two distinct\n // env vars are at play (per binary inspection of v2.1.141 by the\n // peer-MCP team's empirical SDK test, 2026-05-14):\n //\n // - MCP_TIMEOUT — historical/general MCP timeout, may apply to\n // server-startup or initial-handshake but NOT confirmed to reach\n // the per-tool-call HTTP wait on v2.1.138-141 (regressions\n // #50289 / #52137 documented this as silently-ignored on the\n // per-call path). Kept as belt-and-suspenders.\n //\n // - MCP_TOOL_TIMEOUT — the load-bearing one. v2.1.141's `y13()`\n // reads `parseInt(process.env.MCP_TOOL_TIMEOUT)` for the per-\n // tool-call timeout passed to MCP SDK's `.callTool({...},\n // schema, {timeout: W})`. Default `1e8` ms (~27.7 hours) when\n // the env is unset. Setting it to a finite-but-large value\n // (10 min) is safer than relying on the implicit 27.7-hour\n // default — surfaces regressions where the SDK silently caps\n // at 60s, AND prevents long-tail runaway calls from holding\n // resources indefinitely.\n //\n // Without the SDK's `resetTimeoutOnProgress` opt-in (which Claude\n // Code does not pass), SSE notifications/progress events DO NOT\n // reset the per-call timer — they only fire UI callbacks. So\n // MCP_TOOL_TIMEOUT is the actual lever for long-running peer-MCP\n // calls, not the SSE response transport. SSE remains valuable as\n // the canonical Streamable HTTP shape and for progress UI, but the\n // ceiling-busting work is done by these env vars.\n MCP_TIMEOUT: \"600000\",\n MCP_TOOL_TIMEOUT: \"600000\",\n // Suppress non-essential telemetry/model calls. The first two are\n // Anthropic's own knobs (per cc-backup managedEnv.ts); the third\n // (`DISABLE_TELEMETRY`) suppresses Datadog/Statsig/etc. external\n // analytics that would otherwise run regardless of the proxy. None of\n // these calls reach the proxy (they hit external hosts), but they\n // consume user resources and may leak metadata. Setting all three\n // turns the spawned child into a quiet local-only session.\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n DISABLE_TELEMETRY: \"1\",\n }\n if (model) vars.ANTHROPIC_MODEL = model\n\n // Default the small/fast tier model (used by Claude Code for status\n // text, auto-compact summaries, session titles, background ops) to\n // claude-haiku-4-5. Anthropic-published dashed slug; the proxy's\n // resolveModel translates to Copilot's dotted slug at request time.\n // Presence-based guard preserves any user-set value, including the\n // dated slug variant or a different family (gemini, gpt) for users\n // who have custom Copilot mappings — symmetric with the\n // ANTHROPIC_SMALL_FAST_MODEL pass-through documented in launch.ts's\n // STRIPPED_PARENT_ENV_KEYS comment.\n if (process.env.ANTHROPIC_SMALL_FAST_MODEL === undefined) {\n vars.ANTHROPIC_SMALL_FAST_MODEL = \"claude-haiku-4-5\"\n }\n\n // Tier-default knobs read by Claude Code's /model picker (cc-backup\n // src/utils/model/modelOptions.ts:78,109,167) when the user invokes\n // the picker to switch model. Without these, the picker shows\n // Anthropic's catalog-baseline entries (which may be stale relative\n // to what Copilot has). Setting them seeds the three tier rows with\n // ids the proxy's resolveModel knows how to route.\n //\n // Why NO [1m] suffix on Sonnet/Haiku: Copilot has no -1m backend for\n // either family (only opus-4.7-1m-internal exists in the catalog as\n // of 2026-05-22; Anthropic-side modelSupports1M in cc-backup\n // context.ts:43-49 only lists sonnet-4* and opus-4-6 — haiku has no\n // 1M variant on either side). The [1m] decoration for the *active*\n // default lives on ANTHROPIC_MODEL itself (see pickClaudeDefault in\n // src/claude.ts) and is cap-aware against the live catalog.\n //\n // Presence-based guard symmetric with the SMALL_FAST_MODEL guard\n // above — preserves any value (including 0/false/off/unrecognized)\n // the user has explicitly set.\n if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_SONNET_MODEL = \"claude-sonnet-4-6\"\n }\n if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_HAIKU_MODEL = \"claude-haiku-4-5\"\n }\n if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL === undefined) {\n vars.ANTHROPIC_DEFAULT_OPUS_MODEL = \"claude-opus-4-7\"\n }\n\n // Auto-enable Anthropic's experimental \"leverage\" features for proxied\n // claude sessions. Symmetric with the leverage-policy default\n // (extended-betas ON for `claude` subcommand): users running\n // `github-router claude` opted in for the Claude Code feature surface,\n // and these experimental gates default off for non-Anthropic users\n // (gated by GrowthBook flags that don't fire outside Anthropic).\n //\n // Presence-based guard: if the parent env has set ANY value for these\n // keys (including \"0\", \"false\", \"no\", \"off\", or any unrecognized\n // value), preserve the user's intent — only inject \"1\" when the key\n // is unset. The parent env survives `buildLaunchCommand`'s sanitize\n // step because none of these keys are in `STRIPPED_PARENT_ENV_KEYS`,\n // so an unset proxy var means the parent's value (if any) wins\n // naturally.\n //\n // ADVISOR has a documented `CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1` hard\n // opt-out that wins via JI()'s ordering (DISABLE checked before\n // ENABLE). FORK_SUBAGENT and AGENT_TEAMS rely on Anthropic's SH()\n // falsy semantics for opt-out (\"0\"/\"false\"/\"no\"/\"off\"/empty all opt\n // out — preserved by the presence guard). FINE_GRAINED_TOOL_STREAMING\n // is explicitly recommended by Anthropic's docs at\n // code.claude.com/docs/en/env-vars: \"Set to `1` to force on when\n // routing through a proxy via ANTHROPIC_BASE_URL\". TASKS only\n // manifests in `claude -p` headless mode.\n //\n // GATEWAY_MODEL_DISCOVERY is intentionally NOT enabled here — Claude\n // Code's hardcoded slug registry maps slugs to capabilities, not just\n // labels; Copilot's slugs (claude-opus-4.6-1m) don't match\n // Anthropic's registry (claude-opus-4-6), so dynamic discovery would\n // silently degrade advanced tool use. Enable it intentionally only\n // after building a slug-translation shim in /v1/models. See\n // CLAUDE.md \"Experimental Claude Code features auto-enabled\".\n const experimentalEnables: ReadonlyArray<string> = [\n \"CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL\",\n \"CLAUDE_CODE_FORK_SUBAGENT\",\n \"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS\",\n \"CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING\",\n \"CLAUDE_CODE_ENABLE_TASKS\",\n ]\n for (const key of experimentalEnables) {\n if (process.env[key] === undefined) {\n vars[key] = \"1\"\n }\n }\n\n return vars\n}\n\n/**\n * Build environment variables for Codex CLI.\n *\n * Like `getClaudeCodeEnvVars`, the parent env is sanitized of\n * `OPENAI_API_KEY` / `OPENAI_BASE_URL` / `CODEX_HOME` (see\n * `STRIPPED_PARENT_ENV_KEYS` in `src/lib/launch.ts`) before these\n * overrides are merged, so a stale shell `OPENAI_API_KEY` can't leak\n * through. Codex caches a ChatGPT subscription login under\n * `$CODEX_HOME/auth.json` which can override `OPENAI_API_KEY` per\n * openai/codex#2733; pointing `CODEX_HOME` at an isolated directory\n * masks any cached login.\n */\nexport function getCodexEnvVars(serverUrl: string): Record<string, string> {\n return {\n OPENAI_BASE_URL: `${serverUrl}/v1`,\n OPENAI_API_KEY: \"dummy\",\n // Isolated CODEX_HOME — masks any cached ChatGPT login (openai/codex#2733).\n CODEX_HOME: PATHS.CODEX_HOME,\n }\n}\n","import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport {\n autoUpdateClaude,\n checkClaudeVersion,\n} from \"./lib/claude-version-check\"\nimport {\n injectPeerMcpIntoMirror,\n resolveCodexCliBackend,\n writePeerMcpRuntimeFiles,\n} from \"./lib/codex-mcp-config\"\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { getCodexVersion, launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport { ensureClaudeConfigMirror, removeOwnClaudeConfigMirror } from \"./lib/paths\"\nimport { buildPeerAwarenessSnippet } from \"./lib/peer-mcp-personas\"\nimport {\n DEFAULT_CLAUDE_MODEL_FALLBACKS,\n pickClaudeDefault,\n} from \"./lib/port\"\nimport {\n getClaudeCodeEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { state } from \"./lib/state\"\nimport { resolveModel } from \"./lib/utils\"\n\nexport const claude = defineCommand({\n meta: {\n name: \"claude\",\n description: \"Start the proxy server and launch Claude Code\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Claude Code\",\n },\n \"codex-mcp\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Wire peer-model MCP personas (codex-critic, codex-reviewer, gemini-critic) into the spawned Claude Code session\",\n },\n \"codex-cli\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Add a `codex mcp-server` stdio backend so codex-implementer can mutate files. Requires codex CLI 0.129+; gracefully falls back to HTTP-only if absent.\",\n },\n \"codex-mcp-only\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Pass --strict-mcp-config to claude code so only github-router's MCP servers are loaded (hides user's existing MCP servers)\",\n },\n stealth: {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Opt back into VS Code-only beta header filtering. Loses leverage features (task budgets, token-efficient tools, prompt caching, etc.) but minimizes the wire-fingerprint difference from VS Code Copilot Chat. By default the `claude` subcommand enables extended/leverage betas because the spawned Claude Code already identifies itself via UA and other headers — partial stealth doesn't buy much.\",\n },\n \"auto-update\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check for and install latest Claude Code on launch (throttled to once per hour via ~/.local/share/github-router/last-update-check). Set to false (--no-auto-update) to keep the current installed version. Falls back gracefully if npm/network unavailable.\",\n },\n \"update-check\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check the npm registry for a newer Claude Code version on launch and warn if stale (non-blocking ~500ms cost). Set to false (--no-update-check) to skip the check entirely (useful for offline/CI). Independent from --auto-update: --no-update-check implies no auto-install (nothing to install since we never check).\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The claude subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n // Phase E P2.2: stealth-vs-leverage policy.\n // The `claude` subcommand defaults to LEVERAGE mode (extended-betas\n // ON) because the spawned Claude Code already identifies itself via\n // UA / editor-version / x-app headers — partial stealth doesn't\n // meaningfully reduce the wire fingerprint, and the cost of stealth\n // is losing features the user explicitly chose to install Claude\n // Code for (--max-budget-usd, token-efficient tools, prompt caching,\n // structured outputs, MCP, etc.).\n //\n // The `--stealth` flag opts back into the VS Code-only filter for\n // users who specifically want minimal wire diff over leverage.\n // The shared `--extended-betas` flag still works (treated as alias).\n //\n // Note: `advisor-tool-` is stripped in BOTH modes regardless of this\n // setting (Phase A: Copilot 400s on it). ADVISOR will be served via\n // Phase I's proxy-side translate path independently.\n if (args.stealth) {\n // Stealth wins if explicitly requested.\n parsed.extendedBetas = false\n consola.info(\n \"Stealth mode: VS Code-only beta filtering. Leverage features disabled.\",\n )\n } else if (!args[\"extended-betas\"]) {\n // No explicit --extended-betas AND no --stealth → default ON.\n parsed.extendedBetas = true\n }\n // If user passed --extended-betas explicitly, parsed already reflects it.\n\n // Phase H P2: Claude Code version check + opt-in auto-update.\n // Default: check + auto-install if newer version available\n // (throttled to once per hour). The user explicitly chose to install\n // a Claude Code wrapper — they want the latest features and bug\n // fixes. Opt-out via --no-auto-update (check only, warn) or\n // --no-update-check (silence entirely). Best-effort: skips silently\n // if npm is offline or claude is not on PATH. The check happens\n // BEFORE setupAndServe so a stale version doesn't get spawned.\n if (args[\"update-check\"] !== false) {\n try {\n const versionCheck = await checkClaudeVersion({\n noCheck: false,\n })\n if (versionCheck.skipped && versionCheck.skipReason === \"no-claude\") {\n // Claude isn't on PATH — let launchChild surface the more\n // contextual \"claude not found\" error in the spawn step.\n consola.debug(\n \"claude --version probe failed; skipping auto-update.\",\n )\n } else if (versionCheck.skipped && versionCheck.skipReason === \"no-npm\") {\n // npm view failed — likely offline. Don't block launch.\n consola.debug(\n \"npm view @anthropic-ai/claude-code failed; skipping auto-update check (likely offline).\",\n )\n } else if (\n versionCheck.needsUpdate\n && versionCheck.installedVersion\n && versionCheck.latestVersion\n ) {\n if (args[\"auto-update\"] !== false) {\n try {\n await autoUpdateClaude(versionCheck.latestVersion)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.warn(\n `Auto-update of Claude Code from ${versionCheck.installedVersion} to ${versionCheck.latestVersion} failed (${msg}); continuing with installed version. Run \\`npm install -g @anthropic-ai/claude-code@latest\\` manually to retry.`,\n )\n }\n } else {\n consola.warn(\n `Claude Code v${versionCheck.installedVersion} is installed; v${versionCheck.latestVersion} is available. Run with --auto-update (the default) to install on launch, or \\`npm install -g @anthropic-ai/claude-code@latest\\` manually.`,\n )\n }\n }\n } catch (err) {\n // Whole version-check should never block launch.\n consola.debug(\"Claude version check failed:\", err)\n }\n }\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n // Provision the router-owned CLAUDE_CONFIG_DIR with our synthetic\n // .credentials.json + a snapshot copy of the user's ~/.claude/.\n // The spawned Claude Code (and any teammates it spawns via the\n // agent-teams primitive) reads this dir instead of ~/.claude/,\n // finds our synthetic credential, and authenticates — closing the\n // teammate-spawn allowlist gap that drops ANTHROPIC_AUTH_TOKEN.\n // See ensureClaudeConfigMirror in src/lib/paths.ts.\n //\n // Run BEFORE enableFileLogging so a fatal credentials-write failure\n // surfaces on the user's terminal (we only throw on the credentials\n // write — copy failures of individual user files are debug-logged\n // and skipped).\n try {\n await ensureClaudeConfigMirror()\n } catch (err) {\n consola.error(\n `Failed to provision CLAUDE_CONFIG_DIR mirror: ${\n err instanceof Error ? err.message : String(err)\n }. Spawned Claude Code would not be able to authenticate.`,\n )\n process.exit(1)\n }\n\n enableFileLogging() // redirect errors/warnings to file; suppress terminal output\n\n // Two slugs flow through this code:\n // * `chosenSlug` — the value we set for `ANTHROPIC_MODEL`. Must be an\n // Anthropic-published slug (e.g. `claude-opus-4-7`) so Claude Code's\n // hardcoded `/model` registry matches it and the UI shows the right\n // menu entry. The proxy's resolver translates this back to a Copilot\n // slug at request time, so the actual upstream call still works.\n // * `resolvedSlug` — the Copilot-side slug after `resolveModel`. Used\n // only for cache-presence validation (fallback chain) and the\n // launch banner.\n //\n // For the implicit-default path only, we walk\n // DEFAULT_CLAUDE_MODEL_FALLBACKS when neither the default nor any\n // earlier fallback resolves to a model present in the Copilot cache.\n // Explicit `--model` is respected as-is — including Copilot slugs\n // (which Claude Code's UI won't recognize, but power users may want\n // for explicit pinning).\n const usingDefault = !args.model\n const requestedSlug = args.model ?? pickClaudeDefault()\n let chosenSlug = requestedSlug\n let resolvedSlug = resolveModel(chosenSlug)\n\n if (usingDefault && state.models) {\n const inCache = (slug: string) =>\n state.models?.data.some((m) => m.id === resolveModel(slug)) ?? false\n if (!inCache(chosenSlug)) {\n for (const fallback of DEFAULT_CLAUDE_MODEL_FALLBACKS) {\n if (inCache(fallback)) {\n consola.info(\n `Default model \"${chosenSlug}\" not in your Copilot model list; falling back to \"${fallback}\".`,\n )\n chosenSlug = fallback\n resolvedSlug = resolveModel(fallback)\n break\n }\n }\n }\n }\n\n if (resolvedSlug !== chosenSlug) {\n consola.info(`Model \"${chosenSlug}\" resolved to \"${resolvedSlug}\"`)\n }\n const modelEntry = state.models?.data.find((m) => m.id === resolvedSlug)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/v1/messages\")\n consola.warn(\n `Model \"${resolvedSlug}\" not found. Available claude models: ${available.join(\", \")}`,\n )\n }\n\n // Banner shows the round-trip so the user sees both names. Claude Code's\n // UI will display the chosenSlug; Copilot upstream sees resolvedSlug.\n const banner =\n chosenSlug === resolvedSlug\n ? chosenSlug\n : `${chosenSlug} → ${resolvedSlug}`\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Claude Code (${banner})...\\n`)\n\n const envVars = getClaudeCodeEnvVars(serverUrl, chosenSlug)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n // Peer-MCP wiring. Default-on. When enabled:\n // 1. Decide between HTTP backend (always works, read-only personas)\n // and the `--codex-cli` stdio backend (requires codex 0.129+,\n // adds the implementer persona).\n // 2. Probe the live Copilot catalog for gemini-3.1-pro-preview.\n // 3. Generate a per-launch nonce, write the MCP config tempfile\n // under PATHS.CLAUDE_RUNTIME_DIR with mode 0o600, AND write\n // one .md subagent file per peer agent into ~/.claude/agents/\n // (Phase 2.5 — `--agents` JSON does NOT populate Claude Code's\n // Task subagent_type enum on v2.1.138; .md files in the canonical\n // agents dir do).\n // 4. Inject `--mcp-config <path>` into the spawned Claude Code's\n // argv. Add `--strict-mcp-config` if the user explicitly opts\n // out of their existing MCP servers. The `--agents` JSON path\n // is intentionally NOT passed: the .md registration in the\n // canonical agents dir is the authoritative surface.\n // 5. Plumb `cleanup()` into launchChild's onShutdown so tempfiles\n // are unlinked on signal exit.\n //\n // The per-launch CLAUDE_CONFIG_DIR mirror is ALWAYS cleaned up on\n // shutdown (regardless of codex-mcp), since `ensureClaudeConfigMirror`\n // above always provisioned it. We chain the peer-MCP cleanup\n // (if any) ahead of the mirror removal so files inside the mirror\n // get unlinked first via known paths; the recursive `fs.rm` is\n // belt-and-braces for everything else.\n const baseShutdown = async (): Promise<void> => {\n await removeOwnClaudeConfigMirror()\n }\n let onShutdown: () => Promise<void> = baseShutdown\n const codexMcpEnabled = (args as Record<string, unknown>)[\"codex-mcp\"] !== false\n if (codexMcpEnabled) {\n try {\n const requestedCli =\n ((args as Record<string, unknown>)[\"codex-cli\"] as boolean | undefined) ?? false\n const backend = resolveCodexCliBackend({\n requested: requestedCli,\n codexInfo: requestedCli ? getCodexVersion() : null,\n })\n const geminiAvailable =\n state.models?.data.some((m) => /^gemini-3\\..*pro/i.test(m.id)) ?? false\n if (!geminiAvailable) {\n consola.info(\n \"gemini-3.1-pro-preview not found in your Copilot model catalog; gemini-critic persona will not be registered.\",\n )\n }\n\n const runtime = await writePeerMcpRuntimeFiles(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable,\n })\n state.peerMcpNonce = runtime.nonce\n onShutdown = async (): Promise<void> => {\n await runtime.cleanup()\n await baseShutdown()\n }\n\n // Subagent MCP visibility: inject `gh-router-peers` (and the\n // `codex-cli` stdio entry when enabled) into the mirrored\n // `<CLAUDE_CONFIG_DIR>/.claude.json` so subagents — Agent-tool\n // subagents, forks, agent-teams subprocesses — discover the peer\n // MCP from persistent (user-scope) config rather than the parent's\n // ephemeral --mcp-config CLI flag. Same nonce as runtime files\n // (the proxy validates Authorization against the launch nonce\n // regardless of which channel the request came through).\n //\n // On collision with a user-side entry of the same name, this\n // returns ok:false; we then keep --mcp-config as the fallback so\n // at least the parent session retains the peer tools (subagents\n // remain blind in that case, by design — explicit branch, not\n // silent precedence).\n const injected = await injectPeerMcpIntoMirror(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable,\n nonce: runtime.nonce,\n })\n\n // Channel selection: prefer the mirror (subagent-visible) when\n // injection succeeded. Only fall back to --mcp-config when\n // injection refused due to a user-side collision. Pushing BOTH\n // would register the same server name twice (mirror + CLI flag),\n // which is ambiguous across Claude Code versions.\n if (!injected.ok) {\n extraArgs.push(\"--mcp-config\", runtime.mcpConfigPath)\n if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n extraArgs.push(\"--strict-mcp-config\")\n }\n } else if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n // User asked for strict-MCP-only but the mirror inject path\n // can't enforce that (other user-scope MCPs already in the\n // mirror's snapshot are visible). Warn so the flag's mismatch\n // with the new behavior is obvious.\n consola.warn(\n \"--codex-mcp-only has no effect when peer MCP is wired via the \"\n + \"mirrored .claude.json (the user's existing user-scope MCPs in \"\n + \"the snapshot are still visible). Pass --no-codex-mcp to skip \"\n + \"peer-MCP wiring entirely.\",\n )\n }\n\n const personaNames = runtime.personas.map((p) => p.agentName).join(\", \")\n const subagentVisibility = injected.ok\n ? `subagent-visible (mirrored mcpServers: [${injected.serversAdded.join(\", \")}])`\n : `subagent-INVISIBLE (collision on user-side mcpServers: [${injected.conflictingServers.join(\", \")}]; parent-only via --mcp-config)`\n process.stderr.write(\n `Peer MCP wired (backend=${backend}, personas=[${personaNames}], `\n + `subagent .md files=${runtime.agentMdPaths.length}, ${subagentVisibility}).\\n`,\n )\n\n // Awareness snippet: append a short, non-prescriptive system-prompt\n // section telling Claude *what* peer-review tools exist and *when*\n // they tend to be useful — Claude decides *whether* to call them.\n // The auto-invocation triggers live in each MCP tool's own\n // `description` (the prescriptive layer); this snippet is the\n // awareness layer. Opt out with `GH_ROUTER_PEER_AWARENESS` set to\n // 0, false, no, off, or empty string (case-insensitive, trimmed)\n // — same surface as the CLAUDE_CODE_* opt-outs documented in\n // docs/claude-env-injection.md.\n const peerAwarenessOptOut = (\n process.env.GH_ROUTER_PEER_AWARENESS ?? \"1\"\n )\n .trim()\n .toLowerCase()\n const peerAwarenessDisabled =\n peerAwarenessOptOut === \"\"\n || peerAwarenessOptOut === \"0\"\n || peerAwarenessOptOut === \"false\"\n || peerAwarenessOptOut === \"off\"\n || peerAwarenessOptOut === \"no\"\n if (!peerAwarenessDisabled) {\n extraArgs.push(\n \"--append-system-prompt\",\n buildPeerAwarenessSnippet({\n codexCli: backend === \"cli\",\n geminiAvailable,\n }),\n )\n }\n } catch (err) {\n consola.warn(\n `Peer MCP wiring failed (claude will launch without it): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n launchChild(\n { kind: \"claude-code\", envVars, extraArgs, model: chosenSlug },\n server,\n { onShutdown },\n )\n },\n})\n","import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport {\n DEFAULT_CODEX_MODEL,\n DEFAULT_CODEX_MODEL_FALLBACKS,\n} from \"./lib/port\"\nimport {\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { state } from \"./lib/state\"\nimport { resolveCodexModel } from \"./lib/utils\"\n\nexport const codex = defineCommand({\n meta: {\n name: \"codex\",\n description: \"Start the proxy server and launch Codex CLI\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Codex CLI\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The codex subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n const usingDefault = !args.model\n const requestedModel = args.model ?? DEFAULT_CODEX_MODEL\n\n // Resolve model before printing success message (so we show the actual model)\n // but enable file logging first so resolution warnings go to file, not terminal\n enableFileLogging()\n\n let codexModel = resolveCodexModel(requestedModel)\n if (codexModel !== requestedModel) {\n consola.info(`Model \"${requestedModel}\" resolved to \"${codexModel}\"`)\n }\n\n // For the implicit-default path only, walk DEFAULT_CODEX_MODEL_FALLBACKS\n // when the default isn't in the resolved Copilot model list. Layered on\n // top of resolveCodexModel's \"best /responses model\" fallback — that\n // remains the final safety net when every named fallback misses.\n if (usingDefault && state.models) {\n const inCache = (id: string) =>\n state.models?.data.some((m) => m.id === id) ?? false\n if (!inCache(codexModel)) {\n for (const fallback of DEFAULT_CODEX_MODEL_FALLBACKS) {\n const resolved = resolveCodexModel(fallback)\n if (inCache(resolved)) {\n consola.info(\n `Default model \"${codexModel}\" not in your Copilot model list; falling back to \"${resolved}\".`,\n )\n codexModel = resolved\n break\n }\n }\n }\n }\n\n // Validate model exists in Copilot model list\n const modelEntry = state.models?.data.find((m) => m.id === codexModel)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/responses\")\n consola.warn(\n `Model \"${codexModel}\" not found. Available codex models: ${available.join(\", \")}`,\n )\n } else {\n const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens\n if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`)\n }\n\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\\n`)\n\n const envVars = getCodexEnvVars(serverUrl)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n launchChild(\n {\n kind: \"codex\",\n envVars,\n extraArgs,\n model: codexModel,\n serverUrl,\n },\n server,\n )\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { PATHS } from \"./lib/paths\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise<boolean> {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getDebugInfo(): Promise<DebugInfo> {\n const [version, tokenExists] = await Promise.all([\n getPackageVersion(),\n checkTokenExists(),\n ])\n\n return {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n consola.info(`github-router debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise<void> {\n const debugInfo = await getDebugInfo()\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({\n json: args.json,\n })\n },\n})\n","import process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, env } = process\n\n if (platform === \"win32\") {\n // Git Bash / MSYS2 / Cygwin set SHELL even on Windows\n if (env.SHELL) {\n if (env.SHELL.endsWith(\"zsh\")) return \"zsh\"\n if (env.SHELL.endsWith(\"fish\")) return \"fish\"\n if (env.SHELL.endsWith(\"bash\")) return \"bash\"\n return \"sh\"\n }\n\n // Windows PowerShell 5.x sets this\n if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return \"powershell\"\n\n // PowerShell (both 5.x and 7+/pwsh) adds user-scoped module paths\n // at runtime. The system-level PSModulePath in CMD lacks these paths.\n if (env.PSModulePath) {\n const lower = env.PSModulePath.toLowerCase()\n if (\n lower.includes(\"documents\\\\powershell\")\n || lower.includes(\"documents\\\\windowspowershell\")\n ) {\n return \"powershell\"\n }\n }\n\n return \"cmd\"\n }\n\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n}\n\nfunction quotePosixValue(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction quotePowerShellValue(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set \"${key}=${value}\"`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${quotePosixValue(value)}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator =\n shell === \"cmd\" ? \" & \" : shell === \"powershell\" ? \"; \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\n\nimport { generateEnvScript } from \"./lib/shell\"\nimport { DEFAULT_CODEX_MODEL, DEFAULT_PORT } from \"./lib/port\"\nimport {\n getClaudeCodeEnvVars,\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\n\nfunction printAndCopyCommand(command: string, label: string): void {\n consola.box(`${label}\\n\\n${command}`)\n try {\n clipboard.writeSync(command)\n consola.success(`Copied ${label} command to clipboard!`)\n } catch {\n consola.warn(\"Failed to copy to clipboard. Copy the command above manually.\")\n }\n}\n\nfunction generateClaudeCodeCommand(serverUrl: string, model?: string) {\n const envVars = getClaudeCodeEnvVars(serverUrl, model)\n const command = generateEnvScript(envVars, \"claude --dangerously-skip-permissions\")\n printAndCopyCommand(command, \"Claude Code\")\n}\n\nfunction generateCodexCommand(serverUrl: string, model?: string) {\n const codexModel = model ?? DEFAULT_CODEX_MODEL\n const envVars = getCodexEnvVars(serverUrl)\n const command = generateEnvScript(envVars, `codex -m ${codexModel}`)\n printAndCopyCommand(command, \"Codex CLI\")\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the github-router server\",\n },\n args: {\n ...sharedServerArgs,\n cc: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n cx: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Codex CLI with Copilot API config\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model (used with --cc or --cx)\",\n },\n },\n async run({ args }) {\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n const { serverUrl } = await setupAndServe({\n ...parsed,\n port: parsed.port ?? DEFAULT_PORT,\n silent: false,\n })\n\n if (args.cc) generateClaudeCodeCommand(serverUrl, args.model)\n if (args.cx) generateCodexCommand(serverUrl, args.model)\n\n consola.box(\n `🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`,\n )\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand, runMain } from \"citty\"\nimport consola from \"consola\"\n\nimport { auth } from \"./auth\"\nimport { checkUsage } from \"./check-usage\"\nimport { claude } from \"./claude\"\nimport { codex } from \"./codex\"\nimport { debug } from \"./debug\"\nimport { start } from \"./start\"\n\nprocess.on(\"unhandledRejection\", (error) => {\n consola.error(\"Unhandled rejection:\", error)\n})\n\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nconst main = defineCommand({\n meta: {\n name: \"github-router\",\n description:\n \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n },\n subCommands: { auth, start, claude, codex, \"check-usage\": checkUsage, debug },\n})\n\nawait runMain(main)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,SAAS,SAAiB;AACxB,QAAO,KAAK,KAAK,GAAG,SAAS,EAAE,UAAU,SAAS,gBAAgB;;AAGpE,MAAa,QAAQ;CACnB,IAAI,UAAU;AACZ,SAAO,QAAQ;;CAEjB,IAAI,oBAAoB;AACtB,SAAO,KAAK,KAAK,QAAQ,EAAE,eAAe;;CAE5C,IAAI,iBAAiB;AACnB,SAAO,KAAK,KAAK,QAAQ,EAAE,YAAY;;CAOzC,IAAI,aAAa;AACf,SAAO,KAAK,KAAK,QAAQ,EAAE,iBAAiB;;CAS9C,IAAI,qBAAqB;AACvB,SAAO,KAAK,KAAK,QAAQ,EAAE,UAAU;;CAwBvC,IAAI,oBAAoB;AACtB,SAAO,KAAK,KAAK,QAAQ,EAAE,iBAAiB,uBAAuB,CAAC;;CAEvE;;;;;;;;;;;;;;;;;AAkBD,IAAIA;AACJ,SAAS,wBAAgC;AACvC,KAAI,2BAA2B,OAC7B,0BAAyB,GAAG,QAAQ,IAAI,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM;AAE3E,QAAO;;AAGT,eAAsB,cAA6B;AACjD,OAAM,GAAG,MAAM,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAClD,OAAM,GAAG,MAAM,MAAM,YAAY,EAAE,WAAW,MAAM,CAAC;AACrD,OAAM,GAAG,MAAM,MAAM,oBAAoB,EAAE,WAAW,MAAM,CAAC;AAG7D,OAAM,gBAAgB,MAAM,oBAAoB,IAAM;AACtD,OAAM,WAAW,MAAM,kBAAkB;AACzC,OAAM,wBAAwB,CAAC,OAAO,QAAQ;AAC5C,UAAQ,MAAM,0BAA0B,IAAI;GAC5C;AAKF,OAAM,+BAA+B,CAAC,OAAO,QAAQ;AACnD,UAAQ,MAAM,2CAA2C,IAAI;GAC7D;AAMF,OAAM,4BAA4B,CAAC,OAAO,QAAQ;AAChD,UAAQ,MAAM,iCAAiC,IAAI;GACnD;;AA4CJ,MAAMC,qBAAwD,IAAI,IAGhE;CAEA,CAAC,qBAAqB,WAAW;CACjC,CAAC,0BAA0B,WAAW;CACtC,CAAC,uBAAuB,WAAW;CAMnC,CAAC,0BAA0B,WAAW;CACtC,CAAC,WAAW,WAAW;CACvB,CAAC,SAAS,WAAW;CACrB,CAAC,QAAQ,WAAW;CACpB,CAAC,eAAe,WAAW;CAC3B,CAAC,QAAQ,WAAW;CACpB,CAAC,UAAU,WAAW;CACtB,CAAC,cAAc,WAAW;CAE1B,CAAC,YAAY,SAAS;CACtB,CAAC,YAAY,SAAS;CACtB,CAAC,SAAS,SAAS;CACnB,CAAC,SAAS,SAAS;CACnB,CAAC,eAAe,SAAS;CACzB,CAAC,mBAAmB,SAAS;CAI7B,CAAC,mBAAmB,SAAS;CAC7B,CAAC,SAAS,SAAS;CACnB,CAAC,gBAAgB,SAAS;CAC1B,CAAC,WAAW,SAAS;CACtB,CAAC;AAEF,SAAS,UAAU,QAA4B;AAC7C,QAAO,mBAAmB,IAAIC,OAAK,IAAI;;;;;;AAezC,MAAMC,wBAA+C,MAAM,KACzD,mBAAmB,SAAS,CAC7B,CACE,QAAQ,GAAG,UAAU,SAAS,SAAS,CACvC,KAAK,CAACD,YAAUA,OAAK;;;;;;;AAQxB,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;AA0BhC,MAAM,uBAAuB,EAC3B,eAAe;CACb,aAAa;CACb,cAAc;CACd,WAAW;CACX,QAAQ,CAAC,kBAAkB,eAAe;CAC1C,kBAAkB;CAClB,eAAe;CACf,UAAU;CACX,EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCD,eAAsB,yBAAyB,OAE3C,EAAE,EAAiB;CACrB,MAAM,WAAW,KAAK,YAAY,GAAG,SAAS;CAC9C,MAAM,YAAY,KAAK,KAAK,UAAU,UAAU;CAChD,MAAM,YAAY,MAAM;AAGxB,OAAM,GAAG,MAAM,WAAW;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAC3D,OAAM,gBAAgB,WAAW,IAAM;CAKvC,IAAI,eAAe;AACnB,KAAI;AAEF,kBADmB,MAAM,GAAG,KAAK,UAAU,EACjB,aAAa;UAChC,KAAK;AACZ,MAAK,IAA8B,SAAS,SAC1C,SAAQ,MAAM,yCAAyC,UAAU,IAAI,IAAI;;AAG7E,KAAI,aACF,OAAM,mBAAmB,WAAW,WAAW,GAAG;AASpD,OAAM,GAAG,MAAM,KAAK,KAAK,WAAW,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AAInE,MAAK,MAAMA,UAAQ,sBACjB,OAAM,oBAAoBA,QAAM,WAAW,UAAU,CAAC,OAAO,QAAQ;AACnE,UAAQ,MACN,gDAAgDA,OAAK,YACrD,IACD;GACD;CAIJ,MAAM,kBAAkB,KAAK,KAAK,WAAW,oBAAoB;CACjE,MAAM,cAAc,KAAK,UAAU,sBAAsB,MAAM,EAAE;CACjE,IAAI,aAAa;AACjB,KAAI;AAEF,gBADiB,MAAM,GAAG,SAAS,iBAAiB,OAAO,EACrC,MAAM,KAAK,YAAY,MAAM;UAC5C,KAAK;AACZ,MAAK,IAA8B,SAAS,SAC1C,SAAQ,MAAM,+DAA+D,IAAI;;AAGrF,KAAI,YAAY;EAId,MAAM,WAAW,GAAG,gBAAgB,GAAG,QAAQ,IAAI;AACnD,MAAI;AACF,SAAM,GAAG,UAAU,UAAU,cAAc,MAAM;IAAE,MAAM;IAAO,MAAM;IAAM,CAAC;AAC7E,SAAM,GAAG,OAAO,UAAU,gBAAgB;WACnC,KAAK;AAIZ,OAAK,IAA8B,SAAS,SAC1C,SAAQ,MACN,4EACD;QACI;AACL,UAAM,GAAG,OAAO,SAAS,CAAC,YAAY,GAAG;AACzC,UAAM;;;;AAIZ,OAAM,gBAAgB,iBAAiB,IAAM;CAQ7C,MAAM,aAAa,KAAK,KAAK,WAAW,wBAAwB;CAChE,IAAI,eAAe;AACnB,KAAI;EACF,MAAM,aAAa,MAAM,GAAG,MAAM,WAAW;AAC7C,MAAI,WAAW,QAAQ,CACrB,gBAAe;OACV;AAGL,WAAQ,KACN,6BAA6B,WAAW,0CAA0C,WAAW,KAAK,SAAS,EAAE,CAAC,gEAC/G;AACD,kBAAe;;UAEV,KAAK;AACZ,MAAK,IAA8B,SAAS,UAAU;AACpD,WAAQ,MAAM,kDAAkD,IAAI;AACpE,kBAAe;;;AAGnB,KAAI,CAAC,cAAc;EACjB,MAAM,OAAO,sDAAqC,IAAI,MAAM,EAAC,aAAa,CAAC;AAI3E,QAAM,GACH,UAAU,YAAY,MAAM;GAAE,MAAM;GAAO,MAAM;GAAM,CAAC,CACxD,OAAO,QAAQ;AACd,WAAQ,MAAM,mDAAmD,IAAI;IACrE;;;;;;;;;;;;;;;AAgBR,eAAe,mBACb,WACA,WACA,SACe;CACf,MAAM,aAAa,KAAK,KAAK,WAAW,QAAQ;CAChD,IAAIE;AACJ,KAAI;AACF,YAAU,MAAM,GAAG,QAAQ,WAAW;UAC/B,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU;AACtD,UAAQ,MAAM,sCAAsC,WAAW,IAAI,IAAI;AACvE;;AAEF,MAAK,MAAMF,UAAQ,SAAS;AAG1B,MAAI,YAAY,IAAI;GAClB,MAAM,SAAS,UAAUA,OAAK;AAC9B,OAAI,WAAW,cAAc,WAAW,SAAU;;EAEpD,MAAM,WAAW,YAAY,KAAKA,SAAO,KAAK,KAAK,SAASA,OAAK;EACjE,MAAM,cAAc,KAAK,KAAK,WAAW,SAAS;EAClD,MAAM,cAAc,KAAK,KAAK,WAAW,SAAS;EAClD,IAAIG;AACJ,MAAI;AACF,WAAQ,MAAM,GAAG,MAAM,YAAY;WAC5B,KAAK;AACZ,WAAQ,MAAM,oCAAoC,YAAY,IAAI,IAAI;AACtE;;AAEF,MAAI,MAAM,gBAAgB,EAAE;AAe1B,WAAQ,MAAM,wCAAwC,YAAY,oBAAoB;AACtF;;AAEF,MAAI,MAAM,aAAa,EAAE;AACvB,SAAM,GAAG,MAAM,aAAa,EAAE,WAAW,MAAM,CAAC;AAChD,SAAM,mBAAmB,WAAW,WAAW,SAAS;AACxD;;AAEF,MAAI,MAAM,QAAQ,EAAE;GAElB,IAAI,YAAY;AAChB,OAAI;IACF,MAAM,aAAa,MAAM,GAAG,MAAM,YAAY;AAC9C,QAAI,WAAW,QAAQ,IAAI,WAAW,WAAW,MAAM,QACrD,aAAY;YAEP,KAAK;AACZ,QAAK,IAA8B,SAAS,SAC1C,SAAQ,MAAM,oCAAoC,YAAY,IAAI,IAAI;;AAG1E,OAAI,CAAC,UAAW;AAChB,OAAI;AACF,UAAM,GAAG,SAAS,aAAa,aAAa,GAAG,UAAU,iBAAiB;YACnE,KAAK;AACZ,YAAQ,MAAM,4BAA4B,YAAY,KAAK,YAAY,IAAI,IAAI;;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCN,eAAe,oBACb,QACA,WACA,WACe;CACf,MAAM,aAAa,KAAK,KAAK,WAAWH,OAAK;CAC7C,MAAM,aAAa,KAAK,KAAK,WAAWA,OAAK;AAK7C,KAAI;AACF,QAAM,GAAG,MAAM,YAAY,EAAE,WAAW,MAAM,CAAC;UACxC,KAAK;AAWZ,UAAQ,KACN,uBAAuBA,OAAK,yBAAyB,WAAW,IAChE,IACD;AACD;;CAIF,IAAII,WAAwD;AAC5D,KAAI;AACF,aAAW,MAAM,GAAG,MAAM,WAAW;UAC9B,KAAK;AACZ,MAAK,IAA8B,SAAS,UAAU;AAUpD,WAAQ,KACN,uBAAuBJ,OAAK,kBAAkB,WAAW,IACzD,IACD;AACD;;;AAIJ,KAAI,UAAU,gBAAgB,EAAE;EA0B9B,MAAM,aAAa,MAAM,GAAG,SAAS,WAAW,CAAC,YAAY,KAAK;AAClE,MAAI,eAAe,MAAM;AACvB,WAAQ,KACN,uBAAuBA,OAAK,2BAA2B,WAAW,8IAGnE;AACD;;EAEF,MAAM,cAAc,MAAM,GAAG,SAAS,WAAW,CAAC,YAAY,KAAK;AACnE,MAAI,gBAAgB,QAAQ,gBAAgB,WAC1C;YAIO,UAAU,aAAa,CAMhC,KAAI;AACF,QAAM,GAAG,MAAM,WAAW;UAEnB,KAAK;AACZ,UAAQ,KACN,6BAA6B,WAAW,oIAEMA,OAAK,4BAChC,WAAW,gBAAgB,WAAW,yFAErC,IAA8B,QAAQ,UAAU,GACrE;AACD;;UAEO,UAAU;AAEnB,UAAQ,KACN,6BAA6B,WAAW,yJAGzC;AACD;;CAaF,MAAM,WAAW,GAAG,WAAW,OAAO,QAAQ,IAAI,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM;AACnF,KAAI;AACF,QAAM,GAAG,QACP,YACA,UACA,QAAQ,aAAa,UAAU,aAAa,MAC7C;UACM,KAAK;AAOZ,UAAQ,KACN,uBAAuBA,OAAK,aAAa,SAAS,WAClD,IACD;AACD;;AAEF,KAAI,QAAQ,aAAa,WAAW,UAAU,gBAAgB,CAM5D,OAAM,GAAG,OAAO,WAAW,CAAC,YAAY,GAAG;AAE7C,KAAI;AACF,QAAM,GAAG,OAAO,UAAU,WAAW;UAC9B,KAAK;AAMZ,UAAQ,KACN,uBAAuBA,OAAK,YAAY,SAAS,KAAK,WAAW,WACjE,IACD;AACD,QAAM,GAAG,OAAO,SAAS,CAAC,YAAY,GAAG;;;AAI7C,eAAe,WAAW,UAAiC;AACzD,KAAI;AACF,QAAM,GAAG,OAAO,UAAU,GAAG,UAAU,KAAK;SACtC;AACN,QAAM,GAAG,UAAU,UAAU,GAAG;AAChC,QAAM,GAAG,MAAM,UAAU,IAAM;;;AAInC,eAAe,gBAAgB,QAAgB,MAA6B;AAC1E,KAAI,QAAQ,aAAa,QAAS;AAClC,KAAI;AACF,QAAM,GAAG,MAAM,QAAQ,KAAK;UACrB,KAAK;AACZ,UAAQ,MAAM,SAAS,OAAO,GAAG,KAAK,SAAS,EAAE,CAAC,WAAW,IAAI;;;;;;;;;;;;;;;;;;AAmBrE,eAAsB,uBACpB,UACA,SACe;AACf,OAAM,GAAG,UAAU,UAAU,SAAS;EAAE,MAAM;EAAO,MAAM;EAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;AAuBpE,eAAsB,yBAAwC;CAC5D,MAAM,MAAM,MAAM;CAClB,IAAIE;AACJ,KAAI;AACF,YAAU,MAAM,GAAG,QAAQ,IAAI;UACxB,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU;AACtD,QAAM;;AAGR,MAAK,MAAMF,UAAQ,SAAS;EAG1B,MAAM,QAAQ,mDAAmD,KAAKA,OAAK;AAC3E,MAAI,CAAC,MAAO;EACZ,MAAM,MAAM,OAAO,SAAS,MAAM,IAAI,GAAG;EACzC,MAAM,WAAW,KAAK,KAAK,KAAKA,OAAK;AAErC,MAAI,WAAW,IAAI,CAAE;AAErB,QAAM,GAAG,OAAO,SAAS,CAAC,YAAY,GAEpC;;;AAIN,SAAS,WAAW,KAAsB;AACxC,KAAI,CAAC,OAAO,UAAU,IAAI,IAAI,OAAO,EAAG,QAAO;AAC/C,KAAI;AAIF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;UACA,KAAK;AAEZ,MADc,IAA8B,SAC/B,QAAS,QAAO;AAC7B,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BX,eAAsB,6BAA4C;CAChE,MAAM,MAAM,KAAK,KAAK,MAAM,mBAAmB,SAAS;CACxD,IAAIE;AACJ,KAAI;AACF,YAAU,MAAM,GAAG,QAAQ,IAAI;UACxB,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU;AACtD,QAAM;;AAER,MAAK,MAAMF,UAAQ,SAAS;EAC1B,MAAM,QAAQ,uBAAuB,KAAKA,OAAK;AAC/C,MAAI,CAAC,MAAO;AAEZ,MAAI,WADQ,OAAO,SAAS,MAAM,IAAI,GAAG,CACtB,CAAE;AACrB,QAAM,GAAG,OAAO,KAAK,KAAK,KAAKA,OAAK,CAAC,CAAC,YAAY,GAEhD;;;;;;;;;;;AAYN,MAAM,yBACJ;;;;;;;;;AAUF,MAAM,2BAA2B;;;;;;;;;;;;;;;;;AAkBjC,eAAsB,gCAA+C;CACnE,MAAM,SAAS,KAAK,KAAK,QAAQ,EAAE,gBAAgB;CACnD,IAAIE;AACJ,KAAI;AACF,YAAU,MAAM,GAAG,QAAQ,OAAO;UAC3B,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU;AACtD,QAAM;;AAER,MAAK,MAAMF,UAAQ,SAAS;EAC1B,MAAM,QAAQ,yBAAyB,KAAKA,OAAK;AACjD,MAAI,CAAC,MAAO;AAEZ,MAAI,WADQ,OAAO,SAAS,MAAM,IAAI,GAAG,CACtB,CAAE;AACrB,QAAM,GACH,GAAG,KAAK,KAAK,QAAQA,OAAK,EAAE;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC,CAC7D,OAAO,QAAQ;AAMd,WAAQ,MACN,4CAA4CA,OAAK,IACjD,IACD;IACD;;;;;;;;;;;;;;;AAgBR,eAAsB,8BAA6C;CACjE,MAAM,MAAM,MAAM;AAClB,OAAM,GAAG,GAAG,KAAK;EAAE,WAAW;EAAM,OAAO;EAAM,CAAC,CAAC,OAAO,QAAQ;AAChE,UAAQ,MAAM,mCAAmC,IAAI,YAAY,IAAI;GACrE;;;;;ACt4BJ,MAAaK,QAAe;CAC1B,aAAa;CACb,eAAe;CACf,eAAe;CACf,WAAW;CACX,eAAe;CACf,WAAW,YAAY;CACvB,WAAW,YAAY,GAAG,CAAC,SAAS,MAAM;CAC3C;;;;AC3CD,MAAa,yBAAyB;CACpC,gBAAgB;CAChB,QAAQ;CACT;AAED,MAAM,0BAA0B;AAEhC,SAAgB,eAAe,SAAsB;AACnD,QAAOC,QAAM,kBAAkB;;AAGjC,MAAM,cAAc;AAEpB,MAAa,kBAAkB,YAC7BA,QAAM,iBAAiB;AACzB,MAAa,kBACX,SACA,SAAkB,OAClB,gBAAwB,kBACrB;CACH,MAAMC,YAAU,eAAeD,QAAM;CACrC,MAAME,UAAkC;EACtC,eAAe,UAAUF,QAAM;EAC/B,gBAAgB,iBAAiB,CAAC;EAClC,0BAA0B;EAC1B,kBAAkB,UAAUA,QAAM;EAClC,yBAAyB,gBAAgBC;EACzC,cAAc,qBAAqBA;EACnC,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,gBAAgB,YAAY;EAC5B,uCAAuC;EACvC,oBAAoBD,QAAM;EAC1B,oBAAoBA,QAAM;EAC3B;AAED,KAAI,OAAQ,SAAQ,4BAA4B;AAEhD,QAAO;;AAGT,MAAa,sBACX,QAAQ,IAAI,kBAAkB;AAChC,MAAa,iBAAiB,aAAkB;CAC9C,GAAG,iBAAiB;CACpB,eAAe,SAASA,QAAM;CAC9B,kBAAkB,UAAUA,QAAM;CAClC,yBAAyB,gBAAgB,eAAeA,QAAM;CAC9D,cAAc,qBAAqB,eAAeA,QAAM;CACxD,wBAAwB;CACxB,uCAAuC;CACxC;AAED,MAAa,kBAAkB;AAC/B,MAAa,mBAAmB;AAChC,MAAa,oBAAoB,CAAC,YAAY,CAAC,KAAK,IAAI;;;;ACvDxD,IAAa,YAAb,cAA+B,MAAM;CACnC;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,WAAW;;;AAIpB,eAAsB,aAAa,GAAY,OAAgB;AAC7D,SAAQ,MAAM,qBAAqB,EAAE,IAAI,KAAK,IAAI,MAAM;AAExD,KAAI,iBAAiB,WAAW;EAC9B,MAAM,YAAY,MAAM,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;EAC7D,IAAIG;AACJ,MAAI;AACF,eAAY,KAAK,MAAM,UAAU;UAC3B;AACN,eAAY;;AASd,MAAI,kBAAkB,MAAM,SAAS,QAAQ,WAAW,UAAU,EAAE;GAClE,MAAM,WAAW,oBAAoB,WAAW,UAAU;AAC1D,WAAQ,MAAM,oCAAoC,aAAa,UAAU;AACzE,UAAO,EAAE,KACP;IACE,MAAM;IACN,OAAO;KACL,MAAM;KACN,SAAS,uBAAuB;KACjC;IACF,EACD,IACD;;EAcH,MAAM,iBACJ,MAAM,SAAS,WAAW,MAAM,MAAM,MAAM,SAAS;AAGvD,MAAI,iBAAiB,UAAU,EAAE;AAC/B,WAAQ,MAAM,eAAe,UAAU;AACvC,UAAO,EAAE,KAAK,WAAW,eAAuC;;EAGlE,MAAM,UAAU,oBAAoB,WAAW,UAAU;AACzD,UAAQ,MAAM,eAAe,aAAa,UAAU;AACpD,SAAO,EAAE,KACP;GACE,MAAM;GACN,OAAO;IACL,MAAM,iBAAiB,eAAe;IACtC;IACD;GACF,EACD,eACD;;AAGH,QAAO,EAAE,KACP;EACE,MAAM;EACN,OAAO;GACL,MAAM;GACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAChE;EACF,EACD,IACD;;AAIH,SAAS,oBAAoB,WAAoB,UAA0B;AACzE,KAAI,OAAO,cAAc,YAAY,cAAc,KAAM,QAAO;CAEhE,MAAM,cAAc;AACpB,KAAI,YAAY,YAAY,OAAW,QAAO,OAAO,YAAY,QAAQ;AAEzE,KAAI,OAAO,YAAY,UAAU,YAAY,YAAY,UAAU,MAAM;EACvE,MAAM,eAAe,YAAY;AACjC,MAAI,aAAa,YAAY,OAAW,QAAO,OAAO,aAAa,QAAQ;;AAG7E,QAAO;;;;;;AAOT,SAAS,iBAAiB,MAAwB;AAChD,KAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;CACtD,MAAM,SAAS;AACf,KAAI,OAAO,SAAS,QAAS,QAAO;AACpC,KAAI,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAM,QAAO;CACtE,MAAM,QAAQ,OAAO;AACrB,QAAO,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,YAAY;;AAGpE,MAAM,8BAA8B;CAClC;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;AAWD,SAAgB,kBACd,QACA,WACA,WACS;AACT,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;CAE3B,MAAM,YACJ,YACA,OACC,OAAO,cAAc,YAAY,cAAc,OAC5C,KAAK,UAAU,UAAU,GACzB,KACJ,aAAa;AAEf,QAAO,4BAA4B,MAAM,MAAM,SAAS,SAAS,EAAE,CAAC;;;;;;;;;;;AAYtE,SAAS,iBAAiB,QAAwB;AAChD,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,WAAW,IAAK,QAAO;AAC3B,QAAO;;;;;;;;;;;;AClKT,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACD;AAED,SAAS,qBAAqB,QAAyB;CACrD,IAAIC;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,OAAO;SAClB;AACN,SAAO;;AAET,KAAI,OAAO,aAAa,SAAU,QAAO;AACzC,QAAO,uBAAuB,SAAS,OAAO,SAAS;;AAGzD,MAAa,kBAAkB,YAAY;CACzC,MAAM,WAAW,MAAM,MACrB,GAAG,oBAAoB,6BACvB,EACE,SAAS,cAAc,MAAM,EAC9B,CACF;AAED,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,+BAA+B,SAAS;CAE9E,MAAM,OAAQ,MAAM,SAAS,MAAM;AAUnC,KAAI,KAAK,WAAW,IAClB,KAAI,qBAAqB,KAAK,UAAU,IAAI,CAC1C,OAAM,gBAAgB,KAAK,UAAU;KAErC,SAAQ,KACN,2CAA2C,KAAK,UAAU,IAAI,yDAE1D,uBAAuB,KAAK,KAAK,CAAC,QACrC,MAAM,gBACH,8BAA8B,MAAM,cAAc,MAClD,sDACL;AAIL,QAAO;;;;;AC1DT,eAAsB,gBAA6C;CACjE,MAAM,WAAW,MAAM,MAAM,GAAG,gBAAgB,qBAAqB;EACnE,QAAQ;EACR,SAAS,iBAAiB;EAC1B,MAAM,KAAK,UAAU;GACnB,WAAW;GACX,OAAO;GACR,CAAC;EACH,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,6BAA6B,SAAS;AAE5E,QAAQ,MAAM,SAAS,MAAM;;;;;AChB/B,eAAsB,gBAAgB;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAC1D,SAAS;EACP,eAAe,SAAS,MAAM;EAC9B,GAAG,iBAAiB;EACrB,EACF,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,6BAA6B,SAAS;AAE5E,QAAQ,MAAM,SAAS,MAAM;;;;;ACV/B,MAAa,YAAY,YAAY;CACnC,MAAM,WAAW,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC,UAAU,EAC9D,SAAS,eAAe,MAAM,EAC/B,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,wBAAwB,SAAS;AAEvE,QAAQ,MAAM,SAAS,MAAM;;;;;ACX/B,MAAMC,aAAW;AAUjB,eAAsB,wBAAyC;CAC7D,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,UAAU,iBAAiB;AAC/B,aAAW,OAAO;IACjB,IAAK;AAER,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,4EACA;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACT;GACD,MAAM,KAAK,UAAU;IACnB,SAAS,CACP,EACE,UAAU,CAAC;KAAE,YAAY;KAAG,OAAO;KAAuB,CAAC,EAC5D,CACF;IACD,OAAO;IACR,CAAC;GACF,QAAQ,WAAW;GACpB,CACF;AAED,MAAI,CAAC,SAAS,GAAI,QAAOA;AAMzB,UAJc,MAAM,SAAS,MAAM,GAE3B,UAAU,IAAI,aAAa,IAAI,WAAW,IAAI,WAEpCA;SACZ;AACN,SAAOA;WACC;AACR,eAAa,QAAQ;;;;;;AC/CzB,MAAM,WAAW;AAEjB,eAAsB,mBAAmB;CACvC,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,UAAU,iBAAiB;AAC/B,aAAW,OAAO;IACjB,IAAK;AAER,KAAI;EAUF,MAAM,SAFW,OAPA,MAAM,MACrB,kFACA,EACE,QAAQ,WAAW,QACpB,CACF,EAE+B,MAAM,EAEf,MADH,mBACqB;AAEzC,MAAI,MACF,QAAO,MAAM;AAGf,SAAO;SACD;AACN,SAAO;WACC;AACR,eAAa,QAAQ;;;AAIzB,MAAM,kBAAkB;;;;ACxBxB,MAAa,SAAS,OACpB,IAAI,SAAS,YAAY;AACvB,YAAW,SAAS,GAAG;EACvB;AAEJ,MAAa,aAAa,UACxB,UAAU,QAAQ,UAAU;;;;;AAM9B,MAAM,uBAAuB;CAC3B;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;;;AAyBD,MAAM,yBAAyB;CAC7B,GAAG;CACH;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAIA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;AAgBD,MAAM,oCAAoC,CACxC,gBACD;;;;;;;;AASD,SAAgB,iBAAiB,OAAmC;CAClE,MAAM,WAAW,MAAM,gBACnB,yBACA;AAWJ,QAViB,MACd,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QACE,MACC,KACG,SAAS,MAAM,WAAW,EAAE,WAAW,OAAO,CAAC,IAC/C,CAAC,kCAAkC,MAAM,MAAM,EAAE,WAAW,EAAE,CAAC,CACrE,CACA,KAAK,IAAI,IACO;;;;;;;AAQrB,SAAgB,iBAAiB,IAAoB;AACnD,QAAO,GACJ,aAAa,CACb,QAAQ,OAAO,IAAI,CACnB,QAAQ,gBAAgB,QAAQ,CAChC,QAAQ,UAAU,IAAI;;;;;;;;;;;;;;;;;;;AAoB3B,SAAgB,aAAa,SAAyB;CACpD,MAAM,SAAS,MAAM,QAAQ;AAC7B,KAAI,CAAC,OAAQ,QAAO;CAsBpB,MAAM,YAAY,QAAQ,MAAM,gBAAgB;AAChD,KAAI,WAAW;EACb,MAAM,WAAW,UAAU;EAC3B,MAAM,WAAW,aAAa,SAAS;AACvC,MAAI,CAAC,aAAa,KAAK,SAAS,CAC9B,SAAQ,KACN,UAAU,QAAQ,mHAAmH,SAAS,iMAC/I;AAEH,SAAO;;AAIT,KAAI,OAAO,MAAM,MAAM,EAAE,OAAO,QAAQ,CAAE,QAAO;CAGjD,MAAM,QAAQ,QAAQ,aAAa;CACnC,MAAM,UAAU,OAAO,MAAM,MAAM,EAAE,GAAG,aAAa,KAAK,MAAM;AAChE,KAAI,QAAS,QAAO,QAAQ;AAI5B,KAAI,MAAM,SAAS,OAAO,EAAE;EAO1B,MAAM,QAAQ,OAAO,QAClB,MAAM,EAAE,GAAG,SAAS,OAAO,IAAI,aAAa,KAAK,EAAE,GAAG,CACxD;EACD,MAAM,eAAe,MAAM,MAAM,sBAAsB;EACvD,MAAM,mBACJ,eAAe,GAAG,aAAa,GAAG,GAAG,aAAa,OAAO;EAI3D,MAAM,QAHY,mBACd,MAAM,MAAM,MAAM,EAAE,GAAG,SAAS,QAAQ,iBAAiB,GAAG,CAAC,GAC7D,WACsB,MAAM;AAChC,MAAI,KAAM,QAAO,KAAK;;AAGxB,KAAI,MAAM,SAAS,QAAQ,EAAE;EAC3B,MAAM,cAAc,OAAO,QACxB,MAAM,EAAE,GAAG,SAAS,QAAQ,IAAI,CAAC,EAAE,GAAG,SAAS,OAAO,CACxD;AACD,MAAI,YAAY,SAAS,GAAG;AAC1B,eAAY,MAAM,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,CAAC;AACpD,UAAO,YAAY,GAAG;;;CAK1B,MAAM,aAAa,iBAAiB,QAAQ;CAC5C,MAAM,YAAY,OAAO,MACtB,MAAM,iBAAiB,EAAE,GAAG,KAAK,WACnC;AACD,KAAI,UAAW,QAAO,UAAU;CAUhC,MAAM,eAAe,QAAQ,QAAQ,+BAA+B,KAAK;AACzE,KAAI,iBAAiB,SAAS;EAC5B,MAAM,UAAU,aAAa,aAAa;AAK1C,MADE,YAAY,gBAAgB,OAAO,MAAM,MAAM,EAAE,OAAO,aAAa,EACzD;AACZ,WAAQ,KACN,kCAAkC,QAAQ,OAAO,QAAQ,uEAC1D;AACD,UAAO;;;AAwBX,KAAI,MAAM,WAAW,UAAU,EAAE;EAC/B,MAAM,cAAc,uBAAuB,KAAK,MAAM;EACtD,MAAM,aAAa,sBAAsB,KAAK,MAAM;AACpD,MAAI,eAAe,YAAY;GAC7B,MAAM,SAAS,cAAc,WAAW;GACxC,MAAM,gBAAgB,OAAO,QAAQ,uBACnC,IAAI,OAAO,UAAU,OAAO,aAAa,EAAC,KAAK,EAAE,GAAG,CACrD;AACD,OAAI,cAAc,SAAS,GAAG;AAC5B,kBAAc,MAAM,GAAG,MACrB,EAAE,GAAG,cAAc,EAAE,IAAI,QAAW,EAAE,SAAS,MAAM,CAAC,CACvD;IACD,MAAM,OAAO,cAAc,GAAG;AAC9B,YAAQ,KACN,UAAU,QAAQ,+DAA+D,KAAK,YAAY,OAAO,8CAC1G;AACD,WAAO;;;;AAMb,SAAQ,KACN,UAAU,QAAQ,gDAAgD,OAAO,KAAK,MAAM,EAAE,GAAG,CAAC,KAAK,KAAK,GACrG;AACD,QAAO;;;;;;AAOT,SAAgB,kBAAkB,SAAyB;CACzD,MAAM,WAAW,aAAa,QAAQ;CACtC,MAAM,SAAS,MAAM,QAAQ;AAC7B,KAAI,CAAC,OAAQ,QAAO;AAGpB,KAAI,OAAO,MAAM,MAAM,EAAE,OAAO,SAAS,CAAE,QAAO;CAMlD,MAAM,aAAa,OAAO,QAAQ,MAAM;EACtC,MAAM,YAAY,EAAE,uBAAuB,EAAE;AAC7C,MAAI,EAAE,GAAG,SAAS,OAAO,IAAI,EAAE,GAAG,SAAS,OAAO,CAAE,QAAO;AAC3D,SAAO,UAAU,WAAW,KAAK,UAAU,SAAS,aAAa;GACjE;AAEF,KAAI,WAAW,SAAS,GAAG;AACzB,aAAW,MAAM,GAAG,MAAM;GACxB,MAAM,SAAS,EAAE,GAAG,SAAS,QAAQ,GAAG,IAAI;GAC5C,MAAM,SAAS,EAAE,GAAG,SAAS,QAAQ,GAAG,IAAI;AAC5C,OAAI,WAAW,OAAQ,QAAO,SAAS;AACvC,UAAO,EAAE,GAAG,cAAc,EAAE,GAAG;IAC/B;EACF,MAAM,OAAO,WAAW,GAAG;AAC3B,UAAQ,KAAK,UAAU,QAAQ,0BAA0B,KAAK,WAAW;AACzE,SAAO;;AAGT,QAAO;;AAGT,eAAsB,cAA6B;AAEjD,OAAM,SADS,MAAM,WAAW;;AAIlC,MAAa,qBAAqB,YAAY;CAC5C,MAAM,WAAW,MAAM,kBAAkB;AACzC,OAAM,gBAAgB;AAEtB,SAAQ,KAAK,yBAAyB,WAAW;;AAGnD,MAAa,sBAAsB,YAAY;CAC7C,MAAMC,YAAU,MAAM,uBAAuB;AAC7C,OAAM,iBAAiBA;AAEvB,SAAQ,KAAK,+BAA+BA,YAAU;;;;;ACzVxD,eAAsB,gBACpB,YACiB;CAGjB,MAAM,iBAAiB,WAAW,WAAW,KAAK;AAClD,SAAQ,MAAM,yCAAyC,cAAc,IAAI;CACzE,MAAM,YAAY,KAAK,KAAK,GAAG,WAAW,aAAa;AAEvD,QAAO,KAAK,KAAK,GAAG,WAAW;EAC7B,MAAM,WAAW,MAAM,MACrB,GAAG,gBAAgB,4BACnB;GACE,QAAQ;GACR,SAAS,iBAAiB;GAC1B,MAAM,KAAK,UAAU;IACnB,WAAW;IACX,aAAa,WAAW;IACxB,YAAY;IACb,CAAC;GACH,CACF;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,WAAQ,MAAM,gCAAgC,MAAM,SAAS,MAAM,CAAC;AACpE,OAAI,KAAK,KAAK,IAAI,UAAW;AAC7B,SAAM,MAAM,cAAc;AAC1B;;EAGF,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,UAAQ,MAAM,kCAAkC,KAAK;EAErD,MAAM,EAAE,iBAAiB;AAEzB,MAAI,aACF,QAAO;AAGT,MAAI,KAAK,KAAK,IAAI,UAAW;AAC7B,QAAM,MAAM,cAAc;;AAG5B,OAAM,IAAI,MAAM,8CAA8C;;;;;AC1ChE,MAAM,wBAAwB,GAAG,SAAS,MAAM,mBAAmB,OAAO;AAE1E,MAAM,oBAAoB,UACxB,GAAG,UAAU,MAAM,mBAAmB,MAAM;AAE9C,MAAa,oBAAoB,YAAY;CAC3C,MAAM,EAAE,OAAO,eAAe,MAAM,iBAAiB;AACrD,OAAM,eAAe;AAGrB,SAAQ,MAAM,6CAA6C;AAC3D,KAAI,MAAM,UACR,SAAQ,KAAK,kBAAkB,MAAM;CAGvC,MAAM,kBAAkB,KAAK,KAAK,aAAa,MAAM,KAAM,IAAK;AAChE,mBAAkB;AAChB,EAAK,oBAAoB,WAAW;IACnC,gBAAgB;;AAMrB,IAAIC;AAQJ,IAAI,qBAAqB;AACzB,IAAI,qBAAqB;AACzB,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AAEpC,eAAsB,oBACpB,QACe;AACf,KAAI,gBAAiB,QAAO;AAO5B,KAAI,WAAW,aAAa;EAC1B,MAAM,MAAM,KAAK,KAAK;AACtB,MAAI,MAAM,qBAAqB,6BAA6B;AAC1D,WAAQ,MACN,uBAAuB,OAAO,kCAAkC,4BAA4B,IAC7F;AACD;;AAEF,MAAI,MAAM,qBAAqB,6BAA6B;AAC1D,WAAQ,MACN,uBAAuB,OAAO,kCAAkC,4BAA4B,IAC7F;AACD;;;AAIJ,oBAAmB,YAAY;AAC7B,UAAQ,MAAM,oCAAoC,OAAO,GAAG;AAC5D,MAAI;GACF,MAAM,EAAE,UAAU,MAAM,iBAAiB;AACzC,SAAM,eAAe;AACrB,wBAAqB,KAAK,KAAK;AAC/B,WAAQ,MAAM,0BAA0B;AACxC,OAAI,MAAM,UACR,SAAQ,KAAK,4BAA4B,MAAM;WAE1C,OAAO;AACd,wBAAqB,KAAK,KAAK;AAC/B,WAAQ,MACN,2CAA2C,OAAO,KAClD,MACD;YACO;AACR,qBAAkB;;KAElB;AACJ,QAAO;;;;;;;;;;;;AAaT,eAAsB,mBACpB,SACA,WACmB;CACnB,MAAM,QAAQ,MAAM,SAAS;AAC7B,KAAI,MAAM,WAAW,IAAK,QAAO;AAEjC,SAAQ,KACN,GAAG,UAAU,+DACd;AACD,OAAM,oBAAoB,YAAY;AAEtC,QAAO,SAAS;;AAOlB,eAAsB,iBACpB,SACe;AACf,KAAI;EACF,MAAM,cAAc,MAAM,iBAAiB;AAE3C,MAAI,eAAe,CAAC,SAAS,OAAO;AAClC,SAAM,cAAc;AACpB,OAAI,MAAM,UACR,SAAQ,KAAK,iBAAiB,YAAY;AAE5C,SAAM,SAAS;AAEf;;AAGF,UAAQ,KAAK,0CAA0C;EACvD,MAAM,WAAW,MAAM,eAAe;AACtC,UAAQ,MAAM,yBAAyB,SAAS;AAEhD,UAAQ,KACN,0BAA0B,SAAS,UAAU,OAAO,SAAS,mBAC9D;EAED,MAAM,QAAQ,MAAM,gBAAgB,SAAS;AAC7C,QAAM,iBAAiB,MAAM;AAC7B,QAAM,cAAc;AAEpB,MAAI,MAAM,UACR,SAAQ,KAAK,iBAAiB,MAAM;AAEtC,QAAM,SAAS;UACR,OAAO;AACd,MAAI,iBAAiB,WAAW;AAC9B,WAAQ,MAAM,+BAA+B,MAAM,MAAM,SAAS,MAAM,CAAC;AACzE,SAAM;;AAGR,UAAQ,MAAM,+BAA+B,MAAM;AACnD,QAAM;;;AAIV,eAAe,UAAU;CACvB,MAAM,OAAO,MAAM,eAAe;AAClC,SAAQ,KAAK,gBAAgB,KAAK,QAAQ;;;;;AC9J5C,eAAsB,QAAQ,SAAwC;AACpE,KAAI,QAAQ,SAAS;AACnB,UAAQ,QAAQ;AAChB,UAAQ,KAAK,0BAA0B;;AAGzC,OAAM,YAAY,QAAQ;AAE1B,OAAM,aAAa;AACnB,OAAM,iBAAiB,EAAE,OAAO,MAAM,CAAC;AACvC,SAAQ,QAAQ,2BAA2B,MAAM,kBAAkB;;AAGrE,MAAa,OAAO,cAAc;CAChC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,SAAS;GACP,OAAO;GACP,MAAM;GACN,SAAS;GACT,aAAa;GACd;EACD,cAAc;GACZ,MAAM;GACN,SAAS;GACT,aAAa;GACd;EACF;CACD,IAAI,EAAE,QAAQ;AACZ,SAAO,QAAQ;GACb,SAAS,KAAK;GACd,WAAW,KAAK;GACjB,CAAC;;CAEL,CAAC;;;;AC/CF,MAAa,kBAAkB,YAA2C;CACxE,MAAM,WAAW,MAAM,MAAM,GAAG,oBAAoB,yBAAyB,EAC3E,SAAS,cAAc,MAAM,EAC9B,CAAC;AAEF,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,UAAU,+BAA+B,SAAS;AAG9D,QAAQ,MAAM,SAAS,MAAM;;;;;ACH/B,MAAa,aAAa,cAAc;CACtC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,MAAM;AACV,QAAM,aAAa;AACnB,QAAM,kBAAkB;AACxB,MAAI;GACF,MAAM,QAAQ,MAAM,iBAAiB;GACrC,MAAM,UAAU,MAAM,gBAAgB;GACtC,MAAM,eAAe,QAAQ;GAC7B,MAAM,cAAc,eAAe,QAAQ;GAC3C,MAAM,qBACJ,eAAe,IAAK,cAAc,eAAgB,MAAM;GAC1D,MAAM,0BAA0B,QAAQ;GAGxC,SAAS,eAAe,QAAc,MAA+B;AACnE,QAAI,CAAC,KAAM,QAAO,GAAGC,OAAK;IAC1B,MAAM,QAAQ,KAAK;IACnB,MAAM,OAAO,QAAQ,KAAK;IAC1B,MAAM,cAAc,QAAQ,IAAK,OAAO,QAAS,MAAM;IACvD,MAAM,mBAAmB,KAAK;AAC9B,WAAO,GAAGA,OAAK,IAAI,KAAK,GAAG,MAAM,SAAS,YAAY,QAAQ,EAAE,CAAC,UAAU,iBAAiB,QAAQ,EAAE,CAAC;;GAGzG,MAAM,cAAc,YAAY,YAAY,GAAG,aAAa,SAAS,mBAAmB,QAAQ,EAAE,CAAC,UAAU,wBAAwB,QAAQ,EAAE,CAAC;GAChJ,MAAM,WAAW,eAAe,QAAQ,MAAM,gBAAgB,KAAK;GACnE,MAAM,kBAAkB,eACtB,eACA,MAAM,gBAAgB,YACvB;AAED,WAAQ,IACN,wBAAwB,MAAM,aAAa,mBACtB,MAAM,iBAAiB,iBAEnC,YAAY,MACZ,SAAS,MACT,kBACV;WACM,KAAK;AACZ,WAAQ,MAAM,kCAAkC,IAAI;AACpD,WAAQ,KAAK,EAAE;;;CAGpB,CAAC;;;;ACjDF,MAAM,gBAAgB,UAAU,SAAS;AAEzC,MAAM,cAAc;AACpB,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;;AAY/B,SAAS,gBAAwB;AAC/B,QAAO,KAAK,KACV,GAAG,SAAS,EACZ,UACA,SACA,iBACA,oBACD;;;;;;AAOH,eAAe,YAA+C;AAC5D,KAAI;EACF,MAAM,MAAM,MAAM,GAAG,SAAS,eAAe,EAAE,OAAO;EACtD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MACE,OAAO,OAAO,cAAc,YACxB,OAAO,qBAAqB,QAC3B,OAAO,OAAO,qBAAqB,YACpC,OAAO,kBAAkB,QACxB,OAAO,OAAO,kBAAkB,SAErC,QAAO;AAET,SAAO;SACD;AACN,SAAO;;;AAIX,eAAe,WAAW,OAAyC;AACjE,KAAI;AACF,QAAM,GAAG,MAAM,KAAK,QAAQ,eAAe,CAAC,EAAE,EAAE,WAAW,MAAM,CAAC;AAClE,QAAM,GAAG,UAAU,eAAe,EAAE,KAAK,UAAU,MAAM,EAAE,EACzD,MAAM,KACP,CAAC;UACK,KAAK;AAGZ,UAAQ,MAAM,+CAA+C,IAAI;;;;AAKrE,SAAS,eAAe,OAA0C;AAChE,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,YAAY,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS;AACrD,KAAI,OAAO,MAAM,UAAU,CAAE,QAAO;AAEpC,SADoB,KAAK,KAAK,GAAG,aAAa,MAAO,QAChC;;;;;;;AAQvB,SAAS,sBAAqC;AAC5C,KAAI;EAOF,MAAM,QANM,aAAa,UAAU,CAAC,YAAY,EAAE;GAChD,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACT,UAAU;GACX,CAAC,CAEgB,MAAM,mBAAmB;AAC3C,SAAO,QAAQ,MAAM,KAAK;SACpB;AACN,SAAO;;;;;;;AAQX,eAAe,mBAA2C;AACxD,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,cACvB,OACA;GAAC;GAAQ;GAAa;GAAW;GAAW,EAC5C,EAAE,SAAS,qBAAqB,CACjC;EACD,MAAM,IAAI,OAAO,MAAM;AACvB,SAAO,iBAAiB,KAAK,EAAE,GAAG,IAAI;SAChC;AACN,SAAO;;;;;;;;;AAUX,SAAS,QAAQ,WAA0B,QAAgC;AACzE,KAAI,CAAC,aAAa,CAAC,OAAQ,QAAO;CAClC,MAAM,IAAI,UAAU,MAAM,IAAI,CAAC,KAAK,MAAM,SAAS,GAAG,GAAG,CAAC;CAC1D,MAAM,IAAI,OAAO,MAAM,IAAI,CAAC,KAAK,MAAM,SAAS,GAAG,GAAG,CAAC;AACvD,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,KAAK,EAAE,MAAM;EACnB,MAAM,KAAK,EAAE,MAAM;AACnB,MAAI,KAAK,GAAI,QAAO;AACpB,MAAI,KAAK,GAAI,QAAO;;AAEtB,QAAO;;;;;;AAmBT,eAAsB,mBAAmB,OAIrC,EAAE,EAA+B;AACnC,KAAI,KAAK,QACP,QAAO;EACL,WAAW;EACX,kBAAkB;EAClB,eAAe;EACf,aAAa;EACb,SAAS;EACT,YAAY;EACb;CAGH,MAAM,QAAQ,MAAM,WAAW;AAC/B,KAAI,CAAC,KAAK,SAAS,CAAC,eAAe,MAAM,CACvC,QAAO;EACL,WAAW,OAAO,qBAAqB;EACvC,kBAAkB,OAAO,oBAAoB;EAC7C,eAAe,OAAO,iBAAiB;EACvC,aAAa,QACX,OAAO,oBAAoB,MAC3B,OAAO,iBAAiB,KACzB;EACD,SAAS;EACT,YAAY;EACb;CAGH,MAAM,mBAAmB,qBAAqB;AAC9C,KAAI,qBAAqB,KACvB,QAAO;EACL,WAAW;EACX,kBAAkB;EAClB,eAAe;EACf,aAAa;EACb,SAAS;EACT,YAAY;EACb;CAGH,MAAM,gBAAgB,MAAM,kBAAkB;AAG9C,OAAM,WAAW;EACf,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC;EACA;EACD,CAAC;AAEF,KAAI,kBAAkB,KACpB,QAAO;EACL,WAAW;EACX;EACA,eAAe;EACf,aAAa;EACb,SAAS;EACT,YAAY;EACb;AAGH,QAAO;EACL,WAAW;EACX;EACA;EACA,aAAa,QAAQ,kBAAkB,cAAc;EACrD,SAAS;EACV;;;;;;;AAQH,eAAsB,iBAAiB,eAAsC;AAC3E,SAAQ,KACN,YAAY,YAAY,MAAM,cAAc,0BAC7C;AACD,KAAI;AACF,QAAM,cACJ,OACA;GAAC;GAAW;GAAM,GAAG,YAAY;GAAU;GAAW,EACtD,EAAE,SAAS,wBAAwB,CACpC;AACD,UAAQ,QAAQ,GAAG,YAAY,cAAc,gBAAgB;UACtD,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,QAAM,IAAI,MAAM,uBAAuB,MAAM;;;;;;AC/OjD,MAAa,eAAe;;;;;;;;;;;;;;;;;;;AAoB5B,MAAa,uBAAuB;AACpC,MAAa,iCAAiC,CAC5C,mBACA,kBACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCD,SAAgB,oBAA4B;AAG1C,KADE,MAAM,QAAQ,KAAK,MAAM,MAAM,yBAAyB,KAAK,EAAE,GAAG,CAAC,IAAI,OACxD;AACf,UAAQ,KACN,wEAAwE,qBAAqB,iIAAiI,qBAAqB,eACpP;AACD,SAAO,GAAG,qBAAqB;;AAEjC,QAAO;;;;;;;;AAST,MAAa,sBAAsB;AACnC,MAAa,gCAAgC;CAC3C;CACA;CACA;CACD;AAED,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;;AAGvB,SAAgB,qBAA6B;AAC3C,QACE,KAAK,MAAM,KAAK,QAAQ,IAAI,iBAAiB,iBAAiB,GAAG,GAC/D;;AAIN,SAAS,OAAO,KAAa,UAA0B;CACrD,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,CAAC,IAAK,QAAO;AAKjB,KAAI,CAAC,WAAW,KAAK,IAAI,MAAM,CAAC,EAAE;AAChC,UAAQ,KACN,GAAG,IAAI,GAAG,KAAK,UAAU,IAAI,CAAC,iDAAiD,WAChF;AACD,SAAO;;CAET,MAAM,SAAS,OAAO,SAAS,KAAK,GAAG;AACvC,QAAO,OAAO,SAAS,OAAO,IAAI,SAAS,IAAI,SAAS;;AAU1D,MAAa,4BAA4B,OACvC,6BACA,EACD;AAaD,MAAa,iCAAiC,OAC5C,kCACA,IACD;;;;;;;;;;;;;;;;;;;;;AClHD,MAAM,2BAA2B;CAE/B;CACA;CACA;CACA;CACA;CACA;CAOA;CACA;CACA;CACA;CAKA;CASA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAKA;CAOA;CACA;CACA;CACD;;;;;;;AAQD,SAAgB,kBACd,QACmB;CACnB,MAAMC,YAA+B,EAAE,GAAG,QAAQ;AAClD,MAAK,MAAM,OAAO,yBAChB,QAAO,UAAU;AAEnB,QAAO;;AAGT,SAAS,cAAc,QAAuB;AAC5C,KAAI;AACF,eAAaC,UAAQ,aAAa,UAAU,cAAc,SAAS,CAACC,OAAK,EAAE,EACzE,OAAO,UACR,CAAC;AACF,SAAO;SACD;AACN,SAAO;;;;;;;;;;AAWX,SAAgB,8BAA8B,WAAkC;AAC9E,QAAO;EACL;EACA,iEAAiE,UAAU;EAC3E;EACA;EACD;;;;;;;;AASH,SAAgB,kBAAqD;AACnE,KAAI,CAAC,cAAc,QAAQ,CAAE,QAAO,EAAE,IAAI,OAAO;CACjD,IAAIC;AACJ,KAAI;AACF,QAAM,aAAa,SAAS,CAAC,YAAY,EAAE;GACzC,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;SACH;AACN,SAAO,EAAE,IAAI,OAAO;;CAGtB,MAAM,IAAI,sBAAsB,KAAK,IAAI;AACzC,KAAI,CAAC,EAAG,QAAO;EAAE,IAAI;EAAO,SAAS;EAAK;CAC1C,MAAM,QAAQ,OAAO,SAAS,EAAE,IAAI,GAAG;CACvC,MAAM,QAAQ,OAAO,SAAS,EAAE,IAAI,GAAG;CACvC,MAAMC,YAAU,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE;AAGrC,QAAO;EAAE,IADE,QAAQ,KAAM,UAAU,KAAK,SAAS;EACpC;EAAS;;;;;;;;;;;;;;AA6BxB,SAAS,cAAc,QAAgC;CACrD,MAAMC,MAAgB,CAAC,QAAQ;AAC/B,KAAI,OAAO,UACT,KAAI,KAAK,GAAG,8BAA8B,OAAO,UAAU,CAAC;AAE9D,KAAI,KACF,aACA,mBACA,sBACA,cACA,MACA,OAAO,SAAS,qBAChB,GAAG,OAAO,UACX;AACD,QAAO;;AAGT,SAAgB,mBAAmB,QAGjC;AAMA,QAAO;EACL,KALA,OAAO,SAAS,gBACZ;GAAC;GAAU;GAAkC,GAAG,OAAO;GAAU,GACjE,cAAc,OAAO;EAIzB,KAAK;GAAE,GAAG,kBAAkBJ,UAAQ,IAAI;GAAE,GAAG,OAAO;GAAS;EAC9D;;AAGH,SAAgB,YACd,QACA,UACA,UAAuD,EAAE,EACnD;CACN,MAAM,EAAE,KAAK,QAAQ,mBAAmB,OAAO;CAE/C,MAAM,aAAa,IAAI;AACvB,KAAI,CAAC,cAAc,WAAW,EAAE;EAC9B,MAAM,MAAM,IAAI,WAAW;AAC3B,UAAQ,MAAM,IAAI;AAClB,YAAQ,OAAO,MAAM,MAAM,KAAK;AAChC,YAAQ,KAAK,EAAE;;CAGjB,IAAIK;AACJ,KAAI;AACF,MAAIL,UAAQ,aAAa,QAKvB,SAAQ,MADO,IAAI,KAAK,MAAO,EAAE,SAAS,IAAI,GAAG,IAAI,EAAE,KAAK,EAAG,CAAC,KAAK,IAAI,EACnD,EAAE,EAAE;GACxB;GACA,OAAO;GACP,OAAO;GACR,CAAC;MAEF,SAAQ,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,EAAE;GAClC;GACA,OAAO;GACR,CAAC;UAEG,OAAO;EACd,MAAM,MAAM,oBAAoB,WAAW,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACrG,UAAQ,MAAM,IAAI;AAClB,YAAQ,OAAO,MAAM,MAAM,KAAK;AAChC,WAAO,MAAM,KAAK,CAAC,YAAY,GAAG;AAClC,MAAI,QAAQ,WACV,CAAK,QAAQ,QAAQ,QAAQ,YAAY,CAAC,CAAC,YAAY,GAAG;AAE5D,YAAQ,KAAK,EAAE;;CAGjB,IAAI,UAAU;CACd,IAAI,UAAU;CACd,eAAe,UAAyB;AACtC,MAAI,QAAS;AACb,YAAU;AAEV,MAAI;AACF,SAAM,MAAM;UACN;EAIR,MAAM,UAAU,iBAAiBA,UAAQ,KAAK,EAAE,EAAE,IAAK;AACvD,MAAI;AACF,SAAMM,SAAO,MAAM,KAAK;UAClB;AAGR,MAAI,QAAQ,WACV,KAAI;AACF,SAAM,QAAQ,YAAY;UACpB;AAIV,eAAa,QAAQ;;CAGvB,SAAS,KAAK,MAAoB;AAChC,MAAI,QAAS;AACb,YAAU;AACV,YAAQ,KAAK,KAAK;;CAGpB,MAAM,iBAAiB;AACrB,WAAS,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,YAAY,KAAK,EAAE,CAAC;;AAEtD,WAAQ,GAAG,UAAU,SAAS;AAC9B,WAAQ,GAAG,WAAW,SAAS;AAE/B,OAAM,GAAG,SAAS,UAAU,WAAW;EAErC,MAAM,OAAO,aAAa,SAAS,MAAM;AACzC,WAAS,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,YAAY,KAAK,EAAE,CAAC;GACrD;AACF,OAAM,GAAG,eAAe;AACtB,WAAS,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,YAAY,KAAK,EAAE,CAAC;GAClD;;;;;;;;;;AC5OJ,MAAM,WAAW;;;;;;;AAQjB,MAAM,eAAe;CACnB,YAAY;CACZ,gBAAgB;CAChB,WAAW;CACX,SAAS;CACV;;;;;;AAOD,MAAM,kBAAkB;CACtB,YAAY;CACZ,gBAAgB;CAChB,WAAW;CACX,SAAS;CACV;;;;;;AAOD,MAAM,qBAAqB;AAE3B,MAAM,gBAAgB;AACtB,MAAM,eAAe;AACrB,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB,KAAK,OAAO;AACrC,MAAM,eAAe;;;;;;;;;AAUrB,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;;;;;;;AAQ7B,MAAM,4BAA4B,OAAO;;;;;;;AAQzC,MAAM,uBAAuB;;;;;;;;;;AAW7B,MAAM,eACJ;AAyFF,IAAIC;;;;;;;;;;;;;AAcJ,SAAgB,iBAAoC;AAClD,KAAI,cAAe,QAAO;AAK1B,KAAI,kBAAkB,EAAE;AACtB,kBAAgB;GAAE,QAAQ;GAAM,QAAQ;GAAU;AAClD,SAAO;;AAMT,KAAI;EAIF,MAAM,gBAAc,kBAAkB;AACtC,MAAI,IAAI,UAAU,WAAW,IAAI,OAAO,EAAE;AACxC,mBAAgB;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAW;AACzD,UAAO;;SAEH;AAIR,OAAM,IAAI,MACR,mLAGD;;AAGH,SAAS,mBAA4B;AAKnC,KAAI;AAMF,SAJY,aADA,QAAQ,aAAa,UAAU,UAAU,SACvB,CAAC,KAAK,EAAE;GACpC,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CAAC,CACS,SAAS;SACd;AACN,SAAO;;;AAQX,SAAS,eAAe,OAAuC;AAC7D,KAAI,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,WAAW,EAC5D,QAAO;AAET,KAAI,MAAM,MAAM,SAAS,cACvB,QAAO,8BAA8B,cAAc;AAErD,KAAI,WAAW,KAAK,MAAM,MAAM,CAC9B,QAAO;AAET,KAAI,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,WAAW,EACpE,QAAO;AAET,KAAI,MAAM,QAAQ,CAAC;EAAC;EAAU;EAAW;EAAQ,CAAC,SAAS,MAAM,KAAK,CACpE,QAAO;AAET,KAAI,MAAM,cAAc,QAAW;AACjC,MAAI,OAAO,MAAM,cAAc,SAC7B,QAAO;AAET,MAAI,MAAM,UAAU,SAAS,aAC3B,QAAO,kCAAkC,aAAa;AAExD,MAAI,WAAW,KAAK,MAAM,UAAU,CAClC,QAAO;;AAGX,KAAI,MAAM,UAAU,QAClB;MAAI,OAAO,MAAM,UAAU,YAAY,CAAC,OAAO,UAAU,MAAM,MAAM,IAAI,MAAM,QAAQ,EACrF,QAAO;;AAGX,KAAI,MAAM,kBAAkB,QAC1B;MACE,OAAO,MAAM,kBAAkB,YAC/B,CAAC,OAAO,UAAU,MAAM,cAAc,IACtC,MAAM,gBAAgB,EAEtB,QAAO;;AAGX,QAAO;;;;;;;;;;;;;;;;;;;;;AAgCT,SAAgB,kBAAkB,WAAqC;AACrE,KAAI,CAACC,OAAK,WAAW,UAAU,CAC7B,QAAO;EAAE,IAAI;EAAO,OAAO;EAAsC;CAGnE,IAAIC;AACJ,KAAI;AACF,cAAY,aAAa,UAAU;SAC7B;AACN,SAAO;GAAE,IAAI;GAAO,OAAO;GAAoC;;AAGjE,KAAI;AACF,MAAI,CAAC,SAAS,UAAU,CAAC,aAAa,CACpC,QAAO;GAAE,IAAI;GAAO,OAAO;GAAiC;SAExD;AACN,SAAO;GAAE,IAAI;GAAO,OAAO;GAAoC;;AAGjE,QAAO;EAAE,IAAI;EAAM;EAAW;;;;;;;;;;;;;;;;AAqBhC,SAAgB,SAAS,MAA6B;CACpD,MAAMC,MAAqB,EAAE;CAC7B,MAAM,SAAS,KAAK,MAAM,gBAAgB;CAC1C,MAAM,KAAK;AACX,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,MAAO;EACZ,MAAM,UAAU,MAAM,MAAM,GAAG;AAC/B,MAAI,CAAC,QAAS;AACd,OAAK,MAAM,KAAK,SAAS;GACvB,MAAM,KAAK,EAAE,aAAa;AAC1B,OAAI,GAAG,UAAU,EAAG,KAAI,KAAK,GAAG;;;AAGpC,QAAO;;;;;;;;AAaT,SAAS,UAAU,OAA2B;AAC5C,KAAI,CAAC,MAAM,OAAO,MAAM,OAAQ;AAEhC,KAAI,QAAQ,aAAa,SAAS;AAGhC,MAAI;AACF,YAAS,YAAY;IAAC;IAAM;IAAM;IAAQ,OAAO,MAAM,IAAI;IAAC,QAAQ,GAGlE;UACI;AAGR;;AAGF,KAAI;AACF,QAAM,KAAK,UAAU;SACf;AAIR,kBAAiB;AACf,MAAI,CAAC,MAAM,OACT,KAAI;AACF,SAAM,KAAK,UAAU;UACf;IAIT,IAAI,CAAC,OAAO;;;;;;;;;AAcjB,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,SAAS,sBAAsB,YAAmC;CAChE,MAAMC,SAAwB,EAAE;AAChC,MAAK,MAAM,SAAS,WAAW,MAAM,OAAO,EAAE;AAC5C,MAAI,CAAC,MAAO;EAIZ,MAAM,UAAU,MAAM,MACpB,+DACD;AACD,MAAI,QAAS,QAAO,KAAK,GAAG,QAAQ;;AAEtC,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAS,yBAAyB,OAAqC;AACrE,KAAI,CAAC,wBAAwB,KAAK,MAAM,CAAE,QAAO;CACjD,MAAM,SAAS,sBAAsB,MAAM;AAC3C,KAAI,OAAO,SAAS,EAAG,QAAO;CAC9B,MAAM,QAAQ,OAAO,KAAK,MAAM,EAAE,aAAa,CAAC;CAChD,MAAM,QAAQ,OAAO,KAAK,MAAM,EAAE,aAAa,CAAC;CAChD,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC;CACpE,MAAM,2BAAW,IAAI,KAAa;AAClC,UAAS,IAAI,MAAM;AACnB,UAAS,IAAI,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,KAAK,GAAG,CAAC;AAC9C,UAAS,IAAI,IAAI,KAAK,GAAG,CAAC;AAC1B,UAAS,IAAI,MAAM,KAAK,IAAI,CAAC;AAC7B,UAAS,IAAI,MAAM,KAAK,IAAI,CAAC;AAC7B,UAAS,IAAI,MAAM,KAAK,IAAI,CAAC;AAC7B,QAAO,MAAM,KAAK,SAAS;;;;;;;;;AAU7B,SAAS,sBAAsB,UAAyC;AACtE,QAAO,QAAQ,SAAS,KAAK,IAAI,GAAG;;AAGtC,SAAS,YAAY,OAcH;CAChB,MAAMC,OAAsB,CAAC,UAAU,cAAc;AAIrD,KAAI,MAAM,eAAe,EACvB,MAAK,KAAK,MAAM,OAAO,MAAM,aAAa,CAAC;AAa7C,KAAI,CAAC,MAAM,qBAAqB,MAAM,SAAS,aAAa,MAAM,SAAS,UACzE,MAAK,KAAK,KAAK;AAGjB,KAAI,MAAM,YAAY,MAAM,aAAa,OACvC,MAAK,KAAK,MAAM,MAAM,SAAS;AAMjC,MAAK,KAAK,MAAM,MAAM,oBAAoB,MAAM,OAAO,IAAI;AAE3D,QAAO;;;;;;;;;;;;;AAyCT,eAAe,kBACb,OACA,MACsB;CACtB,MAAMC,OAAsB,EAAE;CAC9B,IAAI,cAAc;CAClB,IAAI,iBAAiB;CACrB,IAAI,YAAY;CAChB,IAAI,eAAe;AAMnB,KAAI,KAAK,OAAO,SAAS;AACvB,YAAU,MAAM;AAChB,SAAO;GACL;GACA,cAAc;GACd,WAAW;GACX,WAAW;GACX,aAAa;GACd;;CAMH,MAAMC,uBAAsC,EAAE;CAC9C,IAAIC;AAEJ,KAAI,CAAC,MAAM,OACT,QAAO;EAAE;EAAM,cAAc;EAAG,WAAW;EAAO,WAAW;EAAO,aAAa;EAAG;CAGtF,MAAM,KAAK,gBAAgB;EAAE,OAAO,MAAM;EAAQ,WAAW;EAAU,CAAC;CAKxE,MAAM,gBAAsB;AAC1B,cAAY;AACZ,KAAG,OAAO;AACV,YAAU,MAAM;;AAElB,MAAK,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AAE9D,KAAI;AACF,aAAW,MAAM,WAAW,IAAI;AAC9B,OAAI,UAAW;AACf,kBAAe,QAAQ,SAAS;AAChC,OAAI,cAAc,kBAAkB;AAClC,qBAAiB;AACjB,cAAU,MAAM;AAChB;;AAEF,OAAI,QAAQ,WAAW,EAAG;GAE1B,IAAIC;AACJ,OAAI;AACF,UAAM,KAAK,MAAM,QAAQ;WACnB;AAIN;;AAGF,WAAQ,IAAI,MAAZ;IACE,KAAK;AACH,qBAAgB;AAChB,0BAAqB,SAAS;AAC9B,yBAAoB;AACpB;IAEF,KAAK,WAAW;KACd,MAAM,OAAO,qBAAqB,IAAI,KAAK,OAAO,QAAQ,GAAG;AAC7D,SAAI,qBAAqB,kBAAkB,cAAc,SAAS,KAAK,aACrE,mBAAkB,cAAc,KAAK,KAAK;UACrC;AACL,2BAAqB,KAAK,KAAK;AAC/B,UAAI,qBAAqB,SAAS,KAAK,aACrC,sBAAqB,OAAO;;AAGhC;;IAEF,KAAK,SAAS;AACZ,SAAI,KAAK,UAAU,KAAK,OAAO;AAE7B,gBAAU,MAAM;AAChB;;KAEF,MAAM,MAAM,IAAI,KAAK,aAAa;AAClC,SAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,KAAK,eAAe,CAAC,IACjE;KAEF,MAAMC,MAAc;MAClB,MAAM,IAAI,KAAK,KAAK;MACpB,MAAM,IAAI,KAAK;MACf,cAAc,qBAAqB,IAAI,KAAK,MAAM,KAAK;MACvD,aAAa,IAAI;MACjB,WAAW,IAAI;MACf,gBAAgB,CAAC,GAAG,qBAAqB;MACzC,eAAe,EAAE;MAClB;AACD,0BAAqB,SAAS;AAC9B,yBAAoB;AACpB,UAAK,KAAK,IAAI;AACd;;IAEF,KAAK;IACL,KAAK;IACL,QAIE;;;WAGE;AACR,OAAK,OAAO,oBAAoB,SAAS,QAAQ;;AAGnD,QAAO;EACL;EACA;EACA,WAAW,kBAAkB,KAAK,UAAU,KAAK;EACjD;EACA;EACD;;AAGH,SAAS,qBAAqB,GAAmB;AAC/C,KAAI,EAAE,SAAS,OAAO,CAAE,QAAO,EAAE,MAAM,GAAG,GAAG;AAC7C,KAAI,EAAE,SAAS,KAAK,CAAE,QAAO,EAAE,MAAM,GAAG,GAAG;AAC3C,QAAO;;;;;;;;;AAcT,MAAMC,oBAAsD;CAC1D,OAAO;CACP,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,OAAO;CACP,OAAO;CACP,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACT;;;;;;;AAQD,MAAMC,gBAAkD;CACtD,YAAY;CACZ,KAAK;CACL,YAAY;CACZ,QAAQ;CACR,IAAI;CACJ,MAAM;CACN,MAAM;CACN,GAAG;CACH,KAAK;CACN;;;;;;;;;;;;;AAcD,MAAMC,wBAAuE;CAC3E,YAAY,IAAI,IAAI;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,KAAK,IAAI,IAAI;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,YAAY,IAAI,IAAI;EAClB;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,QAAQ,IAAI,IAAI;EACd;EACA;EACA;EACD,CAAC;CACF,IAAI,IAAI,IAAI;EACV;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,IAAI,IAAI;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,IAAI,IAAI;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,GAAG,IAAI,IAAI;EACT;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,KAAK,IAAI,IAAI;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACH;;;;;;AAOD,MAAM,wBAAwB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAQF,IAAIC;;;;;;;AAQJ,SAAS,qBAAoC;AAC3C,KAAI;EACF,MAAM,oBAAkB,QAAQ,iCAAiC;AACjE,SAAOb,OAAK,KAAKA,OAAK,QAAQ,QAAQ,EAAE,MAAM;SACxC;AACN,SAAO;;;;;;;;;AAUX,SAAS,mBAAkC;AACzC,KAAI,eAAgB,QAAO;AA+B3B,kBAAiB,EAAE,QA9BJ,YAAmD;EAChE,MAAM,sBAAM,IAAI,KAA8B;AAC9C,MAAI;AACF,SAAM,OAAO,MAAM;WACZ,KAAK;AACZ,WAAQ,KACN,8EAA+E,IAAc,UAC9F;AACD,UAAO;;EAET,MAAM,OAAO,oBAAoB;AACjC,MAAI,CAAC,MAAM;AACT,WAAQ,KACN,sFACD;AACD,UAAO;;AAET,OAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,cAAc,EAAE;GAC3D,MAAM,WAAWA,OAAK,KAAK,MAAM,SAAS;AAC1C,OAAI;IACF,MAAM,OAAO,MAAM,OAAO,SAAS,KAAK,SAAS;AACjD,QAAI,IAAI,KAAK,KAAK;YACX,KAAK;AACZ,YAAQ,KACN,qDAAqD,IAAI,SAAS,SAAS,IAAK,IAAc,UAC/F;;;AAGL,SAAO;KACL,EACsB;AAC1B,QAAO;;AAMJ,kBAAkB,CAAC,MAAM,YAAY,GAExC;AAEF,SAAS,sBAAsB,UAAiC;AAE9D,QAAO,kBADKA,OAAK,QAAQ,SAAS,CAAC,aAAa,KACf;;AAoBnC,MAAM,6BAAa,IAAI,KAAyB;AAEhD,SAAS,SAAS,SAAiB,SAAyC;CAC1E,MAAM,MAAM,WAAW,IAAI,QAAQ;AACnC,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,IAAI,YAAY,SAAS;AAE3B,MAAI,IAAI,KACN,KAAI;AACF,OAAI,KAAK,QAAQ;UACX;AAIV,aAAW,OAAO,QAAQ;AAC1B;;AAGF,YAAW,OAAO,QAAQ;AAC1B,YAAW,IAAI,SAAS,IAAI;AAC5B,QAAO;;AAGT,SAAS,SAAS,SAAiB,OAAyB;AAI1D,QAAO,WAAW,QAAQ,sBAAsB;EAC9C,MAAM,WAAW,WAAW,MAAM,CAAC,MAAM,CAAC;AAC1C,MAAI,aAAa,OAAW;EAC5B,MAAM,UAAU,WAAW,IAAI,SAAS;AACxC,MAAI,SAAS,KACX,KAAI;AACF,WAAQ,KAAK,QAAQ;UACf;AAIV,aAAW,OAAO,SAAS;;AAE7B,YAAW,IAAI,SAAS,MAAM;;;;;;;;;AAUhC,SAAS,cAAc,QAAgB,aAA6B;AAClE,KAAI,eAAe,EAAG,QAAO;CAC7B,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,IACjC,KAAI,OAAO,WAAW,EAAE,KAAK,IAAe;AAC1C,UAAQ;AACR,MAAI,SAAS,YAAa,QAAO,IAAI;;AAGzC,QAAO;;;;;;;;;;;;;;;AAgBT,SAAS,eACP,aACA,SACS;CACT,MAAM,WAAW,sBAAsB;AACvC,KAAI,CAAC,SAAU,QAAO;CACtB,IAAIc,MAAgC,YAAY;CAChD,IAAI,QAAQ;AACZ,QAAO,OAAO,QAAQ,GAAG;AACvB,MAAI,SAAS,IAAI,IAAI,KAAK,EAAE;GAI1B,MAAM,YAAY,IAAI,kBAAkB,OAAO;AAC/C,OAAI,aAAa,kBAAkB,WAAW,YAAY,CACxD,QAAO;GAMT,MAAM,aAAa,IAAI,kBAAkB,aAAa;AACtD,OAAI,cAAc,kBAAkB,YAAY,YAAY,EAAE;IAK5D,MAAM,QAAQ,oBAAoB,WAAW;AAC7C,QAAI,SAAS,MAAM,eAAe,YAAY,WAC5C,QAAO;;GAKX,MAAM,YAAY,IAAI,kBAAkB,OAAO;AAC/C,OAAI,aAAa,kBAAkB,WAAW,YAAY,EAAE;IAC1D,MAAM,QAAQ,oBAAoB,UAAU;AAC5C,QAAI,SAAS,MAAM,eAAe,YAAY,WAC5C,QAAO;;;AAIb,QAAM,IAAI;AACV,WAAS;;AAEX,QAAO;;AAGT,SAAS,kBACP,OACA,OACS;AACT,QAAO,MAAM,cAAc,MAAM,cAAc,MAAM,YAAY,MAAM;;AAGzE,SAAS,oBACP,MAC0B;AAC1B,KAAI,sBAAsB,IAAI,KAAK,KAAK,CAAE,QAAO;AACjD,MAAK,MAAM,SAAS,KAAK,eAAe;EACtC,MAAM,IAAI,oBAAoB,MAAM;AACpC,MAAI,EAAG,QAAO;;AAEhB,QAAO;;;;;;;;;;;;AAwBT,eAAe,kBAAkB,MAMC;CAChC,MAAMC,SAA+B;EACnC,qCAAqB,IAAI,KAAK;EAC9B,UAAU;EACX;AACD,KAAI,KAAK,WAAW,WAAW,EAAG,QAAO;AAEzC,KAAI,KAAK,OAAO,QAAS,QAAO;CAEhC,MAAM,WAAW,MAAM,kBAAkB,CAAC;AAC1C,KAAI,SAAS,SAAS,EAIpB,QAAO;CAKT,MAAM,MAAM,KAAK,IAAI,KAAK,WAAW,QAAQ,KAAK,KAAK;CACvD,MAAM,yBAAS,IAAI,KAAoD;AACvE,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,QAAQ,KAAK,WAAW;EAC9B,MAAM,OAAO,OAAO,IAAI,MAAM,IAAI,KAAK,IAAI,EAAE;AAC7C,OAAK,KAAK,MAAM;AAChB,SAAO,IAAI,MAAM,IAAI,MAAM,KAAK;;CAGlC,MAAM,KAAK,KAAK,KAAK;CACrB,IAAI,cAAc;CAClB,IAAI,8BAAc,IAAI,KAAqB;AAE3C,KAAI;AACF,OAAK,MAAM,CAAC,SAAS,YAAY,QAAQ;AACvC,OAAI,KAAK,OAAO,QAAS;AAEzB,OADgB,KAAK,KAAK,GAAG,MACd,KAAK,UAAU;AAC5B,WAAO,WACL,4CAA4C,YAAY,GAAG,IAAI;AAEjE;;GAGF,MAAM,UAAU,sBAAsB,QAAQ;AAC9C,OAAI,CAAC,QAAS;GACd,MAAM,OAAO,SAAS,IAAI,QAAQ;AAClC,OAAI,CAAC,KAAM;GAEX,MAAM,UAAUf,OAAK,KAAK,KAAK,eAAe,QAAQ;GACtD,IAAIgB;GACJ,IAAIC;AACJ,OAAI;IACF,MAAM,KAAK,SAAS,QAAQ;AAC5B,cAAU,GAAG;AACb,WAAO,GAAG;YACH,KAAK;AACZ,YAAQ,MACN,iCAAiC,QAAQ,iBAAkB,IAAc,QAAQ,GAClF;AACD;;AAEF,OAAI,OAAO,2BAA2B;AACpC,YAAQ,MACN,iCAAiC,QAAQ,IAAI,KAAK,eACnD;AACD;;GAGF,IAAI,SAAS,SAAS,SAAS,QAAQ;AACvC,OAAI,CAAC,QAAQ;IACX,IAAIC;AACJ,QAAI;AACF,cAAS,aAAa,SAAS,OAAO;aAC/B,KAAK;AACZ,aAAQ,MACN,iCAAiC,QAAQ,iBAAkB,IAAc,QAAQ,GAClF;AACD,cAAS,SAAS;MAAE;MAAS,MAAM;MAAM,QAAQ;MAAM,CAAC;AACxD;;IAEF,IAAI,SAAS,YAAY,IAAI,QAAQ;AACrC,QAAI,CAAC,QAAQ;AACX,cAAS,IAAI,QAAQ;AACrB,YAAO,YAAY,KAAK;AACxB,iBAAY,IAAI,SAAS,OAAO;;IAElC,IAAIC,OAA2B;AAC/B,QAAI;AACF,YAAO,OAAO,MAAM,OAAO;aACpB,KAAK;AACZ,aAAQ,MACN,8CAA8C,QAAQ,IAAK,IAAc,UAC1E;;AAEH,aAAS;KAAE;KAAS;KAAM,QAAQ,OAAO,SAAS;KAAM;AACxD,aAAS,SAAS,OAAO;AACzB,mBAAe;;AAGjB,OAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,OAAQ;AAGpC,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,cAAc,OAAO,QAAQ,MAAM,IAAI,KAAK;AAC9D,QAAI,YAAY,EAAG;IACnB,MAAM,iBAAiB,YAAY,MAAM,IAAI;IAC7C,MAAM,eAAe,YAAY,MAAM,IAAI;IAC3C,IAAIC;AACJ,QAAI;AACF,YAAO,OAAO,KAAK,SAAS,mBAC1B,gBACA,aACD;YACK;AACN,YAAO;;AAET,QAAI,CAAC,KAAM;AAIX,QAAI,CAAC,sBAAsB,IAAI,KAAK,KAAK,EAAE;KACzC,IAAIN,MAAgC;KACpC,IAAI,QAAQ;AACZ,YAAO,OAAO,CAAC,sBAAsB,IAAI,IAAI,KAAK,IAAI,QAAQ,GAAG;MAK/D,MAAM,OAAO,oBAAoB,IAAI;AACrC,UAAI,QAAQ,KAAK,eAAe,gBAAgB;AAC9C,aAAM;AACN;;AAEF,YAAM,IAAI;AACV,eAAS;;AAEX,YAAO;;AAET,QAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAI,KAAK,KAAK,CAAE;AACpD,QAAI,eAAe,MAAM,QAAQ,CAC/B,QAAO,oBAAoB,IAAI,MAAM,MAAM;;;WAIzC;AAGR,OAAK,MAAM,UAAU,YAAY,QAAQ,CACvC,KAAI;AACF,UAAO,QAAQ;UACT;AAIV,gCAAc,IAAI,KAAK;;AAGzB,QAAO;;AAUT,SAAS,cAAc,KAAa,cAAmC;CACrE,MAAM,MAAM,CAAC,GAAG,IAAI,gBAAgB,GAAG,IAAI,cAAc,CAAC,KAAK,KAAK;CACpE,IAAIO;AACJ,KAAI,cAAc;EAMhB,MAAM,QAAQ,IAAI,aAAa,MAAM,IAAI,aAAa,IAAI,UAAU;AAIpE,kBAAgB,MAAM,SAAS,IAAI,QAAQ,IAAI;YACtC,aAAa,KAAK,IAAI,aAAa,WAAW,CAAC,CAIxD,iBAAgB,IAAI;KAEpB,iBAAgB;AAElB,QAAO;EACL,YAAY,IAAI;EAChB,SAAS;EACT,WAAW,IAAI,KAAK,QAAQ,UAAU,IAAI;EAC1C,gBAAgB;EACjB;;;;;;;;;;;;;;;;;;;AA0BH,SAAS,WACP,MACA,aASA,kBACkB;AAClB,KAAI,KAAK,WAAW,KAAK,YAAY,WAAW,EAC9C,QAAO,KAAK,KAAK,OAAO;EACtB,KAAK;EACL,OAAO;EACP,qBAAqB;GACnB,YAAY;GACZ,gBAAgB;GAChB,WAAW;GACX,SAAS;GACV;EACF,EAAE;CAKL,MAAM,iCAAiB,IAAI,KAAyB;CACpD,MAAMC,eAA+D,EAAE;AACvE,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,MAAM,SAAS,cAAc,KADX,kBAAkB,IAAI,EAAE,IAAI,MACF;AAC5C,iBAAe,IAAI,IAAI,MAAM,OAAO;AACpC,eAAa,KAAK;GAChB,YAAY,SAAS,OAAO,WAAW;GACvC,SAAS,SAAS,OAAO,QAAQ;GACjC,WAAW,SAAS,OAAO,UAAU;GACrC,gBAAgB,SAAS,OAAO,eAAe;GAChD,CAAC;;CAIJ,MAAM,4BAAY,IAAI,KAAa;AACnC,MAAK,MAAM,OAAO,KAAM,WAAU,IAAI,IAAI,KAAK;CAC/C,MAAM,IAAI,UAAU;CAKpB,MAAM,qBAAK,IAAI,KAAqB;CACpC,MAAMC,oBAAwE;EAC5E,4BAAY,IAAI,KAAK;EACrB,yBAAS,IAAI,KAAK;EAClB,2BAAW,IAAI,KAAK;EACpB,gCAAgB,IAAI,KAAK;EAC1B;AAGD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK,GAAG;EACrB,MAAM,IAAI,aAAa;AACvB,OAAK,MAAM,SAAS,OAAO,KAAK,EAAE,EAA6B;GAC7D,IAAI,SAAS,kBAAkB,OAAO,IAAI,KAAK;AAC/C,OAAI,CAAC,QAAQ;AACX,6BAAS,IAAI,KAAK;AAClB,sBAAkB,OAAO,IAAI,MAAM,OAAO;;AAE5C,QAAK,MAAM,OAAO,EAAE,OAAQ,QAAO,IAAI,IAAI;;;AAI/C,MAAK,MAAM,MAAM,aAAa;EAC5B,MAAM,wBAAQ,IAAI,KAAa;AAC/B,OAAK,MAAM,SAAS,OAAO,KAAK,kBAAkB,CAChD,MAAK,MAAM,CAAC,MAAM,WAAW,kBAAkB,OAC7C,KAAI,OAAO,IAAI,GAAG,CAAE,OAAM,IAAI,KAAK;AAGvC,KAAG,IAAI,IAAI,MAAM,KAAK;;CAIxB,MAAMC,SAA2C;EAC/C,YAAY;EACZ,SAAS;EACT,WAAW;EACX,gBAAgB;EACjB;AACD,MAAK,MAAM,SAAS,OAAO,KAAK,OAAO,EAA6B;EAClE,MAAMC,OAAsB,EAAE;EAC9B,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,OAAI,KAAK,IAAI,KAAK,GAAG,KAAK,CAAE;AAC5B,QAAK,IAAI,KAAK,GAAG,KAAK;AACtB,QAAK,KAAK,aAAa,GAAG,OAAO,OAAO;;AAE1C,SAAO,SAAS,KAAK,SAAS,IAAI,KAAK,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,SAAS;AAClF,MAAI,OAAO,WAAW,EAAG,QAAO,SAAS;;CAI3C,MAAM,sBAAM,IAAI,KAAqB;AACrC,MAAK,MAAM,MAAM,aAAa;EAC5B,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI;AACxB,MAAI,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,OAAQ,IAAI,MAAO,EAAE,CAAC;;CAItD,MAAMC,MAAwB,EAAE;AAChC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,SAAS,aAAa;EAC5B,MAAMC,gBAAwC;GAC5C,YAAY;GACZ,gBAAgB;GAChB,WAAW;GACX,SAAS;GACV;AACD,OAAK,MAAM,MAAM,aAAa;GAE5B,IAAI,IAAI;GACR,MAAMC,WAAmC;IACvC,YAAY;IACZ,gBAAgB;IAChB,WAAW;IACX,SAAS;IACV;AACD,QAAK,MAAM,SAAS,OAAO,KAAK,aAAa,EAA6B;IACxE,MAAM,KAAK,OAAO,OAAO,QAAQ,MAAM,MAAM,GAAG,CAAC;AACjD,QAAI,OAAO,EAAG;IACd,MAAM,MAAM,OAAO,OAAO,UAAU;IACpC,MAAM,IAAI,gBAAgB;IAC1B,MAAM,OAAO,IAAI,IAAI,KAAK,OAAO,OAAO,UAAU;IAClD,MAAM,eAAe,aAAa,UAAU,KAAK;AACjD,SAAK;AACL,aAAS,SAAS;;AAEpB,OAAI,MAAM,EAAG;GACb,MAAM,aAAa,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK,IAAI;AAGjD,QAAK,MAAM,SAAS,OAAO,KAAK,SAAS,EAAE;IACzC,MAAM,QAAQ,SAAS,SAAS;AAChC,kBAAc,UAAU,YAAY;;;EAGxC,MAAM,QAAQ,OAAO,OAAO,cAAc,CAAC,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE;AACrE,MAAI,KAAK;GAAE,KAAK,KAAK;GAAI,OAAO;GAAO,qBAAqB;GAAe,CAAC;;AAG9E,QAAO;;AAYT,SAAS,cAAc,QAAwC;AAC7D,KAAI,OAAO,WAAW,EAAG,QAAO;EAAE,MAAM,EAAE;EAAE,qBAAqB;EAAG;AAEpE,QAAO,MAAM,GAAG,MAAM;AACpB,MAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,MAAI,EAAE,IAAI,SAAS,EAAE,IAAI,KAAM,QAAO,EAAE,IAAI,OAAO,EAAE,IAAI,OAAO,KAAK;AACrE,SAAO,EAAE,IAAI,OAAO,EAAE,IAAI;GAC1B;CACF,MAAM,WAAW,OAAO,GAAG;AAC3B,KAAI,YAAY,EAEd,QAAO;EAAE,MAAM;EAAQ,qBAAqB;EAAG;CAEjD,MAAM,YAAY,WAAW;CAC7B,IAAI,MAAM,OAAO;AACjB,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,IACjC,KAAI,OAAO,GAAG,QAAQ,WAAW;AAC/B,QAAM;AACN;;AAGJ,QAAO;EACL,MAAM,OAAO,MAAM,GAAG,IAAI;EAC1B,qBAAqB,OAAO,SAAS;EACtC;;AAOH,SAAS,cAAc,KAAqB;CAM1C,IAAI,UALU;EACZ,GAAG,IAAI;EACP,IAAI;EACJ,GAAG,IAAI;EACR,CACmB,KAAK,KAAK;AAC9B,KAAI,OAAO,WAAW,SAAS,OAAO,GAAG,mBAAmB;EAE1D,MAAM,MAAM,OAAO,KAAK,SAAS,OAAO;EACxC,MAAM,UAAU,KAAK,OAAO,oBAAoB,MAAM,EAAE;AACxD,YACE,IAAI,MAAM,GAAG,QAAQ,CAAC,SAAS,OAAO,GACtC,4BACA,IAAI,MAAM,IAAI,SAAS,QAAQ,CAAC,SAAS,OAAO;;AAEpD,QAAO;;AAOT,eAAsB,WACpB,UACA,gBAC6B;CAC7B,MAAM,KAAK,KAAK,KAAK;CAErB,MAAM,WAAW,eAAe,SAAS;AACzC,KAAI,SAAU,OAAM,IAAI,MAAM,SAAS;CAEvC,MAAM,KAAK,kBAAkB,SAAS,UAAU;AAChD,KAAI,CAAC,GAAG,MAAM,CAAC,GAAG,UAChB,OAAM,IAAI,MAAM,GAAG,SAAS,8BAA8B;CAG5D,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,iBAAiB,SAAS,cAAc;CAC9C,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,eAAe,KAAK,IACxB,SAAS,iBAAiB,uBAC1B,kBACD;CAQD,MAAM,YACJ,SAAS,UAAU,OAAO,yBAAyB,SAAS,MAAM;CACpE,MAAM,mBAAmB,YACrB,sBAAsB,UAAU,GAChC;CAKJ,MAAM,KAAK,IAAI,iBAAiB;CAChC,MAAM,mBAAyB,GAAG,MAAM,WAAW;AACnD,KAAI,eACF,KAAI,eAAe,QAAS,IAAG,MAAM,WAAW;KAC3C,gBAAe,iBAAiB,SAAS,YAAY,EAAE,MAAM,MAAM,CAAC;CAE3E,MAAM,YAAY,iBAAiB,GAAG,MAAM,UAAU,EAAE,aAAa;AACrE,WAAU,OAAO;CAEjB,IAAIC;CACJ,IAAIC;AACJ,KAAI;AACF,iBAAe,gBAAgB;UACxB,KAAK;AACZ,eAAa,UAAU;AACvB,MAAI,eAAgB,gBAAe,oBAAoB,SAAS,WAAW;AAC3E,QAAM;;CAGR,MAAM,OAAO,YAAY;EACvB;EACA,UAAU,SAAS;EACnB;EACA,OAAO,SAAS;EAChB;EACD,CAAC;CAEF,IAAIC;AACJ,KAAI;AACF,UAAQ,MAAM,aAAa,QAAQ,MAAM;GACvC,KAAK,GAAG;GACR,OAAO;GACP,OAAO;IAAC;IAAU;IAAQ;IAAO;GAClC,CAAC;UACK,KAAK;AACZ,eAAa,UAAU;AACvB,MAAI,eAAgB,gBAAe,oBAAoB,SAAS,WAAW;AAC3E,QAAM,IAAI,MAAM,4BAA6B,IAAc,UAAU;;CAOvE,MAAM,kBAAkB,KAAK;CAC7B,IAAI,cAAc;CAClB,IAAI,aAAa;AACjB,KAAI,MAAM,QAAQ;AAChB,QAAM,OAAO,YAAY,OAAO;AAChC,QAAM,OAAO,GAAG,SAAS,UAAkB;AACzC,kBAAe,MAAM;AACrB,OAAI,WAAW,SAAS,gBACtB,eAAc,aAAa,OAAO,MAAM,GAAG,gBAAgB;AAE7D,OAAI,cAAc,OAAO,KAEvB,IAAG,MAAM,aAAa;IAExB;;CASJ,IAAIC,WAA0B;CAC9B,MAAM,cAAc,IAAI,SAAe,YAAY;AACjD,QAAM,GAAG,UAAU,SAAS;AAC1B,cAAW;AACX,YAAS;IACT;GACF;AAEF,KAAI;AACF,gBAAc,MAAM,kBAAkB,OAAO;GAC3C;GACA;GACA,QAAQ,GAAG;GACZ,CAAC;WACM;AACR,eAAa,UAAU;AACvB,MAAI,eAAgB,gBAAe,oBAAoB,SAAS,WAAW;AAC3E,MAAI,CAAC,MAAM,OAAQ,WAAU,MAAM;;AAIrC,KAAI,GAAG,OAAO,WAAW,YAAY,KAAK,WAAW,GAAG;EACtD,MAAM,SAAS,OAAO,GAAG,OAAO,UAAU,UAAU;AACpD,QAAM,IAAI,MAAM,wBAAwB,OAAO,GAAG;;AAcpD,KAAI,CAAC,GAAG,OAAO,QACb,OAAM;AAER,KACE,aAAa,QACb,aAAa,KACb,aAAa,KACb,CAAC,GAAG,OAAO,WACX,YAAY,KAAK,WAAW,GAC5B;EACA,MAAM,UAAU,WAAW,MAAM;EACjC,MAAM,SACJ,QAAQ,SAAS,IACb,QAAQ,QAAQ,YAAY,GAAG,CAAC,MAAM,GAAG,IAAI,GAC7C,4BAA4B;AAClC,QAAM,IAAI,MAAM,gBAAgB,SAAS;;CAI3C,IAAIC;CACJ,IAAIC;CACJ,IAAIC,SAAwB;AAC5B,KAAI,SAAS,UAAU;EACrB,MAAM,cAAc,SAAS,SAAS,MAAM;EAG5C,MAAM,QAAQ,WAAW,YAAY,MAAM,YAAY;AACvD,QAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;EACvC,MAAM,OACJ,mBAAmB,SAAS,uBAAuB;EAIrD,MAAM,6BAAa,IAAI,KAAqB;AAC5C,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK,QAAQ,IAC3C,YAAW,IAAI,YAAY,KAAK,IAAI,EAAE;EAOxC,MAAM,aAAa,MAAM,kBAAkB;GACzC,YANiB,MAChB,MAAM,GAAG,KAAK,IAAI,MAAM,MAAM,OAAO,CAAC,CACtC,KAAK,QAAQ;IAAE,KAAK,GAAG;IAAK,OAAO,WAAW,IAAI,GAAG,IAAI,IAAI;IAAI,EAAE,CACnE,QAAQ,MAAM,EAAE,SAAS,EAAE;GAI5B,eAAe,GAAG;GAClB;GACA,UAAU;GACV,QAAQ,GAAG;GACZ,CAAC;AACF,WAAS,WAAW;EAUpB,MAAM,SAAS,cALD,WACZ,YAAY,MACZ,aACA,WAAW,oBACZ,CACkC;AACnC,SAAO,OAAO,KAAK,MAAM,GAAG,MAAM;AAClC,wBAAsB,OAAO;OAG7B,QAAO,YAAY,KAAK,KAAK,OAAO;EAClC,KAAK;EACL,OAAO;EACP,qBAAqB,EAAE;EACxB,EAAE;CAQL,MAAMC,UAAgC,KAAK,KAAK,OAAO;EACrD,IAAI,OAAO,GAAG,IAAI;AAClB,MAAI,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW,MAAM,CACjD,QAAO,KAAK,MAAM,EAAE;AAEtB,SAAO,KAAK,QAAQ,OAAO,IAAI;EAC/B,MAAMC,UAAyB;GAC7B;GACA,MAAM,GAAG,IAAI;GACb,SAAS,cAAc,GAAG,IAAI;GAC9B,kBAAkB,CAAC,GAAG,IAAI,aAAa,GAAG,IAAI,UAAU;GACzD;AACD,MAAI,SAAS,UAAU;AACrB,WAAQ,QAAQ,OAAO,GAAG,MAAM;AAChC,WAAQ,sBAAsB;IAC5B,YAAY,OAAO,GAAG,oBAAoB,cAAc,EAAE;IAC1D,gBAAgB,OAAO,GAAG,oBAAoB,kBAAkB,EAAE;IAClE,WAAW,OAAO,GAAG,oBAAoB,aAAa,EAAE;IACxD,SAAS,OAAO,GAAG,oBAAoB,WAAW,EAAE;IACrD;QAED,SAAQ,sBAAsB;AAEhC,SAAO;GACP;CAEF,MAAM,aAAa,KAAK,KAAK,GAAG;CAIhC,MAAM,WAAW,QAAQ,IAAI,gCAAgC;AAC7D,SAAQ,KACN,sBAAsB,KAAK,cAAc,eAAe,aACzC,YAAY,UAAU,SAAS,EAAE,WACnC,QAAQ,OAAO,aAAa,YAAY,UAAU,iBAC5C,YAAY,aAAa,cAAc,WAAW,SAC1D,YAAY,UAAU,MAAM,aAAa,OAAO,UAC/C,SAAS,QAAQ,UAC1B,WAAW,WAAW,SAAS,MAAM,eAAe,GAAG,UAAU,KAAK,IAC1E;AAED,QAAO;EACL;EACA,WAAW,YAAY;EACvB,uBAAuB,SAAS,WAAW,sBAAsB;EACjE,eAAe,YAAY;EAC3B;EACA,SACE,SAAS,WACL;GACE,WAAW;GACX,UAAU;GACV,IAAI;GACL,GACD,EAAE,WAAW,0BAA0B;EAC7C;EACD;;AAGH,SAAS,OAAO,GAAmB;AACjC,QAAO,KAAK,MAAM,IAAI,IAAM,GAAG;;;;;ACt4DjC,MAAM,YAAY,EAAE,OAAO;CACzB,SAAS,EAAE,QAAQ,MAAM;CACzB,IAAI,EAAE,QAAQ,CAAC,UAAU;CACzB,QAAQ,EACL,OAAO;EACN,SAAS,EACN,MAAM,EAAE,OAAO;GAAE,MAAM,EAAE,QAAQ,OAAO;GAAE,MAAM,EAAE,QAAQ;GAAE,CAAC,CAAC,CAC9D,UAAU;EACb,SAAS,EAAE,SAAS,CAAC,UAAU;EAChC,CAAC,CACD,UAAU;CACb,OAAO,EACJ,OAAO;EAAE,MAAM,EAAE,QAAQ;EAAE,SAAS,EAAE,QAAQ;EAAE,CAAC,CACjD,UAAU;CACd,CAAC;AAEF,MAAM,cAAc,EAAE,OAAO;CAC3B,MAAM,EAAE,OAAO;EACb,OAAO,EAAE,QAAQ;EAIjB,aAAa,EACV,MACC,EAAE,OAAO,EACP,cAAc,EACX,OAAO;GAAE,OAAO,EAAE,QAAQ;GAAE,KAAK,EAAE,QAAQ;GAAE,CAAC,CAC9C,UAAU,EACd,CAAC,CACH,CACA,UAAU,CACV,UAAU;EACd,CAAC;CACF,eAAe,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,UAAU,CAAC,UAAU;CAC1D,CAAC;AAEF,MAAM,0BAA0B;AAChC,IAAIC,mBAAkC,EAAE;AAMxC,IAAIC,gBAA+B,QAAQ,SAAS;AAEpD,eAAe,iBAAgC;CAC7C,MAAM,SAAS,cAAc,KAAK,YAAY;EAC5C,MAAM,MAAM,KAAK,KAAK;AACtB,qBAAmB,iBAAiB,QAAQ,MAAM,MAAM,IAAI,IAAK;AACjE,MAAI,iBAAiB,UAAU,yBAAyB;GACtD,MAAM,SAAS,OAAQ,MAAM,iBAAiB;AAC9C,OAAI,SAAS,GAAG;AACd,YAAQ,MAAM,oCAAoC,OAAO,IAAI;AAC7D,UAAM,MAAM,OAAO;;;AAGvB,mBAAiB,KAAK,KAAK,KAAK,CAAC;GACjC;AACF,iBAAgB,OAAO,YAAY,GAEjC;AACF,QAAO;;AAGT,SAAS,WAAW,KAAsC;AACxD,KAAI,CAAC,MAAM,YACT,OAAM,IAAI,MACR,mLACD;CAMH,MAAMC,UAAkC;EACtC,eAAe,UAAU,MAAM;EAC/B,gBAAgB;EAChB,QAAQ;EACR,cAAc;EACd,kBAAkB;EAClB,wBAAwB;EACxB,cAAc,qBAAqB,eAAe,MAAM;EACzD;AACD,KAAI,IAAK,SAAQ,oBAAoB;AACrC,QAAO;;AAGT,eAAe,QACb,MACA,KACA,QAAQ,MACW;CACnB,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC;CACrC,MAAM,MAAM,MAAM,MAAM,KAAK;EAC3B,QAAQ;EACR,SAAS,WAAW,IAAI;EACxB,MAAM,KAAK,UAAU,KAAK;EAC3B,CAAC;AACF,KAAI,CAAC,IAAI,MAAM,SAAS,IAAI,UAAU,KAAK;AACzC,QAAM,MAAM,IAAI;AAChB,SAAO,QAAQ,MAAM,KAAK,MAAM;;AAElC,QAAO;;AAGT,eAAsB,UAAU,OAAyC;AACvE,OAAM,gBAAgB;AACtB,SAAQ,KAAK,sBAAsB,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;CAEzD,MAAM,SAAS,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAc;CACxD,IAAIC;AAEJ,KAAI;EAEF,MAAM,UAAU,MAAM,QAAQ;GAC5B,SAAS;GACT,IAAI;GACJ,QAAQ;GACR,QAAQ;IACN,iBAAiB;IACjB,cAAc,EAAE;IAGhB,YAAY;KACV,MAAM;KACN,SAAS,eAAe,MAAM;KAC/B;IACF;GACF,CAAC;AACF,MAAI,CAAC,QAAQ,IAAI;AACf,WAAQ,MAAM,yBAAyB,QAAQ,OAAO;AACtD,SAAM,IAAI,UAAU,yBAAyB,QAAQ;;AAEvD,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,IAAI;AAC/C,MAAI,CAAC,IACH,OAAM,IAAI,UACR,iDACA,QACD;EAIH,MAAM,WAAW,MAAM,QACrB;GAAE,SAAS;GAAO,QAAQ;GAA6B,EACvD,IACD;AACD,MAAI,CAAC,SAAS,MAAM,SAAS,WAAW,KAAK;AAC3C,WAAQ,MAAM,wCAAwC,SAAS,OAAO;AACtE,SAAM,IAAI,UAAU,wCAAwC,SAAS;;EAIvE,MAAM,UAAU,MAAM,QACpB;GACE,SAAS;GACT,IAAI;GACJ,QAAQ;GACR,QAAQ;IACN,MAAM;IACN,WAAW,EAAE,OAAO;IACrB;GACF,EACD,IACD;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAQ,MAAM,yBAAyB,QAAQ,OAAO;AACtD,SAAM,IAAI,UAAU,yBAAyB,QAAQ;;EAGvD,IAAIC;AACJ,aAAW,MAAM,MAAM,OAAO,QAAQ,EAAE;AACtC,OAAI,CAAC,GAAG,KAAM;GACd,IAAIC;AACJ,OAAI;AACF,iBAAa,KAAK,MAAM,GAAG,KAAK;WAC1B;AACN;;GAEF,MAAM,SAAS,UAAU,UAAU,WAAW;AAC9C,OAAI,OAAO,WAAW,OAAO,KAAK,OAAO,QAAQ;AAC/C,UAAM,OAAO;AACb;;;AAGJ,MAAI,CAAC,IACH,OAAM,IAAI,UACR,yDACA,QACD;AAEH,MAAI,IAAI,MACN,OAAM,IAAI,UACR,aAAa,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,WAC1C,QACD;AAEH,MAAI,IAAI,QAAQ,QACd,OAAM,IAAI,UAAU,6BAA6B,QAAQ;EAG3D,MAAM,OAAO,IAAI,QAAQ,UAAU,IAAI;AACvC,MAAI,CAAC,KACH,OAAM,IAAI,UAAU,iCAAiC,QAAQ;EAG/D,IAAIC;AACJ,MAAI;AACF,cAAW,KAAK,MAAM,KAAK;WACpB,KAAK;AACZ,SAAM,IAAI,UACR,2CAA2C,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,IAC3F,QACD;;EAKH,MAAM,cAAc,YAAY,UAAU,SAAS;AACnD,MAAI,CAAC,YAAY,QACf,OAAM,IAAI,UACR,gDAAgD,YAAY,MAAM,OAC/D,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,UAAU,CAC/C,KAAK,KAAK,CAAC,IACd,QACD;EAEH,MAAM,QAAQ,YAAY;EAE1B,MAAMC,aAAoD,EAAE;AAC5D,OAAK,MAAM,OAAO,MAAM,KAAK,eAAe,EAAE,EAAE;GAC9C,MAAM,OAAO,IAAI;AACjB,OAAI,QAAQ,CAAC,KAAK,IAAI,aAAa,CAAC,SAAS,kBAAkB,CAC7D,YAAW,KAAK;IAAE,OAAO,KAAK;IAAO,KAAK,KAAK;IAAK,CAAC;;AAIzD,UAAQ,MAAM,uBAAuB,WAAW,OAAO,aAAa;AACpE,SAAO;GAAE,SAAS,MAAM,KAAK;GAAO;GAAY;WACxC;AACR,MAAI,IAKF,KAAI;AACF,GAAK,MAAM,GAAG,eAAe,MAAM,CAAC,OAAO;IACzC,QAAQ;IACR,SAAS,WAAW,IAAI;IACzB,CAAC,CAAC,YAAY,GAEb;UACI;;;;;;;;;;;;;;;;;;;;;;;;AC7Nd,MAAa,gBAAgB;CAAC;CAAO;CAAU;CAAQ;CAAQ;AAG/D,SAAgB,SAAS,GAAyB;AAChD,QAAO,OAAO,MAAM,YAAa,cAAwC,SAAS,EAAE;;AA2CtF,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAyBpB,MAAM;AAER,MAAM,sBAAsB;;;;;;;EAO1B,MAAM;AAER,MAAM,cAAc;;;;EAIlB,oBAAoB;;EAEpB;AAEF,MAAM,qBAAqB;;;;;;;;;EASzB,oBAAoB;;EAEpB;AAEF,MAAM,gBAAgB;;;;EAIpB,oBAAoB;;;;;;;;;;;;;;;;;;AAmBtB,MAAM,mBAAmB;;;;EAIvB,oBAAoB;;;;;;;;;;;;;;;;;;;;;AAsBtB,MAAM,mBAAmB;;;;;;EAMvB,oBAAoB;;EAEpB;AAEF,MAAaC,gBAA4C,OAAO,OAAO;CACrE;EACE,WAAW;EACX,cAAc;EACd,OAAO;EACP,UAAU;EACV,aACE;EACF,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,cAAc;EACd,gBAAgB;GAAC;GAAO;GAAU;GAAQ;GAAQ;EAClD,eAAe;EAChB;CACD;EACE,WAAW;EACX,cAAc;EACd,OAAO;EACP,UAAU;EACV,aACE;EACF,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,gBAAgB;GAAC;GAAO;GAAU;GAAO;EACzC,eAAe;EAChB;CACD;EACE,WAAW;EACX,cAAc;EACd,OAAO;EACP,UAAU;EACV,aACE;EACF,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,cAAc;EACd,gBAAgB;GAAC;GAAO;GAAU;GAAQ;GAAQ;EAClD,eAAe;EAChB;CACD;EACE,WAAW;EACX,cAAc;EACd,OAAO;EACP,UAAU;EACV,aACE;EACF,kBAAkB;EAClB,aAAa;EACb,cAAc;EAMd,cAAc;EACd,gBAAgB;GAAC;GAAO;GAAU;GAAQ;GAAQ;EAClD,eAAe;EAChB;CACF,CAAC;AAEF,MAAaC,iBAA6C,OAAO,OAAO,CACtE;CACE,WAAW;CACX,cAAc;CACd,OAAO;CACP,UAAU;CACV,aACE;CACF,kBAAkB;CAClB,aAAa;CACb,cAAc;CACd,cAAc;CAEd,gBAAgB;EAAC;EAAO;EAAU;EAAQ;EAAQ;CAClD,eAAe;CAChB,CACF,CAAC;;;;;;;;;;;;;;;AAgBF,SAAgB,iBACd,SACA,MACQ;CACR,MAAM,WAAW,KAAK,YAAY,CAAC,QAAQ;CAC3C,MAAM,WAAW,WACb,0BACA,yBAAyB,QAAQ;CAErC,MAAM,kBAAkB,WACpB;EACE,uBAAuB,SAAS;EAChC;EACA,mBAAmB,QAAQ,MAAM;EACjC;EACA,GAAI,QAAQ,eACR,CACE,sCACA,wCACD,GACD,CAAC,+BAA6B;EACnC,CAAC,KAAK,KAAK,GACZ;EACE,uBAAuB,SAAS;EAChC;EACA;EACA;EACD,CAAC,KAAK,KAAK;AAEhB,QAAO;EACL,eAAe,QAAQ;EACvB;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4Bd,SAAgB,0BAA0B,MAG/B;CACT,MAAMC,aAA4B,CAChC,4BACA,mCACD;AACD,KAAI,KAAK,gBACP,YAAW,KAAK,mCAAmC;AAErD,YAAW,KAAK,2BAA2B;CAE3C,MAAM,iBAAiB,KAAK,WACxB,6GACA;AAEJ,QAAO;EACL;EACA;EACA,8DAA8D,WAAW,KACvE,KACD,CAAC,uNAAuN,eAAe;EACzO,CAAC,KAAK,KAAK;;;AAId,SAAgB,YAAY,MAGL;CACrB,MAAMC,SAA6B,EAAE;AACrC,MAAK,MAAM,KAAK,eAAe;AAK7B,MAAI,EAAE,yBAAyB,CAAC,KAAK,gBAAiB;AACtD,SAAO,KAAK,EAAE;;AAEhB,KAAI,KAAK,SACP,MAAK,MAAM,KAAK,eAAgB,QAAO,KAAK,EAAE;AAEhD,QAAO;;AAwCT,MAAM,yBACJ;;;;;;;;;;;AAYF,SAAS,sBAAsB,SAGpB;AACT,KAAI,QAAQ,WAAW,WAAW,EAAG,QAAO,QAAQ;CACpD,MAAM,WAAW,QAAQ,WACtB,KAAK,MAAM,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CACtC,KAAK,KAAK;AACb,QAAO,GAAG,QAAQ,QAAQ,qBAAqB;;AAGjD,MAAaC,wBACX,OAAO,OAAO,CACZ;CACE,cAAc;CACd,aAAa;CACb,aAAa;EACX,MAAM;EACN,UAAU,CAAC,QAAQ;EACnB,sBAAsB;EACtB,YAAY,EACV,OAAO;GACL,MAAM;GACN,aACE;GACH,EACF;EACF;CAaD,MAAM,QACJ,MACA,SAIC;EACD,MAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC5D,MAAI,CAAC,MACH,QAAO;GACL,SAAS,CACP;IACE,MAAM;IACN,MAAM;IACP,CACF;GACD,SAAS;GACV;AAEH,MAAI;AAEF,UAAO,EACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,sBAHV,MAAM,UAAU,MAAM,CAGkB;IAAE,CACvD,EACF;WACM,KAAK;AAEZ,UAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,sBAFtB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAEG,CAAC;IAC9D,SAAS;IACV;;;CAGN,EACD;CAcE,cAAc;CACd,aACE;CAcF,aAAa;EACX,MAAM;EACN,UAAU,CAAC,SAAS,YAAY;EAChC,sBAAsB;EACtB,YAAY;GACV,OAAO;IACL,MAAM;IACN,aACE;IAMH;GACD,WAAW;IACT,MAAM;IACN,aACE;IACH;GACD,MAAM;IACJ,MAAM;IACN,MAAM;KAAC;KAAU;KAAW;KAAQ;IACpC,aACE;IAKH;GACD,WAAW;IACT,MAAM;IACN,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,aAAa;IACd;GACD,YAAY;IACV,MAAM;IACN,MAAM,CAAC,QAAQ,OAAO;IACtB,aACE;IAOH;GACF;EACF;CACD,MAAM,QACJ,MACA,QAIC;AACD,MAAI;GACF,MAAM,SAAS,MAAM,WACnB;IACE,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;IACrD,WACE,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;IACxD,MACE,KAAK,SAAS,aAAa,KAAK,SAAS,WACzC,KAAK,SAAS,WACV,KAAK,OACL;IACN,WACE,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;IACxD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;IACrD,YACE,KAAK,eAAe,UAAU,KAAK,eAAe,SAC9C,KAAK,aACL;IACP,EACD,OACD;GAcD,MAAM,iBAAiB,MAAM;GAC7B,MAAMC,cAID,EAAE;GACP,IAAI,aAAa;GACjB,IAAI,aAAa;AACjB,QAAK,MAAM,OAAO,OAAO,SAAS;IAChC,MAAM,OAAO;KACX,MAAM,IAAI;KACV,MAAM,IAAI;KACV,SAAS,IAAI;KACd;IACD,MAAM,YAAY,OAAO,WAAW,KAAK,UAAU,KAAK,EAAE,OAAO;AACjE,QAAI,YAAY,SAAS,KAAK,aAAa,YAAY,gBAAgB;AACrE,kBAAa;AACb;;AAEF,gBAAY,KAAK,KAAK;AACtB,kBAAc;;GAGhB,MAAMC,UAIF;IACF,SAAS;IACT,WAAW,OAAO,aAAa;IAChC;AAMD,OAAI,WACF,SAAQ,SACN,kCAAkC,YAAY,OAAO,UAChD,KAAK,MAAM,aAAa,KAAK,CAAC;YAE5B,OAAO,OAAO,WAAW,SAClC,SAAQ,SAAS,OAAO;AAE1B,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,KAAK,UAAU,QAAQ;IAAE,CAAC,EAC3D;WACM,KAAK;AAEZ,UAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,uBAFtB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAEI,CAAC;IAC/D,SAAS;IACV;;;CAGN,CACF,CAAC;;;;;;;;;;;;;;;;;ACjrBJ,SAAgB,uBACd,MACiB;AACjB,KAAI,CAAC,KAAK,UAAW,QAAO;AAC5B,KAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,IAAI;EACzC,MAAM,SAAS,KAAK,WAAW,UAC3B,sBAAsB,KAAK,UAAU,QAAQ,8BAC7C;AACJ,UAAQ,KACN,6BAA6B,OAAO,2FACrC;AACD,SAAO;;AAET,QAAO;;;;;;;;;;;;AAwCT,SAAgB,mBACd,WACA,MACe;CACf,MAAMC,aAA2D,EAC/D,mBAAmB;EACjB,MAAM;EACN,KAAK,GAAG,UAAU;EAClB,SAAS,EACP,eAAe,UAAU,KAAK,SAC/B;EACF,EACF;AAED,KAAI,KAAK,SACP,YAAW,eAAe;EACxB,SAAS;EACT,MAAM,CAAC,cAAc,GAAG,8BAA8B,UAAU,CAAC;EACjE,KAAK;GACH,iBAAiB,GAAG,UAAU;GAC9B,gBAAgB;GAChB,YAAY,KAAK;GAClB;EACF;AAGH,QAAO,EAAE,YAAY;;;;;;;;;;;;;;;;;;;AAyBvB,SAAS,sBAAsB,MAGa;CAO1C,MAAMC,QAAuB,CAAC,gBAAgB,cAAc;AAC5D,KAAI,KAAK,gBAAiB,OAAM,KAAK,gBAAgB;AACrD,OAAM,KAAK,iBAAiB;AAiE5B,QAAO;EAAE,aA9DP;EA8DoB,QA1DP;GACb;GACA;GACA;GACA;GANkB,MAAM,KAAK,MAAM,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK;GAQ3D;GACA;GACA;GACA;GACA;GACA,2EACK,KAAK,kBAAkB,qCAAqC,MAC7D;GACJ,sEACK,KAAK,kBAAkB,8DAA8D,MACtF;GACJ,gFACK,KAAK,kBAAkB,KAAK,0FAC7B;GACJ,oEACK,KAAK,kBAAkB,KAAK,uCAC7B;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,KAAK,KAAK;EAEkB;;;;;;;;;;;AAYhC,SAAgB,0BACd,MACsB;CACtB,MAAMC,MAA4B,EAAE;CACpC,MAAM,WAAW,YAAY;EAC3B,UAAU,KAAK;EACf,iBAAiB,KAAK;EACvB,CAAC;AACF,MAAK,MAAM,WAAW,SACpB,KAAI,QAAQ,aAAa;EACvB,aAAa,QAAQ;EACrB,QAAQ,iBAAiB,SAAS,EAAE,UAAU,KAAK,UAAU,CAAC;EAC/D;AAEH,KAAI,6BAA6B,sBAAsB;EACrD,UAAU,KAAK;EACf,iBAAiB,KAAK;EACvB,CAAC;AACF,QAAO;;;;;;;;;;;;;;AA4CT,SAAS,mBAA2B;AAClC,QAAO,KAAK,KAAK,MAAM,mBAAmB,SAAS;;;;;;;;;;;;;;;;AAiBrD,SAAS,iBAAiB,GAAmB;AAC3C,QACE,IACE,EACG,QAAQ,OAAO,OAAO,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM,CAKrB,QAAQ,sCAAsC,MAC7C,MAAM,EAAE,WAAW,EAAE,CAAC,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GACpD,CACJ;;;;;;;;;;AAYL,MAAM,mBAAmB;;AAGzB,SAAS,aAAa,MAAqE;AACzF,QAAO;EACL;EACA,SAAS,KAAK;EACd,gBAAgB,iBAAiB,KAAK,YAAY;EAClD;EACA;EACA,KAAK;EACL;EACD,CAAC,KAAK,KAAK;;;;;;;;;;;;;;;;;;;AAoBd,eAAsB,sBACpB,QACA,MACiE;AAQjE,MAAK,MAAMC,UAAQ,OAAO,KAAK,OAAO,CACpC,KAAI,CAAC,iBAAiB,KAAKA,OAAK,CAC9B,OAAM,IAAI,MACR,6CAA6C,KAAK,UAAUA,OAAK,CAAC,gBAChD,iBAAiB,SACpC;CAGL,MAAM,MAAM,KAAK,aAAa,kBAAkB;AAChD,OAAM,GAAG,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;CACxC,MAAMC,QAAuB,EAAE;AAC/B,KAAI;AACF,OAAK,MAAM,CAACD,QAAM,QAAQ,OAAO,QAAQ,OAAO,EAAE;GAChD,MAAM,WAAW,KAAK,KAAK,KAAK,QAAQ,KAAK,WAAW,GAAGA,OAAK,KAAK;AAGrE,SAAM,GAAG,OAAO,SAAS,CAAC,YAAY,GAAG;AACzC,SAAM,uBACJ,UACA,aAAa;IAAE;IAAM,aAAa,IAAI;IAAa,QAAQ,IAAI;IAAQ,CAAC,CACzE;AACD,SAAM,KAAK,SAAS;;UAEf,KAAK;AAKZ,QAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;AACxD,QAAM;;CAER,MAAM,UAAU,YAA2B;AACzC,QAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;;AAE1D,QAAO;EAAE;EAAO;EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgD3B,eAAsB,wBACpB,WACA,MAC8B;CAC9B,MAAM,MAAM,KAAK,mBAAmB,MAAM;CAC1C,MAAM,SAAS,KAAK,KAAK,KAAK,eAAe;CAM7C,IAAIE,WAAoC,EAAE;AAC1C,KAAI;EACF,MAAM,MAAM,MAAM,GAAG,SAAS,QAAQ,OAAO;AAC7C,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,OAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,YAAW;OAEX,SAAQ,KACN,4BAA4B,OAAO,gCACpB,OAAO,OAAO,4CAC9B;WAEI,KAAK;AACZ,WAAQ,KACN,yCAAyC,OAAO,oEAEhD,IACD;;UAEI,KAAK;AACZ,MAAK,IAA8B,SAAS,SAC1C,SAAQ,MACN,wCAAwC,OAAO,IAC/C,IACD;;CASL,IAAIC;CACJ,MAAM,aAAa,SAAS;AAC5B,KACE,eAAe,UACZ,eAAe,QACf,OAAO,eAAe,YACtB,CAAC,MAAM,QAAQ,WAAW,CAE7B,cAAa;MACR;AACL,MAAI,eAAe,UAAa,eAAe,KAC7C,SAAQ,KACN,gDAAgD,OAAO,4BACjC,OAAO,WAAW,8BACzC;AAEH,eAAa,EAAE;;CAKjB,MAAM,aAAa,mBAAmB,WAAW;EAC/C,UAAU,KAAK;EACf,iBAAiB,KAAK;EACtB,OAAO,KAAK;EACZ,WAAW,KAAK,aAAa,MAAM;EACpC,CAAC;CAMF,MAAMC,YAA2B,EAAE;AACnC,MAAK,MAAMJ,UAAQ,OAAO,KAAK,WAAW,WAAW,CACnD,KAAI,WAAWA,YAAU,OAAW,WAAU,KAAKA,OAAK;AAE1D,KAAI,UAAU,SAAS,GAAG;AACxB,UAAQ,KACN,8FACiC,UAAU,KAAK,KAAK,CAAC,oNAIvD;AACD,SAAO;GACL,IAAI;GACJ,QAAQ;GACR,oBAAoB;GACrB;;AAIH,MAAK,MAAM,CAACA,QAAM,UAAU,OAAO,QAAQ,WAAW,WAAW,CAC/D,YAAWA,UAAQ;AAErB,UAAS,aAAa;CAOtB,MAAM,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE,GAAG;AACxD,OAAM,GAAG,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;CACxC,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,IAAI,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC;AAC5E,KAAI;AACF,QAAM,GAAG,UAAU,UAAU,aAAa;GAAE,MAAM;GAAO,MAAM;GAAM,CAAC;AACtE,QAAM,GAAG,OAAO,UAAU,OAAO;UAC1B,KAAK;AACZ,QAAM,GAAG,OAAO,SAAS,CAAC,YAAY,GAAG;AACzC,QAAM;;AAGR,QAAO;EAAE,IAAI;EAAM,cAAc,OAAO,KAAK,WAAW,WAAW;EAAE;;;;;;;;;;;;;;AAevE,eAAsB,yBACpB,WACA,MAC8B;CAC9B,MAAM,QAAQ,KAAK,SAAS,YAAY,GAAG,CAAC,SAAS,MAAM;CAC3D,MAAM,aAAa,KAAK,cAAc,MAAM;CAC5C,MAAM,YAAY,KAAK,aAAa,MAAM;AAI1C,OAAM,GAAG,MAAM,YAAY,EAAE,WAAW,MAAM,CAAC;AAC/C,KAAI,QAAQ,aAAa,QACvB,OAAM,GAAG,MAAM,YAAY,IAAM,CAAC,YAAY,GAAG;CAInD,MAAM,aAAa,GAAG,QAAQ,IAAI,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM;CACnE,MAAM,gBAAgB,KAAK,KAAK,YAAY,YAAY,WAAW,OAAO;CAC1E,MAAM,aAAa,KAAK,KAAK,YAAY,eAAe,WAAW,OAAO;CAE1E,MAAM,YAAY,mBAAmB,WAAW;EAC9C,UAAU,KAAK;EACf,iBAAiB,KAAK;EACtB;EACA;EACD,CAAC;CACF,MAAM,SAAS,0BAA0B;EACvC,UAAU,KAAK;EACf,iBAAiB,KAAK;EACtB;EACA;EACD,CAAC;AAOF,OAAM,GAAG,OAAO,cAAc,CAAC,YAAY,GAAG;AAC9C,OAAM,GAAG,OAAO,WAAW,CAAC,YAAY,GAAG;AAE3C,OAAM,uBAAuB,eAAe,KAAK,UAAU,WAAW,MAAM,EAAE,CAAC;AAC/E,OAAM,uBAAuB,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;CAQzE,MAAM,WAAW,MAAM,sBAAsB,QAAQ;EACnD,WAAW,KAAK;EAChB;EACD,CAAC;CAEF,MAAM,WAAW,YAAY;EAC3B,UAAU,KAAK;EACf,iBAAiB,KAAK;EACvB,CAAC;CAEF,MAAM,UAAU,YAA2B;AACzC,QAAM,QAAQ,WAAW;GACvB,GAAG,OAAO,cAAc;GACxB,GAAG,OAAO,WAAW;GACrB,SAAS,SAAS;GACnB,CAAC;;AAGJ,QAAO;EACL;EACA;EACA,cAAc,SAAS;EACvB;EACA;EACA;EACD;;;;;ACppBH,MAAM,gBAAgB,OAAO;AAC7B,MAAM,YAAY;AAClB,MAAM,cAAc;AACpB,MAAM,oBAAoB;AAE1B,MAAM,gBACJ;AAEF,MAAM,gBAAgB,IAAI,IAAI;CAAC;CAAS;CAAS;CAAO,CAAC;AAEzD,SAAS,SAAS,MAAsB;AACtC,QAAO,KAAK,QAAQ,eAAe,aAAa;;AAGlD,SAAS,aAAa,KAAsB;AAC1C,KAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,KAAI,eAAe,OAAO;EACxB,MAAM,QAAQ,CAAC,IAAI,QAAQ;AAC3B,MAAI,IAAI,MAAO,OAAM,KAAK,IAAI,MAAM;AACpC,SAAO,MAAM,KAAK,KAAK;;AAEzB,QAAO,OAAO,IAAI;;AAGpB,SAAS,cAAc,QAA2B;AAWhD,QAAO,SAAS,GAVL,OAAO,KAAK,aAAa,CAUd,KATP,OAAO,QAAQ,SAAS,aAAa,CASpB,IARhB,OAAO,KACpB,KAAK,MAAM;EACV,MAAM,IAAI,aAAa,EAAE;AACzB,SAAO,EAAE,SAAS,cAAc,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM;GAChE,CACD,KAAK,IAAI,CACT,QAAQ,eAAe,MAAM,CAEY,IAAI;;AAGlD,SAAS,cAAc,QAA2B;CAChD,MAAM,WACJ,OAAO,KAAK,SAAS,IAAI,aAAa,OAAO,KAAK,GAAG,GAAG;CAC1D,MAAM,MAAM,GAAG,OAAO,KAAK,GAAG;AAC9B,QAAO,IAAI,SAAS,oBAChB,IAAI,MAAM,GAAG,kBAAkB,GAC/B;;AAGN,SAAS,eAAe,UAAwB;CAC9C,IAAIK;AACJ,KAAI;AACF,SAAOC,KAAG,SAAS,SAAS,CAAC;SACvB;AACN;;AAEF,KAAI,QAAQ,cAAe;AAE3B,KAAI;AACF,OAAG,WAAW,UAAU,WAAW,KAAK;SAClC;;AAKV,IAAa,kBAAb,MAAwD;CACtD,AAAiB;CACjB,AAAiB,uBAAO,IAAI,KAAa;CAEzC,YAAY,UAAkB;AAC5B,OAAK,WAAW;AAChB,iBAAe,SAAS;;CAG1B,IAAI,QAAmB,MAAyC;AAC9D,MAAI,CAAC,cAAc,IAAI,OAAO,KAAK,CAAE;EAErC,MAAM,MAAM,cAAc,OAAO;AACjC,MAAI,KAAK,KAAK,IAAI,IAAI,CAAE;AAExB,MAAI,KAAK,KAAK,QAAQ,UAAW,MAAK,KAAK,OAAO;AAClD,OAAK,KAAK,IAAI,IAAI;EAElB,MAAM,OAAO,cAAc,OAAO;EAalC,IAAIC;AACJ,MAAI;AACF,QAAKD,KAAG,SAAS,KAAK,UAAU,KAAK,IAAM;AAC3C,QAAG,UAAU,IAAI,KAAK;UAChB,WAEE;AACR,OAAI,OAAO,OACT,KAAI;AACF,SAAG,UAAU,GAAG;WACV;;;;AAQhB,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,QAAQ,WAAW,IAAI;AAAE,KAAI;GAAI,CAAC;;;;;;;;;;;AAY1E,SAAgB,oBAA0B;CACxC,MAAM,WAAW,IAAI,gBAAgB,MAAM,eAAe;AAC1D,SAAQ,QAAQ,WAAW;AAC3B,SAAQ,aAAa,CAAC,SAAS,CAAC;AAChC,SAAQ,QAAQ,SAAS;AACzB,SAAQ,QAAQ,SAAS;;;;;ACnI3B,MAAME,mBAA6C;CACjD,qBAAqB;CACrB,wBAAwB;CACxB,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CACjB;;;;;;;;;;AAWD,SAAgB,sBACd,SACA,QACS;CACT,MAAM,WAAW,iBAAiBC,WAASA;CAC3C,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,QAAQ;AAC9D,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,YAAY,MAAM;AACxB,KAAI,CAAC,aAAa,UAAU,WAAW,EAAG,QAAO;AAEjD,QAAO,UAAU,SAAS,SAAS;;;;;;AAOrC,SAAgB,oBACd,SACA,QACS;AACT,KAAI,sBAAsB,SAASA,OAAK,CAAE,QAAO;CAGjD,MAAM,aADQ,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,QAAQ,GACrC,uBAAuB,EAAE;AAElD,SAAQ,MACN,UAAU,QAAQ,qBAAqBA,OAAK,yBAClB,UAAU,KAAK,KAAK,GAC/C;AACD,QAAO;;;;;AAMT,SAAgB,sBAAsB,QAAwB;CAC5D,MAAM,WAAW,iBAAiBA,WAASA;AAG3C,SAFe,MAAM,QAAQ,QAAQ,EAAE,EAGpC,QAAQ,MAAM;EACb,MAAM,YAAY,EAAE;AACpB,MAAI,CAAC,aAAa,UAAU,WAAW,EAAG,QAAO;AACjD,SAAO,UAAU,SAAS,SAAS;GACnC,CACD,KAAK,MAAM,EAAE,GAAG;;;;;AClErB,SAAgB,mBAAyB;AACvC,KAAI,OAAO,QAAQ,YAAa;AAEhC,KAAI;EACF,MAAM,SAAS,IAAI,OAAO;EAC1B,MAAM,0BAAU,IAAI,KAAyB;AAmD7C,sBA7CmB;GACjB,SACE,SACA,SACA;AACA,QAAI;KACF,MAAM,SACJ,OAAO,QAAQ,WAAW,WACxB,IAAI,IAAI,QAAQ,OAAO,GACtB,QAAQ;KAIb,MAAM,MAHM,eAGI,OAAO,UAAU,CAAC;KAClC,MAAM,WAAW,OAAO,IAAI,SAAS,IAAI,MAAM;AAC/C,SAAI,CAAC,UAAU;AACb,cAAQ,MAAM,sBAAsB,OAAO,WAAW;AACtD,aAAQ,OAAiC,SAAS,SAAS,QAAQ;;KAErE,IAAI,QAAQ,QAAQ,IAAI,SAAS;AACjC,SAAI,CAAC,OAAO;AACV,cAAQ,IAAI,WAAW,SAAS;AAChC,cAAQ,IAAI,UAAU,MAAM;;KAE9B,IAAI,QAAQ;AACZ,SAAI;MACF,MAAM,IAAI,IAAI,IAAI,SAAS;AAC3B,cAAQ,GAAG,EAAE,SAAS,IAAI,EAAE;aACtB;AAGR,aAAQ,MAAM,qBAAqB,OAAO,SAAS,OAAO,QAAQ;AAClE,YAAQ,MAAgC,SAAS,SAAS,QAAQ;YAC5D;AACN,YAAQ,OAAiC,SAAS,SAAS,QAAQ;;;GAGvE,QAAQ;AACN,WAAO,OAAO,OAAO;;GAEvB,UAAU;AACR,WAAO,OAAO,SAAS;;GAE1B,CAEuD;AACxD,UAAQ,MAAM,mDAAmD;UAC1D,KAAK;AACZ,UAAQ,MAAM,wBAAwB,IAAI;;;;;;WC9DpC;cACG;;;;ACEb,MAAa,gBAAgB,YAAY;AAKvC,KAAI,CAJa,MAAM,QAAQ,OAAO,4BAA4B,EAChE,MAAM,WACP,CAAC,CAGA,OAAM,IAAI,UACR,4BACA,SAAS,KAAK,EAAE,SAAS,4BAA4B,EAAE,EAAE,QAAQ,KAAK,CAAC,CACxE;;;;;ACFL,MAAM,8BAA8B;AAOpC,IAAIC,iBAAgC,QAAQ,SAAS;AAErD,eAAsB,eAAe,SAAc;AACjD,KAAIC,QAAM,qBAAqB,OAAW;CAQ1C,MAAM,SAAS,EAAE,SAAS,OAAO;CAEjC,MAAM,SAAS,eAAe,WAAW,QAAQA,SAAO,OAAO,CAAC;AAChE,kBAAiB,OAAO,YAAY,GAElC;AAEF,QAAO,QAAQ,KAAK,CAClB,QACA,MAAM,4BAA4B,CAAC,WAAW;AAC5C,SAAO,UAAU;AACjB,QAAM,IAAI,UACR,kCACA,SAAS,KACP;GACE,MAAM;GACN,OAAO;IACL,MAAM;IACN,SAAS,6BAA6B,4BAA4B;IACnE;GACF,EACD,EAAE,QAAQ,KAAK,CAChB,CACF;GACD,CACH,CAAC;;AAGJ,eAAe,QACb,SACA,QACe;AACf,KAAIA,QAAM,qBAAqB,OAAW;AAC1C,KAAI,OAAO,QAAS;CAEpB,MAAM,MAAM,KAAK,KAAK;AAEtB,KAAI,CAACA,QAAM,sBAAsB;AAC/B,UAAM,uBAAuB;AAC7B;;CAGF,MAAM,kBAAkB,MAAMA,QAAM,wBAAwB;AAE5D,KAAI,iBAAiBA,QAAM,kBAAkB;AAC3C,UAAM,uBAAuB;AAC7B;;CAGF,MAAM,kBAAkB,KAAK,KAAKA,QAAM,mBAAmB,eAAe;AAE1E,KAAI,CAACA,QAAM,eAAe;AACxB,UAAQ,KACN,qCAAqC,gBAAgB,gBACtD;AACD,QAAM,IAAI,UACR,uBACA,SAAS,KAAK,EAAE,SAAS,uBAAuB,EAAE,EAAE,QAAQ,KAAK,CAAC,CACnE;;CAGH,MAAM,aAAa,kBAAkB;AACrC,SAAQ,KACN,+BAA+B,gBAAgB,+BAChD;AACD,OAAM,MAAM,WAAW;AAIvB,KAAI,OAAO,QAAS;AACpB,SAAM,uBAAuB,KAAK,KAAK;AACvC,SAAQ,KAAK,qDAAqD;;;;;;;;ACjFpE,SAAS,aAAa,GAAmB;AACvC,KAAI,KAAK,IAAW,QAAO,IAAI,IAAI,KAAW,QAAQ,EAAE,CAAC;AACzD,KAAI,KAAK,IAAO,QAAO,IAAI,IAAI,KAAO,QAAQ,EAAE,CAAC;AACjD,QAAO,OAAO,EAAE;;;;;AAMlB,SAAS,gBACP,aACA,cACA,OACoB;AACpB,KAAI,gBAAgB,OAAW,QAAO;CAEtC,MAAMC,QAAuB,EAAE;CAC/B,MAAM,YAAY,OAAO,cAAc,QAAQ;AAE/C,KAAI,WAAW;EACb,MAAM,OAAQ,cAAc,YAAa,KAAK,QAAQ,EAAE;AACxD,QAAM,KAAK,MAAM,aAAa,YAAY,CAAC,GAAG,aAAa,UAAU,CAAC,IAAI,IAAI,IAAI;OAElF,OAAM,KAAK,MAAM,aAAa,YAAY,GAAG;AAG/C,KAAI,iBAAiB,OACnB,OAAM,KAAK,OAAO,aAAa,aAAa,GAAG;AAGjD,QAAO,MAAM,KAAK,IAAI;;;;;;;;;;AAWxB,SAAgB,WACd,MACA,OACA,WACM;CACN,MAAMA,QAAuB,EAAE;AAE/B,OAAM,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAGzC,KAAI,KAAK,iBAAiB,KAAK,kBAAkB,KAAK,MACpD,OAAM,KAAK,GAAG,KAAK,MAAM,GAAG,KAAK,gBAAgB;UACxC,KAAK,iBAAiB,KAAK,MACpC,OAAM,KAAM,KAAK,iBAAiB,KAAK,MAAQ;CAIjD,MAAM,YAAY,gBAAgB,KAAK,aAAa,KAAK,cAAc,MAAM;AAC7E,KAAI,UACF,OAAM,KAAK,UAAU;AAIvB,KAAI,KAAK,WAAW,OAClB,OAAM,KAAK,OAAO,KAAK,OAAO,CAAC;CAIjC,MAAM,UAAU,KAAK,KAAK,GAAG;CAC7B,MAAM,WACJ,WAAW,MAAO,IAAI,UAAU,KAAM,QAAQ,EAAE,CAAC,KAAK,GAAG,QAAQ;AACnE,OAAM,KAAK,KAAK,YAAY,GAAG,SAAS,WAAW,SAAS;CAE5D,MAAM,OAAO,MAAM,KAAK,KAAK;AAE7B,KAAI,yBAAyB,MAAM,MAAM,CACvC,SAAQ,MAAM,cAAc,OAAO;KAEnC,SAAQ,KAAK,KAAK;;;;;;AAQtB,SAAS,yBACP,MACA,OACS;AACT,KAAI,CAAC,KAAK,aAAa,CAAC,MAAO,QAAO;AACtC,KAAI,CAAC,KAAK,UAAU,KAAK,SAAS,IAAK,QAAO;CAE9C,MAAM,MAAM,KAAK,UAAU,aAAa;AACxC,QACE,IAAI,SAAS,QAAQ,IACrB,IAAI,SAAS,UAAU,IACvB,IAAI,SAAS,WAAW,IACxB,IAAI,SAAS,aAAa,IAC1B,IAAI,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtC,SAAgB,iBAAiB,MAIxB;AACP,KAAI,QAAQ,IAAI,yBAAyB,IAAK;CAC9C,MAAM,WAAW,oBAAoB,KAAK,KAAK;CAC/C,MAAM,gBAAgB,qBAAqB,KAAK,KAAK;CACrD,MAAM,cAAc,KAAK,cAAc,IACpC,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,SAAQ,KACN,iBAAiB,KAAK,kBACN,SAAS,KAAK,IAAI,oBACZ,cAAc,KAAK,IAAI,gBAC3B,WAAW,KAAK,IAAI,GACvC;;AAGH,SAAS,oBAAoB,MAA8B;AACzD,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,QAAO,EAAE;AACvE,QAAO,OAAO,KAAK,KAAgC,CAAC,MAAM;;AAG5D,SAAS,qBAAqB,MAA8B;AAC1D,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,EAAE;CAChD,MAAM,QAAS,KAAiC;AAChD,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO,EAAE;CACpC,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,MACjB,KAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,CAC1D,MAAK,MAAM,KAAK,OAAO,KAAK,KAAgC,CAC1D,MAAK,IAAI,EAAE;AAIjB,QAAO,CAAC,GAAG,KAAK,CAAC,MAAM;;;;;ACrLzB,MAAMC,YAAU,IAAI,aAAa;;;;;;;;;;;;AAiCjC,SAAgB,wBAAwB,OAAyB;AAC/D,KAAI,EAAE,iBAAiB,OAAQ,QAAO;CACtC,MAAM,MAAM,MAAM,QAAQ,aAAa;AACvC,QACE,IAAI,SAAS,+BAA+B,IACzC,IAAI,SAAS,gCAAgC,IAC7C,IAAI,SAAS,2BAA2B,IACxC,IAAI,SAAS,mCAAmC,IAChD,IAAI,SAAS,oBAAoB,IACjC,IAAI,SAAS,2BAA2B,IACxC,IAAI,SAAS,mBAAmB;;;;;;;;;;;;;;;;;;;;;;AAwBvC,SAAgB,qBACd,MACA,MAC4B;CAC5B,MAAM,eAAe,KAAK,uBAAuB;CACjD,MAAM,SAAS,KAAK,WAAW;CAC/B,IAAI,eAAe;CACnB,IAAI,mBAAmB;CACvB,IAAI,oBAAoB;CAExB,MAAM,aAAa,eAA4D;AAC7E,MAAI;AACF,cAAW,OAAO;UACZ;;AAKV,QAAO,IAAI,eAA2B;EACpC,MAAM,KAAK,YAAY;AACrB,OAAI,qBAAqB,kBAAkB;AACzC,cAAU,WAAW;AACrB;;AAGF,OAAI;IACF,MAAM,SAAS,MAAM,0BAA0B,QAAQ,aAAa;AACpE,QAAI,mBAAmB;AAIrB,eAAU,WAAW;AACrB;;AAEF,QAAI,OAAO,MAAM;AAKf,SAAI,iBAAiB,EACnB,SAAQ,KACN,yCAAyC,KAAK,YAC/C;AAEH,wBAAmB;AACnB,eAAU,WAAW;AACrB;;AAEF,QAAI,OAAO,OAAO;AAChB,qBAAgB,OAAO,MAAM;AAC7B,SAAI;AACF,iBAAW,QAAQ,OAAO,MAAM;cACzB,cAAc;AACrB,UAAI,wBAAwB,aAAa,EAAE;AAKzC,2BAAoB;AACpB;;AAEF,YAAM;;;YAGH,OAAO;AACd,uBAAmB;AACnB,QAAI,mBAAmB;AAYrB,YAAO,OAAO,MAAM,CAAC,YAAY,GAE/B;AACF,eAAU,WAAW;AACrB;;IAEF,MAAM,UAAU,iBAAiB,QAAQ,MAAM,OAAO;IACtD,MAAM,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACzE,YAAQ,MACN,kCAAkC,KAAK,UAAU,UAAU,aAAa,WAAW,QAAQ,WAAW,KAAK,UAAU,WAAW,GACjI;IACD,MAAM,QAAQ,yBAAyB,SAAS,WAAW;AAC3D,QAAI;AACF,gBAAW,QAAQA,UAAQ,OAAO,MAAM,CAAC;aAClC,cAAc;AACrB,SAAI,CAAC,wBAAwB,aAAa,CACxC,SAAQ,KACN,gDAAgD,KAAK,UAAU,IAAI,wBAAwB,QAAQ,aAAa,UAAU,OAAO,aAAa,GAC/I;;AAQL,WAAO,OAAO,MAAM,CAAC,YAAY,GAE/B;AACF,cAAU,WAAW;;;EAGzB,OAAO,QAAQ;AACb,uBAAoB;AACpB,sBAAmB;AACnB,UAAO,OAAO,OAAO,CAAC,YAAY,GAEhC;;EAEL,CAAC;;AAGJ,eAAe,0BACb,QACA,WACgD;CAChD,IAAIC;CACJ,MAAM,iBAAiB,IAAI,SAAgB,GAAG,WAAW;AACvD,kBAAgB,iBAAiB;AAC/B,UACE,OAAO,uBAAO,IAAI,MAAM,oBAAoB,EAAE,EAC5C,MAAM,qBACP,CAAC,CACH;KACA,UAAU;GACb;AAMF,gBAAe,YAAY,GAAG;AAC9B,KAAI;AACF,SAAO,MAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,EAAE,eAAe,CAAC;WAClD;AACR,MAAI,kBAAkB,OAAW,cAAa,cAAc;;;;;;;;;AAUhE,SAAgB,yBACd,SACA,YACQ;CACR,MAAM,UAAU;EACd,MAAM;EACN,OAAO;GACL,MAAM,oBAAoB,QAAQ;GAClC,SAAS,gCAAgC,QAAQ,IAAI;GACtD;EACF;AACD,QAAO,uBAAuB,KAAK,UAAU,QAAQ,CAAC;;;;;;AAOxD,SAAgB,sBACd,SACA,YACQ;CACR,MAAM,UAAU,EACd,OAAO;EACL,MAAM,oBAAoB,QAAQ;EAClC,SAAS,gCAAgC,QAAQ,IAAI;EACtD,EACF;AACD,QAAO,SAAS,KAAK,UAAU,QAAQ,CAAC;;AAG1C,SAAS,oBAAoB,SAAyB;AAMpD,KAAI,YAAY,aAAc,QAAO;AACrC,KAAI,YAAY,oBAAqB,QAAO;AAC5C,QAAO;;AAGT,SAAgB,eACd,WACA,OACyC;CACzC,MAAM,UAAU,iBAAiB,QAAQ,MAAM,OAAO;CACtD,MAAM,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACzE,SAAQ,MACN,kCAAkC,UAAU,YAAY,QAAQ,WAAW,KAAK,UAAU,WAAW,GACtG;AACD,QAAO;EAAE;EAAS;EAAY;;;;;ACzQhC,MAAM,eAAe;CACnB,kBAAkB,OAAO;CACzB,mBAAmB,OAAO;CAC1B,iBAAiB,OAAO;CACxB,iBAAiB,OAAO;CACxB,iBAAiB,OAAO;CACzB;AAUD,MAAM,gCAAgB,IAAI,KAAsB;;;;AAKhD,MAAM,4BACJ,WACA,SACA,cACW;CACX,IAAI,SAAS;AACb,MAAK,MAAM,YAAY,WAAW;AAChC,YAAU,UAAU;AACpB,YAAU,QAAQ,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC;;AAErD,WAAU,UAAU;AACpB,QAAO;;;;;AAMT,MAAM,+BACJ,cACA,YACW;CACX,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,aACjB,KAAI,KAAK,SAAS,YAChB,WAAU,QAAQ,OAAO,KAAK,UAAU,IAAI,CAAC,SAAS;UAC7C,KAAK,KACd,WAAU,QAAQ,OAAO,KAAK,KAAK,CAAC;AAGxC,QAAO;;;;;AAMT,MAAM,0BACJ,SACA,SACA,cACW;CACX,MAAM,mBAAmB;CACzB,MAAM,gBAAgB;CACtB,IAAI,SAAS;AACb,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAClD,MAAI,OAAO,UAAU,SACnB,WAAU,QAAQ,OAAO,MAAM,CAAC;AAElC,MAAI,QAAQ,OACV,WAAU;AAEZ,MAAI,QAAQ,aACV,WAAU,yBACR,OACA,SACA,UACD;AAEH,MAAI,QAAQ,aAAa,MAAM,QAAQ,MAAM,CAC3C,WAAU,4BACR,OACA,QACD;;AAGL,QAAO;;;;;AAMT,MAAM,mBACJ,UACA,SACA,cACW;AACX,KAAI,SAAS,WAAW,EACtB,QAAO;CAET,IAAI,YAAY;AAChB,MAAK,MAAM,WAAW,SACpB,cAAa,uBAAuB,SAAS,SAAS,UAAU;AAGlE,cAAa;AACb,QAAO;;;;;AAMT,MAAM,wBAAwB,OAAO,aAAuC;AAC1E,KAAI,cAAc,IAAI,SAAS,EAAE;EAC/B,MAAM,SAAS,cAAc,IAAI,SAAS;AAC1C,MAAI,OACF,QAAO;;CAIX,MAAM,oBAAoB;AAC1B,KAAI,EAAE,qBAAqB,eAAe;EACxC,MAAM,iBAAkB,MAAM,aAAa,YAAY;AACvD,gBAAc,IAAI,UAAU,eAAe;AAC3C,SAAO;;CAGT,MAAM,iBAAkB,MAAM,aAAa,oBAAoB;AAC/D,eAAc,IAAI,UAAU,eAAe;AAC3C,QAAO;;;;;AAMT,MAAa,yBAAyB,UAAyB;AAC7D,QAAO,MAAM,cAAc,aAAa;;;;;AAM1C,MAAM,qBAAqB,UAAiB;AAC1C,QAAO,MAAM,OAAO,mBAAmB,MAAM,OAAO,UAChD;EACE,UAAU;EACV,UAAU;EACV,SAAS;EACT,UAAU;EACV,UAAU;EACV,SAAS;EACV,GACD;EACE,UAAU;EACV,UAAU;EACV,SAAS;EACT,UAAU;EACV,UAAU;EACV,SAAS;EACV;;;;;AAMP,MAAM,4BACJ,KACA,MACA,YAIW;CACX,MAAM,EAAE,SAAS,cAAc;CAC/B,IAAI,SAAS,UAAU;AAGvB,KAAI,OAAO,SAAS,YAAY,SAAS,KACvC,QAAO;CAIT,MAAM,QAAQ;CAOd,MAAM,YAAY;CAClB,MAAM,YAAY,MAAM,QAAQ;CAChC,IAAI,YAAY,MAAM,eAAe;AAGrC,KAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,EAAE;AAC3C,YAAU,UAAU;AACpB,OAAK,MAAM,QAAQ,MAAM,MAAM;AAC7B,aAAU,UAAU;AACpB,aAAU,QAAQ,OAAO,OAAO,KAAK,CAAC,CAAC;;;AAK3C,KAAI,UAAU,SAAS,IAAI,CACzB,aAAY,UAAU,MAAM,GAAG,GAAG;CAIpC,MAAM,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG;AAC1C,WAAU,QAAQ,OAAO,KAAK,CAAC;CAG/B,MAAM,eAAe,IAAI,IAAI;EAAC;EAAQ;EAAe;EAAO,CAAC;AAC7D,MAAK,MAAM,gBAAgB,OAAO,KAAK,MAAM,CAC3C,KAAI,CAAC,aAAa,IAAI,aAAa,EAAE;EACnC,MAAM,gBAAgB,MAAM;EAC5B,MAAM,eACJ,OAAO,kBAAkB,WAAW,gBAClC,KAAK,UAAU,cAAc;AAEjC,YAAU,QAAQ,OAAO,GAAG,aAAa,GAAG,eAAe,CAAC;;AAIhE,QAAO;;;;;AAMT,MAAM,6BACJ,YACA,SACA,cACW;AACX,KAAI,CAAC,cAAc,OAAO,eAAe,SACvC,QAAO;CAGT,MAAM,SAAS;CACf,IAAI,SAAS;AAEb,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,KAAI,QAAQ,cAAc;EACxB,MAAM,aAAa;AACnB,MAAI,OAAO,KAAK,WAAW,CAAC,SAAS,GAAG;AACtC,aAAU,UAAU;AACpB,QAAK,MAAM,WAAW,OAAO,KAAK,WAAW,CAC3C,WAAU,yBAAyB,SAAS,WAAW,UAAU;IAC/D;IACA;IACD,CAAC;;QAGD;EACL,MAAM,YACJ,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,MAAM;AAC3D,YAAU,QAAQ,OAAO,GAAG,IAAI,GAAG,YAAY,CAAC;;AAIpD,QAAO;;;;;AAMT,MAAM,uBACJ,MACA,SACA,cACW;CACX,IAAI,SAAS,UAAU;CACvB,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,KAAK;CACnB,IAAI,QAAQ,KAAK,eAAe;AAChC,KAAI,MAAM,SAAS,IAAI,CACrB,SAAQ,MAAM,MAAM,GAAG,GAAG;CAE5B,MAAM,OAAO,QAAQ,MAAM;AAC3B,WAAU,QAAQ,OAAO,KAAK,CAAC;AAC/B,KACE,OAAO,KAAK,eAAe,YACxB,KAAK,eAAe,KAEvB,WAAU,0BAA0B,KAAK,YAAY,SAAS,UAAU;AAE1E,QAAO;;;;;AAMT,MAAa,qBACX,OACA,SACA,cACW;CACX,IAAI,iBAAiB;AACrB,MAAK,MAAM,QAAQ,MACjB,mBAAkB,oBAAoB,MAAM,SAAS,UAAU;AAEjE,mBAAkB,UAAU;AAC5B,QAAO;;;;;AAMT,MAAa,gBAAgB,OAC3B,SACA,UAC+C;CAK/C,MAAM,UAAU,MAAM,sBAHJ,sBAAsB,MAAM,CAGQ;CAEtD,MAAM,qBAAqB,QAAQ;CACnC,MAAM,gBAAgB,mBAAmB,QACtC,QAAQ,IAAI,SAAS,YACvB;CACD,MAAM,iBAAiB,mBAAmB,QACvC,QAAQ,IAAI,SAAS,YACvB;CAED,MAAM,YAAY,kBAAkB,MAAM;CAC1C,IAAI,cAAc,gBAAgB,eAAe,SAAS,UAAU;AACpE,KAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,EAC1C,gBAAe,kBAAkB,QAAQ,OAAO,SAAS,UAAU;CAErE,MAAM,eAAe,gBAAgB,gBAAgB,SAAS,UAAU;AAExE,QAAO;EACL,OAAO;EACP,QAAQ;EACT;;;;;ACjVH,MAAa,wBAAwB,OACnC,SACA,cACA,iBACG;AACH,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;CAEnE,MAAM,eAAe,QAAQ,SAAS,MACnC,MACC,OAAO,EAAE,YAAY,YAClB,EAAE,SAAS,MAAM,QAAMC,IAAE,SAAS,YAAY,CACpD;CAID,MAAM,cAAc,QAAQ,SAAS,MAAM,QACzC,CAAC,aAAa,OAAO,CAAC,SAAS,IAAI,KAAK,CACzC;CAED,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC;CACrC,MAAM,gBAAmC;EAOvC,MAAMC,YAAyB;GAC7B,QAAQ;GACR,SAPsC;IACtC,GAAG,eAAe,OAAO,aAAa;IACtC,GAAG;IACH,eAAe,cAAc,UAAU;IACxC;GAIC,MAAM,KAAK,UAAU,QAAQ;GAC9B;EACD,MAAMC,UAA8B,EAAE;AACtC,MAAI,4BAA4B,EAC9B,SAAQ,KAAK,YAAY,QAAQ,0BAA0B,CAAC;AAE9D,MAAI,aAAc,SAAQ,KAAK,aAAa;AAC5C,MAAI,QAAQ,WAAW,EAAG,WAAU,SAAS,QAAQ;WAC5C,QAAQ,SAAS,EAAG,WAAU,SAAS,YAAY,IAAI,QAAQ;AACxE,SAAO,MAAM,KAAK,UAAU;;CAE9B,MAAM,WAAW,MAAM,mBAAmB,SAAS,oBAAoB;AAEvE,KAAI,CAAC,SAAS,IAAI;EAChB,IAAI,YAAY;AAChB,MAAI;AACF,eAAY,MAAM,SAAS,MAAM;UAC3B;AACN,eAAY;;EAEd,MAAM,eAAe,MAAM,QAAQ,KAChC,QAAQ,MAAM,EAAE,GAAG,WAAW,SAAS,CAAC,CACxC,KAAK,MAAM,EAAE,GAAG,CAChB,KAAK,KAAK,IAAI;AACjB,UAAQ,MACN,2BAA2B,QAAQ,MAAM,KAAK,SAAS,OAAO,GAAG,UAAU,6BAA6B,aAAa,GACtH;AAOD,QAAM,IAAI,UAAU,qCALE,IAAI,SAAS,WAAW;GAC5C,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB,SAAS,SAAS;GACnB,CAAC,CACqE;;AAGzE,KAAI,QAAQ,OACV,QAAO,OAAO,SAAS;AAGzB,QAAQ,MAAM,SAAS,MAAM;;;;;ACpD/B,MAAMC,YAAU,IAAI,aAAa;AAEjC,SAASC,YAAU,OAAiC;CAClD,MAAMC,QAAuB,EAAE;AAC/B,KAAI,MAAM,MAAO,OAAM,KAAK,UAAU,MAAM,QAAQ;AACpD,KAAI,MAAM,SAAS,OACjB,MAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,CAAC,MAAM,aAAa,CACvD,OAAM,KAAK,SAAS,OAAO;AAG/B,KAAI,MAAM,OAAO,OAAW,OAAM,KAAK,OAAO,OAAO,MAAM,GAAG,GAAG;AACjE,QAAO,MAAM,KAAK,KAAK,GAAG;;AAG5B,eAAsBC,mBAAiB,GAAY;CACjD,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAM,eAAe,MAAM;CAE3B,IAAI,UAAU,MAAM,EAAE,IAAI,MAA8B;CACxD,MAAM,eAAe,QAAQ,SAAS;AACtC,KAAI,aACF,SAAQ,MAAM,oBAAoB,KAAK,UAAU,QAAQ,CAAC,MAAM,KAAK,CAAC;AAGxE,KAAI,MAAM,cAAe,OAAM,eAAe;AAE9C,OAAMC,0BAAwB,QAAQ;CAGtC,MAAM,gBAAgB,QAAQ;CAC9B,MAAM,gBAAgB,aAAa,QAAQ,MAAM;AACjD,KAAI,kBAAkB,QAAQ,MAC5B,SAAQ,QAAQ;CAIlB,MAAM,gBAAgB,MAAM,QAAQ,KAAK,MACtC,UAAU,MAAM,OAAO,QAAQ,MACjC;AAED,qBAAoB,QAAQ,OAAO,oBAAoB;CAGvD,IAAIC;AACJ,KAAI;AACF,MAAI,cAEF,gBADmB,MAAM,cAAc,SAAS,cAAc,EACrC;SAErB;AAIR,KAAI,UAAU,QAAQ,WAAW,EAAE;AACjC,YAAU;GACR,GAAG;GACH,YAAY,eAAe,cAAc,QAAQ;GAClD;AACD,MAAI,aACF,SAAQ,MAAM,sBAAsB,KAAK,UAAU,QAAQ,WAAW,CAAC;;CAI3E,MAAM,WAAW,MAAM,sBAAsB,SAAS,eAAe,eAAe,CAAC,MACnF,OAAO,UAAmB;AACxB,MAAI,iBAAiB,WAAW;GAC9B,MAAM,YAAY,MAAM,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG;AACrE,cACE;IACE,QAAQ;IACR,MAAM,EAAE,IAAI;IACZ,OAAO;IACP;IACA,QAAQ,MAAM,SAAS;IACvB;IACD,EACD,eACA,UACD;;AAEH,QAAM;GAET;CACD,MAAM,cAAc,CAACC,iBAAe,SAAS;CAG7C,MAAM,eAAe,CAAC,cACjB,SAAoC,OAAO,oBAC5C;AAEJ,YACE;EACE,QAAQ;EACR,MAAM,EAAE,IAAI;EACZ,OAAO;EACP;EACA;EACA;EACA,QAAQ;EACR,WAAW;EACZ,EACD,eACA,UACD;AAED,KAAI,CAAC,aAAa;AAChB,MAAI,aACF,SAAQ,MAAM,2BAA2B,KAAK,UAAU,SAAS,CAAC;AAEpE,SAAO,EAAE,KAAK,SAAS;;CAMzB,MAAM,WAAY,SAChB,OAAO,gBACN;CACH,MAAM,cAAc,MAAM,SAAS,MAAM;AACzC,KAAI,YAAY,KACd,SAAQ,KACN,0DAA0D,EAAE,IAAI,OACjE;CAGH,IAAIC,oBAAkD,YAAY,OAC9D,SACA,YAAY;CAChB,IAAI,mBAAmB,YAAY;CACnC,IAAI,oBAAoB;CAExB,MAAM,aAAa,eAA4D;AAC7E,MAAI;AACF,cAAW,OAAO;UACZ;;CAIV,MAAM,mBAAmB,WAAqB;AAC5C,MAAI,OAAO,SAAS,WAAW,WAC7B,UAAS,OAAO,OAAO,CAAC,YAAY,GAElC;;CAGN,MAAM,eACJ,YACA,UACY;AACZ,MAAI;AACF,cAAW,QAAQ,MAAM;AACzB,UAAO;WACA,GAAG;AACV,OAAI,wBAAwB,EAAE,EAAE;AAC9B,wBAAoB;AAKpB,oBAAgB,EAAE;AAClB,WAAO;;AAET,SAAM;;;AAIV,QAAO,IAAI,SACT,IAAI,eAA2B;EAC7B,MAAM,KAAK,YAAY;AACrB,OAAI,qBAAqB,kBAAkB;AACzC,cAAU,WAAW;AACrB;;AAEF,OAAI,sBAAsB,QAAW;IACnC,MAAM,QAAQ;AACd,wBAAoB;AACpB,QAAI,aACF,SAAQ,MAAM,oBAAoB,KAAK,UAAU,MAAM,CAAC;AAE1D,gBAAY,YAAYP,UAAQ,OAAOC,YAAU,MAAM,CAAC,CAAC;AACzD;;AAEF,OAAI;IACF,MAAM,SAAS,MAAM,SAAS,MAAM;AACpC,QAAI,mBAAmB;AACrB,eAAU,WAAW;AACrB;;AAEF,QAAI,OAAO,MAAM;AACf,wBAAmB;AACnB,eAAU,WAAW;AACrB;;AAMF,QAAI,OAAO,UAAU,UAAa,OAAO,UAAU,KAAM;AACzD,QAAI,aACF,SAAQ,MAAM,oBAAoB,KAAK,UAAU,OAAO,MAAM,CAAC;AAEjE,gBAAY,YAAYD,UAAQ,OAAOC,YAAU,OAAO,MAAM,CAAC,CAAC;YACzD,OAAO;AACd,uBAAmB;AACnB,QAAI,mBAAmB;AAWrB,qBAAgB,MAAM;AACtB,eAAU,WAAW;AACrB;;IAEF,MAAM,EAAE,SAAS,eAAe,eAAe,EAAE,IAAI,MAAM,MAAM;AACjE,gBACE,YACAD,UAAQ,OAAO,sBAAsB,SAAS,WAAW,CAAC,CAC3D;AAID,oBAAgB,MAAM;AACtB,cAAU,WAAW;;;EAGzB,SAAS;AACP,uBAAoB;AACpB,sBAAmB;AACnB,oBAAiB;;EAEpB,CAAC,EACF;EACE,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,iBAAiB;GACjB,qBAAqB;GACrB,YAAY;GACb;EACF,CACF;;AAGH,MAAMM,oBACJ,aACuC,OAAO,OAAO,UAAU,UAAU;AAE3E,eAAeF,0BACb,SACe;AAMf,KAAI,CALiB,QAAQ,OAAO,MACjC,MACE,UAAU,KAAM,EAAyC,SAAS,gBAChE,EAAE,UAAU,SAAS,aAC3B,CACkB;CAInB,MAAM,QADgB,QAAQ,SAAS,MAAM,QAAQ,IAAI,SAAS,OAAO,GAC3C,SAAYI,mBAAiB,QAAQ,SAAS;AAE5E,KAAI,MACF,KAAI;EACF,MAAM,UAAU,MAAM,UAAU,MAAM;EACtC,MAAM,gBAAgB;GACpB;GACA,QAAQ;GACR;GACA,QAAQ,WAAW,KAAK,MAAM,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK;GACpE;GACD,CAAC,KAAK,KAAK;EAGZ,MAAM,YAAY,QAAQ,SAAS,MAAM,QAAQ,IAAI,SAAS,SAAS;AACvE,MAAI,UASF,WAAU,UAAU,GAAG,cAAc,MAPnC,OAAO,UAAU,YAAY,WAAW,UAAU,UAChD,MAAM,QAAQ,UAAU,QAAQ,GAChC,UAAU,QACP,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAO,UAAU,IAAI,EAAE,OAAO,GAAI,CACvC,KAAK,KAAK,GACb;MAGJ,SAAQ,SAAS,QAAQ;GACvB,MAAM;GACN,SAAS;GACV,CAAC;UAEG,OAAO;AACd,UAAQ,KAAK,kDAAkD,MAAM;;AAKzE,SAAQ,QAAQ,QAAQ,OAAO,QAC5B,MACC,EACG,UAAU,KAAM,EAAyC,SAAS,gBAChE,EAAE,UAAU,SAAS,cAE7B;AACD,KAAI,QAAQ,OAAO,WAAW,EAC5B,SAAQ,QAAQ;AAElB,KAAI,CAAC,QAAQ,MACX,SAAQ,cAAc;UAEtB,QAAQ,eACL,OAAO,QAAQ,gBAAgB,YAC/B,UAAU,QAAQ,eAClB,QAAQ,YAAY,SAAS,YAChC;EACA,MAAM,iBAAiB,QAAQ,YAAY,UAAU;AACrD,MACE,kBACG,CAAC,QAAQ,MAAM,MAAM,SAAS,KAAK,SAAS,SAAS,eAAe,CAEvE,SAAQ,cAAc;;;AAK5B,SAASA,mBAAiB,UAA8C;AAEtE,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,MAAM,SAAS;AACrB,MAAI,IAAI,SAAS,QAAQ;AACvB,OAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI;AAChD,OAAI,MAAM,QAAQ,IAAI,QAAQ,EAAE;IAC9B,MAAM,OAAO,IAAI,QAAQ,MAAM,MAAM,EAAE,SAAS,OAAO;AACvD,QAAI,QAAQ,UAAU,KAAM,QAAO,KAAK;;;;;;;;ACvWhD,MAAa,mBAAmB,IAAI,MAAM;AAE1C,iBAAiB,KAAK,KAAK,OAAO,MAAM;AACtC,KAAI;AACF,SAAO,MAAMC,mBAAiB,EAAE;UACzB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACVF,MAAa,mBAAmB,OAAO,YAA8B;AACnE,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;CAEnE,MAAM,WAAW,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC,cAAc;EAClE,QAAQ;EACR,SAAS,eAAe,MAAM;EAC9B,MAAM,KAAK,UAAU,QAAQ;EAC9B,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,+BAA+B,SAAS;AAE9E,QAAQ,MAAM,SAAS,MAAM;;;;;ACP/B,MAAa,kBAAkB,IAAI,MAAM;AAEzC,gBAAgB,KAAK,KAAK,OAAO,MAAM;AACrC,KAAI;EAEF,MAAM,WAAW,MAAM,iBADP,MAAM,EAAE,IAAI,MAAwB,CACJ;AAEhD,SAAO,EAAE,KAAK,SAAS;UAChB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;;;;;;;;;;;;;;;;;;;;ACUF,SAAS,aACP,cACwB;AACxB,QAAO;EACL,GAAG,eAAe,MAAM;EACxB,QAAQ;EACR,iBAAiB;EACjB,sBAAsB;EACtB,eAAe;EACf,qBAAqB;EACrB,oBAAoB,YAAY;EAChC,GAAG;EACJ;;;;;;;;;;;;AAaH,eAAsB,eACpB,MACA,cACA,cACmB;AACnB,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;CAEnE,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC;AACrC,SAAQ,MAAM,iBAAiB,MAAM;CAGrC,MAAM,gBAAmC;EAEvC,MAAMC,YAAyB;GAAE,QAAQ;GAAQ,SADjC,aAAa,aAAa;GACgB;GAAM;EAChE,MAAMC,UAA8B,EAAE;AACtC,MAAI,4BAA4B,EAC9B,SAAQ,KAAK,YAAY,QAAQ,0BAA0B,CAAC;AAE9D,MAAI,aAAc,SAAQ,KAAK,aAAa;AAC5C,MAAI,QAAQ,WAAW,EAAG,WAAU,SAAS,QAAQ;WAC5C,QAAQ,SAAS,EAAG,WAAU,SAAS,YAAY,IAAI,QAAQ;AACxE,SAAO,MAAM,KAAK,UAAU;;CAE9B,MAAM,WAAW,MAAM,mBAAmB,SAAS,eAAe;AAElE,KAAI,CAAC,SAAS,IAAI;EAChB,IAAI,YAAY;AAChB,MAAI;AACF,eAAY,MAAM,SAAS,MAAM;UAC3B;AACN,eAAY;;AAEd,UAAQ,MACN,+BAA+B,SAAS,OAAO,GAAG,YACnD;AAMD,QAAM,IAAI,UAAU,mCALE,IAAI,SAAS,WAAW;GAC5C,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB,SAAS,SAAS;GACnB,CAAC,CACmE;;AAGvE,QAAO;;;;;;;;;AAUT,eAAsB,YACpB,MACA,cACA,cACmB;AACnB,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;CAEnE,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC;AACrC,SAAQ,MAAM,iBAAiB,MAAM;CAErC,MAAM,gBAAmC;EAEvC,MAAMD,YAAyB;GAAE,QAAQ;GAAQ,SADjC,aAAa,aAAa;GACgB;GAAM;EAChE,MAAMC,UAA8B,EAAE;AACtC,MAAI,4BAA4B,EAC9B,SAAQ,KAAK,YAAY,QAAQ,0BAA0B,CAAC;AAE9D,MAAI,aAAc,SAAQ,KAAK,aAAa;AAC5C,MAAI,QAAQ,WAAW,EAAG,WAAU,SAAS,QAAQ;WAC5C,QAAQ,SAAS,EAAG,WAAU,SAAS,YAAY,IAAI,QAAQ;AACxE,SAAO,MAAM,KAAK,UAAU;;CAE9B,MAAM,WAAW,MAAM,mBACrB,SACA,4BACD;AAED,KAAI,CAAC,SAAS,IAAI;EAChB,IAAI,YAAY;AAChB,MAAI;AACF,eAAY,MAAM,SAAS,MAAM;UAC3B;AACN,eAAY;;AAEd,UAAQ,MACN,+BAA+B,SAAS,OAAO,GAAG,YACnD;AAMD,QAAM,IAAI,UAAU,uCALE,IAAI,SAAS,WAAW;GAC5C,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB,SAAS,SAAS;GACnB,CAAC,CACuE;;AAG3E,QAAO;;;;;AC/IT,MAAa,kBAAkB,OAC7B,SACA,cACA,iBACG;AACH,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;CAEnE,MAAM,eAAe,aAAa,QAAQ,MAAM;CAEhD,MAAM,cAAc,gBAAgB,QAAQ,MAAM;CAElD,MAAM,MAAM,GAAG,eAAe,MAAM,CAAC;CACrC,MAAM,gBAAmC;EAMvC,MAAMC,YAAyB;GAC7B,QAAQ;GACR,SAPsC;IACtC,GAAG,eAAe,OAAO,aAAa;IACtC,GAAG;IACH,eAAe,cAAc,UAAU;IACxC;GAIC,MAAM,KAAK,UAAU,QAAQ;GAC9B;EACD,MAAMC,UAA8B,EAAE;AACtC,MAAI,4BAA4B,EAC9B,SAAQ,KAAK,YAAY,QAAQ,0BAA0B,CAAC;AAE9D,MAAI,aAAc,SAAQ,KAAK,aAAa;AAC5C,MAAI,QAAQ,WAAW,EAAG,WAAU,SAAS,QAAQ;WAC5C,QAAQ,SAAS,EAAG,WAAU,SAAS,YAAY,IAAI,QAAQ;AACxE,SAAO,MAAM,KAAK,UAAU;;CAE9B,MAAM,WAAW,MAAM,mBAAmB,SAAS,aAAa;AAEhE,KAAI,CAAC,SAAS,IAAI;EAMhB,IAAIC;AACJ,MAAI;AACF,cAAW,MAAM,SAAS,OAAO,CAAC,MAAM;UAClC;AACN,cAAW;;AAEb,UAAQ,MACN,oCAAoC,SAAS,OAAO,GAAG,SAAS,WAAW,QAC/D,IAAI,WAAW,SAAS,MAAM,GAAG,IAAK,GACnD;AACD,QAAM,IAAI,UAAU,8BAA8B,SAAS;;AAG7D,KAAI,QAAQ,OACV,QAAO,OAAO,SAAS;AAGzB,QAAQ,MAAM,SAAS,MAAM;;AAG/B,SAAS,aAAa,OAA2C;AAC/D,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO;AAElC,QAAO,MAAM,MAAM,SAAS;AAC1B,MAAI,aAAa,QAAQ,MAAM,QAAQ,KAAK,QAAQ,CAClD,QAAO,KAAK,QAAQ,MACjB,SAAkC,KAAK,SAAS,cAClD;AAEH,SAAO;GACP;;AAGJ,SAAS,gBAAgB,OAA2C;AAClE,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO;AAElC,QAAO,MAAM,MAAM,SAAS;AAC1B,MAAI,UAAU,QAAQ,KAAK,SAAS,YAAa,QAAO;AACxD,MACE,UAAU,SACN,KAAK,SAAS,mBAAmB,KAAK,SAAS,wBAEnD,QAAO;AAET,SAAO;GACP;;;;;ACpEJ,MAAM,uBAAuB;AAC7B,MAAM,cAAc;AACpB,MAAM,iBAAiB;;;;;;;;;;;;;AAmBvB,MAAM,0BAA0B;AAChC,IAAI,oBAAoB;;;;;;;;;;;;;;;;;AAkBxB,MAAM,iCAAiB,IAAI,KAAuC;AAelE,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAE3B,SAAS,SACP,IACA,MACA,SACA,MACoE;AACpE,QAAO;EACL,SAAS;EACT,IAAI,MAAM;EACV,OAAO,SAAS,SAAY;GAAE;GAAM;GAAS,GAAG;GAAE;GAAM;GAAS;GAAM;EACxE;;AAGH,SAAS,UACP,IACA,QACgE;AAChE,QAAO;EAAE,SAAS;EAAO,IAAI,MAAM;EAAM;EAAQ;;AAGnD,SAAS,eAAe,MAA0C;AAChE,KAAI,CAAC,KAAM,QAAO;CAGlB,MAAM,MAAM,KAAK,YAAY,IAAI;CACjC,MAAM,WAAW,OAAO,IAAI,KAAK,MAAM,GAAG,IAAI,GAAG;AACjD,QAAO,aAAa,eAAe,aAAa;;;;;;AAOlD,SAAS,aAAa,UAAkB,UAA2B;AACjE,KAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;CAChD,MAAM,IAAI,OAAO,KAAK,SAAS;CAC/B,MAAM,IAAI,OAAO,KAAK,SAAS;AAC/B,KAAI;AACF,SAAO,gBAAgB,GAAG,EAAE;SACtB;AACN,SAAO;;;AAIX,SAAS,UAAU,GAA6E;AAI9F,KAAI,CAAC,eAAe,EAAE,IAAI,OAAO,OAAO,CAAC,CACvC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,QAAQ;EAAqC;CAKhF,MAAM,WAAW,MAAM;AACvB,KAAI,CAAC,SACH,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,QAAQ;EAA0C;CAErF,MAAMC,SAAO,EAAE,IAAI,OAAO,gBAAgB,IAAI;CAC9C,MAAM,IAAI,mBAAmB,KAAKA,OAAK;AACvC,KAAI,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,SAAS,CACrC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,QAAQ;EAA2C;AAEtF,QAAO,EAAE,IAAI,MAAM;;AAGrB,SAAS,kBAA2B;CAClC,MAAM,SAAS,MAAM,QAAQ;AAC7B,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,OAAO,MAAM,MAAM,oBAAoB,KAAK,EAAE,GAAG,CAAC;;AAG3D,SAAS,iBAAqC;AAK5C,QAAO,cAAc,QAAQ,MAAM,CAAC,EAAE,yBAAyB,iBAAiB,CAAC;;AAGnF,SAAS,cAAgC;CACvC,MAAMC,iBAAmC,gBAAgB,CAAC,KAAK,OAAO;EACpE,MAAM,EAAE;EACR,aAAa,EAAE;EACf,aAAa;GACX,MAAM;GACN,UAAU,CAAC,SAAS;GACpB,sBAAsB;GACtB,YAAY;IACV,QAAQ;KACN,MAAM;KACN,aAAa;KACd;IACD,SAAS;KACP,MAAM;KACN,aACE;KACH;IACD,QAAQ;KACN,MAAM;KAKN,MAAM,CAAC,GAAG,EAAE,eAAe;KAC3B,aACE,oBAAoB,EAAE,eAAe,KAAK,MAAM,CAAC,cAAc,EAAE,cAAc,kFAE5E,EAAE,aAAa,yBACd,kGACA;KACP;IACF;GACF;EACF,EAAE;CAKH,MAAMC,oBAAsC,sBAAsB,KAC/D,OAAO;EACN,MAAM,EAAE;EACR,aAAa,EAAE;EACf,aAAa,EAAE;EAChB,EACF;AACD,QAAO,CAAC,GAAG,gBAAgB,GAAG,kBAAkB;;AAGlD,SAAS,cAAc,QAAgB,SAA0B;AAC/D,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,GAAG,OAAO,kCAAkC;;AAGrD,SAAS,qBAAqB,UAAwC;CACpE,MAAMC,MAAqB,EAAE;AAC7B,MAAK,MAAM,QAAQ,SAAS,QAAQ;AAClC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM;EAC/C,MAAM,MAAM;AACZ,MAAI,IAAI,SAAS,aAAa,IAAI,SAAS,YAAa;EACxD,MAAM,UAAU,IAAI;AACpB,MAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE;AAC7B,OAAK,MAAM,QAAQ,SAAS;AAC1B,OAAI,OAAO,SAAS,YAAY,SAAS,KAAM;GAC/C,MAAM,IAAI;AACV,QACG,EAAE,SAAS,iBAAiB,EAAE,SAAS,WACrC,OAAO,EAAE,SAAS,SAErB,KAAI,KAAK,EAAE,KAAK;;;AAItB,QAAO,IAAI,KAAK,GAAG;;AAGrB,SAAS,0BAA0B,UAA0C;CAC3E,MAAM,SAAS,SAAS,UAAU;AAClC,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,IAAI,OAAO,SAAS;AAC1B,QAAO,OAAO,MAAM,WAAW,IAAI;;AAoBrC,SAAS,oBAAoB,UAAuC;CAClE,MAAMA,MAAqB,EAAE;AAC7B,MAAK,MAAM,SAAS,SAAS,WAAW,EAAE,CACxC,KAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,SACjD,KAAI,KAAK,MAAM,KAAK;AAGxB,QAAO,IAAI,KAAK,GAAG;;AAQrB,SAAS,UAAU,SAAmC;AACpD,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;GAAS,CAAC;EAC1C,SAAS;EACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCH,MAAMC,kBAID;CACH;EAAE,UAAU;EAAgB,QAAQ;EAAQ,eAAe,IAAI;EAAM;CACrE;EAAE,UAAU;EAAkB,QAAQ;EAAQ,eAAe,KAAK;EAAM;CACxE;EAAE,UAAU;EAAe,QAAQ;EAAU,eAAe,IAAI;EAAM;CACvE;AAED,SAAS,iBACP,SACA,QACA,YAC0D;AAC1D,MAAK,MAAM,OAAO,gBAChB,KACE,IAAI,aAAa,QAAQ,gBACtB,IAAI,WAAW,UACf,aAAa,IAAI,cAEpB,QAAO;EAAE,SAAS;EAAM,UAAU,IAAI;EAAe;AAGzD,QAAO,EAAE,SAAS,OAAO;;;;;;;;;;;;;;;;AAiB3B,SAAS,qBAAqB,MAEhB;AACZ,KAAI,KAAK,OAAO,OAAW,QAAO;CAClC,MAAM,SAAU,KAAK,UAAU,EAAE;CACjC,MAAMC,SAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;CAC7D,MAAM,OAAQ,OAAO,aAAa,EAAE;CACpC,MAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;CAC/D,MAAM,UAAU,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;CAClE,MAAM,YAAY,KAAK;AACvB,KAAI,CAACA,UAAQ,CAAC,OAAQ,QAAO;CAC7B,MAAM,UAAU,gBAAgB,CAAC,MAAM,MAAM,EAAE,iBAAiBA,OAAK;AACrE,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,cAAc,UAAa,CAAC,SAAS,UAAU,CAAE,QAAO;CAC5D,MAAM,cAAc;AACpB,KACE,gBAAgB,UACb,CAAC,QAAQ,eAAe,SAAS,YAAY,CAEhD;CAEF,MAAMC,SAAiB,eAAe,QAAQ;CAC9C,MAAM,aAAa,OAAO,WAAW,cAAc,QAAQ,QAAQ,EAAE,OAAO;CAC5E,MAAM,UAAU,iBAAiB,SAAS,QAAQ,WAAW;AAC7D,KAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,QAAO,UACL,KAAK,IACL,UACE,wBAAwB,QAAQ,aAAa,aAAa,OAAO,QAC1D,WAAW,kFACa,QAAQ,SAAS,uNAIjD,CACF;;AAGH,eAAe,YACb,SACA,QACA,SACA,QACA,QACgF;CAIhF,MAAM,gBAAgB,aAAa,QAAQ,MAAM;CACjD,MAAM,WAAW,cAAc,QAAQ,QAAQ;AAgB/C,KAAI,QAAQ,aAAa,iBAAiB;EAqBxC,MAAMC,SAAO,qBALK,MAAM,gBAfU;GAChC,OAAO;GACP,cAAc,QAAQ;GACtB,OAAO,CACL;IACE,MAAM;IACN,SAAS,CAAC;KAAE,MAAM;KAAc,MAAM;KAAU,CAAC;IAClD,CACF;GACD,QAAQ;GAIR,WAAW,EAAE,QAAQ;GACtB,EAGC,QACA,OACD,CAC0C;AAC3C,MAAI,CAACA,OACH,QAAO,UAAU,WAAW,QAAQ,UAAU,0BAA0B;AAE1E,SAAO,EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC,EAAE;;AAG9C,KAAI,QAAQ,aAAa,gBAAgB;EAavC,MAAM,YACJ,WAAW,QAAQ,OACjB,WAAW,WAAW,OACtB,WAAW,SAAS,QACpB;EAWJ,MAAMA,SAAO,oBADC,OADG,MAAM,eARV,KAAK,UAAU;GAC1B,OAAO;GACP,YAAY;GACZ,QAAQ,QAAQ;GAChB,UAAU,EAAE,MAAM,YAAY;GAC9B,eAAe,EAAE,QAAQ;GACzB,UAAU,CAAC;IAAE,MAAM;IAAQ,SAAS;IAAU,CAAC;GAChD,CAAC,EAC0C,QAAW,OAAO,EACjC,MAAM,CACG;AACtC,MAAI,CAACA,OACH,QAAO,UAAU,WAAW,QAAQ,UAAU,0BAA0B;AAE1E,SAAO,EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC,EAAE;;CAsB9C,MAAM,OAAO,0BALK,MAAM,sBAbgB;EACtC,OAAO;EACP,UAAU,CACR;GAAE,MAAM;GAAU,SAAS,QAAQ;GAAkB,EACrD;GAAE,MAAM;GAAQ,SAAS;GAAU,CACpC;EACD,QAAQ;EAKR,kBAAkB;EACnB,EAGC,QACA,OACD,CAC+C;AAChD,KAAI,CAAC,KACH,QAAO,UAAU,WAAW,QAAQ,UAAU,0BAA0B;AAE1E,QAAO,EAAE,SAAS,CAAC;EAAE,MAAM;EAAQ;EAAM,CAAC,EAAE;;AAW9C,SAAS,aAAa,GAA2B;CAK/C,MAAM,QAAQ;EACZ;EACA,QAAQ,EAAE;EACV,SAAS,EAAE;EACX,eAAe,EAAE;EACjB,UAAU,EAAE;EACb;AACD,KAAI,EAAE,aAAc,OAAM,KAAK,SAAS,KAAK,UAAU,EAAE,aAAa,GAAG;AAEzE,SAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,GAAG,KAAK;;AAG9C,eAAe,gBACb,MACiB;CACjB,MAAM,SAAS,KAAK,UAAU,EAAE;CAChC,MAAMF,SAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;CAC7D,MAAM,OAAQ,OAAO,aAAa,EAAE;AAEpC,KAAI,CAACA,OACH,QAAO,SAAS,KAAK,IAAI,oBAAoB,0BAA0B;CAQzE,MAAM,UAAU,gBAAgB,CAAC,MAAM,MAAM,EAAE,iBAAiBA,OAAK;CACrE,MAAMG,iBAAgD,UAClD,SACA,sBAAsB,MAAM,MAAM,EAAE,iBAAiBH,OAAK;AAE9D,KAAI,CAAC,WAAW,CAAC,eACf,QAAO,SACL,KAAK,IACL,sBACA,6BAA6BA,OAAK,GACnC;CAMH,IAAII;CACJ,IAAIC;CACJ,IAAIC;AACJ,KAAI,SAAS;AAKX,MAAI,KAAK,WAAW,UAAa,CAAC,SAAS,KAAK,OAAO,CACrD,QAAO,SACL,KAAK,IACL,oBACA,+CAA+C,cAAc,KAAK,IAAI,CAAC,QAAQ,KAAK,UAAU,KAAK,OAAO,GAC3G;EAEH,MAAM,kBAAkB,KAAK;EAE7B,MAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAC/D,MAAI,CAAC,OACH,QAAO,SACL,KAAK,IACL,oBACA,2CACD;AAEH,kBAAgB;AAChB,mBAAiB,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAMnE,MACE,oBAAoB,UACjB,CAAC,QAAQ,eAAe,SAAS,gBAAgB,CAEpD,QAAO,SACL,KAAK,IACL,oBACA,wBAAwB,QAAQ,aAAa,4BAA4B,gBAAgB,cACzE,QAAQ,eAAe,KAAK,IAAI,CAAC,GAClD;AAEH,kBAAgB,mBAAmB,QAAQ;;AAe7C,KAAI,qBAAqB,wBAGvB,QAAO,UAAU,KAAK,IAAI;EACxB,SAAS,CACP;GACE,MAAM;GACN,MAAM,wBAAwB,wBAAwB;GACvD,CACF;EACD,SAAS;EACV,CAAC;AAGJ;CACA,MAAM,YAAY,KAAK,KAAK;CAK5B,MAAM,WACJ,KAAK,OAAO,UAAa,KAAK,OAAO,OAAO,KAAK,KAAK;CACxD,IAAIC;AACJ,KAAI,aAAa,QAAW;AAC1B,YAAU,IAAI,iBAAiB;AAC/B,iBAAe,IAAI,UAAU,QAAQ;;CAKvC,MAAM,gBAAgB,UAAU,QAAQ,YAAY,eAAgB;CACpE,MAAM,iBAAiB,UAAU,QAAQ,QAAQ;AACjD,KAAI;EACF,MAAM,SAAS,UACX,MAAM,YACJ,SACA,eACA,gBACA,eACA,SAAS,OACV,GACD,MAAM,eAAgB,QAAQ,MAAM,SAAS,OAAO;AACxD,eAAa;GACX,MAAM;GACN,OAAO;GACP,YAAY,KAAK,KAAK,GAAG;GACzB,QAAQ,OAAO,UAAU,YAAY;GACtC,CAAC;AACF,SAAO,UAAU,KAAK,IAAI,OAAO;UAC1B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,eAAa;GACX,MAAM;GACN,OAAO;GACP,YAAY,KAAK,KAAK,GAAG;GACzB,QAAQ;GACR,cAAc;GACf,CAAC;AASF,SAAO,UAAU,KAAK,IAAI;GACxB,SAAS,CACP;IACE,MAAM;IACN,MAAM,UACF,WAAW,QAAQ,UAAU,WAAW,YACxC,QAAQ,eAAgB,aAAa,WAAW;IACrD,CACF;GACD,SAAS;GACV,CAAC;WACM;AACR;AACA,MAAI,aAAa,OACf,gBAAe,OAAO,SAAS;;;;;;;;;AAWrC,SAAS,4BAA4B,MAA4B;CAE/D,MAAM,aADS,KAAK,UAAU,EAAE,EACsB;AACtD,KACE,cAAc,UACV,OAAO,cAAc,YAAY,OAAO,cAAc,UAC1D;AACA,UAAQ,MACN,+DAA+D,KAAK,UAAU,UAAU,GACzF;AACD;;CAEF,MAAM,UAAU,eAAe,IAAI,UAAU;AAC7C,KAAI,CAAC,QAGH;AAEF,SAAQ,sBAAM,IAAI,MAAM,gCAAgC,CAAC;;AAM3D,eAAe,UACb,IACA,MACkD;AAMlD,KACE,SAAS,QACN,OAAO,SAAS,YAChB,MAAM,QAAQ,KAAK,CAEtB,QAAO;EACL,QAAQ;EACR,MAAM,SAAS,MAAM,qBAAqB,gCAAgC;EAC3E;AAEH,KAAI,KAAK,YAAY,SAAS,OAAO,KAAK,WAAW,SACnD,QAAO;EACL,QAAQ;EACR,MAAM,SAAS,KAAK,MAAM,MAAM,qBAAqB,gCAAgC;EACtF;CAQH,MAAM,iBAAiB,KAAK,OAAO;AAEnC,SAAQ,KAAK,QAAb;EACE,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,UAAU,KAAK,IAAI;KACvB,iBAAiB;KAQjB,cAAc;MACZ,OAAO,EAAE,aAAa,OAAO;MAC7B,WAAW,EAAE;MACb,SAAS,EAAE;MACZ;KACD,YAAY;MAAE,MAAM;MAAa,SAAS;MAAgB;KAC3D,CAAC;IACH;EAEH,KAAK,4BAGH,QAAO;GAAE,QAAQ;GAAK,MAAM;GAAM;EAEpC,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,UAAU,KAAK,IAAI,EAAE,OAAO,aAAa,EAAE,CAAC;IACnD;EAEH,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,MAAM,gBAAgB,KAAK;IAClC;EAMH,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,UAAU,KAAK,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;IAC5C;EAEH,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,UAAU,KAAK,IAAI,EAAE,mBAAmB,EAAE,EAAE,CAAC;IACpD;EAEH,KAAK,kBAAkB;AACrB,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;GAGtD,MAAM,MAAO,KAAK,QAA0C;AAC5D,UAAO;IACL,QAAQ;IACR,MAAM,SACJ,KAAK,IACL,oBACA,2CACE,OAAO,QAAQ,WAAW,MAAM,0BAEnC;IACF;;EAGH,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,UAAU,KAAK,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IAC1C;EAEH,KAAK,eAAe;AAClB,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;GACtD,MAAMP,SAAQ,KAAK,QAA2C;AAC9D,UAAO;IACL,QAAQ;IACR,MAAM,SACJ,KAAK,IACL,oBACA,uCACE,OAAOA,WAAS,WAAWA,SAAO,2BAErC;IACF;;EAIH,KAAK;AAIH,+BAA4B,KAAK;AACjC,UAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;EAEpC,KAAK;AACH,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AAEtD,UAAO;IAAE,QAAQ;IAAK,MAAM,UAAU,KAAK,IAAI,EAAE,CAAC;IAAE;EAEtD;AACE,OAAI,eAAgB,QAAO;IAAE,QAAQ;IAAK,MAAM;IAAM;AACtD,UAAO;IACL,QAAQ;IACR,MAAM,SACJ,KAAK,IACL,sBACA,mBAAmB,KAAK,SACzB;IACF;;;AAIP,eAAsB,cAAc,GAA+B;CACjE,MAAML,SAAO,UAAU,EAAE;AACzB,KAAI,CAACA,OAAK,GACR,QAAO,EAAE,KACP,SAAS,MAAM,qBAAqBA,OAAK,OAAO,EAChDA,OAAK,OACN;CAGH,IAAIa;AACJ,KAAI;AACF,SAAQ,MAAM,EAAE,IAAI,MAAM;UACnB,KAAK;AACZ,UAAQ,MAAM,qBAAqB,IAAI;AACvC,SAAO,EAAE,KACP,SAAS,MAAM,iBAAiB,iCAAiC,EACjE,IACD;;AAcH,KACE,OAAO,SAAS,YACb,SAAS,QACT,CAAC,MAAM,QAAQ,KAAK,IACpB,KAAK,WAAW,gBAChB,mBAAmB,EAAE,IAAI,OAAO,SAAS,CAAC,CAE7C,QAAO,mBAAmB,KAAK;AAcjC,KACE,OAAO,SAAS,YACb,SAAS,QACT,CAAC,MAAM,QAAQ,KAAK,IACpB,KAAK,WAAW,cACnB;EACA,MAAM,YAAY,qBAAqB,KAAK;AAC5C,MAAI,UAAW,QAAO,EAAE,KAAK,WAAW,IAAI;;AAG9C,KAAI;EACF,MAAM,EAAE,QAAQ,MAAM,aAAa,MAAM,UAAU,GAAG,KAAK;AAC3D,MAAI,aAAa,KAAM,QAAO,EAAE,KAAK,MAAM,OAAc;AACzD,SAAO,EAAE,KAAK,UAAU,OAAc;UAC/B,KAAK;AACZ,UAAQ,MAAM,uBAAuB,IAAI;EAGzC,MAAM,SACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,KAAK,GAC5D,KAAwB,MAAM,OAC/B;AACN,SAAO,EAAE,KACP,SACE,QACA,oBACA,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACjD,EACD,IACD;;;;;;;;;;;;;;AAeL,SAAS,mBAAmB,QAAqC;AAC/D,KAAI,CAAC,OAAQ,QAAO;AAKpB,QAJe,OACZ,aAAa,CACb,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,CACvB,SAAS,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B7C,MAAM,4BAA4B;AAElC,eAAe,mBAAmB,MAAyC;CACzE,MAAM,UAAU,IAAI,aAAa;CAIjC,MAAM,cAAc,gBAAgB,KAAK;CAEzC,MAAM,SAAS,IAAI,eAA2B;EAC5C,MAAM,MAAM,YAAY;GACtB,IAAI,SAAS;GACb,MAAM,eAAe,UAA4B;AAC/C,QAAI,OAAQ;AACZ,QAAI;AACF,gBAAW,QAAQ,MAAM;aAClB,KAAK;AAKZ,aAAQ,MAAM,iDAAiD,IAAI;AACnE,cAAS;;;GAGb,MAAM,kBAAwB;AAC5B,QAAI,OAAQ;AACZ,aAAS;AACT,QAAI;AACF,gBAAW,OAAO;aACX,KAAK;AACZ,aAAQ,MAAM,+BAA+B,IAAI;;;GAGrD,MAAM,YAAY,eAChB,QAAQ,OAAO,yBAAyB,KAAK,UAAU,WAAW,CAAC,MAAM;GAC3E,MAAM,uBACJ,SAAS;IACP,SAAS;IACT,QAAQ;IACR,QAAQ;KACN,eAAe,KAAK,MAAM;KAC1B,UAAU;KACV,SAAS;KACV;IACF,CAAC;AAKJ,eAAY,gBAAgB,CAAC;GAC7B,MAAM,kBAAkB,kBAChB,YAAY,gBAAgB,CAAC,EACnC,0BACD;AAED,OAAI;AAEF,gBAAY,SADG,MAAM,YACO,CAAC;YACtB,KAAK;AACZ,YAAQ,MAAM,4BAA4B,IAAI;AAC9C,gBACE,SACE,SACE,KAAK,MAAM,MACX,oBACA,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACjD,CACF,CACF;aACO;AACR,kBAAc,gBAAgB;AAC9B,eAAW;;;EAGf,SAAS;GAOP,MAAM,WACJ,KAAK,OAAO,UAAa,KAAK,OAAO,OAAO,KAAK,KAAK;AACxD,OAAI,aAAa,QAAW;IAC1B,MAAM,UAAU,eAAe,IAAI,SAAS;AAC5C,QAAI,QAAS,SAAQ,sBAAM,IAAI,MAAM,iCAAiC,CAAC;;;EAG5E,CAAC;AAEF,QAAO,IAAI,SAAS,QAAQ;EAC1B,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,iBAAiB;GACjB,cAAc;GAId,qBAAqB;GACtB;EACF,CAAC;;AAGJ,SAAgB,gBAAgB,GAAsB;CAKpD,MAAMb,SAAO,UAAU,EAAE;AACzB,KAAI,CAACA,OAAK,GACR,QAAO,EAAE,KACP,SAAS,MAAM,qBAAqBA,OAAK,OAAO,EAChDA,OAAK,OACN;AAEH,QAAO,EAAE,KAAK,MAAM,IAAI;;;;;ACznC1B,MAAa,YAAY,IAAI,MAAM;AAEnC,UAAU,KAAK,KAAK,OAAO,MAAM;AAC/B,KAAI;AACF,SAAO,MAAM,cAAc,EAAE;UACtB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;AAEF,UAAU,OAAO,MAAM,MAAM;AAC3B,KAAI;AACF,SAAO,gBAAgB,EAAE;SACnB;AACN,SAAO,EAAE,KAAK,MAAM,IAAI;;EAE1B;;;;ACmCF,MAAMc,YAAU,IAAI,aAAa;;;AAIjC,MAAa,6BAA6B;;;;AAK1C,MAAa,2BAA2B;;;;AAKxC,MAAa,oBAAoB;;;;;;;;AASjC,MAAa,wBAAwB;AACrC,MAAa,yBAAyB;;;;AAOtC,MAAa,4BAA4B;;;;;;;;;;;;;;;;AAiBzC,MAAM,sBAAsB;;;;;;;;AAS5B,SAAgB,mBAAmB,eAA4C;AAC7E,KAAI,CAAC,cAAe,QAAO;AAC3B,KAAI,QAAQ,IAAI,qBAAsB,QAAO;AAC7C,QAAO,cACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,MAAM,MAAM,EAAE,WAAW,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;AAsB/C,SAAgB,kBAAkB,SAAyB;CACzD,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,SAAO;;CAET,MAAM,WAAW,MAAM,QAAQ,OAAO,MAAM,GAAG,OAAO,QAAQ,EAAE;CAEhE,MAAM,QAAQ,SAAS,QAAQ,MAAiB;AAC9C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;EAChD,MAAM,OAAQ,EAAgB;AAC9B,SAAO,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,WAAW;GAC/D;CACF,MAAM,WAAW,MAAM,WAAW,SAAS;CAC3C,MAAM,kBAAkB,MAAM,MAC3B,MAAiB,GAAG,SAAS,2BAC/B;AACD,KAAI,mBAAmB,CAAC,SACtB,QAAO;AAET,QAAO,QAAQ,kBACX,QACA,CACE,GAAG,OACH;EACE,MAAM;EACN,aAAa;EACb,cAAc;GACZ,MAAM;GACN,YAAY,EAAE;GACd,UAAU,EAAE;GACb;EACF,CACF;AACL,QAAO,KAAK,UAAU,OAAO;;;;;;;;;;AAW/B,MAAa,iCAAiC;;;;;;;;;;;;;;;;;AAkB9C,SAAgB,yBACd,cACA,WAAmB,gCACX;CACR,MAAMC,aAA4B,EAAE;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,MAAM,aAAa;EACzB,MAAM,OAAQ,IAAI,QAAmB;EACrC,MAAMC,QAAuB,CAAC,YAAY,IAAI,EAAE,KAAK,OAAO;EAC5D,MAAM,UAAU,IAAI;AACpB,MAAI,OAAO,YAAY,SACrB,OAAM,KAAK,QAAQ;WACV,MAAM,QAAQ,QAAQ,CAC/B,MAAK,MAAM,QAAQ,SAAS;AAC1B,OAAI,OAAO,SAAS,YAAY,SAAS,KAAM;GAC/C,MAAM,IAAI;AACV,OAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,SACzC,OAAM,KAAK,EAAE,KAAK;YACT,EAAE,SAAS,WACpB,OAAM,KACJ,aAAa,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,IAAI,KAAK,KAAK,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,GAC9E;YACQ,EAAE,SAAS,eAAe;IACnC,MAAM,IACJ,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,QAAQ;AACvE,UAAM,KAAK,gBAAgB,EAAE,eAAe,IAAI,MAAM,IAAI;SAE1D,OAAM,KAAK,IAAI,EAAE,KAAK,IAAI,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG;;AAInE,QAAM,KAAK,GAAG;AACd,aAAW,KAAK,MAAM,KAAK,KAAK,CAAC;;CAMnC,IAAI,aAAa;CACjB,IAAI,eAAe,WAAW;AAC9B,MAAK,IAAI,IAAI,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK;EAC/C,MAAM,MAAM,WAAW,GAAG,SAAS;AACnC,MAAI,aAAa,MAAM,SAAU;AACjC,gBAAc;AACd,iBAAe;;AAMjB,KAAI,iBAAiB,WAAW,UAAU,WAAW,SAAS,GAAG;EAG/D,MAAM,OAFO,WAAW,WAAW,SAAS,GAE1B,MAAM,EAAE,WADV,KAC8B;AAC9C,SACE,kGACuC,WAAW,OAAO,mBACvD;;CAIN,MAAM,OAAO,WAAW,MAAM,aAAa;AAC3C,KAAI,eAAe,EACjB,MAAK,QACH,eAAe,aAAa,gEACC,WAAW,SAAS,aAAa,qCAE/D;AAEH,QAAO,KAAK,KAAK,KAAK;;;;;;;;;;;;;;;;;;;AAoBxB,eAAe,WACb,cACA,cACA,eACiB;CACjB,MAAM,gBACJ;CASF,MAAM,mBAAmB,yBAAyB,aAAa;CAC/D,MAAM,uBAAuB,aAAa,aAAa;AAUvD,KAFqB,uBAAuB,KAAK,qBAAqB,EAEpD;EAiBhB,MAAM,WAAY,MAAM,gBAhBU;GAChC,OAAO;GACP,cAAc;GACd,OAAO,CACL;IACE,MAAM;IACN,SAAS,CAAC;KAAE,MAAM;KAAc,MAAM;KAAkB,CAAC;IAC1D,CACF;GACD,QAAQ;GAKR,WAAW,EAAE,QAAQ,eAAe;GACrC,CAC+C;EAChD,MAAMC,MAAqB,EAAE;AAC7B,OAAK,MAAM,QAAQ,SAAS,QAAQ;AAClC,OAAI,OAAO,SAAS,YAAY,SAAS,KAAM;GAC/C,MAAM,MAAM;AACZ,OAAI,IAAI,SAAS,aAAa,IAAI,SAAS,YAAa;GACxD,MAAM,UAAU,IAAI;AACpB,OAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE;AAC7B,QAAK,MAAM,QAAQ,SAAS;AAC1B,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM;IAC/C,MAAM,IAAI;AACV,SACG,EAAE,SAAS,iBAAiB,EAAE,SAAS,WACrC,OAAO,EAAE,SAAS,SAErB,KAAI,KAAK,EAAE,KAAK;;;EAItB,MAAMC,SAAO,IAAI,KAAK,GAAG;AACzB,MAAI,CAACA,OACH,OAAM,IAAI,MACR,iBAAiB,qBAAqB,kCACvC;AAEH,SAAOA;;CAcT,MAAM,OAAQ,OADG,MAAM,eAPH,KAAK,UAAU;EACjC,OAAO;EACP,YAAY;EACZ,QAAQ;EACR,UAAU,CAAC;GAAE,MAAM;GAAQ,SAAS;GAAkB,CAAC;EACvD,QAAQ;EACT,CAAC,EACiD,EAAE,CAAC,EACzB,MAAM;CAEnC,MAAM,QADS,MAAM,QAAQ,KAAK,QAAQ,GAAG,KAAK,UAAU,EAAE,EAE3D,QAAQ,MAAiB,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,SAAS,CACzE,KAAK,MAAiB,EAAE,KAAe,CACvC,KAAK,OAAO;AACf,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,iBAAiB,qBAAqB,0BAA0B;AAElF,QAAO;;;;;;;;;;;;;;AAqCT,SAAgB,wBACd,IACA,eACQ;CACR,MAAM,SAAS,GAAG,WAAW,SAAS,GAAG,GAAG,MAAM,EAAgB,GAAG;AACrE,KAAI,kBAAkB,KAAK,OAAO,CAAE,QAAO,YAAY;AACvD,QAAO,oBAAoB;;;;;;;;AAgD7B,SAAS,SAAS,MAAc,MAAyB;AACvD,QAAO,UAAU,KAAK,UAAU,KAAK,UAAU,KAAK,CAAC;;;;;;;;;;;;;;;;;AAkBvD,SAAgB,mBAAmB,MAOJ;CAC7B,MAAM,eAAe,KAAK,gBAAgB;CAC1C,MAAM,gBAAgB,KAAK,iBAAiB;AAE5C,QAAO,IAAI,eAA2B,EACpC,MAAM,MAAM,YAAY;EACtB,MAAM,eAAe,CAAC,GAAG,KAAK,oBAAoB;EAClD,IAAI,wBAAwB;EAC5B,IAAI,qBAAqB;EACzB,IAAI,WAAW;EAEf,MAAM,eAAe,UAA+B;AAClD,OAAI;AACF,eAAW,QAAQ,MAAM;AACzB,WAAO;YACA,KAAK;AACZ,QAAI,wBAAwB,IAAI,CAAE,QAAO;AACzC,UAAM;;;EAIV,MAAM,oBAAoB,MAAc,SACtC,YAAYL,UAAQ,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC;EAKnD,eAAe,eACb,UAIC;GACD,MAAMM,iBAAuC,EAAE;GAC/C,IAAIC,iBAAwC;GAG5C,MAAM,+BAAe,IAAI,KAA4B;AAErD,cAAW,MAAM,MAAM,OAAO,SAAS,EAAE;AACvC,QAAI,CAAC,GAAG,SAAS,CAAC,GAAG,KAAM;IAC3B,IAAIC;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,GAAG,KAAK;YACvB;AAGN,SAAI,CADO,YAAYR,UAAQ,OAAO,UAAU,GAAG,MAAM,UAAU,GAAG,KAAK,MAAM,CAAC,CACzE,QAAO;MAAE;MAAgB;MAAgB;AAClD;;AAGF,YAAQ,GAAG,OAAX;KACE,KAAK;AACH,UAAI,CAAC,uBAAuB;AAC1B,WAAI,CAAC,iBAAiB,GAAG,OAAO,QAAQ,CAAE,QAAO;QAAE;QAAgB;QAAgB;AACnF,+BAAwB;;AAI1B;KAGF,KAAK,uBAAuB;MAC1B,MAAM,QAAS,QAAsB;MACrC,MAAM,gBAAiB,QAAsB;AAC7C,UAAI,SAAS,kBAAkB,QAAW;OAIxC,MAAM,UAAU;AAEhB,WACE,MAAM,SAAS,cACZ,MAAM,SAAS,4BAClB;QAEA,MAAM,KACJ,OAAO,MAAM,OAAO,WAChB,MAAM,KACN,iBAAiB;AACvB,yBAAiB;SACf,OAAO;SACP;SACA,UAAU,wBAAwB,IAAI,QAAQ;SAC9C,WAAW;SACZ;QACD,MAAM,aAAa;SACjB,GAAG;SACH,OAAO;SACP,eAAe;UACb,MAAM;UACN,IAAI,eAAe;UACnB,MAAM;UACN,OAAO,EAAE;UACV;SACF;AACD,YAAI,CAAC,iBAAiB,GAAG,OAAO,WAAW,CAAE,QAAO;SAAE;SAAgB;SAAgB;QAStF,MAAMS,WAA0B;SAC9B,OAAO;UACL,MAAM;UACN;UACA,MAAM;UACN,OAAO,EAAE;UACV;SACD,aAAa;SACb,eAAe,EAAE,IAAI;SACtB;AACD,uBAAe,KAAK,SAAS;AAC7B,qBAAa,IAAI,eAAe,SAAS;cACpC;QAEL,MAAM,YAAY;SAAE,GAAG;SAAS,OAAO;SAAS;AAChD,YAAI,CAAC,iBAAiB,GAAG,OAAO,UAAU,CAAE,QAAO;SAAE;SAAgB;SAAgB;QAMrF,MAAMA,WAA0B;SAC9B,OAAO,EAAE,GAAG,OAAO;SACnB,aAAa;SACd;AACD,uBAAe,KAAK,SAAS;AAC7B,qBAAa,IAAI,eAAe,SAAS;;;AAG7C;;KAGF,KAAK,uBAAuB;MAC1B,MAAM,gBAAiB,QAAsB;MAC7C,MAAM,QAAS,QAAsB;AACrC,UAAI,kBAAkB,QAAW;OAC/B,MAAM,WACJ,kBAAkB,SAAY,aAAa,IAAI,cAAc,GAAG;OAElE,MAAM,YAAY;QAChB,GAAG;QACH,OAAO,WACH,eAAe,QAAQ,SAAS,IAAI,IAElC,qBAAqB,eAAe,SAAS,eAAe,QAAQ,SAAS,GAC7E,gBACF;QACL;AACD,WAAI,CAAC,iBAAiB,GAAG,OAAO,UAAU,CAAE,QAAO;QAAE;QAAgB;QAAgB;AAMrF,WAAI,YAAY,OACd;YAAI,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,SACvD,UAAS,MAAM,QACX,SAAS,MAAM,QAA+B,MAAM,MAAM;iBAE9D,MAAM,SAAS,oBACZ,OAAO,MAAM,aAAa,SAK7B,UAAS,MAAM,YACX,SAAS,MAAM,YAAmC,MAAM,MAAM;iBAElE,MAAM,SAAS,qBACZ,OAAO,MAAM,cAAc,SAM9B,UAAS,MAAM,aACX,SAAS,MAAM,aAAoC,MAAM,MAAM;iBAEnE,MAAM,SAAS,sBACZ,OAAO,MAAM,iBAAiB,SAEjC,UAAS,eAAe,MAAM;iBAE9B,MAAM,SAAS,qBACZ,MAAM,UACT;AAIA,aAAI,CAAC,MAAM,QAAQ,SAAS,MAAM,UAAU,CAC1C,UAAS,MAAM,YAAY,EAAE;AAE9B,SAAC,SAAS,MAAM,UAA6B,KAAK,MAAM,SAAS;;;iBASlE,CAAC,iBAAiB,GAAG,OAAO,QAAQ,CAAE,QAAO;OAAE;OAAgB;OAAgB;AAErF;;KAGF,KAAK,sBAAsB;MACzB,MAAM,gBAAiB,QAAsB;MAC7C,MAAM,WAAW,kBAAkB,SAAY,aAAa,IAAI,cAAc,GAAG;MACjF,MAAM,YAAY;OAChB,GAAG;OACH,OAAO,WACH,qBAAqB,eAAe,SAAS,eAAe,QAAQ,SAAS,GAC5E,iBAAiB;OACvB;AACD,UAAI,CAAC,iBAAiB,GAAG,OAAO,UAAU,CAAE,QAAO;OAAE;OAAgB;OAAgB;AAGrF,UAAI,UAAU;AAQZ,WACE,SAAS,MAAM,SAAS,cACrB,SAAS,YAAY,SAAS,EAEjC,KAAI;AACF,iBAAS,MAAM,QAAQ,KAAK,MAAM,SAAS,YAAY;gBAChD,KAAK;AACZ,gBAAQ,KACN,uDACW,SAAS,MAAM,MAA6B,IAAI,QAC9C,SAAS,MAAM,QAA+B,IAAI,sBACrC,SAAS,YAAY,OAAO,cACpC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACnE;AACD,iBAAS,MAAM,QAAQ,EAAE;;AAM7B,WACE,SAAS,MAAM,SAAS,WACpB,OAAO,SAAS,MAAM,SAAS,YAC7B,SAAS,MAAM,KAAgB,WAAW,GAEhD,UAAS,iBAAiB;;AAG9B;;KAGF,KAAK;AAEH,UAAI,CAAC,iBAAiB,GAAG,OAAO,QAAQ,CAAE,QAAO;OAAE;OAAgB;OAAgB;AACnF;KAGF,KAAK;AAKH,UAAI,eACF,QAAO;OAAE;OAAgB;OAAgB;AAE3C,UAAI,CAAC,iBAAiB,GAAG,OAAO,QAAQ,CAAE,QAAO;OAAE;OAAgB;OAAgB;AACnF,aAAO;OAAE;OAAgB;OAAgB;KAG3C,QAEE,KAAI,CAAC,iBAAiB,GAAG,OAAO,QAAQ,CAAE,QAAO;MAAE;MAAgB;MAAgB;;;AAIzF,UAAO;IAAE;IAAgB;IAAgB;;AAG3C,MAAI;GACF,IAAIC,WAAqB,KAAK;AAE9B,QAAK,WAAW,GAAG,WAAW,mBAAmB,YAAY;IAC3D,MAAM,EAAE,gBAAgB,mBAAmB,MAAM,eAAe,SAAS;AAEzE,QAAI,CAAC,eAGH;IAYF,MAAM,gBAAgB;KACpB,MAAM;KACN,SAAS,eACN,QAAQ,MAAM,CAAC,EAAE,eAAe,CAChC,KAAK,MAAM;AACV,UAAI,EAAE,eAAe;OAKnB,MAAM,QACJ,OAAO,EAAE,MAAM,UAAU,YAAY,EAAE,MAAM,UAAU,OAClD,EAAE,MAAM,QACT,EAAE;AACR,cAAO;QACL,MAAM;QACN,IAAI,EAAE,cAAc;QACpB,MAAM;QACN;QACD;;AAEH,aAAO,EAAE;OACT;KACL;AACD,iBAAa,KAAK,cAAc;IAEhC,IAAIC;AACJ,QAAI;AACF,mBAAc,MAAM,WAAW,cAAc,cAAc,cAAc;aAClE,KAAK;KACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,aAAQ,KAAK,8BAA8B,MAAM;AACjD,mBACE,yBAAyB,IAAI;;IAUjC,MAAM,cAAc;AAUpB,QAAI,CATY,iBAAiB,uBAAuB;KACtD,MAAM;KACN,OAAO;KACP,eAAe;MACb,MAAM;MACN,aAAa,eAAe;MAC5B,SAAS;OAAE,MAAM;OAAkB,MAAM;OAAa;MACvD;KACF,CAAC,CACY;AAKd,QAAI,CAJW,iBAAiB,sBAAsB;KACpD,MAAM;KACN,OAAO;KACR,CAAC,CACW;AAKb,iBAAa,KAAK;KAChB,MAAM;KACN,SAAS,CACP;MACE,MAAM;MACN,aAAa,eAAe;MAC5B,SAAS;MACV,CACF;KACF,CAAC;AAWF,eAAW,MAAM,eALQ,KAAK,UAAU;KACtC,GAAG,KAAK;KACR,UAAU;KACV,QAAQ;KACT,CAAC,EACgD,KAAK,eAAe;;GAKxE,MAAM,aAAa;AACnB,oBAAiB,uBAAuB;IACtC,MAAM;IACN,OAAO;IACP,eAAe;KAAE,MAAM;KAAQ,MAAM;KAAI;IAC1C,CAAC;AACF,oBAAiB,uBAAuB;IACtC,MAAM;IACN,OAAO;IACP,OAAO;KACL,MAAM;KACN,MAAM,8BAA8B,kBAAkB;KACvD;IACF,CAAC;AACF,oBAAiB,sBAAsB;IACrC,MAAM;IACN,OAAO;IACR,CAAC;AACF,oBAAiB,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;WACnD,KAAK;GACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,WAAQ,MAAM,yBAAyB,MAAM;AAC7C,oBAAiB,SAAS;IACxB,MAAM;IACN,OAAO;KAAE,MAAM;KAAa,SAAS,wBAAwB;KAAO;IACrE,CAAC;YACM;AACR,OAAI;AACF,eAAW,OAAO;WACZ;;IAKb,CAAC;;;;;;;;;;;ACx3BJ,SAAS,mBAAmB,OAAe,eAA+B;AACxE,KAAI,MAAM,WAAW,YAAY,EAAE;EACjC,MAAM,SAAS,MAAM,MAAM,EAAmB;AAC9C,MAAI,kBAAkB,KAAK,OAAO,CAAE,QAAO,SAAS;;AAEtD,QAAO,iBAAiB;;;;;;;;;;;AAY1B,SAAS,sBAAsB,SAA0B;AACvD,QACE,QAAQ,SAAS,sBAAoB,IAClC,QAAQ,SAAS,0BAAwB,IACzC,uBAAuB,KAAK,QAAQ;;;;;;;;;;;;AAc3C,SAAS,iCACP,iBACA,mBACqD;CACrD,MAAMC,WAA6B,EAAE;CACrC,IAAIC,0BAA0C,EAAE;CAChD,IAAI,aAAa;CAIjB,IAAI,IAAI;AACR,QAAO,IAAI,gBAAgB,QAAQ;EACjC,MAAM,QAAQ,gBAAgB;EAC9B,MAAM,IAAK,OAAO,UAAU,YAAY,UAAU,OAAS,QAAsB;AAEjF,MACE,KACG,EAAE,SAAS,qBACX,EAAE,SAAS,2BAA2B,QAAQ,iBAAiB,GAAG,EACrE;GACA,MAAM,QAAQ,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK;GAEhD,MAAM,YAAY,gBAAgB,IAAI;GACtC,MAAM,OACJ,OAAO,cAAc,YAAY,cAAc,OAC1C,YACD;GAMN,MAAM,YAAY,MAAM,WAAW,YAAY,GAC3C,mBAAmB,OAAO,kBAAkB,QAAQ,GACpD,MAAM,WAAW,SAAS,IAAI,wBAAwB,KAAK,MAAM,GAC/D,QACA,iBAAiB,kBAAkB;AAGzC,2BAAwB,KAAK;IAC3B,MAAM;IACN,IAAI;IACJ,MAAM;IACN,OAAO,EAAE;IACV,CAAC;AACF,YAAS,KAAK;IAAE,MAAM;IAAa,SAAS;IAAyB,CAAC;AACtE,gBAAa;GAIb,IAAI,aAAa;AACjB,OAAI,QAAQ,KAAK,SAAS,uBAAuB;IAC/C,MAAM,IAAI,KAAK;AACf,QAAI,OAAO,MAAM,SACf,cAAa;aACJ,OAAO,MAAM,YAAY,MAAM,MAAM;KAC9C,MAAM,MAAO,EAAgB;AAC7B,SAAI,OAAO,QAAQ,SAAU,cAAa;;AAE5C,SAAK;UACA;AAGL,iBAAa;AACb,SAAK;;AAEP,YAAS,KAAK;IACZ,MAAM;IACN,SAAS,CACP;KACE,MAAM;KACN,aAAa;KACb,SAAS;KACV,CACF;IACF,CAAC;AAIF,6BAA0B,EAAE;AAC5B;;AAGF,MAAI,KAAK,EAAE,SAAS,uBAAuB;AAIzC,gBAAa;AACb,QAAK;AACL;;AAIF,0BAAwB,KAAK,MAAM;AACnC,OAAK;;AAIP,KAAI,wBAAwB,SAAS,EACnC,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS;EAAyB,CAAC;AAIxE,KAAI,CAAC,WACH,QAAO;EACL,UAAU,CAAC;GAAE,MAAM;GAAa,SAAS;GAAiB,CAAC;EAC3D,YAAY;EACb;AAEH,QAAO;EAAE;EAAU,YAAY;EAAM;;AAGvC,SAAgB,sBAAsB,SAAyB;AAC7D,KAAI,CAAC,sBAAsB,QAAQ,CAAE,QAAO;CAE5C,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,SAAO;;CAGT,IAAI,UAAU;AAOd,KAAI,MAAM,QAAQ,OAAO,MAAM,EAAE;EAC/B,MAAM,QAAQ,OAAO;EACrB,MAAM,SAAS,MAAM;EACrB,MAAM,WAAW,MAAM,QAAQ,MAAM;AACnC,OAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;GAChD,MAAM,OAAQ,EAAgB;AAC9B,UAAO,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,WAAW;IAC/D;AACF,MAAI,SAAS,WAAW,QAAQ;AAC9B,UAAO,QAAQ;AACf,aAAU;;;AAWd,KAAI,MAAM,QAAQ,OAAO,SAAS,EAAE;EAClC,MAAM,WAAW,OAAO;EACxB,MAAMC,UAA0B,EAAE;EAClC,IAAI,gBAAgB;EACpB,MAAM,oBAAoB,EAAE,OAAO,GAAG;AACtC,OAAK,MAAM,OAAO,UAAU;AAC1B,OACE,OAAO,QAAQ,YACZ,QAAQ,QACP,IAAkB,SAAS,aAC/B;AACA,YAAQ,KAAK,IAAI;AACjB;;GAEF,MAAM,UAAW,IAAkB;AACnC,OAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC3B,YAAQ,KAAK,IAAI;AACjB;;AAaF,OAAI,CATqB,QAAQ,MAAM,MAAM;AAC3C,QAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;IAChD,MAAM,OAAQ,EAAgB;IAC9B,MAAMC,SAAQ,EAAgB;AAC9B,WACG,SAAS,qBAAqBA,WAAS,aACrC,SAAS;KAEd,EACqB;AACrB,YAAQ,KAAK,IAAI;AACjB;;GAEF,MAAM,EAAE,UAAU,OAAO,eAAe,iCACtC,SACA,kBACD;AACD,OAAI,YAAY;AACd,oBAAgB;AAChB,SAAK,MAAM,KAAK,MAAO,SAAQ,KAAK,EAAE;SAEtC,SAAQ,KAAK,IAAI;;AAGrB,MAAI,eAAe;AACjB,UAAO,WAAW;AAClB,aAAU;GAIV,MAAM,gBAAgB,MAAM,QAAQ,OAAO,MAAM,GAC5C,OAAO,QACR,EAAE;AAKN,OAAI,CAJoB,cAAc,MAAM,MAAM;AAChD,QAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,WAAQ,EAAgB,SAAS;KACjC,CAEA,QAAO,QAAQ,CACb,GAAG,eACH;IACE,MAAM;IACN,aAAa;IACb,cAAc;KACZ,MAAM;KACN,YAAY,EAAE;KACd,UAAU,EAAE;KACb;IACF,CACF;;;AAKP,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,KAAK,UAAU,OAAO;;;;;AClT/B,MAAM,gBAAgB;AAEtB,eAAsB,oBACpB,UACA,WACY;CACZ,MAAM,SAAS,SAAS,OAAO;AAC/B,KAAI;AACF,SAAQ,MAAM,SAAS,MAAM;UACtB,OAAO;EACd,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;EAC5D,MAAM,WAAW,MAAM,OAAO,MAAM,CAAC,YAAY,eAAe;EAChE,MAAM,UACJ,SAAS,SAAS,gBACd,SAAS,MAAM,GAAG,cAAc,GAAG,mBACnC;AACN,UAAQ,MACN,iCAAiC,UAAU,WAAW,SAAS,OAAO,iBAAiB,YAAY,YAAY,cAAc,IAAI,KAAK,UAAU,QAAQ,GACzJ;AACD,QAAM;;;;;;ACPV,MAAMC,qBAAmB,SACtB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,WAAW,aAAa,IACpE,KAAK,SAAS;;;;;;AAOhB,SAAS,uBAAuB,SAAyB;AACvD,KAAI,CAAC,QAAQ,SAAS,aAAa,CAAE,QAAO;CAE5C,IAAIC;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;AAMT,KAAI,CAHiB,KAAK,OAAO,MAC9B,SAAoBD,kBAAgB,KAAK,CAC3C,CACkB,QAAO;AAE1B,MAAK,QAAQ,KAAK,MAAM,QACrB,SAAoB,CAACA,kBAAgB,KAAK,CAC5C;AAED,KAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,OAAK,QAAQ;AACb,OAAK,cAAc;YAEnB,KAAK,eACL,OAAO,KAAK,gBAAgB,YAC5B,KAAK,YAAY,SAAS,QAC1B;EACA,MAAM,aAAa,KAAK,YAAY;AACpC,MACE,cACA,CAAC,KAAK,MAAM,MAAM,SAAoB,KAAK,SAAS,WAAW,CAE/D,MAAK,cAAc,EAAE,MAAM,QAAQ;;AAIvC,QAAO,KAAK,UAAU,KAAK;;;;;;;AAQ7B,eAAsB,kBAAkB,GAAY;CAClD,MAAM,YAAY,KAAK,KAAK;CAO5B,MAAM,eAAe,uBADC,sBALN,MAAM,EAAE,IAAI,MAAM,CAKkB,CACM;AAK1D,KAAI,aAAa,SAAS,kBAAgB,CACxC,KAAI;EACF,MAAM,QAAQ,KAAK,MAAM,aAAa;AACtC,MAAI,MAAM,QAAQ,MAAM,YAAY,IAAI,MAAM,YAAY,SAAS,EACjE,QAAO,EAAE,KACP;GACE,MAAM;GACN,OAAO;IACL,MAAM;IACN,SACE;IAEH;GACF,EACD,IACD;SAEG;CAKV,MAAM,EAAE,MAAM,WAAW,eAAe,kBAAkBE,qBAAmB,aAAa;CAE1F,MAAMC,eAAuC,EAAE;CAC/C,MAAM,gBAAgB,EAAE,IAAI,OAAO,iBAAiB;AACpD,KAAI,eAAe;EACjB,MAAM,WAAW,iBAAiB,cAAc;AAChD,MAAI,SAAU,cAAa,oBAAoB;;CAGjD,MAAM,UAAU,iBAAiB;CACjC,MAAM,gBAAgB,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,QAAQ;CAEtE,MAAM,WAAW,MAAM,YAAY,WAAW;EAC5C,GAAG,eAAe;EAClB,GAAG;EACJ,CAAC;CACF,MAAM,eAAe,MAAM,oBACzB,UACA,EAAE,IAAI,KACP;AAED,YACE;EACE,QAAQ;EACR,MAAM,EAAE,IAAI;EACZ,OAAO;EACP;EACA,aAAa,aAAa;EAC1B,QAAQ,SAAS;EAClB,EACD,eACA,UACD;AAED,QAAO,EAAE,KAAK,aAAa;;;;;AAM7B,SAASD,qBAAmB,SAI1B;CACA,IAAIE;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,SAAO,EAAE,MAAM,SAAS;;CAG1B,MAAM,gBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;CAEpD,IAAI,WAAW;AACf,KAAI,eAAe;EACjB,MAAM,WAAW,aAAa,cAAc;AAC5C,MAAI,aAAa,eAAe;AAC9B,UAAO,QAAQ;AACf,cAAW;;;AAKf,KADsB,QAAQ,SAAS,YAAU,IAC5BC,uBAAqB,OAAO,CAC/C,YAAW;AAYb,MAJE,QAAQ,SAAS,aAAW,IACzB,QAAQ,SAAS,oBAAkB,IACnC,QAAQ,SAAS,YAAU,IAC3B,QAAQ,SAAS,4BAA0B,KACjBC,2BAAyB,OAAO,CAC7D,YAAW;CAGb,MAAM,gBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAEpD,QAAO;EACL,MAAM,WAAW,KAAK,UAAU,OAAO,GAAG;EAC1C;EACA;EACD;;AAGH,SAASD,uBAAqB,MAA0B;CACtD,IAAI,WAAW;CACf,SAAS,WAAW,OAAwB;AAC1C,MAAI,MAAM,eAAe,UAAU,QAAW;AAC5C,UAAO,MAAM,cAAc;AAC3B,OAAI,OAAO,KAAK,MAAM,cAAc,CAAC,WAAW,EAC9C,QAAO,MAAM;AAEf,cAAW;;;AAIf,KAAI,MAAM,QAAQ,KAAK,OAAO,CAC5B,MAAK,MAAM,SAAS,KAAK,OAAQ,YAAW,MAAM;AAGpD,KAAI,MAAM,QAAQ,KAAK,SAAS,EAC9B;OAAK,MAAM,OAAO,KAAK,SACrB,KAAI,MAAM,QAAQ,IAAI,QAAQ,CAC5B,MAAK,MAAM,SAAS,IAAI,SAAS;AAC/B,cAAW,MAAM;AACjB,OAAI,MAAM,QAAQ,MAAM,QAAQ,CAC9B,MAAK,MAAM,UAAU,MAAM,QAAS,YAAW,OAAO;;;AAOhE,KAAI,MAAM,QAAQ,KAAK,MAAM,CAC3B,MAAK,MAAM,QAAQ,KAAK,MAAO,YAAW,KAAK;AAGjD,QAAO;;;;;;;;;AAUT,SAASC,2BAAyB,MAA0B;CAC1D,IAAI,WAAW;AACf,KAAI,KAAK,WAAW,QAAW;AAC7B,UAAQ,KACN,oEACD;AACD,SAAO,KAAK;AACZ,aAAW;;AAEb,KAAI,KAAK,kBAAkB,QACzB;MAAI,KAAK,iBAAiB,OAAO,KAAK,kBAAkB,UAAU;GAChE,MAAM,KAAK,KAAK;GAChB,MAAM,qBAAqB,IAAI,IAAI,CAAC,SAAS,CAAC;GAC9C,IAAI,cAAc;AAClB,QAAK,MAAM,OAAO,OAAO,KAAK,GAAG,CAC/B,KAAI,CAAC,mBAAmB,IAAI,IAAI,EAAE;AAChC,WAAO,GAAG;AACV,kBAAc;;AAGlB,OAAI,aAAa;AACf,YAAQ,KACN,wIACD;AACD,QAAI,OAAO,KAAK,GAAG,CAAC,WAAW,EAC7B,QAAO,KAAK;AAEd,eAAW;;;;AAIjB,KAAI,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC7B,UAAQ,KACN,wFACD;AACD,SAAO,KAAK;AACZ,aAAW;;AAGb,KAAI,MAAM,QAAQ,KAAK,MAAM,EAAE;EAC7B,IAAI,aAAa;AACjB,OAAK,MAAM,QAAQ,KAAK,MACtB,KAAI,OAAO,SAAS,YAAY,SAAS,MAAM;GAC7C,MAAM,IAAI;AACV,OAAI,EAAE,0BAA0B,QAAW;AACzC,WAAO,EAAE;AACT,eAAW;AACX,QAAI,CAAC,YAAY;AACf,aAAQ,KACN,qHACD;AACD,kBAAa;;;;;AAMvB,QAAO;;;;;AC5QT,MAAM,mBAAmB,SACtB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,WAAW,aAAa,IACpE,KAAK,SAAS;;;;;;AAOhB,SAAS,mBAAmB,GAAoC;CAC9D,MAAMC,UAAkC,EAAE;CAC1C,MAAM,gBAAgB,EAAE,IAAI,OAAO,iBAAiB;AACpD,KAAI,eAAe;EACjB,MAAM,WAAW,iBAAiB,cAAc;AAChD,MAAI,SAAU,SAAQ,oBAAoB;;AAE5C,QAAO;;;;;;AAOT,SAASC,mBACP,UACoB;AACpB,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,MAAM,SAAS;AACrB,MAAI,IAAI,SAAS,QAAQ;AACvB,OAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI;AAChD,OAAI,MAAM,QAAQ,IAAI,QAAQ,EAAE;IAC9B,MAAM,YAAY,IAAI,QAAQ,MAC3B,UAAqB,MAAM,SAAS,OACtC;AACD,QAAI,WAAW,KAAM,QAAO,UAAU;;;;;;;;;;;AAa9C,SAAS,qBAAqB,UAAqC;AACjE,QAAO,SAAS,MACb,QACC,MAAM,QAAQ,IAAI,QAAQ,IAC1B,IAAI,QAAQ,MACT,UAAqB,MAAM,SAAS,cACtC,CACJ;;;;;;;AAQH,SAAS,oBACP,MACA,eACM;AACN,KAAI,KAAK,WAAW,UAAa,KAAK,WAAW,KAC/C,MAAK,SAAS;UACL,OAAO,KAAK,WAAW,SAChC,MAAK,SAAS,GAAG,cAAc,MAAM,KAAK;UACjC,MAAM,QAAQ,KAAK,OAAO,CACnC,MAAK,SAAS,CACZ;EAAE,MAAM;EAAQ,MAAM;EAAe,EACrC,GAAG,KAAK,OACT;;;;;;AAQL,SAAS,mBAAmB,MAAuB;AACjD,KAAI,CAAC,KAAK,MAAO;AAEjB,MAAK,QAAQ,KAAK,MAAM,QACrB,SAAoB,CAAC,gBAAgB,KAAK,CAC5C;AAED,KAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,OAAK,QAAQ;AACb,OAAK,cAAc;YAEnB,KAAK,eACL,OAAO,KAAK,gBAAgB,YAC5B,KAAK,YAAY,SAAS,QAC1B;EAEA,MAAM,aAAa,KAAK,YAAY;AACpC,MACE,cACA,CAAC,KAAK,MAAM,MAAM,SAAoB,KAAK,SAAS,WAAW,CAE/D,MAAK,cAAc,EAAE,MAAM,QAAQ;;;;;;;;AAUzC,eAAe,iBAAiB,SAAkC;AAEhE,KAAI,CAAC,QAAQ,SAAS,aAAa,CAAE,QAAO;CAE5C,IAAIC;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;AAMT,KAAI,CAHiB,KAAK,OAAO,MAC9B,SAAoB,gBAAgB,KAAK,CAC3C,CACkB,QAAO;CAI1B,MAAM,QADgB,qBAAqB,KAAK,YAAY,EAAE,CAAC,GACjC,SAAYD,mBAAiB,KAAK,YAAY,EAAE,CAAC;AAE/E,KAAI,MACF,KAAI;EACF,MAAM,UAAU,MAAM,UAAU,MAAM;EACtC,MAAM,gBAAgB;GACpB;GACA,QAAQ;GACR;GACA,QAAQ,WAAW,KAAK,MAAM,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK;GACpE;GACD,CAAC,KAAK,KAAK;AAEZ,sBAAoB,MAAM,cAAc;UACjC,OAAO;AACd,UAAQ,KAAK,kDAAkD,MAAM;;AAKzE,oBAAmB,KAAK;AAExB,QAAO,KAAK,UAAU,KAAK;;AAG7B,eAAsB,iBAAiB,GAAY;CACjD,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAM,eAAe,MAAM;CAE3B,MAAM,UAAU,MAAM,EAAE,IAAI,MAAM;CAElC,MAAM,eAAe,QAAQ,SAAS;AACtC,KAAI,aACF,SAAQ,MAAM,2BAA2B,QAAQ,MAAM,GAAG,IAAK,CAAC;AAMlE,KAAI,QAAQ,IAAI,yBAAyB,KAAK;EAC5C,IAAIE,eAAwB;AAC5B,MAAI;AACF,kBAAe,KAAK,MAAM,QAAQ;UAC5B;AAGR,mBAAiB;GACf,MAAM,EAAE,IAAI;GACZ,MAAM;GACN,YAAY,EAAE,IAAI,OAAO,iBAAiB;GAC3C,CAAC;;AAGJ,KAAI,MAAM,cACR,OAAM,eAAe;CAGvB,MAAM,cAAc,mBAAmB,EAAE;CAMzC,MAAM,iBAAiB,mBADF,EAAE,IAAI,OAAO,iBAAiB,CACI;CAEvD,IAAI,YAAY,MAAM,iBAAiB,QAAQ;AAU/C,aAAY,sBAAsB,UAAU;AAC5C,KAAI,gBAAgB;AAKlB,cAAY,kBAAkB,UAAU;AACxC,UAAQ,KACN,8IACD;;AAaH,KAAI,UAAU,SAAS,kBAAgB,CACrC,KAAI;EACF,MAAM,QAAQ,KAAK,MAAM,UAAU;AACnC,MAAI,MAAM,QAAQ,MAAM,YAAY,IAAI,MAAM,YAAY,SAAS,EACjE,QAAO,EAAE,KACP;GACE,MAAM;GACN,OAAO;IACL,MAAM;IACN,SACE;IAIH;GACF,EACD,IACD;SAEG;CAQV,MAAM,EACJ,MAAM,cACN,eACA,eACA,kBACE,mBAAmB,UAAU;CAEjC,MAAM,UAAU,iBAAiB;AACjC,KAAI,QAAS,qBAAoB,SAAS,eAAe;CAGzD,MAAM,iBAAiB,kBAAkB,aAAa,iBAAiB,cAAc;CAErF,IAAIC;AACJ,KAAI;AACF,aAAW,MAAM,eAAe,cAAc;GAC5C,GAAG,eAAe;GAClB,GAAG;GACJ,CAAC;UACK,OAAO;AACd,MAAI,iBAAiB,WAAW;GAC9B,MAAM,YAAY,MAAM,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG;AACrE,cACE;IACE,QAAQ;IACR,MAAM,EAAE,IAAI;IACZ,OAAO;IACP;IACA,QAAQ,MAAM,SAAS;IACvB;IACD,EACD,eACA,UACD;;AAEH,QAAM;;CAGR,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;CAS5D,MAAM,oBAAoB,EAAE,IAAI,OAAO,SAAS,IAAI,IAAI,SACtD,oBACD;CACD,IAAI,cAAc,YAAY,SAAS,oBAAoB;AAC3D,KAAI,CAAC,eAAe,kBAClB;MAAI,gBAAgB,MAAM,gBAAgB,4BAA4B;AACpE,WAAQ,KACN,yCAAyC,SAAS,OAAO,gBAAgB,KAAK,UAAU,YAAY,CAAC,gEACtG;AACD,iBAAc;;;AAIlB,KAAI,aACF,SAAQ,MACN,iCAAiC,SAAS,OAAO,iBAAiB,YAAY,gBAAgB,cAC/F;AAKH,KAAI,aAAa;AACf,aACE;GACE,QAAQ;GACR,MAAM,EAAE,IAAI;GACZ,OAAO;GACP;GACA,QAAQ,SAAS;GACjB,WAAW;GACZ,EACD,eACA,UACD;AAED,MAAI,aACF,SAAQ,MAAM,+CAA+C;EAE/D,MAAMC,gBAAwC;GAC5C,gBAAgB;GAChB,iBAAiB;GACjB,qBAAqB;GACrB,YAAY;GACb;EACD,MAAM,YAAY,SAAS,QAAQ,IAAI,eAAe;AACtD,MAAI,UAAW,eAAc,kBAAkB;EAC/C,MAAM,QAAQ,SAAS,QAAQ,IAAI,aAAa;AAChD,MAAI,MAAO,eAAc,gBAAgB;AASzC,MAAI,kBAAkB,SAAS,MAAM;GAInC,IAAIC,aAAwB,EAAE;AAC9B,OAAI;AACF,iBAAa,KAAK,MAAM,aAAa;WAC/B;GAKR,MAAM,sBAAsB,MAAM,QAAQ,WAAW,SAAS,GACzD,WAAW,WACZ,EAAE;AACN,UAAO,IAAI,SACT,mBAAmB;IACjB,eAAe;IACf;IACA,UAAU;IACV,gBAAgB;KACd,GAAG,eAAe;KAClB,GAAG;KACJ;IACF,CAAC,EACF;IACE,QAAQ,SAAS;IACjB,SAAS;IACV,CACF;;AAGH,SAAO,IAAI,SACT,SAAS,OACL,qBAAqB,SAAS,MAAM,EAAE,WAAW,EAAE,IAAI,MAAM,CAAC,GAC9D,MACJ;GACE,QAAQ,SAAS;GACjB,SAAS;GACV,CACF;;CAIH,MAAM,eAAe,MAAM,oBACzB,UACA,EAAE,IAAI,KACP;AAED,YACE;EACE,QAAQ;EACR,MAAM,EAAE,IAAI;EACZ,OAAO;EACP;EACA,aAAa,aAAa,OAAO;EACjC,cAAc,aAAa,OAAO;EAClC,QAAQ,SAAS;EAClB,EACD,eACA,UACD;AAED,KAAI,aACF,SAAQ,MACN,qDACA,KAAK,UAAU,aAAa,CAAC,MAAM,GAAG,IAAK,CAC5C;CAEH,MAAM,aAAa,SAAS,QAAQ,IAAI,eAAe;AACvD,KAAI,WAAY,GAAE,OAAO,gBAAgB,WAAW;CACpD,MAAM,kBAAkB,SAAS,QAAQ,IAAI,aAAa;AAC1D,KAAI,gBAAiB,GAAE,OAAO,cAAc,gBAAgB;AAC5D,QAAO,EAAE,KAAK,cAAc,SAAS,OAAc;;;;;;;;;;AAWrD,SAAS,mBAAmB,SAK1B;CACA,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,SAAO,EAAE,MAAM,SAAS;;CAG1B,MAAM,gBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;CAEpD,IAAI,WAAW;AACf,KAAI,eAAe;EACjB,MAAM,WAAW,aAAa,cAAc;AAC5C,MAAI,aAAa,eAAe;AAC9B,UAAO,QAAQ;AACf,cAAW;;;CAIf,MAAM,gBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;CAEpD,MAAM,gBAAgB,gBAClB,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,cAAc,GACtD;AAKJ,KAAI,kBAAkB,QAAQ,cAAc,CAC1C,YAAW;AAKb,KADsB,QAAQ,SAAS,YAAU,IAC5B,qBAAqB,OAAO,CAC/C,YAAW;AA8Bb,MAJE,QAAQ,SAAS,aAAW,IACzB,QAAQ,SAAS,oBAAkB,IACnC,QAAQ,SAAS,YAAU,IAC3B,QAAQ,SAAS,4BAA0B,KACjB,yBAAyB,OAAO,CAC7D,YAAW;AAGb,QAAO;EACL,MAAM,WAAW,KAAK,UAAU,OAAO,GAAG;EAC1C;EACA;EACA;EACD;;AAGH,MAAa,eAAe;CAAC;CAAO;CAAU;CAAQ;CAAQ;;;;;;AAO9D,SAAgB,aAAa,QAAgD;CAC3E,MAAM,IACJ,OAAO,WAAW,YAAY,OAAO,SAAS,OAAO,GAAG,SAAS;AACnE,KAAI,IAAI,IAAM,QAAO;AACrB,KAAI,IAAI,IAAM,QAAO;AACrB,KAAI,IAAI,KAAO,QAAO;AACtB,QAAO;;;;;;;;;;AAWT,SAAgB,YACd,UACA,WACQ;AACR,KAAI,UAAU,SAAS,SAAS,CAAE,QAAO;CACzC,MAAM,YAAY,aAAa,QAAQ,SAAS;CAChD,IAAIC;CACJ,IAAI,WAAW;AACf,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,QAAQ,aAAa;AAC3B,MAAI,CAAC,UAAU,SAAS,MAAM,CAAE;EAChC,MAAM,OAAO,KAAK,IAAI,IAAI,UAAU;AAEpC,MAAI,OAAO,UAAU;AACnB,cAAW;AACX,UAAO;;;AAGX,QAAO,QAAQ;;;;;;;;;;;;AAajB,SAAS,kBAAkB,MAAiB,OAAwB;AAClE,KAAI,CAAC,OAAO,cAAc,UAAU,kBAAmB,QAAO;CAC9D,MAAM,WAAW,KAAK;AACtB,KAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,KAAI,SAAS,SAAS,UAAW,QAAO;CAExC,MAAM,WAAW,aAAa,SAAS,cAAc;CACrD,MAAM,YAAY,MAAM,aAAa,SAAS;CAC9C,MAAM,SACJ,MAAM,QAAQ,UAAU,IAAI,UAAU,SAAS,IAC3C,YAAY,UAAU,UAAU,GAChC;AAEN,MAAK,WAAW,EAAE,MAAM,YAAY;CAEpC,MAAM,WACJ,KAAK,iBAAiB,OAAO,KAAK,kBAAkB,WAC/C,KAAK,gBACN,EAAE;AACR,MAAK,gBAAgB;EACnB,GAAG;EAEH,QAAQ,SAAS,UAAU;EAC5B;AAED,QAAO;;;;;;;;;;AAWT,SAAS,qBAAqB,MAA0B;CACtD,IAAI,WAAW;CACf,SAAS,WAAW,OAAwB;AAC1C,MAAI,MAAM,eAAe,UAAU,QAAW;AAC5C,UAAO,MAAM,cAAc;AAC3B,OAAI,OAAO,KAAK,MAAM,cAAc,CAAC,WAAW,EAC9C,QAAO,MAAM;AAEf,cAAW;;;AAIf,KAAI,MAAM,QAAQ,KAAK,OAAO,CAC5B,MAAK,MAAM,SAAS,KAAK,OAAQ,YAAW,MAAM;AAGpD,KAAI,MAAM,QAAQ,KAAK,SAAS,EAC9B;OAAK,MAAM,OAAO,KAAK,SACrB,KAAI,MAAM,QAAQ,IAAI,QAAQ,CAC5B,MAAK,MAAM,SAAS,IAAI,SAAS;AAC/B,cAAW,MAAM;AACjB,OAAI,MAAM,QAAQ,MAAM,QAAQ,CAC9B,MAAK,MAAM,UAAU,MAAM,QAAS,YAAW,OAAO;;;AAOhE,KAAI,MAAM,QAAQ,KAAK,MAAM,CAC3B,MAAK,MAAM,QAAQ,KAAK,MAAO,YAAW,KAAK;AAGjD,QAAO;;;;;;;AAQT,SAAS,kBACP,aACA,SACwB;AACxB,KAAI,YAAY,kBAAmB,QAAO;AAC1C,KAAI,CAAC,WAAW,CAAC,QAAQ,WAAW,UAAU,CAAE,QAAO;AAEvD,QAAO;EACL,GAAG;EACH,kBAAkB,CAChB,mCACA,gCACD,CAAC,KAAK,IAAI;EACZ;;;;;;;;;;;;;;;;;;;;;;;;AAyBH,SAAS,yBAAyB,MAA0B;CAC1D,IAAI,WAAW;AACf,KAAI,KAAK,WAAW,QAAW;AAC7B,UAAQ,KACN,gJACD;AACD,SAAO,KAAK;AACZ,aAAW;;AAEb,KAAI,KAAK,kBAAkB,QA0BzB;MAAI,KAAK,iBAAiB,OAAO,KAAK,kBAAkB,UAAU;GAChE,MAAM,KAAK,KAAK;GAChB,MAAM,qBAAqB,IAAI,IAAI,CAAC,SAAS,CAAC;GAE9C,MAAM,SAAS,GAAG;GAClB,MAAM,SAAS,GAAG;GAClB,IAAI,cAAc;AAClB,QAAK,MAAM,OAAO,OAAO,KAAK,GAAG,CAC/B,KAAI,CAAC,mBAAmB,IAAI,IAAI,EAAE;AAChC,WAAO,GAAG;AACV,kBAAc;;AAGlB,OAAI,aAAa;AACf,YAAQ,KACN,4RAKD;AACD,QAAI,OAAO,KAAK,GAAG,CAAC,WAAW,EAC7B,QAAO,KAAK;AAEd,QAAI,WAAW,UAAa,WAAW,cACrC,mCAAkC,MAAM,QAAQ,OAAO;AAEzD,eAAW;;;;AAIjB,KAAI,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC7B,UAAQ,KACN,oHACD;AACD,SAAO,KAAK;AACZ,aAAW;;AAQb,KAAI,MAAM,QAAQ,KAAK,MAAM,EAAE;EAC7B,IAAI,aAAa;AACjB,OAAK,MAAM,QAAQ,KAAK,MACtB,KAAI,OAAO,SAAS,YAAY,SAAS,MAAM;GAC7C,MAAM,IAAI;AACV,OAAI,EAAE,0BAA0B,QAAW;AACzC,WAAO,EAAE;AACT,eAAW;AACX,QAAI,CAAC,YAAY;AACf,aAAQ,KACN,4OACD;AACD,kBAAa;;;;;AAMvB,QAAO;;;;;;;;;;;AAYT,SAAS,kCACP,MACA,QACA,QACM;CACN,IAAI,cACF;AAGF,KAAI,WAAW,OACb,gBACE,uDAAuD,KAAK,UAAU,OAAO;UACtE,OAAO,WAAW,SAC3B,gBACE,2BAA2B,OAAO;AAEtC,KAAI,OAAO,KAAK,WAAW,SACzB,MAAK,SAAS,KAAK,SAAS;UACnB,MAAM,QAAQ,KAAK,OAAO,CACnC,MAAK,SAAS,CACZ,GAAG,KAAK,QACR;EAAE,MAAM;EAAQ,MAAM,YAAY,WAAW;EAAE,CAChD;KAED,MAAK,SAAS,YAAY,WAAW;;;;;ACh1BzC,MAAa,gBAAgB,IAAI,MAAM;AAEvC,cAAc,KAAK,KAAK,OAAO,MAAM;AACnC,KAAI;AACF,SAAO,MAAM,iBAAiB,EAAE;UACzB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;AAEF,cAAc,KAAK,iBAAiB,OAAO,MAAM;AAC/C,KAAI;AACF,SAAO,MAAM,kBAAkB,EAAE;UAC1B,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACjBF,MAAa,cAAc,IAAI,MAAM;AAErC,YAAY,IAAI,KAAK,OAAO,MAAM;AAChC,KAAI;AACF,MAAI,CAAC,MAAM,OAET,OAAM,aAAa;EAGrB,MAAM,SAAS,MAAM,QAAQ,KAAK,KAAK,UAAU;GAI/C,MAAM,EAAE,eAAgB,GAAG,SAAS;AAEpC,UAAO;IACL,GAAG;IACH,QAAQ;IACR,MAAM,MAAM,cAAc,QAAQ;IAClC,SAAS;IACT,6BAAY,IAAI,KAAK,EAAE,EAAC,aAAa;IACrC,UAAU,MAAM;IAChB,cAAc,MAAM;IACrB;IACD;AAEF,SAAO,EAAE,KAAK;GACZ,QAAQ;GACR,MAAM;GACN,UAAU;GACX,CAAC;UACK,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACZF,MAAM,UAAU,IAAI,aAAa;AAEjC,SAAS,UAAU,OAAiC;CAClD,MAAMC,QAAuB,EAAE;AAC/B,KAAI,MAAM,MAAO,OAAM,KAAK,UAAU,MAAM,QAAQ;AACpD,KAAI,MAAM,SAAS,OACjB,MAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,CAAC,MAAM,aAAa,CACvD,OAAM,KAAK,SAAS,OAAO;AAG/B,KAAI,MAAM,OAAO,OAAW,OAAM,KAAK,OAAO,OAAO,MAAM,GAAG,GAAG;AACjE,QAAO,MAAM,KAAK,KAAK,GAAG;;AAG5B,eAAsB,gBAAgB,GAAY;CAChD,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAM,eAAe,MAAM;CAE3B,MAAM,UAAU,MAAM,EAAE,IAAI,MAAwB;CACpD,MAAM,eAAe,QAAQ,SAAS;AACtC,KAAI,aACF,SAAQ,MACN,8BACA,KAAK,UAAU,QAAQ,CAAC,MAAM,KAAK,CACpC;CAIH,MAAM,gBAAgB,QAAQ;CAC9B,MAAM,gBAAgB,aAAa,QAAQ,MAAM;AACjD,KAAI,kBAAkB,QAAQ,MAC5B,SAAQ,QAAQ;CAGlB,MAAM,gBAAgB,MAAM,QAAQ,KAAK,MACtC,UAAU,MAAM,OAAO,QAAQ,MACjC;AAED,qBAAoB,QAAQ,OAAO,aAAa;AAEhD,KAAI,MAAM,cAAe,OAAM,eAAe;AAE9C,OAAM,wBAAwB,QAAQ;CAEtC,MAAM,WAAW,MAAM,gBAAgB,SAAS,eAAe,eAAe,CAAC,MAC7E,OAAO,UAAmB;AACxB,MAAI,iBAAiB,WAAW;GAC9B,MAAM,YAAY,MAAM,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG;AACrE,cACE;IACE,QAAQ;IACR,MAAM,EAAE,IAAI;IACZ,OAAO;IACP;IACA,QAAQ,MAAM,SAAS;IACvB;IACD,EACD,eACA,UACD;;AAEH,QAAM;GAET;CACD,MAAM,cAAc,CAAC,eAAe,SAAS;AAE7C,YACE;EACE,QAAQ;EACR,MAAM,EAAE,IAAI;EACZ,OAAO;EACP;EACA,QAAQ;EACR,WAAW;EACZ,EACD,eACA,UACD;AAED,KAAI,CAAC,aAAa;AAChB,MAAI,aACF,SAAQ,MAAM,2BAA2B,KAAK,UAAU,SAAS,CAAC;AAEpE,SAAO,EAAE,KAAK,SAAS;;CAOzB,MAAM,WAAY,SAChB,OAAO,gBACN;CAGH,IAAIC;CACJ,IAAI,mBAAmB;AACvB,QAAO,MAAM;EACX,MAAM,IAAI,MAAM,SAAS,MAAM;AAC/B,MAAI,EAAE,MAAM;AACV,sBAAmB;AACnB;;AAIF,MAAI,EAAE,UAAU,UAAa,EAAE,UAAU,KAAM;AAC/C,MAAI,EAAE,MAAM,SAAS,UAAU;AAC7B,sBAAmB;AACnB;;AAEF,MAAI,CAAC,EAAE,MAAM,KAAM;AACnB,eAAa,EAAE;AACf;;AAEF,KAAI,eAAe,OACjB,SAAQ,KACN,qDAAqD,EAAE,IAAI,OAC5D;CAGH,IAAIC,oBAAkD;CACtD,IAAI,oBAAoB;CAExB,MAAM,aAAa,eAA4D;AAC7E,MAAI;AACF,cAAW,OAAO;UACZ;;CAIV,MAAM,mBAAmB,WAAqB;AAC5C,MAAI,OAAO,SAAS,WAAW,WAC7B,UAAS,OAAO,OAAO,CAAC,YAAY,GAElC;;CAGN,MAAM,eACJ,YACA,UACY;AACZ,MAAI;AACF,cAAW,QAAQ,MAAM;AACzB,UAAO;WACA,GAAG;AACV,OAAI,wBAAwB,EAAE,EAAE;AAC9B,wBAAoB;AAKpB,oBAAgB,EAAE;AAClB,WAAO;;AAET,SAAM;;;AAIV,QAAO,IAAI,SACT,IAAI,eAA2B;EAC7B,MAAM,KAAK,YAAY;AACrB,OAAI,qBAAqB,kBAAkB;AACzC,cAAU,WAAW;AACrB;;AAEF,OAAI,sBAAsB,QAAW;IACnC,MAAM,QAAQ;AACd,wBAAoB;AACpB,QAAI,aACF,SAAQ,MAAM,oBAAoB,KAAK,UAAU,MAAM,CAAC;AAE1D,gBAAY,YAAY,QAAQ,OAAO,UAAU,MAAM,CAAC,CAAC;AACzD;;AAEF,OAAI;IACF,MAAM,SAAS,MAAM,SAAS,MAAM;AACpC,QAAI,mBAAmB;AACrB,eAAU,WAAW;AACrB;;AAEF,QAAI,OAAO,MAAM;AACf,wBAAmB;AACnB,eAAU,WAAW;AACrB;;AAMF,QAAI,OAAO,UAAU,UAAa,OAAO,UAAU,KAAM;AACzD,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,wBAAmB;AACnB,eAAU,WAAW;AACrB;;AAEF,QAAI,CAAC,OAAO,MAAM,KAAM;AACxB,QAAI,aACF,SAAQ,MAAM,oBAAoB,KAAK,UAAU,OAAO,MAAM,CAAC;AAEjE,gBAAY,YAAY,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC,CAAC;YACzD,OAAO;AACd,uBAAmB;AACnB,QAAI,mBAAmB;AAUrB,qBAAgB,MAAM;AACtB,eAAU,WAAW;AACrB;;IAEF,MAAM,EAAE,SAAS,eAAe,eAAe,EAAE,IAAI,MAAM,MAAM;AACjE,gBACE,YACA,QAAQ,OAAO,sBAAsB,SAAS,WAAW,CAAC,CAC3D;AAGD,oBAAgB,MAAM;AACtB,cAAU,WAAW;;;EAGzB,SAAS;AACP,uBAAoB;AACpB,sBAAmB;AACnB,oBAAiB;;EAEpB,CAAC,EACF;EACE,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,iBAAiB;GACjB,qBAAqB;GACrB,YAAY;GACb;EACF,CACF;;AAGH,MAAM,kBACJ,aACqC,OAAO,OAAO,UAAU,SAAS;AAExE,eAAe,wBACb,SACe;AAEf,KAAI,CADiB,QAAQ,OAAO,MAAM,MAAM,EAAE,SAAS,aAAa,CACrD;AAGnB,KAAI,MAAM,QAAQ,QAAQ,MAAM,EAI9B;MAHoB,QAAQ,MAAM,MAC/B,SAA6B,KAAK,SAAS,uBAC7C,CACgB;;CAGnB,MAAM,QAAQ,iBAAiB,QAAQ,MAAM;AAC7C,KAAI,MACF,KAAI;EACF,MAAM,UAAU,MAAM,UAAU,MAAM;EACtC,MAAM,gBAAgB;GACpB;GACA,QAAQ;GACR;GACA,QAAQ,WAAW,KAAK,MAAM,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK;GACpE;GACD,CAAC,KAAK,KAAK;AAEZ,UAAQ,eACN,QAAQ,eACN,GAAG,cAAc,MAAM,QAAQ,iBAC/B;UACG,OAAO;AACd,UAAQ,KAAK,kDAAkD,MAAM;;AAezE,SAAQ,QAAQ,QAAQ,OAAO,QAAQ,MAAM,EAAE,SAAS,aAAa;AACrE,KAAI,QAAQ,SAAS,QAAQ,MAAM,WAAW,EAC5C,SAAQ,QAAQ;AAElB,KAAI,CAAC,QAAQ,MACX,SAAQ,cAAc;UAEtB,QAAQ,eACL,OAAO,QAAQ,gBAAgB,UAClC;EACA,MAAM,SAAS,QAAQ;AAKvB,OADmB,OAAO,UAAU,QAAQ,OAAO,UAChC,aACjB,SAAQ,cAAc;;;AAK5B,SAAS,iBACP,OACoB;AACpB,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO;AAGlC,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,OAAO,MAAM;AACnB,MAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ;AAC1C,OAAI,OAAO,KAAK,YAAY,SAAU,QAAO,KAAK;AAClD,OAAI,MAAM,QAAQ,KAAK,QAAQ,EAAE;IAC/B,MAAM,OAAO,KAAK,QAAQ,MACvB,MAA+B,EAAE,SAAS,aAC5C;AACD,QAAI,QAAQ,UAAU,KAAM,QAAO,KAAK;;;;;;;;;;AAYhD,MAAM,oBAAoB;;;;;;;;;AAiB1B,eAAsB,uBAAuB,GAAY;CACvD,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAM,eAAe,MAAM;AAE3B,KAAI,CAAC,MAAM,aAAc,OAAM,IAAI,MAAM,0BAA0B;AAEnE,KAAI,MAAM,cAAe,OAAM,eAAe;CAE9C,MAAM,OAAO,MAAM,EAAE,IAAI,MAA6B;CAGtD,MAAM,WAAW,MAAM,MACrB,GAAG,eAAe,MAAM,CAAC,qBACzB;EACE,QAAQ;EACR,SAAS,eAAe,MAAM;EAC9B,MAAM,KAAK,UAAU,KAAK;EAC3B,CACF;AAED,KAAI,SAAS,IAAI;AACf,aACE;GAAE,QAAQ;GAAQ,MAAM,EAAE,IAAI;GAAM,QAAQ;GAAK,EACjD,QACA,UACD;AACD,SAAO,EAAE,KAAK,MAAM,SAAS,MAAM,CAAC;;AAKtC,KAAI,SAAS,WAAW,KAAK;AAC3B,UAAQ,MAAM,8EAA8E;AAC5F,SAAO,MAAM,iBAAiB,GAAG,MAAM,UAAU;;AAInD,YACE;EAAE,QAAQ;EAAQ,MAAM,EAAE,IAAI;EAAM,QAAQ,SAAS;EAAQ,EAC7D,QACA,UACD;AACD,OAAM,IAAI,UAAU,4CAA4C,SAAS;;;;;;;AAQ3E,eAAe,iBACb,GACA,MACA,WACA;CACA,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE;AAG9D,OAAM,KAAK;EACT,MAAM;EACN,MAAM;EACN,SAAS,CAAC;GAAE,MAAM;GAAc,MAAM;GAAmB,CAAC;EAC3D,CAAC;CAEF,MAAMC,UAA4B;EAChC,OAAO,KAAK;EACL;EACP,cAAc,KAAK;EACnB,QAAQ;EACR,OAAO;EACR;CAED,IAAIC;AACJ,KAAI;AACF,WAAU,MAAM,gBAAgB,QAAQ;UACjC,OAAO;AACd,MAAI,iBAAiB,UACnB,YACE;GAAE,QAAQ;GAAQ,MAAM,EAAE,IAAI;GAAM,QAAQ,MAAM,SAAS;GAAQ,EACnE,QACA,UACD;AAEH,QAAM;;AAGR,YACE;EAAE,QAAQ;EAAQ,MAAM,EAAE,IAAI;EAAM,QAAQ;EAAK,EACjD,QACA,UACD;AAED,QAAO,EAAE,KAAK;EACZ,IAAI,gBAAgB,YAAY,CAAC,QAAQ,MAAM,GAAG,CAAC,MAAM,GAAG,GAAG;EAC/D,QAAQ;EACR,YAAY,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;EACzC,QAAQ,OAAO;EACf,OAAO,OAAO,SAAS;GAAE,cAAc;GAAG,eAAe;GAAG,cAAc;GAAG;EAC9E,CAAC;;;;;ACheJ,MAAa,kBAAkB,IAAI,MAAM;AAEzC,gBAAgB,KAAK,KAAK,OAAO,MAAM;AACrC,KAAI;AACF,SAAO,MAAM,gBAAgB,EAAE;UACxB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;AAEF,gBAAgB,KAAK,YAAY,OAAO,MAAM;AAC5C,KAAI;AACF,SAAO,MAAM,uBAAuB,EAAE;UAC/B,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACjBF,MAAa,eAAe,IAAI,MAAM;AAEtC,aAAa,KAAK,KAAK,OAAO,MAAM;AAClC,KAAI;EACF,MAAM,EAAE,UAAU,MAAM,EAAE,IAAI,MAAyB;AAEvD,MAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO,EAAE,KACP,EAAE,OAAO,EAAE,SAAS,iCAAiC,EAAE,EACvD,IACD;EAGH,MAAM,UAAU,MAAM,UAAU,MAAM;AACtC,SAAO,EAAE,KAAK,EAAE,SAAS,CAAC;UACnB,OAAO;AACd,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACnBF,MAAa,aAAa,IAAI,MAAM;AAEpC,WAAW,IAAI,MAAM,MAAM;AACzB,KAAI,CAAC,MAAM,UACT,QAAO,EAAE,KACP,EAAE,OAAO;EAAE,SAAS;EAA2B,MAAM;EAAS,EAAE,EAChE,IACD;AAGH,QAAO,EAAE,KAAK,EACZ,OAAO,MAAM,cACd,CAAC;EACF;;;;ACXF,MAAa,aAAa,IAAI,MAAM;AAEpC,WAAW,IAAI,KAAK,OAAO,MAAM;AAC/B,KAAI;EACF,MAAM,QAAQ,MAAM,iBAAiB;AACrC,SAAO,EAAE,KAAK,MAAM;UACb,OAAO;AACd,UAAQ,MAAM,iCAAiC,MAAM;AACrD,SAAO,MAAM,aAAa,GAAG,MAAM;;EAErC;;;;ACDF,MAAa,SAAS,IAAI,MAAM;AAEhC,OAAO,IAAI,MAAM,CAAC;AAElB,OAAO,IAAI,MAAM,MAAM,EAAE,KAAK,iBAAiB,CAAC;AAKhD,OAAO,IAAI,aAAa,MACtB,EAAE,KAAK;CACCC;CACGC;CACT,QAAQ,QAAQ,IAAI,cAAc;CACnC,CAAC,CACH;AAGD,OAAO,GAAG,QAAQ,CAAC,IAAI,GAAG,MAAM,EAAE,KAAK,MAAM,IAAI,CAAC;AAElD,OAAO,MAAM,qBAAqB,iBAAiB;AACnD,OAAO,MAAM,cAAc,gBAAgB;AAC3C,OAAO,MAAM,WAAW,YAAY;AACpC,OAAO,MAAM,eAAe,gBAAgB;AAC5C,OAAO,MAAM,WAAW,aAAa;AACrC,OAAO,MAAM,UAAU,WAAW;AAClC,OAAO,MAAM,UAAU,WAAW;AAGlC,OAAO,MAAM,wBAAwB,iBAAiB;AACtD,OAAO,MAAM,iBAAiB,gBAAgB;AAC9C,OAAO,MAAM,cAAc,YAAY;AACvC,OAAO,MAAM,kBAAkB,gBAAgB;AAC/C,OAAO,MAAM,cAAc,aAAa;AAGxC,OAAO,MAAM,gBAAgB,cAAc;AAQ3C,OAAO,MAAM,QAAQ,UAAU;AAI/B,OAAO,KAAK,6BAA6B,MAAM,EAAE,KAAK,MAAM,IAAI,CAAC;AAcjE,OAAO,IAAI,gBAAgB,MACzB,EAAE,KACA;CACE,MAAM;CACN,OAAO;EACL,MAAM;EACN,SACE;EAEH;CACF,EACD,IACD,CACF;AAGD,OAAO,UAAU,MACf,EAAE,KACA;CACE,MAAM;CACN,OAAO;EACL,MAAM;EACN,SAAS,GAAG,EAAE,IAAI,OAAO,GAAG,EAAE,IAAI,KAAK;EACxC;CACF,EACD,IACD,CACF;;;;AC7FD,MAAM,mBAAmB;AAgBzB,eAAsB,cACpB,SACkE;AAClE,KAAI,QAAQ,SACV,mBAAkB;AAGpB,KAAI,QAAQ,SAAS;AACnB,UAAQ,QAAQ;AAChB,UAAQ,KAAK,0BAA0B;;AAGzC,OAAM,cAAc,QAAQ;AAC5B,KAAI,QAAQ,gBAAgB,aAC1B,SAAQ,KAAK,SAAS,QAAQ,YAAY,sBAAsB;AAGlE,OAAM,gBAAgB,QAAQ;AAC9B,OAAM,mBAAmB,QAAQ;AACjC,OAAM,gBAAgB,QAAQ;AAC9B,OAAM,YAAY,QAAQ;AAC1B,OAAM,gBAAgB,QAAQ;AAE9B,KAAI,QAAQ,IAAI,gBACd,OAAM,gBAAgB,QAAQ,IAAI;AAGpC,OAAM,aAAa;AACnB,OAAM,oBAAoB;AAC1B,OAAM,qBAAqB;AAE3B,KAAI,QAAQ,aAAa;AACvB,QAAM,cAAc,QAAQ;AAC5B,UAAQ,KAAK,8BAA8B;OAE3C,OAAM,kBAAkB;AAG1B,OAAM,mBAAmB;AACzB,OAAM,aAAa;AAEnB,SAAQ,MACN,uBAAuB,MAAM,QAAQ,KAAK,KAAK,UAAU,KAAK,MAAM,KAAK,CAAC,KAAK,KAAK,GACrF;CAED,MAAM,eAAe;EACnB,OAAOC,OAAI;EACX,UAAU;EACV,QAAQ,QAAQ;EACjB;CAED,IAAIC;AAEJ,KAAI,QAAQ,SAAS,OAEnB,cAAa,MAAM;EAAE,GAAG;EAAc,MAAM,QAAQ;EAAM,CAAC;MACtD;EAEL,IAAIC;AACJ,OAAK,IAAI,UAAU,GAAG,UAAU,kBAAkB,WAAW;GAC3D,MAAM,gBAAgB,oBAAoB;AAC1C,OAAI;AACF,iBAAa,MAAM;KAAE,GAAG;KAAc,MAAM;KAAe,CAAC;AAC5D;YACO,OAAO;AACd,gBAAY;AAOZ,QAAI,EALF,iBAAiB,UACb,MAAM,QAAQ,SAAS,aAAa,IACnC,MAAM,QAAQ,SAAS,yBAAyB,IAC/C,UAAU,SACR,MAAgC,SAAS,eACjC,OAAM;AACxB,YAAQ,MAAM,QAAQ,cAAc,4BAA4B;;;AAIpE,MAAI,eAAe,OACjB,OAAM,IAAI,MACR,0CAA0C,iBAAiB,wEACK,YACjE;;AAKL,OAAM,WAAW,OAAO;CACxB,MAAM,MAAM,WAAW;AACvB,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,0CAA0C;CAE5D,MAAM,YAAY,IAAI,QAAQ,OAAO,GAAG;AAExC,QAAO;EAAE,QAAQ;EAAY;EAAW;;;AAI1C,MAAa,mBAAmB;CAC9B,MAAM;EACJ,OAAO;EACP,MAAM;EACN,aAAa;EACd;CACD,SAAS;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,gBAAgB;EACd,OAAO;EACP,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,QAAQ;EACN,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,cAAc;EACZ,OAAO;EACP,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,OAAO;EACP,MAAM;EACN,SAAS;EACT,aACE;EACH;CACD,gBAAgB;EACd,OAAO;EACP,MAAM;EACN,aACE;EACH;CACD,cAAc;EACZ,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,aAAa;EACX,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,kBAAkB;EAChB,MAAM;EACN,SAAS;EACT,aACE;EACH;CACF;AAED,MAAM,sBAAsB,IAAI,IAAI;CAAC;CAAc;CAAY;CAAa,CAAC;;AAG7E,SAAgB,gBAAgB,MAW9B;CACA,MAAM,UAAU,KAAK;CACrB,IAAIC;AACJ,KAAI,YAAY,QAAW;AACzB,SAAO,OAAO,SAAS,SAAS,GAAG;AACnC,MAAI,OAAO,MAAM,KAAK,IAAI,QAAQ,KAAK,OAAO,MAC5C,OAAM,IAAI,MAAM,6CAA6C;;CAIjE,MAAM,cAAe,KAAK,mBAA8B;AACxD,KAAI,CAAC,oBAAoB,IAAI,YAAY,CACvC,OAAM,IAAI,MACR,qEACD;CAGH,MAAM,eAAe,KAAK;CAC1B,IAAIC;AACJ,KAAI,iBAAiB,QAAW;AAC9B,cAAY,OAAO,SAAS,cAAc,GAAG;AAC7C,MAAI,OAAO,MAAM,UAAU,IAAI,aAAa,EAC1C,OAAM,IAAI,MAAM,kDAAkD;;CAItE,MAAM,gBAAiB,KAAK,QAAoB,cAAc;AAC9D,KAAK,KAAK,QAAoB,cAAc,OAC1C,SAAQ,KAAK,yDAAyD;CAGxE,MAAM,cACH,KAAK,mBAA0C,QAAQ,IAAI;AAE9D,QAAO;EACL;EACA,SAAS,KAAK;EACd;EACA,QAAQ,KAAK;EACb;EACA;EACA;EACA,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,eAAe,KAAK;EACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CH,SAAgB,qBACd,WACA,OACwB;CACxB,MAAMC,OAA+B;EAEnC,oBAAoB;EAKpB,mBAAmB,MAAM;EA4BzB,aAAa;EACb,kBAAkB;EAQlB,mCAAmC;EACnC,0CAA0C;EAC1C,mBAAmB;EACpB;AACD,KAAI,MAAO,MAAK,kBAAkB;AAWlC,KAAI,QAAQ,IAAI,+BAA+B,OAC7C,MAAK,6BAA6B;AAqBpC,KAAI,QAAQ,IAAI,mCAAmC,OACjD,MAAK,iCAAiC;AAExC,KAAI,QAAQ,IAAI,kCAAkC,OAChD,MAAK,gCAAgC;AAEvC,KAAI,QAAQ,IAAI,iCAAiC,OAC/C,MAAK,+BAA+B;AA0CtC,MAAK,MAAM,OAPwC;EACjD;EACA;EACA;EACA;EACA;EACD,CAEC,KAAI,QAAQ,IAAI,SAAS,OACvB,MAAK,OAAO;AAIhB,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,gBAAgB,WAA2C;AACzE,QAAO;EACL,iBAAiB,GAAG,UAAU;EAC9B,gBAAgB;EAEhB,YAAY,MAAM;EACnB;;;;;AC9ZH,MAAa,SAAS,cAAc;CAClC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,GAAG;EACH,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACd;EACD,aAAa;GACX,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,aAAa;GACX,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,kBAAkB;GAChB,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,eAAe;GACb,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,gBAAgB;GACd,MAAM;GACN,SAAS;GACT,aACE;GACH;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;AAClB,MAAI,CAACC,UAAQ,OAAO,OAAO;AACzB,WAAQ,MAAM,+DAA+D;AAC7E,aAAQ,KAAK,EAAE;;EAGjB,MAAM,SAAS,gBAAgB,KAA2C;AAkB1E,MAAI,KAAK,SAAS;AAEhB,UAAO,gBAAgB;AACvB,WAAQ,KACN,yEACD;aACQ,CAAC,KAAK,kBAEf,QAAO,gBAAgB;AAYzB,MAAI,KAAK,oBAAoB,MAC3B,KAAI;GACF,MAAM,eAAe,MAAM,mBAAmB,EAC5C,SAAS,OACV,CAAC;AACF,OAAI,aAAa,WAAW,aAAa,eAAe,YAGtD,SAAQ,MACN,uDACD;YACQ,aAAa,WAAW,aAAa,eAAe,SAE7D,SAAQ,MACN,0FACD;YAED,aAAa,eACV,aAAa,oBACb,aAAa,cAEhB,KAAI,KAAK,mBAAmB,MAC1B,KAAI;AACF,UAAM,iBAAiB,aAAa,cAAc;YAC3C,KAAK;IACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,YAAQ,KACN,mCAAmC,aAAa,iBAAiB,MAAM,aAAa,cAAc,WAAW,IAAI,kHAClH;;OAGH,SAAQ,KACN,gBAAgB,aAAa,iBAAiB,kBAAkB,aAAa,cAAc,4IAC5F;WAGE,KAAK;AAEZ,WAAQ,MAAM,gCAAgC,IAAI;;EAItD,IAAIC;EACJ,IAAIC;AACJ,MAAI;GACF,MAAM,SAAS,MAAM,cAAc;IACjC,GAAG;IACH,MAAM,OAAO;IACb,QAAQ;IACT,CAAC;AACF,cAAS,OAAO;AAChB,eAAY,OAAO;WACZ,OAAO;AACd,WAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACxF,aAAQ,KAAK,EAAE;;AAejB,MAAI;AACF,SAAM,0BAA0B;WACzB,KAAK;AACZ,WAAQ,MACN,iDACE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACjD,0DACF;AACD,aAAQ,KAAK,EAAE;;AAGjB,qBAAmB;EAkBnB,MAAM,eAAe,CAAC,KAAK;EAE3B,IAAI,aADkB,KAAK,SAAS,mBAAmB;EAEvD,IAAI,eAAe,aAAa,WAAW;AAE3C,MAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,SACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,aAAa,KAAK,CAAC,IAAI;AACjE,OAAI,CAAC,QAAQ,WAAW,EACtB;SAAK,MAAM,YAAY,+BACrB,KAAI,QAAQ,SAAS,EAAE;AACrB,aAAQ,KACN,kBAAkB,WAAW,qDAAqD,SAAS,IAC5F;AACD,kBAAa;AACb,oBAAe,aAAa,SAAS;AACrC;;;;AAMR,MAAI,iBAAiB,WACnB,SAAQ,KAAK,UAAU,WAAW,iBAAiB,aAAa,GAAG;AAGrE,MAAI,CADe,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,aAAa,EACvD;GACf,MAAM,YAAY,sBAAsB,eAAe;AACvD,WAAQ,KACN,UAAU,aAAa,wCAAwC,UAAU,KAAK,KAAK,GACpF;;EAKH,MAAM,SACJ,eAAe,eACX,aACA,GAAG,WAAW,KAAK;AAEzB,YAAQ,OAAO,MAAM,mBAAmB,UAAU,2BAA2B,OAAO,QAAQ;EAE5F,MAAM,UAAU,qBAAqB,WAAW,WAAW;EAC3D,MAAM,YAAc,KAA4C,KAAkB,EAAE;EA2BpF,MAAM,eAAe,YAA2B;AAC9C,SAAM,6BAA6B;;EAErC,IAAIC,aAAkC;AAEtC,MADyB,KAAiC,iBAAiB,MAEzE,KAAI;GACF,MAAM,eACF,KAAiC,gBAAwC;GAC7E,MAAM,UAAU,uBAAuB;IACrC,WAAW;IACX,WAAW,eAAe,iBAAiB,GAAG;IAC/C,CAAC;GACF,MAAMC,oBACJ,MAAM,QAAQ,KAAK,MAAM,MAAM,oBAAoB,KAAK,EAAE,GAAG,CAAC,IAAI;AACpE,OAAI,CAACA,kBACH,SAAQ,KACN,gHACD;GAGH,MAAM,UAAU,MAAM,yBAAyB,WAAW;IACxD,UAAU,YAAY;IACtB;IACD,CAAC;AACF,SAAM,eAAe,QAAQ;AAC7B,gBAAa,YAA2B;AACtC,UAAM,QAAQ,SAAS;AACvB,UAAM,cAAc;;GAiBtB,MAAM,WAAW,MAAM,wBAAwB,WAAW;IACxD,UAAU,YAAY;IACtB;IACA,OAAO,QAAQ;IAChB,CAAC;AAOF,OAAI,CAAC,SAAS,IAAI;AAChB,cAAU,KAAK,gBAAgB,QAAQ,cAAc;AACrD,QAAK,KAAiC,sBAAsB,KAC1D,WAAU,KAAK,sBAAsB;cAE7B,KAAiC,sBAAsB,KAKjE,SAAQ,KACN,qNAID;GAGH,MAAM,eAAe,QAAQ,SAAS,KAAK,MAAM,EAAE,UAAU,CAAC,KAAK,KAAK;GACxE,MAAM,qBAAqB,SAAS,KAChC,2CAA2C,SAAS,aAAa,KAAK,KAAK,CAAC,MAC5E,2DAA2D,SAAS,mBAAmB,KAAK,KAAK,CAAC;AACtG,aAAQ,OAAO,MACb,2BAA2B,QAAQ,cAAc,aAAa,wBACpC,QAAQ,aAAa,OAAO,IAAI,mBAAmB,MAC9E;GAWD,MAAM,uBACJJ,UAAQ,IAAI,4BAA4B,KAEvC,MAAM,CACN,aAAa;AAOhB,OAAI,EALF,wBAAwB,MACrB,wBAAwB,OACxB,wBAAwB,WACxB,wBAAwB,SACxB,wBAAwB,MAE3B,WAAU,KACR,0BACA,0BAA0B;IACxB,UAAU,YAAY;IACtB;IACD,CAAC,CACH;WAEI,KAAK;AACZ,WAAQ,KACN,2DACE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAEnD;;AAIL,cACE;GAAE,MAAM;GAAe;GAAS;GAAW,OAAO;GAAY,EAC9DK,UACA,EAAE,YAAY,CACf;;CAEJ,CAAC;;;;AC/YF,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,GAAG;EACH,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;AAClB,MAAI,CAACC,UAAQ,OAAO,OAAO;AACzB,WAAQ,MAAM,8DAA8D;AAC5E,aAAQ,KAAK,EAAE;;EAGjB,MAAM,SAAS,gBAAgB,KAA2C;EAE1E,IAAIC;EACJ,IAAIC;AACJ,MAAI;GACF,MAAM,SAAS,MAAM,cAAc;IACjC,GAAG;IACH,MAAM,OAAO;IACb,QAAQ;IACT,CAAC;AACF,cAAS,OAAO;AAChB,eAAY,OAAO;WACZ,OAAO;AACd,WAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACxF,aAAQ,KAAK,EAAE;;EAGjB,MAAM,eAAe,CAAC,KAAK;EAC3B,MAAM,iBAAiB,KAAK,SAAS;AAIrC,qBAAmB;EAEnB,IAAI,aAAa,kBAAkB,eAAe;AAClD,MAAI,eAAe,eACjB,SAAQ,KAAK,UAAU,eAAe,iBAAiB,WAAW,GAAG;AAOvE,MAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,OACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,GAAG,IAAI;AACjD,OAAI,CAAC,QAAQ,WAAW,CACtB,MAAK,MAAM,YAAY,+BAA+B;IACpD,MAAM,WAAW,kBAAkB,SAAS;AAC5C,QAAI,QAAQ,SAAS,EAAE;AACrB,aAAQ,KACN,kBAAkB,WAAW,qDAAqD,SAAS,IAC5F;AACD,kBAAa;AACb;;;;EAOR,MAAM,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,WAAW;AACtE,MAAI,CAAC,YAAY;GACf,MAAM,YAAY,sBAAsB,aAAa;AACrD,WAAQ,KACN,UAAU,WAAW,uCAAuC,UAAU,KAAK,KAAK,GACjF;SACI;GACL,MAAM,MAAM,WAAW,cAAc,QAAQ;AAC7C,OAAI,IAAK,SAAQ,KAAK,yBAAyB,IAAI,gBAAgB,CAAC,SAAS;;AAI/E,YAAQ,OAAO,MAAM,mBAAmB,UAAU,yBAAyB,WAAW,QAAQ;AAK9F,cACE;GACE,MAAM;GACN,SANY,gBAAgB,UAAU;GAOtC,WANgB,KAA4C,KAAkB,EAAE;GAOhF,OAAO;GACP;GACD,EACDC,SACD;;CAEJ,CAAC;;;;AC3FF,eAAe,oBAAqC;AAClD,KAAI;EACF,MAAM,kBAAkB,IAAI,IAAI,mBAAmB,OAAO,KAAK,IAAI,CAAC;AAMpE,SAHoB,KAAK,MAAM,MAAM,GAAG,SAAS,gBAAgB,CAAC,CAG/C;SACb;AACN,SAAO;;;AAIX,SAAS,iBAAiB;CACxB,MAAM,QAAQ,OAAO,QAAQ;AAE7B,QAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,IAAI,UAAU,QAAQ,QAAQ,MAAM,EAAE;EACvD,UAAU,GAAG,UAAU;EACvB,MAAM,GAAG,MAAM;EAChB;;AAGH,eAAe,mBAAqC;AAClD,KAAI;AAEF,MAAI,EADU,MAAM,GAAG,KAAK,MAAM,kBAAkB,EACzC,QAAQ,CAAE,QAAO;AAG5B,UADgB,MAAM,GAAG,SAAS,MAAM,mBAAmB,OAAO,EACnD,MAAM,CAAC,SAAS;SACzB;AACN,SAAO;;;AAIX,eAAe,eAAmC;CAChD,MAAM,CAACC,WAAS,eAAe,MAAM,QAAQ,IAAI,CAC/C,mBAAmB,EACnB,kBAAkB,CACnB,CAAC;AAEF,QAAO;EACL;EACA,SAAS,gBAAgB;EACzB,OAAO;GACL,SAAS,MAAM;GACf,mBAAmB,MAAM;GAC1B;EACD;EACD;;AAGH,SAAS,oBAAoB,MAAuB;AAClD,SAAQ,KAAK;;WAEJ,KAAK,QAAQ;WACb,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,QAAQ,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,QAAQ,KAAK;;;aAGvF,KAAK,MAAM,QAAQ;uBACT,KAAK,MAAM,kBAAkB;;gBAEpC,KAAK,cAAc,QAAQ,OAAO;;AAGlD,SAAS,mBAAmB,MAAuB;AACjD,SAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;;AAG5C,eAAsB,SAAS,SAAyC;CACtE,MAAM,YAAY,MAAM,cAAc;AAEtC,KAAI,QAAQ,KACV,oBAAmB,UAAU;KAE7B,qBAAoB,UAAU;;AAIlC,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aAAa;EACd,EACF;CACD,IAAI,EAAE,QAAQ;AACZ,SAAO,SAAS,EACd,MAAM,KAAK,MACZ,CAAC;;CAEL,CAAC;;;;ACzHF,SAAS,WAAsB;CAC7B,MAAM,EAAE,UAAU,QAAQC;AAE1B,KAAI,aAAa,SAAS;AAExB,MAAI,IAAI,OAAO;AACb,OAAI,IAAI,MAAM,SAAS,MAAM,CAAE,QAAO;AACtC,OAAI,IAAI,MAAM,SAAS,OAAO,CAAE,QAAO;AACvC,OAAI,IAAI,MAAM,SAAS,OAAO,CAAE,QAAO;AACvC,UAAO;;AAIT,MAAI,IAAI,gCAAiC,QAAO;AAIhD,MAAI,IAAI,cAAc;GACpB,MAAM,QAAQ,IAAI,aAAa,aAAa;AAC5C,OACE,MAAM,SAAS,wBAAwB,IACpC,MAAM,SAAS,+BAA+B,CAEjD,QAAO;;AAIX,SAAO;;CAGT,MAAM,YAAY,IAAI;AACtB,KAAI,WAAW;AACb,MAAI,UAAU,SAAS,MAAM,CAAE,QAAO;AACtC,MAAI,UAAU,SAAS,OAAO,CAAE,QAAO;AACvC,MAAI,UAAU,SAAS,OAAO,CAAE,QAAO;;AAGzC,QAAO;;AAGT,SAAS,gBAAgB,OAAuB;AAC9C,QAAO,IAAI,MAAM,QAAQ,MAAM,QAAQ,CAAC;;AAG1C,SAAS,qBAAqB,OAAuB;AACnD,QAAO,IAAI,MAAM,QAAQ,MAAM,KAAK,CAAC;;;;;;;;;AAUvC,SAAgB,kBACd,SACA,eAAuB,IACf;CACR,MAAM,QAAQ,UAAU;CACxB,MAAM,kBAAkB,OAAO,QAAQ,QAAQ,CAAC,QAC7C,GAAG,WAAW,UAAU,OAC1B;CAED,IAAIC;AAEJ,SAAQ,OAAR;EACE,KAAK;AACH,kBAAe,gBACZ,KAAK,CAAC,KAAK,WAAW,QAAQ,IAAI,KAAK,qBAAqB,MAAM,GAAG,CACrE,KAAK,KAAK;AACb;EAEF,KAAK;AACH,kBAAe,gBACZ,KAAK,CAAC,KAAK,WAAW,QAAQ,IAAI,GAAG,MAAM,GAAG,CAC9C,KAAK,MAAM;AACd;EAEF,KAAK;AACH,kBAAe,gBACZ,KAAK,CAAC,KAAK,WAAW,WAAW,IAAI,GAAG,gBAAgB,MAAM,GAAG,CACjE,KAAK,KAAK;AACb;EAEF,SAAS;GAEP,MAAM,cAAc,gBACjB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,gBAAgB,MAAM,GAAG,CACzD,KAAK,IAAI;AACZ,kBAAe,gBAAgB,SAAS,IAAI,UAAU,gBAAgB;AACtE;;;AAIJ,KAAI,gBAAgB,aAGlB,QAAO,GAAG,eADR,UAAU,QAAQ,QAAQ,UAAU,eAAe,OAAO,SACvB;AAGvC,QAAO,gBAAgB;;;;;AC1FzB,SAAS,oBAAoB,SAAiB,OAAqB;AACjE,SAAQ,IAAI,GAAG,MAAM,MAAM,UAAU;AACrC,KAAI;AACF,YAAU,UAAU,QAAQ;AAC5B,UAAQ,QAAQ,UAAU,MAAM,wBAAwB;SAClD;AACN,UAAQ,KAAK,gEAAgE;;;AAIjF,SAAS,0BAA0B,WAAmB,OAAgB;AAGpE,qBADgB,kBADA,qBAAqB,WAAW,MAAM,EACX,wCAAwC,EACtD,cAAc;;AAG7C,SAAS,qBAAqB,WAAmB,OAAgB;CAC/D,MAAM,aAAa,SAAS;AAG5B,qBADgB,kBADA,gBAAgB,UAAU,EACC,YAAY,aAAa,EACvC,YAAY;;AAG3C,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,GAAG;EACH,IAAI;GACF,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,IAAI;GACF,MAAM;GACN,SAAS;GACT,aACE;GACH;EACD,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,SAAS,gBAAgB,KAA2C;EAE1E,MAAM,EAAE,cAAc,MAAM,cAAc;GACxC,GAAG;GACH,MAAM,OAAO,QAAQ;GACrB,QAAQ;GACT,CAAC;AAEF,MAAI,KAAK,GAAI,2BAA0B,WAAW,KAAK,MAAM;AAC7D,MAAI,KAAK,GAAI,sBAAqB,WAAW,KAAK,MAAM;AAExD,UAAQ,IACN,yFAAyF,UAAU,QACpG;;CAEJ,CAAC;;;;ACpEF,QAAQ,GAAG,uBAAuB,UAAU;AAC1C,SAAQ,MAAM,wBAAwB,MAAM;EAC5C;AAEF,QAAQ,GAAG,sBAAsB,UAAU;AACzC,SAAQ,MAAM,uBAAuB,MAAM;AAC3C,SAAQ,KAAK,EAAE;EACf;AAWF,MAAM,QATO,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,aACE;EACH;CACD,aAAa;EAAE;EAAM;EAAO;EAAQ;EAAO,eAAe;EAAY;EAAO;CAC9E,CAAC,CAEiB"}