copilot-relay 0.3.3 → 0.3.5

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":["parsed: URL","consolaLevelByName: Record<LogLevelName, number>","fileLevelByMethod: Record<string, number>","copilotVersion","editorPluginVersion","userAgent","copilotTokenRefreshTimer: ReturnType<typeof setTimeout> | undefined","sleep","resolve","payload: StoredCopilotToken","refreshIn","refreshIn: number","runtimeState: RuntimeState","defaultReasoningEffort: ReasoningEffort","defaultModelRouting: ModelRoutingConfig","defaultConfig: AppConfig","parsed: URL","config: Record<string, unknown>","logRetentionDays","config: AppConfig","resolve","payload: RelayPidFile","relayPids: Array<number>","isRecord","configPath","parsed: Record<string, unknown>","env: Record<string, unknown>","next: Record<string, unknown>","lastError: unknown","path","undiciFetch","state: ResponseTranslationState","chunks: Array<ResponseStreamEventMessage>","translated: Array<ResponsesInputItem>","continuePrefillPrompt","normalizeFinalAssistantPrefill","newMessages: Array<Message>","contentParts: Array<ContentPart>","allThinkingBlocks: Array<ClaudeThinkingBlock>","allTextBlocks: Array<ClaudeTextBlock>","allToolUseBlocks: Array<ClaudeToolUseBlock>","stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null","events: Array<ClaudeStreamEventData>","events","results: Array<WebSearchResult>","content: Array<ClaudeAssistantContentBlock>","toolCalls: Array<ToolCall>","finishReason: ChatCompletionResponse[\"choices\"][number][\"finish_reason\"]","usage: ChatCompletionResponse[\"usage\"]","buffered: Array<ChatCompletionChunk>","chunk: ChatCompletionChunk","rest: AsyncIterable<{ data?: string }>","events: Array<ClaudeStreamEventData>","events","decidedResponse: ChatCompletionResponse | undefined","bufferedChunks: Array<ChatCompletionChunk>","remainingStream: AsyncIterable<{ data?: string }> | undefined","streamState: ClaudeStreamState","claudeResponse: ClaudeResponse","chunk: ChatCompletionChunk","path","status","claudeConfigPath","defaultClaudeConfigPath","resolve","status","configRowOrder: Record<keyof StatusConfig, number>"],"sources":["../src/lib/error.ts","../src/lib/paths.ts","../src/lib/redact.ts","../src/lib/log.ts","../src/lib/auth.ts","../src/lib/state.ts","../src/lib/models.ts","../src/lib/app-config.ts","../src/lib/config.ts","../src/auth.ts","../src/lib/version.ts","../src/lib/lifecycle.ts","../src/lib/claude-settings.ts","../src/lib/defaults.ts","../src/copilot/client.ts","../src/copilot/responses.ts","../src/copilot/chat.ts","../src/lib/preflight.ts","../src/claude/utils.ts","../src/claude/tool-names.ts","../src/claude/translate.ts","../src/claude/stream.ts","../src/claude/web-search.ts","../src/claude/web-search-stream.ts","../src/lib/tokenizer.ts","../src/routes/claude.ts","../src/server.ts","../src/start.ts","../src/restart.ts","../src/status.ts","../src/stop.ts","../src/main.ts"],"sourcesContent":["// Error wrappers used to preserve upstream HTTP status and response bodies.\nexport class ProxyNotImplementedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"ProxyNotImplementedError\"\n }\n}\n\nexport class HTTPError extends Error {\n detail?: string\n response: Response\n\n constructor(message: string, response: Response, detail?: string) {\n super(message)\n this.name = \"HTTPError\"\n this.detail = detail\n this.response = response\n }\n}\n","// File-system layout for config, token cache, logs, and legacy migration paths.\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst homeDir = os.homedir()\n\nconst appDir = path.join(homeDir, \".copilot-relay\")\n\nconst legacyGithubTokenPaths = [\n path.join(homeDir, \".copilot-tennel\", \"github_token\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-tennel\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-tennel\", \"github_token\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-relay\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-relay\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-bridge\", \"github_token\"),\n]\n\nconst legacyCopilotTokenPaths = [\n path.join(homeDir, \".copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-relay\", \"copilot_token.json\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-relay\", \"copilot_token.json\"),\n]\n\nconst configPath = path.join(appDir, \"config.yaml\")\nconst copilotTokenPath = path.join(appDir, \"copilot_token.json\")\nconst githubTokenPath = path.join(appDir, \"github_token\")\nconst logsDir = path.join(appDir, \"logs\")\nconst logFileBaseName = \"copilot-relay\"\n// Pre-rotation installs wrote every line to this single undated file. Retention\n// still sweeps it, so upgrading installs drain themselves without manual steps.\nconst legacyLogPath = path.join(logsDir, `${logFileBaseName}.log`)\nconst pidPath = path.join(appDir, \"copilot-relay.pid\")\n\n/**\n * Local-date stamp used in log file names.\n *\n * Local rather than UTC on purpose: `logRetentionDays` is a human-facing \"how\n * many days do I keep\" setting, and a UTC stamp would roll the active file over\n * in the middle of the local afternoon for anyone west of Greenwich.\n */\nexport const formatLogDate = (date: Date): string =>\n [\n date.getFullYear(),\n String(date.getMonth() + 1).padStart(2, \"0\"),\n String(date.getDate()).padStart(2, \"0\"),\n ].join(\"-\")\n\n/**\n * Path of the log file that owns `date`. Resolved per write rather than cached,\n * so a long-running relay rotates at local midnight with no timer to drift.\n */\nexport const getLogPath = (date: Date = new Date()): string =>\n path.join(logsDir, `${logFileBaseName}.${formatLogDate(date)}.log`)\n\nexport const paths = {\n appDir,\n configPath,\n copilotTokenPath,\n githubTokenPath,\n legacyConfigPaths: [\n path.join(homeDir, \".copilot-relay.yaml\"),\n path.join(homeDir, \".copilot-tennel\", \"config.yaml\"),\n path.join(homeDir, \".copilot-tennel.yaml\"),\n ],\n legacyCopilotTokenPaths,\n legacyLogPath,\n logFileBaseName,\n logsDir,\n pidPath,\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(paths.appDir, { recursive: true })\n // GitHub tokens are plain text and safe to migrate here. Copilot token\n // migration is handled in auth.ts because that cache has a JSON schema.\n await ensureFile(paths.githubTokenPath, {\n legacyPaths: legacyGithubTokenPaths,\n })\n}\n\nasync function ensureFile(\n filePath: string,\n options: { legacyPaths?: Array<string> } = {},\n): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n for (const legacyPath of options.legacyPaths ?? []) {\n if (legacyPath === filePath) {\n continue\n }\n\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n if (content.trim()) {\n await fs.writeFile(filePath, content, { mode: 0o600 })\n await fs.chmod(filePath, 0o600)\n return\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n }\n\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}","// Disclosure-safe rendering of URLs and terminal strings.\n//\n// Exists because copilotBaseUrl is user-supplied and may legitimately carry a\n// credential in its path — `https://gateway.example/tenant/token-ABC` is a\n// working custom-gateway config, used as `${base}/models`. That value was\n// written verbatim to the log on every start and into `status` output, which is\n// the one file wiki/EN-Logging-Troubleshooting.md asks users to paste into an\n// issue. See #47.\n//\n// Pure and dependency-free on purpose: it sits on the logging path, so anything\n// that could throw or block here would take a request down with it.\n\n/**\n * Origins known to carry a secret somewhere after the origin.\n *\n * Process-lifetime and append-only. Never pruned, because config hot-reload can\n * change copilotBaseUrl while a request against the previous one is still in\n * flight; dropping the old policy would un-redact that request's error on its\n * way to the log.\n */\nconst sensitiveOrigins = new Set<string>()\n\n/** Marker written in place of everything after the origin. */\nconst redactedMarker = \"[redacted]\"\n\n/**\n * ANSI escape sequences: CSI (`ESC [ … final`), OSC (`ESC ] … BEL/ST`), and the\n * two-byte Fe forms. Matched whole rather than stripping the ESC byte alone,\n * which would leave the printable remainder (`[2K`) in the output.\n *\n * Written with \\x escapes rather than literal bytes: an invisible ESC in the\n * source survives no round trip through an editor, a diff view, or a paste.\n */\nconst ansiEscapePattern =\n /\\x1B(?:\\[[0-?]*[ -/]*[@-~]|\\][\\s\\S]*?(?:\\x07|\\x1B\\\\)|[@-Z\\\\-_])/g\n\n/** C0 controls and DEL, including CR/LF: one config value is one row. */\nconst controlBytePattern = /[\\x00-\\x1F\\x7F]/g\n\n/**\n * Absolute http(s) URLs inside arbitrary text.\n *\n * Stops at whitespace, quotes, backticks and angle brackets so a URL embedded\n * in inspect() output — `url: 'https://host/x'` — matches without swallowing\n * the surrounding punctuation.\n *\n * Backslashes are consumed rather than terminating the match. WHATWG folds `\\`\n * into `/` for http(s), so `https://host\\tenant\\TOKEN` parses with the tail in\n * the path, carries no userinfo, and is therefore accepted and stored verbatim\n * by normalizeCopilotBaseUrl — and inspect() doubles each backslash on the way\n * to both sinks. Stopping at the first one matched only the origin, which then\n * looked origin-only and left the entire tail in the log.\n */\nconst absoluteUrlPattern = /https?:\\/\\/[^\\s'\"`<>]+/gi\n\nconst parseHttpUrl = (value: string): URL | undefined => {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n return undefined\n }\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ?\n parsed\n : undefined\n}\n\n/** True when the URL carries nothing after the origin worth hiding. */\nconst isOriginOnly = (parsed: URL): boolean =>\n (parsed.pathname === \"\" || parsed.pathname === \"/\")\n && !parsed.search\n && !parsed.hash\n\n/**\n * Renders a URL for display, keeping the origin and hiding the rest.\n *\n * The origin is the operationally useful half — it answers \"which gateway am I\n * talking to\" — while path, query and fragment are where a credential ends up.\n * An explicit marker rather than silent truncation, so a reader can tell the\n * difference between a base URL that has no path and one whose path was\n * withheld.\n *\n * Never throws: it also runs on values that failed validation.\n */\nexport const formatUrlForDisplay = (raw: string): string => {\n const parsed = parseHttpUrl(raw)\n if (!parsed) {\n return \"(invalid url)\"\n }\n\n // Returned as written, not re-serialized, so a trailing slash the user\n // configured still reads back the way their config file has it.\n return isOriginOnly(parsed) ?\n raw\n : `${parsed.origin} (path/query/fragment hidden)`\n}\n\n/**\n * Strips ANSI escapes and control bytes from a string bound for the terminal.\n *\n * `status` prints config values directly to stdout. A config string carrying\n * raw control bytes could clear the line and paint status rows the relay never\n * produced. The config file is user-owned, so this is hardening rather than a\n * vulnerability — but it costs nothing to make the output non-forgeable. #47.\n */\nexport const sanitizeTerminalString = (text: string): string =>\n text.replace(ansiEscapePattern, \"\").replace(controlBytePattern, \"\")\n\n/**\n * Marks a URL's origin as secret-bearing, if it has anything after the origin.\n *\n * An origin-only URL registers nothing: there is no tail to hide, and marking\n * the origin would redact every unrelated URL on that host for no benefit.\n *\n * Additive and idempotent. Never throws on an unparseable value.\n */\nexport const registerSensitiveOrigin = (raw: string): void => {\n const parsed = parseHttpUrl(raw)\n if (!parsed || isOriginOnly(parsed)) {\n return\n }\n\n sensitiveOrigins.add(parsed.origin)\n}\n\n/**\n * Rewrites URLs on registered origins to `origin[redacted]`.\n *\n * Anchored on the parsed origin rather than on the configured string, which is\n * what makes the variants match: fetch reports back a canonicalized URL (host\n * case-folded, default port dropped) with the fragment already stripped, and\n * clients append their own endpoint to the base. All of those share an origin.\n *\n * Deliberately not a substring substitution of the secret path. Replacing that\n * text wherever it appeared would also rewrite unrelated prose and detached\n * paths; only an absolute URL on a registered origin is rewritten here.\n */\nexport const scrubSensitiveUrls = (text: string): string => {\n if (sensitiveOrigins.size === 0) {\n return text\n }\n\n return text.replace(absoluteUrlPattern, (match) => {\n // The whole match is treated as the URL - nothing is trimmed off the end\n // and nothing is restored after the marker.\n //\n // Trailing punctuation used to be trimmed as presumed prose and put back\n // afterwards. In a query string it is not prose, it is the value:\n // `?token=!!!` is a valid configured URL whose secret is punctuation and\n // nothing else, and the restore handed that token back whole. Arbitrary\n // log text cannot tell a token made of punctuation from a sentence that\n // ends in one, so the ambiguity is resolved toward disclosure safety: a\n // sentence-ending period after a sensitive URL is absorbed into the\n // marker. That costs a character of prose on origins already known to\n // carry a secret; the alternative cost was the secret.\n //\n // Unregistered origins are returned untouched below, so ordinary\n // diagnostic URLs keep their punctuation byte-for-byte.\n //\n // There is also no text-level shortcut for already-scrubbed input.\n // Skipping anything that *ended* with the marker was a bypass, not an\n // optimization: `https://host/tenant/SECRET[redacted]` is a valid\n // configured value, and that check handed it back with the secret intact.\n // Idempotence is structural instead - `origin[redacted]` is not a\n // parseable URL, because a bracket is illegal in a hostname unless it\n // delimits an IPv6 literal, so parseHttpUrl rejects it and a second pass\n // finds nothing to rewrite, for a bare host, a host with a port, and an\n // IPv6 literal alike.\n const parsed = parseHttpUrl(match)\n if (!parsed || !sensitiveOrigins.has(parsed.origin)) {\n return match\n }\n\n // Where the authority ends in the *matched text*, which may differ from\n // the canonical origin in case or port. Nothing after it means nothing to\n // hide.\n //\n // Backslash ends the authority exactly as \"/\" does: WHATWG normalizes it\n // to \"/\" for http(s), so `https://host\\tenant` has \"tenant\" in its path,\n // not its host. Omitting it here would classify that URL as origin-only\n // and return it unredacted.\n const authorityStart = match.indexOf(\"://\") + 3\n let authorityEnd = match.length\n for (let index = authorityStart; index < match.length; index += 1) {\n const character = match[index]\n if (\n character === \"/\"\n || character === \"\\\\\"\n || character === \"?\"\n || character === \"#\"\n ) {\n authorityEnd = index\n break\n }\n }\n\n return authorityEnd === match.length ?\n match\n : `${parsed.origin}${redactedMarker}`\n })\n}\n","// Central logger: writes to console and ~/.copilot-relay/logs with daily\n// rotation and retention cleanup.\nimport fs from \"node:fs/promises\"\nimport { inspect } from \"node:util\"\n\nimport consola from \"consola\"\n\nimport type { LogLevelName } from \"~/lib/app-config\"\nimport { getLogPath, paths } from \"~/lib/paths\"\nimport { scrubSensitiveUrls } from \"~/lib/redact\"\n\nconst logCleanupCheckIntervalMs = 60 * 60 * 1000\nconst millisecondsPerDay = 24 * 60 * 60 * 1000\nlet logRetentionDays = 3\nlet nextLogCleanupCheckAt = 0\n\n// Matches the dated files getLogPath() writes, e.g. copilot-relay.2026-07-24.log.\nconst datedLogFilePattern = new RegExp(\n `^${paths.logFileBaseName}\\\\.(\\\\d{4})-(\\\\d{2})-(\\\\d{2})\\\\.log$`,\n)\n\nconst consolaLevelByName: Record<LogLevelName, number> = {\n error: 0,\n info: 3,\n debug: 4,\n}\n\n// Keep runtime logging intentionally small: error carries full failure context,\n// info is operational status, and debug is detailed tracing for local diagnosis.\nconst fileLevelByMethod: Record<string, number> = {\n error: consolaLevelByName.error,\n info: consolaLevelByName.info,\n debug: consolaLevelByName.debug,\n}\n\nlet currentLogLevel = consolaLevelByName.info\n\nexport const setLogLevel = (level: LogLevelName): void => {\n currentLogLevel = consolaLevelByName[level]\n consola.level = consolaLevelByName[level]\n}\n\n/**\n * Render one logged value as a single physical line.\n *\n * `breakLength: Infinity` matters as much as the depth cap. The previous\n * `inspect(value, { depth: null })` pretty-printed each payload across\n * thousands of physical lines, which was both the dominant source of log volume\n * and the reason the `grep` recipes in wiki/EN-Logging-Troubleshooting.md\n * returned a fragment of an object instead of the matching entry. One entry is\n * now one line.\n */\nconst formatLogValue = (value: unknown): string =>\n typeof value === \"string\" ? value : (\n inspect(value, {\n breakLength: Infinity,\n // compact: true is load-bearing next to breakLength, not redundant with\n // it. The Node docs say breakLength: Infinity formats input on one line\n // \"in combination with compact set to true or any number >= 1\", which\n // reads as though the default compact: 3 would suffice. It does not: the\n // number is a count of inner elements united, not a threshold, so it only\n // collapses payloads nesting no deeper than that count. Measured on\n // v26.5.0 with a real Copilot error payload (4 levels deep):\n //\n // compact: 3 + breakLength: Infinity -> 10 lines\n // compact: 1 + breakLength: Infinity -> 22 lines\n // compact: 10 + breakLength: Infinity -> 1 line\n // compact: true + breakLength: Infinity -> 1 line\n //\n // Lowering the number makes it worse, which no threshold reading\n // predicts. compact: true is depth-independent, so it holds for payloads\n // deeper than any fixed count we might pick.\n compact: true,\n depth: 6,\n maxArrayLength: 100,\n maxStringLength: 4000,\n })\n )\n\n/**\n * Appends one already-rendered entry.\n *\n * Takes strings rather than raw values: rendering happens once in wrapFileLog,\n * so the console and the file are guaranteed to carry the same redacted text.\n * Inspecting again here would reintroduce the gap this closes - the file would\n * be scrubbed while the console showed the original object. See #47.\n */\nconst writeLogFile = async (\n level: string,\n values: Array<string>,\n): Promise<void> => {\n await fs.mkdir(paths.logsDir, { recursive: true })\n await cleanupLogsIfDue()\n const line = [new Date().toISOString(), level, values.join(\" \")].join(\" \")\n // Resolved per write, so a relay running across local midnight starts the next\n // dated file on its own; there is no rotation timer to drift or miss.\n await fs.appendFile(getLogPath(), `${line}\\n`)\n}\n\nconst cleanupLogsIfDue = async (): Promise<void> => {\n const now = Date.now()\n if (now < nextLogCleanupCheckAt) {\n return\n }\n\n nextLogCleanupCheckAt = now + logCleanupCheckIntervalMs\n await cleanupLogs(logRetentionDays)\n}\n\n/**\n * The single boundary where every logged value becomes redacted text.\n *\n * Each argument is rendered exactly once and scrubbed once, and the identical\n * array of strings goes to both the file and the console. Scrubbing only on the\n * way to disk would leave the original object on the console - the screen a\n * user screenshots into an issue - so the two sinks must not diverge. See #47.\n *\n * Rendering is skipped only when neither sink would emit, so the cost matches\n * the old behavior at info level. The console gate is read from consola rather\n * than assumed equal to currentLogLevel: if the two ever diverge, rendering\n * must follow whichever sink is still emitting, or that sink gets raw values.\n */\nconst wrapFileLog = <T extends (...args: Array<unknown>) => unknown>(\n level: string,\n fn: T,\n): T =>\n ((...args: Array<unknown>) => {\n const methodLevel = fileLevelByMethod[level] ?? consolaLevelByName.info\n const writesToFile = methodLevel <= currentLogLevel\n const writesToConsole = methodLevel <= consola.level\n\n if (!writesToFile && !writesToConsole) {\n return fn(...args)\n }\n\n const rendered = args.map((value) =>\n scrubSensitiveUrls(formatLogValue(value)),\n )\n\n if (writesToFile) {\n // File logging must never block the console path or fail a request. If\n // the disk write fails, the original consola call still runs.\n void writeLogFile(level, rendered).catch(() => undefined)\n }\n return fn(...rendered)\n }) as T\n\nconsola.error = wrapFileLog(\"error\", consola.error.bind(consola))\nconsola.info = wrapFileLog(\"info\", consola.info.bind(consola))\nconsola.debug = wrapFileLog(\"debug\", consola.debug.bind(consola))\n\n/**\n * Calendar day a log file belongs to, or undefined when its name carries no\n * date stamp.\n *\n * The filename stamp is preferred over mtime because mtime is rewritten by\n * backups, `cp`, and editors touching the file, any of which would silently\n * extend or shorten the retention window.\n */\nconst parseLogFileDate = (fileName: string): Date | undefined => {\n const match = datedLogFilePattern.exec(fileName)\n if (!match) {\n return undefined\n }\n\n const [, year, month, day] = match\n const date = new Date(Number(year), Number(month) - 1, Number(day))\n // Rejects impossible stamps such as 2026-13-45, which Date would otherwise\n // roll forward into a plausible-looking day.\n return (\n date.getFullYear() === Number(year)\n && date.getMonth() === Number(month) - 1\n && date.getDate() === Number(day)\n ) ?\n date\n : undefined\n}\n\nexport const cleanupLogs = async (retentionDays: number): Promise<void> => {\n logRetentionDays = retentionDays\n await fs.mkdir(paths.logsDir, { recursive: true })\n\n // retentionDays counts today plus the preceding retentionDays - 1 days, so the\n // default of 3 keeps today, yesterday, and the day before.\n const startOfToday = new Date()\n startOfToday.setHours(0, 0, 0, 0)\n const cutoff =\n startOfToday.getTime() - (retentionDays - 1) * millisecondsPerDay\n\n const entries = await fs.readdir(paths.logsDir, { withFileTypes: true })\n await Promise.all(\n entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".log\"))\n .map(async (entry) => {\n const filePath = `${paths.logsDir}/${entry.name}`\n const fileDate = parseLogFileDate(entry.name)\n // Undated files - notably the pre-rotation copilot-relay.log - keep the\n // original mtime rule, so upgrading installs drain without manual steps.\n const timestamp =\n fileDate?.getTime() ?? (await fs.stat(filePath)).mtimeMs\n if (timestamp < cutoff) {\n // Two relay processes can sweep the same directory; losing that race\n // is not a failure worth surfacing.\n await fs.rm(filePath, { force: true })\n }\n }),\n )\n}\n\nexport const log = consola\n","// GitHub device auth plus file-backed Copilot token cache and refresh scheduling.\nimport fs from \"node:fs/promises\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { paths, ensurePaths } from \"~/lib/paths\"\n\nconst copilotVersion = \"0.26.7\"\nconst editorPluginVersion = `copilot-chat/${copilotVersion}`\nconst userAgent = `GitHubCopilotChat/${copilotVersion}`\nconst githubApiVersion = \"2022-11-28\"\n\nconst githubApiBaseUrl = \"https://api.github.com\"\nconst githubBaseUrl = \"https://github.com\"\nconst githubClientId = \"Iv1.b507a08c87ecfe98\"\nconst githubAppScopes = [\"read:user\"].join(\" \")\n\ninterface DeviceCodeResponse {\n device_code: string\n expires_in: number\n interval: number\n user_code: string\n verification_uri: string\n}\n\ninterface AccessTokenResponse {\n access_token?: string\n}\n\ninterface CopilotTokenResponse {\n refresh_in: number\n token: string\n}\n\ninterface StoredCopilotToken {\n refreshedAt: number\n refreshIn: number\n token: string\n}\n\ninterface GitHubUserResponse {\n login: string\n}\n\ninterface AuthOptions {\n force?: boolean\n}\n\nexport interface ProxyAuthSession {\n githubLogin?: string\n source: \"github-token\"\n}\n\nlet copilotTokenRefreshTimer: ReturnType<typeof setTimeout> | undefined\n\nconst standardHeaders = () => ({\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n})\n\nconst sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nconst readGitHubToken = async () => {\n await ensurePaths()\n const token = await fs.readFile(paths.githubTokenPath, \"utf8\")\n return token.trim()\n}\n\nconst writeGitHubToken = async (token: string) => {\n await ensurePaths()\n await fs.writeFile(paths.githubTokenPath, `${token.trim()}\\n`, {\n mode: 0o600,\n })\n}\n\nconst readStoredCopilotToken = async (): Promise<StoredCopilotToken | undefined> => {\n await ensurePaths()\n try {\n const payload = JSON.parse(\n await fs.readFile(paths.copilotTokenPath, \"utf8\"),\n ) as Partial<StoredCopilotToken>\n if (\n typeof payload.token === \"string\"\n && typeof payload.refreshedAt === \"number\"\n && typeof payload.refreshIn === \"number\"\n ) {\n return payload as StoredCopilotToken\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n\n // Migrate only caches that still match the current JSON shape; older files\n // are ignored so a corrupt token cache does not poison new installs.\n for (const legacyPath of paths.legacyCopilotTokenPaths) {\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n const payload = JSON.parse(content) as Partial<StoredCopilotToken>\n if (\n typeof payload.token === \"string\"\n && typeof payload.refreshedAt === \"number\"\n && typeof payload.refreshIn === \"number\"\n ) {\n await fs.writeFile(paths.copilotTokenPath, content, { mode: 0o600 })\n await fs.chmod(paths.copilotTokenPath, 0o600)\n return payload as StoredCopilotToken\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n }\n\n return undefined\n}\n\nconst writeStoredCopilotToken = async (\n input: CopilotTokenResponse,\n): Promise<void> => {\n await ensurePaths()\n const payload: StoredCopilotToken = {\n refreshedAt: Date.now(),\n refreshIn: input.refresh_in,\n token: input.token,\n }\n await fs.writeFile(\n paths.copilotTokenPath,\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n )\n}\n\nconst getStoredTokenRemainingSeconds = (token: StoredCopilotToken): number => {\n const ageSeconds = Math.floor((Date.now() - token.refreshedAt) / 1000)\n return token.refreshIn - ageSeconds\n}\n\nconst githubHeaders = (githubToken: string, vsCodeVersion: string) => ({\n ...standardHeaders(),\n authorization: `token ${githubToken}`,\n \"editor-plugin-version\": editorPluginVersion,\n \"editor-version\": `vscode/${vsCodeVersion}`,\n \"user-agent\": userAgent,\n \"x-github-api-version\": githubApiVersion,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nconst getDeviceCode = async (): Promise<DeviceCodeResponse> => {\n const response = await fetch(`${githubBaseUrl}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: githubClientId,\n scope: githubAppScopes,\n }),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get device code\", response)\n }\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nconst pollAccessToken = async (\n deviceCode: DeviceCodeResponse,\n): Promise<string> => {\n // GitHub asks clients to poll at `interval`; add a one-second cushion so\n // long waits do not accidentally trigger OAuth `slow_down` responses.\n const sleepDuration = (deviceCode.interval + 1) * 1000\n\n // Device auth is intentionally user-driven: keep polling until the user\n // finishes authorization in the browser instead of treating pending states\n // as fatal startup errors.\n while (true) {\n const response = await fetch(`${githubBaseUrl}/login/oauth/access_token`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: githubClientId,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n })\n\n if (!response.ok) {\n await sleep(sleepDuration)\n continue\n }\n\n const json = (await response.json()) as AccessTokenResponse\n if (json.access_token) {\n return json.access_token\n }\n\n await sleep(sleepDuration)\n }\n}\n\nconst getGitHubUser = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<GitHubUserResponse> => {\n const response = await fetch(`${githubApiBaseUrl}/user`, {\n headers: githubHeaders(githubToken, vsCodeVersion),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get GitHub user\", response)\n }\n\n return (await response.json()) as GitHubUserResponse\n}\n\nconst getCopilotToken = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<CopilotTokenResponse> => {\n const response = await fetch(\n `${githubApiBaseUrl}/copilot_internal/v2/token`,\n {\n headers: githubHeaders(githubToken, vsCodeVersion),\n },\n )\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot token\", response)\n }\n\n return (await response.json()) as CopilotTokenResponse\n}\n\nconst isCopilotTokenError = (error: unknown): boolean =>\n error instanceof HTTPError && error.message === \"Failed to get Copilot token\"\n\nconst ensureGitHubToken = async (options: AuthOptions = {}) => {\n const existingToken = options.force ? \"\" : await readGitHubToken()\n if (existingToken) {\n log.info(`Using cached GitHub token at ${paths.githubTokenPath}`)\n return existingToken\n }\n\n const deviceCode = await getDeviceCode()\n log.info(\n `Open ${deviceCode.verification_uri} and enter code ${deviceCode.user_code}`,\n )\n\n const githubToken = await pollAccessToken(deviceCode)\n await writeGitHubToken(githubToken)\n log.info(`GitHub token synced to ${paths.githubTokenPath}`)\n\n return githubToken\n}\n\nconst loadGitHubLogin = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<string | undefined> => {\n try {\n const user = await getGitHubUser(githubToken, vsCodeVersion)\n return user.login\n } catch (error) {\n log.error(\"Could not load GitHub user:\", error)\n return undefined\n }\n}\n\nexport const setupProxyAuth = async (\n config: ProxyConfig,\n options: AuthOptions = {},\n): Promise<ProxyAuthSession> => {\n let githubToken = await ensureGitHubToken(options)\n const applyCopilotToken = async () => {\n const tokenResponse = await getCopilotToken(\n githubToken,\n config.vsCodeVersion,\n )\n\n config.copilotToken = tokenResponse.token\n await writeStoredCopilotToken(tokenResponse)\n return tokenResponse.refresh_in\n }\n\n const scheduleCopilotTokenRefresh = (refreshIn: number) => {\n if (copilotTokenRefreshTimer) {\n clearTimeout(copilotTokenRefreshTimer)\n }\n\n // Refresh one minute before GitHub's `refresh_in` deadline, but keep a\n // one-minute floor so failure retries do not spin in a tight loop.\n const refreshMs = Math.max(refreshIn - 60, 60) * 1000\n log.info(\n `Next Copilot token refresh in ${Math.round(refreshMs / 1000)}s`,\n )\n\n copilotTokenRefreshTimer = setTimeout(async () => {\n try {\n const nextRefreshIn = await applyCopilotToken()\n log.info(\"Refreshed Copilot token\")\n scheduleCopilotTokenRefresh(nextRefreshIn)\n } catch (error) {\n log.error(\"Failed to refresh Copilot token:\", error)\n // Keep the last token active and retry soon; in-flight requests can\n // continue until the bearer token actually expires.\n scheduleCopilotTokenRefresh(60)\n }\n }, refreshMs)\n\n if (typeof copilotTokenRefreshTimer.unref === \"function\") {\n copilotTokenRefreshTimer.unref()\n }\n }\n\n const storedToken = await readStoredCopilotToken()\n let refreshIn: number\n if (storedToken && getStoredTokenRemainingSeconds(storedToken) > 60) {\n // Avoid using tokens that are close to expiry; Claude Code requests can\n // be long-running, so keep at least one minute of freshness for new calls.\n config.copilotToken = storedToken.token\n refreshIn = getStoredTokenRemainingSeconds(storedToken)\n log.info(`Using cached Copilot token at ${paths.copilotTokenPath}`)\n } else {\n try {\n refreshIn = await applyCopilotToken()\n } catch (error) {\n if (options.force || !isCopilotTokenError(error)) {\n throw error\n }\n\n // A cached GitHub token can still become unable to mint Copilot tokens\n // after revocation or account changes; only this specific failure gets\n // a fresh device-auth attempt.\n log.error(\n \"Cached GitHub auth could not get a Copilot token; running device auth again.\",\n )\n githubToken = await ensureGitHubToken({\n ...options,\n force: true,\n })\n refreshIn = await applyCopilotToken()\n }\n }\n scheduleCopilotTokenRefresh(refreshIn)\n\n return {\n githubLogin: await loadGitHubLogin(githubToken, config.vsCodeVersion),\n source: \"github-token\",\n }\n}\n\nexport const runProxyAuth = async (\n config: ProxyConfig,\n options: AuthOptions = {},\n) => {\n const githubToken = await ensureGitHubToken({\n ...options,\n force: true,\n })\n\n const user = await getGitHubUser(githubToken, config.vsCodeVersion)\n log.info(`GitHub token written to ${paths.githubTokenPath}`)\n log.info(`Logged in as ${user.login}`)\n}","// Process-local mutable state for values that can change after config hot reload.\nimport type { ReasoningEffort } from \"~/lib/models\"\nimport type { ModelRoutingConfig } from \"~/lib/models\"\n\nexport interface RuntimeState {\n debug?: boolean\n modelRouting?: ModelRoutingConfig\n thinkEffort?: ReasoningEffort\n}\n\nexport const runtimeState: RuntimeState = {}\n","// Config-driven model routing and think-effort validation.\nimport { runtimeState } from \"~/lib/state\"\n\nexport type ReasoningEffort =\n | \"none\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n\nexport interface ModelRoutingConfig {\n gptModel: string\n opusModel: string\n}\n\nexport const defaultReasoningEffort: ReasoningEffort = \"max\"\n\nexport const defaultModelRouting: ModelRoutingConfig = {\n gptModel: \"gpt-5.6-sol\",\n opusModel: \"claude-opus-5\",\n}\n\nexport const isReasoningEffort = (value: unknown): value is ReasoningEffort =>\n value === \"none\"\n || value === \"low\"\n || value === \"medium\"\n || value === \"high\"\n || value === \"xhigh\"\n || value === \"max\"\n\nconst exactGpt56SolPattern = /^gpt-5\\.6-sol(?:\\[1m\\])*$/i\nconst claudeContextModelId = \"gpt-5.6-sol[1m]\"\nconst copilotModelId = \"gpt-5.6-sol\"\n\nexport const normalizeClaudeModelId = (model: string): string =>\n exactGpt56SolPattern.test(model) ? claudeContextModelId : model\n\nexport const normalizeCopilotModelId = (model: string): string =>\n exactGpt56SolPattern.test(model) ? copilotModelId : model\n\nconst getConfiguredModelRouting = (): ModelRoutingConfig =>\n runtimeState.modelRouting ?? defaultModelRouting\n\nexport const getModelRouting = (): ModelRoutingConfig => {\n const routing = getConfiguredModelRouting()\n return {\n gptModel: normalizeCopilotModelId(routing.gptModel),\n opusModel: normalizeCopilotModelId(routing.opusModel),\n }\n}\n\nexport const getExposedModelIds = (): Array<string> => {\n const routing = getConfiguredModelRouting()\n return [normalizeClaudeModelId(routing.gptModel), routing.opusModel]\n}\n\nexport const getUpstreamModelIds = (): Array<string> => {\n const routing = getModelRouting()\n return [routing.gptModel, routing.opusModel]\n}\n\nexport const routeModelId = (model: string): string => {\n const routing = getModelRouting()\n // Keep routing intentionally predictable for Claude Code: any alias that\n // mentions Opus gets the configured Opus upstream, all other model names use\n // the configured GPT upstream.\n return model.trim().toLowerCase().includes(\"opus\") ?\n routing.opusModel\n : routing.gptModel\n}\n","// Runtime YAML config loader/writer with hot-reload support for ~/.copilot-relay/config.yaml.\nimport fs from \"node:fs/promises\"\nimport { dirname, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nimport {\n defaultReasoningEffort,\n isReasoningEffort,\n type ReasoningEffort,\n} from \"~/lib/models\"\nimport { paths } from \"~/lib/paths\"\n\nexport const logLevels = [\"error\", \"info\", \"debug\"] as const\nexport type LogLevelName = (typeof logLevels)[number]\nexport interface AppConfig {\n claudeSetup: boolean\n copilotBaseUrl: string\n gptModel: string\n host: string\n logLevel: LogLevelName\n logRetentionDays: number\n opusModel: string\n port: number\n thinkEffort: ReasoningEffort\n upstreamTimeoutSeconds: number\n webSearchBackend?: string\n}\n\n/**\n * Values used when a key is absent from the user's config.\n *\n * readAppConfig() writes the resolved config back to disk, so an existing\n * install has every key materialized and never consults these again. Changing\n * one therefore affects fresh installs only, by design: copilot-relay does not\n * rewrite a value the user's config already holds.\n */\nconst defaultConfig: AppConfig = {\n claudeSetup: true,\n copilotBaseUrl: \"https://api.githubcopilot.com\",\n gptModel: \"gpt-5.6-sol\",\n host: \"127.0.0.1\",\n logLevel: \"info\",\n logRetentionDays: 3,\n opusModel: \"claude-opus-5\",\n port: 4142,\n thinkEffort: defaultReasoningEffort,\n upstreamTimeoutSeconds: 180,\n webSearchBackend: undefined,\n}\n\nexport const isLogLevelName = (value: unknown): value is LogLevelName =>\n typeof value === \"string\"\n && logLevels.includes(value.toLowerCase() as LogLevelName)\n\nexport const normalizeLogLevel = (value: unknown): LogLevelName | undefined => {\n if (value === undefined) {\n return undefined\n }\n\n if (typeof value !== \"string\") {\n throw new Error(\n `Invalid logLevel: expected one of ${logLevels.join(\", \")}`,\n )\n }\n\n const normalized = value.toLowerCase()\n if (!isLogLevelName(normalized)) {\n throw new Error(\n `Invalid logLevel \"${value}\": expected one of ${logLevels.join(\", \")}`,\n )\n }\n\n return normalized\n}\n\nconst normalizeBoolean = (value: unknown): boolean | undefined => {\n if (typeof value === \"boolean\") {\n return value\n }\n if (typeof value !== \"string\") {\n return undefined\n }\n const normalized = value.toLowerCase()\n if (normalized === \"true\") return true\n if (normalized === \"false\") return false\n return undefined\n}\n\nconst normalizePort = (value: unknown): number | undefined => {\n const port =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value, 10)\n : Number.NaN\n return Number.isInteger(port) && port > 0 && port <= 65_535 ? port : undefined\n}\n\nconst normalizePositiveInteger = (value: unknown): number | undefined => {\n const number =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value, 10)\n : Number.NaN\n return Number.isInteger(number) && number > 0 ? number : undefined\n}\n\nconst normalizeString = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.trim() ? value.trim() : undefined\n\n/**\n * A conventional http(s) URL: scheme followed by a literal \"//\" authority.\n *\n * WHATWG is far more permissive than this. `https:host/path`, `https:/host/path`\n * and `https:\\\\host\\path` all parse, all normalize to a real origin with the\n * tail in the path, and all work upstream - so a protocol check alone lets them\n * through. None of them can be found again in arbitrary log text: practical URL\n * detection anchors on a literal \"://\", and loosening that to chase a bare\n * \"https:\" would start matching ordinary prose. Requiring the authority prefix\n * is the one boundary where this has a definite answer. See #47.\n */\nconst conventionalHttpUrlPattern = /^https?:\\/\\//i\n\n/**\n * Raw characters that cannot be delimited safely in rendered log text.\n *\n * A raw apostrophe is the clearest case. WHATWG accepts it and leaves it raw\n * in the path, so the configured value keeps it - and inspect() renders that\n * value inside quotes, where the apostrophe reads as the closing delimiter.\n * Any URL scan ends there, and the rest of the path prints in full. Quotes,\n * backticks and angle brackets are the same story; whitespace ends a URL for\n * every reader and scanner alike; and tab and newline are worse still,\n * because URL parsing strips them, so the stored value would not even match\n * what is sent upstream.\n *\n * Rejecting the raw byte costs nothing: every one of these has a\n * percent-encoded spelling that is unambiguous in log text and is accepted\n * here unchanged. See #47.\n */\nconst unsafeRawDelimiterPattern = /['\"`<>\\s\\u0000-\\u001F\\u007F]/\n\n/**\n * Both fixed strings. The rejected value is never interpolated: these messages\n * reach the terminal and, via the startup failure path, the log file, which is\n * exactly the disclosure being prevented.\n */\nconst invalidCopilotBaseUrlMessage =\n \"Invalid copilotBaseUrl: expected an absolute http(s) URL, e.g. https://api.githubcopilot.com\"\nconst copilotBaseUrlCredentialsMessage =\n \"Invalid copilotBaseUrl: URL credentials (user:password@host) are not supported; remove them and use a plain https URL\"\nconst copilotBaseUrlDelimiterMessage =\n \"Invalid copilotBaseUrl: quotes, backticks, angle brackets, whitespace and control characters must be percent-encoded\"\n\n/**\n * Validates copilotBaseUrl as a conventional HTTP(S) URL without credentials.\n *\n * Four rules, all learned from #47:\n *\n * The value is concatenated as `${base}${path}` for every upstream call, so it\n * must be something Undici will accept. A relative or non-HTTP value fails at\n * request time as an unrelated-looking fetch error; failing here names the key.\n *\n * It must be written with an explicit `http://` or `https://` authority. See\n * conventionalHttpUrlPattern: the shorthand forms WHATWG also accepts are\n * unfindable in log text, so a configured secret in one of them cannot be\n * redacted by anything downstream. Rejecting is the only safe answer, and this\n * is the only place with enough context to give it.\n *\n * Raw quote-like characters, angle brackets, whitespace and control bytes are\n * rejected for the same reason - see unsafeRawDelimiterPattern. They are what\n * marks the end of a URL in rendered text, so a value containing one cannot be\n * matched whole afterwards. Their percent-encoded forms are accepted.\n *\n * URL userinfo (`https://user:pass@host`) is rejected rather than passed\n * through. Undici refuses it at request time anyway, so accepting it buys only\n * a confusing failure plus a credential written to the log on every start.\n *\n * The accepted value is returned as the trimmed original, never URL-normalized.\n * Round-tripping through URL would add a trailing slash, lowercase the host and\n * re-encode the path, silently changing both the request URL and what gets\n * written back to config.yaml.\n */\nexport const normalizeCopilotBaseUrl = (value: unknown): string | undefined => {\n const trimmed = normalizeString(value)\n if (trimmed === undefined) {\n return undefined\n }\n\n // Before new URL(), because the shorthand this rejects parses successfully -\n // it is a valid URL, just not one that can be redacted later.\n if (!conventionalHttpUrlPattern.test(trimmed)) {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n // Also before new URL(), for two reasons: it accepts every one of these, and\n // it silently strips tab and newline - so by the time it returned, the value\n // stored in config.yaml would no longer match the URL actually requested.\n if (unsafeRawDelimiterPattern.test(trimmed)) {\n throw new Error(copilotBaseUrlDelimiterMessage)\n }\n\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n if (parsed.username || parsed.password) {\n throw new Error(copilotBaseUrlCredentialsMessage)\n }\n\n return trimmed\n}\n\nexport const normalizeThinkEffort = (\n value: unknown,\n): ReasoningEffort | undefined => {\n if (typeof value !== \"string\") {\n return undefined\n }\n const normalized = value.toLowerCase() === \"minimal\" ? \"low\" : value.toLowerCase()\n return isReasoningEffort(normalized) ? normalized : undefined\n}\n\nexport const normalizeUpstreamTimeoutSeconds = (\n value: unknown,\n): number | undefined => {\n if (value === undefined) {\n return undefined\n }\n\n const timeout = normalizePositiveInteger(value)\n if (timeout === undefined) {\n throw new Error(\"Invalid upstreamTimeoutSeconds: expected a positive integer\")\n }\n\n return timeout\n}\n\nconst readRawConfig = async (): Promise<Record<string, unknown>> => {\n try {\n const content = await fs.readFile(paths.configPath, \"utf8\")\n return parseConfigYaml(content)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n for (const legacyPath of paths.legacyConfigPaths) {\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n return parseConfigYaml(content)\n } catch (legacyError) {\n if ((legacyError as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw legacyError\n }\n }\n }\n return readDefaultConfigTemplate()\n }\n throw error\n }\n}\n\nconst readDefaultConfigTemplate = async (): Promise<Record<string, unknown>> => {\n let currentDir = dirname(fileURLToPath(import.meta.url))\n\n // Bundled code may run from src/ during development or from dist/ after\n // packaging, so walk upward until the package-level config template is found.\n while (true) {\n try {\n const content = await fs.readFile(\n resolve(currentDir, \"config.default.yaml\"),\n \"utf8\",\n )\n return parseConfigYaml(content)\n } catch {\n const parentDir = dirname(currentDir)\n if (parentDir === currentDir) {\n return {}\n }\n currentDir = parentDir\n }\n }\n}\n\nconst unquoteYamlScalar = (value: string): string => {\n const trimmed = value.trim()\n if (\n (trimmed.startsWith('\"') && trimmed.endsWith('\"'))\n || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))\n ) {\n return trimmed.slice(1, -1)\n }\n return trimmed\n}\n\nconst parseConfigYaml = (content: string): Record<string, unknown> => {\n const config: Record<string, unknown> = {}\n for (const line of content.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue\n }\n\n const match = trimmed.match(/^([A-Za-z][A-Za-z0-9_-]*)\\s*:\\s*(.*?)\\s*$/)\n if (!match) {\n throw new Error(`Invalid config line in ${paths.configPath}: ${line}`)\n }\n\n const [, key, value] = match\n // The runtime config intentionally supports only a flat key/value YAML\n // subset. That keeps startup dependency-free and makes bad edits fail\n // predictably instead of being partially interpreted.\n switch (key) {\n case \"claudeSetup\":\n case \"claude_setup\": {\n config.claudeSetup = unquoteYamlScalar(value)\n break\n }\n case \"copilotBaseUrl\":\n case \"copilot_base_url\": {\n config.copilotBaseUrl = unquoteYamlScalar(value)\n break\n }\n case \"host\": {\n config.host = unquoteYamlScalar(value)\n break\n }\n case \"gptModel\":\n case \"gpt_model\": {\n config.gptModel = unquoteYamlScalar(value)\n break\n }\n case \"logLevel\":\n case \"log_level\": {\n config.logLevel = unquoteYamlScalar(value)\n break\n }\n case \"logRetentionDays\":\n case \"log_retention_days\": {\n config.logRetentionDays = unquoteYamlScalar(value)\n break\n }\n case \"opusModel\":\n case \"opus_model\": {\n config.opusModel = unquoteYamlScalar(value)\n break\n }\n case \"port\": {\n config.port = unquoteYamlScalar(value)\n break\n }\n case \"thinkEffort\":\n case \"think_effort\": {\n config.thinkEffort = unquoteYamlScalar(value)\n break\n }\n case \"upstreamTimeoutSeconds\":\n case \"upstream_timeout_seconds\": {\n config.upstreamTimeoutSeconds = unquoteYamlScalar(value)\n break\n }\n case \"webSearchBackend\":\n case \"web_search_backend\": {\n config.webSearchBackend = unquoteYamlScalar(value)\n break\n }\n default: {\n break\n }\n }\n }\n return config\n}\n\nconst writeConfig = async (config: AppConfig): Promise<void> => {\n await fs.mkdir(paths.appDir, { recursive: true })\n const content = serializeConfig(config)\n const existing = await fs.readFile(paths.configPath, \"utf8\").catch(() => \"\")\n if (existing === content) {\n return\n }\n await fs.writeFile(paths.configPath, content, { mode: 0o600 })\n}\n\nconst serializeConfig = (config: AppConfig): string =>\n [\n \"# copilot-relay configuration\",\n \"#\",\n \"# This file is hot-reloaded while copilot-relay is running.\",\n \"\",\n \"# Local host for the Claude Code-compatible HTTP server.\",\n `host: ${config.host}`,\n \"\",\n \"# Local port for the HTTP server.\",\n `port: ${config.port}`,\n \"\",\n \"# GitHub Copilot API base URL.\",\n `copilotBaseUrl: ${config.copilotBaseUrl}`,\n \"\",\n \"# Update ~/.claude/settings.json on start.\",\n `claudeSetup: ${config.claudeSetup}`,\n \"\",\n \"# Log verbosity:\",\n \"# error - startup/preflight/request failures only\",\n \"# info - error logs plus startup status, preflight status, and local HTTP\",\n \"# status codes\",\n \"# debug - info logs plus model routing summaries, Copilot upstream timings,\",\n \"# and request payloads\",\n `logLevel: ${config.logLevel}`,\n \"\",\n \"# Number of days to keep files in ~/.copilot-relay/logs.\",\n `logRetentionDays: ${config.logRetentionDays}`,\n \"\",\n \"# Default upstream thinking/reasoning effort: none, low, medium, high, xhigh, max.\",\n `thinkEffort: ${config.thinkEffort}`,\n \"\",\n \"# Max seconds to wait for a single Claude request's upstream Copilot calls.\",\n `upstreamTimeoutSeconds: ${config.upstreamTimeoutSeconds}`,\n \"\",\n \"# Copilot model used for bridge-managed Claude WebSearch. Empty uses gptModel.\",\n `webSearchBackend: ${config.webSearchBackend ?? \"\"}`,\n \"\",\n \"# Model routing: requests containing \\\"opus\\\" use opusModel; all others use gptModel.\",\n \"\",\n \"# Upstream Copilot model used for non-Opus requests.\",\n `gptModel: ${config.gptModel}`,\n \"\",\n \"# Upstream Copilot model used for requests containing \\\"opus\\\".\",\n `opusModel: ${config.opusModel}`,\n \"\",\n ].join(\"\\n\")\n\nexport async function readAppConfig(): Promise<AppConfig> {\n const raw = await readRawConfig()\n const claudeSetup = normalizeBoolean(raw.claudeSetup)\n const host = normalizeString(raw.host)\n const logLevel = normalizeLogLevel(raw.logLevel)\n const logRetentionDays = normalizePositiveInteger(raw.logRetentionDays)\n const port = normalizePort(raw.port)\n const thinkEffort = normalizeThinkEffort(raw.thinkEffort)\n const upstreamTimeoutSeconds = normalizeUpstreamTimeoutSeconds(\n raw.upstreamTimeoutSeconds,\n )\n\n // Every field follows the same rule: whatever the user's config holds wins,\n // and a shipped default applies only where the key is absent.\n const config: AppConfig = {\n claudeSetup: claudeSetup ?? defaultConfig.claudeSetup,\n copilotBaseUrl:\n normalizeCopilotBaseUrl(raw.copilotBaseUrl) ?? defaultConfig.copilotBaseUrl,\n gptModel: normalizeString(raw.gptModel) ?? defaultConfig.gptModel,\n host: host ?? defaultConfig.host,\n logLevel: logLevel ?? defaultConfig.logLevel,\n logRetentionDays: logRetentionDays ?? defaultConfig.logRetentionDays,\n opusModel: normalizeString(raw.opusModel) ?? defaultConfig.opusModel,\n port: port ?? defaultConfig.port,\n thinkEffort: thinkEffort ?? defaultConfig.thinkEffort,\n upstreamTimeoutSeconds:\n upstreamTimeoutSeconds ?? defaultConfig.upstreamTimeoutSeconds,\n webSearchBackend: normalizeString(raw.webSearchBackend),\n }\n\n await writeConfig(config)\n return config\n}\n\nconst getConfigMtime = async (): Promise<number> => {\n try {\n return (await fs.stat(paths.configPath)).mtimeMs\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return 0\n }\n throw error\n }\n}\n\nexport const watchAppConfig = (\n onReload: (config: AppConfig) => void,\n): ReturnType<typeof setInterval> => {\n let lastMtime = 0\n\n const timer = setInterval(async () => {\n try {\n const nextMtime = await getConfigMtime()\n if (nextMtime === lastMtime) {\n return\n }\n\n lastMtime = nextMtime\n onReload(await readAppConfig())\n } catch {\n // Config editors can briefly write invalid partial files. Keep the last\n // good runtime config rather than degrading live requests mid-edit.\n }\n }, 1000)\n\n if (typeof timer.unref === \"function\") {\n timer.unref()\n }\n\n void getConfigMtime().then((mtime) => {\n lastMtime = mtime\n })\n\n return timer\n}\n","// Converts hot-loaded app config into the mutable runtime config shared with routes.\nimport type { AppConfig } from \"~/lib/app-config\"\n\nconst vscodeVersion = \"1.99.3\"\n\nexport interface ProxyConfig {\n host: string\n port: number\n copilotBaseUrl: string\n copilotToken: string | undefined\n upstreamTimeoutMs: number\n vsCodeVersion: string\n webSearchBackend?: string\n}\n\nexport interface ProxyEnv {\n Variables: {\n config: ProxyConfig\n requestErrorMessage?: string\n requestId: string\n }\n}\n\nexport const readProxyConfig = (config: AppConfig): ProxyConfig => ({\n copilotBaseUrl: config.copilotBaseUrl,\n copilotToken: undefined,\n host: config.host,\n port: config.port,\n upstreamTimeoutMs: config.upstreamTimeoutSeconds * 1000,\n vsCodeVersion: vscodeVersion,\n webSearchBackend: config.webSearchBackend,\n})\n","// `copilot-relay auth`: performs an explicit device-login flow and syncs tokens.\nimport { defineCommand } from \"citty\"\n\nimport { setupProxyAuth } from \"~/lib/auth\"\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub device auth and cache credentials for copilot-relay.\",\n },\n async run() {\n const config = readProxyConfig(await readAppConfig())\n\n config.copilotToken = undefined\n\n const authSession = await setupProxyAuth(config, {\n force: true,\n })\n\n if (authSession.githubLogin) {\n log.info(`Logged in as ${authSession.githubLogin}`)\n }\n\n log.info(\"copilot-relay auth completed\")\n },\n})\n","// Reads package.json version from either source or bundled dist layouts.\nimport { readFileSync } from \"node:fs\"\nimport { dirname, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\ninterface PackageJson {\n name?: string\n version?: string\n}\n\nconst packageName = \"copilot-relay\"\n\nconst readPackageVersion = (): string => {\n let currentDir = dirname(fileURLToPath(import.meta.url))\n\n while (true) {\n try {\n const packageJsonPath = resolve(currentDir, \"package.json\")\n const packageJson = JSON.parse(\n readFileSync(packageJsonPath, \"utf8\"),\n ) as PackageJson\n\n if (\n packageJson.name === packageName\n && typeof packageJson.version === \"string\"\n ) {\n return packageJson.version\n }\n } catch {\n // Keep walking upward; source and dist builds sit at different depths.\n }\n\n const parentDir = dirname(currentDir)\n if (parentDir === currentDir) {\n return \"unknown\"\n }\n\n currentDir = parentDir\n }\n}\n\nexport const appVersion = readPackageVersion()\n","// Process lifecycle helpers for stopping stale local relay instances.\nimport { execFile } from \"node:child_process\"\nimport fs from \"node:fs/promises\"\nimport { promisify } from \"node:util\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\nimport { paths } from \"~/lib/paths\"\nimport { appVersion } from \"~/lib/version\"\n\nconst execFileAsync = promisify(execFile)\nconst stopTimeoutMs = 5_000\nconst stopPollMs = 100\n\nexport interface RelayPidFile {\n host: string\n pid: number\n port: number\n startedAt: string\n /**\n * The version of the build that wrote this file — the daemon's, never the\n * caller's. Optional because a file written before v0.3.1 has no such field,\n * and because it must be readable even when the daemon is not yet healthy.\n */\n version?: string\n}\n\nconst isNodeErrno = (error: unknown): error is NodeJS.ErrnoException =>\n typeof error === \"object\" && error !== null && \"code\" in error\n\nconst sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nconst parsePid = (value: unknown): number | undefined => {\n const pid =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value.trim(), 10)\n : Number.NaN\n\n return Number.isInteger(pid) && pid > 0 ? pid : undefined\n}\n\nconst readCommandOutput = async (\n file: string,\n args: Array<string>,\n): Promise<string> => {\n try {\n const { stdout } = await execFileAsync(file, args, {\n maxBuffer: 1024 * 1024,\n windowsHide: true,\n })\n return String(stdout)\n } catch (error) {\n if (\n typeof error === \"object\"\n && error !== null\n && \"stdout\" in error\n && (typeof error.stdout === \"string\" || Buffer.isBuffer(error.stdout))\n ) {\n return String(error.stdout)\n }\n return \"\"\n }\n}\n\nconst parsePidLines = (output: string): Array<number> =>\n output\n .split(/\\r?\\n/)\n .map((line) => parsePid(line))\n .filter((pid): pid is number => pid !== undefined)\n\nexport const isRelayStartProcess = (\n command: string,\n cwd?: string,\n): boolean => {\n const normalizedCommand = command.replaceAll(\"\\\\\", \"/\")\n const normalizedCwd = cwd?.replaceAll(\"\\\\\", \"/\")\n const hasStartArg = /(?:^|\\s)start(?:\\s|$)/.test(normalizedCommand)\n\n if (!hasStartArg) {\n return false\n }\n\n if (normalizedCommand.includes(\"copilot-relay\")) {\n return true\n }\n\n const usesNode = /(?:^|\\s|\\/)node(?:\\.exe)?(?:\\s|$)/i.test(normalizedCommand)\n const usesDistMain = /(?:^|\\s|\\/)dist\\/main\\.js(?:\\s|$)/.test(\n normalizedCommand,\n )\n\n return Boolean(\n usesNode\n && usesDistMain\n && normalizedCwd\n && normalizedCwd.split(\"/\").some((part) => part.includes(\"copilot-relay\")),\n )\n}\n\nconst isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return isNodeErrno(error) && error.code === \"EPERM\"\n }\n}\n\n/**\n * Full pid-file record, for callers that need the listening address or start\n * time rather than just the pid.\n *\n * Deliberately tolerant: a corrupt or half-written pid file yields undefined\n * rather than throwing, because `status` must be able to report \"not running\"\n * instead of crashing on a file the user may have been mid-edit on.\n *\n * Returns undefined for the legacy bare-pid format, which carries no address.\n */\nexport const readRelayPidFileEntry = async (): Promise<\n RelayPidFile | undefined\n> => {\n try {\n const content = await fs.readFile(paths.pidPath, \"utf8\")\n const trimmed = content.trim()\n if (!trimmed || !trimmed.startsWith(\"{\")) {\n return undefined\n }\n\n const payload = JSON.parse(trimmed) as Partial<RelayPidFile>\n const pid = parsePid(payload.pid)\n if (\n pid === undefined\n || typeof payload.host !== \"string\"\n || typeof payload.port !== \"number\"\n ) {\n return undefined\n }\n\n return {\n host: payload.host,\n pid,\n port: payload.port,\n startedAt:\n typeof payload.startedAt === \"string\" ? payload.startedAt : \"\",\n ...(typeof payload.version === \"string\" && payload.version ?\n { version: payload.version }\n : {}),\n }\n } catch {\n return undefined\n }\n}\n\nconst readRelayPidFile = async (): Promise<number | undefined> => {\n try {\n const content = await fs.readFile(paths.pidPath, \"utf8\")\n const trimmed = content.trim()\n if (!trimmed) {\n return undefined\n }\n\n if (trimmed.startsWith(\"{\")) {\n const payload = JSON.parse(trimmed) as Partial<RelayPidFile>\n return parsePid(payload.pid)\n }\n\n return parsePid(trimmed)\n } catch (error) {\n if (isNodeErrno(error) && error.code === \"ENOENT\") {\n return undefined\n }\n throw error\n }\n}\n\nexport const writeRelayPidFile = async (\n config: Pick<ProxyConfig, \"host\" | \"port\">,\n): Promise<void> => {\n await fs.mkdir(paths.appDir, { recursive: true })\n const payload: RelayPidFile = {\n host: config.host,\n pid: process.pid,\n port: config.port,\n startedAt: new Date().toISOString(),\n // Written by the daemon at startup, so it is the build actually serving —\n // unlike `status`, which used to report the version of whichever CLI was\n // invoked. See #43. Covers the window before the daemon answers /healthz.\n version: appVersion,\n }\n await fs.writeFile(paths.pidPath, `${JSON.stringify(payload, null, 2)}\\n`, {\n mode: 0o600,\n })\n}\n\nexport const clearRelayPidFile = async (\n pid = process.pid,\n options: { force?: boolean } = {},\n): Promise<void> => {\n const currentPid = await readRelayPidFile()\n if (!options.force && currentPid !== undefined && currentPid !== pid) {\n return\n }\n\n await fs.unlink(paths.pidPath).catch((error: unknown) => {\n if (!isNodeErrno(error) || error.code !== \"ENOENT\") {\n throw error\n }\n })\n}\n\nconst getProcessCommand = async (pid: number): Promise<string | undefined> => {\n if (process.platform === \"win32\") {\n const output = await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\" | Select-Object -ExpandProperty CommandLine`,\n ])\n return output.trim() || undefined\n }\n\n const output = await readCommandOutput(\"ps\", [\n \"-p\",\n String(pid),\n \"-o\",\n \"command=\",\n ])\n return output.trim() || undefined\n}\n\nconst getProcessCwd = async (pid: number): Promise<string | undefined> => {\n if (process.platform === \"win32\") {\n return undefined\n }\n\n const output = await readCommandOutput(\"lsof\", [\n \"-a\",\n \"-p\",\n String(pid),\n \"-d\",\n \"cwd\",\n \"-Fn\",\n ])\n return output\n .split(/\\r?\\n/)\n .find((line) => line.startsWith(\"n\"))\n ?.slice(1)\n}\n\nconst getPortListenerPids = async (port: number): Promise<Array<number>> => {\n if (process.platform === \"win32\") {\n return parsePidLines(await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique`,\n ]))\n }\n\n return parsePidLines(await readCommandOutput(\"lsof\", [\n \"-nP\",\n `-iTCP:${port}`,\n \"-sTCP:LISTEN\",\n \"-t\",\n ]))\n}\n\nconst getProcessList = async (): Promise<Array<{ command: string; pid: number }>> => {\n const output =\n process.platform === \"win32\" ?\n await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n \"Get-CimInstance Win32_Process | ForEach-Object { \\\"$($_.ProcessId)`t$($_.CommandLine)\\\" }\",\n ])\n : await readCommandOutput(\"ps\", [\"-axo\", \"pid=,command=\"])\n\n return output\n .split(/\\r?\\n/)\n .flatMap((line) => {\n const match =\n process.platform === \"win32\" ?\n line.match(/^(\\d+)\\t(.+)$/)\n : line.match(/^\\s*(\\d+)\\s+(.+)$/)\n const pid = parsePid(match?.[1])\n const command = match?.[2]?.trim()\n return pid && command ? [{ command, pid }] : []\n })\n}\n\nconst isRelayPid = async (pid: number): Promise<boolean> => {\n if (pid === process.pid || !isProcessAlive(pid)) {\n return false\n }\n\n const command = await getProcessCommand(pid)\n if (!command) {\n return false\n }\n\n return isRelayStartProcess(command, await getProcessCwd(pid))\n}\n\n/**\n * The relay serving `config.port`, or undefined if nothing is.\n *\n * Deliberately narrower than findRelayProcessIds: it never consults the global\n * process list. `stop` wants \"any relay anywhere\" so it can clean up strays;\n * `status` is asked about one configured relay, and answering with a different\n * one is worse than answering \"not running\" because it looks authoritative.\n *\n * Returns pid and address as a single coherent record, so a pid discovered one\n * way can never be printed next to an address taken from another.\n */\nexport const findRelayOnPort = async (\n config: Pick<ProxyConfig, \"host\" | \"port\">,\n): Promise<RelayPidFile | undefined> => {\n // The pid file is preferred when it describes this port: it is the only\n // source carrying host and startedAt, and its port is where the socket is\n // actually bound - hot reload changes config.port without rebinding.\n const entry = await readRelayPidFileEntry()\n if (\n entry\n && entry.port === config.port\n && isProcessAlive(entry.pid)\n && (await isRelayPid(entry.pid))\n ) {\n return entry\n }\n\n // No usable pid file - fall back to whoever holds the port. Covers a relay\n // started before pid files, or one whose pid file was removed. No version\n // here on purpose: nothing in this path knows the daemon's build, and\n // borrowing the caller's would recreate #43.\n for (const pid of await getPortListenerPids(config.port)) {\n if (await isRelayPid(pid)) {\n return {\n host: config.host,\n pid,\n port: config.port,\n startedAt: \"\",\n }\n }\n }\n\n return undefined\n}\n\nexport const findRelayProcessIds = async (\n config: Pick<ProxyConfig, \"port\">,\n): Promise<Array<number>> => {\n const candidates = new Set<number>()\n const storedPid = await readRelayPidFile()\n if (storedPid) {\n candidates.add(storedPid)\n }\n\n for (const pid of await getPortListenerPids(config.port)) {\n candidates.add(pid)\n }\n\n for (const { command, pid } of await getProcessList()) {\n if (pid === process.pid) {\n continue\n }\n\n if (command.includes(\"copilot-relay\") || command.includes(\"dist/main.js\")) {\n const cwd = await getProcessCwd(pid)\n if (isRelayStartProcess(command, cwd)) {\n candidates.add(pid)\n }\n }\n }\n\n const relayPids: Array<number> = []\n for (const pid of candidates) {\n if (await isRelayPid(pid)) {\n relayPids.push(pid)\n }\n }\n return [...new Set(relayPids)].sort((left, right) => left - right)\n}\n\nconst waitForExit = async (pid: number, timeoutMs: number): Promise<boolean> => {\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid)) {\n return true\n }\n await sleep(stopPollMs)\n }\n return !isProcessAlive(pid)\n}\n\nconst stopProcess = async (pid: number): Promise<void> => {\n if (!isProcessAlive(pid)) {\n return\n }\n\n log.info(`Stopping existing copilot-relay process pid=${pid}`)\n process.kill(pid, \"SIGTERM\")\n if (await waitForExit(pid, stopTimeoutMs)) {\n return\n }\n\n log.error(`copilot-relay process pid=${pid} did not stop; forcing termination`)\n process.kill(pid, \"SIGKILL\")\n if (!(await waitForExit(pid, stopTimeoutMs))) {\n throw new Error(`Could not stop copilot-relay process pid=${pid}`)\n }\n}\n\nexport const stopExistingRelay = async (\n config: Pick<ProxyConfig, \"port\">,\n): Promise<Array<number>> => {\n const relayPids = await findRelayProcessIds(config)\n if (relayPids.length === 0) {\n log.info(\"No existing copilot-relay instance found\")\n await clearRelayPidFile(0, { force: true })\n return []\n }\n\n for (const pid of relayPids) {\n await stopProcess(pid)\n }\n await clearRelayPidFile(0, { force: true })\n return relayPids\n}\n","// Optional writer for Claude Code settings so it points at the local proxy.\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { normalizeClaudeModelId } from \"~/lib/models\"\n\ninterface ApplyClaudeConfigInput {\n baseUrl: string\n configPath: string\n gptModel: string\n}\n\ninterface ApplyClaudeResult {\n configPath: string\n changed: boolean\n created: boolean\n previousBaseUrl?: string\n}\n\nconst knownModelEnvKeys = [\n \"ANTHROPIC_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n \"ANTHROPIC_DEFAULT_FABLE_MODEL\",\n \"ANTHROPIC_SMALL_FAST_MODEL\",\n \"CLAUDE_CODE_SUBAGENT_MODEL\",\n] as const\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n\nconst hasMalformedModelOverride = (\n parsed: Record<string, unknown>,\n): boolean => {\n if (parsed.model !== undefined && typeof parsed.model !== \"string\") {\n return true\n }\n\n const env = parsed.env\n if (!isRecord(env)) {\n return false\n }\n\n return knownModelEnvKeys.some(\n (key) => env[key] !== undefined && typeof env[key] !== \"string\",\n )\n}\n\nconst hasPrimaryModelOverride = (\n parsed: Record<string, unknown>,\n env: Record<string, unknown>,\n): boolean =>\n typeof parsed.model === \"string\"\n || typeof env.ANTHROPIC_MODEL === \"string\"\n\n/**\n * Update `~/.claude/settings.json` so its env block points Claude Code at the\n * running proxy. Preserves unrelated keys and model choices while normalizing\n * managed gpt-5.6-sol overrides. Sets a dummy auth token only if none is present.\n */\nexport async function applyClaudeConfig(\n input: ApplyClaudeConfigInput,\n): Promise<ApplyClaudeResult> {\n const { configPath, baseUrl, gptModel } = input\n\n let raw = \"\"\n let created = false\n try {\n raw = await fs.readFile(configPath, \"utf8\")\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n created = true\n } else {\n throw error\n }\n }\n\n let parsed: Record<string, unknown> = {}\n if (!created && raw.trim().length === 0) {\n return { configPath, changed: false, created: false }\n }\n if (raw.trim().length > 0) {\n try {\n const value = JSON.parse(raw) as unknown\n if (\n !isRecord(value)\n || (value.env !== undefined && !isRecord(value.env))\n || hasMalformedModelOverride(value)\n ) {\n return { configPath, changed: false, created: false }\n }\n parsed = value\n } catch {\n // Refuse to overwrite a malformed file; bail without changes.\n return { configPath, changed: false, created: false }\n }\n }\n\n const env: Record<string, unknown> = isRecord(parsed.env) ? { ...parsed.env } : {}\n\n const previousBaseUrl =\n typeof env.ANTHROPIC_BASE_URL === \"string\"\n ? (env.ANTHROPIC_BASE_URL as string)\n : undefined\n const hadPrimaryModelOverride = hasPrimaryModelOverride(parsed, env)\n const claudeGptModel = normalizeClaudeModelId(gptModel)\n const shouldSeedTopLevelModel = !hadPrimaryModelOverride\n const nextModel =\n typeof parsed.model === \"string\"\n ? normalizeClaudeModelId(parsed.model)\n : undefined\n\n // ANTHROPIC_MODEL outranks the saved model setting, so only normalize it\n // when the user already set it; managed defaults belong in `model`.\n for (const key of knownModelEnvKeys) {\n if (typeof env[key] === \"string\") {\n env[key] = normalizeClaudeModelId(env[key])\n }\n }\n\n env.ANTHROPIC_BASE_URL = baseUrl\n // Claude Code only requires a syntactically present auth token here; real\n // upstream authentication is handled by copilot-relay's Copilot token.\n if (typeof env.ANTHROPIC_AUTH_TOKEN !== \"string\" || !env.ANTHROPIC_AUTH_TOKEN) {\n env.ANTHROPIC_AUTH_TOKEN = \"dummy\"\n }\n\n const next: Record<string, unknown> = {\n ...parsed,\n ...(nextModel !== undefined && { model: nextModel }),\n ...(shouldSeedTopLevelModel && { model: claudeGptModel }),\n env,\n }\n const serialized = `${JSON.stringify(next, null, 2)}\\n`\n\n if (serialized === raw) {\n return { configPath, changed: false, created: false, previousBaseUrl }\n }\n\n await fs.mkdir(path.dirname(configPath), { recursive: true })\n await fs.writeFile(configPath, serialized)\n return { configPath, changed: true, created, previousBaseUrl }\n}\n","// Static process defaults that are not intended to be user-tuned.\nimport path from \"node:path\"\nimport os from \"node:os\"\n\n/**\n * Static defaults for copilot-relay. Previously these lived in\n * `~/.config/copilot-relay/settings.json`, but that file became an implicit\n * third source of truth alongside `~/.claude/settings.json`, which caused\n * subtle port-mismatch bugs. copilot-relay now relies on these defaults plus\n * CLI flags only.\n */\n\nexport const defaultHost = \"127.0.0.1\"\nexport const defaultPort = 4142\n\nexport const claudeConfigPath = path.join(\n os.homedir(),\n \".claude\",\n \"settings.json\",\n)\n","// Low-level GitHub Copilot HTTP client: adds required headers, retries transient failures, and logs timing.\nimport { randomUUID } from \"node:crypto\"\n\nimport { Agent, fetch as undiciFetch } from \"undici\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError, ProxyNotImplementedError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\n\nconst copilotVersion = \"0.26.7\"\nconst editorPluginVersion = `copilot-chat/${copilotVersion}`\nconst userAgent = `GitHubCopilotChat/${copilotVersion}`\nconst apiVersion = \"2025-04-01\"\nconst maxFetchAttempts = 2\nexport const copilotRequestTimeoutMs = 180_000\nconst copilotDispatcher = new Agent({\n allowH2: false,\n connect: { allowH2: false },\n})\n\nexport interface CopilotProviderContext {\n baseUrl: string\n token: string | undefined\n vsCodeVersion: string\n}\n\nexport const getCopilotProviderContext = (\n config: ProxyConfig,\n): CopilotProviderContext => ({\n baseUrl: config.copilotBaseUrl,\n token: config.copilotToken,\n vsCodeVersion: config.vsCodeVersion,\n})\n\nexport interface FetchCopilotOptions {\n vision?: boolean\n initiator?: \"agent\" | \"user\"\n requestId?: string\n signal?: AbortSignal\n timeoutMs?: number\n}\n\ninterface CopilotRequestInit {\n body?: string\n headers?: RequestInit[\"headers\"]\n method?: string\n}\n\nconst shouldRetryResponse = (response: Response): boolean =>\n // Retry only transient upstream failures; 4xx responses may contain routing\n // signals, such as \"unsupported_api_for_model\", that callers need to inspect.\n response.status >= 500 && response.status <= 599\n\nexport const createCopilotRequestSignal = (\n signal?: AbortSignal,\n timeoutMs = copilotRequestTimeoutMs,\n): AbortSignal | undefined => {\n const signals = [\n signal,\n timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined,\n ].filter((value): value is AbortSignal => value !== undefined)\n\n if (signals.length === 0) {\n return undefined\n }\n\n return signals.length === 1 ? signals[0] : AbortSignal.any(signals)\n}\n\nconst getAbortName = (value: unknown): string | undefined =>\n typeof value === \"object\"\n && value !== null\n && \"name\" in value\n && typeof value.name === \"string\" ?\n value.name\n : undefined\n\nconst isAbortLikeError = (error: unknown): boolean => {\n const name = getAbortName(error)\n return name === \"AbortError\" || name === \"TimeoutError\"\n}\n\nconst getErrorCode = (value: unknown): string | undefined =>\n typeof value === \"object\"\n && value !== null\n && \"code\" in value\n && typeof value.code === \"string\" ?\n value.code\n : undefined\n\nexport const isRetryableFetchError = (error: unknown): boolean =>\n getErrorCode(error) === \"ERR_HTTP2_INVALID_SESSION\"\n || (\n typeof error === \"object\"\n && error !== null\n && \"cause\" in error\n && getErrorCode(error.cause) === \"ERR_HTTP2_INVALID_SESSION\"\n )\n\nexport const toCopilotAbortHTTPError = (\n error: unknown,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): HTTPError | undefined => {\n if (!signal?.aborted && !isAbortLikeError(error)) {\n return undefined\n }\n\n const timedOut =\n getAbortName(signal?.reason) === \"TimeoutError\"\n || getAbortName(error) === \"TimeoutError\"\n const message =\n timedOut ?\n `Copilot upstream request timed out after ${Math.round(timeoutMs / 1000)}s.`\n : \"Client request cancelled before Copilot upstream completed.\"\n const code = timedOut ? \"upstream_timeout\" : \"request_cancelled\"\n\n return new HTTPError(\n message,\n new Response(JSON.stringify({ error: { message, code } }), {\n status: timedOut ? 504 : 499,\n headers: { \"content-type\": \"application/json\" },\n }),\n message,\n )\n}\n\nexport const readCopilotJson = async <T>(\n response: Response,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): Promise<T> => {\n try {\n return (await response.json()) as T\n } catch (error) {\n throw toCopilotAbortHTTPError(error, signal, timeoutMs) ?? error\n }\n}\n\nexport const readCopilotText = async (\n response: Response,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): Promise<string> => {\n try {\n return await response.text()\n } catch (error) {\n throw toCopilotAbortHTTPError(error, signal, timeoutMs) ?? error\n }\n}\n\nconst buildHeaders = (\n provider: CopilotProviderContext,\n init: CopilotRequestInit,\n options: FetchCopilotOptions,\n upstreamRequestId: string,\n): Record<string, string> => {\n const headers = new Headers(init.headers)\n\n headers.set(\"authorization\", `Bearer ${provider.token}`)\n headers.set(\"copilot-integration-id\", \"vscode-chat\")\n headers.set(\"editor-version\", `vscode/${provider.vsCodeVersion}`)\n headers.set(\"editor-plugin-version\", editorPluginVersion)\n headers.set(\"user-agent\", userAgent)\n headers.set(\"openai-intent\", \"conversation-panel\")\n headers.set(\"x-github-api-version\", apiVersion)\n headers.set(\"x-request-id\", upstreamRequestId)\n headers.set(\"x-vscode-user-agent-library-version\", \"electron-fetch\")\n\n if (options.vision) {\n headers.set(\"copilot-vision-request\", \"true\")\n }\n\n if (options.initiator) {\n headers.set(\"x-initiator\", options.initiator)\n }\n\n if (!headers.has(\"content-type\") && init.body !== undefined) {\n headers.set(\"content-type\", \"application/json\")\n }\n\n if (!headers.has(\"accept\")) {\n headers.set(\"accept\", \"application/json\")\n }\n\n return Object.fromEntries(headers.entries())\n}\n\nconst formatRequestId = (requestId: string | undefined): string =>\n requestId ? `request_id=${requestId} ` : \"\"\n\nconst logUpstreamLifecycle = (\n requestId: string | undefined,\n message: string,\n): void => {\n if (requestId) {\n log.info(`${formatRequestId(requestId)}${message}`)\n return\n }\n\n log.debug(message)\n}\n\nexport const fetchCopilot = async (\n provider: CopilotProviderContext,\n path: string,\n init: CopilotRequestInit,\n options: FetchCopilotOptions = {},\n) => {\n if (!provider.token) {\n throw new ProxyNotImplementedError(\n \"Copilot token is not configured for copilot-relay.\",\n )\n }\n\n const timeoutMs = options.timeoutMs ?? copilotRequestTimeoutMs\n const signal = createCopilotRequestSignal(options.signal, timeoutMs)\n let lastError: unknown\n\n for (let attempt = 1; attempt <= maxFetchAttempts; attempt++) {\n const upstreamRequestId = randomUUID()\n try {\n const started = performance.now()\n logUpstreamLifecycle(\n options.requestId,\n `send upstream method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n const response = await undiciFetch(`${provider.baseUrl}${path}`, {\n ...init,\n headers: buildHeaders(provider, init, options, upstreamRequestId),\n dispatcher: copilotDispatcher,\n signal,\n })\n const ms = Math.round(performance.now() - started)\n logUpstreamLifecycle(\n options.requestId,\n `return from upstream method=${init.method ?? \"GET\"} path=${path} status=${response.status} ms=${ms} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n log.debug(\n `${formatRequestId(options.requestId)}Copilot ${init.method ?? \"GET\"} ${path} -> ${response.status} ${ms}ms (attempt ${attempt}) upstream_request_id=${upstreamRequestId}`,\n )\n\n if (!shouldRetryResponse(response) || attempt === maxFetchAttempts) {\n return response\n }\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} returned ${response.status}; retrying (${attempt}/${maxFetchAttempts}) upstream_request_id=${upstreamRequestId}`,\n )\n } catch (error) {\n const abortError = toCopilotAbortHTTPError(error, signal, timeoutMs)\n if (abortError) {\n logUpstreamLifecycle(\n options.requestId,\n `upstream failed method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n throw abortError\n }\n\n logUpstreamLifecycle(\n options.requestId,\n `upstream failed method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n lastError = error\n if (attempt === maxFetchAttempts) {\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} request failed after ${attempt} attempts upstream_request_id=${upstreamRequestId}`,\n error,\n )\n throw error\n }\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} request failed; retrying (${attempt}/${maxFetchAttempts}) upstream_request_id=${upstreamRequestId}`,\n error,\n )\n }\n }\n\n throw lastError\n}\n","// Internal conversion between Copilot Responses API payloads and chat-completion-like results.\nimport { createHash, randomUUID } from \"node:crypto\"\n\nimport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n} from \"~/copilot/types\"\n\nimport { sanitizeUserIdentifier } from \"./chat\"\n\nexport interface ResponseStreamEventMessage {\n data?: string\n event?: string\n}\n\nexport interface ResponsesApiResponse {\n id: string\n created_at: number\n model: string\n output: Array<ResponsesOutputItem>\n reasoning?: ResponsesReasoningSummaryContainer | null\n usage?: {\n input_tokens: number\n input_tokens_details?: {\n cached_tokens?: number\n }\n output_tokens: number\n output_tokens_details?: {\n reasoning_tokens?: number\n }\n total_tokens: number\n }\n incomplete_details?: {\n reason?: string | null\n } | null\n}\n\nexport type ResponsesReasoningEffort =\n | \"none\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n\ntype ResponsesInput = string | Array<ResponsesInputItem>\n\ntype ResponsesInputItem =\n | ResponsesMessageInput\n | ResponsesFunctionCallInput\n | ResponsesFunctionCallOutputInput\n\ntype ResponsesMessageInput = {\n role: \"user\" | \"assistant\" | \"system\" | \"developer\"\n content: string | Array<ResponsesInputContentPart>\n}\n\ntype ResponsesInputContentPart =\n | {\n type: \"input_text\"\n text: string\n }\n | {\n type: \"input_image\"\n image_url: string\n detail: \"low\" | \"high\" | \"auto\"\n }\n\ntype ResponsesFunctionCallInput = {\n type: \"function_call\"\n call_id: string\n name: string\n arguments: string\n}\n\ntype ResponsesFunctionCallOutputInput = {\n type: \"function_call_output\"\n call_id: string\n output: string\n}\n\ntype ResponsesToolChoice =\n | \"none\"\n | \"auto\"\n | \"required\"\n | { type: \"function\"; name: string }\n\nexport interface ResponsesRequestPayload {\n model: string\n input: ResponsesInput\n stream?: boolean | null\n max_output_tokens?: number | null\n temperature?: number | null\n top_p?: number | null\n user?: string | null\n prompt_cache_key?: string | null\n reasoning?: {\n effort: ResponsesReasoningEffort\n }\n tools?: Array<ResponsesTool>\n tool_choice?: ResponsesToolChoice | null\n text?: {\n format: {\n type: \"json_object\"\n }\n }\n}\n\ninterface ResponsesTool {\n type: \"function\"\n name: string\n description?: string\n parameters: Record<string, unknown>\n}\n\ntype ResponsesOutputItem =\n | ResponsesMessageOutputItem\n | ResponsesFunctionCallOutputItem\n | ResponsesReasoningOutputItem\n\ninterface ResponsesMessageOutputItem {\n type: \"message\"\n role: \"assistant\"\n content: Array<ResponsesMessageContentPart>\n}\n\ninterface ResponsesMessageContentPart {\n type: \"output_text\"\n text: string\n}\n\ninterface ResponsesFunctionCallOutputItem {\n type: \"function_call\"\n call_id: string\n name: string\n arguments: string\n}\n\ninterface ResponsesReasoningOutputItem {\n type: \"reasoning\"\n summary?: ResponsesReasoningSummary\n}\n\ntype ResponsesReasoningSummary =\n | string\n | Array<ResponsesReasoningSummaryPart>\n | null\n\ntype ResponsesReasoningSummaryPart = {\n type?: string\n text?: string\n}\n\ninterface ResponsesReasoningSummaryContainer {\n summary?: ResponsesReasoningSummary\n}\n\ninterface ResponsesStreamEnvelope {\n type: string\n response?: {\n id: string\n created_at: number\n model: string\n usage?: ResponsesApiResponse[\"usage\"]\n output?: Array<ResponsesOutputItem>\n reasoning?: ResponsesReasoningSummaryContainer | null\n incomplete_details?: ResponsesApiResponse[\"incomplete_details\"]\n }\n item?:\n | Partial<ResponsesMessageOutputItem>\n | Partial<ResponsesFunctionCallOutputItem>\n | Partial<ResponsesReasoningOutputItem>\n output_index?: number\n delta?: string\n text?: string\n arguments?: string\n}\n\ninterface ResponseTranslationState {\n responseId: string\n createdAt: number\n model: string\n started: boolean\n reasoningTextSent: boolean\n outputTextByIndex: Record<number, string>\n toolArgumentsByIndex: Record<number, string>\n toolStartedByIndex: Record<number, boolean>\n}\n\ninterface CreateChunkOptions {\n delta: ChatCompletionChunk[\"choices\"][0][\"delta\"]\n finishReason?: ChatCompletionChunk[\"choices\"][0][\"finish_reason\"]\n usage?: ChatCompletionChunk[\"usage\"]\n}\n\n// /models does not tell us which API surface a model requires, so keep the\n// Responses-only allowlist narrow and suffix-tolerant. gpt-5.5 and the gpt-5.6\n// family (luna/sol/terra) reject /chat/completions with unsupported_api_for_model\n// and must go straight to /responses.\nconst responsesOnlyModelPattern = /^(?:gpt-5\\.5|gpt-5\\.6)(?:-|$)/i\n\nexport function shouldUseResponsesApiForModel(model: string): boolean {\n return responsesOnlyModelPattern.test(model)\n}\n\nexport function buildResponsesRequestPayload(\n payload: ChatCompletionsPayload,\n reasoningEffort: ResponsesReasoningEffort | undefined,\n): ResponsesRequestPayload {\n // Responses API uses an `input` tree instead of chat `messages`, but the\n // rest of the bridge still reasons in chat-completion terms. Keep this as\n // the single boundary where chat payloads become Responses payloads.\n return {\n model: payload.model,\n input: translateMessagesToResponsesInput(payload.messages),\n stream: payload.stream,\n max_output_tokens: payload.max_tokens,\n temperature: payload.temperature,\n top_p: payload.top_p,\n user: sanitizeUserIdentifier(payload.user),\n // Copilot /responses only returns prompt cache hits when a stable\n // prompt_cache_key pins repeated requests to the same backend. Without it,\n // cached_tokens randomly drops to 0 across turns even for identical\n // prefixes (measured). Derive a per-conversation key so a Claude Code\n // session keeps hitting its own warm cache while staying isolated from\n // other sessions.\n prompt_cache_key: deriveResponsesCacheKey(payload),\n tools: translateTools(payload.tools),\n tool_choice: translateToolChoice(payload.tool_choice),\n reasoning: reasoningEffort ? { effort: reasoningEffort } : undefined,\n text:\n payload.response_format?.type === \"json_object\" ?\n { format: { type: \"json_object\" } }\n : undefined,\n }\n}\n\nfunction deriveResponsesCacheKey(\n payload: ChatCompletionsPayload,\n): string | undefined {\n // Prefer the client-supplied conversation id (Claude Code sends a stable\n // metadata.user_id per session). Fall back to a hash of the system prompt so\n // even clients without a user id still route a stable prefix to one cache.\n const user = sanitizeUserIdentifier(payload.user)\n if (user) {\n return `cr-${createHash(\"sha256\").update(user).digest(\"hex\").slice(0, 32)}`\n }\n\n const systemPrompt = payload.messages.find((message) => message.role === \"system\")\n const systemText =\n typeof systemPrompt?.content === \"string\" ? systemPrompt.content\n : Array.isArray(systemPrompt?.content) ?\n systemPrompt.content\n .map((part) => (part.type === \"text\" ? part.text : \"\"))\n .join(\"\")\n : \"\"\n\n if (!systemText) {\n return undefined\n }\n\n return `cr-sys-${createHash(\"sha256\").update(systemText).digest(\"hex\").slice(0, 32)}`\n}\n\nexport function translateResponsesToChatCompletion(\n response: ResponsesApiResponse,\n): ChatCompletionResponse {\n const assistantMessages = response.output.filter(\n (item): item is ResponsesMessageOutputItem => item.type === \"message\",\n )\n const functionCalls = response.output.filter(\n (item): item is ResponsesFunctionCallOutputItem =>\n item.type === \"function_call\",\n )\n\n const content = assistantMessages\n .flatMap((item) => item.content)\n .map((part) => part.text)\n .join(\"\")\n const reasoningText = getResponsesReasoningText(response)\n\n // Merge Responses output items back into one chat-completion choice so the\n // Claude translation layer can stay shared for /chat/completions and\n // /responses upstream calls.\n return {\n id: response.id,\n object: \"chat.completion\",\n created: response.created_at,\n model: response.model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: content || null,\n ...(reasoningText && { reasoning_text: reasoningText }),\n ...(functionCalls.length > 0 && {\n tool_calls: functionCalls.map((toolCall) => ({\n id: toolCall.call_id,\n type: \"function\" as const,\n function: {\n name: toolCall.name,\n arguments: toolCall.arguments,\n },\n })),\n }),\n },\n logprobs: null,\n finish_reason: getFinishReason(response, functionCalls.length > 0),\n },\n ],\n usage: translateUsage(response.usage),\n }\n}\n\nexport async function* translateResponsesStreamToChatCompletionStream(\n responseStream: AsyncIterable<ResponseStreamEventMessage>,\n): AsyncGenerator<ResponseStreamEventMessage> {\n // Responses streams can send deltas, done snapshots, and a final completed\n // payload. Track emitted suffixes so chat clients do not receive duplicates.\n const state: ResponseTranslationState = {\n responseId: randomUUID(),\n createdAt: Math.floor(Date.now() / 1000),\n model: \"\",\n started: false,\n reasoningTextSent: false,\n outputTextByIndex: {},\n toolArgumentsByIndex: {},\n toolStartedByIndex: {},\n }\n\n for await (const rawEvent of responseStream) {\n if (!rawEvent.data || rawEvent.data === \"[DONE]\") {\n continue\n }\n\n const event = JSON.parse(rawEvent.data) as ResponsesStreamEnvelope\n\n if (event.response) {\n state.responseId = event.response.id\n state.createdAt = event.response.created_at\n state.model = event.response.model\n }\n\n if (event.type === \"response.output_item.added\") {\n const chunk = handleOutputItemAdded(state, event)\n if (chunk) {\n yield chunk\n }\n continue\n }\n\n if (event.type === \"response.output_item.done\") {\n for (const chunk of handleOutputItemDone(state, event)) {\n yield chunk\n }\n continue\n }\n\n if (event.type === \"response.reasoning_summary_text.delta\") {\n if (event.delta) {\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { reasoning_text: event.delta } })\n state.reasoningTextSent = true\n }\n continue\n }\n\n if (event.type === \"response.output_text.delta\") {\n const outputIndex = event.output_index ?? 0\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${event.delta ?? \"\"}`\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { content: event.delta } })\n continue\n }\n\n if (event.type === \"response.output_text.done\") {\n const outputIndex = event.output_index ?? 0\n const content = getMissingSuffix(\n state.outputTextByIndex[outputIndex] ?? \"\",\n event.text,\n )\n if (content) {\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${content}`\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { content } })\n }\n continue\n }\n\n if (event.type === \"response.function_call_arguments.delta\") {\n if (event.output_index === undefined) {\n continue\n }\n\n state.toolArgumentsByIndex[event.output_index] = `${state.toolArgumentsByIndex[event.output_index] ?? \"\"}${event.delta ?? \"\"}`\n yield createRoleChunk(state)\n yield createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: event.output_index,\n type: \"function\",\n function: {\n arguments: event.delta ?? \"\",\n },\n },\n ],\n },\n })\n continue\n }\n\n if (event.type === \"response.function_call_arguments.done\") {\n if (event.output_index === undefined) {\n continue\n }\n\n const content = getMissingSuffix(\n state.toolArgumentsByIndex[event.output_index] ?? \"\",\n event.arguments,\n )\n if (content) {\n state.toolArgumentsByIndex[event.output_index] = `${state.toolArgumentsByIndex[event.output_index] ?? \"\"}${content}`\n yield createRoleChunk(state)\n yield createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: event.output_index,\n type: \"function\",\n function: { arguments: content },\n },\n ],\n },\n })\n }\n continue\n }\n\n if (event.type === \"response.completed\") {\n if (!event.response) {\n continue\n }\n\n const reasoningText = getResponsesReasoningText({\n output: event.response.output ?? [],\n reasoning: event.response.reasoning,\n })\n if (reasoningText && !state.reasoningTextSent) {\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { reasoning_text: reasoningText } })\n state.reasoningTextSent = true\n }\n\n yield createChunk(\n {\n ...state,\n responseId: event.response.id,\n createdAt: event.response.created_at,\n model: event.response.model,\n },\n {\n delta: {},\n finishReason: getFinishReason(\n {\n output: event.response.output ?? [],\n incomplete_details: event.response.incomplete_details,\n },\n (event.response.output ?? []).some(\n (item) => item.type === \"function_call\",\n ),\n ),\n usage: translateUsage(event.response.usage),\n },\n )\n yield { data: \"[DONE]\" }\n return\n }\n }\n}\n\nfunction handleOutputItemAdded(\n state: ResponseTranslationState,\n event: ResponsesStreamEnvelope,\n): ResponseStreamEventMessage | undefined {\n if (event.item?.type === \"message\") {\n return createRoleChunk(state)\n }\n\n if (event.item?.type === \"reasoning\") {\n const reasoningText = getSummaryText(event.item.summary)\n if (!reasoningText) {\n return undefined\n }\n\n state.reasoningTextSent = true\n return createChunk(state, { delta: { reasoning_text: reasoningText } })\n }\n\n if (\n event.item?.type === \"function_call\"\n && event.output_index !== undefined\n ) {\n const initialArguments = event.item.arguments ?? \"\"\n state.started = true\n state.toolStartedByIndex[event.output_index] = true\n state.toolArgumentsByIndex[event.output_index] = initialArguments\n return createChunk(state, {\n delta: {\n role: \"assistant\",\n tool_calls: [\n {\n index: event.output_index,\n id: event.item.call_id,\n type: \"function\",\n function: {\n name: event.item.name,\n arguments: initialArguments,\n },\n },\n ],\n },\n })\n }\n\n return undefined\n}\n\nfunction handleOutputItemDone(\n state: ResponseTranslationState,\n event: ResponsesStreamEnvelope,\n): Array<ResponseStreamEventMessage> {\n if (event.item?.type === \"reasoning\") {\n const reasoningText = getSummaryText(event.item.summary)\n if (!reasoningText || state.reasoningTextSent) {\n return []\n }\n\n state.reasoningTextSent = true\n return [createChunk(state, { delta: { reasoning_text: reasoningText } })]\n }\n\n if (event.item?.type === \"message\") {\n const outputIndex = event.output_index ?? 0\n const fullText = getMessageOutputText(event.item.content)\n const content = getMissingSuffix(\n state.outputTextByIndex[outputIndex] ?? \"\",\n fullText,\n )\n if (!content) {\n return []\n }\n\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${content}`\n return [createRoleChunk(state), createChunk(state, { delta: { content } })]\n }\n\n if (\n event.item?.type === \"function_call\"\n && event.output_index !== undefined\n ) {\n const outputIndex = event.output_index\n const chunks: Array<ResponseStreamEventMessage> = []\n\n if (!state.toolStartedByIndex[outputIndex]) {\n const initialArguments = event.item.arguments ?? \"\"\n state.started = true\n state.toolStartedByIndex[outputIndex] = true\n state.toolArgumentsByIndex[outputIndex] = initialArguments\n chunks.push(\n createChunk(state, {\n delta: {\n role: \"assistant\",\n tool_calls: [\n {\n index: outputIndex,\n id: event.item.call_id,\n type: \"function\",\n function: {\n name: event.item.name,\n arguments: initialArguments,\n },\n },\n ],\n },\n }),\n )\n return chunks\n }\n\n const content = getMissingSuffix(\n state.toolArgumentsByIndex[outputIndex] ?? \"\",\n event.item.arguments,\n )\n if (content) {\n state.toolArgumentsByIndex[outputIndex] = `${state.toolArgumentsByIndex[outputIndex] ?? \"\"}${content}`\n chunks.push(\n createRoleChunk(state),\n createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: outputIndex,\n type: \"function\",\n function: { arguments: content },\n },\n ],\n },\n }),\n )\n }\n\n return chunks\n }\n\n return []\n}\n\nfunction createRoleChunk(\n state: ResponseTranslationState,\n): ResponseStreamEventMessage {\n if (state.started) {\n return { data: \"\" }\n }\n\n state.started = true\n return createChunk(state, { delta: { role: \"assistant\" } })\n}\n\nfunction createChunk(\n state: ResponseTranslationState,\n { delta, finishReason = null, usage }: CreateChunkOptions,\n): ResponseStreamEventMessage {\n return {\n data: JSON.stringify({\n id: state.responseId,\n object: \"chat.completion.chunk\",\n created: state.createdAt,\n model: state.model,\n choices: [\n {\n index: 0,\n delta,\n finish_reason: finishReason,\n logprobs: null,\n },\n ],\n usage,\n } satisfies ChatCompletionChunk),\n }\n}\n\nfunction translateMessagesToResponsesInput(\n messages: Array<Message>,\n): ResponsesInput {\n // Copilot accepts a bare string for the simplest single-user prompt, but\n // multi-turn, tool, or image conversations need structured input items.\n const items = messages.flatMap((message) => translateMessage(message))\n\n if (\n items.length === 1\n && \"role\" in items[0]\n && items[0].role === \"user\"\n && typeof items[0].content === \"string\"\n ) {\n return items[0].content\n }\n\n return items\n}\n\nfunction translateMessage(message: Message): Array<ResponsesInputItem> {\n if (message.role === \"tool\") {\n return [\n {\n type: \"function_call_output\",\n call_id: message.tool_call_id ?? \"\",\n output: stringifyToolOutput(message.content),\n },\n ]\n }\n\n const translated: Array<ResponsesInputItem> = []\n\n if (hasContent(message.content)) {\n translated.push({\n role: message.role,\n content: translateContent(message.content),\n })\n }\n\n if (message.role === \"assistant\" && message.tool_calls) {\n translated.push(\n ...message.tool_calls.map((toolCall) => ({\n type: \"function_call\" as const,\n call_id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n })),\n )\n }\n\n return translated\n}\n\nfunction hasContent(content: Message[\"content\"]): boolean {\n if (content === null) {\n return false\n }\n\n if (typeof content === \"string\") {\n return content.length > 0\n }\n\n return content.length > 0\n}\n\nfunction translateContent(\n content: Message[\"content\"],\n): ResponsesMessageInput[\"content\"] {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!content || content.length === 0) {\n return \"\"\n }\n\n return content.map((part) => translateContentPart(part))\n}\n\nfunction translateContentPart(part: ContentPart): ResponsesInputContentPart {\n if (part.type === \"text\") {\n return {\n type: \"input_text\",\n text: part.text,\n }\n }\n\n return {\n type: \"input_image\",\n image_url: part.image_url.url,\n detail: part.image_url.detail ?? \"auto\",\n }\n}\n\nfunction stringifyToolOutput(content: Message[\"content\"]): string {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!content) {\n return \"\"\n }\n\n const text = content\n .filter(\n (part): part is Extract<ContentPart, { type: \"text\" }> =>\n part.type === \"text\",\n )\n .map((part) => part.text)\n .join(\"\\n\\n\")\n\n return text || JSON.stringify(content)\n}\n\nfunction translateTools(\n tools: Array<Tool> | null | undefined,\n): Array<ResponsesTool> | undefined {\n if (!tools) {\n return undefined\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n name: tool.function.name,\n description: tool.function.description,\n parameters: tool.function.parameters,\n }))\n}\n\nfunction translateToolChoice(\n toolChoice: ChatCompletionsPayload[\"tool_choice\"],\n): ResponsesToolChoice | undefined {\n if (!toolChoice) {\n return undefined\n }\n\n if (typeof toolChoice === \"string\") {\n return toolChoice\n }\n\n return {\n type: \"function\",\n name: toolChoice.function.name,\n }\n}\n\nfunction translateUsage(\n usage: ResponsesApiResponse[\"usage\"] | undefined,\n): ChatCompletionResponse[\"usage\"] | undefined {\n if (!usage) {\n return undefined\n }\n\n return {\n prompt_tokens: usage.input_tokens,\n completion_tokens: usage.output_tokens,\n total_tokens: usage.total_tokens,\n ...(usage.input_tokens_details?.cached_tokens !== undefined && {\n prompt_tokens_details: {\n cached_tokens: usage.input_tokens_details.cached_tokens,\n },\n }),\n }\n}\n\nfunction getMessageOutputText(\n content: ResponsesMessageOutputItem[\"content\"] | undefined,\n): string | undefined {\n const text = content\n ?.filter((part) => part.type === \"output_text\")\n .map((part) => part.text)\n .join(\"\")\n\n return text || undefined\n}\n\nfunction getMissingSuffix(\n alreadySent: string,\n finalValue: string | undefined,\n): string | undefined {\n // Responses \"done\" events often repeat the full text/arguments after deltas;\n // emit only the suffix that has not already been forwarded to the chat stream.\n if (!finalValue) {\n return undefined\n }\n\n if (!alreadySent) {\n return finalValue\n }\n\n if (!finalValue.startsWith(alreadySent)) {\n return undefined\n }\n\n const suffix = finalValue.slice(alreadySent.length)\n return suffix || undefined\n}\n\nfunction getResponsesReasoningText(\n response: Pick<ResponsesApiResponse, \"output\" | \"reasoning\">,\n): string | undefined {\n const parts = [\n getSummaryText(response.reasoning?.summary),\n ...response.output\n .filter((item): item is ResponsesReasoningOutputItem => item.type === \"reasoning\")\n .map((item) => getSummaryText(item.summary)),\n ].filter((part): part is string => Boolean(part))\n\n return parts.length > 0 ? parts.join(\"\\n\\n\") : undefined\n}\n\nfunction getSummaryText(summary: ResponsesReasoningSummary | undefined): string | undefined {\n if (typeof summary === \"string\") {\n return summary || undefined\n }\n\n if (!Array.isArray(summary)) {\n return undefined\n }\n\n const text = summary\n .map((part) => part.text)\n .filter((part): part is string => Boolean(part))\n .join(\"\\n\\n\")\n\n return text || undefined\n}\n\nfunction getFinishReason(\n response: Pick<ResponsesApiResponse, \"output\" | \"incomplete_details\">,\n hasFunctionCalls: boolean,\n): \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" {\n if (hasFunctionCalls) {\n return \"tool_calls\"\n }\n\n if (response.incomplete_details?.reason?.includes(\"max_output_tokens\")) {\n return \"length\"\n }\n\n return \"stop\"\n}\n","// Internal Copilot chat API wrapper used by the Claude route and startup preflight.\nimport { events } from \"fetch-event-stream\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { defaultReasoningEffort } from \"~/lib/models\"\nimport { routeModelId } from \"~/lib/models\"\nimport { runtimeState } from \"~/lib/state\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n readCopilotText,\n} from \"~/copilot/client\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n} from \"~/copilot/types\"\n\nimport {\n buildResponsesRequestPayload,\n shouldUseResponsesApiForModel,\n translateResponsesStreamToChatCompletionStream,\n translateResponsesToChatCompletion,\n type ResponsesApiResponse,\n type ResponsesReasoningEffort,\n type ResponsesRequestPayload,\n} from \"./responses\"\n\nconst usesMaxCompletionTokens = (modelId: string): boolean =>\n modelId.startsWith(\"gpt-5\")\n\nconst continuePrefillPrompt =\n \"Continue the assistant response from the previous assistant message.\"\n\nconst continueAfterNonUserPrompt =\n \"Continue based on the context above.\"\n\ntype ClientKind = \"claude\" | \"generic\"\n\ninterface CreateChatCompletionsOptions {\n client?: ClientKind\n requestId?: string\n requestedModel?: string\n requestedThinkEffort?: string\n requestedThinking?: string\n signal?: AbortSignal\n timeoutMs?: number\n}\n\nconst maxUserLength = 64\n\nexport const sanitizeReasoningEffortForModel = (\n _modelId: string,\n reasoningEffort: ChatCompletionsPayload[\"reasoning_effort\"],\n): ChatCompletionsPayload[\"reasoning_effort\"] => {\n return reasoningEffort ?? undefined\n}\n\nconst getRequestedReasoningEffort = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsPayload[\"reasoning_effort\"] => {\n // Configured think effort wins over client input so startup preflight and\n // actual Claude Code traffic exercise the same upstream behavior.\n return sanitizeReasoningEffortForModel(\n payload.model,\n runtimeState.thinkEffort ?? defaultReasoningEffort,\n )\n}\n\nexport const sanitizeUserIdentifier = (\n user: string | null | undefined,\n): string | undefined => {\n if (!user) {\n return undefined\n }\n\n return user.slice(0, maxUserLength)\n}\n\ntype ChatCompletionsRequestPayload = Omit<\n ChatCompletionsPayload,\n \"max_tokens\"\n> & {\n max_tokens?: number | null\n max_completion_tokens?: number | null\n}\n\nconst buildRequestPayload = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsRequestPayload => {\n const requestedReasoningEffort = getRequestedReasoningEffort(payload)\n\n if (\n !usesMaxCompletionTokens(payload.model)\n || payload.max_tokens === null\n || payload.max_tokens === undefined\n ) {\n const sanitizedPayload = {\n ...payload,\n reasoning_effort: requestedReasoningEffort,\n user: sanitizeUserIdentifier(payload.user),\n }\n\n return sanitizedPayload\n }\n\n // GPT-5-class Copilot endpoints reject max_tokens and require the newer\n // max_completion_tokens field; older models still use max_tokens.\n return {\n ...payload,\n max_tokens: undefined,\n max_completion_tokens: payload.max_tokens,\n reasoning_effort: requestedReasoningEffort,\n user: sanitizeUserIdentifier(payload.user),\n }\n}\n\n// Copilot's Claude-family models reject a conversation that does not end with a\n// user message: \"This model does not support assistant message prefill. The\n// conversation must end with a user message.\" Anthropic removed prefill support\n// in Opus 4.7+ / Sonnet 4.6+, so this is an upstream constraint, not a Copilot\n// quirk.\n//\n// A trailing assistant message is the prefill case and keeps its dedicated\n// handling below. Any other non-user trailing role (system, developer, tool)\n// gets a short user turn appended. This guard is deliberately at the shared\n// /chat/completions layer rather than in one caller: the same bug has been\n// reported elsewhere as a fixup applied on one code path and missed on another\n// (openclaw#75395), and a payload that reaches here ending on a non-user role is\n// rejected no matter which caller built it.\nconst normalizeFinalAssistantPrefill = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsPayload => {\n const lastMessage = payload.messages.at(-1)\n if (!lastMessage || lastMessage.role === \"user\") {\n return payload\n }\n\n if (lastMessage.role !== \"assistant\") {\n return {\n ...payload,\n messages: [\n ...payload.messages,\n { role: \"user\", content: continueAfterNonUserPrompt },\n ],\n }\n }\n\n const messages = [...payload.messages]\n if (typeof lastMessage.content !== \"string\") {\n messages.pop()\n return { ...payload, messages }\n }\n\n const content = lastMessage.content.trimEnd()\n if (!content) {\n messages.pop()\n return { ...payload, messages }\n }\n\n messages[messages.length - 1] = {\n ...lastMessage,\n content,\n }\n messages.push({ role: \"user\", content: continuePrefillPrompt })\n return { ...payload, messages }\n}\n\nconst isAgentInitiator = (\n messages: ChatCompletionsPayload[\"messages\"],\n): \"agent\" | \"user\" =>\n messages.some((msg) => msg.role === \"assistant\" || msg.role === \"tool\") ?\n \"agent\"\n : \"user\"\n\nconst messagesIncludeImage = (\n messages: ChatCompletionsPayload[\"messages\"],\n): boolean =>\n messages.some(\n (msg) =>\n typeof msg.content !== \"string\"\n && msg.content?.some((part) => part.type === \"image_url\"),\n )\n\nexport const createChatCompletions = async (\n config: ProxyConfig,\n payload: ChatCompletionsPayload,\n options: CreateChatCompletionsOptions = {},\n) => {\n const client = options.client ?? \"generic\"\n const requestedModel = options.requestedModel ?? payload.model\n const requestedThinkEffort =\n options.requestedThinkEffort ?? payload.reasoning_effort ?? \"none\"\n const requestedThinking = options.requestedThinking ?? \"none\"\n const signal = createCopilotRequestSignal(options.signal, options.timeoutMs)\n const upstreamModelId = routeModelId(payload.model)\n const upstreamPayload =\n upstreamModelId === payload.model ? payload : { ...payload, model: upstreamModelId }\n const useResponsesApi = shouldUseResponsesApiForModel(upstreamPayload.model)\n const compatiblePayload =\n useResponsesApi ? upstreamPayload : normalizeFinalAssistantPrefill(upstreamPayload)\n const provider = getCopilotProviderContext(config)\n const enableVision = messagesIncludeImage(compatiblePayload.messages)\n const initiator = isAgentInitiator(compatiblePayload.messages)\n const requestPayload = buildRequestPayload(compatiblePayload)\n log.debug(\n [\n \"Model request\",\n `client=${client}`,\n `requested_model=${requestedModel}`,\n ...(options.requestId ? [`request_id=${options.requestId}`] : []),\n `upstream_model=${compatiblePayload.model}`,\n `requested_think_effort=${requestedThinkEffort}`,\n `requested_thinking=${requestedThinking}`,\n `effective_think_effort=${requestPayload.reasoning_effort ?? \"none\"}`,\n ].join(\" \"),\n )\n log.debug(\"Full request payload\", {\n payload: requestPayload,\n })\n // Choose the Copilot API surface after model routing, because aliases can\n // resolve to a Responses-only upstream model even when the client asked for a\n // generic Claude model name.\n if (useResponsesApi) {\n return createResponses(provider, compatiblePayload, {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n })\n }\n\n const response = await fetchCopilot(\n provider,\n \"/chat/completions\",\n {\n method: \"POST\",\n headers: {\n accept: compatiblePayload.stream ? \"text/event-stream\" : \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(requestPayload),\n },\n {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n if (await shouldRetryWithResponses(response, signal, options.timeoutMs)) {\n return createResponses(provider, compatiblePayload, {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n })\n }\n\n const detail = await logUpstreamError(\"Failed to create chat completions\", response, {\n model: payload.model,\n request: requestPayload,\n route: \"/chat/completions\",\n requestId: options.requestId,\n }, signal, options.timeoutMs)\n throw new HTTPError(\n \"Failed to create chat completions\",\n response,\n detail,\n )\n }\n\n if (compatiblePayload.stream) {\n return events(response)\n }\n\n return readCopilotJson<ChatCompletionResponse>(\n response,\n signal,\n options.timeoutMs,\n )\n}\n\nasync function createResponses(\n provider: ReturnType<typeof getCopilotProviderContext>,\n payload: ChatCompletionsPayload,\n options: {\n vision: boolean\n initiator: \"agent\" | \"user\"\n requestId?: string\n signal?: AbortSignal\n timeoutMs?: number\n },\n) {\n const reasoningEffort = getRequestedReasoningEffort(\n payload,\n ) as ResponsesReasoningEffort | undefined\n const requestPayload = buildResponsesRequestPayload(payload, reasoningEffort)\n\n const response = await fetchCopilot(\n provider,\n \"/responses\",\n {\n method: \"POST\",\n headers: {\n accept: payload.stream ? \"text/event-stream\" : \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(requestPayload),\n },\n {\n vision: options.vision,\n initiator: options.initiator,\n requestId: options.requestId,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n const detail = await logUpstreamError(\"Failed to create responses\", response, {\n model: payload.model,\n request: requestPayload,\n route: \"/responses\",\n requestId: options.requestId,\n }, options.signal, options.timeoutMs)\n throw new HTTPError(\n \"Failed to create responses\",\n response,\n detail,\n )\n }\n\n if (payload.stream) {\n return translateResponsesStreamToChatCompletionStream(events(response))\n }\n\n return translateResponsesToChatCompletion(\n await readCopilotJson<ResponsesApiResponse>(\n response,\n options.signal,\n options.timeoutMs,\n ),\n )\n}\n\nasync function logUpstreamError(\n message: string,\n response: Response,\n context: {\n model: string\n request?: ChatCompletionsRequestPayload | ResponsesRequestPayload\n route: string\n requestId?: string\n },\n signal?: AbortSignal,\n timeoutMs?: number,\n): Promise<string | undefined> {\n const errorBody = await readCopilotText(\n response.clone(),\n signal,\n timeoutMs,\n ).catch(() => \"\")\n const detail = getUpstreamErrorDetail(response, errorBody)\n\n log.error(`${message}: route=${context.route} model=${context.model} status=${response.status}${context.requestId ? ` request_id=${context.requestId}` : \"\"}`, {\n message,\n route: context.route,\n model: context.model,\n requestId: context.requestId,\n request: context.request,\n response: {\n status: response.status,\n statusText: response.statusText || undefined,\n url: response.url || undefined,\n headers: Object.fromEntries(response.headers.entries()),\n body: errorBody || undefined,\n },\n })\n\n return detail\n}\n\nfunction getUpstreamErrorDetail(\n response: Response,\n body: string,\n): string | undefined {\n if (!body) {\n return response.statusText || undefined\n }\n\n try {\n const payload = JSON.parse(body) as {\n error?: {\n code?: string\n message?: string\n }\n }\n return payload.error?.message ?? payload.error?.code ?? body.slice(0, 240)\n } catch {\n return body.slice(0, 240)\n }\n}\n\nasync function shouldRetryWithResponses(\n response: Response,\n signal?: AbortSignal,\n timeoutMs?: number,\n): Promise<boolean> {\n // Copilot can list a model but reject the chat endpoint for it; only this\n // explicit upstream code is treated as a signal to retry via /responses.\n try {\n const errorBody = await readCopilotJson<{\n error?: {\n code?: string\n }\n }>(response.clone(), signal, timeoutMs)\n return errorBody.error?.code === \"unsupported_api_for_model\"\n } catch {\n return false\n }\n}\n","// Startup preflight: fail fast if configured models or think effort cannot be used upstream.\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport type { ReasoningEffort } from \"~/lib/models\"\nimport { getUpstreamModelIds } from \"~/lib/models\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n} from \"~/copilot/client\"\nimport type { ChatCompletionsPayload } from \"~/copilot/types\"\nimport { createChatCompletions } from \"~/copilot/chat\"\n\ninterface CopilotModelsResponse {\n data?: Array<{ id?: string }>\n}\n\nconst fetchUpstreamModelIds = async (config: ProxyConfig): Promise<Set<string>> => {\n const provider = getCopilotProviderContext(config)\n const signal = createCopilotRequestSignal(undefined, config.upstreamTimeoutMs)\n const response = await fetchCopilot(provider, \"/models\", {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n }, { signal, timeoutMs: config.upstreamTimeoutMs })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to validate upstream models\", response)\n }\n\n const payload = await readCopilotJson<CopilotModelsResponse>(\n response,\n signal,\n config.upstreamTimeoutMs,\n )\n return new Set(\n (payload.data ?? [])\n .map((model) => model.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0),\n )\n}\n\nconst ensureRequiredModels = async (config: ProxyConfig): Promise<void> => {\n const upstreamModels = await fetchUpstreamModelIds(config)\n const requiredModels = getUpstreamModelIds()\n const missingModels = requiredModels.filter((model) => !upstreamModels.has(model))\n\n if (missingModels.length > 0) {\n throw new Error(\n `Required Copilot model(s) unavailable upstream: ${missingModels.join(\", \")}`,\n )\n }\n\n log.info(`Upstream models available: ${requiredModels.join(\", \")}`)\n}\n\nconst createProbePayload = (model: string): ChatCompletionsPayload => ({\n model,\n max_tokens: 16,\n stream: false,\n messages: [\n {\n role: \"user\",\n content: \"Reply with OK only.\",\n },\n ],\n})\n\nconst validateModelRequest = async (\n config: ProxyConfig,\n model: string,\n thinkEffort: ReasoningEffort,\n): Promise<void> => {\n try {\n // Probe through the same internal chat path as real requests so routing,\n // token headers, think effort, and /responses fallback are validated together.\n const response = await createChatCompletions(\n config,\n createProbePayload(model),\n {\n client: \"generic\",\n requestedModel: model,\n timeoutMs: config.upstreamTimeoutMs,\n },\n )\n\n if (typeof response !== \"object\" || response === null || !(\"choices\" in response)) {\n throw new Error(`Preflight request for ${model} unexpectedly streamed`)\n }\n\n log.info(`Preflight OK: model=${model} think_effort=${thinkEffort}`)\n } catch (error) {\n if (error instanceof HTTPError) {\n const text = await error.response.text().catch(() => \"\")\n throw new Error(\n `Preflight failed for model=${model} think_effort=${thinkEffort}: ${error.response.status} ${error.response.statusText}${text ? ` ${text}` : \"\"}`,\n )\n }\n throw error\n }\n}\n\nexport const validateUpstream = async (\n config: ProxyConfig,\n thinkEffort: ReasoningEffort,\n): Promise<void> => {\n log.info(\"Running upstream preflight\")\n await ensureRequiredModels(config)\n\n for (const model of getUpstreamModelIds()) {\n await validateModelRequest(config, model, thinkEffort)\n }\n}\n","// Small Claude protocol helpers shared by streaming and non-streaming translators.\nimport type { ClaudeResponse } from \"~/claude/types\"\n\nexport function mapOpenAIStopReasonToClaude(\n finishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null,\n): ClaudeResponse[\"stop_reason\"] {\n if (finishReason === null) {\n return null\n }\n\n const stopReasonMap = {\n stop: \"end_turn\",\n length: \"max_tokens\",\n tool_calls: \"tool_use\",\n content_filter: \"end_turn\",\n } as const\n\n return stopReasonMap[finishReason]\n}\n","// Maps Claude tool names to Copilot-compatible tool names and back.\nimport { createHash } from \"node:crypto\"\n\nimport type { ClaudeTool } from \"~/claude/types\"\n\nconst strictToolNameCharsPattern = /^[A-Za-z0-9_-]+$/\nconst dottedToolNameCharsPattern = /^[A-Za-z0-9_.-]+$/\nconst defaultToolNameMaxLength = 64\nconst extendedToolNameMaxLength = 128\nconst hashLength = 10\n\nexport interface ClaudeToolNameMapper {\n toClaude(name: string): string\n toOpenAI(name: string): string\n}\n\ninterface ToolNameMapperOptions {\n allowDots?: boolean\n maxNameLength?: number\n}\n\nexport const getToolNameMapperOptionsForModel = (\n modelId: string,\n): Required<ToolNameMapperOptions> => {\n const normalized = modelId\n .trim()\n .toLowerCase()\n .replace(/\\[1m\\]$/, \"-1m\")\n .replace(/[._]/g, \"-\")\n\n if (normalized.startsWith(\"gpt-\")) {\n return { allowDots: false, maxNameLength: extendedToolNameMaxLength }\n }\n\n return { allowDots: false, maxNameLength: defaultToolNameMaxLength }\n}\n\nexport const getClaudeToolNameMaxLength = (modelId: string): number =>\n getToolNameMapperOptionsForModel(modelId).maxNameLength\n\nconst makeHash = (value: string): string =>\n createHash(\"sha1\").update(value).digest(\"hex\").slice(0, hashLength)\n\nconst getAllowedNamePattern = (allowDots: boolean): RegExp =>\n allowDots ? dottedToolNameCharsPattern : strictToolNameCharsPattern\n\nconst cleanToolName = (name: string, allowDots: boolean): string => {\n const invalidCharsPattern = allowDots ? /[^A-Za-z0-9_.-]/g : /[^A-Za-z0-9_-]/g\n const cleaned = name.replace(invalidCharsPattern, \"_\").replace(/_+/g, \"_\")\n return cleaned.replace(/^_+|_+$/g, \"\") || \"tool\"\n}\n\nconst isValidToolName = (\n name: string,\n maxNameLength: number,\n allowDots: boolean,\n): boolean =>\n name.length > 0\n && name.length <= maxNameLength\n && getAllowedNamePattern(allowDots).test(name)\n\nconst makeValidToolName = (\n name: string,\n maxNameLength: number,\n allowDots: boolean,\n): string => {\n if (isValidToolName(name, maxNameLength, allowDots)) {\n return name\n }\n\n const cleaned = cleanToolName(name, allowDots)\n if (cleaned.length <= maxNameLength) {\n return cleaned\n }\n\n const hash = makeHash(name)\n const prefixLength = maxNameLength - hash.length - 1\n return `${cleaned.slice(0, prefixLength)}_${hash}`\n}\n\nconst makeUniqueToolName = (\n name: string,\n used: Set<string>,\n maxNameLength: number,\n allowDots: boolean,\n): string => {\n const candidate = makeValidToolName(name, maxNameLength, allowDots)\n if (!used.has(candidate)) {\n return candidate\n }\n\n for (let index = 2; ; index++) {\n const suffix = `_${makeHash(`${name}:${index}`)}`\n const prefixLength = maxNameLength - suffix.length\n const next = `${cleanToolName(name, allowDots).slice(0, prefixLength)}${suffix}`\n if (!used.has(next)) {\n return next\n }\n }\n}\n\nexport const createClaudeToolNameMapper = (\n tools: Array<ClaudeTool> | undefined,\n options: ToolNameMapperOptions = {},\n): ClaudeToolNameMapper => {\n const maxNameLength = options.maxNameLength ?? defaultToolNameMaxLength\n const allowDots = options.allowDots ?? false\n const claudeToOpenAI = new Map<string, string>()\n const openAIToClaude = new Map<string, string>()\n const used = new Set<string>()\n\n for (const tool of tools ?? []) {\n if (claudeToOpenAI.has(tool.name)) {\n continue\n }\n\n const openAIName = makeUniqueToolName(\n tool.name,\n used,\n maxNameLength,\n allowDots,\n )\n used.add(openAIName)\n claudeToOpenAI.set(tool.name, openAIName)\n openAIToClaude.set(openAIName, tool.name)\n }\n\n return {\n toClaude: (name) => openAIToClaude.get(name) ?? name,\n toOpenAI: (name) =>\n claudeToOpenAI.get(name)\n ?? makeValidToolName(name, maxNameLength, allowDots),\n }\n}\n","// Non-streaming protocol translation between Claude Messages and Copilot chat completions.\nimport {\n defaultReasoningEffort,\n normalizeClaudeModelId,\n routeModelId,\n} from \"~/lib/models\"\nimport { runtimeState } from \"~/lib/state\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n ContentPart,\n Message,\n TextPart,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\n\nimport {\n type ClaudeAssistantContentBlock,\n type ClaudeAssistantMessage,\n type ClaudeMessage,\n type ClaudeMessagesPayload,\n type ClaudeResponse,\n type ClaudeTextBlock,\n type ClaudeThinkingBlock,\n type ClaudeTool,\n type ClaudeToolResultBlock,\n type ClaudeToolUseBlock,\n type ClaudeUserContentBlock,\n type ClaudeUserMessage,\n} from \"~/claude/types\"\nimport { mapOpenAIStopReasonToClaude } from \"~/claude/utils\"\nimport {\n createClaudeToolNameMapper,\n getToolNameMapperOptionsForModel,\n type ClaudeToolNameMapper,\n} from \"~/claude/tool-names\"\n\nexport function translateModelName(model: string): string {\n return routeModelId(model)\n}\n\nexport function translateToOpenAI(\n payload: ClaudeMessagesPayload,\n _settings?: undefined,\n toolNameMapper?: ClaudeToolNameMapper,\n): ChatCompletionsPayload {\n const model = translateModelName(payload.model)\n const mapper = toolNameMapper ?? createClaudeToolNameMapper(payload.tools, {\n ...getToolNameMapperOptionsForModel(model),\n })\n const tools = translateClaudeToolsToOpenAI(payload.tools, mapper)\n const messages = translateClaudeMessagesToOpenAI(\n payload.messages,\n payload.system,\n mapper,\n )\n\n return {\n model,\n messages: normalizeFinalAssistantPrefill(messages),\n max_tokens: payload.max_tokens,\n stop: payload.stop_sequences,\n stream: payload.stream,\n temperature: payload.temperature,\n top_p: payload.top_p,\n reasoning_effort: runtimeState.thinkEffort ?? defaultReasoningEffort,\n user: payload.metadata?.user_id,\n tools,\n tool_choice:\n tools && tools.length > 0 ?\n translateClaudeToolChoiceToOpenAI(payload.tool_choice, mapper)\n : undefined,\n }\n}\n\nfunction translateClaudeMessagesToOpenAI(\n claudeMessages: Array<ClaudeMessage>,\n system: string | Array<ClaudeTextBlock> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Message> {\n const systemMessages = handleSystemPrompt(system)\n const otherMessages = claudeMessages.flatMap((message) =>\n message.role === \"user\" ? handleUserMessage(message) : handleAssistantMessage(message, toolNameMapper),\n )\n return [...systemMessages, ...otherMessages]\n}\n\nfunction handleSystemPrompt(\n system: string | Array<ClaudeTextBlock> | undefined,\n): Array<Message> {\n if (!system) {\n return []\n }\n\n if (typeof system === \"string\") {\n return [{ role: \"system\", content: system }]\n }\n\n // Claude accepts multiple system text blocks; Copilot chat expects one\n // system message, so preserve block boundaries with blank lines.\n return [{ role: \"system\", content: system.map((block) => block.text).join(\"\\n\\n\") }]\n}\n\nfunction handleUserMessage(message: ClaudeUserMessage): Array<Message> {\n const newMessages: Array<Message> = []\n\n if (Array.isArray(message.content)) {\n const toolResultBlocks = message.content.filter(\n (block): block is ClaudeToolResultBlock => block.type === \"tool_result\",\n )\n const otherBlocks = message.content.filter((block) => block.type !== \"tool_result\")\n\n // Tool results must become standalone OpenAI tool messages before any\n // remaining user content, otherwise upstream cannot associate them with\n // prior assistant tool calls.\n for (const block of toolResultBlocks) {\n newMessages.push({\n role: \"tool\",\n tool_call_id: block.tool_use_id,\n content: mapContent(block.content),\n })\n }\n\n if (otherBlocks.length > 0) {\n newMessages.push({\n role: \"user\",\n content: mapContent(otherBlocks),\n })\n }\n } else {\n newMessages.push({\n role: \"user\",\n content: mapContent(message.content),\n })\n }\n\n return newMessages\n}\n\nfunction handleAssistantMessage(\n message: ClaudeAssistantMessage,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Message> {\n if (!Array.isArray(message.content)) {\n return [{ role: \"assistant\", content: mapContent(message.content) }]\n }\n\n const toolUseBlocks = message.content.filter(\n (block): block is ClaudeToolUseBlock => block.type === \"tool_use\",\n )\n const textBlocks = message.content.filter(\n (block): block is ClaudeTextBlock => block.type === \"text\",\n )\n const thinkingBlocks = message.content.filter(\n (block): block is ClaudeThinkingBlock => block.type === \"thinking\",\n )\n\n const allTextContent = [\n ...textBlocks.map((b) => b.text),\n ...thinkingBlocks.map((b) => b.thinking),\n ].join(\"\\n\\n\")\n\n // Assistant tool_use blocks bridge to OpenAI tool_calls; plain text and\n // thinking content stay on the assistant message as context for those calls.\n return toolUseBlocks.length > 0\n ? [\n {\n role: \"assistant\",\n content: allTextContent || null,\n tool_calls: toolUseBlocks.map((toolUse) => ({\n id: toolUse.id,\n type: \"function\",\n function: {\n name: toolNameMapper.toOpenAI(toolUse.name),\n arguments: JSON.stringify(toolUse.input),\n },\n })),\n },\n ]\n : [{ role: \"assistant\", content: mapContent(message.content) }]\n}\n\nconst continuePrefillPrompt =\n \"Continue the assistant response from the previous assistant message.\"\n\nconst normalizeFinalAssistantPrefill = (\n messages: Array<Message>,\n): Array<Message> => {\n let lastAssistantIndex = -1\n for (let index = messages.length - 1; index >= 0; index--) {\n if (messages[index]?.role === \"assistant\") {\n lastAssistantIndex = index\n break\n }\n }\n if (lastAssistantIndex < 0) {\n return messages\n }\n if (lastAssistantIndex !== messages.length - 1) {\n return messages\n }\n\n const lastAssistant = messages[lastAssistantIndex]\n if (typeof lastAssistant.content !== \"string\") {\n return messages.slice(0, -1)\n }\n\n const trimmedContent = lastAssistant.content.trimEnd()\n // Claude Messages allows a final assistant message as a prefill prefix.\n // Copilot rejects that shape, so keep the prefix as context and append a\n // minimal user turn that asks upstream to continue from it.\n const nextMessages = [...messages]\n if (trimmedContent) {\n nextMessages[lastAssistantIndex] = {\n ...lastAssistant,\n content: trimmedContent,\n }\n nextMessages.push({ role: \"user\", content: continuePrefillPrompt })\n } else {\n nextMessages.pop()\n }\n return nextMessages\n}\n\nfunction mapContent(\n content:\n | string\n | Array<ClaudeUserContentBlock | ClaudeAssistantContentBlock>,\n): string | Array<ContentPart> | null {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!Array.isArray(content)) {\n return null\n }\n\n const hasImage = content.some((block) => block.type === \"image\")\n if (!hasImage) {\n // Without images the OpenAI-compatible chat surface accepts plain strings;\n // image messages must use content parts instead.\n return content\n .filter(\n (block): block is ClaudeTextBlock | ClaudeThinkingBlock =>\n block.type === \"text\" || block.type === \"thinking\",\n )\n .map((block) => (block.type === \"text\" ? block.text : block.thinking))\n .join(\"\\n\\n\")\n }\n\n const contentParts: Array<ContentPart> = []\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n contentParts.push({ type: \"text\", text: block.text })\n break\n }\n case \"thinking\": {\n contentParts.push({ type: \"text\", text: block.thinking })\n break\n }\n case \"image\": {\n contentParts.push({\n type: \"image_url\",\n image_url: {\n url: `data:${block.source.media_type};base64,${block.source.data}`,\n },\n })\n break\n }\n }\n }\n\n return contentParts\n}\n\nfunction translateClaudeToolsToOpenAI(\n claudeTools: Array<ClaudeTool> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Tool> | undefined {\n if (!claudeTools || claudeTools.length === 0) {\n return undefined\n }\n\n const tools = claudeTools.flatMap((tool) => {\n if (!tool.input_schema) {\n return []\n }\n\n return [{\n type: \"function\" as const,\n function: {\n name: toolNameMapper.toOpenAI(tool.name),\n description: tool.description,\n parameters: tool.input_schema,\n },\n }]\n })\n\n return tools.length > 0 ? tools : undefined\n}\n\nfunction translateClaudeToolChoiceToOpenAI(\n claudeToolChoice: ClaudeMessagesPayload[\"tool_choice\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tool_choice\"] {\n if (!claudeToolChoice) {\n return undefined\n }\n\n switch (claudeToolChoice.type) {\n case \"auto\": {\n return \"auto\"\n }\n case \"any\": {\n return \"required\"\n }\n case \"tool\": {\n if (claudeToolChoice.name) {\n return {\n type: \"function\",\n function: { name: toolNameMapper.toOpenAI(claudeToolChoice.name) },\n }\n }\n return undefined\n }\n case \"none\": {\n return \"none\"\n }\n default: {\n return undefined\n }\n }\n}\n\nexport function translateToClaude(\n response: ChatCompletionResponse,\n toolNameMapper: ClaudeToolNameMapper = createClaudeToolNameMapper(\n undefined,\n ),\n): ClaudeResponse {\n const allThinkingBlocks: Array<ClaudeThinkingBlock> = []\n const allTextBlocks: Array<ClaudeTextBlock> = []\n const allToolUseBlocks: Array<ClaudeToolUseBlock> = []\n let stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null = null\n\n stopReason = response.choices[0]?.finish_reason ?? stopReason\n\n // Multiple choices can mix text and tool calls. Claude has one stop_reason,\n // so prefer tool_calls when present while preserving a normal stop result.\n for (const choice of response.choices) {\n allThinkingBlocks.push(\n ...getClaudeThinkingBlocks(\n choice.message.reasoning_text ?? choice.message.reasoning_content,\n ),\n )\n allTextBlocks.push(...getClaudeTextBlocks(choice.message.content))\n allToolUseBlocks.push(\n ...getClaudeToolUseBlocks(choice.message.tool_calls, toolNameMapper),\n )\n\n if (choice.finish_reason === \"tool_calls\" || stopReason === \"stop\") {\n stopReason = choice.finish_reason\n }\n }\n\n return {\n id: response.id,\n type: \"message\",\n role: \"assistant\",\n model: normalizeClaudeModelId(response.model),\n content: [...allThinkingBlocks, ...allTextBlocks, ...allToolUseBlocks],\n stop_reason: mapOpenAIStopReasonToClaude(stopReason),\n stop_sequence: null,\n usage: {\n input_tokens:\n (response.usage?.prompt_tokens ?? 0)\n - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: response.usage?.completion_tokens ?? 0,\n ...(response.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n }\n}\n\nfunction getClaudeThinkingBlocks(\n reasoningContent: string | null | undefined,\n): Array<ClaudeThinkingBlock> {\n if (!reasoningContent) {\n return []\n }\n\n return [{ type: \"thinking\", thinking: reasoningContent }]\n}\n\nfunction getClaudeTextBlocks(\n messageContent: Message[\"content\"],\n): Array<ClaudeTextBlock> {\n if (typeof messageContent === \"string\") {\n return [{ type: \"text\", text: messageContent }]\n }\n\n if (Array.isArray(messageContent)) {\n return messageContent\n .filter((part): part is TextPart => part.type === \"text\")\n .map((part) => ({ type: \"text\", text: part.text }))\n }\n\n return []\n}\n\nfunction getClaudeToolUseBlocks(\n toolCalls: Array<ToolCall> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<ClaudeToolUseBlock> {\n if (!toolCalls) {\n return []\n }\n\n return toolCalls.map((toolCall) => ({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolNameMapper.toClaude(toolCall.function.name),\n input: safeJsonParse(toolCall.function.arguments),\n }))\n}\n\nfunction safeJsonParse(input: string): Record<string, unknown> {\n try {\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return { raw: input }\n }\n}\n","// Stateful streaming adapter from Copilot chat completion chunks to Claude SSE events.\nimport type {\n ClaudeStreamEventData,\n ClaudeStreamState,\n} from \"~/claude/types\"\nimport {\n createClaudeToolNameMapper,\n type ClaudeToolNameMapper,\n} from \"~/claude/tool-names\"\nimport { mapOpenAIStopReasonToClaude } from \"~/claude/utils\"\nimport type { ChatCompletionChunk } from \"~/copilot/types\"\nimport { normalizeClaudeModelId } from \"~/lib/models\"\n\nfunction isToolBlockOpen(state: ClaudeStreamState): boolean {\n if (!state.contentBlockOpen) {\n return false\n }\n\n return Object.values(state.toolCalls).some(\n (tc) => tc.claudeBlockIndex === state.contentBlockIndex,\n )\n}\n\nconst closeOpenContentBlock = (\n events: Array<ClaudeStreamEventData>,\n state: ClaudeStreamState,\n): void => {\n if (!state.contentBlockOpen) {\n return\n }\n\n events.push({\n type: \"content_block_stop\",\n index: state.contentBlockIndex,\n })\n state.contentBlockIndex++\n state.contentBlockOpen = false\n state.thinkingBlockOpen = false\n}\n\nexport function translateChunkToClaudeEvents(\n chunk: ChatCompletionChunk,\n state: ClaudeStreamState,\n toolNameMapper: ClaudeToolNameMapper = createClaudeToolNameMapper(\n undefined,\n ),\n): Array<ClaudeStreamEventData> {\n // This is a stateful adapter from Copilot's OpenAI-style deltas to Claude's\n // stricter SSE protocol. Claude requires every thinking/text/tool block to\n // be explicitly opened, filled with deltas, and then closed in order.\n const events: Array<ClaudeStreamEventData> = []\n\n if (chunk.choices.length === 0) {\n return events\n }\n\n const choice = chunk.choices[0]\n const { delta } = choice\n const reasoningContent = delta.reasoning_text ?? delta.reasoning_content\n\n if (!state.messageStartSent) {\n events.push({\n type: \"message_start\",\n message: {\n id: chunk.id,\n type: \"message\",\n role: \"assistant\",\n content: [],\n model: normalizeClaudeModelId(chunk.model),\n stop_reason: null,\n stop_sequence: null,\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: 0,\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n })\n state.messageStartSent = true\n }\n\n if (reasoningContent) {\n // Claude allows only one open content block at a time. Switching from text\n // or tool output into thinking must close the previous block first.\n if (state.contentBlockOpen && !state.thinkingBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n if (!state.contentBlockOpen) {\n events.push({\n type: \"content_block_start\",\n index: state.contentBlockIndex,\n content_block: {\n type: \"thinking\",\n thinking: \"\",\n },\n })\n state.contentBlockOpen = true\n state.thinkingBlockOpen = true\n }\n\n events.push({\n type: \"content_block_delta\",\n index: state.contentBlockIndex,\n delta: {\n type: \"thinking_delta\",\n thinking: reasoningContent,\n },\n })\n }\n\n if (delta.content) {\n // Text deltas cannot be appended to an open thinking or tool_use block, so\n // close whichever block is active before starting/resuming text output.\n if (state.thinkingBlockOpen || isToolBlockOpen(state)) {\n closeOpenContentBlock(events, state)\n }\n\n if (!state.contentBlockOpen) {\n events.push({\n type: \"content_block_start\",\n index: state.contentBlockIndex,\n content_block: {\n type: \"text\",\n text: \"\",\n },\n })\n state.contentBlockOpen = true\n }\n\n events.push({\n type: \"content_block_delta\",\n index: state.contentBlockIndex,\n delta: {\n type: \"text_delta\",\n text: delta.content,\n },\n })\n }\n\n if (delta.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n if (toolCall.id && toolCall.function?.name) {\n // The first tool delta opens a Claude tool_use block; later argument\n // deltas are routed back to this block by Copilot's tool call index.\n if (state.contentBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n const toolName = toolNameMapper.toClaude(toolCall.function.name)\n const claudeBlockIndex = state.contentBlockIndex\n state.toolCalls[toolCall.index] = {\n id: toolCall.id,\n name: toolName,\n claudeBlockIndex,\n }\n\n events.push({\n type: \"content_block_start\",\n index: claudeBlockIndex,\n content_block: {\n type: \"tool_use\",\n id: toolCall.id,\n name: toolName,\n input: {},\n },\n })\n state.contentBlockOpen = true\n }\n\n if (toolCall.function?.arguments) {\n const toolCallInfo = state.toolCalls[toolCall.index]\n if (toolCallInfo) {\n events.push({\n type: \"content_block_delta\",\n index: toolCallInfo.claudeBlockIndex,\n delta: {\n type: \"input_json_delta\",\n partial_json: toolCall.function.arguments,\n },\n })\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n if (state.contentBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n events.push(\n {\n type: \"message_delta\",\n delta: {\n stop_reason: mapOpenAIStopReasonToClaude(choice.finish_reason),\n stop_sequence: null,\n },\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: chunk.usage?.completion_tokens ?? 0,\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n {\n type: \"message_stop\",\n },\n )\n }\n\n return events\n}\n\nexport function translateErrorToClaudeErrorEvent(): ClaudeStreamEventData {\n return {\n type: \"error\",\n error: {\n type: \"api_error\",\n message: \"An unexpected error occurred during streaming.\",\n },\n }\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type {\n ClaudeAssistantContentBlock,\n ClaudeMessage,\n ClaudeMessagesPayload,\n ClaudeResponse,\n ClaudeTextBlock,\n ClaudeTool,\n ClaudeWebSearchResultBlock,\n} from \"~/claude/types\"\nimport type { ClaudeToolNameMapper } from \"~/claude/tool-names\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n readCopilotText,\n} from \"~/copilot/client\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n Message,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\nimport type { ProxyConfig } from \"~/lib/config\"\nimport {\n getModelRouting,\n normalizeClaudeModelId,\n normalizeCopilotModelId,\n} from \"~/lib/models\"\n\nconst anthropicWebSearchToolPattern = /^web_search_\\d{8}$/\nconst claudeCodeWebSearchToolName = \"WebSearch\"\nconst searchResultLimit = 8\n\nconst webSearchInputSchema = {\n type: \"object\",\n properties: {\n query: {\n type: \"string\",\n description: \"The web search query.\",\n },\n },\n required: [\"query\"],\n additionalProperties: false,\n}\n\ninterface ResponsesWebSearchResponse {\n id: string\n created_at: number\n model: string\n output?: Array<ResponsesOutputItem>\n usage?: {\n input_tokens?: number\n output_tokens?: number\n }\n}\n\ntype ResponsesOutputItem =\n | {\n type: \"web_search_call\"\n action?: {\n query?: string\n queries?: Array<string>\n }\n }\n | {\n type: \"message\"\n content?: Array<{\n type?: string\n text?: string\n }>\n }\n | Record<string, unknown>\n\nexport interface WebSearchResult {\n title: string\n url: string\n}\n\nexport interface WebSearchExecutionResult {\n id: string\n inputTokens: number\n model: string\n outputTokens: number\n query: string\n results: Array<WebSearchResult>\n text: string\n}\n\nexport interface ClaudeWebSearchToolCall {\n query: string\n toolCall: ToolCall\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n\nconst isAnthropicNativeWebSearchTool = (tool: ClaudeTool): boolean =>\n tool.name === \"web_search\"\n && typeof tool.type === \"string\"\n && anthropicWebSearchToolPattern.test(tool.type)\n\nconst isClaudeCodeWebSearchTool = (tool: ClaudeTool): boolean =>\n tool.name === claudeCodeWebSearchToolName\n\nexport const isClaudeWebSearchTool = (tool: ClaudeTool): boolean =>\n isAnthropicNativeWebSearchTool(tool) || isClaudeCodeWebSearchTool(tool)\n\nexport const hasClaudeWebSearch = (payload: ClaudeMessagesPayload): boolean =>\n payload.tools?.some(isClaudeWebSearchTool) ?? false\n\nexport const isClaudeWebSearchToolName = (name: string): boolean =>\n name === \"web_search\" || name === claudeCodeWebSearchToolName\n\nexport const getWebSearchBackendModel = (config: ProxyConfig): string =>\n normalizeCopilotModelId(\n config.webSearchBackend?.trim() || getModelRouting().gptModel,\n )\n\nexport const prepareClaudeWebSearchDecisionPayload = (\n payload: ClaudeMessagesPayload,\n): ClaudeMessagesPayload => {\n if (!hasClaudeWebSearch(payload)) {\n return payload\n }\n\n return {\n ...payload,\n tools: payload.tools?.map((tool) =>\n isClaudeWebSearchTool(tool) ?\n {\n ...tool,\n input_schema: tool.input_schema ?? webSearchInputSchema,\n }\n : tool,\n ),\n }\n}\n\nconst textFromMessageContent = (content: ClaudeMessage[\"content\"]): string => {\n if (typeof content === \"string\") {\n return content\n }\n\n return content\n .flatMap((block) => {\n if (block.type === \"text\") return [block.text]\n if (block.type === \"tool_result\") return [block.content]\n return []\n })\n .join(\"\\n\\n\")\n}\n\nconst getRequestedQuery = (payload: ClaudeMessagesPayload): string => {\n const lastUserMessage = [...payload.messages]\n .reverse()\n .find((message) => message.role === \"user\")\n const rawText = lastUserMessage ? textFromMessageContent(lastUserMessage.content) : \"\"\n const cleaned = rawText\n .replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n const searchMatch = cleaned.match(/\\bsearch(?:\\s+the\\s+web)?(?:\\s+for)?\\s+(.+)$/i)\n return searchMatch?.[1]?.trim() || cleaned || \"web search\"\n}\n\nconst getQueryFromToolArguments = (value: string): string | undefined => {\n const trimmed = value.trim()\n if (!trimmed) {\n return undefined\n }\n\n try {\n const parsed = JSON.parse(trimmed) as unknown\n if (\n isRecord(parsed)\n && typeof parsed.query === \"string\"\n && parsed.query.trim()\n ) {\n return parsed.query.trim()\n }\n } catch {\n return trimmed\n }\n\n return trimmed\n}\n\nexport const getClaudeWebSearchToolCallFromChatResponse = (\n response: ChatCompletionResponse,\n toolNameMapper: ClaudeToolNameMapper,\n): ClaudeWebSearchToolCall | undefined => {\n const toolCall = response.choices\n .flatMap((choice) => choice.message.tool_calls ?? [])\n .find((call) =>\n isClaudeWebSearchToolName(\n toolNameMapper.toClaude(call.function.name),\n ),\n )\n\n if (!toolCall) {\n return undefined\n }\n\n const query = getQueryFromToolArguments(toolCall.function.arguments)\n return query ? { query, toolCall } : undefined\n}\n\nconst buildSearchInput = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n): string => {\n const systemText =\n typeof payload.system === \"string\" ? payload.system\n : Array.isArray(payload.system) ?\n payload.system.map((block: ClaudeTextBlock) => block.text).join(\"\\n\\n\")\n : \"\"\n const messages = payload.messages\n .map((message) => `${message.role}: ${textFromMessageContent(message.content)}`)\n .join(\"\\n\\n\")\n\n return [\n \"You are fulfilling an Anthropic web_search server tool request for Claude Code.\",\n \"Search the web using the provided web_search_preview tool.\",\n `Requested web search query: ${requestedQuery}`,\n \"Return useful search results as plain text lines in this exact shape:\",\n \"1. Title - https://example.com/page\",\n \"Include only real source URLs from the search results.\",\n systemText ? `System context:\\n${systemText}` : \"\",\n `Conversation:\\n${messages}`,\n ]\n .filter(Boolean)\n .join(\"\\n\\n\")\n}\n\nconst buildWebSearchRequestPayload = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n model: string,\n) => ({\n model,\n input: buildSearchInput(payload, requestedQuery),\n tools: [{ type: \"web_search_preview\" }],\n max_output_tokens: Math.max(256, Math.min(payload.max_tokens ?? 1024, 1200)),\n temperature: payload.temperature,\n top_p: payload.top_p,\n})\n\nconst getSearchQuery = (\n response: ResponsesWebSearchResponse,\n requestedQuery: string,\n): string => {\n for (const item of response.output ?? []) {\n if (item.type !== \"web_search_call\") continue\n const action = isRecord(item.action) ? item.action : undefined\n const queries = Array.isArray(action?.queries) ? action.queries : []\n const query =\n typeof action?.query === \"string\" ? action.query\n : typeof queries[0] === \"string\" ? queries[0]\n : undefined\n if (query) return query\n }\n\n return requestedQuery.slice(0, 200)\n}\n\nconst getResponseText = (response: ResponsesWebSearchResponse): string =>\n (response.output ?? [])\n .flatMap((item) => {\n if (item.type !== \"message\") return []\n const content = Array.isArray(item.content) ? item.content : []\n return content.flatMap((part) =>\n isRecord(part)\n && part.type === \"output_text\"\n && typeof part.text === \"string\" ?\n [part.text]\n : [],\n )\n })\n .join(\"\\n\")\n .trim()\n\nconst cleanTitle = (line: string, url: string): string => {\n const beforeUrl = line.slice(0, line.indexOf(url))\n const cleaned = beforeUrl\n .replace(/^\\s*(?:[-*]|\\d+[.)])\\s*/, \"\")\n .replace(/\\s*(?:[-:|])\\s*$/, \"\")\n .trim()\n return cleaned || new URL(url).hostname\n}\n\nconst parseSearchResults = (text: string): Array<WebSearchResult> => {\n const results: Array<WebSearchResult> = []\n const seenUrls = new Set<string>()\n\n for (const line of text.split(/\\r?\\n/)) {\n const markdownMatch = line.match(/\\[([^\\]]+)]\\((https?:\\/\\/[^)\\s]+)\\)/)\n const url = markdownMatch?.[2] ?? line.match(/https?:\\/\\/[^\\s)]+/)?.[0]\n if (!url || seenUrls.has(url)) {\n continue\n }\n\n seenUrls.add(url)\n results.push({\n title: markdownMatch?.[1]?.trim() || cleanTitle(line, url),\n url,\n })\n\n if (results.length >= searchResultLimit) {\n break\n }\n }\n\n return results\n}\n\nconst createFailedSearchExecution = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n model: string,\n message: string,\n): WebSearchExecutionResult => ({\n id: `msg_${randomUUID().replaceAll(\"-\", \"\")}`,\n inputTokens: 0,\n model,\n outputTokens: 0,\n query: requestedQuery || getRequestedQuery(payload),\n results: [],\n text: message,\n})\n\nexport const createClaudeWebSearchExecution = async (\n config: ProxyConfig,\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n options: { requestId?: string; signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<WebSearchExecutionResult> => {\n const backendModel = getWebSearchBackendModel(config)\n const signal = createCopilotRequestSignal(options.signal, options.timeoutMs)\n const response = await fetchCopilot(\n getCopilotProviderContext(config),\n \"/responses\",\n {\n method: \"POST\",\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(\n buildWebSearchRequestPayload(payload, requestedQuery, backendModel),\n ),\n },\n {\n initiator: \"agent\",\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n const detail = await readCopilotText(\n response,\n signal,\n options.timeoutMs,\n ).catch(() => \"\")\n return createFailedSearchExecution(\n payload,\n requestedQuery,\n backendModel,\n [\n `Copilot web search is not available for model ${backendModel}.`,\n detail ? `Upstream response: ${detail}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n )\n }\n\n const upstream = await readCopilotJson<ResponsesWebSearchResponse>(\n response,\n signal,\n options.timeoutMs,\n )\n const text = getResponseText(upstream)\n const query = getSearchQuery(upstream, requestedQuery)\n const results = parseSearchResults(text)\n\n if (results.length === 0 && !text.trim()) {\n return createFailedSearchExecution(\n payload,\n requestedQuery,\n upstream.model,\n \"Copilot web search did not return search results.\",\n )\n }\n\n return {\n id: upstream.id,\n inputTokens: upstream.usage?.input_tokens ?? 0,\n model: upstream.model,\n outputTokens: upstream.usage?.output_tokens ?? 0,\n query,\n results,\n text,\n }\n}\n\nconst buildSearchResultBlock = (\n toolUseId: string,\n results: Array<WebSearchResult>,\n): ClaudeWebSearchResultBlock => ({\n type: \"web_search_tool_result\",\n tool_use_id: toolUseId,\n content:\n results.length > 0 ?\n results.map((result) => ({\n type: \"web_search_result\" as const,\n title: result.title,\n url: result.url,\n encrypted_content: \"\",\n page_age: null,\n }))\n : {\n type: \"web_search_tool_result_error\",\n error_code: \"unavailable\",\n },\n})\n\nexport const createClaudeWebSearchResponse = (\n search: WebSearchExecutionResult,\n): ClaudeResponse => {\n const toolUseId = `srvtoolu_${randomUUID().replaceAll(\"-\", \"\")}`\n const content: Array<ClaudeAssistantContentBlock> = [\n {\n type: \"server_tool_use\",\n id: toolUseId,\n name: \"web_search\",\n input: { query: search.query },\n },\n buildSearchResultBlock(toolUseId, search.results),\n ]\n\n if (search.text) {\n content.push({ type: \"text\", text: search.text })\n }\n\n return {\n id: search.id,\n type: \"message\",\n role: \"assistant\",\n content,\n model: normalizeClaudeModelId(search.model),\n stop_reason: \"end_turn\",\n stop_sequence: null,\n usage: {\n input_tokens: search.inputTokens,\n output_tokens: search.outputTokens,\n server_tool_use: { web_search_requests: 1 },\n },\n }\n}\n\nconst getWebSearchResultText = (search: WebSearchExecutionResult): string => {\n if (search.results.length === 0) {\n return search.text || \"Web search did not return search results.\"\n }\n\n return [\n `Web search results for query: \"${search.query}\"`,\n \"\",\n ...search.results.map((result, index) =>\n `${index + 1}. ${result.title} - ${result.url}`,\n ),\n ].join(\"\\n\")\n}\n\n// Delivered as a user turn, not a system turn, for two reasons. Copilot's\n// Claude-family models reject a conversation that does not end with a user\n// message (\"This model does not support assistant message prefill\"), and this\n// message is appended last. Anthropic removed prefill support in Opus 4.7+ /\n// Sonnet 4.6+, so this is an upstream constraint rather than a Copilot quirk.\nconst createWebSearchResultContextMessage = (\n search: WebSearchExecutionResult,\n): Message => ({\n role: \"user\",\n content: [\n \"Trusted bridge retrieval context: the assistant selected web_search, and copilot-relay executed it.\",\n \"Use this context to complete the request. Do not describe it as user-provided or injected.\",\n \"If the user requested a specific output format, answer using only matching information from this context.\",\n \"If the user asked for a URL only, output only that URL with no surrounding text.\",\n \"\",\n `Query: ${search.query}`,\n \"\",\n getWebSearchResultText(search),\n ].join(\"\\n\"),\n})\n\nconst isWebSearchTool = (\n tool: Tool,\n toolNameMapper: ClaudeToolNameMapper,\n): boolean =>\n isClaudeWebSearchToolName(toolNameMapper.toClaude(tool.function.name))\n\n// The search already ran, so re-advertising it would let the model select it a\n// second time. The final response is never re-checked for a web-search call, so\n// that selection would reach Claude Code as a client tool_use named WebSearch\n// instead of a server_tool_use block. Removing the tool makes that structurally\n// impossible instead of relying on a prompt instruction.\nconst removeWebSearchTool = (\n tools: ChatCompletionsPayload[\"tools\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tools\"] => {\n if (!tools || tools.length === 0) {\n return undefined\n }\n\n const remaining = tools.filter((tool) => !isWebSearchTool(tool, toolNameMapper))\n // An empty array is not the same as an absent field upstream.\n return remaining.length > 0 ? remaining : undefined\n}\n\nconst normalizeFinalToolChoice = (\n toolChoice: ChatCompletionsPayload[\"tool_choice\"],\n tools: ChatCompletionsPayload[\"tools\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tool_choice\"] => {\n if (!tools || tools.length === 0) {\n return undefined\n }\n\n // \"required\" would force a tool call on a pass whose job is to answer.\n if (toolChoice === \"required\") {\n return \"auto\"\n }\n\n // A choice pinned to the now-removed search tool would be unsatisfiable.\n if (\n toolChoice\n && typeof toolChoice === \"object\"\n && isClaudeWebSearchToolName(\n toolNameMapper.toClaude(toolChoice.function.name),\n )\n ) {\n return \"auto\"\n }\n\n return toolChoice\n}\n\nexport const createFinalWebSearchPayload = (\n payload: ChatCompletionsPayload,\n search: WebSearchExecutionResult,\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload => {\n const tools = removeWebSearchTool(payload.tools, toolNameMapper)\n\n // payload.messages is passed through untouched. An earlier version rewrote\n // tool messages to developer messages and stripped assistant tool_calls,\n // because removing every tool definition would have orphaned those\n // references and produced a 400. The definitions now survive, so the rewrite\n // is unnecessary — and keeping the history byte-identical preserves the\n // prompt-cache prefix shared with the decision pass.\n return {\n ...payload,\n stream: false,\n tools,\n tool_choice: normalizeFinalToolChoice(\n payload.tool_choice,\n tools,\n toolNameMapper,\n ),\n messages: [\n ...payload.messages,\n createWebSearchResultContextMessage(search),\n ],\n }\n}\n\nexport const mergeWebSearchAndFinalResponse = (\n searchResponse: ClaudeResponse,\n finalResponse: ClaudeResponse,\n): ClaudeResponse => ({\n ...finalResponse,\n content: [...searchResponse.content.slice(0, 2), ...finalResponse.content],\n usage: {\n ...finalResponse.usage,\n input_tokens:\n searchResponse.usage.input_tokens + finalResponse.usage.input_tokens,\n output_tokens:\n searchResponse.usage.output_tokens + finalResponse.usage.output_tokens,\n server_tool_use: { web_search_requests: 1 },\n },\n})\n","// Streaming support for turns that advertise Claude's WebSearch tool.\n//\n// The relay has to know whether the model selected web_search before it can\n// decide how to answer: a search turn needs a bridge-managed search plus a\n// second recompose pass, while every other turn is an ordinary completion. That\n// used to be resolved by forcing `stream: false` on any request that merely\n// *advertised* WebSearch. Claude Code advertises it by default, so most turns\n// paid a full non-streaming completion and then replayed it as synthetic SSE.\n//\n// The decision does not actually need the whole response — only enough of it to\n// rule a web-search call in or out. A chat completion emits tool call names in\n// the first delta that opens each tool call, so the question is settled the\n// moment the first tool call appears, or as soon as content arrives without one.\n// This module streams the decision pass and buffers only up to that point:\n//\n// no search -> replay the buffered chunks, then stream the rest live\n// search -> accumulate the full response and hand it to the bridge path\n//\n// A search turn therefore behaves exactly as it did before, and every other turn\n// gets real streaming back.\nimport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ToolCall,\n} from \"~/copilot/types\"\nimport type { ClaudeToolNameMapper } from \"~/claude/tool-names\"\n\nexport type WebSearchStreamDecision =\n | { kind: \"streamed\"; buffered: Array<ChatCompletionChunk> }\n | { kind: \"webSearch\"; response: ChatCompletionResponse }\nconst isWebSearchName = (\n name: string | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n): boolean => !!name && isWebSearchToolName(toolNameMapper.toClaude(name))\n\n// Only a named tool call or a finish_reason settles the question.\n//\n// Text does NOT settle it. Copilot routinely emits a preamble — \"I'll search\n// for that now.\" — before the tool call, so treating content as proof that no\n// search is coming lets the later web_search call escape unintercepted and\n// reach the client as a client tool_use named WebSearch. That is a malformed\n// turn: Claude Code would try to run a tool it expects the server to execute.\n//\n// Text is still streamed live while the question is open (see onChunk), so\n// waiting costs no perceived latency.\nconst chunkSettlesDecision = (chunk: ChatCompletionChunk): boolean => {\n const choice = chunk.choices[0]\n if (!choice) {\n return false\n }\n\n return (\n choice.delta?.tool_calls?.some((call) => call.function?.name) === true\n || !!choice.finish_reason\n )\n}\n\nconst chunkHasWebSearchCall = (\n chunk: ChatCompletionChunk,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n): boolean =>\n chunk.choices[0]?.delta?.tool_calls?.some((call) =>\n isWebSearchName(call.function?.name, toolNameMapper, isWebSearchToolName),\n ) ?? false\n\n// Rebuilds the non-streaming shape the bridge path expects. Only reached on\n// turns that actually selected web_search, so the cost is paid by the requests\n// that were already paying it.\nexport const accumulateChunks = (\n chunks: Array<ChatCompletionChunk>,\n): ChatCompletionResponse => {\n const first = chunks[0]\n const toolCalls: Array<ToolCall> = []\n let content = \"\"\n let reasoning = \"\"\n let finishReason: ChatCompletionResponse[\"choices\"][number][\"finish_reason\"] =\n null\n let usage: ChatCompletionResponse[\"usage\"]\n\n for (const chunk of chunks) {\n if (chunk.usage) {\n usage = chunk.usage\n }\n\n const choice = chunk.choices[0]\n if (!choice) {\n continue\n }\n\n if (typeof choice.delta?.content === \"string\") {\n content += choice.delta.content\n }\n\n const chunkReasoning =\n choice.delta?.reasoning_text ?? choice.delta?.reasoning_content\n if (typeof chunkReasoning === \"string\") {\n reasoning += chunkReasoning\n }\n\n if (choice.finish_reason) {\n finishReason = choice.finish_reason\n }\n\n for (const call of choice.delta?.tool_calls ?? []) {\n // Copilot indexes tool calls; arguments arrive as fragments across chunks.\n const existing = toolCalls[call.index]\n if (existing) {\n existing.function.arguments += call.function?.arguments ?? \"\"\n continue\n }\n\n toolCalls[call.index] = {\n id: call.id ?? `call_${call.index}`,\n type: \"function\",\n function: {\n name: call.function?.name ?? \"\",\n arguments: call.function?.arguments ?? \"\",\n },\n }\n }\n }\n\n const collectedToolCalls = toolCalls.filter(Boolean)\n\n return {\n id: first?.id ?? \"chat_stream_accumulated\",\n object: \"chat.completion\",\n created: first?.created ?? 0,\n model: first?.model ?? \"\",\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: content || null,\n ...(reasoning ? { reasoning_text: reasoning } : {}),\n ...(collectedToolCalls.length > 0 ?\n { tool_calls: collectedToolCalls }\n : {}),\n },\n finish_reason: finishReason ?? \"stop\",\n },\n ],\n ...(usage ? { usage } : {}),\n }\n}\n\n// Reads the decision pass only as far as needed to classify the turn.\n//\n// onChunk receives every chunk consumed while the question is still open, so a\n// caller can stream them live. Turns that never search — the common case, since\n// Claude Code advertises WebSearch on every turn — are then indistinguishable\n// from an ordinary stream. When a search does appear, `alreadyStreamed` tells\n// the caller that a message_start and some content blocks are already on the\n// wire, so the search blocks must continue that message rather than start a new\n// one. The resulting order is text -> server_tool_use -> web_search_tool_result\n// -> text, which is Anthropic's documented native shape for a search turn.\nexport const resolveWebSearchStreamDecision = async (\n stream: AsyncIterable<{ data?: string }>,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n onChunk?: (chunk: ChatCompletionChunk) => Promise<void>,\n): Promise<{\n decision: WebSearchStreamDecision\n rest: AsyncIterable<{ data?: string }>\n alreadyStreamed: boolean\n}> => {\n const buffered: Array<ChatCompletionChunk> = []\n const iterator = stream[Symbol.asyncIterator]()\n let sawWebSearch = false\n let settled = false\n let done = false\n let alreadyStreamed = false\n\n while (!settled) {\n const next = await iterator.next()\n if (next.done) {\n done = true\n break\n }\n\n const raw = next.value\n if (raw.data === \"[DONE]\") {\n done = true\n break\n }\n if (!raw.data) {\n continue\n }\n\n let chunk: ChatCompletionChunk\n try {\n chunk = JSON.parse(raw.data) as ChatCompletionChunk\n } catch {\n continue\n }\n\n buffered.push(chunk)\n\n if (chunkHasWebSearchCall(chunk, toolNameMapper, isWebSearchToolName)) {\n sawWebSearch = true\n settled = true\n break\n }\n\n // Not a search call, so this chunk belongs to the visible answer either way.\n // Emitting it now is what keeps non-search turns streaming in real time.\n if (onChunk) {\n await onChunk(chunk)\n alreadyStreamed = true\n }\n\n if (chunkSettlesDecision(chunk)) {\n settled = true\n }\n }\n\n // The remainder of the upstream stream, if the decision was reached early.\n const rest: AsyncIterable<{ data?: string }> = {\n async *[Symbol.asyncIterator]() {\n if (done) {\n return\n }\n while (true) {\n const next = await iterator.next()\n if (next.done) {\n return\n }\n yield next.value\n }\n },\n }\n\n if (!sawWebSearch) {\n // Anything handed to onChunk is already on the wire; replaying it would\n // duplicate content. Only chunks consumed without being emitted are\n // returned for the caller to flush.\n return {\n decision: { kind: \"streamed\", buffered: onChunk ? [] : buffered },\n rest,\n alreadyStreamed,\n }\n }\n\n // A web-search turn needs the whole response, so drain what is left.\n for await (const raw of rest) {\n if (raw.data === \"[DONE]\") {\n break\n }\n if (!raw.data) {\n continue\n }\n try {\n buffered.push(JSON.parse(raw.data) as ChatCompletionChunk)\n } catch {\n continue\n }\n }\n\n return {\n decision: { kind: \"webSearch\", response: accumulateChunks(buffered) },\n rest: { async *[Symbol.asyncIterator]() {} },\n alreadyStreamed,\n }\n}\n","// Token estimator for Claude Code's /v1/messages/count_tokens endpoint.\nimport type {\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\n\nexport interface TokenizerModel {\n capabilities: {\n tokenizer: string\n }\n id: string\n}\n\nconst encodingMap = {\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 encodingMap\n\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\nconst encodingCache = new Map<string, Encoder>()\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\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 // Claude Code needs a fast local estimate, not exact upstream billing.\n // Use a fixed image overhead plus URL token count to avoid severe\n // under-counting when screenshots are present.\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\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n // Mirrors OpenAI chat formatting heuristics closely enough for UI budgeting;\n // /count_tokens is advisory and should not call real Copilot services.\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\") tokens += tokensPerName\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\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) return 0\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n numTokens += 3\n return numTokens\n}\n\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) return cached\n }\n\n const supported = encoding as SupportedEncoding\n if (!(supported in encodingMap)) {\n const fallback = (await encodingMap.o200k_base()) as Encoder\n encodingCache.set(encoding, fallback)\n return fallback\n }\n\n const mod = (await encodingMap[supported]()) as Encoder\n encodingCache.set(encoding, mod)\n return mod\n}\n\nexport const getTokenizerFromModel = (model: TokenizerModel): string =>\n model.capabilities.tokenizer || \"o200k_base\"\n\nconst getModelConstants = (model: TokenizerModel) =>\n // Tool/function overhead differs by tokenizer family. Keep these constants\n // small and explicit so count_tokens remains deterministic in tests.\n 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\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 if (typeof prop !== \"object\" || prop === null) return tokens\n\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 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 if (paramDesc.endsWith(\".\")) paramDesc = paramDesc.slice(0, -1)\n\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (excludedKeys.has(propertyName)) continue\n const propertyValue = param[propertyName]\n const propertyText =\n typeof propertyValue === \"string\" ? propertyValue : JSON.stringify(propertyValue)\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n\n return tokens\n}\n\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") return 0\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\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(\".\")) fDesc = fDesc.slice(0, -1)\n tokens += encoder.encode(`${fName}:${fDesc}`).length\n if (typeof func.parameters === \"object\" && func.parameters !== null) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\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\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: TokenizerModel,\n): Promise<{ input: number; output: number }> => {\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const inputMessages = payload.messages.filter((msg) => msg.role !== \"assistant\")\n const outputMessages = payload.messages.filter((msg) => msg.role === \"assistant\")\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 { input: inputTokens, output: outputTokens }\n}\n","// Claude Code route surface: /v1/messages, /v1/messages/count_tokens, and /v1/models.\nimport { Hono } from \"hono\"\nimport { streamSSE } from \"hono/streaming\"\n\nimport {\n type ClaudeAssistantContentBlock,\n type ClaudeMessagesPayload,\n type ClaudeResponse,\n type ClaudeStreamEventData,\n type ClaudeStreamState,\n} from \"~/claude/types\"\nimport {\n translateModelName,\n translateToClaude,\n translateToOpenAI,\n} from \"~/claude/translate\"\nimport {\n translateChunkToClaudeEvents,\n translateErrorToClaudeErrorEvent,\n} from \"~/claude/stream\"\nimport {\n createClaudeToolNameMapper,\n getToolNameMapperOptionsForModel,\n} from \"~/claude/tool-names\"\nimport {\n createClaudeWebSearchExecution,\n createClaudeWebSearchResponse,\n createFinalWebSearchPayload,\n getClaudeWebSearchToolCallFromChatResponse,\n hasClaudeWebSearch,\n isClaudeWebSearchToolName,\n mergeWebSearchAndFinalResponse,\n prepareClaudeWebSearchDecisionPayload,\n} from \"~/claude/web-search\"\nimport { resolveWebSearchStreamDecision } from \"~/claude/web-search-stream\"\nimport type { ProxyEnv } from \"~/lib/config\"\nimport { HTTPError, ProxyNotImplementedError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { getExposedModelIds } from \"~/lib/models\"\nimport { getTokenCount, type TokenizerModel } from \"~/lib/tokenizer\"\nimport type { ChatCompletionChunk, ChatCompletionResponse } from \"~/copilot/types\"\nimport { createChatCompletions } from \"~/copilot/chat\"\nimport { createCopilotRequestSignal } from \"~/copilot/client\"\n\nexport const claudeRoutes = new Hono<ProxyEnv>()\n\nconst createTokenCountModel = (modelId: string): TokenizerModel => ({\n capabilities: { tokenizer: \"o200k_base\" },\n id: modelId,\n})\n\nconst isNonStreamingResponse = (\n response: Awaited<ReturnType<typeof createChatCompletions>>,\n): response is ChatCompletionResponse =>\n typeof response === \"object\"\n && response !== null\n && Object.hasOwn(response, \"choices\")\n\nconst getClaudeRequestedThinkEffort = (\n payload: ClaudeMessagesPayload,\n): string => payload.reasoning_effort ?? \"none\"\n\nconst getClaudeRequestedThinking = (\n payload: ClaudeMessagesPayload,\n): string => {\n if (!payload.thinking) {\n return \"none\"\n }\n\n return [\n `type:${payload.thinking.type}`,\n `budget:${payload.thinking.budget_tokens ?? \"none\"}`,\n ].join(\",\")\n}\n\nconst eventsFromClaudeResponse = (\n response: ClaudeResponse,\n): Array<ClaudeStreamEventData> => {\n // When Claude Code asks for streaming but Copilot returned a completed JSON\n // response, synthesize the minimal Claude SSE sequence so client bookkeeping\n // remains identical to a real streaming response.\n const events: Array<ClaudeStreamEventData> = [\n {\n type: \"message_start\",\n message: {\n id: response.id,\n type: response.type,\n role: response.role,\n content: [],\n model: response.model,\n stop_reason: null,\n stop_sequence: null,\n usage: response.usage,\n },\n },\n ]\n\n response.content.forEach((block: ClaudeAssistantContentBlock, index) => {\n events.push({\n type: \"content_block_start\",\n index,\n content_block: block,\n })\n\n if (block.type === \"text\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"text_delta\", text: block.text },\n })\n } else if (block.type === \"thinking\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"thinking_delta\", thinking: block.thinking },\n })\n } else if (block.type === \"tool_use\" || block.type === \"server_tool_use\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: {\n type: \"input_json_delta\",\n partial_json: JSON.stringify(block.input),\n },\n })\n }\n\n events.push({ type: \"content_block_stop\", index })\n })\n\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: response.stop_reason,\n stop_sequence: response.stop_sequence,\n },\n usage: { output_tokens: response.usage.output_tokens },\n })\n events.push({ type: \"message_stop\" })\n return events\n}\n\n// Emits a completed response into a stream that is already open.\n//\n// Used when the web-search decision pass streamed a preamble before the search\n// was detected: message_start has gone out and some block indices are spent, so\n// the search blocks have to continue that message. Mirrors\n// eventsFromClaudeResponse minus message_start, with indices taken from the\n// live stream state. The result is the native shape for a search turn —\n// text -> server_tool_use -> web_search_tool_result -> text.\nconst continuationEventsFromClaudeResponse = (\n response: ClaudeResponse,\n state: ClaudeStreamState,\n): Array<ClaudeStreamEventData> => {\n const events: Array<ClaudeStreamEventData> = []\n\n // The preamble block may still be open if the tool call arrived mid-text.\n if (state.contentBlockOpen) {\n events.push({ type: \"content_block_stop\", index: state.contentBlockIndex })\n state.contentBlockIndex += 1\n state.contentBlockOpen = false\n state.thinkingBlockOpen = false\n }\n\n for (const block of response.content) {\n const index = state.contentBlockIndex\n events.push({ type: \"content_block_start\", index, content_block: block })\n\n if (block.type === \"text\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"text_delta\", text: block.text },\n })\n } else if (block.type === \"thinking\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"thinking_delta\", thinking: block.thinking },\n })\n } else if (block.type === \"tool_use\" || block.type === \"server_tool_use\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: {\n type: \"input_json_delta\",\n partial_json: JSON.stringify(block.input),\n },\n })\n }\n\n events.push({ type: \"content_block_stop\", index })\n state.contentBlockIndex += 1\n }\n\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: response.stop_reason,\n stop_sequence: response.stop_sequence,\n },\n usage: { output_tokens: response.usage.output_tokens },\n })\n events.push({ type: \"message_stop\" })\n return events\n}\n\ntype ClaudeStreamEventWriter = (\n event: ClaudeStreamEventData,\n) => Promise<void>\n\nconst createQueuedClaudeStreamWriter = (\n write: ClaudeStreamEventWriter,\n): ClaudeStreamEventWriter => {\n let pending = Promise.resolve()\n\n return (event) => {\n pending = pending.then(() => write(event))\n return pending\n }\n}\n\nconst writeClaudeStreamEvents = async (\n events: Array<ClaudeStreamEventData>,\n writeEvent: ClaudeStreamEventWriter,\n): Promise<void> => {\n for (const event of events) {\n await writeEvent(event)\n }\n}\n\nconst handleClaudeMessageRequest = async (\n config: ProxyEnv[\"Variables\"][\"config\"],\n claudePayload: ClaudeMessagesPayload,\n requestSignal: AbortSignal | undefined,\n requestId: string,\n writeEvent?: ClaudeStreamEventWriter,\n): Promise<ClaudeResponse | undefined> => {\n const upstreamModel = translateModelName(claudePayload.model)\n const shouldLetModelDecideWebSearch = hasClaudeWebSearch(claudePayload)\n const decisionPayload =\n shouldLetModelDecideWebSearch ?\n prepareClaudeWebSearchDecisionPayload(claudePayload)\n : claudePayload\n const toolNameMapper = createClaudeToolNameMapper(decisionPayload.tools, {\n ...getToolNameMapperOptionsForModel(upstreamModel),\n })\n const openAIPayload = translateToOpenAI(\n decisionPayload,\n undefined,\n toolNameMapper,\n )\n // A streaming client that advertises WebSearch used to be forced into a full\n // non-streaming completion, because the relay had to know whether the model\n // selected web_search before it could decide how to answer. Claude Code\n // advertises WebSearch by default, so that penalised most turns for a branch\n // few of them took. The decision pass now streams, and resolveWebSearchStreamDecision\n // buffers only as far as it takes to rule a search call in or out.\n const canStreamWebSearchDecision = shouldLetModelDecideWebSearch && !!writeEvent\n if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) {\n openAIPayload.stream = false\n }\n const response = await createChatCompletions(config, openAIPayload, {\n client: \"claude\",\n requestedModel: claudePayload.model,\n requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),\n requestedThinking: getClaudeRequestedThinking(claudePayload),\n requestId,\n signal: requestSignal,\n timeoutMs: config.upstreamTimeoutMs,\n })\n\n // Streamed decision pass: classify the turn, then either replay the buffered\n // chunks and keep streaming, or hand the accumulated response to the bridge\n // path below exactly as the non-streaming route would have.\n let decidedResponse: ChatCompletionResponse | undefined\n let bufferedChunks: Array<ChatCompletionChunk> = []\n let remainingStream: AsyncIterable<{ data?: string }> | undefined\n // Shared with the streaming tail so block indices stay monotonic across a\n // decision pass that streamed some content before a search was detected.\n const streamState: ClaudeStreamState = {\n messageStartSent: false,\n contentBlockIndex: 0,\n contentBlockOpen: false,\n thinkingBlockOpen: false,\n toolCalls: {},\n }\n let streamedBeforeDecision = false\n\n if (canStreamWebSearchDecision && !isNonStreamingResponse(response)) {\n const { decision, rest, alreadyStreamed } =\n await resolveWebSearchStreamDecision(\n response,\n toolNameMapper,\n isClaudeWebSearchToolName,\n // Emit while classifying so a turn that never searches streams in real\n // time. Copilot often writes a preamble before calling a tool, so the\n // classifier cannot treat text as proof that no search is coming.\n async (chunk) => {\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(chunk, streamState, toolNameMapper),\n writeEvent!,\n )\n },\n )\n streamedBeforeDecision = alreadyStreamed\n if (decision.kind === \"webSearch\") {\n decidedResponse = decision.response\n } else {\n bufferedChunks = decision.buffered\n remainingStream = rest\n }\n }\n\n const effectiveResponse = decidedResponse ?? response\n\n if (isNonStreamingResponse(effectiveResponse)) {\n const webSearchToolCall = shouldLetModelDecideWebSearch ?\n getClaudeWebSearchToolCallFromChatResponse(effectiveResponse, toolNameMapper)\n : undefined\n let claudeResponse: ClaudeResponse\n\n if (webSearchToolCall) {\n const search = await createClaudeWebSearchExecution(\n config,\n claudePayload,\n webSearchToolCall.query,\n { requestId, signal: requestSignal, timeoutMs: config.upstreamTimeoutMs },\n )\n const searchResponse = createClaudeWebSearchResponse(search)\n\n if (search.results.length === 0) {\n claudeResponse = searchResponse\n } else {\n const finalResponse = await createChatCompletions(\n config,\n createFinalWebSearchPayload(openAIPayload, search, toolNameMapper),\n {\n client: \"claude\",\n requestedModel: claudePayload.model,\n requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),\n requestedThinking: getClaudeRequestedThinking(claudePayload),\n requestId,\n signal: requestSignal,\n timeoutMs: config.upstreamTimeoutMs,\n },\n )\n\n if (!isNonStreamingResponse(finalResponse)) {\n throw new HTTPError(\n \"Claude web search final answer request unexpectedly streamed\",\n new Response(\"Claude web search final answer request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n claudeResponse = mergeWebSearchAndFinalResponse(\n searchResponse,\n translateToClaude(finalResponse, toolNameMapper),\n )\n }\n } else {\n claudeResponse = translateToClaude(effectiveResponse, toolNameMapper)\n }\n\n if (writeEvent) {\n await writeClaudeStreamEvents(\n // When the decision pass already streamed a preamble, the message is\n // open and its early block indices are spent. Continue it instead of\n // starting a second one, which would leave the client with two\n // message_start events and colliding indices.\n streamedBeforeDecision ?\n continuationEventsFromClaudeResponse(claudeResponse, streamState)\n : eventsFromClaudeResponse(claudeResponse),\n writeEvent,\n )\n return undefined\n }\n\n return claudeResponse\n }\n\n // Only reachable when the decision pass was forced non-streaming and came back\n // streamed anyway. A streamed decision that found no search falls through to\n // the normal streaming path below with its buffered chunks.\n if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) {\n throw new HTTPError(\n \"Claude web search model-decision request unexpectedly streamed\",\n new Response(\"Claude web search model-decision request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n if (!writeEvent) {\n throw new HTTPError(\n \"Claude non-streaming request unexpectedly streamed\",\n new Response(\"Claude non-streaming request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n // streamState is declared above and shared with the decision pass, so block\n // indices stay monotonic when classification already emitted content.\n\n // Chunks consumed while classifying the turn, replayed in order so the client\n // sees an unbroken stream.\n for (const chunk of bufferedChunks) {\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(chunk, streamState, toolNameMapper),\n writeEvent,\n )\n }\n\n // Past the isNonStreamingResponse guard, so this is the streamed form. Either\n // the remainder left by the decision classifier, or the whole stream when the\n // turn never advertised WebSearch.\n const upstreamStream = remainingStream ?? effectiveResponse\n\n for await (const rawEvent of upstreamStream) {\n if (rawEvent.data === \"[DONE]\") {\n break\n }\n if (!rawEvent.data) {\n continue\n }\n\n let chunk: ChatCompletionChunk\n try {\n chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk\n } catch {\n continue\n }\n\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(\n chunk,\n streamState,\n toolNameMapper,\n ),\n writeEvent,\n )\n }\n\n return undefined\n}\n\nclaudeRoutes.get(\"/models\", (c) =>\n c.json({\n object: \"list\",\n data: getExposedModelIds().map((id) => ({\n id,\n object: \"model\",\n created: 0,\n owned_by: \"github-copilot\",\n })),\n }),\n)\n\nclaudeRoutes.post(\"/messages\", async (c) => {\n const config = c.get(\"config\")\n const requestId = c.get(\"requestId\")\n const claudePayload = await c.req.json<ClaudeMessagesPayload>()\n const requestSignal = createCopilotRequestSignal(\n c.req.raw.signal,\n config.upstreamTimeoutMs,\n )\n log.debug(\"Full Claude request payload\", { payload: claudePayload })\n\n if (claudePayload.stream) {\n return streamSSE(c, async (stream) => {\n const streamStarted = performance.now()\n const writeEvent = createQueuedClaudeStreamWriter((event) =>\n stream.writeSSE({\n event: event.type,\n data: JSON.stringify(event),\n }),\n )\n\n try {\n await handleClaudeMessageRequest(\n config,\n claudePayload,\n requestSignal,\n requestId,\n writeEvent,\n )\n } catch (error) {\n log.error(\"Error during Claude stream request:\", error)\n const errorEvent = translateErrorToClaudeErrorEvent()\n await writeEvent(errorEvent)\n } finally {\n log.info(\n `request_id=${requestId} stream completed ${Math.round(performance.now() - streamStarted)}ms`,\n )\n }\n })\n }\n\n try {\n return c.json(await handleClaudeMessageRequest(\n config,\n claudePayload,\n requestSignal,\n requestId,\n ))\n } catch (error) {\n if (error instanceof ProxyNotImplementedError) {\n c.set(\"requestErrorMessage\", error.message)\n return c.json(\n { error: { type: error.name, message: error.message } },\n 501,\n )\n }\n\n if (error instanceof HTTPError) {\n const text = await error.response.text().catch(() => \"\")\n c.set(\"requestErrorMessage\", error.detail ?? text.slice(0, 240))\n return new Response(text, {\n status: error.response.status,\n headers: {\n \"content-type\":\n error.response.headers.get(\"content-type\") ?? \"application/json\",\n },\n })\n }\n\n throw error\n }\n})\n\nclaudeRoutes.post(\"/messages/count_tokens\", async (c) => {\n try {\n const claudeBeta = c.req.header(\"claude-beta\")\n const claudePayload = await c.req.json<ClaudeMessagesPayload>()\n const openAIPayload = translateToOpenAI(claudePayload)\n const exposedModels = getExposedModelIds()\n const selectedModel = createTokenCountModel(\n openAIPayload.model === exposedModels[1] ? exposedModels[1] : exposedModels[0],\n )\n\n const tokenCount = await getTokenCount(openAIPayload, selectedModel)\n const effectiveModelId = selectedModel.id\n\n // Claude Code already accounts for MCP tool payloads differently. For\n // non-MCP local tools, add a small Claude-family overhead to avoid\n // under-reporting context use in the UI.\n if (claudePayload.tools && claudePayload.tools.length > 0) {\n let mcpToolExist = false\n if (claudeBeta?.startsWith(\"claude-code\")) {\n mcpToolExist = claudePayload.tools.some((tool) =>\n tool.name.startsWith(\"mcp__\"),\n )\n }\n if (!mcpToolExist && effectiveModelId.startsWith(\"claude\")) {\n tokenCount.input += 346\n }\n }\n\n const multiplier = effectiveModelId.startsWith(\"claude\") ? 1.15 : 1\n const finalTokenCount = Math.round(\n (tokenCount.input + tokenCount.output) * multiplier,\n )\n\n return c.json({ input_tokens: Math.max(1, finalTokenCount) })\n } catch (error) {\n log.error(\"Error counting tokens:\", error)\n return c.json({ input_tokens: 1 })\n }\n})\n","// HTTP server assembly: exposes only Claude Code-compatible public routes.\nimport { randomUUID } from \"node:crypto\"\n\nimport { createAdaptorServer, type ServerType } from \"@hono/node-server\"\nimport { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\n\nimport type { ProxyConfig, ProxyEnv } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\nimport { appVersion } from \"~/lib/version\"\nimport { claudeRoutes } from \"~/routes/claude\"\n\nconst loggedRequestHeaders = [\n \"anthropic-beta\",\n \"anthropic-version\",\n \"claude-beta\",\n \"content-type\",\n]\n\nconst readRequestPayloadForLog = async (request: Request): Promise<unknown> => {\n const text = await request.clone().text().catch(() => \"\")\n if (!text) {\n return undefined\n }\n\n try {\n return JSON.parse(text) as unknown\n } catch {\n return text\n }\n}\n\nconst getLoggedHeaders = (request: Request): Record<string, string> =>\n Object.fromEntries(\n loggedRequestHeaders.flatMap((name) => {\n const value = request.headers.get(name)\n return value ? [[name, value]] : []\n }),\n )\n\nconst formatStatusLog = (\n requestId: string,\n method: string,\n path: string,\n status: number,\n ms: number,\n errorMessage: string | undefined,\n): string => {\n const base = `request_id=${requestId} ${method} ${path} -> ${status} ${ms}ms`\n return status >= 400 && errorMessage ? `${base} error=${JSON.stringify(errorMessage)}` : base\n}\n\nexport const createServer = (config: ProxyConfig) => {\n const app = new Hono<ProxyEnv>()\n\n app.use(\"*\", async (c, next) => {\n const requestId = randomUUID()\n c.set(\"config\", config)\n c.set(\"requestId\", requestId)\n c.header(\"x-copilot-relay-request-id\", requestId)\n log.info(`request_id=${requestId} request received method=${c.req.method} path=${c.req.path}`)\n\n const started = performance.now()\n try {\n await next()\n } finally {\n const ms = Math.round(performance.now() - started)\n // Emit exactly one info-level request summary even when downstream route\n // handling throws; deeper diagnostics belong to debug/error logs.\n log.info(formatStatusLog(\n requestId,\n c.req.method,\n c.req.path,\n c.res.status,\n ms,\n c.get(\"requestErrorMessage\"),\n ))\n }\n })\n app.use(\"*\", cors())\n\n app.onError((error, c) => {\n c.set(\"requestErrorMessage\", error.message)\n log.error(\"Unhandled Claude API request error\", {\n method: c.req.method,\n path: c.req.path,\n requestId: c.get(\"requestId\"),\n error,\n })\n return c.json({ error: { message: \"Internal server error\" } }, 500)\n })\n\n app.get(\"/\", (c) =>\n c.json({\n name: \"copilot-relay\",\n status: \"ok\",\n }),\n )\n\n // `version` is the build answering this request, which is what makes it\n // worth serving here: `status` already probes /healthz, so reporting the\n // daemon's own version costs no extra round trip and no new route. See #43.\n // Still static — process-local data, never an upstream call.\n app.get(\"/healthz\", (c) => c.json({ ok: true, version: appVersion }))\n\n // Claude Code probes this before and around real traffic. Three call sites in\n // the CLI: a fire-and-forget HEAD connection warmup that reads\n // ANTHROPIC_BASE_URL and lands here, plus a connectivity diagnostic and a\n // startup preflight that currently read the first-party base URL. The\n // preflight gates on status !== 200 and exits the process on failure, so the\n // cost of answering is a static 200 and the cost of not answering is a client\n // that hard-fails if that call site is ever pointed at a configured base URL.\n //\n // Static by design, like /healthz: it must not contact Copilot upstream. It\n // proves the relay is reachable, nothing more.\n app.on([\"GET\", \"HEAD\"], \"/api/hello\", (c) => c.body(null, 200))\n\n app.route(\"/v1\", claudeRoutes)\n app.notFound(async (c) => {\n const message = \"Unsupported Claude API route\"\n c.set(\"requestErrorMessage\", message)\n log.error(\"Unsupported Claude API request\", {\n method: c.req.method,\n path: c.req.path,\n requestId: c.get(\"requestId\"),\n headers: getLoggedHeaders(c.req.raw),\n payload: await readRequestPayloadForLog(c.req.raw),\n })\n return c.json({ error: { message } }, 500)\n })\n\n return app\n}\n\nexport const startServer = (config: ProxyConfig): Promise<ServerType> =>\n new Promise((resolve, reject) => {\n const server = createAdaptorServer({\n fetch: createServer(config).fetch,\n hostname: config.host,\n port: config.port,\n })\n\n const onError = (error: Error) => {\n server.off(\"listening\", onListening)\n reject(error)\n }\n const onListening = () => {\n server.off(\"error\", onError)\n resolve(server)\n }\n\n server.once(\"error\", onError)\n server.once(\"listening\", onListening)\n server.listen(config.port, config.host)\n })\n","// `copilot-relay start`: loads config, validates upstream access, and starts the local Claude Code proxy.\nimport { type ServerType } from \"@hono/node-server\"\nimport { defineCommand } from \"citty\"\n\nimport { setupProxyAuth } from \"~/lib/auth\"\nimport { readAppConfig, watchAppConfig, type AppConfig } from \"~/lib/app-config\"\nimport { applyClaudeConfig } from \"~/lib/claude-settings\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { claudeConfigPath as defaultClaudeConfigPath } from \"~/lib/defaults\"\nimport { clearRelayPidFile, writeRelayPidFile } from \"~/lib/lifecycle\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { getExposedModelIds } from \"~/lib/models\"\nimport { validateUpstream } from \"~/lib/preflight\"\nimport { formatUrlForDisplay, registerSensitiveOrigin } from \"~/lib/redact\"\nimport { runtimeState } from \"~/lib/state\"\nimport { appVersion } from \"~/lib/version\"\nimport { startServer } from \"~/server\"\n\n/**\n * Narrows the server to one that can close connections directly.\n *\n * `ServerType` from @hono/node-server is a union that includes `Http2Server`,\n * which has neither method, so this cannot be a plain property access. The relay\n * runs over HTTP/1.1 in practice; the guard keeps the HTTP/2 case correct rather\n * than assumed.\n */\ninterface ConnectionClosable {\n closeAllConnections: () => void\n closeIdleConnections: () => void\n}\n\nconst canCloseConnections = (\n server: ServerType,\n): server is ServerType & ConnectionClosable =>\n typeof (server as Partial<ConnectionClosable>).closeIdleConnections\n === \"function\"\n && typeof (server as Partial<ConnectionClosable>).closeAllConnections\n === \"function\"\n\nexport async function startRelay(appConfig?: AppConfig): Promise<void> {\n appConfig ??= await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const claudeConfigPath = defaultClaudeConfigPath\n const config = readProxyConfig(appConfig)\n const applyRuntimeConfig = (nextConfig: AppConfig) => {\n // Hot reload updates behavior for future requests; it intentionally does\n // not rebind the already-listening socket when host or port changes.\n //\n // The redaction policy is registered here rather than once at startup, so a\n // base URL introduced by a later edit is covered too. Registration is\n // additive and never removes a previous origin: a request against the old\n // base URL can still be in flight when the config changes, and its error\n // must stay redacted on its way to the log. See #47.\n registerSensitiveOrigin(nextConfig.copilotBaseUrl)\n setLogLevel(nextConfig.logLevel)\n void cleanupLogs(nextConfig.logRetentionDays)\n runtimeState.debug = nextConfig.logLevel === \"debug\"\n runtimeState.thinkEffort = nextConfig.thinkEffort\n config.copilotBaseUrl = nextConfig.copilotBaseUrl\n config.host = nextConfig.host\n config.port = nextConfig.port\n config.upstreamTimeoutMs = nextConfig.upstreamTimeoutSeconds * 1000\n config.webSearchBackend = nextConfig.webSearchBackend\n runtimeState.modelRouting = {\n gptModel: nextConfig.gptModel,\n opusModel: nextConfig.opusModel,\n }\n }\n applyRuntimeConfig(appConfig)\n\n log.info(`Log level: ${appConfig.logLevel}`)\n log.info(`Think effort: ${appConfig.thinkEffort}`)\n log.info(`Upstream timeout: ${appConfig.upstreamTimeoutSeconds}s`)\n log.info(`Exposed models: ${getExposedModelIds().join(\", \")}`)\n\n const authSession = await setupProxyAuth(config)\n\n try {\n await validateUpstream(config, appConfig.thinkEffort)\n } catch (error) {\n log.error(\"Startup preflight failed:\", error)\n process.exit(1)\n }\n\n const server = await startServer(config)\n await writeRelayPidFile(config)\n\n log.info(`copilot-relay version: ${appVersion}`)\n if (authSession.githubLogin) {\n log.info(`GitHub user: ${authSession.githubLogin}`)\n } else {\n log.error(\"GitHub user: unavailable\")\n }\n\n log.info(\n `copilot-relay listening on http://${config.host}:${config.port}`,\n )\n log.info(`copilot base url: ${formatUrlForDisplay(config.copilotBaseUrl)}`)\n\n const baseUrl = `http://${config.host}:${config.port}`\n if (appConfig.claudeSetup) {\n try {\n const claudeResult = await applyClaudeConfig({\n baseUrl,\n configPath: claudeConfigPath,\n gptModel: appConfig.gptModel,\n })\n if (claudeResult.changed) {\n log.info(\n `claude settings ${claudeResult.created ? \"created\" : \"updated\"}: ${claudeResult.configPath}`,\n )\n if (\n claudeResult.previousBaseUrl\n && claudeResult.previousBaseUrl !== baseUrl\n ) {\n log.info(\n `ANTHROPIC_BASE_URL: ${claudeResult.previousBaseUrl} → ${baseUrl}`,\n )\n }\n } else {\n log.info(\n `claude settings already up to date: ${claudeResult.configPath}`,\n )\n }\n } catch (error) {\n log.error(\n `Could not update claude settings (${claudeConfigPath}):`,\n error,\n )\n }\n }\n watchAppConfig((nextConfig) => {\n applyRuntimeConfig(nextConfig)\n log.info(\n `Config reloaded: logLevel=${nextConfig.logLevel} thinkEffort=${nextConfig.thinkEffort} upstreamTimeoutSeconds=${nextConfig.upstreamTimeoutSeconds}`,\n )\n })\n\n // Shorter than stopProcess's 5s SIGTERM timeout, so a clean exit reliably\n // wins that race and `stop` never has to escalate to SIGKILL.\n const shutdownGraceMs = 2_000\n\n const shutdown = (signal: NodeJS.Signals) => {\n log.info(`Received ${signal}; shutting down copilot-relay`)\n server.close((error) => {\n if (error) {\n log.error(\"Error while shutting down copilot-relay:\", error)\n process.exitCode = 1\n }\n })\n\n // server.close() stops accepting new connections but waits for existing\n // ones to finish. A Claude Code keep-alive socket sitting idle between\n // requests never finishes on its own, so without this the close hangs, the\n // 5s timeout expires, and the process is SIGKILLed - skipping the cleanup\n // in the finally below and severing any in-flight stream anyway.\n //\n // Releasing idle sockets immediately and active ones after a grace period\n // means a request in flight still gets time to drain.\n if (canCloseConnections(server)) {\n server.closeIdleConnections()\n\n const forceClose = setTimeout(() => {\n log.info(\"Grace period elapsed; closing remaining connections\")\n server.closeAllConnections()\n }, shutdownGraceMs)\n // Must not itself hold the event loop open, or it would delay the exit\n // it exists to accelerate.\n forceClose.unref()\n }\n }\n process.once(\"SIGINT\", shutdown)\n process.once(\"SIGTERM\", shutdown)\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.on(\"close\", resolve)\n server.on(\"error\", reject)\n })\n } finally {\n process.off(\"SIGINT\", shutdown)\n process.off(\"SIGTERM\", shutdown)\n await clearRelayPidFile()\n }\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the copilot-relay HTTP server.\",\n },\n async run() {\n await startRelay()\n },\n})\n","// `copilot-relay restart`: stop stale local relay processes, then start normally.\nimport { defineCommand } from \"citty\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { stopExistingRelay } from \"~/lib/lifecycle\"\nimport { startRelay } from \"./start\"\n\nexport const restart = defineCommand({\n meta: {\n name: \"restart\",\n description: \"Stop any existing copilot-relay instance and start a new one.\",\n },\n async run() {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const stopped = await stopExistingRelay(readProxyConfig(appConfig))\n if (stopped.length > 0) {\n log.info(`Stopped copilot-relay pid(s): ${stopped.join(\", \")}`)\n }\n log.info(\"Starting copilot-relay\")\n\n await startRelay(appConfig)\n },\n})\n","// `copilot-relay status`: report whether a relay is running, and whether it works.\nimport { defineCommand } from \"citty\"\n\nimport type { AppConfig } from \"~/lib/app-config\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { findRelayOnPort } from \"~/lib/lifecycle\"\nimport { setLogLevel } from \"~/lib/log\"\nimport { getLogPath, paths } from \"~/lib/paths\"\nimport {\n formatUrlForDisplay,\n registerSensitiveOrigin,\n sanitizeTerminalString,\n scrubSensitiveUrls,\n} from \"~/lib/redact\"\nimport { appVersion } from \"~/lib/version\"\n\n/**\n * Exit codes, so `status` is usable in a health check or a script.\n *\n * 2 is distinct from 1 on purpose: \"running but broken\" and \"not running\" call\n * for different responses - restart the service versus re-authenticate.\n *\n * A failed health probe is a 2, not a 0. Printing FAILED while exiting 0 would\n * make every scripted caller treat an unreachable relay as fine. See #34.\n */\nconst exitCodes = {\n notRunning: 1,\n notUsable: 2,\n running: 0,\n} as const\n\nconst localProbeTimeoutMs = 5_000\n// Generous: a real Copilot round trip, not a local socket check.\nconst deepProbeTimeoutMs = 60_000\n\ninterface ProbeResult {\n detail?: string\n ms?: number\n ok: boolean\n}\n\n/**\n * The resolved config as `status` reports it.\n *\n * Derived from AppConfig rather than restated, so a twelfth config key cannot\n * be added without this type following it automatically. Two deliberate\n * differences:\n *\n * `webSearchBackend` is null rather than undefined when unset. JSON.stringify\n * drops undefined properties, so leaving it optional would make `status --json`\n * emit 10 config keys on a default install and 11 on a customized one; anything\n * parsing that deserves a stable key set.\n *\n * `copilotBaseUrl` is the display form, not the raw value. It is user-supplied\n * and may legitimately carry a credential in its path, and `status` output is\n * what a user pastes into a bug report. Mapped here rather than at each render\n * site so the text block and `--json` cannot disagree about it. See #47.\n */\nexport type StatusConfig = Omit<AppConfig, \"webSearchBackend\"> & {\n webSearchBackend: string | null\n}\n\nexport const toStatusConfig = (config: AppConfig): StatusConfig => ({\n ...config,\n copilotBaseUrl: formatUrlForDisplay(config.copilotBaseUrl),\n webSearchBackend: config.webSearchBackend ?? null,\n})\n\nexport interface RelayStatus {\n baseUrl?: string\n /**\n * Every key readAppConfig() resolved — not the two that happened to fit in a\n * parenthetical. These are the values on disk; see renderConfig for why that\n * distinction is printed rather than assumed.\n */\n config: StatusConfig\n configPath: string\n /**\n * The version the running daemon reports about itself, which is not\n * necessarily `version` — that one is whichever CLI was invoked. Undefined\n * when nothing is running, or when the daemon predates version reporting.\n * See #43.\n */\n daemonVersion?: string\n deep?: ProbeResult\n health?: ProbeResult\n logPath: string\n logLevel: string\n models?: Array<string>\n pid?: number\n port?: number\n running: boolean\n startedAt?: string\n thinkEffort: string\n /** The CLI binary being invoked. Says nothing about what is serving. */\n version: string\n}\n\n/**\n * True when the daemon is serving a different build than the invoked CLI.\n *\n * Pure and exported because it drives both the rendered warning and the\n * deliberate decision to leave the exit code at 0; both deserve direct\n * coverage. Undefined daemonVersion is not a mismatch - unknown is not\n * different, and claiming otherwise would warn on every pre-v0.3.1 daemon.\n */\nexport const hasVersionMismatch = (status: RelayStatus): boolean =>\n status.running\n && status.daemonVersion !== undefined\n && status.daemonVersion !== status.version\n\n/**\n * Status to exit code. Pure, because this mapping is the contract every\n * scripted caller depends on and deserves direct coverage.\n *\n * 0 requires a live process *and* a passing health probe. Printing FAILED while\n * exiting 0 would make every scripted caller treat an unreachable relay as\n * fine. See #34.\n *\n * A version mismatch stays 0 on purpose (#43). The relay works; it is just not\n * the build that was installed. Mapping it to 2 would overstate it and break\n * every scripted caller that treats non-zero as broken. It is surfaced in the\n * text instead.\n */\nexport const resolveExitCode = (status: RelayStatus): number =>\n !status.running ? exitCodes.notRunning\n : !status.health?.ok ? exitCodes.notUsable\n : status.deep && !status.deep.ok ? exitCodes.notUsable\n : exitCodes.running\n\nconst formatUptime = (startedAt: string | undefined): string => {\n if (!startedAt) {\n return \"unknown\"\n }\n\n const started = Date.parse(startedAt)\n if (Number.isNaN(started)) {\n return \"unknown\"\n }\n\n const seconds = Math.max(0, Math.floor((Date.now() - started) / 1000))\n if (seconds < 60) return `${seconds}s`\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m`\n if (seconds < 86_400) {\n return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`\n }\n return `${Math.floor(seconds / 86_400)}d ${Math.floor((seconds % 86_400) / 3600)}h`\n}\n\n/**\n * Every config key with its position in the block, in config.default.yaml order\n * rather than alphabetical so it reads like the file it was resolved from.\n *\n * A Record keyed by StatusConfig rather than an array of keys: an array only\n * checks that each entry *is* a key, so a twelfth config key would type-check\n * while silently never appearing in `status` — the exact failure this command\n * exists to prevent. Keyed this way, adding one fails the build here.\n */\nconst configRowOrder: Record<keyof StatusConfig, number> = {\n host: 0,\n port: 1,\n copilotBaseUrl: 2,\n claudeSetup: 3,\n logLevel: 4,\n logRetentionDays: 5,\n thinkEffort: 6,\n upstreamTimeoutSeconds: 7,\n webSearchBackend: 8,\n gptModel: 9,\n opusModel: 10,\n}\n\nconst configRowKeys = (\n Object.keys(configRowOrder) as Array<keyof StatusConfig>\n).sort((a, b) => configRowOrder[a] - configRowOrder[b])\n\n/**\n * The resolved config, one key per line.\n *\n * These are the values on disk. That is not the same question as \"what is the\n * running daemon honouring\", and the footnote says so rather than leaving it\n * implied: applyRuntimeConfig() in start.ts hot-reloads eight of these keys,\n * never reads claudeSetup, and deliberately does not rebind the listening\n * socket when host or port changes. Printing all eleven without that line\n * would imply a live daemon had read values it has not.\n *\n * Only shown when running, because with nothing up every value applies at the\n * next start and the note would be noise.\n */\nconst renderConfig = (\n config: StatusConfig,\n running: boolean,\n): Array<string> => {\n const lines = configRowKeys.map((key) => {\n const value = config[key]\n const rendered =\n key === \"webSearchBackend\" && value === null ?\n \"(unset — uses gptModel)\"\n : String(value)\n // Every value is user-supplied and lands on a terminal unescaped. Raw\n // control bytes could clear the line and paint status rows the relay never\n // produced, so no config value is trusted to be printable. See #47.\n return ` ${key.padEnd(24)}${sanitizeTerminalString(rendered)}`\n })\n\n if (running) {\n lines.push(\n \" host, port and claudeSetup take effect on restart; the rest hot-reload.\",\n )\n }\n\n return lines\n}\n\n/**\n * Pure state-to-lines mapping, kept free of IO so it can be tested directly\n * without spawning a relay or opening a socket.\n */\nexport const renderStatus = (status: RelayStatus): Array<string> => {\n const lines = [`copilot-relay ${status.version}`]\n\n if (!status.running) {\n lines.push(\n \" process not running\",\n ` log ${status.logPath}`,\n ` config ${status.configPath}`,\n ...renderConfig(status.config, false),\n \"\",\n \" Start it with: copilot-relay start\",\n )\n return lines\n }\n\n lines.push(\n ` process running (pid ${status.pid}, up ${formatUptime(status.startedAt)})`,\n )\n\n // The daemon's own build, distinct from the header line above, which is the\n // CLI that was invoked. Always shown when running: the case this exists for\n // is an upgrade the user believes landed, and a row that appears only on\n // mismatch would leave \"did it even check?\" unanswered. See #43.\n lines.push(\n status.daemonVersion === undefined ?\n \" version unknown (daemon predates version reporting — restart to report it)\"\n : hasVersionMismatch(status) ?\n ` version ${status.daemonVersion} — MISMATCH, ${status.version} is installed`\n : ` version ${status.daemonVersion}`,\n )\n\n lines.push(` listening ${status.baseUrl}`)\n\n const health = status.health\n lines.push(\n health?.ok ?\n ` health ok (${health.ms}ms)`\n : ` health FAILED${health?.detail ? ` — ${health.detail}` : \"\"}`,\n )\n\n lines.push(\n status.models?.length ?\n ` models ${status.models.join(\", \")}`\n : \" models unavailable\",\n )\n\n if (status.deep) {\n lines.push(\n status.deep.ok ?\n ` upstream ok (${status.deep.ms}ms) — end-to-end Copilot round trip`\n : ` upstream FAILED${status.deep.detail ? ` — ${status.deep.detail}` : \"\"}`,\n )\n } else {\n lines.push(\" upstream not checked (use --deep)\")\n }\n\n lines.push(\n ` log ${status.logPath}`,\n ` config ${status.configPath}`,\n ...renderConfig(status.config, true),\n )\n\n // Only /v1/messages proves the relay can serve Claude Code; the first two\n // layers pass on a relay whose Copilot token expired an hour ago.\n if (status.health?.ok && status.deep && !status.deep.ok) {\n lines.push(\n \"\",\n \" The relay is listening but cannot reach Copilot.\",\n \" Try: copilot-relay auth\",\n )\n }\n\n // A mismatch is nearly always an upgrade that installed but never restarted,\n // so the next step is named rather than left to be inferred. Surfaced, not\n // just recorded: the version row alone reads as trivia next to a green\n // health line. See #43.\n if (hasVersionMismatch(status)) {\n lines.push(\n \"\",\n ` The running relay is ${status.daemonVersion}; ${status.version} is installed.`,\n \" Restart it to serve the installed version: copilot-relay restart\",\n )\n }\n\n return lines\n}\n\nconst probe = async (\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<{ body: unknown; ms: number; ok: boolean; status: number }> => {\n const started = performance.now()\n const response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(timeoutMs),\n })\n const body = (await response.json().catch(() => undefined)) as unknown\n return {\n body,\n ms: Math.round(performance.now() - started),\n ok: response.ok,\n status: response.status,\n }\n}\n\nconst describeError = (error: unknown): string =>\n error instanceof Error ?\n error.name === \"TimeoutError\" ? \"timed out\"\n : error.message\n : String(error)\n\n/**\n * Probes /healthz, which also reports the daemon's own version.\n *\n * The version rides along on a probe `status` already makes, so asking what is\n * running costs nothing extra. Absent for a daemon older than v0.3.1, which is\n * itself informative, so it stays optional rather than defaulting to the\n * caller's version - that substitution is the bug. See #43.\n */\nconst checkHealth = async (\n baseUrl: string,\n): Promise<{ result: ProbeResult; version?: string }> => {\n try {\n const probeResult = await probe(`${baseUrl}/healthz`, {}, localProbeTimeoutMs)\n const version = (probeResult.body as { version?: unknown } | undefined)\n ?.version\n return {\n result:\n probeResult.ok ?\n { ms: probeResult.ms, ok: true }\n : { detail: `http ${probeResult.status}`, ms: probeResult.ms, ok: false },\n ...(typeof version === \"string\" && version ? { version } : {}),\n }\n } catch (error) {\n return { result: { detail: describeError(error), ok: false } }\n }\n}\n\nconst readModels = async (baseUrl: string): Promise<Array<string>> => {\n try {\n const result = await probe(`${baseUrl}/v1/models`, {}, localProbeTimeoutMs)\n const data = (result.body as { data?: Array<{ id?: string }> } | undefined)?.data\n return Array.isArray(data) ?\n data.flatMap((model) => (typeof model.id === \"string\" ? [model.id] : []))\n : []\n } catch {\n return []\n }\n}\n\n/**\n * Layer 3: a real request through Copilot.\n *\n * Opt-in because it spends tokens. It is the only check that exercises token\n * refresh, the upstream call, and translation back to Claude shape.\n */\nconst checkDeep = async (\n baseUrl: string,\n model: string,\n): Promise<ProbeResult> => {\n try {\n const result = await probe(\n `${baseUrl}/v1/messages`,\n {\n body: JSON.stringify({\n max_tokens: 16,\n messages: [{ content: \"Reply with the single word: ok\", role: \"user\" }],\n model,\n }),\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n method: \"POST\",\n },\n deepProbeTimeoutMs,\n )\n\n if (!result.ok) {\n const message = (\n result.body as { error?: { message?: string } } | undefined\n )?.error?.message\n return {\n detail: message ? `http ${result.status}: ${message}` : `http ${result.status}`,\n ms: result.ms,\n ok: false,\n }\n }\n\n const content = (result.body as { content?: Array<unknown> } | undefined)?.content\n return Array.isArray(content) && content.length > 0 ?\n { ms: result.ms, ok: true }\n : { detail: \"empty response\", ms: result.ms, ok: false }\n } catch (error) {\n return { detail: describeError(error), ok: false }\n }\n}\n\nexport const collectStatus = async (options: {\n deep: boolean\n}): Promise<RelayStatus> => {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n // Before any probe runs, so a URL this origin appears in - a fetch failure\n // from checkHealth or checkDeep, which quotes the URL it tried - is already\n // covered by the time it reaches a detail string. See #47.\n registerSensitiveOrigin(appConfig.copilotBaseUrl)\n\n const base = {\n // Both of these are derived from the same appConfig as `config`, so they\n // cannot drift from it. They predate the config block and something may\n // parse them out of `status --json`, so they stay.\n config: toStatusConfig(appConfig),\n configPath: paths.configPath,\n logLevel: appConfig.logLevel,\n logPath: getLogPath(),\n thinkEffort: appConfig.thinkEffort,\n version: appVersion,\n }\n\n // Scoped to the configured port on purpose. `stop` scans globally so it can\n // clean up strays; `status` was asked about one relay, and reporting a\n // different one is worse than reporting nothing because it looks\n // authoritative. See #33.\n const relay = await findRelayOnPort(readProxyConfig(appConfig))\n if (!relay) {\n return { ...base, running: false }\n }\n\n // Address comes from the same record as the pid, so the two can never\n // describe different processes. The pid file's port is where the socket is\n // actually bound: hot reload updates config.port without rebinding.\n const baseUrl = `http://${relay.host}:${relay.port}`\n\n const health = await checkHealth(baseUrl)\n const models = health.result.ok ? await readModels(baseUrl) : []\n const deep =\n options.deep && health.result.ok ?\n await checkDeep(baseUrl, models[0] ?? appConfig.gptModel)\n : undefined\n\n // /healthz first: it is answered by the process itself, so it cannot be\n // stale. The pid file backs it up for a daemon that is up but not yet\n // healthy, which is exactly when the two sources are complementary. Both\n // absent means a daemon older than v0.3.1 — reported as unknown rather than\n // silently filled in with the CLI's version. See #43.\n const daemonVersion = health.version ?? relay.version\n\n return {\n ...base,\n baseUrl,\n ...(daemonVersion ? { daemonVersion } : {}),\n ...(deep ? { deep } : {}),\n health: health.result,\n models,\n pid: relay.pid,\n port: relay.port,\n running: true,\n ...(relay.startedAt ? { startedAt: relay.startedAt } : {}),\n }\n}\n\nexport const status = defineCommand({\n meta: {\n name: \"status\",\n description: \"Show whether copilot-relay is running and reachable.\",\n },\n args: {\n deep: {\n description:\n \"Also send a real request through Copilot. Proves the relay works end to end; spends a few tokens.\",\n type: \"boolean\",\n },\n json: {\n description: \"Emit machine-readable JSON.\",\n type: \"boolean\",\n },\n },\n async run({ args }) {\n const result = await collectStatus({ deep: Boolean(args.deep) })\n\n // stdout, not the logger: this is the command's output, and routing it\n // through the logger would write a log line every time it is polled.\n //\n // Scrubbed as a whole rather than field by field. toStatusConfig already\n // handles the copilotBaseUrl row, but a probe failure detail quotes the URL\n // it tried, and those strings come from fetch rather than from config. This\n // is the backstop for every such path, including --deep. Applied to the\n // complete output so legitimate newlines between rows survive. See #47.\n if (args.json) {\n console.log(scrubSensitiveUrls(JSON.stringify(result, null, 2)))\n } else {\n console.log(scrubSensitiveUrls(renderStatus(result).join(\"\\n\")))\n }\n\n // 0 requires both a live process and a passing health probe. A relay that\n // cannot answer /healthz is running but not usable, which is a 2.\n process.exitCode = resolveExitCode(result)\n },\n})\n","// `copilot-relay stop`: terminate all detected local relay server instances.\nimport { defineCommand } from \"citty\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { stopExistingRelay } from \"~/lib/lifecycle\"\n\nexport const stop = defineCommand({\n meta: {\n name: \"stop\",\n description: \"Stop all detected copilot-relay server instances.\",\n },\n async run() {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const stopped = await stopExistingRelay(readProxyConfig(appConfig))\n if (stopped.length > 0) {\n log.info(`Stopped copilot-relay pid(s): ${stopped.join(\", \")}`)\n }\n },\n})\n","#!/usr/bin/env node\n\n// CLI entrypoint: wires top-level commands without owning runtime behavior.\nimport { defineCommand, runMain } from \"citty\"\n\nimport { auth } from \"./auth\"\nimport { restart } from \"./restart\"\nimport { start } from \"./start\"\nimport { status } from \"./status\"\nimport { stop } from \"./stop\"\n\nconst main = defineCommand({\n meta: {\n name: \"copilot-relay\",\n description:\n \"Yet, just another relay for Claude Code to use a GitHub Copilot subscription.\",\n },\n subCommands: { auth, restart, start, status, stop },\n})\n\nawait runMain(main)\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.js","names":["parsed: URL","consolaLevelByName: Record<LogLevelName, number>","fileLevelByMethod: Record<string, number>","copilotVersion","editorPluginVersion","userAgent","copilotTokenRefreshTimer: ReturnType<typeof setTimeout> | undefined","sleep","resolve","payload: StoredCopilotToken","refreshIn","refreshIn: number","runtimeState: RuntimeState","defaultReasoningEffort: ReasoningEffort","defaultModelRouting: ModelRoutingConfig","defaultConfig: AppConfig","parsed: URL","config: Record<string, unknown>","logRetentionDays","config: AppConfig","resolve","payload: RelayPidFile","relayPids: Array<number>","isRecord","configPath","parsed: Record<string, unknown>","env: Record<string, unknown>","next: Record<string, unknown>","lastError: unknown","path","undiciFetch","state: ResponseTranslationState","chunks: Array<ResponseStreamEventMessage>","translated: Array<ResponsesInputItem>","continuePrefillPrompt","normalizeFinalAssistantPrefill","newMessages: Array<Message>","contentParts: Array<ContentPart>","allThinkingBlocks: Array<ClaudeThinkingBlock>","allTextBlocks: Array<ClaudeTextBlock>","allToolUseBlocks: Array<ClaudeToolUseBlock>","stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null","events: Array<ClaudeStreamEventData>","events","results: Array<WebSearchResult>","content: Array<ClaudeAssistantContentBlock>","toolCalls: Array<ToolCall>","finishReason: ChatCompletionResponse[\"choices\"][number][\"finish_reason\"]","usage: ChatCompletionResponse[\"usage\"]","buffered: Array<ChatCompletionChunk>","chunk: ChatCompletionChunk","rest: AsyncIterable<{ data?: string }>","events: Array<ClaudeStreamEventData>","events","decidedResponse: ChatCompletionResponse | undefined","bufferedChunks: Array<ChatCompletionChunk>","remainingStream: AsyncIterable<{ data?: string }> | undefined","streamState: ClaudeStreamState","claudeResponse: ClaudeResponse","chunk: ChatCompletionChunk","path","status","claudeConfigPath","defaultClaudeConfigPath","resolve","status","configRowOrder: Record<keyof StatusConfig, number>"],"sources":["../src/lib/error.ts","../src/lib/paths.ts","../src/lib/redact.ts","../src/lib/log.ts","../src/lib/auth.ts","../src/lib/state.ts","../src/lib/models.ts","../src/lib/app-config.ts","../src/lib/config.ts","../src/auth.ts","../src/lib/version.ts","../src/lib/lifecycle.ts","../src/lib/claude-settings.ts","../src/lib/defaults.ts","../src/copilot/client.ts","../src/copilot/responses.ts","../src/copilot/chat.ts","../src/lib/preflight.ts","../src/claude/utils.ts","../src/claude/tool-names.ts","../src/claude/translate.ts","../src/claude/stream.ts","../src/claude/web-search.ts","../src/claude/web-search-stream.ts","../src/lib/tokenizer.ts","../src/routes/claude.ts","../src/server.ts","../src/start.ts","../src/restart.ts","../src/status.ts","../src/stop.ts","../src/main.ts"],"sourcesContent":["// Error wrappers used to preserve upstream HTTP status and response bodies.\nexport class ProxyNotImplementedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"ProxyNotImplementedError\"\n }\n}\n\nexport class HTTPError extends Error {\n detail?: string\n response: Response\n\n constructor(message: string, response: Response, detail?: string) {\n super(message)\n this.name = \"HTTPError\"\n this.detail = detail\n this.response = response\n }\n}\n","// File-system layout for config, token cache, logs, and legacy migration paths.\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst homeDir = os.homedir()\n\nconst appDir = path.join(homeDir, \".copilot-relay\")\n\nconst legacyGithubTokenPaths = [\n path.join(homeDir, \".copilot-tennel\", \"github_token\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-tennel\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-tennel\", \"github_token\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-relay\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-relay\", \"github_token\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-bridge\", \"github_token\"),\n]\n\nconst legacyCopilotTokenPaths = [\n path.join(homeDir, \".copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-tennel\", \"copilot_token.json\"),\n path.join(homeDir, \"Library\", \"Application Support\", \"copilot-relay\", \"copilot_token.json\"),\n path.join(homeDir, \".local\", \"share\", \"copilot-relay\", \"copilot_token.json\"),\n]\n\nconst configPath = path.join(appDir, \"config.yaml\")\nconst copilotTokenPath = path.join(appDir, \"copilot_token.json\")\nconst githubTokenPath = path.join(appDir, \"github_token\")\nconst logsDir = path.join(appDir, \"logs\")\nconst logFileBaseName = \"copilot-relay\"\n// Pre-rotation installs wrote every line to this single undated file. Retention\n// still sweeps it, so upgrading installs drain themselves without manual steps.\nconst legacyLogPath = path.join(logsDir, `${logFileBaseName}.log`)\nconst pidPath = path.join(appDir, \"copilot-relay.pid\")\n\n/**\n * Local-date stamp used in log file names.\n *\n * Local rather than UTC on purpose: `logRetentionDays` is a human-facing \"how\n * many days do I keep\" setting, and a UTC stamp would roll the active file over\n * in the middle of the local afternoon for anyone west of Greenwich.\n */\nexport const formatLogDate = (date: Date): string =>\n [\n date.getFullYear(),\n String(date.getMonth() + 1).padStart(2, \"0\"),\n String(date.getDate()).padStart(2, \"0\"),\n ].join(\"-\")\n\n/**\n * Path of the log file that owns `date`. Resolved per write rather than cached,\n * so a long-running relay rotates at local midnight with no timer to drift.\n */\nexport const getLogPath = (date: Date = new Date()): string =>\n path.join(logsDir, `${logFileBaseName}.${formatLogDate(date)}.log`)\n\nexport const paths = {\n appDir,\n configPath,\n copilotTokenPath,\n githubTokenPath,\n legacyConfigPaths: [\n path.join(homeDir, \".copilot-relay.yaml\"),\n path.join(homeDir, \".copilot-tennel\", \"config.yaml\"),\n path.join(homeDir, \".copilot-tennel.yaml\"),\n ],\n legacyCopilotTokenPaths,\n legacyLogPath,\n logFileBaseName,\n logsDir,\n pidPath,\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(paths.appDir, { recursive: true })\n // GitHub tokens are plain text and safe to migrate here. Copilot token\n // migration is handled in auth.ts because that cache has a JSON schema.\n await ensureFile(paths.githubTokenPath, {\n legacyPaths: legacyGithubTokenPaths,\n })\n}\n\nasync function ensureFile(\n filePath: string,\n options: { legacyPaths?: Array<string> } = {},\n): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n for (const legacyPath of options.legacyPaths ?? []) {\n if (legacyPath === filePath) {\n continue\n }\n\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n if (content.trim()) {\n await fs.writeFile(filePath, content, { mode: 0o600 })\n await fs.chmod(filePath, 0o600)\n return\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n }\n\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}","// Disclosure-safe rendering of URLs and terminal strings.\n//\n// Exists because copilotBaseUrl is user-supplied and may legitimately carry a\n// credential in its path — `https://gateway.example/tenant/token-ABC` is a\n// working custom-gateway config, used as `${base}/models`. That value was\n// written verbatim to the log on every start and into `status` output, which is\n// the one file wiki/EN-Logging-Troubleshooting.md asks users to paste into an\n// issue. See #47.\n//\n// Pure and dependency-free on purpose: it sits on the logging path, so anything\n// that could throw or block here would take a request down with it.\n\n/**\n * Origins known to carry a secret somewhere after the origin.\n *\n * Process-lifetime and append-only. Never pruned, because config hot-reload can\n * change copilotBaseUrl while a request against the previous one is still in\n * flight; dropping the old policy would un-redact that request's error on its\n * way to the log.\n */\nconst sensitiveOrigins = new Set<string>()\n\n/** Marker written in place of everything after the origin. */\nconst redactedMarker = \"[redacted]\"\n\n/**\n * ANSI escape sequences: CSI (`ESC [ … final`), OSC (`ESC ] … BEL/ST`), and the\n * two-byte Fe forms. Matched whole rather than stripping the ESC byte alone,\n * which would leave the printable remainder (`[2K`) in the output.\n *\n * Written with \\x escapes rather than literal bytes: an invisible ESC in the\n * source survives no round trip through an editor, a diff view, or a paste.\n */\nconst ansiEscapePattern =\n /\\x1B(?:\\[[0-?]*[ -/]*[@-~]|\\][\\s\\S]*?(?:\\x07|\\x1B\\\\)|[@-Z\\\\-_])/g\n\n/** C0 controls and DEL, including CR/LF: one config value is one row. */\nconst controlBytePattern = /[\\x00-\\x1F\\x7F]/g\n\n/**\n * Absolute http(s) URLs inside arbitrary text.\n *\n * Stops at whitespace, quotes, backticks and angle brackets so a URL embedded\n * in inspect() output — `url: 'https://host/x'` — matches without swallowing\n * the surrounding punctuation.\n *\n * Backslashes are consumed rather than terminating the match. WHATWG folds `\\`\n * into `/` for http(s), so `https://host\\tenant\\TOKEN` parses with the tail in\n * the path, carries no userinfo, and is therefore accepted and stored verbatim\n * by normalizeCopilotBaseUrl — and inspect() doubles each backslash on the way\n * to both sinks. Stopping at the first one matched only the origin, which then\n * looked origin-only and left the entire tail in the log.\n */\nconst absoluteUrlPattern = /https?:\\/\\/[^\\s'\"`<>]+/gi\n\nconst parseHttpUrl = (value: string): URL | undefined => {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n return undefined\n }\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ?\n parsed\n : undefined\n}\n\n/** True when the URL carries nothing after the origin worth hiding. */\nconst isOriginOnly = (parsed: URL): boolean =>\n (parsed.pathname === \"\" || parsed.pathname === \"/\")\n && !parsed.search\n && !parsed.hash\n\n/**\n * Renders a URL for display, keeping the origin and hiding the rest.\n *\n * The origin is the operationally useful half — it answers \"which gateway am I\n * talking to\" — while path, query and fragment are where a credential ends up.\n * An explicit marker rather than silent truncation, so a reader can tell the\n * difference between a base URL that has no path and one whose path was\n * withheld.\n *\n * Never throws: it also runs on values that failed validation.\n */\nexport const formatUrlForDisplay = (raw: string): string => {\n const parsed = parseHttpUrl(raw)\n if (!parsed) {\n return \"(invalid url)\"\n }\n\n // Returned as written, not re-serialized, so a trailing slash the user\n // configured still reads back the way their config file has it.\n return isOriginOnly(parsed) ?\n raw\n : `${parsed.origin} (path/query/fragment hidden)`\n}\n\n/**\n * Strips ANSI escapes and control bytes from a string bound for the terminal.\n *\n * `status` prints config values directly to stdout. A config string carrying\n * raw control bytes could clear the line and paint status rows the relay never\n * produced. The config file is user-owned, so this is hardening rather than a\n * vulnerability — but it costs nothing to make the output non-forgeable. #47.\n */\nexport const sanitizeTerminalString = (text: string): string =>\n text.replace(ansiEscapePattern, \"\").replace(controlBytePattern, \"\")\n\n/**\n * Marks a URL's origin as secret-bearing, if it has anything after the origin.\n *\n * An origin-only URL registers nothing: there is no tail to hide, and marking\n * the origin would redact every unrelated URL on that host for no benefit.\n *\n * Additive and idempotent. Never throws on an unparseable value.\n */\nexport const registerSensitiveOrigin = (raw: string): void => {\n const parsed = parseHttpUrl(raw)\n if (!parsed || isOriginOnly(parsed)) {\n return\n }\n\n sensitiveOrigins.add(parsed.origin)\n}\n\n/**\n * Rewrites URLs on registered origins to `origin[redacted]`.\n *\n * Anchored on the parsed origin rather than on the configured string, which is\n * what makes the variants match: fetch reports back a canonicalized URL (host\n * case-folded, default port dropped) with the fragment already stripped, and\n * clients append their own endpoint to the base. All of those share an origin.\n *\n * Deliberately not a substring substitution of the secret path. Replacing that\n * text wherever it appeared would also rewrite unrelated prose and detached\n * paths; only an absolute URL on a registered origin is rewritten here.\n */\nexport const scrubSensitiveUrls = (text: string): string => {\n if (sensitiveOrigins.size === 0) {\n return text\n }\n\n return text.replace(absoluteUrlPattern, (match) => {\n // The whole match is treated as the URL - nothing is trimmed off the end\n // and nothing is restored after the marker.\n //\n // Trailing punctuation used to be trimmed as presumed prose and put back\n // afterwards. In a query string it is not prose, it is the value:\n // `?token=!!!` is a valid configured URL whose secret is punctuation and\n // nothing else, and the restore handed that token back whole. Arbitrary\n // log text cannot tell a token made of punctuation from a sentence that\n // ends in one, so the ambiguity is resolved toward disclosure safety: a\n // sentence-ending period after a sensitive URL is absorbed into the\n // marker. That costs a character of prose on origins already known to\n // carry a secret; the alternative cost was the secret.\n //\n // Unregistered origins are returned untouched below, so ordinary\n // diagnostic URLs keep their punctuation byte-for-byte.\n //\n // There is also no text-level shortcut for already-scrubbed input.\n // Skipping anything that *ended* with the marker was a bypass, not an\n // optimization: `https://host/tenant/SECRET[redacted]` is a valid\n // configured value, and that check handed it back with the secret intact.\n // Idempotence is structural instead - `origin[redacted]` is not a\n // parseable URL, because a bracket is illegal in a hostname unless it\n // delimits an IPv6 literal, so parseHttpUrl rejects it and a second pass\n // finds nothing to rewrite, for a bare host, a host with a port, and an\n // IPv6 literal alike.\n const parsed = parseHttpUrl(match)\n if (!parsed || !sensitiveOrigins.has(parsed.origin)) {\n return match\n }\n\n // Where the authority ends in the *matched text*, which may differ from\n // the canonical origin in case or port. Nothing after it means nothing to\n // hide.\n //\n // Backslash ends the authority exactly as \"/\" does: WHATWG normalizes it\n // to \"/\" for http(s), so `https://host\\tenant` has \"tenant\" in its path,\n // not its host. Omitting it here would classify that URL as origin-only\n // and return it unredacted.\n const authorityStart = match.indexOf(\"://\") + 3\n let authorityEnd = match.length\n for (let index = authorityStart; index < match.length; index += 1) {\n const character = match[index]\n if (\n character === \"/\"\n || character === \"\\\\\"\n || character === \"?\"\n || character === \"#\"\n ) {\n authorityEnd = index\n break\n }\n }\n\n return authorityEnd === match.length ?\n match\n : `${parsed.origin}${redactedMarker}`\n })\n}\n","// Central logger: writes to console and ~/.copilot-relay/logs with daily\n// rotation and retention cleanup.\nimport fs from \"node:fs/promises\"\nimport { inspect } from \"node:util\"\n\nimport consola from \"consola\"\n\nimport type { LogLevelName } from \"~/lib/app-config\"\nimport { getLogPath, paths } from \"~/lib/paths\"\nimport { scrubSensitiveUrls } from \"~/lib/redact\"\n\nconst logCleanupCheckIntervalMs = 60 * 60 * 1000\nconst millisecondsPerDay = 24 * 60 * 60 * 1000\nlet logRetentionDays = 3\nlet nextLogCleanupCheckAt = 0\n\n// Matches the dated files getLogPath() writes, e.g. copilot-relay.2026-07-24.log.\nconst datedLogFilePattern = new RegExp(\n `^${paths.logFileBaseName}\\\\.(\\\\d{4})-(\\\\d{2})-(\\\\d{2})\\\\.log$`,\n)\n\nconst consolaLevelByName: Record<LogLevelName, number> = {\n error: 0,\n info: 3,\n debug: 4,\n}\n\n// Keep runtime logging intentionally small: error carries full failure context,\n// info is operational status, and debug is detailed tracing for local diagnosis.\nconst fileLevelByMethod: Record<string, number> = {\n error: consolaLevelByName.error,\n info: consolaLevelByName.info,\n debug: consolaLevelByName.debug,\n}\n\nlet currentLogLevel = consolaLevelByName.info\n\nexport const setLogLevel = (level: LogLevelName): void => {\n currentLogLevel = consolaLevelByName[level]\n consola.level = consolaLevelByName[level]\n}\n\n/**\n * Render one logged value as a single physical line.\n *\n * `breakLength: Infinity` matters as much as the depth cap. The previous\n * `inspect(value, { depth: null })` pretty-printed each payload across\n * thousands of physical lines, which was both the dominant source of log volume\n * and the reason the `grep` recipes in wiki/EN-Logging-Troubleshooting.md\n * returned a fragment of an object instead of the matching entry. One entry is\n * now one line.\n */\nconst formatLogValue = (value: unknown): string =>\n typeof value === \"string\" ? value : (\n inspect(value, {\n breakLength: Infinity,\n // compact: true is load-bearing next to breakLength, not redundant with\n // it. The Node docs say breakLength: Infinity formats input on one line\n // \"in combination with compact set to true or any number >= 1\", which\n // reads as though the default compact: 3 would suffice. It does not: the\n // number is a count of inner elements united, not a threshold, so it only\n // collapses payloads nesting no deeper than that count. Measured on\n // v26.5.0 with a real Copilot error payload (4 levels deep):\n //\n // compact: 3 + breakLength: Infinity -> 10 lines\n // compact: 1 + breakLength: Infinity -> 22 lines\n // compact: 10 + breakLength: Infinity -> 1 line\n // compact: true + breakLength: Infinity -> 1 line\n //\n // Lowering the number makes it worse, which no threshold reading\n // predicts. compact: true is depth-independent, so it holds for payloads\n // deeper than any fixed count we might pick.\n compact: true,\n depth: 6,\n maxArrayLength: 100,\n maxStringLength: 4000,\n })\n )\n\n/**\n * Appends one already-rendered entry.\n *\n * Takes strings rather than raw values: rendering happens once in wrapFileLog,\n * so the console and the file are guaranteed to carry the same redacted text.\n * Inspecting again here would reintroduce the gap this closes - the file would\n * be scrubbed while the console showed the original object. See #47.\n */\nconst writeLogFile = async (\n level: string,\n values: Array<string>,\n): Promise<void> => {\n await fs.mkdir(paths.logsDir, { recursive: true })\n await cleanupLogsIfDue()\n const line = [new Date().toISOString(), level, values.join(\" \")].join(\" \")\n // Resolved per write, so a relay running across local midnight starts the next\n // dated file on its own; there is no rotation timer to drift or miss.\n await fs.appendFile(getLogPath(), `${line}\\n`)\n}\n\nconst cleanupLogsIfDue = async (): Promise<void> => {\n const now = Date.now()\n if (now < nextLogCleanupCheckAt) {\n return\n }\n\n nextLogCleanupCheckAt = now + logCleanupCheckIntervalMs\n await cleanupLogs(logRetentionDays)\n}\n\n/**\n * The single boundary where every logged value becomes redacted text.\n *\n * Each argument is rendered exactly once and scrubbed once, and the identical\n * array of strings goes to both the file and the console. Scrubbing only on the\n * way to disk would leave the original object on the console - the screen a\n * user screenshots into an issue - so the two sinks must not diverge. See #47.\n *\n * Rendering is skipped only when neither sink would emit, so the cost matches\n * the old behavior at info level. The console gate is read from consola rather\n * than assumed equal to currentLogLevel: if the two ever diverge, rendering\n * must follow whichever sink is still emitting, or that sink gets raw values.\n */\nconst wrapFileLog = <T extends (...args: Array<unknown>) => unknown>(\n level: string,\n fn: T,\n): T =>\n ((...args: Array<unknown>) => {\n const methodLevel = fileLevelByMethod[level] ?? consolaLevelByName.info\n const writesToFile = methodLevel <= currentLogLevel\n const writesToConsole = methodLevel <= consola.level\n\n if (!writesToFile && !writesToConsole) {\n return fn(...args)\n }\n\n const rendered = args.map((value) =>\n scrubSensitiveUrls(formatLogValue(value)),\n )\n\n if (writesToFile) {\n // File logging must never block the console path or fail a request. If\n // the disk write fails, the original consola call still runs.\n void writeLogFile(level, rendered).catch(() => undefined)\n }\n return fn(...rendered)\n }) as T\n\nconsola.error = wrapFileLog(\"error\", consola.error.bind(consola))\nconsola.info = wrapFileLog(\"info\", consola.info.bind(consola))\nconsola.debug = wrapFileLog(\"debug\", consola.debug.bind(consola))\n\n/**\n * Calendar day a log file belongs to, or undefined when its name carries no\n * date stamp.\n *\n * The filename stamp is preferred over mtime because mtime is rewritten by\n * backups, `cp`, and editors touching the file, any of which would silently\n * extend or shorten the retention window.\n */\nconst parseLogFileDate = (fileName: string): Date | undefined => {\n const match = datedLogFilePattern.exec(fileName)\n if (!match) {\n return undefined\n }\n\n const [, year, month, day] = match\n const date = new Date(Number(year), Number(month) - 1, Number(day))\n // Rejects impossible stamps such as 2026-13-45, which Date would otherwise\n // roll forward into a plausible-looking day.\n return (\n date.getFullYear() === Number(year)\n && date.getMonth() === Number(month) - 1\n && date.getDate() === Number(day)\n ) ?\n date\n : undefined\n}\n\nexport const cleanupLogs = async (retentionDays: number): Promise<void> => {\n logRetentionDays = retentionDays\n await fs.mkdir(paths.logsDir, { recursive: true })\n\n // retentionDays counts today plus the preceding retentionDays - 1 days, so the\n // default of 3 keeps today, yesterday, and the day before.\n const startOfToday = new Date()\n startOfToday.setHours(0, 0, 0, 0)\n const cutoff =\n startOfToday.getTime() - (retentionDays - 1) * millisecondsPerDay\n\n const entries = await fs.readdir(paths.logsDir, { withFileTypes: true })\n await Promise.all(\n entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".log\"))\n .map(async (entry) => {\n const filePath = `${paths.logsDir}/${entry.name}`\n const fileDate = parseLogFileDate(entry.name)\n // Undated files - notably the pre-rotation copilot-relay.log - keep the\n // original mtime rule, so upgrading installs drain without manual steps.\n const timestamp =\n fileDate?.getTime() ?? (await fs.stat(filePath)).mtimeMs\n if (timestamp < cutoff) {\n // Two relay processes can sweep the same directory; losing that race\n // is not a failure worth surfacing.\n await fs.rm(filePath, { force: true })\n }\n }),\n )\n}\n\nexport const log = consola\n","// GitHub device auth plus file-backed Copilot token cache and refresh scheduling.\nimport fs from \"node:fs/promises\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { paths, ensurePaths } from \"~/lib/paths\"\n\nconst copilotVersion = \"0.26.7\"\nconst editorPluginVersion = `copilot-chat/${copilotVersion}`\nconst userAgent = `GitHubCopilotChat/${copilotVersion}`\nconst githubApiVersion = \"2022-11-28\"\n\nconst githubApiBaseUrl = \"https://api.github.com\"\nconst githubBaseUrl = \"https://github.com\"\nconst githubClientId = \"Iv1.b507a08c87ecfe98\"\nconst githubAppScopes = [\"read:user\"].join(\" \")\n\ninterface DeviceCodeResponse {\n device_code: string\n expires_in: number\n interval: number\n user_code: string\n verification_uri: string\n}\n\ninterface AccessTokenResponse {\n access_token?: string\n}\n\ninterface CopilotTokenResponse {\n refresh_in: number\n token: string\n}\n\ninterface StoredCopilotToken {\n refreshedAt: number\n refreshIn: number\n token: string\n}\n\ninterface GitHubUserResponse {\n login: string\n}\n\ninterface AuthOptions {\n force?: boolean\n}\n\nexport interface ProxyAuthSession {\n githubLogin?: string\n source: \"github-token\"\n}\n\nlet copilotTokenRefreshTimer: ReturnType<typeof setTimeout> | undefined\n\nconst standardHeaders = () => ({\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n})\n\nconst sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nconst readGitHubToken = async () => {\n await ensurePaths()\n const token = await fs.readFile(paths.githubTokenPath, \"utf8\")\n return token.trim()\n}\n\nconst writeGitHubToken = async (token: string) => {\n await ensurePaths()\n await fs.writeFile(paths.githubTokenPath, `${token.trim()}\\n`, {\n mode: 0o600,\n })\n}\n\nconst readStoredCopilotToken = async (): Promise<StoredCopilotToken | undefined> => {\n await ensurePaths()\n try {\n const payload = JSON.parse(\n await fs.readFile(paths.copilotTokenPath, \"utf8\"),\n ) as Partial<StoredCopilotToken>\n if (\n typeof payload.token === \"string\"\n && typeof payload.refreshedAt === \"number\"\n && typeof payload.refreshIn === \"number\"\n ) {\n return payload as StoredCopilotToken\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n\n // Migrate only caches that still match the current JSON shape; older files\n // are ignored so a corrupt token cache does not poison new installs.\n for (const legacyPath of paths.legacyCopilotTokenPaths) {\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n const payload = JSON.parse(content) as Partial<StoredCopilotToken>\n if (\n typeof payload.token === \"string\"\n && typeof payload.refreshedAt === \"number\"\n && typeof payload.refreshIn === \"number\"\n ) {\n await fs.writeFile(paths.copilotTokenPath, content, { mode: 0o600 })\n await fs.chmod(paths.copilotTokenPath, 0o600)\n return payload as StoredCopilotToken\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n }\n }\n\n return undefined\n}\n\nconst writeStoredCopilotToken = async (\n input: CopilotTokenResponse,\n): Promise<void> => {\n await ensurePaths()\n const payload: StoredCopilotToken = {\n refreshedAt: Date.now(),\n refreshIn: input.refresh_in,\n token: input.token,\n }\n await fs.writeFile(\n paths.copilotTokenPath,\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n )\n}\n\nconst getStoredTokenRemainingSeconds = (token: StoredCopilotToken): number => {\n const ageSeconds = Math.floor((Date.now() - token.refreshedAt) / 1000)\n return token.refreshIn - ageSeconds\n}\n\nconst githubHeaders = (githubToken: string, vsCodeVersion: string) => ({\n ...standardHeaders(),\n authorization: `token ${githubToken}`,\n \"editor-plugin-version\": editorPluginVersion,\n \"editor-version\": `vscode/${vsCodeVersion}`,\n \"user-agent\": userAgent,\n \"x-github-api-version\": githubApiVersion,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nconst getDeviceCode = async (): Promise<DeviceCodeResponse> => {\n const response = await fetch(`${githubBaseUrl}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: githubClientId,\n scope: githubAppScopes,\n }),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get device code\", response)\n }\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nconst pollAccessToken = async (\n deviceCode: DeviceCodeResponse,\n): Promise<string> => {\n // GitHub asks clients to poll at `interval`; add a one-second cushion so\n // long waits do not accidentally trigger OAuth `slow_down` responses.\n const sleepDuration = (deviceCode.interval + 1) * 1000\n\n // Device auth is intentionally user-driven: keep polling until the user\n // finishes authorization in the browser instead of treating pending states\n // as fatal startup errors.\n while (true) {\n const response = await fetch(`${githubBaseUrl}/login/oauth/access_token`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: githubClientId,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n })\n\n if (!response.ok) {\n await sleep(sleepDuration)\n continue\n }\n\n const json = (await response.json()) as AccessTokenResponse\n if (json.access_token) {\n return json.access_token\n }\n\n await sleep(sleepDuration)\n }\n}\n\nconst getGitHubUser = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<GitHubUserResponse> => {\n const response = await fetch(`${githubApiBaseUrl}/user`, {\n headers: githubHeaders(githubToken, vsCodeVersion),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get GitHub user\", response)\n }\n\n return (await response.json()) as GitHubUserResponse\n}\n\nconst getCopilotToken = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<CopilotTokenResponse> => {\n const response = await fetch(\n `${githubApiBaseUrl}/copilot_internal/v2/token`,\n {\n headers: githubHeaders(githubToken, vsCodeVersion),\n },\n )\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot token\", response)\n }\n\n return (await response.json()) as CopilotTokenResponse\n}\n\nconst isCopilotTokenError = (error: unknown): boolean =>\n error instanceof HTTPError && error.message === \"Failed to get Copilot token\"\n\nconst ensureGitHubToken = async (options: AuthOptions = {}) => {\n const existingToken = options.force ? \"\" : await readGitHubToken()\n if (existingToken) {\n log.info(`Using cached GitHub token at ${paths.githubTokenPath}`)\n return existingToken\n }\n\n const deviceCode = await getDeviceCode()\n log.info(\n `Open ${deviceCode.verification_uri} and enter code ${deviceCode.user_code}`,\n )\n\n const githubToken = await pollAccessToken(deviceCode)\n await writeGitHubToken(githubToken)\n log.info(`GitHub token synced to ${paths.githubTokenPath}`)\n\n return githubToken\n}\n\nconst loadGitHubLogin = async (\n githubToken: string,\n vsCodeVersion: string,\n): Promise<string | undefined> => {\n try {\n const user = await getGitHubUser(githubToken, vsCodeVersion)\n return user.login\n } catch (error) {\n log.error(\"Could not load GitHub user:\", error)\n return undefined\n }\n}\n\nexport const setupProxyAuth = async (\n config: ProxyConfig,\n options: AuthOptions = {},\n): Promise<ProxyAuthSession> => {\n let githubToken = await ensureGitHubToken(options)\n const applyCopilotToken = async () => {\n const tokenResponse = await getCopilotToken(\n githubToken,\n config.vsCodeVersion,\n )\n\n config.copilotToken = tokenResponse.token\n await writeStoredCopilotToken(tokenResponse)\n return tokenResponse.refresh_in\n }\n\n const scheduleCopilotTokenRefresh = (refreshIn: number) => {\n if (copilotTokenRefreshTimer) {\n clearTimeout(copilotTokenRefreshTimer)\n }\n\n // Refresh one minute before GitHub's `refresh_in` deadline, but keep a\n // one-minute floor so failure retries do not spin in a tight loop.\n const refreshMs = Math.max(refreshIn - 60, 60) * 1000\n log.info(\n `Next Copilot token refresh in ${Math.round(refreshMs / 1000)}s`,\n )\n\n copilotTokenRefreshTimer = setTimeout(async () => {\n try {\n const nextRefreshIn = await applyCopilotToken()\n log.info(\"Refreshed Copilot token\")\n scheduleCopilotTokenRefresh(nextRefreshIn)\n } catch (error) {\n log.error(\"Failed to refresh Copilot token:\", error)\n // Keep the last token active and retry soon; in-flight requests can\n // continue until the bearer token actually expires.\n scheduleCopilotTokenRefresh(60)\n }\n }, refreshMs)\n\n if (typeof copilotTokenRefreshTimer.unref === \"function\") {\n copilotTokenRefreshTimer.unref()\n }\n }\n\n const storedToken = await readStoredCopilotToken()\n let refreshIn: number\n if (storedToken && getStoredTokenRemainingSeconds(storedToken) > 60) {\n // Avoid using tokens that are close to expiry; Claude Code requests can\n // be long-running, so keep at least one minute of freshness for new calls.\n config.copilotToken = storedToken.token\n refreshIn = getStoredTokenRemainingSeconds(storedToken)\n log.info(`Using cached Copilot token at ${paths.copilotTokenPath}`)\n } else {\n try {\n refreshIn = await applyCopilotToken()\n } catch (error) {\n if (options.force || !isCopilotTokenError(error)) {\n throw error\n }\n\n // A cached GitHub token can still become unable to mint Copilot tokens\n // after revocation or account changes; only this specific failure gets\n // a fresh device-auth attempt.\n log.error(\n \"Cached GitHub auth could not get a Copilot token; running device auth again.\",\n )\n githubToken = await ensureGitHubToken({\n ...options,\n force: true,\n })\n refreshIn = await applyCopilotToken()\n }\n }\n scheduleCopilotTokenRefresh(refreshIn)\n\n return {\n githubLogin: await loadGitHubLogin(githubToken, config.vsCodeVersion),\n source: \"github-token\",\n }\n}\n\nexport const runProxyAuth = async (\n config: ProxyConfig,\n options: AuthOptions = {},\n) => {\n const githubToken = await ensureGitHubToken({\n ...options,\n force: true,\n })\n\n const user = await getGitHubUser(githubToken, config.vsCodeVersion)\n log.info(`GitHub token written to ${paths.githubTokenPath}`)\n log.info(`Logged in as ${user.login}`)\n}","// Process-local mutable state for values that can change after config hot reload.\nimport type { ReasoningEffort } from \"~/lib/models\"\nimport type { ModelRoutingConfig } from \"~/lib/models\"\n\nexport interface RuntimeState {\n debug?: boolean\n modelRouting?: ModelRoutingConfig\n thinkEffort?: ReasoningEffort\n}\n\nexport const runtimeState: RuntimeState = {}\n","// Config-driven model routing and think-effort validation.\nimport { runtimeState } from \"~/lib/state\"\n\nexport type ReasoningEffort =\n | \"none\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n\nexport interface ModelRoutingConfig {\n gptModel: string\n opusModel: string\n}\n\nexport const defaultReasoningEffort: ReasoningEffort = \"max\"\n\nexport const defaultModelRouting: ModelRoutingConfig = {\n gptModel: \"gpt-6-astra\",\n opusModel: \"claude-opus-5\",\n}\n\nexport const isReasoningEffort = (value: unknown): value is ReasoningEffort =>\n value === \"none\"\n || value === \"low\"\n || value === \"medium\"\n || value === \"high\"\n || value === \"xhigh\"\n || value === \"max\"\n\nconst oneMillionContextModelPattern = /^(gpt-5\\.6-sol|gpt-6-astra)(?:\\[1m\\])*$/i\n\nconst normalizeOneMillionContextModel = (model: string): string | undefined => {\n const match = oneMillionContextModelPattern.exec(model)\n return match?.[1]?.toLowerCase()\n}\n\nexport const normalizeClaudeModelId = (model: string): string => {\n const normalized = normalizeOneMillionContextModel(model)\n return normalized ? `${normalized}[1m]` : model\n}\n\nexport const normalizeCopilotModelId = (model: string): string =>\n normalizeOneMillionContextModel(model) ?? model\n\nconst getConfiguredModelRouting = (): ModelRoutingConfig =>\n runtimeState.modelRouting ?? defaultModelRouting\n\nexport const getModelRouting = (): ModelRoutingConfig => {\n const routing = getConfiguredModelRouting()\n return {\n gptModel: normalizeCopilotModelId(routing.gptModel),\n opusModel: normalizeCopilotModelId(routing.opusModel),\n }\n}\n\nexport const getExposedModelIds = (): Array<string> => {\n const routing = getConfiguredModelRouting()\n return [normalizeClaudeModelId(routing.gptModel), routing.opusModel]\n}\n\nexport const getUpstreamModelIds = (): Array<string> => {\n const routing = getModelRouting()\n return [routing.gptModel, routing.opusModel]\n}\n\nexport const routeModelId = (model: string): string => {\n const routing = getModelRouting()\n // Keep routing intentionally predictable for Claude Code: any alias that\n // mentions Opus gets the configured Opus upstream, all other model names use\n // the configured GPT upstream.\n return model.trim().toLowerCase().includes(\"opus\") ?\n routing.opusModel\n : routing.gptModel\n}\n","// Runtime YAML config loader/writer with hot-reload support for ~/.copilot-relay/config.yaml.\nimport fs from \"node:fs/promises\"\nimport { dirname, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nimport {\n defaultReasoningEffort,\n isReasoningEffort,\n type ReasoningEffort,\n} from \"~/lib/models\"\nimport { paths } from \"~/lib/paths\"\n\nexport const logLevels = [\"error\", \"info\", \"debug\"] as const\nexport type LogLevelName = (typeof logLevels)[number]\nexport interface AppConfig {\n claudeSetup: boolean\n copilotBaseUrl: string\n gptModel: string\n host: string\n logLevel: LogLevelName\n logRetentionDays: number\n opusModel: string\n port: number\n thinkEffort: ReasoningEffort\n upstreamTimeoutSeconds: number\n webSearchBackend?: string\n}\n\n/**\n * Values used when a key is absent from the user's config.\n *\n * readAppConfig() writes the resolved config back to disk, so an existing\n * install has every key materialized and never consults these again. Changing\n * one therefore affects fresh installs only, by design: copilot-relay does not\n * rewrite a value the user's config already holds.\n */\nconst defaultConfig: AppConfig = {\n claudeSetup: true,\n copilotBaseUrl: \"https://api.githubcopilot.com\",\n gptModel: \"gpt-6-astra\",\n host: \"127.0.0.1\",\n logLevel: \"info\",\n logRetentionDays: 3,\n opusModel: \"claude-opus-5\",\n port: 4142,\n thinkEffort: defaultReasoningEffort,\n upstreamTimeoutSeconds: 180,\n webSearchBackend: undefined,\n}\n\nexport const isLogLevelName = (value: unknown): value is LogLevelName =>\n typeof value === \"string\"\n && logLevels.includes(value.toLowerCase() as LogLevelName)\n\nexport const normalizeLogLevel = (value: unknown): LogLevelName | undefined => {\n if (value === undefined) {\n return undefined\n }\n\n if (typeof value !== \"string\") {\n throw new Error(\n `Invalid logLevel: expected one of ${logLevels.join(\", \")}`,\n )\n }\n\n const normalized = value.toLowerCase()\n if (!isLogLevelName(normalized)) {\n throw new Error(\n `Invalid logLevel \"${value}\": expected one of ${logLevels.join(\", \")}`,\n )\n }\n\n return normalized\n}\n\nconst normalizeBoolean = (value: unknown): boolean | undefined => {\n if (typeof value === \"boolean\") {\n return value\n }\n if (typeof value !== \"string\") {\n return undefined\n }\n const normalized = value.toLowerCase()\n if (normalized === \"true\") return true\n if (normalized === \"false\") return false\n return undefined\n}\n\nconst normalizePort = (value: unknown): number | undefined => {\n const port =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value, 10)\n : Number.NaN\n return Number.isInteger(port) && port > 0 && port <= 65_535 ? port : undefined\n}\n\nconst normalizePositiveInteger = (value: unknown): number | undefined => {\n const number =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value, 10)\n : Number.NaN\n return Number.isInteger(number) && number > 0 ? number : undefined\n}\n\nconst normalizeString = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.trim() ? value.trim() : undefined\n\n/**\n * A conventional http(s) URL: scheme followed by a literal \"//\" authority.\n *\n * WHATWG is far more permissive than this. `https:host/path`, `https:/host/path`\n * and `https:\\\\host\\path` all parse, all normalize to a real origin with the\n * tail in the path, and all work upstream - so a protocol check alone lets them\n * through. None of them can be found again in arbitrary log text: practical URL\n * detection anchors on a literal \"://\", and loosening that to chase a bare\n * \"https:\" would start matching ordinary prose. Requiring the authority prefix\n * is the one boundary where this has a definite answer. See #47.\n */\nconst conventionalHttpUrlPattern = /^https?:\\/\\//i\n\n/**\n * Raw characters that cannot be delimited safely in rendered log text.\n *\n * A raw apostrophe is the clearest case. WHATWG accepts it and leaves it raw\n * in the path, so the configured value keeps it - and inspect() renders that\n * value inside quotes, where the apostrophe reads as the closing delimiter.\n * Any URL scan ends there, and the rest of the path prints in full. Quotes,\n * backticks and angle brackets are the same story; whitespace ends a URL for\n * every reader and scanner alike; and tab and newline are worse still,\n * because URL parsing strips them, so the stored value would not even match\n * what is sent upstream.\n *\n * Rejecting the raw byte costs nothing: every one of these has a\n * percent-encoded spelling that is unambiguous in log text and is accepted\n * here unchanged. See #47.\n */\nconst unsafeRawDelimiterPattern = /['\"`<>\\s\\u0000-\\u001F\\u007F]/\n\n/**\n * Both fixed strings. The rejected value is never interpolated: these messages\n * reach the terminal and, via the startup failure path, the log file, which is\n * exactly the disclosure being prevented.\n */\nconst invalidCopilotBaseUrlMessage =\n \"Invalid copilotBaseUrl: expected an absolute http(s) URL, e.g. https://api.githubcopilot.com\"\nconst copilotBaseUrlCredentialsMessage =\n \"Invalid copilotBaseUrl: URL credentials (user:password@host) are not supported; remove them and use a plain https URL\"\nconst copilotBaseUrlDelimiterMessage =\n \"Invalid copilotBaseUrl: quotes, backticks, angle brackets, whitespace and control characters must be percent-encoded\"\n\n/**\n * Validates copilotBaseUrl as a conventional HTTP(S) URL without credentials.\n *\n * Four rules, all learned from #47:\n *\n * The value is concatenated as `${base}${path}` for every upstream call, so it\n * must be something Undici will accept. A relative or non-HTTP value fails at\n * request time as an unrelated-looking fetch error; failing here names the key.\n *\n * It must be written with an explicit `http://` or `https://` authority. See\n * conventionalHttpUrlPattern: the shorthand forms WHATWG also accepts are\n * unfindable in log text, so a configured secret in one of them cannot be\n * redacted by anything downstream. Rejecting is the only safe answer, and this\n * is the only place with enough context to give it.\n *\n * Raw quote-like characters, angle brackets, whitespace and control bytes are\n * rejected for the same reason - see unsafeRawDelimiterPattern. They are what\n * marks the end of a URL in rendered text, so a value containing one cannot be\n * matched whole afterwards. Their percent-encoded forms are accepted.\n *\n * URL userinfo (`https://user:pass@host`) is rejected rather than passed\n * through. Undici refuses it at request time anyway, so accepting it buys only\n * a confusing failure plus a credential written to the log on every start.\n *\n * The accepted value is returned as the trimmed original, never URL-normalized.\n * Round-tripping through URL would add a trailing slash, lowercase the host and\n * re-encode the path, silently changing both the request URL and what gets\n * written back to config.yaml.\n */\nexport const normalizeCopilotBaseUrl = (value: unknown): string | undefined => {\n const trimmed = normalizeString(value)\n if (trimmed === undefined) {\n return undefined\n }\n\n // Before new URL(), because the shorthand this rejects parses successfully -\n // it is a valid URL, just not one that can be redacted later.\n if (!conventionalHttpUrlPattern.test(trimmed)) {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n // Also before new URL(), for two reasons: it accepts every one of these, and\n // it silently strips tab and newline - so by the time it returned, the value\n // stored in config.yaml would no longer match the URL actually requested.\n if (unsafeRawDelimiterPattern.test(trimmed)) {\n throw new Error(copilotBaseUrlDelimiterMessage)\n }\n\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(invalidCopilotBaseUrlMessage)\n }\n\n if (parsed.username || parsed.password) {\n throw new Error(copilotBaseUrlCredentialsMessage)\n }\n\n return trimmed\n}\n\nexport const normalizeThinkEffort = (\n value: unknown,\n): ReasoningEffort | undefined => {\n if (typeof value !== \"string\") {\n return undefined\n }\n const normalized = value.toLowerCase() === \"minimal\" ? \"low\" : value.toLowerCase()\n return isReasoningEffort(normalized) ? normalized : undefined\n}\n\nexport const normalizeUpstreamTimeoutSeconds = (\n value: unknown,\n): number | undefined => {\n if (value === undefined) {\n return undefined\n }\n\n const timeout = normalizePositiveInteger(value)\n if (timeout === undefined) {\n throw new Error(\"Invalid upstreamTimeoutSeconds: expected a positive integer\")\n }\n\n return timeout\n}\n\nconst readRawConfig = async (): Promise<Record<string, unknown>> => {\n try {\n const content = await fs.readFile(paths.configPath, \"utf8\")\n return parseConfigYaml(content)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n for (const legacyPath of paths.legacyConfigPaths) {\n try {\n const content = await fs.readFile(legacyPath, \"utf8\")\n return parseConfigYaml(content)\n } catch (legacyError) {\n if ((legacyError as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw legacyError\n }\n }\n }\n return readDefaultConfigTemplate()\n }\n throw error\n }\n}\n\nconst readDefaultConfigTemplate = async (): Promise<Record<string, unknown>> => {\n let currentDir = dirname(fileURLToPath(import.meta.url))\n\n // Bundled code may run from src/ during development or from dist/ after\n // packaging, so walk upward until the package-level config template is found.\n while (true) {\n try {\n const content = await fs.readFile(\n resolve(currentDir, \"config.default.yaml\"),\n \"utf8\",\n )\n return parseConfigYaml(content)\n } catch {\n const parentDir = dirname(currentDir)\n if (parentDir === currentDir) {\n return {}\n }\n currentDir = parentDir\n }\n }\n}\n\nconst unquoteYamlScalar = (value: string): string => {\n const trimmed = value.trim()\n if (\n (trimmed.startsWith('\"') && trimmed.endsWith('\"'))\n || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))\n ) {\n return trimmed.slice(1, -1)\n }\n return trimmed\n}\n\nconst parseConfigYaml = (content: string): Record<string, unknown> => {\n const config: Record<string, unknown> = {}\n for (const line of content.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue\n }\n\n const match = trimmed.match(/^([A-Za-z][A-Za-z0-9_-]*)\\s*:\\s*(.*?)\\s*$/)\n if (!match) {\n throw new Error(`Invalid config line in ${paths.configPath}: ${line}`)\n }\n\n const [, key, value] = match\n // The runtime config intentionally supports only a flat key/value YAML\n // subset. That keeps startup dependency-free and makes bad edits fail\n // predictably instead of being partially interpreted.\n switch (key) {\n case \"claudeSetup\":\n case \"claude_setup\": {\n config.claudeSetup = unquoteYamlScalar(value)\n break\n }\n case \"copilotBaseUrl\":\n case \"copilot_base_url\": {\n config.copilotBaseUrl = unquoteYamlScalar(value)\n break\n }\n case \"host\": {\n config.host = unquoteYamlScalar(value)\n break\n }\n case \"gptModel\":\n case \"gpt_model\": {\n config.gptModel = unquoteYamlScalar(value)\n break\n }\n case \"logLevel\":\n case \"log_level\": {\n config.logLevel = unquoteYamlScalar(value)\n break\n }\n case \"logRetentionDays\":\n case \"log_retention_days\": {\n config.logRetentionDays = unquoteYamlScalar(value)\n break\n }\n case \"opusModel\":\n case \"opus_model\": {\n config.opusModel = unquoteYamlScalar(value)\n break\n }\n case \"port\": {\n config.port = unquoteYamlScalar(value)\n break\n }\n case \"thinkEffort\":\n case \"think_effort\": {\n config.thinkEffort = unquoteYamlScalar(value)\n break\n }\n case \"upstreamTimeoutSeconds\":\n case \"upstream_timeout_seconds\": {\n config.upstreamTimeoutSeconds = unquoteYamlScalar(value)\n break\n }\n case \"webSearchBackend\":\n case \"web_search_backend\": {\n config.webSearchBackend = unquoteYamlScalar(value)\n break\n }\n default: {\n break\n }\n }\n }\n return config\n}\n\nconst writeConfig = async (config: AppConfig): Promise<void> => {\n await fs.mkdir(paths.appDir, { recursive: true })\n const content = serializeConfig(config)\n const existing = await fs.readFile(paths.configPath, \"utf8\").catch(() => \"\")\n if (existing === content) {\n return\n }\n await fs.writeFile(paths.configPath, content, { mode: 0o600 })\n}\n\nconst serializeConfig = (config: AppConfig): string =>\n [\n \"# copilot-relay configuration\",\n \"#\",\n \"# This file is hot-reloaded while copilot-relay is running.\",\n \"\",\n \"# Local host for the Claude Code-compatible HTTP server.\",\n `host: ${config.host}`,\n \"\",\n \"# Local port for the HTTP server.\",\n `port: ${config.port}`,\n \"\",\n \"# GitHub Copilot API base URL.\",\n `copilotBaseUrl: ${config.copilotBaseUrl}`,\n \"\",\n \"# Update ~/.claude/settings.json on start.\",\n `claudeSetup: ${config.claudeSetup}`,\n \"\",\n \"# Log verbosity:\",\n \"# error - startup/preflight/request failures only\",\n \"# info - error logs plus startup status, preflight status, and local HTTP\",\n \"# status codes\",\n \"# debug - info logs plus model routing summaries, Copilot upstream timings,\",\n \"# and request payloads\",\n `logLevel: ${config.logLevel}`,\n \"\",\n \"# Number of days to keep files in ~/.copilot-relay/logs.\",\n `logRetentionDays: ${config.logRetentionDays}`,\n \"\",\n \"# Default upstream thinking/reasoning effort: none, low, medium, high, xhigh, max.\",\n `thinkEffort: ${config.thinkEffort}`,\n \"\",\n \"# Max seconds to wait for a single Claude request's upstream Copilot calls.\",\n `upstreamTimeoutSeconds: ${config.upstreamTimeoutSeconds}`,\n \"\",\n \"# Copilot model used for bridge-managed Claude WebSearch. Empty uses gptModel.\",\n `webSearchBackend: ${config.webSearchBackend ?? \"\"}`,\n \"\",\n \"# Model routing: requests containing \\\"opus\\\" use opusModel; all others use gptModel.\",\n \"\",\n \"# Upstream Copilot model used for non-Opus requests.\",\n `gptModel: ${config.gptModel}`,\n \"\",\n \"# Upstream Copilot model used for requests containing \\\"opus\\\".\",\n `opusModel: ${config.opusModel}`,\n \"\",\n ].join(\"\\n\")\n\nexport async function readAppConfig(): Promise<AppConfig> {\n const raw = await readRawConfig()\n const claudeSetup = normalizeBoolean(raw.claudeSetup)\n const host = normalizeString(raw.host)\n const logLevel = normalizeLogLevel(raw.logLevel)\n const logRetentionDays = normalizePositiveInteger(raw.logRetentionDays)\n const port = normalizePort(raw.port)\n const thinkEffort = normalizeThinkEffort(raw.thinkEffort)\n const upstreamTimeoutSeconds = normalizeUpstreamTimeoutSeconds(\n raw.upstreamTimeoutSeconds,\n )\n\n // Every field follows the same rule: whatever the user's config holds wins,\n // and a shipped default applies only where the key is absent.\n const config: AppConfig = {\n claudeSetup: claudeSetup ?? defaultConfig.claudeSetup,\n copilotBaseUrl:\n normalizeCopilotBaseUrl(raw.copilotBaseUrl) ?? defaultConfig.copilotBaseUrl,\n gptModel: normalizeString(raw.gptModel) ?? defaultConfig.gptModel,\n host: host ?? defaultConfig.host,\n logLevel: logLevel ?? defaultConfig.logLevel,\n logRetentionDays: logRetentionDays ?? defaultConfig.logRetentionDays,\n opusModel: normalizeString(raw.opusModel) ?? defaultConfig.opusModel,\n port: port ?? defaultConfig.port,\n thinkEffort: thinkEffort ?? defaultConfig.thinkEffort,\n upstreamTimeoutSeconds:\n upstreamTimeoutSeconds ?? defaultConfig.upstreamTimeoutSeconds,\n webSearchBackend: normalizeString(raw.webSearchBackend),\n }\n\n await writeConfig(config)\n return config\n}\n\nconst getConfigMtime = async (): Promise<number> => {\n try {\n return (await fs.stat(paths.configPath)).mtimeMs\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return 0\n }\n throw error\n }\n}\n\nexport const watchAppConfig = (\n onReload: (config: AppConfig) => void,\n): ReturnType<typeof setInterval> => {\n let lastMtime = 0\n\n const timer = setInterval(async () => {\n try {\n const nextMtime = await getConfigMtime()\n if (nextMtime === lastMtime) {\n return\n }\n\n lastMtime = nextMtime\n onReload(await readAppConfig())\n } catch {\n // Config editors can briefly write invalid partial files. Keep the last\n // good runtime config rather than degrading live requests mid-edit.\n }\n }, 1000)\n\n if (typeof timer.unref === \"function\") {\n timer.unref()\n }\n\n void getConfigMtime().then((mtime) => {\n lastMtime = mtime\n })\n\n return timer\n}\n","// Converts hot-loaded app config into the mutable runtime config shared with routes.\nimport type { AppConfig } from \"~/lib/app-config\"\n\nconst vscodeVersion = \"1.99.3\"\n\nexport interface ProxyConfig {\n host: string\n port: number\n copilotBaseUrl: string\n copilotToken: string | undefined\n upstreamTimeoutMs: number\n vsCodeVersion: string\n webSearchBackend?: string\n}\n\nexport interface ProxyEnv {\n Variables: {\n config: ProxyConfig\n requestErrorMessage?: string\n requestId: string\n }\n}\n\nexport const readProxyConfig = (config: AppConfig): ProxyConfig => ({\n copilotBaseUrl: config.copilotBaseUrl,\n copilotToken: undefined,\n host: config.host,\n port: config.port,\n upstreamTimeoutMs: config.upstreamTimeoutSeconds * 1000,\n vsCodeVersion: vscodeVersion,\n webSearchBackend: config.webSearchBackend,\n})\n","// `copilot-relay auth`: performs an explicit device-login flow and syncs tokens.\nimport { defineCommand } from \"citty\"\n\nimport { setupProxyAuth } from \"~/lib/auth\"\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub device auth and cache credentials for copilot-relay.\",\n },\n async run() {\n const config = readProxyConfig(await readAppConfig())\n\n config.copilotToken = undefined\n\n const authSession = await setupProxyAuth(config, {\n force: true,\n })\n\n if (authSession.githubLogin) {\n log.info(`Logged in as ${authSession.githubLogin}`)\n }\n\n log.info(\"copilot-relay auth completed\")\n },\n})\n","// Reads package.json version from either source or bundled dist layouts.\nimport { readFileSync } from \"node:fs\"\nimport { dirname, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\ninterface PackageJson {\n name?: string\n version?: string\n}\n\nconst packageName = \"copilot-relay\"\n\nconst readPackageVersion = (): string => {\n let currentDir = dirname(fileURLToPath(import.meta.url))\n\n while (true) {\n try {\n const packageJsonPath = resolve(currentDir, \"package.json\")\n const packageJson = JSON.parse(\n readFileSync(packageJsonPath, \"utf8\"),\n ) as PackageJson\n\n if (\n packageJson.name === packageName\n && typeof packageJson.version === \"string\"\n ) {\n return packageJson.version\n }\n } catch {\n // Keep walking upward; source and dist builds sit at different depths.\n }\n\n const parentDir = dirname(currentDir)\n if (parentDir === currentDir) {\n return \"unknown\"\n }\n\n currentDir = parentDir\n }\n}\n\nexport const appVersion = readPackageVersion()\n","// Process lifecycle helpers for stopping stale local relay instances.\nimport { execFile } from \"node:child_process\"\nimport fs from \"node:fs/promises\"\nimport { promisify } from \"node:util\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\nimport { paths } from \"~/lib/paths\"\nimport { appVersion } from \"~/lib/version\"\n\nconst execFileAsync = promisify(execFile)\nconst stopTimeoutMs = 5_000\nconst stopPollMs = 100\n\nexport interface RelayPidFile {\n host: string\n pid: number\n port: number\n startedAt: string\n /**\n * The version of the build that wrote this file — the daemon's, never the\n * caller's. Optional because a file written before v0.3.1 has no such field,\n * and because it must be readable even when the daemon is not yet healthy.\n */\n version?: string\n}\n\nconst isNodeErrno = (error: unknown): error is NodeJS.ErrnoException =>\n typeof error === \"object\" && error !== null && \"code\" in error\n\nconst sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nconst parsePid = (value: unknown): number | undefined => {\n const pid =\n typeof value === \"number\" ? value\n : typeof value === \"string\" ? Number.parseInt(value.trim(), 10)\n : Number.NaN\n\n return Number.isInteger(pid) && pid > 0 ? pid : undefined\n}\n\nconst readCommandOutput = async (\n file: string,\n args: Array<string>,\n): Promise<string> => {\n try {\n const { stdout } = await execFileAsync(file, args, {\n maxBuffer: 1024 * 1024,\n windowsHide: true,\n })\n return String(stdout)\n } catch (error) {\n if (\n typeof error === \"object\"\n && error !== null\n && \"stdout\" in error\n && (typeof error.stdout === \"string\" || Buffer.isBuffer(error.stdout))\n ) {\n return String(error.stdout)\n }\n return \"\"\n }\n}\n\nconst parsePidLines = (output: string): Array<number> =>\n output\n .split(/\\r?\\n/)\n .map((line) => parsePid(line))\n .filter((pid): pid is number => pid !== undefined)\n\nexport const isRelayStartProcess = (\n command: string,\n cwd?: string,\n): boolean => {\n const normalizedCommand = command.replaceAll(\"\\\\\", \"/\")\n const normalizedCwd = cwd?.replaceAll(\"\\\\\", \"/\")\n const hasStartArg = /(?:^|\\s)start(?:\\s|$)/.test(normalizedCommand)\n\n if (!hasStartArg) {\n return false\n }\n\n if (normalizedCommand.includes(\"copilot-relay\")) {\n return true\n }\n\n const usesNode = /(?:^|\\s|\\/)node(?:\\.exe)?(?:\\s|$)/i.test(normalizedCommand)\n const usesDistMain = /(?:^|\\s|\\/)dist\\/main\\.js(?:\\s|$)/.test(\n normalizedCommand,\n )\n\n return Boolean(\n usesNode\n && usesDistMain\n && normalizedCwd\n && normalizedCwd.split(\"/\").some((part) => part.includes(\"copilot-relay\")),\n )\n}\n\nconst isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return isNodeErrno(error) && error.code === \"EPERM\"\n }\n}\n\n/**\n * Full pid-file record, for callers that need the listening address or start\n * time rather than just the pid.\n *\n * Deliberately tolerant: a corrupt or half-written pid file yields undefined\n * rather than throwing, because `status` must be able to report \"not running\"\n * instead of crashing on a file the user may have been mid-edit on.\n *\n * Returns undefined for the legacy bare-pid format, which carries no address.\n */\nexport const readRelayPidFileEntry = async (): Promise<\n RelayPidFile | undefined\n> => {\n try {\n const content = await fs.readFile(paths.pidPath, \"utf8\")\n const trimmed = content.trim()\n if (!trimmed || !trimmed.startsWith(\"{\")) {\n return undefined\n }\n\n const payload = JSON.parse(trimmed) as Partial<RelayPidFile>\n const pid = parsePid(payload.pid)\n if (\n pid === undefined\n || typeof payload.host !== \"string\"\n || typeof payload.port !== \"number\"\n ) {\n return undefined\n }\n\n return {\n host: payload.host,\n pid,\n port: payload.port,\n startedAt:\n typeof payload.startedAt === \"string\" ? payload.startedAt : \"\",\n ...(typeof payload.version === \"string\" && payload.version ?\n { version: payload.version }\n : {}),\n }\n } catch {\n return undefined\n }\n}\n\nconst readRelayPidFile = async (): Promise<number | undefined> => {\n try {\n const content = await fs.readFile(paths.pidPath, \"utf8\")\n const trimmed = content.trim()\n if (!trimmed) {\n return undefined\n }\n\n if (trimmed.startsWith(\"{\")) {\n const payload = JSON.parse(trimmed) as Partial<RelayPidFile>\n return parsePid(payload.pid)\n }\n\n return parsePid(trimmed)\n } catch (error) {\n if (isNodeErrno(error) && error.code === \"ENOENT\") {\n return undefined\n }\n throw error\n }\n}\n\nexport const writeRelayPidFile = async (\n config: Pick<ProxyConfig, \"host\" | \"port\">,\n): Promise<void> => {\n await fs.mkdir(paths.appDir, { recursive: true })\n const payload: RelayPidFile = {\n host: config.host,\n pid: process.pid,\n port: config.port,\n startedAt: new Date().toISOString(),\n // Written by the daemon at startup, so it is the build actually serving —\n // unlike `status`, which used to report the version of whichever CLI was\n // invoked. See #43. Covers the window before the daemon answers /healthz.\n version: appVersion,\n }\n await fs.writeFile(paths.pidPath, `${JSON.stringify(payload, null, 2)}\\n`, {\n mode: 0o600,\n })\n}\n\nexport const clearRelayPidFile = async (\n pid = process.pid,\n options: { force?: boolean } = {},\n): Promise<void> => {\n const currentPid = await readRelayPidFile()\n if (!options.force && currentPid !== undefined && currentPid !== pid) {\n return\n }\n\n await fs.unlink(paths.pidPath).catch((error: unknown) => {\n if (!isNodeErrno(error) || error.code !== \"ENOENT\") {\n throw error\n }\n })\n}\n\nconst getProcessCommand = async (pid: number): Promise<string | undefined> => {\n if (process.platform === \"win32\") {\n const output = await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\" | Select-Object -ExpandProperty CommandLine`,\n ])\n return output.trim() || undefined\n }\n\n const output = await readCommandOutput(\"ps\", [\n \"-p\",\n String(pid),\n \"-o\",\n \"command=\",\n ])\n return output.trim() || undefined\n}\n\nconst getProcessCwd = async (pid: number): Promise<string | undefined> => {\n if (process.platform === \"win32\") {\n return undefined\n }\n\n const output = await readCommandOutput(\"lsof\", [\n \"-a\",\n \"-p\",\n String(pid),\n \"-d\",\n \"cwd\",\n \"-Fn\",\n ])\n return output\n .split(/\\r?\\n/)\n .find((line) => line.startsWith(\"n\"))\n ?.slice(1)\n}\n\nconst getPortListenerPids = async (port: number): Promise<Array<number>> => {\n if (process.platform === \"win32\") {\n return parsePidLines(await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique`,\n ]))\n }\n\n return parsePidLines(await readCommandOutput(\"lsof\", [\n \"-nP\",\n `-iTCP:${port}`,\n \"-sTCP:LISTEN\",\n \"-t\",\n ]))\n}\n\nconst getProcessList = async (): Promise<Array<{ command: string; pid: number }>> => {\n const output =\n process.platform === \"win32\" ?\n await readCommandOutput(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n \"Get-CimInstance Win32_Process | ForEach-Object { \\\"$($_.ProcessId)`t$($_.CommandLine)\\\" }\",\n ])\n : await readCommandOutput(\"ps\", [\"-axo\", \"pid=,command=\"])\n\n return output\n .split(/\\r?\\n/)\n .flatMap((line) => {\n const match =\n process.platform === \"win32\" ?\n line.match(/^(\\d+)\\t(.+)$/)\n : line.match(/^\\s*(\\d+)\\s+(.+)$/)\n const pid = parsePid(match?.[1])\n const command = match?.[2]?.trim()\n return pid && command ? [{ command, pid }] : []\n })\n}\n\nconst isRelayPid = async (pid: number): Promise<boolean> => {\n if (pid === process.pid || !isProcessAlive(pid)) {\n return false\n }\n\n const command = await getProcessCommand(pid)\n if (!command) {\n return false\n }\n\n return isRelayStartProcess(command, await getProcessCwd(pid))\n}\n\n/**\n * The relay serving `config.port`, or undefined if nothing is.\n *\n * Deliberately narrower than findRelayProcessIds: it never consults the global\n * process list. `stop` wants \"any relay anywhere\" so it can clean up strays;\n * `status` is asked about one configured relay, and answering with a different\n * one is worse than answering \"not running\" because it looks authoritative.\n *\n * Returns pid and address as a single coherent record, so a pid discovered one\n * way can never be printed next to an address taken from another.\n */\nexport const findRelayOnPort = async (\n config: Pick<ProxyConfig, \"host\" | \"port\">,\n): Promise<RelayPidFile | undefined> => {\n // The pid file is preferred when it describes this port: it is the only\n // source carrying host and startedAt, and its port is where the socket is\n // actually bound - hot reload changes config.port without rebinding.\n const entry = await readRelayPidFileEntry()\n if (\n entry\n && entry.port === config.port\n && isProcessAlive(entry.pid)\n && (await isRelayPid(entry.pid))\n ) {\n return entry\n }\n\n // No usable pid file - fall back to whoever holds the port. Covers a relay\n // started before pid files, or one whose pid file was removed. No version\n // here on purpose: nothing in this path knows the daemon's build, and\n // borrowing the caller's would recreate #43.\n for (const pid of await getPortListenerPids(config.port)) {\n if (await isRelayPid(pid)) {\n return {\n host: config.host,\n pid,\n port: config.port,\n startedAt: \"\",\n }\n }\n }\n\n return undefined\n}\n\nexport const findRelayProcessIds = async (\n config: Pick<ProxyConfig, \"port\">,\n): Promise<Array<number>> => {\n const candidates = new Set<number>()\n const storedPid = await readRelayPidFile()\n if (storedPid) {\n candidates.add(storedPid)\n }\n\n for (const pid of await getPortListenerPids(config.port)) {\n candidates.add(pid)\n }\n\n for (const { command, pid } of await getProcessList()) {\n if (pid === process.pid) {\n continue\n }\n\n if (command.includes(\"copilot-relay\") || command.includes(\"dist/main.js\")) {\n const cwd = await getProcessCwd(pid)\n if (isRelayStartProcess(command, cwd)) {\n candidates.add(pid)\n }\n }\n }\n\n const relayPids: Array<number> = []\n for (const pid of candidates) {\n if (await isRelayPid(pid)) {\n relayPids.push(pid)\n }\n }\n return [...new Set(relayPids)].sort((left, right) => left - right)\n}\n\nconst waitForExit = async (pid: number, timeoutMs: number): Promise<boolean> => {\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid)) {\n return true\n }\n await sleep(stopPollMs)\n }\n return !isProcessAlive(pid)\n}\n\nconst stopProcess = async (pid: number): Promise<void> => {\n if (!isProcessAlive(pid)) {\n return\n }\n\n log.info(`Stopping existing copilot-relay process pid=${pid}`)\n process.kill(pid, \"SIGTERM\")\n if (await waitForExit(pid, stopTimeoutMs)) {\n return\n }\n\n log.error(`copilot-relay process pid=${pid} did not stop; forcing termination`)\n process.kill(pid, \"SIGKILL\")\n if (!(await waitForExit(pid, stopTimeoutMs))) {\n throw new Error(`Could not stop copilot-relay process pid=${pid}`)\n }\n}\n\nexport const stopExistingRelay = async (\n config: Pick<ProxyConfig, \"port\">,\n): Promise<Array<number>> => {\n const relayPids = await findRelayProcessIds(config)\n if (relayPids.length === 0) {\n log.info(\"No existing copilot-relay instance found\")\n await clearRelayPidFile(0, { force: true })\n return []\n }\n\n for (const pid of relayPids) {\n await stopProcess(pid)\n }\n await clearRelayPidFile(0, { force: true })\n return relayPids\n}\n","// Optional writer for Claude Code settings so it points at the local proxy.\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { normalizeClaudeModelId } from \"~/lib/models\"\n\ninterface ApplyClaudeConfigInput {\n baseUrl: string\n configPath: string\n gptModel: string\n}\n\ninterface ApplyClaudeResult {\n configPath: string\n changed: boolean\n created: boolean\n previousBaseUrl?: string\n}\n\nconst knownModelEnvKeys = [\n \"ANTHROPIC_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n \"ANTHROPIC_DEFAULT_FABLE_MODEL\",\n \"ANTHROPIC_SMALL_FAST_MODEL\",\n \"CLAUDE_CODE_SUBAGENT_MODEL\",\n] as const\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n\nconst hasMalformedModelOverride = (\n parsed: Record<string, unknown>,\n): boolean => {\n if (parsed.model !== undefined && typeof parsed.model !== \"string\") {\n return true\n }\n\n const env = parsed.env\n if (!isRecord(env)) {\n return false\n }\n\n return knownModelEnvKeys.some(\n (key) => env[key] !== undefined && typeof env[key] !== \"string\",\n )\n}\n\nconst hasPrimaryModelOverride = (\n parsed: Record<string, unknown>,\n env: Record<string, unknown>,\n): boolean =>\n typeof parsed.model === \"string\"\n || typeof env.ANTHROPIC_MODEL === \"string\"\n\n/**\n * Update `~/.claude/settings.json` so its env block points Claude Code at the\n * running proxy. Preserves unrelated keys and model choices while normalizing\n * managed one-million-context GPT overrides. Sets a dummy auth token only if absent.\n */\nexport async function applyClaudeConfig(\n input: ApplyClaudeConfigInput,\n): Promise<ApplyClaudeResult> {\n const { configPath, baseUrl, gptModel } = input\n\n let raw = \"\"\n let created = false\n try {\n raw = await fs.readFile(configPath, \"utf8\")\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n created = true\n } else {\n throw error\n }\n }\n\n let parsed: Record<string, unknown> = {}\n if (!created && raw.trim().length === 0) {\n return { configPath, changed: false, created: false }\n }\n if (raw.trim().length > 0) {\n try {\n const value = JSON.parse(raw) as unknown\n if (\n !isRecord(value)\n || (value.env !== undefined && !isRecord(value.env))\n || hasMalformedModelOverride(value)\n ) {\n return { configPath, changed: false, created: false }\n }\n parsed = value\n } catch {\n // Refuse to overwrite a malformed file; bail without changes.\n return { configPath, changed: false, created: false }\n }\n }\n\n const env: Record<string, unknown> = isRecord(parsed.env) ? { ...parsed.env } : {}\n\n const previousBaseUrl =\n typeof env.ANTHROPIC_BASE_URL === \"string\"\n ? (env.ANTHROPIC_BASE_URL as string)\n : undefined\n const hadPrimaryModelOverride = hasPrimaryModelOverride(parsed, env)\n const claudeGptModel = normalizeClaudeModelId(gptModel)\n const shouldSeedTopLevelModel = !hadPrimaryModelOverride\n const nextModel =\n typeof parsed.model === \"string\"\n ? normalizeClaudeModelId(parsed.model)\n : undefined\n\n // ANTHROPIC_MODEL outranks the saved model setting, so only normalize it\n // when the user already set it; managed defaults belong in `model`.\n for (const key of knownModelEnvKeys) {\n if (typeof env[key] === \"string\") {\n env[key] = normalizeClaudeModelId(env[key])\n }\n }\n\n env.ANTHROPIC_BASE_URL = baseUrl\n // Claude Code only requires a syntactically present auth token here; real\n // upstream authentication is handled by copilot-relay's Copilot token.\n if (typeof env.ANTHROPIC_AUTH_TOKEN !== \"string\" || !env.ANTHROPIC_AUTH_TOKEN) {\n env.ANTHROPIC_AUTH_TOKEN = \"dummy\"\n }\n\n const next: Record<string, unknown> = {\n ...parsed,\n ...(nextModel !== undefined && { model: nextModel }),\n ...(shouldSeedTopLevelModel && { model: claudeGptModel }),\n env,\n }\n const serialized = `${JSON.stringify(next, null, 2)}\\n`\n\n if (serialized === raw) {\n return { configPath, changed: false, created: false, previousBaseUrl }\n }\n\n await fs.mkdir(path.dirname(configPath), { recursive: true })\n await fs.writeFile(configPath, serialized)\n return { configPath, changed: true, created, previousBaseUrl }\n}\n","// Static process defaults that are not intended to be user-tuned.\nimport path from \"node:path\"\nimport os from \"node:os\"\n\n/**\n * Static defaults for copilot-relay. Previously these lived in\n * `~/.config/copilot-relay/settings.json`, but that file became an implicit\n * third source of truth alongside `~/.claude/settings.json`, which caused\n * subtle port-mismatch bugs. copilot-relay now relies on these defaults plus\n * CLI flags only.\n */\n\nexport const defaultHost = \"127.0.0.1\"\nexport const defaultPort = 4142\n\nexport const claudeConfigPath = path.join(\n os.homedir(),\n \".claude\",\n \"settings.json\",\n)\n","// Low-level GitHub Copilot HTTP client: adds required headers, retries transient failures, and logs timing.\nimport { randomUUID } from \"node:crypto\"\n\nimport { Agent, fetch as undiciFetch } from \"undici\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError, ProxyNotImplementedError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\n\nconst copilotVersion = \"0.26.7\"\nconst editorPluginVersion = `copilot-chat/${copilotVersion}`\nconst userAgent = `GitHubCopilotChat/${copilotVersion}`\nconst apiVersion = \"2025-04-01\"\nconst maxFetchAttempts = 2\nexport const copilotRequestTimeoutMs = 180_000\nconst copilotDispatcher = new Agent({\n allowH2: false,\n connect: { allowH2: false },\n})\n\nexport interface CopilotProviderContext {\n baseUrl: string\n token: string | undefined\n vsCodeVersion: string\n}\n\nexport const getCopilotProviderContext = (\n config: ProxyConfig,\n): CopilotProviderContext => ({\n baseUrl: config.copilotBaseUrl,\n token: config.copilotToken,\n vsCodeVersion: config.vsCodeVersion,\n})\n\nexport interface FetchCopilotOptions {\n vision?: boolean\n initiator?: \"agent\" | \"user\"\n requestId?: string\n signal?: AbortSignal\n timeoutMs?: number\n}\n\ninterface CopilotRequestInit {\n body?: string\n headers?: RequestInit[\"headers\"]\n method?: string\n}\n\nconst shouldRetryResponse = (response: Response): boolean =>\n // Retry only transient upstream failures; 4xx responses may contain routing\n // signals, such as \"unsupported_api_for_model\", that callers need to inspect.\n response.status >= 500 && response.status <= 599\n\nexport const createCopilotRequestSignal = (\n signal?: AbortSignal,\n timeoutMs = copilotRequestTimeoutMs,\n): AbortSignal | undefined => {\n const signals = [\n signal,\n timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined,\n ].filter((value): value is AbortSignal => value !== undefined)\n\n if (signals.length === 0) {\n return undefined\n }\n\n return signals.length === 1 ? signals[0] : AbortSignal.any(signals)\n}\n\nconst getAbortName = (value: unknown): string | undefined =>\n typeof value === \"object\"\n && value !== null\n && \"name\" in value\n && typeof value.name === \"string\" ?\n value.name\n : undefined\n\nconst isAbortLikeError = (error: unknown): boolean => {\n const name = getAbortName(error)\n return name === \"AbortError\" || name === \"TimeoutError\"\n}\n\nconst getErrorCode = (value: unknown): string | undefined =>\n typeof value === \"object\"\n && value !== null\n && \"code\" in value\n && typeof value.code === \"string\" ?\n value.code\n : undefined\n\nexport const isRetryableFetchError = (error: unknown): boolean =>\n getErrorCode(error) === \"ERR_HTTP2_INVALID_SESSION\"\n || (\n typeof error === \"object\"\n && error !== null\n && \"cause\" in error\n && getErrorCode(error.cause) === \"ERR_HTTP2_INVALID_SESSION\"\n )\n\nexport const toCopilotAbortHTTPError = (\n error: unknown,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): HTTPError | undefined => {\n if (!signal?.aborted && !isAbortLikeError(error)) {\n return undefined\n }\n\n const timedOut =\n getAbortName(signal?.reason) === \"TimeoutError\"\n || getAbortName(error) === \"TimeoutError\"\n const message =\n timedOut ?\n `Copilot upstream request timed out after ${Math.round(timeoutMs / 1000)}s.`\n : \"Client request cancelled before Copilot upstream completed.\"\n const code = timedOut ? \"upstream_timeout\" : \"request_cancelled\"\n\n return new HTTPError(\n message,\n new Response(JSON.stringify({ error: { message, code } }), {\n status: timedOut ? 504 : 499,\n headers: { \"content-type\": \"application/json\" },\n }),\n message,\n )\n}\n\nexport const readCopilotJson = async <T>(\n response: Response,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): Promise<T> => {\n try {\n return (await response.json()) as T\n } catch (error) {\n throw toCopilotAbortHTTPError(error, signal, timeoutMs) ?? error\n }\n}\n\nexport const readCopilotText = async (\n response: Response,\n signal: AbortSignal | undefined,\n timeoutMs = copilotRequestTimeoutMs,\n): Promise<string> => {\n try {\n return await response.text()\n } catch (error) {\n throw toCopilotAbortHTTPError(error, signal, timeoutMs) ?? error\n }\n}\n\nconst buildHeaders = (\n provider: CopilotProviderContext,\n init: CopilotRequestInit,\n options: FetchCopilotOptions,\n upstreamRequestId: string,\n): Record<string, string> => {\n const headers = new Headers(init.headers)\n\n headers.set(\"authorization\", `Bearer ${provider.token}`)\n headers.set(\"copilot-integration-id\", \"vscode-chat\")\n headers.set(\"editor-version\", `vscode/${provider.vsCodeVersion}`)\n headers.set(\"editor-plugin-version\", editorPluginVersion)\n headers.set(\"user-agent\", userAgent)\n headers.set(\"openai-intent\", \"conversation-panel\")\n headers.set(\"x-github-api-version\", apiVersion)\n headers.set(\"x-request-id\", upstreamRequestId)\n headers.set(\"x-vscode-user-agent-library-version\", \"electron-fetch\")\n\n if (options.vision) {\n headers.set(\"copilot-vision-request\", \"true\")\n }\n\n if (options.initiator) {\n headers.set(\"x-initiator\", options.initiator)\n }\n\n if (!headers.has(\"content-type\") && init.body !== undefined) {\n headers.set(\"content-type\", \"application/json\")\n }\n\n if (!headers.has(\"accept\")) {\n headers.set(\"accept\", \"application/json\")\n }\n\n return Object.fromEntries(headers.entries())\n}\n\nconst formatRequestId = (requestId: string | undefined): string =>\n requestId ? `request_id=${requestId} ` : \"\"\n\nconst logUpstreamLifecycle = (\n requestId: string | undefined,\n message: string,\n): void => {\n if (requestId) {\n log.info(`${formatRequestId(requestId)}${message}`)\n return\n }\n\n log.debug(message)\n}\n\nexport const fetchCopilot = async (\n provider: CopilotProviderContext,\n path: string,\n init: CopilotRequestInit,\n options: FetchCopilotOptions = {},\n) => {\n if (!provider.token) {\n throw new ProxyNotImplementedError(\n \"Copilot token is not configured for copilot-relay.\",\n )\n }\n\n const timeoutMs = options.timeoutMs ?? copilotRequestTimeoutMs\n const signal = createCopilotRequestSignal(options.signal, timeoutMs)\n let lastError: unknown\n\n for (let attempt = 1; attempt <= maxFetchAttempts; attempt++) {\n const upstreamRequestId = randomUUID()\n try {\n const started = performance.now()\n logUpstreamLifecycle(\n options.requestId,\n `send upstream method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n const response = await undiciFetch(`${provider.baseUrl}${path}`, {\n ...init,\n headers: buildHeaders(provider, init, options, upstreamRequestId),\n dispatcher: copilotDispatcher,\n signal,\n })\n const ms = Math.round(performance.now() - started)\n logUpstreamLifecycle(\n options.requestId,\n `return from upstream method=${init.method ?? \"GET\"} path=${path} status=${response.status} ms=${ms} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n log.debug(\n `${formatRequestId(options.requestId)}Copilot ${init.method ?? \"GET\"} ${path} -> ${response.status} ${ms}ms (attempt ${attempt}) upstream_request_id=${upstreamRequestId}`,\n )\n\n if (!shouldRetryResponse(response) || attempt === maxFetchAttempts) {\n return response\n }\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} returned ${response.status}; retrying (${attempt}/${maxFetchAttempts}) upstream_request_id=${upstreamRequestId}`,\n )\n } catch (error) {\n const abortError = toCopilotAbortHTTPError(error, signal, timeoutMs)\n if (abortError) {\n logUpstreamLifecycle(\n options.requestId,\n `upstream failed method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n throw abortError\n }\n\n logUpstreamLifecycle(\n options.requestId,\n `upstream failed method=${init.method ?? \"GET\"} path=${path} attempt=${attempt} upstream_request_id=${upstreamRequestId}`,\n )\n lastError = error\n if (attempt === maxFetchAttempts) {\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} request failed after ${attempt} attempts upstream_request_id=${upstreamRequestId}`,\n error,\n )\n throw error\n }\n log.error(\n `${formatRequestId(options.requestId)}Copilot ${path} request failed; retrying (${attempt}/${maxFetchAttempts}) upstream_request_id=${upstreamRequestId}`,\n error,\n )\n }\n }\n\n throw lastError\n}\n","// Internal conversion between Copilot Responses API payloads and chat-completion-like results.\nimport { createHash, randomUUID } from \"node:crypto\"\n\nimport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n} from \"~/copilot/types\"\n\nimport { sanitizeUserIdentifier } from \"./chat\"\n\nexport interface ResponseStreamEventMessage {\n data?: string\n event?: string\n}\n\nexport interface ResponsesApiResponse {\n id: string\n created_at: number\n model: string\n output: Array<ResponsesOutputItem>\n reasoning?: ResponsesReasoningSummaryContainer | null\n usage?: {\n input_tokens: number\n input_tokens_details?: {\n cached_tokens?: number\n }\n output_tokens: number\n output_tokens_details?: {\n reasoning_tokens?: number\n }\n total_tokens: number\n }\n incomplete_details?: {\n reason?: string | null\n } | null\n}\n\nexport type ResponsesReasoningEffort =\n | \"none\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n\ntype ResponsesInput = string | Array<ResponsesInputItem>\n\ntype ResponsesInputItem =\n | ResponsesMessageInput\n | ResponsesFunctionCallInput\n | ResponsesFunctionCallOutputInput\n\ntype ResponsesMessageInput = {\n role: \"user\" | \"assistant\" | \"system\" | \"developer\"\n content: string | Array<ResponsesInputContentPart>\n}\n\ntype ResponsesInputContentPart =\n | {\n type: \"input_text\"\n text: string\n }\n | {\n type: \"input_image\"\n image_url: string\n detail: \"low\" | \"high\" | \"auto\"\n }\n\ntype ResponsesFunctionCallInput = {\n type: \"function_call\"\n call_id: string\n name: string\n arguments: string\n}\n\ntype ResponsesFunctionCallOutputInput = {\n type: \"function_call_output\"\n call_id: string\n output: string\n}\n\ntype ResponsesToolChoice =\n | \"none\"\n | \"auto\"\n | \"required\"\n | { type: \"function\"; name: string }\n\nexport interface ResponsesRequestPayload {\n model: string\n input: ResponsesInput\n stream?: boolean | null\n max_output_tokens?: number | null\n temperature?: number | null\n top_p?: number | null\n user?: string | null\n prompt_cache_key?: string | null\n reasoning?: {\n effort: ResponsesReasoningEffort\n }\n tools?: Array<ResponsesTool>\n tool_choice?: ResponsesToolChoice | null\n text?: {\n format: {\n type: \"json_object\"\n }\n }\n}\n\ninterface ResponsesTool {\n type: \"function\"\n name: string\n description?: string\n parameters: Record<string, unknown>\n}\n\ntype ResponsesOutputItem =\n | ResponsesMessageOutputItem\n | ResponsesFunctionCallOutputItem\n | ResponsesReasoningOutputItem\n\ninterface ResponsesMessageOutputItem {\n type: \"message\"\n role: \"assistant\"\n content: Array<ResponsesMessageContentPart>\n}\n\ninterface ResponsesMessageContentPart {\n type: \"output_text\"\n text: string\n}\n\ninterface ResponsesFunctionCallOutputItem {\n type: \"function_call\"\n call_id: string\n name: string\n arguments: string\n}\n\ninterface ResponsesReasoningOutputItem {\n type: \"reasoning\"\n summary?: ResponsesReasoningSummary\n}\n\ntype ResponsesReasoningSummary =\n | string\n | Array<ResponsesReasoningSummaryPart>\n | null\n\ntype ResponsesReasoningSummaryPart = {\n type?: string\n text?: string\n}\n\ninterface ResponsesReasoningSummaryContainer {\n summary?: ResponsesReasoningSummary\n}\n\ninterface ResponsesStreamEnvelope {\n type: string\n response?: {\n id: string\n created_at: number\n model: string\n usage?: ResponsesApiResponse[\"usage\"]\n output?: Array<ResponsesOutputItem>\n reasoning?: ResponsesReasoningSummaryContainer | null\n incomplete_details?: ResponsesApiResponse[\"incomplete_details\"]\n }\n item?:\n | Partial<ResponsesMessageOutputItem>\n | Partial<ResponsesFunctionCallOutputItem>\n | Partial<ResponsesReasoningOutputItem>\n output_index?: number\n delta?: string\n text?: string\n arguments?: string\n}\n\ninterface ResponseTranslationState {\n responseId: string\n createdAt: number\n model: string\n started: boolean\n reasoningTextSent: boolean\n outputTextByIndex: Record<number, string>\n toolArgumentsByIndex: Record<number, string>\n toolStartedByIndex: Record<number, boolean>\n}\n\ninterface CreateChunkOptions {\n delta: ChatCompletionChunk[\"choices\"][0][\"delta\"]\n finishReason?: ChatCompletionChunk[\"choices\"][0][\"finish_reason\"]\n usage?: ChatCompletionChunk[\"usage\"]\n}\n\n// Keep the Responses-only allowlist narrow and suffix-tolerant. These models\n// reject /chat/completions with unsupported_api_for_model and must use /responses.\nconst responsesOnlyModelPattern = /^(?:gpt-5\\.5|gpt-5\\.6|gpt-6-astra)(?:-|$)/i\n\nexport function shouldUseResponsesApiForModel(model: string): boolean {\n return responsesOnlyModelPattern.test(model)\n}\n\nexport function buildResponsesRequestPayload(\n payload: ChatCompletionsPayload,\n reasoningEffort: ResponsesReasoningEffort | undefined,\n): ResponsesRequestPayload {\n // Responses API uses an `input` tree instead of chat `messages`, but the\n // rest of the bridge still reasons in chat-completion terms. Keep this as\n // the single boundary where chat payloads become Responses payloads.\n return {\n model: payload.model,\n input: translateMessagesToResponsesInput(payload.messages),\n stream: payload.stream,\n max_output_tokens: payload.max_tokens,\n temperature: payload.temperature,\n top_p: payload.top_p,\n user: sanitizeUserIdentifier(payload.user),\n // Copilot /responses only returns prompt cache hits when a stable\n // prompt_cache_key pins repeated requests to the same backend. Without it,\n // cached_tokens randomly drops to 0 across turns even for identical\n // prefixes (measured). Derive a per-conversation key so a Claude Code\n // session keeps hitting its own warm cache while staying isolated from\n // other sessions.\n prompt_cache_key: deriveResponsesCacheKey(payload),\n tools: translateTools(payload.tools),\n tool_choice: translateToolChoice(payload.tool_choice),\n reasoning: reasoningEffort ? { effort: reasoningEffort } : undefined,\n text:\n payload.response_format?.type === \"json_object\" ?\n { format: { type: \"json_object\" } }\n : undefined,\n }\n}\n\nfunction deriveResponsesCacheKey(\n payload: ChatCompletionsPayload,\n): string | undefined {\n // Prefer the client-supplied conversation id (Claude Code sends a stable\n // metadata.user_id per session). Fall back to a hash of the system prompt so\n // even clients without a user id still route a stable prefix to one cache.\n const user = sanitizeUserIdentifier(payload.user)\n if (user) {\n return `cr-${createHash(\"sha256\").update(user).digest(\"hex\").slice(0, 32)}`\n }\n\n const systemPrompt = payload.messages.find((message) => message.role === \"system\")\n const systemText =\n typeof systemPrompt?.content === \"string\" ? systemPrompt.content\n : Array.isArray(systemPrompt?.content) ?\n systemPrompt.content\n .map((part) => (part.type === \"text\" ? part.text : \"\"))\n .join(\"\")\n : \"\"\n\n if (!systemText) {\n return undefined\n }\n\n return `cr-sys-${createHash(\"sha256\").update(systemText).digest(\"hex\").slice(0, 32)}`\n}\n\nexport function translateResponsesToChatCompletion(\n response: ResponsesApiResponse,\n): ChatCompletionResponse {\n const assistantMessages = response.output.filter(\n (item): item is ResponsesMessageOutputItem => item.type === \"message\",\n )\n const functionCalls = response.output.filter(\n (item): item is ResponsesFunctionCallOutputItem =>\n item.type === \"function_call\",\n )\n\n const content = assistantMessages\n .flatMap((item) => item.content)\n .map((part) => part.text)\n .join(\"\")\n const reasoningText = getResponsesReasoningText(response)\n\n // Merge Responses output items back into one chat-completion choice so the\n // Claude translation layer can stay shared for /chat/completions and\n // /responses upstream calls.\n return {\n id: response.id,\n object: \"chat.completion\",\n created: response.created_at,\n model: response.model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: content || null,\n ...(reasoningText && { reasoning_text: reasoningText }),\n ...(functionCalls.length > 0 && {\n tool_calls: functionCalls.map((toolCall) => ({\n id: toolCall.call_id,\n type: \"function\" as const,\n function: {\n name: toolCall.name,\n arguments: toolCall.arguments,\n },\n })),\n }),\n },\n logprobs: null,\n finish_reason: getFinishReason(response, functionCalls.length > 0),\n },\n ],\n usage: translateUsage(response.usage),\n }\n}\n\nexport async function* translateResponsesStreamToChatCompletionStream(\n responseStream: AsyncIterable<ResponseStreamEventMessage>,\n): AsyncGenerator<ResponseStreamEventMessage> {\n // Responses streams can send deltas, done snapshots, and a final completed\n // payload. Track emitted suffixes so chat clients do not receive duplicates.\n const state: ResponseTranslationState = {\n responseId: randomUUID(),\n createdAt: Math.floor(Date.now() / 1000),\n model: \"\",\n started: false,\n reasoningTextSent: false,\n outputTextByIndex: {},\n toolArgumentsByIndex: {},\n toolStartedByIndex: {},\n }\n\n for await (const rawEvent of responseStream) {\n if (!rawEvent.data || rawEvent.data === \"[DONE]\") {\n continue\n }\n\n const event = JSON.parse(rawEvent.data) as ResponsesStreamEnvelope\n\n if (event.response) {\n state.responseId = event.response.id\n state.createdAt = event.response.created_at\n state.model = event.response.model\n }\n\n if (event.type === \"response.output_item.added\") {\n const chunk = handleOutputItemAdded(state, event)\n if (chunk) {\n yield chunk\n }\n continue\n }\n\n if (event.type === \"response.output_item.done\") {\n for (const chunk of handleOutputItemDone(state, event)) {\n yield chunk\n }\n continue\n }\n\n if (event.type === \"response.reasoning_summary_text.delta\") {\n if (event.delta) {\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { reasoning_text: event.delta } })\n state.reasoningTextSent = true\n }\n continue\n }\n\n if (event.type === \"response.output_text.delta\") {\n const outputIndex = event.output_index ?? 0\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${event.delta ?? \"\"}`\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { content: event.delta } })\n continue\n }\n\n if (event.type === \"response.output_text.done\") {\n const outputIndex = event.output_index ?? 0\n const content = getMissingSuffix(\n state.outputTextByIndex[outputIndex] ?? \"\",\n event.text,\n )\n if (content) {\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${content}`\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { content } })\n }\n continue\n }\n\n if (event.type === \"response.function_call_arguments.delta\") {\n if (event.output_index === undefined) {\n continue\n }\n\n state.toolArgumentsByIndex[event.output_index] = `${state.toolArgumentsByIndex[event.output_index] ?? \"\"}${event.delta ?? \"\"}`\n yield createRoleChunk(state)\n yield createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: event.output_index,\n type: \"function\",\n function: {\n arguments: event.delta ?? \"\",\n },\n },\n ],\n },\n })\n continue\n }\n\n if (event.type === \"response.function_call_arguments.done\") {\n if (event.output_index === undefined) {\n continue\n }\n\n const content = getMissingSuffix(\n state.toolArgumentsByIndex[event.output_index] ?? \"\",\n event.arguments,\n )\n if (content) {\n state.toolArgumentsByIndex[event.output_index] = `${state.toolArgumentsByIndex[event.output_index] ?? \"\"}${content}`\n yield createRoleChunk(state)\n yield createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: event.output_index,\n type: \"function\",\n function: { arguments: content },\n },\n ],\n },\n })\n }\n continue\n }\n\n if (event.type === \"response.completed\") {\n if (!event.response) {\n continue\n }\n\n const reasoningText = getResponsesReasoningText({\n output: event.response.output ?? [],\n reasoning: event.response.reasoning,\n })\n if (reasoningText && !state.reasoningTextSent) {\n yield createRoleChunk(state)\n yield createChunk(state, { delta: { reasoning_text: reasoningText } })\n state.reasoningTextSent = true\n }\n\n yield createChunk(\n {\n ...state,\n responseId: event.response.id,\n createdAt: event.response.created_at,\n model: event.response.model,\n },\n {\n delta: {},\n finishReason: getFinishReason(\n {\n output: event.response.output ?? [],\n incomplete_details: event.response.incomplete_details,\n },\n (event.response.output ?? []).some(\n (item) => item.type === \"function_call\",\n ),\n ),\n usage: translateUsage(event.response.usage),\n },\n )\n yield { data: \"[DONE]\" }\n return\n }\n }\n}\n\nfunction handleOutputItemAdded(\n state: ResponseTranslationState,\n event: ResponsesStreamEnvelope,\n): ResponseStreamEventMessage | undefined {\n if (event.item?.type === \"message\") {\n return createRoleChunk(state)\n }\n\n if (event.item?.type === \"reasoning\") {\n const reasoningText = getSummaryText(event.item.summary)\n if (!reasoningText) {\n return undefined\n }\n\n state.reasoningTextSent = true\n return createChunk(state, { delta: { reasoning_text: reasoningText } })\n }\n\n if (\n event.item?.type === \"function_call\"\n && event.output_index !== undefined\n ) {\n const initialArguments = event.item.arguments ?? \"\"\n state.started = true\n state.toolStartedByIndex[event.output_index] = true\n state.toolArgumentsByIndex[event.output_index] = initialArguments\n return createChunk(state, {\n delta: {\n role: \"assistant\",\n tool_calls: [\n {\n index: event.output_index,\n id: event.item.call_id,\n type: \"function\",\n function: {\n name: event.item.name,\n arguments: initialArguments,\n },\n },\n ],\n },\n })\n }\n\n return undefined\n}\n\nfunction handleOutputItemDone(\n state: ResponseTranslationState,\n event: ResponsesStreamEnvelope,\n): Array<ResponseStreamEventMessage> {\n if (event.item?.type === \"reasoning\") {\n const reasoningText = getSummaryText(event.item.summary)\n if (!reasoningText || state.reasoningTextSent) {\n return []\n }\n\n state.reasoningTextSent = true\n return [createChunk(state, { delta: { reasoning_text: reasoningText } })]\n }\n\n if (event.item?.type === \"message\") {\n const outputIndex = event.output_index ?? 0\n const fullText = getMessageOutputText(event.item.content)\n const content = getMissingSuffix(\n state.outputTextByIndex[outputIndex] ?? \"\",\n fullText,\n )\n if (!content) {\n return []\n }\n\n state.outputTextByIndex[outputIndex] = `${state.outputTextByIndex[outputIndex] ?? \"\"}${content}`\n return [createRoleChunk(state), createChunk(state, { delta: { content } })]\n }\n\n if (\n event.item?.type === \"function_call\"\n && event.output_index !== undefined\n ) {\n const outputIndex = event.output_index\n const chunks: Array<ResponseStreamEventMessage> = []\n\n if (!state.toolStartedByIndex[outputIndex]) {\n const initialArguments = event.item.arguments ?? \"\"\n state.started = true\n state.toolStartedByIndex[outputIndex] = true\n state.toolArgumentsByIndex[outputIndex] = initialArguments\n chunks.push(\n createChunk(state, {\n delta: {\n role: \"assistant\",\n tool_calls: [\n {\n index: outputIndex,\n id: event.item.call_id,\n type: \"function\",\n function: {\n name: event.item.name,\n arguments: initialArguments,\n },\n },\n ],\n },\n }),\n )\n return chunks\n }\n\n const content = getMissingSuffix(\n state.toolArgumentsByIndex[outputIndex] ?? \"\",\n event.item.arguments,\n )\n if (content) {\n state.toolArgumentsByIndex[outputIndex] = `${state.toolArgumentsByIndex[outputIndex] ?? \"\"}${content}`\n chunks.push(\n createRoleChunk(state),\n createChunk(state, {\n delta: {\n tool_calls: [\n {\n index: outputIndex,\n type: \"function\",\n function: { arguments: content },\n },\n ],\n },\n }),\n )\n }\n\n return chunks\n }\n\n return []\n}\n\nfunction createRoleChunk(\n state: ResponseTranslationState,\n): ResponseStreamEventMessage {\n if (state.started) {\n return { data: \"\" }\n }\n\n state.started = true\n return createChunk(state, { delta: { role: \"assistant\" } })\n}\n\nfunction createChunk(\n state: ResponseTranslationState,\n { delta, finishReason = null, usage }: CreateChunkOptions,\n): ResponseStreamEventMessage {\n return {\n data: JSON.stringify({\n id: state.responseId,\n object: \"chat.completion.chunk\",\n created: state.createdAt,\n model: state.model,\n choices: [\n {\n index: 0,\n delta,\n finish_reason: finishReason,\n logprobs: null,\n },\n ],\n usage,\n } satisfies ChatCompletionChunk),\n }\n}\n\nfunction translateMessagesToResponsesInput(\n messages: Array<Message>,\n): ResponsesInput {\n // Copilot accepts a bare string for the simplest single-user prompt, but\n // multi-turn, tool, or image conversations need structured input items.\n const items = messages.flatMap((message) => translateMessage(message))\n\n if (\n items.length === 1\n && \"role\" in items[0]\n && items[0].role === \"user\"\n && typeof items[0].content === \"string\"\n ) {\n return items[0].content\n }\n\n return items\n}\n\nfunction translateMessage(message: Message): Array<ResponsesInputItem> {\n if (message.role === \"tool\") {\n return [\n {\n type: \"function_call_output\",\n call_id: message.tool_call_id ?? \"\",\n output: stringifyToolOutput(message.content),\n },\n ]\n }\n\n const translated: Array<ResponsesInputItem> = []\n\n if (hasContent(message.content)) {\n translated.push({\n role: message.role,\n content: translateContent(message.content),\n })\n }\n\n if (message.role === \"assistant\" && message.tool_calls) {\n translated.push(\n ...message.tool_calls.map((toolCall) => ({\n type: \"function_call\" as const,\n call_id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n })),\n )\n }\n\n return translated\n}\n\nfunction hasContent(content: Message[\"content\"]): boolean {\n if (content === null) {\n return false\n }\n\n if (typeof content === \"string\") {\n return content.length > 0\n }\n\n return content.length > 0\n}\n\nfunction translateContent(\n content: Message[\"content\"],\n): ResponsesMessageInput[\"content\"] {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!content || content.length === 0) {\n return \"\"\n }\n\n return content.map((part) => translateContentPart(part))\n}\n\nfunction translateContentPart(part: ContentPart): ResponsesInputContentPart {\n if (part.type === \"text\") {\n return {\n type: \"input_text\",\n text: part.text,\n }\n }\n\n return {\n type: \"input_image\",\n image_url: part.image_url.url,\n detail: part.image_url.detail ?? \"auto\",\n }\n}\n\nfunction stringifyToolOutput(content: Message[\"content\"]): string {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!content) {\n return \"\"\n }\n\n const text = content\n .filter(\n (part): part is Extract<ContentPart, { type: \"text\" }> =>\n part.type === \"text\",\n )\n .map((part) => part.text)\n .join(\"\\n\\n\")\n\n return text || JSON.stringify(content)\n}\n\nfunction translateTools(\n tools: Array<Tool> | null | undefined,\n): Array<ResponsesTool> | undefined {\n if (!tools) {\n return undefined\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n name: tool.function.name,\n description: tool.function.description,\n parameters: tool.function.parameters,\n }))\n}\n\nfunction translateToolChoice(\n toolChoice: ChatCompletionsPayload[\"tool_choice\"],\n): ResponsesToolChoice | undefined {\n if (!toolChoice) {\n return undefined\n }\n\n if (typeof toolChoice === \"string\") {\n return toolChoice\n }\n\n return {\n type: \"function\",\n name: toolChoice.function.name,\n }\n}\n\nfunction translateUsage(\n usage: ResponsesApiResponse[\"usage\"] | undefined,\n): ChatCompletionResponse[\"usage\"] | undefined {\n if (!usage) {\n return undefined\n }\n\n return {\n prompt_tokens: usage.input_tokens,\n completion_tokens: usage.output_tokens,\n total_tokens: usage.total_tokens,\n ...(usage.input_tokens_details?.cached_tokens !== undefined && {\n prompt_tokens_details: {\n cached_tokens: usage.input_tokens_details.cached_tokens,\n },\n }),\n }\n}\n\nfunction getMessageOutputText(\n content: ResponsesMessageOutputItem[\"content\"] | undefined,\n): string | undefined {\n const text = content\n ?.filter((part) => part.type === \"output_text\")\n .map((part) => part.text)\n .join(\"\")\n\n return text || undefined\n}\n\nfunction getMissingSuffix(\n alreadySent: string,\n finalValue: string | undefined,\n): string | undefined {\n // Responses \"done\" events often repeat the full text/arguments after deltas;\n // emit only the suffix that has not already been forwarded to the chat stream.\n if (!finalValue) {\n return undefined\n }\n\n if (!alreadySent) {\n return finalValue\n }\n\n if (!finalValue.startsWith(alreadySent)) {\n return undefined\n }\n\n const suffix = finalValue.slice(alreadySent.length)\n return suffix || undefined\n}\n\nfunction getResponsesReasoningText(\n response: Pick<ResponsesApiResponse, \"output\" | \"reasoning\">,\n): string | undefined {\n const parts = [\n getSummaryText(response.reasoning?.summary),\n ...response.output\n .filter((item): item is ResponsesReasoningOutputItem => item.type === \"reasoning\")\n .map((item) => getSummaryText(item.summary)),\n ].filter((part): part is string => Boolean(part))\n\n return parts.length > 0 ? parts.join(\"\\n\\n\") : undefined\n}\n\nfunction getSummaryText(summary: ResponsesReasoningSummary | undefined): string | undefined {\n if (typeof summary === \"string\") {\n return summary || undefined\n }\n\n if (!Array.isArray(summary)) {\n return undefined\n }\n\n const text = summary\n .map((part) => part.text)\n .filter((part): part is string => Boolean(part))\n .join(\"\\n\\n\")\n\n return text || undefined\n}\n\nfunction getFinishReason(\n response: Pick<ResponsesApiResponse, \"output\" | \"incomplete_details\">,\n hasFunctionCalls: boolean,\n): \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" {\n if (hasFunctionCalls) {\n return \"tool_calls\"\n }\n\n if (response.incomplete_details?.reason?.includes(\"max_output_tokens\")) {\n return \"length\"\n }\n\n return \"stop\"\n}\n","// Internal Copilot chat API wrapper used by the Claude route and startup preflight.\nimport { events } from \"fetch-event-stream\"\n\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { defaultReasoningEffort } from \"~/lib/models\"\nimport { routeModelId } from \"~/lib/models\"\nimport { runtimeState } from \"~/lib/state\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n readCopilotText,\n} from \"~/copilot/client\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n} from \"~/copilot/types\"\n\nimport {\n buildResponsesRequestPayload,\n shouldUseResponsesApiForModel,\n translateResponsesStreamToChatCompletionStream,\n translateResponsesToChatCompletion,\n type ResponsesApiResponse,\n type ResponsesReasoningEffort,\n type ResponsesRequestPayload,\n} from \"./responses\"\n\nconst usesMaxCompletionTokens = (modelId: string): boolean =>\n modelId.startsWith(\"gpt-5\")\n\nconst continuePrefillPrompt =\n \"Continue the assistant response from the previous assistant message.\"\n\nconst continueAfterNonUserPrompt =\n \"Continue based on the context above.\"\n\ntype ClientKind = \"claude\" | \"generic\"\n\ninterface CreateChatCompletionsOptions {\n client?: ClientKind\n requestId?: string\n requestedModel?: string\n requestedThinkEffort?: string\n requestedThinking?: string\n signal?: AbortSignal\n timeoutMs?: number\n}\n\nconst maxUserLength = 64\n\nexport const sanitizeReasoningEffortForModel = (\n _modelId: string,\n reasoningEffort: ChatCompletionsPayload[\"reasoning_effort\"],\n): ChatCompletionsPayload[\"reasoning_effort\"] => {\n return reasoningEffort ?? undefined\n}\n\nconst getRequestedReasoningEffort = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsPayload[\"reasoning_effort\"] => {\n // Configured think effort wins over client input so startup preflight and\n // actual Claude Code traffic exercise the same upstream behavior.\n return sanitizeReasoningEffortForModel(\n payload.model,\n runtimeState.thinkEffort ?? defaultReasoningEffort,\n )\n}\n\nexport const sanitizeUserIdentifier = (\n user: string | null | undefined,\n): string | undefined => {\n if (!user) {\n return undefined\n }\n\n return user.slice(0, maxUserLength)\n}\n\ntype ChatCompletionsRequestPayload = Omit<\n ChatCompletionsPayload,\n \"max_tokens\"\n> & {\n max_tokens?: number | null\n max_completion_tokens?: number | null\n}\n\nconst buildRequestPayload = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsRequestPayload => {\n const requestedReasoningEffort = getRequestedReasoningEffort(payload)\n\n if (\n !usesMaxCompletionTokens(payload.model)\n || payload.max_tokens === null\n || payload.max_tokens === undefined\n ) {\n const sanitizedPayload = {\n ...payload,\n reasoning_effort: requestedReasoningEffort,\n user: sanitizeUserIdentifier(payload.user),\n }\n\n return sanitizedPayload\n }\n\n // GPT-5-class Copilot endpoints reject max_tokens and require the newer\n // max_completion_tokens field; older models still use max_tokens.\n return {\n ...payload,\n max_tokens: undefined,\n max_completion_tokens: payload.max_tokens,\n reasoning_effort: requestedReasoningEffort,\n user: sanitizeUserIdentifier(payload.user),\n }\n}\n\n// Copilot's Claude-family models reject a conversation that does not end with a\n// user message: \"This model does not support assistant message prefill. The\n// conversation must end with a user message.\" Anthropic removed prefill support\n// in Opus 4.7+ / Sonnet 4.6+, so this is an upstream constraint, not a Copilot\n// quirk.\n//\n// A trailing assistant message is the prefill case and keeps its dedicated\n// handling below. Any other non-user trailing role (system, developer, tool)\n// gets a short user turn appended. This guard is deliberately at the shared\n// /chat/completions layer rather than in one caller: the same bug has been\n// reported elsewhere as a fixup applied on one code path and missed on another\n// (openclaw#75395), and a payload that reaches here ending on a non-user role is\n// rejected no matter which caller built it.\nconst normalizeFinalAssistantPrefill = (\n payload: ChatCompletionsPayload,\n): ChatCompletionsPayload => {\n const lastMessage = payload.messages.at(-1)\n if (!lastMessage || lastMessage.role === \"user\") {\n return payload\n }\n\n if (lastMessage.role !== \"assistant\") {\n return {\n ...payload,\n messages: [\n ...payload.messages,\n { role: \"user\", content: continueAfterNonUserPrompt },\n ],\n }\n }\n\n const messages = [...payload.messages]\n if (typeof lastMessage.content !== \"string\") {\n messages.pop()\n return { ...payload, messages }\n }\n\n const content = lastMessage.content.trimEnd()\n if (!content) {\n messages.pop()\n return { ...payload, messages }\n }\n\n messages[messages.length - 1] = {\n ...lastMessage,\n content,\n }\n messages.push({ role: \"user\", content: continuePrefillPrompt })\n return { ...payload, messages }\n}\n\nconst isAgentInitiator = (\n messages: ChatCompletionsPayload[\"messages\"],\n): \"agent\" | \"user\" =>\n messages.some((msg) => msg.role === \"assistant\" || msg.role === \"tool\") ?\n \"agent\"\n : \"user\"\n\nconst messagesIncludeImage = (\n messages: ChatCompletionsPayload[\"messages\"],\n): boolean =>\n messages.some(\n (msg) =>\n typeof msg.content !== \"string\"\n && msg.content?.some((part) => part.type === \"image_url\"),\n )\n\nexport const createChatCompletions = async (\n config: ProxyConfig,\n payload: ChatCompletionsPayload,\n options: CreateChatCompletionsOptions = {},\n) => {\n const client = options.client ?? \"generic\"\n const requestedModel = options.requestedModel ?? payload.model\n const requestedThinkEffort =\n options.requestedThinkEffort ?? payload.reasoning_effort ?? \"none\"\n const requestedThinking = options.requestedThinking ?? \"none\"\n const signal = createCopilotRequestSignal(options.signal, options.timeoutMs)\n const upstreamModelId = routeModelId(payload.model)\n const upstreamPayload =\n upstreamModelId === payload.model ? payload : { ...payload, model: upstreamModelId }\n const useResponsesApi = shouldUseResponsesApiForModel(upstreamPayload.model)\n const compatiblePayload =\n useResponsesApi ? upstreamPayload : normalizeFinalAssistantPrefill(upstreamPayload)\n const provider = getCopilotProviderContext(config)\n const enableVision = messagesIncludeImage(compatiblePayload.messages)\n const initiator = isAgentInitiator(compatiblePayload.messages)\n const requestPayload = buildRequestPayload(compatiblePayload)\n log.debug(\n [\n \"Model request\",\n `client=${client}`,\n `requested_model=${requestedModel}`,\n ...(options.requestId ? [`request_id=${options.requestId}`] : []),\n `upstream_model=${compatiblePayload.model}`,\n `requested_think_effort=${requestedThinkEffort}`,\n `requested_thinking=${requestedThinking}`,\n `effective_think_effort=${requestPayload.reasoning_effort ?? \"none\"}`,\n ].join(\" \"),\n )\n log.debug(\"Full request payload\", {\n payload: requestPayload,\n })\n // Choose the Copilot API surface after model routing, because aliases can\n // resolve to a Responses-only upstream model even when the client asked for a\n // generic Claude model name.\n if (useResponsesApi) {\n return createResponses(provider, compatiblePayload, {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n })\n }\n\n const response = await fetchCopilot(\n provider,\n \"/chat/completions\",\n {\n method: \"POST\",\n headers: {\n accept: compatiblePayload.stream ? \"text/event-stream\" : \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(requestPayload),\n },\n {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n if (await shouldRetryWithResponses(response, signal, options.timeoutMs)) {\n return createResponses(provider, compatiblePayload, {\n vision: enableVision,\n initiator,\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n })\n }\n\n const detail = await logUpstreamError(\"Failed to create chat completions\", response, {\n model: payload.model,\n request: requestPayload,\n route: \"/chat/completions\",\n requestId: options.requestId,\n }, signal, options.timeoutMs)\n throw new HTTPError(\n \"Failed to create chat completions\",\n response,\n detail,\n )\n }\n\n if (compatiblePayload.stream) {\n return events(response)\n }\n\n return readCopilotJson<ChatCompletionResponse>(\n response,\n signal,\n options.timeoutMs,\n )\n}\n\nasync function createResponses(\n provider: ReturnType<typeof getCopilotProviderContext>,\n payload: ChatCompletionsPayload,\n options: {\n vision: boolean\n initiator: \"agent\" | \"user\"\n requestId?: string\n signal?: AbortSignal\n timeoutMs?: number\n },\n) {\n const reasoningEffort = getRequestedReasoningEffort(\n payload,\n ) as ResponsesReasoningEffort | undefined\n const requestPayload = buildResponsesRequestPayload(payload, reasoningEffort)\n\n const response = await fetchCopilot(\n provider,\n \"/responses\",\n {\n method: \"POST\",\n headers: {\n accept: payload.stream ? \"text/event-stream\" : \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(requestPayload),\n },\n {\n vision: options.vision,\n initiator: options.initiator,\n requestId: options.requestId,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n const detail = await logUpstreamError(\"Failed to create responses\", response, {\n model: payload.model,\n request: requestPayload,\n route: \"/responses\",\n requestId: options.requestId,\n }, options.signal, options.timeoutMs)\n throw new HTTPError(\n \"Failed to create responses\",\n response,\n detail,\n )\n }\n\n if (payload.stream) {\n return translateResponsesStreamToChatCompletionStream(events(response))\n }\n\n return translateResponsesToChatCompletion(\n await readCopilotJson<ResponsesApiResponse>(\n response,\n options.signal,\n options.timeoutMs,\n ),\n )\n}\n\nasync function logUpstreamError(\n message: string,\n response: Response,\n context: {\n model: string\n request?: ChatCompletionsRequestPayload | ResponsesRequestPayload\n route: string\n requestId?: string\n },\n signal?: AbortSignal,\n timeoutMs?: number,\n): Promise<string | undefined> {\n const errorBody = await readCopilotText(\n response.clone(),\n signal,\n timeoutMs,\n ).catch(() => \"\")\n const detail = getUpstreamErrorDetail(response, errorBody)\n\n log.error(`${message}: route=${context.route} model=${context.model} status=${response.status}${context.requestId ? ` request_id=${context.requestId}` : \"\"}`, {\n message,\n route: context.route,\n model: context.model,\n requestId: context.requestId,\n request: context.request,\n response: {\n status: response.status,\n statusText: response.statusText || undefined,\n url: response.url || undefined,\n headers: Object.fromEntries(response.headers.entries()),\n body: errorBody || undefined,\n },\n })\n\n return detail\n}\n\nfunction getUpstreamErrorDetail(\n response: Response,\n body: string,\n): string | undefined {\n if (!body) {\n return response.statusText || undefined\n }\n\n try {\n const payload = JSON.parse(body) as {\n error?: {\n code?: string\n message?: string\n }\n }\n return payload.error?.message ?? payload.error?.code ?? body.slice(0, 240)\n } catch {\n return body.slice(0, 240)\n }\n}\n\nasync function shouldRetryWithResponses(\n response: Response,\n signal?: AbortSignal,\n timeoutMs?: number,\n): Promise<boolean> {\n // Copilot can list a model but reject the chat endpoint for it; only this\n // explicit upstream code is treated as a signal to retry via /responses.\n try {\n const errorBody = await readCopilotJson<{\n error?: {\n code?: string\n }\n }>(response.clone(), signal, timeoutMs)\n return errorBody.error?.code === \"unsupported_api_for_model\"\n } catch {\n return false\n }\n}\n","// Startup preflight: fail fast if configured models or think effort cannot be used upstream.\nimport type { ProxyConfig } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport type { ReasoningEffort } from \"~/lib/models\"\nimport { getUpstreamModelIds } from \"~/lib/models\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n} from \"~/copilot/client\"\nimport type { ChatCompletionsPayload } from \"~/copilot/types\"\nimport { createChatCompletions } from \"~/copilot/chat\"\n\ninterface CopilotModelsResponse {\n data?: Array<{ id?: string }>\n}\n\nconst fetchUpstreamModelIds = async (config: ProxyConfig): Promise<Set<string>> => {\n const provider = getCopilotProviderContext(config)\n const signal = createCopilotRequestSignal(undefined, config.upstreamTimeoutMs)\n const response = await fetchCopilot(provider, \"/models\", {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n }, { signal, timeoutMs: config.upstreamTimeoutMs })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to validate upstream models\", response)\n }\n\n const payload = await readCopilotJson<CopilotModelsResponse>(\n response,\n signal,\n config.upstreamTimeoutMs,\n )\n return new Set(\n (payload.data ?? [])\n .map((model) => model.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0),\n )\n}\n\nconst ensureRequiredModels = async (config: ProxyConfig): Promise<void> => {\n const upstreamModels = await fetchUpstreamModelIds(config)\n const requiredModels = getUpstreamModelIds()\n const missingModels = requiredModels.filter((model) => !upstreamModels.has(model))\n\n if (missingModels.length > 0) {\n throw new Error(\n `Required Copilot model(s) unavailable upstream: ${missingModels.join(\", \")}`,\n )\n }\n\n log.info(`Upstream models available: ${requiredModels.join(\", \")}`)\n}\n\nconst createProbePayload = (model: string): ChatCompletionsPayload => ({\n model,\n max_tokens: 16,\n stream: false,\n messages: [\n {\n role: \"user\",\n content: \"Reply with OK only.\",\n },\n ],\n})\n\nconst validateModelRequest = async (\n config: ProxyConfig,\n model: string,\n thinkEffort: ReasoningEffort,\n): Promise<void> => {\n try {\n // Probe through the same internal chat path as real requests so routing,\n // token headers, think effort, and /responses fallback are validated together.\n const response = await createChatCompletions(\n config,\n createProbePayload(model),\n {\n client: \"generic\",\n requestedModel: model,\n timeoutMs: config.upstreamTimeoutMs,\n },\n )\n\n if (typeof response !== \"object\" || response === null || !(\"choices\" in response)) {\n throw new Error(`Preflight request for ${model} unexpectedly streamed`)\n }\n\n log.info(`Preflight OK: model=${model} think_effort=${thinkEffort}`)\n } catch (error) {\n if (error instanceof HTTPError) {\n const text = await error.response.text().catch(() => \"\")\n throw new Error(\n `Preflight failed for model=${model} think_effort=${thinkEffort}: ${error.response.status} ${error.response.statusText}${text ? ` ${text}` : \"\"}`,\n )\n }\n throw error\n }\n}\n\nexport const validateUpstream = async (\n config: ProxyConfig,\n thinkEffort: ReasoningEffort,\n): Promise<void> => {\n log.info(\"Running upstream preflight\")\n await ensureRequiredModels(config)\n\n for (const model of getUpstreamModelIds()) {\n await validateModelRequest(config, model, thinkEffort)\n }\n}\n","// Small Claude protocol helpers shared by streaming and non-streaming translators.\nimport type { ClaudeResponse } from \"~/claude/types\"\n\nexport function mapOpenAIStopReasonToClaude(\n finishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null,\n): ClaudeResponse[\"stop_reason\"] {\n if (finishReason === null) {\n return null\n }\n\n const stopReasonMap = {\n stop: \"end_turn\",\n length: \"max_tokens\",\n tool_calls: \"tool_use\",\n content_filter: \"end_turn\",\n } as const\n\n return stopReasonMap[finishReason]\n}\n","// Maps Claude tool names to Copilot-compatible tool names and back.\nimport { createHash } from \"node:crypto\"\n\nimport type { ClaudeTool } from \"~/claude/types\"\n\nconst strictToolNameCharsPattern = /^[A-Za-z0-9_-]+$/\nconst dottedToolNameCharsPattern = /^[A-Za-z0-9_.-]+$/\nconst defaultToolNameMaxLength = 64\nconst extendedToolNameMaxLength = 128\nconst hashLength = 10\n\nexport interface ClaudeToolNameMapper {\n toClaude(name: string): string\n toOpenAI(name: string): string\n}\n\ninterface ToolNameMapperOptions {\n allowDots?: boolean\n maxNameLength?: number\n}\n\nexport const getToolNameMapperOptionsForModel = (\n modelId: string,\n): Required<ToolNameMapperOptions> => {\n const normalized = modelId\n .trim()\n .toLowerCase()\n .replace(/\\[1m\\]$/, \"-1m\")\n .replace(/[._]/g, \"-\")\n\n if (normalized.startsWith(\"gpt-\")) {\n return { allowDots: false, maxNameLength: extendedToolNameMaxLength }\n }\n\n return { allowDots: false, maxNameLength: defaultToolNameMaxLength }\n}\n\nexport const getClaudeToolNameMaxLength = (modelId: string): number =>\n getToolNameMapperOptionsForModel(modelId).maxNameLength\n\nconst makeHash = (value: string): string =>\n createHash(\"sha1\").update(value).digest(\"hex\").slice(0, hashLength)\n\nconst getAllowedNamePattern = (allowDots: boolean): RegExp =>\n allowDots ? dottedToolNameCharsPattern : strictToolNameCharsPattern\n\nconst cleanToolName = (name: string, allowDots: boolean): string => {\n const invalidCharsPattern = allowDots ? /[^A-Za-z0-9_.-]/g : /[^A-Za-z0-9_-]/g\n const cleaned = name.replace(invalidCharsPattern, \"_\").replace(/_+/g, \"_\")\n return cleaned.replace(/^_+|_+$/g, \"\") || \"tool\"\n}\n\nconst isValidToolName = (\n name: string,\n maxNameLength: number,\n allowDots: boolean,\n): boolean =>\n name.length > 0\n && name.length <= maxNameLength\n && getAllowedNamePattern(allowDots).test(name)\n\nconst makeValidToolName = (\n name: string,\n maxNameLength: number,\n allowDots: boolean,\n): string => {\n if (isValidToolName(name, maxNameLength, allowDots)) {\n return name\n }\n\n const cleaned = cleanToolName(name, allowDots)\n if (cleaned.length <= maxNameLength) {\n return cleaned\n }\n\n const hash = makeHash(name)\n const prefixLength = maxNameLength - hash.length - 1\n return `${cleaned.slice(0, prefixLength)}_${hash}`\n}\n\nconst makeUniqueToolName = (\n name: string,\n used: Set<string>,\n maxNameLength: number,\n allowDots: boolean,\n): string => {\n const candidate = makeValidToolName(name, maxNameLength, allowDots)\n if (!used.has(candidate)) {\n return candidate\n }\n\n for (let index = 2; ; index++) {\n const suffix = `_${makeHash(`${name}:${index}`)}`\n const prefixLength = maxNameLength - suffix.length\n const next = `${cleanToolName(name, allowDots).slice(0, prefixLength)}${suffix}`\n if (!used.has(next)) {\n return next\n }\n }\n}\n\nexport const createClaudeToolNameMapper = (\n tools: Array<ClaudeTool> | undefined,\n options: ToolNameMapperOptions = {},\n): ClaudeToolNameMapper => {\n const maxNameLength = options.maxNameLength ?? defaultToolNameMaxLength\n const allowDots = options.allowDots ?? false\n const claudeToOpenAI = new Map<string, string>()\n const openAIToClaude = new Map<string, string>()\n const used = new Set<string>()\n\n for (const tool of tools ?? []) {\n if (claudeToOpenAI.has(tool.name)) {\n continue\n }\n\n const openAIName = makeUniqueToolName(\n tool.name,\n used,\n maxNameLength,\n allowDots,\n )\n used.add(openAIName)\n claudeToOpenAI.set(tool.name, openAIName)\n openAIToClaude.set(openAIName, tool.name)\n }\n\n return {\n toClaude: (name) => openAIToClaude.get(name) ?? name,\n toOpenAI: (name) =>\n claudeToOpenAI.get(name)\n ?? makeValidToolName(name, maxNameLength, allowDots),\n }\n}\n","// Non-streaming protocol translation between Claude Messages and Copilot chat completions.\nimport {\n defaultReasoningEffort,\n normalizeClaudeModelId,\n routeModelId,\n} from \"~/lib/models\"\nimport { runtimeState } from \"~/lib/state\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n ContentPart,\n Message,\n TextPart,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\n\nimport {\n type ClaudeAssistantContentBlock,\n type ClaudeAssistantMessage,\n type ClaudeMessage,\n type ClaudeMessagesPayload,\n type ClaudeResponse,\n type ClaudeTextBlock,\n type ClaudeThinkingBlock,\n type ClaudeTool,\n type ClaudeToolResultBlock,\n type ClaudeToolUseBlock,\n type ClaudeUserContentBlock,\n type ClaudeUserMessage,\n} from \"~/claude/types\"\nimport { mapOpenAIStopReasonToClaude } from \"~/claude/utils\"\nimport {\n createClaudeToolNameMapper,\n getToolNameMapperOptionsForModel,\n type ClaudeToolNameMapper,\n} from \"~/claude/tool-names\"\n\nexport function translateModelName(model: string): string {\n return routeModelId(model)\n}\n\nexport function translateToOpenAI(\n payload: ClaudeMessagesPayload,\n _settings?: undefined,\n toolNameMapper?: ClaudeToolNameMapper,\n): ChatCompletionsPayload {\n const model = translateModelName(payload.model)\n const mapper = toolNameMapper ?? createClaudeToolNameMapper(payload.tools, {\n ...getToolNameMapperOptionsForModel(model),\n })\n const tools = translateClaudeToolsToOpenAI(payload.tools, mapper)\n const messages = translateClaudeMessagesToOpenAI(\n payload.messages,\n payload.system,\n mapper,\n )\n\n return {\n model,\n messages: normalizeFinalAssistantPrefill(messages),\n max_tokens: payload.max_tokens,\n stop: payload.stop_sequences,\n stream: payload.stream,\n temperature: payload.temperature,\n top_p: payload.top_p,\n reasoning_effort: runtimeState.thinkEffort ?? defaultReasoningEffort,\n user: payload.metadata?.user_id,\n tools,\n tool_choice:\n tools && tools.length > 0 ?\n translateClaudeToolChoiceToOpenAI(payload.tool_choice, mapper)\n : undefined,\n }\n}\n\nfunction translateClaudeMessagesToOpenAI(\n claudeMessages: Array<ClaudeMessage>,\n system: string | Array<ClaudeTextBlock> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Message> {\n const systemMessages = handleSystemPrompt(system)\n const otherMessages = claudeMessages.flatMap((message) =>\n message.role === \"user\" ? handleUserMessage(message) : handleAssistantMessage(message, toolNameMapper),\n )\n return [...systemMessages, ...otherMessages]\n}\n\nfunction handleSystemPrompt(\n system: string | Array<ClaudeTextBlock> | undefined,\n): Array<Message> {\n if (!system) {\n return []\n }\n\n if (typeof system === \"string\") {\n return [{ role: \"system\", content: system }]\n }\n\n // Claude accepts multiple system text blocks; Copilot chat expects one\n // system message, so preserve block boundaries with blank lines.\n return [{ role: \"system\", content: system.map((block) => block.text).join(\"\\n\\n\") }]\n}\n\nfunction handleUserMessage(message: ClaudeUserMessage): Array<Message> {\n const newMessages: Array<Message> = []\n\n if (Array.isArray(message.content)) {\n const toolResultBlocks = message.content.filter(\n (block): block is ClaudeToolResultBlock => block.type === \"tool_result\",\n )\n const otherBlocks = message.content.filter((block) => block.type !== \"tool_result\")\n\n // Tool results must become standalone OpenAI tool messages before any\n // remaining user content, otherwise upstream cannot associate them with\n // prior assistant tool calls.\n for (const block of toolResultBlocks) {\n newMessages.push({\n role: \"tool\",\n tool_call_id: block.tool_use_id,\n content: mapContent(block.content),\n })\n }\n\n if (otherBlocks.length > 0) {\n newMessages.push({\n role: \"user\",\n content: mapContent(otherBlocks),\n })\n }\n } else {\n newMessages.push({\n role: \"user\",\n content: mapContent(message.content),\n })\n }\n\n return newMessages\n}\n\nfunction handleAssistantMessage(\n message: ClaudeAssistantMessage,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Message> {\n if (!Array.isArray(message.content)) {\n return [{ role: \"assistant\", content: mapContent(message.content) }]\n }\n\n const toolUseBlocks = message.content.filter(\n (block): block is ClaudeToolUseBlock => block.type === \"tool_use\",\n )\n const textBlocks = message.content.filter(\n (block): block is ClaudeTextBlock => block.type === \"text\",\n )\n const thinkingBlocks = message.content.filter(\n (block): block is ClaudeThinkingBlock => block.type === \"thinking\",\n )\n\n const allTextContent = [\n ...textBlocks.map((b) => b.text),\n ...thinkingBlocks.map((b) => b.thinking),\n ].join(\"\\n\\n\")\n\n // Assistant tool_use blocks bridge to OpenAI tool_calls; plain text and\n // thinking content stay on the assistant message as context for those calls.\n return toolUseBlocks.length > 0\n ? [\n {\n role: \"assistant\",\n content: allTextContent || null,\n tool_calls: toolUseBlocks.map((toolUse) => ({\n id: toolUse.id,\n type: \"function\",\n function: {\n name: toolNameMapper.toOpenAI(toolUse.name),\n arguments: JSON.stringify(toolUse.input),\n },\n })),\n },\n ]\n : [{ role: \"assistant\", content: mapContent(message.content) }]\n}\n\nconst continuePrefillPrompt =\n \"Continue the assistant response from the previous assistant message.\"\n\nconst normalizeFinalAssistantPrefill = (\n messages: Array<Message>,\n): Array<Message> => {\n let lastAssistantIndex = -1\n for (let index = messages.length - 1; index >= 0; index--) {\n if (messages[index]?.role === \"assistant\") {\n lastAssistantIndex = index\n break\n }\n }\n if (lastAssistantIndex < 0) {\n return messages\n }\n if (lastAssistantIndex !== messages.length - 1) {\n return messages\n }\n\n const lastAssistant = messages[lastAssistantIndex]\n if (typeof lastAssistant.content !== \"string\") {\n return messages.slice(0, -1)\n }\n\n const trimmedContent = lastAssistant.content.trimEnd()\n // Claude Messages allows a final assistant message as a prefill prefix.\n // Copilot rejects that shape, so keep the prefix as context and append a\n // minimal user turn that asks upstream to continue from it.\n const nextMessages = [...messages]\n if (trimmedContent) {\n nextMessages[lastAssistantIndex] = {\n ...lastAssistant,\n content: trimmedContent,\n }\n nextMessages.push({ role: \"user\", content: continuePrefillPrompt })\n } else {\n nextMessages.pop()\n }\n return nextMessages\n}\n\nfunction mapContent(\n content:\n | string\n | Array<ClaudeUserContentBlock | ClaudeAssistantContentBlock>,\n): string | Array<ContentPart> | null {\n if (typeof content === \"string\") {\n return content\n }\n\n if (!Array.isArray(content)) {\n return null\n }\n\n const hasImage = content.some((block) => block.type === \"image\")\n if (!hasImage) {\n // Without images the OpenAI-compatible chat surface accepts plain strings;\n // image messages must use content parts instead.\n return content\n .filter(\n (block): block is ClaudeTextBlock | ClaudeThinkingBlock =>\n block.type === \"text\" || block.type === \"thinking\",\n )\n .map((block) => (block.type === \"text\" ? block.text : block.thinking))\n .join(\"\\n\\n\")\n }\n\n const contentParts: Array<ContentPart> = []\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n contentParts.push({ type: \"text\", text: block.text })\n break\n }\n case \"thinking\": {\n contentParts.push({ type: \"text\", text: block.thinking })\n break\n }\n case \"image\": {\n contentParts.push({\n type: \"image_url\",\n image_url: {\n url: `data:${block.source.media_type};base64,${block.source.data}`,\n },\n })\n break\n }\n }\n }\n\n return contentParts\n}\n\nfunction translateClaudeToolsToOpenAI(\n claudeTools: Array<ClaudeTool> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<Tool> | undefined {\n if (!claudeTools || claudeTools.length === 0) {\n return undefined\n }\n\n const tools = claudeTools.flatMap((tool) => {\n if (!tool.input_schema) {\n return []\n }\n\n return [{\n type: \"function\" as const,\n function: {\n name: toolNameMapper.toOpenAI(tool.name),\n description: tool.description,\n parameters: tool.input_schema,\n },\n }]\n })\n\n return tools.length > 0 ? tools : undefined\n}\n\nfunction translateClaudeToolChoiceToOpenAI(\n claudeToolChoice: ClaudeMessagesPayload[\"tool_choice\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tool_choice\"] {\n if (!claudeToolChoice) {\n return undefined\n }\n\n switch (claudeToolChoice.type) {\n case \"auto\": {\n return \"auto\"\n }\n case \"any\": {\n return \"required\"\n }\n case \"tool\": {\n if (claudeToolChoice.name) {\n return {\n type: \"function\",\n function: { name: toolNameMapper.toOpenAI(claudeToolChoice.name) },\n }\n }\n return undefined\n }\n case \"none\": {\n return \"none\"\n }\n default: {\n return undefined\n }\n }\n}\n\nexport function translateToClaude(\n response: ChatCompletionResponse,\n toolNameMapper: ClaudeToolNameMapper = createClaudeToolNameMapper(\n undefined,\n ),\n): ClaudeResponse {\n const allThinkingBlocks: Array<ClaudeThinkingBlock> = []\n const allTextBlocks: Array<ClaudeTextBlock> = []\n const allToolUseBlocks: Array<ClaudeToolUseBlock> = []\n let stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null = null\n\n stopReason = response.choices[0]?.finish_reason ?? stopReason\n\n // Multiple choices can mix text and tool calls. Claude has one stop_reason,\n // so prefer tool_calls when present while preserving a normal stop result.\n for (const choice of response.choices) {\n allThinkingBlocks.push(\n ...getClaudeThinkingBlocks(\n choice.message.reasoning_text ?? choice.message.reasoning_content,\n ),\n )\n allTextBlocks.push(...getClaudeTextBlocks(choice.message.content))\n allToolUseBlocks.push(\n ...getClaudeToolUseBlocks(choice.message.tool_calls, toolNameMapper),\n )\n\n if (choice.finish_reason === \"tool_calls\" || stopReason === \"stop\") {\n stopReason = choice.finish_reason\n }\n }\n\n return {\n id: response.id,\n type: \"message\",\n role: \"assistant\",\n model: normalizeClaudeModelId(response.model),\n content: [...allThinkingBlocks, ...allTextBlocks, ...allToolUseBlocks],\n stop_reason: mapOpenAIStopReasonToClaude(stopReason),\n stop_sequence: null,\n usage: {\n input_tokens:\n (response.usage?.prompt_tokens ?? 0)\n - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: response.usage?.completion_tokens ?? 0,\n ...(response.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n }\n}\n\nfunction getClaudeThinkingBlocks(\n reasoningContent: string | null | undefined,\n): Array<ClaudeThinkingBlock> {\n if (!reasoningContent) {\n return []\n }\n\n return [{ type: \"thinking\", thinking: reasoningContent }]\n}\n\nfunction getClaudeTextBlocks(\n messageContent: Message[\"content\"],\n): Array<ClaudeTextBlock> {\n if (typeof messageContent === \"string\") {\n return [{ type: \"text\", text: messageContent }]\n }\n\n if (Array.isArray(messageContent)) {\n return messageContent\n .filter((part): part is TextPart => part.type === \"text\")\n .map((part) => ({ type: \"text\", text: part.text }))\n }\n\n return []\n}\n\nfunction getClaudeToolUseBlocks(\n toolCalls: Array<ToolCall> | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n): Array<ClaudeToolUseBlock> {\n if (!toolCalls) {\n return []\n }\n\n return toolCalls.map((toolCall) => ({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolNameMapper.toClaude(toolCall.function.name),\n input: safeJsonParse(toolCall.function.arguments),\n }))\n}\n\nfunction safeJsonParse(input: string): Record<string, unknown> {\n try {\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return { raw: input }\n }\n}\n","// Stateful streaming adapter from Copilot chat completion chunks to Claude SSE events.\nimport type {\n ClaudeStreamEventData,\n ClaudeStreamState,\n} from \"~/claude/types\"\nimport {\n createClaudeToolNameMapper,\n type ClaudeToolNameMapper,\n} from \"~/claude/tool-names\"\nimport { mapOpenAIStopReasonToClaude } from \"~/claude/utils\"\nimport type { ChatCompletionChunk } from \"~/copilot/types\"\nimport { normalizeClaudeModelId } from \"~/lib/models\"\n\nfunction isToolBlockOpen(state: ClaudeStreamState): boolean {\n if (!state.contentBlockOpen) {\n return false\n }\n\n return Object.values(state.toolCalls).some(\n (tc) => tc.claudeBlockIndex === state.contentBlockIndex,\n )\n}\n\nconst closeOpenContentBlock = (\n events: Array<ClaudeStreamEventData>,\n state: ClaudeStreamState,\n): void => {\n if (!state.contentBlockOpen) {\n return\n }\n\n events.push({\n type: \"content_block_stop\",\n index: state.contentBlockIndex,\n })\n state.contentBlockIndex++\n state.contentBlockOpen = false\n state.thinkingBlockOpen = false\n}\n\nexport function translateChunkToClaudeEvents(\n chunk: ChatCompletionChunk,\n state: ClaudeStreamState,\n toolNameMapper: ClaudeToolNameMapper = createClaudeToolNameMapper(\n undefined,\n ),\n): Array<ClaudeStreamEventData> {\n // This is a stateful adapter from Copilot's OpenAI-style deltas to Claude's\n // stricter SSE protocol. Claude requires every thinking/text/tool block to\n // be explicitly opened, filled with deltas, and then closed in order.\n const events: Array<ClaudeStreamEventData> = []\n\n if (chunk.choices.length === 0) {\n return events\n }\n\n const choice = chunk.choices[0]\n const { delta } = choice\n const reasoningContent = delta.reasoning_text ?? delta.reasoning_content\n\n if (!state.messageStartSent) {\n events.push({\n type: \"message_start\",\n message: {\n id: chunk.id,\n type: \"message\",\n role: \"assistant\",\n content: [],\n model: normalizeClaudeModelId(chunk.model),\n stop_reason: null,\n stop_sequence: null,\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: 0,\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n })\n state.messageStartSent = true\n }\n\n if (reasoningContent) {\n // Claude allows only one open content block at a time. Switching from text\n // or tool output into thinking must close the previous block first.\n if (state.contentBlockOpen && !state.thinkingBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n if (!state.contentBlockOpen) {\n events.push({\n type: \"content_block_start\",\n index: state.contentBlockIndex,\n content_block: {\n type: \"thinking\",\n thinking: \"\",\n },\n })\n state.contentBlockOpen = true\n state.thinkingBlockOpen = true\n }\n\n events.push({\n type: \"content_block_delta\",\n index: state.contentBlockIndex,\n delta: {\n type: \"thinking_delta\",\n thinking: reasoningContent,\n },\n })\n }\n\n if (delta.content) {\n // Text deltas cannot be appended to an open thinking or tool_use block, so\n // close whichever block is active before starting/resuming text output.\n if (state.thinkingBlockOpen || isToolBlockOpen(state)) {\n closeOpenContentBlock(events, state)\n }\n\n if (!state.contentBlockOpen) {\n events.push({\n type: \"content_block_start\",\n index: state.contentBlockIndex,\n content_block: {\n type: \"text\",\n text: \"\",\n },\n })\n state.contentBlockOpen = true\n }\n\n events.push({\n type: \"content_block_delta\",\n index: state.contentBlockIndex,\n delta: {\n type: \"text_delta\",\n text: delta.content,\n },\n })\n }\n\n if (delta.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n if (toolCall.id && toolCall.function?.name) {\n // The first tool delta opens a Claude tool_use block; later argument\n // deltas are routed back to this block by Copilot's tool call index.\n if (state.contentBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n const toolName = toolNameMapper.toClaude(toolCall.function.name)\n const claudeBlockIndex = state.contentBlockIndex\n state.toolCalls[toolCall.index] = {\n id: toolCall.id,\n name: toolName,\n claudeBlockIndex,\n }\n\n events.push({\n type: \"content_block_start\",\n index: claudeBlockIndex,\n content_block: {\n type: \"tool_use\",\n id: toolCall.id,\n name: toolName,\n input: {},\n },\n })\n state.contentBlockOpen = true\n }\n\n if (toolCall.function?.arguments) {\n const toolCallInfo = state.toolCalls[toolCall.index]\n if (toolCallInfo) {\n events.push({\n type: \"content_block_delta\",\n index: toolCallInfo.claudeBlockIndex,\n delta: {\n type: \"input_json_delta\",\n partial_json: toolCall.function.arguments,\n },\n })\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n if (state.contentBlockOpen) {\n closeOpenContentBlock(events, state)\n }\n\n events.push(\n {\n type: \"message_delta\",\n delta: {\n stop_reason: mapOpenAIStopReasonToClaude(choice.finish_reason),\n stop_sequence: null,\n },\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: chunk.usage?.completion_tokens ?? 0,\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n {\n type: \"message_stop\",\n },\n )\n }\n\n return events\n}\n\nexport function translateErrorToClaudeErrorEvent(): ClaudeStreamEventData {\n return {\n type: \"error\",\n error: {\n type: \"api_error\",\n message: \"An unexpected error occurred during streaming.\",\n },\n }\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type {\n ClaudeAssistantContentBlock,\n ClaudeMessage,\n ClaudeMessagesPayload,\n ClaudeResponse,\n ClaudeTextBlock,\n ClaudeTool,\n ClaudeWebSearchResultBlock,\n} from \"~/claude/types\"\nimport type { ClaudeToolNameMapper } from \"~/claude/tool-names\"\nimport {\n createCopilotRequestSignal,\n fetchCopilot,\n getCopilotProviderContext,\n readCopilotJson,\n readCopilotText,\n} from \"~/copilot/client\"\nimport type {\n ChatCompletionResponse,\n ChatCompletionsPayload,\n Message,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\nimport type { ProxyConfig } from \"~/lib/config\"\nimport {\n getModelRouting,\n normalizeClaudeModelId,\n normalizeCopilotModelId,\n} from \"~/lib/models\"\n\nconst anthropicWebSearchToolPattern = /^web_search_\\d{8}$/\nconst claudeCodeWebSearchToolName = \"WebSearch\"\nconst searchResultLimit = 8\n\nconst webSearchInputSchema = {\n type: \"object\",\n properties: {\n query: {\n type: \"string\",\n description: \"The web search query.\",\n },\n },\n required: [\"query\"],\n additionalProperties: false,\n}\n\ninterface ResponsesWebSearchResponse {\n id: string\n created_at: number\n model: string\n output?: Array<ResponsesOutputItem>\n usage?: {\n input_tokens?: number\n output_tokens?: number\n }\n}\n\ntype ResponsesOutputItem =\n | {\n type: \"web_search_call\"\n action?: {\n query?: string\n queries?: Array<string>\n }\n }\n | {\n type: \"message\"\n content?: Array<{\n type?: string\n text?: string\n }>\n }\n | Record<string, unknown>\n\nexport interface WebSearchResult {\n title: string\n url: string\n}\n\nexport interface WebSearchExecutionResult {\n id: string\n inputTokens: number\n model: string\n outputTokens: number\n query: string\n results: Array<WebSearchResult>\n text: string\n}\n\nexport interface ClaudeWebSearchToolCall {\n query: string\n toolCall: ToolCall\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n\nconst isAnthropicNativeWebSearchTool = (tool: ClaudeTool): boolean =>\n tool.name === \"web_search\"\n && typeof tool.type === \"string\"\n && anthropicWebSearchToolPattern.test(tool.type)\n\nconst isClaudeCodeWebSearchTool = (tool: ClaudeTool): boolean =>\n tool.name === claudeCodeWebSearchToolName\n\nexport const isClaudeWebSearchTool = (tool: ClaudeTool): boolean =>\n isAnthropicNativeWebSearchTool(tool) || isClaudeCodeWebSearchTool(tool)\n\nexport const hasClaudeWebSearch = (payload: ClaudeMessagesPayload): boolean =>\n payload.tools?.some(isClaudeWebSearchTool) ?? false\n\nexport const isClaudeWebSearchToolName = (name: string): boolean =>\n name === \"web_search\" || name === claudeCodeWebSearchToolName\n\nexport const getWebSearchBackendModel = (config: ProxyConfig): string =>\n normalizeCopilotModelId(\n config.webSearchBackend?.trim() || getModelRouting().gptModel,\n )\n\nexport const prepareClaudeWebSearchDecisionPayload = (\n payload: ClaudeMessagesPayload,\n): ClaudeMessagesPayload => {\n if (!hasClaudeWebSearch(payload)) {\n return payload\n }\n\n return {\n ...payload,\n tools: payload.tools?.map((tool) =>\n isClaudeWebSearchTool(tool) ?\n {\n ...tool,\n input_schema: tool.input_schema ?? webSearchInputSchema,\n }\n : tool,\n ),\n }\n}\n\nconst textFromMessageContent = (content: ClaudeMessage[\"content\"]): string => {\n if (typeof content === \"string\") {\n return content\n }\n\n return content\n .flatMap((block) => {\n if (block.type === \"text\") return [block.text]\n if (block.type === \"tool_result\") return [block.content]\n return []\n })\n .join(\"\\n\\n\")\n}\n\nconst getRequestedQuery = (payload: ClaudeMessagesPayload): string => {\n const lastUserMessage = [...payload.messages]\n .reverse()\n .find((message) => message.role === \"user\")\n const rawText = lastUserMessage ? textFromMessageContent(lastUserMessage.content) : \"\"\n const cleaned = rawText\n .replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n const searchMatch = cleaned.match(/\\bsearch(?:\\s+the\\s+web)?(?:\\s+for)?\\s+(.+)$/i)\n return searchMatch?.[1]?.trim() || cleaned || \"web search\"\n}\n\nconst getQueryFromToolArguments = (value: string): string | undefined => {\n const trimmed = value.trim()\n if (!trimmed) {\n return undefined\n }\n\n try {\n const parsed = JSON.parse(trimmed) as unknown\n if (\n isRecord(parsed)\n && typeof parsed.query === \"string\"\n && parsed.query.trim()\n ) {\n return parsed.query.trim()\n }\n } catch {\n return trimmed\n }\n\n return trimmed\n}\n\nexport const getClaudeWebSearchToolCallFromChatResponse = (\n response: ChatCompletionResponse,\n toolNameMapper: ClaudeToolNameMapper,\n): ClaudeWebSearchToolCall | undefined => {\n const toolCall = response.choices\n .flatMap((choice) => choice.message.tool_calls ?? [])\n .find((call) =>\n isClaudeWebSearchToolName(\n toolNameMapper.toClaude(call.function.name),\n ),\n )\n\n if (!toolCall) {\n return undefined\n }\n\n const query = getQueryFromToolArguments(toolCall.function.arguments)\n return query ? { query, toolCall } : undefined\n}\n\nconst buildSearchInput = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n): string => {\n const systemText =\n typeof payload.system === \"string\" ? payload.system\n : Array.isArray(payload.system) ?\n payload.system.map((block: ClaudeTextBlock) => block.text).join(\"\\n\\n\")\n : \"\"\n const messages = payload.messages\n .map((message) => `${message.role}: ${textFromMessageContent(message.content)}`)\n .join(\"\\n\\n\")\n\n return [\n \"You are fulfilling an Anthropic web_search server tool request for Claude Code.\",\n \"Search the web using the provided web_search_preview tool.\",\n `Requested web search query: ${requestedQuery}`,\n \"Return useful search results as plain text lines in this exact shape:\",\n \"1. Title - https://example.com/page\",\n \"Include only real source URLs from the search results.\",\n systemText ? `System context:\\n${systemText}` : \"\",\n `Conversation:\\n${messages}`,\n ]\n .filter(Boolean)\n .join(\"\\n\\n\")\n}\n\nconst buildWebSearchRequestPayload = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n model: string,\n) => ({\n model,\n input: buildSearchInput(payload, requestedQuery),\n tools: [{ type: \"web_search_preview\" }],\n max_output_tokens: Math.max(256, Math.min(payload.max_tokens ?? 1024, 1200)),\n temperature: payload.temperature,\n top_p: payload.top_p,\n})\n\nconst getSearchQuery = (\n response: ResponsesWebSearchResponse,\n requestedQuery: string,\n): string => {\n for (const item of response.output ?? []) {\n if (item.type !== \"web_search_call\") continue\n const action = isRecord(item.action) ? item.action : undefined\n const queries = Array.isArray(action?.queries) ? action.queries : []\n const query =\n typeof action?.query === \"string\" ? action.query\n : typeof queries[0] === \"string\" ? queries[0]\n : undefined\n if (query) return query\n }\n\n return requestedQuery.slice(0, 200)\n}\n\nconst getResponseText = (response: ResponsesWebSearchResponse): string =>\n (response.output ?? [])\n .flatMap((item) => {\n if (item.type !== \"message\") return []\n const content = Array.isArray(item.content) ? item.content : []\n return content.flatMap((part) =>\n isRecord(part)\n && part.type === \"output_text\"\n && typeof part.text === \"string\" ?\n [part.text]\n : [],\n )\n })\n .join(\"\\n\")\n .trim()\n\nconst cleanTitle = (line: string, url: string): string => {\n const beforeUrl = line.slice(0, line.indexOf(url))\n const cleaned = beforeUrl\n .replace(/^\\s*(?:[-*]|\\d+[.)])\\s*/, \"\")\n .replace(/\\s*(?:[-:|])\\s*$/, \"\")\n .trim()\n return cleaned || new URL(url).hostname\n}\n\nconst parseSearchResults = (text: string): Array<WebSearchResult> => {\n const results: Array<WebSearchResult> = []\n const seenUrls = new Set<string>()\n\n for (const line of text.split(/\\r?\\n/)) {\n const markdownMatch = line.match(/\\[([^\\]]+)]\\((https?:\\/\\/[^)\\s]+)\\)/)\n const url = markdownMatch?.[2] ?? line.match(/https?:\\/\\/[^\\s)]+/)?.[0]\n if (!url || seenUrls.has(url)) {\n continue\n }\n\n seenUrls.add(url)\n results.push({\n title: markdownMatch?.[1]?.trim() || cleanTitle(line, url),\n url,\n })\n\n if (results.length >= searchResultLimit) {\n break\n }\n }\n\n return results\n}\n\nconst createFailedSearchExecution = (\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n model: string,\n message: string,\n): WebSearchExecutionResult => ({\n id: `msg_${randomUUID().replaceAll(\"-\", \"\")}`,\n inputTokens: 0,\n model,\n outputTokens: 0,\n query: requestedQuery || getRequestedQuery(payload),\n results: [],\n text: message,\n})\n\nexport const createClaudeWebSearchExecution = async (\n config: ProxyConfig,\n payload: ClaudeMessagesPayload,\n requestedQuery: string,\n options: { requestId?: string; signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<WebSearchExecutionResult> => {\n const backendModel = getWebSearchBackendModel(config)\n const signal = createCopilotRequestSignal(options.signal, options.timeoutMs)\n const response = await fetchCopilot(\n getCopilotProviderContext(config),\n \"/responses\",\n {\n method: \"POST\",\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(\n buildWebSearchRequestPayload(payload, requestedQuery, backendModel),\n ),\n },\n {\n initiator: \"agent\",\n requestId: options.requestId,\n signal,\n timeoutMs: options.timeoutMs,\n },\n )\n\n if (!response.ok) {\n const detail = await readCopilotText(\n response,\n signal,\n options.timeoutMs,\n ).catch(() => \"\")\n return createFailedSearchExecution(\n payload,\n requestedQuery,\n backendModel,\n [\n `Copilot web search is not available for model ${backendModel}.`,\n detail ? `Upstream response: ${detail}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n )\n }\n\n const upstream = await readCopilotJson<ResponsesWebSearchResponse>(\n response,\n signal,\n options.timeoutMs,\n )\n const text = getResponseText(upstream)\n const query = getSearchQuery(upstream, requestedQuery)\n const results = parseSearchResults(text)\n\n if (results.length === 0 && !text.trim()) {\n return createFailedSearchExecution(\n payload,\n requestedQuery,\n upstream.model,\n \"Copilot web search did not return search results.\",\n )\n }\n\n return {\n id: upstream.id,\n inputTokens: upstream.usage?.input_tokens ?? 0,\n model: upstream.model,\n outputTokens: upstream.usage?.output_tokens ?? 0,\n query,\n results,\n text,\n }\n}\n\nconst buildSearchResultBlock = (\n toolUseId: string,\n results: Array<WebSearchResult>,\n): ClaudeWebSearchResultBlock => ({\n type: \"web_search_tool_result\",\n tool_use_id: toolUseId,\n content:\n results.length > 0 ?\n results.map((result) => ({\n type: \"web_search_result\" as const,\n title: result.title,\n url: result.url,\n encrypted_content: \"\",\n page_age: null,\n }))\n : {\n type: \"web_search_tool_result_error\",\n error_code: \"unavailable\",\n },\n})\n\nexport const createClaudeWebSearchResponse = (\n search: WebSearchExecutionResult,\n): ClaudeResponse => {\n const toolUseId = `srvtoolu_${randomUUID().replaceAll(\"-\", \"\")}`\n const content: Array<ClaudeAssistantContentBlock> = [\n {\n type: \"server_tool_use\",\n id: toolUseId,\n name: \"web_search\",\n input: { query: search.query },\n },\n buildSearchResultBlock(toolUseId, search.results),\n ]\n\n if (search.text) {\n content.push({ type: \"text\", text: search.text })\n }\n\n return {\n id: search.id,\n type: \"message\",\n role: \"assistant\",\n content,\n model: normalizeClaudeModelId(search.model),\n stop_reason: \"end_turn\",\n stop_sequence: null,\n usage: {\n input_tokens: search.inputTokens,\n output_tokens: search.outputTokens,\n server_tool_use: { web_search_requests: 1 },\n },\n }\n}\n\nconst getWebSearchResultText = (search: WebSearchExecutionResult): string => {\n if (search.results.length === 0) {\n return search.text || \"Web search did not return search results.\"\n }\n\n return [\n `Web search results for query: \"${search.query}\"`,\n \"\",\n ...search.results.map((result, index) =>\n `${index + 1}. ${result.title} - ${result.url}`,\n ),\n ].join(\"\\n\")\n}\n\n// Delivered as a user turn, not a system turn, for two reasons. Copilot's\n// Claude-family models reject a conversation that does not end with a user\n// message (\"This model does not support assistant message prefill\"), and this\n// message is appended last. Anthropic removed prefill support in Opus 4.7+ /\n// Sonnet 4.6+, so this is an upstream constraint rather than a Copilot quirk.\nconst createWebSearchResultContextMessage = (\n search: WebSearchExecutionResult,\n): Message => ({\n role: \"user\",\n content: [\n \"Trusted bridge retrieval context: the assistant selected web_search, and copilot-relay executed it.\",\n \"Use this context to complete the request. Do not describe it as user-provided or injected.\",\n \"If the user requested a specific output format, answer using only matching information from this context.\",\n \"If the user asked for a URL only, output only that URL with no surrounding text.\",\n \"\",\n `Query: ${search.query}`,\n \"\",\n getWebSearchResultText(search),\n ].join(\"\\n\"),\n})\n\nconst isWebSearchTool = (\n tool: Tool,\n toolNameMapper: ClaudeToolNameMapper,\n): boolean =>\n isClaudeWebSearchToolName(toolNameMapper.toClaude(tool.function.name))\n\n// The search already ran, so re-advertising it would let the model select it a\n// second time. The final response is never re-checked for a web-search call, so\n// that selection would reach Claude Code as a client tool_use named WebSearch\n// instead of a server_tool_use block. Removing the tool makes that structurally\n// impossible instead of relying on a prompt instruction.\nconst removeWebSearchTool = (\n tools: ChatCompletionsPayload[\"tools\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tools\"] => {\n if (!tools || tools.length === 0) {\n return undefined\n }\n\n const remaining = tools.filter((tool) => !isWebSearchTool(tool, toolNameMapper))\n // An empty array is not the same as an absent field upstream.\n return remaining.length > 0 ? remaining : undefined\n}\n\nconst normalizeFinalToolChoice = (\n toolChoice: ChatCompletionsPayload[\"tool_choice\"],\n tools: ChatCompletionsPayload[\"tools\"],\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload[\"tool_choice\"] => {\n if (!tools || tools.length === 0) {\n return undefined\n }\n\n // \"required\" would force a tool call on a pass whose job is to answer.\n if (toolChoice === \"required\") {\n return \"auto\"\n }\n\n // A choice pinned to the now-removed search tool would be unsatisfiable.\n if (\n toolChoice\n && typeof toolChoice === \"object\"\n && isClaudeWebSearchToolName(\n toolNameMapper.toClaude(toolChoice.function.name),\n )\n ) {\n return \"auto\"\n }\n\n return toolChoice\n}\n\nexport const createFinalWebSearchPayload = (\n payload: ChatCompletionsPayload,\n search: WebSearchExecutionResult,\n toolNameMapper: ClaudeToolNameMapper,\n): ChatCompletionsPayload => {\n const tools = removeWebSearchTool(payload.tools, toolNameMapper)\n\n // payload.messages is passed through untouched. An earlier version rewrote\n // tool messages to developer messages and stripped assistant tool_calls,\n // because removing every tool definition would have orphaned those\n // references and produced a 400. The definitions now survive, so the rewrite\n // is unnecessary — and keeping the history byte-identical preserves the\n // prompt-cache prefix shared with the decision pass.\n return {\n ...payload,\n stream: false,\n tools,\n tool_choice: normalizeFinalToolChoice(\n payload.tool_choice,\n tools,\n toolNameMapper,\n ),\n messages: [\n ...payload.messages,\n createWebSearchResultContextMessage(search),\n ],\n }\n}\n\nexport const mergeWebSearchAndFinalResponse = (\n searchResponse: ClaudeResponse,\n finalResponse: ClaudeResponse,\n): ClaudeResponse => ({\n ...finalResponse,\n content: [...searchResponse.content.slice(0, 2), ...finalResponse.content],\n usage: {\n ...finalResponse.usage,\n input_tokens:\n searchResponse.usage.input_tokens + finalResponse.usage.input_tokens,\n output_tokens:\n searchResponse.usage.output_tokens + finalResponse.usage.output_tokens,\n server_tool_use: { web_search_requests: 1 },\n },\n})\n","// Streaming support for turns that advertise Claude's WebSearch tool.\n//\n// The relay has to know whether the model selected web_search before it can\n// decide how to answer: a search turn needs a bridge-managed search plus a\n// second recompose pass, while every other turn is an ordinary completion. That\n// used to be resolved by forcing `stream: false` on any request that merely\n// *advertised* WebSearch. Claude Code advertises it by default, so most turns\n// paid a full non-streaming completion and then replayed it as synthetic SSE.\n//\n// The decision does not actually need the whole response — only enough of it to\n// rule a web-search call in or out. A chat completion emits tool call names in\n// the first delta that opens each tool call, so the question is settled the\n// moment the first tool call appears, or as soon as content arrives without one.\n// This module streams the decision pass and buffers only up to that point:\n//\n// no search -> replay the buffered chunks, then stream the rest live\n// search -> accumulate the full response and hand it to the bridge path\n//\n// A search turn therefore behaves exactly as it did before, and every other turn\n// gets real streaming back.\nimport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ToolCall,\n} from \"~/copilot/types\"\nimport type { ClaudeToolNameMapper } from \"~/claude/tool-names\"\n\nexport type WebSearchStreamDecision =\n | { kind: \"streamed\"; buffered: Array<ChatCompletionChunk> }\n | { kind: \"webSearch\"; response: ChatCompletionResponse }\nconst isWebSearchName = (\n name: string | undefined,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n): boolean => !!name && isWebSearchToolName(toolNameMapper.toClaude(name))\n\n// Only a named tool call or a finish_reason settles the question.\n//\n// Text does NOT settle it. Copilot routinely emits a preamble — \"I'll search\n// for that now.\" — before the tool call, so treating content as proof that no\n// search is coming lets the later web_search call escape unintercepted and\n// reach the client as a client tool_use named WebSearch. That is a malformed\n// turn: Claude Code would try to run a tool it expects the server to execute.\n//\n// Text is still streamed live while the question is open (see onChunk), so\n// waiting costs no perceived latency.\nconst chunkSettlesDecision = (chunk: ChatCompletionChunk): boolean => {\n const choice = chunk.choices[0]\n if (!choice) {\n return false\n }\n\n return (\n choice.delta?.tool_calls?.some((call) => call.function?.name) === true\n || !!choice.finish_reason\n )\n}\n\nconst chunkHasWebSearchCall = (\n chunk: ChatCompletionChunk,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n): boolean =>\n chunk.choices[0]?.delta?.tool_calls?.some((call) =>\n isWebSearchName(call.function?.name, toolNameMapper, isWebSearchToolName),\n ) ?? false\n\n// Rebuilds the non-streaming shape the bridge path expects. Only reached on\n// turns that actually selected web_search, so the cost is paid by the requests\n// that were already paying it.\nexport const accumulateChunks = (\n chunks: Array<ChatCompletionChunk>,\n): ChatCompletionResponse => {\n const first = chunks[0]\n const toolCalls: Array<ToolCall> = []\n let content = \"\"\n let reasoning = \"\"\n let finishReason: ChatCompletionResponse[\"choices\"][number][\"finish_reason\"] =\n null\n let usage: ChatCompletionResponse[\"usage\"]\n\n for (const chunk of chunks) {\n if (chunk.usage) {\n usage = chunk.usage\n }\n\n const choice = chunk.choices[0]\n if (!choice) {\n continue\n }\n\n if (typeof choice.delta?.content === \"string\") {\n content += choice.delta.content\n }\n\n const chunkReasoning =\n choice.delta?.reasoning_text ?? choice.delta?.reasoning_content\n if (typeof chunkReasoning === \"string\") {\n reasoning += chunkReasoning\n }\n\n if (choice.finish_reason) {\n finishReason = choice.finish_reason\n }\n\n for (const call of choice.delta?.tool_calls ?? []) {\n // Copilot indexes tool calls; arguments arrive as fragments across chunks.\n const existing = toolCalls[call.index]\n if (existing) {\n existing.function.arguments += call.function?.arguments ?? \"\"\n continue\n }\n\n toolCalls[call.index] = {\n id: call.id ?? `call_${call.index}`,\n type: \"function\",\n function: {\n name: call.function?.name ?? \"\",\n arguments: call.function?.arguments ?? \"\",\n },\n }\n }\n }\n\n const collectedToolCalls = toolCalls.filter(Boolean)\n\n return {\n id: first?.id ?? \"chat_stream_accumulated\",\n object: \"chat.completion\",\n created: first?.created ?? 0,\n model: first?.model ?? \"\",\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: content || null,\n ...(reasoning ? { reasoning_text: reasoning } : {}),\n ...(collectedToolCalls.length > 0 ?\n { tool_calls: collectedToolCalls }\n : {}),\n },\n finish_reason: finishReason ?? \"stop\",\n },\n ],\n ...(usage ? { usage } : {}),\n }\n}\n\n// Reads the decision pass only as far as needed to classify the turn.\n//\n// onChunk receives every chunk consumed while the question is still open, so a\n// caller can stream them live. Turns that never search — the common case, since\n// Claude Code advertises WebSearch on every turn — are then indistinguishable\n// from an ordinary stream. When a search does appear, `alreadyStreamed` tells\n// the caller that a message_start and some content blocks are already on the\n// wire, so the search blocks must continue that message rather than start a new\n// one. The resulting order is text -> server_tool_use -> web_search_tool_result\n// -> text, which is Anthropic's documented native shape for a search turn.\nexport const resolveWebSearchStreamDecision = async (\n stream: AsyncIterable<{ data?: string }>,\n toolNameMapper: ClaudeToolNameMapper,\n isWebSearchToolName: (name: string) => boolean,\n onChunk?: (chunk: ChatCompletionChunk) => Promise<void>,\n): Promise<{\n decision: WebSearchStreamDecision\n rest: AsyncIterable<{ data?: string }>\n alreadyStreamed: boolean\n}> => {\n const buffered: Array<ChatCompletionChunk> = []\n const iterator = stream[Symbol.asyncIterator]()\n let sawWebSearch = false\n let settled = false\n let done = false\n let alreadyStreamed = false\n\n while (!settled) {\n const next = await iterator.next()\n if (next.done) {\n done = true\n break\n }\n\n const raw = next.value\n if (raw.data === \"[DONE]\") {\n done = true\n break\n }\n if (!raw.data) {\n continue\n }\n\n let chunk: ChatCompletionChunk\n try {\n chunk = JSON.parse(raw.data) as ChatCompletionChunk\n } catch {\n continue\n }\n\n buffered.push(chunk)\n\n if (chunkHasWebSearchCall(chunk, toolNameMapper, isWebSearchToolName)) {\n sawWebSearch = true\n settled = true\n break\n }\n\n // Not a search call, so this chunk belongs to the visible answer either way.\n // Emitting it now is what keeps non-search turns streaming in real time.\n if (onChunk) {\n await onChunk(chunk)\n alreadyStreamed = true\n }\n\n if (chunkSettlesDecision(chunk)) {\n settled = true\n }\n }\n\n // The remainder of the upstream stream, if the decision was reached early.\n const rest: AsyncIterable<{ data?: string }> = {\n async *[Symbol.asyncIterator]() {\n if (done) {\n return\n }\n while (true) {\n const next = await iterator.next()\n if (next.done) {\n return\n }\n yield next.value\n }\n },\n }\n\n if (!sawWebSearch) {\n // Anything handed to onChunk is already on the wire; replaying it would\n // duplicate content. Only chunks consumed without being emitted are\n // returned for the caller to flush.\n return {\n decision: { kind: \"streamed\", buffered: onChunk ? [] : buffered },\n rest,\n alreadyStreamed,\n }\n }\n\n // A web-search turn needs the whole response, so drain what is left.\n for await (const raw of rest) {\n if (raw.data === \"[DONE]\") {\n break\n }\n if (!raw.data) {\n continue\n }\n try {\n buffered.push(JSON.parse(raw.data) as ChatCompletionChunk)\n } catch {\n continue\n }\n }\n\n return {\n decision: { kind: \"webSearch\", response: accumulateChunks(buffered) },\n rest: { async *[Symbol.asyncIterator]() {} },\n alreadyStreamed,\n }\n}\n","// Token estimator for Claude Code's /v1/messages/count_tokens endpoint.\nimport type {\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n ToolCall,\n} from \"~/copilot/types\"\n\nexport interface TokenizerModel {\n capabilities: {\n tokenizer: string\n }\n id: string\n}\n\nconst encodingMap = {\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 encodingMap\n\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\nconst encodingCache = new Map<string, Encoder>()\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\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 // Claude Code needs a fast local estimate, not exact upstream billing.\n // Use a fixed image overhead plus URL token count to avoid severe\n // under-counting when screenshots are present.\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\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n // Mirrors OpenAI chat formatting heuristics closely enough for UI budgeting;\n // /count_tokens is advisory and should not call real Copilot services.\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\") tokens += tokensPerName\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\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) return 0\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n numTokens += 3\n return numTokens\n}\n\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) return cached\n }\n\n const supported = encoding as SupportedEncoding\n if (!(supported in encodingMap)) {\n const fallback = (await encodingMap.o200k_base()) as Encoder\n encodingCache.set(encoding, fallback)\n return fallback\n }\n\n const mod = (await encodingMap[supported]()) as Encoder\n encodingCache.set(encoding, mod)\n return mod\n}\n\nexport const getTokenizerFromModel = (model: TokenizerModel): string =>\n model.capabilities.tokenizer || \"o200k_base\"\n\nconst getModelConstants = (model: TokenizerModel) =>\n // Tool/function overhead differs by tokenizer family. Keep these constants\n // small and explicit so count_tokens remains deterministic in tests.\n 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\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 if (typeof prop !== \"object\" || prop === null) return tokens\n\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 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 if (paramDesc.endsWith(\".\")) paramDesc = paramDesc.slice(0, -1)\n\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (excludedKeys.has(propertyName)) continue\n const propertyValue = param[propertyName]\n const propertyText =\n typeof propertyValue === \"string\" ? propertyValue : JSON.stringify(propertyValue)\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n\n return tokens\n}\n\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") return 0\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\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(\".\")) fDesc = fDesc.slice(0, -1)\n tokens += encoder.encode(`${fName}:${fDesc}`).length\n if (typeof func.parameters === \"object\" && func.parameters !== null) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\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\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: TokenizerModel,\n): Promise<{ input: number; output: number }> => {\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const inputMessages = payload.messages.filter((msg) => msg.role !== \"assistant\")\n const outputMessages = payload.messages.filter((msg) => msg.role === \"assistant\")\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 { input: inputTokens, output: outputTokens }\n}\n","// Claude Code route surface: /v1/messages, /v1/messages/count_tokens, and /v1/models.\nimport { Hono } from \"hono\"\nimport { streamSSE } from \"hono/streaming\"\n\nimport {\n type ClaudeAssistantContentBlock,\n type ClaudeMessagesPayload,\n type ClaudeResponse,\n type ClaudeStreamEventData,\n type ClaudeStreamState,\n} from \"~/claude/types\"\nimport {\n translateModelName,\n translateToClaude,\n translateToOpenAI,\n} from \"~/claude/translate\"\nimport {\n translateChunkToClaudeEvents,\n translateErrorToClaudeErrorEvent,\n} from \"~/claude/stream\"\nimport {\n createClaudeToolNameMapper,\n getToolNameMapperOptionsForModel,\n} from \"~/claude/tool-names\"\nimport {\n createClaudeWebSearchExecution,\n createClaudeWebSearchResponse,\n createFinalWebSearchPayload,\n getClaudeWebSearchToolCallFromChatResponse,\n hasClaudeWebSearch,\n isClaudeWebSearchToolName,\n mergeWebSearchAndFinalResponse,\n prepareClaudeWebSearchDecisionPayload,\n} from \"~/claude/web-search\"\nimport { resolveWebSearchStreamDecision } from \"~/claude/web-search-stream\"\nimport type { ProxyEnv } from \"~/lib/config\"\nimport { HTTPError, ProxyNotImplementedError } from \"~/lib/error\"\nimport { log } from \"~/lib/log\"\nimport { getExposedModelIds } from \"~/lib/models\"\nimport { getTokenCount, type TokenizerModel } from \"~/lib/tokenizer\"\nimport type { ChatCompletionChunk, ChatCompletionResponse } from \"~/copilot/types\"\nimport { createChatCompletions } from \"~/copilot/chat\"\nimport { createCopilotRequestSignal } from \"~/copilot/client\"\n\nexport const claudeRoutes = new Hono<ProxyEnv>()\n\nconst createTokenCountModel = (modelId: string): TokenizerModel => ({\n capabilities: { tokenizer: \"o200k_base\" },\n id: modelId,\n})\n\nconst isNonStreamingResponse = (\n response: Awaited<ReturnType<typeof createChatCompletions>>,\n): response is ChatCompletionResponse =>\n typeof response === \"object\"\n && response !== null\n && Object.hasOwn(response, \"choices\")\n\nconst getClaudeRequestedThinkEffort = (\n payload: ClaudeMessagesPayload,\n): string => payload.reasoning_effort ?? \"none\"\n\nconst getClaudeRequestedThinking = (\n payload: ClaudeMessagesPayload,\n): string => {\n if (!payload.thinking) {\n return \"none\"\n }\n\n return [\n `type:${payload.thinking.type}`,\n `budget:${payload.thinking.budget_tokens ?? \"none\"}`,\n ].join(\",\")\n}\n\nconst eventsFromClaudeResponse = (\n response: ClaudeResponse,\n): Array<ClaudeStreamEventData> => {\n // When Claude Code asks for streaming but Copilot returned a completed JSON\n // response, synthesize the minimal Claude SSE sequence so client bookkeeping\n // remains identical to a real streaming response.\n const events: Array<ClaudeStreamEventData> = [\n {\n type: \"message_start\",\n message: {\n id: response.id,\n type: response.type,\n role: response.role,\n content: [],\n model: response.model,\n stop_reason: null,\n stop_sequence: null,\n usage: response.usage,\n },\n },\n ]\n\n response.content.forEach((block: ClaudeAssistantContentBlock, index) => {\n events.push({\n type: \"content_block_start\",\n index,\n content_block: block,\n })\n\n if (block.type === \"text\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"text_delta\", text: block.text },\n })\n } else if (block.type === \"thinking\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"thinking_delta\", thinking: block.thinking },\n })\n } else if (block.type === \"tool_use\" || block.type === \"server_tool_use\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: {\n type: \"input_json_delta\",\n partial_json: JSON.stringify(block.input),\n },\n })\n }\n\n events.push({ type: \"content_block_stop\", index })\n })\n\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: response.stop_reason,\n stop_sequence: response.stop_sequence,\n },\n usage: { output_tokens: response.usage.output_tokens },\n })\n events.push({ type: \"message_stop\" })\n return events\n}\n\n// Emits a completed response into a stream that is already open.\n//\n// Used when the web-search decision pass streamed a preamble before the search\n// was detected: message_start has gone out and some block indices are spent, so\n// the search blocks have to continue that message. Mirrors\n// eventsFromClaudeResponse minus message_start, with indices taken from the\n// live stream state. The result is the native shape for a search turn —\n// text -> server_tool_use -> web_search_tool_result -> text.\nconst continuationEventsFromClaudeResponse = (\n response: ClaudeResponse,\n state: ClaudeStreamState,\n): Array<ClaudeStreamEventData> => {\n const events: Array<ClaudeStreamEventData> = []\n\n // The preamble block may still be open if the tool call arrived mid-text.\n if (state.contentBlockOpen) {\n events.push({ type: \"content_block_stop\", index: state.contentBlockIndex })\n state.contentBlockIndex += 1\n state.contentBlockOpen = false\n state.thinkingBlockOpen = false\n }\n\n for (const block of response.content) {\n const index = state.contentBlockIndex\n events.push({ type: \"content_block_start\", index, content_block: block })\n\n if (block.type === \"text\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"text_delta\", text: block.text },\n })\n } else if (block.type === \"thinking\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: { type: \"thinking_delta\", thinking: block.thinking },\n })\n } else if (block.type === \"tool_use\" || block.type === \"server_tool_use\") {\n events.push({\n type: \"content_block_delta\",\n index,\n delta: {\n type: \"input_json_delta\",\n partial_json: JSON.stringify(block.input),\n },\n })\n }\n\n events.push({ type: \"content_block_stop\", index })\n state.contentBlockIndex += 1\n }\n\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: response.stop_reason,\n stop_sequence: response.stop_sequence,\n },\n usage: { output_tokens: response.usage.output_tokens },\n })\n events.push({ type: \"message_stop\" })\n return events\n}\n\ntype ClaudeStreamEventWriter = (\n event: ClaudeStreamEventData,\n) => Promise<void>\n\nconst createQueuedClaudeStreamWriter = (\n write: ClaudeStreamEventWriter,\n): ClaudeStreamEventWriter => {\n let pending = Promise.resolve()\n\n return (event) => {\n pending = pending.then(() => write(event))\n return pending\n }\n}\n\nconst writeClaudeStreamEvents = async (\n events: Array<ClaudeStreamEventData>,\n writeEvent: ClaudeStreamEventWriter,\n): Promise<void> => {\n for (const event of events) {\n await writeEvent(event)\n }\n}\n\nconst handleClaudeMessageRequest = async (\n config: ProxyEnv[\"Variables\"][\"config\"],\n claudePayload: ClaudeMessagesPayload,\n requestSignal: AbortSignal | undefined,\n requestId: string,\n writeEvent?: ClaudeStreamEventWriter,\n): Promise<ClaudeResponse | undefined> => {\n const upstreamModel = translateModelName(claudePayload.model)\n const shouldLetModelDecideWebSearch = hasClaudeWebSearch(claudePayload)\n const decisionPayload =\n shouldLetModelDecideWebSearch ?\n prepareClaudeWebSearchDecisionPayload(claudePayload)\n : claudePayload\n const toolNameMapper = createClaudeToolNameMapper(decisionPayload.tools, {\n ...getToolNameMapperOptionsForModel(upstreamModel),\n })\n const openAIPayload = translateToOpenAI(\n decisionPayload,\n undefined,\n toolNameMapper,\n )\n // A streaming client that advertises WebSearch used to be forced into a full\n // non-streaming completion, because the relay had to know whether the model\n // selected web_search before it could decide how to answer. Claude Code\n // advertises WebSearch by default, so that penalised most turns for a branch\n // few of them took. The decision pass now streams, and resolveWebSearchStreamDecision\n // buffers only as far as it takes to rule a search call in or out.\n const canStreamWebSearchDecision = shouldLetModelDecideWebSearch && !!writeEvent\n if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) {\n openAIPayload.stream = false\n }\n const response = await createChatCompletions(config, openAIPayload, {\n client: \"claude\",\n requestedModel: claudePayload.model,\n requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),\n requestedThinking: getClaudeRequestedThinking(claudePayload),\n requestId,\n signal: requestSignal,\n timeoutMs: config.upstreamTimeoutMs,\n })\n\n // Streamed decision pass: classify the turn, then either replay the buffered\n // chunks and keep streaming, or hand the accumulated response to the bridge\n // path below exactly as the non-streaming route would have.\n let decidedResponse: ChatCompletionResponse | undefined\n let bufferedChunks: Array<ChatCompletionChunk> = []\n let remainingStream: AsyncIterable<{ data?: string }> | undefined\n // Shared with the streaming tail so block indices stay monotonic across a\n // decision pass that streamed some content before a search was detected.\n const streamState: ClaudeStreamState = {\n messageStartSent: false,\n contentBlockIndex: 0,\n contentBlockOpen: false,\n thinkingBlockOpen: false,\n toolCalls: {},\n }\n let streamedBeforeDecision = false\n\n if (canStreamWebSearchDecision && !isNonStreamingResponse(response)) {\n const { decision, rest, alreadyStreamed } =\n await resolveWebSearchStreamDecision(\n response,\n toolNameMapper,\n isClaudeWebSearchToolName,\n // Emit while classifying so a turn that never searches streams in real\n // time. Copilot often writes a preamble before calling a tool, so the\n // classifier cannot treat text as proof that no search is coming.\n async (chunk) => {\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(chunk, streamState, toolNameMapper),\n writeEvent!,\n )\n },\n )\n streamedBeforeDecision = alreadyStreamed\n if (decision.kind === \"webSearch\") {\n decidedResponse = decision.response\n } else {\n bufferedChunks = decision.buffered\n remainingStream = rest\n }\n }\n\n const effectiveResponse = decidedResponse ?? response\n\n if (isNonStreamingResponse(effectiveResponse)) {\n const webSearchToolCall = shouldLetModelDecideWebSearch ?\n getClaudeWebSearchToolCallFromChatResponse(effectiveResponse, toolNameMapper)\n : undefined\n let claudeResponse: ClaudeResponse\n\n if (webSearchToolCall) {\n const search = await createClaudeWebSearchExecution(\n config,\n claudePayload,\n webSearchToolCall.query,\n { requestId, signal: requestSignal, timeoutMs: config.upstreamTimeoutMs },\n )\n const searchResponse = createClaudeWebSearchResponse(search)\n\n if (search.results.length === 0) {\n claudeResponse = searchResponse\n } else {\n const finalResponse = await createChatCompletions(\n config,\n createFinalWebSearchPayload(openAIPayload, search, toolNameMapper),\n {\n client: \"claude\",\n requestedModel: claudePayload.model,\n requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),\n requestedThinking: getClaudeRequestedThinking(claudePayload),\n requestId,\n signal: requestSignal,\n timeoutMs: config.upstreamTimeoutMs,\n },\n )\n\n if (!isNonStreamingResponse(finalResponse)) {\n throw new HTTPError(\n \"Claude web search final answer request unexpectedly streamed\",\n new Response(\"Claude web search final answer request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n claudeResponse = mergeWebSearchAndFinalResponse(\n searchResponse,\n translateToClaude(finalResponse, toolNameMapper),\n )\n }\n } else {\n claudeResponse = translateToClaude(effectiveResponse, toolNameMapper)\n }\n\n if (writeEvent) {\n await writeClaudeStreamEvents(\n // When the decision pass already streamed a preamble, the message is\n // open and its early block indices are spent. Continue it instead of\n // starting a second one, which would leave the client with two\n // message_start events and colliding indices.\n streamedBeforeDecision ?\n continuationEventsFromClaudeResponse(claudeResponse, streamState)\n : eventsFromClaudeResponse(claudeResponse),\n writeEvent,\n )\n return undefined\n }\n\n return claudeResponse\n }\n\n // Only reachable when the decision pass was forced non-streaming and came back\n // streamed anyway. A streamed decision that found no search falls through to\n // the normal streaming path below with its buffered chunks.\n if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) {\n throw new HTTPError(\n \"Claude web search model-decision request unexpectedly streamed\",\n new Response(\"Claude web search model-decision request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n if (!writeEvent) {\n throw new HTTPError(\n \"Claude non-streaming request unexpectedly streamed\",\n new Response(\"Claude non-streaming request unexpectedly streamed\", {\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n }),\n )\n }\n\n // streamState is declared above and shared with the decision pass, so block\n // indices stay monotonic when classification already emitted content.\n\n // Chunks consumed while classifying the turn, replayed in order so the client\n // sees an unbroken stream.\n for (const chunk of bufferedChunks) {\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(chunk, streamState, toolNameMapper),\n writeEvent,\n )\n }\n\n // Past the isNonStreamingResponse guard, so this is the streamed form. Either\n // the remainder left by the decision classifier, or the whole stream when the\n // turn never advertised WebSearch.\n const upstreamStream = remainingStream ?? effectiveResponse\n\n for await (const rawEvent of upstreamStream) {\n if (rawEvent.data === \"[DONE]\") {\n break\n }\n if (!rawEvent.data) {\n continue\n }\n\n let chunk: ChatCompletionChunk\n try {\n chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk\n } catch {\n continue\n }\n\n await writeClaudeStreamEvents(\n translateChunkToClaudeEvents(\n chunk,\n streamState,\n toolNameMapper,\n ),\n writeEvent,\n )\n }\n\n return undefined\n}\n\nclaudeRoutes.get(\"/models\", (c) =>\n c.json({\n object: \"list\",\n data: getExposedModelIds().map((id) => ({\n id,\n object: \"model\",\n created: 0,\n owned_by: \"github-copilot\",\n })),\n }),\n)\n\nclaudeRoutes.post(\"/messages\", async (c) => {\n const config = c.get(\"config\")\n const requestId = c.get(\"requestId\")\n const claudePayload = await c.req.json<ClaudeMessagesPayload>()\n const requestSignal = createCopilotRequestSignal(\n c.req.raw.signal,\n config.upstreamTimeoutMs,\n )\n log.debug(\"Full Claude request payload\", { payload: claudePayload })\n\n if (claudePayload.stream) {\n return streamSSE(c, async (stream) => {\n const streamStarted = performance.now()\n const writeEvent = createQueuedClaudeStreamWriter((event) =>\n stream.writeSSE({\n event: event.type,\n data: JSON.stringify(event),\n }),\n )\n\n try {\n await handleClaudeMessageRequest(\n config,\n claudePayload,\n requestSignal,\n requestId,\n writeEvent,\n )\n } catch (error) {\n log.error(\"Error during Claude stream request:\", error)\n const errorEvent = translateErrorToClaudeErrorEvent()\n await writeEvent(errorEvent)\n } finally {\n log.info(\n `request_id=${requestId} stream completed ${Math.round(performance.now() - streamStarted)}ms`,\n )\n }\n })\n }\n\n try {\n return c.json(await handleClaudeMessageRequest(\n config,\n claudePayload,\n requestSignal,\n requestId,\n ))\n } catch (error) {\n if (error instanceof ProxyNotImplementedError) {\n c.set(\"requestErrorMessage\", error.message)\n return c.json(\n { error: { type: error.name, message: error.message } },\n 501,\n )\n }\n\n if (error instanceof HTTPError) {\n const text = await error.response.text().catch(() => \"\")\n c.set(\"requestErrorMessage\", error.detail ?? text.slice(0, 240))\n return new Response(text, {\n status: error.response.status,\n headers: {\n \"content-type\":\n error.response.headers.get(\"content-type\") ?? \"application/json\",\n },\n })\n }\n\n throw error\n }\n})\n\nclaudeRoutes.post(\"/messages/count_tokens\", async (c) => {\n try {\n const claudeBeta = c.req.header(\"claude-beta\")\n const claudePayload = await c.req.json<ClaudeMessagesPayload>()\n const openAIPayload = translateToOpenAI(claudePayload)\n const exposedModels = getExposedModelIds()\n const selectedModel = createTokenCountModel(\n openAIPayload.model === exposedModels[1] ? exposedModels[1] : exposedModels[0],\n )\n\n const tokenCount = await getTokenCount(openAIPayload, selectedModel)\n const effectiveModelId = selectedModel.id\n\n // Claude Code already accounts for MCP tool payloads differently. For\n // non-MCP local tools, add a small Claude-family overhead to avoid\n // under-reporting context use in the UI.\n if (claudePayload.tools && claudePayload.tools.length > 0) {\n let mcpToolExist = false\n if (claudeBeta?.startsWith(\"claude-code\")) {\n mcpToolExist = claudePayload.tools.some((tool) =>\n tool.name.startsWith(\"mcp__\"),\n )\n }\n if (!mcpToolExist && effectiveModelId.startsWith(\"claude\")) {\n tokenCount.input += 346\n }\n }\n\n const multiplier = effectiveModelId.startsWith(\"claude\") ? 1.15 : 1\n const finalTokenCount = Math.round(\n (tokenCount.input + tokenCount.output) * multiplier,\n )\n\n return c.json({ input_tokens: Math.max(1, finalTokenCount) })\n } catch (error) {\n log.error(\"Error counting tokens:\", error)\n return c.json({ input_tokens: 1 })\n }\n})\n","// HTTP server assembly: exposes only Claude Code-compatible public routes.\nimport { randomUUID } from \"node:crypto\"\n\nimport { createAdaptorServer, type ServerType } from \"@hono/node-server\"\nimport { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\n\nimport type { ProxyConfig, ProxyEnv } from \"~/lib/config\"\nimport { log } from \"~/lib/log\"\nimport { appVersion } from \"~/lib/version\"\nimport { claudeRoutes } from \"~/routes/claude\"\n\nconst loggedRequestHeaders = [\n \"anthropic-beta\",\n \"anthropic-version\",\n \"claude-beta\",\n \"content-type\",\n]\n\nconst readRequestPayloadForLog = async (request: Request): Promise<unknown> => {\n const text = await request.clone().text().catch(() => \"\")\n if (!text) {\n return undefined\n }\n\n try {\n return JSON.parse(text) as unknown\n } catch {\n return text\n }\n}\n\nconst getLoggedHeaders = (request: Request): Record<string, string> =>\n Object.fromEntries(\n loggedRequestHeaders.flatMap((name) => {\n const value = request.headers.get(name)\n return value ? [[name, value]] : []\n }),\n )\n\nconst formatStatusLog = (\n requestId: string,\n method: string,\n path: string,\n status: number,\n ms: number,\n errorMessage: string | undefined,\n): string => {\n const base = `request_id=${requestId} ${method} ${path} -> ${status} ${ms}ms`\n return status >= 400 && errorMessage ? `${base} error=${JSON.stringify(errorMessage)}` : base\n}\n\nexport const createServer = (config: ProxyConfig) => {\n const app = new Hono<ProxyEnv>()\n\n app.use(\"*\", async (c, next) => {\n const requestId = randomUUID()\n c.set(\"config\", config)\n c.set(\"requestId\", requestId)\n c.header(\"x-copilot-relay-request-id\", requestId)\n log.info(`request_id=${requestId} request received method=${c.req.method} path=${c.req.path}`)\n\n const started = performance.now()\n try {\n await next()\n } finally {\n const ms = Math.round(performance.now() - started)\n // Emit exactly one info-level request summary even when downstream route\n // handling throws; deeper diagnostics belong to debug/error logs.\n log.info(formatStatusLog(\n requestId,\n c.req.method,\n c.req.path,\n c.res.status,\n ms,\n c.get(\"requestErrorMessage\"),\n ))\n }\n })\n app.use(\"*\", cors())\n\n app.onError((error, c) => {\n c.set(\"requestErrorMessage\", error.message)\n log.error(\"Unhandled Claude API request error\", {\n method: c.req.method,\n path: c.req.path,\n requestId: c.get(\"requestId\"),\n error,\n })\n return c.json({ error: { message: \"Internal server error\" } }, 500)\n })\n\n app.get(\"/\", (c) =>\n c.json({\n name: \"copilot-relay\",\n status: \"ok\",\n }),\n )\n\n // `version` is the build answering this request, which is what makes it\n // worth serving here: `status` already probes /healthz, so reporting the\n // daemon's own version costs no extra round trip and no new route. See #43.\n // Still static — process-local data, never an upstream call.\n app.get(\"/healthz\", (c) => c.json({ ok: true, version: appVersion }))\n\n // Claude Code probes this before and around real traffic. Three call sites in\n // the CLI: a fire-and-forget HEAD connection warmup that reads\n // ANTHROPIC_BASE_URL and lands here, plus a connectivity diagnostic and a\n // startup preflight that currently read the first-party base URL. The\n // preflight gates on status !== 200 and exits the process on failure, so the\n // cost of answering is a static 200 and the cost of not answering is a client\n // that hard-fails if that call site is ever pointed at a configured base URL.\n //\n // Static by design, like /healthz: it must not contact Copilot upstream. It\n // proves the relay is reachable, nothing more.\n app.on([\"GET\", \"HEAD\"], \"/api/hello\", (c) => c.body(null, 200))\n\n app.route(\"/v1\", claudeRoutes)\n app.notFound(async (c) => {\n const message = \"Unsupported Claude API route\"\n c.set(\"requestErrorMessage\", message)\n log.error(\"Unsupported Claude API request\", {\n method: c.req.method,\n path: c.req.path,\n requestId: c.get(\"requestId\"),\n headers: getLoggedHeaders(c.req.raw),\n payload: await readRequestPayloadForLog(c.req.raw),\n })\n return c.json({ error: { message } }, 500)\n })\n\n return app\n}\n\nexport const startServer = (config: ProxyConfig): Promise<ServerType> =>\n new Promise((resolve, reject) => {\n const server = createAdaptorServer({\n fetch: createServer(config).fetch,\n hostname: config.host,\n port: config.port,\n })\n\n const onError = (error: Error) => {\n server.off(\"listening\", onListening)\n reject(error)\n }\n const onListening = () => {\n server.off(\"error\", onError)\n resolve(server)\n }\n\n server.once(\"error\", onError)\n server.once(\"listening\", onListening)\n server.listen(config.port, config.host)\n })\n","// `copilot-relay start`: loads config, validates upstream access, and starts the local Claude Code proxy.\nimport { type ServerType } from \"@hono/node-server\"\nimport { defineCommand } from \"citty\"\n\nimport { setupProxyAuth } from \"~/lib/auth\"\nimport { readAppConfig, watchAppConfig, type AppConfig } from \"~/lib/app-config\"\nimport { applyClaudeConfig } from \"~/lib/claude-settings\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { claudeConfigPath as defaultClaudeConfigPath } from \"~/lib/defaults\"\nimport { clearRelayPidFile, writeRelayPidFile } from \"~/lib/lifecycle\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { getExposedModelIds } from \"~/lib/models\"\nimport { validateUpstream } from \"~/lib/preflight\"\nimport { formatUrlForDisplay, registerSensitiveOrigin } from \"~/lib/redact\"\nimport { runtimeState } from \"~/lib/state\"\nimport { appVersion } from \"~/lib/version\"\nimport { startServer } from \"~/server\"\n\n/**\n * Narrows the server to one that can close connections directly.\n *\n * `ServerType` from @hono/node-server is a union that includes `Http2Server`,\n * which has neither method, so this cannot be a plain property access. The relay\n * runs over HTTP/1.1 in practice; the guard keeps the HTTP/2 case correct rather\n * than assumed.\n */\ninterface ConnectionClosable {\n closeAllConnections: () => void\n closeIdleConnections: () => void\n}\n\nconst canCloseConnections = (\n server: ServerType,\n): server is ServerType & ConnectionClosable =>\n typeof (server as Partial<ConnectionClosable>).closeIdleConnections\n === \"function\"\n && typeof (server as Partial<ConnectionClosable>).closeAllConnections\n === \"function\"\n\nexport async function startRelay(appConfig?: AppConfig): Promise<void> {\n appConfig ??= await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const claudeConfigPath = defaultClaudeConfigPath\n const config = readProxyConfig(appConfig)\n const applyRuntimeConfig = (nextConfig: AppConfig) => {\n // Hot reload updates behavior for future requests; it intentionally does\n // not rebind the already-listening socket when host or port changes.\n //\n // The redaction policy is registered here rather than once at startup, so a\n // base URL introduced by a later edit is covered too. Registration is\n // additive and never removes a previous origin: a request against the old\n // base URL can still be in flight when the config changes, and its error\n // must stay redacted on its way to the log. See #47.\n registerSensitiveOrigin(nextConfig.copilotBaseUrl)\n setLogLevel(nextConfig.logLevel)\n void cleanupLogs(nextConfig.logRetentionDays)\n runtimeState.debug = nextConfig.logLevel === \"debug\"\n runtimeState.thinkEffort = nextConfig.thinkEffort\n config.copilotBaseUrl = nextConfig.copilotBaseUrl\n config.host = nextConfig.host\n config.port = nextConfig.port\n config.upstreamTimeoutMs = nextConfig.upstreamTimeoutSeconds * 1000\n config.webSearchBackend = nextConfig.webSearchBackend\n runtimeState.modelRouting = {\n gptModel: nextConfig.gptModel,\n opusModel: nextConfig.opusModel,\n }\n }\n applyRuntimeConfig(appConfig)\n\n log.info(`Log level: ${appConfig.logLevel}`)\n log.info(`Think effort: ${appConfig.thinkEffort}`)\n log.info(`Upstream timeout: ${appConfig.upstreamTimeoutSeconds}s`)\n log.info(`Exposed models: ${getExposedModelIds().join(\", \")}`)\n\n const authSession = await setupProxyAuth(config)\n\n try {\n await validateUpstream(config, appConfig.thinkEffort)\n } catch (error) {\n log.error(\"Startup preflight failed:\", error)\n process.exit(1)\n }\n\n const server = await startServer(config)\n await writeRelayPidFile(config)\n\n log.info(`copilot-relay version: ${appVersion}`)\n if (authSession.githubLogin) {\n log.info(`GitHub user: ${authSession.githubLogin}`)\n } else {\n log.error(\"GitHub user: unavailable\")\n }\n\n log.info(\n `copilot-relay listening on http://${config.host}:${config.port}`,\n )\n log.info(`copilot base url: ${formatUrlForDisplay(config.copilotBaseUrl)}`)\n\n const baseUrl = `http://${config.host}:${config.port}`\n if (appConfig.claudeSetup) {\n try {\n const claudeResult = await applyClaudeConfig({\n baseUrl,\n configPath: claudeConfigPath,\n gptModel: appConfig.gptModel,\n })\n if (claudeResult.changed) {\n log.info(\n `claude settings ${claudeResult.created ? \"created\" : \"updated\"}: ${claudeResult.configPath}`,\n )\n if (\n claudeResult.previousBaseUrl\n && claudeResult.previousBaseUrl !== baseUrl\n ) {\n log.info(\n `ANTHROPIC_BASE_URL: ${claudeResult.previousBaseUrl} → ${baseUrl}`,\n )\n }\n } else {\n log.info(\n `claude settings already up to date: ${claudeResult.configPath}`,\n )\n }\n } catch (error) {\n log.error(\n `Could not update claude settings (${claudeConfigPath}):`,\n error,\n )\n }\n }\n watchAppConfig((nextConfig) => {\n applyRuntimeConfig(nextConfig)\n log.info(\n `Config reloaded: logLevel=${nextConfig.logLevel} thinkEffort=${nextConfig.thinkEffort} upstreamTimeoutSeconds=${nextConfig.upstreamTimeoutSeconds}`,\n )\n })\n\n // Shorter than stopProcess's 5s SIGTERM timeout, so a clean exit reliably\n // wins that race and `stop` never has to escalate to SIGKILL.\n const shutdownGraceMs = 2_000\n\n const shutdown = (signal: NodeJS.Signals) => {\n log.info(`Received ${signal}; shutting down copilot-relay`)\n server.close((error) => {\n if (error) {\n log.error(\"Error while shutting down copilot-relay:\", error)\n process.exitCode = 1\n }\n })\n\n // server.close() stops accepting new connections but waits for existing\n // ones to finish. A Claude Code keep-alive socket sitting idle between\n // requests never finishes on its own, so without this the close hangs, the\n // 5s timeout expires, and the process is SIGKILLed - skipping the cleanup\n // in the finally below and severing any in-flight stream anyway.\n //\n // Releasing idle sockets immediately and active ones after a grace period\n // means a request in flight still gets time to drain.\n if (canCloseConnections(server)) {\n server.closeIdleConnections()\n\n const forceClose = setTimeout(() => {\n log.info(\"Grace period elapsed; closing remaining connections\")\n server.closeAllConnections()\n }, shutdownGraceMs)\n // Must not itself hold the event loop open, or it would delay the exit\n // it exists to accelerate.\n forceClose.unref()\n }\n }\n process.once(\"SIGINT\", shutdown)\n process.once(\"SIGTERM\", shutdown)\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.on(\"close\", resolve)\n server.on(\"error\", reject)\n })\n } finally {\n process.off(\"SIGINT\", shutdown)\n process.off(\"SIGTERM\", shutdown)\n await clearRelayPidFile()\n }\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the copilot-relay HTTP server.\",\n },\n async run() {\n await startRelay()\n },\n})\n","// `copilot-relay restart`: stop stale local relay processes, then start normally.\nimport { defineCommand } from \"citty\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { stopExistingRelay } from \"~/lib/lifecycle\"\nimport { startRelay } from \"./start\"\n\nexport const restart = defineCommand({\n meta: {\n name: \"restart\",\n description: \"Stop any existing copilot-relay instance and start a new one.\",\n },\n async run() {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const stopped = await stopExistingRelay(readProxyConfig(appConfig))\n if (stopped.length > 0) {\n log.info(`Stopped copilot-relay pid(s): ${stopped.join(\", \")}`)\n }\n log.info(\"Starting copilot-relay\")\n\n await startRelay(appConfig)\n },\n})\n","// `copilot-relay status`: report whether a relay is running, and whether it works.\nimport { defineCommand } from \"citty\"\n\nimport type { AppConfig } from \"~/lib/app-config\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { findRelayOnPort } from \"~/lib/lifecycle\"\nimport { setLogLevel } from \"~/lib/log\"\nimport { getLogPath, paths } from \"~/lib/paths\"\nimport {\n formatUrlForDisplay,\n registerSensitiveOrigin,\n sanitizeTerminalString,\n scrubSensitiveUrls,\n} from \"~/lib/redact\"\nimport { appVersion } from \"~/lib/version\"\n\n/**\n * Exit codes, so `status` is usable in a health check or a script.\n *\n * 2 is distinct from 1 on purpose: \"running but broken\" and \"not running\" call\n * for different responses - restart the service versus re-authenticate.\n *\n * A failed health probe is a 2, not a 0. Printing FAILED while exiting 0 would\n * make every scripted caller treat an unreachable relay as fine. See #34.\n */\nconst exitCodes = {\n notRunning: 1,\n notUsable: 2,\n running: 0,\n} as const\n\nconst localProbeTimeoutMs = 5_000\n// Generous: a real Copilot round trip, not a local socket check.\nconst deepProbeTimeoutMs = 60_000\n\ninterface ProbeResult {\n detail?: string\n ms?: number\n ok: boolean\n}\n\n/**\n * The resolved config as `status` reports it.\n *\n * Derived from AppConfig rather than restated, so a twelfth config key cannot\n * be added without this type following it automatically. Two deliberate\n * differences:\n *\n * `webSearchBackend` is null rather than undefined when unset. JSON.stringify\n * drops undefined properties, so leaving it optional would make `status --json`\n * emit 10 config keys on a default install and 11 on a customized one; anything\n * parsing that deserves a stable key set.\n *\n * `copilotBaseUrl` is the display form, not the raw value. It is user-supplied\n * and may legitimately carry a credential in its path, and `status` output is\n * what a user pastes into a bug report. Mapped here rather than at each render\n * site so the text block and `--json` cannot disagree about it. See #47.\n */\nexport type StatusConfig = Omit<AppConfig, \"webSearchBackend\"> & {\n webSearchBackend: string | null\n}\n\nexport const toStatusConfig = (config: AppConfig): StatusConfig => ({\n ...config,\n copilotBaseUrl: formatUrlForDisplay(config.copilotBaseUrl),\n webSearchBackend: config.webSearchBackend ?? null,\n})\n\nexport interface RelayStatus {\n baseUrl?: string\n /**\n * Every key readAppConfig() resolved — not the two that happened to fit in a\n * parenthetical. These are the values on disk; see renderConfig for why that\n * distinction is printed rather than assumed.\n */\n config: StatusConfig\n configPath: string\n /**\n * The version the running daemon reports about itself, which is not\n * necessarily `version` — that one is whichever CLI was invoked. Undefined\n * when nothing is running, or when the daemon predates version reporting.\n * See #43.\n */\n daemonVersion?: string\n deep?: ProbeResult\n health?: ProbeResult\n logPath: string\n logLevel: string\n models?: Array<string>\n pid?: number\n port?: number\n running: boolean\n startedAt?: string\n thinkEffort: string\n /** The CLI binary being invoked. Says nothing about what is serving. */\n version: string\n}\n\n/**\n * True when the daemon is serving a different build than the invoked CLI.\n *\n * Pure and exported because it drives both the rendered warning and the\n * deliberate decision to leave the exit code at 0; both deserve direct\n * coverage. Undefined daemonVersion is not a mismatch - unknown is not\n * different, and claiming otherwise would warn on every pre-v0.3.1 daemon.\n */\nexport const hasVersionMismatch = (status: RelayStatus): boolean =>\n status.running\n && status.daemonVersion !== undefined\n && status.daemonVersion !== status.version\n\n/**\n * Status to exit code. Pure, because this mapping is the contract every\n * scripted caller depends on and deserves direct coverage.\n *\n * 0 requires a live process *and* a passing health probe. Printing FAILED while\n * exiting 0 would make every scripted caller treat an unreachable relay as\n * fine. See #34.\n *\n * A version mismatch stays 0 on purpose (#43). The relay works; it is just not\n * the build that was installed. Mapping it to 2 would overstate it and break\n * every scripted caller that treats non-zero as broken. It is surfaced in the\n * text instead.\n */\nexport const resolveExitCode = (status: RelayStatus): number =>\n !status.running ? exitCodes.notRunning\n : !status.health?.ok ? exitCodes.notUsable\n : status.deep && !status.deep.ok ? exitCodes.notUsable\n : exitCodes.running\n\nconst formatUptime = (startedAt: string | undefined): string => {\n if (!startedAt) {\n return \"unknown\"\n }\n\n const started = Date.parse(startedAt)\n if (Number.isNaN(started)) {\n return \"unknown\"\n }\n\n const seconds = Math.max(0, Math.floor((Date.now() - started) / 1000))\n if (seconds < 60) return `${seconds}s`\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m`\n if (seconds < 86_400) {\n return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`\n }\n return `${Math.floor(seconds / 86_400)}d ${Math.floor((seconds % 86_400) / 3600)}h`\n}\n\n/**\n * Every config key with its position in the block, in config.default.yaml order\n * rather than alphabetical so it reads like the file it was resolved from.\n *\n * A Record keyed by StatusConfig rather than an array of keys: an array only\n * checks that each entry *is* a key, so a twelfth config key would type-check\n * while silently never appearing in `status` — the exact failure this command\n * exists to prevent. Keyed this way, adding one fails the build here.\n */\nconst configRowOrder: Record<keyof StatusConfig, number> = {\n host: 0,\n port: 1,\n copilotBaseUrl: 2,\n claudeSetup: 3,\n logLevel: 4,\n logRetentionDays: 5,\n thinkEffort: 6,\n upstreamTimeoutSeconds: 7,\n webSearchBackend: 8,\n gptModel: 9,\n opusModel: 10,\n}\n\nconst configRowKeys = (\n Object.keys(configRowOrder) as Array<keyof StatusConfig>\n).sort((a, b) => configRowOrder[a] - configRowOrder[b])\n\n/**\n * The resolved config, one key per line.\n *\n * These are the values on disk. That is not the same question as \"what is the\n * running daemon honouring\", and the footnote says so rather than leaving it\n * implied: applyRuntimeConfig() in start.ts hot-reloads eight of these keys,\n * never reads claudeSetup, and deliberately does not rebind the listening\n * socket when host or port changes. Printing all eleven without that line\n * would imply a live daemon had read values it has not.\n *\n * Only shown when running, because with nothing up every value applies at the\n * next start and the note would be noise.\n */\nconst renderConfig = (\n config: StatusConfig,\n running: boolean,\n): Array<string> => {\n const lines = configRowKeys.map((key) => {\n const value = config[key]\n const rendered =\n key === \"webSearchBackend\" && value === null ?\n \"(unset — uses gptModel)\"\n : String(value)\n // Every value is user-supplied and lands on a terminal unescaped. Raw\n // control bytes could clear the line and paint status rows the relay never\n // produced, so no config value is trusted to be printable. See #47.\n return ` ${key.padEnd(24)}${sanitizeTerminalString(rendered)}`\n })\n\n if (running) {\n lines.push(\n \" host, port and claudeSetup take effect on restart; the rest hot-reload.\",\n )\n }\n\n return lines\n}\n\n/**\n * Pure state-to-lines mapping, kept free of IO so it can be tested directly\n * without spawning a relay or opening a socket.\n */\nexport const renderStatus = (status: RelayStatus): Array<string> => {\n const lines = [`copilot-relay ${status.version}`]\n\n if (!status.running) {\n lines.push(\n \" process not running\",\n ` log ${status.logPath}`,\n ` config ${status.configPath}`,\n ...renderConfig(status.config, false),\n \"\",\n \" Start it with: copilot-relay start\",\n )\n return lines\n }\n\n lines.push(\n ` process running (pid ${status.pid}, up ${formatUptime(status.startedAt)})`,\n )\n\n // The daemon's own build, distinct from the header line above, which is the\n // CLI that was invoked. Always shown when running: the case this exists for\n // is an upgrade the user believes landed, and a row that appears only on\n // mismatch would leave \"did it even check?\" unanswered. See #43.\n lines.push(\n status.daemonVersion === undefined ?\n \" version unknown (daemon predates version reporting — restart to report it)\"\n : hasVersionMismatch(status) ?\n ` version ${status.daemonVersion} — MISMATCH, ${status.version} is installed`\n : ` version ${status.daemonVersion}`,\n )\n\n lines.push(` listening ${status.baseUrl}`)\n\n const health = status.health\n lines.push(\n health?.ok ?\n ` health ok (${health.ms}ms)`\n : ` health FAILED${health?.detail ? ` — ${health.detail}` : \"\"}`,\n )\n\n lines.push(\n status.models?.length ?\n ` models ${status.models.join(\", \")}`\n : \" models unavailable\",\n )\n\n if (status.deep) {\n lines.push(\n status.deep.ok ?\n ` upstream ok (${status.deep.ms}ms) — ${status.deep.detail ?? \"end-to-end Copilot round trip\"}`\n : ` upstream FAILED${status.deep.detail ? ` — ${status.deep.detail}` : \"\"}`,\n )\n } else {\n lines.push(\" upstream not checked (use --deep)\")\n }\n\n lines.push(\n ` log ${status.logPath}`,\n ` config ${status.configPath}`,\n ...renderConfig(status.config, true),\n )\n\n // Only /v1/messages proves the relay can serve Claude Code; the first two\n // layers pass on a relay whose Copilot token expired an hour ago.\n if (status.health?.ok && status.deep && !status.deep.ok) {\n lines.push(\n \"\",\n \" The relay is listening but cannot reach Copilot.\",\n \" Try: copilot-relay auth\",\n )\n }\n\n // A mismatch is nearly always an upgrade that installed but never restarted,\n // so the next step is named rather than left to be inferred. Surfaced, not\n // just recorded: the version row alone reads as trivia next to a green\n // health line. See #43.\n if (hasVersionMismatch(status)) {\n lines.push(\n \"\",\n ` The running relay is ${status.daemonVersion}; ${status.version} is installed.`,\n \" Restart it to serve the installed version: copilot-relay restart\",\n )\n }\n\n return lines\n}\n\nconst probe = async (\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<{ body: unknown; ms: number; ok: boolean; status: number }> => {\n const started = performance.now()\n const response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(timeoutMs),\n })\n const body = (await response.json().catch(() => undefined)) as unknown\n return {\n body,\n ms: Math.round(performance.now() - started),\n ok: response.ok,\n status: response.status,\n }\n}\n\nconst describeError = (error: unknown): string =>\n error instanceof Error ?\n error.name === \"TimeoutError\" ? \"timed out\"\n : error.message\n : String(error)\n\n/**\n * Probes /healthz, which also reports the daemon's own version.\n *\n * The version rides along on a probe `status` already makes, so asking what is\n * running costs nothing extra. Absent for a daemon older than v0.3.1, which is\n * itself informative, so it stays optional rather than defaulting to the\n * caller's version - that substitution is the bug. See #43.\n */\nconst checkHealth = async (\n baseUrl: string,\n): Promise<{ result: ProbeResult; version?: string }> => {\n try {\n const probeResult = await probe(`${baseUrl}/healthz`, {}, localProbeTimeoutMs)\n const version = (probeResult.body as { version?: unknown } | undefined)\n ?.version\n return {\n result:\n probeResult.ok ?\n { ms: probeResult.ms, ok: true }\n : { detail: `http ${probeResult.status}`, ms: probeResult.ms, ok: false },\n ...(typeof version === \"string\" && version ? { version } : {}),\n }\n } catch (error) {\n return { result: { detail: describeError(error), ok: false } }\n }\n}\n\nconst readModels = async (baseUrl: string): Promise<Array<string>> => {\n try {\n const result = await probe(`${baseUrl}/v1/models`, {}, localProbeTimeoutMs)\n const data = (result.body as { data?: Array<{ id?: string }> } | undefined)?.data\n return Array.isArray(data) ?\n data.flatMap((model) => (typeof model.id === \"string\" ? [model.id] : []))\n : []\n } catch {\n return []\n }\n}\n\n/**\n * Layer 3: a real request through Copilot.\n *\n * Opt-in because it spends tokens. It is the only check that exercises token\n * refresh, the upstream call, and translation back to Claude shape.\n */\nexport const checkDeep = async (\n baseUrl: string,\n model: string,\n): Promise<ProbeResult> => {\n try {\n const result = await probe(\n `${baseUrl}/v1/messages`,\n {\n body: JSON.stringify({\n max_tokens: 16,\n messages: [{ content: \"Reply with the single word: ok\", role: \"user\" }],\n model,\n }),\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n method: \"POST\",\n },\n deepProbeTimeoutMs,\n )\n\n if (!result.ok) {\n const message = (\n result.body as { error?: { message?: string } } | undefined\n )?.error?.message\n return {\n detail: message ? `http ${result.status}: ${message}` : `http ${result.status}`,\n ms: result.ms,\n ok: false,\n }\n }\n\n const body = result.body as {\n content?: unknown\n role?: unknown\n stop_reason?: unknown\n type?: unknown\n usage?: { output_tokens?: unknown }\n } | undefined\n if (Array.isArray(body?.content)) {\n if (body.content.length > 0) {\n return { ms: result.ms, ok: true }\n }\n // Reasoning can consume the probe's entire budget before visible text.\n if (\n body.type === \"message\"\n && body.role === \"assistant\"\n && body.stop_reason === \"max_tokens\"\n && typeof body.usage?.output_tokens === \"number\"\n && Number.isFinite(body.usage.output_tokens)\n && body.usage.output_tokens > 0\n ) {\n return {\n detail: \"output budget exhausted before visible text\",\n ms: result.ms,\n ok: true,\n }\n }\n }\n return { detail: \"empty response\", ms: result.ms, ok: false }\n } catch (error) {\n return { detail: describeError(error), ok: false }\n }\n}\n\nexport const collectStatus = async (options: {\n deep: boolean\n}): Promise<RelayStatus> => {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n // Before any probe runs, so a URL this origin appears in - a fetch failure\n // from checkHealth or checkDeep, which quotes the URL it tried - is already\n // covered by the time it reaches a detail string. See #47.\n registerSensitiveOrigin(appConfig.copilotBaseUrl)\n\n const base = {\n // Both of these are derived from the same appConfig as `config`, so they\n // cannot drift from it. They predate the config block and something may\n // parse them out of `status --json`, so they stay.\n config: toStatusConfig(appConfig),\n configPath: paths.configPath,\n logLevel: appConfig.logLevel,\n logPath: getLogPath(),\n thinkEffort: appConfig.thinkEffort,\n version: appVersion,\n }\n\n // Scoped to the configured port on purpose. `stop` scans globally so it can\n // clean up strays; `status` was asked about one relay, and reporting a\n // different one is worse than reporting nothing because it looks\n // authoritative. See #33.\n const relay = await findRelayOnPort(readProxyConfig(appConfig))\n if (!relay) {\n return { ...base, running: false }\n }\n\n // Address comes from the same record as the pid, so the two can never\n // describe different processes. The pid file's port is where the socket is\n // actually bound: hot reload updates config.port without rebinding.\n const baseUrl = `http://${relay.host}:${relay.port}`\n\n const health = await checkHealth(baseUrl)\n const models = health.result.ok ? await readModels(baseUrl) : []\n const deep =\n options.deep && health.result.ok ?\n await checkDeep(baseUrl, models[0] ?? appConfig.gptModel)\n : undefined\n\n // /healthz first: it is answered by the process itself, so it cannot be\n // stale. The pid file backs it up for a daemon that is up but not yet\n // healthy, which is exactly when the two sources are complementary. Both\n // absent means a daemon older than v0.3.1 — reported as unknown rather than\n // silently filled in with the CLI's version. See #43.\n const daemonVersion = health.version ?? relay.version\n\n return {\n ...base,\n baseUrl,\n ...(daemonVersion ? { daemonVersion } : {}),\n ...(deep ? { deep } : {}),\n health: health.result,\n models,\n pid: relay.pid,\n port: relay.port,\n running: true,\n ...(relay.startedAt ? { startedAt: relay.startedAt } : {}),\n }\n}\n\nexport const status = defineCommand({\n meta: {\n name: \"status\",\n description: \"Show whether copilot-relay is running and reachable.\",\n },\n args: {\n deep: {\n description:\n \"Also send a real request through Copilot. Proves the relay works end to end; spends a few tokens.\",\n type: \"boolean\",\n },\n json: {\n description: \"Emit machine-readable JSON.\",\n type: \"boolean\",\n },\n },\n async run({ args }) {\n const result = await collectStatus({ deep: Boolean(args.deep) })\n\n // stdout, not the logger: this is the command's output, and routing it\n // through the logger would write a log line every time it is polled.\n //\n // Scrubbed as a whole rather than field by field. toStatusConfig already\n // handles the copilotBaseUrl row, but a probe failure detail quotes the URL\n // it tried, and those strings come from fetch rather than from config. This\n // is the backstop for every such path, including --deep. Applied to the\n // complete output so legitimate newlines between rows survive. See #47.\n if (args.json) {\n console.log(scrubSensitiveUrls(JSON.stringify(result, null, 2)))\n } else {\n console.log(scrubSensitiveUrls(renderStatus(result).join(\"\\n\")))\n }\n\n // 0 requires both a live process and a passing health probe. A relay that\n // cannot answer /healthz is running but not usable, which is a 2.\n process.exitCode = resolveExitCode(result)\n },\n})\n","// `copilot-relay stop`: terminate all detected local relay server instances.\nimport { defineCommand } from \"citty\"\n\nimport { readAppConfig } from \"~/lib/app-config\"\nimport { readProxyConfig } from \"~/lib/config\"\nimport { cleanupLogs, log, setLogLevel } from \"~/lib/log\"\nimport { stopExistingRelay } from \"~/lib/lifecycle\"\n\nexport const stop = defineCommand({\n meta: {\n name: \"stop\",\n description: \"Stop all detected copilot-relay server instances.\",\n },\n async run() {\n const appConfig = await readAppConfig()\n setLogLevel(appConfig.logLevel)\n await cleanupLogs(appConfig.logRetentionDays)\n\n const stopped = await stopExistingRelay(readProxyConfig(appConfig))\n if (stopped.length > 0) {\n log.info(`Stopped copilot-relay pid(s): ${stopped.join(\", \")}`)\n }\n },\n})\n","#!/usr/bin/env node\n\n// CLI entrypoint: wires top-level commands without owning runtime behavior.\nimport { defineCommand, runMain } from \"citty\"\n\nimport { auth } from \"./auth\"\nimport { restart } from \"./restart\"\nimport { start } from \"./start\"\nimport { status } from \"./status\"\nimport { stop } from \"./stop\"\n\nconst main = defineCommand({\n meta: {\n name: \"copilot-relay\",\n description:\n \"Yet, just another relay for Claude Code to use a GitHub Copilot subscription.\",\n },\n subCommands: { auth, restart, start, status, stop },\n})\n\nawait runMain(main)\n"],"mappings":"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"}