@hsupu/copilot-api 0.7.18-beta.2 → 0.7.18-beta.3

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.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","names":["fsPromises","path","fs","createTruncationMarker","estimateMessageTokens","smartCompressToolResults","DEFAULT_CONTEXT_WINDOW","calculateTokenLimit","findOptimalPreserveIndex","generateRemovedMessagesSummary","addCompressionNotice","createTruncationSystemContext","createTruncationMarker","CLAUDE_CODE_OFFICIAL_TOOLS","NON_DEFERRED_TOOL_NAMES","createTruncationMarker","packageJson.version"],"sources":["../src/lib/config/paths.ts","../src/lib/state.ts","../src/lib/copilot-api.ts","../src/lib/sanitize-system-reminder.ts","../src/lib/auto-truncate/index.ts","../src/lib/error.ts","../src/lib/token/copilot-client.ts","../src/lib/token/copilot-token-manager.ts","../src/lib/utils.ts","../src/lib/token/github-client.ts","../src/lib/token/providers/base.ts","../src/lib/token/providers/cli.ts","../src/lib/token/providers/file.ts","../src/lib/token/providers/device-auth.ts","../src/lib/token/providers/env.ts","../src/lib/token/github-token-manager.ts","../src/lib/token/index.ts","../src/auth.ts","../src/check-usage.ts","../src/lib/models/client.ts","../src/debug.ts","../src/lib/adaptive-rate-limiter.ts","../src/lib/models/resolver.ts","../src/lib/context/request.ts","../src/lib/context/manager.ts","../src/lib/history/ws.ts","../src/lib/history/store.ts","../src/lib/shutdown.ts","../src/lib/tui/tracker.ts","../src/lib/tui/middleware.ts","../src/lib/tui/format.ts","../src/lib/tui/console-renderer.ts","../src/lib/tui/index.ts","../src/list-claude-code.ts","../src/logout.ts","../src/setup-claude-code.ts","../package.json","../src/lib/config/config.ts","../src/lib/context/error-persistence.ts","../src/lib/context/consumers.ts","../src/lib/proxy.ts","../src/routes/history/api.ts","../src/routes/history/assets.ts","../src/routes/history/route.ts","../src/lib/models/endpoint.ts","../src/lib/models/tokenizer.ts","../src/lib/request/payload.ts","../src/lib/request/pipeline.ts","../src/lib/openai/responses-stream-accumulator.ts","../src/lib/request/response.ts","../src/lib/request/recording.ts","../src/lib/request/strategies/auto-truncate.ts","../src/lib/request/truncation.ts","../src/lib/request/strategies/deferred-tool-retry.ts","../src/lib/request/strategies/token-refresh.ts","../src/lib/system-prompt.ts","../src/lib/anthropic/message-mapping.ts","../src/types/api/anthropic.ts","../src/lib/anthropic/sanitize.ts","../src/lib/anthropic/auto-truncate.ts","../src/lib/fetch-utils.ts","../src/lib/anthropic/features.ts","../src/lib/anthropic/client.ts","../src/lib/anthropic/stream-accumulator.ts","../src/lib/anthropic/handlers.ts","../src/lib/openai/orphan-filter.ts","../src/lib/openai/auto-truncate.ts","../src/lib/openai/client.ts","../src/lib/openai/sanitize.ts","../src/lib/openai/stream-accumulator.ts","../src/routes/chat-completions/handler.ts","../src/routes/chat-completions/route.ts","../src/lib/openai/embeddings.ts","../src/routes/embeddings/route.ts","../src/routes/event-logging/route.ts","../src/routes/messages/count-tokens-handler.ts","../src/routes/messages/route.ts","../src/routes/models/route.ts","../src/lib/openai/responses-client.ts","../src/routes/responses/handler.ts","../src/routes/responses/route.ts","../src/routes/token/route.ts","../src/routes/usage/route.ts","../src/routes/index.ts","../src/server.ts","../src/start.ts","../src/main.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst APP_DIR = path.join(os.homedir(), \".local\", \"share\", \"copilot-api\")\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, \"github_token\")\n\nexport const PATHS = {\n APP_DIR,\n GITHUB_TOKEN_PATH,\n CONFIG_YAML: path.join(APP_DIR, \"config.yaml\"),\n LEARNED_LIMITS: path.join(APP_DIR, \"learned-limits.json\"),\n ERROR_DIR: path.join(APP_DIR, \"errmsgs\"),\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n // File exists, ensure it has secure permissions (owner read/write only)\n const stats = await fs.stat(filePath)\n const currentMode = stats.mode & 0o777\n if (currentMode !== 0o600) {\n await fs.chmod(filePath, 0o600)\n }\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n","import type { Model, ModelsResponse } from \"~/lib/models/client\"\n\nimport type { AdaptiveRateLimiterConfig } from \"./adaptive-rate-limiter\"\nimport type { CopilotTokenInfo, TokenInfo } from \"./token/types\"\n\n/** A compiled rewrite rule (regex pre-compiled from config string) */\nexport interface CompiledRewriteRule {\n /** Pattern to match (regex in regex mode, string in line mode) */\n from: RegExp | string\n /** Replacement string (supports $0, $1, etc. in regex mode) */\n to: string\n /** Match method: \"regex\" (default) or \"line\" */\n method?: \"regex\" | \"line\"\n}\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n /** Token metadata (new token system) */\n tokenInfo?: TokenInfo\n copilotTokenInfo?: CopilotTokenInfo\n\n accountType: \"individual\" | \"business\" | \"enterprise\"\n models?: ModelsResponse\n /** O(1) lookup index: model ID → Model object. Rebuilt on cacheModels(). */\n modelIndex: Map<string, Model>\n /** O(1) membership check: set of available model IDs. Rebuilt on cacheModels(). */\n modelIds: Set<string>\n vsCodeVersion?: string\n\n /** Show GitHub token in logs */\n showGitHubToken: boolean\n verbose: boolean\n\n /** Adaptive rate limiting configuration */\n adaptiveRateLimitConfig?: Partial<AdaptiveRateLimiterConfig>\n\n /**\n * Auto-truncate: reactively truncate on limit errors and pre-check for known limits.\n * Enabled by default; disable with --no-auto-truncate.\n */\n autoTruncate: boolean\n\n /**\n * Compress old tool results before truncating messages.\n * When enabled, large tool_result content is compressed to reduce context size.\n */\n compressToolResultsBeforeTruncate: boolean\n\n /** Rewrite Anthropic server-side tools to custom tool format */\n rewriteAnthropicTools: boolean\n\n /**\n * Model name overrides: request model → target model.\n *\n * Override values can be full model names or short aliases (opus, sonnet, haiku).\n * If the target is not in available models, it's resolved as an alias.\n * Defaults to DEFAULT_MODEL_OVERRIDES; config.yaml `model.model_overrides` replaces entirely.\n */\n modelOverrides: Record<string, string>\n\n /**\n * Deduplicate repeated tool calls: remove duplicate tool_use/tool_result pairs,\n * keeping only the last occurrence of each matching combination.\n *\n * - `false` — disabled (default)\n * - `\"input\"` — match by (tool_name, input); different results are still deduped\n * - `\"result\"` — match by (tool_name, input, result); only dedup when result is identical\n */\n dedupToolCalls: false | \"input\" | \"result\"\n\n /**\n * Rewrite `<system-reminder>` tags in messages.\n *\n * - `false` — disabled, keep all tags unchanged (default)\n * - `true` — remove ALL system-reminder tags\n * - `Array<CompiledRewriteRule>` — rewrite rules evaluated top-down, first match wins:\n * - If replacement produces the original content → keep tag unchanged\n * - If replacement produces an empty string → remove the tag\n * - Otherwise → replace tag content with the result\n */\n rewriteSystemReminders: boolean | Array<CompiledRewriteRule>\n\n /**\n * Strip injected `<system-reminder>` tags from Read tool results.\n * Reduces context bloat from repeated system reminders in file content.\n * Disabled by default; enable with --truncate-read-tool-result.\n */\n truncateReadToolResult: boolean\n\n /** Pre-compiled system prompt override rules from config.yaml */\n systemPromptOverrides: Array<CompiledRewriteRule>\n\n /**\n * Maximum number of history entries to keep in memory.\n * 0 = unlimited. Default: 200.\n */\n historyLimit: number\n\n /**\n * Fetch timeout in seconds.\n * Time from request start to receiving HTTP response headers.\n * Applies to both streaming and non-streaming requests.\n * 0 = no timeout (rely on upstream gateway timeout).\n */\n fetchTimeout: number\n\n /**\n * Stream idle timeout in seconds.\n * Maximum time to wait between consecutive SSE events during streaming.\n * Aborts the stream if no event arrives within this window.\n * Applies to all streaming paths (Anthropic, Chat Completions, Responses).\n * 0 = no idle timeout. Default: 300.\n */\n streamIdleTimeout: number\n\n /**\n * Shutdown Phase 2 timeout in seconds.\n * Wait for in-flight requests to complete naturally before sending abort signal.\n * Default: 60.\n */\n shutdownGracefulWait: number\n\n /**\n * Shutdown Phase 3 timeout in seconds.\n * After abort signal, wait for handlers to wrap up before force-closing.\n * Default: 120.\n */\n shutdownAbortWait: number\n\n /**\n * Maximum age of an active request before the stale reaper forces it to fail (seconds).\n * Requests exceeding this age are assumed stuck and cleaned up.\n * 0 = disabled. Default: 600 (10 minutes).\n */\n staleRequestMaxAge: number\n}\n\n/**\n * Rebuild model lookup indexes from state.models.\n * Called by cacheModels() in production; call directly in tests after setting state.models.\n */\nexport function rebuildModelIndex(): void {\n const data = state.models?.data ?? []\n state.modelIndex = new Map(data.map((m) => [m.id, m]))\n state.modelIds = new Set(data.map((m) => m.id))\n}\nexport const DEFAULT_MODEL_OVERRIDES: Record<string, string> = {\n opus: \"claude-opus-4.6\",\n sonnet: \"claude-sonnet-4.6\",\n haiku: \"claude-haiku-4.5\",\n}\n\nexport const state: State = {\n accountType: \"individual\",\n modelIndex: new Map(),\n modelIds: new Set(),\n showGitHubToken: false,\n verbose: false,\n autoTruncate: true,\n compressToolResultsBeforeTruncate: true,\n rewriteAnthropicTools: true,\n modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },\n dedupToolCalls: false,\n rewriteSystemReminders: false,\n truncateReadToolResult: false,\n systemPromptOverrides: [],\n historyLimit: 200,\n fetchTimeout: 0,\n streamIdleTimeout: 300,\n shutdownGracefulWait: 60,\n shutdownAbortWait: 120,\n staleRequestMaxAge: 600,\n}\n","import consola from \"consola\"\nimport { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nimport { state } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.38.0\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\n/** Copilot Chat API version (for chat/completions requests) */\nconst COPILOT_API_VERSION = \"2025-05-01\"\n\n/** Copilot internal API version (for token & usage endpoints) */\nexport const COPILOT_INTERNAL_API_VERSION = \"2025-04-01\"\n\n/** GitHub public API version (for /user, repos, etc.) */\nconst GITHUB_API_VERSION = \"2022-11-28\"\n\n/**\n * Session-level interaction ID.\n * Used to correlate all requests within a single server session.\n * Unlike x-request-id (per-request UUID), this stays constant for the server lifetime.\n */\nconst INTERACTION_ID = randomUUID()\n\nexport const copilotBaseUrl = (state: State) =>\n state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": COPILOT_API_VERSION,\n \"x-request-id\": randomUUID(),\n \"X-Interaction-Id\": INTERACTION_ID,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": GITHUB_API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n\n// ============================================================================\n// VSCode version detection\n// ============================================================================\n\n/** Fallback VSCode version when GitHub API is unavailable */\nconst VSCODE_VERSION_FALLBACK = \"1.104.3\"\n\n/** GitHub API endpoint for latest VSCode release */\nconst VSCODE_RELEASE_URL = \"https://api.github.com/repos/microsoft/vscode/releases/latest\"\n\n/** GitHub release response shape */\ninterface GitHubRelease {\n tag_name: string\n}\n\n/** Fetch the latest VSCode version and cache in global state */\nexport async function cacheVSCodeVersion(): Promise<void> {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n consola.info(`Using VSCode version: ${response}`)\n}\n\n/** Fetch the latest VSCode version from GitHub releases, falling back to a hardcoded version */\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(VSCODE_RELEASE_URL, {\n signal: controller.signal,\n headers: {\n Accept: \"application/vnd.github.v3+json\",\n \"User-Agent\": \"copilot-api\",\n },\n })\n\n if (!response.ok) {\n return VSCODE_VERSION_FALLBACK\n }\n\n const release = (await response.json()) as GitHubRelease\n // tag_name is in format \"1.107.1\"\n const version = release.tag_name\n if (version && /^\\d+\\.\\d+\\.\\d+$/.test(version)) {\n return version\n }\n\n return VSCODE_VERSION_FALLBACK\n } catch {\n return VSCODE_VERSION_FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n","/**\n * System-reminder tag detection, rewriting, and removal.\n *\n * Claude Code injects `<system-reminder>` tags into tool results and user\n * messages. Each tag always occupies its own line:\n * \\n<system-reminder>\\n...content...\\n</system-reminder>\n *\n * This module:\n * - Defines all known system-reminder content types\n * - Provides configurable rewriting (transform, keep, or remove tags)\n * - Processes tags at the start/end of text content\n *\n * Rewriting is controlled by `state.rewriteSystemReminders`:\n * - `false` — keep all tags unchanged (default)\n * - `true` — remove all tags\n * - `Array<CompiledRewriteRule>` — rewrite rules evaluated top-down,\n * first match wins. If replacement produces the original content, tag is\n * kept unchanged. If replacement produces an empty string, tag is removed.\n * Otherwise, tag content is replaced with the result.\n */\n\nimport { state } from \"~/lib/state\"\n\n// ============================================================================\n// Tag Constants\n// ============================================================================\n\n/** Opening tag — always appears on its own line */\nexport const OPEN_TAG = \"<system-reminder>\"\n\n/** Closing tag — always appears on its own line */\nexport const CLOSE_TAG = \"</system-reminder>\"\n\n// ============================================================================\n// Tag Parsing Types\n// ============================================================================\n\n/** A parsed system-reminder tag found at a text boundary. */\nexport interface ParsedSystemReminderTag {\n /** The inner content between `<system-reminder>` and `</system-reminder>` */\n content: string\n /** Start position of the tag in the original text (the `\\n` before `<system-reminder>`) */\n tagStart: number\n /** End position of the tag (exclusive), i.e. the range is [tagStart, tagEnd) */\n tagEnd: number\n}\n\n/**\n * Extract trailing `<system-reminder>` tags from text.\n *\n * Scans backwards from the end, collecting each tag that sits on its own\n * lines at the text boundary. Returns them outermost-first and the position\n * where main (non-tag) content ends.\n *\n * Used by both:\n * - `removeSystemReminderTags` (filter by content, then rebuild)\n * - `compressToolResultContent` (extract all, generate summaries)\n */\nexport function extractTrailingSystemReminderTags(text: string): {\n mainContentEnd: number\n tags: Array<ParsedSystemReminderTag>\n} {\n const tags: Array<ParsedSystemReminderTag> = []\n let scanEnd = text.length\n\n while (true) {\n const currentTagEnd = scanEnd\n\n // Skip trailing whitespace/newlines (charCode: \\n=10, space=32, \\t=9, \\r=13)\n let end = scanEnd\n while (end > 0) {\n const c = text.charCodeAt(end - 1)\n if (c !== 10 && c !== 32 && c !== 9 && c !== 13) break\n end--\n }\n\n // Must end with </system-reminder>\n if (end < CLOSE_TAG.length) break\n if (text.slice(end - CLOSE_TAG.length, end) !== CLOSE_TAG) break\n\n const closeTagStart = end - CLOSE_TAG.length\n\n // </system-reminder> must be at line start (preceded by \\n)\n if (closeTagStart === 0 || text[closeTagStart - 1] !== \"\\n\") break\n\n // Find matching \\n<system-reminder>\\n before it\n const openSearch = \"\\n\" + OPEN_TAG + \"\\n\"\n const openPos = text.lastIndexOf(openSearch, closeTagStart)\n if (openPos === -1) break\n\n // Extract inner content\n const innerStart = openPos + openSearch.length\n const innerEnd = closeTagStart - 1 // the \\n before </system-reminder>\n if (innerStart > innerEnd) break\n\n const content = text.slice(innerStart, innerEnd)\n tags.push({ content, tagStart: openPos, tagEnd: currentTagEnd })\n\n scanEnd = openPos\n }\n\n return { mainContentEnd: scanEnd, tags }\n}\n\n/**\n * Extract leading `<system-reminder>` tags from text.\n *\n * Scans forward from the start, collecting each tag that begins at the text\n * boundary (possibly preceded by whitespace). Returns tags in order and the\n * position where main (non-tag) content starts.\n *\n * Leading tags use the format:\n * [whitespace]<system-reminder>\\n...content...\\n</system-reminder>[\\n|EOF]\n *\n * Note: The first tag may start without a preceding `\\n` (beginning of text).\n */\nexport function extractLeadingSystemReminderTags(text: string): {\n mainContentStart: number\n tags: Array<ParsedSystemReminderTag>\n} {\n const tags: Array<ParsedSystemReminderTag> = []\n let scanStart = 0\n\n while (true) {\n const currentTagStart = scanStart\n\n // Skip leading whitespace (charCode: space=32, \\t=9, \\r=13)\n let start = scanStart\n while (start < text.length) {\n const c = text.charCodeAt(start)\n if (c !== 32 && c !== 9 && c !== 13) break\n start++\n }\n\n // Must start with <system-reminder>\n if (start + OPEN_TAG.length > text.length) break\n if (text.slice(start, start + OPEN_TAG.length) !== OPEN_TAG) break\n\n const afterOpen = start + OPEN_TAG.length\n if (afterOpen >= text.length || text[afterOpen] !== \"\\n\") break\n\n // Find closing tag: \\n</system-reminder> followed by \\n or EOF\n const closeNeedle = \"\\n\" + CLOSE_TAG\n let searchFrom = afterOpen\n let closePos = -1\n while (true) {\n const pos = text.indexOf(closeNeedle, searchFrom)\n if (pos === -1) break\n const afterClose = pos + closeNeedle.length\n if (afterClose >= text.length || text[afterClose] === \"\\n\") {\n closePos = pos\n break\n }\n searchFrom = pos + 1\n }\n if (closePos === -1) break\n\n const content = text.slice(afterOpen + 1, closePos)\n\n // tagEnd: skip past \\n</system-reminder> and any trailing newlines\n let endPos = closePos + closeNeedle.length\n while (endPos < text.length && text[endPos] === \"\\n\") endPos++\n\n tags.push({ content, tagStart: currentTagStart, tagEnd: endPos })\n scanStart = endPos\n }\n\n return { mainContentStart: scanStart, tags }\n}\n\n// ============================================================================\n// Filter Definitions\n// ============================================================================\n\n/**\n * A system-reminder filter type.\n *\n * `match` is a plain function using `startsWith` / `includes` instead of\n * RegExp — the content inside system-reminder tags has well-known structure,\n * so string methods are faster and more readable.\n */\nexport interface SystemReminderFilter {\n key: string\n description: string\n match: (content: string) => boolean\n defaultEnabled: boolean\n}\n\n/**\n * All known Claude Code system-reminder types.\n *\n * IMPORTANT: These patterns match content INSIDE `<system-reminder>` tags.\n * Content that appears directly in messages should NOT be in this list.\n */\nexport const SYSTEM_REMINDER_FILTERS: Array<SystemReminderFilter> = [\n {\n key: \"malware\",\n description: \"Malware analysis reminder\",\n match: (c) => c.startsWith(\"Whenever you read a file, you should consider whether it would be considered malware.\"),\n defaultEnabled: true,\n },\n]\n\n// ============================================================================\n// Rewrite Configuration\n// ============================================================================\n\n/**\n * Get the list of currently enabled filters.\n * Can be customized via enabledFilterKeys parameter.\n *\n * @deprecated Use `state.rewriteSystemReminders` instead. Retained for backward\n * compatibility with tests that call this directly.\n */\nexport function getEnabledFilters(enabledFilterKeys?: Array<string>): Array<SystemReminderFilter> {\n if (enabledFilterKeys) {\n return SYSTEM_REMINDER_FILTERS.filter((f) => enabledFilterKeys.includes(f.key))\n }\n return SYSTEM_REMINDER_FILTERS.filter((f) => f.defaultEnabled)\n}\n\n/**\n * Configure which system-reminder filters are enabled.\n * Pass an array of filter keys to enable, or undefined to reset to defaults.\n *\n * @deprecated Use `state.rewriteSystemReminders` instead. Retained for backward\n * compatibility with tests that call this directly.\n */\nexport function configureSystemReminderFilters(_filterKeys?: Array<string>): void {\n // No-op — rewriting is now driven entirely by state.rewriteSystemReminders.\n // Kept as a no-op export so existing test teardown code (`afterEach`) doesn't break.\n}\n\n/**\n * Determine how to rewrite a system-reminder tag's content.\n *\n * Reads from `state.rewriteSystemReminders`:\n * - `true` → return `\"\"` (remove all tags)\n * - `false` → return `null` (keep all tags unchanged)\n * - `Array<CompiledRewriteRule>` → first matching rule wins (top-down):\n * - `to: \"\"` → return `\"\"` (remove the tag entirely)\n * - `to: \"$0\"` (regex mode) → return `null` (keep unchanged, fast path)\n * - Otherwise → apply replacement:\n * - regex mode: `content.replace(from, to)` with capture group support\n * - line mode: replace exact `from` substring with `to`\n * - If result === original → return `null` (keep)\n * - Otherwise → return the new content\n * - If no rule matches → return `null` (keep)\n *\n * @returns `null` to keep original, `\"\"` to remove, or a new content string\n */\nfunction rewriteReminder(content: string): string | null {\n const rewrite = state.rewriteSystemReminders\n if (rewrite === true) return \"\"\n if (rewrite === false) return null\n\n for (const rule of rewrite) {\n const matched = rule.method === \"line\" ? content.includes(rule.from as string) : (rule.from as RegExp).test(content)\n\n // Reset lastIndex after test() in case of global flag\n if (rule.method !== \"line\") (rule.from as RegExp).lastIndex = 0\n\n if (!matched) continue\n\n // Empty replacement = remove the entire tag\n if (rule.to === \"\") return \"\"\n\n // $0 replacement in regex mode = keep tag unchanged (identity)\n if (rule.method !== \"line\" && rule.to === \"$0\") return null\n\n const result =\n rule.method === \"line\" ?\n content.replaceAll(rule.from as string, rule.to)\n : content.replace(rule.from as RegExp, rule.to)\n\n if (result === content) return null // replacement produced no change → keep\n return result\n }\n\n return null // no rule matched → keep\n}\n\n// ============================================================================\n// Tag Removal\n// ============================================================================\n\n/**\n * Rewrite, remove, or keep `<system-reminder>` tags in text content.\n *\n * Only processes reminders that:\n * 1. Appear at the START or END of content (not embedded in code)\n * 2. Are separated from main content by newlines (indicating injection points)\n *\n * For each tag, `rewriteReminder(content)` decides the action:\n * - `null` → keep the tag unchanged\n * - `\"\"` → remove the tag entirely\n * - new string → replace the tag's inner content\n *\n * This prevents accidental modification of system-reminder tags that appear\n * in tool_result content (e.g., when reading source files that contain\n * these tags as string literals or documentation).\n */\nexport function removeSystemReminderTags(text: string): string {\n let result = text\n let modified = false\n\n // Process trailing tags\n const trailing = extractTrailingSystemReminderTags(result)\n if (trailing.tags.length > 0) {\n let tail = \"\"\n for (const tag of trailing.tags) {\n const rewritten = rewriteReminder(tag.content)\n if (rewritten === null) {\n // Keep original\n tail += result.slice(tag.tagStart, tag.tagEnd)\n } else if (rewritten === \"\") {\n // Remove — don't append anything\n modified = true\n } else {\n // Replace content\n tail += `\\n${OPEN_TAG}\\n${rewritten}\\n${CLOSE_TAG}`\n modified = true\n }\n }\n if (modified) {\n result = result.slice(0, trailing.mainContentEnd) + tail\n }\n }\n\n // Process leading tags\n const leading = extractLeadingSystemReminderTags(result)\n if (leading.tags.length > 0) {\n let head = \"\"\n let leadingModified = false\n for (const tag of leading.tags) {\n const rewritten = rewriteReminder(tag.content)\n if (rewritten === null) {\n // Keep original\n head += result.slice(tag.tagStart, tag.tagEnd)\n } else if (rewritten === \"\") {\n // Remove — don't append anything\n leadingModified = true\n } else {\n // Replace content\n head += `${OPEN_TAG}\\n${rewritten}\\n${CLOSE_TAG}\\n`\n leadingModified = true\n }\n }\n if (leadingModified) {\n result = head + result.slice(leading.mainContentStart)\n modified = true\n }\n }\n\n if (!modified) return text\n\n // Only strip trailing newlines left behind by tag removal — never touch\n // leading whitespace (e.g. indentation in tool_result content like\n // \" 1→const x = 1\") to avoid false \"rewritten\" diffs.\n let end = result.length\n while (end > 0 && result[end - 1] === \"\\n\") end--\n return end < result.length ? result.slice(0, end) : result\n}\n","/**\n * Common types and configuration for auto-truncate modules.\n * Shared between OpenAI and Anthropic format handlers.\n */\n\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/config/paths\"\nimport { HTTPError, parseTokenLimitError } from \"~/lib/error\"\nimport {\n CLOSE_TAG,\n extractLeadingSystemReminderTags,\n extractTrailingSystemReminderTags,\n OPEN_TAG,\n} from \"~/lib/sanitize-system-reminder\"\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/** Configuration for auto-truncate behavior */\nexport interface AutoTruncateConfig {\n /** Safety margin percentage to account for token counting differences (default: 2) */\n safetyMarginPercent: number\n /** Percentage of context to preserve uncompressed from the end (default: 0.7 = 70%) */\n preserveRecentPercent: number\n /** Whether to enforce token limit (default: true) */\n checkTokenLimit: boolean\n /** Explicit token limit override (used in reactive retry — caller has already applied margin) */\n targetTokenLimit?: number\n}\n\n/** Maximum number of reactive auto-truncate retries per request */\nexport const MAX_AUTO_TRUNCATE_RETRIES = 5\n\n/** Factor to apply to error-reported limit when retrying (90% of limit) */\nexport const AUTO_TRUNCATE_RETRY_FACTOR = 0.9\n\nexport const DEFAULT_AUTO_TRUNCATE_CONFIG: AutoTruncateConfig = {\n safetyMarginPercent: 2,\n preserveRecentPercent: 0.7,\n checkTokenLimit: true,\n}\n\n// ============================================================================\n// Learned Limits (per-model, with calibration)\n// ============================================================================\n\n/** Per-model learned limits with tokenizer calibration */\nexport interface ModelLimits {\n /** Token upper bound (from error response's reported limit) */\n tokenLimit: number\n /** Calibration factor: actualTokens / gptEstimatedTokens.\n * > 1.0 means GPT tokenizer underestimates (Claude tokenizer produces more tokens).\n * < 1.0 means GPT tokenizer overestimates. */\n calibrationFactor: number\n /** Number of calibration samples (higher = more reliable factor) */\n sampleCount: number\n /** Last updated timestamp (ms since epoch) */\n updatedAt: number\n}\n\nconst learnedLimits = new Map<string, ModelLimits>()\n\n/** Get learned limits for a model (including calibration data) */\nexport function getLearnedLimits(modelId: string): ModelLimits | undefined {\n return learnedLimits.get(modelId)\n}\n\n/**\n * Check whether a model has known limits from previous failures.\n * Used to decide whether to pre-check requests before sending.\n */\nexport function hasKnownLimits(modelId: string): boolean {\n return learnedLimits.has(modelId)\n}\n\n// ============================================================================\n// Token Limit Learning\n// ============================================================================\n\n/**\n * Called when a token limit error (400) occurs.\n * Records the learned limit and optionally updates calibration.\n */\nexport function onTokenLimitExceeded(\n modelId: string,\n reportedLimit: number,\n reportedCurrent?: number,\n estimatedTokens?: number,\n): void {\n // Update learned limits (with calibration data for future pre-checks)\n const existing = learnedLimits.get(modelId)\n\n // Only update if this is the first time or the new limit is lower (more restrictive)\n if (!existing || reportedLimit < existing.tokenLimit) {\n learnedLimits.set(modelId, {\n tokenLimit: reportedLimit,\n calibrationFactor: existing?.calibrationFactor ?? 1.0,\n sampleCount: existing?.sampleCount ?? 0,\n updatedAt: Date.now(),\n })\n consola.info(\n `[AutoTruncate] Learned token limit for ${modelId}: ${reportedLimit}`,\n )\n }\n\n // Calibrate tokenizer if we have both actual and estimated token counts\n if (reportedCurrent !== undefined && estimatedTokens !== undefined && estimatedTokens > 0) {\n updateCalibration(modelId, reportedCurrent, estimatedTokens)\n const lim = learnedLimits.get(modelId)!\n consola.info(\n `[AutoTruncate] Calibration for ${modelId}: actual=${reportedCurrent} vs estimated=${estimatedTokens}`\n + ` → factor=${lim.calibrationFactor.toFixed(3)} (${lim.sampleCount} samples)`,\n )\n }\n\n schedulePersist()\n}\n\n/** Reset all dynamic limits (for testing) */\nexport function resetAllLimitsForTesting(): void {\n learnedLimits.clear()\n if (persistTimer) {\n clearTimeout(persistTimer)\n persistTimer = null\n }\n}\n\n// ============================================================================\n// Calibration (EWMA)\n// ============================================================================\n\nconst CALIBRATION_ALPHA = 0.3\nconst CALIBRATION_MIN = 0.5\nconst CALIBRATION_MAX = 3.0\n\n/**\n * Update the per-model calibration factor using EWMA.\n *\n * Called after a token limit error when we know both the GPT tokenizer estimate\n * and the actual token count (from the error response). The ratio between them\n * tells us how much the GPT tokenizer over/under-estimates for this model.\n */\nexport function updateCalibration(modelId: string, actualTokens: number, estimatedTokens: number): void {\n if (estimatedTokens <= 0) return\n const limits = learnedLimits.get(modelId)\n if (!limits) return\n\n const rawFactor = actualTokens / estimatedTokens\n const clamped = Math.max(CALIBRATION_MIN, Math.min(CALIBRATION_MAX, rawFactor))\n\n if (limits.sampleCount === 0) {\n limits.calibrationFactor = clamped\n } else {\n limits.calibrationFactor = CALIBRATION_ALPHA * clamped + (1 - CALIBRATION_ALPHA) * limits.calibrationFactor\n }\n limits.sampleCount++\n limits.updatedAt = Date.now()\n}\n\n/** Apply calibration factor to a GPT tokenizer estimate */\nexport function calibrate(modelId: string, gptEstimate: number): number {\n const limits = learnedLimits.get(modelId)\n if (!limits || limits.sampleCount === 0) return gptEstimate\n return Math.ceil(gptEstimate * limits.calibrationFactor)\n}\n\n// ============================================================================\n// Dynamic Safety Margin\n// ============================================================================\n\nconst BASE_MARGIN = 0.03\nconst BONUS_MARGIN_PER_SAMPLE = 0.07\n\n/**\n * Compute dynamic safety margin based on calibration confidence.\n * Fewer samples → wider margin (conservative). More samples → narrower margin.\n *\n * - 0 samples: 10% (0.03 + 0.07)\n * - 1 sample: 10%\n * - 10 samples: ~3.7%\n * - ∞ samples: 3%\n */\nexport function computeSafetyMargin(sampleCount: number): number {\n if (sampleCount <= 0) return BASE_MARGIN + BONUS_MARGIN_PER_SAMPLE\n return BASE_MARGIN + BONUS_MARGIN_PER_SAMPLE / sampleCount\n}\n\n// ============================================================================\n// Limit Persistence\n// ============================================================================\n\ninterface LearnedLimitsFile {\n version: 1\n limits: Record<string, ModelLimits>\n}\n\nlet persistTimer: ReturnType<typeof setTimeout> | null = null\nconst PERSIST_DEBOUNCE_MS = 5000\n\n/** Schedule an async write of learned limits (debounced) */\nexport function schedulePersist(): void {\n if (persistTimer) return\n persistTimer = setTimeout(() => {\n persistTimer = null\n void persistLimits()\n }, PERSIST_DEBOUNCE_MS)\n}\n\n/** Write learned limits to disk */\nexport async function persistLimits(): Promise<void> {\n if (learnedLimits.size === 0) return\n const data: LearnedLimitsFile = { version: 1, limits: Object.fromEntries(learnedLimits) }\n try {\n await fs.writeFile(PATHS.LEARNED_LIMITS, JSON.stringify(data, null, 2), \"utf8\")\n } catch {\n // Write failure is non-critical — limits will be re-learned on next error\n }\n}\n\n/** Load previously persisted limits from disk (called at startup) */\nexport async function loadPersistedLimits(): Promise<void> {\n try {\n const raw = await fs.readFile(PATHS.LEARNED_LIMITS, \"utf8\")\n const data = JSON.parse(raw) as LearnedLimitsFile\n if (data.version !== 1) return\n for (const [modelId, lim] of Object.entries(data.limits)) {\n if (\n lim.tokenLimit > 0\n && lim.calibrationFactor >= CALIBRATION_MIN\n && lim.calibrationFactor <= CALIBRATION_MAX\n ) {\n learnedLimits.set(modelId, lim)\n }\n }\n if (learnedLimits.size > 0) {\n consola.info(`[AutoTruncate] Loaded learned limits for ${learnedLimits.size} model(s)`)\n }\n } catch {\n // File doesn't exist or is corrupted — start fresh\n }\n}\n\n// ============================================================================\n// Reactive Auto-Truncate Helpers\n// ============================================================================\n\n/** Copilot error structure for JSON parsing */\ninterface CopilotErrorBody {\n error?: {\n message?: string\n code?: string\n type?: string\n }\n}\n\n/** Result from tryParseAndLearnLimit */\nexport interface LimitErrorInfo {\n type: \"token_limit\"\n /** The reported limit (tokens) */\n limit?: number\n /** The current usage that exceeded the limit */\n current?: number\n}\n\n/**\n * Parse an HTTPError to detect token limit errors,\n * and record the learned limit for future pre-checks.\n *\n * When `estimatedTokens` is provided (the GPT tokenizer estimate at the time\n * of the error), also updates the per-model calibration factor.\n *\n * Returns error info if the error is a retryable token limit error, null otherwise.\n */\nexport function tryParseAndLearnLimit(\n error: HTTPError,\n modelId: string,\n learn = true,\n estimatedTokens?: number,\n): LimitErrorInfo | null {\n // 400 → try to parse token limit\n if (error.status === 400) {\n let errorJson: CopilotErrorBody | undefined\n try {\n errorJson = JSON.parse(error.responseText) as CopilotErrorBody\n } catch {\n return null\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- errorJson.error may be undefined at runtime\n if (!errorJson?.error?.message) return null\n\n // Check OpenAI format (code: \"model_max_prompt_tokens_exceeded\")\n // or Anthropic format (type: \"invalid_request_error\")\n const isTokenError =\n errorJson.error.code === \"model_max_prompt_tokens_exceeded\" || errorJson.error.type === \"invalid_request_error\"\n\n if (!isTokenError) return null\n\n const tokenInfo = parseTokenLimitError(errorJson.error.message)\n if (!tokenInfo) return null\n\n // Record the learned limit (only when auto-truncate is enabled)\n if (learn) {\n onTokenLimitExceeded(modelId, tokenInfo.limit, tokenInfo.current, estimatedTokens)\n }\n\n return {\n type: \"token_limit\",\n limit: tokenInfo.limit,\n current: tokenInfo.current,\n }\n }\n\n return null\n}\n\n// ============================================================================\n// Tool Result Compression\n// ============================================================================\n\n/** Threshold for large tool_result content (bytes) */\nexport const LARGE_TOOL_RESULT_THRESHOLD = 10000 // 10KB\n\n/** Maximum length for compressed tool_result summary */\nconst COMPRESSED_SUMMARY_LENGTH = 500\n\n/**\n * Compress a large tool_result content to a summary.\n * Keeps the first and last portions with a note about truncation.\n *\n * Preserves `<system-reminder>` tag wrappers (injected by Claude Code)\n * with a truncated summary of their content, instead of letting them\n * get sliced into broken XML fragments by character-level truncation.\n */\nexport function compressToolResultContent(content: string): string {\n if (content.length <= LARGE_TOOL_RESULT_THRESHOLD) {\n return content\n }\n\n // Extract trailing <system-reminder> tags before compression.\n // These are preserved as truncated summaries instead of being sliced\n // into broken XML fragments by character-level truncation.\n const { mainContentEnd, tags } = extractTrailingSystemReminderTags(content)\n const reminders = tags.map((tag) => {\n const summary = tag.content.trim().split(\"\\n\")[0].slice(0, 80)\n return `${OPEN_TAG}\\n[Truncated] ${summary}\\n${CLOSE_TAG}`\n })\n\n const mainContent = content.slice(0, mainContentEnd)\n\n // Compress the main content (without trailing system-reminder tags)\n const halfLen = Math.floor(COMPRESSED_SUMMARY_LENGTH / 2)\n const start = mainContent.slice(0, halfLen)\n const end = mainContent.slice(-halfLen)\n const removedChars = mainContent.length - COMPRESSED_SUMMARY_LENGTH\n\n let result = `${start}\\n\\n[... ${removedChars.toLocaleString()} characters omitted for brevity ...]\\n\\n${end}`\n\n // Re-append preserved system-reminder tags\n if (reminders.length > 0) {\n result += \"\\n\" + reminders.join(\"\\n\")\n }\n\n return result\n}\n\n// ============================================================================\n// Compacted Text Block Compression\n// ============================================================================\n\n/** Prefix that identifies a compacted tool result in a system-reminder tag */\nconst COMPACTED_RESULT_PREFIX = \"Result of calling the \"\n\n/**\n * Compress a compacted tool result text block.\n *\n * Claude Code compacts tool_result blocks into text blocks wrapped in\n * `<system-reminder>` tags during conversation summarization. Format:\n *\n * <system-reminder>\n * Result of calling the Read tool: \" 1→...file content...\"\n * </system-reminder>\n *\n * These blocks can be very large (entire file contents) but are low-value\n * since the file can be re-read. This replaces the full content with a\n * compressed summary preserving the tool name and a short preview.\n *\n * Returns the compressed text, or `null` if the text doesn't match\n * the expected compacted format.\n */\nexport function compressCompactedReadResult(text: string): string | null {\n const { mainContentStart, tags } = extractLeadingSystemReminderTags(text)\n\n // Must be exactly one system-reminder tag covering the entire text\n if (tags.length !== 1) return null\n // Allow trailing whitespace/newlines after the tag\n if (mainContentStart < text.length && text.slice(mainContentStart).trim() !== \"\") return null\n\n const content = tags[0].content\n if (!content.startsWith(COMPACTED_RESULT_PREFIX)) return null\n\n // Extract tool name: \"Result of calling the Read tool: \"...\"\n const colonPos = content.indexOf(\": \", COMPACTED_RESULT_PREFIX.length)\n if (colonPos === -1) return null\n\n const toolName = content.slice(COMPACTED_RESULT_PREFIX.length, colonPos).replace(/ tool$/, \"\")\n\n // Extract the quoted content after \": \"\n const afterColon = content.slice(colonPos + 2)\n if (!afterColon.startsWith('\"')) return null\n\n // Get the inner content (between quotes) — may use \\\" escapes\n const innerContent = afterColon.slice(1, afterColon.endsWith('\"') ? -1 : undefined)\n\n // Build a short preview from the first meaningful line\n const firstLines = innerContent.split(String.raw`\\n`).slice(0, 3)\n const preview = firstLines.join(\" | \").slice(0, 150)\n\n return (\n `${OPEN_TAG}\\n`\n + `[Compressed] ${toolName} tool result (${innerContent.length.toLocaleString()} chars). `\n + `Preview: ${preview}\\n`\n + CLOSE_TAG\n )\n}\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nimport { tryParseAndLearnLimit } from \"./auto-truncate\"\nimport { state } from \"./state\"\n\nexport class HTTPError extends Error {\n status: number\n responseText: string\n /** Model ID that caused the error (if known) */\n modelId?: string\n\n constructor(message: string, status: number, responseText: string, modelId?: string) {\n super(message)\n this.status = status\n this.responseText = responseText\n this.modelId = modelId\n }\n\n static async fromResponse(message: string, response: Response, modelId?: string): Promise<HTTPError> {\n const text = await response.text()\n return new HTTPError(message, response.status, text, modelId)\n }\n}\n\n/** Copilot error structure */\ninterface CopilotError {\n error?: {\n message?: string\n code?: string\n }\n}\n\n/** Parse token limit info from error message */\nexport function parseTokenLimitError(message: string): {\n current: number\n limit: number\n} | null {\n // Match OpenAI format: \"prompt token count of 135355 exceeds the limit of 128000\"\n const openaiMatch = message.match(/prompt token count of (\\d+) exceeds the limit of (\\d+)/)\n if (openaiMatch) {\n return {\n current: Number.parseInt(openaiMatch[1], 10),\n limit: Number.parseInt(openaiMatch[2], 10),\n }\n }\n\n // Match Anthropic format: \"prompt is too long: 208598 tokens > 200000 maximum\"\n const anthropicMatch = message.match(/prompt is too long: (\\d+) tokens > (\\d+) maximum/)\n if (anthropicMatch) {\n return {\n current: Number.parseInt(anthropicMatch[1], 10),\n limit: Number.parseInt(anthropicMatch[2], 10),\n }\n }\n\n return null\n}\n\n/** Format Anthropic-compatible error for token limit exceeded */\nfunction formatTokenLimitError(current: number, limit: number) {\n const excess = current - limit\n const percentage = Math.round((excess / limit) * 100)\n\n // Return Anthropic-compatible error that clients can recognize and handle\n // The \"prompt_too_long\" type is what Anthropic's API returns for context limit errors\n // This should trigger Claude Code's auto-truncate behavior\n return {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n `prompt is too long: ${current} tokens > ${limit} maximum ` + `(${excess} tokens over, ${percentage}% excess)`,\n },\n }\n}\n\n/** Format Anthropic-compatible error for request too large (413) */\nfunction formatRequestTooLargeError() {\n // Return Anthropic-compatible error for 413 Request Entity Too Large\n // This happens when the HTTP body is too large, separate from token limits\n return {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Request body too large. The HTTP request exceeds the server's size limit. \"\n + \"Try reducing the conversation history or removing large content like images.\",\n },\n }\n}\n\n/** Format Anthropic-compatible error for rate limit exceeded (429) */\nfunction formatRateLimitError(copilotMessage?: string) {\n // Return Anthropic-compatible error for 429 rate limit\n // The \"rate_limit_error\" type is what Anthropic's API returns for rate limiting\n return {\n type: \"error\",\n error: {\n type: \"rate_limit_error\",\n message: copilotMessage ?? \"You have exceeded your rate limit. Please try again later.\",\n },\n }\n}\n\n/** Anthropic error structure */\ninterface AnthropicError {\n type?: string\n error?: {\n type?: string\n message?: string\n }\n}\n\nexport function forwardError(c: Context, error: unknown) {\n // Error file persistence is handled by the error-persistence consumer\n // (subscribes to \"failed\" events on RequestContext) — no inline writing here.\n\n if (error instanceof HTTPError) {\n // Try to detect and learn from token limit / body size errors\n // Only record limits for future pre-checks when auto-truncate is enabled\n const limitInfo = tryParseAndLearnLimit(error, error.modelId ?? \"unknown\", state.autoTruncate)\n\n // Handle 413 Request Entity Too Large\n if (error.status === 413) {\n const formattedError = formatRequestTooLargeError()\n consola.warn(`HTTP 413: Request too large`)\n return c.json(formattedError, 413 as ContentfulStatusCode)\n }\n\n // Handle token limit exceeded (detected by tryParseAndLearnLimit)\n if (limitInfo?.type === \"token_limit\" && limitInfo.current && limitInfo.limit) {\n const formattedError = formatTokenLimitError(limitInfo.current, limitInfo.limit)\n const excess = limitInfo.current - limitInfo.limit\n const percentage = Math.round((excess / limitInfo.limit) * 100)\n consola.warn(\n `HTTP ${error.status}: Token limit exceeded for ${error.modelId ?? \"unknown\"} `\n + `(${limitInfo.current.toLocaleString()} > ${limitInfo.limit.toLocaleString()}, `\n + `${excess.toLocaleString()} over, ${percentage}% excess)`,\n )\n return c.json(formattedError, 400 as ContentfulStatusCode)\n }\n\n let errorJson: unknown\n try {\n errorJson = JSON.parse(error.responseText)\n } catch {\n errorJson = error.responseText\n }\n\n // Only attempt structured error detection on parsed JSON objects\n if (typeof errorJson === \"object\" && errorJson !== null) {\n const errorObj = errorJson as CopilotError & AnthropicError\n\n // Check for rate limit error from Copilot (429 with code \"rate_limited\")\n if (error.status === 429 || errorObj.error?.code === \"rate_limited\") {\n const formattedError = formatRateLimitError(errorObj.error?.message)\n consola.warn(`HTTP 429: Rate limit exceeded`)\n return c.json(formattedError, 429 as ContentfulStatusCode)\n }\n } else if (error.status === 429) {\n // Rate limit with non-JSON response\n const formattedError = formatRateLimitError()\n consola.warn(`HTTP 429: Rate limit exceeded`)\n return c.json(formattedError, 429 as ContentfulStatusCode)\n }\n\n // Log unhandled HTTP errors\n consola.error(`HTTP ${error.status}:`, errorJson)\n\n return c.json(\n {\n error: {\n message: error.responseText,\n type: \"error\",\n },\n },\n error.status as ContentfulStatusCode,\n )\n }\n\n // Non-HTTP errors (socket closures, DNS failures, timeouts, etc.)\n const errorMessage = error instanceof Error ? formatErrorWithCause(error) : String(error)\n consola.error(`Unexpected non-HTTP error in ${c.req.method} ${c.req.path}:`, errorMessage)\n\n return c.json(\n {\n error: {\n message: errorMessage,\n type: \"error\",\n },\n },\n 500,\n )\n}\n\n// ─── Error Classification System ───\n\n/** Structured error types for pipeline retry decisions */\nexport type ApiErrorType =\n | \"rate_limited\" // 429\n | \"payload_too_large\" // 413\n | \"token_limit\" // 200/400 but body contains token limit error\n | \"content_filtered\" // Content filtering\n | \"auth_expired\" // Token expired\n | \"network_error\" // Connection failure\n | \"server_error\" // 5xx\n | \"bad_request\" // 400 (non-token-limit)\n\n/** Classified API error with structured metadata */\nexport interface ApiError {\n type: ApiErrorType\n status: number\n message: string\n /** Retry-After seconds (rate_limited) */\n retryAfter?: number\n /** Token limit from error response (token_limit) */\n tokenLimit?: number\n /** Current token count from error response (token_limit) */\n tokenCurrent?: number\n /** Original error object */\n raw: unknown\n}\n\n/**\n * Classify a raw error into a structured ApiError.\n * Used by the pipeline to route errors to appropriate RetryStrategies.\n */\nexport function classifyError(error: unknown): ApiError {\n if (error instanceof HTTPError) {\n return classifyHTTPError(error)\n }\n\n // Network errors: fetch failures, socket closures, connection resets, timeouts, DNS failures\n // Bun throws TypeError for some fetch failures, and plain Error for socket closures.\n // Match broadly on error message patterns to catch all network-level failures.\n if (error instanceof Error && isNetworkError(error)) {\n return {\n type: \"network_error\",\n status: 0,\n message: formatErrorWithCause(error),\n raw: error,\n }\n }\n\n // Generic Error\n if (error instanceof Error) {\n return {\n type: \"bad_request\",\n status: 0,\n message: formatErrorWithCause(error),\n raw: error,\n }\n }\n\n return {\n type: \"bad_request\",\n status: 0,\n message: String(error),\n raw: error,\n }\n}\n\nfunction classifyHTTPError(error: HTTPError): ApiError {\n const { status, responseText, message } = error\n\n // 429 Rate Limited\n if (status === 429) {\n const retryAfter = extractRetryAfterFromBody(responseText)\n return {\n type: \"rate_limited\",\n status,\n message,\n retryAfter,\n raw: error,\n }\n }\n\n // 413 Payload Too Large\n if (status === 413) {\n return {\n type: \"payload_too_large\",\n status,\n message,\n raw: error,\n }\n }\n\n // 5xx Server Errors\n if (status >= 500) {\n return {\n type: \"server_error\",\n status,\n message,\n raw: error,\n }\n }\n\n // 401/403 Auth Errors\n if (status === 401 || status === 403) {\n return {\n type: \"auth_expired\",\n status,\n message,\n raw: error,\n }\n }\n\n // 400 — check for token limit error in response body\n if (status === 400) {\n const tokenLimit = tryExtractTokenLimit(responseText)\n if (tokenLimit) {\n return {\n type: \"token_limit\",\n status,\n message,\n tokenLimit: tokenLimit.limit,\n tokenCurrent: tokenLimit.current,\n raw: error,\n }\n }\n\n // Check for rate_limited code in body (some APIs return 400 for rate limits)\n if (isRateLimitedInBody(responseText)) {\n const retryAfter = extractRetryAfterFromBody(responseText)\n return {\n type: \"rate_limited\",\n status,\n message,\n retryAfter,\n raw: error,\n }\n }\n }\n\n // Default: bad_request\n return {\n type: \"bad_request\",\n status,\n message,\n raw: error,\n }\n}\n\n/** Extract retry_after from JSON response body */\nfunction extractRetryAfterFromBody(responseText: string): number | undefined {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\") {\n // Top-level retry_after\n if (\"retry_after\" in parsed && typeof (parsed as Record<string, unknown>).retry_after === \"number\") {\n return (parsed as { retry_after: number }).retry_after\n }\n // Nested error.retry_after\n if (\"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (\n err\n && typeof err === \"object\"\n && \"retry_after\" in err\n && typeof (err as Record<string, unknown>).retry_after === \"number\"\n ) {\n return (err as { retry_after: number }).retry_after\n }\n }\n }\n } catch {\n // Not JSON\n }\n return undefined\n}\n\n/** Check if response body contains rate_limited code */\nfunction isRateLimitedInBody(responseText: string): boolean {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (err && typeof err === \"object\" && \"code\" in err) {\n return (err as { code: unknown }).code === \"rate_limited\"\n }\n }\n } catch {\n // Not JSON\n }\n return false\n}\n\n/** Try to extract token limit info from response body */\nfunction tryExtractTokenLimit(responseText: string): { current: number; limit: number } | null {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (\n err\n && typeof err === \"object\"\n && \"message\" in err\n && typeof (err as Record<string, unknown>).message === \"string\"\n ) {\n return parseTokenLimitError((err as { message: string }).message)\n }\n }\n } catch {\n // Not JSON\n }\n return null\n}\n\n// ─── Network Error Detection ───\n\n/** Known network/socket error message patterns from Bun and Node.js fetch */\nconst NETWORK_ERROR_PATTERNS = [\n \"socket\", // \"The socket connection was closed unexpectedly\"\n \"ECONNRESET\", // Connection reset by peer\n \"ECONNREFUSED\", // Connection refused\n \"ETIMEDOUT\", // Connection timed out\n \"ENETUNREACH\", // Network unreachable\n \"EHOSTUNREACH\", // Host unreachable\n \"EAI_AGAIN\", // DNS lookup timeout\n \"UND_ERR_SOCKET\", // undici socket errors (Node.js)\n \"fetch failed\", // Generic fetch failure\n \"network\", // General network errors\n \"TLS\", // TLS/SSL errors\n \"CERT\", // Certificate errors\n \"abort\", // AbortError from timeouts\n]\n\n/** Check if an error is a network-level failure (socket, DNS, TLS, connection errors) */\nfunction isNetworkError(error: Error): boolean {\n const msg = error.message.toLowerCase()\n if (NETWORK_ERROR_PATTERNS.some((p) => msg.includes(p.toLowerCase()))) return true\n\n // Check cause chain for network indicators\n if (error.cause instanceof Error) return isNetworkError(error.cause)\n\n return false\n}\n\n/**\n * Strip Bun's unhelpful verbose hint from error messages.\n * Bun appends \"For more information, pass `verbose: true` in the second argument to fetch()\"\n * to socket/network errors — this is an implementation detail, not useful to the user.\n */\nfunction stripBunVerboseHint(message: string): string {\n return message.replace(/\\s*For more information, pass `verbose: true`.*$/i, \"\")\n}\n\n/**\n * Format error message including cause chain, with Bun noise stripped.\n * Surfaces error.cause details (e.g. underlying socket/TLS reason) inline.\n */\nexport function formatErrorWithCause(error: Error): string {\n let msg = stripBunVerboseHint(error.message)\n if (error.cause instanceof Error && error.cause.message && error.cause.message !== error.message) {\n msg += ` (cause: ${stripBunVerboseHint(error.cause.message)})`\n }\n return msg\n}\n\n// ─── Error Message Extraction ───\n\n/** Extract error message with fallback. For HTTPError, extracts the actual API error response. */\nexport function getErrorMessage(error: unknown, fallback = \"Unknown error\"): string {\n if (error instanceof Error) {\n if (\"responseText\" in error && typeof (error as { responseText: unknown }).responseText === \"string\") {\n const responseText = (error as { responseText: string }).responseText\n const status = \"status\" in error ? (error as { status: number }).status : undefined\n try {\n const parsed = JSON.parse(responseText) as { error?: { message?: string; type?: string } }\n if (parsed.error?.message) {\n return status ? `HTTP ${status}: ${parsed.error.message}` : parsed.error.message\n }\n } catch {\n if (responseText.length > 0 && responseText.length < 500) {\n return status ? `HTTP ${status}: ${responseText}` : responseText\n }\n }\n return status ? `HTTP ${status}: ${error.message}` : error.message\n }\n // For non-HTTP errors, include cause information (e.g. Bun verbose fetch details)\n return formatErrorWithCause(error)\n }\n return fallback\n}\n","/** Copilot API client — token and usage */\n\nimport { COPILOT_INTERNAL_API_VERSION, GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n// ============================================================================\n// Token\n// ============================================================================\n\nexport const getCopilotToken = async (): Promise<CopilotTokenResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, {\n headers: { ...githubHeaders(state), \"x-github-api-version\": COPILOT_INTERNAL_API_VERSION },\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get Copilot token\", response)\n\n return (await response.json()) as CopilotTokenResponse\n}\n\n/**\n * Copilot token API response.\n * Only the fields we actively use are typed; the full response is\n * preserved as-is in CopilotTokenInfo.raw for future consumers.\n */\nexport interface CopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n [key: string]: unknown\n}\n\n// ============================================================================\n// Usage\n// ============================================================================\n\nexport const getCopilotUsage = async (): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: { ...githubHeaders(state), \"x-github-api-version\": COPILOT_INTERNAL_API_VERSION },\n })\n\n if (!response.ok) {\n throw await HTTPError.fromResponse(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\n\nimport type { GitHubTokenManager } from \"./github-token-manager\"\nimport type { CopilotTokenInfo } from \"./types\"\n\nimport { getCopilotToken } from \"./copilot-client\"\n\nexport interface CopilotTokenManagerOptions {\n /** GitHub token manager instance */\n githubTokenManager: GitHubTokenManager\n /** Minimum refresh interval in seconds (default: 60) */\n minRefreshIntervalSeconds?: number\n /** Maximum retries for token refresh (default: 3) */\n maxRetries?: number\n}\n\n/**\n * Manages Copilot token lifecycle including automatic refresh.\n * Depends on GitHubTokenManager for authentication.\n *\n * All refresh paths (scheduled + on-demand via 401) go through `refresh()`,\n * which deduplicates concurrent callers and reschedules the next refresh based\n * on the server's `refresh_in` value.\n */\nexport class CopilotTokenManager {\n private githubTokenManager: GitHubTokenManager\n private currentToken: CopilotTokenInfo | null = null\n private refreshTimeout: ReturnType<typeof setTimeout> | null = null\n private minRefreshIntervalMs: number\n private maxRetries: number\n /** Shared promise to prevent concurrent refresh attempts */\n private refreshInFlight: Promise<CopilotTokenInfo | null> | null = null\n\n constructor(options: CopilotTokenManagerOptions) {\n this.githubTokenManager = options.githubTokenManager\n this.minRefreshIntervalMs = (options.minRefreshIntervalSeconds ?? 60) * 1000\n this.maxRetries = options.maxRetries ?? 3\n }\n\n /**\n * Get the current Copilot token info.\n */\n getCurrentToken(): CopilotTokenInfo | null {\n return this.currentToken\n }\n\n /**\n * Initialize the Copilot token and start automatic refresh.\n */\n async initialize(): Promise<CopilotTokenInfo> {\n const tokenInfo = await this.fetchCopilotToken()\n\n // Update global state\n state.copilotToken = tokenInfo.token\n\n // Show token in verbose mode\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n\n // Schedule first refresh based on server's refresh_in\n this.scheduleRefresh(tokenInfo.refreshIn)\n\n return tokenInfo\n }\n\n /**\n * Fetch a new Copilot token from the API.\n */\n private async fetchCopilotToken(): Promise<CopilotTokenInfo> {\n const response = await getCopilotToken()\n\n const tokenInfo: CopilotTokenInfo = {\n token: response.token,\n expiresAt: response.expires_at,\n refreshIn: response.refresh_in,\n raw: response,\n }\n\n this.currentToken = tokenInfo\n return tokenInfo\n }\n\n /**\n * Fetch a new Copilot token with exponential backoff retry.\n * Pure acquisition logic — does not update global state or reschedule timers.\n */\n private async fetchTokenWithRetry(): Promise<CopilotTokenInfo | null> {\n let lastError: unknown = null\n\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n return await this.fetchCopilotToken()\n } catch (error) {\n lastError = error\n\n // Check if this is a 401 error - might need to refresh GitHub token\n if (this.isUnauthorizedError(error)) {\n consola.warn(\"Copilot token refresh got 401, trying to refresh GitHub token...\")\n const newGithubToken = await this.githubTokenManager.refresh()\n if (newGithubToken) {\n // Update state and retry\n state.githubToken = newGithubToken.token\n continue\n }\n }\n\n const delay = Math.min(1000 * 2 ** attempt, 30000) // Max 30s delay\n consola.warn(`Token refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay}ms`)\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n\n consola.error(\"All token refresh attempts failed:\", lastError)\n return null\n }\n\n /**\n * Check if an error is a 401 Unauthorized error.\n */\n private isUnauthorizedError(error: unknown): boolean {\n if (error && typeof error === \"object\" && \"status\" in error) {\n return (error as { status: number }).status === 401\n }\n return false\n }\n\n /**\n * Schedule the next refresh using setTimeout.\n *\n * Uses the server-provided `refresh_in` value each time, adapting to\n * changing token lifetimes. After each refresh, reschedules based on\n * the new token's `refresh_in`.\n */\n private scheduleRefresh(refreshInSeconds: number): void {\n // Sanity check: refresh_in should be positive and reasonable\n let effectiveRefreshIn = refreshInSeconds\n if (refreshInSeconds <= 0) {\n consola.warn(`[CopilotToken] Invalid refresh_in=${refreshInSeconds}s, using default 30 minutes`)\n effectiveRefreshIn = 1800 // 30 minutes\n }\n\n // Calculate delay (refresh a bit before expiration)\n const delayMs = Math.max((effectiveRefreshIn - 60) * 1000, this.minRefreshIntervalMs)\n\n consola.debug(\n `[CopilotToken] refresh_in=${effectiveRefreshIn}s, scheduling next refresh in ${Math.round(delayMs / 1000)}s`,\n )\n\n // Clear any existing timer\n this.cancelScheduledRefresh()\n\n this.refreshTimeout = setTimeout(() => {\n this.refresh().catch((error: unknown) => {\n consola.error(\"Unexpected error during scheduled token refresh:\", error)\n })\n }, delayMs)\n }\n\n /**\n * Cancel the currently scheduled refresh.\n */\n private cancelScheduledRefresh(): void {\n if (this.refreshTimeout) {\n clearTimeout(this.refreshTimeout)\n this.refreshTimeout = null\n }\n }\n\n /**\n * Stop automatic token refresh.\n * Call this during cleanup/shutdown.\n */\n stopAutoRefresh(): void {\n this.cancelScheduledRefresh()\n }\n\n /**\n * Refresh the Copilot token.\n *\n * Single entry point for all refreshes — both scheduled and on-demand\n * (e.g. after a 401). Concurrent callers share the same in-flight refresh.\n * On success, updates global state and reschedules the next refresh based\n * on the new token's `refresh_in`.\n */\n async refresh(): Promise<CopilotTokenInfo | null> {\n // If a refresh is already in progress, piggyback on it\n if (this.refreshInFlight) {\n consola.debug(\"[CopilotToken] Refresh already in progress, waiting...\")\n return this.refreshInFlight\n }\n\n this.refreshInFlight = this.fetchTokenWithRetry()\n .then((tokenInfo) => {\n if (tokenInfo) {\n state.copilotToken = tokenInfo.token\n // Reschedule based on new token's refresh_in\n this.scheduleRefresh(tokenInfo.refreshIn)\n consola.verbose(`[CopilotToken] Token refreshed (next refresh_in=${tokenInfo.refreshIn}s)`)\n } else {\n consola.error(\"[CopilotToken] Token refresh failed, keeping existing token\")\n // Still reschedule with a fallback to avoid stopping the refresh loop entirely\n this.scheduleRefresh(300) // retry in 5 minutes\n }\n return tokenInfo\n })\n .finally(() => {\n this.refreshInFlight = null\n })\n\n return this.refreshInFlight\n }\n\n /**\n * Check if the current token is expired or about to expire.\n */\n isExpiredOrExpiring(marginSeconds = 60): boolean {\n if (!this.currentToken) {\n return true\n }\n\n const now = Date.now() / 1000\n return this.currentToken.expiresAt - marginSeconds <= now\n }\n}\n","export const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined => value === null || value === undefined\n\n/** Convert bytes to KB with rounding */\nexport function bytesToKB(bytes: number): number {\n return Math.round(bytes / 1024)\n}\n\n/** Generate unique ID (timestamp + random) */\nexport function generateId(randomLength = 7): string {\n return (\n Date.now().toString(36)\n + Math.random()\n .toString(36)\n .slice(2, 2 + randomLength)\n )\n}\n","/** GitHub OAuth API client — device code flow and user info */\n\nimport consola from \"consola\"\n\nimport {\n GITHUB_API_BASE_URL,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n githubHeaders,\n standardHeaders,\n} from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { sleep } from \"~/lib/utils\"\n\n// ============================================================================\n// User\n// ============================================================================\n\nexport interface GitHubUser {\n login: string\n id: number\n name: string | null\n email: string | null\n created_at: string\n updated_at: string\n two_factor_authentication: boolean\n}\n\nexport const getGitHubUser = async (): Promise<GitHubUser> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GitHubUser\n}\n\n// ============================================================================\n// Device code flow\n// ============================================================================\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n\nexport const getDeviceCode = async (): Promise<DeviceCodeResponse> => {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: \"read:user\",\n }),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport async function pollAccessToken(deviceCode: DeviceCodeResponse): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n // Calculate expiration time based on expires_in from device code response\n const expiresAt = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expiresAt) {\n const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n })\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = (await response.json()) as AccessTokenResponse\n consola.debug(\"Polling access token response:\", json)\n\n const { access_token } = json\n\n if (access_token) {\n return access_token\n } else {\n await sleep(sleepDuration)\n }\n }\n\n throw new Error(\"Device code expired. Please run the authentication flow again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n","import { state } from \"~/lib/state\"\nimport { getGitHubUser } from \"~/lib/token/github-client\"\n\nimport type { TokenInfo, TokenValidationResult } from \"../types\"\n\n/**\n * Abstract base class for GitHub token providers.\n * Each provider represents a different source of GitHub tokens.\n */\nexport abstract class GitHubTokenProvider {\n /** Human-readable name of the provider */\n abstract readonly name: string\n\n /** Priority (lower = higher priority, tried first) */\n abstract readonly priority: number\n\n /** Whether this provider can refresh tokens */\n abstract readonly refreshable: boolean\n\n /**\n * Check if this provider is available (has required configuration).\n * For example, CLI provider is only available if token was passed via args.\n */\n abstract isAvailable(): boolean | Promise<boolean>\n\n /**\n * Get the token from this provider.\n * Returns null if not available or token cannot be obtained.\n */\n abstract getToken(): Promise<TokenInfo | null>\n\n /**\n * Refresh the token (if supported).\n * Default implementation returns null (not supported).\n */\n // eslint-disable-next-line @typescript-eslint/require-await\n async refresh(): Promise<TokenInfo | null> {\n return null\n }\n\n /**\n * Validate the token by calling GitHub API.\n * Returns validation result with username if successful.\n */\n async validate(token: string): Promise<TokenValidationResult> {\n // Temporarily set the token to validate\n const originalToken = state.githubToken\n\n try {\n state.githubToken = token\n const user = await getGitHubUser()\n return {\n valid: true,\n username: user.login,\n }\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n }\n } finally {\n // Restore original token\n state.githubToken = originalToken\n }\n }\n}\n","import type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Provider for tokens passed via CLI --github-token argument.\n * Highest priority (1) - if user explicitly provides a token, use it.\n */\nexport class CLITokenProvider extends GitHubTokenProvider {\n readonly name = \"CLI\"\n readonly priority = 1\n readonly refreshable = false\n\n private token: string | undefined\n\n constructor(token?: string) {\n super()\n this.token = token\n }\n\n isAvailable(): boolean {\n return Boolean(this.token && this.token.trim())\n }\n\n getToken(): Promise<TokenInfo | null> {\n if (!this.isAvailable() || !this.token) {\n return Promise.resolve(null)\n }\n\n return Promise.resolve({\n token: this.token.trim(),\n source: \"cli\",\n refreshable: false,\n })\n }\n}\n","import fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/config/paths\"\n\nimport type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Provider for tokens stored in file system.\n * Priority 3 - checked after CLI and environment variables.\n */\nexport class FileTokenProvider extends GitHubTokenProvider {\n readonly name = \"File\"\n readonly priority = 3\n readonly refreshable = false\n\n async isAvailable(): Promise<boolean> {\n try {\n const token = await this.readTokenFile()\n return Boolean(token && token.trim())\n } catch {\n return false\n }\n }\n\n async getToken(): Promise<TokenInfo | null> {\n try {\n const token = await this.readTokenFile()\n if (!token || !token.trim()) {\n return null\n }\n\n return {\n token: token.trim(),\n source: \"file\",\n refreshable: false,\n }\n } catch {\n return null\n }\n }\n\n /**\n * Save a token to the file.\n * This is used by device auth provider to persist tokens.\n */\n async saveToken(token: string): Promise<void> {\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token.trim())\n }\n\n /**\n * Clear the stored token.\n */\n async clearToken(): Promise<void> {\n try {\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, \"\")\n } catch {\n // Ignore errors when clearing\n }\n }\n\n private async readTokenFile(): Promise<string> {\n return fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n }\n}\n","import consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getDeviceCode, pollAccessToken } from \"~/lib/token/github-client\"\n\nimport type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\nimport { FileTokenProvider } from \"./file\"\n\n/**\n * Provider for tokens obtained via GitHub device authorization flow.\n * Priority 4 (lowest) - only used when no other token source is available.\n * This is the interactive fallback that prompts the user to authenticate.\n */\nexport class DeviceAuthProvider extends GitHubTokenProvider {\n readonly name = \"DeviceAuth\"\n readonly priority = 4\n readonly refreshable = true\n\n private fileProvider: FileTokenProvider\n\n constructor() {\n super()\n this.fileProvider = new FileTokenProvider()\n }\n\n /**\n * Device auth is always \"available\" as a fallback.\n * It will prompt the user to authenticate interactively.\n */\n isAvailable(): boolean {\n return true\n }\n\n /**\n * Run the device authorization flow to get a new token.\n * This will prompt the user to visit a URL and enter a code.\n */\n async getToken(): Promise<TokenInfo | null> {\n try {\n consola.info(\"Not logged in, starting device authorization flow...\")\n\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(`Please enter the code \"${response.user_code}\" at ${response.verification_uri}`)\n\n const token = await pollAccessToken(response)\n\n // Save to file for future sessions\n await this.fileProvider.saveToken(token)\n\n // Show token if configured\n if (state.showGitHubToken) {\n consola.info(\"GitHub token:\", token)\n }\n\n return {\n token,\n source: \"device-auth\",\n refreshable: true,\n }\n } catch (error) {\n consola.error(\"Device authorization failed:\", error)\n return null\n }\n }\n\n /**\n * Refresh by running the device auth flow again.\n */\n async refresh(): Promise<TokenInfo | null> {\n return this.getToken()\n }\n}\n","import type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Environment variable names to check for GitHub token.\n * Checked in order - first found wins.\n */\nconst ENV_VARS = [\n \"COPILOT_API_GITHUB_TOKEN\", // Our dedicated variable\n \"GH_TOKEN\", // GitHub CLI compatible\n \"GITHUB_TOKEN\", // Common convention\n]\n\n/**\n * Provider for tokens from environment variables.\n * Priority 2 - checked after CLI but before file storage.\n */\nexport class EnvTokenProvider extends GitHubTokenProvider {\n readonly name = \"Environment\"\n readonly priority = 2\n readonly refreshable = false\n\n /** The env var name where the token was found */\n private foundEnvVar: string | undefined\n\n isAvailable(): boolean {\n return this.findEnvVar() !== undefined\n }\n\n getToken(): Promise<TokenInfo | null> {\n const envVar = this.findEnvVar()\n if (!envVar) {\n return Promise.resolve(null)\n }\n\n const token = process.env[envVar]\n if (!token) {\n return Promise.resolve(null)\n }\n\n this.foundEnvVar = envVar\n\n return Promise.resolve({\n token: token.trim(),\n source: \"env\",\n refreshable: false,\n })\n }\n\n /**\n * Find the first environment variable that contains a token.\n */\n private findEnvVar(): string | undefined {\n for (const envVar of ENV_VARS) {\n const value = process.env[envVar]\n if (value && value.trim()) {\n return envVar\n }\n }\n return undefined\n }\n\n /**\n * Get the name of the environment variable that provided the token.\n */\n getFoundEnvVar(): string | undefined {\n return this.foundEnvVar\n }\n}\n","import consola from \"consola\"\n\nimport type { GitHubTokenProvider } from \"./providers/base\"\nimport type { TokenInfo, TokenValidationResult } from \"./types\"\n\nimport { CLITokenProvider } from \"./providers/cli\"\nimport { DeviceAuthProvider } from \"./providers/device-auth\"\nimport { EnvTokenProvider } from \"./providers/env\"\nimport { FileTokenProvider } from \"./providers/file\"\n\nexport interface GitHubTokenManagerOptions {\n /** Token provided via CLI --github-token argument */\n cliToken?: string\n /** Whether to validate tokens before use */\n validateOnInit?: boolean\n /** Callback when token expires and cannot be refreshed */\n onTokenExpired?: () => void\n}\n\n/**\n * Manages GitHub token acquisition from multiple providers.\n * Providers are tried in priority order until one succeeds.\n */\nexport class GitHubTokenManager {\n private providers: Array<GitHubTokenProvider> = []\n private currentToken: TokenInfo | null = null\n private onTokenExpired?: () => void\n private validateOnInit: boolean\n\n constructor(options: GitHubTokenManagerOptions = {}) {\n this.validateOnInit = options.validateOnInit ?? false\n this.onTokenExpired = options.onTokenExpired\n\n // Initialize providers in priority order\n // Note: GhCliTokenProvider is NOT included because GitHub CLI tokens\n // are obtained via a different OAuth app and cannot access Copilot internal APIs.\n this.providers = [\n new CLITokenProvider(options.cliToken),\n new EnvTokenProvider(),\n new FileTokenProvider(),\n new DeviceAuthProvider(),\n ]\n\n // Sort by priority (lower = higher priority)\n this.providers.sort((a, b) => a.priority - b.priority)\n }\n\n /**\n * Get the current token info (without fetching a new one).\n */\n getCurrentToken(): TokenInfo | null {\n return this.currentToken\n }\n\n /**\n * Get a GitHub token, trying providers in priority order.\n * Caches the result for subsequent calls.\n */\n async getToken(): Promise<TokenInfo> {\n // Return cached token if available\n if (this.currentToken) {\n return this.currentToken\n }\n\n for (const provider of this.providers) {\n if (!(await provider.isAvailable())) {\n continue\n }\n\n consola.debug(`Trying ${provider.name} token provider...`)\n\n const tokenInfo = await provider.getToken()\n if (!tokenInfo) {\n continue\n }\n\n // Optionally validate the token\n if (this.validateOnInit) {\n const validation = await this.validateToken(tokenInfo.token, provider)\n if (!validation.valid) {\n consola.warn(`Token from ${provider.name} provider is invalid: ${validation.error}`)\n continue\n }\n consola.info(`Logged in as ${validation.username}`)\n }\n\n consola.debug(`Using token from ${provider.name} provider`)\n this.currentToken = tokenInfo\n return tokenInfo\n }\n\n throw new Error(\"No valid GitHub token available from any provider\")\n }\n\n /**\n * Validate a token using a provider's validate method.\n */\n async validateToken(token: string, provider?: GitHubTokenProvider): Promise<TokenValidationResult> {\n const p = provider ?? this.providers[0]\n return p.validate(token)\n }\n\n /**\n * Force refresh the current token.\n * Only works if the current token source supports refresh.\n * For non-refreshable sources (CLI, env), this will call onTokenExpired.\n */\n async refresh(): Promise<TokenInfo | null> {\n if (!this.currentToken) {\n // No current token, get a new one\n return this.getToken()\n }\n\n // Check if current token source is refreshable\n if (!this.currentToken.refreshable) {\n consola.warn(`Current token from ${this.currentToken.source} cannot be refreshed`)\n this.onTokenExpired?.()\n return null\n }\n\n // Find the device auth provider for refresh\n const deviceAuthProvider = this.providers.find((p) => p instanceof DeviceAuthProvider)\n if (!deviceAuthProvider) {\n consola.warn(\"[GitHubToken] No provider supports token refresh, triggering re-authentication\")\n this.onTokenExpired?.()\n return null\n }\n\n const newToken = await deviceAuthProvider.refresh()\n if (newToken) {\n this.currentToken = newToken\n return newToken\n }\n\n consola.error(\"[GitHubToken] Failed to refresh token via DeviceAuthProvider\")\n this.onTokenExpired?.()\n return null\n }\n\n /**\n * Clear the current token cache.\n * Does not delete persisted tokens.\n */\n clearCache(): void {\n this.currentToken = null\n }\n\n /**\n * Clear all tokens (including persisted ones).\n */\n async clearAll(): Promise<void> {\n this.currentToken = null\n\n // Clear file-based token\n const fileProvider = this.providers.find((p) => p instanceof FileTokenProvider)\n if (fileProvider) {\n await fileProvider.clearToken()\n }\n }\n\n /**\n * Get all available providers for debugging.\n */\n async getProviders(): Promise<\n Array<{\n name: string\n priority: number\n available: boolean\n }>\n > {\n return Promise.all(\n this.providers.map(async (p) => ({\n name: p.name,\n priority: p.priority,\n available: await p.isAvailable(),\n })),\n )\n }\n}\n","export { CopilotTokenManager, type CopilotTokenManagerOptions } from \"./copilot-token-manager\"\n\n// Managers\nexport { GitHubTokenManager, type GitHubTokenManagerOptions } from \"./github-token-manager\"\n// Providers\nexport { GitHubTokenProvider } from \"./providers/base\"\nexport { CLITokenProvider } from \"./providers/cli\"\nexport { DeviceAuthProvider } from \"./providers/device-auth\"\nexport { EnvTokenProvider } from \"./providers/env\"\nexport { FileTokenProvider } from \"./providers/file\"\n// Types\nexport type { CopilotTokenInfo, TokenInfo, TokenSource, TokenValidationResult } from \"./types\"\n\nimport consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getGitHubUser } from \"~/lib/token/github-client\"\n\nimport { CopilotTokenManager } from \"./copilot-token-manager\"\nimport { GitHubTokenManager } from \"./github-token-manager\"\n\n/** Global manager instances */\nlet githubTokenManager: GitHubTokenManager | null = null\nlet copilotTokenManager: CopilotTokenManager | null = null\n\nexport interface InitTokenManagersOptions {\n /** Token provided via CLI --github-token argument */\n cliToken?: string\n}\n\n/**\n * Initialize the token management system.\n * This sets up both GitHub and Copilot token managers.\n */\nexport async function initTokenManagers(options: InitTokenManagersOptions = {}): Promise<{\n githubTokenManager: GitHubTokenManager\n copilotTokenManager: CopilotTokenManager\n}> {\n // Create GitHub token manager\n githubTokenManager = new GitHubTokenManager({\n cliToken: options.cliToken,\n validateOnInit: false, // We'll validate manually to show login info\n onTokenExpired: () => {\n consola.error(\"GitHub token has expired. Please run `copilot-api auth` to re-authenticate.\")\n },\n })\n\n // Get GitHub token\n const tokenInfo = await githubTokenManager.getToken()\n state.githubToken = tokenInfo.token\n state.tokenInfo = tokenInfo\n\n // Log token source\n const isExplicitToken = tokenInfo.source === \"cli\" || tokenInfo.source === \"env\"\n switch (tokenInfo.source) {\n case \"cli\": {\n consola.info(\"Using provided GitHub token (from CLI)\")\n\n break\n }\n case \"env\": {\n consola.info(\"Using GitHub token from environment variable\")\n\n break\n }\n case \"file\": {\n // File is the default, no need to log\n\n break\n }\n // No default\n }\n\n // Show token if configured\n if (state.showGitHubToken) {\n consola.info(\"GitHub token:\", tokenInfo.token)\n }\n\n // Validate and show user info\n // If the token was explicitly provided (CLI or env), give a clear error and abort on failure\n try {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n } catch (error) {\n if (isExplicitToken) {\n const source = tokenInfo.source === \"cli\" ? \"--github-token\" : \"environment variable\"\n consola.error(\n `The GitHub token provided via ${source} is invalid or expired.`,\n error instanceof Error ? error.message : error,\n )\n process.exit(1)\n }\n throw error\n }\n\n // Create Copilot token manager\n copilotTokenManager = new CopilotTokenManager({\n githubTokenManager,\n })\n\n // Initialize Copilot token\n // If the token was explicitly provided and Copilot rejects it, abort with clear error\n try {\n const copilotTokenInfo = await copilotTokenManager.initialize()\n state.copilotTokenInfo = copilotTokenInfo\n } catch (error) {\n if (isExplicitToken) {\n const source = tokenInfo.source === \"cli\" ? \"--github-token\" : \"environment variable\"\n consola.error(\n `The GitHub token provided via ${source} does not have Copilot access.`,\n error instanceof Error ? error.message : error,\n )\n process.exit(1)\n }\n throw error\n }\n\n return { githubTokenManager, copilotTokenManager }\n}\n\n/**\n * Get the global GitHub token manager instance.\n */\nexport function getGitHubTokenManager(): GitHubTokenManager | null {\n return githubTokenManager\n}\n\n/**\n * Get the global Copilot token manager instance.\n */\nexport function getCopilotTokenManager(): CopilotTokenManager | null {\n return copilotTokenManager\n}\n\n/**\n * Stop all token refresh timers.\n * Call this during cleanup/shutdown.\n */\nexport function stopTokenRefresh(): void {\n copilotTokenManager?.stopAutoRefresh()\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/config/paths\"\nimport { state } from \"./lib/state\"\nimport { DeviceAuthProvider, FileTokenProvider } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showGitHubToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showGitHubToken = options.showGitHubToken\n\n await ensurePaths()\n\n // Use DeviceAuthProvider directly for force authentication\n const deviceAuthProvider = new DeviceAuthProvider()\n const tokenInfo = await deviceAuthProvider.getToken()\n\n if (!tokenInfo) {\n throw new Error(\"Failed to obtain GitHub token via device authorization\")\n }\n\n // Validate and show user info\n const validation = await deviceAuthProvider.validate(tokenInfo.token)\n if (validation.valid) {\n consola.info(`Logged in as ${validation.username}`)\n }\n\n // File provider will have already saved the token during device auth\n // But we can verify the file exists\n const fileProvider = new FileTokenProvider()\n if (await fileProvider.isAvailable()) {\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n }\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-github-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showGitHubToken: args[\"show-github-token\"],\n })\n },\n})\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { state } from \"./lib/state\"\nimport { GitHubTokenManager } from \"./lib/token\"\nimport { getCopilotUsage, type QuotaDetail } from \"./lib/token/copilot-client\"\nimport { getGitHubUser } from \"./lib/token/github-client\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n\n // Show logged in user\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed = premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\"Completions\", usage.quota_snapshots.completions)\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { rebuildModelIndex, state } from \"~/lib/state\"\n\n/** Fetch models from Copilot API and cache in global state */\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n rebuildModelIndex()\n}\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface VisionLimits {\n max_prompt_image_size?: number\n max_prompt_images?: number\n supported_media_types?: Array<string>\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_non_streaming_output_tokens?: number\n max_inputs?: number\n vision?: VisionLimits\n}\n\ninterface ModelSupports {\n [key: string]: boolean | number | undefined\n}\n\ninterface ModelCapabilities {\n family?: string\n limits?: ModelLimits\n object?: string\n supports?: ModelSupports\n tokenizer?: string\n type?: string\n}\n\nexport interface Model {\n billing?: {\n is_premium?: boolean\n multiplier?: number\n restricted_to?: Array<string>\n }\n capabilities?: ModelCapabilities\n id: string\n model_picker_category?: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n supported_endpoints?: Array<string>\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { ensurePaths, PATHS } from \"./lib/config/paths\"\nimport { getModels } from \"./lib/models/client\"\nimport { state } from \"./lib/state\"\nimport { GitHubTokenManager } from \"./lib/token\"\nimport { getCopilotToken, getCopilotUsage } from \"./lib/token/copilot-client\"\nimport { getGitHubUser } from \"./lib/token/github-client\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n account?: {\n user: unknown\n copilot: unknown\n }\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise<boolean> {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getAccountInfo(): Promise<{\n user: unknown\n copilot: unknown\n} | null> {\n try {\n await ensurePaths()\n\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n\n if (!state.githubToken) return null\n\n const [user, copilot] = await Promise.all([getGitHubUser(), getCopilotUsage()])\n\n return { user, copilot }\n } catch {\n return null\n }\n}\n\nasync function getDebugInfo(includeAccount: boolean): Promise<DebugInfo> {\n const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()])\n\n const info: DebugInfo = {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n\n if (includeAccount && tokenExists) {\n const account = await getAccountInfo()\n if (account) {\n info.account = account\n }\n }\n\n return info\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n let output = `copilot-api debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`\n\n if (info.account) {\n output += `\n\nAccount Info:\n${JSON.stringify(info.account, null, 2)}`\n }\n\n consola.info(output)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise<void> {\n const debugInfo = await getDebugInfo(true)\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\n/** Subcommand: debug info (default behavior) */\nconst debugInfo = defineCommand({\n meta: {\n name: \"info\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({ json: args.json })\n },\n})\n\n/** Subcommand: debug models */\nconst debugModels = defineCommand({\n meta: {\n name: \"models\",\n description: \"Fetch and display raw model data from Copilot API\",\n },\n args: {\n \"account-type\": {\n type: \"string\",\n alias: \"a\",\n default: \"individual\",\n description: \"The type of GitHub account (individual, business, enterprise)\",\n },\n \"github-token\": {\n type: \"string\",\n alias: \"g\",\n description: \"GitHub token to use (skips interactive auth)\",\n },\n },\n async run({ args }) {\n state.accountType = args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\"\n\n await ensurePaths()\n\n if (args[\"github-token\"]) {\n state.githubToken = args[\"github-token\"]\n consola.info(\"Using provided GitHub token\")\n } else {\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n }\n\n // Get Copilot token without setting up refresh interval\n const { token } = await getCopilotToken()\n state.copilotToken = token\n\n const models = await getModels()\n\n console.log(JSON.stringify(models, null, 2))\n },\n})\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Debug commands for troubleshooting\",\n },\n subCommands: {\n info: debugInfo,\n models: debugModels,\n },\n})\n","import consola from \"consola\"\n\n/**\n * Adaptive Rate Limiter\n *\n * Normal mode: Full speed, no delay between requests\n * Rate-limited mode: Queue requests and process with exponential backoff\n * Gradual recovery: After recovery, slowly ramp up speed before full speed\n *\n * Mode transitions:\n * - Normal → Rate-limited: When a 429 error is detected\n * - Rate-limited → Recovering: After recovery timeout OR consecutive successes\n * - Recovering → Normal: After gradual speedup completes\n *\n * Features:\n * - Exponential backoff: Retry delays double each time (10s → 20s → 40s...)\n * - Retry-After support: Uses server-provided wait time if available\n * - Gradual recovery: Slowly ramps up speed after leaving rate-limited mode\n */\n\nexport interface AdaptiveRateLimiterConfig {\n /** Base interval for retries, doubles with each retry (default: 10s) */\n baseRetryIntervalSeconds: number\n /** Maximum retry interval cap (default: 120s) */\n maxRetryIntervalSeconds: number\n /** Interval between requests in rate-limited mode (default: 10s) */\n requestIntervalSeconds: number\n /** Time after which to attempt recovery to normal mode (default: 10 minutes) */\n recoveryTimeoutMinutes: number\n /** Number of consecutive successes needed to recover (default: 5) */\n consecutiveSuccessesForRecovery: number\n /** Gradual recovery steps: intervals to use before full speed (default: [5, 2, 1, 0]) */\n gradualRecoverySteps: Array<number>\n}\n\nconst DEFAULT_CONFIG: AdaptiveRateLimiterConfig = {\n baseRetryIntervalSeconds: 10,\n maxRetryIntervalSeconds: 120,\n requestIntervalSeconds: 10,\n recoveryTimeoutMinutes: 10,\n consecutiveSuccessesForRecovery: 5,\n gradualRecoverySteps: [5, 2, 1, 0], // 5s → 2s → 1s → full speed\n}\n\ninterface QueuedRequest<T> {\n execute: () => Promise<T>\n resolve: (value: T) => void\n reject: (error: unknown) => void\n retryCount: number\n /** Server-provided retry delay from Retry-After header */\n retryAfterSeconds?: number\n /** Timestamp when request was enqueued */\n enqueuedAt: number\n}\n\n/** Result wrapper that includes queue wait time */\nexport interface RateLimitedResult<T> {\n result: T\n /** Time spent waiting in queue (ms), 0 if not queued */\n queueWaitMs: number\n}\n\ntype RateLimiterMode = \"normal\" | \"rate-limited\" | \"recovering\"\n\n/**\n * Adaptive rate limiter that switches between normal, rate-limited, and recovering modes\n * based on API responses.\n */\nexport class AdaptiveRateLimiter {\n private config: AdaptiveRateLimiterConfig\n private mode: RateLimiterMode = \"normal\"\n private queue: Array<QueuedRequest<unknown>> = []\n private processing = false\n private rateLimitedAt: number | null = null\n private consecutiveSuccesses = 0\n private lastRequestTime = 0\n /** Current step in gradual recovery (index into gradualRecoverySteps) */\n private recoveryStepIndex = 0\n /** Abort controller for cancelling pending sleeps during shutdown */\n private sleepAbortController = new AbortController()\n\n constructor(config: Partial<AdaptiveRateLimiterConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * Execute a request with adaptive rate limiting.\n * Returns a promise that resolves when the request succeeds.\n * The request will be retried automatically on 429 errors.\n */\n async execute<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n if (this.mode === \"normal\") {\n return this.executeInNormalMode(fn)\n }\n if (this.mode === \"recovering\") {\n return this.executeInRecoveringMode(fn)\n }\n return this.enqueue(fn)\n }\n\n /**\n * Check if an error is a rate limit error (429) and extract Retry-After if available\n */\n isRateLimitError(error: unknown): {\n isRateLimit: boolean\n retryAfter?: number\n } {\n if (error && typeof error === \"object\") {\n // Check HTTPError\n if (\"status\" in error && error.status === 429) {\n // Try to extract Retry-After from response headers or body\n const retryAfter = this.extractRetryAfter(error)\n return { isRateLimit: true, retryAfter }\n }\n // Check nested error structure from Copilot\n if (\"responseText\" in error && typeof error.responseText === \"string\") {\n try {\n const parsed: unknown = JSON.parse(error.responseText)\n if (\n parsed\n && typeof parsed === \"object\"\n && \"error\" in parsed\n && parsed.error\n && typeof parsed.error === \"object\"\n && \"code\" in parsed.error\n && parsed.error.code === \"rate_limited\"\n ) {\n return { isRateLimit: true }\n }\n } catch {\n // Not JSON, ignore\n }\n }\n }\n return { isRateLimit: false }\n }\n\n /**\n * Extract Retry-After value from error response\n */\n private extractRetryAfter(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined\n\n // Check responseText for JSON with retry_after field\n if (\"responseText\" in error && typeof error.responseText === \"string\") {\n try {\n const parsed: unknown = JSON.parse(error.responseText)\n if (parsed && typeof parsed === \"object\" && \"retry_after\" in parsed && typeof parsed.retry_after === \"number\") {\n return parsed.retry_after\n }\n // Also check nested error.retry_after\n if (\n parsed\n && typeof parsed === \"object\"\n && \"error\" in parsed\n && parsed.error\n && typeof parsed.error === \"object\"\n && \"retry_after\" in parsed.error\n && typeof parsed.error.retry_after === \"number\"\n ) {\n return parsed.error.retry_after\n }\n } catch {\n // Not JSON, ignore\n }\n }\n\n return undefined\n }\n\n /**\n * Execute in normal mode - full speed\n */\n private async executeInNormalMode<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n try {\n const result = await fn()\n return { result, queueWaitMs: 0 }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n this.enterRateLimitedMode()\n // Queue this request for retry instead of failing\n return this.enqueue(fn, retryAfter)\n }\n throw error\n }\n }\n\n /**\n * Execute in recovering mode - gradual speedup\n */\n private async executeInRecoveringMode<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n const startTime = Date.now()\n const currentInterval = this.config.gradualRecoverySteps[this.recoveryStepIndex] ?? 0\n\n // Wait for the current recovery interval\n if (currentInterval > 0) {\n const now = Date.now()\n const elapsedMs = now - this.lastRequestTime\n const requiredMs = currentInterval * 1000\n\n if (this.lastRequestTime > 0 && elapsedMs < requiredMs) {\n const waitMs = requiredMs - elapsedMs\n await this.sleep(waitMs)\n }\n }\n\n this.lastRequestTime = Date.now()\n\n try {\n const result = await fn()\n\n // Success - advance recovery step\n this.recoveryStepIndex++\n if (this.recoveryStepIndex >= this.config.gradualRecoverySteps.length) {\n this.completeRecovery()\n } else {\n const nextInterval = this.config.gradualRecoverySteps[this.recoveryStepIndex] ?? 0\n consola.info(\n `[RateLimiter] Ramp-up step ${this.recoveryStepIndex}/${this.config.gradualRecoverySteps.length} `\n + `(next interval: ${nextInterval}s)`,\n )\n }\n\n const queueWaitMs = Date.now() - startTime\n return { result, queueWaitMs }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n // Back to rate-limited mode\n consola.warn(\"[RateLimiter] Hit rate limit during ramp-up, returning to rate-limited mode\")\n this.enterRateLimitedMode()\n return this.enqueue(fn, retryAfter)\n }\n throw error\n }\n }\n\n /**\n * Enter rate-limited mode\n */\n private enterRateLimitedMode(): void {\n if (this.mode === \"rate-limited\") return\n\n this.mode = \"rate-limited\"\n this.rateLimitedAt = Date.now()\n this.consecutiveSuccesses = 0\n\n consola.warn(\n `[RateLimiter] Entering rate-limited mode. `\n + `Requests will be queued with exponential backoff (base: ${this.config.baseRetryIntervalSeconds}s).`,\n )\n }\n\n /**\n * Check if we should try to recover to normal mode\n */\n private shouldAttemptRecovery(): boolean {\n // Check consecutive successes\n if (this.consecutiveSuccesses >= this.config.consecutiveSuccessesForRecovery) {\n consola.info(`[RateLimiter] ${this.consecutiveSuccesses} consecutive successes. Starting ramp-up.`)\n return true\n }\n\n // Check timeout\n if (this.rateLimitedAt) {\n const elapsed = Date.now() - this.rateLimitedAt\n const timeout = this.config.recoveryTimeoutMinutes * 60 * 1000\n if (elapsed >= timeout) {\n consola.info(`[RateLimiter] ${this.config.recoveryTimeoutMinutes} minutes elapsed. Starting ramp-up.`)\n return true\n }\n }\n\n return false\n }\n\n /**\n * Start gradual recovery mode\n */\n private startGradualRecovery(): void {\n this.mode = \"recovering\"\n this.recoveryStepIndex = 0\n this.rateLimitedAt = null\n this.consecutiveSuccesses = 0\n\n const firstInterval = this.config.gradualRecoverySteps[0] ?? 0\n consola.info(\n `[RateLimiter] Starting ramp-up (${this.config.gradualRecoverySteps.length} steps, `\n + `first interval: ${firstInterval}s)`,\n )\n }\n\n /**\n * Complete recovery to normal mode\n */\n private completeRecovery(): void {\n this.mode = \"normal\"\n this.recoveryStepIndex = 0\n\n consola.success(\"[RateLimiter] Exiting rate-limited mode.\")\n }\n\n /**\n * Enqueue a request for later execution\n */\n private enqueue<T>(fn: () => Promise<T>, retryAfterSeconds?: number): Promise<RateLimitedResult<T>> {\n return new Promise<RateLimitedResult<T>>((resolve, reject) => {\n const request: QueuedRequest<unknown> = {\n execute: fn as () => Promise<unknown>,\n resolve: resolve as (value: unknown) => void,\n reject,\n retryCount: 0,\n retryAfterSeconds,\n enqueuedAt: Date.now(),\n }\n\n this.queue.push(request)\n\n if (this.queue.length > 1) {\n const position = this.queue.length\n const estimatedWait = (position - 1) * this.config.requestIntervalSeconds\n consola.info(`[RateLimiter] Request queued (position ${position}, ~${estimatedWait}s wait)`)\n }\n\n void this.processQueue()\n })\n }\n\n /**\n * Calculate retry interval with exponential backoff\n */\n private calculateRetryInterval(request: QueuedRequest<unknown>): number {\n // Use server-provided Retry-After if available\n if (request.retryAfterSeconds !== undefined && request.retryAfterSeconds > 0) {\n return request.retryAfterSeconds\n }\n\n // Exponential backoff: base * 2^(retryCount-1), capped at max\n const backoff = this.config.baseRetryIntervalSeconds * Math.pow(2, request.retryCount)\n return Math.min(backoff, this.config.maxRetryIntervalSeconds)\n }\n\n /**\n * Process the queue\n */\n private async processQueue(): Promise<void> {\n if (this.processing) return\n this.processing = true\n\n while (this.queue.length > 0) {\n const request = this.queue[0]\n\n // Check if we should try recovery before processing\n if (this.shouldAttemptRecovery()) {\n this.startGradualRecovery()\n // Continue processing remaining queue items in recovering mode\n // But first, let the current queue drain\n }\n\n // Calculate wait time based on whether this is a retry or new request\n const now = Date.now()\n const elapsedMs = now - this.lastRequestTime\n const intervalSeconds =\n request.retryCount > 0 ? this.calculateRetryInterval(request) : this.config.requestIntervalSeconds\n const requiredMs = intervalSeconds * 1000\n\n if (this.lastRequestTime > 0 && elapsedMs < requiredMs) {\n const waitMs = requiredMs - elapsedMs\n const waitSec = Math.ceil(waitMs / 1000)\n consola.info(`[RateLimiter] Waiting ${waitSec}s before next request...`)\n await this.sleep(waitMs)\n }\n\n this.lastRequestTime = Date.now()\n\n try {\n const result = await request.execute()\n\n // Success!\n this.queue.shift()\n this.consecutiveSuccesses++\n // Clear retry-after on success\n request.retryAfterSeconds = undefined\n // Calculate queue wait time\n const queueWaitMs = Date.now() - request.enqueuedAt\n request.resolve({ result, queueWaitMs })\n\n if (this.mode === \"rate-limited\") {\n consola.info(\n `[RateLimiter] Request succeeded (${this.consecutiveSuccesses}/${this.config.consecutiveSuccessesForRecovery} for ramp-up)`,\n )\n }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n // Still rate limited, retry with exponential backoff\n request.retryCount++\n request.retryAfterSeconds = retryAfter\n this.consecutiveSuccesses = 0\n this.rateLimitedAt = Date.now() // Reset timeout\n\n const nextInterval = this.calculateRetryInterval(request)\n const source = retryAfter ? \"server Retry-After\" : \"exponential backoff\"\n consola.warn(\n `[RateLimiter] Request failed with 429 (retry #${request.retryCount}). `\n + `Retrying in ${nextInterval}s (${source})...`,\n )\n } else {\n // Other error, fail this request and continue with queue\n this.queue.shift()\n request.reject(error)\n }\n }\n }\n\n this.processing = false\n\n // If queue is empty and we're in rate-limited mode, stay in that mode\n // until recovery conditions are met on next request\n }\n\n /**\n * Reject all queued requests immediately.\n * Called during shutdown Phase 1 to drain the queue so queued requests\n * don't waste drain time. Returns the number of rejected requests.\n */\n rejectQueued(): number {\n const count = this.queue.length\n while (this.queue.length > 0) {\n const request = this.queue.shift()\n if (!request) break\n request.reject(new Error(\"Server shutting down\"))\n }\n this.processing = false\n\n // Cancel any pending sleep (processQueue or executeInRecoveringMode)\n this.sleepAbortController.abort()\n this.sleepAbortController = new AbortController()\n\n return count\n }\n\n private sleep(ms: number): Promise<void> {\n const signal = this.sleepAbortController.signal\n if (signal.aborted) return Promise.resolve()\n\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms)\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer)\n resolve()\n },\n { once: true },\n )\n })\n }\n\n /**\n * Get current status for debugging/monitoring\n */\n getStatus(): {\n mode: RateLimiterMode\n queueLength: number\n consecutiveSuccesses: number\n rateLimitedAt: number | null\n } {\n return {\n mode: this.mode,\n queueLength: this.queue.length,\n consecutiveSuccesses: this.consecutiveSuccesses,\n rateLimitedAt: this.rateLimitedAt,\n }\n }\n}\n\n/** Singleton instance */\nlet rateLimiterInstance: AdaptiveRateLimiter | null = null\n\n/**\n * Initialize the adaptive rate limiter with configuration\n */\nexport function initAdaptiveRateLimiter(config: Partial<AdaptiveRateLimiterConfig> = {}): void {\n rateLimiterInstance = new AdaptiveRateLimiter(config)\n\n const baseRetry = config.baseRetryIntervalSeconds ?? DEFAULT_CONFIG.baseRetryIntervalSeconds\n const maxRetry = config.maxRetryIntervalSeconds ?? DEFAULT_CONFIG.maxRetryIntervalSeconds\n const interval = config.requestIntervalSeconds ?? DEFAULT_CONFIG.requestIntervalSeconds\n const recovery = config.recoveryTimeoutMinutes ?? DEFAULT_CONFIG.recoveryTimeoutMinutes\n const successes = config.consecutiveSuccessesForRecovery ?? DEFAULT_CONFIG.consecutiveSuccessesForRecovery\n const steps = config.gradualRecoverySteps ?? DEFAULT_CONFIG.gradualRecoverySteps\n\n consola.info(\n `[RateLimiter] Initialized (backoff: ${baseRetry}s-${maxRetry}s, `\n + `interval: ${interval}s, recovery: ${recovery}min or ${successes} successes, `\n + `gradual: [${steps.join(\"s, \")}s])`,\n )\n}\n\n/**\n * Get the rate limiter instance\n */\nexport function getAdaptiveRateLimiter(): AdaptiveRateLimiter | null {\n return rateLimiterInstance\n}\n\n/**\n * Reset the rate limiter singleton (for testing only).\n */\nexport function resetAdaptiveRateLimiter(): void {\n rateLimiterInstance = null\n}\n\n/**\n * Execute a request with adaptive rate limiting.\n * If rate limiter is not initialized, executes immediately.\n * Returns the result along with queue wait time.\n */\nexport async function executeWithAdaptiveRateLimit<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n if (!rateLimiterInstance) {\n const result = await fn()\n return { result, queueWaitMs: 0 }\n }\n return rateLimiterInstance.execute(fn)\n}\n","/**\n * Unified model name resolution and normalization.\n *\n * Handles short aliases (opus/sonnet/haiku), versioned names with date suffixes,\n * hyphenated versions (claude-opus-4-6 → claude-opus-4.6), model overrides,\n * and family-level fallbacks.\n */\n\nimport { DEFAULT_MODEL_OVERRIDES, state } from \"~/lib/state\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ModelFamily = \"opus\" | \"sonnet\" | \"haiku\"\n\n// ============================================================================\n// Model Preference Lists\n// ============================================================================\n\n/** Preferred model order per family, highest priority first. */\nexport const MODEL_PREFERENCE: Record<ModelFamily, Array<string>> = {\n opus: [\n \"claude-opus-4.6\",\n \"claude-opus-4.5\",\n \"claude-opus-41\", // 4.1\n // \"claude-opus-4\",\n ],\n sonnet: [\n \"claude-sonnet-4.6\",\n \"claude-sonnet-4.5\",\n \"claude-sonnet-4\",\n // \"claude-sonnet-3.5\",\n ],\n haiku: [\n \"claude-haiku-4.5\",\n // \"claude-haiku-3.5\",\n ],\n}\n\n// ============================================================================\n// Normalization and Detection\n// ============================================================================\n\n/** Pre-compiled regex: claude-{family}-{major}-{minor}[-YYYYMMDD] */\nconst VERSIONED_RE = /^(claude-(?:opus|sonnet|haiku))-(\\d+)-(\\d{1,2})(?:-\\d{8,})?$/\n\n/** Pre-compiled regex: claude-{family}-{major}-YYYYMMDD (date-only suffix) */\nconst DATE_ONLY_RE = /^(claude-(opus|sonnet|haiku)-\\d+)-\\d{8,}$/\n\n/**\n * Normalize model ID for matching: lowercase and replace dots with dashes.\n * e.g. \"claude-sonnet-4.5\" → \"claude-sonnet-4-5\"\n *\n * Used for feature detection (startsWith matching), NOT for API calls.\n */\nexport function normalizeForMatching(modelId: string): string {\n return modelId.toLowerCase().replaceAll(\".\", \"-\")\n}\n\n/**\n * Normalize a model ID to canonical dot-version form.\n * e.g. \"claude-opus-4-6\" → \"claude-opus-4.6\", \"claude-opus-4-6-1m\" → \"claude-opus-4.6-1m\"\n *\n * Handles modifier suffixes (-fast, -1m) and strips date suffixes (-YYYYMMDD).\n * Non-Claude models or unrecognized patterns are returned as-is.\n *\n * Used for normalizing API response model names to match `/models` endpoint IDs.\n */\nexport function normalizeModelId(modelId: string): string {\n const { base, suffix } = extractModifierSuffix(modelId)\n const versionedMatch = base.match(VERSIONED_RE)\n if (versionedMatch) {\n return `${versionedMatch[1]}-${versionedMatch[2]}.${versionedMatch[3]}${suffix}`\n }\n return modelId\n}\n\n/** Extract the model family from a model ID. */\nexport function getModelFamily(modelId: string): ModelFamily | undefined {\n const normalized = normalizeForMatching(modelId)\n if (normalized.includes(\"opus\")) return \"opus\"\n if (normalized.includes(\"sonnet\")) return \"sonnet\"\n if (normalized.includes(\"haiku\")) return \"haiku\"\n return undefined\n}\n\n/** Check if a model ID belongs to the Sonnet family. */\nexport function isSonnetModel(modelId: string): boolean {\n return getModelFamily(modelId) === \"sonnet\"\n}\n\n/** Check if a model ID belongs to the Opus family. */\nexport function isOpusModel(modelId: string): boolean {\n return getModelFamily(modelId) === \"opus\"\n}\n\n// ============================================================================\n// Model Resolution\n// ============================================================================\n\n/**\n * Find the best available model for a family by checking the preference list\n * against actually available models. Returns the first match, or the top\n * preference as fallback when state.models is unavailable.\n */\nexport function findPreferredModel(family: string): string {\n const preference = MODEL_PREFERENCE[family as ModelFamily]\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for arbitrary family strings\n if (!preference) return family\n\n if (state.modelIds.size === 0) {\n return preference[0]\n }\n\n for (const candidate of preference) {\n if (state.modelIds.has(candidate)) {\n return candidate\n }\n }\n\n return preference[0]\n}\n\n/** Known model modifier suffixes (e.g., \"-fast\" for fast output mode, \"-1m\" for 1M context). */\nconst KNOWN_MODIFIERS = [\"-fast\", \"-1m\"]\n\n/**\n * Extract known modifier suffix from a model name.\n * e.g. \"claude-opus-4-6-fast\" → { base: \"claude-opus-4-6\", suffix: \"-fast\" }\n */\nfunction extractModifierSuffix(model: string): { base: string; suffix: string } {\n const lower = model.toLowerCase()\n for (const modifier of KNOWN_MODIFIERS) {\n if (lower.endsWith(modifier)) {\n return { base: model.slice(0, -modifier.length), suffix: modifier }\n }\n }\n return { base: model, suffix: \"\" }\n}\n\n/**\n * Normalize bracket notation to hyphen suffix.\n * Claude Code CLI sends model keys like \"opus[1m]\" or \"claude-opus-4.6[1m]\".\n * This converts them to the standard hyphen form: \"opus-1m\", \"claude-opus-4.6-1m\".\n */\nfunction normalizeBracketNotation(model: string): string {\n const match = model.match(/^(.+)\\[([^\\]]+)\\]$/)\n if (!match) return model\n return `${match[1]}-${match[2].toLowerCase()}`\n}\n\n/**\n * Resolve a model name to its canonical form, then apply overrides.\n *\n * Override matching order:\n * 1. Check the raw (original) model name against state.modelOverrides\n * 2. Resolve via alias/normalization (resolveModelNameCore)\n * 3. If resolved name differs from raw, check resolved name against overrides\n * 4. Check if the model's family (opus/sonnet/haiku) has an override\n *\n * This is the main entry point for route handlers.\n */\nexport function resolveModelName(model: string): string {\n // 0. Normalize bracket notation: \"opus[1m]\" → \"opus-1m\"\n model = normalizeBracketNotation(model)\n\n // 1. Check raw model name against overrides first\n const rawOverride = state.modelOverrides[model]\n if (rawOverride) {\n return resolveOverrideTarget(model, rawOverride)\n }\n\n // 2. Normal alias/normalization resolution\n const resolved = resolveModelNameCore(model)\n\n // 3. If resolved name is different, check it against overrides too\n if (resolved !== model) {\n const resolvedOverride = state.modelOverrides[resolved]\n if (resolvedOverride) {\n return resolveOverrideTarget(resolved, resolvedOverride)\n }\n }\n\n // 4. Check if the model's family has a user-customized override\n // Last-resort fallback: only applies when steps 1-3 didn't match.\n // Propagates to ALL family members regardless of target family.\n // e.g., opus → claude-opus-4.6-1m: claude-opus-4-6 also redirects\n // e.g., sonnet → opus: claude-sonnet-4 also redirects (cross-family)\n // Only skipped when override equals the built-in default (pure alias, not redirection).\n const family = getModelFamily(resolved)\n if (family) {\n const familyOverride = state.modelOverrides[family]\n if (familyOverride && familyOverride !== DEFAULT_MODEL_OVERRIDES[family]) {\n const familyResolved = resolveOverrideTarget(family, familyOverride)\n if (familyResolved !== resolved) {\n return familyResolved\n }\n }\n }\n\n return resolved\n}\n\n/**\n * Resolve override target: if target is directly available, use it;\n * otherwise check for chained overrides, then treat as alias.\n * If still unavailable, fall back to the best available model in the same family.\n *\n * Uses `seen` set to prevent circular override chains.\n */\nfunction resolveOverrideTarget(source: string, target: string, seen?: Set<string>): string {\n if (state.modelIds.size === 0 || state.modelIds.has(target)) {\n return target\n }\n\n // Check if target itself has an override (chained overrides: sonnet → opus → claude-opus-4.6-1m)\n const visited = seen ?? new Set([source])\n const targetOverride = state.modelOverrides[target]\n if (targetOverride && !visited.has(target)) {\n visited.add(target)\n return resolveOverrideTarget(target, targetOverride, visited)\n }\n\n // Target not directly available — might be an alias, resolve it\n const resolved = resolveModelNameCore(target)\n if (resolved !== target) {\n return resolved\n }\n\n // Still not resolved — check if target belongs to a known family and find best available\n const family = getModelFamily(target)\n if (family) {\n const preferred = findPreferredModel(family)\n if (preferred !== target) {\n return preferred\n }\n }\n\n // Can't resolve further — use target as-is\n return target\n}\n\n/**\n * Core model name resolution (without overrides).\n *\n * Handles:\n * 1. Modifier suffixes: \"claude-opus-4-6-fast\" → \"claude-opus-4.6-fast\"\n * 2. Short aliases: \"opus\" → best available opus\n * 3. Hyphenated versions: \"claude-opus-4-6\" → \"claude-opus-4.6\"\n * 4. Date suffixes: \"claude-opus-4-20250514\" → best opus\n */\nfunction resolveModelNameCore(model: string): string {\n // Extract modifier suffix (e.g., \"-fast\") before resolution\n const { base, suffix } = extractModifierSuffix(model)\n\n // Resolve the base model name\n const resolvedBase = resolveBase(base)\n\n // Re-attach suffix and validate availability\n if (suffix) {\n const withSuffix = resolvedBase + suffix\n if (state.modelIds.size === 0 || state.modelIds.has(withSuffix)) {\n return withSuffix\n }\n // Suffixed variant not available, fall back to base\n return resolvedBase\n }\n\n return resolvedBase\n}\n\n/** Resolve a base model name (without modifier suffix) to its canonical form. */\nfunction resolveBase(model: string): string {\n // 1. Short alias: \"opus\" → best opus\n if (model in MODEL_PREFERENCE) {\n return findPreferredModel(model)\n }\n\n // 2. Hyphenated: claude-opus-4-6 or claude-opus-4-6-20250514 → claude-opus-4.6\n // Pattern: claude-{family}-{major}-{minor}[-YYYYMMDD]\n // Minor version is 1-2 digits; date suffix is 8+ digits\n const versionedMatch = model.match(VERSIONED_RE)\n if (versionedMatch) {\n const dotModel = `${versionedMatch[1]}-${versionedMatch[2]}.${versionedMatch[3]}`\n if (state.modelIds.size === 0 || state.modelIds.has(dotModel)) {\n return dotModel\n }\n }\n\n // 3. Date-only suffix: claude-{family}-{major}-YYYYMMDD → base model or best family\n const dateOnlyMatch = model.match(DATE_ONLY_RE)\n if (dateOnlyMatch) {\n const baseModel = dateOnlyMatch[1]\n const family = dateOnlyMatch[2]\n if (state.modelIds.has(baseModel)) {\n return baseModel\n }\n return findPreferredModel(family)\n }\n\n return model\n}\n","/**\n * RequestContext — Complete active representation of a request\n *\n * Holds all data from request entry to completion. Independent of the history\n * system — history is a consumer of RequestContext through events.\n * Each retry creates a new Attempt in the attempts array.\n */\n\nimport type { ApiError } from \"~/lib/error\"\nimport type {\n EndpointType,\n PreprocessInfo,\n RewriteInfo,\n SanitizationInfo,\n SseEventRecord,\n} from \"~/lib/history/store\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { getErrorMessage } from \"~/lib/error\"\nimport { normalizeModelId } from \"~/lib/models/resolver\"\n\n// ─── Request State Machine ───\n\nexport type RequestState =\n | \"pending\" // Just created, not yet started\n | \"sanitizing\" // Sanitizing messages\n | \"executing\" // Executing API call\n | \"retrying\" // Retrying (429 wait or 413 truncation)\n | \"streaming\" // Streaming response in progress\n | \"completed\" // Successfully completed\n | \"failed\" // Failed\n\n// ─── Three-Part Data Model ───\n\n/** 1. Original request: client's raw payload (one per request, immutable) */\nexport interface OriginalRequest {\n model: string\n messages: Array<unknown>\n stream: boolean\n tools?: Array<unknown>\n system?: unknown\n payload: unknown\n}\n\n/** 2. Effective request: what's sent to upstream API (per attempt, may differ) */\nexport interface EffectiveRequest {\n model: string\n resolvedModel: Model | undefined\n messages: Array<unknown>\n payload: unknown\n format: \"anthropic\" | \"openai\"\n}\n\n/** 3. Response data: upstream API response (per attempt) */\nexport interface ResponseData {\n success: boolean\n model: string\n usage: {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n }\n content: unknown\n stop_reason?: string\n toolCalls?: Array<unknown>\n error?: string\n /** HTTP status code from upstream (only on error) */\n status?: number\n /** Raw response body from upstream (only on error, for post-mortem debugging) */\n responseText?: string\n}\n\n// ─── Attempt ───\n\n/** A single API call attempt (each retry produces a new Attempt) */\nexport interface Attempt {\n index: number\n effectiveRequest: EffectiveRequest | null\n response: ResponseData | null\n error: ApiError | null\n /** Strategy that triggered this retry (undefined for first attempt) */\n strategy?: string\n sanitization?: SanitizationState\n truncation?: TruncationState\n /** Wait time before this retry (rate-limit) */\n waitMs?: number\n startTime: number\n durationMs: number\n}\n\n// ─── Pipeline Processing State ───\n\nexport interface SanitizationState {\n removedCount: number\n systemReminderRemovals: number\n orphanedToolUseCount?: number\n orphanedToolResultCount?: number\n fixedNameCount?: number\n emptyTextBlocksRemoved?: number\n}\n\nexport interface TruncationState {\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n processingTimeMs: number\n}\n\n// ─── History Entry Data ───\n\n/** Serialized form of a completed request (decoupled from history store) */\nexport interface HistoryEntryData {\n id: string\n endpoint: EndpointType\n timestamp: number\n durationMs: number\n sessionId?: string\n request: {\n model?: string\n messages?: Array<unknown>\n stream?: boolean\n tools?: Array<unknown>\n system?: unknown\n max_tokens?: number\n temperature?: number\n }\n response?: ResponseData\n truncation?: TruncationState\n rewrites?: RewriteInfo\n sseEvents?: Array<SseEventRecord>\n attempts?: Array<{\n index: number\n strategy?: string\n durationMs: number\n error?: string\n truncation?: TruncationState\n }>\n}\n\n// ─── Stream Accumulator Result ───\n\n/** Data extracted from a stream accumulator for completeFromStream */\nexport interface StreamAccumulatorResult {\n model: string\n content: string\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreationTokens: number\n stopReason: string\n contentBlocks: Array<{\n type: string\n text?: string\n thinking?: string\n id?: string\n name?: string\n input?: unknown\n tool_use_id?: string\n content?: unknown\n }>\n}\n\n// ─── RequestContext Event ───\n\nexport interface RequestContextEventData {\n type: string\n context: RequestContext\n previousState?: RequestState\n field?: string\n meta?: Record<string, unknown>\n entry?: HistoryEntryData\n}\n\nexport type RequestContextEventCallback = (event: RequestContextEventData) => void\n\n// ─── RequestContext Interface ───\n\nexport interface RequestContext {\n // --- Identity + State ---\n readonly id: string\n readonly tuiLogId: string | undefined\n readonly startTime: number\n readonly endpoint: EndpointType\n readonly state: RequestState\n readonly durationMs: number\n /** Whether this context has been settled (completed or failed). Handler code can check this to detect reaper force-fail. */\n readonly settled: boolean\n\n // --- Top-level Data ---\n readonly originalRequest: OriginalRequest | null\n readonly response: ResponseData | null\n readonly rewrites: RewriteInfo | null\n readonly preprocessInfo: PreprocessInfo | null\n\n // --- Attempts ---\n readonly attempts: ReadonlyArray<Attempt>\n readonly currentAttempt: Attempt | null\n readonly queueWaitMs: number\n\n // --- Mutation Methods ---\n setOriginalRequest(req: OriginalRequest): void\n setPreprocessInfo(info: PreprocessInfo): void\n addSanitizationInfo(info: SanitizationInfo): void\n setRewrites(info: RewriteInfo): void\n setSseEvents(events: Array<SseEventRecord>): void\n beginAttempt(opts: { strategy?: string; waitMs?: number; truncation?: TruncationState }): void\n setAttemptSanitization(info: SanitizationState): void\n setAttemptEffectiveRequest(req: EffectiveRequest): void\n setAttemptResponse(response: ResponseData): void\n setAttemptError(error: ApiError): void\n addQueueWaitMs(ms: number): void\n transition(newState: RequestState, meta?: Record<string, unknown>): void\n complete(response: ResponseData): void\n completeFromStream(acc: StreamAccumulatorResult): void\n fail(model: string, error: unknown): void\n toHistoryEntry(): HistoryEntryData\n}\n\n// ─── Implementation ───\n\nlet idCounter = 0\n\nexport function createRequestContext(opts: {\n endpoint: EndpointType\n tuiLogId?: string\n onEvent: RequestContextEventCallback\n}): RequestContext {\n const id = `req_${Date.now()}_${++idCounter}`\n const startTime = Date.now()\n const onEvent = opts.onEvent\n\n // Mutable internal state\n let _state: RequestState = \"pending\"\n let _originalRequest: OriginalRequest | null = null\n let _response: ResponseData | null = null\n let _rewrites: RewriteInfo | null = null\n let _preprocessInfo: PreprocessInfo | null = null\n let _sseEvents: Array<SseEventRecord> | null = null\n const _sanitizationHistory: Array<SanitizationInfo> = []\n let _queueWaitMs = 0\n const _attempts: Array<Attempt> = []\n /** Guard: once complete() or fail() is called, subsequent calls are no-ops */\n let settled = false\n\n function emit(event: RequestContextEventData) {\n try {\n onEvent(event)\n } catch {\n // Swallow event handler errors\n }\n }\n\n const ctx: RequestContext = {\n id,\n tuiLogId: opts.tuiLogId,\n startTime,\n endpoint: opts.endpoint,\n\n get state() {\n return _state\n },\n get durationMs() {\n return Date.now() - startTime\n },\n get settled() {\n return settled\n },\n get originalRequest() {\n return _originalRequest\n },\n get response() {\n return _response\n },\n get rewrites() {\n return _rewrites\n },\n get preprocessInfo() {\n return _preprocessInfo\n },\n get attempts() {\n return _attempts\n },\n get currentAttempt() {\n return _attempts.at(-1) ?? null\n },\n get queueWaitMs() {\n return _queueWaitMs\n },\n\n setOriginalRequest(req: OriginalRequest) {\n _originalRequest = req\n emit({ type: \"updated\", context: ctx, field: \"originalRequest\" })\n },\n\n setPreprocessInfo(info: PreprocessInfo) {\n _preprocessInfo = info\n },\n\n addSanitizationInfo(info: SanitizationInfo) {\n _sanitizationHistory.push(info)\n },\n\n setRewrites(info: RewriteInfo) {\n _rewrites = {\n ...(_preprocessInfo && { preprocessing: _preprocessInfo }),\n ...(_sanitizationHistory.length > 0 && { sanitization: _sanitizationHistory }),\n ...info,\n }\n emit({ type: \"updated\", context: ctx, field: \"rewrites\" })\n },\n\n setSseEvents(events: Array<SseEventRecord>) {\n _sseEvents = events.length > 0 ? events : null\n },\n\n beginAttempt(attemptOpts: { strategy?: string; waitMs?: number; truncation?: TruncationState }) {\n const attempt: Attempt = {\n index: _attempts.length,\n effectiveRequest: null, // Set later via setAttemptEffectiveRequest\n response: null,\n error: null,\n strategy: attemptOpts.strategy,\n truncation: attemptOpts.truncation,\n waitMs: attemptOpts.waitMs,\n startTime: Date.now(),\n durationMs: 0,\n }\n _attempts.push(attempt)\n emit({ type: \"updated\", context: ctx, field: \"attempts\" })\n },\n\n setAttemptSanitization(info: SanitizationState) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.sanitization = info\n }\n },\n\n setAttemptEffectiveRequest(req: EffectiveRequest) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.effectiveRequest = req\n }\n },\n\n setAttemptResponse(response: ResponseData) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.response = response\n attempt.durationMs = Date.now() - attempt.startTime\n }\n },\n\n setAttemptError(error: ApiError) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.error = error\n attempt.durationMs = Date.now() - attempt.startTime\n }\n },\n\n addQueueWaitMs(ms: number) {\n _queueWaitMs += ms\n },\n\n transition(newState: RequestState, meta?: Record<string, unknown>) {\n const previousState = _state\n _state = newState\n emit({ type: \"state_changed\", context: ctx, previousState, meta })\n },\n\n complete(response: ResponseData) {\n if (settled) return\n settled = true\n\n // Normalize response model to canonical dot-version form\n // (API may return \"claude-opus-4-6\" instead of \"claude-opus-4.6\")\n if (response.model) response.model = normalizeModelId(response.model)\n _response = response\n ctx.setAttemptResponse(response)\n _state = \"completed\"\n const entry = ctx.toHistoryEntry()\n emit({ type: \"completed\", context: ctx, entry })\n },\n\n completeFromStream(acc: StreamAccumulatorResult) {\n const response: ResponseData = {\n success: true,\n model: acc.model,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cacheReadTokens > 0 && { cache_read_input_tokens: acc.cacheReadTokens }),\n ...(acc.cacheCreationTokens > 0 && { cache_creation_input_tokens: acc.cacheCreationTokens }),\n },\n content: acc.contentBlocks.length > 0 ? { role: \"assistant\", content: acc.contentBlocks } : null,\n stop_reason: acc.stopReason || undefined,\n }\n\n ctx.complete(response)\n },\n\n fail(model: string, error: unknown) {\n if (settled) return\n settled = true\n\n const errorMsg = getErrorMessage(error)\n _response = {\n success: false,\n model: normalizeModelId(model),\n usage: { input_tokens: 0, output_tokens: 0 },\n error: errorMsg,\n content: null,\n }\n\n // Preserve upstream HTTP error details as structured fields\n if (\n error instanceof Error\n && \"responseText\" in error\n && typeof (error as { responseText: unknown }).responseText === \"string\"\n ) {\n const responseText = (error as { responseText: string }).responseText\n if (responseText) {\n _response.responseText = responseText\n }\n }\n if (error instanceof Error && \"status\" in error && typeof (error as { status: unknown }).status === \"number\") {\n _response.status = (error as { status: number }).status\n }\n\n _state = \"failed\"\n const entry = ctx.toHistoryEntry()\n emit({ type: \"failed\", context: ctx, entry })\n },\n\n toHistoryEntry(): HistoryEntryData {\n const entry: HistoryEntryData = {\n id,\n endpoint: opts.endpoint,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n request: {\n model: _originalRequest?.model,\n messages: _originalRequest?.messages,\n stream: _originalRequest?.stream,\n tools: _originalRequest?.tools,\n system: _originalRequest?.system,\n },\n }\n\n if (_response) {\n entry.response = _response\n }\n\n // Find truncation from the last attempt that had one\n const lastTruncation = _attempts.findLast((a) => a.truncation)?.truncation\n if (lastTruncation) {\n entry.truncation = lastTruncation\n }\n\n if (_rewrites) {\n entry.rewrites = _rewrites\n }\n\n if (_sseEvents) {\n entry.sseEvents = _sseEvents\n }\n\n // Include attempt summary\n if (_attempts.length > 1) {\n entry.attempts = _attempts.map((a) => ({\n index: a.index,\n strategy: a.strategy,\n durationMs: a.durationMs,\n error: a.error?.message,\n truncation: a.truncation,\n }))\n }\n\n return entry\n },\n }\n\n return ctx\n}\n","/**\n * RequestContextManager — Active request management\n *\n * Manages all in-flight RequestContext instances. Publishes events for\n * WebSocket push and history persistence. The \"active layer\" complementing\n * the history store (persistence layer).\n *\n * Data flow:\n * Handler creates RequestContext → manager.create() registers + emits \"created\"\n * → pipeline processes request, calls ctx.transition()/setRewrites()/etc\n * → each change → manager emits events\n * → ws receives events → pushes to browser\n * → ctx.complete()/fail() → ctx.toHistoryEntry() → store.insert()\n * → manager emits \"completed\"/\"failed\" → removes active context\n */\n\nimport type {\n HistoryEntryData,\n RequestContext,\n RequestContextEventData,\n RequestState,\n} from \"./request\"\n\nimport type { EndpointType } from \"~/lib/history/store\"\n\nimport { consola } from \"consola\"\n\nimport { state } from \"~/lib/state\"\n\nimport { createRequestContext } from \"./request\"\n\n// ─── Event Types ───\n\nexport type RequestContextEvent =\n | { type: \"created\"; context: RequestContext }\n | { type: \"state_changed\"; context: RequestContext; previousState: RequestState; meta?: Record<string, unknown> }\n | { type: \"updated\"; context: RequestContext; field: string }\n | { type: \"completed\"; context: RequestContext; entry: HistoryEntryData }\n | { type: \"failed\"; context: RequestContext; entry: HistoryEntryData }\n\n// ─── Manager Interface ───\n\nexport interface RequestContextManager {\n /** Create and register a new active request context */\n create(opts: { endpoint: EndpointType; tuiLogId?: string }): RequestContext\n\n /** Get an active request by ID */\n get(id: string): RequestContext | undefined\n\n /** Get all active requests (for history UI real-time view) */\n getAll(): Array<RequestContext>\n\n /** Number of active requests */\n readonly activeCount: number\n\n /** Subscribe to context events */\n on(event: \"change\", listener: (event: RequestContextEvent) => void): void\n\n /** Unsubscribe from context events */\n off(event: \"change\", listener: (event: RequestContextEvent) => void): void\n\n /** Start periodic cleanup of stale active contexts */\n startReaper(): void\n\n /** Stop the reaper (for shutdown/cleanup) */\n stopReaper(): void\n\n /** Run a single reaper scan (exposed for testing) */\n _runReaperOnce(): void\n}\n\n// ─── Implementation ───\n\n// ─── Module-level Singleton ───\n\nlet _manager: RequestContextManager | null = null\n\nexport function initRequestContextManager(): RequestContextManager {\n _manager = createRequestContextManager()\n return _manager\n}\n\nexport function getRequestContextManager(): RequestContextManager {\n if (!_manager) throw new Error(\"RequestContextManager not initialized — call initRequestContextManager() first\")\n return _manager\n}\n\n// ─── Factory ───\n\nexport function createRequestContextManager(): RequestContextManager {\n const activeContexts = new Map<string, RequestContext>()\n const listeners = new Set<(event: RequestContextEvent) => void>()\n\n // ─── Stale Request Reaper ───\n\n const REAPER_INTERVAL_MS = 60_000\n let reaperTimer: ReturnType<typeof setInterval> | null = null\n\n /** Single reaper scan — force-fail contexts exceeding maxAge */\n function runReaperOnce() {\n const maxAgeMs = state.staleRequestMaxAge * 1000\n if (maxAgeMs <= 0) return // disabled\n\n for (const [id, ctx] of activeContexts) {\n if (ctx.durationMs > maxAgeMs) {\n consola.warn(\n `[context] Force-failing stale request ${id}`\n + ` (endpoint: ${ctx.endpoint}`\n + `, model: ${ctx.originalRequest?.model ?? \"unknown\"}`\n + `, stream: ${ctx.originalRequest?.stream ?? \"?\"}`\n + `, state: ${ctx.state}`\n + `, age: ${Math.round(ctx.durationMs / 1000)}s`\n + `, max: ${state.staleRequestMaxAge}s)`,\n )\n ctx.fail(\n ctx.originalRequest?.model ?? \"unknown\",\n new Error(`Request exceeded maximum age of ${state.staleRequestMaxAge}s (stale context reaper)`),\n )\n }\n }\n }\n\n function startReaper() {\n if (reaperTimer) return // idempotent\n reaperTimer = setInterval(runReaperOnce, REAPER_INTERVAL_MS)\n }\n\n function stopReaper() {\n if (reaperTimer) {\n clearInterval(reaperTimer)\n reaperTimer = null\n }\n }\n\n function emit(event: RequestContextEvent) {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch {\n // Swallow listener errors\n }\n }\n }\n\n function handleContextEvent(rawEvent: RequestContextEventData) {\n const { type, context } = rawEvent\n\n switch (type) {\n case \"state_changed\": {\n if (rawEvent.previousState) {\n emit({\n type: \"state_changed\",\n context,\n previousState: rawEvent.previousState,\n meta: rawEvent.meta,\n })\n }\n break\n }\n case \"updated\": {\n if (rawEvent.field) {\n emit({\n type: \"updated\",\n context,\n field: rawEvent.field,\n })\n }\n break\n }\n case \"completed\": {\n if (rawEvent.entry) {\n emit({\n type: \"completed\",\n context,\n entry: rawEvent.entry,\n })\n }\n activeContexts.delete(context.id)\n break\n }\n case \"failed\": {\n if (rawEvent.entry) {\n emit({\n type: \"failed\",\n context,\n entry: rawEvent.entry,\n })\n }\n activeContexts.delete(context.id)\n break\n }\n default: {\n break\n }\n }\n }\n\n return {\n create(opts) {\n const ctx = createRequestContext({\n endpoint: opts.endpoint,\n tuiLogId: opts.tuiLogId,\n onEvent: handleContextEvent,\n })\n activeContexts.set(ctx.id, ctx)\n emit({ type: \"created\", context: ctx })\n return ctx\n },\n\n get(id) {\n return activeContexts.get(id)\n },\n\n getAll() {\n return Array.from(activeContexts.values())\n },\n\n get activeCount() {\n return activeContexts.size\n },\n\n on(_event: \"change\", listener: (event: RequestContextEvent) => void) {\n listeners.add(listener)\n },\n\n off(_event: \"change\", listener: (event: RequestContextEvent) => void) {\n listeners.delete(listener)\n },\n\n startReaper,\n stopReaper,\n _runReaperOnce: runReaperOnce,\n }\n}\n","/**\n * WebSocket support for History API.\n * Enables real-time updates when new requests are recorded.\n */\n\nimport consola from \"consola\"\n\nimport type { EntrySummary, HistoryStats } from \"./store\"\n\n/** Discriminated union of WebSocket message types */\nexport type WSMessageType = \"entry_added\" | \"entry_updated\" | \"stats_updated\" | \"connected\"\n\n/** A WebSocket message sent to connected clients */\nexport interface WSMessage {\n type: WSMessageType\n data: unknown\n timestamp: number\n}\n\n/** Track connected WebSocket clients */\nconst clients = new Set<WebSocket>()\n\n/** Register a new WebSocket client and send connection confirmation */\nexport function addClient(ws: WebSocket): void {\n clients.add(ws)\n\n // Send connected confirmation\n const msg: WSMessage = {\n type: \"connected\",\n data: { clientCount: clients.size },\n timestamp: Date.now(),\n }\n ws.send(JSON.stringify(msg))\n}\n\n/** Unregister a WebSocket client */\nexport function removeClient(ws: WebSocket): void {\n clients.delete(ws)\n}\n\n/** Get the number of currently connected WebSocket clients */\nexport function getClientCount(): number {\n return clients.size\n}\n\n/** Close all connected WebSocket clients */\nexport function closeAllClients(): void {\n for (const client of clients) {\n try {\n client.close(1001, \"Server shutting down\")\n } catch {\n // Ignore errors during shutdown\n }\n }\n clients.clear()\n}\n\nfunction broadcast(message: WSMessage): void {\n const data = JSON.stringify(message)\n for (const client of clients) {\n try {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data)\n } else {\n // Remove clients that are no longer open (CLOSING, CLOSED)\n clients.delete(client)\n }\n } catch (error) {\n consola.debug(\"WebSocket send failed, removing client:\", error)\n clients.delete(client)\n }\n }\n}\n\n/** Called when a new entry is recorded */\nexport function notifyEntryAdded(summary: EntrySummary): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"entry_added\",\n data: summary,\n timestamp: Date.now(),\n })\n}\n\n/** Called when an entry is updated (e.g., response received) */\nexport function notifyEntryUpdated(summary: EntrySummary): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"entry_updated\",\n data: summary,\n timestamp: Date.now(),\n })\n}\n\n/** Called when stats change */\nexport function notifyStatsUpdated(stats: HistoryStats): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"stats_updated\",\n data: stats,\n timestamp: Date.now(),\n })\n}\n","/**\n * History recording module for API requests/responses.\n * Supports full message content, session grouping, and rich querying.\n */\n\nimport { generateId } from \"../utils\"\nimport { notifyEntryAdded, notifyEntryUpdated } from \"./ws\"\n\n// Format timestamp as local ISO-like string (YYYY-MM-DD HH:MM:SS)\nfunction formatLocalTimestamp(ts: number): string {\n const d = new Date(ts)\n const y = d.getFullYear()\n const mo = String(d.getMonth() + 1).padStart(2, \"0\")\n const day = String(d.getDate()).padStart(2, \"0\")\n const h = String(d.getHours()).padStart(2, \"0\")\n const m = String(d.getMinutes()).padStart(2, \"0\")\n const s = String(d.getSeconds()).padStart(2, \"0\")\n return `${y}-${mo}-${day} ${h}:${m}:${s}`\n}\n\n/** Supported API endpoint types */\nexport type EndpointType = \"anthropic\" | \"openai\" | \"openai-responses\"\n\n/** Message types for full content storage */\nexport interface MessageContent {\n role: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n content: string | Array<any> | null\n tool_calls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n tool_call_id?: string\n name?: string\n}\n\n// ============================================================================\n// Content block types — Anthropic API content block variants\n// ============================================================================\n\nexport interface TextContentBlock {\n type: \"text\"\n text: string\n}\n\nexport interface ThinkingContentBlock {\n type: \"thinking\"\n thinking: string\n signature?: string\n}\n\nexport interface ToolUseContentBlock {\n type: \"tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n}\n\n/** Text block within a tool_result content array */\nexport interface ToolResultTextBlock {\n type: \"text\"\n text: string\n}\n\n/** Image block within a tool_result content array */\nexport interface ToolResultImageBlock {\n type: \"image\"\n source: ImageSource\n}\n\nexport interface ToolResultContentBlock {\n type: \"tool_result\"\n tool_use_id: string\n content: string | Array<ToolResultTextBlock | ToolResultImageBlock>\n is_error?: boolean\n}\n\nexport type ImageSource =\n | {\n type: \"base64\"\n media_type: string\n data: string\n }\n | {\n type: \"url\"\n url: string\n }\n\nexport interface ImageContentBlock {\n type: \"image\"\n source: ImageSource\n}\n\nexport interface ServerToolUseContentBlock {\n type: \"server_tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n}\n\nexport interface RedactedThinkingContentBlock {\n type: \"redacted_thinking\"\n data?: string\n}\n\nexport interface WebSearchToolResultContentBlock {\n type: \"web_search_tool_result\"\n tool_use_id: string\n content: unknown\n}\n\n/** Union of all content block types that can appear in messages */\nexport type ContentBlock =\n | TextContentBlock\n | ThinkingContentBlock\n | ToolUseContentBlock\n | ToolResultContentBlock\n | ImageContentBlock\n | ServerToolUseContentBlock\n | RedactedThinkingContentBlock\n | WebSearchToolResultContentBlock\n\nexport interface ToolDefinition {\n name: string\n description?: string\n type?: string\n input_schema?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface TruncationInfo {\n /** Number of messages removed from the beginning of the conversation */\n removedMessageCount: number\n /** Estimated token count before truncation */\n originalTokens: number\n /** Estimated token count after truncation */\n compactedTokens: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\nexport interface SanitizationInfo {\n /** Total content blocks removed */\n totalBlocksRemoved: number\n /** Number of orphaned tool_use blocks removed */\n orphanedToolUseCount: number\n /** Number of orphaned tool_result blocks removed */\n orphanedToolResultCount: number\n /** Number of tool_use names fixed (casing) */\n fixedNameCount: number\n /** Number of empty text blocks removed */\n emptyTextBlocksRemoved: number\n /** Number of system-reminder tags removed */\n systemReminderRemovals: number\n}\n\nexport interface PreprocessInfo {\n /** Number of system-reminder tags stripped from Read tool results */\n strippedReadTagCount: number\n /** Number of duplicate tool_use/tool_result pairs deduplicated */\n dedupedToolCallCount: number\n}\n\n/** A single SSE event captured from an Anthropic streaming response */\nexport interface SseEventRecord {\n /** Milliseconds since request start */\n offsetMs: number\n /** SSE event type (e.g. \"message_start\", \"content_block_start\") */\n type: string\n /** Raw event data (the parsed JSON payload) */\n data: unknown\n}\n\nexport interface RewriteInfo {\n /** Auto-truncation metadata */\n truncation?: TruncationInfo\n /** Phase 1 preprocessing metadata (idempotent, run once before routing) */\n preprocessing?: PreprocessInfo\n /** Phase 2 sanitization metadata (repeatable, one entry per attempt) */\n sanitization?: Array<SanitizationInfo>\n /** Rewritten messages as actually sent to the API */\n rewrittenMessages?: Array<MessageContent>\n /** Rewritten system prompt (if modified) */\n rewrittenSystem?: string\n /** Rewritten→original message index mapping: messageMapping[rwIdx] = origIdx */\n messageMapping?: Array<number>\n}\n\nexport interface UsageData {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n}\n\nexport interface SystemBlock {\n type: \"text\"\n text: string\n cache_control?: { type: string } | null\n}\n\nexport interface HistoryEntry {\n id: string\n sessionId: string // Group related requests together\n timestamp: number\n endpoint: EndpointType\n\n request: {\n model?: string\n messages?: Array<MessageContent> // Full message history\n stream?: boolean\n tools?: Array<ToolDefinition>\n max_tokens?: number\n temperature?: number\n system?: string | Array<SystemBlock>\n }\n\n response?: {\n success: boolean\n model: string\n usage: UsageData\n stop_reason?: string\n error?: string\n content: MessageContent | null // Full response content\n }\n\n /** All rewrite metadata (truncation + sanitization + rewritten content) */\n rewrites?: RewriteInfo\n\n /** Filtered SSE events from Anthropic streaming (excludes content_block_delta and ping) */\n sseEvents?: Array<SseEventRecord>\n\n durationMs?: number\n}\n\nexport interface Session {\n id: string\n startTime: number\n lastActivity: number\n requestCount: number\n totalInputTokens: number\n totalOutputTokens: number\n models: Array<string>\n endpoint: EndpointType\n toolsUsed?: Array<string> // Tool names used in this session\n}\n\nexport interface HistoryState {\n enabled: boolean\n entries: Array<HistoryEntry>\n sessions: Map<string, Session>\n currentSessionId: string\n maxEntries: number\n}\n\nexport interface QueryOptions {\n page?: number\n limit?: number\n model?: string\n endpoint?: EndpointType\n success?: boolean\n from?: number\n to?: number\n search?: string\n sessionId?: string\n}\n\nexport interface HistoryResult {\n entries: Array<HistoryEntry>\n total: number\n page: number\n limit: number\n totalPages: number\n}\n\nexport interface SessionResult {\n sessions: Array<Session>\n total: number\n}\n\nexport interface HistoryStats {\n totalRequests: number\n successfulRequests: number\n failedRequests: number\n totalInputTokens: number\n totalOutputTokens: number\n averageDurationMs: number\n modelDistribution: Record<string, number>\n endpointDistribution: Record<string, number>\n recentActivity: Array<{ hour: string; count: number }>\n activeSessions: number\n}\n\n// ─── Entry Summary ───\n\n/** Lightweight projection of a HistoryEntry for list views and WebSocket broadcasts */\nexport interface EntrySummary {\n id: string\n sessionId: string\n timestamp: number\n endpoint: EndpointType\n\n requestModel?: string\n stream?: boolean\n messageCount: number\n\n responseModel?: string\n responseSuccess?: boolean\n responseError?: string\n usage?: {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n }\n\n durationMs?: number\n /** First 100 characters of the last user message, for list preview */\n previewText: string\n /** Pre-computed lowercase text for search matching */\n searchText: string\n}\n\nexport interface SummaryResult {\n entries: Array<EntrySummary>\n total: number\n page: number\n limit: number\n totalPages: number\n}\n\n/** Extract a preview from the last user message (first 100 chars) */\nfunction extractPreviewText(entry: HistoryEntry): string {\n const messages = entry.request.messages\n if (!messages || messages.length === 0) return \"\"\n\n // Walk backwards to find the last user message (skip tool responses)\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n // Skip OpenAI tool responses and Anthropic tool_result messages\n if (msg.role === \"tool\") continue\n if (msg.role !== \"user\") continue\n\n if (typeof msg.content === \"string\") {\n return msg.content.slice(0, 100)\n }\n if (Array.isArray(msg.content)) {\n // Anthropic-style content blocks: look for text, skip tool_result\n for (const block of msg.content) {\n if (block.type === \"text\" && block.text) {\n return (block.text as string).slice(0, 100)\n }\n if (block.type === \"tool_result\") {\n // user message that only contains tool_result — skip this message entirely\n break\n }\n }\n continue // try previous messages\n }\n break\n }\n\n // Fallback: if the last message is an assistant with tool_calls, show tool name\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n const names = msg.tool_calls.map((tc) => tc.function.name).join(\", \")\n return `[tool_call: ${names}]`.slice(0, 100)\n }\n if (msg.role === \"tool\") {\n return `[tool_result: ${msg.tool_call_id ?? msg.name ?? \"unknown\"}]`.slice(0, 100)\n }\n break\n }\n\n return \"\"\n}\n\n/**\n * Build a pre-computed lowercase string for fast search matching.\n * Includes model names, error, system prompt preview, and message text snippets.\n * Deliberately kept compact — only the first ~200 chars of each message for memory efficiency.\n */\nfunction buildSearchText(entry: HistoryEntry): string {\n const parts: Array<string> = []\n\n // Model names\n if (entry.request.model) parts.push(entry.request.model)\n if (entry.response?.model) parts.push(entry.response.model)\n\n // Error\n if (entry.response?.error) parts.push(entry.response.error)\n\n // System prompt (first 500 chars)\n if (entry.request.system) {\n if (typeof entry.request.system === \"string\") {\n parts.push(entry.request.system.slice(0, 500))\n } else {\n for (const block of entry.request.system) {\n parts.push(block.text.slice(0, 200))\n }\n }\n }\n\n // Message text snippets\n if (entry.request.messages) {\n for (const msg of entry.request.messages) {\n if (typeof msg.content === \"string\") {\n parts.push(msg.content.slice(0, 200))\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === \"text\" && block.text) {\n parts.push((block.text as string).slice(0, 200))\n } else if (block.type === \"tool_use\") {\n if (block.name) parts.push(block.name as string)\n if (block.input) {\n const inputStr = typeof block.input === \"string\" ? block.input : JSON.stringify(block.input)\n parts.push(inputStr.slice(0, 500))\n }\n } else if (block.type === \"tool_result\" && block.content) {\n const contentStr\n = typeof block.content === \"string\" ? block.content : JSON.stringify(block.content)\n parts.push(contentStr.slice(0, 500))\n } else if (block.type === \"thinking\" && block.thinking) {\n parts.push((block.thinking as string).slice(0, 200))\n }\n }\n }\n // OpenAI tool_calls\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n if (tc.function.name) parts.push(tc.function.name)\n if (tc.function.arguments) parts.push(tc.function.arguments.slice(0, 500))\n }\n }\n }\n }\n\n // Response content snippets\n if (entry.response?.content) {\n const rc = entry.response.content\n if (typeof rc.content === \"string\") {\n parts.push(rc.content.slice(0, 200))\n } else if (Array.isArray(rc.content)) {\n for (const block of rc.content) {\n if (block.type === \"text\" && block.text) {\n parts.push((block.text as string).slice(0, 200))\n } else if (block.type === \"tool_use\" && block.name) {\n parts.push(block.name as string)\n }\n }\n }\n }\n\n return parts.join(\" \").toLowerCase()\n}\n\n/** Build a summary from a full HistoryEntry (searchText is computed lazily) */\nfunction toSummary(entry: HistoryEntry): EntrySummary {\n return {\n id: entry.id,\n sessionId: entry.sessionId,\n timestamp: entry.timestamp,\n endpoint: entry.endpoint,\n requestModel: entry.request.model,\n stream: entry.request.stream,\n messageCount: entry.request.messages?.length ?? 0,\n responseModel: entry.response?.model,\n responseSuccess: entry.response?.success,\n responseError: entry.response?.error,\n usage: entry.response?.usage,\n durationMs: entry.durationMs,\n previewText: extractPreviewText(entry),\n // Lazy: computed on first search/history API access via ensureSearchText()\n searchText: \"\",\n }\n}\n\n/** Lazily compute and cache searchText for an entry */\nfunction ensureSearchText(id: string): string {\n const summary = summaryIndex.get(id)\n if (!summary) return \"\"\n if (summary.searchText === \"\") {\n const entry = entryIndex.get(id)\n if (entry) {\n summary.searchText = buildSearchText(entry)\n }\n }\n return summary.searchText\n}\n\n/** Global history state */\nexport const historyState: HistoryState = {\n enabled: false,\n entries: [],\n sessions: new Map(),\n currentSessionId: \"\",\n maxEntries: 200,\n}\n\n/** O(1) lookup index for entries by ID */\nconst entryIndex = new Map<string, HistoryEntry>()\n\n/** O(1) lookup for entry summaries by ID */\nconst summaryIndex = new Map<string, EntrySummary>()\n\n/** Track entry count per session to avoid O(n) filter during FIFO eviction */\nconst sessionEntryCount = new Map<string, number>()\n\n/** O(1) uniqueness tracking for session.models (avoids Array.includes in hot path) */\nconst sessionModelsSet = new Map<string, Set<string>>()\n\n/** O(1) uniqueness tracking for session.toolsUsed (avoids Array.includes in hot path) */\nconst sessionToolsSet = new Map<string, Set<string>>()\n\n/** Dirty flag for stats cache — set true when entries are inserted/updated */\nlet statsDirty = true\n\n/** Cached stats result — recomputed only when statsDirty is true */\nlet cachedStats: HistoryStats | null = null\n\nexport function initHistory(enabled: boolean, maxEntries: number): void {\n historyState.enabled = enabled\n historyState.maxEntries = maxEntries\n historyState.entries = []\n historyState.sessions = new Map()\n historyState.currentSessionId = enabled ? generateId() : \"\"\n entryIndex.clear()\n summaryIndex.clear()\n sessionEntryCount.clear()\n sessionModelsSet.clear()\n sessionToolsSet.clear()\n statsDirty = true\n cachedStats = null\n}\n\n/** Update the maximum number of history entries (for config hot-reload) */\nexport function setHistoryMaxEntries(limit: number): void {\n historyState.maxEntries = limit\n}\n\nexport function isHistoryEnabled(): boolean {\n return historyState.enabled\n}\n\n/**\n * Get or create current session.\n * Currently treats all requests as belonging to one session per server lifetime,\n * since clients don't provide session identifiers yet.\n * TODO: When clients support session headers, use that to group requests.\n */\nexport function getCurrentSession(endpoint: EndpointType): string {\n if (historyState.currentSessionId) {\n const session = historyState.sessions.get(historyState.currentSessionId)\n if (session) {\n session.lastActivity = Date.now()\n return historyState.currentSessionId\n }\n }\n\n // Create initial session\n const now = Date.now()\n const sessionId = generateId()\n historyState.currentSessionId = sessionId\n sessionModelsSet.set(sessionId, new Set())\n sessionToolsSet.set(sessionId, new Set())\n historyState.sessions.set(sessionId, {\n id: sessionId,\n startTime: now,\n lastActivity: now,\n requestCount: 0,\n totalInputTokens: 0,\n totalOutputTokens: 0,\n models: [],\n endpoint,\n })\n\n return sessionId\n}\n\n// ─── Context-driven API ───\n\n/**\n * Insert a pre-built history entry.\n * Used by the context consumer — the entry already has an ID and sessionId.\n */\nexport function insertEntry(entry: HistoryEntry): void {\n if (!historyState.enabled) return\n\n const session = historyState.sessions.get(entry.sessionId)\n if (!session) return\n\n historyState.entries.push(entry)\n entryIndex.set(entry.id, entry)\n session.requestCount++\n sessionEntryCount.set(entry.sessionId, (sessionEntryCount.get(entry.sessionId) ?? 0) + 1)\n\n // Track model (O(1) via Set)\n const model = entry.request.model\n if (model) {\n const modelsSet = sessionModelsSet.get(entry.sessionId)\n if (modelsSet && !modelsSet.has(model)) {\n modelsSet.add(model)\n session.models.push(model)\n }\n }\n\n // Track tools (O(1) via Set)\n if (entry.request.tools && entry.request.tools.length > 0) {\n if (!session.toolsUsed) {\n session.toolsUsed = []\n }\n let toolsSet = sessionToolsSet.get(entry.sessionId)\n if (!toolsSet) {\n toolsSet = new Set(session.toolsUsed)\n sessionToolsSet.set(entry.sessionId, toolsSet)\n }\n for (const tool of entry.request.tools) {\n if (!toolsSet.has(tool.name)) {\n toolsSet.add(tool.name)\n session.toolsUsed.push(tool.name)\n }\n }\n }\n\n // Build and cache summary\n const summary = toSummary(entry)\n summaryIndex.set(entry.id, summary)\n\n // FIFO eviction (splice instead of repeated shift for O(1) amortized)\n if (historyState.maxEntries > 0 && historyState.entries.length > historyState.maxEntries) {\n const excess = historyState.entries.length - historyState.maxEntries\n const removed = historyState.entries.splice(0, excess)\n for (const r of removed) {\n entryIndex.delete(r.id)\n summaryIndex.delete(r.id)\n const count = (sessionEntryCount.get(r.sessionId) ?? 1) - 1\n if (count <= 0) {\n sessionEntryCount.delete(r.sessionId)\n sessionModelsSet.delete(r.sessionId)\n sessionToolsSet.delete(r.sessionId)\n historyState.sessions.delete(r.sessionId)\n } else {\n sessionEntryCount.set(r.sessionId, count)\n }\n }\n }\n\n statsDirty = true\n notifyEntryAdded(summary)\n}\n\n/**\n * Update an existing entry's response, rewrites, or duration.\n * Used by the context consumer on completion/failure events.\n */\nexport function updateEntry(\n id: string,\n update: Partial<Pick<HistoryEntry, \"request\" | \"response\" | \"rewrites\" | \"durationMs\">>,\n): void {\n if (!historyState.enabled) return\n\n const entry = entryIndex.get(id)\n if (!entry) return\n\n if (update.request) {\n entry.request = update.request\n\n // Update session metadata that depends on request data\n const session = historyState.sessions.get(entry.sessionId)\n if (session) {\n const model = update.request.model\n if (model) {\n const modelsSet = sessionModelsSet.get(entry.sessionId)\n if (modelsSet && !modelsSet.has(model)) {\n modelsSet.add(model)\n session.models.push(model)\n }\n }\n if (update.request.tools && update.request.tools.length > 0) {\n if (!session.toolsUsed) {\n session.toolsUsed = []\n }\n let toolsSet = sessionToolsSet.get(entry.sessionId)\n if (!toolsSet) {\n toolsSet = new Set(session.toolsUsed)\n sessionToolsSet.set(entry.sessionId, toolsSet)\n }\n for (const tool of update.request.tools) {\n if (!toolsSet.has(tool.name)) {\n toolsSet.add(tool.name)\n session.toolsUsed.push(tool.name)\n }\n }\n }\n }\n }\n if (update.response) {\n entry.response = update.response\n }\n if (update.rewrites) {\n entry.rewrites = update.rewrites\n }\n if (update.durationMs !== undefined) {\n entry.durationMs = update.durationMs\n }\n\n // Update session token stats when response is set\n if (update.response) {\n const session = historyState.sessions.get(entry.sessionId)\n if (session) {\n session.totalInputTokens += update.response.usage.input_tokens\n session.totalOutputTokens += update.response.usage.output_tokens\n session.lastActivity = Date.now()\n }\n }\n\n // Rebuild summary cache and broadcast\n statsDirty = true\n const summary = toSummary(entry)\n summaryIndex.set(entry.id, summary)\n notifyEntryUpdated(summary)\n}\n\nexport function getHistory(options: QueryOptions = {}): HistoryResult {\n const { page = 1, limit = 50, model, endpoint, success, from, to, search, sessionId } = options\n\n let filtered = [...historyState.entries]\n\n // Apply filters\n if (sessionId) {\n filtered = filtered.filter((e) => e.sessionId === sessionId)\n }\n\n if (model) {\n const modelLower = model.toLowerCase()\n filtered = filtered.filter(\n (e) =>\n e.request.model?.toLowerCase().includes(modelLower) || e.response?.model.toLowerCase().includes(modelLower),\n )\n }\n\n if (endpoint) {\n filtered = filtered.filter((e) => e.endpoint === endpoint)\n }\n\n if (success !== undefined) {\n filtered = filtered.filter((e) => e.response?.success === success)\n }\n\n if (from) {\n filtered = filtered.filter((e) => e.timestamp >= from)\n }\n\n if (to) {\n filtered = filtered.filter((e) => e.timestamp <= to)\n }\n\n if (search) {\n const searchLower = search.toLowerCase()\n filtered = filtered.filter((e) => ensureSearchText(e.id).includes(searchLower))\n }\n\n // Sort by timestamp descending (newest first)\n filtered.sort((a, b) => b.timestamp - a.timestamp)\n\n const total = filtered.length\n const totalPages = Math.ceil(total / limit)\n const start = (page - 1) * limit\n const entries = filtered.slice(start, start + limit)\n\n return {\n entries,\n total,\n page,\n limit,\n totalPages,\n }\n}\n\nexport function getEntry(id: string): HistoryEntry | undefined {\n return entryIndex.get(id) ?? historyState.entries.find((e) => e.id === id)\n}\n\nexport function getSummary(id: string): EntrySummary | undefined {\n return summaryIndex.get(id)\n}\n\n/**\n * Efficient summary-only query for list views. Filters and paginates using\n * the lightweight summaryIndex instead of full entries.\n * Search matches against the pre-computed `searchText` field — O(n) string\n * includes instead of O(n*m*b) deep content block traversal.\n */\nexport function getHistorySummaries(options: QueryOptions = {}): SummaryResult {\n const { page = 1, limit = 50, model, endpoint, success, from, to, search, sessionId } = options\n\n let summaries = Array.from(summaryIndex.values())\n\n // Filter\n if (sessionId) summaries = summaries.filter((s) => s.sessionId === sessionId)\n if (model) {\n const modelLower = model.toLowerCase()\n summaries = summaries.filter(\n (s) => s.requestModel?.toLowerCase().includes(modelLower) || s.responseModel?.toLowerCase().includes(modelLower),\n )\n }\n if (endpoint) summaries = summaries.filter((s) => s.endpoint === endpoint)\n if (success !== undefined) summaries = summaries.filter((s) => s.responseSuccess === success)\n if (from) summaries = summaries.filter((s) => s.timestamp >= from)\n if (to) summaries = summaries.filter((s) => s.timestamp <= to)\n\n // Search against pre-computed lowercase text (lazy-computed on first access)\n if (search) {\n const needle = search.toLowerCase()\n summaries = summaries.filter((s) => {\n if (s.searchText === \"\") {\n const entry = entryIndex.get(s.id)\n if (entry) s.searchText = buildSearchText(entry)\n }\n return s.searchText.includes(needle)\n })\n }\n\n // Sort newest first\n summaries.sort((a, b) => b.timestamp - a.timestamp)\n\n const total = summaries.length\n const totalPages = Math.ceil(total / limit)\n const start = (page - 1) * limit\n const entries = summaries.slice(start, start + limit)\n\n return { entries, total, page, limit, totalPages }\n}\n\nexport function getSessions(): SessionResult {\n const sessions = Array.from(historyState.sessions.values()).sort((a, b) => b.lastActivity - a.lastActivity)\n\n return {\n sessions,\n total: sessions.length,\n }\n}\n\nexport function getSession(id: string): Session | undefined {\n return historyState.sessions.get(id)\n}\n\nexport function getSessionEntries(sessionId: string): Array<HistoryEntry> {\n return historyState.entries.filter((e) => e.sessionId === sessionId).sort((a, b) => a.timestamp - b.timestamp) // Chronological order for sessions\n}\n\nexport function clearHistory(): void {\n historyState.entries = []\n historyState.sessions = new Map()\n historyState.currentSessionId = generateId()\n entryIndex.clear()\n summaryIndex.clear()\n sessionEntryCount.clear()\n sessionModelsSet.clear()\n sessionToolsSet.clear()\n statsDirty = true\n cachedStats = null\n}\n\nexport function deleteSession(sessionId: string): boolean {\n if (!historyState.sessions.has(sessionId)) {\n return false\n }\n\n const remaining: Array<HistoryEntry> = []\n for (const e of historyState.entries) {\n if (e.sessionId === sessionId) {\n entryIndex.delete(e.id)\n summaryIndex.delete(e.id)\n } else {\n remaining.push(e)\n }\n }\n historyState.entries = remaining\n historyState.sessions.delete(sessionId)\n sessionEntryCount.delete(sessionId)\n sessionModelsSet.delete(sessionId)\n sessionToolsSet.delete(sessionId)\n statsDirty = true\n cachedStats = null\n\n if (historyState.currentSessionId === sessionId) {\n historyState.currentSessionId = generateId()\n }\n\n return true\n}\n\nexport function getStats(): HistoryStats {\n // Return cached stats if nothing has changed\n if (!statsDirty && cachedStats) return cachedStats\n\n const entries = historyState.entries\n\n const modelDist: Record<string, number> = {}\n const endpointDist: Record<string, number> = {}\n const hourlyActivity: Record<string, number> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalDuration = 0\n let durationCount = 0\n let successCount = 0\n let failCount = 0\n\n for (const entry of entries) {\n // Model distribution\n const model = entry.response?.model || entry.request.model || \"unknown\"\n modelDist[model] = (modelDist[model] || 0) + 1\n\n // Endpoint distribution\n endpointDist[entry.endpoint] = (endpointDist[entry.endpoint] || 0) + 1\n\n // Hourly activity (last 24 hours) - use local time\n const d = new Date(entry.timestamp)\n const y = d.getFullYear()\n const mo = String(d.getMonth() + 1).padStart(2, \"0\")\n const day = String(d.getDate()).padStart(2, \"0\")\n const h = String(d.getHours()).padStart(2, \"0\")\n const hour = `${y}-${mo}-${day}T${h}`\n hourlyActivity[hour] = (hourlyActivity[hour] || 0) + 1\n\n if (entry.response) {\n if (entry.response.success) {\n successCount++\n } else {\n failCount++\n }\n\n totalInput += entry.response.usage.input_tokens\n totalOutput += entry.response.usage.output_tokens\n }\n\n if (entry.durationMs) {\n totalDuration += entry.durationMs\n durationCount++\n }\n }\n\n // Convert hourly activity to sorted array (last 24 entries)\n const recentActivity = Object.entries(hourlyActivity)\n .sort(([a], [b]) => a.localeCompare(b))\n .slice(-24)\n .map(([hour, count]) => ({ hour, count }))\n\n const stats: HistoryStats = {\n totalRequests: entries.length,\n successfulRequests: successCount,\n failedRequests: failCount,\n totalInputTokens: totalInput,\n totalOutputTokens: totalOutput,\n averageDurationMs: durationCount > 0 ? totalDuration / durationCount : 0,\n modelDistribution: modelDist,\n endpointDistribution: endpointDist,\n recentActivity,\n activeSessions: historyState.sessions.size,\n }\n\n statsDirty = false\n cachedStats = stats\n return stats\n}\n\n/** Escape a value for CSV: wrap in quotes if it contains comma, quote, or newline; convert nullish to empty string */\nfunction escapeCsvValue(value: unknown): string {\n if (value === null || value === undefined) return \"\"\n const str = typeof value === \"string\" ? value : JSON.stringify(value)\n if (str.includes(\",\") || str.includes('\"') || str.includes(\"\\n\")) {\n return `\"${str.replaceAll('\"', '\"\"')}\"`\n }\n return str\n}\n\nexport function exportHistory(format: \"json\" | \"csv\" = \"json\"): string {\n if (format === \"json\") {\n return JSON.stringify(\n {\n sessions: Array.from(historyState.sessions.values()),\n entries: historyState.entries,\n },\n null,\n 2,\n )\n }\n\n // CSV format - simplified view\n const headers = [\n \"id\",\n \"session_id\",\n \"timestamp\",\n \"endpoint\",\n \"request_model\",\n \"message_count\",\n \"stream\",\n \"success\",\n \"response_model\",\n \"input_tokens\",\n \"output_tokens\",\n \"duration_ms\",\n \"stop_reason\",\n \"error\",\n ]\n\n const rows = historyState.entries.map((e) => [\n e.id,\n e.sessionId,\n formatLocalTimestamp(e.timestamp),\n e.endpoint,\n e.request.model,\n e.request.messages?.length,\n e.request.stream,\n e.response?.success,\n e.response?.model,\n e.response?.usage.input_tokens,\n e.response?.usage.output_tokens,\n e.durationMs,\n e.response?.stop_reason,\n e.response?.error,\n ])\n\n return [headers.join(\",\"), ...rows.map((r) => r.map((v) => escapeCsvValue(v)).join(\",\"))].join(\"\\n\")\n}\n","/**\n * Centralized graceful shutdown management.\n *\n * Coordinates a 4-phase shutdown sequence:\n * Phase 1 (0s): Stop accepting new requests, drain rate limiter queue\n * Phase 2 (0–Ns): Wait for in-flight requests to complete naturally\n * Phase 3 (N–N+Ms): Fire abort signal, wait for handlers to wrap up\n * Phase 4: Force-close all connections, clean up\n *\n * Phase 2/3 timeouts are configurable via state.shutdownGracefulWait and\n * state.shutdownAbortWait (seconds), set from config.yaml `shutdown` section.\n *\n * Handlers integrate via getShutdownSignal() to detect Phase 3 abort.\n */\n\nimport type { Server } from \"srvx\"\n\nimport consola from \"consola\"\n\nimport type { AdaptiveRateLimiter } from \"./adaptive-rate-limiter\"\nimport type { TuiLogEntry } from \"./tui\"\n\nimport { getAdaptiveRateLimiter } from \"./adaptive-rate-limiter\"\nimport { getRequestContextManager } from \"./context/manager\"\nimport { closeAllClients, getClientCount } from \"./history\"\nimport { state } from \"./state\"\nimport { stopTokenRefresh } from \"./token\"\nimport { tuiLogger } from \"./tui\"\n\n// ============================================================================\n// Configuration constants\n// ============================================================================\n\n/** Polling interval during drain */\nexport const DRAIN_POLL_INTERVAL_MS = 500\n/** Progress log interval during drain */\nexport const DRAIN_PROGRESS_INTERVAL_MS = 5_000\n\n// ============================================================================\n// Module state\n// ============================================================================\n\nlet serverInstance: Server | null = null\nlet _isShuttingDown = false\nlet shutdownResolve: (() => void) | null = null\nlet shutdownAbortController: AbortController | null = null\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/** Check if the server is in shutdown state (used by middleware to reject new requests) */\nexport function getIsShuttingDown(): boolean {\n return _isShuttingDown\n}\n\n/**\n * Get the shutdown abort signal.\n * Returns undefined before shutdown starts. During Phase 1–2 the signal is\n * not aborted; it fires at Phase 3 to tell handlers to wrap up.\n */\nexport function getShutdownSignal(): AbortSignal | undefined {\n return shutdownAbortController?.signal\n}\n\n/**\n * Returns a promise that resolves when the server is shut down via signal.\n * Used by runServer() to keep the async function alive until shutdown.\n */\nexport function waitForShutdown(): Promise<void> {\n return new Promise((resolve) => {\n shutdownResolve = resolve\n })\n}\n\n/** Store the server instance for shutdown */\nexport function setServerInstance(server: Server): void {\n serverInstance = server\n}\n\n// ============================================================================\n// Dependency injection for testing\n// ============================================================================\n\n/** Dependencies that can be injected for testing */\nexport interface ShutdownDeps {\n tracker?: {\n getActiveRequests: () => Array<TuiLogEntry>\n destroy: () => void\n }\n server?: {\n close: (force?: boolean) => Promise<void>\n }\n rateLimiter?: AdaptiveRateLimiter | null\n stopTokenRefreshFn?: () => void\n closeAllClientsFn?: () => void\n getClientCountFn?: () => number\n /** Request context manager (for stopping stale reaper during shutdown) */\n contextManager?: { stopReaper: () => void }\n /** Timing overrides (for testing — avoids real 20s/120s waits) */\n gracefulWaitMs?: number\n abortWaitMs?: number\n drainPollIntervalMs?: number\n drainProgressIntervalMs?: number\n}\n\n// ============================================================================\n// Drain logic\n// ============================================================================\n\n/** Format a summary of active requests for logging */\nexport function formatActiveRequestsSummary(requests: Array<TuiLogEntry>): string {\n const now = Date.now()\n const lines = requests.map((req) => {\n const age = Math.round((now - req.startTime) / 1000)\n const model = req.model || \"unknown\"\n const tags = req.tags?.length ? ` [${req.tags.join(\", \")}]` : \"\"\n return ` ${req.method} ${req.path} ${model} (${req.status}, ${age}s)${tags}`\n })\n return `Waiting for ${requests.length} active request(s):\\n${lines.join(\"\\n\")}`\n}\n\n/**\n * Wait for all active requests to complete, with periodic progress logging.\n * Returns \"drained\" when all requests finish, \"timeout\" if deadline is reached.\n */\nexport async function drainActiveRequests(\n timeoutMs: number,\n tracker: { getActiveRequests: () => Array<TuiLogEntry> },\n opts?: { pollIntervalMs?: number; progressIntervalMs?: number },\n): Promise<\"drained\" | \"timeout\"> {\n const pollInterval = opts?.pollIntervalMs ?? DRAIN_POLL_INTERVAL_MS\n const progressInterval = opts?.progressIntervalMs ?? DRAIN_PROGRESS_INTERVAL_MS\n const deadline = Date.now() + timeoutMs\n let lastProgressLog = 0\n\n while (Date.now() < deadline) {\n const active = tracker.getActiveRequests()\n if (active.length === 0) return \"drained\"\n\n // Log progress periodically\n const now = Date.now()\n if (now - lastProgressLog >= progressInterval) {\n lastProgressLog = now\n consola.info(formatActiveRequestsSummary(active))\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollInterval))\n }\n\n return \"timeout\"\n}\n\n// ============================================================================\n// Graceful shutdown (4 phases)\n// ============================================================================\n\n/**\n * Perform graceful shutdown in 4 phases.\n *\n * @param signal - The signal that triggered shutdown (e.g. \"SIGINT\")\n * @param deps - Optional dependency injection for testing\n */\nexport async function gracefulShutdown(signal: string, deps?: ShutdownDeps): Promise<void> {\n const tracker = deps?.tracker ?? tuiLogger\n const server = deps?.server ?? serverInstance\n const rateLimiter = deps?.rateLimiter !== undefined ? deps.rateLimiter : getAdaptiveRateLimiter()\n const stopRefresh = deps?.stopTokenRefreshFn ?? stopTokenRefresh\n const closeWsClients = deps?.closeAllClientsFn ?? closeAllClients\n const getWsClientCount = deps?.getClientCountFn ?? getClientCount\n\n // Timing (defaults to state values from config, overridable for testing)\n const gracefulWaitMs = deps?.gracefulWaitMs ?? state.shutdownGracefulWait * 1000\n const abortWaitMs = deps?.abortWaitMs ?? state.shutdownAbortWait * 1000\n const drainOpts = {\n pollIntervalMs: deps?.drainPollIntervalMs ?? DRAIN_POLL_INTERVAL_MS,\n progressIntervalMs: deps?.drainProgressIntervalMs ?? DRAIN_PROGRESS_INTERVAL_MS,\n }\n\n // ── Phase 1: Stop accepting new requests ──────────────────────────────\n _isShuttingDown = true\n shutdownAbortController = new AbortController()\n\n consola.info(`Received ${signal}, shutting down gracefully...`)\n\n // Stop stale context reaper before drain (avoid racing with drain logic)\n try {\n const ctxMgr = deps?.contextManager ?? getRequestContextManager()\n ctxMgr.stopReaper()\n } catch {\n // Context manager may not be initialized in tests or early shutdown\n }\n\n // Stop background services\n stopRefresh()\n\n const wsClients = getWsClientCount()\n if (wsClients > 0) {\n closeWsClients()\n consola.info(`Disconnected ${wsClients} WebSocket client(s)`)\n }\n\n // Drain rate limiter queue immediately\n if (rateLimiter) {\n const rejected = rateLimiter.rejectQueued()\n if (rejected > 0) {\n consola.info(`Rejected ${rejected} queued request(s) from rate limiter`)\n }\n }\n\n // Stop listening for new connections (but keep existing ones alive).\n // Do NOT await — server.close(false) stops accepting new connections immediately,\n // but the returned promise won't resolve until all existing connections end.\n // Upgraded WebSocket connections (even after close handshake) keep the HTTP\n // server open indefinitely, which would block the entire shutdown sequence.\n if (server) {\n server.close(false).catch((error: unknown) => {\n consola.error(\"Error stopping listener:\", error)\n })\n consola.info(\"Stopped accepting new connections\")\n }\n\n // ── Phase 2: Wait for natural completion ──────────────────────────────\n const activeCount = tracker.getActiveRequests().length\n if (activeCount > 0) {\n consola.info(`Phase 2: Waiting up to ${gracefulWaitMs / 1000}s for ${activeCount} active request(s)...`)\n\n try {\n const phase2Result = await drainActiveRequests(gracefulWaitMs, tracker, drainOpts)\n if (phase2Result === \"drained\") {\n consola.info(\"All requests completed naturally\")\n finalize(tracker)\n return\n }\n } catch (error) {\n consola.error(\"Error during Phase 2 drain:\", error)\n }\n\n // ── Phase 3: Abort signal + extended wait ─────────────────────────────\n const remaining = tracker.getActiveRequests().length\n consola.info(\n `Phase 3: Sending abort signal to ${remaining} remaining request(s), `\n + `waiting up to ${abortWaitMs / 1000}s...`,\n )\n\n shutdownAbortController.abort()\n\n try {\n const phase3Result = await drainActiveRequests(abortWaitMs, tracker, drainOpts)\n if (phase3Result === \"drained\") {\n consola.info(\"All requests completed after abort signal\")\n finalize(tracker)\n return\n }\n } catch (error) {\n consola.error(\"Error during Phase 3 drain:\", error)\n }\n\n // ── Phase 4: Force close ────────────────────────────────────────────\n const forceRemaining = tracker.getActiveRequests().length\n consola.warn(`Phase 4: Force-closing ${forceRemaining} remaining request(s)`)\n\n if (server) {\n try {\n await server.close(true)\n } catch (error) {\n consola.error(\"Error force-closing server:\", error)\n }\n }\n }\n\n finalize(tracker)\n}\n\n/** Final cleanup after drain/force-close */\nfunction finalize(tracker: { destroy: () => void }): void {\n tracker.destroy()\n consola.info(\"Shutdown complete\")\n shutdownResolve?.()\n}\n\n// ============================================================================\n// Signal handlers\n// ============================================================================\n\n/** Setup process signal handlers for graceful shutdown */\nexport function setupShutdownHandlers(): void {\n const handler = (signal: string) => {\n if (_isShuttingDown) {\n // Second signal = force exit immediately\n consola.warn(\"Second signal received, forcing immediate exit\")\n process.exit(1)\n }\n gracefulShutdown(signal).catch((error: unknown) => {\n consola.error(\"Fatal error during shutdown:\", error)\n shutdownResolve?.() // Ensure waitForShutdown resolves even on error\n process.exit(1)\n })\n }\n process.on(\"SIGINT\", () => handler(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handler(\"SIGTERM\"))\n}\n\n// ============================================================================\n// Testing utilities\n// ============================================================================\n\n/** Reset module state (for tests only) */\nexport function _resetShutdownState(): void {\n _isShuttingDown = false\n shutdownResolve = null\n shutdownAbortController = null\n serverInstance = null\n}\n","/** TUI logger - manages request log entries independently of rendering */\n\nimport { state } from \"~/lib/state\"\nimport { generateId } from \"~/lib/utils\"\n\nimport type { RequestUpdate, TuiLogEntry, TuiRenderer } from \"./types\"\n\ninterface StartRequestOptions {\n method: string\n path: string\n model?: string\n isHistoryAccess?: boolean\n requestBodySize?: number\n}\n\n/** Outcome passed to finishRequest to mark a request as completed or failed */\nexport interface RequestOutcome {\n statusCode?: number\n error?: string\n usage?: { inputTokens: number; outputTokens: number }\n}\n\nexport class TuiLogger {\n private entries: Map<string, TuiLogEntry> = new Map()\n private renderer: TuiRenderer | null = null\n private completedQueue: Array<TuiLogEntry> = []\n private completedTimeouts: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private historySize = 5\n private completedDisplayMs = 2000\n\n setRenderer(renderer: TuiRenderer | null): void {\n this.renderer = renderer\n }\n\n setOptions(options: { historySize?: number; completedDisplayMs?: number }): void {\n if (options.historySize !== undefined) {\n this.historySize = options.historySize\n }\n if (options.completedDisplayMs !== undefined) {\n this.completedDisplayMs = options.completedDisplayMs\n }\n }\n\n /**\n * Start tracking a new request\n * Returns the log entry ID\n */\n startRequest(options: StartRequestOptions): string {\n const id = generateId()\n const entry: TuiLogEntry = {\n id,\n method: options.method,\n path: options.path,\n model: options.model,\n startTime: Date.now(),\n status: \"executing\",\n isHistoryAccess: options.isHistoryAccess,\n requestBodySize: options.requestBodySize,\n }\n\n this.entries.set(id, entry)\n this.renderer?.onRequestStart(entry)\n\n return id\n }\n\n /**\n * Update request status\n */\n updateRequest(id: string, update: RequestUpdate): void {\n const entry = this.entries.get(id)\n if (!entry) return\n\n if (update.model !== undefined) {\n entry.model = update.model\n const multiplier = state.modelIndex.get(update.model)?.billing?.multiplier\n if (multiplier !== undefined) entry.multiplier = multiplier\n }\n if (update.clientModel !== undefined) entry.clientModel = update.clientModel\n if (update.status !== undefined) entry.status = update.status\n if (update.statusCode !== undefined) entry.statusCode = update.statusCode\n if (update.durationMs !== undefined) entry.durationMs = update.durationMs\n if (update.inputTokens !== undefined) entry.inputTokens = update.inputTokens\n if (update.outputTokens !== undefined) entry.outputTokens = update.outputTokens\n if (update.cacheReadInputTokens !== undefined) entry.cacheReadInputTokens = update.cacheReadInputTokens\n if (update.cacheCreationInputTokens !== undefined) entry.cacheCreationInputTokens = update.cacheCreationInputTokens\n if (update.estimatedTokens !== undefined) entry.estimatedTokens = update.estimatedTokens\n if (update.error !== undefined) entry.error = update.error\n if (update.queuePosition !== undefined) entry.queuePosition = update.queuePosition\n if (update.queueWaitMs !== undefined) entry.queueWaitMs = update.queueWaitMs\n if (update.streamBytesIn !== undefined) entry.streamBytesIn = update.streamBytesIn\n if (update.streamEventsIn !== undefined) entry.streamEventsIn = update.streamEventsIn\n if (update.streamBlockType !== undefined) entry.streamBlockType = update.streamBlockType\n if (update.tags) {\n entry.tags ??= []\n for (const tag of update.tags) {\n if (!entry.tags.includes(tag)) entry.tags.push(tag)\n }\n }\n\n this.renderer?.onRequestUpdate(id, update)\n }\n\n /**\n * Mark a request as finished (completed or failed).\n *\n * Determines final status from the outcome:\n * - `error` present → \"error\"\n * - `statusCode` in success range (101, 2xx, 3xx) → \"completed\"\n * - `statusCode` outside success range → \"error\"\n * - Neither → \"completed\" (e.g. streaming success with no HTTP status)\n *\n * Safe to call multiple times for the same ID — second call is a no-op.\n * This eliminates the dual-path race between middleware and context consumer.\n */\n finishRequest(id: string, outcome: RequestOutcome): void {\n const entry = this.entries.get(id)\n if (!entry) return\n\n // Determine final status\n if (outcome.error) {\n entry.status = \"error\"\n entry.error = outcome.error\n } else if (outcome.statusCode !== undefined) {\n const sc = outcome.statusCode\n entry.status = sc === 101 || (sc >= 200 && sc < 400) ? \"completed\" : \"error\"\n } else {\n entry.status = \"completed\"\n }\n\n if (outcome.statusCode !== undefined) entry.statusCode = outcome.statusCode\n if (outcome.usage) {\n entry.inputTokens = outcome.usage.inputTokens\n entry.outputTokens = outcome.usage.outputTokens\n }\n entry.durationMs = Date.now() - entry.startTime\n\n this.renderer?.onRequestComplete(entry)\n this.moveToCompleted(id, entry)\n }\n\n // ─── Completed queue management ───\n\n /** Move entry from active to completed queue with auto-cleanup */\n private moveToCompleted(id: string, entry: TuiLogEntry): void {\n this.entries.delete(id)\n this.completedQueue.push(entry)\n\n // Trim queue to max history size\n while (this.completedQueue.length > this.historySize) {\n const removed = this.completedQueue.shift()\n if (removed) {\n const timeoutId = this.completedTimeouts.get(removed.id)\n if (timeoutId) {\n clearTimeout(timeoutId)\n this.completedTimeouts.delete(removed.id)\n }\n }\n }\n\n // Schedule removal from display after delay\n const timeoutId = setTimeout(() => {\n const idx = this.completedQueue.indexOf(entry)\n if (idx !== -1) {\n this.completedQueue.splice(idx, 1)\n }\n this.completedTimeouts.delete(id)\n }, this.completedDisplayMs)\n this.completedTimeouts.set(id, timeoutId)\n }\n\n // ─── Queries ───\n\n /**\n * Get all active entries\n */\n getActiveRequests(): Array<TuiLogEntry> {\n return Array.from(this.entries.values())\n }\n\n /**\n * Get recently completed entries\n */\n getCompletedRequests(): Array<TuiLogEntry> {\n return [...this.completedQueue]\n }\n\n /**\n * Get entry by ID (only active/in-flight entries)\n */\n getRequest(id: string): TuiLogEntry | undefined {\n return this.entries.get(id)\n }\n\n /**\n * Clear all entries and pending timeouts\n */\n clear(): void {\n this.entries.clear()\n this.completedQueue = []\n // Clear all pending timeouts\n for (const timeoutId of this.completedTimeouts.values()) {\n clearTimeout(timeoutId)\n }\n this.completedTimeouts.clear()\n }\n\n /**\n * Destroy the logger and its renderer.\n * Called during graceful shutdown to clean up terminal state (e.g. footer).\n */\n destroy(): void {\n this.clear()\n this.renderer?.destroy()\n this.renderer = null\n }\n}\n\n/** Singleton instance */\nexport const tuiLogger = new TuiLogger()\n","/**\n * TUI logger middleware — tracks every HTTP request in the TUI.\n *\n * Lifecycle:\n * startRequest() → [handler runs] → finishRequest()\n *\n * Completion ownership:\n * - Streaming API requests (SSE): consumer calls finishRequest asynchronously\n * when the stream ends, with correct duration and full usage data.\n * Middleware detects SSE content-type and skips finishRequest.\n * - Non-streaming API requests: consumer calls finishRequest synchronously\n * during await next(). Middleware's subsequent finishRequest is a no-op.\n * - Simple routes (/models, /history, etc.): no consumer — middleware calls\n * finishRequest after await next() with c.res.status.\n * - WebSocket upgrades: middleware calls finishRequest with status 101.\n *\n * finishRequest is idempotent — second call for the same ID is a no-op.\n */\n\nimport type { Context, MiddlewareHandler, Next } from \"hono\"\n\nimport { getErrorMessage, HTTPError } from \"~/lib/error\"\nimport { getIsShuttingDown } from \"~/lib/shutdown\"\n\nimport { tuiLogger } from \"./tracker\"\n\nexport function tuiMiddleware(): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n // Reject new requests during shutdown\n if (getIsShuttingDown()) {\n return c.json({ type: \"error\", error: { type: \"server_error\", message: \"Server is shutting down\" } }, 503)\n }\n\n const method = c.req.method\n const path = c.req.path\n\n // Capture request body size from Content-Length header\n const contentLength = c.req.header(\"content-length\")\n const requestBodySize = contentLength ? Number.parseInt(contentLength, 10) : undefined\n\n const tuiLogId = tuiLogger.startRequest({\n method,\n path,\n model: \"\",\n isHistoryAccess: path.startsWith(\"/history\"),\n requestBodySize,\n })\n\n // Store tracking ID in context for handlers/consumers to use\n c.set(\"tuiLogId\", tuiLogId)\n\n // Detect WebSocket upgrade before calling next() — after the upgrade,\n // c.res may not have a meaningful status.\n const isWebSocketUpgrade = c.req.header(\"upgrade\")?.toLowerCase() === \"websocket\"\n\n try {\n await next()\n\n // WebSocket: treat as 101 regardless of c.res.status\n // (Bun returns 200, Node.js handles upgrade outside Hono)\n if (isWebSocketUpgrade) {\n tuiLogger.finishRequest(tuiLogId, { statusCode: 101 })\n return\n }\n\n // Streaming (SSE): the consumer handles completion asynchronously when\n // the stream finishes — with correct duration and full usage data.\n // Calling finishRequest here would finish prematurely (before the stream\n // ends, without usage).\n const contentType = c.res.headers.get(\"content-type\")\n if (contentType?.includes(\"text/event-stream\")) return\n\n // Non-streaming: finish the request with the actual HTTP status.\n // If a consumer already finished it synchronously during next(), this is a no-op.\n tuiLogger.finishRequest(tuiLogId, { statusCode: c.res.status })\n } catch (error) {\n tuiLogger.finishRequest(tuiLogId, {\n error: getErrorMessage(error),\n statusCode: error instanceof HTTPError ? error.status : undefined,\n })\n throw error\n }\n }\n}\n","/** Pure formatting functions for TUI display */\n\nimport pc from \"picocolors\"\n\nimport type { TuiLogEntry } from \"./types\"\n\nexport function formatTime(date: Date = new Date()): string {\n const h = String(date.getHours()).padStart(2, \"0\")\n const m = String(date.getMinutes()).padStart(2, \"0\")\n const s = String(date.getSeconds()).padStart(2, \"0\")\n return `${h}:${m}:${s}`\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n return `${(ms / 1000).toFixed(1)}s`\n}\n\nexport function formatNumber(n: number): string {\n if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}K`\n return String(n)\n}\n\nexport function formatBytes(n: number): string {\n if (n >= 1048576) return `${(n / 1048576).toFixed(1)}MB`\n if (n >= 1024) return `${(n / 1024).toFixed(1)}KB`\n return `${n}B`\n}\n\n/** Format streaming metrics for footer display: ↓12.3KB 42ev [thinking] */\nexport function formatStreamInfo(req: TuiLogEntry): string {\n if (req.streamBytesIn === undefined) return \"\"\n const bytes = formatBytes(req.streamBytesIn)\n const events = req.streamEventsIn ?? 0\n const blockType = req.streamBlockType ? ` [${req.streamBlockType}]` : \"\"\n return ` ↓${bytes} ${events}ev${blockType}`\n}\n\n/** Format token counts with colors: dim for cache read, cyan for cache creation */\nexport function formatTokens(input?: number, output?: number, cacheRead?: number, cacheCreation?: number): string {\n if (input === undefined && output === undefined) return \"-\"\n let result = `↑${formatNumber(input ?? 0)}`\n if (cacheRead) result += pc.dim(`+${formatNumber(cacheRead)}`)\n if (cacheCreation) result += pc.cyan(`+${formatNumber(cacheCreation)}`)\n result += ` ↓${formatNumber(output ?? 0)}`\n return result\n}\n","/**\n * Console renderer - simple single-line output for each completed request.\n * Replaces Hono's default logger with cleaner, more informative output.\n */\n\nimport consola from \"consola\"\nimport pc from \"picocolors\"\n\nimport type { RequestUpdate, TuiLogEntry, TuiRenderer } from \"./types\"\n\nimport { formatBytes, formatDuration, formatStreamInfo, formatTime, formatTokens } from \"./format\"\n\n// ANSI escape codes for cursor control\nconst CLEAR_LINE = \"\\x1b[2K\\r\"\n\n/**\n * Console renderer that shows request lifecycle with apt-get style footer\n *\n * Log format:\n * - Start: [....] HH:MM:SS METHOD /path model-name (debug only, dim)\n * - Streaming: [<-->] HH:MM:SS METHOD /path model-name streaming... (dim)\n * - Complete: [ OK ] HH:MM:SS 200 POST /path model-name (3x) 1.2s ↑12.3KB ↓45.6KB ↑1.5K+300 ↓500 (colored)\n * - Error: [FAIL] HH:MM:SS 500 POST /path model-name (3x) 1.2s: error message (red)\n *\n * Color scheme for completed requests:\n * - Prefix: green (success) / red (error)\n * - Time: dim\n * - Method: white\n * - Path: white\n * - Model: magenta\n * - Status: green (success) / red (error)\n * - Duration: yellow\n * - Tokens: cyan (req/res info)\n *\n * Features:\n * - Start lines only shown in debug mode (--verbose)\n * - Streaming lines are dim (less important)\n * - /history API requests are always dim\n * - Sticky footer shows active requests with model and elapsed time\n * - Footer auto-refreshes every second while requests are in-flight\n * - Intercepts consola output to properly handle footer\n */\nexport class ConsoleRenderer implements TuiRenderer {\n private activeRequests: Map<string, TuiLogEntry> = new Map()\n private showActive: boolean\n private footerVisible = false\n private isTTY: boolean\n private originalReporters: Array<unknown> = []\n private footerTimer: ReturnType<typeof setInterval> | null = null\n\n constructor(options?: { showActive?: boolean }) {\n this.showActive = options?.showActive ?? true\n this.isTTY = process.stdout.isTTY\n\n // Install consola reporter that coordinates with footer\n this.installConsolaReporter()\n }\n\n /**\n * Install a custom consola reporter that coordinates with footer\n */\n private installConsolaReporter(): void {\n // Save original reporters\n this.originalReporters = [...consola.options.reporters]\n\n // Create a wrapper reporter that handles footer\n const footerAwareReporter = {\n log: (logObj: { args: Array<unknown>; type: string }) => {\n // Clear footer before any consola output\n this.clearFooterForLog()\n\n // Format and print the log message\n // Trim trailing whitespace/newlines to prevent blank lines\n // (e.g. citty's runMain passes \"\\n\" as a separate arg on errors)\n const message = logObj.args\n .map((arg) => {\n if (typeof arg === \"string\") return arg\n // Error objects have non-enumerable properties, JSON.stringify gives \"{}\"\n if (arg instanceof Error) {\n return arg.stack ?? arg.message\n }\n return JSON.stringify(arg)\n })\n .join(\" \")\n .trimEnd()\n\n // Use appropriate formatting based on log type\n const prefix = this.getLogPrefix(logObj.type)\n if (prefix) {\n process.stdout.write(`${prefix} ${message}\\n`)\n } else {\n process.stdout.write(`${message}\\n`)\n }\n\n // Re-render footer after log\n this.renderFooter()\n },\n }\n\n consola.setReporters([footerAwareReporter])\n }\n\n /**\n * Get log prefix based on log type (includes timestamp)\n */\n private getLogPrefix(type: string): string {\n const time = pc.dim(formatTime())\n\n switch (type) {\n case \"error\":\n case \"fatal\": {\n return `${pc.red(\"[ERR ]\")} ${time}`\n }\n case \"warn\": {\n return `${pc.yellow(\"[WARN]\")} ${time}`\n }\n case \"info\": {\n return `${pc.cyan(\"[INFO]\")} ${time}`\n }\n case \"success\": {\n return `${pc.green(\"[SUCC]\")} ${time}`\n }\n case \"debug\": {\n return `${pc.gray(\"[DBG ]\")} ${time}`\n }\n default: {\n return time\n }\n }\n }\n\n // ─── Footer (active request status line) ───\n\n /**\n * Build footer text showing per-request model and elapsed time.\n *\n * Single: [<-->] POST /v1/messages claude-sonnet-4 3.2s ↓12.3KB 42ev [thinking]\n * Multi: [<-->] claude-sonnet-4 5.2s ↓456KB 120ev [thinking] | claude-haiku-3 0.3s\n */\n private getFooterText(): string {\n const activeCount = this.activeRequests.size\n if (activeCount === 0) return \"\"\n\n const now = Date.now()\n\n if (activeCount === 1) {\n const req = this.activeRequests.values().next().value!\n const elapsed = formatDuration(now - req.startTime)\n const model = req.model ? ` ${req.model}` : \"\"\n const streamInfo = formatStreamInfo(req)\n return pc.dim(`[<-->] ${req.method} ${req.path}${model} ${elapsed}${streamInfo}`)\n }\n\n // Multiple requests — compact: model elapsed stream-info | model elapsed stream-info\n const items = Array.from(this.activeRequests.values()).map((req) => {\n const elapsed = formatDuration(now - req.startTime)\n const label = req.model || `${req.method} ${req.path}`\n const streamInfo = formatStreamInfo(req)\n return `${label} ${elapsed}${streamInfo}`\n })\n return pc.dim(`[<-->] ${items.join(\" | \")}`)\n }\n\n /**\n * Render footer in-place on current line (no newline)\n * Only works on TTY terminals\n */\n private renderFooter(): void {\n if (!this.isTTY) return\n\n const footerText = this.getFooterText()\n if (footerText) {\n process.stdout.write(CLEAR_LINE + footerText)\n this.footerVisible = true\n } else if (this.footerVisible) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n }\n\n /**\n * Clear footer and prepare for log output\n */\n private clearFooterForLog(): void {\n if (this.footerVisible && this.isTTY) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n }\n\n /** Start periodic footer refresh (every 100ms) to keep elapsed time current */\n private startFooterTimer(): void {\n if (this.footerTimer || !this.isTTY) return\n this.footerTimer = setInterval(() => {\n if (this.activeRequests.size > 0) {\n this.renderFooter()\n } else {\n this.stopFooterTimer()\n }\n }, 100)\n // Don't prevent process exit\n this.footerTimer.unref()\n }\n\n /** Stop periodic footer refresh */\n private stopFooterTimer(): void {\n if (this.footerTimer) {\n clearInterval(this.footerTimer)\n this.footerTimer = null\n }\n }\n\n /**\n * Format a complete log line with colored parts\n *\n * Format: [xxxx] HH:mm:ss <status> <method> <path> <model> (<multiplier>x) <duration> ↑<reqSize> ↓<respSize> ↑<inTokens>+<cache> ↓<outTokens>\n */\n private formatLogLine(parts: {\n prefix: string\n time: string\n method: string\n path: string\n model?: string\n /** Original model name from client (shown when different from resolved model) */\n clientModel?: string\n multiplier?: number\n status?: number\n duration?: string\n requestBodySize?: number\n responseBodySize?: number\n inputTokens?: number\n outputTokens?: number\n cacheReadInputTokens?: number\n cacheCreationInputTokens?: number\n queueWait?: string\n extra?: string\n isError?: boolean\n isDim?: boolean\n }): string {\n const {\n prefix,\n time,\n method,\n path,\n model,\n clientModel,\n multiplier,\n status,\n duration,\n requestBodySize,\n responseBodySize,\n inputTokens,\n outputTokens,\n cacheReadInputTokens,\n cacheCreationInputTokens,\n queueWait,\n extra,\n isError,\n isDim,\n } = parts\n\n if (isDim) {\n const modelPart = model ? ` ${model}` : \"\"\n const extraPart = extra ? ` ${extra}` : \"\"\n return pc.dim(`${prefix} ${time} ${method} ${path}${modelPart}${extraPart}`)\n }\n\n // Colored lines: each part has its own color\n const coloredPrefix = isError ? pc.red(prefix) : pc.green(prefix)\n const coloredTime = pc.dim(time)\n const coloredStatus =\n status !== undefined ?\n isError ? pc.red(String(status))\n : pc.green(String(status))\n : undefined\n const coloredMethod = pc.white(method)\n const coloredPath = pc.white(path)\n\n // Show \"clientModel → model\" when client requested a different model name\n let coloredModel = \"\"\n if (model) {\n coloredModel =\n clientModel && clientModel !== model ?\n ` ${pc.dim(clientModel)} → ${pc.magenta(model)}`\n : pc.magenta(` ${model}`)\n }\n const coloredMultiplier = multiplier !== undefined ? pc.dim(` (${multiplier}x)`) : \"\"\n const coloredDuration = duration ? ` ${pc.yellow(duration)}` : \"\"\n const coloredQueueWait = queueWait ? ` ${pc.dim(`(queued ${queueWait})`)}` : \"\"\n\n // req/resp body sizes with ↑↓ arrows\n let sizeInfo = \"\"\n if (model) {\n const reqSize = requestBodySize !== undefined ? `↑${formatBytes(requestBodySize)}` : \"\"\n const respSize = responseBodySize !== undefined ? `↓${formatBytes(responseBodySize)}` : \"\"\n const parts = [reqSize, respSize].filter(Boolean).join(\" \")\n if (parts) sizeInfo = ` ${pc.dim(parts)}`\n }\n\n // in-tokens/out-tokens (with cache breakdown)\n let tokenInfo = \"\"\n if (model && (inputTokens !== undefined || outputTokens !== undefined)) {\n tokenInfo = ` ${formatTokens(inputTokens, outputTokens, cacheReadInputTokens, cacheCreationInputTokens)}`\n }\n\n let extraPart = \"\"\n if (extra) {\n extraPart = isError ? pc.red(extra) : extra\n }\n\n const statusAndMethod = coloredStatus ? `${coloredStatus} ${coloredMethod}` : coloredMethod\n\n return `${coloredPrefix} ${coloredTime} ${statusAndMethod} ${coloredPath}${coloredModel}${coloredMultiplier}${coloredDuration}${coloredQueueWait}${sizeInfo}${tokenInfo}${extraPart}`\n }\n\n /**\n * Print a log line with proper footer handling\n */\n private printLog(message: string): void {\n this.clearFooterForLog()\n process.stdout.write(message + \"\\n\")\n this.renderFooter()\n }\n\n onRequestStart(request: TuiLogEntry): void {\n this.activeRequests.set(request.id, request)\n this.startFooterTimer()\n\n // Only show start line in debug mode (consola.level >= 5)\n if (this.showActive && consola.level >= 5) {\n const message = this.formatLogLine({\n prefix: \"[....]\",\n time: formatTime(),\n method: request.method,\n path: request.path,\n model: request.model,\n extra:\n request.queuePosition !== undefined && request.queuePosition > 0 ? `[q#${request.queuePosition}]` : undefined,\n isDim: true,\n })\n this.printLog(message)\n }\n }\n\n onRequestUpdate(id: string, update: RequestUpdate): void {\n const request = this.activeRequests.get(id)\n if (!request) return\n\n Object.assign(request, update)\n }\n\n onRequestComplete(request: TuiLogEntry): void {\n this.activeRequests.delete(request.id)\n\n // Stop timer when no more active requests\n if (this.activeRequests.size === 0) {\n this.stopFooterTimer()\n }\n\n // Skip completed log line for history access (only errors are shown)\n if (request.isHistoryAccess && request.status !== \"error\") {\n this.renderFooter()\n return\n }\n\n const status = request.statusCode\n const isError = request.status === \"error\" || (status !== undefined && status >= 400)\n\n // Only show queue wait if it's significant (> 100ms)\n const queueWait = request.queueWaitMs && request.queueWaitMs > 100 ? formatDuration(request.queueWaitMs) : undefined\n\n // Build extra text from tags and error\n // Tags are supplementary metadata — dim the entire group\n const tagStr = !isError && request.tags?.length ? pc.dim(` (${request.tags.join(\", \")})`) : \"\"\n const errorStr = isError && request.error ? `: ${request.error}` : \"\"\n const extra = tagStr + errorStr || undefined\n\n const message = this.formatLogLine({\n prefix: isError ? \"[FAIL]\" : \"[ OK ]\",\n time: formatTime(),\n method: request.method,\n path: request.path,\n model: request.model,\n clientModel: request.clientModel,\n multiplier: request.multiplier,\n status,\n duration: formatDuration(request.durationMs ?? 0),\n queueWait,\n requestBodySize: request.requestBodySize,\n responseBodySize: request.streamBytesIn,\n inputTokens: request.inputTokens,\n outputTokens: request.outputTokens,\n cacheReadInputTokens: request.cacheReadInputTokens,\n cacheCreationInputTokens: request.cacheCreationInputTokens,\n extra,\n isError,\n })\n this.printLog(message)\n }\n\n destroy(): void {\n this.stopFooterTimer()\n if (this.footerVisible && this.isTTY) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n this.activeRequests.clear()\n\n // Restore original reporters\n if (this.originalReporters.length > 0) {\n consola.setReporters(this.originalReporters as Parameters<typeof consola.setReporters>[0])\n }\n }\n}\n","/** TUI module exports */\n\nexport { tuiMiddleware } from \"./middleware\"\nexport { tuiLogger } from \"./tracker\"\nexport type { RequestStatus, RequestUpdate, TuiLogEntry, TuiOptions, TuiRenderer } from \"./types\"\n\nimport type { TuiOptions } from \"./types\"\n\nimport { ConsoleRenderer } from \"./console-renderer\"\nimport { tuiLogger } from \"./tracker\"\n\n/** Singleton renderer instance (created once, used for both logging and request tracking) */\nlet renderer: ConsoleRenderer | null = null\n\n/**\n * Initialize the consola reporter for unified log formatting.\n * This should be called as early as possible to capture all logs.\n * Does NOT set up request tracking - call initTuiLogger() for that.\n *\n * @param forceEnable - Force enable even if not TTY (useful for consistent log format)\n */\nexport function initConsolaReporter(forceEnable = true): void {\n if (!renderer && (forceEnable || process.stdout.isTTY)) {\n renderer = new ConsoleRenderer()\n }\n}\n\n/**\n * Initialize request tracking with the TUI renderer.\n * Should be called after initConsolaReporter() and before handling requests.\n */\nexport function initTuiLogger(options?: TuiOptions): void {\n if (renderer) {\n tuiLogger.setRenderer(renderer)\n }\n\n if (options?.historySize !== undefined || options?.completedDisplayMs !== undefined) {\n tuiLogger.setOptions({\n historySize: options.historySize,\n completedDisplayMs: options.completedDisplayMs,\n })\n }\n}\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\n\n/**\n * Get Claude Code version from package.json\n */\nfunction getClaudeCodeVersion(cliPath: string): string | null {\n try {\n const packageJsonPath = join(dirname(cliPath), \"package.json\")\n if (!existsSync(packageJsonPath)) return null\n\n const packageJson: unknown = JSON.parse(readFileSync(packageJsonPath, \"utf8\"))\n if (\n typeof packageJson === \"object\"\n && packageJson !== null\n && \"version\" in packageJson\n && typeof packageJson.version === \"string\"\n ) {\n return packageJson.version\n }\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Search volta tools directory for Claude Code\n */\nfunction findInVoltaTools(voltaHome: string): Array<string> {\n const paths: Array<string> = []\n\n // Check volta packages directory (npm install -g @anthropic-ai/claude-code)\n const packagesPath = join(\n voltaHome,\n \"tools\",\n \"image\",\n \"packages\",\n \"@anthropic-ai\",\n \"claude-code\",\n \"lib\",\n \"node_modules\",\n \"@anthropic-ai\",\n \"claude-code\",\n \"cli.js\",\n )\n if (existsSync(packagesPath)) {\n paths.push(packagesPath)\n }\n\n // Check volta node tools directory (older installation method)\n const toolsDir = join(voltaHome, \"tools\", \"image\", \"node\")\n if (existsSync(toolsDir)) {\n try {\n for (const version of readdirSync(toolsDir)) {\n const claudePath = join(toolsDir, version, \"lib\", \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\")\n if (existsSync(claudePath)) {\n paths.push(claudePath)\n }\n }\n } catch {\n // Ignore errors reading directory\n }\n }\n\n return paths\n}\n\n/**\n * Find all Claude Code CLI paths by checking common locations\n */\nfunction findAllClaudeCodePaths(): Array<string> {\n const possiblePaths: Array<string> = []\n const home = process.env.HOME || \"\"\n\n // Check volta installation\n const voltaHome = process.env.VOLTA_HOME || join(home, \".volta\")\n if (existsSync(voltaHome)) {\n possiblePaths.push(...findInVoltaTools(voltaHome))\n }\n\n // Check npm global installation\n const npmPrefix = process.env.npm_config_prefix\n if (npmPrefix) {\n possiblePaths.push(join(npmPrefix, \"lib\", \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Check common global npm paths\n const globalPaths = [\n join(home, \".npm-global\", \"lib\", \"node_modules\"),\n \"/usr/local/lib/node_modules\",\n \"/usr/lib/node_modules\",\n ]\n\n for (const base of globalPaths) {\n possiblePaths.push(join(base, \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Check bun global installation\n const bunGlobal = join(home, \".bun\", \"install\", \"global\")\n if (existsSync(bunGlobal)) {\n possiblePaths.push(join(bunGlobal, \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Return all existing paths (deduplicated)\n return [...new Set(possiblePaths.filter((p) => existsSync(p)))]\n}\n\nexport const listClaudeCode = defineCommand({\n meta: {\n name: \"list-claude-code\",\n description: \"List all locally installed Claude Code versions\",\n },\n run() {\n const installations = findAllClaudeCodePaths()\n\n if (installations.length === 0) {\n consola.info(\"No Claude Code installations found\")\n consola.info(\"Searched in: volta, npm global, bun global\")\n return\n }\n\n consola.info(`Found ${installations.length} Claude Code installation(s):`)\n\n for (const [i, path] of installations.entries()) {\n const version = getClaudeCodeVersion(path) ?? \"unknown\"\n consola.info(` ${i + 1}. v${version} ${path}`)\n }\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"./lib/config/paths\"\n\nexport async function runLogout(): Promise<void> {\n try {\n await fs.unlink(PATHS.GITHUB_TOKEN_PATH)\n consola.success(\"Logged out successfully. GitHub token removed.\")\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n consola.info(\"No token found. Already logged out.\")\n } else {\n consola.error(\"Failed to remove token:\", error)\n throw error\n }\n }\n}\n\nexport const logout = defineCommand({\n meta: {\n name: \"logout\",\n description: \"Remove stored GitHub token and log out\",\n },\n run() {\n return runLogout()\n },\n})\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport { existsSync, promises as fsPromises } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport invariant from \"tiny-invariant\"\n\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { cacheVSCodeVersion } from \"./lib/copilot-api\"\nimport { cacheModels } from \"./lib/models/client\"\nimport { state } from \"./lib/state\"\nimport { initTokenManagers } from \"./lib/token\"\n\n/**\n * Write Claude Code configuration files for use with Copilot API.\n * Creates/updates:\n * - $HOME/.claude.json - Sets hasCompletedOnboarding: true\n * - $HOME/.claude/settings.json - Sets env variables for Copilot API\n */\nexport async function writeClaudeCodeConfig(serverUrl: string, model: string, smallModel: string): Promise<void> {\n const home = homedir()\n const claudeJsonPath = join(home, \".claude.json\")\n const claudeDir = join(home, \".claude\")\n const settingsPath = join(claudeDir, \"settings.json\")\n\n // Ensure .claude directory exists\n if (!existsSync(claudeDir)) {\n await fsPromises.mkdir(claudeDir, { recursive: true })\n consola.info(`Created directory: ${claudeDir}`)\n }\n\n // Update $HOME/.claude.json\n let claudeJson: Record<string, unknown> = {}\n if (existsSync(claudeJsonPath)) {\n try {\n const buffer = await fsPromises.readFile(claudeJsonPath)\n claudeJson = JSON.parse(buffer.toString()) as Record<string, unknown>\n } catch {\n consola.warn(`Failed to parse ${claudeJsonPath}, creating new file`)\n }\n }\n claudeJson.hasCompletedOnboarding = true\n await fsPromises.writeFile(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + \"\\n\")\n consola.success(`Updated ${claudeJsonPath}`)\n\n // Update $HOME/.claude/settings.json\n let settings: Record<string, unknown> = {}\n if (existsSync(settingsPath)) {\n try {\n const buffer = await fsPromises.readFile(settingsPath)\n settings = JSON.parse(buffer.toString()) as Record<string, unknown>\n } catch {\n consola.warn(`Failed to parse ${settingsPath}, creating new file`)\n }\n }\n\n // Set env configuration\n settings.env = {\n ...(settings.env as Record<string, string> | undefined),\n ANTHROPIC_BASE_URL: serverUrl,\n ANTHROPIC_AUTH_TOKEN: \"copilot-api\",\n ANTHROPIC_MODEL: model,\n ANTHROPIC_DEFAULT_SONNET_MODEL: model,\n ANTHROPIC_SMALL_FAST_MODEL: smallModel,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: smallModel,\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n CLAUDE_CODE_ENABLE_TELEMETRY: \"0\",\n }\n\n await fsPromises.writeFile(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\")\n consola.success(`Updated ${settingsPath}`)\n\n consola.box(\n `Claude Code configured!\\n\\n`\n + `Model: ${model}\\n`\n + `Small Model: ${smallModel}\\n`\n + `API URL: ${serverUrl}\\n\\n`\n + `Run 'claude' to start Claude Code.`,\n )\n}\n\ninterface SetupClaudeCodeOptions {\n port: number\n host?: string\n model?: string\n smallModel?: string\n accountType: \"individual\" | \"business\" | \"enterprise\"\n githubToken?: string\n verbose: boolean\n}\n\nexport async function runSetupClaudeCode(options: SetupClaudeCodeOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n\n // Authenticate and fetch models\n await ensurePaths()\n await cacheVSCodeVersion()\n await initTokenManagers({ cliToken: options.githubToken })\n await cacheModels()\n\n invariant(state.models, \"Models should be loaded by now\")\n const availableModelIds = state.models.data.map((m) => m.id)\n\n let selectedModel: string\n let selectedSmallModel: string\n\n if (options.model && options.smallModel) {\n // Validate the provided models exist\n if (!availableModelIds.includes(options.model)) {\n consola.error(`Invalid model: ${options.model}\\nAvailable models: ${availableModelIds.join(\", \")}`)\n process.exit(1)\n }\n if (!availableModelIds.includes(options.smallModel)) {\n consola.error(`Invalid small model: ${options.smallModel}\\nAvailable models: ${availableModelIds.join(\", \")}`)\n process.exit(1)\n }\n selectedModel = options.model\n selectedSmallModel = options.smallModel\n } else if (options.model || options.smallModel) {\n consola.error(\"Both --model and --small-model must be provided together, or neither for interactive selection\")\n process.exit(1)\n } else {\n // Interactive selection\n selectedModel = await consola.prompt(\"Select a model to use with Claude Code\", {\n type: \"select\",\n options: availableModelIds,\n })\n\n selectedSmallModel = await consola.prompt(\"Select a small model to use with Claude Code\", {\n type: \"select\",\n options: availableModelIds,\n })\n }\n\n const displayHost = options.host ?? \"localhost\"\n const serverUrl = `http://${displayHost}:${options.port}`\n\n await writeClaudeCodeConfig(serverUrl, selectedModel, selectedSmallModel)\n}\n\nexport const setupClaudeCode = defineCommand({\n meta: {\n name: \"setup-claude-code\",\n description: \"Setup Claude Code configuration files to use Copilot API as backend\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port the Copilot API server will run on\",\n },\n host: {\n alias: \"H\",\n type: \"string\",\n description: \"Host the Copilot API server will bind to (default: localhost)\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Model to use with Claude Code (skips interactive selection, requires --small-model)\",\n },\n \"small-model\": {\n alias: \"s\",\n type: \"string\",\n description: \"Small/fast model to use with Claude Code (skips interactive selection, requires --model)\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description: \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n },\n run({ args }) {\n return runSetupClaudeCode({\n port: Number.parseInt(args.port, 10),\n host: args.host,\n model: args.model,\n smallModel: args[\"small-model\"],\n accountType: args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\",\n githubToken: args[\"github-token\"],\n verbose: args.verbose,\n })\n },\n})\n","","/**\n * Application configuration: types, YAML loading, and state application.\n *\n * All config types live here as the single source of truth.\n * config.yaml is loaded with mtime-based caching.\n */\n\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { setHistoryMaxEntries } from \"~/lib/history\"\nimport { type CompiledRewriteRule, DEFAULT_MODEL_OVERRIDES, state } from \"~/lib/state\"\n\nimport { PATHS } from \"./paths\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Raw rewrite rule from config.yaml (shared by system_prompt_overrides and rewrite_system_reminders) */\nexport interface RewriteRule {\n from: string\n to: string\n /** Match method: \"line\" = exact line match, \"regex\" = regex on full text. Default: \"regex\". */\n method?: \"line\" | \"regex\"\n}\n\n// ============================================================================\n// Rule Compilation\n// ============================================================================\n\n/** Compile a raw rewrite rule into a CompiledRewriteRule. Returns null for invalid regex. */\nexport function compileRewriteRule(raw: RewriteRule): CompiledRewriteRule | null {\n const method = raw.method ?? \"regex\"\n if (method === \"line\") return { from: raw.from, to: raw.to, method }\n try {\n // Strip leading inline flags (?flags) — merge with base gms flags\n // e.g. \"(?i)pattern\" → pattern \"pattern\", flags \"gmsi\"\n // e.g. \"(?s).*\" → pattern \".*\", flags \"gms\" (s already present)\n let pattern = raw.from\n let flags = \"gms\"\n const inlineMatch = pattern.match(/^\\(\\?([a-z]+)\\)/i)\n if (inlineMatch) {\n pattern = pattern.slice(inlineMatch[0].length)\n // Merge unique flags\n for (const f of inlineMatch[1]) {\n if (!flags.includes(f)) flags += f\n }\n }\n return { from: new RegExp(pattern, flags), to: raw.to, method }\n } catch (err) {\n consola.warn(`[config] Invalid regex in rewrite rule: \"${raw.from}\"`, err)\n return null\n }\n}\n\n/** Compile an array of raw rewrite rules, skipping invalid ones */\nexport function compileRewriteRules(raws: Array<RewriteRule>): Array<CompiledRewriteRule> {\n return raws.map(compileRewriteRule).filter((r): r is CompiledRewriteRule => r !== null)\n}\n\n/** Rate limiter configuration section */\nexport interface RateLimiterConfig {\n /** Seconds to wait before retrying after rate limit error (default: 10) */\n retry_interval?: number\n /** Seconds between requests in rate-limited mode (default: 10) */\n request_interval?: number\n /** Minutes before attempting recovery from rate-limited mode (default: 10) */\n recovery_timeout?: number\n /** Number of consecutive successes needed to recover (default: 5) */\n consecutive_successes?: number\n}\n\n/** Anthropic-specific configuration section */\nexport interface AnthropicConfig {\n /** Rewrite server-side tools (web_search, etc.) to custom tool format (default: true) */\n rewrite_tools?: boolean\n /** Fetch timeout in seconds: request start → HTTP response headers (0 = no timeout) */\n fetch_timeout?: number\n /** Stream idle timeout in seconds: max wait between SSE events (0 = no timeout). Backward compat; prefer top-level stream_idle_timeout. */\n stream_idle_timeout?: number\n /**\n * Remove duplicate tool_use/tool_result pairs (keep last occurrence).\n * - `false` — disabled (default)\n * - `true` or `\"input\"` — match by (tool_name, input)\n * - `\"result\"` — match by (tool_name, input, result)\n */\n dedup_tool_calls?: boolean | \"input\" | \"result\"\n /** Strip injected system-reminder tags from Read tool results */\n truncate_read_tool_result?: boolean\n /**\n * Rewrite system-reminder tags in messages.\n * - `false` — keep all tags unchanged (default)\n * - `true` — remove all system-reminder tags\n * - Array of rewrite rules — first matching rule wins (top-down):\n * - `from`: pattern to match against tag content\n * - `to`: replacement string (supports $0, $1, etc. in regex mode)\n * Empty string = remove the tag. `$0` = keep unchanged.\n * - `method`: `\"regex\"` (default) or `\"line\"`\n */\n rewrite_system_reminders?: boolean | Array<RewriteRule>\n}\n\n/** Shutdown timing configuration section */\nexport interface ShutdownConfig {\n /** Phase 2 timeout in seconds: wait for in-flight requests to complete naturally (default: 60) */\n graceful_wait?: number\n /** Phase 3 timeout in seconds: wait after abort signal for handlers to wrap up (default: 120) */\n abort_wait?: number\n}\n\n/** Application configuration loaded from config.yaml */\nexport interface Config {\n system_prompt_overrides?: Array<RewriteRule>\n system_prompt_prepend?: string\n system_prompt_append?: string\n rate_limiter?: RateLimiterConfig\n anthropic?: AnthropicConfig\n /** Model name overrides: request model → target model */\n model_overrides?: Record<string, string>\n /** Compress old tool_result content before truncating (default: true) */\n compress_tool_results_before_truncate?: boolean\n /** Maximum number of history entries to keep in memory (0 = unlimited, default: 200) */\n history_limit?: number\n /** Shutdown timing configuration */\n shutdown?: ShutdownConfig\n /** Stream idle timeout in seconds for all paths (default: 300, 0 = no timeout). Overrides anthropic.stream_idle_timeout. */\n stream_idle_timeout?: number\n /** Maximum age (seconds) of an active request before stale reaper forces fail (0 = disabled, default: 600) */\n stale_request_max_age?: number\n}\n\n// ============================================================================\n// Config Loading (mtime-cached)\n// ============================================================================\n\nlet cachedConfig: Config | null = null\nlet configLastMtimeMs: number = 0\n/** Time-based debounce: skip stat() if checked recently */\nlet lastStatTimeMs: number = 0\nconst STAT_DEBOUNCE_MS = 2000\n\nexport async function loadConfig(): Promise<Config> {\n try {\n // Debounce: if we already have a cached config and checked recently, skip stat()\n const now = Date.now()\n if (cachedConfig && now - lastStatTimeMs < STAT_DEBOUNCE_MS) {\n return cachedConfig\n }\n\n const stat = await fs.stat(PATHS.CONFIG_YAML)\n lastStatTimeMs = now\n if (cachedConfig && stat.mtimeMs === configLastMtimeMs) {\n return cachedConfig\n }\n const content = await fs.readFile(PATHS.CONFIG_YAML, \"utf8\")\n const { parse } = await import(\"yaml\")\n const parsed = parse(content)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- yaml.parse returns null for empty files\n cachedConfig = (parsed as Config) ?? {}\n configLastMtimeMs = stat.mtimeMs\n return cachedConfig\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return {}\n }\n // Cache the failed mtime to avoid re-parsing the same broken file every request.\n // The user sees one warning per config change, not one per request.\n try {\n const stat = await fs.stat(PATHS.CONFIG_YAML)\n configLastMtimeMs = stat.mtimeMs\n } catch {\n // File disappeared between first stat and this one — ignore\n }\n consola.warn(\"[config] Failed to load config.yaml:\", err)\n return {}\n }\n}\n\n/** Get the mtime of the currently cached config (0 if not loaded) */\nexport function getConfigMtimeMs(): number {\n return configLastMtimeMs\n}\n\n/** Exposed for testing: reset the mtime cache */\nexport function resetConfigCache(): void {\n cachedConfig = null\n configLastMtimeMs = 0\n lastStatTimeMs = 0\n}\n\n// ============================================================================\n// Config → State Application (hot-reloadable)\n// ============================================================================\n\nlet hasApplied = false\nlet lastAppliedMtimeMs = 0\n\n/**\n * Load config.yaml and apply all hot-reloadable settings to global state.\n *\n * Scalar fields: only overridden when explicitly present in config (deleted keys keep current runtime value).\n * Collection fields (model_overrides, rewrite_system_reminders array): entire replacement when present.\n *\n * Safe to call per-request — loadConfig() is mtime-cached, so unchanged config\n * only costs one stat() syscall.\n *\n * NOT hot-reloaded: rate_limiter (stateful singleton initialized at startup).\n */\nexport async function applyConfigToState(): Promise<Config> {\n const config = await loadConfig()\n\n // Anthropic settings (scalar: override only when present)\n if (config.anthropic) {\n const a = config.anthropic\n if (a.rewrite_tools !== undefined) state.rewriteAnthropicTools = a.rewrite_tools\n if (a.fetch_timeout !== undefined) state.fetchTimeout = a.fetch_timeout\n // anthropic.stream_idle_timeout is backward compat; top-level takes priority (see below)\n if (a.stream_idle_timeout !== undefined) state.streamIdleTimeout = a.stream_idle_timeout\n if (a.dedup_tool_calls !== undefined) {\n // Normalize: true → \"input\" for backward compatibility, false → false\n state.dedupToolCalls = a.dedup_tool_calls === true ? \"input\" : a.dedup_tool_calls\n }\n if (a.truncate_read_tool_result !== undefined) state.truncateReadToolResult = a.truncate_read_tool_result\n if (a.rewrite_system_reminders !== undefined) {\n // Collection: entire replacement — deleted rules disappear\n if (typeof a.rewrite_system_reminders === \"boolean\") {\n state.rewriteSystemReminders = a.rewrite_system_reminders\n } else if (Array.isArray(a.rewrite_system_reminders)) {\n state.rewriteSystemReminders = compileRewriteRules(a.rewrite_system_reminders)\n }\n }\n }\n\n // System prompt overrides (collection: entire replacement)\n // Use Array.isArray to guard against YAML null (which passes !== undefined but crashes on .length)\n if (Array.isArray(config.system_prompt_overrides)) {\n state.systemPromptOverrides =\n config.system_prompt_overrides.length > 0 ? compileRewriteRules(config.system_prompt_overrides) : []\n }\n\n // Model overrides (collection: entire replacement from defaults + config)\n // User deletes a key → it reverts to default; user adds a key → it overrides default\n if (config.model_overrides) {\n state.modelOverrides = { ...DEFAULT_MODEL_OVERRIDES, ...config.model_overrides }\n }\n\n // Other settings (scalar: override only when present)\n if (config.compress_tool_results_before_truncate !== undefined)\n state.compressToolResultsBeforeTruncate = config.compress_tool_results_before_truncate\n if (config.history_limit !== undefined) {\n state.historyLimit = config.history_limit\n setHistoryMaxEntries(config.history_limit)\n }\n\n // Shutdown timing (scalar: override only when present)\n if (config.shutdown) {\n const s = config.shutdown\n if (s.graceful_wait !== undefined) state.shutdownGracefulWait = s.graceful_wait\n if (s.abort_wait !== undefined) state.shutdownAbortWait = s.abort_wait\n }\n\n // Top-level stream idle timeout (higher priority than anthropic.stream_idle_timeout)\n if (config.stream_idle_timeout !== undefined) state.streamIdleTimeout = config.stream_idle_timeout\n\n // Stale request reaper max age (scalar: override only when present)\n if (config.stale_request_max_age !== undefined) state.staleRequestMaxAge = config.stale_request_max_age\n\n // Log when config actually changes (skip initial startup load)\n const currentMtime = getConfigMtimeMs()\n if (hasApplied && currentMtime !== lastAppliedMtimeMs) {\n consola.info(\"[config] Reloaded config.yaml\")\n }\n hasApplied = true\n lastAppliedMtimeMs = currentMtime\n\n return config\n}\n\n/** Exposed for testing: reset the apply-tracking state */\nexport function resetApplyState(): void {\n hasApplied = false\n lastAppliedMtimeMs = 0\n}\n","/**\n * Error persistence consumer.\n *\n * Subscribes to \"failed\" events on RequestContext and writes structured\n * error files to disk for post-mortem debugging. All data comes from\n * RequestContext (via HistoryEntryData on the event), not from Hono\n * Context — ensuring reliability regardless of whether the HTTP body\n * has been consumed.\n *\n * Output directory: PATHS.ERROR_DIR/{timestamp}_{id}/\n * Files:\n * - meta.json: structured metadata (timestamp, endpoint, model, error, attempts)\n * - request.json: full request payload (messages capped at 50 for size)\n * - response.txt: raw upstream response body (if available)\n * - sse-events.json: recorded SSE events (if streaming request failed mid-stream)\n */\n\nimport { randomBytes } from \"node:crypto\"\nimport * as fs from \"node:fs/promises\"\nimport * as path from \"node:path\"\n\nimport consola from \"consola\"\n\nimport type { RequestContextEvent } from \"./manager\"\nimport type { HistoryEntryData } from \"./request\"\n\nimport { PATHS } from \"~/lib/config/paths\"\n\n// ============================================================================\n// Consumer entry point\n// ============================================================================\n\n/** Handle context events — only acts on \"failed\" */\nexport function handleErrorPersistence(event: RequestContextEvent): void {\n if (event.type !== \"failed\") return\n\n writeErrorEntry(event.entry).catch((err) => {\n consola.debug(`[ErrorPersistence] Failed to write error file: ${err}`)\n })\n}\n\n// ============================================================================\n// File writing\n// ============================================================================\n\n/** Max number of messages to include in request.json (to avoid huge files) */\nconst MAX_MESSAGES_IN_DUMP = 50\n\nasync function writeErrorEntry(entry: HistoryEntryData): Promise<void> {\n // Build compact meta (focused on debugging, not full replay)\n const meta = {\n timestamp: new Date(entry.timestamp).toISOString(),\n id: entry.id,\n endpoint: entry.endpoint,\n durationMs: entry.durationMs,\n request: {\n model: entry.request.model,\n stream: entry.request.stream,\n messageCount: entry.request.messages?.length,\n toolCount: entry.request.tools?.length,\n },\n response: entry.response\n ? {\n success: entry.response.success,\n model: entry.response.model,\n error: entry.response.error,\n status: entry.response.status,\n }\n : undefined,\n truncation: entry.truncation,\n attempts: entry.attempts,\n }\n\n // Collect file entries: [filename, content] pairs\n const files: Array<[string, string]> = [[\"meta.json\", JSON.stringify(meta, null, 2)]]\n\n // Full request payload (from RequestContext — always available)\n if (entry.request) {\n const { messages, ...requestWithoutMessages } = entry.request\n const requestData = {\n ...requestWithoutMessages,\n messageCount: messages?.length,\n // Include messages only for small payloads to avoid huge files\n ...(messages && messages.length <= MAX_MESSAGES_IN_DUMP && { messages }),\n }\n files.push([\"request.json\", JSON.stringify(requestData, null, 2)])\n }\n\n // Raw upstream response body (from HTTPError.responseText, preserved in ResponseData)\n if (entry.response?.responseText) {\n files.push([\"response.txt\", entry.response.responseText])\n }\n\n // SSE events (useful for diagnosing mid-stream failures)\n if (entry.sseEvents?.length) {\n files.push([\"sse-events.json\", JSON.stringify(entry.sseEvents, null, 2)])\n }\n\n // Create directory and write all files (only after all data is collected)\n const id = randomBytes(4).toString(\"hex\")\n const dirPath = path.join(PATHS.ERROR_DIR, `${formatTimestamp()}_${id}`)\n\n await fs.mkdir(dirPath, { recursive: true })\n await Promise.all(files.map(([name, content]) => fs.writeFile(path.join(dirPath, name), content)))\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Format timestamp as YYMMDD_HHmmss for error directory names */\nfunction formatTimestamp(): string {\n const now = new Date()\n const pad = (n: number) => String(n).padStart(2, \"0\")\n const YY = String(now.getFullYear()).slice(2)\n return `${YY}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`\n}\n","/**\n * Context event consumers — bridge between RequestContext events and subsystems.\n *\n * Three consumers subscribe to RequestContextManager \"change\" events:\n * 1. History consumer → inserts/updates HistoryEntry in the store\n * 2. TUI consumer → updates tuiLogger for terminal display\n * 3. (WebSocket is handled implicitly via store's notifyEntryAdded/Updated)\n */\n\nimport type { HistoryEntry, MessageContent } from \"~/lib/history\"\n\nimport { getCurrentSession, insertEntry, isHistoryEnabled, updateEntry } from \"~/lib/history/store\"\nimport { tuiLogger } from \"~/lib/tui\"\n\nimport type { RequestContextEvent, RequestContextManager } from \"./manager\"\nimport type { HistoryEntryData, ResponseData } from \"./request\"\n\n// ─── History Consumer ───\n\nfunction handleHistoryEvent(event: RequestContextEvent): void {\n if (!isHistoryEnabled()) return\n\n switch (event.type) {\n case \"created\": {\n const ctx = event.context\n const sessionId = getCurrentSession(ctx.endpoint)\n\n const entry: HistoryEntry = {\n id: ctx.id,\n sessionId,\n timestamp: ctx.startTime,\n endpoint: ctx.endpoint,\n request: {\n model: ctx.originalRequest?.model,\n messages: ctx.originalRequest?.messages as Array<MessageContent> | undefined,\n stream: ctx.originalRequest?.stream,\n tools: ctx.originalRequest?.tools as HistoryEntry[\"request\"][\"tools\"],\n system: ctx.originalRequest?.system as HistoryEntry[\"request\"][\"system\"],\n },\n }\n\n insertEntry(entry)\n break\n }\n\n case \"updated\": {\n // originalRequest is set after the \"created\" event fires (the context\n // doesn't exist yet when create() is called, so setOriginalRequest()\n // comes after). Update the entry with the actual request data now.\n if (event.field === \"originalRequest\" && event.context.originalRequest) {\n const orig = event.context.originalRequest\n updateEntry(event.context.id, {\n request: {\n model: orig.model,\n messages: orig.messages as Array<MessageContent> | undefined,\n stream: orig.stream,\n tools: orig.tools as HistoryEntry[\"request\"][\"tools\"],\n system: orig.system as HistoryEntry[\"request\"][\"system\"],\n },\n })\n }\n if (event.field === \"rewrites\" && event.context.rewrites) {\n updateEntry(event.context.id, { rewrites: event.context.rewrites })\n }\n break\n }\n\n case \"completed\":\n case \"failed\": {\n const entryData = event.entry\n const response = toHistoryResponse(entryData)\n\n updateEntry(entryData.id, {\n response,\n durationMs: entryData.durationMs,\n })\n break\n }\n\n default: {\n break\n }\n }\n}\n\n// ─── TUI Consumer ───\n\nfunction handleTuiEvent(event: RequestContextEvent): void {\n switch (event.type) {\n case \"state_changed\": {\n const tuiLogId = event.context.tuiLogId\n if (!tuiLogId) return\n\n const newState = event.context.state\n if (newState === \"streaming\") {\n tuiLogger.updateRequest(tuiLogId, { status: \"streaming\" })\n } else if (newState === \"executing\") {\n tuiLogger.updateRequest(tuiLogId, { status: \"executing\" })\n }\n break\n }\n\n case \"updated\": {\n const tuiLogId = event.context.tuiLogId\n if (!tuiLogId) return\n\n // When attempts are updated, add retry tags\n if (event.field === \"attempts\" && event.context.attempts.length > 1) {\n const attempt = event.context.currentAttempt\n if (attempt?.strategy) {\n tuiLogger.updateRequest(tuiLogId, { tags: [attempt.strategy] })\n }\n }\n break\n }\n\n case \"completed\": {\n const ctx = event.context\n const tuiLogId = ctx.tuiLogId\n if (!tuiLogId) return\n\n const response = ctx.response\n if (response) {\n tuiLogger.updateRequest(tuiLogId, {\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n cacheReadInputTokens: response.usage.cache_read_input_tokens ?? undefined,\n cacheCreationInputTokens: response.usage.cache_creation_input_tokens ?? undefined,\n queueWaitMs: ctx.queueWaitMs || undefined,\n })\n // \"completed\" event implies upstream returned 200\n tuiLogger.finishRequest(tuiLogId, { statusCode: 200 })\n }\n break\n }\n\n case \"failed\": {\n const ctx = event.context\n const tuiLogId = ctx.tuiLogId\n if (!tuiLogId) return\n\n tuiLogger.finishRequest(tuiLogId, {\n error: ctx.response?.error ?? \"Unknown error\",\n // HTTP status from the last attempt's classified error (if available)\n statusCode: ctx.currentAttempt?.error?.status || undefined,\n })\n break\n }\n\n default: {\n break\n }\n }\n}\n\n// ─── Helpers ───\n\nfunction toHistoryResponse(entryData: HistoryEntryData): HistoryEntry[\"response\"] | undefined {\n if (!entryData.response) return undefined\n\n const r: ResponseData = entryData.response\n return {\n success: r.success,\n model: r.model,\n usage: {\n input_tokens: r.usage.input_tokens,\n output_tokens: r.usage.output_tokens,\n cache_read_input_tokens: r.usage.cache_read_input_tokens,\n cache_creation_input_tokens: r.usage.cache_creation_input_tokens,\n },\n stop_reason: r.stop_reason,\n error: r.error,\n content: r.content as MessageContent | null,\n }\n}\n\n// ─── Registration ───\n\nimport { handleErrorPersistence } from \"./error-persistence\"\n\nexport function registerContextConsumers(manager: RequestContextManager): void {\n manager.on(\"change\", handleHistoryEvent)\n manager.on(\"change\", handleTuiEvent)\n manager.on(\"change\", handleErrorPersistence)\n}\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\n/**\n * Custom dispatcher that routes requests through proxies based on environment variables.\n * Extends Agent to properly inherit the Dispatcher interface.\n */\nclass ProxyDispatcher extends Agent {\n private proxies = new Map<string, ProxyAgent>()\n\n dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {\n try {\n const origin = this.getOriginUrl(options.origin)\n const proxyUrl = this.getProxyUrl(origin)\n\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return super.dispatch(options, handler)\n }\n\n const agent = this.getOrCreateProxyAgent(proxyUrl)\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${this.formatProxyLabel(proxyUrl)}`)\n return agent.dispatch(options, handler)\n } catch {\n return super.dispatch(options, handler)\n }\n }\n\n private getOriginUrl(origin: Dispatcher.DispatchOptions[\"origin\"]): URL {\n return typeof origin === \"string\" ? new URL(origin) : (origin as URL)\n }\n\n private getProxyUrl(origin: URL): string | undefined {\n const raw = getProxyForUrl(origin.toString())\n return raw && raw.length > 0 ? raw : undefined\n }\n\n private getOrCreateProxyAgent(proxyUrl: string): ProxyAgent {\n let agent = this.proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n this.proxies.set(proxyUrl, agent)\n }\n return agent\n }\n\n private formatProxyLabel(proxyUrl: string): string {\n try {\n const u = new URL(proxyUrl)\n return `${u.protocol}//${u.host}`\n } catch {\n return proxyUrl\n }\n }\n\n override async close(): Promise<void> {\n await super.close()\n await Promise.all([...this.proxies.values()].map((p) => p.close()))\n this.proxies.clear()\n }\n\n override destroy(err?: Error | null): Promise<void>\n override destroy(callback: () => void): void\n override destroy(err: Error | null, callback: () => void): void\n override destroy(errOrCallback?: Error | null | (() => void), callback?: () => void): Promise<void> | void {\n // Clean up proxy agents (fire-and-forget, errors are ignored)\n for (const agent of this.proxies.values()) {\n if (typeof errOrCallback === \"function\") {\n agent.destroy(errOrCallback)\n } else if (callback) {\n agent.destroy(errOrCallback ?? null, callback)\n } else {\n agent.destroy(errOrCallback ?? null).catch(() => {\n // Ignore cleanup errors\n })\n }\n }\n this.proxies.clear()\n\n // Call super with appropriate overload\n if (typeof errOrCallback === \"function\") {\n super.destroy(errOrCallback)\n return\n } else if (callback) {\n super.destroy(errOrCallback ?? null, callback)\n return\n } else {\n return super.destroy(errOrCallback ?? null)\n }\n }\n}\n\nexport function initProxyFromEnv(): void {\n if (typeof Bun !== \"undefined\") return\n\n try {\n const dispatcher = new ProxyDispatcher()\n setGlobalDispatcher(dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","import type { Context } from \"hono\"\n\nimport {\n clearHistory,\n deleteSession,\n exportHistory,\n getEntry,\n getHistorySummaries,\n getSession,\n getSessionEntries,\n getSessions,\n getStats,\n isHistoryEnabled,\n type QueryOptions,\n} from \"~/lib/history\"\n\nexport function handleGetEntries(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const query = c.req.query()\n const options: QueryOptions = {\n page: query.page ? Number.parseInt(query.page, 10) : undefined,\n limit: query.limit ? Number.parseInt(query.limit, 10) : undefined,\n model: query.model || undefined,\n endpoint: query.endpoint as \"anthropic\" | \"openai\" | \"openai-responses\" | undefined,\n success: query.success ? query.success === \"true\" : undefined,\n from: query.from ? Number.parseInt(query.from, 10) : undefined,\n to: query.to ? Number.parseInt(query.to, 10) : undefined,\n search: query.search || undefined,\n sessionId: query.sessionId || undefined,\n }\n\n const result = getHistorySummaries(options)\n return c.json(result)\n}\n\nexport function handleGetEntry(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const entry = getEntry(id)\n\n if (!entry) {\n return c.json({ error: \"Entry not found\" }, 404)\n }\n\n return c.json(entry)\n}\n\nexport function handleDeleteEntries(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n clearHistory()\n return c.json({ success: true, message: \"History cleared\" })\n}\n\nexport function handleGetStats(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const stats = getStats()\n return c.json(stats)\n}\n\nexport function handleExport(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const format = (c.req.query(\"format\") || \"json\") as \"json\" | \"csv\"\n const data = exportHistory(format)\n\n if (format === \"csv\") {\n c.header(\"Content-Type\", \"text/csv\")\n c.header(\"Content-Disposition\", \"attachment; filename=history.csv\")\n } else {\n c.header(\"Content-Type\", \"application/json\")\n c.header(\"Content-Disposition\", \"attachment; filename=history.json\")\n }\n\n return c.body(data)\n}\n\n/** Session management endpoints */\nexport function handleGetSessions(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const result = getSessions()\n return c.json(result)\n}\n\nexport function handleGetSession(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const session = getSession(id)\n\n if (!session) {\n return c.json({ error: \"Session not found\" }, 404)\n }\n\n // Include entries in the session response\n const entries = getSessionEntries(id)\n\n return c.json({\n ...session,\n entries,\n })\n}\n\nexport function handleDeleteSession(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const success = deleteSession(id)\n\n if (!success) {\n return c.json({ error: \"Session not found\" }, 404)\n }\n\n return c.json({ success: true, message: \"Session deleted\" })\n}\n","/** MIME type helper for history UI static assets */\nexport function getMimeType(path: string): string {\n if (path.endsWith(\".html\")) return \"text/html\"\n if (path.endsWith(\".js\")) return \"application/javascript\"\n if (path.endsWith(\".css\")) return \"text/css\"\n if (path.endsWith(\".json\")) return \"application/json\"\n if (path.endsWith(\".svg\")) return \"image/svg+xml\"\n if (path.endsWith(\".png\")) return \"image/png\"\n if (path.endsWith(\".ico\")) return \"image/x-icon\"\n return \"application/octet-stream\"\n}\n","import type { UpgradeWebSocket } from \"hono/ws\"\nimport type { Server as NodeHttpServer } from \"node:http\"\n\nimport consola from \"consola\"\nimport { Hono } from \"hono\"\nimport { existsSync } from \"node:fs\"\nimport { access, constants } from \"node:fs/promises\"\nimport { readFile } from \"node:fs/promises\"\nimport { join, resolve } from \"node:path\"\n\nimport { addClient, removeClient } from \"~/lib/history\"\n\nimport {\n handleDeleteEntries,\n handleDeleteSession,\n handleExport,\n handleGetEntries,\n handleGetEntry,\n handleGetSession,\n handleGetSessions,\n handleGetStats,\n} from \"./api\"\nimport { getMimeType } from \"./assets\"\n\nexport const historyRoutes = new Hono()\n\n/** API endpoints */\nhistoryRoutes.get(\"/api/entries\", handleGetEntries)\nhistoryRoutes.get(\"/api/entries/:id\", handleGetEntry)\nhistoryRoutes.delete(\"/api/entries\", handleDeleteEntries)\nhistoryRoutes.get(\"/api/stats\", handleGetStats)\nhistoryRoutes.get(\"/api/export\", handleExport)\n\n/** Session endpoints */\nhistoryRoutes.get(\"/api/sessions\", handleGetSessions)\nhistoryRoutes.get(\"/api/sessions/:id\", handleGetSession)\nhistoryRoutes.delete(\"/api/sessions/:id\", handleDeleteSession)\n\n/**\n * Initialize WebSocket support for history real-time updates.\n * Registers the /ws route on historyRoutes using the appropriate WebSocket\n * adapter for the current runtime (hono/bun for Bun, @hono/node-ws for Node.js).\n *\n * @param rootApp - The root Hono app instance (needed by @hono/node-ws to match upgrade requests)\n * @returns An `injectWebSocket` function that must be called with the Node.js HTTP server\n * after the server is created. Returns `undefined` under Bun (no injection needed).\n */\nexport async function initHistoryWebSocket(rootApp: Hono): Promise<((server: NodeHttpServer) => void) | undefined> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let upgradeWs: UpgradeWebSocket<any>\n let injectFn: ((server: NodeHttpServer) => void) | undefined\n\n if (typeof globalThis.Bun !== \"undefined\") {\n // Bun runtime: use hono/bun adapter\n const { upgradeWebSocket } = await import(\"hono/bun\")\n upgradeWs = upgradeWebSocket\n } else {\n // Node.js runtime: use @hono/node-ws adapter\n const { createNodeWebSocket } = await import(\"@hono/node-ws\")\n const nodeWs = createNodeWebSocket({ app: rootApp })\n upgradeWs = nodeWs.upgradeWebSocket\n injectFn = (server: NodeHttpServer) => nodeWs.injectWebSocket(server)\n }\n\n // Register on the root app directly — historyRoutes sub-app has already been\n // mounted via app.route(\"/history\", historyRoutes) at import time, so adding\n // routes to historyRoutes here won't be visible on the root app.\n rootApp.get(\n \"/history/ws\",\n upgradeWs(() => ({\n onOpen(_event, ws) {\n addClient(ws.raw as unknown as WebSocket)\n },\n onClose(_event, ws) {\n removeClient(ws.raw as unknown as WebSocket)\n },\n onMessage(_event, _ws) {\n // Currently we don't process messages from clients\n },\n onError(event, ws) {\n consola.debug(\"WebSocket error:\", event)\n removeClient(ws.raw as unknown as WebSocket)\n },\n })),\n )\n\n return injectFn\n}\n\n/**\n * Resolve a UI directory that exists at runtime.\n * In dev mode this file lives at src/routes/history/ — 3 levels below project root.\n * In bundled mode (dist/main.mjs) — 1 level below project root.\n * We try both and fall back to the first candidate.\n */\nfunction resolveUiDir(subpath: string): string {\n const candidates = [\n join(import.meta.dirname, \"../../..\", \"ui\", subpath), // dev: src/routes/history/ → root\n join(import.meta.dirname, \"..\", \"ui\", subpath), // bundled: dist/ → root\n ]\n return candidates.find((c) => existsSync(c)) ?? candidates[0]\n}\n\n/** Static assets for legacy UI v1 */\nconst v1Dir = resolveUiDir(\"history-v1\")\n\n/** v1 root serves index.html directly */\nhistoryRoutes.get(\"/v1\", (c) => {\n return c.redirect(\"/history/v1/index.html\")\n})\n\n/** v1 static assets (CSS, JS) - no caching for development */\nhistoryRoutes.get(\"/v1/*\", async (c) => {\n const filePath = c.req.path.replace(\"/history/v1\", \"\")\n if (!filePath) return c.notFound()\n const fullPath = resolve(join(v1Dir, filePath))\n // Prevent path traversal\n if (!fullPath.startsWith(v1Dir)) return c.notFound()\n try {\n await access(fullPath, constants.R_OK)\n } catch {\n return c.notFound()\n }\n const content = await readFile(fullPath, \"utf8\")\n return new Response(content, {\n headers: {\n \"Content-Type\": getMimeType(filePath),\n \"Cache-Control\": \"no-cache\",\n },\n })\n})\n\n/** Static assets and routes for Vue UI v3 */\nconst v3Dir = resolveUiDir(\"history-v3/dist\")\n\nhistoryRoutes.get(\"/v3\", async (c) => {\n try {\n await access(join(v3Dir, \"index.html\"), constants.R_OK)\n const content = await readFile(join(v3Dir, \"index.html\"), \"utf8\")\n return c.html(content)\n } catch {\n return c.notFound()\n }\n})\n\nhistoryRoutes.get(\"/v3/assets/*\", async (c) => {\n const filePath = c.req.path.replace(\"/history/v3\", \"\")\n if (!filePath) return c.notFound()\n const fullPath = resolve(join(v3Dir, filePath))\n if (!fullPath.startsWith(v3Dir)) return c.notFound()\n try {\n await access(fullPath, constants.R_OK)\n const content = await readFile(fullPath)\n return new Response(content, {\n headers: {\n \"Content-Type\": getMimeType(filePath),\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n })\n } catch {\n return c.notFound()\n }\n})\n\nhistoryRoutes.get(\"/\", (c) => {\n return c.redirect(\"/history/v1\")\n})\n\nhistoryRoutes.get(\"/index.html\", (c) => {\n return c.redirect(\"/history/\")\n})\n","import type { Model } from \"./client\"\n\n// ============================================================================\n// Copilot API endpoint identifiers\n// ============================================================================\n\nexport const ENDPOINT = {\n MESSAGES: \"/v1/messages\",\n CHAT_COMPLETIONS: \"/chat/completions\",\n RESPONSES: \"/responses\",\n} as const\n\n// ============================================================================\n// Endpoint support checks\n// ============================================================================\n\n/**\n * Check if a model supports a given API endpoint.\n *\n * When `supported_endpoints` is absent (legacy models like gpt-4, gemini),\n * we assume all endpoints are supported — these models predate the field\n * and rely on /chat/completions as a universal fallback.\n */\nexport function isEndpointSupported(model: Model | undefined, endpoint: string): boolean {\n if (!model?.supported_endpoints) return true\n return model.supported_endpoints.includes(endpoint)\n}\n\n/**\n * Assert that a model supports a given endpoint, throwing a descriptive error if not.\n * Returns the validated model for chaining.\n */\nexport function assertEndpointSupported(\n model: Model | undefined,\n endpoint: string,\n): void {\n if (isEndpointSupported(model, endpoint)) return\n\n const modelId = model?.id ?? \"unknown\"\n const supported = model?.supported_endpoints?.join(\", \") ?? \"none\"\n const msg = `Model \"${modelId}\" does not support ${endpoint}. Supported endpoints: ${supported}`\n throw new Error(msg)\n}\n","import type { ChatCompletionsPayload, ContentPart, Message, Tool, ToolCall } from \"~/lib/openai/client\"\n\nimport type { Model } from \"./client\"\n\n// ============================================================================\n// GPT Encoder Support\n// ============================================================================\n\n/** Encoder type mapping */\nconst ENCODING_MAP = {\n o200k_base: () => import(\"gpt-tokenizer/encoding/o200k_base\"),\n cl100k_base: () => import(\"gpt-tokenizer/encoding/cl100k_base\"),\n p50k_base: () => import(\"gpt-tokenizer/encoding/p50k_base\"),\n p50k_edit: () => import(\"gpt-tokenizer/encoding/p50k_edit\"),\n r50k_base: () => import(\"gpt-tokenizer/encoding/r50k_base\"),\n} as const\n\ntype SupportedEncoding = keyof typeof ENCODING_MAP\n\n/** Encoder interface for tokenization */\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\n/** Cache loaded encoders to avoid repeated imports */\nconst encodingCache = new Map<string, Encoder>()\n\n/**\n * Calculate tokens for tool calls\n */\nconst calculateToolCallsTokens = (\n toolCalls: Array<ToolCall>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = 0\n for (const toolCall of toolCalls) {\n tokens += constants.funcInit\n tokens += encoder.encode(JSON.stringify(toolCall)).length\n }\n tokens += constants.funcEnd\n return tokens\n}\n\n/**\n * Calculate tokens for content parts\n */\nconst calculateContentPartsTokens = (contentParts: Array<ContentPart>, encoder: Encoder): number => {\n let tokens = 0\n for (const part of contentParts) {\n if (part.type === \"image_url\") {\n // Image URLs incur ~85 tokens overhead for the image processing metadata\n // This is an approximation based on OpenAI's image token calculation\n tokens += encoder.encode(part.image_url.url).length + 85\n } else if (part.text) {\n tokens += encoder.encode(part.text).length\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens for a single message\n */\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n // Each message incurs 3 tokens overhead for role/metadata framing\n // Based on OpenAI's token counting methodology\n const tokensPerMessage = 3\n // Additional token when a \"name\" field is present\n const tokensPerName = 1\n let tokens = tokensPerMessage\n for (const [key, value] of Object.entries(message)) {\n if (typeof value === \"string\") {\n tokens += encoder.encode(value).length\n }\n if (key === \"name\") {\n tokens += tokensPerName\n }\n if (key === \"tool_calls\") {\n tokens += calculateToolCallsTokens(value as Array<ToolCall>, encoder, constants)\n }\n if (key === \"content\" && Array.isArray(value)) {\n tokens += calculateContentPartsTokens(value as Array<ContentPart>, encoder)\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens using custom algorithm\n */\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) {\n return 0\n }\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n // every reply is primed with <|start|>assistant<|message|> (3 tokens)\n numTokens += 3\n return numTokens\n}\n\n/**\n * Get the corresponding encoder module based on encoding type\n */\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) {\n return cached\n }\n }\n\n const supportedEncoding = encoding as SupportedEncoding\n const rawModule =\n supportedEncoding in ENCODING_MAP ? await ENCODING_MAP[supportedEncoding]() : await ENCODING_MAP.o200k_base()\n\n // Wrap encode to disable special token checks.\n // gpt-tokenizer defaults to disallowedSpecial='all', which throws on\n // tokens like <|im_start|> that appear in tool_result content.\n const encoder: Encoder = {\n encode: (text: string) => rawModule.encode(text, { disallowedSpecial: new Set() }),\n }\n\n encodingCache.set(encoding, encoder)\n return encoder\n}\n\n/**\n * Get tokenizer type from model information\n */\nexport const getTokenizerFromModel = (model: Model): string => {\n return model.capabilities?.tokenizer || \"o200k_base\"\n}\n\n/**\n * Count tokens in a text string using the model's tokenizer.\n * This is a simple wrapper for counting tokens in plain text.\n */\nexport const countTextTokens = async (text: string, model: Model): Promise<number> => {\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n return encoder.encode(text).length\n}\n\n/**\n * Get model-specific constants for token calculation.\n * These values are empirically determined based on OpenAI's function calling token overhead.\n * - funcInit: Tokens for initializing a function definition\n * - propInit: Tokens for initializing the properties section\n * - propKey: Tokens per property key\n * - enumInit: Token adjustment when enum is present (negative because type info is replaced)\n * - enumItem: Tokens per enum value\n * - funcEnd: Tokens for closing the function definition\n */\nconst getModelConstants = (model: Model) => {\n return model.id === \"gpt-3.5-turbo\" || model.id === \"gpt-4\" ?\n {\n funcInit: 10,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n : {\n funcInit: 7,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n}\n\n/**\n * Calculate tokens for a single parameter\n */\nconst calculateParameterTokens = (\n key: string,\n prop: unknown,\n context: {\n encoder: Encoder\n constants: ReturnType<typeof getModelConstants>\n },\n): number => {\n const { encoder, constants } = context\n let tokens = constants.propKey\n\n // Early return if prop is not an object\n if (typeof prop !== \"object\" || prop === null) {\n return tokens\n }\n\n // Type assertion for parameter properties\n const param = prop as {\n type?: string\n description?: string\n enum?: Array<unknown>\n [key: string]: unknown\n }\n\n const paramName = key\n const paramType = param.type || \"string\"\n let paramDesc = param.description || \"\"\n\n // Handle enum values\n if (param.enum && Array.isArray(param.enum)) {\n tokens += constants.enumInit\n for (const item of param.enum) {\n tokens += constants.enumItem\n tokens += encoder.encode(String(item)).length\n }\n }\n\n // Clean up description\n if (paramDesc.endsWith(\".\")) {\n paramDesc = paramDesc.slice(0, -1)\n }\n\n // Encode the main parameter line\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n // Handle additional properties (excluding standard ones)\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (!excludedKeys.has(propertyName)) {\n const propertyValue = param[propertyName]\n const propertyText = typeof propertyValue === \"string\" ? propertyValue : JSON.stringify(propertyValue)\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for function parameters\n */\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") {\n return 0\n }\n\n const params = parameters as Record<string, unknown>\n let tokens = 0\n\n for (const [key, value] of Object.entries(params)) {\n if (key === \"properties\") {\n const properties = value as Record<string, unknown>\n if (Object.keys(properties).length > 0) {\n tokens += constants.propInit\n for (const propKey of Object.keys(properties)) {\n tokens += calculateParameterTokens(propKey, properties[propKey], {\n encoder,\n constants,\n })\n }\n }\n } else {\n const paramText = typeof value === \"string\" ? value : JSON.stringify(value)\n tokens += encoder.encode(`${key}:${paramText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for a single tool\n */\nconst calculateToolTokens = (tool: Tool, encoder: Encoder, constants: ReturnType<typeof getModelConstants>): number => {\n let tokens = constants.funcInit\n const func = tool.function\n const fName = func.name\n let fDesc = func.description || \"\"\n if (fDesc.endsWith(\".\")) {\n fDesc = fDesc.slice(0, -1)\n }\n const line = `${fName}:${fDesc}`\n tokens += encoder.encode(line).length\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typeof null === \"object\" in JavaScript\n if (typeof func.parameters === \"object\" && func.parameters !== null) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\n\n/**\n * Calculate token count for tools based on model\n */\nexport const numTokensForTools = (\n tools: Array<Tool>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let funcTokenCount = 0\n for (const tool of tools) {\n funcTokenCount += calculateToolTokens(tool, encoder, constants)\n }\n funcTokenCount += constants.funcEnd\n return funcTokenCount\n}\n\n// ============================================================================\n// Main Token Count API\n// ============================================================================\n\n/**\n * Calculate the token count of messages.\n * Uses the tokenizer specified by the GitHub Copilot API model info.\n * All models (including Claude) use GPT tokenizers (o200k_base or cl100k_base).\n */\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: Model,\n): Promise<{ input: number; output: number }> => {\n // Use the tokenizer specified by the API (defaults to o200k_base)\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const simplifiedMessages = payload.messages\n const inputMessages = simplifiedMessages.filter((msg) => msg.role !== \"assistant\")\n const outputMessages = simplifiedMessages.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 {\n input: inputTokens,\n output: outputTokens,\n }\n}\n","/**\n * Payload utilities for request handlers.\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\nimport type { ChatCompletionsPayload } from \"~/lib/openai/client\"\nimport type { MessagesPayload } from \"~/types/api/anthropic\"\n\nimport { getTokenCount } from \"~/lib/models/tokenizer\"\nimport { bytesToKB } from \"~/lib/utils\"\n\n/**\n * Log helpful debugging information when a 413 error occurs.\n *\n * @param precomputedBytes - Optional pre-computed payload byte size to avoid redundant JSON.stringify\n */\nexport async function logPayloadSizeInfo(\n payload: ChatCompletionsPayload,\n model: Model | undefined,\n precomputedBytes?: number,\n) {\n const messageCount = payload.messages.length\n const bodySize = precomputedBytes ?? JSON.stringify(payload).length\n const bodySizeKB = bytesToKB(bodySize)\n\n // Count images and large messages\n let imageCount = 0\n let largeMessages = 0\n let totalImageSize = 0\n\n for (const msg of payload.messages) {\n if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.type === \"image_url\") {\n imageCount++\n if (part.image_url.url.startsWith(\"data:\")) {\n totalImageSize += part.image_url.url.length\n }\n }\n }\n }\n\n const msgSize = typeof msg.content === \"string\" ? msg.content.length : JSON.stringify(msg.content).length\n if (msgSize > 50000) largeMessages++\n }\n\n consola.info(\"\")\n consola.info(\"╭─────────────────────────────────────────────────────────╮\")\n consola.info(\"│ 413 Request Entity Too Large │\")\n consola.info(\"╰─────────────────────────────────────────────────────────╯\")\n consola.info(\"\")\n consola.info(` Request body size: ${bodySizeKB} KB (${bodySize.toLocaleString()} bytes)`)\n consola.info(` Message count: ${messageCount}`)\n\n if (model) {\n try {\n const tokenCount = await getTokenCount(payload, model)\n const limit = model.capabilities?.limits?.max_prompt_tokens ?? 128000\n consola.info(` Estimated tokens: ${tokenCount.input.toLocaleString()} / ${limit.toLocaleString()}`)\n } catch (error) {\n consola.debug(\"Token count estimation failed:\", error)\n }\n }\n\n if (imageCount > 0) {\n const imageSizeKB = bytesToKB(totalImageSize)\n consola.info(` Images: ${imageCount} (${imageSizeKB} KB base64 data)`)\n }\n if (largeMessages > 0) {\n consola.info(` Large messages (>50KB): ${largeMessages}`)\n }\n\n consola.info(\"\")\n consola.info(\" Suggestions:\")\n if (imageCount > 0) {\n consola.info(\" • Remove or resize large images in the conversation\")\n }\n consola.info(\" • Start a new conversation with /clear or /reset\")\n consola.info(\" • Reduce conversation history by deleting old messages\")\n consola.info(\"\")\n}\n\n/** Log payload size info for Anthropic format when a 413 error occurs */\nexport function logPayloadSizeInfoAnthropic(payload: MessagesPayload, model: Model | undefined) {\n const payloadSize = JSON.stringify(payload).length\n const messageCount = payload.messages.length\n const toolCount = payload.tools?.length ?? 0\n const systemSize = payload.system ? JSON.stringify(payload.system).length : 0\n\n consola.info(\n `[Anthropic 413] Payload size: ${bytesToKB(payloadSize)}KB, `\n + `messages: ${messageCount}, tools: ${toolCount}, system: ${bytesToKB(systemSize)}KB`,\n )\n\n if (model?.capabilities?.limits) {\n const limits = model.capabilities.limits\n consola.info(\n `[Anthropic 413] Model limits: context=${limits.max_context_window_tokens}, `\n + `prompt=${limits.max_prompt_tokens}, output=${limits.max_output_tokens}`,\n )\n }\n}\n","/**\n * Request execution pipeline with pluggable retry strategies.\n *\n * Unifies the retry loop pattern shared by direct-anthropic-handler,\n * translated-handler, and (soon) completions handler.\n */\n\nimport consola from \"consola\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { ApiError } from \"~/lib/error\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { classifyError } from \"~/lib/error\"\n\n// --- FormatAdapter ---\n\nexport interface SanitizeResult<TPayload> {\n payload: TPayload\n /** Convenience: total blocks removed (sum of orphans + empty text) */\n removedCount: number\n /** Convenience: number of system reminder tags removed */\n systemReminderRemovals: number\n /** Structured breakdown of what was removed/modified — format-specific detail */\n stats?: Record<string, number>\n}\n\nexport interface FormatAdapter<TPayload> {\n readonly format: string\n sanitize(payload: TPayload): SanitizeResult<TPayload>\n /** Execute API call — raw execution without rate limiting wrapper */\n execute(payload: TPayload): Promise<{ result: unknown; queueWaitMs: number }>\n logPayloadSize(payload: TPayload): void | Promise<void>\n}\n\n// --- RetryStrategy ---\n\nexport interface RetryContext<TPayload> {\n attempt: number\n originalPayload: TPayload\n model: Model | undefined\n maxRetries: number\n}\n\nexport type RetryAction<TPayload> =\n | { action: \"retry\"; payload: TPayload; waitMs?: number; meta?: Record<string, unknown> }\n | { action: \"abort\"; error: ApiError }\n\nexport interface RetryStrategy<TPayload> {\n readonly name: string\n /** Check if this strategy can handle the given error */\n canHandle(error: ApiError): boolean\n /** Handle the error and decide whether to retry or abort */\n handle(error: ApiError, payload: TPayload, context: RetryContext<TPayload>): Promise<RetryAction<TPayload>>\n}\n\n// --- Pipeline ---\n\nexport interface PipelineResult {\n response: unknown\n effectivePayload: unknown\n queueWaitMs: number\n totalRetries: number\n}\n\nexport interface PipelineOptions<TPayload> {\n adapter: FormatAdapter<TPayload>\n strategies: Array<RetryStrategy<TPayload>>\n payload: TPayload\n originalPayload: TPayload\n model: Model | undefined\n maxRetries?: number\n /** Optional request context for lifecycle tracking */\n requestContext?: RequestContext\n /** Called before each attempt (for tracking tags, etc.) */\n onBeforeAttempt?: (attempt: number, payload: TPayload) => void\n /** Called after successful truncation retry (for recording rewrites, etc.) */\n onRetry?: (attempt: number, strategyName: string, newPayload: TPayload, meta?: Record<string, unknown>) => void\n}\n\n/**\n * Execute a request through the pipeline with retry strategies.\n *\n * Flow:\n * 1. Execute API call with the current payload\n * 2. On success → return response\n * 3. On failure → classify error → find first matching strategy → handle\n * - retry → use new payload, loop back to step 1\n * - abort or no strategy → throw error\n */\nexport async function executeRequestPipeline<TPayload>(opts: PipelineOptions<TPayload>): Promise<PipelineResult> {\n const { adapter, strategies, originalPayload, model, maxRetries = 3, requestContext, onBeforeAttempt, onRetry } = opts\n\n let effectivePayload = opts.payload\n let lastError: unknown = null\n let totalQueueWaitMs = 0\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n onBeforeAttempt?.(attempt, effectivePayload)\n requestContext?.beginAttempt({ strategy: attempt > 0 ? \"retry\" : undefined })\n requestContext?.transition(\"executing\")\n\n try {\n const { result: response, queueWaitMs } = await adapter.execute(effectivePayload)\n totalQueueWaitMs += queueWaitMs\n requestContext?.addQueueWaitMs(queueWaitMs)\n\n return {\n response,\n effectivePayload,\n queueWaitMs: totalQueueWaitMs,\n totalRetries: attempt,\n }\n } catch (error) {\n lastError = error\n\n // Classify and record the error on the current attempt (always, including final attempt)\n const apiError = classifyError(error)\n requestContext?.setAttemptError(apiError)\n\n // Don't retry if we've exhausted attempts\n if (attempt >= maxRetries) break\n\n // Find first strategy that can handle this error\n let handled = false\n for (const strategy of strategies) {\n if (!strategy.canHandle(apiError)) continue\n\n const retryContext: RetryContext<TPayload> = {\n attempt,\n originalPayload,\n model,\n maxRetries,\n }\n\n try {\n const action = await strategy.handle(apiError, effectivePayload, retryContext)\n\n if (action.action === \"retry\") {\n consola.debug(\n `[Pipeline] Strategy \"${strategy.name}\" requests retry ` + `(attempt ${attempt + 1}/${maxRetries + 1})`,\n )\n\n if (action.waitMs && action.waitMs > 0) {\n totalQueueWaitMs += action.waitMs\n requestContext?.addQueueWaitMs(action.waitMs)\n }\n\n effectivePayload = action.payload\n onRetry?.(attempt, strategy.name, action.payload, action.meta)\n handled = true\n break\n }\n\n // action === \"abort\": fall through to break\n break\n } catch (strategyError) {\n consola.warn(\n `[Pipeline] Strategy \"${strategy.name}\" failed on attempt ${attempt + 1}:`,\n strategyError instanceof Error ? strategyError.message : strategyError,\n )\n // Strategy itself failed, break out to throw original error\n break\n }\n }\n\n if (!handled) break\n }\n }\n\n // If we exit the loop, it means all retries failed or no strategy handled the error\n if (lastError) {\n // Log payload size info for 413 errors\n const apiError = classifyError(lastError)\n if (apiError.type === \"payload_too_large\") {\n await adapter.logPayloadSize(effectivePayload)\n }\n\n throw lastError instanceof Error ? lastError : new Error(\"Unknown error\")\n }\n\n // Should not reach here\n throw new Error(\"Unexpected state in pipeline retry loop\")\n}\n","/**\n * Stream accumulator for Responses API format.\n * Accumulates semantic SSE events into a final state for history/tracking.\n */\n\nimport type { BaseStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\nimport type { ResponsesStreamEvent } from \"~/types/api/openai-responses\"\n\n/** Internal tool call accumulator using string array to avoid O(n²) concatenation */\ninterface ToolCallAccumulator {\n id: string\n callId: string\n name: string\n argumentParts: Array<string>\n}\n\n/** Stream accumulator for Responses API format */\nexport interface ResponsesStreamAccumulator extends BaseStreamAccumulator {\n status: string\n responseId: string\n toolCalls: Array<{ id: string; callId: string; name: string; arguments: string }>\n /** Tool call accumulators indexed by output_index */\n toolCallMap: Map<number, ToolCallAccumulator>\n /** Text content parts for O(1) accumulation, joined on read via finalContent() */\n contentParts: Array<string>\n}\n\nexport function createResponsesStreamAccumulator(): ResponsesStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n content: \"\",\n status: \"\",\n responseId: \"\",\n toolCalls: [],\n toolCallMap: new Map(),\n contentParts: [],\n }\n}\n\n/** Get the final accumulated content string */\nexport function finalizeResponsesContent(acc: ResponsesStreamAccumulator): string {\n if (acc.contentParts.length > 0) {\n acc.content = acc.contentParts.join(\"\")\n acc.contentParts = []\n }\n return acc.content\n}\n\n/** Accumulate a single parsed Responses API event into the accumulator */\nexport function accumulateResponsesStreamEvent(event: ResponsesStreamEvent, acc: ResponsesStreamAccumulator) {\n switch (event.type) {\n case \"response.created\":\n case \"response.in_progress\": {\n if (event.response.model) acc.model = event.response.model\n if (event.response.id) acc.responseId = event.response.id\n break\n }\n\n case \"response.completed\": {\n acc.status = event.response.status\n if (event.response.model) acc.model = event.response.model\n if (event.response.usage) {\n acc.inputTokens = event.response.usage.input_tokens\n acc.outputTokens = event.response.usage.output_tokens\n }\n break\n }\n\n case \"response.failed\":\n case \"response.incomplete\": {\n acc.status = event.response.status\n break\n }\n\n case \"response.output_item.added\": {\n if (event.item.type === \"function_call\") {\n acc.toolCallMap.set(event.output_index, {\n id: event.item.id,\n callId: \"call_id\" in event.item ? event.item.call_id : \"\",\n name: \"name\" in event.item ? event.item.name : \"\",\n argumentParts: [],\n })\n }\n break\n }\n\n case \"response.output_text.delta\": {\n acc.contentParts.push(event.delta)\n break\n }\n\n case \"response.function_call_arguments.delta\": {\n const tcAcc = acc.toolCallMap.get(event.output_index)\n if (tcAcc) {\n tcAcc.argumentParts.push(event.delta)\n }\n break\n }\n\n case \"response.function_call_arguments.done\": {\n const tcAcc = acc.toolCallMap.get(event.output_index)\n if (tcAcc) {\n acc.toolCalls.push({\n id: tcAcc.id,\n callId: tcAcc.callId,\n name: tcAcc.name,\n arguments: tcAcc.argumentParts.join(\"\"),\n })\n }\n break\n }\n\n case \"response.output_item.done\": {\n // Final output item — if it's a function call that wasn't already finalized\n // via arguments.done, finalize it now\n if (event.item.type === \"function_call\") {\n const existing = acc.toolCalls.find((tc) => tc.id === event.item.id)\n if (!existing) {\n acc.toolCalls.push({\n id: event.item.id,\n callId: \"call_id\" in event.item ? event.item.call_id : \"\",\n name: \"name\" in event.item ? event.item.name : \"\",\n arguments: \"arguments\" in event.item ? event.item.arguments : \"\",\n })\n }\n }\n break\n }\n\n // Other events don't need accumulation\n default: {\n break\n }\n }\n}\n","/**\n * Response utilities for request handlers.\n */\n\nimport type { ChatCompletionResponse } from \"~/lib/openai/client\"\n\n/** Type guard for non-streaming responses */\nexport function isNonStreaming(\n response: ChatCompletionResponse | AsyncIterable<unknown>,\n): response is ChatCompletionResponse {\n return Object.hasOwn(response, \"choices\")\n}\n\n/** Parse a JSON string to object, returning the value as-is if already an object */\nexport function safeParseJson(input: string | Record<string, unknown>): Record<string, unknown> {\n if (typeof input !== \"string\") return input\n try {\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\n/** Prepend a marker string to the first text content block of an Anthropic-format response */\nexport function prependMarkerToResponse<T extends { content: Array<{ type: string; text?: string }> }>(\n response: T,\n marker: string,\n): T {\n if (!marker) return response\n\n // Find first text block and prepend, or add new text block at start\n const content = [...response.content]\n const firstTextIndex = content.findIndex((block) => block.type === \"text\")\n\n if (firstTextIndex !== -1) {\n const textBlock = content[firstTextIndex]\n if (textBlock.type === \"text\") {\n content[firstTextIndex] = {\n ...textBlock,\n text: marker + (textBlock.text ?? \"\"),\n }\n }\n } else {\n // No text block found, add one at the beginning\n content.unshift({ type: \"text\", text: marker } as (typeof content)[number])\n }\n\n return { ...response, content }\n}\n","/** Shared recording utilities for streaming responses */\n\nimport consola from \"consola\"\n\nimport type { AnthropicStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\nimport type { ResponseData } from \"~/lib/context/request\"\nimport type { OpenAIStreamAccumulator } from \"~/lib/openai/stream-accumulator\"\nimport type { ResponsesStreamAccumulator } from \"~/lib/openai/responses-stream-accumulator\"\n\nimport { finalizeResponsesContent } from \"~/lib/openai/responses-stream-accumulator\"\nimport { safeParseJson } from \"./response\"\n\n/**\n * Map Anthropic content blocks to history-friendly format.\n */\nfunction mapAnthropicContentBlocks(acc: AnthropicStreamAccumulator): Array<unknown> {\n return acc.contentBlocks.map((block) => {\n // Generic (unknown) blocks are passed through as-is\n if (\"_generic\" in block) {\n const { _generic: _, ...rest } = block\n return rest\n }\n\n switch (block.type) {\n case \"text\": {\n return { type: \"text\" as const, text: block.text }\n }\n case \"thinking\": {\n return { type: \"thinking\" as const, thinking: block.thinking }\n }\n case \"redacted_thinking\": {\n return { type: \"redacted_thinking\" as const }\n }\n case \"tool_use\":\n case \"server_tool_use\": {\n return {\n type: block.type as string,\n id: block.id,\n name: block.name,\n input: safeParseJson(block.input),\n }\n }\n case \"web_search_tool_result\": {\n return {\n type: \"web_search_tool_result\" as const,\n tool_use_id: block.tool_use_id,\n content: block.content,\n }\n }\n default: {\n const unknown = block as { type: string }\n consola.warn(`[recording] Unhandled content block type in stream result: ${unknown.type}`)\n return { type: unknown.type }\n }\n }\n })\n}\n\n/**\n * Build a ResponseData from a completed Anthropic stream accumulator.\n * Does not include durationMs or queueWaitMs — those are tracked by RequestContext.\n */\nexport function buildAnthropicResponseData(acc: AnthropicStreamAccumulator, fallbackModel: string): ResponseData {\n const contentBlocks = mapAnthropicContentBlocks(acc)\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cacheReadTokens > 0 && { cache_read_input_tokens: acc.cacheReadTokens }),\n ...(acc.cacheCreationTokens > 0 && { cache_creation_input_tokens: acc.cacheCreationTokens }),\n },\n stop_reason: acc.stopReason || undefined,\n content: contentBlocks.length > 0 ? { role: \"assistant\", content: contentBlocks } : null,\n }\n}\n\n/**\n * Build a ResponseData from a completed OpenAI stream accumulator.\n * Does not include durationMs or queueWaitMs — those are tracked by RequestContext.\n */\nexport function buildOpenAIResponseData(acc: OpenAIStreamAccumulator, fallbackModel: string): ResponseData {\n // Collect tool calls from map, joining accumulated argument parts\n for (const tc of acc.toolCallMap.values()) {\n if (tc.id && tc.name) acc.toolCalls.push({ id: tc.id, name: tc.name, arguments: tc.argumentParts.join(\"\") })\n }\n\n const toolCalls = acc.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }))\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cachedTokens > 0 && { cache_read_input_tokens: acc.cachedTokens }),\n },\n stop_reason: acc.finishReason || undefined,\n content: {\n role: \"assistant\",\n content: acc.content,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n },\n }\n}\n\n/**\n * Build a ResponseData from a completed Responses API stream accumulator.\n * Converts tool calls from Responses format (callId) to OpenAI Chat Completions format (id).\n */\nexport function buildResponsesResponseData(\n acc: ResponsesStreamAccumulator,\n fallbackModel: string,\n): ResponseData {\n // Finalize tool calls from the accumulator map\n for (const tcAcc of acc.toolCallMap.values()) {\n const existing = acc.toolCalls.find((tc) => tc.id === tcAcc.id)\n if (!existing && tcAcc.id && tcAcc.name) {\n acc.toolCalls.push({\n id: tcAcc.id,\n callId: tcAcc.callId,\n name: tcAcc.name,\n arguments: tcAcc.argumentParts.join(\"\"),\n })\n }\n }\n\n const finalContent = finalizeResponsesContent(acc)\n\n const toolCalls = acc.toolCalls.map((tc) => ({\n id: tc.callId || tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }))\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n },\n stop_reason: acc.status || undefined,\n content:\n finalContent || toolCalls.length > 0\n ? {\n role: \"assistant\",\n content: finalContent || null,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n }\n : null,\n }\n}\n","/**\n * Auto-truncate retry strategy.\n *\n * Handles token limit errors by truncating the message payload and retrying.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { AUTO_TRUNCATE_RETRY_FACTOR, tryParseAndLearnLimit } from \"~/lib/auto-truncate\"\nimport { HTTPError } from \"~/lib/error\"\n\nimport type { RetryAction, RetryContext, RetryStrategy, SanitizeResult } from \"../pipeline\"\n\n/** Result from a truncation operation */\nexport interface TruncateResult<TPayload> {\n wasTruncated: boolean\n payload: TPayload\n removedMessageCount: number\n originalTokens: number\n compactedTokens: number\n processingTimeMs: number\n}\n\n/** Options passed to the truncation function */\nexport interface TruncateOptions {\n checkTokenLimit: boolean\n targetTokenLimit?: number\n}\n\n/**\n * Create an auto-truncate retry strategy.\n *\n * @param truncate - Format-specific truncation function\n * @param resanitize - Format-specific re-sanitization after truncation\n * @param isEnabled - Check if auto-truncate is enabled (typically reads state.autoTruncate)\n */\nexport function createAutoTruncateStrategy<TPayload>(opts: {\n truncate: (payload: TPayload, model: Model, options: TruncateOptions) => Promise<TruncateResult<TPayload>>\n resanitize: (payload: TPayload) => SanitizeResult<TPayload>\n isEnabled: () => boolean\n label: string\n}): RetryStrategy<TPayload> {\n const { truncate, resanitize, isEnabled, label } = opts\n\n return {\n name: \"auto-truncate\",\n\n canHandle(error: ApiError): boolean {\n if (!isEnabled()) return false\n return error.type === \"payload_too_large\" || error.type === \"token_limit\"\n },\n\n async handle(\n error: ApiError,\n currentPayload: TPayload,\n context: RetryContext<TPayload>,\n ): Promise<RetryAction<TPayload>> {\n const { attempt, originalPayload, model, maxRetries } = context\n\n if (!model) {\n return { action: \"abort\", error }\n }\n\n // Extract the raw error to get HTTP details for tryParseAndLearnLimit\n const rawError = error.raw\n if (!(rawError instanceof HTTPError)) {\n return { action: \"abort\", error }\n }\n\n // Estimate tokens using GPT tokenizer for calibration feedback\n const payloadJson = JSON.stringify(currentPayload)\n const estimatedTokens = Math.ceil(payloadJson.length / 4)\n\n const parsed = tryParseAndLearnLimit(rawError, model.id, true, estimatedTokens)\n\n if (!parsed) {\n // For 413 errors without parseable limit info, still retry with truncation\n if (rawError.status === 413) {\n consola.info(\n `[${label}] Attempt ${attempt + 1}/${maxRetries + 1}: `\n + `413 Body too large, retrying with truncation...`,\n )\n\n const truncateResult = await truncate(originalPayload, model, {\n checkTokenLimit: true,\n })\n\n if (!truncateResult.wasTruncated) {\n return { action: \"abort\", error }\n }\n\n const sanitizeResult = resanitize(truncateResult.payload)\n return {\n action: \"retry\",\n payload: sanitizeResult.payload,\n meta: {\n truncateResult,\n sanitization: sanitizeResult.stats ?? {\n totalBlocksRemoved: sanitizeResult.removedCount,\n systemReminderRemovals: sanitizeResult.systemReminderRemovals,\n },\n attempt: attempt + 1,\n },\n }\n }\n\n return { action: \"abort\", error }\n }\n\n // Calculate target token limit based on error info\n let targetTokenLimit: number | undefined\n\n if (parsed.limit) {\n targetTokenLimit = Math.floor(parsed.limit * AUTO_TRUNCATE_RETRY_FACTOR)\n consola.info(\n `[${label}] Attempt ${attempt + 1}/${maxRetries + 1}: `\n + `Token limit error (${parsed.current}>${parsed.limit}), `\n + `retrying with limit ${targetTokenLimit}...`,\n )\n }\n\n // Truncate from original payload (not from already-truncated)\n const truncateResult = await truncate(originalPayload, model, {\n checkTokenLimit: true,\n targetTokenLimit,\n })\n\n if (!truncateResult.wasTruncated) {\n // Truncation didn't help\n return { action: \"abort\", error }\n }\n\n // Re-sanitize the truncated payload\n const sanitizeResult = resanitize(truncateResult.payload)\n\n return {\n action: \"retry\",\n payload: sanitizeResult.payload,\n meta: {\n truncateResult,\n sanitization: sanitizeResult.stats ?? {\n totalBlocksRemoved: sanitizeResult.removedCount,\n systemReminderRemovals: sanitizeResult.systemReminderRemovals,\n },\n attempt: attempt + 1,\n },\n }\n },\n }\n}\n","/**\n * Truncation marker utilities.\n */\n\n/** Minimal truncate result info needed for usage adjustment and markers */\nexport interface TruncateResultInfo {\n wasTruncated: boolean\n originalTokens?: number\n compactedTokens?: number\n removedMessageCount?: number\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n * Works with both OpenAI and Anthropic truncate results.\n */\nexport function createTruncationMarker(result: TruncateResultInfo): string {\n if (!result.wasTruncated) return \"\"\n\n const { originalTokens, compactedTokens, removedMessageCount } = result\n\n if (originalTokens === undefined || compactedTokens === undefined || removedMessageCount === undefined) {\n return `\\n\\n---\\n[Auto-truncated: conversation history was reduced to fit context limits]`\n }\n\n const reduction = originalTokens - compactedTokens\n const percentage = Math.round((reduction / originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${removedMessageCount} messages removed, `\n + `${originalTokens} → ${compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n","/**\n * Deferred tool retry strategy.\n *\n * Handles 400 errors caused by deferred tools being referenced in the request\n * (e.g., in message history) before they've been loaded via tool_search.\n *\n * When context_management clears older tool_search activations but keeps\n * tool_use/tool_result pairs, or when the client compacts history, a deferred\n * tool may appear in the conversation without its tool_search \"load\" record.\n * The API then rejects the request with:\n * \"Tool reference 'X' not found in available tools\"\n *\n * This strategy parses the tool name from the error, marks it as non-deferred\n * (defer_loading: false) in the payload's tools array, and retries.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\nimport type { Tool } from \"~/types/api/anthropic\"\n\nimport type { RetryAction, RetryContext, RetryStrategy } from \"../pipeline\"\n\n// ============================================================================\n// Error parsing\n// ============================================================================\n\n/** Pattern: \"Tool reference 'tool_name' not found in available tools\" */\nconst TOOL_REFERENCE_NOT_FOUND_PATTERN = /Tool reference '([^']+)' not found in available tools/\n\n/**\n * Extract tool name from a \"Tool reference not found\" error.\n * Returns the tool name or null if the error doesn't match.\n */\nexport function parseToolReferenceError(message: string): string | null {\n const match = TOOL_REFERENCE_NOT_FOUND_PATTERN.exec(message)\n return match?.[1] ?? null\n}\n\n// ============================================================================\n// Strategy\n// ============================================================================\n\n/**\n * Create a deferred tool retry strategy.\n *\n * When the API rejects a request because a deferred tool is referenced\n * in the message history, this strategy un-defers that tool and retries.\n */\nexport function createDeferredToolRetryStrategy<TPayload extends { tools?: Array<Tool> }>(): RetryStrategy<TPayload> {\n // Track tool names that have already been un-deferred across retries\n // to avoid infinite retry loops on the same tool\n const undeferredTools = new Set<string>()\n\n return {\n name: \"deferred-tool-retry\",\n\n canHandle(error: ApiError): boolean {\n if (error.type !== \"bad_request\" || error.status !== 400) return false\n\n const raw = error.raw\n if (!raw || typeof raw !== \"object\" || !(\"responseText\" in raw)) return false\n\n const responseText = (raw as { responseText: string }).responseText\n const toolName = parseToolReferenceFromResponse(responseText)\n if (!toolName) return false\n\n // Only handle if we haven't already retried for this tool\n return !undeferredTools.has(toolName)\n },\n\n handle(error: ApiError, currentPayload: TPayload, context: RetryContext<TPayload>): Promise<RetryAction<TPayload>> {\n const raw = error.raw as { responseText: string }\n const toolName = parseToolReferenceFromResponse(raw.responseText)\n\n if (!toolName || !currentPayload.tools) {\n return Promise.resolve({ action: \"abort\", error })\n }\n\n // Find the tool in the payload\n const toolIndex = currentPayload.tools.findIndex((t) => t.name === toolName)\n if (toolIndex === -1) {\n consola.warn(`[DeferredToolRetry] Tool \"${toolName}\" not found in payload tools, cannot un-defer`)\n return Promise.resolve({ action: \"abort\", error })\n }\n\n // Mark as un-deferred and track it\n undeferredTools.add(toolName)\n\n const newTools = [...currentPayload.tools]\n newTools[toolIndex] = { ...newTools[toolIndex], defer_loading: false }\n\n consola.info(\n `[DeferredToolRetry] Attempt ${context.attempt + 1}/${context.maxRetries + 1}: `\n + `Un-deferring tool \"${toolName}\" and retrying`,\n )\n\n return Promise.resolve({\n action: \"retry\",\n payload: { ...currentPayload, tools: newTools },\n meta: { undeferredTool: toolName },\n })\n },\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Parse tool name from the error response JSON */\nfunction parseToolReferenceFromResponse(responseText: string): string | null {\n try {\n const parsed = JSON.parse(responseText) as { error?: { message?: string } }\n const message = parsed.error?.message\n if (!message) return null\n return parseToolReferenceError(message)\n } catch {\n // Try raw text match as fallback\n return parseToolReferenceError(responseText)\n }\n}\n","/**\n * Token refresh retry strategy.\n *\n * Handles 401/403 errors by refreshing the Copilot token and retrying.\n * When the Copilot token expires between scheduled refreshes, this strategy\n * triggers an immediate refresh so the request can be retried transparently.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\n\nimport { getCopilotTokenManager } from \"~/lib/token\"\n\nimport type { RetryAction, RetryContext, RetryStrategy } from \"../pipeline\"\n\n/**\n * Refresh the Copilot token via the global manager.\n * Returns true on success, false on failure.\n */\nasync function refreshCopilotToken(): Promise<boolean> {\n const manager = getCopilotTokenManager()\n if (!manager) return false\n const result = await manager.refresh()\n return result !== null\n}\n\n/**\n * Create a token refresh retry strategy.\n *\n * On `auth_expired` errors (401/403), refreshes the Copilot token via\n * `CopilotTokenManager.refresh()`, then retries with the same payload.\n * Only retries once per pipeline execution to avoid infinite refresh loops.\n */\nexport function createTokenRefreshStrategy<TPayload>(): RetryStrategy<TPayload> {\n // Track whether we've already attempted a refresh in this pipeline execution.\n // A second 401 after refresh means the problem isn't a stale token.\n let hasRefreshed = false\n\n return {\n name: \"token-refresh\",\n\n canHandle(error: ApiError): boolean {\n return error.type === \"auth_expired\" && !hasRefreshed\n },\n\n async handle(\n error: ApiError,\n currentPayload: TPayload,\n context: RetryContext<TPayload>,\n ): Promise<RetryAction<TPayload>> {\n consola.info(\n `[TokenRefresh] Attempt ${context.attempt + 1}/${context.maxRetries + 1}: `\n + `Got ${error.status}, refreshing Copilot token...`,\n )\n\n const success = await refreshCopilotToken()\n hasRefreshed = true\n\n if (!success) {\n consola.error(\"[TokenRefresh] Token refresh failed, aborting request\")\n return { action: \"abort\", error }\n }\n\n consola.info(\"[TokenRefresh] Token refreshed, retrying request\")\n\n // Retry with the same payload — the new token is in global state,\n // which the adapter reads when constructing the Authorization header\n return {\n action: \"retry\",\n payload: currentPayload,\n meta: { tokenRefreshed: true },\n }\n },\n }\n}\n","/**\n * System Prompt Manager: config-based overrides.\n *\n * - **Overrides**: Applies per-line replacement rules from config.yaml.\n * Always active.\n */\n\nimport type { TextBlockParam } from \"~/types/api/anthropic\"\nimport type { ContentPart, Message } from \"~/types/api/openai\"\n\nimport { applyConfigToState } from \"./config/config\"\nimport { state, type CompiledRewriteRule } from \"./state\"\n\n// ============================================================================\n// Override Application\n// ============================================================================\n\n/**\n * Apply overrides to a text block.\n * - line: split by newlines, if a trimmed line matches trimmed `from`, replace that line with `to`\n * - regex: apply regex on the entire text with gms flags (multiline: ^$ match line boundaries, dotAll: . matches \\n)\n */\nexport function applyOverrides(text: string, rules: Array<CompiledRewriteRule>): string {\n let result = text\n for (const rule of rules) {\n if (rule.method === \"line\") {\n const lines = result.split(\"\\n\")\n result = lines.map((line) => (line.trim() === (rule.from as string).trim() ? rule.to : line)).join(\"\\n\")\n } else {\n result = result.replace(rule.from as RegExp, rule.to)\n }\n }\n return result\n}\n\n// ============================================================================\n// Public API: Anthropic\n// ============================================================================\n\nexport async function processAnthropicSystem(\n system: string | Array<TextBlockParam> | undefined,\n): Promise<string | Array<TextBlockParam> | undefined> {\n if (!system) return system\n\n // Load config (also applies to state, populating systemPromptOverrides)\n const config = await applyConfigToState()\n const prepend = config.system_prompt_prepend\n const append = config.system_prompt_append\n\n // Apply overrides per block\n let result = system\n if (state.systemPromptOverrides.length > 0) {\n result =\n typeof result === \"string\" ?\n applyOverrides(result, state.systemPromptOverrides)\n : result.map((block) => ({\n ...block,\n text: applyOverrides(block.text, state.systemPromptOverrides),\n }))\n }\n\n // Apply prepend\n if (prepend) {\n result =\n typeof result === \"string\" ? prepend + \"\\n\\n\" + result : [{ type: \"text\" as const, text: prepend }, ...result]\n }\n\n // Apply append\n if (append) {\n result =\n typeof result === \"string\" ? result + \"\\n\\n\" + append : [...result, { type: \"text\" as const, text: append }]\n }\n\n return result\n}\n\n// ============================================================================\n// Public API: OpenAI\n// ============================================================================\n\nexport async function processOpenAIMessages(messages: Array<Message>): Promise<Array<Message>> {\n // Extract system/developer messages\n const systemMessages = messages.filter((m) => m.role === \"system\" || m.role === \"developer\")\n if (systemMessages.length === 0) {\n // Even with no system messages, we may need to prepend/append\n const config = await applyConfigToState()\n let result = messages\n if (config.system_prompt_prepend) {\n result = [{ role: \"system\" as const, content: config.system_prompt_prepend }, ...result]\n }\n if (config.system_prompt_append) {\n result = [...result, { role: \"system\" as const, content: config.system_prompt_append }]\n }\n return result\n }\n\n // Load config (also applies to state, populating systemPromptOverrides)\n const config = await applyConfigToState()\n const prepend = config.system_prompt_prepend\n const append = config.system_prompt_append\n\n // Apply overrides to system/developer messages\n let result =\n state.systemPromptOverrides.length > 0 ?\n messages.map((msg) => {\n if (msg.role !== \"system\" && msg.role !== \"developer\") return msg\n\n if (typeof msg.content === \"string\") {\n return { ...msg, content: applyOverrides(msg.content, state.systemPromptOverrides) }\n }\n\n if (Array.isArray(msg.content)) {\n return {\n ...msg,\n content: msg.content.map((part: ContentPart) => {\n if (part.type === \"text\") {\n return { ...part, text: applyOverrides(part.text, state.systemPromptOverrides) }\n }\n return part\n }),\n }\n }\n\n return msg\n })\n : messages\n\n // Apply prepend — insert a system message at the beginning\n if (prepend) {\n result = [{ role: \"system\" as const, content: prepend }, ...result]\n }\n\n // Apply append — insert a system message at the end\n if (append) {\n result = [...result, { role: \"system\" as const, content: append }]\n }\n\n return result\n}\n","/**\n * Message mapping utilities for correlating original and rewritten message arrays.\n *\n * Used by both direct Anthropic and translated handlers to track which\n * rewritten messages correspond to which original messages.\n */\n\nimport type { MessageParam } from \"~/types/api/anthropic\"\n\n/**\n * Check if two messages likely correspond to the same original message.\n * Used by buildMessageMapping to handle cases where sanitization removes\n * content blocks within a message (changing its shape) or removes entire messages.\n */\nexport function messagesMatch(orig: MessageParam, rewritten: MessageParam): boolean {\n if (orig.role !== rewritten.role) return false\n\n // String content: compare prefix\n if (typeof orig.content === \"string\" && typeof rewritten.content === \"string\")\n return (\n rewritten.content.startsWith(orig.content.slice(0, 100))\n || orig.content.startsWith(rewritten.content.slice(0, 100))\n )\n\n // Array content: compare first block's type and id\n const origBlocks = Array.isArray(orig.content) ? orig.content : []\n const rwBlocks = Array.isArray(rewritten.content) ? rewritten.content : []\n\n if (origBlocks.length === 0 || rwBlocks.length === 0) return true\n\n const ob = origBlocks[0]\n const rb = rwBlocks[0]\n if (ob.type !== rb.type) return false\n if (ob.type === \"tool_use\" && rb.type === \"tool_use\") return ob.id === rb.id\n if (ob.type === \"tool_result\" && rb.type === \"tool_result\") return ob.tool_use_id === rb.tool_use_id\n return true\n}\n\n/**\n * Build messageMapping (rwIdx → origIdx) for the direct Anthropic path.\n * Uses a two-pointer approach since rewritten messages maintain the same relative\n * order as originals (all transformations are deletions, never reorderings).\n */\nexport function buildMessageMapping(original: Array<MessageParam>, rewritten: Array<MessageParam>): Array<number> {\n const mapping: Array<number> = []\n let origIdx = 0\n\n for (const element of rewritten) {\n while (origIdx < original.length) {\n if (messagesMatch(original[origIdx], element)) {\n mapping.push(origIdx)\n origIdx++\n break\n }\n origIdx++\n }\n }\n\n // If matching missed some (shouldn't happen), fill with -1\n while (mapping.length < rewritten.length) {\n mapping.push(-1)\n }\n\n return mapping\n}\n","/**\n * Anthropic API Types\n *\n * Content block types, stream events, and response types are imported from\n * the `@anthropic-ai/sdk`. Request payload and tool types remain our own\n * definitions since Copilot proxies arbitrary model names (not SDK's literal\n * union) and adds extensions (context_management, copilot_annotations).\n */\n\n// ============================================================================\n// Re-export SDK types\n// ============================================================================\n\n// Response content blocks\nexport type {\n ContentBlock,\n RedactedThinkingBlock,\n ServerToolUseBlock,\n TextBlock,\n ThinkingBlock,\n ToolUseBlock,\n WebSearchToolResultBlock,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Request content blocks\nexport type {\n ContentBlockParam,\n ImageBlockParam,\n TextBlockParam,\n ToolResultBlockParam,\n ToolUseBlockParam,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Messages\nexport type { Message, MessageParam } from \"@anthropic-ai/sdk/resources/messages\"\n\n// Thinking & cache\nexport type { CacheControlEphemeral, ThinkingConfigParam } from \"@anthropic-ai/sdk/resources/messages\"\n\n// Stream events\nexport type {\n RawContentBlockDelta,\n RawContentBlockStartEvent,\n RawContentBlockStopEvent,\n RawMessageDeltaEvent,\n RawMessageStartEvent,\n RawMessageStopEvent,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Internal-only SDK imports (not re-exported)\nimport type {\n ContentBlock,\n ContentBlockParam,\n TextBlockParam,\n MessageParam,\n ThinkingConfigParam,\n CacheControlEphemeral,\n WebSearchToolResultBlock,\n ToolResultBlockParam,\n RawContentBlockDeltaEvent,\n RawMessageStartEvent,\n RawMessageStopEvent,\n RawMessageDeltaEvent,\n RawContentBlockStartEvent,\n RawContentBlockStopEvent,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// ============================================================================\n// Request payload (our own — SDK uses Model literal union, we proxy strings)\n// ============================================================================\n\nexport interface MessagesPayload {\n model: string\n max_tokens: number\n messages: Array<MessageParam>\n system?: string | Array<TextBlockParam>\n temperature?: number\n top_p?: number\n top_k?: number\n stop_sequences?: Array<string>\n stream?: boolean\n tools?: Array<Tool>\n tool_choice?: ToolChoice\n thinking?: ThinkingConfigParam\n metadata?: { user_id?: string }\n context_management?: Record<string, unknown>\n}\n\nexport interface Tool {\n name: string\n description?: string\n input_schema?: Record<string, unknown>\n cache_control?: CacheControlEphemeral\n type?: string\n defer_loading?: boolean\n}\n\nexport type ToolChoice = { type: \"auto\" } | { type: \"any\" } | { type: \"none\" } | { type: \"tool\"; name: string }\n\n// ============================================================================\n// Message subtypes (narrow role for cast convenience)\n// ============================================================================\n\nexport interface UserMessage {\n role: \"user\"\n content: string | Array<ContentBlockParam>\n}\n\nexport interface AssistantMessage {\n role: \"assistant\"\n content: string | Array<ContentBlockParam>\n}\n\n// ============================================================================\n// Copilot Extensions (not part of the Anthropic API)\n// ============================================================================\n\nexport interface CopilotIPCodeCitation {\n url: string\n license: string\n repository: string\n start_line: number\n end_line: number\n}\n\n/** Copilot-specific annotations attached to SSE content block deltas */\nexport interface CopilotAnnotations {\n ip_code_citations?: Array<CopilotIPCodeCitation>\n}\n\n/** Content block delta event with Copilot annotations extension */\ntype CopilotContentBlockDeltaEvent = RawContentBlockDeltaEvent & {\n copilot_annotations?: CopilotAnnotations\n}\n\nexport interface StreamPingEvent {\n type: \"ping\"\n}\n\nexport interface StreamErrorEvent {\n type: \"error\"\n error: { type: string; message: string }\n}\n\n/** Stream event union — replaces SDK's delta event with our Copilot-extended version */\nexport type StreamEvent =\n | RawMessageStartEvent\n | RawMessageStopEvent\n | RawMessageDeltaEvent\n | RawContentBlockStartEvent\n | RawContentBlockStopEvent\n | CopilotContentBlockDeltaEvent\n | StreamPingEvent\n | StreamErrorEvent\n\n// ============================================================================\n// Type guards\n// ============================================================================\n\n/** Type guard for ToolResultBlockParam */\nexport function isToolResultBlock(block: ContentBlockParam): block is ToolResultBlockParam {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- type guard pattern requires cast\n return (block as ToolResultBlockParam).type === \"tool_result\"\n}\n\n/** Type guard for server-side tool result blocks (web_search, tool_search, code_execution, etc.) */\nexport function isServerToolResultBlock(block: ContentBlockParam | ContentBlock): block is WebSearchToolResultBlock {\n // Cast to string to allow matching beyond the SDK's narrow literal type union.\n // Server tool results include: web_search_tool_result, tool_search_tool_result,\n // code_execution_tool_result, etc. They all end with \"_tool_result\" and carry a tool_use_id.\n // Exclude plain \"tool_result\" which is the standard user-side tool result.\n const type = (block as unknown as Record<string, unknown>).type as string | undefined\n if (!type) return false\n return type !== \"tool_result\" && type.endsWith(\"_tool_result\") && \"tool_use_id\" in block\n}\n","/**\n * Anthropic message sanitization orchestrator.\n *\n * Combines system-reminder removal, orphan filtering, and empty block cleanup\n * into a single sanitization pipeline for Anthropic messages.\n */\n\nimport consola from \"consola\"\n\nimport type { SanitizeResult } from \"~/lib/request/pipeline\"\nimport type {\n ContentBlock,\n AssistantMessage,\n MessageParam,\n MessagesPayload,\n ContentBlockParam,\n Tool,\n UserMessage,\n} from \"~/types/api/anthropic\"\n\nimport { removeSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\nimport { extractLeadingSystemReminderTags, extractTrailingSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\nimport { state } from \"~/lib/state\"\nimport { isServerToolResultBlock } from \"~/types/api/anthropic\"\n\n// ============================================================================\n// Shared: Sanitize text blocks in an array\n// ============================================================================\n\n/**\n * Remove system-reminder tags from text blocks in an array.\n * Drops blocks whose text becomes empty after sanitization.\n * Returns the original array reference if nothing changed (for cheap identity checks).\n */\nfunction sanitizeTextBlocksInArray<T extends { type: string }>(\n blocks: Array<T>,\n getText: (b: T) => string | undefined,\n setText: (b: T, text: string) => T,\n): { blocks: Array<T>; modified: boolean } {\n let modified = false\n const result: Array<T> = []\n\n for (const block of blocks) {\n const text = getText(block)\n if (text !== undefined) {\n const sanitized = removeSystemReminderTags(text)\n if (sanitized !== text) {\n modified = true\n if (sanitized) {\n result.push(setText(block, sanitized))\n }\n continue\n }\n }\n result.push(block)\n }\n\n return { blocks: modified ? result : blocks, modified }\n}\n\n// ============================================================================\n// Tool Result Content Sanitization\n// ============================================================================\n\n/**\n * Sanitize tool_result content (can be string or array of text/image blocks).\n * Returns the sanitized content and whether it was modified.\n */\nfunction sanitizeToolResultContent(\n content: string | Array<{ type: \"text\"; text: string } | { type: \"image\"; source: unknown }>,\n): { content: typeof content; modified: boolean } {\n if (typeof content === \"string\") {\n const sanitized = removeSystemReminderTags(content)\n // Don't return empty content — keep original if sanitized is empty\n if (!sanitized && sanitized !== content) {\n return { content, modified: false }\n }\n return { content: sanitized, modified: sanitized !== content }\n }\n\n const { blocks, modified } = sanitizeTextBlocksInArray(\n content,\n (b) => (b.type === \"text\" ? b.text : undefined),\n (b, text) => ({ ...b, text }),\n )\n return { content: modified ? blocks : content, modified }\n}\n\n// ============================================================================\n// Message Content Sanitization\n// ============================================================================\n\n/**\n * Remove system-reminder tags from Anthropic message content.\n */\nfunction sanitizeMessageParamContent(msg: MessageParam): MessageParam {\n if (typeof msg.content === \"string\") {\n const sanitized = removeSystemReminderTags(msg.content)\n if (sanitized !== msg.content) {\n // Don't return empty content — keep original if sanitized is empty\n return sanitized ? { ...msg, content: sanitized } : msg\n }\n return msg\n }\n\n if (msg.role === \"user\") {\n // User messages: sanitize text blocks + tool_result content\n let modified = false\n const blocks: Array<ContentBlockParam> = []\n\n for (const block of msg.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n const sanitized = removeSystemReminderTags(block.text)\n if (sanitized !== block.text) {\n modified = true\n if (sanitized) blocks.push({ ...block, text: sanitized })\n continue\n }\n } else if (block.type === \"tool_result\" && block.content) {\n const sanitizedResult = sanitizeToolResultContent(\n block.content as Parameters<typeof sanitizeToolResultContent>[0],\n )\n if (sanitizedResult.modified) {\n modified = true\n blocks.push({ ...block, content: sanitizedResult.content } as ContentBlockParam)\n continue\n }\n }\n blocks.push(block)\n }\n\n return modified ? ({ role: \"user\", content: blocks } as UserMessage) : msg\n }\n\n // Assistant message: only sanitize text blocks\n const { blocks, modified } = sanitizeTextBlocksInArray(\n msg.content,\n (b) => (b.type === \"text\" && \"text\" in b ? (b as { text: string }).text : undefined),\n (b, text) => ({ ...b, text }) as ContentBlock,\n )\n return modified ? ({ role: \"assistant\", content: blocks } as AssistantMessage) : msg\n}\n\n/**\n * Remove system-reminder tags from all Anthropic messages.\n */\nexport function removeAnthropicSystemReminders(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n modifiedCount: number\n} {\n let modifiedCount = 0\n const result = messages.map((msg) => {\n const sanitized = sanitizeMessageParamContent(msg)\n if (sanitized !== msg) modifiedCount++\n return sanitized\n })\n // Return original array reference when nothing changed (avoids unnecessary allocation)\n return { messages: modifiedCount === 0 ? messages : result, modifiedCount }\n}\n\n// ============================================================================\n// System Prompt Sanitization\n// ============================================================================\n\n/**\n * Sanitize Anthropic system prompt (can be string or array of text blocks).\n * Only removes system-reminder tags here.\n *\n * NOTE: Restrictive statement filtering is handled separately by:\n * - system-prompt-manager.ts (via config.yaml overrides)\n * This avoids duplicate processing of the system prompt.\n */\nfunction sanitizeAnthropicSystemPrompt(system: string | Array<{ type: \"text\"; text: string }> | undefined): {\n system: typeof system\n modified: boolean\n} {\n if (!system) {\n return { system, modified: false }\n }\n\n if (typeof system === \"string\") {\n const sanitized = removeSystemReminderTags(system)\n return { system: sanitized, modified: sanitized !== system }\n }\n\n const { blocks, modified } = sanitizeTextBlocksInArray(\n system,\n (b) => b.text,\n (b, text) => ({ ...b, text }),\n )\n return { system: modified ? blocks : system, modified }\n}\n\n// ============================================================================\n// Empty Block Cleanup\n// ============================================================================\n\n/**\n * Final pass: remove any empty/whitespace-only text content blocks from Anthropic messages.\n * This is a safety net that catches empty blocks regardless of how they were produced\n * (original input, sanitization, truncation, etc.).\n * Anthropic API rejects text blocks with empty text: \"text content blocks must be non-empty\"\n */\nfunction filterEmptyAnthropicTextBlocks(messages: Array<MessageParam>): Array<MessageParam> {\n return messages.map((msg) => {\n if (typeof msg.content === \"string\") return msg\n\n // Never modify assistant messages that contain thinking/redacted_thinking blocks.\n // The API validates thinking block signatures against the original response —\n // even removing an adjacent empty text block causes the content array to change,\n // which can trigger \"thinking blocks cannot be modified\" errors after\n // context_management truncation changes which message becomes the \"latest\".\n if (msg.role === \"assistant\" && msg.content.some((b) => b.type === \"thinking\" || b.type === \"redacted_thinking\")) {\n return msg\n }\n\n const filtered = msg.content.filter((block) => {\n if (block.type === \"text\" && \"text\" in block) {\n return block.text.trim() !== \"\"\n }\n return true\n })\n\n if (filtered.length === msg.content.length) return msg\n return { ...msg, content: filtered } as MessageParam\n })\n}\n\n/**\n * Final pass: remove any empty/whitespace-only text blocks from Anthropic system prompt.\n */\nfunction filterEmptySystemTextBlocks(system: MessagesPayload[\"system\"]): MessagesPayload[\"system\"] {\n if (!system || typeof system === \"string\") return system\n return system.filter((block) => block.text.trim() !== \"\")\n}\n\n// ============================================================================\n// Combined Tool Block Processing\n// ============================================================================\n\n/**\n * Parse a potentially stringified JSON input into a proper object.\n * Handles double-serialized strings (e.g., \"\\\"{ ... }\\\"\") by parsing iteratively.\n */\nfunction parseStringifiedInput(input: unknown): Record<string, unknown> {\n if (typeof input !== \"string\") return input as Record<string, unknown>\n try {\n let parsed: unknown = input\n while (typeof parsed === \"string\") {\n parsed = JSON.parse(parsed)\n }\n return (typeof parsed === \"object\" && parsed !== null ? parsed : {}) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\n/**\n * Process all tool-related operations in a single pass:\n * 1. Fix tool_use name casing\n * 2. Filter orphaned tool_result blocks\n * 3. Filter orphaned tool_use blocks\n *\n * This combines what were previously three separate operations (each with their own iterations)\n * into two passes through the messages array for better performance.\n */\nexport function processToolBlocks(\n messages: Array<MessageParam>,\n tools: Array<{ name: string }> | undefined,\n): {\n messages: Array<MessageParam>\n fixedNameCount: number\n orphanedToolUseCount: number\n orphanedToolResultCount: number\n} {\n // Build case-insensitive tool name map if tools are provided\n const nameMap = new Map<string, string>()\n if (tools && tools.length > 0) {\n for (const tool of tools) {\n nameMap.set(tool.name.toLowerCase(), tool.name)\n }\n }\n\n // Pass 1: Collect all tool_use/server_tool_use and tool_result/web_search_tool_result IDs\n const toolUseIds = new Set<string>()\n const toolResultIds = new Set<string>()\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") continue\n\n if (msg.role === \"assistant\") {\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && block.id) {\n toolUseIds.add(block.id)\n }\n // Server tool results can appear in assistant messages (server-side execution).\n // Collect their IDs so the corresponding server_tool_use is not treated as orphaned.\n if (isServerToolResultBlock(block)) {\n toolResultIds.add(block.tool_use_id)\n }\n }\n } else {\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && block.tool_use_id) {\n toolResultIds.add(block.tool_use_id)\n } else if (isServerToolResultBlock(block)) {\n toolResultIds.add(block.tool_use_id)\n }\n }\n }\n }\n\n // Pass 2: Process messages - fix names and filter orphans\n const result: Array<MessageParam> = []\n let fixedNameCount = 0\n let orphanedToolUseCount = 0\n let orphanedToolResultCount = 0\n // Track tool_use IDs that were filtered (orphaned) so their tool_results are also filtered\n const filteredToolUseIds = new Set<string>()\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n if (msg.role === \"assistant\") {\n // Process assistant messages: fix tool names and filter orphaned tool_use/server_tool_use.\n // IMPORTANT: Only create a new message object when content is actually modified.\n // Assistant messages may contain thinking/redacted_thinking blocks with signatures\n // that the API validates. Creating a new object (even with identical content) can\n // trigger \"thinking blocks cannot be modified\" errors after context_management\n // truncation changes which message the API considers the \"latest assistant message\".\n const newContent: Array<ContentBlockParam> = []\n let modified = false\n\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n // Check if orphaned (no corresponding tool_result)\n if (!toolResultIds.has(block.id)) {\n orphanedToolUseCount++\n filteredToolUseIds.add(block.id)\n modified = true\n continue // Skip orphaned tool_use\n }\n\n // Apply fixes: name casing and input deserialization\n const correctName = nameMap.get(block.name.toLowerCase())\n const needsNameFix = correctName !== undefined && correctName !== block.name\n const needsInputFix = typeof block.input === \"string\"\n\n if (needsNameFix || needsInputFix) {\n modified = true\n const fixed = { ...block } as typeof block\n if (needsNameFix) {\n fixedNameCount++\n ;(fixed as { name: string }).name = correctName\n }\n if (needsInputFix) {\n ;(fixed as { input: Record<string, unknown> }).input = parseStringifiedInput(block.input)\n }\n newContent.push(fixed)\n } else {\n newContent.push(block)\n }\n } else if (block.type === \"server_tool_use\") {\n // Check if orphaned (no corresponding web_search_tool_result)\n if (!toolResultIds.has(block.id)) {\n orphanedToolUseCount++\n filteredToolUseIds.add(block.id)\n modified = true\n continue // Skip orphaned server_tool_use\n }\n // Ensure input is an object (clients may send it as a JSON string from stream accumulation)\n if (typeof block.input === \"string\") {\n modified = true\n newContent.push({ ...block, input: parseStringifiedInput(block.input) })\n } else {\n newContent.push(block)\n }\n } else {\n // For server tool results in assistant messages (e.g., tool_search_tool_result),\n // check if their corresponding server_tool_use is still present\n if (\n isServerToolResultBlock(block)\n && (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id))\n ) {\n orphanedToolResultCount++\n modified = true\n continue // Skip orphaned server tool result\n }\n newContent.push(block as ContentBlockParam)\n }\n }\n\n // Skip message if all content was removed\n if (newContent.length === 0) continue\n\n // Preserve original message object when no modifications were made — this is\n // critical for messages with thinking blocks whose signatures must not change\n result.push(modified ? { ...msg, content: newContent } : msg)\n } else {\n // Process user messages: filter orphaned tool_result/web_search_tool_result\n const newContent: Array<ContentBlockParam> = []\n\n for (const block of msg.content) {\n if (block.type === \"tool_result\") {\n // Check if orphaned (no corresponding tool_use) or tool_use was filtered\n if (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id)) {\n orphanedToolResultCount++\n continue // Skip orphaned tool_result\n }\n } else if (isServerToolResultBlock(block)) {\n // Check if orphaned (no corresponding server_tool_use) or server_tool_use was filtered\n if (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id)) {\n orphanedToolResultCount++\n continue // Skip orphaned server tool result\n }\n } else if (\n (block as unknown as Record<string, unknown>).type !== \"text\"\n && (block as unknown as Record<string, unknown>).type !== \"image\"\n ) {\n // Unknown block type without tool_use_id (e.g., corrupted server tool result\n // from older history where tool_use_id was lost during conversion).\n // Filter it out to prevent API errors.\n orphanedToolResultCount++\n continue\n }\n newContent.push(block)\n }\n\n // Skip message if all content was removed\n if (newContent.length === 0) continue\n\n result.push({ ...msg, content: newContent })\n }\n }\n\n return {\n messages: result,\n fixedNameCount,\n orphanedToolUseCount,\n orphanedToolResultCount,\n }\n}\n\n// ============================================================================\n// Phase 1: One-time Preprocessing (幂等预处理,路由前执行一次)\n// ============================================================================\n// These operations are idempotent — once processed, re-running produces no\n// further changes. They do NOT need to re-run after auto-truncate retries.\n//\n// Includes:\n// - deduplicateToolCalls: remove repeated tool_use/tool_result pairs\n// - stripReadToolResultTags: strip injected <system-reminder> from Read results\n// ============================================================================\n\n// Dedup Tool Calls\n// ----------------------------------------------------------------------------\n\n/**\n * Remove duplicate tool_use/tool_result pairs, keeping only the last occurrence\n * of each matching combination.\n *\n * Claude Code sometimes enters a \"read loop\" where it repeatedly reads the same\n * files without progressing, causing prompt inflation. This removes redundant\n * earlier calls while preserving the most recent result for each unique call.\n *\n * @param mode - `\"input\"`: match by (tool_name, input).\n * `\"result\"`: match by (tool_name, input, result_content) — only dedup\n * when the result is also identical.\n *\n * After removal, empty messages are dropped and consecutive same-role messages\n * are merged (Anthropic requires strict user/assistant alternation).\n */\nexport function deduplicateToolCalls(\n messages: Array<MessageParam>,\n mode: \"input\" | \"result\" = \"input\",\n): {\n messages: Array<MessageParam>\n dedupedCount: number\n /** Per-tool breakdown of how many duplicate calls were removed */\n dedupedByTool: Record<string, number>\n} {\n // Step 1: Build tool_use name+input map from assistant messages\n // Map: tool_use id → (name, JSON(input)) key (may be extended with result content below)\n const toolUseKeys = new Map<string, string>()\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n const key = `${block.name}:${JSON.stringify(block.input)}`\n toolUseKeys.set(block.id, key)\n }\n }\n }\n\n // Step 1.5 (\"result\" mode): Extend keys with tool_result content.\n // This makes the dedup key (name, input, result) so calls with identical input\n // but different results are NOT considered duplicates.\n if (mode === \"result\") {\n // Collect tool_result content by tool_use_id\n const resultContentById = new Map<string, string>()\n for (const msg of messages) {\n if (msg.role !== \"user\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && toolUseKeys.has(block.tool_use_id)) {\n const resultStr = typeof block.content === \"string\" ? block.content : JSON.stringify(block.content)\n resultContentById.set(block.tool_use_id, resultStr)\n }\n }\n }\n\n // Extend each tool_use key with its result content\n for (const [id, baseKey] of toolUseKeys) {\n const resultContent = resultContentById.get(id)\n if (resultContent !== undefined) {\n toolUseKeys.set(id, `${baseKey}::${resultContent}`)\n }\n }\n }\n\n // Step 2: Reverse scan to find the LAST occurrence of each key (the keeper)\n const keeperIds = new Set<string>()\n const seenKeys = new Set<string>()\n\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (let j = msg.content.length - 1; j >= 0; j--) {\n const block = msg.content[j]\n if (block.type === \"tool_use\") {\n const key = toolUseKeys.get(block.id)\n if (!key) continue\n if (!seenKeys.has(key)) {\n seenKeys.add(key)\n keeperIds.add(block.id)\n }\n }\n }\n }\n\n // Step 2.5: Protect tool_use IDs in messages with thinking/redacted_thinking blocks.\n // The API validates thinking block signatures against the original response —\n // modifying the content array (even removing an adjacent tool_use) can trigger\n // \"thinking blocks cannot be modified\" errors.\n const protectedIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n const hasThinking = msg.content.some((b) => b.type === \"thinking\" || b.type === \"redacted_thinking\")\n if (!hasThinking) continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n protectedIds.add(block.id)\n }\n }\n }\n\n // Step 3: Forward scan — remove non-keeper tool_use blocks, collect their IDs\n const removedIds = new Set<string>()\n\n for (const [id, key] of toolUseKeys) {\n // If this ID has a key that was seen (i.e., has duplicates) but is not the keeper,\n // AND is not in a message with thinking blocks (which must not be modified)\n if (seenKeys.has(key) && !keeperIds.has(id) && !protectedIds.has(id)) {\n removedIds.add(id)\n }\n }\n\n if (removedIds.size === 0) {\n return { messages, dedupedCount: 0, dedupedByTool: {} }\n }\n\n // Build per-tool breakdown from removed IDs\n const dedupedByTool: Record<string, number> = {}\n for (const id of removedIds) {\n const key = toolUseKeys.get(id)\n if (key) {\n const toolName = key.slice(0, key.indexOf(\":\"))\n dedupedByTool[toolName] = (dedupedByTool[toolName] ?? 0) + 1\n }\n }\n\n // Step 4: Filter out removed tool_use and tool_result blocks\n const filtered: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n filtered.push(msg)\n continue\n }\n\n if (msg.role === \"assistant\") {\n const newContent = msg.content.filter((block) => block.type !== \"tool_use\" || !removedIds.has(block.id))\n if (newContent.length > 0) {\n // Preserve original object if nothing removed (thinking block signatures)\n if (newContent.length === msg.content.length) {\n filtered.push(msg)\n } else {\n filtered.push({ ...msg, content: newContent } as MessageParam)\n }\n }\n } else {\n const newContent = msg.content.filter(\n (block) => block.type !== \"tool_result\" || !removedIds.has(block.tool_use_id),\n )\n if (newContent.length > 0) {\n if (newContent.length === msg.content.length) {\n filtered.push(msg)\n } else {\n filtered.push({ ...msg, content: newContent } as MessageParam)\n }\n }\n }\n }\n\n // Step 5: Merge consecutive same-role messages (Anthropic requires alternation)\n const merged: Array<MessageParam> = []\n for (const msg of filtered) {\n const prev = merged.at(-1)\n if (prev && prev.role === msg.role) {\n // Merge content arrays\n const prevContent =\n typeof prev.content === \"string\" ? [{ type: \"text\" as const, text: prev.content }] : prev.content\n const currContent = typeof msg.content === \"string\" ? [{ type: \"text\" as const, text: msg.content }] : msg.content\n merged[merged.length - 1] = {\n ...prev,\n content: [...prevContent, ...currContent],\n } as MessageParam\n } else {\n merged.push(msg)\n }\n }\n\n return { messages: merged, dedupedCount: removedIds.size, dedupedByTool }\n}\n\n// Strip Read Tool Result Tags\n// ----------------------------------------------------------------------------\n\n/**\n * Strip ALL `<system-reminder>` tags from Read tool results.\n *\n * Claude Code injects system-reminder tags (TodoWrite reminders, Plan mode\n * reminders, etc.) into every tool_result. For Read tool results, these tags\n * are pure noise — they repeat every time the same file is read and inflate\n * context by 7-14%.\n *\n * Unlike the main `removeSystemReminderTags` (which only removes tags matching\n * enabled filters), this function removes ALL system-reminder tags regardless\n * of content, since Read results should contain only file content.\n */\nexport function stripReadToolResultTags(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n strippedCount: number\n tagPreviews: Array<string>\n} {\n // Step 1: Collect all Read tool_use IDs\n const readToolUseIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.name === \"Read\") {\n readToolUseIds.add(block.id)\n }\n }\n }\n\n if (readToolUseIds.size === 0) {\n return { messages, strippedCount: 0, tagPreviews: [] }\n }\n\n // Step 2: Strip tags from matching tool_result blocks\n let strippedCount = 0\n const allPreviews: Array<string> = []\n const result = messages.map((msg) => {\n if (msg.role !== \"user\" || typeof msg.content === \"string\") return msg\n\n let modified = false\n const newContent = msg.content.map((block) => {\n if (block.type !== \"tool_result\" || !readToolUseIds.has(block.tool_use_id)) {\n return block\n }\n\n const stripped = stripAllSystemReminderTags(block.content as string | Array<{ type: string; text?: string }>)\n if (stripped.modified) {\n modified = true\n strippedCount += stripped.tagCount\n allPreviews.push(...stripped.tagPreviews)\n return { ...block, content: stripped.content } as ContentBlockParam\n }\n return block\n })\n\n return modified ? ({ ...msg, content: newContent } as UserMessage) : msg\n })\n\n return { messages: strippedCount > 0 ? result : messages, strippedCount, tagPreviews: allPreviews }\n}\n\n/**\n * Strip ALL system-reminder tags from tool_result content (string or array form).\n * Returns the cleaned content and whether anything was modified.\n */\nfunction stripAllSystemReminderTags(content: string | Array<{ type: string; text?: string }>): {\n content: typeof content\n modified: boolean\n tagCount: number\n tagPreviews: Array<string>\n} {\n if (typeof content === \"string\") {\n return stripAllTagsFromString(content)\n }\n\n let totalTagCount = 0\n const allPreviews: Array<string> = []\n let modified = false\n const result = content.map((block) => {\n if (block.type !== \"text\" || !block.text) return block\n const stripped = stripAllTagsFromString(block.text)\n if (stripped.modified) {\n modified = true\n totalTagCount += stripped.tagCount\n allPreviews.push(...stripped.tagPreviews)\n return { ...block, text: stripped.content }\n }\n return block\n })\n\n return { content: modified ? result : content, modified, tagCount: totalTagCount, tagPreviews: allPreviews }\n}\n\n/**\n * Remove ALL system-reminder tags from a string, keeping only the main content.\n */\nfunction stripAllTagsFromString(text: string): {\n content: string\n modified: boolean\n tagCount: number\n tagPreviews: Array<string>\n} {\n let tagCount = 0\n const tagPreviews: Array<string> = []\n\n // Extract trailing tags\n const trailing = extractTrailingSystemReminderTags(text)\n tagCount += trailing.tags.length\n for (const tag of trailing.tags) {\n tagPreviews.push(tag.content.slice(0, 80))\n }\n\n // Extract leading tags from the remaining content\n const mainSlice = text.slice(0, trailing.mainContentEnd)\n const leading = extractLeadingSystemReminderTags(mainSlice)\n tagCount += leading.tags.length\n for (const tag of leading.tags) {\n tagPreviews.push(tag.content.slice(0, 80))\n }\n\n if (tagCount === 0) {\n return { content: text, modified: false, tagCount: 0, tagPreviews: [] }\n }\n\n const content = mainSlice.slice(leading.mainContentStart)\n return { content, modified: true, tagCount, tagPreviews }\n}\n\n// Preprocess Orchestrator\n// ----------------------------------------------------------------------------\n\n/**\n * One-time preprocessing of Anthropic messages.\n *\n * Runs idempotent operations that reduce context noise before the request\n * enters the routing / retry pipeline. These do NOT need to re-run after\n * auto-truncate retries because truncation cannot introduce new duplicates\n * or new system-reminder tags.\n */\nexport function preprocessAnthropicMessages(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n strippedReadTagCount: number\n dedupedToolCallCount: number\n} {\n let result = messages\n let strippedReadTagCount = 0\n let dedupedToolCallCount = 0\n\n // Strip injected <system-reminder> tags from Read tool results\n if (state.truncateReadToolResult) {\n const strip = stripReadToolResultTags(result)\n result = strip.messages\n strippedReadTagCount = strip.strippedCount\n if (strippedReadTagCount > 0) {\n consola.info(\n `[Preprocess] Stripped ${strippedReadTagCount} system-reminder tags from Read results:\\n`\n + strip.tagPreviews.map((p) => ` - \"${p}${p.length >= 80 ? \"…\" : \"\"}\"`).join(\"\\n\"),\n )\n }\n }\n\n // Deduplicate repeated tool_use/tool_result pairs (keep last occurrence)\n if (state.dedupToolCalls) {\n const dedup = deduplicateToolCalls(result, state.dedupToolCalls)\n result = dedup.messages\n dedupedToolCallCount = dedup.dedupedCount\n if (dedupedToolCallCount > 0) {\n const breakdown = Object.entries(dedup.dedupedByTool)\n .map(([name, count]) => `${name}×${count}`)\n .join(\", \")\n consola.info(`[Preprocess] Deduped ${dedupedToolCallCount} tool calls [${state.dedupToolCalls}] (${breakdown})`)\n }\n }\n\n return { messages: result, strippedReadTagCount, dedupedToolCallCount }\n}\n\n// ============================================================================\n// Phase 2: Repeatable Sanitization (可重复清洗,truncate 后需重新执行)\n// ============================================================================\n// These operations must re-run after every auto-truncate retry because\n// truncation can break tool_use/tool_result pairing and produce empty blocks.\n//\n// Includes:\n// - sanitizeAnthropicSystemPrompt: clean system prompt system-reminders\n// - removeAnthropicSystemReminders: clean message system-reminders\n// - processToolBlocks: fix tool names + filter orphaned tool blocks\n// - filterEmptyAnthropicTextBlocks: safety net for empty text blocks\n// ============================================================================\n\n/**\n * Count total content blocks in Anthropic messages.\n */\nfunction countAnthropicContentBlocks(messages: Array<MessageParam>): number {\n let count = 0\n for (const msg of messages) {\n count += typeof msg.content === \"string\" ? 1 : msg.content.length\n }\n return count\n}\n\nexport interface SanitizationStats {\n orphanedToolUseCount: number\n orphanedToolResultCount: number\n fixedNameCount: number\n emptyTextBlocksRemoved: number\n systemReminderRemovals: number\n totalBlocksRemoved: number\n}\n\n/**\n * Sanitize Anthropic messages by filtering orphaned tool blocks and system reminders.\n *\n * Returns both convenience totals (removedCount, systemReminderRemovals) for backward\n * compatibility, and structured stats for callers that need detail.\n */\nexport function sanitizeAnthropicMessages(\n payload: MessagesPayload,\n): SanitizeResult<MessagesPayload> & { stats: SanitizationStats } {\n let messages = payload.messages\n const originalBlocks = countAnthropicContentBlocks(messages)\n\n // Remove system-reminder tags from system prompt\n const { system: sanitizedSystem } = sanitizeAnthropicSystemPrompt(payload.system)\n\n // Remove system-reminder tags from all messages\n const reminderResult = removeAnthropicSystemReminders(messages)\n messages = reminderResult.messages\n const systemReminderRemovals = reminderResult.modifiedCount\n\n // Process all tool-related operations in a single pass:\n // - Fix tool_use name casing (e.g., \"bash\" → \"Bash\")\n // - Filter orphaned tool_result blocks\n // - Filter orphaned tool_use blocks\n const toolResult = processToolBlocks(messages, payload.tools)\n messages = toolResult.messages\n\n if (toolResult.fixedNameCount > 0) {\n consola.debug(`[Sanitizer:Anthropic] Fixed ${toolResult.fixedNameCount} tool name casing mismatches`)\n }\n\n // Final safety net: remove any remaining empty/whitespace-only text blocks\n // This catches empty blocks from any source (input, sanitization, truncation)\n messages = filterEmptyAnthropicTextBlocks(messages)\n const finalSystem = filterEmptySystemTextBlocks(sanitizedSystem)\n\n const newBlocks = countAnthropicContentBlocks(messages)\n const totalBlocksRemoved = originalBlocks - newBlocks\n const emptyTextBlocksRemoved =\n totalBlocksRemoved - toolResult.orphanedToolUseCount - toolResult.orphanedToolResultCount\n\n if (totalBlocksRemoved > 0 && (toolResult.orphanedToolUseCount > 0 || toolResult.orphanedToolResultCount > 0)) {\n const parts: Array<string> = []\n if (toolResult.orphanedToolUseCount > 0) parts.push(`${toolResult.orphanedToolUseCount} orphaned tool_use`)\n if (toolResult.orphanedToolResultCount > 0) parts.push(`${toolResult.orphanedToolResultCount} orphaned tool_result`)\n if (emptyTextBlocksRemoved > 0) parts.push(`${emptyTextBlocksRemoved} empty text blocks`)\n consola.info(`[Sanitizer:Anthropic] Removed ${totalBlocksRemoved} content blocks (${parts.join(\", \")})`)\n }\n\n return {\n payload: { ...payload, system: finalSystem, messages },\n removedCount: totalBlocksRemoved,\n systemReminderRemovals,\n stats: {\n orphanedToolUseCount: toolResult.orphanedToolUseCount,\n orphanedToolResultCount: toolResult.orphanedToolResultCount,\n fixedNameCount: toolResult.fixedNameCount,\n emptyTextBlocksRemoved: Math.max(0, emptyTextBlocksRemoved),\n systemReminderRemovals,\n totalBlocksRemoved,\n },\n }\n}\n\n// ============================================================================\n// Server Tool Rewriting\n// ============================================================================\n\n/**\n * Server-side tool type prefixes that need special handling.\n * These tools have a special `type` field (e.g., \"web_search_20250305\")\n * and are normally executed by Anthropic's servers.\n */\ninterface ServerToolConfig {\n description: string\n input_schema: Record<string, unknown>\n /** If true, this tool will be removed from the request and Claude won't see it */\n remove?: boolean\n /** Error message to show if the tool is removed */\n removalReason?: string\n}\n\nconst SERVER_TOOL_CONFIGS: Record<string, ServerToolConfig> = {\n web_search: {\n description:\n \"Search the web for current information. \"\n + \"Returns web search results that can help answer questions about recent events, \"\n + \"current data, or information that may have changed since your knowledge cutoff.\",\n input_schema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"The search query\" },\n },\n required: [\"query\"],\n },\n },\n web_fetch: {\n description:\n \"Fetch content from a URL. \"\n + \"NOTE: This is a client-side tool - the client must fetch the URL and return the content.\",\n input_schema: {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"The URL to fetch\" },\n },\n required: [\"url\"],\n },\n },\n code_execution: {\n description: \"Execute code in a sandbox. \" + \"NOTE: This is a client-side tool - the client must execute the code.\",\n input_schema: {\n type: \"object\",\n properties: {\n code: { type: \"string\", description: \"The code to execute\" },\n language: { type: \"string\", description: \"The programming language\" },\n },\n required: [\"code\"],\n },\n },\n computer: {\n description:\n \"Control computer desktop. \" + \"NOTE: This is a client-side tool - the client must handle computer control.\",\n input_schema: {\n type: \"object\",\n properties: {\n action: { type: \"string\", description: \"The action to perform\" },\n },\n required: [\"action\"],\n },\n },\n}\n\n// Match tool.type (e.g., \"web_search_20250305\") to a server tool config\nfunction findServerToolConfig(type: string | undefined): ServerToolConfig | null {\n if (!type) return null\n for (const [prefix, config] of Object.entries(SERVER_TOOL_CONFIGS)) {\n if (type.startsWith(prefix)) return config\n }\n return null\n}\n\n/**\n * Convert server-side tools to custom tools, or pass them through unchanged.\n * Only converts when state.rewriteAnthropicTools is enabled.\n */\nexport function convertServerToolsToCustom(tools: Array<Tool> | undefined): Array<Tool> | undefined {\n if (!tools) return undefined\n\n // When rewriting is disabled, pass all tools through unchanged\n if (!state.rewriteAnthropicTools) return tools\n\n const result: Array<Tool> = []\n\n for (const tool of tools) {\n const config = findServerToolConfig(tool.type)\n if (!config) {\n result.push(tool)\n continue\n }\n\n if (config.remove) {\n consola.warn(`[DirectAnthropic] Removing server tool: ${tool.name}. Reason: ${config.removalReason}`)\n continue\n }\n\n consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`)\n result.push({\n name: tool.name,\n description: config.description,\n input_schema: config.input_schema,\n })\n }\n\n return result.length > 0 ? result : undefined\n}\n","/**\n * Auto-truncate module for Anthropic-style messages.\n *\n * This module handles automatic truncation of Anthropic message format\n * when it exceeds token limits.\n *\n * Key features:\n * - Binary search for optimal truncation point\n * - Token limit enforcement with learned calibration\n * - Preserves system messages\n * - Filters orphaned tool_result and tool_use messages\n * - Smart compression of old tool_result content (e.g., Read tool results)\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\nimport type { ContentBlock, MessageParam, MessagesPayload, ContentBlockParam } from \"~/types/api/anthropic\"\n\nimport { countTextTokens } from \"~/lib/models/tokenizer\"\nimport { state } from \"~/lib/state\"\nimport { bytesToKB } from \"~/lib/utils\"\nimport { isServerToolResultBlock, isToolResultBlock } from \"~/types/api/anthropic\"\n\nimport type { AutoTruncateConfig } from \"../auto-truncate\"\n\nimport {\n DEFAULT_AUTO_TRUNCATE_CONFIG,\n LARGE_TOOL_RESULT_THRESHOLD,\n calibrate,\n compressCompactedReadResult,\n compressToolResultContent,\n computeSafetyMargin,\n getLearnedLimits,\n} from \"../auto-truncate\"\nimport { processToolBlocks } from \"./sanitize\"\n\n// ============================================================================\n// Orphan Filtering Utilities (specific to auto-truncate)\n// ============================================================================\n\n/**\n * Get tool_use IDs from an Anthropic assistant message.\n */\nexport function getAnthropicToolUseIds(msg: MessageParam): Array<string> {\n if (msg.role !== \"assistant\") return []\n if (typeof msg.content === \"string\") return []\n\n const ids: Array<string> = []\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && block.id) {\n ids.push(block.id)\n }\n }\n return ids\n}\n\n/**\n * Get tool_result IDs from an Anthropic message.\n * Checks both user messages (regular tool_result) and assistant messages\n * (server tool results like tool_search_tool_result which appear inline).\n */\nexport function getAnthropicToolResultIds(msg: MessageParam): Array<string> {\n if (typeof msg.content === \"string\") return []\n\n const ids: Array<string> = []\n for (const block of msg.content) {\n if (isToolResultBlock(block)) {\n ids.push(block.tool_use_id)\n } else if (isServerToolResultBlock(block)) {\n ids.push(block.tool_use_id)\n }\n }\n return ids\n}\n\n/**\n * Ensure Anthropic messages start with a user message.\n * Drops leading non-user messages (e.g., orphaned assistant messages after truncation).\n */\nexport function ensureAnthropicStartsWithUser(messages: Array<MessageParam>): Array<MessageParam> {\n let startIndex = 0\n while (startIndex < messages.length && messages[startIndex].role !== \"user\") {\n startIndex++\n }\n\n if (startIndex > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Skipped ${startIndex} leading non-user messages`)\n }\n\n return messages.slice(startIndex)\n}\n\n/**\n * Filter orphaned tool_result blocks (no matching tool_use).\n */\nexport function filterAnthropicOrphanedToolResults(messages: Array<MessageParam>): Array<MessageParam> {\n const toolUseIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolUseIds(msg)) {\n toolUseIds.add(id)\n }\n }\n\n let removed = 0\n const result: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n const filtered = msg.content.filter((block) => {\n if (isToolResultBlock(block) && !toolUseIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n if (isServerToolResultBlock(block) && !toolUseIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n return true\n })\n\n if (filtered.length === 0) continue\n if (filtered.length === msg.content.length) {\n result.push(msg)\n } else {\n result.push({ ...msg, content: filtered } as MessageParam)\n }\n }\n\n if (removed > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Filtered ${removed} orphaned tool results`)\n }\n\n return result\n}\n\n/**\n * Filter orphaned tool_use blocks (no matching tool_result).\n */\nexport function filterAnthropicOrphanedToolUse(messages: Array<MessageParam>): Array<MessageParam> {\n const toolResultIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolResultIds(msg)) {\n toolResultIds.add(id)\n }\n }\n\n const toolUseIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolUseIds(msg)) {\n toolUseIds.add(id)\n }\n }\n\n let removed = 0\n const result: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n const survivingIds = new Set<string>()\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && toolResultIds.has(block.id)) {\n survivingIds.add(block.id)\n }\n }\n\n const filtered = msg.content.filter((block) => {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && !toolResultIds.has(block.id)) {\n removed++\n return false\n }\n if (isServerToolResultBlock(block) && !survivingIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n return true\n })\n\n if (filtered.length === 0) continue\n if (filtered.length === msg.content.length) {\n result.push(msg)\n } else {\n result.push({ ...msg, content: filtered } as MessageParam)\n }\n }\n\n if (removed > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Filtered ${removed} orphaned tool blocks`)\n }\n\n return result\n}\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\nexport interface AnthropicAutoTruncateResult {\n payload: MessagesPayload\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\n// ============================================================================\n// Token Counting (using official Anthropic tokenizer)\n// ============================================================================\n\n/**\n * Convert Anthropic message content to text for token counting.\n * @param options.includeThinking Whether to include thinking blocks (default: true)\n */\nexport function contentToText(content: MessageParam[\"content\"], options?: { includeThinking?: boolean }): string {\n if (typeof content === \"string\") {\n return content\n }\n\n const includeThinking = options?.includeThinking ?? true\n const parts: Array<string> = []\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n parts.push(block.text)\n break\n }\n case \"tool_use\": {\n parts.push(`[tool_use: ${block.name}]`, JSON.stringify(block.input))\n break\n }\n case \"tool_result\": {\n if (typeof block.content === \"string\") {\n parts.push(block.content)\n } else if (Array.isArray(block.content)) {\n for (const inner of block.content) {\n if (inner.type === \"text\") {\n parts.push(inner.text)\n }\n // Images are not counted as text tokens\n }\n }\n break\n }\n case \"thinking\": {\n if (includeThinking) {\n parts.push(block.thinking)\n }\n break\n }\n case \"redacted_thinking\": {\n // Redacted thinking blocks have opaque data, not text — skip for token counting\n break\n }\n case \"server_tool_use\": {\n parts.push(`[server_tool_use: ${block.name}]`, JSON.stringify(block.input))\n break\n }\n case \"web_search_tool_result\": {\n parts.push(`[web_search_tool_result]`)\n break\n }\n default: {\n // Handle generic server tool results (e.g., tool_search_tool_result)\n // Cast to Record to bypass type narrowing — API may return unknown block types\n const genericBlock = block as unknown as Record<string, unknown>\n if (\"tool_use_id\" in genericBlock && genericBlock.type !== \"image\") {\n parts.push(`[${String(genericBlock.type)}]`)\n break\n }\n // Images and other binary content are not counted as text tokens\n break\n }\n }\n }\n\n return parts.join(\"\\n\")\n}\n\n/**\n * Estimate tokens for a message (fast, synchronous).\n * Uses ~4 chars per token approximation for internal calculations.\n * The final result is verified with the accurate tokenizer.\n */\nfunction estimateMessageTokens(msg: MessageParam): number {\n const text = contentToText(msg.content)\n // ~4 chars per token + message framing overhead\n return Math.ceil(text.length / 4) + 4\n}\n\n/**\n * Count tokens for an Anthropic message using the model's tokenizer.\n */\nexport async function countMessageTokens(\n msg: MessageParam,\n model: Model,\n options?: { includeThinking?: boolean },\n): Promise<number> {\n const text = contentToText(msg.content, options)\n // Add message framing overhead (role + structure)\n return (await countTextTokens(text, model)) + 4\n}\n\n/**\n * Count tokens for system prompt.\n */\nexport async function countSystemTokens(system: MessagesPayload[\"system\"], model: Model): Promise<number> {\n if (!system) return 0\n if (typeof system === \"string\") {\n return (await countTextTokens(system, model)) + 4\n }\n const text = system.map((block) => block.text).join(\"\\n\")\n return (await countTextTokens(text, model)) + 4\n}\n\n/**\n * Count tokens for just the messages array.\n * Used internally to avoid re-counting system/tools tokens that don't change.\n */\nexport async function countMessagesTokens(messages: Array<MessageParam>, model: Model): Promise<number> {\n let total = 0\n for (const msg of messages) {\n total += await countMessageTokens(msg, model)\n }\n return total\n}\n\n/**\n * Count tokens for system + tools (the parts that don't change during truncation).\n * Returns the combined fixed overhead token count.\n */\nexport async function countFixedTokens(payload: MessagesPayload, model: Model): Promise<number> {\n let total = await countSystemTokens(payload.system, model)\n if (payload.tools) {\n const toolsText = JSON.stringify(payload.tools)\n total += await countTextTokens(toolsText, model)\n }\n return total\n}\n\n/**\n * Count total tokens for the payload using the model's tokenizer.\n * Includes thinking blocks — used by auto-truncate decisions.\n */\nexport async function countTotalTokens(payload: MessagesPayload, model: Model): Promise<number> {\n const fixed = await countFixedTokens(payload, model)\n const msgs = await countMessagesTokens(payload.messages, model)\n return fixed + msgs\n}\n\n/**\n * Count total input tokens for the payload, excluding thinking blocks\n * from assistant messages per Anthropic token counting spec.\n *\n * Per Anthropic docs: \"Thinking blocks from previous assistant turns are\n * ignored (don't count toward input tokens).\"\n *\n * This function is designed for the /v1/messages/count_tokens endpoint.\n * For auto-truncate decisions, use countTotalTokens instead (which includes\n * thinking blocks since they affect actual payload size).\n */\nexport async function countTotalInputTokens(payload: MessagesPayload, model: Model): Promise<number> {\n let total = await countSystemTokens(payload.system, model)\n for (const msg of payload.messages) {\n // Exclude thinking blocks from assistant messages\n const skipThinking = msg.role === \"assistant\"\n total += await countMessageTokens(msg, model, {\n includeThinking: !skipThinking,\n })\n }\n // Add overhead for tools\n if (payload.tools) {\n const toolsText = JSON.stringify(payload.tools)\n total += await countTextTokens(toolsText, model)\n }\n return total\n}\n\n// ============================================================================\n// Thinking Block Stripping\n// ============================================================================\n\n/**\n * Strip thinking/redacted_thinking blocks from old assistant messages.\n *\n * Per Anthropic docs, thinking blocks from previous turns don't count toward\n * input tokens (for billing), but they DO consume space in the request body.\n * Stripping them from older messages frees up context for actual content.\n *\n * @param messages - The message array to process\n * @param preserveRecentCount - Number of recent messages to preserve (keep thinking in recent messages)\n * @returns Object with stripped messages and count of removed blocks\n */\nfunction stripThinkingBlocks(\n messages: Array<MessageParam>,\n preserveRecentCount: number,\n): { messages: Array<MessageParam>; strippedCount: number } {\n const n = messages.length\n const stripBefore = Math.max(0, n - preserveRecentCount)\n let strippedCount = 0\n\n const result = messages.map((msg, i) => {\n if (i >= stripBefore || msg.role !== \"assistant\" || !Array.isArray(msg.content)) {\n return msg\n }\n\n const hasThinking = msg.content.some((block) => block.type === \"thinking\" || block.type === \"redacted_thinking\")\n if (!hasThinking) return msg\n\n const filtered = msg.content.filter((block): block is ContentBlock => {\n if (block.type === \"thinking\" || block.type === \"redacted_thinking\") {\n strippedCount++\n return false\n }\n return true\n })\n\n // If all content was thinking blocks, replace with empty text to preserve message structure\n if (filtered.length === 0) {\n return { ...msg, content: [{ type: \"text\" as const, text: \"\" }] }\n }\n\n return { ...msg, content: filtered }\n })\n\n return { messages: result, strippedCount }\n}\n\n// ============================================================================\n// Smart Tool Result Compression\n// ============================================================================\n\n/**\n * Compress a tool_result block in an Anthropic message.\n */\nfunction compressToolResultBlock(block: ContentBlockParam): ContentBlockParam {\n if (\n block.type === \"tool_result\"\n && typeof block.content === \"string\"\n && block.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n return {\n ...block,\n content: compressToolResultContent(block.content),\n }\n }\n return block\n}\n\n/**\n * Smart compression strategy:\n * 1. Calculate tokens from the end until reaching preservePercent of limit\n * 2. Messages before that threshold get their tool_results compressed\n * 3. Returns compressed messages and stats\n *\n * @param preservePercent - Percentage of context to preserve uncompressed (0.0-1.0)\n */\nfunction smartCompressToolResults(\n messages: Array<MessageParam>,\n tokenLimit: number,\n preservePercent: number,\n): {\n messages: Array<MessageParam>\n compressedCount: number\n compressThresholdIndex: number\n} {\n // Calculate cumulative token size from the end\n const n = messages.length\n const cumTokens: Array<number> = Array.from({ length: n + 1 }, () => 0)\n\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n\n // Find the threshold index where we've used the preserve percentage of the limit\n const preserveTokenLimit = Math.floor(tokenLimit * preservePercent)\n\n let thresholdIndex = n\n for (let i = n - 1; i >= 0; i--) {\n if (cumTokens[i] > preserveTokenLimit) {\n thresholdIndex = i + 1\n break\n }\n thresholdIndex = i\n }\n\n // If threshold is at the end, nothing to compress\n if (thresholdIndex >= n) {\n return { messages, compressedCount: 0, compressThresholdIndex: n }\n }\n\n // Compress tool_results and compacted text blocks in messages before threshold\n const result: Array<MessageParam> = []\n let compressedCount = 0\n\n for (const [i, msg] of messages.entries()) {\n if (i < thresholdIndex && msg.role === \"user\" && Array.isArray(msg.content)) {\n // Directly attempt compression on each block, avoiding a separate `some` pre-check\n let hadCompression = false\n const compressedContent = msg.content.map((block) => {\n if (\n block.type === \"tool_result\"\n && typeof block.content === \"string\"\n && block.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n compressedCount++\n hadCompression = true\n return compressToolResultBlock(block)\n }\n if (block.type === \"text\" && block.text.length > LARGE_TOOL_RESULT_THRESHOLD) {\n const compressed = compressCompactedReadResult(block.text)\n if (compressed) {\n compressedCount++\n hadCompression = true\n return { ...block, text: compressed }\n }\n }\n return block\n })\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hadCompression set in synchronous .map() callback\n if (hadCompression) {\n result.push({ ...msg, content: compressedContent })\n continue\n }\n }\n result.push(msg)\n }\n\n return {\n messages: result,\n compressedCount,\n compressThresholdIndex: thresholdIndex,\n }\n}\n\n// ============================================================================\n// Limit Calculation\n// ============================================================================\n\n/** Default fallback for when model capabilities are not available */\nconst DEFAULT_CONTEXT_WINDOW = 200000\n\n/**\n * Calculate the effective token limit for auto-truncate.\n * Uses explicit target if provided, otherwise learned limits with calibration,\n * otherwise model capabilities with safety margin.\n */\nfunction calculateTokenLimit(model: Model, config: AutoTruncateConfig): number {\n // Use explicit target if provided (reactive retry — caller already applied margin)\n if (config.targetTokenLimit !== undefined) {\n return config.targetTokenLimit\n }\n\n // Check for learned limits (adjusted based on previous errors)\n const learned = getLearnedLimits(model.id)\n if (learned) {\n const margin = computeSafetyMargin(learned.sampleCount)\n return Math.floor(learned.tokenLimit * (1 - margin))\n }\n\n // Use model capabilities with static safety margin\n const rawTokenLimit =\n model.capabilities?.limits?.max_context_window_tokens\n ?? model.capabilities?.limits?.max_prompt_tokens\n ?? DEFAULT_CONTEXT_WINDOW\n\n return Math.floor(rawTokenLimit * (1 - config.safetyMarginPercent / 100))\n}\n\n// ============================================================================\n// Binary Search Algorithm\n// ============================================================================\n\ninterface PreserveSearchParams {\n messages: Array<MessageParam>\n systemTokens: number\n tokenLimit: number\n}\n\nfunction findOptimalPreserveIndex(params: PreserveSearchParams): number {\n const { messages, systemTokens, tokenLimit } = params\n\n if (messages.length === 0) return 0\n\n // Account for truncation marker\n const markerTokens = 50\n\n const availableTokens = tokenLimit - systemTokens - markerTokens\n\n if (availableTokens <= 0) {\n return messages.length\n }\n\n // Pre-calculate cumulative token sums from the end\n const n = messages.length\n const cumTokens: Array<number> = Array.from({ length: n + 1 }, () => 0)\n\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n\n // Binary search for the smallest index where token limit is satisfied\n let left = 0\n let right = n\n\n while (left < right) {\n const mid = (left + right) >>> 1\n if (cumTokens[mid] <= availableTokens) {\n right = mid\n } else {\n left = mid + 1\n }\n }\n\n return left\n}\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Generate a summary of removed messages for context.\n * Extracts key information like tool calls and topics.\n */\nfunction generateRemovedMessagesSummary(removedMessages: Array<MessageParam>): string {\n const toolCalls: Array<string> = []\n let userMessageCount = 0\n let assistantMessageCount = 0\n\n for (const msg of removedMessages) {\n if (msg.role === \"user\") {\n userMessageCount++\n } else {\n assistantMessageCount++\n }\n\n // Extract tool use names\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n toolCalls.push(block.name)\n }\n if (block.type === \"server_tool_use\") {\n toolCalls.push(block.name)\n }\n }\n }\n }\n\n // Build summary parts\n const parts: Array<string> = []\n\n // Message breakdown\n if (userMessageCount > 0 || assistantMessageCount > 0) {\n const breakdown = []\n if (userMessageCount > 0) breakdown.push(`${userMessageCount} user`)\n if (assistantMessageCount > 0) breakdown.push(`${assistantMessageCount} assistant`)\n parts.push(`Messages: ${breakdown.join(\", \")}`)\n }\n\n // Tool calls\n if (toolCalls.length > 0) {\n // Deduplicate and limit\n const uniqueTools = [...new Set(toolCalls)]\n const displayTools =\n uniqueTools.length > 5 ? [...uniqueTools.slice(0, 5), `+${uniqueTools.length - 5} more`] : uniqueTools\n parts.push(`Tools used: ${displayTools.join(\", \")}`)\n }\n\n return parts.join(\". \")\n}\n\n/**\n * Add a compression notice to the system prompt.\n * Informs the model that some tool_result content has been compressed.\n */\nfunction addCompressionNotice(payload: MessagesPayload, compressedCount: number): MessagesPayload {\n const notice =\n `[CONTEXT NOTE]\\n`\n + `${compressedCount} large tool_result blocks have been compressed to reduce context size.\\n`\n + `The compressed results show the beginning and end of the content with an omission marker.\\n`\n + `If you need the full content, you can re-read the file or re-run the tool.\\n`\n + `[END NOTE]\\n\\n`\n\n let newSystem: MessagesPayload[\"system\"]\n if (typeof payload.system === \"string\") {\n newSystem = notice + payload.system\n } else if (Array.isArray(payload.system)) {\n newSystem = [{ type: \"text\" as const, text: notice }, ...payload.system]\n } else {\n newSystem = notice\n }\n\n return { ...payload, system: newSystem }\n}\n\n/**\n * Create truncation context to prepend to system prompt.\n */\nfunction createTruncationSystemContext(removedCount: number, compressedCount: number, summary: string): string {\n let context = `[CONVERSATION CONTEXT]\\n`\n\n if (removedCount > 0) {\n context += `${removedCount} earlier messages have been removed due to context window limits.\\n`\n }\n\n if (compressedCount > 0) {\n context += `${compressedCount} large tool_result blocks have been compressed.\\n`\n }\n\n if (summary) {\n context += `Summary of removed content: ${summary}\\n`\n }\n\n context +=\n `If you need earlier context, ask the user or check available tools for conversation history access.\\n`\n + `[END CONTEXT]\\n\\n`\n\n return context\n}\n\n/**\n * Create a truncation marker message (fallback when no system prompt).\n */\nfunction createTruncationMarker(removedCount: number, compressedCount: number, summary: string): MessageParam {\n const parts: Array<string> = []\n\n if (removedCount > 0) {\n parts.push(`${removedCount} earlier messages removed`)\n }\n if (compressedCount > 0) {\n parts.push(`${compressedCount} tool_result blocks compressed`)\n }\n\n let content = `[CONTEXT MODIFIED: ${parts.join(\", \")} to fit context limits]`\n if (summary) {\n content += `\\n[Summary: ${summary}]`\n }\n return {\n role: \"user\",\n content,\n }\n}\n\n/**\n * Perform auto-truncation on an Anthropic payload that exceeds limits.\n */\nexport async function autoTruncateAnthropic(\n payload: MessagesPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<AnthropicAutoTruncateResult> {\n const startTime = performance.now()\n\n // Helper to build result with timing\n const buildResult = (result: Omit<AnthropicAutoTruncateResult, \"processingTimeMs\">): AnthropicAutoTruncateResult => ({\n ...result,\n processingTimeMs: Math.round(performance.now() - startTime),\n })\n\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // Compute fixed overhead tokens (system + tools) once — these don't change during truncation\n const fixedTokens = await countFixedTokens(payload, model)\n\n // Measure original size\n const originalMsgTokens = await countMessagesTokens(payload.messages, model)\n const originalTokens = fixedTokens + originalMsgTokens\n\n // Check if compaction is needed\n if (originalTokens <= tokenLimit) {\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Step 1: Strip thinking blocks from old assistant messages\n // These don't count as input tokens per Anthropic docs, but they consume request body space.\n // Preserve thinking in the last 4 messages (2 exchanges) for context continuity.\n const { messages: thinkingStripped, strippedCount: thinkingStrippedCount } = stripThinkingBlocks(payload.messages, 4)\n let workingMessages = thinkingStripped\n\n // Check if stripping alone was enough\n if (thinkingStrippedCount > 0) {\n const strippedMsgTokens = await countMessagesTokens(workingMessages, model)\n const strippedTokens = fixedTokens + strippedMsgTokens\n\n if (strippedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${strippedTokens} `\n + `(stripped ${thinkingStrippedCount} thinking blocks) [${elapsedMs}ms]`,\n )\n\n return buildResult({\n payload: { ...payload, messages: workingMessages },\n wasTruncated: true,\n originalTokens,\n compactedTokens: strippedTokens,\n removedMessageCount: 0,\n })\n }\n }\n\n // Step 2: Smart compress old tool_results (if enabled)\n // Compress tool_results in messages that are beyond the preserve threshold\n let compressedCount = 0\n\n if (state.compressToolResultsBeforeTruncate) {\n const compressionResult = smartCompressToolResults(\n workingMessages,\n tokenLimit,\n cfg.preserveRecentPercent,\n )\n workingMessages = compressionResult.messages\n compressedCount = compressionResult.compressedCount\n\n // Check if compression alone was enough\n const compressedMsgTokens = await countMessagesTokens(workingMessages, model)\n const compressedTokens = fixedTokens + compressedMsgTokens\n\n if (compressedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${compressedTokens} `\n + `(compressed ${compressedCount} tool_results) [${elapsedMs}ms]`,\n )\n\n // Add compression notice to system prompt\n const compressedPayload = { ...payload, messages: workingMessages }\n const noticePayload = addCompressionNotice(compressedPayload, compressedCount)\n\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 4 // ~150 chars in notice text\n return buildResult({\n payload: noticePayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: compressedTokens + noticeTokenOverhead,\n removedMessageCount: 0,\n })\n }\n\n // Step 2.5: Compress ALL tool_results (including recent ones)\n // If compressing only old tool_results wasn't enough, try compressing all of them\n // before resorting to message removal\n const allCompression = smartCompressToolResults(\n workingMessages,\n tokenLimit,\n 0.0, // preservePercent=0 means compress all messages\n )\n if (allCompression.compressedCount > 0) {\n workingMessages = allCompression.messages\n compressedCount += allCompression.compressedCount\n\n // Check if compressing all was enough\n const allCompressedMsgTokens = await countMessagesTokens(workingMessages, model)\n const allCompressedTokens = fixedTokens + allCompressedMsgTokens\n\n if (allCompressedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${allCompressedTokens} `\n + `(compressed ${compressedCount} tool_results, including recent) [${elapsedMs}ms]`,\n )\n\n const allCompressedPayload = { ...payload, messages: workingMessages }\n const noticePayload = addCompressionNotice(allCompressedPayload, compressedCount)\n\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 4\n return buildResult({\n payload: noticePayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: allCompressedTokens + noticeTokenOverhead,\n removedMessageCount: 0,\n })\n }\n }\n }\n\n // Step 3: Compression wasn't enough (or disabled), proceed with message removal\n // Use working messages (compressed if enabled, original otherwise)\n\n // Calculate system tokens for the binary search\n const systemTokens = await countSystemTokens(payload.system, model)\n\n // Find optimal preserve index on working messages\n const preserveIndex = findOptimalPreserveIndex({\n messages: workingMessages,\n systemTokens,\n tokenLimit,\n })\n\n // Check if we can compact\n if (preserveIndex >= workingMessages.length) {\n consola.warn(\"[AutoTruncate:Anthropic] Would need to remove all messages\")\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Build preserved messages from working (compressed) messages\n let preserved = workingMessages.slice(preserveIndex)\n\n // Clean up the message list - filter orphaned tool blocks in two passes\n // (one pass to collect IDs, one to filter), then ensure starts with user\n let { messages: cleaned } = processToolBlocks(preserved, undefined)\n cleaned = ensureAnthropicStartsWithUser(cleaned)\n // Run again after ensuring starts with user, in case skipping leading messages created new orphans\n ;({ messages: cleaned } = processToolBlocks(cleaned, undefined))\n preserved = cleaned\n\n if (preserved.length === 0) {\n consola.warn(\"[AutoTruncate:Anthropic] All messages filtered out after cleanup\")\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Calculate removed messages and generate summary\n // Use original messages for summary (uncompressed content is more informative)\n const removedMessages = payload.messages.slice(0, preserveIndex)\n const removedCount = workingMessages.length - preserved.length\n const summary = generateRemovedMessagesSummary(removedMessages)\n\n // Build new payload with truncation context\n let newSystem = payload.system\n let newMessages = preserved\n\n // Prefer adding context to system prompt (cleaner for the model)\n if (payload.system !== undefined) {\n const truncationContext = createTruncationSystemContext(removedCount, compressedCount, summary)\n if (typeof payload.system === \"string\") {\n newSystem = truncationContext + payload.system\n } else if (Array.isArray(payload.system)) {\n // Prepend as first text block\n newSystem = [{ type: \"text\" as const, text: truncationContext }, ...payload.system]\n }\n } else {\n // No system prompt, use marker message\n const marker = createTruncationMarker(removedCount, compressedCount, summary)\n newMessages = [marker, ...preserved]\n }\n\n const newPayload: MessagesPayload = {\n ...payload,\n system: newSystem,\n messages: newMessages,\n }\n\n // Verify the result — only count messages (reuse cached fixed tokens)\n const newBytes = JSON.stringify(newPayload).length\n const newMsgTokens = await countMessagesTokens(newMessages, model)\n // Re-count system tokens if system was modified (truncation context added)\n const newSystemTokens = newSystem !== payload.system ? await countSystemTokens(newSystem, model) : systemTokens\n const toolsTokens = fixedTokens - (await countSystemTokens(payload.system, model))\n const newTokens = newSystemTokens + toolsTokens + newMsgTokens\n\n // Log single line summary\n const actions: Array<string> = []\n if (removedCount > 0) actions.push(`removed ${removedCount} msgs`)\n if (thinkingStrippedCount > 0) actions.push(`stripped ${thinkingStrippedCount} thinking blocks`)\n if (compressedCount > 0) actions.push(`compressed ${compressedCount} tool_results`)\n const actionInfo = actions.length > 0 ? ` (${actions.join(\", \")})` : \"\"\n\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${newTokens}, `\n + `${bytesToKB(newBytes)}KB${actionInfo} [${elapsedMs}ms]`,\n )\n\n // Warn if still over token limit\n if (newTokens > tokenLimit) {\n consola.warn(\n `[AutoTruncate:Anthropic] Result still over token limit (${newTokens} > ${tokenLimit})`,\n )\n }\n\n return buildResult({\n payload: newPayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: newTokens,\n removedMessageCount: removedCount,\n })\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n */\nexport function createTruncationResponseMarkerAnthropic(result: AnthropicAutoTruncateResult): string {\n if (!result.wasTruncated) return \"\"\n\n const reduction = result.originalTokens - result.compactedTokens\n const percentage = Math.round((reduction / result.originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${result.removedMessageCount} messages removed, `\n + `${result.originalTokens} → ${result.compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n\n/**\n * Check if payload needs compaction based on learned model limits.\n * Returns early with `needed: false` when no limits are known for the model.\n */\nexport async function checkNeedsCompactionAnthropic(\n payload: MessagesPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<{\n needed: boolean\n currentTokens: number\n tokenLimit: number\n reason?: \"tokens\"\n}> {\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n\n // If no learned limits and no explicit target, skip pre-check\n // (we don't know the real limit yet — let the server tell us)\n const learned = getLearnedLimits(model.id)\n if (!learned && cfg.targetTokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const tokenLimit = calculateTokenLimit(model, cfg)\n const rawTokens = await countTotalTokens(payload, model)\n\n // Apply calibration to adjust the GPT tokenizer estimate\n const currentTokens = learned && learned.sampleCount > 0\n ? calibrate(model.id, rawTokens)\n : rawTokens\n\n const exceedsTokens = cfg.checkTokenLimit && currentTokens > tokenLimit\n\n return {\n needed: exceedsTokens,\n currentTokens,\n tokenLimit,\n reason: exceedsTokens ? \"tokens\" : undefined,\n }\n}\n","import { state } from \"~/lib/state\"\n\n/**\n * Create an AbortSignal for fetch timeout if configured.\n * Controls the time from request start to receiving response headers.\n * Returns undefined if fetchTimeout is 0 (disabled).\n */\nexport function createFetchSignal(): AbortSignal | undefined {\n return state.fetchTimeout > 0 ? AbortSignal.timeout(state.fetchTimeout * 1000) : undefined\n}\n","/**\n * Anthropic model feature detection and request header construction.\n *\n * Mirrors VSCode Copilot Chat's feature detection logic from:\n * - anthropic.ts: modelSupportsInterleavedThinking, modelSupportsContextEditing, modelSupportsToolSearch\n * - chatEndpoint.ts: getExtraHeaders (anthropic-beta, capi-beta-1)\n * - anthropic.ts: buildContextManagement, nonDeferredToolNames\n */\n\nimport type { Tool } from \"~/types/api/anthropic\"\n\nimport { normalizeForMatching } from \"~/lib/models/resolver\"\n\n// ============================================================================\n// Model Feature Detection\n// ============================================================================\n\n/**\n * Interleaved thinking is supported by:\n * - Claude Sonnet 4/4.5\n * - Claude Haiku 4.5\n * - Claude Opus 4.5/4.6\n *\n * Notably, claude-opus-4 and claude-opus-4-1 do NOT support interleaved thinking.\n */\nexport function modelSupportsInterleavedThinking(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return (\n normalized.startsWith(\"claude-sonnet-4-5\")\n || normalized.startsWith(\"claude-sonnet-4\")\n || normalized.startsWith(\"claude-haiku-4-5\")\n || normalized.startsWith(\"claude-opus-4-5\")\n || normalized.startsWith(\"claude-opus-4-6\")\n )\n}\n\n/**\n * Context editing is supported by a broader set of models:\n * - Claude Haiku 4.5\n * - Claude Sonnet 4/4.5\n * - Claude Opus 4/4.1/4.5/4.6\n */\nexport function modelSupportsContextEditing(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return (\n normalized.startsWith(\"claude-haiku-4-5\")\n || normalized.startsWith(\"claude-sonnet-4-5\")\n || normalized.startsWith(\"claude-sonnet-4\")\n || normalized.startsWith(\"claude-opus-4-5\")\n || normalized.startsWith(\"claude-opus-4-6\")\n || normalized.startsWith(\"claude-opus-4-1\")\n || normalized.startsWith(\"claude-opus-4\")\n )\n}\n\n/**\n * Tool search is supported by:\n * - Claude Opus 4.5/4.6\n */\nexport function modelSupportsToolSearch(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return normalized.startsWith(\"claude-opus-4-5\") || normalized.startsWith(\"claude-opus-4-6\")\n}\n\n// ============================================================================\n// Anthropic Beta Headers\n// ============================================================================\n\nexport interface AnthropicBetaHeaders {\n /** Comma-separated beta feature identifiers */\n \"anthropic-beta\"?: string\n /** Fallback for models without interleaved thinking support */\n \"capi-beta-1\"?: string\n}\n\n/**\n * Build anthropic-beta and capi-beta-1 headers based on model capabilities.\n *\n * Logic from chatEndpoint.ts:166-201:\n * - If model supports interleaved thinking → add \"interleaved-thinking-2025-05-14\"\n * - Otherwise → set \"capi-beta-1: true\"\n * - If model supports context editing → add \"context-management-2025-06-27\"\n * - If model supports tool search → add \"advanced-tool-use-2025-11-20\"\n */\nexport function buildAnthropicBetaHeaders(modelId: string): AnthropicBetaHeaders {\n const headers: AnthropicBetaHeaders = {}\n const betaFeatures: Array<string> = []\n\n if (modelSupportsInterleavedThinking(modelId)) {\n betaFeatures.push(\"interleaved-thinking-2025-05-14\")\n } else {\n headers[\"capi-beta-1\"] = \"true\"\n }\n\n if (modelSupportsContextEditing(modelId)) {\n betaFeatures.push(\"context-management-2025-06-27\")\n }\n\n if (modelSupportsToolSearch(modelId)) {\n betaFeatures.push(\"advanced-tool-use-2025-11-20\")\n }\n\n if (betaFeatures.length > 0) {\n headers[\"anthropic-beta\"] = betaFeatures.join(\",\")\n }\n\n return headers\n}\n\n// ============================================================================\n// Context Management\n// ============================================================================\n\ninterface ContextManagementEdit {\n type: string\n trigger?: { type: string; value: number }\n keep?: { type: string; value: number }\n clear_at_least?: { type: string; value: number }\n exclude_tools?: Array<string>\n clear_tool_inputs?: boolean\n}\n\nexport interface ContextManagement {\n edits: Array<ContextManagementEdit>\n}\n\n/**\n * Build context_management config for the request body.\n *\n * From anthropic.ts:270-329 (buildContextManagement + getContextManagementFromConfig):\n * - clear_thinking: keep last N thinking turns\n * - clear_tool_uses: triggered by input_tokens threshold, keep last N tool uses\n */\nexport function buildContextManagement(modelId: string, hasThinking: boolean): ContextManagement | undefined {\n if (!modelSupportsContextEditing(modelId)) {\n return undefined\n }\n\n // Default config from getContextManagementFromConfig\n const triggerType = \"input_tokens\"\n const triggerValue = 100_000\n const keepCount = 3\n const thinkingKeepTurns = 1\n\n const edits: Array<ContextManagementEdit> = []\n\n // Add clear_thinking only if thinking is enabled\n if (hasThinking) {\n edits.push({\n type: \"clear_thinking_20251015\",\n keep: { type: \"thinking_turns\", value: Math.max(1, thinkingKeepTurns) },\n })\n }\n\n // Always add clear_tool_uses\n edits.push({\n type: \"clear_tool_uses_20250919\",\n trigger: { type: triggerType, value: triggerValue },\n keep: { type: \"tool_uses\", value: keepCount },\n })\n\n return { edits }\n}\n\n// ============================================================================\n// Tool Search / Defer Loading\n// ============================================================================\n\n/**\n * Claude Code official tool names that must always be present in the tools array.\n * If any of these are missing from the request, they will be injected as stub definitions.\n */\nconst CLAUDE_CODE_OFFICIAL_TOOLS = [\n \"Task\",\n \"TaskOutput\",\n \"Bash\",\n \"Glob\",\n \"Grep\",\n \"Read\",\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n \"WebFetch\",\n \"TodoWrite\",\n \"KillShell\",\n \"AskUserQuestion\",\n \"Skill\",\n \"EnterPlanMode\",\n \"ExitPlanMode\",\n]\n\n/** Tool names that should NOT be deferred (core tools always available) */\nconst NON_DEFERRED_TOOL_NAMES = new Set([\n // VSCode Copilot Chat original tool names (snake_case)\n \"read_file\",\n \"list_dir\",\n \"grep_search\",\n \"semantic_search\",\n \"file_search\",\n \"replace_string_in_file\",\n \"multi_replace_string_in_file\",\n \"insert_edit_into_file\",\n \"apply_patch\",\n \"create_file\",\n \"run_in_terminal\",\n \"get_terminal_output\",\n \"get_errors\",\n \"manage_todo_list\",\n \"runSubagent\",\n \"search_subagent\",\n \"runTests\",\n \"ask_questions\",\n \"switch_agent\",\n // Claude Code official tool names (PascalCase)\n ...CLAUDE_CODE_OFFICIAL_TOOLS,\n])\n\nconst TOOL_SEARCH_TOOL_NAME = \"tool_search_tool_regex\"\nconst TOOL_SEARCH_TOOL_TYPE = \"tool_search_tool_regex_20251119\"\n\n/**\n * Ensure all Claude Code official tools are present in the tools array.\n * Injects stub definitions for any missing official tools.\n */\nexport function ensureOfficialTools(tools: Array<Tool>): Array<Tool> {\n const existingNames = new Set(tools.map((t) => t.name))\n const missing = CLAUDE_CODE_OFFICIAL_TOOLS.filter((name) => !existingNames.has(name))\n\n if (missing.length === 0) {\n return tools\n }\n\n const result = [...tools]\n for (const name of missing) {\n result.push({\n name,\n description: `Claude Code ${name} tool`,\n input_schema: { type: \"object\" },\n })\n }\n\n return result\n}\n\n/**\n * Apply tool search to the tools list.\n *\n * From anthropic.ts and messagesApi.ts:\n * - Prepend tool_search_tool_regex tool\n * - Mark non-core tools with defer_loading: true\n * - Core tools (VSCode + Claude Code official) keep defer_loading: false\n */\nexport function applyToolSearch(tools: Array<Tool>, modelId: string): Array<Tool> {\n if (!modelSupportsToolSearch(modelId) || tools.length === 0) {\n return tools\n }\n\n const result: Array<Tool> = []\n\n // 1. Add tool_search_tool_regex at the beginning\n result.push({\n name: TOOL_SEARCH_TOOL_NAME,\n type: TOOL_SEARCH_TOOL_TYPE,\n })\n\n // 2. Add tools with defer_loading based on whether they're core tools\n for (const tool of tools) {\n if (NON_DEFERRED_TOOL_NAMES.has(tool.name)) {\n result.push(tool) // Core tool: no defer_loading\n } else {\n result.push({ ...tool, defer_loading: true })\n }\n }\n\n return result\n}\n","/**\n * Direct Anthropic-style message API for Copilot.\n *\n * Owns the full request lifecycle: wire payload construction, header building,\n * model-aware request enrichment (beta headers, context management, tool pipeline),\n * and HTTP execution against Copilot's /v1/messages endpoint.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport type { MessageParam, MessagesPayload, Message as AnthropicResponse, Tool } from \"~/types/api/anthropic\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\nimport { modelSupportsContextEditing, modelSupportsInterleavedThinking, modelSupportsToolSearch } from \"./features\"\nimport { convertServerToolsToCustom } from \"./sanitize\"\n\n/** Re-export the response type for consumers */\nexport type AnthropicMessageResponse = AnthropicResponse\n\n// ============================================================================\n// Wire payload construction\n// ============================================================================\n\n/**\n * Fields known to be rejected by Copilot's Anthropic API endpoint\n * with \"Extra inputs are not permitted\".\n *\n * We use a blacklist instead of a whitelist so that new Anthropic fields\n * are forwarded by default — no code change needed when Copilot adds support.\n */\nconst COPILOT_REJECTED_FIELDS = new Set([\"output_config\", \"inference_geo\"])\n\n/**\n * Build the wire payload: strip rejected fields and convert server tools.\n * Returns a plain record — the wire format may carry fields beyond what\n * MessagesPayload declares (e.g. context_management), so we don't pretend\n * it's a typed struct.\n */\nfunction buildWirePayload(payload: MessagesPayload): Record<string, unknown> {\n const wire: Record<string, unknown> = {}\n const rejectedFields: Array<string> = []\n\n for (const [key, value] of Object.entries(payload)) {\n if (COPILOT_REJECTED_FIELDS.has(key)) {\n rejectedFields.push(key)\n } else {\n wire[key] = value\n }\n }\n\n if (rejectedFields.length > 0) {\n consola.debug(`[DirectAnthropic] Stripped rejected fields: ${rejectedFields.join(\", \")}`)\n }\n\n // Convert server-side tools (web_search, etc.) to custom tool equivalents\n if (wire.tools) {\n wire.tools = convertServerToolsToCustom(wire.tools as Array<Tool>)\n }\n\n return wire\n}\n\n/**\n * Ensure max_tokens > budget_tokens when thinking is enabled.\n *\n * Anthropic API requires max_tokens > thinking.budget_tokens. Some clients\n * send budget_tokens >= max_tokens. We cap budget_tokens to max_tokens - 1,\n * matching the approach in vscode-copilot-chat (messagesApi.ts:132).\n */\nfunction adjustThinkingBudget(wire: Record<string, unknown>): void {\n const thinking = wire.thinking as MessagesPayload[\"thinking\"]\n if (!thinking || thinking.type === \"disabled\" || thinking.type === \"adaptive\") return\n\n const budgetTokens = thinking.budget_tokens\n if (!budgetTokens) return\n\n const maxTokens = wire.max_tokens as number\n if (budgetTokens >= maxTokens) {\n const adjusted = maxTokens - 1\n ;(wire.thinking as { budget_tokens: number }).budget_tokens = adjusted\n consola.debug(\n `[DirectAnthropic] Capped thinking.budget_tokens: ${budgetTokens} → ${adjusted} ` + `(max_tokens=${maxTokens})`,\n )\n }\n}\n\n// ============================================================================\n// Anthropic Beta Headers\n// ============================================================================\n\nexport interface AnthropicBetaHeaders {\n /** Comma-separated beta feature identifiers */\n \"anthropic-beta\"?: string\n /** Fallback for models without interleaved thinking support */\n \"capi-beta-1\"?: string\n}\n\n/**\n * Build anthropic-beta and capi-beta-1 headers based on model capabilities.\n *\n * Logic from chatEndpoint.ts:166-201:\n * - If model supports interleaved thinking → add \"interleaved-thinking-2025-05-14\"\n * - Otherwise → set \"capi-beta-1: true\"\n * - If model supports context editing → add \"context-management-2025-06-27\"\n * - If model supports tool search → add \"advanced-tool-use-2025-11-20\"\n */\nfunction buildAnthropicBetaHeaders(modelId: string): AnthropicBetaHeaders {\n const headers: AnthropicBetaHeaders = {}\n const betaFeatures: Array<string> = []\n\n if (modelSupportsInterleavedThinking(modelId)) {\n betaFeatures.push(\"interleaved-thinking-2025-05-14\")\n } else {\n headers[\"capi-beta-1\"] = \"true\"\n }\n\n if (modelSupportsContextEditing(modelId)) {\n betaFeatures.push(\"context-management-2025-06-27\")\n }\n\n if (modelSupportsToolSearch(modelId)) {\n betaFeatures.push(\"advanced-tool-use-2025-11-20\")\n }\n\n if (betaFeatures.length > 0) {\n headers[\"anthropic-beta\"] = betaFeatures.join(\",\")\n }\n\n return headers\n}\n\n// ============================================================================\n// Context Management\n// ============================================================================\n\ninterface ContextManagementEdit {\n type: string\n trigger?: { type: string; value: number }\n keep?: { type: string; value: number }\n clear_at_least?: { type: string; value: number }\n exclude_tools?: Array<string>\n clear_tool_inputs?: boolean\n}\n\nexport interface ContextManagement {\n edits: Array<ContextManagementEdit>\n}\n\n/**\n * Build context_management config for the request body.\n *\n * From anthropic.ts:270-329 (buildContextManagement + getContextManagementFromConfig):\n * - clear_thinking: keep last N thinking turns\n * - clear_tool_uses: triggered by input_tokens threshold, keep last N tool uses\n */\nfunction buildContextManagement(modelId: string, hasThinking: boolean): ContextManagement | undefined {\n if (!modelSupportsContextEditing(modelId)) {\n return undefined\n }\n\n // Default config from getContextManagementFromConfig\n const triggerType = \"input_tokens\"\n const triggerValue = 100_000\n const keepCount = 3\n const thinkingKeepTurns = 1\n\n const edits: Array<ContextManagementEdit> = []\n\n // Add clear_thinking only if thinking is enabled\n if (hasThinking) {\n edits.push({\n type: \"clear_thinking_20251015\",\n keep: { type: \"thinking_turns\", value: Math.max(1, thinkingKeepTurns) },\n })\n }\n\n // Always add clear_tool_uses\n edits.push({\n type: \"clear_tool_uses_20250919\",\n trigger: { type: triggerType, value: triggerValue },\n keep: { type: \"tool_uses\", value: keepCount },\n })\n\n return { edits }\n}\n\n// ============================================================================\n// Tool Pipeline\n// ============================================================================\n\n/**\n * Claude Code official tool names that must always be present in the tools array.\n * If any of these are missing from the request, they will be injected as stub definitions.\n */\nconst CLAUDE_CODE_OFFICIAL_TOOLS = [\n \"Task\",\n \"TaskOutput\",\n \"Bash\",\n \"Glob\",\n \"Grep\",\n \"Read\",\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n \"WebFetch\",\n \"TodoWrite\",\n \"KillShell\",\n \"AskUserQuestion\",\n \"Skill\",\n \"EnterPlanMode\",\n \"ExitPlanMode\",\n]\n\n/** Tool names that should NOT be deferred (core tools always available) */\nconst NON_DEFERRED_TOOL_NAMES = new Set([\n // VSCode Copilot Chat original tool names (snake_case)\n \"read_file\",\n \"list_dir\",\n \"grep_search\",\n \"semantic_search\",\n \"file_search\",\n \"replace_string_in_file\",\n \"multi_replace_string_in_file\",\n \"insert_edit_into_file\",\n \"apply_patch\",\n \"create_file\",\n \"run_in_terminal\",\n \"get_terminal_output\",\n \"get_errors\",\n \"manage_todo_list\",\n \"runSubagent\",\n \"search_subagent\",\n \"runTests\",\n \"ask_questions\",\n \"switch_agent\",\n // Claude Code official tool names (PascalCase)\n ...CLAUDE_CODE_OFFICIAL_TOOLS,\n])\n\nconst TOOL_SEARCH_TOOL_NAME = \"tool_search_tool_regex\"\nconst TOOL_SEARCH_TOOL_TYPE = \"tool_search_tool_regex_20251119\"\n\nconst EMPTY_INPUT_SCHEMA = { type: \"object\", properties: {}, required: [] } as const\n\n/** Ensure a tool has input_schema — required by Anthropic API for custom tools. */\nfunction ensureInputSchema(tool: Tool): Tool {\n return tool.input_schema ? tool : { ...tool, input_schema: EMPTY_INPUT_SCHEMA }\n}\n\n/**\n * Collect tool names referenced in message history via tool_use blocks.\n *\n * When tool_search is enabled, deferred tools must be \"loaded\" via\n * tool_search_tool_regex before they can be called. But in multi-turn\n * conversations, message history may already contain tool_use blocks\n * referencing tools that were loaded in earlier turns. If we mark those\n * tools as deferred again, the API rejects the request because the\n * historical tool_use references a tool that isn't \"loaded\" in this turn.\n *\n * By collecting all tool names from history, we ensure those tools remain\n * non-deferred (immediately available) — preserving the tool_use/tool_result\n * pairing that the API requires.\n */\nfunction collectHistoryToolNames(messages: Array<MessageParam>): Set<string> {\n const names = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n names.add(block.name)\n }\n }\n }\n return names\n}\n\n/**\n * Build minimal tool stubs for tools referenced in message history.\n *\n * Used when the request has no tools but messages contain tool_use blocks.\n * Only needed when tool search is enabled (advanced-tool-use beta),\n * which enforces tool reference validation.\n */\nfunction buildHistoryToolStubs(historyToolNames: Set<string>): Array<Tool> {\n return Array.from(historyToolNames).map((name) => ({\n name,\n description: `Stub for tool referenced in conversation history`,\n input_schema: EMPTY_INPUT_SCHEMA,\n }))\n}\n\n/**\n * Process tools through the full pipeline:\n * 1. Inject missing Claude Code official tool stubs\n * 2. If model supports tool search: prepend search tool, mark non-core as deferred\n * 3. Ensure all custom tools have input_schema (skip API-defined typed tools)\n *\n * Returns a new array — never mutates the input.\n */\nfunction processToolPipeline(tools: Array<Tool>, modelId: string, messages: Array<MessageParam>): Array<Tool> {\n const existingNames = new Set(tools.map((t) => t.name))\n const toolSearchEnabled = modelSupportsToolSearch(modelId)\n\n // Collect tool names already referenced in message history — these must\n // stay non-deferred to avoid \"Tool reference not found\" errors\n const historyToolNames = toolSearchEnabled ? collectHistoryToolNames(messages) : undefined\n\n const result: Array<Tool> = []\n\n // Prepend tool_search_tool_regex if model supports it\n if (toolSearchEnabled) {\n result.push({\n name: TOOL_SEARCH_TOOL_NAME,\n type: TOOL_SEARCH_TOOL_TYPE,\n defer_loading: false,\n })\n }\n\n // Process existing tools: ensure input_schema, apply defer_loading\n for (const tool of tools) {\n // Tools with a `type` field are API-defined (tool_search, memory, web_search) —\n // schema is managed server-side, don't touch input_schema\n const normalized = tool.type ? tool : ensureInputSchema(tool)\n\n // Respect explicit defer_loading: false from retry strategies (deferred-tool-retry\n // sets this when a tool was rejected as \"not found in available tools\")\n const shouldDefer =\n toolSearchEnabled\n && tool.defer_loading !== false\n && !NON_DEFERRED_TOOL_NAMES.has(tool.name)\n && !historyToolNames?.has(tool.name)\n\n result.push(shouldDefer ? { ...normalized, defer_loading: true } : normalized)\n }\n\n // Inject stubs for any missing Claude Code official tools\n for (const name of CLAUDE_CODE_OFFICIAL_TOOLS) {\n if (!existingNames.has(name)) {\n const stub: Tool = {\n name,\n description: `Claude Code ${name} tool`,\n input_schema: EMPTY_INPUT_SCHEMA,\n }\n // Official tools are always non-deferred, no defer_loading needed\n result.push(stub)\n }\n }\n\n // Inject minimal stubs for tools referenced in message history but missing\n // from the tools array. This happens when MCP tools were available in earlier\n // turns but not included in the current request. Without these stubs, the API\n // rejects the request because the historical tool_use references a tool that\n // doesn't exist in the tools list at all.\n if (historyToolNames) {\n const allResultNames = new Set(result.map((t) => t.name))\n for (const name of historyToolNames) {\n if (!allResultNames.has(name)) {\n consola.debug(`[ToolPipeline] Injecting stub for history-referenced tool: ${name}`)\n result.push({\n name,\n description: `Stub for tool referenced in conversation history`,\n input_schema: EMPTY_INPUT_SCHEMA,\n })\n }\n }\n }\n\n return result\n}\n\n// ============================================================================\n// Main entry point — createAnthropicMessages\n// ============================================================================\n\n/**\n * Create messages using Anthropic-style API directly.\n * Calls Copilot's native Anthropic endpoint for Anthropic-vendor models.\n */\nexport async function createAnthropicMessages(\n payload: MessagesPayload,\n): Promise<AnthropicMessageResponse | AsyncGenerator<ServerSentEventMessage>> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const wire = buildWirePayload(payload)\n adjustThinkingBudget(wire)\n\n // Destructure known fields for typed access\n const model = wire.model as string\n const messages = wire.messages as MessagesPayload[\"messages\"]\n const tools = wire.tools as Array<Tool> | undefined\n const thinking = wire.thinking as MessagesPayload[\"thinking\"]\n\n // Check for vision content\n const enableVision = messages.some((msg) => {\n if (typeof msg.content === \"string\") return false\n return msg.content.some((block) => block.type === \"image\")\n })\n\n // Agent/user check for X-Initiator header\n const isAgentCall = messages.some((msg) => msg.role === \"assistant\")\n\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n \"anthropic-version\": \"2023-06-01\",\n ...buildAnthropicBetaHeaders(model),\n }\n\n // Add context_management if model supports it and payload doesn't already have one\n if (!wire.context_management) {\n const hasThinking = Boolean(thinking && thinking.type !== \"disabled\")\n const contextManagement = buildContextManagement(model, hasThinking)\n if (contextManagement) {\n wire.context_management = contextManagement\n consola.debug(\"[DirectAnthropic] Added context_management:\", JSON.stringify(contextManagement))\n }\n }\n\n // Process tools through pipeline\n if (tools && tools.length > 0) {\n wire.tools = processToolPipeline(tools, model, messages)\n } else if (modelSupportsToolSearch(model)) {\n // When tool search is enabled (advanced-tool-use beta header), the API\n // validates all tool_use references in messages against the tools array.\n // If tools is empty but messages contain tool_use blocks, inject minimal\n // stubs to satisfy API validation. This happens during /compact and\n // other requests where the client doesn't send tools.\n const historyToolNames = collectHistoryToolNames(messages)\n if (historyToolNames.size > 0) {\n consola.debug(\n `[DirectAnthropic] Injecting ${historyToolNames.size} tool stubs for`\n + ` history references (no tools in request): ${[...historyToolNames].join(\", \")}`,\n )\n wire.tools = buildHistoryToolStubs(historyToolNames)\n }\n }\n\n consola.debug(\"Sending direct Anthropic request to Copilot /v1/messages\")\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(wire),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.debug(\"Request failed:\", {\n model,\n max_tokens: wire.max_tokens,\n stream: wire.stream,\n toolCount: tools?.length ?? 0,\n thinking,\n messageCount: messages.length,\n })\n throw await HTTPError.fromResponse(\"Failed to create Anthropic messages\", response, model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as AnthropicMessageResponse\n}\n","/**\n * Stream accumulator for Anthropic format responses.\n * Handles accumulating stream events for history recording and tracking.\n */\n\nimport consola from \"consola\"\n\nimport type { CopilotAnnotations, StreamEvent, RawMessageStartEvent } from \"~/types/api/anthropic\"\n\n// ============================================================================\n// Accumulated content block types\n// ============================================================================\n\n/**\n * A single content block accumulated from the stream, preserving original order.\n * Known block types have typed variants; unknown types are stored via\n * AccumulatedGenericBlock with all original fields preserved.\n */\nexport type AccumulatedContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"thinking\"; thinking: string; signature?: string }\n | { type: \"redacted_thinking\"; data: string }\n | { type: \"tool_use\"; id: string; name: string; input: string }\n | { type: \"server_tool_use\"; id: string; name: string; input: string }\n | { type: \"web_search_tool_result\"; tool_use_id: string; content: unknown }\n | AccumulatedGenericBlock\n\n/**\n * Generic block for unknown/future content block types.\n * Branded with `_generic` to distinguish from known types in discriminated unions.\n */\nexport interface AccumulatedGenericBlock {\n type: string\n _generic: true\n [key: string]: unknown\n}\n\n// ============================================================================\n// Base accumulator interface (shared with OpenAI accumulator)\n// ============================================================================\n\n/** Minimal accumulator contract for tracking and error recording */\nexport interface BaseStreamAccumulator {\n model: string\n inputTokens: number\n outputTokens: number\n /** Plain text content for error recording fallback */\n content: string\n}\n\n// ============================================================================\n// Anthropic stream accumulator\n// ============================================================================\n\n/** Stream accumulator for Anthropic format */\nexport interface AnthropicStreamAccumulator extends BaseStreamAccumulator {\n cacheReadTokens: number\n cacheCreationTokens: number\n stopReason: string\n /** Content blocks in stream order, indexed by the event's `index` field. */\n contentBlocks: Array<AccumulatedContentBlock>\n /** Copilot-specific: IP code citations collected from stream events */\n copilotAnnotations: Array<CopilotAnnotations>\n /** Error received from stream, if any */\n streamError?: { type: string; message: string }\n}\n\n/** Create a fresh Anthropic stream accumulator */\nexport function createAnthropicStreamAccumulator(): AnthropicStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n stopReason: \"\",\n content: \"\",\n contentBlocks: [],\n copilotAnnotations: [],\n }\n}\n\n// ============================================================================\n// Event processing\n// ============================================================================\n\n/** Accumulate a single Anthropic stream event into the accumulator */\nexport function accumulateAnthropicStreamEvent(event: StreamEvent, acc: AnthropicStreamAccumulator) {\n switch (event.type) {\n case \"message_start\": {\n handleMessageStart(event.message, acc)\n break\n }\n case \"content_block_start\": {\n handleContentBlockStart(event.index, event.content_block as AccContentBlock, acc)\n break\n }\n case \"content_block_delta\": {\n handleContentBlockDelta(event.index, event.delta as AccDelta, acc, event.copilot_annotations)\n break\n }\n case \"content_block_stop\": {\n // Nothing to do — block is already stored by index, no state to clear\n break\n }\n case \"message_delta\": {\n handleMessageDelta(event.delta as MessageDelta, event.usage as MessageDeltaUsage, acc)\n break\n }\n case \"message_stop\": {\n // Nothing to do — stop_reason is provided in message_delta, no state to clear\n break\n }\n case \"ping\": {\n // No accumulation needed for ping events, but could track last ping time if desired\n break\n }\n case \"error\": {\n const err = (event as { error?: { type?: string; message?: string } }).error\n acc.streamError = {\n type: err?.type ?? \"unknown_error\",\n message: err?.message ?? \"Unknown stream error\",\n }\n break\n }\n default: {\n consola.warn(`[stream-accumulator] Unknown event type: ${(event as { type: string }).type}`)\n break\n }\n }\n}\n\n// ============================================================================\n// message_start handler\n// ============================================================================\n\n/**\n * Handle message_start event.\n * This is where input_tokens, model, and cache stats are first reported.\n */\nfunction handleMessageStart(message: RawMessageStartEvent[\"message\"], acc: AnthropicStreamAccumulator) {\n if (message.model) acc.model = message.model\n acc.inputTokens = message.usage.input_tokens\n acc.outputTokens = message.usage.output_tokens\n if (message.usage.cache_read_input_tokens) {\n acc.cacheReadTokens = message.usage.cache_read_input_tokens\n }\n if (message.usage.cache_creation_input_tokens) {\n acc.cacheCreationTokens = message.usage.cache_creation_input_tokens\n }\n}\n\n// ============================================================================\n// content_block handlers\n// ============================================================================\n\n/** Content block delta types (local — looser than SDK's RawContentBlockDelta for proxy use) */\ntype AccDelta =\n | { type: \"text_delta\"; text: string }\n | { type: \"input_json_delta\"; partial_json: string }\n | { type: \"thinking_delta\"; thinking: string }\n | { type: \"signature_delta\"; signature: string }\n\n/** Content block start types (local — looser than SDK's ContentBlock for proxy use) */\ntype AccContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n }\n | { type: \"thinking\"; thinking: string; signature?: string }\n | { type: \"redacted_thinking\"; data: string }\n | { type: \"server_tool_use\"; id: string; name: string }\n | { type: \"web_search_tool_result\"; tool_use_id: string; content: unknown }\n\nfunction handleContentBlockStart(index: number, block: AccContentBlock, acc: AnthropicStreamAccumulator) {\n let newBlock: AccumulatedContentBlock\n\n switch (block.type) {\n case \"text\": {\n newBlock = { type: \"text\", text: \"\" }\n break\n }\n case \"thinking\": {\n newBlock = { type: \"thinking\", thinking: \"\", signature: undefined }\n break\n }\n case \"redacted_thinking\": {\n // Complete at block_start, no subsequent deltas\n newBlock = { type: \"redacted_thinking\", data: block.data }\n break\n }\n case \"tool_use\": {\n newBlock = { type: \"tool_use\", id: block.id, name: block.name, input: \"\" }\n break\n }\n case \"server_tool_use\": {\n newBlock = { type: \"server_tool_use\", id: block.id, name: block.name, input: \"\" }\n break\n }\n case \"web_search_tool_result\": {\n // Complete at block_start, no subsequent deltas\n newBlock = {\n type: \"web_search_tool_result\",\n tool_use_id: block.tool_use_id,\n content: block.content,\n }\n break\n }\n default: {\n // Unknown block type — store all fields as-is for forward compatibility.\n // Cast needed because TypeScript narrows to `never` after exhaustive cases,\n // but runtime data from the API may contain types not yet in our definitions.\n const unknownBlock = block as unknown as Record<string, unknown>\n consola.warn(`[stream-accumulator] Unknown content block type: ${String(unknownBlock.type)}`)\n newBlock = { ...unknownBlock, _generic: true } as AccumulatedGenericBlock\n break\n }\n }\n\n acc.contentBlocks[index] = newBlock\n}\n\nfunction handleContentBlockDelta(\n index: number,\n delta: AccDelta,\n acc: AnthropicStreamAccumulator,\n copilotAnnotations?: CopilotAnnotations,\n) {\n const block = acc.contentBlocks[index]\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive: index from untrusted SSE data\n if (!block) return\n\n switch (delta.type) {\n case \"text_delta\": {\n const b = block as { text: string }\n b.text += delta.text\n acc.content += delta.text // Sync BaseStreamAccumulator.content for error fallback\n break\n }\n case \"thinking_delta\": {\n const b = block as { thinking: string }\n b.thinking += delta.thinking\n break\n }\n case \"input_json_delta\": {\n const b = block as { input: string }\n b.input += delta.partial_json\n break\n }\n case \"signature_delta\": {\n // signature_delta is part of the thinking block integrity, it's not accumulated actually (it, not content)\n const b = block as { signature?: string }\n if (b.signature) {\n consola.error(\n \"[stream-accumulator] Received unexpected signature_delta for a block that already has a signature. Overwriting existing signature.\",\n )\n }\n b.signature = delta.signature\n break\n }\n default: {\n consola.warn(`[stream-accumulator] Unknown delta type: ${(delta as { type: string }).type}`)\n break\n }\n }\n\n // Collect Copilot-specific IP code citations\n if (copilotAnnotations?.ip_code_citations?.length) {\n acc.copilotAnnotations.push(copilotAnnotations)\n }\n}\n\n// ============================================================================\n// message_delta handler\n// ============================================================================\n\n// Message delta types\ninterface MessageDelta {\n stop_reason?: string | null\n stop_sequence?: string | null\n}\n\ninterface MessageDeltaUsage {\n input_tokens?: number\n output_tokens: number\n cache_creation_input_tokens?: number\n cache_read_input_tokens?: number\n}\n\n/**\n * Handle message_delta event.\n * output_tokens is the final count here (replaces message_start's 0).\n * input_tokens may or may not be present — only update if provided.\n */\nfunction handleMessageDelta(\n delta: MessageDelta,\n usage: MessageDeltaUsage | undefined,\n acc: AnthropicStreamAccumulator,\n) {\n if (delta.stop_reason) acc.stopReason = delta.stop_reason\n if (usage) {\n // output_tokens in message_delta is the final output count\n acc.outputTokens = usage.output_tokens\n // input_tokens in message_delta is optional; only override if explicitly present\n if (usage.input_tokens !== undefined) {\n acc.inputTokens = usage.input_tokens\n }\n // Accumulate cache stats if present (may complement message_start values)\n if (usage.cache_read_input_tokens !== undefined) {\n acc.cacheReadTokens = usage.cache_read_input_tokens\n }\n if (usage.cache_creation_input_tokens !== undefined) {\n acc.cacheCreationTokens = usage.cache_creation_input_tokens\n }\n }\n}\n\n// ============================================================================\n// Convenience extractors\n// ============================================================================\n\n/** Get concatenated text content from all text blocks */\nexport function getTextContent(acc: AnthropicStreamAccumulator): string {\n return acc.contentBlocks\n .filter((b): b is AccumulatedContentBlock & { type: \"text\" } => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\")\n}\n\n/** Get concatenated thinking content from all thinking blocks */\nexport function getThinkingContent(acc: AnthropicStreamAccumulator): string {\n return acc.contentBlocks\n .filter((b): b is AccumulatedContentBlock & { type: \"thinking\" } => b.type === \"thinking\")\n .map((b) => b.thinking)\n .join(\"\")\n}\n\n/** Get count of redacted_thinking blocks */\nexport function getRedactedThinkingCount(acc: AnthropicStreamAccumulator): number {\n return acc.contentBlocks.filter((b) => b.type === \"redacted_thinking\").length\n}\n","/**\n * Anthropic stream processing, response handlers, and completion orchestration.\n * Parses SSE events, accumulates for history/tracking, checks shutdown signals.\n * Handles both streaming and non-streaming response finalization.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { SSEStreamingApi, streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { MessageContent, ToolDefinition } from \"~/lib/history\"\nimport type { SseEventRecord } from \"~/lib/history/store\"\nimport type { MessagesPayload, StreamEvent } from \"~/types/api/anthropic\"\n\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { MAX_AUTO_TRUNCATE_RETRIES } from \"~/lib/auto-truncate\"\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { buildAnthropicResponseData, createTruncationMarker } from \"~/lib/request\"\nimport { logPayloadSizeInfoAnthropic } from \"~/lib/request/payload\"\nimport { executeRequestPipeline, type FormatAdapter } from \"~/lib/request/pipeline\"\nimport { prependMarkerToResponse } from \"~/lib/request/response\"\nimport { createAutoTruncateStrategy, type TruncateResult } from \"~/lib/request/strategies/auto-truncate\"\nimport { createDeferredToolRetryStrategy } from \"~/lib/request/strategies/deferred-tool-retry\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { processAnthropicSystem } from \"~/lib/system-prompt\"\nimport { buildMessageMapping } from \"./message-mapping\"\nimport { tuiLogger } from \"~/lib/tui\"\n\nimport { type AnthropicAutoTruncateResult, autoTruncateAnthropic } from \"./auto-truncate\"\nimport { createAnthropicMessages, type AnthropicMessageResponse } from \"./client\"\nimport { preprocessAnthropicMessages, sanitizeAnthropicMessages, type SanitizationStats } from \"./sanitize\"\nimport {\n type AnthropicStreamAccumulator,\n accumulateAnthropicStreamEvent,\n createAnthropicStreamAccumulator,\n} from \"./stream-accumulator\"\n\n// ============================================================================\n// Stream idle timeout & abort signal racing\n// ============================================================================\n\n/** Error thrown when no SSE event arrives within the configured idle timeout window */\nexport class StreamIdleTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`Stream idle timeout: no event received within ${timeoutMs / 1000}s`)\n this.name = \"StreamIdleTimeoutError\"\n }\n}\n\n/** Sentinel value returned when shutdown abort signal fires during iterator.next() */\nexport const STREAM_ABORTED = Symbol(\"STREAM_ABORTED\")\n\n/**\n * Race `iterator.next()` against idle timeout and/or shutdown abort signal.\n *\n * Without this, `await iterator.next()` blocks indefinitely when the upstream\n * connection is alive but sends no data — the shutdown signal check at the top\n * of the loop never gets reached. This function ensures the abort signal can\n * interrupt the wait.\n *\n * Returns `STREAM_ABORTED` when the abort signal fires (caller should break).\n * Rejects with `StreamIdleTimeoutError` if idle timeout fires first.\n */\nexport function raceIteratorNext<T>(\n promise: Promise<IteratorResult<T>>,\n opts: { idleTimeoutMs: number; abortSignal?: AbortSignal },\n): Promise<IteratorResult<T> | typeof STREAM_ABORTED> {\n const { idleTimeoutMs, abortSignal } = opts\n\n // Fast path: already aborted\n if (abortSignal?.aborted) return Promise.resolve(STREAM_ABORTED)\n\n // Build the set of racing promises\n const racers: Array<Promise<IteratorResult<T> | typeof STREAM_ABORTED>> = [promise]\n const cleanups: Array<() => void> = []\n\n // Idle timeout racer\n if (idleTimeoutMs > 0) {\n let timeoutId: ReturnType<typeof setTimeout>\n racers.push(\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new StreamIdleTimeoutError(idleTimeoutMs)), idleTimeoutMs)\n }),\n )\n cleanups.push(() => clearTimeout(timeoutId!))\n }\n\n // Abort signal racer — resolves (not rejects) with sentinel so the caller\n // can distinguish shutdown from errors and complete gracefully\n if (abortSignal && !abortSignal.aborted) {\n let onAbort: () => void\n racers.push(\n new Promise<typeof STREAM_ABORTED>((resolve) => {\n onAbort = () => resolve(STREAM_ABORTED)\n abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }),\n )\n cleanups.push(() => abortSignal.removeEventListener(\"abort\", onAbort!))\n }\n\n return Promise.race(racers).finally(() => {\n for (const cleanup of cleanups) cleanup()\n })\n}\n\n// ============================================================================\n// API routing\n// ============================================================================\n\nexport interface ApiRoutingDecision {\n supported: boolean\n reason: string\n}\n\n/**\n * Check if a model supports direct Anthropic API.\n * Returns a decision with reason so callers can log/display the routing rationale.\n */\nexport function supportsDirectAnthropicApi(modelId: string): ApiRoutingDecision {\n const model = state.modelIndex.get(modelId)\n if (model?.vendor !== \"Anthropic\") {\n return { supported: false, reason: `vendor is \"${model?.vendor ?? \"unknown\"}\", not Anthropic` }\n }\n\n if (!isEndpointSupported(model, ENDPOINT.MESSAGES)) {\n return { supported: false, reason: \"model does not support /v1/messages endpoint\" }\n }\n\n return { supported: true, reason: \"Anthropic vendor with /v1/messages support\" }\n}\n\n// ============================================================================\n// Main entry point — Anthropic /v1/messages completion\n// ============================================================================\n\n/**\n * Handle an Anthropic messages completion request.\n * Processes system prompt, creates RequestContext, builds context,\n * and routes to direct Anthropic API.\n */\nexport async function handleAnthropicMessagesCompletion(\n c: Context,\n anthropicPayload: MessagesPayload,\n options?: { clientModel?: string },\n) {\n // System prompt collection + config-based overrides (always active)\n if (anthropicPayload.system) {\n anthropicPayload.system = await processAnthropicSystem(anthropicPayload.system)\n }\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Route validation BEFORE creating RequestContext — prevents dangling history entries\n // when routing fails (reqCtx.create() triggers history insertion, and a subsequent throw\n // without reqCtx.fail() would leave an entry with no response)\n const routingDecision = supportsDirectAnthropicApi(anthropicPayload.model)\n if (!routingDecision.supported) {\n const msg = `Model \"${anthropicPayload.model}\" does not support /v1/messages: ${routingDecision.reason}`\n throw new HTTPError(msg, 400, msg)\n }\n consola.debug(`[AnthropicRouting] ${anthropicPayload.model}: ${routingDecision.reason}`)\n\n // Create request context — this triggers the \"created\" event → history consumer inserts entry\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"anthropic\", tuiLogId })\n reqCtx.setOriginalRequest({\n // Use client's original model name (before resolution/overrides)\n model: options?.clientModel ?? anthropicPayload.model,\n messages: anthropicPayload.messages as unknown as Array<MessageContent>,\n stream: anthropicPayload.stream ?? false,\n tools: anthropicPayload.tools as Array<ToolDefinition> | undefined,\n system: anthropicPayload.system,\n payload: anthropicPayload,\n })\n\n // Update TUI tracker with model info (immediate feedback, don't wait for event loop)\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: anthropicPayload.model,\n ...(options?.clientModel && { clientModel: options.clientModel }),\n })\n }\n\n // Phase 1: One-time preprocessing (idempotent, before routing)\n const preprocessed = preprocessAnthropicMessages(anthropicPayload.messages)\n anthropicPayload.messages = preprocessed.messages\n reqCtx.setPreprocessInfo({\n strippedReadTagCount: preprocessed.strippedReadTagCount,\n dedupedToolCallCount: preprocessed.dedupedToolCallCount,\n })\n\n return handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx)\n}\n\n// ============================================================================\n// Direct Anthropic completion orchestration\n// ============================================================================\n\n// Handle completion using direct Anthropic API (no translation needed)\nasync function handleDirectAnthropicCompletion(c: Context, anthropicPayload: MessagesPayload, reqCtx: RequestContext) {\n consola.debug(\"Using direct Anthropic API path for model:\", anthropicPayload.model)\n\n // Find model for auto-truncate and usage adjustment\n const selectedModel = state.modelIndex.get(anthropicPayload.model)\n\n // Always sanitize messages to filter orphaned tool_result/tool_use blocks\n const { payload: initialSanitized, stats: sanitizationStats } = sanitizeAnthropicMessages(anthropicPayload)\n reqCtx.addSanitizationInfo(toSanitizationInfo(sanitizationStats))\n\n // Record sanitization/preprocessing if anything was modified\n const hasPreprocessing =\n reqCtx.preprocessInfo ?\n reqCtx.preprocessInfo.dedupedToolCallCount > 0 || reqCtx.preprocessInfo.strippedReadTagCount > 0\n : false\n if (\n sanitizationStats.totalBlocksRemoved > 0\n || sanitizationStats.systemReminderRemovals > 0\n || sanitizationStats.fixedNameCount > 0\n || hasPreprocessing\n ) {\n const messageMapping = buildMessageMapping(anthropicPayload.messages, initialSanitized.messages)\n reqCtx.setRewrites({\n rewrittenMessages: initialSanitized.messages as unknown as Array<MessageContent>,\n rewrittenSystem: typeof initialSanitized.system === \"string\" ? initialSanitized.system : undefined,\n messageMapping,\n })\n }\n\n // Set initial tracking tags for log display\n if (reqCtx.tuiLogId) {\n const tags: Array<string> = []\n if (initialSanitized.thinking && initialSanitized.thinking.type !== \"disabled\")\n tags.push(`thinking:${initialSanitized.thinking.type}`)\n if (tags.length > 0) tuiLogger.updateRequest(reqCtx.tuiLogId, { tags })\n }\n\n // Build adapter and strategy for the pipeline\n const adapter: FormatAdapter<MessagesPayload> = {\n format: \"anthropic\",\n sanitize: (p) => sanitizeAnthropicMessages(p),\n execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p)),\n logPayloadSize: (p) => logPayloadSizeInfoAnthropic(p, selectedModel),\n }\n\n const strategies = [\n createTokenRefreshStrategy<MessagesPayload>(),\n createDeferredToolRetryStrategy<MessagesPayload>(),\n createAutoTruncateStrategy<MessagesPayload>({\n truncate: (p, model, opts) => autoTruncateAnthropic(p, model, opts) as Promise<TruncateResult<MessagesPayload>>,\n resanitize: (p) => sanitizeAnthropicMessages(p),\n isEnabled: () => state.autoTruncate,\n label: \"Anthropic\",\n }),\n ]\n\n // Track truncation result for non-streaming response marker\n let truncateResult: AnthropicAutoTruncateResult | undefined\n\n try {\n const result = await executeRequestPipeline({\n adapter,\n strategies,\n payload: initialSanitized,\n originalPayload: anthropicPayload,\n model: selectedModel,\n maxRetries: MAX_AUTO_TRUNCATE_RETRIES,\n requestContext: reqCtx,\n onRetry: (_attempt, _strategyName, newPayload, meta) => {\n // Capture truncation result for response marker\n const retryTruncateResult = meta?.truncateResult as AnthropicAutoTruncateResult | undefined\n if (retryTruncateResult) {\n truncateResult = retryTruncateResult\n }\n\n // Record rewrites for the retried payload\n const retrySanitization = meta?.sanitization as SanitizationStats | undefined\n if (retrySanitization) {\n reqCtx.addSanitizationInfo(toSanitizationInfo(retrySanitization))\n }\n const retryMessageMapping = buildMessageMapping(anthropicPayload.messages, newPayload.messages)\n reqCtx.setRewrites({\n truncation:\n retryTruncateResult ?\n {\n removedMessageCount: retryTruncateResult.removedMessageCount,\n originalTokens: retryTruncateResult.originalTokens,\n compactedTokens: retryTruncateResult.compactedTokens,\n processingTimeMs: retryTruncateResult.processingTimeMs,\n }\n : undefined,\n rewrittenMessages: newPayload.messages as unknown as Array<MessageContent>,\n rewrittenSystem: typeof newPayload.system === \"string\" ? newPayload.system : undefined,\n messageMapping: retryMessageMapping,\n })\n\n // Update tracking tags\n if (reqCtx.tuiLogId) {\n const retryAttempt = (meta?.attempt as number | undefined) ?? 1\n const retryTags = [\"truncated\", `retry-${retryAttempt}`]\n if (newPayload.thinking && newPayload.thinking.type !== \"disabled\")\n retryTags.push(`thinking:${newPayload.thinking.type}`)\n tuiLogger.updateRequest(reqCtx.tuiLogId, { tags: retryTags })\n }\n },\n })\n\n const response = result.response\n const effectivePayload = result.effectivePayload as MessagesPayload\n\n // Check if response is streaming (AsyncIterable)\n if (Symbol.asyncIterator in (response as object)) {\n consola.debug(\"Streaming response from Copilot (direct Anthropic)\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n await handleDirectAnthropicStreamingResponse({\n stream,\n response: response as AsyncIterable<ServerSentEventMessage>,\n anthropicPayload: effectivePayload,\n reqCtx,\n clientAbortSignal: clientAbort.signal,\n })\n })\n }\n\n // Non-streaming response\n return handleDirectAnthropicNonStreamingResponse(c, response as AnthropicMessageResponse, reqCtx, truncateResult)\n } catch (error) {\n reqCtx.fail(anthropicPayload.model, error)\n throw error\n }\n}\n\n// ============================================================================\n// Stream processing\n// ============================================================================\n\n/** Combine multiple abort signals into one (any signal triggers abort) */\nexport function combineAbortSignals(\n ...signals: Array<AbortSignal | undefined>\n): AbortSignal | undefined {\n const valid = signals.filter((s): s is AbortSignal => s !== undefined)\n if (valid.length === 0) return undefined\n if (valid.length === 1) return valid[0]\n return AbortSignal.any(valid)\n}\n\n/** Processed event from the Anthropic stream */\nexport interface ProcessedAnthropicEvent {\n /** Original SSE message for forwarding */\n raw: ServerSentEventMessage\n /** Parsed event for accumulation (undefined for keepalives / [DONE]) */\n parsed?: StreamEvent\n}\n\n/**\n * Process raw Anthropic SSE stream: parse events, accumulate, check shutdown.\n * Yields each event for the caller to forward to the client.\n *\n * Each iteration races `iterator.next()` against idle timeout (if configured)\n * and the shutdown abort signal — so a stalled upstream connection can be\n * interrupted by either mechanism without waiting for the next event.\n */\nexport async function* processAnthropicStream(\n response: AsyncIterable<ServerSentEventMessage>,\n acc: AnthropicStreamAccumulator,\n clientAbortSignal?: AbortSignal,\n): AsyncGenerator<ProcessedAnthropicEvent> {\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n const iterator = response[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbortSignal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n // Shutdown abort signal fired while waiting for the next event\n if (result === STREAM_ABORTED) break\n\n if (result.done) break\n\n const rawEvent = result.value\n\n // No data — keepalive, nothing to accumulate\n if (!rawEvent.data) {\n consola.debug(\"SSE event with no data (keepalive):\", rawEvent.event ?? \"(no event type)\")\n yield { raw: rawEvent }\n continue\n }\n\n // [DONE] is not part of the SSE spec - it's an OpenAI convention.\n // Copilot's gateway injects it at the end of all streams, including Anthropic.\n // see refs/vscode-copilot-chat/src/platform/endpoint/node/messagesApi.ts:326\n if (rawEvent.data === \"[DONE]\") break\n\n // Try to parse and accumulate for history/tracking\n let parsed: StreamEvent | undefined\n try {\n parsed = JSON.parse(rawEvent.data) as StreamEvent\n accumulateAnthropicStreamEvent(parsed, acc)\n } catch (parseError) {\n consola.error(\"Failed to parse Anthropic stream event:\", parseError, rawEvent.data)\n }\n\n yield { raw: rawEvent, parsed }\n\n // Error event is terminal — Anthropic sends no more events after this\n if (parsed?.type === \"error\") break\n }\n}\n\n// ============================================================================\n// Response handlers (streaming / non-streaming)\n// ============================================================================\n\n/** Options for handleDirectAnthropicStreamingResponse */\nexport interface DirectAnthropicStreamHandlerOptions {\n stream: SSEStreamingApi\n response: AsyncIterable<ServerSentEventMessage>\n anthropicPayload: MessagesPayload\n reqCtx: RequestContext\n /** Abort signal that fires when the downstream client disconnects */\n clientAbortSignal?: AbortSignal\n}\n\n/** Handle streaming direct Anthropic response (passthrough SSE events) */\nexport async function handleDirectAnthropicStreamingResponse(opts: DirectAnthropicStreamHandlerOptions) {\n const { stream, response, anthropicPayload, reqCtx, clientAbortSignal } = opts\n const acc = createAnthropicStreamAccumulator()\n\n // SSE event recording for debugging (excludes high-volume content_block_delta and ping)\n const sseEvents: Array<SseEventRecord> = []\n\n // Streaming metrics for TUI footer and debug timing\n const streamStartMs = Date.now()\n let bytesIn = 0\n let eventsIn = 0\n let currentBlockType = \"\"\n let firstEventLogged = false\n\n try {\n for await (const { raw: rawEvent, parsed } of processAnthropicStream(response, acc, clientAbortSignal)) {\n const dataLen = rawEvent.data?.length ?? 0\n bytesIn += dataLen\n eventsIn++\n\n // Record non-delta SSE events for history debugging\n if (parsed && parsed.type !== \"content_block_delta\" && parsed.type !== \"ping\") {\n sseEvents.push({\n offsetMs: Date.now() - streamStartMs,\n type: parsed.type,\n data: parsed,\n })\n }\n\n // Debug: log first event arrival (measures TTFB from stream perspective)\n if (!firstEventLogged) {\n const eventType = parsed?.type ?? \"keepalive\"\n consola.debug(`[Stream] First event at +${Date.now() - streamStartMs}ms (${eventType})`)\n firstEventLogged = true\n }\n\n // Debug: log content block boundaries with timing\n if (parsed?.type === \"content_block_start\") {\n currentBlockType = (parsed.content_block as { type: string }).type\n consola.debug(`[Stream] Block #${parsed.index} start: ${currentBlockType} at +${Date.now() - streamStartMs}ms`)\n } else if (parsed?.type === \"content_block_stop\") {\n const offset = Date.now() - streamStartMs\n consola.debug(\n `[Stream] Block #${parsed.index} stop (${currentBlockType}) at +${offset}ms, cumulative ↓${bytesIn}B ${eventsIn}ev`,\n )\n currentBlockType = \"\"\n }\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n streamBlockType: currentBlockType,\n })\n }\n\n // Forward every event to client — proxy preserves upstream data\n await stream.writeSSE({\n data: rawEvent.data ?? \"\",\n event: rawEvent.event,\n id: rawEvent.id != null ? String(rawEvent.id) : undefined,\n retry: rawEvent.retry,\n })\n }\n\n // Debug: stream completion summary\n consola.debug(`[Stream] Completed: ↓${bytesIn}B ${eventsIn}ev in ${Date.now() - streamStartMs}ms`)\n\n // Record SSE events for history debugging (must be before complete/fail which calls toHistoryEntry)\n reqCtx.setSseEvents(sseEvents)\n\n if (acc.streamError) {\n reqCtx.fail(acc.model || anthropicPayload.model, new Error(`${acc.streamError.type}: ${acc.streamError.message}`))\n } else {\n const responseData = buildAnthropicResponseData(acc, anthropicPayload.model)\n reqCtx.complete(responseData)\n }\n } catch (error) {\n consola.error(\"Direct Anthropic stream error:\", error)\n reqCtx.fail(acc.model || anthropicPayload.model, error)\n\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorType = error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"api_error\"\n await stream.writeSSE({\n event: \"error\",\n data: JSON.stringify({\n type: \"error\",\n error: { type: errorType, message: errorMessage },\n }),\n })\n }\n}\n\n/** Handle non-streaming direct Anthropic response */\nexport function handleDirectAnthropicNonStreamingResponse(\n c: Context,\n response: AnthropicMessageResponse,\n reqCtx: RequestContext,\n truncateResult: AnthropicAutoTruncateResult | undefined,\n) {\n reqCtx.complete({\n success: true,\n model: response.model,\n usage: {\n input_tokens: response.usage.input_tokens,\n output_tokens: response.usage.output_tokens,\n cache_read_input_tokens: response.usage.cache_read_input_tokens ?? undefined,\n cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? undefined,\n },\n stop_reason: response.stop_reason ?? undefined,\n content: { role: \"assistant\", content: response.content },\n })\n\n // Add truncation marker to response if verbose mode and truncation occurred\n let finalResponse = response\n if (state.verbose && truncateResult?.wasTruncated) {\n const marker = createTruncationMarker(truncateResult)\n finalResponse = prependMarkerToResponse(response, marker)\n }\n\n return c.json(finalResponse)\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Convert SanitizationStats to the format expected by rewrites */\nfunction toSanitizationInfo(stats: SanitizationStats) {\n return {\n totalBlocksRemoved: stats.totalBlocksRemoved,\n orphanedToolUseCount: stats.orphanedToolUseCount,\n orphanedToolResultCount: stats.orphanedToolResultCount,\n fixedNameCount: stats.fixedNameCount,\n emptyTextBlocksRemoved: stats.emptyTextBlocksRemoved,\n systemReminderRemovals: stats.systemReminderRemovals,\n }\n}\n","/**\n * OpenAI orphaned tool block filtering.\n *\n * Filters orphaned tool messages and tool_calls from OpenAI messages\n * to ensure API compatibility.\n */\n\nimport consola from \"consola\"\n\nimport type { Message, ToolCall } from \"./client\"\n\n/**\n * Get tool_call IDs from an OpenAI assistant message.\n */\nexport function getOpenAIToolCallIds(msg: Message): Array<string> {\n if (msg.role === \"assistant\" && msg.tool_calls) {\n return msg.tool_calls.map((tc: ToolCall) => tc.id)\n }\n return []\n}\n\n/**\n * Get tool_result IDs from OpenAI tool messages.\n */\nexport function getOpenAIToolResultIds(messages: Array<Message>): Set<string> {\n const ids = new Set<string>()\n for (const msg of messages) {\n if (msg.role === \"tool\" && msg.tool_call_id) {\n ids.add(msg.tool_call_id)\n }\n }\n return ids\n}\n\n/**\n * Filter orphaned tool messages from OpenAI messages.\n */\nexport function filterOpenAIOrphanedToolResults(messages: Array<Message>): Array<Message> {\n // Collect all available tool_call IDs\n const toolCallIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getOpenAIToolCallIds(msg)) {\n toolCallIds.add(id)\n }\n }\n\n // Filter out orphaned tool messages\n let removedCount = 0\n const filtered = messages.filter((msg) => {\n if (msg.role === \"tool\" && msg.tool_call_id && !toolCallIds.has(msg.tool_call_id)) {\n removedCount++\n return false\n }\n return true\n })\n\n if (removedCount > 0) {\n consola.debug(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool_result`)\n }\n\n return filtered\n}\n\n/**\n * Filter orphaned tool_calls from OpenAI assistant messages.\n */\nexport function filterOpenAIOrphanedToolUse(messages: Array<Message>): Array<Message> {\n const toolResultIds = getOpenAIToolResultIds(messages)\n\n // Filter out orphaned tool_calls from assistant messages\n const result: Array<Message> = []\n let removedCount = 0\n\n for (const msg of messages) {\n if (msg.role === \"assistant\" && msg.tool_calls) {\n const filteredToolCalls = msg.tool_calls.filter((tc: ToolCall) => {\n if (!toolResultIds.has(tc.id)) {\n removedCount++\n return false\n }\n return true\n })\n\n // If all tool_calls were removed but there's still content, keep the message\n if (filteredToolCalls.length === 0) {\n if (msg.content) {\n result.push({ ...msg, tool_calls: undefined })\n }\n // Skip message entirely if no content and no tool_calls\n continue\n }\n\n result.push({ ...msg, tool_calls: filteredToolCalls })\n continue\n }\n\n result.push(msg)\n }\n\n if (removedCount > 0) {\n consola.debug(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool_use`)\n }\n\n return result\n}\n\n/**\n * Ensure OpenAI messages start with a user message.\n */\nexport function ensureOpenAIStartsWithUser(messages: Array<Message>): Array<Message> {\n let startIndex = 0\n while (startIndex < messages.length && messages[startIndex].role !== \"user\") {\n startIndex++\n }\n\n if (startIndex > 0) {\n consola.debug(`[Sanitizer:OpenAI] Skipped ${startIndex} leading non-user messages`)\n }\n\n return messages.slice(startIndex)\n}\n\n/**\n * Extract system/developer messages from the beginning of OpenAI messages.\n */\nexport function extractOpenAISystemMessages(messages: Array<Message>): {\n systemMessages: Array<Message>\n conversationMessages: Array<Message>\n} {\n let splitIndex = 0\n while (splitIndex < messages.length) {\n const role = messages[splitIndex].role\n if (role !== \"system\" && role !== \"developer\") break\n splitIndex++\n }\n\n return {\n systemMessages: messages.slice(0, splitIndex),\n conversationMessages: messages.slice(splitIndex),\n }\n}\n","/**\n * Auto-truncate module: Automatically truncates conversation history\n * when it exceeds token limits (OpenAI format).\n *\n * Key features:\n * - Binary search for optimal truncation point\n * - Token limit enforcement with learned calibration\n * - Preserves system messages\n * - Filters orphaned tool_result and tool_use messages\n * - Optional smart compression of old tool_result content\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\n\nimport { getTokenCount } from \"~/lib/models/tokenizer\"\nimport { state } from \"~/lib/state\"\nimport { bytesToKB } from \"~/lib/utils\"\n\nimport type { AutoTruncateConfig } from \"../auto-truncate\"\nimport type { ChatCompletionsPayload, Message } from \"./client\"\n\nimport {\n DEFAULT_AUTO_TRUNCATE_CONFIG,\n LARGE_TOOL_RESULT_THRESHOLD,\n calibrate,\n compressToolResultContent,\n computeSafetyMargin,\n getLearnedLimits,\n} from \"../auto-truncate\"\nimport {\n ensureOpenAIStartsWithUser,\n extractOpenAISystemMessages,\n filterOpenAIOrphanedToolResults,\n filterOpenAIOrphanedToolUse,\n} from \"./orphan-filter\"\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/** Result of auto-truncate operation */\nexport interface OpenAIAutoTruncateResult {\n payload: ChatCompletionsPayload\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\n/** Result of needs-compaction check */\nexport interface OpenAICompactionCheckResult {\n needed: boolean\n currentTokens: number\n tokenLimit: number\n reason?: \"tokens\"\n}\n\n// ============================================================================\n// Limit Calculation\n// ============================================================================\n\n/** Default fallback for when model capabilities are not available */\nconst DEFAULT_CONTEXT_WINDOW = 128000\n\n/**\n * Calculate the effective token limit for auto-truncate.\n * Uses explicit target if provided, otherwise learned limits with calibration,\n * otherwise model capabilities with safety margin.\n */\nfunction calculateTokenLimit(model: Model, config: AutoTruncateConfig): number {\n // Use explicit target if provided (reactive retry — caller already applied margin)\n if (config.targetTokenLimit !== undefined) {\n return config.targetTokenLimit\n }\n\n // Check for learned limits (adjusted based on previous errors)\n const learned = getLearnedLimits(model.id)\n if (learned) {\n const margin = computeSafetyMargin(learned.sampleCount)\n return Math.floor(learned.tokenLimit * (1 - margin))\n }\n\n // Use model capabilities with static safety margin\n const rawTokenLimit =\n model.capabilities?.limits?.max_context_window_tokens\n ?? model.capabilities?.limits?.max_prompt_tokens\n ?? DEFAULT_CONTEXT_WINDOW\n\n return Math.floor(rawTokenLimit * (1 - config.safetyMarginPercent / 100))\n}\n\n// ============================================================================\n// Message Utilities\n// ============================================================================\n\n/** Estimate tokens for a single message (fast approximation) */\nfunction estimateMessageTokens(msg: Message): number {\n let charCount = 0\n\n if (typeof msg.content === \"string\") {\n charCount = msg.content.length\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.type === \"text\") {\n charCount += part.text.length\n } else if (\"image_url\" in part) {\n // Base64 images are large but compressed in token counting\n charCount += Math.min(part.image_url.url.length, 10000)\n }\n }\n }\n\n if (msg.tool_calls) {\n charCount += JSON.stringify(msg.tool_calls).length\n }\n\n // ~4 chars per token + message overhead\n return Math.ceil(charCount / 4) + 10\n}\n\n/** Calculate cumulative token sums from the end of the message array */\nfunction calculateCumulativeSums(messages: Array<Message>): { cumTokens: Array<number> } {\n const n = messages.length\n const cumTokens = Array.from<number>({ length: n + 1 }).fill(0)\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n return { cumTokens }\n}\n\n/**\n * Clean up orphaned tool messages and ensure valid conversation start.\n * Loops until stable since each pass may create new orphans.\n */\nfunction cleanupMessages(messages: Array<Message>): Array<Message> {\n let result = messages\n let prevLength: number\n do {\n prevLength = result.length\n result = filterOpenAIOrphanedToolResults(result)\n result = filterOpenAIOrphanedToolUse(result)\n result = ensureOpenAIStartsWithUser(result)\n } while (result.length !== prevLength)\n return result\n}\n\n// ============================================================================\n// Smart Tool Result Compression\n// ============================================================================\n\n/**\n * Smart compression strategy for OpenAI format:\n * 1. Calculate tokens from the end until reaching preservePercent of limit\n * 2. Messages before that threshold get their tool content compressed\n * 3. Returns compressed messages and stats\n *\n * @param preservePercent - Percentage of context to preserve uncompressed (0.0-1.0)\n */\nfunction smartCompressToolResults(\n messages: Array<Message>,\n tokenLimit: number,\n preservePercent: number,\n): {\n messages: Array<Message>\n compressedCount: number\n compressThresholdIndex: number\n} {\n // Calculate cumulative token size from the end\n const n = messages.length\n const { cumTokens } = calculateCumulativeSums(messages)\n\n // Find the threshold index where we've used the preserve percentage of the limit\n const preserveTokenLimit = Math.floor(tokenLimit * preservePercent)\n\n let thresholdIndex = n\n for (let i = n - 1; i >= 0; i--) {\n if (cumTokens[i] > preserveTokenLimit) {\n thresholdIndex = i + 1\n break\n }\n thresholdIndex = i\n }\n\n // If threshold is at the end, nothing to compress\n if (thresholdIndex >= n) {\n return { messages, compressedCount: 0, compressThresholdIndex: n }\n }\n\n // Compress tool messages before threshold\n const result: Array<Message> = []\n let compressedCount = 0\n\n for (const [i, msg] of messages.entries()) {\n if (\n i < thresholdIndex\n && msg.role === \"tool\"\n && typeof msg.content === \"string\"\n && msg.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n compressedCount++\n result.push({\n ...msg,\n content: compressToolResultContent(msg.content),\n })\n continue\n }\n result.push(msg)\n }\n\n return {\n messages: result,\n compressedCount,\n compressThresholdIndex: thresholdIndex,\n }\n}\n\n// ============================================================================\n// Binary Search Algorithm\n// ============================================================================\n\ninterface PreserveSearchParams {\n messages: Array<Message>\n systemTokens: number\n tokenLimit: number\n}\n\n/**\n * Find the optimal index from which to preserve messages.\n * Uses binary search with pre-calculated cumulative sums.\n * Returns the smallest index where the preserved portion fits within limits.\n */\nfunction findOptimalPreserveIndex(params: PreserveSearchParams): number {\n const { messages, systemTokens, tokenLimit } = params\n\n if (messages.length === 0) return 0\n\n // Account for truncation marker (~50 tokens)\n const markerTokens = 50\n\n const availableTokens = tokenLimit - systemTokens - markerTokens\n\n if (availableTokens <= 0) {\n return messages.length // Cannot fit any messages\n }\n\n // Pre-calculate cumulative sums from the end\n const n = messages.length\n const { cumTokens } = calculateCumulativeSums(messages)\n\n // Binary search for the smallest index where token limit is satisfied\n let left = 0\n let right = n\n\n while (left < right) {\n const mid = (left + right) >>> 1\n if (cumTokens[mid] <= availableTokens) {\n right = mid // Can keep more messages\n } else {\n left = mid + 1 // Need to remove more\n }\n }\n\n return left\n}\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Check if payload needs compaction based on learned model limits.\n * Returns early with `needed: false` when no limits are known for the model.\n */\nexport async function checkNeedsCompactionOpenAI(\n payload: ChatCompletionsPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<OpenAICompactionCheckResult> {\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n\n // If no learned limits and no explicit target, skip pre-check\n // (we don't know the real limit yet — let the server tell us)\n const learned = getLearnedLimits(model.id)\n if (!learned && cfg.targetTokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const tokenLimit = calculateTokenLimit(model, cfg)\n const tokenCount = await getTokenCount(payload, model)\n const rawTokens = tokenCount.input\n\n // Apply calibration to adjust the GPT tokenizer estimate\n const currentTokens = learned && learned.sampleCount > 0\n ? calibrate(model.id, rawTokens)\n : rawTokens\n\n const exceedsTokens = cfg.checkTokenLimit && currentTokens > tokenLimit\n\n return {\n needed: exceedsTokens,\n currentTokens,\n tokenLimit,\n reason: exceedsTokens ? \"tokens\" : undefined,\n }\n}\n\n/**\n * Generate a summary of removed messages for context.\n * Extracts key information like tool calls and topics.\n */\nfunction generateRemovedMessagesSummary(removedMessages: Array<Message>): string {\n const toolCalls: Array<string> = []\n let userMessageCount = 0\n let assistantMessageCount = 0\n\n for (const msg of removedMessages) {\n if (msg.role === \"user\") {\n userMessageCount++\n } else if (msg.role === \"assistant\") {\n assistantMessageCount++\n }\n\n // Extract tool call names\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n if (tc.function.name) {\n toolCalls.push(tc.function.name)\n }\n }\n }\n }\n\n // Build summary parts\n const parts: Array<string> = []\n\n // Message breakdown\n if (userMessageCount > 0 || assistantMessageCount > 0) {\n const breakdown = []\n if (userMessageCount > 0) breakdown.push(`${userMessageCount} user`)\n if (assistantMessageCount > 0) breakdown.push(`${assistantMessageCount} assistant`)\n parts.push(`Messages: ${breakdown.join(\", \")}`)\n }\n\n // Tool calls\n if (toolCalls.length > 0) {\n // Deduplicate and limit\n const uniqueTools = [...new Set(toolCalls)]\n const displayTools =\n uniqueTools.length > 5 ? [...uniqueTools.slice(0, 5), `+${uniqueTools.length - 5} more`] : uniqueTools\n parts.push(`Tools used: ${displayTools.join(\", \")}`)\n }\n\n return parts.join(\". \")\n}\n\n/**\n * Add a compression notice to the system message.\n * Informs the model that some tool content has been compressed.\n */\nfunction addCompressionNotice(payload: ChatCompletionsPayload, compressedCount: number): ChatCompletionsPayload {\n const notice =\n `\\n\\n[CONTEXT NOTE]\\n`\n + `${compressedCount} large tool results have been compressed to reduce context size.\\n`\n + `The compressed results show the beginning and end of the content with an omission marker.\\n`\n + `If you need the full content, you can re-read the file or re-run the tool.\\n`\n + `[END NOTE]`\n\n // Find last system message and append notice\n const messages = [...payload.messages]\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"system\" || msg.role === \"developer\") {\n if (typeof msg.content === \"string\") {\n messages[i] = { ...msg, content: msg.content + notice }\n }\n break\n }\n }\n\n return { ...payload, messages }\n}\n\n/**\n * Create truncation context to append to system messages.\n */\nfunction createTruncationSystemContext(removedCount: number, compressedCount: number, summary: string): string {\n let context = `\\n\\n[CONVERSATION CONTEXT]\\n`\n\n if (removedCount > 0) {\n context += `${removedCount} earlier messages have been removed due to context window limits.\\n`\n }\n\n if (compressedCount > 0) {\n context += `${compressedCount} large tool results have been compressed.\\n`\n }\n\n if (summary) {\n context += `Summary of removed content: ${summary}\\n`\n }\n\n context +=\n `If you need earlier context, ask the user or check available tools for conversation history access.\\n`\n + `[END CONTEXT]`\n\n return context\n}\n\n/** Create a truncation marker message (fallback when no system message) */\nfunction createTruncationMarker(removedCount: number, compressedCount: number, summary: string): Message {\n const parts: Array<string> = []\n\n if (removedCount > 0) {\n parts.push(`${removedCount} earlier messages removed`)\n }\n if (compressedCount > 0) {\n parts.push(`${compressedCount} tool results compressed`)\n }\n\n let content = `[CONTEXT MODIFIED: ${parts.join(\", \")} to fit context limits]`\n if (summary) {\n content += `\\n[Summary: ${summary}]`\n }\n return {\n role: \"user\",\n content,\n }\n}\n\n// ============================================================================\n// Truncation Steps\n// ============================================================================\n\n/** Shared context for truncation operations */\ninterface TruncationContext {\n payload: ChatCompletionsPayload\n model: Model\n cfg: AutoTruncateConfig\n tokenLimit: number\n originalTokens: number\n originalBytes: number\n startTime: number\n}\n\nfunction buildTimedResult(\n ctx: TruncationContext,\n result: Omit<OpenAIAutoTruncateResult, \"processingTimeMs\">,\n): OpenAIAutoTruncateResult {\n return { ...result, processingTimeMs: Math.round(performance.now() - ctx.startTime) }\n}\n\n/**\n * Step 1: Try compressing tool results to fit within limits.\n * First compresses old tool results, then all if needed.\n * Returns early result if compression alone is sufficient.\n */\nasync function tryCompressToolResults(\n ctx: TruncationContext,\n): Promise<{ workingMessages: Array<Message>; compressedCount: number; earlyResult?: OpenAIAutoTruncateResult }> {\n if (!state.compressToolResultsBeforeTruncate) {\n return { workingMessages: ctx.payload.messages, compressedCount: 0 }\n }\n\n // Step 1a: Compress old tool messages\n const compressionResult = smartCompressToolResults(\n ctx.payload.messages,\n ctx.tokenLimit,\n ctx.cfg.preserveRecentPercent,\n )\n let workingMessages = compressionResult.messages\n let compressedCount = compressionResult.compressedCount\n\n // Check if compression alone was enough\n const compressedPayload = { ...ctx.payload, messages: workingMessages }\n const compressedBytes = JSON.stringify(compressedPayload).length\n const compressedTokenCount = await getTokenCount(compressedPayload, ctx.model)\n\n if (compressedTokenCount.input <= ctx.tokenLimit) {\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${compressedTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(compressedBytes)}KB `\n + `(compressed ${compressedCount} tool_results) [${elapsedMs}ms]`,\n )\n\n const noticePayload = addCompressionNotice(compressedPayload, compressedCount)\n // Estimate notice token overhead instead of full recount (~150 chars / 4 + framing)\n const noticeTokenOverhead = Math.ceil(150 / 4) + 10\n\n return {\n workingMessages,\n compressedCount,\n earlyResult: buildTimedResult(ctx, {\n payload: noticePayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: compressedTokenCount.input + noticeTokenOverhead,\n removedMessageCount: 0,\n }),\n }\n }\n\n // Step 1b: Compress ALL tool messages (including recent ones)\n const allCompression = smartCompressToolResults(\n workingMessages,\n ctx.tokenLimit,\n 0.0, // preservePercent=0 means compress all messages\n )\n if (allCompression.compressedCount > 0) {\n workingMessages = allCompression.messages\n compressedCount += allCompression.compressedCount\n\n // Check if compressing all was enough\n const allCompressedPayload = { ...ctx.payload, messages: workingMessages }\n const allCompressedBytes = JSON.stringify(allCompressedPayload).length\n const allCompressedTokenCount = await getTokenCount(allCompressedPayload, ctx.model)\n\n if (allCompressedTokenCount.input <= ctx.tokenLimit) {\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${allCompressedTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(allCompressedBytes)}KB `\n + `(compressed ${compressedCount} tool_results, including recent) [${elapsedMs}ms]`,\n )\n\n const noticePayload = addCompressionNotice(allCompressedPayload, compressedCount)\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 10\n\n return {\n workingMessages,\n compressedCount,\n earlyResult: buildTimedResult(ctx, {\n payload: noticePayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: allCompressedTokenCount.input + noticeTokenOverhead,\n removedMessageCount: 0,\n }),\n }\n }\n }\n\n return { workingMessages, compressedCount }\n}\n\n/**\n * Step 2: Remove messages to fit within limits using binary search.\n * Handles orphan cleanup, summary generation, and result assembly.\n */\nasync function truncateByMessageRemoval(\n ctx: TruncationContext,\n workingMessages: Array<Message>,\n compressedCount: number,\n): Promise<OpenAIAutoTruncateResult> {\n // Extract system messages from working messages\n const { systemMessages, conversationMessages } = extractOpenAISystemMessages(workingMessages)\n\n // Calculate system message token sizes\n const systemTokens = systemMessages.reduce((sum, m) => sum + estimateMessageTokens(m), 0)\n\n // Find optimal preserve index\n const preserveIndex = findOptimalPreserveIndex({\n messages: conversationMessages,\n systemTokens,\n tokenLimit: ctx.tokenLimit,\n })\n\n // Check if we can compact\n if (preserveIndex >= conversationMessages.length) {\n consola.warn(\"[AutoTruncate:OpenAI] Would need to remove all messages\")\n return buildTimedResult(ctx, {\n payload: ctx.payload,\n wasTruncated: false,\n originalTokens: ctx.originalTokens,\n compactedTokens: ctx.originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Build preserved messages and clean up orphans\n let preserved = conversationMessages.slice(preserveIndex)\n preserved = cleanupMessages(preserved)\n\n if (preserved.length === 0) {\n consola.warn(\"[AutoTruncate:OpenAI] All messages filtered out after cleanup\")\n return buildTimedResult(ctx, {\n payload: ctx.payload,\n wasTruncated: false,\n originalTokens: ctx.originalTokens,\n compactedTokens: ctx.originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Calculate removed messages and generate summary\n const removedMessages = conversationMessages.slice(0, preserveIndex)\n const removedCount = conversationMessages.length - preserved.length\n const summary = generateRemovedMessagesSummary(removedMessages)\n\n // Build new payload with truncation context\n let newSystemMessages = systemMessages\n let newMessages = preserved\n\n // Prefer adding context to last system message (cleaner for the model)\n if (systemMessages.length > 0) {\n const truncationContext = createTruncationSystemContext(removedCount, compressedCount, summary)\n const lastSystemIdx = systemMessages.length - 1\n const lastSystem = systemMessages[lastSystemIdx]\n\n // Append context to last system message\n const updatedSystem: Message = {\n ...lastSystem,\n\n content: typeof lastSystem.content === \"string\" ? lastSystem.content + truncationContext : lastSystem.content, // Can't append to array content\n }\n newSystemMessages = [...systemMessages.slice(0, lastSystemIdx), updatedSystem]\n } else {\n // No system messages, use marker message\n const marker = createTruncationMarker(removedCount, compressedCount, summary)\n newMessages = [marker, ...preserved]\n }\n\n const newPayload: ChatCompletionsPayload = {\n ...ctx.payload,\n messages: [...newSystemMessages, ...newMessages],\n }\n\n // Verify the result\n const newBytes = JSON.stringify(newPayload).length\n const newTokenCount = await getTokenCount(newPayload, ctx.model)\n\n // Log single line summary\n const actions: Array<string> = []\n if (removedCount > 0) actions.push(`removed ${removedCount} msgs`)\n if (compressedCount > 0) actions.push(`compressed ${compressedCount} tool_results`)\n const actionInfo = actions.length > 0 ? ` (${actions.join(\", \")})` : \"\"\n\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${newTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(newBytes)}KB${actionInfo} [${elapsedMs}ms]`,\n )\n\n // Warn if still over token limit\n if (newTokenCount.input > ctx.tokenLimit) {\n consola.warn(\n `[AutoTruncate:OpenAI] Result still over token limit (${newTokenCount.input} > ${ctx.tokenLimit})`,\n )\n }\n\n return buildTimedResult(ctx, {\n payload: newPayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: newTokenCount.input,\n removedMessageCount: removedCount,\n })\n}\n\n// ============================================================================\n// Public Entry Points\n// ============================================================================\n\n/**\n * Perform auto-truncation on a payload that exceeds limits.\n * Uses binary search to find the optimal truncation point.\n *\n * Pipeline:\n * 1. Check if compaction is needed\n * 2. Try compressing tool results (old first, then all)\n * 3. If still over limit, remove messages via binary search\n */\nexport async function autoTruncateOpenAI(\n payload: ChatCompletionsPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<OpenAIAutoTruncateResult> {\n const startTime = performance.now()\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // Measure original size\n const originalBytes = JSON.stringify(payload).length\n const originalTokens = (await getTokenCount(payload, model)).input\n\n const ctx: TruncationContext = {\n payload,\n model,\n cfg,\n tokenLimit,\n originalTokens,\n originalBytes,\n startTime,\n }\n\n // Check if compaction is needed\n if (originalTokens <= tokenLimit) {\n return buildTimedResult(ctx, {\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Step 1: Try tool result compression\n const { workingMessages, compressedCount, earlyResult } = await tryCompressToolResults(ctx)\n if (earlyResult) return earlyResult\n\n // Step 2: Message removal via binary search\n return await truncateByMessageRemoval(ctx, workingMessages, compressedCount)\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n */\nexport function createTruncationResponseMarkerOpenAI(result: OpenAIAutoTruncateResult): string {\n if (!result.wasTruncated) return \"\"\n\n const reduction = result.originalTokens - result.compactedTokens\n const percentage = Math.round((reduction / result.originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${result.removedMessageCount} messages removed, `\n + `${result.originalTokens} → ${result.compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n","import type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\n/** Re-export types from centralized location */\nexport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ChatCompletionsPayload,\n ContentPart,\n ImagePart,\n Message,\n TextPart,\n Tool,\n ToolCall,\n} from \"~/types/api/openai\"\n\nimport type { ChatCompletionsPayload, ChatCompletionResponse } from \"~/types/api/openai\"\n\nexport const createChatCompletions = async (\n payload: ChatCompletionsPayload,\n): Promise<ChatCompletionResponse | AsyncGenerator<ServerSentEventMessage>> => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = payload.messages.some(\n (x) => typeof x.content !== \"string\" && x.content?.some((x) => x.type === \"image_url\"),\n )\n\n // Agent/user check for X-Initiator header\n // Determine if any message is from an agent (\"assistant\" or \"tool\")\n const isAgentCall = payload.messages.some((msg) => [\"assistant\", \"tool\"].includes(msg.role))\n\n // Build headers and add X-Initiator\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create chat completions\", response)\n throw await HTTPError.fromResponse(\"Failed to create chat completions\", response, payload.model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ChatCompletionResponse\n}\n","/**\n * OpenAI message sanitization orchestrator.\n *\n * Combines system-reminder removal, orphan filtering, and empty block cleanup\n * into a single sanitization pipeline for OpenAI messages.\n */\n\nimport consola from \"consola\"\n\nimport { removeSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\n\nimport type { ChatCompletionsPayload, Message } from \"./client\"\n\nimport {\n extractOpenAISystemMessages,\n filterOpenAIOrphanedToolResults,\n filterOpenAIOrphanedToolUse,\n} from \"./orphan-filter\"\n\n// ============================================================================\n// Message Content Sanitization\n// ============================================================================\n\n/**\n * Remove system-reminder tags from OpenAI message content.\n * Handles both string content and array of content parts.\n *\n * NOTE: System prompt overrides are handled by\n * system-prompt-manager.ts via config.yaml.\n */\nfunction sanitizeOpenAIMessageContent(msg: Message): Message {\n if (typeof msg.content === \"string\") {\n const sanitized = removeSystemReminderTags(msg.content)\n if (sanitized !== msg.content) {\n // Don't return empty content — keep original if sanitized is empty\n return sanitized ? { ...msg, content: sanitized } : msg\n }\n return msg\n }\n\n // Handle array of content parts (TextPart | ImagePart)\n if (Array.isArray(msg.content)) {\n const result = msg.content.reduce<{\n parts: Array<\n | { type: \"text\"; text: string }\n | {\n type: \"image_url\"\n image_url: { url: string; detail?: \"low\" | \"high\" | \"auto\" }\n }\n >\n modified: boolean\n }>(\n (acc, part) => {\n if (part.type === \"text\" && typeof part.text === \"string\") {\n const sanitized = removeSystemReminderTags(part.text)\n if (sanitized !== part.text) {\n if (sanitized) {\n acc.parts.push({ ...part, text: sanitized })\n }\n acc.modified = true\n return acc\n }\n }\n acc.parts.push(part)\n return acc\n },\n { parts: [], modified: false },\n )\n\n if (result.modified) {\n return { ...msg, content: result.parts }\n }\n }\n\n return msg\n}\n\n/**\n * Remove system-reminder tags from all OpenAI messages.\n */\nexport function removeOpenAISystemReminders(messages: Array<Message>): {\n messages: Array<Message>\n modifiedCount: number\n} {\n let modifiedCount = 0\n const result = messages.map((msg) => {\n const sanitized = sanitizeOpenAIMessageContent(msg)\n if (sanitized !== msg) modifiedCount++\n return sanitized\n })\n return { messages: result, modifiedCount }\n}\n\n// ============================================================================\n// Main Orchestrator\n// ============================================================================\n\n/**\n * Sanitize OpenAI messages by filtering orphaned tool messages and system reminders.\n *\n * @returns Sanitized payload and count of removed items\n */\nexport function sanitizeOpenAIMessages(payload: ChatCompletionsPayload): {\n payload: ChatCompletionsPayload\n removedCount: number\n systemReminderRemovals: number\n} {\n const { systemMessages, conversationMessages } = extractOpenAISystemMessages(payload.messages)\n\n // Remove system-reminder tags from all messages\n const convResult = removeOpenAISystemReminders(conversationMessages)\n let messages = convResult.messages\n const sysResult = removeOpenAISystemReminders(systemMessages)\n const sanitizedSystemMessages = sysResult.messages\n const systemReminderRemovals = convResult.modifiedCount + sysResult.modifiedCount\n\n const originalCount = messages.length\n\n // Filter orphaned tool_result and tool_use messages\n messages = filterOpenAIOrphanedToolResults(messages)\n messages = filterOpenAIOrphanedToolUse(messages)\n\n // Final safety net: remove empty/whitespace-only text parts from array content\n const allMessages = [...sanitizedSystemMessages, ...messages].map((msg) => {\n if (!Array.isArray(msg.content)) return msg\n const filtered = msg.content.filter((part) => {\n if (part.type === \"text\") return part.text.trim() !== \"\"\n return true\n })\n if (filtered.length === msg.content.length) return msg\n return { ...msg, content: filtered }\n })\n\n const removedCount = originalCount - messages.length\n\n if (removedCount > 0) {\n consola.info(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool messages`)\n }\n\n return {\n payload: {\n ...payload,\n messages: allMessages,\n },\n removedCount,\n systemReminderRemovals,\n }\n}\n","/**\n * Stream accumulator for OpenAI format responses.\n * Handles accumulating ChatCompletionChunk events for history recording and tracking.\n */\n\nimport type { BaseStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\n\nimport type { ChatCompletionChunk } from \"./client\"\n\n/** Internal tool call accumulator using string array to avoid O(n²) concatenation */\ninterface ToolCallAccumulator {\n id: string\n name: string\n argumentParts: Array<string>\n}\n\n/** Stream accumulator for OpenAI format */\nexport interface OpenAIStreamAccumulator extends BaseStreamAccumulator {\n cachedTokens: number\n finishReason: string\n toolCalls: Array<{ id: string; name: string; arguments: string }>\n toolCallMap: Map<number, ToolCallAccumulator>\n}\n\nexport function createOpenAIStreamAccumulator(): OpenAIStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n cachedTokens: 0,\n finishReason: \"\",\n content: \"\",\n toolCalls: [],\n toolCallMap: new Map(),\n }\n}\n\n/** Accumulate a single parsed OpenAI chunk into the accumulator */\nexport function accumulateOpenAIStreamEvent(parsed: ChatCompletionChunk, acc: OpenAIStreamAccumulator) {\n if (parsed.model && !acc.model) acc.model = parsed.model\n\n if (parsed.usage) {\n acc.inputTokens = parsed.usage.prompt_tokens\n acc.outputTokens = parsed.usage.completion_tokens\n if (parsed.usage.prompt_tokens_details?.cached_tokens !== undefined) {\n acc.cachedTokens = parsed.usage.prompt_tokens_details.cached_tokens\n }\n }\n\n const choice = parsed.choices[0] as (typeof parsed.choices)[0] | undefined\n if (choice) {\n if (choice.delta.content) acc.content += choice.delta.content\n if (choice.delta.tool_calls) {\n for (const tc of choice.delta.tool_calls) {\n const idx = tc.index\n if (!acc.toolCallMap.has(idx)) {\n acc.toolCallMap.set(idx, {\n id: tc.id ?? \"\",\n name: tc.function?.name ?? \"\",\n argumentParts: [],\n })\n }\n const item = acc.toolCallMap.get(idx)\n if (item) {\n if (tc.id) item.id = tc.id\n if (tc.function?.name) item.name = tc.function.name\n if (tc.function?.arguments) item.argumentParts.push(tc.function.arguments)\n }\n }\n }\n if (choice.finish_reason) acc.finishReason = choice.finish_reason\n }\n}\n","import type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { SSEStreamingApi, streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { MessageContent } from \"~/lib/history\"\nimport type { Model } from \"~/lib/models/client\"\nimport type { FormatAdapter } from \"~/lib/request/pipeline\"\n\nimport {\n STREAM_ABORTED,\n StreamIdleTimeoutError,\n combineAbortSignals,\n raceIteratorNext,\n} from \"~/lib/anthropic/handlers\"\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { MAX_AUTO_TRUNCATE_RETRIES } from \"~/lib/auto-truncate\"\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport {\n autoTruncateOpenAI,\n createTruncationResponseMarkerOpenAI,\n type OpenAIAutoTruncateResult,\n} from \"~/lib/openai/auto-truncate\"\nimport { createChatCompletions } from \"~/lib/openai/client\"\nimport type {\n ChatCompletionChunk,\n ChatCompletionResponse,\n ChatCompletionsPayload,\n} from \"~/lib/openai/client\"\nimport { sanitizeOpenAIMessages } from \"~/lib/openai/sanitize\"\nimport { createOpenAIStreamAccumulator, accumulateOpenAIStreamEvent } from \"~/lib/openai/stream-accumulator\"\nimport { buildOpenAIResponseData, isNonStreaming, logPayloadSizeInfo } from \"~/lib/request\"\nimport { executeRequestPipeline } from \"~/lib/request/pipeline\"\nimport { createAutoTruncateStrategy, type TruncateResult } from \"~/lib/request/strategies/auto-truncate\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { processOpenAIMessages } from \"~/lib/system-prompt\"\nimport { tuiLogger } from \"~/lib/tui\"\nimport { isNullish } from \"~/lib/utils\"\n\nexport async function handleCompletion(c: Context) {\n const originalPayload = await c.req.json<ChatCompletionsPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n const clientModel = originalPayload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n originalPayload.model = resolvedModel\n }\n\n // Find the selected model and validate endpoint support\n const selectedModel = state.modelIndex.get(originalPayload.model)\n if (!isEndpointSupported(selectedModel, ENDPOINT.CHAT_COMPLETIONS)) {\n const msg = `Model \"${originalPayload.model}\" does not support the ${ENDPOINT.CHAT_COMPLETIONS} endpoint`\n throw new HTTPError(msg, 400, msg)\n }\n\n // System prompt collection + config-based overrides (always active)\n originalPayload.messages = await processOpenAIMessages(originalPayload.messages)\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Create request context — triggers \"created\" event → history consumer inserts entry\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"openai\", tuiLogId })\n reqCtx.setOriginalRequest({\n // Use client's original model name (before resolution/overrides)\n model: clientModel,\n messages: originalPayload.messages as unknown as Array<MessageContent>,\n stream: originalPayload.stream ?? false,\n tools: originalPayload.tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n })),\n payload: originalPayload,\n })\n\n // Update TUI tracker with model info (immediate feedback)\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: originalPayload.model,\n ...(clientModel !== originalPayload.model && { clientModel }),\n })\n }\n\n // Sanitize messages (filter orphaned tool blocks, system-reminders)\n const { payload: sanitizedPayload } = sanitizeOpenAIMessages(originalPayload)\n\n const finalPayload =\n isNullish(sanitizedPayload.max_tokens) ?\n {\n ...sanitizedPayload,\n max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens,\n }\n : sanitizedPayload\n\n if (isNullish(originalPayload.max_tokens)) {\n consola.debug(\"Set max_tokens to:\", JSON.stringify(finalPayload.max_tokens))\n }\n\n // Execute request with reactive retry pipeline\n return executeRequest({\n c,\n payload: finalPayload,\n originalPayload,\n selectedModel,\n reqCtx,\n })\n}\n\n/** Options for executeRequest */\ninterface ExecuteRequestOptions {\n c: Context\n payload: ChatCompletionsPayload\n originalPayload: ChatCompletionsPayload\n selectedModel: Model | undefined\n reqCtx: RequestContext\n}\n\n/**\n * Execute the API call with reactive retry pipeline.\n * Handles 413 and token limit errors with auto-truncation.\n */\nasync function executeRequest(opts: ExecuteRequestOptions) {\n const { c, payload, originalPayload, selectedModel, reqCtx } = opts\n\n // Build adapter and strategy for the pipeline\n const adapter: FormatAdapter<ChatCompletionsPayload> = {\n format: \"openai\",\n sanitize: (p) => sanitizeOpenAIMessages(p),\n execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p)),\n logPayloadSize: (p) => logPayloadSizeInfo(p, selectedModel),\n }\n\n const strategies = [\n createTokenRefreshStrategy<ChatCompletionsPayload>(),\n createAutoTruncateStrategy<ChatCompletionsPayload>({\n truncate: (p, model, truncOpts) =>\n autoTruncateOpenAI(p, model, truncOpts) as Promise<TruncateResult<ChatCompletionsPayload>>,\n resanitize: (p) => sanitizeOpenAIMessages(p),\n isEnabled: () => state.autoTruncate,\n label: \"Completions\",\n }),\n ]\n\n // Track truncation result for non-streaming response marker\n let truncateResult: OpenAIAutoTruncateResult | undefined\n\n try {\n const result = await executeRequestPipeline({\n adapter,\n strategies,\n payload,\n originalPayload,\n model: selectedModel,\n maxRetries: MAX_AUTO_TRUNCATE_RETRIES,\n requestContext: reqCtx,\n onRetry: (attempt, _strategyName, _newPayload, meta) => {\n // Capture truncation result for response marker\n const retryTruncateResult = meta?.truncateResult as OpenAIAutoTruncateResult | undefined\n if (retryTruncateResult) {\n truncateResult = retryTruncateResult\n }\n\n // Update tracking tags\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, { tags: [\"truncated\", `retry-${attempt + 1}`] })\n }\n },\n })\n\n const response = result.response\n\n if (isNonStreaming(response as ChatCompletionResponse | AsyncIterable<unknown>)) {\n return handleNonStreamingResponse(c, response as ChatCompletionResponse, reqCtx, truncateResult)\n }\n\n consola.debug(\"Streaming response\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n await handleStreamingResponse({\n stream,\n response: response as AsyncIterable<ServerSentEventMessage>,\n payload,\n reqCtx,\n truncateResult,\n clientAbortSignal: clientAbort.signal,\n })\n })\n } catch (error) {\n reqCtx.fail(payload.model, error)\n throw error\n }\n}\n\n// Handle non-streaming response\nfunction handleNonStreamingResponse(\n c: Context,\n originalResponse: ChatCompletionResponse,\n reqCtx: RequestContext,\n truncateResult: OpenAIAutoTruncateResult | undefined,\n) {\n // Prepend truncation marker if auto-truncate was performed (only in verbose mode)\n let response = originalResponse\n if (state.verbose && truncateResult?.wasTruncated && response.choices[0]?.message.content) {\n const marker = createTruncationResponseMarkerOpenAI(truncateResult)\n const firstChoice = response.choices[0]\n response = {\n ...response,\n choices: [\n { ...firstChoice, message: { ...firstChoice.message, content: `${marker}${firstChoice.message.content}` } },\n ...response.choices.slice(1),\n ],\n }\n }\n\n const choice = response.choices[0]\n const usage = response.usage\n\n reqCtx.complete({\n success: true,\n model: response.model,\n usage: {\n input_tokens: usage?.prompt_tokens ?? 0,\n output_tokens: usage?.completion_tokens ?? 0,\n ...(usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: usage.prompt_tokens_details.cached_tokens,\n }),\n },\n stop_reason: choice.finish_reason ?? undefined,\n content: choice.message,\n })\n\n return c.json(response)\n}\n\n/** Options for handleStreamingResponse */\ninterface StreamingOptions {\n stream: SSEStreamingApi\n response: AsyncIterable<ServerSentEventMessage>\n payload: ChatCompletionsPayload\n reqCtx: RequestContext\n truncateResult: OpenAIAutoTruncateResult | undefined\n /** Abort signal that fires when the downstream client disconnects */\n clientAbortSignal?: AbortSignal\n}\n\n// Handle streaming response\nasync function handleStreamingResponse(opts: StreamingOptions) {\n const { stream, response, payload, reqCtx, truncateResult, clientAbortSignal } = opts\n const acc = createOpenAIStreamAccumulator()\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n\n // Streaming metrics for TUI footer\n let bytesIn = 0\n let eventsIn = 0\n\n try {\n // Prepend truncation marker as first chunk if auto-truncate was performed (only in verbose mode)\n if (state.verbose && truncateResult?.wasTruncated) {\n const marker = createTruncationResponseMarkerOpenAI(truncateResult)\n const markerChunk: ChatCompletionChunk = {\n id: `truncation-marker-${Date.now()}`,\n object: \"chat.completion.chunk\",\n created: Math.floor(Date.now() / 1000),\n model: payload.model,\n choices: [\n {\n index: 0,\n delta: { content: marker },\n finish_reason: null,\n logprobs: null,\n },\n ],\n }\n await stream.writeSSE({\n data: JSON.stringify(markerChunk),\n event: \"message\",\n })\n acc.content += marker\n }\n\n const iterator = response[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbortSignal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n if (result === STREAM_ABORTED) break\n if (result.done) break\n\n const rawEvent = result.value\n\n bytesIn += rawEvent.data?.length ?? 0\n eventsIn++\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n })\n }\n\n // Parse and accumulate for history/tracking (skip [DONE] and empty data)\n if (rawEvent.data && rawEvent.data !== \"[DONE]\") {\n try {\n const chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk\n accumulateOpenAIStreamEvent(chunk, acc)\n } catch {\n // Ignore parse errors\n }\n }\n\n // Forward every event to client — proxy preserves upstream data\n await stream.writeSSE({\n data: rawEvent.data ?? \"\",\n event: rawEvent.event,\n id: rawEvent.id != null ? String(rawEvent.id) : undefined,\n retry: rawEvent.retry,\n })\n }\n\n const responseData = buildOpenAIResponseData(acc, payload.model)\n reqCtx.complete(responseData)\n } catch (error) {\n consola.error(\"[ChatCompletions] Stream error:\", error)\n reqCtx.fail(acc.model || payload.model, error)\n\n // Send error to client as final SSE event (consistent with Anthropic path)\n const errorMessage = error instanceof Error ? error.message : String(error)\n await stream.writeSSE({\n data: JSON.stringify({\n error: {\n message: errorMessage,\n type: error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"server_error\",\n },\n }),\n event: \"error\",\n })\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCompletion } from \"./handler\"\n\nexport const completionRoutes = new Hono()\n\ncompletionRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createEmbeddings = async (payload: EmbeddingRequest) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n // Normalize input to array — some API providers reject bare string input\n const normalizedPayload = {\n ...payload,\n input: typeof payload.input === \"string\" ? [payload.input] : payload.input,\n }\n\n const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(normalizedPayload),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to create embeddings\", response)\n\n return (await response.json()) as EmbeddingResponse\n}\n\nexport interface EmbeddingRequest {\n input: string | Array<string>\n model: string\n encoding_format?: \"float\" | \"base64\"\n dimensions?: number\n}\n\nexport interface Embedding {\n object: string\n embedding: Array<number>\n index: number\n}\n\nexport interface EmbeddingResponse {\n object: string\n data: Array<Embedding>\n model: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { createEmbeddings, type EmbeddingRequest } from \"~/lib/openai/embeddings\"\n\nexport const embeddingRoutes = new Hono()\n\nembeddingRoutes.post(\"/\", async (c) => {\n try {\n const payload = await c.req.json<EmbeddingRequest>()\n const response = await createEmbeddings(payload)\n\n return c.json(response)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nexport const eventLoggingRoutes = new Hono()\n\n// Anthropic SDK sends telemetry to this endpoint\n// Return 200 OK to prevent errors in the SDK\neventLoggingRoutes.post(\"/batch\", (c) => {\n return c.text(\"OK\", 200)\n})\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { checkNeedsCompactionAnthropic, countTotalInputTokens } from \"~/lib/anthropic/auto-truncate\"\nimport { hasKnownLimits } from \"~/lib/auto-truncate\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { state } from \"~/lib/state\"\nimport { tuiLogger } from \"~/lib/tui\"\nimport { type MessagesPayload } from \"~/types/api/anthropic\"\n\n/**\n * Handles token counting for Anthropic /v1/messages/count_tokens endpoint.\n *\n * Counts tokens directly on the Anthropic payload using native counting functions.\n *\n * Per Anthropic docs:\n * - Returns { input_tokens: N } where N is the total input tokens\n * - Thinking blocks from previous assistant turns don't count as input tokens\n * - The count is an estimate\n */\nexport async function handleCountTokens(c: Context) {\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n try {\n const anthropicPayload = await c.req.json<MessagesPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n anthropicPayload.model = resolveModelName(anthropicPayload.model)\n\n // Update tracker with model name\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { model: anthropicPayload.model })\n }\n\n const selectedModel = state.modelIndex.get(anthropicPayload.model)\n\n if (!selectedModel) {\n consola.warn(`[count_tokens] Model \"${anthropicPayload.model}\" not found, returning input_tokens=1`)\n return c.json({ input_tokens: 1 })\n }\n\n // Check if auto-truncate would be triggered (only for models with known limits)\n // If so, return an inflated token count to encourage Claude Code auto-compact\n if (state.autoTruncate && hasKnownLimits(selectedModel.id)) {\n const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel, {\n checkTokenLimit: true,\n })\n\n if (truncateCheck.needed) {\n const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 200000\n const inflatedTokens = Math.floor(contextWindow * 0.95)\n\n consola.info(\n `[count_tokens] Prompt too long: `\n + `${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit} limit, `\n + `returning inflated count ${inflatedTokens} to trigger client-side compaction`,\n )\n\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { inputTokens: inflatedTokens })\n }\n\n return c.json({ input_tokens: inflatedTokens })\n }\n }\n\n // Count tokens directly on Anthropic payload\n // Excludes thinking blocks from assistant messages per Anthropic spec\n const inputTokens = await countTotalInputTokens(anthropicPayload, selectedModel)\n\n consola.debug(\n `[count_tokens] ${inputTokens} tokens (native Anthropic) `\n + `(tokenizer: ${selectedModel.capabilities?.tokenizer ?? \"o200k_base\"})`,\n )\n\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { inputTokens })\n }\n\n return c.json({ input_tokens: inputTokens })\n } catch (error) {\n consola.error(\"[count_tokens] Error counting tokens:\", error)\n return c.json({ input_tokens: 1 })\n }\n}\n","import consola from \"consola\"\nimport { Hono } from \"hono\"\n\nimport type { MessagesPayload } from \"~/types/api/anthropic\"\n\nimport { handleAnthropicMessagesCompletion } from \"~/lib/anthropic/handlers\"\nimport { forwardError } from \"~/lib/error\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\n\nimport { handleCountTokens } from \"./count-tokens-handler\"\n\nexport const messageRoutes = new Hono()\n\nmessageRoutes.post(\"/\", async (c) => {\n try {\n const anthropicPayload = await c.req.json<MessagesPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n // e.g., \"haiku\" → \"claude-haiku-4.5\", \"claude-sonnet-4-20250514\" → \"claude-sonnet-4\"\n const clientModel = anthropicPayload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n anthropicPayload.model = resolvedModel\n }\n\n return await handleAnthropicMessagesCompletion(c, anthropicPayload, {\n clientModel: clientModel !== resolvedModel ? clientModel : undefined,\n })\n } catch (error) {\n return forwardError(c, error)\n }\n})\n\nmessageRoutes.post(\"/count_tokens\", async (c) => {\n try {\n return await handleCountTokens(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport type { Model } from \"~/lib/models/client\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { cacheModels } from \"~/lib/models/client\"\nimport { state } from \"~/lib/state\"\n\nexport const modelRoutes = new Hono()\n\nconst EPOCH_ISO = new Date(0).toISOString()\n\nfunction formatModel(model: Model) {\n return {\n id: model.id,\n object: \"model\" as const,\n type: \"model\" as const,\n created: 0, // No date available from source\n created_at: EPOCH_ISO, // No date available from source\n owned_by: model.vendor,\n display_name: model.name,\n capabilities: model.capabilities,\n }\n}\n\nmodelRoutes.get(\"/\", async (c) => {\n try {\n if (!state.models) {\n // This should be handled by startup logic, but as a fallback.\n await cacheModels()\n }\n\n const models = state.models?.data.map((m) => formatModel(m))\n\n return c.json({\n object: \"list\",\n data: models,\n has_more: false,\n })\n } catch (error) {\n return forwardError(c, error)\n }\n})\n\nmodelRoutes.get(\"/:model\", async (c) => {\n try {\n if (!state.models) {\n await cacheModels()\n }\n\n const modelId = c.req.param(\"model\")\n const model = state.modelIndex.get(modelId)\n\n if (!model) {\n return c.json(\n {\n error: {\n message: `The model '${modelId}' does not exist`,\n type: \"invalid_request_error\",\n param: \"model\",\n code: \"model_not_found\",\n },\n },\n 404,\n )\n }\n\n return c.json(formatModel(model))\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","/**\n * Responses API client for Copilot /responses endpoint.\n * Follows the same pattern as client.ts but targets the /responses endpoint.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport type {\n ResponsesPayload,\n ResponsesResponse,\n ResponsesInputItem,\n} from \"~/types/api/openai-responses\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\nexport type { ResponsesPayload, ResponsesResponse } from \"~/types/api/openai-responses\"\n\n/** Call Copilot /responses endpoint */\nexport const createResponses = async (\n payload: ResponsesPayload,\n): Promise<ResponsesResponse | AsyncGenerator<ServerSentEventMessage>> => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n // Check for vision content in input\n const enableVision = hasVisionContent(payload.input)\n\n // Determine if this is an agent call (has assistant or function_call items in history)\n const isAgentCall =\n Array.isArray(payload.input)\n && payload.input.some(\n (item) => item.role === \"assistant\" || item.type === \"function_call\" || item.type === \"function_call_output\",\n )\n\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/responses`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create responses\", response)\n throw await HTTPError.fromResponse(\"Failed to create responses\", response, payload.model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ResponsesResponse\n}\n\n/** Check if the input contains any image content */\nfunction hasVisionContent(input: string | Array<ResponsesInputItem>): boolean {\n if (typeof input === \"string\") return false\n return input.some(\n (item) => Array.isArray(item.content) && item.content.some((part) => \"type\" in part && part.type === \"input_image\"),\n )\n}\n","/**\n * Handler for inbound OpenAI Responses API requests.\n * Routes directly to Copilot /responses endpoint.\n * Models that do not support /responses get a 400 error.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { MessageContent } from \"~/lib/history/store\"\nimport type { FormatAdapter } from \"~/lib/request/pipeline\"\nimport type {\n ResponsesInputItem,\n ResponsesOutputItem,\n ResponsesPayload,\n ResponsesResponse,\n ResponsesStreamEvent,\n} from \"~/types/api/openai-responses\"\n\nimport {\n STREAM_ABORTED,\n StreamIdleTimeoutError,\n combineAbortSignals,\n raceIteratorNext,\n} from \"~/lib/anthropic/handlers\"\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { createResponses } from \"~/lib/openai/responses-client\"\nimport {\n createResponsesStreamAccumulator,\n accumulateResponsesStreamEvent,\n} from \"~/lib/openai/responses-stream-accumulator\"\nimport { executeRequestPipeline } from \"~/lib/request/pipeline\"\nimport { buildResponsesResponseData } from \"~/lib/request/recording\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { tuiLogger } from \"~/lib/tui\"\n\n// ============================================================================\n// Input/Output conversion helpers for history recording\n// ============================================================================\n\n/**\n * Convert Responses API input items to MessageContent format for history storage.\n * Maps Responses-specific item types to the unified MessageContent structure\n * that the history UI understands.\n */\n/** Exported for testing */\nexport function responsesInputToMessages(input: string | Array<ResponsesInputItem>): Array<MessageContent> {\n if (typeof input === \"string\") {\n return [{ role: \"user\", content: input }]\n }\n\n const messages: Array<MessageContent> = []\n for (const item of input) {\n switch (item.type) {\n case \"message\":\n case undefined: {\n // Regular message — convert content parts to Anthropic-style blocks\n const role = item.role ?? \"user\"\n let content: string | Array<unknown> | null\n\n if (typeof item.content === \"string\") {\n content = item.content\n } else if (Array.isArray(item.content)) {\n content = item.content.map((part) => {\n switch (part.type) {\n case \"input_text\":\n return { type: \"text\", text: part.text }\n case \"output_text\":\n return { type: \"text\", text: part.text }\n case \"input_image\":\n return { type: \"image\", source: { type: \"url\", url: part.image_url } }\n case \"input_file\":\n return { type: \"file\", file_id: part.file_id, filename: part.filename }\n default:\n return part\n }\n })\n } else {\n content = null\n }\n\n messages.push({ role, content })\n break\n }\n\n case \"function_call\": {\n // Function call from assistant — convert to OpenAI tool_calls format\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? item.id ?? \"\",\n type: \"function\",\n function: { name: item.name ?? \"\", arguments: item.arguments ?? \"\" },\n },\n ],\n })\n break\n }\n\n case \"function_call_output\": {\n // Function output — convert to OpenAI tool response format\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id ?? \"\",\n })\n break\n }\n\n case \"item_reference\": {\n // Item reference — store as informational marker\n messages.push({\n role: \"system\",\n content: `[item_reference: ${item.id ?? \"unknown\"}]`,\n })\n break\n }\n }\n }\n\n return messages\n}\n\n/**\n * Convert Responses API output items to a unified MessageContent for history storage.\n * Extracts text content and function calls from the output array.\n */\n/** Exported for testing */\nexport function responsesOutputToContent(output: Array<ResponsesOutputItem>): MessageContent | null {\n const textParts: Array<string> = []\n const toolCalls: Array<{ id: string; type: string; function: { name: string; arguments: string } }> = []\n\n for (const item of output) {\n if (item.type === \"message\") {\n for (const part of item.content) {\n if (part.type === \"output_text\") textParts.push(part.text)\n if (part.type === \"refusal\") textParts.push(`[Refusal: ${part.refusal}]`)\n }\n }\n if (item.type === \"function_call\") {\n toolCalls.push({\n id: item.call_id,\n type: \"function\",\n function: { name: item.name, arguments: item.arguments },\n })\n }\n }\n\n if (textParts.length === 0 && toolCalls.length === 0) return null\n\n return {\n role: \"assistant\",\n content: textParts.join(\"\") || null,\n ...(toolCalls.length > 0 && { tool_calls: toolCalls }),\n }\n}\n\n/** Handle an inbound Responses API request */\nexport async function handleResponsesCompletion(c: Context) {\n const payload = await c.req.json<ResponsesPayload>()\n\n // Resolve model name aliases\n const clientModel = payload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n payload.model = resolvedModel\n }\n\n // Validate that the model supports /responses endpoint\n const selectedModel = state.modelIndex.get(payload.model)\n if (!isEndpointSupported(selectedModel, ENDPOINT.RESPONSES)) {\n const msg = `Model \"${payload.model}\" does not support the ${ENDPOINT.RESPONSES} endpoint`\n throw new HTTPError(msg, 400, msg)\n }\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Create request context (Responses API is a distinct OpenAI-format endpoint)\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"openai-responses\", tuiLogId })\n\n // Record original request for history\n reqCtx.setOriginalRequest({\n model: clientModel,\n messages: responsesInputToMessages(payload.input),\n stream: payload.stream ?? false,\n tools: payload.tools,\n system: payload.instructions ?? undefined,\n payload,\n })\n\n // Update TUI tracker with model info\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: payload.model,\n ...(clientModel !== payload.model && { clientModel }),\n })\n }\n\n return handleDirectResponses({ c, payload, reqCtx })\n}\n\n// ============================================================================\n// Direct passthrough to /responses endpoint\n// ============================================================================\n\ninterface ResponsesHandlerOptions {\n c: Context\n payload: ResponsesPayload\n reqCtx: RequestContext\n}\n\n/** Pass through to Copilot /responses endpoint directly */\nasync function handleDirectResponses(opts: ResponsesHandlerOptions) {\n const { c, payload, reqCtx } = opts\n\n // Build pipeline adapter for Responses API\n const adapter: FormatAdapter<ResponsesPayload> = {\n format: \"openai-responses\",\n sanitize: (p) => ({ payload: p, removedCount: 0, systemReminderRemovals: 0 }),\n execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p)),\n logPayloadSize: (p) => {\n const count = typeof p.input === \"string\" ? 1 : p.input.length\n consola.debug(`Responses payload: ${count} input item(s), model: ${p.model}`)\n },\n }\n\n const strategies = [createTokenRefreshStrategy<ResponsesPayload>()]\n const selectedModel = state.modelIndex.get(payload.model)\n\n try {\n const pipelineResult = await executeRequestPipeline({\n adapter,\n strategies,\n payload,\n originalPayload: payload,\n model: selectedModel,\n maxRetries: 1,\n requestContext: reqCtx,\n })\n\n const response = pipelineResult.response\n reqCtx.addQueueWaitMs(pipelineResult.queueWaitMs)\n\n // Determine streaming vs non-streaming based on the request payload,\n // not by inspecting the response shape (isNonStreaming checks for \"choices\"\n // which only exists in Chat Completions format, not Responses format)\n if (!payload.stream) {\n // Non-streaming response — build content from output items\n const responsesResponse = response as ResponsesResponse\n const content = responsesOutputToContent(responsesResponse.output)\n\n reqCtx.complete({\n success: true,\n model: responsesResponse.model,\n usage: {\n input_tokens: responsesResponse.usage?.input_tokens ?? 0,\n output_tokens: responsesResponse.usage?.output_tokens ?? 0,\n ...(responsesResponse.usage?.input_tokens_details?.cached_tokens && {\n cache_read_input_tokens: responsesResponse.usage.input_tokens_details.cached_tokens,\n }),\n },\n stop_reason: responsesResponse.status,\n content,\n })\n return c.json(responsesResponse)\n }\n\n // Streaming response — forward Responses SSE events directly\n consola.debug(\"Streaming response (/responses)\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n const acc = createResponsesStreamAccumulator()\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n\n // Streaming metrics for TUI footer\n let bytesIn = 0\n let eventsIn = 0\n\n try {\n const iterator = (response as AsyncIterable<ServerSentEventMessage>)[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbort.signal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n if (result === STREAM_ABORTED) break\n if (result.done) break\n\n const rawEvent = result.value\n\n if (rawEvent.data && rawEvent.data !== \"[DONE]\") {\n bytesIn += rawEvent.data.length\n eventsIn++\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n })\n }\n\n try {\n const event = JSON.parse(rawEvent.data) as ResponsesStreamEvent\n accumulateResponsesStreamEvent(event, acc)\n\n // Forward the event as-is\n await stream.writeSSE({ data: rawEvent.data })\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n // Use shared recording utility for consistent response data\n const responseData = buildResponsesResponseData(acc, payload.model)\n reqCtx.complete(responseData)\n } catch (error) {\n consola.error(\"[Responses] Stream error:\", error)\n reqCtx.fail(acc.model || payload.model, error)\n\n // Send error to client as final SSE event\n const errorMessage = error instanceof Error ? error.message : String(error)\n await stream.writeSSE({\n data: JSON.stringify({\n error: {\n message: errorMessage,\n type: error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"server_error\",\n },\n }),\n })\n }\n })\n } catch (error) {\n reqCtx.fail(payload.model, error)\n throw error\n }\n}\n","/**\n * OpenAI Responses API route definition.\n * Handles POST /responses and POST /v1/responses.\n */\n\nimport { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleResponsesCompletion } from \"./handler\"\n\nexport const responsesRoutes = new Hono()\n\nresponsesRoutes.post(\"/\", async (c) => {\n try {\n return await handleResponsesCompletion(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const tokenRoute = new Hono()\n\ntokenRoute.get(\"/\", (c) => {\n try {\n return c.json({\n token: state.copilotToken,\n })\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { getCopilotUsage } from \"~/lib/token/copilot-client\"\n\nexport const usageRoute = new Hono()\n\nusageRoute.get(\"/\", async (c) => {\n try {\n const usage = await getCopilotUsage()\n return c.json(usage)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","/**\n * Centralized route registration.\n * All API routes are registered here instead of scattered in server.ts.\n */\n\nimport type { Hono } from \"hono\"\n\nimport { completionRoutes } from \"./chat-completions/route\"\nimport { embeddingRoutes } from \"./embeddings/route\"\nimport { eventLoggingRoutes } from \"./event-logging/route\"\nimport { historyRoutes } from \"./history/route\"\nimport { messageRoutes } from \"./messages/route\"\nimport { modelRoutes } from \"./models/route\"\nimport { responsesRoutes } from \"./responses/route\"\nimport { tokenRoute } from \"./token/route\"\nimport { usageRoute } from \"./usage/route\"\n\n/**\n * Register all API routes on the given Hono app.\n */\nexport function registerRoutes(app: Hono) {\n // OpenAI-compatible endpoints\n app.route(\"/chat/completions\", completionRoutes)\n app.route(\"/models\", modelRoutes)\n app.route(\"/embeddings\", embeddingRoutes)\n app.route(\"/usage\", usageRoute)\n app.route(\"/token\", tokenRoute)\n\n // OpenAI-compatible with /v1 prefix\n app.route(\"/v1/chat/completions\", completionRoutes)\n app.route(\"/v1/models\", modelRoutes)\n app.route(\"/v1/embeddings\", embeddingRoutes)\n\n // OpenAI Responses API\n app.route(\"/responses\", responsesRoutes)\n app.route(\"/v1/responses\", responsesRoutes)\n\n // Anthropic-compatible endpoints\n app.route(\"/v1/messages\", messageRoutes)\n app.route(\"/api/event_logging\", eventLoggingRoutes)\n\n // History viewer (optional, enabled with --history flag)\n app.route(\"/history\", historyRoutes)\n}\n","import consola from \"consola\"\nimport { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport { trimTrailingSlash } from \"hono/trailing-slash\"\n\nimport { applyConfigToState } from \"./lib/config/config\"\nimport { forwardError } from \"./lib/error\"\nimport { state } from \"./lib/state\"\nimport { tuiMiddleware } from \"./lib/tui\"\nimport { registerRoutes } from \"./routes\"\n\nexport const server = new Hono()\n\n// Global error handler - catches any unhandled errors from route handlers\nserver.onError((error, c) => {\n // WebSocket errors after upgrade - connection is already upgraded,\n // cannot send HTTP response; log at debug level since these are normal\n // (e.g. client disconnect)\n if (c.req.header(\"upgrade\")?.toLowerCase() === \"websocket\") {\n consola.debug(\"WebSocket error:\", error)\n return c.text(\"\", 500)\n }\n\n consola.error(`Unhandled route error in ${c.req.method} ${c.req.path}:`, error)\n return forwardError(c, error)\n})\n\n// Browser auto-requests (favicon, devtools config) — return 204 silently\n// to avoid [FAIL] 404 noise in TUI logs.\nconst browserProbePaths = new Set([\"/favicon.ico\", \"/.well-known/appspecific/com.chrome.devtools.json\"])\n\nserver.notFound((c) => {\n if (browserProbePaths.has(c.req.path)) {\n return c.body(null, 204)\n }\n return c.json({ error: \"Not Found\" }, 404)\n})\n\n// Config hot-reload: re-apply config.yaml settings before each request.\n// loadConfig() is mtime-cached — only costs one stat() syscall when config is unchanged.\nserver.use(async (_c, next) => {\n await applyConfigToState()\n await next()\n})\n\nserver.use(tuiMiddleware())\nserver.use(cors())\nserver.use(trimTrailingSlash())\n\nserver.get(\"/\", (c) => c.text(\"Server running\"))\n\n// Health check endpoint for container orchestration (Docker, Kubernetes)\nserver.get(\"/health\", (c) => {\n const healthy = Boolean(state.copilotToken && state.githubToken)\n return c.json(\n {\n status: healthy ? \"healthy\" : \"unhealthy\",\n checks: {\n copilotToken: Boolean(state.copilotToken),\n githubToken: Boolean(state.githubToken),\n models: Boolean(state.models),\n },\n },\n healthy ? 200 : 503,\n )\n})\n\n// Register all API routes\nregisterRoutes(server)\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport pc from \"picocolors\"\nimport { serve, type ServerHandler } from \"srvx\"\n\nimport type { Model } from \"./lib/models/client\"\n\nimport packageJson from \"../package.json\"\nimport { initAdaptiveRateLimiter } from \"./lib/adaptive-rate-limiter\"\nimport { loadPersistedLimits } from \"./lib/auto-truncate\"\nimport { applyConfigToState } from \"./lib/config/config\"\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { registerContextConsumers } from \"./lib/context/consumers\"\nimport { initRequestContextManager } from \"./lib/context/manager\"\nimport { cacheVSCodeVersion } from \"./lib/copilot-api\"\nimport { initHistory } from \"./lib/history\"\nimport { cacheModels } from \"./lib/models/client\"\nimport { resolveModelName } from \"./lib/models/resolver\"\nimport { initProxyFromEnv } from \"./lib/proxy\"\nimport { setServerInstance, setupShutdownHandlers, waitForShutdown } from \"./lib/shutdown\"\nimport { state } from \"./lib/state\"\nimport { initTokenManagers } from \"./lib/token\"\nimport { initTuiLogger } from \"./lib/tui\"\nimport { initHistoryWebSocket } from \"./routes/history/route\"\nimport { server } from \"./server\"\n\n/** Format limit values as \"Xk\" or \"?\" if not available */\nfunction formatLimit(value?: number): string {\n return value ? `${Math.round(value / 1000)}k` : \"?\"\n}\n\n/**\n * Format a model as 3 lines: main info, features, and supported endpoints.\n *\n * Example output:\n * - claude-opus-4.6-1m Anthropic ctx:1000k prp: 936k out: 64k\n * features: adaptive-thinking, thinking, streaming, vision, tool-calls\n * endpoints: messages, completions\n */\nfunction formatModelInfo(model: Model): string {\n const limits = model.capabilities?.limits\n const supports = model.capabilities?.supports\n\n const contextK = formatLimit(limits?.max_context_window_tokens)\n const promptK = formatLimit(limits?.max_prompt_tokens)\n const outputK = formatLimit(limits?.max_output_tokens)\n\n // Main line: model name + vendor + token limits\n const modelName = model.id.length > 28 ? `${model.id.slice(0, 25)}...` : model.id.padEnd(28)\n const vendor = model.vendor.padEnd(13)\n const mainLine\n = ` - ${modelName} ${vendor} `\n + `ctx:${contextK.padStart(5)} `\n + `prp:${promptK.padStart(5)} `\n + `out:${outputK.padStart(5)}`\n\n // Features line: boolean capabilities + inferred features\n const features = [\n ...Object.entries(supports ?? {})\n .filter(([, value]) => value === true)\n .map(([key]) => key.replaceAll(\"_\", \"-\")),\n supports?.max_thinking_budget && \"thinking\",\n model.capabilities?.type === \"embeddings\" && \"embeddings\",\n model.preview && \"preview\",\n ]\n .filter(Boolean)\n .join(\", \")\n const featLine = features ? pc.dim(` features: ${features}`) : \"\"\n\n // Endpoints line: short names from supported_endpoints\n const endpoints = formatEndpoints(model.supported_endpoints)\n const endpLine = pc.dim(` endpoints: ${endpoints}`)\n\n return [mainLine, featLine, endpLine].filter(Boolean).join(\"\\n\")\n}\n\n/** Format endpoint paths as short display names */\nfunction formatEndpoints(endpoints?: Array<string>): string {\n if (!endpoints || endpoints.length === 0) return \"(legacy)\"\n return endpoints\n .map((e) => e.replace(/^\\/(v1\\/|chat\\/)?/, \"\"))\n .join(\", \")\n}\n\n/** Parse an integer from a string, returning a default if the result is NaN. */\nfunction parseIntOrDefault(value: string, defaultValue: number): number {\n const parsed = Number.parseInt(value, 10)\n return Number.isFinite(parsed) ? parsed : defaultValue\n}\n\ninterface RunServerOptions {\n port: number\n host?: string\n verbose: boolean\n accountType: \"individual\" | \"business\" | \"enterprise\"\n // Adaptive rate limiting (disabled if rateLimit is false)\n rateLimit: boolean\n githubToken?: string\n showGitHubToken: boolean\n httpProxyFromEnv: boolean\n autoTruncate: boolean\n}\n\nexport async function runServer(options: RunServerOptions): Promise<void> {\n // ===========================================================================\n // Phase 1: Logging and Verbose Mode\n // ===========================================================================\n if (options.verbose) {\n consola.level = 5\n state.verbose = true\n }\n\n // ===========================================================================\n // Phase 2: Version and Configuration Display\n // ===========================================================================\n consola.info(`copilot-api v${packageJson.version}`)\n\n if (options.httpProxyFromEnv) {\n initProxyFromEnv()\n }\n\n // Set global state from CLI options\n state.accountType = options.accountType\n state.showGitHubToken = options.showGitHubToken\n state.autoTruncate = options.autoTruncate\n\n // ===========================================================================\n // Phase 2.5: Load config.yaml and apply runtime settings\n // ===========================================================================\n // ensurePaths must run first so the config directory exists\n await ensurePaths()\n\n const config = await applyConfigToState()\n\n // Log configuration status for all features\n // Source labels: --flag for CLI options, [key] for config.yaml options\n const configLines: Array<string> = []\n const on = (source: string, label: string, detail?: string) =>\n configLines.push(` ${pc.dim(source)} ${label}: ON${detail ? ` ${pc.dim(`(${detail})`)}` : \"\"}`)\n const off = (source: string, label: string) => configLines.push(pc.dim(` ${source} ${label}: OFF`))\n const toggle = (flag: boolean | undefined, source: string, label: string, detail?: string) =>\n flag ? on(source, label, detail) : off(source, label)\n\n toggle(options.verbose, \"--verbose\", \"Verbose logging\")\n configLines.push(` ${pc.dim(\"--account-type\")} Account type: ${options.accountType}`)\n\n // Rate limiter: merge CLI flag (enable/disable) with config.yaml sub-parameters\n const rlConfig = config.rate_limiter\n const rlRetryInterval = rlConfig?.retry_interval ?? 10\n const rlRequestInterval = rlConfig?.request_interval ?? 10\n const rlRecoveryTimeout = rlConfig?.recovery_timeout ?? 10\n const rlConsecutiveSuccesses = rlConfig?.consecutive_successes ?? 5\n\n if (options.rateLimit) {\n on(\n \"--rate-limit\",\n \"Rate limiter\",\n `retry=${rlRetryInterval}s interval=${rlRequestInterval}s recovery=${rlRecoveryTimeout}m successes=${rlConsecutiveSuccesses}`,\n )\n } else {\n off(\"--rate-limit\", \"Rate limiter\")\n }\n\n if (options.autoTruncate) {\n const detail = state.compressToolResultsBeforeTruncate ? \"reactive, compress\" : \"reactive\"\n on(\"--auto-truncate\", \"Auto-truncate\", detail)\n } else {\n off(\"--auto-truncate\", \"Auto-truncate\")\n }\n\n if (state.compressToolResultsBeforeTruncate && !options.autoTruncate) {\n // Only show separately if auto-truncate is off but compress is on (unusual)\n on(\"[compress_tool_results_before_truncate]\", \"Compress tool results\")\n }\n toggle(state.rewriteAnthropicTools, \"[anthropic.rewrite_tools]\", \"Rewrite Anthropic tools\")\n toggle(options.httpProxyFromEnv, \"--http-proxy-from-env\", \"HTTP proxy from env\")\n toggle(options.showGitHubToken, \"--show-github-token\", \"Show GitHub token\")\n const overrideEntries = Object.entries(state.modelOverrides)\n if (overrideEntries.length > 0) {\n on(\"[model_overrides]\", \"Model overrides\")\n } else {\n off(\"[model_overrides]\", \"Model overrides\")\n }\n if (state.dedupToolCalls) {\n on(\"[anthropic.dedup_tool_calls]\", \"Dedup tool calls\", `mode: ${state.dedupToolCalls}`)\n } else {\n off(\"[anthropic.dedup_tool_calls]\", \"Dedup tool calls\")\n }\n toggle(state.truncateReadToolResult, \"[anthropic.truncate_read_tool_result]\", \"Truncate Read tool result\")\n if (state.rewriteSystemReminders === true) {\n on(\"[anthropic.rewrite_system_reminders]\", \"Rewrite system reminders\", \"remove all\")\n } else if (state.rewriteSystemReminders === false) {\n off(\"[anthropic.rewrite_system_reminders]\", \"Rewrite system reminders\")\n } else {\n on(\n \"[anthropic.rewrite_system_reminders]\",\n \"Rewrite system reminders\",\n `${state.rewriteSystemReminders.length} rules`,\n )\n }\n\n // Show timeout settings (always show since streamIdleTimeout defaults to 300)\n {\n const parts: Array<string> = []\n if (state.fetchTimeout > 0) parts.push(`fetch=${state.fetchTimeout}s`)\n parts.push(`stream-idle=${state.streamIdleTimeout}s`)\n if (state.staleRequestMaxAge > 0) parts.push(`stale-reaper=${state.staleRequestMaxAge}s`)\n on(\"[timeouts]\", \"Timeouts\", parts.join(\", \"))\n }\n\n const historyLimitText = state.historyLimit === 0 ? \"unlimited\" : `max=${state.historyLimit}`\n on(\"[history_limit]\", \"History\", historyLimitText)\n\n consola.info(`Configuration:\\n${configLines.join(\"\\n\")}`)\n\n // ===========================================================================\n // Phase 3: Initialize Internal Services (rate limiter, history)\n // ===========================================================================\n if (options.rateLimit) {\n initAdaptiveRateLimiter({\n baseRetryIntervalSeconds: rlRetryInterval,\n requestIntervalSeconds: rlRequestInterval,\n recoveryTimeoutMinutes: rlRecoveryTimeout,\n consecutiveSuccessesForRecovery: rlConsecutiveSuccesses,\n })\n }\n\n initHistory(true, state.historyLimit)\n\n // Initialize request context manager and register event consumers\n // Must be after initHistory so history store is ready to receive events\n const contextManager = initRequestContextManager()\n registerContextConsumers(contextManager)\n\n // Start stale request reaper (periodic cleanup of stuck active contexts)\n contextManager.startReaper()\n\n // Initialize TUI request tracking (renderer was created in main.ts via initConsolaReporter)\n initTuiLogger()\n\n // ===========================================================================\n // Phase 4: External Dependencies (network)\n // ===========================================================================\n // cacheVSCodeVersion is independent network call\n await cacheVSCodeVersion()\n\n // Initialize token management and authenticate\n await initTokenManagers({ cliToken: options.githubToken })\n\n // Fetch available models from Copilot API\n try {\n await cacheModels()\n } catch (error) {\n consola.warn(\"Failed to fetch models from Copilot API:\", error instanceof Error ? error.message : error)\n }\n\n consola.info(`Available models:\\n${state.models?.data.map((m) => formatModelInfo(m)).join(\"\\n\")}`)\n\n // Load previously learned auto-truncate limits (calibration + token limits)\n await loadPersistedLimits()\n\n // Show resolved model overrides after models are fetched\n const availableIds = state.models?.data.map((m) => m.id) ?? []\n const overrideLines = Object.entries(state.modelOverrides)\n .map(([from, to]) => {\n const resolved = resolveModelName(from)\n const colorize = (name: string) => (availableIds.includes(name) ? name : pc.red(name))\n if (resolved !== to) {\n return ` - ${from} → ${to} ${pc.dim(`(→ ${colorize(resolved)})`)}`\n }\n return ` - ${from} → ${colorize(to)}`\n })\n .join(\"\\n\")\n if (overrideLines) {\n consola.info(`Model overrides:\\n${overrideLines}`)\n }\n\n // ===========================================================================\n // Phase 5: Start Server\n // ===========================================================================\n const displayHost = options.host ?? \"localhost\"\n const serverUrl = `http://${displayHost}:${options.port}`\n\n // Bun + srvx workaround: srvx's bun adapter wraps the fetch handler and\n // drops the `server` parameter that Bun.serve normally passes as the second\n // arg to fetch. hono/bun's upgradeWebSocket needs `c.env.server` to call\n // server.upgrade(). srvx stores the server on `request.runtime.bun.server`,\n // so we forward it into Hono's env via middleware.\n // Must be registered BEFORE any WebSocket routes so Hono dispatches it first.\n if (typeof globalThis.Bun !== \"undefined\") {\n server.use(\"*\", async (c, next) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const runtime = (c.req.raw as any).runtime as { bun?: { server?: unknown } } | undefined\n if (runtime?.bun?.server) {\n c.env = { server: runtime.bun.server }\n }\n await next()\n })\n }\n\n // Initialize history WebSocket support (registers /history/ws route)\n // Must be called after the Bun server injection middleware above, and before\n // the srvx serve() call so routes are ready when the server starts.\n const injectWebSocket = await initHistoryWebSocket(server)\n\n consola.box(\n `Web UI:\\n🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage\\n📜 History UI: ${serverUrl}/history`,\n )\n\n // Import hono/bun websocket handler for Bun's WebSocket support.\n // Bun.serve() requires an explicit `websocket` handler object alongside `fetch`\n // for WebSocket upgrades to work. Without this, server.upgrade() in\n // hono/bun's upgradeWebSocket middleware silently fails.\n const bunWebSocket = typeof globalThis.Bun !== \"undefined\" ? (await import(\"hono/bun\")).websocket : undefined\n\n let serverInstance\n try {\n serverInstance = serve({\n fetch: server.fetch as ServerHandler,\n port: options.port,\n hostname: options.host,\n reusePort: true,\n // Disable srvx's built-in graceful shutdown — we have our own\n // multi-phase shutdown handler (see lib/shutdown.ts) that provides\n // request draining, abort signaling, and WebSocket cleanup.\n gracefulShutdown: false,\n bun: {\n // Default idleTimeout is 10s, too short for LLM streaming responses\n idleTimeout: 255, // seconds (Bun max)\n ...(bunWebSocket && { websocket: bunWebSocket }),\n },\n })\n } catch (error) {\n consola.error(`Failed to start server on port ${options.port}. Is the port already in use?`, error)\n process.exit(1)\n }\n\n // Store server instance and register signal handlers for graceful shutdown.\n // Order matters: setServerInstance must be called before setupShutdownHandlers\n // so the handler has access to the server instance when closing.\n setServerInstance(serverInstance)\n setupShutdownHandlers()\n\n // Inject WebSocket upgrade handler into Node.js HTTP server (no-op under Bun)\n if (injectWebSocket) {\n const nodeServer = serverInstance.node?.server\n if (nodeServer && \"on\" in nodeServer) {\n injectWebSocket(nodeServer as import(\"node:http\").Server)\n }\n }\n\n // Block until a shutdown signal (SIGINT/SIGTERM) is received.\n // This prevents runMain() from returning, which would trigger\n // process.exit(0) in main.ts (needed for one-shot commands).\n await waitForShutdown()\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the Copilot API server\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port to listen on\",\n },\n host: {\n alias: \"H\",\n type: \"string\",\n description: \"Host/interface to bind to (e.g., 127.0.0.1 for localhost only, 0.0.0.0 for all interfaces)\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n \"rate-limit\": {\n type: \"boolean\",\n default: true,\n description: \"Adaptive rate limiting (disable with --no-rate-limit)\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description: \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"show-github-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token in logs (use --verbose for Copilot token refresh logs)\",\n },\n \"http-proxy-from-env\": {\n type: \"boolean\",\n default: true,\n description: \"Use HTTP proxy from environment variables (disable with --no-http-proxy-from-env)\",\n },\n \"auto-truncate\": {\n type: \"boolean\",\n default: true,\n description:\n \"Reactive auto-truncate: retries with truncated payload on limit errors (disable with --no-auto-truncate)\",\n },\n },\n run({ args }) {\n // Check for unknown arguments\n // Known args include both kebab-case (as defined) and camelCase (citty auto-converts)\n const knownArgs = new Set([\n \"_\",\n // port\n \"port\",\n \"p\",\n // host\n \"host\",\n \"H\",\n // verbose\n \"verbose\",\n \"v\",\n // account-type\n \"account-type\",\n \"accountType\",\n \"a\",\n // rate-limit (citty handles --no-rate-limit via built-in negation)\n \"rate-limit\",\n \"rateLimit\",\n // github-token\n \"github-token\",\n \"githubToken\",\n \"g\",\n // show-github-token\n \"show-github-token\",\n \"showGithubToken\",\n // http-proxy-from-env (citty handles --no-http-proxy-from-env via built-in negation)\n \"http-proxy-from-env\",\n \"httpProxyFromEnv\",\n // auto-truncate (citty handles --no-auto-truncate via built-in negation)\n \"auto-truncate\",\n \"autoTruncate\",\n ])\n const unknownArgs = Object.keys(args).filter((key) => !knownArgs.has(key))\n if (unknownArgs.length > 0) {\n consola.warn(`Unknown argument(s): ${unknownArgs.map((a) => `--${a}`).join(\", \")}`)\n }\n\n return runServer({\n port: parseIntOrDefault(args.port, 4141),\n host: args.host,\n verbose: args.verbose,\n accountType: args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\",\n rateLimit: args[\"rate-limit\"],\n githubToken: args[\"github-token\"],\n showGitHubToken: args[\"show-github-token\"],\n httpProxyFromEnv: args[\"http-proxy-from-env\"],\n autoTruncate: args[\"auto-truncate\"],\n })\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand, runMain } from \"citty\"\nimport consola from \"consola\"\n\nimport { auth } from \"./auth\"\nimport { checkUsage } from \"./check-usage\"\nimport { debug } from \"./debug\"\nimport { initConsolaReporter } from \"./lib/tui\"\nimport { listClaudeCode } from \"./list-claude-code\"\nimport { logout } from \"./logout\"\nimport { setupClaudeCode } from \"./setup-claude-code\"\nimport { start } from \"./start\"\n\n// Initialize console reporter before any logging\ninitConsolaReporter()\n\n// Global error handlers - catch errors from timers, callbacks, etc.\n// that would otherwise cause a silent process exit\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nprocess.on(\"unhandledRejection\", (reason) => {\n consola.error(\"Unhandled rejection:\", reason)\n process.exit(1)\n})\n\nconst main = defineCommand({\n meta: {\n name: \"copilot-api\",\n description: \"A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools.\",\n },\n subCommands: {\n auth,\n logout,\n start,\n \"check-usage\": checkUsage,\n debug,\n \"list-claude-code\": listClaudeCode,\n \"setup-claude-code\": setupClaudeCode,\n },\n})\n\nawait runMain(main)\n\n// When runMain() returns, the command has finished.\n// The `start` subcommand keeps the event loop alive (HTTP server),\n// so this line only executes for one-shot commands (debug, auth, etc.).\n// Explicit exit is needed because `bun run --watch` keeps the process alive otherwise.\nprocess.exit(0)\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.mjs","names":["DEFAULT_CONFIG","fsPromises","path","fs","calculateTokenLimit","estimateMessageTokens","smartCompressToolResults","findOptimalPreserveIndex","generateRemovedMessagesSummary","addCompressionNotice","createTruncationSystemContext","createTruncationMarker","createTruncationMarker","createTruncationMarker","packageJson.version"],"sources":["../src/lib/config/paths.ts","../src/lib/state.ts","../src/lib/copilot-api.ts","../src/lib/sanitize-system-reminder.ts","../src/lib/auto-truncate/index.ts","../src/lib/error.ts","../src/lib/token/copilot-client.ts","../src/lib/token/copilot-token-manager.ts","../src/lib/utils.ts","../src/lib/token/github-client.ts","../src/lib/token/providers/base.ts","../src/lib/token/providers/cli.ts","../src/lib/token/providers/file.ts","../src/lib/token/providers/device-auth.ts","../src/lib/token/providers/env.ts","../src/lib/token/github-token-manager.ts","../src/lib/token/index.ts","../src/auth.ts","../src/check-usage.ts","../src/lib/models/client.ts","../src/debug.ts","../src/lib/adaptive-rate-limiter.ts","../src/lib/models/resolver.ts","../src/lib/context/request.ts","../src/lib/context/manager.ts","../src/lib/history/ws.ts","../src/lib/history/store.ts","../src/lib/shutdown.ts","../src/lib/tui/tracker.ts","../src/lib/tui/middleware.ts","../src/lib/tui/format.ts","../src/lib/tui/console-renderer.ts","../src/lib/tui/index.ts","../src/list-claude-code.ts","../src/logout.ts","../src/setup-claude-code.ts","../package.json","../src/lib/config/config.ts","../src/lib/context/error-persistence.ts","../src/lib/context/consumers.ts","../src/lib/models/endpoint.ts","../src/lib/proxy.ts","../src/routes/history/api.ts","../src/routes/history/assets.ts","../src/routes/history/route.ts","../src/lib/openai/responses-conversion.ts","../src/lib/openai/responses-stream-accumulator.ts","../src/lib/request/pipeline.ts","../src/lib/request/response.ts","../src/lib/request/recording.ts","../src/lib/stream.ts","../src/lib/system-prompt.ts","../src/lib/fetch-utils.ts","../src/lib/openai/responses-client.ts","../src/lib/request/strategies/network-retry.ts","../src/lib/request/strategies/token-refresh.ts","../src/routes/responses/pipeline.ts","../src/routes/responses/ws.ts","../src/lib/models/tokenizer.ts","../src/lib/openai/orphan-filter.ts","../src/lib/openai/auto-truncate.ts","../src/lib/openai/client.ts","../src/lib/openai/sanitize.ts","../src/lib/openai/stream-accumulator.ts","../src/lib/request/payload.ts","../src/lib/request/strategies/auto-truncate.ts","../src/lib/request/truncation.ts","../src/routes/chat-completions/handler.ts","../src/routes/chat-completions/route.ts","../src/lib/openai/embeddings.ts","../src/routes/embeddings/route.ts","../src/routes/event-logging/route.ts","../src/types/api/anthropic.ts","../src/lib/anthropic/sanitize.ts","../src/lib/anthropic/auto-truncate.ts","../src/routes/messages/count-tokens-handler.ts","../src/lib/anthropic/features.ts","../src/lib/anthropic/message-tools.ts","../src/lib/anthropic/client.ts","../src/lib/anthropic/stream-accumulator.ts","../src/lib/anthropic/handlers.ts","../src/lib/anthropic/message-mapping.ts","../src/lib/repetition-detector.ts","../src/lib/request/strategies/deferred-tool-retry.ts","../src/routes/messages/handler.ts","../src/routes/messages/route.ts","../src/routes/models/route.ts","../src/routes/responses/handler.ts","../src/routes/responses/route.ts","../src/routes/token/route.ts","../src/routes/usage/route.ts","../src/routes/index.ts","../src/server.ts","../src/start.ts","../src/main.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst APP_DIR = path.join(os.homedir(), \".local\", \"share\", \"copilot-api\")\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, \"github_token\")\n\nexport const PATHS = {\n APP_DIR,\n GITHUB_TOKEN_PATH,\n CONFIG_YAML: path.join(APP_DIR, \"config.yaml\"),\n LEARNED_LIMITS: path.join(APP_DIR, \"learned-limits.json\"),\n ERROR_DIR: path.join(APP_DIR, \"errmsgs\"),\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n // File exists, ensure it has secure permissions (owner read/write only)\n const stats = await fs.stat(filePath)\n const currentMode = stats.mode & 0o777\n if (currentMode !== 0o600) {\n await fs.chmod(filePath, 0o600)\n }\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n","import type { Model, ModelsResponse } from \"~/lib/models/client\"\n\nimport type { AdaptiveRateLimiterConfig } from \"./adaptive-rate-limiter\"\nimport type { CopilotTokenInfo, TokenInfo } from \"./token/types\"\n\n/** A compiled rewrite rule (regex pre-compiled from config string) */\nexport interface CompiledRewriteRule {\n /** Pattern to match (regex in regex mode, string in line mode) */\n from: RegExp | string\n /** Replacement string (supports $0, $1, etc. in regex mode) */\n to: string\n /** Match method: \"regex\" (default) or \"line\" */\n method?: \"regex\" | \"line\"\n /** Compiled regex for model name filtering. undefined = apply to all models. */\n modelPattern?: RegExp\n}\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n /** Token metadata (new token system) */\n tokenInfo?: TokenInfo\n copilotTokenInfo?: CopilotTokenInfo\n\n accountType: \"individual\" | \"business\" | \"enterprise\"\n models?: ModelsResponse\n /** O(1) lookup index: model ID → Model object. Rebuilt on cacheModels(). */\n modelIndex: Map<string, Model>\n /** O(1) membership check: set of available model IDs. Rebuilt on cacheModels(). */\n modelIds: Set<string>\n vsCodeVersion?: string\n\n /** Show GitHub token in logs */\n showGitHubToken: boolean\n verbose: boolean\n\n /** Adaptive rate limiting configuration */\n adaptiveRateLimitConfig?: Partial<AdaptiveRateLimiterConfig>\n\n /**\n * Auto-truncate: reactively truncate on limit errors and pre-check for known limits.\n * Enabled by default; disable with --no-auto-truncate.\n */\n autoTruncate: boolean\n\n /**\n * Compress old tool results before truncating messages.\n * When enabled, large tool_result content is compressed to reduce context size.\n */\n compressToolResultsBeforeTruncate: boolean\n\n /** Convert Anthropic server-side tool declarations to custom tool format */\n convertServerToolsToCustom: boolean\n\n /**\n * Model name overrides: request model → target model.\n *\n * Override values can be full model names or short aliases (opus, sonnet, haiku).\n * If the target is not in available models, it's resolved as an alias.\n * Defaults to DEFAULT_MODEL_OVERRIDES; config.yaml `model.model_overrides` replaces entirely.\n */\n modelOverrides: Record<string, string>\n\n /**\n * Deduplicate repeated tool calls: remove duplicate tool_use/tool_result pairs,\n * keeping only the last occurrence of each matching combination.\n *\n * - `false` — disabled (default)\n * - `\"input\"` — match by (tool_name, input); different results are still deduped\n * - `\"result\"` — match by (tool_name, input, result); only dedup when result is identical\n */\n dedupToolCalls: false | \"input\" | \"result\"\n\n /**\n * Rewrite `<system-reminder>` tags in messages.\n *\n * - `false` — disabled, keep all tags unchanged (default)\n * - `true` — remove ALL system-reminder tags\n * - `Array<CompiledRewriteRule>` — rewrite rules evaluated top-down, first match wins:\n * - If replacement produces the original content → keep tag unchanged\n * - If replacement produces an empty string → remove the tag\n * - Otherwise → replace tag content with the result\n */\n rewriteSystemReminders: boolean | Array<CompiledRewriteRule>\n\n /**\n * Strip injected `<system-reminder>` tags from Read tool results.\n * Reduces context bloat from repeated system reminders in file content.\n * Disabled by default; enable with --truncate-read-tool-result.\n */\n truncateReadToolResult: boolean\n\n /** Pre-compiled system prompt override rules from config.yaml */\n systemPromptOverrides: Array<CompiledRewriteRule>\n\n /**\n * Maximum number of history entries to keep in memory.\n * 0 = unlimited. Default: 200.\n */\n historyLimit: number\n\n /**\n * Fetch timeout in seconds.\n * Time from request start to receiving HTTP response headers.\n * Applies to both streaming and non-streaming requests.\n * 0 = no timeout (rely on upstream gateway timeout).\n */\n fetchTimeout: number\n\n /**\n * Stream idle timeout in seconds.\n * Maximum time to wait between consecutive SSE events during streaming.\n * Aborts the stream if no event arrives within this window.\n * Applies to all streaming paths (Anthropic, Chat Completions, Responses).\n * 0 = no idle timeout. Default: 300.\n */\n streamIdleTimeout: number\n\n /**\n * Shutdown Phase 2 timeout in seconds.\n * Wait for in-flight requests to complete naturally before sending abort signal.\n * Default: 60.\n */\n shutdownGracefulWait: number\n\n /**\n * Shutdown Phase 3 timeout in seconds.\n * After abort signal, wait for handlers to wrap up before force-closing.\n * Default: 120.\n */\n shutdownAbortWait: number\n\n /**\n * Maximum age of an active request before the stale reaper forces it to fail (seconds).\n * Requests exceeding this age are assumed stuck and cleaned up.\n * 0 = disabled. Default: 600 (10 minutes).\n */\n staleRequestMaxAge: number\n}\n\n/**\n * Rebuild model lookup indexes from state.models.\n * Called by cacheModels() in production; call directly in tests after setting state.models.\n */\nexport function rebuildModelIndex(): void {\n const data = state.models?.data ?? []\n state.modelIndex = new Map(data.map((m) => [m.id, m]))\n state.modelIds = new Set(data.map((m) => m.id))\n}\nexport const DEFAULT_MODEL_OVERRIDES: Record<string, string> = {\n opus: \"claude-opus-4.6\",\n sonnet: \"claude-sonnet-4.6\",\n haiku: \"claude-haiku-4.5\",\n}\n\nexport const state: State = {\n accountType: \"individual\",\n modelIndex: new Map(),\n modelIds: new Set(),\n showGitHubToken: false,\n verbose: false,\n autoTruncate: true,\n compressToolResultsBeforeTruncate: true,\n convertServerToolsToCustom: true,\n modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },\n dedupToolCalls: false,\n rewriteSystemReminders: false,\n truncateReadToolResult: false,\n systemPromptOverrides: [],\n historyLimit: 200,\n fetchTimeout: 60,\n streamIdleTimeout: 300,\n shutdownGracefulWait: 60,\n shutdownAbortWait: 120,\n staleRequestMaxAge: 600,\n}\n","import consola from \"consola\"\nimport { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nimport { state } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.38.0\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\n/** Copilot Chat API version (for chat/completions requests) */\nconst COPILOT_API_VERSION = \"2025-05-01\"\n\n/** Copilot internal API version (for token & usage endpoints) */\nexport const COPILOT_INTERNAL_API_VERSION = \"2025-04-01\"\n\n/** GitHub public API version (for /user, repos, etc.) */\nconst GITHUB_API_VERSION = \"2022-11-28\"\n\n/**\n * Session-level interaction ID.\n * Used to correlate all requests within a single server session.\n * Unlike x-request-id (per-request UUID), this stays constant for the server lifetime.\n */\nconst INTERACTION_ID = randomUUID()\n\nexport const copilotBaseUrl = (state: State) =>\n state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": COPILOT_API_VERSION,\n \"x-request-id\": randomUUID(),\n \"X-Interaction-Id\": INTERACTION_ID,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": GITHUB_API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n\n// ============================================================================\n// VSCode version detection\n// ============================================================================\n\n/** Fallback VSCode version when GitHub API is unavailable */\nconst VSCODE_VERSION_FALLBACK = \"1.104.3\"\n\n/** GitHub API endpoint for latest VSCode release */\nconst VSCODE_RELEASE_URL = \"https://api.github.com/repos/microsoft/vscode/releases/latest\"\n\n/** GitHub release response shape */\ninterface GitHubRelease {\n tag_name: string\n}\n\n/** Fetch the latest VSCode version and cache in global state */\nexport async function cacheVSCodeVersion(): Promise<void> {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n consola.info(`Using VSCode version: ${response}`)\n}\n\n/** Fetch the latest VSCode version from GitHub releases, falling back to a hardcoded version */\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(VSCODE_RELEASE_URL, {\n signal: controller.signal,\n headers: {\n Accept: \"application/vnd.github.v3+json\",\n \"User-Agent\": \"copilot-api\",\n },\n })\n\n if (!response.ok) {\n return VSCODE_VERSION_FALLBACK\n }\n\n const release = (await response.json()) as GitHubRelease\n // tag_name is in format \"1.107.1\"\n const version = release.tag_name\n if (version && /^\\d+\\.\\d+\\.\\d+$/.test(version)) {\n return version\n }\n\n return VSCODE_VERSION_FALLBACK\n } catch {\n return VSCODE_VERSION_FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n","/**\n * System-reminder tag detection, rewriting, and removal.\n *\n * Claude Code injects `<system-reminder>` tags into tool results and user\n * messages. Each tag always occupies its own line:\n * \\n<system-reminder>\\n...content...\\n</system-reminder>\n *\n * This module:\n * - Defines all known system-reminder content types\n * - Provides configurable rewriting (transform, keep, or remove tags)\n * - Processes tags at the start/end of text content\n *\n * Rewriting is controlled by `state.rewriteSystemReminders`:\n * - `false` — keep all tags unchanged (default)\n * - `true` — remove all tags\n * - `Array<CompiledRewriteRule>` — rewrite rules evaluated top-down,\n * first match wins. If replacement produces the original content, tag is\n * kept unchanged. If replacement produces an empty string, tag is removed.\n * Otherwise, tag content is replaced with the result.\n */\n\nimport { state } from \"~/lib/state\"\n\n// ============================================================================\n// Tag Constants\n// ============================================================================\n\n/** Opening tag — always appears on its own line */\nexport const OPEN_TAG = \"<system-reminder>\"\n\n/** Closing tag — always appears on its own line */\nexport const CLOSE_TAG = \"</system-reminder>\"\n\n// ============================================================================\n// Tag Parsing Types\n// ============================================================================\n\n/** A parsed system-reminder tag found at a text boundary. */\nexport interface ParsedSystemReminderTag {\n /** The inner content between `<system-reminder>` and `</system-reminder>` */\n content: string\n /** Start position of the tag in the original text (the `\\n` before `<system-reminder>`) */\n tagStart: number\n /** End position of the tag (exclusive), i.e. the range is [tagStart, tagEnd) */\n tagEnd: number\n}\n\n/**\n * Extract trailing `<system-reminder>` tags from text.\n *\n * Scans backwards from the end, collecting each tag that sits on its own\n * lines at the text boundary. Returns them outermost-first and the position\n * where main (non-tag) content ends.\n *\n * Used by both:\n * - `removeSystemReminderTags` (filter by content, then rebuild)\n * - `compressToolResultContent` (extract all, generate summaries)\n */\nexport function extractTrailingSystemReminderTags(text: string): {\n mainContentEnd: number\n tags: Array<ParsedSystemReminderTag>\n} {\n const tags: Array<ParsedSystemReminderTag> = []\n let scanEnd = text.length\n\n while (true) {\n const currentTagEnd = scanEnd\n\n // Skip trailing whitespace/newlines (charCode: \\n=10, space=32, \\t=9, \\r=13)\n let end = scanEnd\n while (end > 0) {\n const c = text.codePointAt(end - 1)\n if (c !== 10 && c !== 32 && c !== 9 && c !== 13) break\n end--\n }\n\n // Must end with </system-reminder>\n if (end < CLOSE_TAG.length) break\n if (text.slice(end - CLOSE_TAG.length, end) !== CLOSE_TAG) break\n\n const closeTagStart = end - CLOSE_TAG.length\n\n // </system-reminder> must be at line start (preceded by \\n)\n if (closeTagStart === 0 || text[closeTagStart - 1] !== \"\\n\") break\n\n // Find matching \\n<system-reminder>\\n before it\n const openSearch = \"\\n\" + OPEN_TAG + \"\\n\"\n const openPos = text.lastIndexOf(openSearch, closeTagStart)\n if (openPos === -1) break\n\n // Extract inner content\n const innerStart = openPos + openSearch.length\n const innerEnd = closeTagStart - 1 // the \\n before </system-reminder>\n if (innerStart > innerEnd) break\n\n const content = text.slice(innerStart, innerEnd)\n tags.push({ content, tagStart: openPos, tagEnd: currentTagEnd })\n\n scanEnd = openPos\n }\n\n return { mainContentEnd: scanEnd, tags }\n}\n\n/**\n * Extract leading `<system-reminder>` tags from text.\n *\n * Scans forward from the start, collecting each tag that begins at the text\n * boundary (possibly preceded by whitespace). Returns tags in order and the\n * position where main (non-tag) content starts.\n *\n * Leading tags use the format:\n * [whitespace]<system-reminder>\\n...content...\\n</system-reminder>[\\n|EOF]\n *\n * Note: The first tag may start without a preceding `\\n` (beginning of text).\n */\nexport function extractLeadingSystemReminderTags(text: string): {\n mainContentStart: number\n tags: Array<ParsedSystemReminderTag>\n} {\n const tags: Array<ParsedSystemReminderTag> = []\n let scanStart = 0\n\n while (true) {\n const currentTagStart = scanStart\n\n // Skip leading whitespace (charCode: space=32, \\t=9, \\r=13)\n let start = scanStart\n while (start < text.length) {\n const c = text.codePointAt(start)\n if (c !== 32 && c !== 9 && c !== 13) break\n start++\n }\n\n // Must start with <system-reminder>\n if (start + OPEN_TAG.length > text.length) break\n if (text.slice(start, start + OPEN_TAG.length) !== OPEN_TAG) break\n\n const afterOpen = start + OPEN_TAG.length\n if (afterOpen >= text.length || text[afterOpen] !== \"\\n\") break\n\n // Find closing tag: \\n</system-reminder> followed by \\n or EOF\n const closeNeedle = \"\\n\" + CLOSE_TAG\n let searchFrom = afterOpen\n let closePos = -1\n while (true) {\n const pos = text.indexOf(closeNeedle, searchFrom)\n if (pos === -1) break\n const afterClose = pos + closeNeedle.length\n if (afterClose >= text.length || text[afterClose] === \"\\n\") {\n closePos = pos\n break\n }\n searchFrom = pos + 1\n }\n if (closePos === -1) break\n\n const content = text.slice(afterOpen + 1, closePos)\n\n // tagEnd: skip past \\n</system-reminder> and any trailing newlines\n let endPos = closePos + closeNeedle.length\n while (endPos < text.length && text[endPos] === \"\\n\") endPos++\n\n tags.push({ content, tagStart: currentTagStart, tagEnd: endPos })\n scanStart = endPos\n }\n\n return { mainContentStart: scanStart, tags }\n}\n\n// ============================================================================\n// Rewrite Configuration\n// ============================================================================\n\n/**\n * Determine how to rewrite a system-reminder tag's content.\n *\n * Reads from `state.rewriteSystemReminders`:\n * - `true` → return `\"\"` (remove all tags)\n * - `false` → return `null` (keep all tags unchanged)\n * - `Array<CompiledRewriteRule>` → first matching rule wins (top-down):\n * - `to: \"\"` → return `\"\"` (remove the tag entirely)\n * - `to: \"$0\"` (regex mode) → return `null` (keep unchanged, fast path)\n * - Otherwise → apply replacement:\n * - regex mode: `content.replace(from, to)` with capture group support\n * - line mode: replace exact `from` substring with `to`\n * - If result === original → return `null` (keep)\n * - Otherwise → return the new content\n * - If no rule matches → return `null` (keep)\n *\n * @returns `null` to keep original, `\"\"` to remove, or a new content string\n */\nfunction rewriteReminder(content: string): string | null {\n const rewrite = state.rewriteSystemReminders\n if (rewrite === true) return \"\"\n if (rewrite === false) return null\n\n for (const rule of rewrite) {\n const matched = rule.method === \"line\" ? content.includes(rule.from as string) : (rule.from as RegExp).test(content)\n\n // Reset lastIndex after test() in case of global flag\n if (rule.method !== \"line\") (rule.from as RegExp).lastIndex = 0\n\n if (!matched) continue\n\n // Empty replacement = remove the entire tag\n if (rule.to === \"\") return \"\"\n\n // $0 replacement in regex mode = keep tag unchanged (identity)\n if (rule.method !== \"line\" && rule.to === \"$0\") return null\n\n const result =\n rule.method === \"line\" ?\n content.replaceAll(rule.from as string, rule.to)\n : content.replace(rule.from as RegExp, rule.to)\n\n if (result === content) return null // replacement produced no change → keep\n return result\n }\n\n return null // no rule matched → keep\n}\n\n// ============================================================================\n// Tag Removal\n// ============================================================================\n\n/**\n * Rewrite, remove, or keep `<system-reminder>` tags in text content.\n *\n * Only processes reminders that:\n * 1. Appear at the START or END of content (not embedded in code)\n * 2. Are separated from main content by newlines (indicating injection points)\n *\n * For each tag, `rewriteReminder(content)` decides the action:\n * - `null` → keep the tag unchanged\n * - `\"\"` → remove the tag entirely\n * - new string → replace the tag's inner content\n *\n * This prevents accidental modification of system-reminder tags that appear\n * in tool_result content (e.g., when reading source files that contain\n * these tags as string literals or documentation).\n */\nexport function removeSystemReminderTags(text: string): string {\n let result = text\n let modified = false\n\n // Process trailing tags\n const trailing = extractTrailingSystemReminderTags(result)\n if (trailing.tags.length > 0) {\n let tail = \"\"\n for (const tag of trailing.tags) {\n const rewritten = rewriteReminder(tag.content)\n if (rewritten === null) {\n // Keep original\n tail += result.slice(tag.tagStart, tag.tagEnd)\n } else if (rewritten === \"\") {\n // Remove — don't append anything\n modified = true\n } else {\n // Replace content\n tail += `\\n${OPEN_TAG}\\n${rewritten}\\n${CLOSE_TAG}`\n modified = true\n }\n }\n if (modified) {\n result = result.slice(0, trailing.mainContentEnd) + tail\n }\n }\n\n // Process leading tags\n const leading = extractLeadingSystemReminderTags(result)\n if (leading.tags.length > 0) {\n let head = \"\"\n let leadingModified = false\n for (const tag of leading.tags) {\n const rewritten = rewriteReminder(tag.content)\n if (rewritten === null) {\n // Keep original\n head += result.slice(tag.tagStart, tag.tagEnd)\n } else if (rewritten === \"\") {\n // Remove — don't append anything\n leadingModified = true\n } else {\n // Replace content\n head += `${OPEN_TAG}\\n${rewritten}\\n${CLOSE_TAG}\\n`\n leadingModified = true\n }\n }\n if (leadingModified) {\n result = head + result.slice(leading.mainContentStart)\n modified = true\n }\n }\n\n if (!modified) return text\n\n // Only strip trailing newlines left behind by tag removal — never touch\n // leading whitespace (e.g. indentation in tool_result content like\n // \" 1→const x = 1\") to avoid false \"rewritten\" diffs.\n let end = result.length\n while (end > 0 && result[end - 1] === \"\\n\") end--\n return end < result.length ? result.slice(0, end) : result\n}\n","/**\n * Common types and configuration for auto-truncate modules.\n * Shared between OpenAI and Anthropic format handlers.\n */\n\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/config/paths\"\nimport { HTTPError, parseTokenLimitError } from \"~/lib/error\"\nimport {\n CLOSE_TAG,\n extractLeadingSystemReminderTags,\n extractTrailingSystemReminderTags,\n OPEN_TAG,\n} from \"~/lib/sanitize-system-reminder\"\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/** Configuration for auto-truncate behavior */\nexport interface AutoTruncateConfig {\n /** Safety margin percentage to account for token counting differences (default: 2) */\n safetyMarginPercent: number\n /** Percentage of context to preserve uncompressed from the end (default: 0.7 = 70%) */\n preserveRecentPercent: number\n /** Whether to enforce token limit (default: true) */\n checkTokenLimit: boolean\n /** Explicit token limit override (used in reactive retry — caller has already applied margin) */\n targetTokenLimit?: number\n}\n\n/** Maximum number of reactive auto-truncate retries per request */\nexport const MAX_AUTO_TRUNCATE_RETRIES = 5\n\n/** Factor to apply to error-reported limit when retrying (90% of limit) */\nexport const AUTO_TRUNCATE_RETRY_FACTOR = 0.9\n\nexport const DEFAULT_AUTO_TRUNCATE_CONFIG: AutoTruncateConfig = {\n safetyMarginPercent: 2,\n preserveRecentPercent: 0.7,\n checkTokenLimit: true,\n}\n\n// ============================================================================\n// Learned Limits (per-model, with calibration)\n// ============================================================================\n\n/** Per-model learned limits with tokenizer calibration */\nexport interface ModelLimits {\n /** Token upper bound (from error response's reported limit) */\n tokenLimit: number\n /** Calibration factor: actualTokens / gptEstimatedTokens.\n * > 1.0 means GPT tokenizer underestimates (Claude tokenizer produces more tokens).\n * < 1.0 means GPT tokenizer overestimates. */\n calibrationFactor: number\n /** Number of calibration samples (higher = more reliable factor) */\n sampleCount: number\n /** Last updated timestamp (ms since epoch) */\n updatedAt: number\n}\n\nconst learnedLimits = new Map<string, ModelLimits>()\n\n/** Get learned limits for a model (including calibration data) */\nexport function getLearnedLimits(modelId: string): ModelLimits | undefined {\n return learnedLimits.get(modelId)\n}\n\n/**\n * Check whether a model has known limits from previous failures.\n * Used to decide whether to pre-check requests before sending.\n */\nexport function hasKnownLimits(modelId: string): boolean {\n return learnedLimits.has(modelId)\n}\n\n// ============================================================================\n// Token Limit Learning\n// ============================================================================\n\n/**\n * Called when a token limit error (400) occurs.\n * Records the learned limit and optionally updates calibration.\n */\nexport function onTokenLimitExceeded(\n modelId: string,\n reportedLimit: number,\n reportedCurrent?: number,\n estimatedTokens?: number,\n): void {\n // Update learned limits (with calibration data for future pre-checks)\n const existing = learnedLimits.get(modelId)\n\n // Only update if this is the first time or the new limit is lower (more restrictive)\n if (!existing || reportedLimit < existing.tokenLimit) {\n learnedLimits.set(modelId, {\n tokenLimit: reportedLimit,\n calibrationFactor: existing?.calibrationFactor ?? 1.0,\n sampleCount: existing?.sampleCount ?? 0,\n updatedAt: Date.now(),\n })\n consola.info(`[AutoTruncate] Learned token limit for ${modelId}: ${reportedLimit}`)\n }\n\n // Calibrate tokenizer if we have both actual and estimated token counts\n if (reportedCurrent !== undefined && estimatedTokens !== undefined && estimatedTokens > 0) {\n updateCalibration(modelId, reportedCurrent, estimatedTokens)\n const lim = learnedLimits.get(modelId)\n if (lim) {\n consola.info(\n `[AutoTruncate] Calibration for ${modelId}: actual=${reportedCurrent} vs estimated=${estimatedTokens}`\n + ` → factor=${lim.calibrationFactor.toFixed(3)} (${lim.sampleCount} samples)`,\n )\n }\n }\n\n schedulePersist()\n}\n\n/** Reset all dynamic limits (for testing) */\nexport function resetAllLimitsForTesting(): void {\n learnedLimits.clear()\n if (persistTimer) {\n clearTimeout(persistTimer)\n persistTimer = null\n }\n}\n\n// ============================================================================\n// Calibration (EWMA)\n// ============================================================================\n\nconst CALIBRATION_ALPHA = 0.3\nconst CALIBRATION_MIN = 0.5\nconst CALIBRATION_MAX = 3.0\n\n/**\n * Update the per-model calibration factor using EWMA.\n *\n * Called after a token limit error when we know both the GPT tokenizer estimate\n * and the actual token count (from the error response). The ratio between them\n * tells us how much the GPT tokenizer over/under-estimates for this model.\n */\nexport function updateCalibration(modelId: string, actualTokens: number, estimatedTokens: number): void {\n if (estimatedTokens <= 0) return\n const limits = learnedLimits.get(modelId)\n if (!limits) return\n\n const rawFactor = actualTokens / estimatedTokens\n const clamped = Math.max(CALIBRATION_MIN, Math.min(CALIBRATION_MAX, rawFactor))\n\n if (limits.sampleCount === 0) {\n limits.calibrationFactor = clamped\n } else {\n limits.calibrationFactor = CALIBRATION_ALPHA * clamped + (1 - CALIBRATION_ALPHA) * limits.calibrationFactor\n }\n limits.sampleCount++\n limits.updatedAt = Date.now()\n}\n\n/** Apply calibration factor to a GPT tokenizer estimate */\nexport function calibrate(modelId: string, gptEstimate: number): number {\n const limits = learnedLimits.get(modelId)\n if (!limits || limits.sampleCount === 0) return gptEstimate\n return Math.ceil(gptEstimate * limits.calibrationFactor)\n}\n\n// ============================================================================\n// Dynamic Safety Margin\n// ============================================================================\n\nconst BASE_MARGIN = 0.03\nconst BONUS_MARGIN_PER_SAMPLE = 0.07\n\n/**\n * Compute dynamic safety margin based on calibration confidence.\n * Fewer samples → wider margin (conservative). More samples → narrower margin.\n *\n * - 0 samples: 10% (0.03 + 0.07)\n * - 1 sample: 10%\n * - 10 samples: ~3.7%\n * - ∞ samples: 3%\n */\nexport function computeSafetyMargin(sampleCount: number): number {\n if (sampleCount <= 0) return BASE_MARGIN + BONUS_MARGIN_PER_SAMPLE\n return BASE_MARGIN + BONUS_MARGIN_PER_SAMPLE / sampleCount\n}\n\n// ============================================================================\n// Limit Persistence\n// ============================================================================\n\ninterface LearnedLimitsFile {\n version: 1\n limits: Record<string, ModelLimits>\n}\n\nlet persistTimer: ReturnType<typeof setTimeout> | null = null\nconst PERSIST_DEBOUNCE_MS = 5000\n\n/** Schedule an async write of learned limits (debounced) */\nexport function schedulePersist(): void {\n if (persistTimer) return\n persistTimer = setTimeout(() => {\n persistTimer = null\n void persistLimits()\n }, PERSIST_DEBOUNCE_MS)\n}\n\n/** Write learned limits to disk */\nexport async function persistLimits(): Promise<void> {\n if (learnedLimits.size === 0) return\n const data: LearnedLimitsFile = { version: 1, limits: Object.fromEntries(learnedLimits) }\n try {\n await fs.writeFile(PATHS.LEARNED_LIMITS, JSON.stringify(data, null, 2), \"utf8\")\n } catch {\n // Write failure is non-critical — limits will be re-learned on next error\n }\n}\n\n/** Load previously persisted limits from disk (called at startup) */\nexport async function loadPersistedLimits(): Promise<void> {\n try {\n const raw = await fs.readFile(PATHS.LEARNED_LIMITS, \"utf8\")\n const data = JSON.parse(raw) as LearnedLimitsFile\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive: persisted file may have unexpected version\n if (data.version !== 1) return\n for (const [modelId, lim] of Object.entries(data.limits)) {\n if (lim.tokenLimit > 0 && lim.calibrationFactor >= CALIBRATION_MIN && lim.calibrationFactor <= CALIBRATION_MAX) {\n learnedLimits.set(modelId, lim)\n }\n }\n if (learnedLimits.size > 0) {\n consola.info(`[AutoTruncate] Loaded learned limits for ${learnedLimits.size} model(s)`)\n }\n } catch {\n // File doesn't exist or is corrupted — start fresh\n }\n}\n\n// ============================================================================\n// Reactive Auto-Truncate Helpers\n// ============================================================================\n\n/** Copilot error structure for JSON parsing */\ninterface CopilotErrorBody {\n error?: {\n message?: string\n code?: string\n type?: string\n }\n}\n\n/** Result from tryParseAndLearnLimit */\nexport interface LimitErrorInfo {\n type: \"token_limit\"\n /** The reported limit (tokens) */\n limit?: number\n /** The current usage that exceeded the limit */\n current?: number\n}\n\n/**\n * Parse an HTTPError to detect token limit errors,\n * and record the learned limit for future pre-checks.\n *\n * When `estimatedTokens` is provided (the GPT tokenizer estimate at the time\n * of the error), also updates the per-model calibration factor.\n *\n * Returns error info if the error is a retryable token limit error, null otherwise.\n */\nexport function tryParseAndLearnLimit(\n error: HTTPError,\n modelId: string,\n learn = true,\n estimatedTokens?: number,\n): LimitErrorInfo | null {\n // 400 → try to parse token limit\n if (error.status === 400) {\n let errorJson: CopilotErrorBody | undefined\n try {\n errorJson = JSON.parse(error.responseText) as CopilotErrorBody\n } catch {\n return null\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- errorJson.error may be undefined at runtime\n if (!errorJson?.error?.message) return null\n\n // Check OpenAI format (code: \"model_max_prompt_tokens_exceeded\")\n // or Anthropic format (type: \"invalid_request_error\")\n const isTokenError =\n errorJson.error.code === \"model_max_prompt_tokens_exceeded\" || errorJson.error.type === \"invalid_request_error\"\n\n if (!isTokenError) return null\n\n const tokenInfo = parseTokenLimitError(errorJson.error.message)\n if (!tokenInfo) return null\n\n // Record the learned limit (only when auto-truncate is enabled)\n if (learn) {\n onTokenLimitExceeded(modelId, tokenInfo.limit, tokenInfo.current, estimatedTokens)\n }\n\n return {\n type: \"token_limit\",\n limit: tokenInfo.limit,\n current: tokenInfo.current,\n }\n }\n\n return null\n}\n\n// ============================================================================\n// Tool Result Compression\n// ============================================================================\n\n/** Threshold for large tool_result content (bytes) */\nexport const LARGE_TOOL_RESULT_THRESHOLD = 10000 // 10KB\n\n/** Maximum length for compressed tool_result summary */\nconst COMPRESSED_SUMMARY_LENGTH = 500\n\n/**\n * Compress a large tool_result content to a summary.\n * Keeps the first and last portions with a note about truncation.\n *\n * Preserves `<system-reminder>` tag wrappers (injected by Claude Code)\n * with a truncated summary of their content, instead of letting them\n * get sliced into broken XML fragments by character-level truncation.\n */\nexport function compressToolResultContent(content: string): string {\n if (content.length <= LARGE_TOOL_RESULT_THRESHOLD) {\n return content\n }\n\n // Extract trailing <system-reminder> tags before compression.\n // These are preserved as truncated summaries instead of being sliced\n // into broken XML fragments by character-level truncation.\n const { mainContentEnd, tags } = extractTrailingSystemReminderTags(content)\n const reminders = tags.map((tag) => {\n const summary = tag.content.trim().split(\"\\n\")[0].slice(0, 80)\n return `${OPEN_TAG}\\n[Truncated] ${summary}\\n${CLOSE_TAG}`\n })\n\n const mainContent = content.slice(0, mainContentEnd)\n\n // Compress the main content (without trailing system-reminder tags)\n const halfLen = Math.floor(COMPRESSED_SUMMARY_LENGTH / 2)\n const start = mainContent.slice(0, halfLen)\n const end = mainContent.slice(-halfLen)\n const removedChars = mainContent.length - COMPRESSED_SUMMARY_LENGTH\n\n let result = `${start}\\n\\n[... ${removedChars.toLocaleString()} characters omitted for brevity ...]\\n\\n${end}`\n\n // Re-append preserved system-reminder tags\n if (reminders.length > 0) {\n result += \"\\n\" + reminders.join(\"\\n\")\n }\n\n return result\n}\n\n// ============================================================================\n// Compacted Text Block Compression\n// ============================================================================\n\n/** Prefix that identifies a compacted tool result in a system-reminder tag */\nconst COMPACTED_RESULT_PREFIX = \"Result of calling the \"\n\n/**\n * Compress a compacted tool result text block.\n *\n * Claude Code compacts tool_result blocks into text blocks wrapped in\n * `<system-reminder>` tags during conversation summarization. Format:\n *\n * <system-reminder>\n * Result of calling the Read tool: \" 1→...file content...\"\n * </system-reminder>\n *\n * These blocks can be very large (entire file contents) but are low-value\n * since the file can be re-read. This replaces the full content with a\n * compressed summary preserving the tool name and a short preview.\n *\n * Returns the compressed text, or `null` if the text doesn't match\n * the expected compacted format.\n */\nexport function compressCompactedReadResult(text: string): string | null {\n const { mainContentStart, tags } = extractLeadingSystemReminderTags(text)\n\n // Must be exactly one system-reminder tag covering the entire text\n if (tags.length !== 1) return null\n // Allow trailing whitespace/newlines after the tag\n if (mainContentStart < text.length && text.slice(mainContentStart).trim() !== \"\") return null\n\n const content = tags[0].content\n if (!content.startsWith(COMPACTED_RESULT_PREFIX)) return null\n\n // Extract tool name: \"Result of calling the Read tool: \"...\"\n const colonPos = content.indexOf(\": \", COMPACTED_RESULT_PREFIX.length)\n if (colonPos === -1) return null\n\n const toolName = content.slice(COMPACTED_RESULT_PREFIX.length, colonPos).replace(/ tool$/, \"\")\n\n // Extract the quoted content after \": \"\n const afterColon = content.slice(colonPos + 2)\n if (!afterColon.startsWith('\"')) return null\n\n // Get the inner content (between quotes) — may use \\\" escapes\n const innerContent = afterColon.slice(1, afterColon.endsWith('\"') ? -1 : undefined)\n\n // Build a short preview from the first meaningful line\n const firstLines = innerContent.split(String.raw`\\n`).slice(0, 3)\n const preview = firstLines.join(\" | \").slice(0, 150)\n\n return (\n `${OPEN_TAG}\\n`\n + `[Compressed] ${toolName} tool result (${innerContent.length.toLocaleString()} chars). `\n + `Preview: ${preview}\\n`\n + CLOSE_TAG\n )\n}\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nimport { tryParseAndLearnLimit } from \"./auto-truncate\"\nimport { state } from \"./state\"\n\nexport class HTTPError extends Error {\n status: number\n responseText: string\n /** Model ID that caused the error (if known) */\n modelId?: string\n /** Original response headers (for Retry-After, quota snapshots, etc.) */\n responseHeaders?: Headers\n\n constructor(message: string, status: number, responseText: string, modelId?: string, responseHeaders?: Headers) {\n super(message)\n this.status = status\n this.responseText = responseText\n this.modelId = modelId\n this.responseHeaders = responseHeaders\n }\n\n static async fromResponse(message: string, response: Response, modelId?: string): Promise<HTTPError> {\n const text = await response.text()\n return new HTTPError(message, response.status, text, modelId, response.headers)\n }\n}\n\n/** Copilot error structure */\ninterface CopilotError {\n error?: {\n message?: string\n code?: string\n }\n}\n\n/** Parse token limit info from error message */\nexport function parseTokenLimitError(message: string): {\n current: number\n limit: number\n} | null {\n // Match OpenAI format: \"prompt token count of 135355 exceeds the limit of 128000\"\n const openaiMatch = message.match(/prompt token count of (\\d+) exceeds the limit of (\\d+)/)\n if (openaiMatch) {\n return {\n current: Number.parseInt(openaiMatch[1], 10),\n limit: Number.parseInt(openaiMatch[2], 10),\n }\n }\n\n // Match Anthropic format: \"prompt is too long: 208598 tokens > 200000 maximum\"\n const anthropicMatch = message.match(/prompt is too long: (\\d+) tokens > (\\d+) maximum/)\n if (anthropicMatch) {\n return {\n current: Number.parseInt(anthropicMatch[1], 10),\n limit: Number.parseInt(anthropicMatch[2], 10),\n }\n }\n\n return null\n}\n\n/** Format Anthropic-compatible error for token limit exceeded */\nfunction formatTokenLimitError(current: number, limit: number) {\n const excess = current - limit\n const percentage = Math.round((excess / limit) * 100)\n\n // Return Anthropic-compatible error that clients can recognize and handle\n // The \"prompt_too_long\" type is what Anthropic's API returns for context limit errors\n // This should trigger Claude Code's auto-truncate behavior\n return {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n `prompt is too long: ${current} tokens > ${limit} maximum ` + `(${excess} tokens over, ${percentage}% excess)`,\n },\n }\n}\n\n/** Format Anthropic-compatible error for request too large (413) */\nfunction formatRequestTooLargeError() {\n // Return Anthropic-compatible error for 413 Request Entity Too Large\n // This happens when the HTTP body is too large, separate from token limits\n return {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message:\n \"Request body too large. The HTTP request exceeds the server's size limit. \"\n + \"Try reducing the conversation history or removing large content like images.\",\n },\n }\n}\n\n/** Format Anthropic-compatible error for rate limit exceeded (429) */\nfunction formatRateLimitError(copilotMessage?: string) {\n // Return Anthropic-compatible error for 429 rate limit\n // The \"rate_limit_error\" type is what Anthropic's API returns for rate limiting\n return {\n type: \"error\",\n error: {\n type: \"rate_limit_error\",\n message: copilotMessage ?? \"You have exceeded your rate limit. Please try again later.\",\n },\n }\n}\n\n/** Format Anthropic-compatible error for quota exceeded (402) */\nfunction formatQuotaExceededError(retryAfter?: number) {\n const retryInfo = retryAfter ? ` Quota resets in approximately ${retryAfter} seconds.` : \"\"\n return {\n type: \"error\",\n error: {\n type: \"rate_limit_error\",\n message: `You have exceeded your usage quota. Please try again later.${retryInfo}`,\n },\n ...(retryAfter !== undefined && { retry_after: retryAfter }),\n }\n}\n\n/** Format Anthropic-compatible error for content filtered (422) */\nfunction formatContentFilteredError(responseText: string) {\n // Try to extract the original error message from the response\n let detail = \"\"\n try {\n const parsed = JSON.parse(responseText) as { error?: { message?: string } }\n if (parsed.error?.message) detail = `: ${parsed.error.message}`\n } catch {\n // Not JSON — use generic message\n }\n return {\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: `Content filtered by safety system${detail}`,\n },\n }\n}\n\n/** Anthropic error structure */\ninterface AnthropicError {\n type?: string\n error?: {\n type?: string\n message?: string\n }\n}\n\nexport function forwardError(c: Context, error: unknown) {\n // Error file persistence is handled by the error-persistence consumer\n // (subscribes to \"failed\" events on RequestContext) — no inline writing here.\n\n if (error instanceof HTTPError) {\n // Try to detect and learn from token limit / body size errors\n // Only record limits for future pre-checks when auto-truncate is enabled\n const limitInfo = tryParseAndLearnLimit(error, error.modelId ?? \"unknown\", state.autoTruncate)\n\n // Handle 413 Request Entity Too Large\n if (error.status === 413) {\n const formattedError = formatRequestTooLargeError()\n consola.warn(`HTTP 413: Request too large`)\n return c.json(formattedError, 413 as ContentfulStatusCode)\n }\n\n // Handle token limit exceeded (detected by tryParseAndLearnLimit)\n if (limitInfo?.type === \"token_limit\" && limitInfo.current && limitInfo.limit) {\n const formattedError = formatTokenLimitError(limitInfo.current, limitInfo.limit)\n const excess = limitInfo.current - limitInfo.limit\n const percentage = Math.round((excess / limitInfo.limit) * 100)\n consola.warn(\n `HTTP ${error.status}: Token limit exceeded for ${error.modelId ?? \"unknown\"} `\n + `(${limitInfo.current.toLocaleString()} > ${limitInfo.limit.toLocaleString()}, `\n + `${excess.toLocaleString()} over, ${percentage}% excess)`,\n )\n return c.json(formattedError, 400 as ContentfulStatusCode)\n }\n\n // Handle 402 Quota Exceeded\n if (error.status === 402) {\n const retryAfter = parseRetryAfterHeader(error.responseHeaders)\n const formattedError = formatQuotaExceededError(retryAfter)\n consola.warn(`HTTP 402: Quota exceeded${retryAfter ? ` (retry after ${retryAfter}s)` : \"\"}`)\n return c.json(formattedError, 402 as ContentfulStatusCode)\n }\n\n // Handle 422 Content Filtered (Responsible AI Service)\n if (error.status === 422) {\n const formattedError = formatContentFilteredError(error.responseText)\n consola.warn(`HTTP 422: Content filtered by safety system`)\n return c.json(formattedError, 422 as ContentfulStatusCode)\n }\n\n let errorJson: unknown\n try {\n errorJson = JSON.parse(error.responseText)\n } catch {\n errorJson = error.responseText\n }\n\n // Only attempt structured error detection on parsed JSON objects\n if (typeof errorJson === \"object\" && errorJson !== null) {\n const errorObj = errorJson as CopilotError & AnthropicError\n\n // Check for rate limit error from Copilot (429 with code \"rate_limited\")\n if (error.status === 429 || errorObj.error?.code === \"rate_limited\") {\n const formattedError = formatRateLimitError(errorObj.error?.message)\n consola.warn(`HTTP 429: Rate limit exceeded`)\n return c.json(formattedError, 429 as ContentfulStatusCode)\n }\n\n // Handle 503 upstream rate limit (distinguish from generic 503)\n if (error.status === 503 && isUpstreamRateLimited(error.responseText)) {\n const retryAfter = parseRetryAfterHeader(error.responseHeaders)\n const formattedError = formatRateLimitError(\n errorObj.error?.message ?? \"Upstream provider rate limited. Please try again later.\",\n )\n if (retryAfter) {\n ;(formattedError as Record<string, unknown>).retry_after = retryAfter\n }\n consola.warn(`HTTP 503: Upstream provider rate limited${retryAfter ? ` (retry after ${retryAfter}s)` : \"\"}`)\n return c.json(formattedError, 503 as ContentfulStatusCode)\n }\n } else if (error.status === 429) {\n // Rate limit with non-JSON response\n const formattedError = formatRateLimitError()\n consola.warn(`HTTP 429: Rate limit exceeded`)\n return c.json(formattedError, 429 as ContentfulStatusCode)\n }\n\n // Log unhandled HTTP errors\n consola.error(`HTTP ${error.status}:`, errorJson)\n\n return c.json(\n {\n error: {\n message: error.responseText,\n type: \"error\",\n },\n },\n error.status as ContentfulStatusCode,\n )\n }\n\n // Non-HTTP errors (socket closures, DNS failures, timeouts, etc.)\n const errorMessage = error instanceof Error ? formatErrorWithCause(error) : String(error)\n consola.error(`Unexpected non-HTTP error in ${c.req.method} ${c.req.path}:`, errorMessage)\n\n return c.json(\n {\n error: {\n message: errorMessage,\n type: \"error\",\n },\n },\n 500,\n )\n}\n\n// ─── Error Classification System ───\n\n/** Structured error types for pipeline retry decisions */\nexport type ApiErrorType =\n | \"rate_limited\" // 429\n | \"payload_too_large\" // 413\n | \"token_limit\" // 200/400 but body contains token limit error\n | \"content_filtered\" // 422 — Responsible AI Service filtering\n | \"quota_exceeded\" // 402 — free tier / premium quota exceeded\n | \"auth_expired\" // Token expired\n | \"network_error\" // Connection failure\n | \"server_error\" // 5xx (non-503-upstream)\n | \"upstream_rate_limited\" // 503 — upstream provider rate limited\n | \"bad_request\" // 400 (non-token-limit)\n\n/** Classified API error with structured metadata */\nexport interface ApiError {\n type: ApiErrorType\n status: number\n message: string\n /** Retry-After seconds (rate_limited / quota_exceeded / upstream_rate_limited) */\n retryAfter?: number\n /** Token limit from error response (token_limit) */\n tokenLimit?: number\n /** Current token count from error response (token_limit) */\n tokenCurrent?: number\n /** Original response headers (for quota snapshots, etc.) */\n responseHeaders?: Headers\n /** Original error object */\n raw: unknown\n}\n\n/**\n * Classify a raw error into a structured ApiError.\n * Used by the pipeline to route errors to appropriate RetryStrategies.\n */\nexport function classifyError(error: unknown): ApiError {\n if (error instanceof HTTPError) {\n return classifyHTTPError(error)\n }\n\n // Network errors: fetch failures, socket closures, connection resets, timeouts, DNS failures\n // Bun throws TypeError for some fetch failures, and plain Error for socket closures.\n // Match broadly on error message patterns to catch all network-level failures.\n if (error instanceof Error && isNetworkError(error)) {\n return {\n type: \"network_error\",\n status: 0,\n message: formatErrorWithCause(error),\n raw: error,\n }\n }\n\n // Generic Error\n if (error instanceof Error) {\n return {\n type: \"bad_request\",\n status: 0,\n message: formatErrorWithCause(error),\n raw: error,\n }\n }\n\n return {\n type: \"bad_request\",\n status: 0,\n message: String(error),\n raw: error,\n }\n}\n\nfunction classifyHTTPError(error: HTTPError): ApiError {\n const { status, responseText, message } = error\n\n // 422 Content Filtered (Responsible AI Service)\n if (status === 422) {\n return {\n type: \"content_filtered\",\n status,\n message,\n responseHeaders: error.responseHeaders,\n raw: error,\n }\n }\n\n // 402 Quota Exceeded (free tier / premium quota)\n if (status === 402) {\n const retryAfter = extractRetryAfterFromBody(responseText) ?? parseRetryAfterHeader(error.responseHeaders)\n return {\n type: \"quota_exceeded\",\n status,\n message,\n retryAfter,\n responseHeaders: error.responseHeaders,\n raw: error,\n }\n }\n\n // 429 Rate Limited\n if (status === 429) {\n const retryAfter = extractRetryAfterFromBody(responseText) ?? parseRetryAfterHeader(error.responseHeaders)\n return {\n type: \"rate_limited\",\n status,\n message,\n retryAfter,\n responseHeaders: error.responseHeaders,\n raw: error,\n }\n }\n\n // 413 Payload Too Large\n if (status === 413) {\n return {\n type: \"payload_too_large\",\n status,\n message,\n raw: error,\n }\n }\n\n // 503 — check if upstream provider rate limited\n if (status === 503) {\n const retryAfter = extractRetryAfterFromBody(responseText) ?? parseRetryAfterHeader(error.responseHeaders)\n if (isUpstreamRateLimited(responseText)) {\n return {\n type: \"upstream_rate_limited\",\n status,\n message,\n retryAfter,\n responseHeaders: error.responseHeaders,\n raw: error,\n }\n }\n return {\n type: \"server_error\",\n status,\n message,\n retryAfter,\n raw: error,\n }\n }\n\n // 5xx Server Errors (non-503, already handled above)\n if (status >= 500) {\n return {\n type: \"server_error\",\n status,\n message,\n raw: error,\n }\n }\n\n // 401/403 Auth Errors\n if (status === 401 || status === 403) {\n return {\n type: \"auth_expired\",\n status,\n message,\n raw: error,\n }\n }\n\n // 400 — check for token limit error in response body\n if (status === 400) {\n const tokenLimit = tryExtractTokenLimit(responseText)\n if (tokenLimit) {\n return {\n type: \"token_limit\",\n status,\n message,\n tokenLimit: tokenLimit.limit,\n tokenCurrent: tokenLimit.current,\n raw: error,\n }\n }\n\n // Check for rate_limited code in body (some APIs return 400 for rate limits)\n if (isRateLimitedInBody(responseText)) {\n const retryAfter = extractRetryAfterFromBody(responseText) ?? parseRetryAfterHeader(error.responseHeaders)\n return {\n type: \"rate_limited\",\n status,\n message,\n retryAfter,\n raw: error,\n }\n }\n }\n\n // Default: bad_request\n return {\n type: \"bad_request\",\n status,\n message,\n raw: error,\n }\n}\n\n// ─── Retry-After Header Parsing ───\n\n/**\n * Parse the `Retry-After` HTTP response header.\n * Supports both formats per RFC 7231:\n * - Seconds: `Retry-After: 120` → 120\n * - HTTP-date: `Retry-After: Fri, 31 Dec 2025 23:59:59 GMT` → seconds from now\n * Returns undefined if header is missing or unparseable.\n */\nexport function parseRetryAfterHeader(headers: Headers | undefined): number | undefined {\n if (!headers) return undefined\n\n const value = headers.get(\"retry-after\")\n if (!value) return undefined\n\n // Try as seconds (integer)\n const seconds = Number.parseInt(value, 10)\n if (!Number.isNaN(seconds) && String(seconds) === value.trim()) {\n return seconds > 0 ? seconds : undefined\n }\n\n // Try as HTTP-date\n const date = new Date(value)\n if (!Number.isNaN(date.getTime())) {\n const deltaMs = date.getTime() - Date.now()\n const deltaSec = Math.ceil(deltaMs / 1000)\n return deltaSec > 0 ? deltaSec : undefined\n }\n\n return undefined\n}\n\n// ─── Upstream Rate Limit Detection ───\n\n/** Check if a 503 response body indicates upstream provider rate limiting */\nfunction isUpstreamRateLimited(responseText: string): boolean {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (err && typeof err === \"object\") {\n // Check for rate limit indicators in error code or message\n const errObj = err as Record<string, unknown>\n if (typeof errObj.code === \"string\" && errObj.code.includes(\"rate\")) return true\n if (typeof errObj.message === \"string\") {\n const msg = errObj.message.toLowerCase()\n if (msg.includes(\"rate limit\") || msg.includes(\"too many requests\") || msg.includes(\"quota\")) return true\n }\n }\n }\n } catch {\n // Not JSON — check raw text\n const lower = responseText.toLowerCase()\n if (lower.includes(\"rate limit\") || lower.includes(\"too many requests\")) return true\n }\n return false\n}\n\n/** Extract retry_after from JSON response body */\nfunction extractRetryAfterFromBody(responseText: string): number | undefined {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\") {\n // Top-level retry_after\n if (\"retry_after\" in parsed && typeof (parsed as Record<string, unknown>).retry_after === \"number\") {\n return (parsed as { retry_after: number }).retry_after\n }\n // Nested error.retry_after\n if (\"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (\n err\n && typeof err === \"object\"\n && \"retry_after\" in err\n && typeof (err as Record<string, unknown>).retry_after === \"number\"\n ) {\n return (err as { retry_after: number }).retry_after\n }\n }\n }\n } catch {\n // Not JSON\n }\n return undefined\n}\n\n/** Check if response body contains rate_limited code */\nfunction isRateLimitedInBody(responseText: string): boolean {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (err && typeof err === \"object\" && \"code\" in err) {\n return (err as { code: unknown }).code === \"rate_limited\"\n }\n }\n } catch {\n // Not JSON\n }\n return false\n}\n\n/** Try to extract token limit info from response body */\nfunction tryExtractTokenLimit(responseText: string): { current: number; limit: number } | null {\n try {\n const parsed: unknown = JSON.parse(responseText)\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error\n if (\n err\n && typeof err === \"object\"\n && \"message\" in err\n && typeof (err as Record<string, unknown>).message === \"string\"\n ) {\n return parseTokenLimitError((err as { message: string }).message)\n }\n }\n } catch {\n // Not JSON\n }\n return null\n}\n\n// ─── Network Error Detection ───\n\n/** Known network/socket error message patterns from Bun and Node.js fetch */\nconst NETWORK_ERROR_PATTERNS = [\n \"socket\", // \"The socket connection was closed unexpectedly\"\n \"ECONNRESET\", // Connection reset by peer\n \"ECONNREFUSED\", // Connection refused\n \"ETIMEDOUT\", // Connection timed out\n \"ENETUNREACH\", // Network unreachable\n \"EHOSTUNREACH\", // Host unreachable\n \"EAI_AGAIN\", // DNS lookup timeout\n \"UND_ERR_SOCKET\", // undici socket errors (Node.js)\n \"fetch failed\", // Generic fetch failure\n \"network\", // General network errors\n \"TLS\", // TLS/SSL errors\n \"CERT\", // Certificate errors\n \"abort\", // AbortError from timeouts\n]\n\n/** Check if an error is a network-level failure (socket, DNS, TLS, connection errors) */\nfunction isNetworkError(error: Error): boolean {\n const msg = error.message.toLowerCase()\n if (NETWORK_ERROR_PATTERNS.some((p) => msg.includes(p.toLowerCase()))) return true\n\n // Check cause chain for network indicators\n if (error.cause instanceof Error) return isNetworkError(error.cause)\n\n return false\n}\n\n/**\n * Strip Bun's unhelpful verbose hint from error messages.\n * Bun appends \"For more information, pass `verbose: true` in the second argument to fetch()\"\n * to socket/network errors — this is an implementation detail, not useful to the user.\n */\nfunction stripBunVerboseHint(message: string): string {\n return message.replace(/\\s*For more information, pass `verbose: true`.*$/i, \"\")\n}\n\n/**\n * Format error message including cause chain, with Bun noise stripped.\n * Surfaces error.cause details (e.g. underlying socket/TLS reason) inline.\n */\nexport function formatErrorWithCause(error: Error): string {\n let msg = stripBunVerboseHint(error.message)\n if (error.cause instanceof Error && error.cause.message && error.cause.message !== error.message) {\n msg += ` (cause: ${stripBunVerboseHint(error.cause.message)})`\n }\n return msg\n}\n\n// ─── Error Message Extraction ───\n\n/** Extract error message with fallback. For HTTPError, extracts the actual API error response. */\nexport function getErrorMessage(error: unknown, fallback = \"Unknown error\"): string {\n if (error instanceof Error) {\n if (\"responseText\" in error && typeof (error as { responseText: unknown }).responseText === \"string\") {\n const responseText = (error as { responseText: string }).responseText\n const status = \"status\" in error ? (error as { status: number }).status : undefined\n try {\n const parsed = JSON.parse(responseText) as { error?: { message?: string; type?: string } }\n if (parsed.error?.message) {\n return status ? `HTTP ${status}: ${parsed.error.message}` : parsed.error.message\n }\n } catch {\n if (responseText.length > 0 && responseText.length < 500) {\n return status ? `HTTP ${status}: ${responseText}` : responseText\n }\n }\n return status ? `HTTP ${status}: ${error.message}` : error.message\n }\n // For non-HTTP errors, include cause information (e.g. Bun verbose fetch details)\n return formatErrorWithCause(error)\n }\n return fallback\n}\n","/** Copilot API client — token and usage */\n\nimport { COPILOT_INTERNAL_API_VERSION, GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n// ============================================================================\n// Token\n// ============================================================================\n\nexport const getCopilotToken = async (): Promise<CopilotTokenResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, {\n headers: { ...githubHeaders(state), \"x-github-api-version\": COPILOT_INTERNAL_API_VERSION },\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get Copilot token\", response)\n\n return (await response.json()) as CopilotTokenResponse\n}\n\n/**\n * Copilot token API response.\n * Only the fields we actively use are typed; the full response is\n * preserved as-is in CopilotTokenInfo.raw for future consumers.\n */\nexport interface CopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n [key: string]: unknown\n}\n\n// ============================================================================\n// Usage\n// ============================================================================\n\nexport const getCopilotUsage = async (): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: { ...githubHeaders(state), \"x-github-api-version\": COPILOT_INTERNAL_API_VERSION },\n })\n\n if (!response.ok) {\n throw await HTTPError.fromResponse(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\n\nimport type { GitHubTokenManager } from \"./github-token-manager\"\nimport type { CopilotTokenInfo } from \"./types\"\n\nimport { getCopilotToken } from \"./copilot-client\"\n\nexport interface CopilotTokenManagerOptions {\n /** GitHub token manager instance */\n githubTokenManager: GitHubTokenManager\n /** Minimum refresh interval in seconds (default: 60) */\n minRefreshIntervalSeconds?: number\n /** Maximum retries for token refresh (default: 3) */\n maxRetries?: number\n}\n\n/**\n * Manages Copilot token lifecycle including automatic refresh.\n * Depends on GitHubTokenManager for authentication.\n *\n * All refresh paths (scheduled + on-demand via 401) go through `refresh()`,\n * which deduplicates concurrent callers and reschedules the next refresh based\n * on the server's `refresh_in` value.\n */\nexport class CopilotTokenManager {\n private githubTokenManager: GitHubTokenManager\n private currentToken: CopilotTokenInfo | null = null\n private refreshTimeout: ReturnType<typeof setTimeout> | null = null\n private minRefreshIntervalMs: number\n private maxRetries: number\n /** Shared promise to prevent concurrent refresh attempts */\n private refreshInFlight: Promise<CopilotTokenInfo | null> | null = null\n\n constructor(options: CopilotTokenManagerOptions) {\n this.githubTokenManager = options.githubTokenManager\n this.minRefreshIntervalMs = (options.minRefreshIntervalSeconds ?? 60) * 1000\n this.maxRetries = options.maxRetries ?? 3\n }\n\n /**\n * Get the current Copilot token info.\n */\n getCurrentToken(): CopilotTokenInfo | null {\n return this.currentToken\n }\n\n /**\n * Initialize the Copilot token and start automatic refresh.\n */\n async initialize(): Promise<CopilotTokenInfo> {\n const tokenInfo = await this.fetchCopilotToken()\n\n // Update global state\n state.copilotToken = tokenInfo.token\n\n // Show token in verbose mode\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n\n // Schedule first refresh based on server's refresh_in\n this.scheduleRefresh(tokenInfo.refreshIn)\n\n return tokenInfo\n }\n\n /**\n * Fetch a new Copilot token from the API.\n */\n private async fetchCopilotToken(): Promise<CopilotTokenInfo> {\n const response = await getCopilotToken()\n\n const tokenInfo: CopilotTokenInfo = {\n token: response.token,\n expiresAt: response.expires_at,\n refreshIn: response.refresh_in,\n raw: response,\n }\n\n this.currentToken = tokenInfo\n return tokenInfo\n }\n\n /**\n * Fetch a new Copilot token with exponential backoff retry.\n * Pure acquisition logic — does not update global state or reschedule timers.\n */\n private async fetchTokenWithRetry(): Promise<CopilotTokenInfo | null> {\n let lastError: unknown = null\n\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n return await this.fetchCopilotToken()\n } catch (error) {\n lastError = error\n\n // Check if this is a 401 error - might need to refresh GitHub token\n if (this.isUnauthorizedError(error)) {\n consola.warn(\"Copilot token refresh got 401, trying to refresh GitHub token...\")\n const newGithubToken = await this.githubTokenManager.refresh()\n if (newGithubToken) {\n // Update state and retry\n state.githubToken = newGithubToken.token\n continue\n }\n }\n\n const delay = Math.min(1000 * 2 ** attempt, 30000) // Max 30s delay\n consola.warn(`Token refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay}ms`)\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n\n consola.error(\"All token refresh attempts failed:\", lastError)\n return null\n }\n\n /**\n * Check if an error is a 401 Unauthorized error.\n */\n private isUnauthorizedError(error: unknown): boolean {\n if (error && typeof error === \"object\" && \"status\" in error) {\n return (error as { status: number }).status === 401\n }\n return false\n }\n\n /**\n * Schedule the next refresh using setTimeout.\n *\n * Uses the server-provided `refresh_in` value each time, adapting to\n * changing token lifetimes. After each refresh, reschedules based on\n * the new token's `refresh_in`.\n */\n private scheduleRefresh(refreshInSeconds: number): void {\n // Sanity check: refresh_in should be positive and reasonable\n let effectiveRefreshIn = refreshInSeconds\n if (refreshInSeconds <= 0) {\n consola.warn(`[CopilotToken] Invalid refresh_in=${refreshInSeconds}s, using default 30 minutes`)\n effectiveRefreshIn = 1800 // 30 minutes\n }\n\n // Calculate delay (refresh a bit before expiration)\n const delayMs = Math.max((effectiveRefreshIn - 60) * 1000, this.minRefreshIntervalMs)\n\n consola.debug(\n `[CopilotToken] refresh_in=${effectiveRefreshIn}s, scheduling next refresh in ${Math.round(delayMs / 1000)}s`,\n )\n\n // Clear any existing timer\n this.cancelScheduledRefresh()\n\n this.refreshTimeout = setTimeout(() => {\n this.refresh().catch((error: unknown) => {\n consola.error(\"Unexpected error during scheduled token refresh:\", error)\n })\n }, delayMs)\n }\n\n /**\n * Cancel the currently scheduled refresh.\n */\n private cancelScheduledRefresh(): void {\n if (this.refreshTimeout) {\n clearTimeout(this.refreshTimeout)\n this.refreshTimeout = null\n }\n }\n\n /**\n * Stop automatic token refresh.\n * Call this during cleanup/shutdown.\n */\n stopAutoRefresh(): void {\n this.cancelScheduledRefresh()\n }\n\n /**\n * Refresh the Copilot token.\n *\n * Single entry point for all refreshes — both scheduled and on-demand\n * (e.g. after a 401). Concurrent callers share the same in-flight refresh.\n * On success, updates global state and reschedules the next refresh based\n * on the new token's `refresh_in`.\n */\n async refresh(): Promise<CopilotTokenInfo | null> {\n // If a refresh is already in progress, piggyback on it\n if (this.refreshInFlight) {\n consola.debug(\"[CopilotToken] Refresh already in progress, waiting...\")\n return this.refreshInFlight\n }\n\n this.refreshInFlight = this.fetchTokenWithRetry()\n .then((tokenInfo) => {\n if (tokenInfo) {\n state.copilotToken = tokenInfo.token\n // Reschedule based on new token's refresh_in\n this.scheduleRefresh(tokenInfo.refreshIn)\n consola.verbose(`[CopilotToken] Token refreshed (next refresh_in=${tokenInfo.refreshIn}s)`)\n } else {\n consola.error(\"[CopilotToken] Token refresh failed, keeping existing token\")\n // Still reschedule with a fallback to avoid stopping the refresh loop entirely\n this.scheduleRefresh(300) // retry in 5 minutes\n }\n return tokenInfo\n })\n .finally(() => {\n this.refreshInFlight = null\n })\n\n return this.refreshInFlight\n }\n\n /**\n * Check if the current token is expired or about to expire.\n */\n isExpiredOrExpiring(marginSeconds = 60): boolean {\n if (!this.currentToken) {\n return true\n }\n\n const now = Date.now() / 1000\n return this.currentToken.expiresAt - marginSeconds <= now\n }\n}\n","export const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined => value === null || value === undefined\n\n/** Convert bytes to KB with rounding */\nexport function bytesToKB(bytes: number): number {\n return Math.round(bytes / 1024)\n}\n\n/** Generate unique ID (timestamp + random) */\nexport function generateId(randomLength = 7): string {\n return (\n Date.now().toString(36)\n + Math.random()\n .toString(36)\n .slice(2, 2 + randomLength)\n )\n}\n","/** GitHub OAuth API client — device code flow and user info */\n\nimport consola from \"consola\"\n\nimport {\n GITHUB_API_BASE_URL,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n githubHeaders,\n standardHeaders,\n} from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { sleep } from \"~/lib/utils\"\n\n// ============================================================================\n// User\n// ============================================================================\n\nexport interface GitHubUser {\n login: string\n id: number\n name: string | null\n email: string | null\n created_at: string\n updated_at: string\n two_factor_authentication: boolean\n}\n\nexport const getGitHubUser = async (): Promise<GitHubUser> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GitHubUser\n}\n\n// ============================================================================\n// Device code flow\n// ============================================================================\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n\nexport const getDeviceCode = async (): Promise<DeviceCodeResponse> => {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: \"read:user\",\n }),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport async function pollAccessToken(deviceCode: DeviceCodeResponse): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n // Calculate expiration time based on expires_in from device code response\n const expiresAt = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expiresAt) {\n const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n })\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = (await response.json()) as AccessTokenResponse\n consola.debug(\"Polling access token response:\", json)\n\n const { access_token } = json\n\n if (access_token) {\n return access_token\n } else {\n await sleep(sleepDuration)\n }\n }\n\n throw new Error(\"Device code expired. Please run the authentication flow again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n","import { state } from \"~/lib/state\"\nimport { getGitHubUser } from \"~/lib/token/github-client\"\n\nimport type { TokenInfo, TokenValidationResult } from \"../types\"\n\n/**\n * Abstract base class for GitHub token providers.\n * Each provider represents a different source of GitHub tokens.\n */\nexport abstract class GitHubTokenProvider {\n /** Human-readable name of the provider */\n abstract readonly name: string\n\n /** Priority (lower = higher priority, tried first) */\n abstract readonly priority: number\n\n /** Whether this provider can refresh tokens */\n abstract readonly refreshable: boolean\n\n /**\n * Check if this provider is available (has required configuration).\n * For example, CLI provider is only available if token was passed via args.\n */\n abstract isAvailable(): boolean | Promise<boolean>\n\n /**\n * Get the token from this provider.\n * Returns null if not available or token cannot be obtained.\n */\n abstract getToken(): Promise<TokenInfo | null>\n\n /**\n * Refresh the token (if supported).\n * Default implementation returns null (not supported).\n */\n // eslint-disable-next-line @typescript-eslint/require-await\n async refresh(): Promise<TokenInfo | null> {\n return null\n }\n\n /**\n * Validate the token by calling GitHub API.\n * Returns validation result with username if successful.\n */\n async validate(token: string): Promise<TokenValidationResult> {\n // Temporarily set the token to validate\n const originalToken = state.githubToken\n\n try {\n state.githubToken = token\n const user = await getGitHubUser()\n return {\n valid: true,\n username: user.login,\n }\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n }\n } finally {\n // Restore original token\n state.githubToken = originalToken\n }\n }\n}\n","import type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Provider for tokens passed via CLI --github-token argument.\n * Highest priority (1) - if user explicitly provides a token, use it.\n */\nexport class CLITokenProvider extends GitHubTokenProvider {\n readonly name = \"CLI\"\n readonly priority = 1\n readonly refreshable = false\n\n private token: string | undefined\n\n constructor(token?: string) {\n super()\n this.token = token\n }\n\n isAvailable(): boolean {\n return Boolean(this.token && this.token.trim())\n }\n\n getToken(): Promise<TokenInfo | null> {\n if (!this.isAvailable() || !this.token) {\n return Promise.resolve(null)\n }\n\n return Promise.resolve({\n token: this.token.trim(),\n source: \"cli\",\n refreshable: false,\n })\n }\n}\n","import fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/config/paths\"\n\nimport type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Provider for tokens stored in file system.\n * Priority 3 - checked after CLI and environment variables.\n */\nexport class FileTokenProvider extends GitHubTokenProvider {\n readonly name = \"File\"\n readonly priority = 3\n readonly refreshable = false\n\n async isAvailable(): Promise<boolean> {\n try {\n const token = await this.readTokenFile()\n return Boolean(token && token.trim())\n } catch {\n return false\n }\n }\n\n async getToken(): Promise<TokenInfo | null> {\n try {\n const token = await this.readTokenFile()\n if (!token || !token.trim()) {\n return null\n }\n\n return {\n token: token.trim(),\n source: \"file\",\n refreshable: false,\n }\n } catch {\n return null\n }\n }\n\n /**\n * Save a token to the file.\n * This is used by device auth provider to persist tokens.\n */\n async saveToken(token: string): Promise<void> {\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token.trim())\n }\n\n /**\n * Clear the stored token.\n */\n async clearToken(): Promise<void> {\n try {\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, \"\")\n } catch {\n // Ignore errors when clearing\n }\n }\n\n private async readTokenFile(): Promise<string> {\n return fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n }\n}\n","import consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getDeviceCode, pollAccessToken } from \"~/lib/token/github-client\"\n\nimport type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\nimport { FileTokenProvider } from \"./file\"\n\n/**\n * Provider for tokens obtained via GitHub device authorization flow.\n * Priority 4 (lowest) - only used when no other token source is available.\n * This is the interactive fallback that prompts the user to authenticate.\n */\nexport class DeviceAuthProvider extends GitHubTokenProvider {\n readonly name = \"DeviceAuth\"\n readonly priority = 4\n readonly refreshable = true\n\n private fileProvider: FileTokenProvider\n\n constructor() {\n super()\n this.fileProvider = new FileTokenProvider()\n }\n\n /**\n * Device auth is always \"available\" as a fallback.\n * It will prompt the user to authenticate interactively.\n */\n isAvailable(): boolean {\n return true\n }\n\n /**\n * Run the device authorization flow to get a new token.\n * This will prompt the user to visit a URL and enter a code.\n */\n async getToken(): Promise<TokenInfo | null> {\n try {\n consola.info(\"Not logged in, starting device authorization flow...\")\n\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(`Please enter the code \"${response.user_code}\" at ${response.verification_uri}`)\n\n const token = await pollAccessToken(response)\n\n // Save to file for future sessions\n await this.fileProvider.saveToken(token)\n\n // Show token if configured\n if (state.showGitHubToken) {\n consola.info(\"GitHub token:\", token)\n }\n\n return {\n token,\n source: \"device-auth\",\n refreshable: true,\n }\n } catch (error) {\n consola.error(\"Device authorization failed:\", error)\n return null\n }\n }\n\n /**\n * Refresh by running the device auth flow again.\n */\n async refresh(): Promise<TokenInfo | null> {\n return this.getToken()\n }\n}\n","import type { TokenInfo } from \"../types\"\n\nimport { GitHubTokenProvider } from \"./base\"\n\n/**\n * Environment variable names to check for GitHub token.\n * Checked in order - first found wins.\n */\nconst ENV_VARS = [\n \"COPILOT_API_GITHUB_TOKEN\", // Our dedicated variable\n \"GH_TOKEN\", // GitHub CLI compatible\n \"GITHUB_TOKEN\", // Common convention\n]\n\n/**\n * Provider for tokens from environment variables.\n * Priority 2 - checked after CLI but before file storage.\n */\nexport class EnvTokenProvider extends GitHubTokenProvider {\n readonly name = \"Environment\"\n readonly priority = 2\n readonly refreshable = false\n\n /** The env var name where the token was found */\n private foundEnvVar: string | undefined\n\n isAvailable(): boolean {\n return this.findEnvVar() !== undefined\n }\n\n getToken(): Promise<TokenInfo | null> {\n const envVar = this.findEnvVar()\n if (!envVar) {\n return Promise.resolve(null)\n }\n\n const token = process.env[envVar]\n if (!token) {\n return Promise.resolve(null)\n }\n\n this.foundEnvVar = envVar\n\n return Promise.resolve({\n token: token.trim(),\n source: \"env\",\n refreshable: false,\n })\n }\n\n /**\n * Find the first environment variable that contains a token.\n */\n private findEnvVar(): string | undefined {\n for (const envVar of ENV_VARS) {\n const value = process.env[envVar]\n if (value && value.trim()) {\n return envVar\n }\n }\n return undefined\n }\n\n /**\n * Get the name of the environment variable that provided the token.\n */\n getFoundEnvVar(): string | undefined {\n return this.foundEnvVar\n }\n}\n","import consola from \"consola\"\n\nimport type { GitHubTokenProvider } from \"./providers/base\"\nimport type { TokenInfo, TokenValidationResult } from \"./types\"\n\nimport { CLITokenProvider } from \"./providers/cli\"\nimport { DeviceAuthProvider } from \"./providers/device-auth\"\nimport { EnvTokenProvider } from \"./providers/env\"\nimport { FileTokenProvider } from \"./providers/file\"\n\nexport interface GitHubTokenManagerOptions {\n /** Token provided via CLI --github-token argument */\n cliToken?: string\n /** Whether to validate tokens before use */\n validateOnInit?: boolean\n /** Callback when token expires and cannot be refreshed */\n onTokenExpired?: () => void\n}\n\n/**\n * Manages GitHub token acquisition from multiple providers.\n * Providers are tried in priority order until one succeeds.\n */\nexport class GitHubTokenManager {\n private providers: Array<GitHubTokenProvider> = []\n private currentToken: TokenInfo | null = null\n private onTokenExpired?: () => void\n private validateOnInit: boolean\n\n constructor(options: GitHubTokenManagerOptions = {}) {\n this.validateOnInit = options.validateOnInit ?? false\n this.onTokenExpired = options.onTokenExpired\n\n // Initialize providers in priority order\n // Note: GhCliTokenProvider is NOT included because GitHub CLI tokens\n // are obtained via a different OAuth app and cannot access Copilot internal APIs.\n this.providers = [\n new CLITokenProvider(options.cliToken),\n new EnvTokenProvider(),\n new FileTokenProvider(),\n new DeviceAuthProvider(),\n ]\n\n // Sort by priority (lower = higher priority)\n this.providers.sort((a, b) => a.priority - b.priority)\n }\n\n /**\n * Get the current token info (without fetching a new one).\n */\n getCurrentToken(): TokenInfo | null {\n return this.currentToken\n }\n\n /**\n * Get a GitHub token, trying providers in priority order.\n * Caches the result for subsequent calls.\n */\n async getToken(): Promise<TokenInfo> {\n // Return cached token if available\n if (this.currentToken) {\n return this.currentToken\n }\n\n for (const provider of this.providers) {\n if (!(await provider.isAvailable())) {\n continue\n }\n\n consola.debug(`Trying ${provider.name} token provider...`)\n\n const tokenInfo = await provider.getToken()\n if (!tokenInfo) {\n continue\n }\n\n // Optionally validate the token\n if (this.validateOnInit) {\n const validation = await this.validateToken(tokenInfo.token, provider)\n if (!validation.valid) {\n consola.warn(`Token from ${provider.name} provider is invalid: ${validation.error}`)\n continue\n }\n consola.info(`Logged in as ${validation.username}`)\n }\n\n consola.debug(`Using token from ${provider.name} provider`)\n this.currentToken = tokenInfo\n return tokenInfo\n }\n\n throw new Error(\"No valid GitHub token available from any provider\")\n }\n\n /**\n * Validate a token using a provider's validate method.\n */\n async validateToken(token: string, provider?: GitHubTokenProvider): Promise<TokenValidationResult> {\n const p = provider ?? this.providers[0]\n return p.validate(token)\n }\n\n /**\n * Force refresh the current token.\n * Only works if the current token source supports refresh.\n * For non-refreshable sources (CLI, env), this will call onTokenExpired.\n */\n async refresh(): Promise<TokenInfo | null> {\n if (!this.currentToken) {\n // No current token, get a new one\n return this.getToken()\n }\n\n // Check if current token source is refreshable\n if (!this.currentToken.refreshable) {\n consola.warn(`Current token from ${this.currentToken.source} cannot be refreshed`)\n this.onTokenExpired?.()\n return null\n }\n\n // Find the device auth provider for refresh\n const deviceAuthProvider = this.providers.find((p) => p instanceof DeviceAuthProvider)\n if (!deviceAuthProvider) {\n consola.warn(\"[GitHubToken] No provider supports token refresh, triggering re-authentication\")\n this.onTokenExpired?.()\n return null\n }\n\n const newToken = await deviceAuthProvider.refresh()\n if (newToken) {\n this.currentToken = newToken\n return newToken\n }\n\n consola.error(\"[GitHubToken] Failed to refresh token via DeviceAuthProvider\")\n this.onTokenExpired?.()\n return null\n }\n\n /**\n * Clear the current token cache.\n * Does not delete persisted tokens.\n */\n clearCache(): void {\n this.currentToken = null\n }\n\n /**\n * Clear all tokens (including persisted ones).\n */\n async clearAll(): Promise<void> {\n this.currentToken = null\n\n // Clear file-based token\n const fileProvider = this.providers.find((p) => p instanceof FileTokenProvider)\n if (fileProvider) {\n await fileProvider.clearToken()\n }\n }\n\n /**\n * Get all available providers for debugging.\n */\n async getProviders(): Promise<\n Array<{\n name: string\n priority: number\n available: boolean\n }>\n > {\n return Promise.all(\n this.providers.map(async (p) => ({\n name: p.name,\n priority: p.priority,\n available: await p.isAvailable(),\n })),\n )\n }\n}\n","export { CopilotTokenManager, type CopilotTokenManagerOptions } from \"./copilot-token-manager\"\n\n// Managers\nexport { GitHubTokenManager, type GitHubTokenManagerOptions } from \"./github-token-manager\"\n// Providers\nexport { GitHubTokenProvider } from \"./providers/base\"\nexport { CLITokenProvider } from \"./providers/cli\"\nexport { DeviceAuthProvider } from \"./providers/device-auth\"\nexport { EnvTokenProvider } from \"./providers/env\"\nexport { FileTokenProvider } from \"./providers/file\"\n// Types\nexport type { CopilotTokenInfo, TokenInfo, TokenSource, TokenValidationResult } from \"./types\"\n\nimport consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getGitHubUser } from \"~/lib/token/github-client\"\n\nimport { CopilotTokenManager } from \"./copilot-token-manager\"\nimport { GitHubTokenManager } from \"./github-token-manager\"\n\n/** Global manager instances */\nlet githubTokenManager: GitHubTokenManager | null = null\nlet copilotTokenManager: CopilotTokenManager | null = null\n\nexport interface InitTokenManagersOptions {\n /** Token provided via CLI --github-token argument */\n cliToken?: string\n}\n\n/**\n * Initialize the token management system.\n * This sets up both GitHub and Copilot token managers.\n */\nexport async function initTokenManagers(options: InitTokenManagersOptions = {}): Promise<{\n githubTokenManager: GitHubTokenManager\n copilotTokenManager: CopilotTokenManager\n}> {\n // Create GitHub token manager\n githubTokenManager = new GitHubTokenManager({\n cliToken: options.cliToken,\n validateOnInit: false, // We'll validate manually to show login info\n onTokenExpired: () => {\n consola.error(\"GitHub token has expired. Please run `copilot-api auth` to re-authenticate.\")\n },\n })\n\n // Get GitHub token\n const tokenInfo = await githubTokenManager.getToken()\n state.githubToken = tokenInfo.token\n state.tokenInfo = tokenInfo\n\n // Log token source\n const isExplicitToken = tokenInfo.source === \"cli\" || tokenInfo.source === \"env\"\n switch (tokenInfo.source) {\n case \"cli\": {\n consola.info(\"Using provided GitHub token (from CLI)\")\n\n break\n }\n case \"env\": {\n consola.info(\"Using GitHub token from environment variable\")\n\n break\n }\n case \"file\": {\n // File is the default, no need to log\n\n break\n }\n // No default\n }\n\n // Show token if configured\n if (state.showGitHubToken) {\n consola.info(\"GitHub token:\", tokenInfo.token)\n }\n\n // Validate and show user info\n // If the token was explicitly provided (CLI or env), give a clear error and abort on failure\n try {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n } catch (error) {\n if (isExplicitToken) {\n const source = tokenInfo.source === \"cli\" ? \"--github-token\" : \"environment variable\"\n consola.error(\n `The GitHub token provided via ${source} is invalid or expired.`,\n error instanceof Error ? error.message : error,\n )\n process.exit(1)\n }\n throw error\n }\n\n // Create Copilot token manager\n copilotTokenManager = new CopilotTokenManager({\n githubTokenManager,\n })\n\n // Initialize Copilot token\n // If the token was explicitly provided and Copilot rejects it, abort with clear error\n try {\n const copilotTokenInfo = await copilotTokenManager.initialize()\n state.copilotTokenInfo = copilotTokenInfo\n } catch (error) {\n if (isExplicitToken) {\n const source = tokenInfo.source === \"cli\" ? \"--github-token\" : \"environment variable\"\n consola.error(\n `The GitHub token provided via ${source} does not have Copilot access.`,\n error instanceof Error ? error.message : error,\n )\n process.exit(1)\n }\n throw error\n }\n\n return { githubTokenManager, copilotTokenManager }\n}\n\n/**\n * Get the global GitHub token manager instance.\n */\nexport function getGitHubTokenManager(): GitHubTokenManager | null {\n return githubTokenManager\n}\n\n/**\n * Get the global Copilot token manager instance.\n */\nexport function getCopilotTokenManager(): CopilotTokenManager | null {\n return copilotTokenManager\n}\n\n/**\n * Stop all token refresh timers.\n * Call this during cleanup/shutdown.\n */\nexport function stopTokenRefresh(): void {\n copilotTokenManager?.stopAutoRefresh()\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/config/paths\"\nimport { state } from \"./lib/state\"\nimport { DeviceAuthProvider, FileTokenProvider } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showGitHubToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showGitHubToken = options.showGitHubToken\n\n await ensurePaths()\n\n // Use DeviceAuthProvider directly for force authentication\n const deviceAuthProvider = new DeviceAuthProvider()\n const tokenInfo = await deviceAuthProvider.getToken()\n\n if (!tokenInfo) {\n throw new Error(\"Failed to obtain GitHub token via device authorization\")\n }\n\n // Validate and show user info\n const validation = await deviceAuthProvider.validate(tokenInfo.token)\n if (validation.valid) {\n consola.info(`Logged in as ${validation.username}`)\n }\n\n // File provider will have already saved the token during device auth\n // But we can verify the file exists\n const fileProvider = new FileTokenProvider()\n if (await fileProvider.isAvailable()) {\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n }\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-github-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showGitHubToken: args[\"show-github-token\"],\n })\n },\n})\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { state } from \"./lib/state\"\nimport { GitHubTokenManager } from \"./lib/token\"\nimport { getCopilotUsage, type QuotaDetail } from \"./lib/token/copilot-client\"\nimport { getGitHubUser } from \"./lib/token/github-client\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n\n // Show logged in user\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed = premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\"Completions\", usage.quota_snapshots.completions)\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { rebuildModelIndex, state } from \"~/lib/state\"\n\n/** Fetch models from Copilot API and cache in global state */\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n rebuildModelIndex()\n}\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface VisionLimits {\n max_prompt_image_size?: number\n max_prompt_images?: number\n supported_media_types?: Array<string>\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_non_streaming_output_tokens?: number\n max_inputs?: number\n vision?: VisionLimits\n}\n\ninterface ModelSupports {\n [key: string]: boolean | number | undefined\n}\n\ninterface ModelCapabilities {\n family?: string\n limits?: ModelLimits\n object?: string\n supports?: ModelSupports\n tokenizer?: string\n type?: string\n}\n\nexport interface Model {\n billing?: {\n is_premium?: boolean\n multiplier?: number\n restricted_to?: Array<string>\n }\n capabilities?: ModelCapabilities\n id: string\n model_picker_category?: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n supported_endpoints?: Array<string>\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { ensurePaths, PATHS } from \"./lib/config/paths\"\nimport { getModels } from \"./lib/models/client\"\nimport { state } from \"./lib/state\"\nimport { GitHubTokenManager } from \"./lib/token\"\nimport { getCopilotToken, getCopilotUsage } from \"./lib/token/copilot-client\"\nimport { getGitHubUser } from \"./lib/token/github-client\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n account?: {\n user: unknown\n copilot: unknown\n }\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise<boolean> {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getAccountInfo(): Promise<{\n user: unknown\n copilot: unknown\n} | null> {\n try {\n await ensurePaths()\n\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n\n if (!state.githubToken) return null\n\n const [user, copilot] = await Promise.all([getGitHubUser(), getCopilotUsage()])\n\n return { user, copilot }\n } catch {\n return null\n }\n}\n\nasync function getDebugInfo(includeAccount: boolean): Promise<DebugInfo> {\n const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()])\n\n const info: DebugInfo = {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n\n if (includeAccount && tokenExists) {\n const account = await getAccountInfo()\n if (account) {\n info.account = account\n }\n }\n\n return info\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n let output = `copilot-api debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`\n\n if (info.account) {\n output += `\n\nAccount Info:\n${JSON.stringify(info.account, null, 2)}`\n }\n\n consola.info(output)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise<void> {\n const debugInfo = await getDebugInfo(true)\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\n/** Subcommand: debug info (default behavior) */\nconst debugInfo = defineCommand({\n meta: {\n name: \"info\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({ json: args.json })\n },\n})\n\n/** Subcommand: debug models */\nconst debugModels = defineCommand({\n meta: {\n name: \"models\",\n description: \"Fetch and display raw model data from Copilot API\",\n },\n args: {\n \"account-type\": {\n type: \"string\",\n alias: \"a\",\n default: \"individual\",\n description: \"The type of GitHub account (individual, business, enterprise)\",\n },\n \"github-token\": {\n type: \"string\",\n alias: \"g\",\n description: \"GitHub token to use (skips interactive auth)\",\n },\n },\n async run({ args }) {\n state.accountType = args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\"\n\n await ensurePaths()\n\n if (args[\"github-token\"]) {\n state.githubToken = args[\"github-token\"]\n consola.info(\"Using provided GitHub token\")\n } else {\n // Use GitHubTokenManager to get token\n const tokenManager = new GitHubTokenManager()\n const tokenInfo = await tokenManager.getToken()\n state.githubToken = tokenInfo.token\n }\n\n // Get Copilot token without setting up refresh interval\n const { token } = await getCopilotToken()\n state.copilotToken = token\n\n const models = await getModels()\n\n console.log(JSON.stringify(models, null, 2))\n },\n})\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Debug commands for troubleshooting\",\n },\n subCommands: {\n info: debugInfo,\n models: debugModels,\n },\n})\n","import consola from \"consola\"\n\n/**\n * Adaptive Rate Limiter\n *\n * Normal mode: Full speed, no delay between requests\n * Rate-limited mode: Queue requests and process with exponential backoff\n * Gradual recovery: After recovery, slowly ramp up speed before full speed\n *\n * Mode transitions:\n * - Normal → Rate-limited: When a 429 error is detected\n * - Rate-limited → Recovering: After recovery timeout OR consecutive successes\n * - Recovering → Normal: After gradual speedup completes\n *\n * Features:\n * - Exponential backoff: Retry delays double each time (10s → 20s → 40s...)\n * - Retry-After support: Uses server-provided wait time if available\n * - Gradual recovery: Slowly ramps up speed after leaving rate-limited mode\n */\n\nexport interface AdaptiveRateLimiterConfig {\n /** Base interval for retries, doubles with each retry (default: 10s) */\n baseRetryIntervalSeconds: number\n /** Maximum retry interval cap (default: 120s) */\n maxRetryIntervalSeconds: number\n /** Interval between requests in rate-limited mode (default: 10s) */\n requestIntervalSeconds: number\n /** Time after which to attempt recovery to normal mode (default: 10 minutes) */\n recoveryTimeoutMinutes: number\n /** Number of consecutive successes needed to recover (default: 5) */\n consecutiveSuccessesForRecovery: number\n /** Gradual recovery steps: intervals to use before full speed (default: [5, 2, 1, 0]) */\n gradualRecoverySteps: Array<number>\n}\n\nconst DEFAULT_CONFIG: AdaptiveRateLimiterConfig = {\n baseRetryIntervalSeconds: 10,\n maxRetryIntervalSeconds: 120,\n requestIntervalSeconds: 10,\n recoveryTimeoutMinutes: 10,\n consecutiveSuccessesForRecovery: 5,\n gradualRecoverySteps: [5, 2, 1, 0], // 5s → 2s → 1s → full speed\n}\n\ninterface QueuedRequest<T> {\n execute: () => Promise<T>\n resolve: (value: T) => void\n reject: (error: unknown) => void\n retryCount: number\n /** Server-provided retry delay from Retry-After header */\n retryAfterSeconds?: number\n /** Timestamp when request was enqueued */\n enqueuedAt: number\n}\n\n/** Result wrapper that includes queue wait time */\nexport interface RateLimitedResult<T> {\n result: T\n /** Time spent waiting in queue (ms), 0 if not queued */\n queueWaitMs: number\n}\n\ntype RateLimiterMode = \"normal\" | \"rate-limited\" | \"recovering\"\n\n/**\n * Adaptive rate limiter that switches between normal, rate-limited, and recovering modes\n * based on API responses.\n */\nexport class AdaptiveRateLimiter {\n private config: AdaptiveRateLimiterConfig\n private mode: RateLimiterMode = \"normal\"\n private queue: Array<QueuedRequest<unknown>> = []\n private processing = false\n private rateLimitedAt: number | null = null\n private consecutiveSuccesses = 0\n private lastRequestTime = 0\n /** Current step in gradual recovery (index into gradualRecoverySteps) */\n private recoveryStepIndex = 0\n /** Abort controller for cancelling pending sleeps during shutdown */\n private sleepAbortController = new AbortController()\n\n constructor(config: Partial<AdaptiveRateLimiterConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * Execute a request with adaptive rate limiting.\n * Returns a promise that resolves when the request succeeds.\n * The request will be retried automatically on 429 errors.\n */\n async execute<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n if (this.mode === \"normal\") {\n return this.executeInNormalMode(fn)\n }\n if (this.mode === \"recovering\") {\n return this.executeInRecoveringMode(fn)\n }\n return this.enqueue(fn)\n }\n\n /**\n * Check if an error is a rate limit error (429) and extract Retry-After if available\n */\n isRateLimitError(error: unknown): {\n isRateLimit: boolean\n retryAfter?: number\n } {\n if (error && typeof error === \"object\") {\n // Check HTTPError\n if (\"status\" in error && error.status === 429) {\n // Try to extract Retry-After from response headers or body\n const retryAfter = this.extractRetryAfter(error)\n return { isRateLimit: true, retryAfter }\n }\n // Check nested error structure from Copilot\n if (\"responseText\" in error && typeof error.responseText === \"string\") {\n try {\n const parsed: unknown = JSON.parse(error.responseText)\n if (\n parsed\n && typeof parsed === \"object\"\n && \"error\" in parsed\n && parsed.error\n && typeof parsed.error === \"object\"\n && \"code\" in parsed.error\n && parsed.error.code === \"rate_limited\"\n ) {\n return { isRateLimit: true }\n }\n } catch {\n // Not JSON, ignore\n }\n }\n }\n return { isRateLimit: false }\n }\n\n /**\n * Extract Retry-After value from error response\n */\n private extractRetryAfter(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined\n\n // Check responseText for JSON with retry_after field\n if (\"responseText\" in error && typeof error.responseText === \"string\") {\n try {\n const parsed: unknown = JSON.parse(error.responseText)\n if (parsed && typeof parsed === \"object\" && \"retry_after\" in parsed && typeof parsed.retry_after === \"number\") {\n return parsed.retry_after\n }\n // Also check nested error.retry_after\n if (\n parsed\n && typeof parsed === \"object\"\n && \"error\" in parsed\n && parsed.error\n && typeof parsed.error === \"object\"\n && \"retry_after\" in parsed.error\n && typeof parsed.error.retry_after === \"number\"\n ) {\n return parsed.error.retry_after\n }\n } catch {\n // Not JSON, ignore\n }\n }\n\n return undefined\n }\n\n /**\n * Execute in normal mode - full speed\n */\n private async executeInNormalMode<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n try {\n const result = await fn()\n return { result, queueWaitMs: 0 }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n this.enterRateLimitedMode()\n // Queue this request for retry instead of failing\n return this.enqueue(fn, retryAfter)\n }\n throw error\n }\n }\n\n /**\n * Execute in recovering mode - gradual speedup\n */\n private async executeInRecoveringMode<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n const startTime = Date.now()\n const currentInterval = this.config.gradualRecoverySteps[this.recoveryStepIndex] ?? 0\n\n // Wait for the current recovery interval\n if (currentInterval > 0) {\n const now = Date.now()\n const elapsedMs = now - this.lastRequestTime\n const requiredMs = currentInterval * 1000\n\n if (this.lastRequestTime > 0 && elapsedMs < requiredMs) {\n const waitMs = requiredMs - elapsedMs\n await this.sleep(waitMs)\n }\n }\n\n this.lastRequestTime = Date.now()\n\n try {\n const result = await fn()\n\n // Success - advance recovery step\n this.recoveryStepIndex++\n if (this.recoveryStepIndex >= this.config.gradualRecoverySteps.length) {\n this.completeRecovery()\n } else {\n const nextInterval = this.config.gradualRecoverySteps[this.recoveryStepIndex] ?? 0\n consola.info(\n `[RateLimiter] Ramp-up step ${this.recoveryStepIndex}/${this.config.gradualRecoverySteps.length} `\n + `(next interval: ${nextInterval}s)`,\n )\n }\n\n const queueWaitMs = Date.now() - startTime\n return { result, queueWaitMs }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n // Back to rate-limited mode\n consola.warn(\"[RateLimiter] Hit rate limit during ramp-up, returning to rate-limited mode\")\n this.enterRateLimitedMode()\n return this.enqueue(fn, retryAfter)\n }\n throw error\n }\n }\n\n /**\n * Enter rate-limited mode\n */\n private enterRateLimitedMode(): void {\n if (this.mode === \"rate-limited\") return\n\n this.mode = \"rate-limited\"\n this.rateLimitedAt = Date.now()\n this.consecutiveSuccesses = 0\n\n consola.warn(\n `[RateLimiter] Entering rate-limited mode. `\n + `Requests will be queued with exponential backoff (base: ${this.config.baseRetryIntervalSeconds}s).`,\n )\n }\n\n /**\n * Check if we should try to recover to normal mode\n */\n private shouldAttemptRecovery(): boolean {\n // Check consecutive successes\n if (this.consecutiveSuccesses >= this.config.consecutiveSuccessesForRecovery) {\n consola.info(`[RateLimiter] ${this.consecutiveSuccesses} consecutive successes. Starting ramp-up.`)\n return true\n }\n\n // Check timeout\n if (this.rateLimitedAt) {\n const elapsed = Date.now() - this.rateLimitedAt\n const timeout = this.config.recoveryTimeoutMinutes * 60 * 1000\n if (elapsed >= timeout) {\n consola.info(`[RateLimiter] ${this.config.recoveryTimeoutMinutes} minutes elapsed. Starting ramp-up.`)\n return true\n }\n }\n\n return false\n }\n\n /**\n * Start gradual recovery mode\n */\n private startGradualRecovery(): void {\n this.mode = \"recovering\"\n this.recoveryStepIndex = 0\n this.rateLimitedAt = null\n this.consecutiveSuccesses = 0\n\n const firstInterval = this.config.gradualRecoverySteps[0] ?? 0\n consola.info(\n `[RateLimiter] Starting ramp-up (${this.config.gradualRecoverySteps.length} steps, `\n + `first interval: ${firstInterval}s)`,\n )\n }\n\n /**\n * Complete recovery to normal mode\n */\n private completeRecovery(): void {\n this.mode = \"normal\"\n this.recoveryStepIndex = 0\n\n consola.success(\"[RateLimiter] Exiting rate-limited mode.\")\n }\n\n /**\n * Enqueue a request for later execution\n */\n private enqueue<T>(fn: () => Promise<T>, retryAfterSeconds?: number): Promise<RateLimitedResult<T>> {\n return new Promise<RateLimitedResult<T>>((resolve, reject) => {\n const request: QueuedRequest<unknown> = {\n execute: fn as () => Promise<unknown>,\n resolve: resolve as (value: unknown) => void,\n reject,\n retryCount: 0,\n retryAfterSeconds,\n enqueuedAt: Date.now(),\n }\n\n this.queue.push(request)\n\n if (this.queue.length > 1) {\n const position = this.queue.length\n const estimatedWait = (position - 1) * this.config.requestIntervalSeconds\n consola.info(`[RateLimiter] Request queued (position ${position}, ~${estimatedWait}s wait)`)\n }\n\n void this.processQueue()\n })\n }\n\n /**\n * Calculate retry interval with exponential backoff\n */\n private calculateRetryInterval(request: QueuedRequest<unknown>): number {\n // Use server-provided Retry-After if available\n if (request.retryAfterSeconds !== undefined && request.retryAfterSeconds > 0) {\n return request.retryAfterSeconds\n }\n\n // Exponential backoff: base * 2^(retryCount-1), capped at max\n const backoff = this.config.baseRetryIntervalSeconds * Math.pow(2, request.retryCount)\n return Math.min(backoff, this.config.maxRetryIntervalSeconds)\n }\n\n /**\n * Process the queue\n */\n private async processQueue(): Promise<void> {\n if (this.processing) return\n this.processing = true\n\n while (this.queue.length > 0) {\n const request = this.queue[0]\n\n // Check if we should try recovery before processing\n if (this.shouldAttemptRecovery()) {\n this.startGradualRecovery()\n // Continue processing remaining queue items in recovering mode\n // But first, let the current queue drain\n }\n\n // Calculate wait time based on whether this is a retry or new request\n const now = Date.now()\n const elapsedMs = now - this.lastRequestTime\n const intervalSeconds =\n request.retryCount > 0 ? this.calculateRetryInterval(request) : this.config.requestIntervalSeconds\n const requiredMs = intervalSeconds * 1000\n\n if (this.lastRequestTime > 0 && elapsedMs < requiredMs) {\n const waitMs = requiredMs - elapsedMs\n const waitSec = Math.ceil(waitMs / 1000)\n consola.info(`[RateLimiter] Waiting ${waitSec}s before next request...`)\n await this.sleep(waitMs)\n }\n\n this.lastRequestTime = Date.now()\n\n try {\n const result = await request.execute()\n\n // Success!\n this.queue.shift()\n this.consecutiveSuccesses++\n // Clear retry-after on success\n request.retryAfterSeconds = undefined\n // Calculate queue wait time\n const queueWaitMs = Date.now() - request.enqueuedAt\n request.resolve({ result, queueWaitMs })\n\n if (this.mode === \"rate-limited\") {\n consola.info(\n `[RateLimiter] Request succeeded (${this.consecutiveSuccesses}/${this.config.consecutiveSuccessesForRecovery} for ramp-up)`,\n )\n }\n } catch (error) {\n const { isRateLimit, retryAfter } = this.isRateLimitError(error)\n if (isRateLimit) {\n // Still rate limited, retry with exponential backoff\n request.retryCount++\n request.retryAfterSeconds = retryAfter\n this.consecutiveSuccesses = 0\n this.rateLimitedAt = Date.now() // Reset timeout\n\n const nextInterval = this.calculateRetryInterval(request)\n const source = retryAfter ? \"server Retry-After\" : \"exponential backoff\"\n consola.warn(\n `[RateLimiter] Request failed with 429 (retry #${request.retryCount}). `\n + `Retrying in ${nextInterval}s (${source})...`,\n )\n } else {\n // Other error, fail this request and continue with queue\n this.queue.shift()\n request.reject(error)\n }\n }\n }\n\n this.processing = false\n\n // If queue is empty and we're in rate-limited mode, stay in that mode\n // until recovery conditions are met on next request\n }\n\n /**\n * Reject all queued requests immediately.\n * Called during shutdown Phase 1 to drain the queue so queued requests\n * don't waste drain time. Returns the number of rejected requests.\n */\n rejectQueued(): number {\n const count = this.queue.length\n while (this.queue.length > 0) {\n const request = this.queue.shift()\n if (!request) break\n request.reject(new Error(\"Server shutting down\"))\n }\n this.processing = false\n\n // Cancel any pending sleep (processQueue or executeInRecoveringMode)\n this.sleepAbortController.abort()\n this.sleepAbortController = new AbortController()\n\n return count\n }\n\n private sleep(ms: number): Promise<void> {\n const signal = this.sleepAbortController.signal\n if (signal.aborted) return Promise.resolve()\n\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms)\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer)\n resolve()\n },\n { once: true },\n )\n })\n }\n\n /**\n * Get current status for debugging/monitoring\n */\n getStatus(): {\n mode: RateLimiterMode\n queueLength: number\n consecutiveSuccesses: number\n rateLimitedAt: number | null\n } {\n return {\n mode: this.mode,\n queueLength: this.queue.length,\n consecutiveSuccesses: this.consecutiveSuccesses,\n rateLimitedAt: this.rateLimitedAt,\n }\n }\n}\n\n/** Singleton instance */\nlet rateLimiterInstance: AdaptiveRateLimiter | null = null\n\n/**\n * Initialize the adaptive rate limiter with configuration\n */\nexport function initAdaptiveRateLimiter(config: Partial<AdaptiveRateLimiterConfig> = {}): void {\n rateLimiterInstance = new AdaptiveRateLimiter(config)\n\n const baseRetry = config.baseRetryIntervalSeconds ?? DEFAULT_CONFIG.baseRetryIntervalSeconds\n const maxRetry = config.maxRetryIntervalSeconds ?? DEFAULT_CONFIG.maxRetryIntervalSeconds\n const interval = config.requestIntervalSeconds ?? DEFAULT_CONFIG.requestIntervalSeconds\n const recovery = config.recoveryTimeoutMinutes ?? DEFAULT_CONFIG.recoveryTimeoutMinutes\n const successes = config.consecutiveSuccessesForRecovery ?? DEFAULT_CONFIG.consecutiveSuccessesForRecovery\n const steps = config.gradualRecoverySteps ?? DEFAULT_CONFIG.gradualRecoverySteps\n\n consola.info(\n `[RateLimiter] Initialized (backoff: ${baseRetry}s-${maxRetry}s, `\n + `interval: ${interval}s, recovery: ${recovery}min or ${successes} successes, `\n + `gradual: [${steps.join(\"s, \")}s])`,\n )\n}\n\n/**\n * Get the rate limiter instance\n */\nexport function getAdaptiveRateLimiter(): AdaptiveRateLimiter | null {\n return rateLimiterInstance\n}\n\n/**\n * Reset the rate limiter singleton (for testing only).\n */\nexport function resetAdaptiveRateLimiter(): void {\n rateLimiterInstance = null\n}\n\n/**\n * Execute a request with adaptive rate limiting.\n * If rate limiter is not initialized, executes immediately.\n * Returns the result along with queue wait time.\n */\nexport async function executeWithAdaptiveRateLimit<T>(fn: () => Promise<T>): Promise<RateLimitedResult<T>> {\n if (!rateLimiterInstance) {\n const result = await fn()\n return { result, queueWaitMs: 0 }\n }\n return rateLimiterInstance.execute(fn)\n}\n","/**\n * Unified model name resolution and normalization.\n *\n * Handles short aliases (opus/sonnet/haiku), versioned names with date suffixes,\n * hyphenated versions (claude-opus-4-6 → claude-opus-4.6), model overrides,\n * and family-level fallbacks.\n */\n\nimport { DEFAULT_MODEL_OVERRIDES, state } from \"~/lib/state\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ModelFamily = \"opus\" | \"sonnet\" | \"haiku\"\n\n// ============================================================================\n// Model Preference Lists\n// ============================================================================\n\n/** Preferred model order per family, highest priority first. */\nexport const MODEL_PREFERENCE: Record<ModelFamily, Array<string>> = {\n opus: [\n \"claude-opus-4.6\",\n \"claude-opus-4.5\",\n \"claude-opus-41\", // 4.1\n // \"claude-opus-4\",\n ],\n sonnet: [\n \"claude-sonnet-4.6\",\n \"claude-sonnet-4.5\",\n \"claude-sonnet-4\",\n // \"claude-sonnet-3.5\",\n ],\n haiku: [\n \"claude-haiku-4.5\",\n // \"claude-haiku-3.5\",\n ],\n}\n\n// ============================================================================\n// Normalization and Detection\n// ============================================================================\n\n/** Pre-compiled regex: claude-{family}-{major}-{minor}[-YYYYMMDD] */\nconst VERSIONED_RE = /^(claude-(?:opus|sonnet|haiku))-(\\d+)-(\\d{1,2})(?:-\\d{8,})?$/\n\n/** Pre-compiled regex: claude-{family}-{major}-YYYYMMDD (date-only suffix) */\nconst DATE_ONLY_RE = /^(claude-(opus|sonnet|haiku)-\\d+)-\\d{8,}$/\n\n/**\n * Normalize model ID for matching: lowercase and replace dots with dashes.\n * e.g. \"claude-sonnet-4.5\" → \"claude-sonnet-4-5\"\n *\n * Used for feature detection (startsWith matching), NOT for API calls.\n */\nexport function normalizeForMatching(modelId: string): string {\n return modelId.toLowerCase().replaceAll(\".\", \"-\")\n}\n\n/**\n * Normalize a model ID to canonical dot-version form.\n * e.g. \"claude-opus-4-6\" → \"claude-opus-4.6\", \"claude-opus-4-6-1m\" → \"claude-opus-4.6-1m\"\n *\n * Handles modifier suffixes (-fast, -1m) and strips date suffixes (-YYYYMMDD).\n * Non-Claude models or unrecognized patterns are returned as-is.\n *\n * Used for normalizing API response model names to match `/models` endpoint IDs.\n */\nexport function normalizeModelId(modelId: string): string {\n const { base, suffix } = extractModifierSuffix(modelId)\n const versionedMatch = base.match(VERSIONED_RE)\n if (versionedMatch) {\n return `${versionedMatch[1]}-${versionedMatch[2]}.${versionedMatch[3]}${suffix}`\n }\n return modelId\n}\n\n/** Extract the model family from a model ID. */\nexport function getModelFamily(modelId: string): ModelFamily | undefined {\n const normalized = normalizeForMatching(modelId)\n if (normalized.includes(\"opus\")) return \"opus\"\n if (normalized.includes(\"sonnet\")) return \"sonnet\"\n if (normalized.includes(\"haiku\")) return \"haiku\"\n return undefined\n}\n\n/** Check if a model ID belongs to the Sonnet family. */\nexport function isSonnetModel(modelId: string): boolean {\n return getModelFamily(modelId) === \"sonnet\"\n}\n\n/** Check if a model ID belongs to the Opus family. */\nexport function isOpusModel(modelId: string): boolean {\n return getModelFamily(modelId) === \"opus\"\n}\n\n// ============================================================================\n// Model Resolution\n// ============================================================================\n\n/**\n * Find the best available model for a family by checking the preference list\n * against actually available models. Returns the first match, or the top\n * preference as fallback when state.models is unavailable.\n */\nexport function findPreferredModel(family: string): string {\n const preference = MODEL_PREFERENCE[family as ModelFamily]\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for arbitrary family strings\n if (!preference) return family\n\n if (state.modelIds.size === 0) {\n return preference[0]\n }\n\n for (const candidate of preference) {\n if (state.modelIds.has(candidate)) {\n return candidate\n }\n }\n\n return preference[0]\n}\n\n/** Known model modifier suffixes (e.g., \"-fast\" for fast output mode, \"-1m\" for 1M context). */\nconst KNOWN_MODIFIERS = [\"-fast\", \"-1m\"]\n\n/**\n * Extract known modifier suffix from a model name.\n * e.g. \"claude-opus-4-6-fast\" → { base: \"claude-opus-4-6\", suffix: \"-fast\" }\n */\nfunction extractModifierSuffix(model: string): { base: string; suffix: string } {\n const lower = model.toLowerCase()\n for (const modifier of KNOWN_MODIFIERS) {\n if (lower.endsWith(modifier)) {\n return { base: model.slice(0, -modifier.length), suffix: modifier }\n }\n }\n return { base: model, suffix: \"\" }\n}\n\n/**\n * Normalize bracket notation to hyphen suffix.\n * Claude Code CLI sends model keys like \"opus[1m]\" or \"claude-opus-4.6[1m]\".\n * This converts them to the standard hyphen form: \"opus-1m\", \"claude-opus-4.6-1m\".\n */\nfunction normalizeBracketNotation(model: string): string {\n const match = model.match(/^([^[]+)\\[([^\\]]+)\\]$/)\n if (!match) return model\n return `${match[1]}-${match[2].toLowerCase()}`\n}\n\n/**\n * Resolve a model name to its canonical form, then apply overrides.\n *\n * Override matching order:\n * 1. Check the raw (original) model name against state.modelOverrides\n * 2. Resolve via alias/normalization (resolveModelNameCore)\n * 3. If resolved name differs from raw, check resolved name against overrides\n * 4. Check if the model's family (opus/sonnet/haiku) has an override\n *\n * This is the main entry point for route handlers.\n */\nexport function resolveModelName(model: string): string {\n // 0. Normalize bracket notation: \"opus[1m]\" → \"opus-1m\"\n const normalized = normalizeBracketNotation(model)\n\n // 1. Check raw model name against overrides first\n const rawOverride = state.modelOverrides[normalized]\n if (rawOverride) {\n return resolveOverrideTarget(normalized, rawOverride)\n }\n\n // 2. Normal alias/normalization resolution\n const resolved = resolveModelNameCore(normalized)\n\n // 3. If resolved name is different, check it against overrides too\n if (resolved !== normalized) {\n const resolvedOverride = state.modelOverrides[resolved]\n if (resolvedOverride) {\n return resolveOverrideTarget(resolved, resolvedOverride)\n }\n }\n\n // 4. Check if the model's family has a user-customized override\n // Last-resort fallback: only applies when steps 1-3 didn't match.\n // Propagates to ALL family members regardless of target family.\n // e.g., opus → claude-opus-4.6-1m: claude-opus-4-6 also redirects\n // e.g., sonnet → opus: claude-sonnet-4 also redirects (cross-family)\n // Only skipped when override equals the built-in default (pure alias, not redirection).\n const family = getModelFamily(resolved)\n if (family) {\n const familyOverride = state.modelOverrides[family]\n if (familyOverride && familyOverride !== DEFAULT_MODEL_OVERRIDES[family]) {\n const familyResolved = resolveOverrideTarget(family, familyOverride)\n if (familyResolved !== resolved) {\n return familyResolved\n }\n }\n }\n\n return resolved\n}\n\n/**\n * Resolve override target: if target is directly available, use it;\n * otherwise check for chained overrides, then treat as alias.\n * If still unavailable, fall back to the best available model in the same family.\n *\n * Uses `seen` set to prevent circular override chains.\n */\nfunction resolveOverrideTarget(source: string, target: string, seen?: Set<string>): string {\n if (state.modelIds.size === 0 || state.modelIds.has(target)) {\n return target\n }\n\n // Check if target itself has an override (chained overrides: sonnet → opus → claude-opus-4.6-1m)\n const visited = seen ?? new Set([source])\n const targetOverride = state.modelOverrides[target]\n if (targetOverride && !visited.has(target)) {\n visited.add(target)\n return resolveOverrideTarget(target, targetOverride, visited)\n }\n\n // Target not directly available — might be an alias, resolve it\n const resolved = resolveModelNameCore(target)\n if (resolved !== target) {\n return resolved\n }\n\n // Still not resolved — check if target belongs to a known family and find best available\n const family = getModelFamily(target)\n if (family) {\n const preferred = findPreferredModel(family)\n if (preferred !== target) {\n return preferred\n }\n }\n\n // Can't resolve further — use target as-is\n return target\n}\n\n/**\n * Core model name resolution (without overrides).\n *\n * Handles:\n * 1. Modifier suffixes: \"claude-opus-4-6-fast\" → \"claude-opus-4.6-fast\"\n * 2. Short aliases: \"opus\" → best available opus\n * 3. Hyphenated versions: \"claude-opus-4-6\" → \"claude-opus-4.6\"\n * 4. Date suffixes: \"claude-opus-4-20250514\" → best opus\n */\nfunction resolveModelNameCore(model: string): string {\n // Extract modifier suffix (e.g., \"-fast\") before resolution\n const { base, suffix } = extractModifierSuffix(model)\n\n // Resolve the base model name\n const resolvedBase = resolveBase(base)\n\n // Re-attach suffix and validate availability\n if (suffix) {\n const withSuffix = resolvedBase + suffix\n if (state.modelIds.size === 0 || state.modelIds.has(withSuffix)) {\n return withSuffix\n }\n // Suffixed variant not available, fall back to base\n return resolvedBase\n }\n\n return resolvedBase\n}\n\n/** Resolve a base model name (without modifier suffix) to its canonical form. */\nfunction resolveBase(model: string): string {\n // 1. Short alias: \"opus\" → best opus\n if (model in MODEL_PREFERENCE) {\n return findPreferredModel(model)\n }\n\n // 2. Hyphenated: claude-opus-4-6 or claude-opus-4-6-20250514 → claude-opus-4.6\n // Pattern: claude-{family}-{major}-{minor}[-YYYYMMDD]\n // Minor version is 1-2 digits; date suffix is 8+ digits\n const versionedMatch = model.match(VERSIONED_RE)\n if (versionedMatch) {\n const dotModel = `${versionedMatch[1]}-${versionedMatch[2]}.${versionedMatch[3]}`\n if (state.modelIds.size === 0 || state.modelIds.has(dotModel)) {\n return dotModel\n }\n }\n\n // 3. Date-only suffix: claude-{family}-{major}-YYYYMMDD → base model or best family\n const dateOnlyMatch = model.match(DATE_ONLY_RE)\n if (dateOnlyMatch) {\n const baseModel = dateOnlyMatch[1]\n const family = dateOnlyMatch[2]\n if (state.modelIds.has(baseModel)) {\n return baseModel\n }\n return findPreferredModel(family)\n }\n\n return model\n}\n","/**\n * RequestContext — Complete active representation of a request\n *\n * Holds all data from request entry to completion. Independent of the history\n * system — history is a consumer of RequestContext through events.\n * Each retry creates a new Attempt in the attempts array.\n */\n\nimport type { ApiError } from \"~/lib/error\"\nimport type { EndpointType, PreprocessInfo, RewriteInfo, SanitizationInfo, SseEventRecord } from \"~/lib/history/store\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { getErrorMessage } from \"~/lib/error\"\nimport { normalizeModelId } from \"~/lib/models/resolver\"\n\n// ─── Request State Machine ───\n\nexport type RequestState =\n | \"pending\" // Just created, not yet started\n | \"sanitizing\" // Sanitizing messages\n | \"executing\" // Executing API call\n | \"retrying\" // Retrying (429 wait or 413 truncation)\n | \"streaming\" // Streaming response in progress\n | \"completed\" // Successfully completed\n | \"failed\" // Failed\n\n// ─── Three-Part Data Model ───\n\n/** 1. Original request: client's raw payload (one per request, immutable) */\nexport interface OriginalRequest {\n model: string\n messages: Array<unknown>\n stream: boolean\n tools?: Array<unknown>\n system?: unknown\n payload: unknown\n}\n\n/** 2. Effective request: what's sent to upstream API (per attempt, may differ) */\nexport interface EffectiveRequest {\n model: string\n resolvedModel: Model | undefined\n messages: Array<unknown>\n payload: unknown\n format: EndpointType\n}\n\n/** 3. Response data: upstream API response (per attempt) */\nexport interface ResponseData {\n success: boolean\n model: string\n usage: {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n }\n content: unknown\n stop_reason?: string\n toolCalls?: Array<unknown>\n error?: string\n /** HTTP status code from upstream (only on error) */\n status?: number\n /** Raw response body from upstream (only on error, for post-mortem debugging) */\n responseText?: string\n}\n\n// ─── Attempt ───\n\n/** A single API call attempt (each retry produces a new Attempt) */\nexport interface Attempt {\n index: number\n effectiveRequest: EffectiveRequest | null\n response: ResponseData | null\n error: ApiError | null\n /** Strategy that triggered this retry (undefined for first attempt) */\n strategy?: string\n sanitization?: SanitizationState\n truncation?: TruncationState\n /** Wait time before this retry (rate-limit) */\n waitMs?: number\n startTime: number\n durationMs: number\n}\n\n// ─── Pipeline Processing State ───\n\nexport interface SanitizationState {\n removedCount: number\n systemReminderRemovals: number\n orphanedToolUseCount?: number\n orphanedToolResultCount?: number\n fixedNameCount?: number\n emptyTextBlocksRemoved?: number\n}\n\nexport interface TruncationState {\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n processingTimeMs: number\n}\n\n// ─── History Entry Data ───\n\n/** Serialized form of a completed request (decoupled from history store) */\nexport interface HistoryEntryData {\n id: string\n endpoint: EndpointType\n timestamp: number\n durationMs: number\n sessionId?: string\n request: {\n model?: string\n messages?: Array<unknown>\n stream?: boolean\n tools?: Array<unknown>\n system?: unknown\n max_tokens?: number\n temperature?: number\n }\n response?: ResponseData\n truncation?: TruncationState\n rewrites?: RewriteInfo\n sseEvents?: Array<SseEventRecord>\n attempts?: Array<{\n index: number\n strategy?: string\n durationMs: number\n error?: string\n truncation?: TruncationState\n }>\n}\n\n// ─── Stream Accumulator Result ───\n\n/** Data extracted from a stream accumulator for completeFromStream */\nexport interface StreamAccumulatorResult {\n model: string\n content: string\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreationTokens: number\n stopReason: string\n contentBlocks: Array<{\n type: string\n text?: string\n thinking?: string\n id?: string\n name?: string\n input?: unknown\n tool_use_id?: string\n content?: unknown\n }>\n}\n\n// ─── RequestContext Event ───\n\nexport interface RequestContextEventData {\n type: string\n context: RequestContext\n previousState?: RequestState\n field?: string\n meta?: Record<string, unknown>\n entry?: HistoryEntryData\n}\n\nexport type RequestContextEventCallback = (event: RequestContextEventData) => void\n\n// ─── RequestContext Interface ───\n\nexport interface RequestContext {\n // --- Identity + State ---\n readonly id: string\n readonly tuiLogId: string | undefined\n readonly startTime: number\n readonly endpoint: EndpointType\n readonly state: RequestState\n readonly durationMs: number\n /** Whether this context has been settled (completed or failed). Handler code can check this to detect reaper force-fail. */\n readonly settled: boolean\n\n // --- Top-level Data ---\n readonly originalRequest: OriginalRequest | null\n readonly response: ResponseData | null\n readonly rewrites: RewriteInfo | null\n readonly preprocessInfo: PreprocessInfo | null\n\n // --- Attempts ---\n readonly attempts: ReadonlyArray<Attempt>\n readonly currentAttempt: Attempt | null\n readonly queueWaitMs: number\n\n // --- Mutation Methods ---\n setOriginalRequest(req: OriginalRequest): void\n setPreprocessInfo(info: PreprocessInfo): void\n addSanitizationInfo(info: SanitizationInfo): void\n setRewrites(info: RewriteInfo): void\n setSseEvents(events: Array<SseEventRecord>): void\n beginAttempt(opts: { strategy?: string; waitMs?: number; truncation?: TruncationState }): void\n setAttemptSanitization(info: SanitizationState): void\n setAttemptEffectiveRequest(req: EffectiveRequest): void\n setAttemptResponse(response: ResponseData): void\n setAttemptError(error: ApiError): void\n addQueueWaitMs(ms: number): void\n transition(newState: RequestState, meta?: Record<string, unknown>): void\n complete(response: ResponseData): void\n completeFromStream(acc: StreamAccumulatorResult): void\n fail(model: string, error: unknown): void\n toHistoryEntry(): HistoryEntryData\n}\n\n// ─── Implementation ───\n\nlet idCounter = 0\n\nexport function createRequestContext(opts: {\n endpoint: EndpointType\n tuiLogId?: string\n onEvent: RequestContextEventCallback\n}): RequestContext {\n const id = `req_${Date.now()}_${++idCounter}`\n const startTime = Date.now()\n const onEvent = opts.onEvent\n\n // Mutable internal state\n let _state: RequestState = \"pending\"\n let _originalRequest: OriginalRequest | null = null\n let _response: ResponseData | null = null\n let _rewrites: RewriteInfo | null = null\n let _preprocessInfo: PreprocessInfo | null = null\n let _sseEvents: Array<SseEventRecord> | null = null\n const _sanitizationHistory: Array<SanitizationInfo> = []\n let _queueWaitMs = 0\n const _attempts: Array<Attempt> = []\n /** Guard: once complete() or fail() is called, subsequent calls are no-ops */\n let settled = false\n\n function emit(event: RequestContextEventData) {\n try {\n onEvent(event)\n } catch {\n // Swallow event handler errors\n }\n }\n\n const ctx: RequestContext = {\n id,\n tuiLogId: opts.tuiLogId,\n startTime,\n endpoint: opts.endpoint,\n\n get state() {\n return _state\n },\n get durationMs() {\n return Date.now() - startTime\n },\n get settled() {\n return settled\n },\n get originalRequest() {\n return _originalRequest\n },\n get response() {\n return _response\n },\n get rewrites() {\n return _rewrites\n },\n get preprocessInfo() {\n return _preprocessInfo\n },\n get attempts() {\n return _attempts\n },\n get currentAttempt() {\n return _attempts.at(-1) ?? null\n },\n get queueWaitMs() {\n return _queueWaitMs\n },\n\n setOriginalRequest(req: OriginalRequest) {\n _originalRequest = req\n emit({ type: \"updated\", context: ctx, field: \"originalRequest\" })\n },\n\n setPreprocessInfo(info: PreprocessInfo) {\n _preprocessInfo = info\n },\n\n addSanitizationInfo(info: SanitizationInfo) {\n _sanitizationHistory.push(info)\n },\n\n setRewrites(info: RewriteInfo) {\n _rewrites = {\n ...(_preprocessInfo && { preprocessing: _preprocessInfo }),\n ...(_sanitizationHistory.length > 0 && { sanitization: _sanitizationHistory }),\n ...info,\n }\n emit({ type: \"updated\", context: ctx, field: \"rewrites\" })\n },\n\n setSseEvents(events: Array<SseEventRecord>) {\n _sseEvents = events.length > 0 ? events : null\n },\n\n beginAttempt(attemptOpts: { strategy?: string; waitMs?: number; truncation?: TruncationState }) {\n const attempt: Attempt = {\n index: _attempts.length,\n effectiveRequest: null, // Set later via setAttemptEffectiveRequest\n response: null,\n error: null,\n strategy: attemptOpts.strategy,\n truncation: attemptOpts.truncation,\n waitMs: attemptOpts.waitMs,\n startTime: Date.now(),\n durationMs: 0,\n }\n _attempts.push(attempt)\n emit({ type: \"updated\", context: ctx, field: \"attempts\" })\n },\n\n setAttemptSanitization(info: SanitizationState) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.sanitization = info\n }\n },\n\n setAttemptEffectiveRequest(req: EffectiveRequest) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.effectiveRequest = req\n }\n },\n\n setAttemptResponse(response: ResponseData) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.response = response\n attempt.durationMs = Date.now() - attempt.startTime\n }\n },\n\n setAttemptError(error: ApiError) {\n const attempt = ctx.currentAttempt\n if (attempt) {\n attempt.error = error\n attempt.durationMs = Date.now() - attempt.startTime\n }\n },\n\n addQueueWaitMs(ms: number) {\n _queueWaitMs += ms\n },\n\n transition(newState: RequestState, meta?: Record<string, unknown>) {\n const previousState = _state\n _state = newState\n emit({ type: \"state_changed\", context: ctx, previousState, meta })\n },\n\n complete(response: ResponseData) {\n if (settled) return\n settled = true\n\n // Normalize response model to canonical dot-version form\n // (API may return \"claude-opus-4-6\" instead of \"claude-opus-4.6\")\n if (response.model) response.model = normalizeModelId(response.model)\n _response = response\n ctx.setAttemptResponse(response)\n _state = \"completed\"\n const entry = ctx.toHistoryEntry()\n emit({ type: \"completed\", context: ctx, entry })\n },\n\n completeFromStream(acc: StreamAccumulatorResult) {\n const response: ResponseData = {\n success: true,\n model: acc.model,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cacheReadTokens > 0 && { cache_read_input_tokens: acc.cacheReadTokens }),\n ...(acc.cacheCreationTokens > 0 && { cache_creation_input_tokens: acc.cacheCreationTokens }),\n },\n content: acc.contentBlocks.length > 0 ? { role: \"assistant\", content: acc.contentBlocks } : null,\n stop_reason: acc.stopReason || undefined,\n }\n\n ctx.complete(response)\n },\n\n fail(model: string, error: unknown) {\n if (settled) return\n settled = true\n\n const errorMsg = getErrorMessage(error)\n _response = {\n success: false,\n model: normalizeModelId(model),\n usage: { input_tokens: 0, output_tokens: 0 },\n error: errorMsg,\n content: null,\n }\n\n // Preserve upstream HTTP error details as structured fields\n if (\n error instanceof Error\n && \"responseText\" in error\n && typeof (error as { responseText: unknown }).responseText === \"string\"\n ) {\n const responseText = (error as { responseText: string }).responseText\n if (responseText) {\n _response.responseText = responseText\n }\n }\n if (error instanceof Error && \"status\" in error && typeof (error as { status: unknown }).status === \"number\") {\n _response.status = (error as { status: number }).status\n }\n\n _state = \"failed\"\n const entry = ctx.toHistoryEntry()\n emit({ type: \"failed\", context: ctx, entry })\n },\n\n toHistoryEntry(): HistoryEntryData {\n const entry: HistoryEntryData = {\n id,\n endpoint: opts.endpoint,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n request: {\n model: _originalRequest?.model,\n messages: _originalRequest?.messages,\n stream: _originalRequest?.stream,\n tools: _originalRequest?.tools,\n system: _originalRequest?.system,\n },\n }\n\n if (_response) {\n entry.response = _response\n }\n\n // Find truncation from the last attempt that had one\n const lastTruncation = _attempts.findLast((a) => a.truncation)?.truncation\n if (lastTruncation) {\n entry.truncation = lastTruncation\n }\n\n if (_rewrites) {\n entry.rewrites = _rewrites\n }\n\n if (_sseEvents) {\n entry.sseEvents = _sseEvents\n }\n\n // Include attempt summary\n if (_attempts.length > 1) {\n entry.attempts = _attempts.map((a) => ({\n index: a.index,\n strategy: a.strategy,\n durationMs: a.durationMs,\n error: a.error?.message,\n truncation: a.truncation,\n }))\n }\n\n return entry\n },\n }\n\n return ctx\n}\n","/**\n * RequestContextManager — Active request management\n *\n * Manages all in-flight RequestContext instances. Publishes events for\n * WebSocket push and history persistence. The \"active layer\" complementing\n * the history store (persistence layer).\n *\n * Data flow:\n * Handler creates RequestContext → manager.create() registers + emits \"created\"\n * → pipeline processes request, calls ctx.transition()/setRewrites()/etc\n * → each change → manager emits events\n * → ws receives events → pushes to browser\n * → ctx.complete()/fail() → ctx.toHistoryEntry() → store.insert()\n * → manager emits \"completed\"/\"failed\" → removes active context\n */\n\nimport { consola } from \"consola\"\n\nimport type { EndpointType } from \"~/lib/history/store\"\n\nimport { state } from \"~/lib/state\"\n\nimport type { HistoryEntryData, RequestContext, RequestContextEventData, RequestState } from \"./request\"\n\nimport { createRequestContext } from \"./request\"\n\n// ─── Event Types ───\n\nexport type RequestContextEvent =\n | { type: \"created\"; context: RequestContext }\n | { type: \"state_changed\"; context: RequestContext; previousState: RequestState; meta?: Record<string, unknown> }\n | { type: \"updated\"; context: RequestContext; field: string }\n | { type: \"completed\"; context: RequestContext; entry: HistoryEntryData }\n | { type: \"failed\"; context: RequestContext; entry: HistoryEntryData }\n\n// ─── Manager Interface ───\n\nexport interface RequestContextManager {\n /** Create and register a new active request context */\n create(opts: { endpoint: EndpointType; tuiLogId?: string }): RequestContext\n\n /** Get an active request by ID */\n get(id: string): RequestContext | undefined\n\n /** Get all active requests (for history UI real-time view) */\n getAll(): Array<RequestContext>\n\n /** Number of active requests */\n readonly activeCount: number\n\n /** Subscribe to context events */\n on(event: \"change\", listener: (event: RequestContextEvent) => void): void\n\n /** Unsubscribe from context events */\n off(event: \"change\", listener: (event: RequestContextEvent) => void): void\n\n /** Start periodic cleanup of stale active contexts */\n startReaper(): void\n\n /** Stop the reaper (for shutdown/cleanup) */\n stopReaper(): void\n\n /** Run a single reaper scan (exposed for testing) */\n _runReaperOnce(): void\n}\n\n// ─── Implementation ───\n\n// ─── Module-level Singleton ───\n\nlet _manager: RequestContextManager | null = null\n\nexport function initRequestContextManager(): RequestContextManager {\n _manager = createRequestContextManager()\n return _manager\n}\n\nexport function getRequestContextManager(): RequestContextManager {\n if (!_manager) throw new Error(\"RequestContextManager not initialized — call initRequestContextManager() first\")\n return _manager\n}\n\n// ─── Factory ───\n\nexport function createRequestContextManager(): RequestContextManager {\n const activeContexts = new Map<string, RequestContext>()\n const listeners = new Set<(event: RequestContextEvent) => void>()\n\n // ─── Stale Request Reaper ───\n\n const REAPER_INTERVAL_MS = 60_000\n let reaperTimer: ReturnType<typeof setInterval> | null = null\n\n /** Single reaper scan — force-fail contexts exceeding maxAge */\n function runReaperOnce() {\n const maxAgeMs = state.staleRequestMaxAge * 1000\n if (maxAgeMs <= 0) return // disabled\n\n for (const [id, ctx] of activeContexts) {\n if (ctx.durationMs > maxAgeMs) {\n consola.warn(\n `[context] Force-failing stale request ${id}`\n + ` (endpoint: ${ctx.endpoint}`\n + `, model: ${ctx.originalRequest?.model ?? \"unknown\"}`\n + `, stream: ${ctx.originalRequest?.stream ?? \"?\"}`\n + `, state: ${ctx.state}`\n + `, age: ${Math.round(ctx.durationMs / 1000)}s`\n + `, max: ${state.staleRequestMaxAge}s)`,\n )\n ctx.fail(\n ctx.originalRequest?.model ?? \"unknown\",\n new Error(`Request exceeded maximum age of ${state.staleRequestMaxAge}s (stale context reaper)`),\n )\n }\n }\n }\n\n function startReaper() {\n if (reaperTimer) return // idempotent\n reaperTimer = setInterval(runReaperOnce, REAPER_INTERVAL_MS)\n }\n\n function stopReaper() {\n if (reaperTimer) {\n clearInterval(reaperTimer)\n reaperTimer = null\n }\n }\n\n function emit(event: RequestContextEvent) {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch {\n // Swallow listener errors\n }\n }\n }\n\n function handleContextEvent(rawEvent: RequestContextEventData) {\n const { type, context } = rawEvent\n\n switch (type) {\n case \"state_changed\": {\n if (rawEvent.previousState) {\n emit({\n type: \"state_changed\",\n context,\n previousState: rawEvent.previousState,\n meta: rawEvent.meta,\n })\n }\n break\n }\n case \"updated\": {\n if (rawEvent.field) {\n emit({\n type: \"updated\",\n context,\n field: rawEvent.field,\n })\n }\n break\n }\n case \"completed\": {\n if (rawEvent.entry) {\n emit({\n type: \"completed\",\n context,\n entry: rawEvent.entry,\n })\n }\n activeContexts.delete(context.id)\n break\n }\n case \"failed\": {\n if (rawEvent.entry) {\n emit({\n type: \"failed\",\n context,\n entry: rawEvent.entry,\n })\n }\n activeContexts.delete(context.id)\n break\n }\n default: {\n break\n }\n }\n }\n\n return {\n create(opts) {\n const ctx = createRequestContext({\n endpoint: opts.endpoint,\n tuiLogId: opts.tuiLogId,\n onEvent: handleContextEvent,\n })\n activeContexts.set(ctx.id, ctx)\n emit({ type: \"created\", context: ctx })\n return ctx\n },\n\n get(id) {\n return activeContexts.get(id)\n },\n\n getAll() {\n return Array.from(activeContexts.values())\n },\n\n get activeCount() {\n return activeContexts.size\n },\n\n on(_event: \"change\", listener: (event: RequestContextEvent) => void) {\n listeners.add(listener)\n },\n\n off(_event: \"change\", listener: (event: RequestContextEvent) => void) {\n listeners.delete(listener)\n },\n\n startReaper,\n stopReaper,\n _runReaperOnce: runReaperOnce,\n }\n}\n","/**\n * WebSocket support for History API.\n * Enables real-time updates when new requests are recorded.\n */\n\nimport consola from \"consola\"\n\nimport type { EntrySummary, HistoryStats } from \"./store\"\n\n/** Discriminated union of WebSocket message types */\nexport type WSMessageType =\n | \"entry_added\"\n | \"entry_updated\"\n | \"stats_updated\"\n | \"history_cleared\"\n | \"session_deleted\"\n | \"connected\"\n\n/** A WebSocket message sent to connected clients */\nexport interface WSMessage {\n type: WSMessageType\n data: unknown\n timestamp: number\n}\n\n/** Track connected WebSocket clients */\nconst clients = new Set<WebSocket>()\n\n/** Register a new WebSocket client and send connection confirmation */\nexport function addClient(ws: WebSocket): void {\n clients.add(ws)\n\n // Send connected confirmation\n const msg: WSMessage = {\n type: \"connected\",\n data: { clientCount: clients.size },\n timestamp: Date.now(),\n }\n ws.send(JSON.stringify(msg))\n}\n\n/** Unregister a WebSocket client */\nexport function removeClient(ws: WebSocket): void {\n clients.delete(ws)\n}\n\n/** Get the number of currently connected WebSocket clients */\nexport function getClientCount(): number {\n return clients.size\n}\n\n/** Close all connected WebSocket clients */\nexport function closeAllClients(): void {\n for (const client of clients) {\n try {\n client.close(1001, \"Server shutting down\")\n } catch {\n // Ignore errors during shutdown\n }\n }\n clients.clear()\n}\n\nfunction broadcast(message: WSMessage): void {\n const data = JSON.stringify(message)\n for (const client of clients) {\n try {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data)\n } else {\n // Remove clients that are no longer open (CLOSING, CLOSED)\n clients.delete(client)\n }\n } catch (error) {\n consola.debug(\"WebSocket send failed, removing client:\", error)\n clients.delete(client)\n }\n }\n}\n\n/** Called when a new entry is recorded */\nexport function notifyEntryAdded(summary: EntrySummary): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"entry_added\",\n data: summary,\n timestamp: Date.now(),\n })\n}\n\n/** Called when an entry is updated (e.g., response received) */\nexport function notifyEntryUpdated(summary: EntrySummary): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"entry_updated\",\n data: summary,\n timestamp: Date.now(),\n })\n}\n\n/** Called when stats change */\nexport function notifyStatsUpdated(stats: HistoryStats): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"stats_updated\",\n data: stats,\n timestamp: Date.now(),\n })\n}\n\n/** Called when all history is cleared */\nexport function notifyHistoryCleared(): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"history_cleared\",\n data: null,\n timestamp: Date.now(),\n })\n}\n\n/** Called when a session is deleted */\nexport function notifySessionDeleted(sessionId: string): void {\n if (clients.size === 0) return\n\n broadcast({\n type: \"session_deleted\",\n data: { sessionId },\n timestamp: Date.now(),\n })\n}\n","/**\n * History recording module for API requests/responses.\n * Supports full message content, session grouping, and rich querying.\n */\n\nimport { generateId } from \"../utils\"\nimport { notifyEntryAdded, notifyEntryUpdated, notifyHistoryCleared, notifySessionDeleted, notifyStatsUpdated } from \"./ws\"\n\n// Format timestamp as local ISO-like string (YYYY-MM-DD HH:MM:SS)\nfunction formatLocalTimestamp(ts: number): string {\n const d = new Date(ts)\n const y = d.getFullYear()\n const mo = String(d.getMonth() + 1).padStart(2, \"0\")\n const day = String(d.getDate()).padStart(2, \"0\")\n const h = String(d.getHours()).padStart(2, \"0\")\n const m = String(d.getMinutes()).padStart(2, \"0\")\n const s = String(d.getSeconds()).padStart(2, \"0\")\n return `${y}-${mo}-${day} ${h}:${m}:${s}`\n}\n\n/** Supported API endpoint types */\nexport type EndpointType = \"anthropic-messages\" | \"openai-chat-completions\" | \"openai-responses\"\n\n/** Message types for full content storage */\nexport interface MessageContent {\n role: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n content: string | Array<any> | null\n tool_calls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n tool_call_id?: string\n name?: string\n}\n\n// ============================================================================\n// Content block types — Anthropic API content block variants\n// ============================================================================\n\nexport interface TextContentBlock {\n type: \"text\"\n text: string\n}\n\nexport interface ThinkingContentBlock {\n type: \"thinking\"\n thinking: string\n signature?: string\n}\n\nexport interface ToolUseContentBlock {\n type: \"tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n}\n\n/** Text block within a tool_result content array */\nexport interface ToolResultTextBlock {\n type: \"text\"\n text: string\n}\n\n/** Image block within a tool_result content array */\nexport interface ToolResultImageBlock {\n type: \"image\"\n source: ImageSource\n}\n\nexport interface ToolResultContentBlock {\n type: \"tool_result\"\n tool_use_id: string\n content: string | Array<ToolResultTextBlock | ToolResultImageBlock>\n is_error?: boolean\n}\n\nexport type ImageSource =\n | {\n type: \"base64\"\n media_type: string\n data: string\n }\n | {\n type: \"url\"\n url: string\n }\n\nexport interface ImageContentBlock {\n type: \"image\"\n source: ImageSource\n}\n\nexport interface ServerToolUseContentBlock {\n type: \"server_tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n}\n\nexport interface RedactedThinkingContentBlock {\n type: \"redacted_thinking\"\n data?: string\n}\n\nexport interface WebSearchToolResultContentBlock {\n type: \"web_search_tool_result\"\n tool_use_id: string\n content: unknown\n}\n\n/** Union of all content block types that can appear in messages */\nexport type ContentBlock =\n | TextContentBlock\n | ThinkingContentBlock\n | ToolUseContentBlock\n | ToolResultContentBlock\n | ImageContentBlock\n | ServerToolUseContentBlock\n | RedactedThinkingContentBlock\n | WebSearchToolResultContentBlock\n\nexport interface ToolDefinition {\n name: string\n description?: string\n type?: string\n input_schema?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface TruncationInfo {\n /** Number of messages removed from the beginning of the conversation */\n removedMessageCount: number\n /** Estimated token count before truncation */\n originalTokens: number\n /** Estimated token count after truncation */\n compactedTokens: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\nexport interface SanitizationInfo {\n /** Total content blocks removed */\n totalBlocksRemoved: number\n /** Number of orphaned tool_use blocks removed */\n orphanedToolUseCount: number\n /** Number of orphaned tool_result blocks removed */\n orphanedToolResultCount: number\n /** Number of tool_use names fixed (casing) */\n fixedNameCount: number\n /** Number of empty text blocks removed */\n emptyTextBlocksRemoved: number\n /** Number of system-reminder tags removed */\n systemReminderRemovals: number\n}\n\nexport interface PreprocessInfo {\n /** Number of system-reminder tags stripped from Read tool results */\n strippedReadTagCount: number\n /** Number of duplicate tool_use/tool_result pairs deduplicated */\n dedupedToolCallCount: number\n}\n\n/** A single SSE event captured from an Anthropic streaming response */\nexport interface SseEventRecord {\n /** Milliseconds since request start */\n offsetMs: number\n /** SSE event type (e.g. \"message_start\", \"content_block_start\") */\n type: string\n /** Raw event data (the parsed JSON payload) */\n data: unknown\n}\n\nexport interface RewriteInfo {\n /** Auto-truncation metadata */\n truncation?: TruncationInfo\n /** Phase 1 preprocessing metadata (idempotent, run once before routing) */\n preprocessing?: PreprocessInfo\n /** Phase 2 sanitization metadata (repeatable, one entry per attempt) */\n sanitization?: Array<SanitizationInfo>\n /** Rewritten messages as actually sent to the API */\n rewrittenMessages?: Array<MessageContent>\n /** Rewritten system prompt (if modified) */\n rewrittenSystem?: string\n /** Rewritten→original message index mapping: messageMapping[rwIdx] = origIdx */\n messageMapping?: Array<number>\n}\n\nexport interface UsageData {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n}\n\nexport interface SystemBlock {\n type: \"text\"\n text: string\n cache_control?: { type: string } | null\n}\n\nexport interface HistoryEntry {\n id: string\n sessionId: string // Group related requests together\n timestamp: number\n endpoint: EndpointType\n\n request: {\n model?: string\n messages?: Array<MessageContent> // Full message history\n stream?: boolean\n tools?: Array<ToolDefinition>\n max_tokens?: number\n temperature?: number\n system?: string | Array<SystemBlock>\n }\n\n response?: {\n success: boolean\n model: string\n usage: UsageData\n stop_reason?: string\n error?: string\n content: MessageContent | null // Full response content\n }\n\n /** All rewrite metadata (truncation + sanitization + rewritten content) */\n rewrites?: RewriteInfo\n\n /** Filtered SSE events from Anthropic streaming (excludes content_block_delta and ping) */\n sseEvents?: Array<SseEventRecord>\n\n durationMs?: number\n}\n\nexport interface Session {\n id: string\n startTime: number\n lastActivity: number\n requestCount: number\n totalInputTokens: number\n totalOutputTokens: number\n models: Array<string>\n /** Endpoint types used in this session (may include multiple if mixed) */\n endpoints: Array<EndpointType>\n toolsUsed?: Array<string> // Tool names used in this session\n}\n\nexport interface HistoryState {\n enabled: boolean\n entries: Array<HistoryEntry>\n sessions: Map<string, Session>\n currentSessionId: string\n maxEntries: number\n}\n\nexport interface QueryOptions {\n page?: number\n limit?: number\n model?: string\n endpoint?: EndpointType\n success?: boolean\n from?: number\n to?: number\n search?: string\n sessionId?: string\n}\n\nexport interface HistoryResult {\n entries: Array<HistoryEntry>\n total: number\n page: number\n limit: number\n totalPages: number\n}\n\nexport interface SessionResult {\n sessions: Array<Session>\n total: number\n}\n\nexport interface HistoryStats {\n totalRequests: number\n successfulRequests: number\n failedRequests: number\n totalInputTokens: number\n totalOutputTokens: number\n averageDurationMs: number\n modelDistribution: Record<string, number>\n endpointDistribution: Record<string, number>\n recentActivity: Array<{ hour: string; count: number }>\n activeSessions: number\n}\n\n// ─── Entry Summary ───\n\n/** Lightweight projection of a HistoryEntry for list views and WebSocket broadcasts */\nexport interface EntrySummary {\n id: string\n sessionId: string\n timestamp: number\n endpoint: EndpointType\n\n requestModel?: string\n stream?: boolean\n messageCount: number\n\n responseModel?: string\n responseSuccess?: boolean\n responseError?: string\n usage?: {\n input_tokens: number\n output_tokens: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n }\n\n durationMs?: number\n /** First 100 characters of the last user message, for list preview */\n previewText: string\n /** Pre-computed lowercase text for search matching */\n searchText: string\n}\n\nexport interface SummaryResult {\n entries: Array<EntrySummary>\n total: number\n page: number\n limit: number\n totalPages: number\n}\n\n/** Extract a preview from the last user message (first 100 chars) */\nfunction extractPreviewText(entry: HistoryEntry): string {\n const messages = entry.request.messages\n if (!messages || messages.length === 0) return \"\"\n\n // Walk backwards to find the last user message (skip tool responses)\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n // Skip OpenAI tool responses and Anthropic tool_result messages\n if (msg.role === \"tool\") continue\n if (msg.role !== \"user\") continue\n\n if (typeof msg.content === \"string\") {\n return msg.content.slice(0, 100)\n }\n if (Array.isArray(msg.content)) {\n // Anthropic-style content blocks: look for text, skip tool_result\n for (const block of msg.content) {\n if (block.type === \"text\" && block.text) {\n return (block.text as string).slice(0, 100)\n }\n if (block.type === \"tool_result\") {\n // user message that only contains tool_result — skip this message entirely\n break\n }\n }\n continue // try previous messages\n }\n break\n }\n\n // Fallback: if the last message is an assistant with tool_calls, show tool name\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n const names = msg.tool_calls.map((tc) => tc.function.name).join(\", \")\n return `[tool_call: ${names}]`.slice(0, 100)\n }\n if (msg.role === \"tool\") {\n return `[tool_result: ${msg.tool_call_id ?? msg.name ?? \"unknown\"}]`.slice(0, 100)\n }\n break\n }\n\n return \"\"\n}\n\n/**\n * Build a pre-computed lowercase string for fast search matching.\n * Includes model names, error, system prompt preview, and message text snippets.\n * Deliberately kept compact — only the first ~200 chars of each message for memory efficiency.\n */\nfunction buildSearchText(entry: HistoryEntry): string {\n const parts: Array<string> = []\n\n // Model names\n if (entry.request.model) parts.push(entry.request.model)\n if (entry.response?.model) parts.push(entry.response.model)\n\n // Error\n if (entry.response?.error) parts.push(entry.response.error)\n\n // System prompt (first 500 chars)\n if (entry.request.system) {\n if (typeof entry.request.system === \"string\") {\n parts.push(entry.request.system.slice(0, 500))\n } else {\n for (const block of entry.request.system) {\n parts.push(block.text.slice(0, 200))\n }\n }\n }\n\n // Message text snippets\n if (entry.request.messages) {\n for (const msg of entry.request.messages) {\n if (typeof msg.content === \"string\") {\n parts.push(msg.content.slice(0, 200))\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === \"text\" && block.text) {\n parts.push((block.text as string).slice(0, 200))\n } else if (block.type === \"tool_use\") {\n if (block.name) parts.push(block.name as string)\n if (block.input) {\n const inputStr = typeof block.input === \"string\" ? block.input : JSON.stringify(block.input)\n parts.push(inputStr.slice(0, 500))\n }\n } else if (block.type === \"tool_result\" && block.content) {\n const contentStr = typeof block.content === \"string\" ? block.content : JSON.stringify(block.content)\n parts.push(contentStr.slice(0, 500))\n } else if (block.type === \"thinking\" && block.thinking) {\n parts.push((block.thinking as string).slice(0, 200))\n }\n }\n }\n // OpenAI tool_calls\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n if (tc.function.name) parts.push(tc.function.name)\n if (tc.function.arguments) parts.push(tc.function.arguments.slice(0, 500))\n }\n }\n }\n }\n\n // Response content snippets\n if (entry.response?.content) {\n const rc = entry.response.content\n if (typeof rc.content === \"string\") {\n parts.push(rc.content.slice(0, 200))\n } else if (Array.isArray(rc.content)) {\n for (const block of rc.content) {\n if (block.type === \"text\" && block.text) {\n parts.push((block.text as string).slice(0, 200))\n } else if (block.type === \"tool_use\" && block.name) {\n parts.push(block.name as string)\n }\n }\n }\n }\n\n return parts.join(\" \").toLowerCase()\n}\n\n/** Build a summary from a full HistoryEntry (searchText is computed lazily) */\nfunction toSummary(entry: HistoryEntry): EntrySummary {\n return {\n id: entry.id,\n sessionId: entry.sessionId,\n timestamp: entry.timestamp,\n endpoint: entry.endpoint,\n requestModel: entry.request.model,\n stream: entry.request.stream,\n messageCount: entry.request.messages?.length ?? 0,\n responseModel: entry.response?.model,\n responseSuccess: entry.response?.success,\n responseError: entry.response?.error,\n usage: entry.response?.usage,\n durationMs: entry.durationMs,\n previewText: extractPreviewText(entry),\n // Lazy: computed on first search/history API access via ensureSearchText()\n searchText: \"\",\n }\n}\n\n/** Lazily compute and cache searchText for an entry */\nfunction ensureSearchText(id: string): string {\n const summary = summaryIndex.get(id)\n if (!summary) return \"\"\n if (summary.searchText === \"\") {\n const entry = entryIndex.get(id)\n if (entry) {\n summary.searchText = buildSearchText(entry)\n }\n }\n return summary.searchText\n}\n\n/** Global history state */\nexport const historyState: HistoryState = {\n enabled: false,\n entries: [],\n sessions: new Map(),\n currentSessionId: \"\",\n maxEntries: 200,\n}\n\n/** O(1) lookup index for entries by ID */\nconst entryIndex = new Map<string, HistoryEntry>()\n\n/** O(1) lookup for entry summaries by ID */\nconst summaryIndex = new Map<string, EntrySummary>()\n\n/** Track entry count per session to avoid O(n) filter during FIFO eviction */\nconst sessionEntryCount = new Map<string, number>()\n\n/** O(1) uniqueness tracking for session.models (avoids Array.includes in hot path) */\nconst sessionModelsSet = new Map<string, Set<string>>()\n\n/** O(1) uniqueness tracking for session.toolsUsed (avoids Array.includes in hot path) */\nconst sessionToolsSet = new Map<string, Set<string>>()\n\n/** Dirty flag for stats cache — set true when entries are inserted/updated */\nlet statsDirty = true\n\n/** Cached stats result — recomputed only when statsDirty is true */\nlet cachedStats: HistoryStats | null = null\n\nexport function initHistory(enabled: boolean, maxEntries: number): void {\n historyState.enabled = enabled\n historyState.maxEntries = maxEntries\n historyState.entries = []\n historyState.sessions = new Map()\n historyState.currentSessionId = enabled ? generateId() : \"\"\n entryIndex.clear()\n summaryIndex.clear()\n sessionEntryCount.clear()\n sessionModelsSet.clear()\n sessionToolsSet.clear()\n statsDirty = true\n cachedStats = null\n}\n\n/** Update the maximum number of history entries (for config hot-reload) */\nexport function setHistoryMaxEntries(limit: number): void {\n historyState.maxEntries = limit\n}\n\nexport function isHistoryEnabled(): boolean {\n return historyState.enabled\n}\n\n/**\n * Get or create current session.\n * Currently treats all requests as belonging to one session per server lifetime,\n * since clients don't provide session identifiers yet.\n * TODO: When clients support session headers, use that to group requests.\n */\nexport function getCurrentSession(endpoint: EndpointType): string {\n if (historyState.currentSessionId) {\n const session = historyState.sessions.get(historyState.currentSessionId)\n if (session) {\n session.lastActivity = Date.now()\n // Track all endpoint types used in this session\n if (!session.endpoints.includes(endpoint)) {\n session.endpoints.push(endpoint)\n }\n return historyState.currentSessionId\n }\n }\n\n // Create initial session\n const now = Date.now()\n const sessionId = generateId()\n historyState.currentSessionId = sessionId\n sessionModelsSet.set(sessionId, new Set())\n sessionToolsSet.set(sessionId, new Set())\n historyState.sessions.set(sessionId, {\n id: sessionId,\n startTime: now,\n lastActivity: now,\n requestCount: 0,\n totalInputTokens: 0,\n totalOutputTokens: 0,\n models: [],\n endpoints: [endpoint],\n })\n\n return sessionId\n}\n\n// ─── Context-driven API ───\n\n/**\n * Insert a pre-built history entry.\n * Used by the context consumer — the entry already has an ID and sessionId.\n */\nexport function insertEntry(entry: HistoryEntry): void {\n if (!historyState.enabled) return\n\n const session = historyState.sessions.get(entry.sessionId)\n if (!session) return\n\n historyState.entries.push(entry)\n entryIndex.set(entry.id, entry)\n session.requestCount++\n sessionEntryCount.set(entry.sessionId, (sessionEntryCount.get(entry.sessionId) ?? 0) + 1)\n\n // Track model (O(1) via Set)\n const model = entry.request.model\n if (model) {\n const modelsSet = sessionModelsSet.get(entry.sessionId)\n if (modelsSet && !modelsSet.has(model)) {\n modelsSet.add(model)\n session.models.push(model)\n }\n }\n\n // Track tools (O(1) via Set)\n if (entry.request.tools && entry.request.tools.length > 0) {\n if (!session.toolsUsed) {\n session.toolsUsed = []\n }\n let toolsSet = sessionToolsSet.get(entry.sessionId)\n if (!toolsSet) {\n toolsSet = new Set(session.toolsUsed)\n sessionToolsSet.set(entry.sessionId, toolsSet)\n }\n for (const tool of entry.request.tools) {\n if (!toolsSet.has(tool.name)) {\n toolsSet.add(tool.name)\n session.toolsUsed.push(tool.name)\n }\n }\n }\n\n // Build and cache summary\n const summary = toSummary(entry)\n summaryIndex.set(entry.id, summary)\n\n // FIFO eviction (splice instead of repeated shift for O(1) amortized)\n if (historyState.maxEntries > 0 && historyState.entries.length > historyState.maxEntries) {\n const excess = historyState.entries.length - historyState.maxEntries\n const removed = historyState.entries.splice(0, excess)\n for (const r of removed) {\n entryIndex.delete(r.id)\n summaryIndex.delete(r.id)\n const count = (sessionEntryCount.get(r.sessionId) ?? 1) - 1\n if (count <= 0) {\n sessionEntryCount.delete(r.sessionId)\n sessionModelsSet.delete(r.sessionId)\n sessionToolsSet.delete(r.sessionId)\n historyState.sessions.delete(r.sessionId)\n } else {\n sessionEntryCount.set(r.sessionId, count)\n }\n }\n }\n\n statsDirty = true\n notifyEntryAdded(summary)\n notifyStatsUpdated(getStats())\n}\n\n/**\n * Update an existing entry's response, rewrites, or duration.\n * Used by the context consumer on completion/failure events.\n */\nexport function updateEntry(\n id: string,\n update: Partial<Pick<HistoryEntry, \"request\" | \"response\" | \"rewrites\" | \"sseEvents\" | \"durationMs\">>,\n): void {\n if (!historyState.enabled) return\n\n const entry = entryIndex.get(id)\n if (!entry) return\n\n if (update.request) {\n entry.request = update.request\n\n // Update session metadata that depends on request data\n const session = historyState.sessions.get(entry.sessionId)\n if (session) {\n const model = update.request.model\n if (model) {\n const modelsSet = sessionModelsSet.get(entry.sessionId)\n if (modelsSet && !modelsSet.has(model)) {\n modelsSet.add(model)\n session.models.push(model)\n }\n }\n if (update.request.tools && update.request.tools.length > 0) {\n if (!session.toolsUsed) {\n session.toolsUsed = []\n }\n let toolsSet = sessionToolsSet.get(entry.sessionId)\n if (!toolsSet) {\n toolsSet = new Set(session.toolsUsed)\n sessionToolsSet.set(entry.sessionId, toolsSet)\n }\n for (const tool of update.request.tools) {\n if (!toolsSet.has(tool.name)) {\n toolsSet.add(tool.name)\n session.toolsUsed.push(tool.name)\n }\n }\n }\n }\n }\n if (update.response) {\n entry.response = update.response\n }\n if (update.rewrites) {\n entry.rewrites = update.rewrites\n }\n if (update.durationMs !== undefined) {\n entry.durationMs = update.durationMs\n }\n if (update.sseEvents) {\n entry.sseEvents = update.sseEvents\n }\n\n // Update session token stats when response is set\n if (update.response) {\n const session = historyState.sessions.get(entry.sessionId)\n if (session) {\n session.totalInputTokens += update.response.usage.input_tokens\n session.totalOutputTokens += update.response.usage.output_tokens\n session.lastActivity = Date.now()\n }\n }\n\n // Rebuild summary cache and broadcast\n statsDirty = true\n const summary = toSummary(entry)\n summaryIndex.set(entry.id, summary)\n notifyEntryUpdated(summary)\n notifyStatsUpdated(getStats())\n}\n\nexport function getHistory(options: QueryOptions = {}): HistoryResult {\n const { page = 1, limit = 50, model, endpoint, success, from, to, search, sessionId } = options\n\n let filtered = [...historyState.entries]\n\n // Apply filters\n if (sessionId) {\n filtered = filtered.filter((e) => e.sessionId === sessionId)\n }\n\n if (model) {\n const modelLower = model.toLowerCase()\n filtered = filtered.filter(\n (e) =>\n e.request.model?.toLowerCase().includes(modelLower) || e.response?.model.toLowerCase().includes(modelLower),\n )\n }\n\n if (endpoint) {\n filtered = filtered.filter((e) => e.endpoint === endpoint)\n }\n\n if (success !== undefined) {\n filtered = filtered.filter((e) => e.response?.success === success)\n }\n\n if (from) {\n filtered = filtered.filter((e) => e.timestamp >= from)\n }\n\n if (to) {\n filtered = filtered.filter((e) => e.timestamp <= to)\n }\n\n if (search) {\n const searchLower = search.toLowerCase()\n filtered = filtered.filter((e) => ensureSearchText(e.id).includes(searchLower))\n }\n\n // Sort by timestamp descending (newest first)\n filtered.sort((a, b) => b.timestamp - a.timestamp)\n\n const total = filtered.length\n const totalPages = Math.ceil(total / limit)\n const start = (page - 1) * limit\n const entries = filtered.slice(start, start + limit)\n\n return {\n entries,\n total,\n page,\n limit,\n totalPages,\n }\n}\n\nexport function getEntry(id: string): HistoryEntry | undefined {\n return entryIndex.get(id) ?? historyState.entries.find((e) => e.id === id)\n}\n\nexport function getSummary(id: string): EntrySummary | undefined {\n return summaryIndex.get(id)\n}\n\n/**\n * Efficient summary-only query for list views. Filters and paginates using\n * the lightweight summaryIndex instead of full entries.\n * Search matches against the pre-computed `searchText` field — O(n) string\n * includes instead of O(n*m*b) deep content block traversal.\n */\nexport function getHistorySummaries(options: QueryOptions = {}): SummaryResult {\n const { page = 1, limit = 50, model, endpoint, success, from, to, search, sessionId } = options\n\n let summaries = Array.from(summaryIndex.values())\n\n // Filter\n if (sessionId) summaries = summaries.filter((s) => s.sessionId === sessionId)\n if (model) {\n const modelLower = model.toLowerCase()\n summaries = summaries.filter(\n (s) => s.requestModel?.toLowerCase().includes(modelLower) || s.responseModel?.toLowerCase().includes(modelLower),\n )\n }\n if (endpoint) summaries = summaries.filter((s) => s.endpoint === endpoint)\n if (success !== undefined) summaries = summaries.filter((s) => s.responseSuccess === success)\n if (from) summaries = summaries.filter((s) => s.timestamp >= from)\n if (to) summaries = summaries.filter((s) => s.timestamp <= to)\n\n // Search against pre-computed lowercase text (lazy-computed on first access)\n if (search) {\n const needle = search.toLowerCase()\n summaries = summaries.filter((s) => {\n if (s.searchText === \"\") {\n const entry = entryIndex.get(s.id)\n if (entry) s.searchText = buildSearchText(entry)\n }\n return s.searchText.includes(needle)\n })\n }\n\n // Sort newest first\n summaries.sort((a, b) => b.timestamp - a.timestamp)\n\n const total = summaries.length\n const totalPages = Math.ceil(total / limit)\n const start = (page - 1) * limit\n const entries = summaries.slice(start, start + limit)\n\n return { entries, total, page, limit, totalPages }\n}\n\nexport function getSessions(): SessionResult {\n const sessions = Array.from(historyState.sessions.values()).sort((a, b) => b.lastActivity - a.lastActivity)\n\n return {\n sessions,\n total: sessions.length,\n }\n}\n\nexport function getSession(id: string): Session | undefined {\n return historyState.sessions.get(id)\n}\n\nexport function getSessionEntries(\n sessionId: string,\n options: { page?: number; limit?: number } = {},\n): HistoryResult {\n const { page = 1, limit = 50 } = options\n const all = historyState.entries\n .filter((e) => e.sessionId === sessionId)\n .sort((a, b) => a.timestamp - b.timestamp) // Chronological order for sessions\n\n const total = all.length\n const totalPages = Math.max(1, Math.ceil(total / limit))\n const start = (page - 1) * limit\n const entries = all.slice(start, start + limit)\n\n return { entries, total, page, limit, totalPages }\n}\n\nexport function clearHistory(): void {\n historyState.entries = []\n historyState.sessions = new Map()\n historyState.currentSessionId = generateId()\n entryIndex.clear()\n summaryIndex.clear()\n sessionEntryCount.clear()\n sessionModelsSet.clear()\n sessionToolsSet.clear()\n statsDirty = true\n cachedStats = null\n notifyHistoryCleared()\n notifyStatsUpdated(getStats())\n}\n\nexport function deleteSession(sessionId: string): boolean {\n if (!historyState.sessions.has(sessionId)) {\n return false\n }\n\n const remaining: Array<HistoryEntry> = []\n for (const e of historyState.entries) {\n if (e.sessionId === sessionId) {\n entryIndex.delete(e.id)\n summaryIndex.delete(e.id)\n } else {\n remaining.push(e)\n }\n }\n historyState.entries = remaining\n historyState.sessions.delete(sessionId)\n sessionEntryCount.delete(sessionId)\n sessionModelsSet.delete(sessionId)\n sessionToolsSet.delete(sessionId)\n statsDirty = true\n cachedStats = null\n\n if (historyState.currentSessionId === sessionId) {\n historyState.currentSessionId = generateId()\n }\n\n notifySessionDeleted(sessionId)\n notifyStatsUpdated(getStats())\n\n return true\n}\n\nexport function getStats(): HistoryStats {\n // Return cached stats if nothing has changed\n if (!statsDirty && cachedStats) return cachedStats\n\n const entries = historyState.entries\n\n const modelDist: Record<string, number> = {}\n const endpointDist: Record<string, number> = {}\n const hourlyActivity: Record<string, number> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalDuration = 0\n let durationCount = 0\n let successCount = 0\n let failCount = 0\n\n for (const entry of entries) {\n // Model distribution\n const model = entry.response?.model || entry.request.model || \"unknown\"\n modelDist[model] = (modelDist[model] || 0) + 1\n\n // Endpoint distribution\n endpointDist[entry.endpoint] = (endpointDist[entry.endpoint] || 0) + 1\n\n // Hourly activity (last 24 hours) - use local time\n const d = new Date(entry.timestamp)\n const y = d.getFullYear()\n const mo = String(d.getMonth() + 1).padStart(2, \"0\")\n const day = String(d.getDate()).padStart(2, \"0\")\n const h = String(d.getHours()).padStart(2, \"0\")\n const hour = `${y}-${mo}-${day}T${h}`\n hourlyActivity[hour] = (hourlyActivity[hour] || 0) + 1\n\n if (entry.response) {\n if (entry.response.success) {\n successCount++\n } else {\n failCount++\n }\n\n totalInput += entry.response.usage.input_tokens\n totalOutput += entry.response.usage.output_tokens\n }\n\n if (entry.durationMs) {\n totalDuration += entry.durationMs\n durationCount++\n }\n }\n\n // Convert hourly activity to sorted array (last 24 entries)\n const recentActivity = Object.entries(hourlyActivity)\n .sort(([a], [b]) => a.localeCompare(b))\n .slice(-24)\n .map(([hour, count]) => ({ hour, count }))\n\n const stats: HistoryStats = {\n totalRequests: entries.length,\n successfulRequests: successCount,\n failedRequests: failCount,\n totalInputTokens: totalInput,\n totalOutputTokens: totalOutput,\n averageDurationMs: durationCount > 0 ? totalDuration / durationCount : 0,\n modelDistribution: modelDist,\n endpointDistribution: endpointDist,\n recentActivity,\n activeSessions: historyState.sessions.size,\n }\n\n statsDirty = false\n cachedStats = stats\n return stats\n}\n\n/** Escape a value for CSV: wrap in quotes if it contains comma, quote, or newline; convert nullish to empty string */\nfunction escapeCsvValue(value: unknown): string {\n if (value === null || value === undefined) return \"\"\n const str = typeof value === \"string\" ? value : JSON.stringify(value)\n if (str.includes(\",\") || str.includes('\"') || str.includes(\"\\n\")) {\n return `\"${str.replaceAll('\"', '\"\"')}\"`\n }\n return str\n}\n\nexport function exportHistory(format: \"json\" | \"csv\" = \"json\"): string {\n if (format === \"json\") {\n return JSON.stringify(\n {\n sessions: Array.from(historyState.sessions.values()),\n entries: historyState.entries,\n },\n null,\n 2,\n )\n }\n\n // CSV format - simplified view\n const headers = [\n \"id\",\n \"session_id\",\n \"timestamp\",\n \"endpoint\",\n \"request_model\",\n \"message_count\",\n \"stream\",\n \"success\",\n \"response_model\",\n \"input_tokens\",\n \"output_tokens\",\n \"duration_ms\",\n \"stop_reason\",\n \"error\",\n ]\n\n const rows = historyState.entries.map((e) => [\n e.id,\n e.sessionId,\n formatLocalTimestamp(e.timestamp),\n e.endpoint,\n e.request.model,\n e.request.messages?.length,\n e.request.stream,\n e.response?.success,\n e.response?.model,\n e.response?.usage.input_tokens,\n e.response?.usage.output_tokens,\n e.durationMs,\n e.response?.stop_reason,\n e.response?.error,\n ])\n\n return [headers.join(\",\"), ...rows.map((r) => r.map((v) => escapeCsvValue(v)).join(\",\"))].join(\"\\n\")\n}\n","/**\n * Centralized graceful shutdown management.\n *\n * Coordinates a 4-phase shutdown sequence:\n * Phase 1 (0s): Stop accepting new requests, drain rate limiter queue\n * Phase 2 (0–Ns): Wait for in-flight requests to complete naturally\n * Phase 3 (N–N+Ms): Fire abort signal, wait for handlers to wrap up\n * Phase 4: Force-close all connections, clean up\n *\n * Phase 2/3 timeouts are configurable via state.shutdownGracefulWait and\n * state.shutdownAbortWait (seconds), set from config.yaml `shutdown` section.\n *\n * Handlers integrate via getShutdownSignal() to detect Phase 3 abort.\n */\n\nimport type { Server } from \"srvx\"\n\nimport consola from \"consola\"\n\nimport type { AdaptiveRateLimiter } from \"./adaptive-rate-limiter\"\nimport type { TuiLogEntry } from \"./tui\"\n\nimport { getAdaptiveRateLimiter } from \"./adaptive-rate-limiter\"\nimport { getRequestContextManager } from \"./context/manager\"\nimport { closeAllClients, getClientCount } from \"./history\"\nimport { state } from \"./state\"\nimport { stopTokenRefresh } from \"./token\"\nimport { tuiLogger } from \"./tui\"\n\n// ============================================================================\n// Configuration constants\n// ============================================================================\n\n/** Polling interval during drain */\nexport const DRAIN_POLL_INTERVAL_MS = 500\n/** Progress log interval during drain */\nexport const DRAIN_PROGRESS_INTERVAL_MS = 5_000\n\n// ============================================================================\n// Module state\n// ============================================================================\n\nlet serverInstance: Server | null = null\nlet _isShuttingDown = false\nlet shutdownResolve: (() => void) | null = null\nlet shutdownAbortController: AbortController | null = null\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/** Check if the server is in shutdown state (used by middleware to reject new requests) */\nexport function getIsShuttingDown(): boolean {\n return _isShuttingDown\n}\n\n/**\n * Get the shutdown abort signal.\n * Returns undefined before shutdown starts. During Phase 1–2 the signal is\n * not aborted; it fires at Phase 3 to tell handlers to wrap up.\n */\nexport function getShutdownSignal(): AbortSignal | undefined {\n return shutdownAbortController?.signal\n}\n\n/**\n * Returns a promise that resolves when the server is shut down via signal.\n * Used by runServer() to keep the async function alive until shutdown.\n */\nexport function waitForShutdown(): Promise<void> {\n return new Promise((resolve) => {\n shutdownResolve = resolve\n })\n}\n\n/** Store the server instance for shutdown */\nexport function setServerInstance(server: Server): void {\n serverInstance = server\n}\n\n// ============================================================================\n// Dependency injection for testing\n// ============================================================================\n\n/** Dependencies that can be injected for testing */\nexport interface ShutdownDeps {\n tracker?: {\n getActiveRequests: () => Array<TuiLogEntry>\n destroy: () => void\n }\n server?: {\n close: (force?: boolean) => Promise<void>\n }\n rateLimiter?: AdaptiveRateLimiter | null\n stopTokenRefreshFn?: () => void\n closeAllClientsFn?: () => void\n getClientCountFn?: () => number\n /** Request context manager (for stopping stale reaper during shutdown) */\n contextManager?: { stopReaper: () => void }\n /** Timing overrides (for testing — avoids real 20s/120s waits) */\n gracefulWaitMs?: number\n abortWaitMs?: number\n drainPollIntervalMs?: number\n drainProgressIntervalMs?: number\n}\n\n// ============================================================================\n// Drain logic\n// ============================================================================\n\n/** Format a summary of active requests for logging */\nexport function formatActiveRequestsSummary(requests: Array<TuiLogEntry>): string {\n const now = Date.now()\n const lines = requests.map((req) => {\n const age = Math.round((now - req.startTime) / 1000)\n const model = req.model || \"unknown\"\n const tags = req.tags?.length ? ` [${req.tags.join(\", \")}]` : \"\"\n return ` ${req.method} ${req.path} ${model} (${req.status}, ${age}s)${tags}`\n })\n return `Waiting for ${requests.length} active request(s):\\n${lines.join(\"\\n\")}`\n}\n\n/**\n * Wait for all active requests to complete, with periodic progress logging.\n * Returns \"drained\" when all requests finish, \"timeout\" if deadline is reached.\n */\nexport async function drainActiveRequests(\n timeoutMs: number,\n tracker: { getActiveRequests: () => Array<TuiLogEntry> },\n opts?: { pollIntervalMs?: number; progressIntervalMs?: number },\n): Promise<\"drained\" | \"timeout\"> {\n const pollInterval = opts?.pollIntervalMs ?? DRAIN_POLL_INTERVAL_MS\n const progressInterval = opts?.progressIntervalMs ?? DRAIN_PROGRESS_INTERVAL_MS\n const deadline = Date.now() + timeoutMs\n let lastProgressLog = 0\n\n while (Date.now() < deadline) {\n const active = tracker.getActiveRequests()\n if (active.length === 0) return \"drained\"\n\n // Log progress periodically\n const now = Date.now()\n if (now - lastProgressLog >= progressInterval) {\n lastProgressLog = now\n consola.info(formatActiveRequestsSummary(active))\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollInterval))\n }\n\n return \"timeout\"\n}\n\n// ============================================================================\n// Graceful shutdown (4 phases)\n// ============================================================================\n\n/**\n * Perform graceful shutdown in 4 phases.\n *\n * @param signal - The signal that triggered shutdown (e.g. \"SIGINT\")\n * @param deps - Optional dependency injection for testing\n */\nexport async function gracefulShutdown(signal: string, deps?: ShutdownDeps): Promise<void> {\n const tracker = deps?.tracker ?? tuiLogger\n const server = deps?.server ?? serverInstance\n const rateLimiter = deps?.rateLimiter !== undefined ? deps.rateLimiter : getAdaptiveRateLimiter()\n const stopRefresh = deps?.stopTokenRefreshFn ?? stopTokenRefresh\n const closeWsClients = deps?.closeAllClientsFn ?? closeAllClients\n const getWsClientCount = deps?.getClientCountFn ?? getClientCount\n\n // Timing (defaults to state values from config, overridable for testing)\n const gracefulWaitMs = deps?.gracefulWaitMs ?? state.shutdownGracefulWait * 1000\n const abortWaitMs = deps?.abortWaitMs ?? state.shutdownAbortWait * 1000\n const drainOpts = {\n pollIntervalMs: deps?.drainPollIntervalMs ?? DRAIN_POLL_INTERVAL_MS,\n progressIntervalMs: deps?.drainProgressIntervalMs ?? DRAIN_PROGRESS_INTERVAL_MS,\n }\n\n // ── Phase 1: Stop accepting new requests ──────────────────────────────\n _isShuttingDown = true\n shutdownAbortController = new AbortController()\n\n consola.info(`Received ${signal}, shutting down gracefully...`)\n\n // Stop stale context reaper before drain (avoid racing with drain logic)\n try {\n const ctxMgr = deps?.contextManager ?? getRequestContextManager()\n ctxMgr.stopReaper()\n } catch {\n // Context manager may not be initialized in tests or early shutdown\n }\n\n // Stop background services\n stopRefresh()\n\n const wsClients = getWsClientCount()\n if (wsClients > 0) {\n closeWsClients()\n consola.info(`Disconnected ${wsClients} WebSocket client(s)`)\n }\n\n // Drain rate limiter queue immediately\n if (rateLimiter) {\n const rejected = rateLimiter.rejectQueued()\n if (rejected > 0) {\n consola.info(`Rejected ${rejected} queued request(s) from rate limiter`)\n }\n }\n\n // Stop listening for new connections (but keep existing ones alive).\n // Do NOT await — server.close(false) stops accepting new connections immediately,\n // but the returned promise won't resolve until all existing connections end.\n // Upgraded WebSocket connections (even after close handshake) keep the HTTP\n // server open indefinitely, which would block the entire shutdown sequence.\n if (server) {\n server.close(false).catch((error: unknown) => {\n consola.error(\"Error stopping listener:\", error)\n })\n consola.info(\"Stopped accepting new connections\")\n }\n\n // ── Phase 2: Wait for natural completion ──────────────────────────────\n const activeCount = tracker.getActiveRequests().length\n if (activeCount > 0) {\n consola.info(`Phase 2: Waiting up to ${gracefulWaitMs / 1000}s for ${activeCount} active request(s)...`)\n\n try {\n const phase2Result = await drainActiveRequests(gracefulWaitMs, tracker, drainOpts)\n if (phase2Result === \"drained\") {\n consola.info(\"All requests completed naturally\")\n finalize(tracker)\n return\n }\n } catch (error) {\n consola.error(\"Error during Phase 2 drain:\", error)\n }\n\n // ── Phase 3: Abort signal + extended wait ─────────────────────────────\n const remaining = tracker.getActiveRequests().length\n consola.info(\n `Phase 3: Sending abort signal to ${remaining} remaining request(s), `\n + `waiting up to ${abortWaitMs / 1000}s...`,\n )\n\n shutdownAbortController.abort()\n\n try {\n const phase3Result = await drainActiveRequests(abortWaitMs, tracker, drainOpts)\n if (phase3Result === \"drained\") {\n consola.info(\"All requests completed after abort signal\")\n finalize(tracker)\n return\n }\n } catch (error) {\n consola.error(\"Error during Phase 3 drain:\", error)\n }\n\n // ── Phase 4: Force close ────────────────────────────────────────────\n const forceRemaining = tracker.getActiveRequests().length\n consola.warn(`Phase 4: Force-closing ${forceRemaining} remaining request(s)`)\n\n if (server) {\n try {\n await server.close(true)\n } catch (error) {\n consola.error(\"Error force-closing server:\", error)\n }\n }\n }\n\n finalize(tracker)\n}\n\n/** Final cleanup after drain/force-close */\nfunction finalize(tracker: { destroy: () => void }): void {\n tracker.destroy()\n consola.info(\"Shutdown complete\")\n shutdownResolve?.()\n}\n\n// ============================================================================\n// Signal handlers\n// ============================================================================\n\n/** Setup process signal handlers for graceful shutdown */\nexport function setupShutdownHandlers(): void {\n const handler = (signal: string) => {\n if (_isShuttingDown) {\n // Second signal = force exit immediately\n consola.warn(\"Second signal received, forcing immediate exit\")\n process.exit(1)\n }\n gracefulShutdown(signal).catch((error: unknown) => {\n consola.error(\"Fatal error during shutdown:\", error)\n shutdownResolve?.() // Ensure waitForShutdown resolves even on error\n process.exit(1)\n })\n }\n process.on(\"SIGINT\", () => handler(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handler(\"SIGTERM\"))\n}\n\n// ============================================================================\n// Testing utilities\n// ============================================================================\n\n/** Reset module state (for tests only) */\nexport function _resetShutdownState(): void {\n _isShuttingDown = false\n shutdownResolve = null\n shutdownAbortController = null\n serverInstance = null\n}\n","/** TUI logger - manages request log entries independently of rendering */\n\nimport { state } from \"~/lib/state\"\nimport { generateId } from \"~/lib/utils\"\n\nimport type { RequestUpdate, TuiLogEntry, TuiRenderer } from \"./types\"\n\ninterface StartRequestOptions {\n method: string\n path: string\n model?: string\n isHistoryAccess?: boolean\n requestBodySize?: number\n}\n\n/** Outcome passed to finishRequest to mark a request as completed or failed */\nexport interface RequestOutcome {\n statusCode?: number\n error?: string\n usage?: { inputTokens: number; outputTokens: number }\n}\n\nexport class TuiLogger {\n private entries: Map<string, TuiLogEntry> = new Map()\n private renderer: TuiRenderer | null = null\n private completedQueue: Array<TuiLogEntry> = []\n private completedTimeouts: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private historySize = 5\n private completedDisplayMs = 2000\n\n setRenderer(renderer: TuiRenderer | null): void {\n this.renderer = renderer\n }\n\n setOptions(options: { historySize?: number; completedDisplayMs?: number }): void {\n if (options.historySize !== undefined) {\n this.historySize = options.historySize\n }\n if (options.completedDisplayMs !== undefined) {\n this.completedDisplayMs = options.completedDisplayMs\n }\n }\n\n /**\n * Start tracking a new request\n * Returns the log entry ID\n */\n startRequest(options: StartRequestOptions): string {\n const id = generateId()\n const entry: TuiLogEntry = {\n id,\n method: options.method,\n path: options.path,\n model: options.model,\n startTime: Date.now(),\n status: \"executing\",\n isHistoryAccess: options.isHistoryAccess,\n requestBodySize: options.requestBodySize,\n }\n\n this.entries.set(id, entry)\n this.renderer?.onRequestStart(entry)\n\n return id\n }\n\n /**\n * Update request status\n */\n updateRequest(id: string, update: RequestUpdate): void {\n const entry = this.entries.get(id)\n if (!entry) return\n\n if (update.model !== undefined) {\n entry.model = update.model\n const multiplier = state.modelIndex.get(update.model)?.billing?.multiplier\n if (multiplier !== undefined) entry.multiplier = multiplier\n }\n if (update.clientModel !== undefined) entry.clientModel = update.clientModel\n if (update.status !== undefined) entry.status = update.status\n if (update.statusCode !== undefined) entry.statusCode = update.statusCode\n if (update.durationMs !== undefined) entry.durationMs = update.durationMs\n if (update.inputTokens !== undefined) entry.inputTokens = update.inputTokens\n if (update.outputTokens !== undefined) entry.outputTokens = update.outputTokens\n if (update.cacheReadInputTokens !== undefined) entry.cacheReadInputTokens = update.cacheReadInputTokens\n if (update.cacheCreationInputTokens !== undefined) entry.cacheCreationInputTokens = update.cacheCreationInputTokens\n if (update.estimatedTokens !== undefined) entry.estimatedTokens = update.estimatedTokens\n if (update.error !== undefined) entry.error = update.error\n if (update.queuePosition !== undefined) entry.queuePosition = update.queuePosition\n if (update.queueWaitMs !== undefined) entry.queueWaitMs = update.queueWaitMs\n if (update.streamBytesIn !== undefined) entry.streamBytesIn = update.streamBytesIn\n if (update.streamEventsIn !== undefined) entry.streamEventsIn = update.streamEventsIn\n if (update.streamBlockType !== undefined) entry.streamBlockType = update.streamBlockType\n if (update.tags) {\n entry.tags ??= []\n for (const tag of update.tags) {\n if (!entry.tags.includes(tag)) entry.tags.push(tag)\n }\n }\n\n this.renderer?.onRequestUpdate(id, update)\n }\n\n /**\n * Mark a request as finished (completed or failed).\n *\n * Determines final status from the outcome:\n * - `error` present → \"error\"\n * - `statusCode` in success range (101, 2xx, 3xx) → \"completed\"\n * - `statusCode` outside success range → \"error\"\n * - Neither → \"completed\" (e.g. streaming success with no HTTP status)\n *\n * Safe to call multiple times for the same ID — second call is a no-op.\n * This eliminates the dual-path race between middleware and context consumer.\n */\n finishRequest(id: string, outcome: RequestOutcome): void {\n const entry = this.entries.get(id)\n if (!entry) return\n\n // Determine final status\n if (outcome.error) {\n entry.status = \"error\"\n entry.error = outcome.error\n } else if (outcome.statusCode !== undefined) {\n const sc = outcome.statusCode\n entry.status = sc === 101 || (sc >= 200 && sc < 400) ? \"completed\" : \"error\"\n } else {\n entry.status = \"completed\"\n }\n\n if (outcome.statusCode !== undefined) entry.statusCode = outcome.statusCode\n if (outcome.usage) {\n entry.inputTokens = outcome.usage.inputTokens\n entry.outputTokens = outcome.usage.outputTokens\n }\n entry.durationMs = Date.now() - entry.startTime\n\n this.renderer?.onRequestComplete(entry)\n this.moveToCompleted(id, entry)\n }\n\n // ─── Completed queue management ───\n\n /** Move entry from active to completed queue with auto-cleanup */\n private moveToCompleted(id: string, entry: TuiLogEntry): void {\n this.entries.delete(id)\n this.completedQueue.push(entry)\n\n // Trim queue to max history size\n while (this.completedQueue.length > this.historySize) {\n const removed = this.completedQueue.shift()\n if (removed) {\n const timeoutId = this.completedTimeouts.get(removed.id)\n if (timeoutId) {\n clearTimeout(timeoutId)\n this.completedTimeouts.delete(removed.id)\n }\n }\n }\n\n // Schedule removal from display after delay\n const timeoutId = setTimeout(() => {\n const idx = this.completedQueue.indexOf(entry)\n if (idx !== -1) {\n this.completedQueue.splice(idx, 1)\n }\n this.completedTimeouts.delete(id)\n }, this.completedDisplayMs)\n this.completedTimeouts.set(id, timeoutId)\n }\n\n // ─── Queries ───\n\n /**\n * Get all active entries\n */\n getActiveRequests(): Array<TuiLogEntry> {\n return Array.from(this.entries.values())\n }\n\n /**\n * Get recently completed entries\n */\n getCompletedRequests(): Array<TuiLogEntry> {\n return [...this.completedQueue]\n }\n\n /**\n * Get entry by ID (only active/in-flight entries)\n */\n getRequest(id: string): TuiLogEntry | undefined {\n return this.entries.get(id)\n }\n\n /**\n * Clear all entries and pending timeouts\n */\n clear(): void {\n this.entries.clear()\n this.completedQueue = []\n // Clear all pending timeouts\n for (const timeoutId of this.completedTimeouts.values()) {\n clearTimeout(timeoutId)\n }\n this.completedTimeouts.clear()\n }\n\n /**\n * Destroy the logger and its renderer.\n * Called during graceful shutdown to clean up terminal state (e.g. footer).\n */\n destroy(): void {\n this.clear()\n this.renderer?.destroy()\n this.renderer = null\n }\n}\n\n/** Singleton instance */\nexport const tuiLogger = new TuiLogger()\n","/**\n * TUI logger middleware — tracks every HTTP request in the TUI.\n *\n * Lifecycle:\n * startRequest() → [handler runs] → finishRequest()\n *\n * Completion ownership:\n * - Streaming API requests (SSE): consumer calls finishRequest asynchronously\n * when the stream ends, with correct duration and full usage data.\n * Middleware detects SSE content-type and skips finishRequest.\n * - Non-streaming API requests: consumer calls finishRequest synchronously\n * during await next(). Middleware's subsequent finishRequest is a no-op.\n * - Simple routes (/models, /history, etc.): no consumer — middleware calls\n * finishRequest after await next() with c.res.status.\n * - WebSocket upgrades: middleware calls finishRequest with status 101.\n *\n * finishRequest is idempotent — second call for the same ID is a no-op.\n */\n\nimport type { Context, MiddlewareHandler, Next } from \"hono\"\n\nimport { getErrorMessage, HTTPError } from \"~/lib/error\"\nimport { getIsShuttingDown } from \"~/lib/shutdown\"\n\nimport { tuiLogger } from \"./tracker\"\n\nexport function tuiMiddleware(): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n // Reject new requests during shutdown\n if (getIsShuttingDown()) {\n return c.json({ type: \"error\", error: { type: \"server_error\", message: \"Server is shutting down\" } }, 503)\n }\n\n const method = c.req.method\n const path = c.req.path\n\n // Capture request body size from Content-Length header\n const contentLength = c.req.header(\"content-length\")\n const requestBodySize = contentLength ? Number.parseInt(contentLength, 10) : undefined\n\n const tuiLogId = tuiLogger.startRequest({\n method,\n path,\n model: \"\",\n isHistoryAccess: path.startsWith(\"/history\"),\n requestBodySize,\n })\n\n // Store tracking ID in context for handlers/consumers to use\n c.set(\"tuiLogId\", tuiLogId)\n\n // Detect WebSocket upgrade before calling next() — after the upgrade,\n // c.res may not have a meaningful status.\n const isWebSocketUpgrade = c.req.header(\"upgrade\")?.toLowerCase() === \"websocket\"\n\n try {\n await next()\n\n // WebSocket: treat as 101 regardless of c.res.status\n // (Bun returns 200, Node.js handles upgrade outside Hono)\n if (isWebSocketUpgrade) {\n tuiLogger.finishRequest(tuiLogId, { statusCode: 101 })\n return\n }\n\n // Streaming (SSE): the consumer handles completion asynchronously when\n // the stream finishes — with correct duration and full usage data.\n // Calling finishRequest here would finish prematurely (before the stream\n // ends, without usage).\n const contentType = c.res.headers.get(\"content-type\")\n if (contentType?.includes(\"text/event-stream\")) return\n\n // Non-streaming: finish the request with the actual HTTP status.\n // If a consumer already finished it synchronously during next(), this is a no-op.\n tuiLogger.finishRequest(tuiLogId, { statusCode: c.res.status })\n } catch (error) {\n tuiLogger.finishRequest(tuiLogId, {\n error: getErrorMessage(error),\n statusCode: error instanceof HTTPError ? error.status : undefined,\n })\n throw error\n }\n }\n}\n","/** Pure formatting functions for TUI display */\n\nimport pc from \"picocolors\"\n\nimport type { TuiLogEntry } from \"./types\"\n\nexport function formatTime(date: Date = new Date()): string {\n const h = String(date.getHours()).padStart(2, \"0\")\n const m = String(date.getMinutes()).padStart(2, \"0\")\n const s = String(date.getSeconds()).padStart(2, \"0\")\n return `${h}:${m}:${s}`\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n return `${(ms / 1000).toFixed(1)}s`\n}\n\nexport function formatNumber(n: number): string {\n if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}K`\n return String(n)\n}\n\nexport function formatBytes(n: number): string {\n if (n >= 1048576) return `${(n / 1048576).toFixed(1)}MB`\n if (n >= 1024) return `${(n / 1024).toFixed(1)}KB`\n return `${n}B`\n}\n\n/** Format streaming metrics for footer display: ↓12.3KB 42ev [thinking] */\nexport function formatStreamInfo(req: TuiLogEntry): string {\n if (req.streamBytesIn === undefined) return \"\"\n const bytes = formatBytes(req.streamBytesIn)\n const events = req.streamEventsIn ?? 0\n const blockType = req.streamBlockType ? ` [${req.streamBlockType}]` : \"\"\n return ` ↓${bytes} ${events}ev${blockType}`\n}\n\n/** Format token counts with colors: dim for cache read, cyan for cache creation */\nexport function formatTokens(input?: number, output?: number, cacheRead?: number, cacheCreation?: number): string {\n if (input === undefined && output === undefined) return \"-\"\n let result = `↑${formatNumber(input ?? 0)}`\n if (cacheRead) result += pc.dim(`+${formatNumber(cacheRead)}`)\n if (cacheCreation) result += pc.cyan(`+${formatNumber(cacheCreation)}`)\n result += ` ↓${formatNumber(output ?? 0)}`\n return result\n}\n","/**\n * Console renderer - simple single-line output for each completed request.\n * Replaces Hono's default logger with cleaner, more informative output.\n */\n\nimport consola from \"consola\"\nimport pc from \"picocolors\"\n\nimport type { RequestUpdate, TuiLogEntry, TuiRenderer } from \"./types\"\n\nimport { formatBytes, formatDuration, formatStreamInfo, formatTime, formatTokens } from \"./format\"\n\n// ANSI escape codes for cursor control\nconst CLEAR_LINE = \"\\x1b[2K\\r\"\n\n/**\n * Console renderer that shows request lifecycle with apt-get style footer\n *\n * Log format:\n * - Start: [....] HH:MM:SS METHOD /path model-name (debug only, dim)\n * - Streaming: [<-->] HH:MM:SS METHOD /path model-name streaming... (dim)\n * - Complete: [ OK ] HH:MM:SS 200 POST /path model-name (3x) 1.2s ↑12.3KB ↓45.6KB ↑1.5K+300 ↓500 (colored)\n * - Error: [FAIL] HH:MM:SS 500 POST /path model-name (3x) 1.2s: error message (red)\n *\n * Color scheme for completed requests:\n * - Prefix: green (success) / red (error)\n * - Time: dim\n * - Method: white\n * - Path: white\n * - Model: magenta\n * - Status: green (success) / red (error)\n * - Duration: yellow\n * - Tokens: cyan (req/res info)\n *\n * Features:\n * - Start lines only shown in debug mode (--verbose)\n * - Streaming lines are dim (less important)\n * - /history API requests are always dim\n * - Sticky footer shows active requests with model and elapsed time\n * - Footer auto-refreshes every second while requests are in-flight\n * - Intercepts consola output to properly handle footer\n */\nexport class ConsoleRenderer implements TuiRenderer {\n private activeRequests: Map<string, TuiLogEntry> = new Map()\n private showActive: boolean\n private footerVisible = false\n private isTTY: boolean\n private originalReporters: Array<unknown> = []\n private footerTimer: ReturnType<typeof setInterval> | null = null\n\n constructor(options?: { showActive?: boolean }) {\n this.showActive = options?.showActive ?? true\n this.isTTY = process.stdout.isTTY\n\n // Install consola reporter that coordinates with footer\n this.installConsolaReporter()\n }\n\n /**\n * Install a custom consola reporter that coordinates with footer\n */\n private installConsolaReporter(): void {\n // Save original reporters\n this.originalReporters = [...consola.options.reporters]\n\n // Create a wrapper reporter that handles footer\n const footerAwareReporter = {\n log: (logObj: { args: Array<unknown>; type: string }) => {\n // Clear footer before any consola output\n this.clearFooterForLog()\n\n // Format and print the log message\n // Trim trailing whitespace/newlines to prevent blank lines\n // (e.g. citty's runMain passes \"\\n\" as a separate arg on errors)\n const message = logObj.args\n .map((arg) => {\n if (typeof arg === \"string\") return arg\n // Error objects have non-enumerable properties, JSON.stringify gives \"{}\"\n if (arg instanceof Error) {\n return arg.stack ?? arg.message\n }\n return JSON.stringify(arg)\n })\n .join(\" \")\n .trimEnd()\n\n // Use appropriate formatting based on log type\n const prefix = this.getLogPrefix(logObj.type)\n if (prefix) {\n process.stdout.write(`${prefix} ${message}\\n`)\n } else {\n process.stdout.write(`${message}\\n`)\n }\n\n // Re-render footer after log\n this.renderFooter()\n },\n }\n\n consola.setReporters([footerAwareReporter])\n }\n\n /**\n * Get log prefix based on log type (includes timestamp)\n */\n private getLogPrefix(type: string): string {\n const time = pc.dim(formatTime())\n\n switch (type) {\n case \"error\":\n case \"fatal\": {\n return `${pc.red(\"[ERR ]\")} ${time}`\n }\n case \"warn\": {\n return `${pc.yellow(\"[WARN]\")} ${time}`\n }\n case \"info\": {\n return `${pc.cyan(\"[INFO]\")} ${time}`\n }\n case \"success\": {\n return `${pc.green(\"[SUCC]\")} ${time}`\n }\n case \"debug\": {\n return `${pc.gray(\"[DBG ]\")} ${time}`\n }\n default: {\n return time\n }\n }\n }\n\n // ─── Footer (active request status line) ───\n\n /**\n * Build footer text showing per-request model and elapsed time.\n *\n * Single: [<-->] POST /v1/messages claude-sonnet-4 3.2s ↓12.3KB 42ev [thinking]\n * Multi: [<-->] claude-sonnet-4 5.2s ↓456KB 120ev [thinking] | claude-haiku-3 0.3s\n */\n private getFooterText(): string {\n const activeCount = this.activeRequests.size\n if (activeCount === 0) return \"\"\n\n const now = Date.now()\n\n if (activeCount === 1) {\n const req = this.activeRequests.values().next().value\n if (!req) return \"\" // should never happen when activeCount === 1\n const elapsed = formatDuration(now - req.startTime)\n const model = req.model ? ` ${req.model}` : \"\"\n const streamInfo = formatStreamInfo(req)\n return pc.dim(`[<-->] ${req.method} ${req.path}${model} ${elapsed}${streamInfo}`)\n }\n\n // Multiple requests — compact: model elapsed stream-info | model elapsed stream-info\n const items = Array.from(this.activeRequests.values()).map((req) => {\n const elapsed = formatDuration(now - req.startTime)\n const label = req.model || `${req.method} ${req.path}`\n const streamInfo = formatStreamInfo(req)\n return `${label} ${elapsed}${streamInfo}`\n })\n return pc.dim(`[<-->] ${items.join(\" | \")}`)\n }\n\n /**\n * Render footer in-place on current line (no newline)\n * Only works on TTY terminals\n */\n private renderFooter(): void {\n if (!this.isTTY) return\n\n const footerText = this.getFooterText()\n if (footerText) {\n process.stdout.write(CLEAR_LINE + footerText)\n this.footerVisible = true\n } else if (this.footerVisible) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n }\n\n /**\n * Clear footer and prepare for log output\n */\n private clearFooterForLog(): void {\n if (this.footerVisible && this.isTTY) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n }\n\n /** Start periodic footer refresh (every 100ms) to keep elapsed time current */\n private startFooterTimer(): void {\n if (this.footerTimer || !this.isTTY) return\n this.footerTimer = setInterval(() => {\n if (this.activeRequests.size > 0) {\n this.renderFooter()\n } else {\n this.stopFooterTimer()\n }\n }, 100)\n // Don't prevent process exit\n this.footerTimer.unref()\n }\n\n /** Stop periodic footer refresh */\n private stopFooterTimer(): void {\n if (this.footerTimer) {\n clearInterval(this.footerTimer)\n this.footerTimer = null\n }\n }\n\n /**\n * Format a complete log line with colored parts\n *\n * Format: [xxxx] HH:mm:ss <status> <method> <path> <model> (<multiplier>x) <duration> ↑<reqSize> ↓<respSize> ↑<inTokens>+<cache> ↓<outTokens>\n */\n private formatLogLine(parts: {\n prefix: string\n time: string\n method: string\n path: string\n model?: string\n /** Original model name from client (shown when different from resolved model) */\n clientModel?: string\n multiplier?: number\n status?: number\n duration?: string\n requestBodySize?: number\n responseBodySize?: number\n inputTokens?: number\n outputTokens?: number\n cacheReadInputTokens?: number\n cacheCreationInputTokens?: number\n queueWait?: string\n extra?: string\n isError?: boolean\n isDim?: boolean\n }): string {\n const {\n prefix,\n time,\n method,\n path,\n model,\n clientModel,\n multiplier,\n status,\n duration,\n requestBodySize,\n responseBodySize,\n inputTokens,\n outputTokens,\n cacheReadInputTokens,\n cacheCreationInputTokens,\n queueWait,\n extra,\n isError,\n isDim,\n } = parts\n\n if (isDim) {\n const modelPart = model ? ` ${model}` : \"\"\n const extraPart = extra ? ` ${extra}` : \"\"\n return pc.dim(`${prefix} ${time} ${method} ${path}${modelPart}${extraPart}`)\n }\n\n // Colored lines: each part has its own color\n const coloredPrefix = isError ? pc.red(prefix) : pc.green(prefix)\n const coloredTime = pc.dim(time)\n let coloredStatus: string | undefined\n if (status !== undefined) {\n coloredStatus = isError ? pc.red(String(status)) : pc.green(String(status))\n }\n const coloredMethod = pc.white(method)\n const coloredPath = pc.white(path)\n\n // Show \"clientModel → model\" when client requested a different model name\n let coloredModel = \"\"\n if (model) {\n coloredModel =\n clientModel && clientModel !== model ?\n ` ${pc.dim(clientModel)} → ${pc.magenta(model)}`\n : pc.magenta(` ${model}`)\n }\n const coloredMultiplier = multiplier !== undefined ? pc.dim(` (${multiplier}x)`) : \"\"\n const coloredDuration = duration ? ` ${pc.yellow(duration)}` : \"\"\n const coloredQueueWait = queueWait ? ` ${pc.dim(`(queued ${queueWait})`)}` : \"\"\n\n // req/resp body sizes with ↑↓ arrows\n let sizeInfo = \"\"\n if (model) {\n const reqSize = requestBodySize !== undefined ? `↑${formatBytes(requestBodySize)}` : \"\"\n const respSize = responseBodySize !== undefined ? `↓${formatBytes(responseBodySize)}` : \"\"\n const parts = [reqSize, respSize].filter(Boolean).join(\" \")\n if (parts) sizeInfo = ` ${pc.dim(parts)}`\n }\n\n // in-tokens/out-tokens (with cache breakdown)\n let tokenInfo = \"\"\n if (model && (inputTokens !== undefined || outputTokens !== undefined)) {\n tokenInfo = ` ${formatTokens(inputTokens, outputTokens, cacheReadInputTokens, cacheCreationInputTokens)}`\n }\n\n let extraPart = \"\"\n if (extra) {\n extraPart = isError ? pc.red(extra) : extra\n }\n\n const statusAndMethod = coloredStatus ? `${coloredStatus} ${coloredMethod}` : coloredMethod\n\n return `${coloredPrefix} ${coloredTime} ${statusAndMethod} ${coloredPath}${coloredModel}${coloredMultiplier}${coloredDuration}${coloredQueueWait}${sizeInfo}${tokenInfo}${extraPart}`\n }\n\n /**\n * Print a log line with proper footer handling\n */\n private printLog(message: string): void {\n this.clearFooterForLog()\n process.stdout.write(message + \"\\n\")\n this.renderFooter()\n }\n\n onRequestStart(request: TuiLogEntry): void {\n this.activeRequests.set(request.id, request)\n this.startFooterTimer()\n\n // Only show start line in debug mode (consola.level >= 5)\n if (this.showActive && consola.level >= 5) {\n const message = this.formatLogLine({\n prefix: \"[....]\",\n time: formatTime(),\n method: request.method,\n path: request.path,\n model: request.model,\n extra:\n request.queuePosition !== undefined && request.queuePosition > 0 ? `[q#${request.queuePosition}]` : undefined,\n isDim: true,\n })\n this.printLog(message)\n }\n }\n\n onRequestUpdate(id: string, update: RequestUpdate): void {\n const request = this.activeRequests.get(id)\n if (!request) return\n\n Object.assign(request, update)\n }\n\n onRequestComplete(request: TuiLogEntry): void {\n this.activeRequests.delete(request.id)\n\n // Stop timer when no more active requests\n if (this.activeRequests.size === 0) {\n this.stopFooterTimer()\n }\n\n // Skip completed log line for history access (only errors are shown)\n if (request.isHistoryAccess && request.status !== \"error\") {\n this.renderFooter()\n return\n }\n\n const status = request.statusCode\n const isError = request.status === \"error\" || (status !== undefined && status >= 400)\n\n // Only show queue wait if it's significant (> 100ms)\n const queueWait = request.queueWaitMs && request.queueWaitMs > 100 ? formatDuration(request.queueWaitMs) : undefined\n\n // Build extra text from tags and error\n // Tags are supplementary metadata — dim the entire group\n const tagStr = !isError && request.tags?.length ? pc.dim(` (${request.tags.join(\", \")})`) : \"\"\n const errorStr = isError && request.error ? `: ${request.error}` : \"\"\n const extra = tagStr + errorStr || undefined\n\n const message = this.formatLogLine({\n prefix: isError ? \"[FAIL]\" : \"[ OK ]\",\n time: formatTime(),\n method: request.method,\n path: request.path,\n model: request.model,\n clientModel: request.clientModel,\n multiplier: request.multiplier,\n status,\n duration: formatDuration(request.durationMs ?? 0),\n queueWait,\n requestBodySize: request.requestBodySize,\n responseBodySize: request.streamBytesIn,\n inputTokens: request.inputTokens,\n outputTokens: request.outputTokens,\n cacheReadInputTokens: request.cacheReadInputTokens,\n cacheCreationInputTokens: request.cacheCreationInputTokens,\n extra,\n isError,\n })\n this.printLog(message)\n }\n\n destroy(): void {\n this.stopFooterTimer()\n if (this.footerVisible && this.isTTY) {\n process.stdout.write(CLEAR_LINE)\n this.footerVisible = false\n }\n this.activeRequests.clear()\n\n // Restore original reporters\n if (this.originalReporters.length > 0) {\n consola.setReporters(this.originalReporters as Parameters<typeof consola.setReporters>[0])\n }\n }\n}\n","/** TUI module exports */\n\nexport { tuiMiddleware } from \"./middleware\"\nexport { tuiLogger } from \"./tracker\"\nexport type { RequestOutcome } from \"./tracker\"\nexport type { RequestStatus, RequestUpdate, TuiLogEntry, TuiOptions, TuiRenderer } from \"./types\"\n\nimport type { TuiOptions } from \"./types\"\n\nimport { ConsoleRenderer } from \"./console-renderer\"\nimport { tuiLogger } from \"./tracker\"\n\n/** Singleton renderer instance (created once, used for both logging and request tracking) */\nlet renderer: ConsoleRenderer | null = null\n\n/**\n * Initialize the consola reporter for unified log formatting.\n * This should be called as early as possible to capture all logs.\n * Does NOT set up request tracking - call initTuiLogger() for that.\n *\n * @param forceEnable - Force enable even if not TTY (useful for consistent log format)\n */\nexport function initConsolaReporter(forceEnable = true): void {\n if (!renderer && (forceEnable || process.stdout.isTTY)) {\n renderer = new ConsoleRenderer()\n }\n}\n\n/**\n * Initialize request tracking with the TUI renderer.\n * Should be called after initConsolaReporter() and before handling requests.\n */\nexport function initTuiLogger(options?: TuiOptions): void {\n if (renderer) {\n tuiLogger.setRenderer(renderer)\n }\n\n if (options?.historySize !== undefined || options?.completedDisplayMs !== undefined) {\n tuiLogger.setOptions({\n historySize: options.historySize,\n completedDisplayMs: options.completedDisplayMs,\n })\n }\n}\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\n\n/**\n * Get Claude Code version from package.json\n */\nfunction getClaudeCodeVersion(cliPath: string): string | null {\n try {\n const packageJsonPath = join(dirname(cliPath), \"package.json\")\n if (!existsSync(packageJsonPath)) return null\n\n const packageJson: unknown = JSON.parse(readFileSync(packageJsonPath, \"utf8\"))\n if (\n typeof packageJson === \"object\"\n && packageJson !== null\n && \"version\" in packageJson\n && typeof packageJson.version === \"string\"\n ) {\n return packageJson.version\n }\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Search volta tools directory for Claude Code\n */\nfunction findInVoltaTools(voltaHome: string): Array<string> {\n const paths: Array<string> = []\n\n // Check volta packages directory (npm install -g @anthropic-ai/claude-code)\n const packagesPath = join(\n voltaHome,\n \"tools\",\n \"image\",\n \"packages\",\n \"@anthropic-ai\",\n \"claude-code\",\n \"lib\",\n \"node_modules\",\n \"@anthropic-ai\",\n \"claude-code\",\n \"cli.js\",\n )\n if (existsSync(packagesPath)) {\n paths.push(packagesPath)\n }\n\n // Check volta node tools directory (older installation method)\n const toolsDir = join(voltaHome, \"tools\", \"image\", \"node\")\n if (existsSync(toolsDir)) {\n try {\n for (const version of readdirSync(toolsDir)) {\n const claudePath = join(toolsDir, version, \"lib\", \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\")\n if (existsSync(claudePath)) {\n paths.push(claudePath)\n }\n }\n } catch {\n // Ignore errors reading directory\n }\n }\n\n return paths\n}\n\n/**\n * Find all Claude Code CLI paths by checking common locations\n */\nfunction findAllClaudeCodePaths(): Array<string> {\n const possiblePaths: Array<string> = []\n const home = process.env.HOME || \"\"\n\n // Check volta installation\n const voltaHome = process.env.VOLTA_HOME || join(home, \".volta\")\n if (existsSync(voltaHome)) {\n possiblePaths.push(...findInVoltaTools(voltaHome))\n }\n\n // Check npm global installation\n const npmPrefix = process.env.npm_config_prefix\n if (npmPrefix) {\n possiblePaths.push(join(npmPrefix, \"lib\", \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Check common global npm paths\n const globalPaths = [\n join(home, \".npm-global\", \"lib\", \"node_modules\"),\n \"/usr/local/lib/node_modules\",\n \"/usr/lib/node_modules\",\n ]\n\n for (const base of globalPaths) {\n possiblePaths.push(join(base, \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Check bun global installation\n const bunGlobal = join(home, \".bun\", \"install\", \"global\")\n if (existsSync(bunGlobal)) {\n possiblePaths.push(join(bunGlobal, \"node_modules\", \"@anthropic-ai\", \"claude-code\", \"cli.js\"))\n }\n\n // Return all existing paths (deduplicated)\n return [...new Set(possiblePaths.filter((p) => existsSync(p)))]\n}\n\nexport const listClaudeCode = defineCommand({\n meta: {\n name: \"list-claude-code\",\n description: \"List all locally installed Claude Code versions\",\n },\n run() {\n const installations = findAllClaudeCodePaths()\n\n if (installations.length === 0) {\n consola.info(\"No Claude Code installations found\")\n consola.info(\"Searched in: volta, npm global, bun global\")\n return\n }\n\n consola.info(`Found ${installations.length} Claude Code installation(s):`)\n\n for (const [i, path] of installations.entries()) {\n const version = getClaudeCodeVersion(path) ?? \"unknown\"\n consola.info(` ${i + 1}. v${version} ${path}`)\n }\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"./lib/config/paths\"\n\nexport async function runLogout(): Promise<void> {\n try {\n await fs.unlink(PATHS.GITHUB_TOKEN_PATH)\n consola.success(\"Logged out successfully. GitHub token removed.\")\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n consola.info(\"No token found. Already logged out.\")\n } else {\n consola.error(\"Failed to remove token:\", error)\n throw error\n }\n }\n}\n\nexport const logout = defineCommand({\n meta: {\n name: \"logout\",\n description: \"Remove stored GitHub token and log out\",\n },\n run() {\n return runLogout()\n },\n})\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport { existsSync, promises as fsPromises } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport invariant from \"tiny-invariant\"\n\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { cacheVSCodeVersion } from \"./lib/copilot-api\"\nimport { cacheModels } from \"./lib/models/client\"\nimport { state } from \"./lib/state\"\nimport { initTokenManagers } from \"./lib/token\"\n\n/**\n * Write Claude Code configuration files for use with Copilot API.\n * Creates/updates:\n * - $HOME/.claude.json - Sets hasCompletedOnboarding: true\n * - $HOME/.claude/settings.json - Sets env variables for Copilot API\n */\nexport async function writeClaudeCodeConfig(serverUrl: string, model: string, smallModel: string): Promise<void> {\n const home = homedir()\n const claudeJsonPath = join(home, \".claude.json\")\n const claudeDir = join(home, \".claude\")\n const settingsPath = join(claudeDir, \"settings.json\")\n\n // Ensure .claude directory exists\n if (!existsSync(claudeDir)) {\n await fsPromises.mkdir(claudeDir, { recursive: true })\n consola.info(`Created directory: ${claudeDir}`)\n }\n\n // Update $HOME/.claude.json\n let claudeJson: Record<string, unknown> = {}\n if (existsSync(claudeJsonPath)) {\n try {\n const buffer = await fsPromises.readFile(claudeJsonPath)\n claudeJson = JSON.parse(buffer.toString()) as Record<string, unknown>\n } catch {\n consola.warn(`Failed to parse ${claudeJsonPath}, creating new file`)\n }\n }\n claudeJson.hasCompletedOnboarding = true\n await fsPromises.writeFile(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + \"\\n\")\n consola.success(`Updated ${claudeJsonPath}`)\n\n // Update $HOME/.claude/settings.json\n let settings: Record<string, unknown> = {}\n if (existsSync(settingsPath)) {\n try {\n const buffer = await fsPromises.readFile(settingsPath)\n settings = JSON.parse(buffer.toString()) as Record<string, unknown>\n } catch {\n consola.warn(`Failed to parse ${settingsPath}, creating new file`)\n }\n }\n\n // Set env configuration\n settings.env = {\n ...(settings.env as Record<string, string> | undefined),\n ANTHROPIC_BASE_URL: serverUrl,\n ANTHROPIC_AUTH_TOKEN: \"copilot-api\",\n ANTHROPIC_MODEL: model,\n ANTHROPIC_DEFAULT_SONNET_MODEL: model,\n ANTHROPIC_SMALL_FAST_MODEL: smallModel,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: smallModel,\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n CLAUDE_CODE_ENABLE_TELEMETRY: \"0\",\n }\n\n await fsPromises.writeFile(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\")\n consola.success(`Updated ${settingsPath}`)\n\n consola.box(\n `Claude Code configured!\\n\\n`\n + `Model: ${model}\\n`\n + `Small Model: ${smallModel}\\n`\n + `API URL: ${serverUrl}\\n\\n`\n + `Run 'claude' to start Claude Code.`,\n )\n}\n\ninterface SetupClaudeCodeOptions {\n port: number\n host?: string\n model?: string\n smallModel?: string\n accountType: \"individual\" | \"business\" | \"enterprise\"\n githubToken?: string\n verbose: boolean\n}\n\nexport async function runSetupClaudeCode(options: SetupClaudeCodeOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n\n // Authenticate and fetch models\n await ensurePaths()\n await cacheVSCodeVersion()\n await initTokenManagers({ cliToken: options.githubToken })\n await cacheModels()\n\n invariant(state.models, \"Models should be loaded by now\")\n const availableModelIds = state.models.data.map((m) => m.id)\n\n let selectedModel: string\n let selectedSmallModel: string\n\n if (options.model && options.smallModel) {\n // Validate the provided models exist\n if (!availableModelIds.includes(options.model)) {\n consola.error(`Invalid model: ${options.model}\\nAvailable models: ${availableModelIds.join(\", \")}`)\n process.exit(1)\n }\n if (!availableModelIds.includes(options.smallModel)) {\n consola.error(`Invalid small model: ${options.smallModel}\\nAvailable models: ${availableModelIds.join(\", \")}`)\n process.exit(1)\n }\n selectedModel = options.model\n selectedSmallModel = options.smallModel\n } else if (options.model || options.smallModel) {\n consola.error(\"Both --model and --small-model must be provided together, or neither for interactive selection\")\n process.exit(1)\n } else {\n // Interactive selection\n selectedModel = await consola.prompt(\"Select a model to use with Claude Code\", {\n type: \"select\",\n options: availableModelIds,\n })\n\n selectedSmallModel = await consola.prompt(\"Select a small model to use with Claude Code\", {\n type: \"select\",\n options: availableModelIds,\n })\n }\n\n const displayHost = options.host ?? \"localhost\"\n const serverUrl = `http://${displayHost}:${options.port}`\n\n await writeClaudeCodeConfig(serverUrl, selectedModel, selectedSmallModel)\n}\n\nexport const setupClaudeCode = defineCommand({\n meta: {\n name: \"setup-claude-code\",\n description: \"Setup Claude Code configuration files to use Copilot API as backend\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port the Copilot API server will run on\",\n },\n host: {\n alias: \"H\",\n type: \"string\",\n description: \"Host the Copilot API server will bind to (default: localhost)\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Model to use with Claude Code (skips interactive selection, requires --small-model)\",\n },\n \"small-model\": {\n alias: \"s\",\n type: \"string\",\n description: \"Small/fast model to use with Claude Code (skips interactive selection, requires --model)\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description: \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n },\n run({ args }) {\n return runSetupClaudeCode({\n port: Number.parseInt(args.port, 10),\n host: args.host,\n model: args.model,\n smallModel: args[\"small-model\"],\n accountType: args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\",\n githubToken: args[\"github-token\"],\n verbose: args.verbose,\n })\n },\n})\n","","/**\n * Application configuration: types, YAML loading, and state application.\n *\n * All config types live here as the single source of truth.\n * config.yaml is loaded with mtime-based caching.\n */\n\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { setHistoryMaxEntries } from \"~/lib/history\"\nimport { type CompiledRewriteRule, DEFAULT_MODEL_OVERRIDES, state } from \"~/lib/state\"\n\nimport { PATHS } from \"./paths\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Raw rewrite rule from config.yaml (shared by system_prompt_overrides and rewrite_system_reminders) */\nexport interface RewriteRule {\n from: string\n to: string\n /** Match method: \"line\" = exact line match, \"regex\" = regex on full text. Default: \"regex\". */\n method?: \"line\" | \"regex\"\n /** Resolved model name regex pattern (case-insensitive). When set, this rule only applies to matching models. */\n model?: string\n}\n\n// ============================================================================\n// Rule Compilation\n// ============================================================================\n\n/** Compile a raw rewrite rule into a CompiledRewriteRule. Returns null for invalid regex. */\nexport function compileRewriteRule(raw: RewriteRule): CompiledRewriteRule | null {\n const method = raw.method ?? \"regex\"\n\n // Compile model filter regex (shared by both line and regex methods)\n let modelPattern: RegExp | undefined\n if (raw.model) {\n try {\n modelPattern = new RegExp(raw.model, \"i\")\n } catch (err) {\n consola.warn(`[config] Invalid model regex in rewrite rule: \"${raw.model}\"`, err)\n return null\n }\n }\n\n if (method === \"line\") return { from: raw.from, to: raw.to, method, modelPattern }\n try {\n // Strip leading inline flags (?flags) — merge with base gms flags\n // e.g. \"(?i)pattern\" → pattern \"pattern\", flags \"gmsi\"\n // e.g. \"(?s).*\" → pattern \".*\", flags \"gms\" (s already present)\n let pattern = raw.from\n let flags = \"gms\"\n const inlineMatch = pattern.match(/^\\(\\?([a-z]+)\\)/i)\n if (inlineMatch) {\n pattern = pattern.slice(inlineMatch[0].length)\n // Merge unique flags\n for (const f of inlineMatch[1]) {\n if (!flags.includes(f)) flags += f\n }\n }\n return { from: new RegExp(pattern, flags), to: raw.to, method, modelPattern }\n } catch (err) {\n consola.warn(`[config] Invalid regex in rewrite rule: \"${raw.from}\"`, err)\n return null\n }\n}\n\n/** Compile an array of raw rewrite rules, skipping invalid ones */\nexport function compileRewriteRules(raws: Array<RewriteRule>): Array<CompiledRewriteRule> {\n return raws.map((r) => compileRewriteRule(r)).filter((r): r is CompiledRewriteRule => r !== null)\n}\n\n/** Rate limiter configuration section */\nexport interface RateLimiterConfig {\n /** Seconds to wait before retrying after rate limit error (default: 10) */\n retry_interval?: number\n /** Seconds between requests in rate-limited mode (default: 10) */\n request_interval?: number\n /** Minutes before attempting recovery from rate-limited mode (default: 10) */\n recovery_timeout?: number\n /** Number of consecutive successes needed to recover (default: 5) */\n consecutive_successes?: number\n}\n\n/** Anthropic-specific configuration section */\nexport interface AnthropicConfig {\n /** Convert server-side tool declarations (web_search, etc.) to custom tool format (default: true) */\n convert_server_tools_to_custom?: boolean\n /**\n * Remove duplicate tool_use/tool_result pairs (keep last occurrence).\n * - `false` — disabled (default)\n * - `true` or `\"input\"` — match by (tool_name, input)\n * - `\"result\"` — match by (tool_name, input, result)\n */\n dedup_tool_calls?: boolean | \"input\" | \"result\"\n /** Strip injected system-reminder tags from Read tool results */\n truncate_read_tool_result?: boolean\n /**\n * Rewrite system-reminder tags in messages.\n * - `false` — keep all tags unchanged (default)\n * - `true` — remove all system-reminder tags\n * - Array of rewrite rules — first matching rule wins (top-down):\n * - `from`: pattern to match against tag content\n * - `to`: replacement string (supports $0, $1, etc. in regex mode)\n * Empty string = remove the tag. `$0` = keep unchanged.\n * - `method`: `\"regex\"` (default) or `\"line\"`\n */\n rewrite_system_reminders?: boolean | Array<RewriteRule>\n}\n\n/** Shutdown timing configuration section */\nexport interface ShutdownConfig {\n /** Phase 2 timeout in seconds: wait for in-flight requests to complete naturally (default: 60) */\n graceful_wait?: number\n /** Phase 3 timeout in seconds: wait after abort signal for handlers to wrap up (default: 120) */\n abort_wait?: number\n}\n\n/** Application configuration loaded from config.yaml */\nexport interface Config {\n /**\n * Proxy URL for all outgoing requests.\n * Supports http://, https://, socks5://, socks5h:// schemes.\n * Authentication via URL credentials: socks5h://user:pass@host:port\n * Takes precedence over HTTP_PROXY/HTTPS_PROXY environment variables.\n * Not hot-reloadable (requires restart).\n */\n proxy?: string\n system_prompt_overrides?: Array<RewriteRule>\n system_prompt_prepend?: string\n system_prompt_append?: string\n rate_limiter?: RateLimiterConfig\n anthropic?: AnthropicConfig\n /** Model name overrides: request model → target model */\n model_overrides?: Record<string, string>\n /** Compress old tool_result content before truncating (default: true) */\n compress_tool_results_before_truncate?: boolean\n /** Maximum number of history entries to keep in memory (0 = unlimited, default: 200) */\n history_limit?: number\n /** Shutdown timing configuration */\n shutdown?: ShutdownConfig\n /** Stream idle timeout in seconds for all paths (default: 300, 0 = no timeout) */\n stream_idle_timeout?: number\n /** Fetch timeout in seconds: request start → HTTP response headers (default: 0 = no timeout) */\n fetch_timeout?: number\n /** Maximum age (seconds) of an active request before stale reaper forces fail (0 = disabled, default: 600) */\n stale_request_max_age?: number\n}\n\n// ============================================================================\n// Config Loading (mtime-cached)\n// ============================================================================\n\nlet cachedConfig: Config | null = null\nlet configLastMtimeMs: number = 0\n/** Time-based debounce: skip stat() if checked recently */\nlet lastStatTimeMs: number = 0\nconst STAT_DEBOUNCE_MS = 2000\n\nexport async function loadConfig(): Promise<Config> {\n try {\n // Debounce: if we already have a cached config and checked recently, skip stat()\n const now = Date.now()\n if (cachedConfig && now - lastStatTimeMs < STAT_DEBOUNCE_MS) {\n return cachedConfig\n }\n\n const stat = await fs.stat(PATHS.CONFIG_YAML)\n lastStatTimeMs = now\n if (cachedConfig && stat.mtimeMs === configLastMtimeMs) {\n return cachedConfig\n }\n const content = await fs.readFile(PATHS.CONFIG_YAML, \"utf8\")\n const { parse } = await import(\"yaml\")\n const parsed = parse(content)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- yaml.parse returns null for empty files\n cachedConfig = (parsed as Config) ?? {}\n configLastMtimeMs = stat.mtimeMs\n return cachedConfig\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return {}\n }\n // Cache the failed mtime to avoid re-parsing the same broken file every request.\n // The user sees one warning per config change, not one per request.\n try {\n const stat = await fs.stat(PATHS.CONFIG_YAML)\n configLastMtimeMs = stat.mtimeMs\n } catch {\n // File disappeared between first stat and this one — ignore\n }\n consola.warn(\"[config] Failed to load config.yaml:\", err)\n return {}\n }\n}\n\n/** Get the mtime of the currently cached config (0 if not loaded) */\nexport function getConfigMtimeMs(): number {\n return configLastMtimeMs\n}\n\n/** Exposed for testing: reset the mtime cache */\nexport function resetConfigCache(): void {\n cachedConfig = null\n configLastMtimeMs = 0\n lastStatTimeMs = 0\n}\n\n// ============================================================================\n// Config → State Application (hot-reloadable)\n// ============================================================================\n\nlet hasApplied = false\nlet lastAppliedMtimeMs = 0\n\n/**\n * Load config.yaml and apply all hot-reloadable settings to global state.\n *\n * Scalar fields: only overridden when explicitly present in config (deleted keys keep current runtime value).\n * Collection fields (model_overrides, rewrite_system_reminders array): entire replacement when present.\n *\n * Safe to call per-request — loadConfig() is mtime-cached, so unchanged config\n * only costs one stat() syscall.\n *\n * NOT hot-reloaded: rate_limiter (stateful singleton initialized at startup).\n */\nexport async function applyConfigToState(): Promise<Config> {\n const config = await loadConfig()\n\n // Anthropic settings (scalar: override only when present)\n if (config.anthropic) {\n const a = config.anthropic\n if (a.convert_server_tools_to_custom !== undefined) state.convertServerToolsToCustom = a.convert_server_tools_to_custom\n if (a.dedup_tool_calls !== undefined) {\n // Normalize: true → \"input\" for backward compatibility, false → false\n state.dedupToolCalls = a.dedup_tool_calls === true ? \"input\" : a.dedup_tool_calls\n }\n if (a.truncate_read_tool_result !== undefined) state.truncateReadToolResult = a.truncate_read_tool_result\n if (a.rewrite_system_reminders !== undefined) {\n // Collection: entire replacement — deleted rules disappear\n if (typeof a.rewrite_system_reminders === \"boolean\") {\n state.rewriteSystemReminders = a.rewrite_system_reminders\n } else if (Array.isArray(a.rewrite_system_reminders)) {\n state.rewriteSystemReminders = compileRewriteRules(a.rewrite_system_reminders)\n }\n }\n }\n\n // System prompt overrides (collection: entire replacement)\n // Use Array.isArray to guard against YAML null (which passes !== undefined but crashes on .length)\n if (Array.isArray(config.system_prompt_overrides)) {\n state.systemPromptOverrides =\n config.system_prompt_overrides.length > 0 ? compileRewriteRules(config.system_prompt_overrides) : []\n }\n\n // Model overrides (collection: entire replacement from defaults + config)\n // User deletes a key → it reverts to default; user adds a key → it overrides default\n if (config.model_overrides) {\n state.modelOverrides = { ...DEFAULT_MODEL_OVERRIDES, ...config.model_overrides }\n }\n\n // Other settings (scalar: override only when present)\n if (config.compress_tool_results_before_truncate !== undefined)\n state.compressToolResultsBeforeTruncate = config.compress_tool_results_before_truncate\n if (config.history_limit !== undefined) {\n state.historyLimit = config.history_limit\n setHistoryMaxEntries(config.history_limit)\n }\n\n // Shutdown timing (scalar: override only when present)\n if (config.shutdown) {\n const s = config.shutdown\n if (s.graceful_wait !== undefined) state.shutdownGracefulWait = s.graceful_wait\n if (s.abort_wait !== undefined) state.shutdownAbortWait = s.abort_wait\n }\n\n // Top-level timeouts\n if (config.fetch_timeout !== undefined) state.fetchTimeout = config.fetch_timeout\n if (config.stream_idle_timeout !== undefined) state.streamIdleTimeout = config.stream_idle_timeout\n\n // Stale request reaper max age (scalar: override only when present)\n if (config.stale_request_max_age !== undefined) state.staleRequestMaxAge = config.stale_request_max_age\n\n // Log when config actually changes (skip initial startup load)\n const currentMtime = getConfigMtimeMs()\n if (hasApplied && currentMtime !== lastAppliedMtimeMs) {\n consola.info(\"[config] Reloaded config.yaml\")\n }\n hasApplied = true\n lastAppliedMtimeMs = currentMtime\n\n return config\n}\n\n/** Exposed for testing: reset the apply-tracking state */\nexport function resetApplyState(): void {\n hasApplied = false\n lastAppliedMtimeMs = 0\n}\n","/**\n * Error persistence consumer.\n *\n * Subscribes to \"failed\" events on RequestContext and writes structured\n * error files to disk for post-mortem debugging. All data comes from\n * RequestContext (via HistoryEntryData on the event), not from Hono\n * Context — ensuring reliability regardless of whether the HTTP body\n * has been consumed.\n *\n * Output directory: PATHS.ERROR_DIR/{timestamp}_{id}/\n * Files:\n * - meta.json: structured metadata (timestamp, endpoint, model, error, attempts)\n * - request.json: full request payload (messages capped at 50 for size)\n * - response.txt: raw upstream response body (if available)\n * - sse-events.json: recorded SSE events (if streaming request failed mid-stream)\n */\n\nimport consola from \"consola\"\nimport { randomBytes } from \"node:crypto\"\nimport * as fs from \"node:fs/promises\"\nimport * as path from \"node:path\"\n\nimport { PATHS } from \"~/lib/config/paths\"\n\nimport type { RequestContextEvent } from \"./manager\"\nimport type { HistoryEntryData } from \"./request\"\n\n// ============================================================================\n// Consumer entry point\n// ============================================================================\n\n/** Handle context events — only acts on \"failed\" */\nexport function handleErrorPersistence(event: RequestContextEvent): void {\n if (event.type !== \"failed\") return\n\n writeErrorEntry(event.entry).catch((err: unknown) => {\n consola.debug(`[ErrorPersistence] Failed to write error file: ${String(err)}`)\n })\n}\n\n// ============================================================================\n// File writing\n// ============================================================================\n\n/** Max number of messages to include in request.json (to avoid huge files) */\nconst MAX_MESSAGES_IN_DUMP = 50\n\nasync function writeErrorEntry(entry: HistoryEntryData): Promise<void> {\n // Build compact meta (focused on debugging, not full replay)\n const meta = {\n timestamp: new Date(entry.timestamp).toISOString(),\n id: entry.id,\n endpoint: entry.endpoint,\n durationMs: entry.durationMs,\n request: {\n model: entry.request.model,\n stream: entry.request.stream,\n messageCount: entry.request.messages?.length,\n toolCount: entry.request.tools?.length,\n },\n response:\n entry.response ?\n {\n success: entry.response.success,\n model: entry.response.model,\n error: entry.response.error,\n status: entry.response.status,\n }\n : undefined,\n truncation: entry.truncation,\n attempts: entry.attempts,\n }\n\n // Collect file entries: [filename, content] pairs\n const files: Array<[string, string]> = [[\"meta.json\", JSON.stringify(meta, null, 2)]]\n\n // Full request payload (from RequestContext — always available)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive: request may not be set in all failure paths\n if (entry.request) {\n const { messages, ...requestWithoutMessages } = entry.request\n const requestData = {\n ...requestWithoutMessages,\n messageCount: messages?.length,\n // Include messages only for small payloads to avoid huge files\n ...(messages && messages.length <= MAX_MESSAGES_IN_DUMP && { messages }),\n }\n files.push([\"request.json\", JSON.stringify(requestData, null, 2)])\n }\n\n // Raw upstream response body (from HTTPError.responseText, preserved in ResponseData)\n if (entry.response?.responseText) {\n files.push([\"response.txt\", entry.response.responseText])\n }\n\n // SSE events (useful for diagnosing mid-stream failures)\n if (entry.sseEvents?.length) {\n files.push([\"sse-events.json\", JSON.stringify(entry.sseEvents, null, 2)])\n }\n\n // Create directory and write all files (only after all data is collected)\n const id = randomBytes(4).toString(\"hex\")\n const dirPath = path.join(PATHS.ERROR_DIR, `${formatTimestamp()}_${id}`)\n\n await fs.mkdir(dirPath, { recursive: true })\n await Promise.all(files.map(([name, content]) => fs.writeFile(path.join(dirPath, name), content)))\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Format timestamp as YYMMDD_HHmmss for error directory names */\nfunction formatTimestamp(): string {\n const now = new Date()\n const pad = (n: number) => String(n).padStart(2, \"0\")\n const YY = String(now.getFullYear()).slice(2)\n return `${YY}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`\n}\n","/**\n * Context event consumers — bridge between RequestContext events and subsystems.\n *\n * Three consumers subscribe to RequestContextManager \"change\" events:\n * 1. History consumer → inserts/updates HistoryEntry in the store\n * 2. TUI consumer → updates tuiLogger for terminal display\n * 3. (WebSocket is handled implicitly via store's notifyEntryAdded/Updated)\n */\n\nimport type { HistoryEntry, MessageContent } from \"~/lib/history\"\n\nimport { getCurrentSession, insertEntry, isHistoryEnabled, updateEntry } from \"~/lib/history/store\"\nimport { tuiLogger } from \"~/lib/tui\"\n\nimport type { RequestContextEvent, RequestContextManager } from \"./manager\"\nimport type { HistoryEntryData, ResponseData } from \"./request\"\n\n// ─── History Consumer ───\n\nfunction handleHistoryEvent(event: RequestContextEvent): void {\n if (!isHistoryEnabled()) return\n\n switch (event.type) {\n case \"created\": {\n const ctx = event.context\n const sessionId = getCurrentSession(ctx.endpoint)\n\n const entry: HistoryEntry = {\n id: ctx.id,\n sessionId,\n timestamp: ctx.startTime,\n endpoint: ctx.endpoint,\n request: {\n model: ctx.originalRequest?.model,\n messages: ctx.originalRequest?.messages as Array<MessageContent> | undefined,\n stream: ctx.originalRequest?.stream,\n tools: ctx.originalRequest?.tools as HistoryEntry[\"request\"][\"tools\"],\n system: ctx.originalRequest?.system as HistoryEntry[\"request\"][\"system\"],\n },\n }\n\n insertEntry(entry)\n break\n }\n\n case \"updated\": {\n // originalRequest is set after the \"created\" event fires (the context\n // doesn't exist yet when create() is called, so setOriginalRequest()\n // comes after). Update the entry with the actual request data now.\n if (event.field === \"originalRequest\" && event.context.originalRequest) {\n const orig = event.context.originalRequest\n updateEntry(event.context.id, {\n request: {\n model: orig.model,\n messages: orig.messages as Array<MessageContent> | undefined,\n stream: orig.stream,\n tools: orig.tools as HistoryEntry[\"request\"][\"tools\"],\n system: orig.system as HistoryEntry[\"request\"][\"system\"],\n },\n })\n }\n if (event.field === \"rewrites\" && event.context.rewrites) {\n updateEntry(event.context.id, { rewrites: event.context.rewrites })\n }\n break\n }\n\n case \"completed\":\n case \"failed\": {\n const entryData = event.entry\n const response = toHistoryResponse(entryData)\n\n updateEntry(entryData.id, {\n response,\n durationMs: entryData.durationMs,\n sseEvents: entryData.sseEvents,\n })\n break\n }\n\n default: {\n break\n }\n }\n}\n\n// ─── TUI Consumer ───\n\nfunction handleTuiEvent(event: RequestContextEvent): void {\n switch (event.type) {\n case \"state_changed\": {\n const tuiLogId = event.context.tuiLogId\n if (!tuiLogId) return\n\n const newState = event.context.state\n if (newState === \"streaming\") {\n tuiLogger.updateRequest(tuiLogId, { status: \"streaming\" })\n } else if (newState === \"executing\") {\n tuiLogger.updateRequest(tuiLogId, { status: \"executing\" })\n }\n break\n }\n\n case \"updated\": {\n const tuiLogId = event.context.tuiLogId\n if (!tuiLogId) return\n\n // When attempts are updated, add retry tags\n if (event.field === \"attempts\" && event.context.attempts.length > 1) {\n const attempt = event.context.currentAttempt\n if (attempt?.strategy) {\n tuiLogger.updateRequest(tuiLogId, { tags: [attempt.strategy] })\n }\n }\n break\n }\n\n case \"completed\": {\n const ctx = event.context\n const tuiLogId = ctx.tuiLogId\n if (!tuiLogId) return\n\n const response = ctx.response\n if (response) {\n tuiLogger.updateRequest(tuiLogId, {\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n cacheReadInputTokens: response.usage.cache_read_input_tokens ?? undefined,\n cacheCreationInputTokens: response.usage.cache_creation_input_tokens ?? undefined,\n queueWaitMs: ctx.queueWaitMs || undefined,\n })\n // \"completed\" event implies upstream returned 200\n tuiLogger.finishRequest(tuiLogId, { statusCode: 200 })\n }\n break\n }\n\n case \"failed\": {\n const ctx = event.context\n const tuiLogId = ctx.tuiLogId\n if (!tuiLogId) return\n\n tuiLogger.finishRequest(tuiLogId, {\n error: ctx.response?.error ?? \"Unknown error\",\n // HTTP status from the last attempt's classified error (if available)\n statusCode: ctx.currentAttempt?.error?.status || undefined,\n })\n break\n }\n\n default: {\n break\n }\n }\n}\n\n// ─── Helpers ───\n\nfunction toHistoryResponse(entryData: HistoryEntryData): HistoryEntry[\"response\"] | undefined {\n if (!entryData.response) return undefined\n\n const r: ResponseData = entryData.response\n return {\n success: r.success,\n model: r.model,\n usage: {\n input_tokens: r.usage.input_tokens,\n output_tokens: r.usage.output_tokens,\n cache_read_input_tokens: r.usage.cache_read_input_tokens,\n cache_creation_input_tokens: r.usage.cache_creation_input_tokens,\n },\n stop_reason: r.stop_reason,\n error: r.error,\n content: r.content as MessageContent | null,\n }\n}\n\n// ─── Registration ───\n\nimport { handleErrorPersistence } from \"./error-persistence\"\n\nexport function registerContextConsumers(manager: RequestContextManager): void {\n manager.on(\"change\", handleHistoryEvent)\n manager.on(\"change\", handleTuiEvent)\n manager.on(\"change\", handleErrorPersistence)\n}\n","import type { Model } from \"./client\"\n\n// ============================================================================\n// Copilot API endpoint identifiers\n// ============================================================================\n\nexport const ENDPOINT = {\n MESSAGES: \"/v1/messages\",\n CHAT_COMPLETIONS: \"/chat/completions\",\n RESPONSES: \"/responses\",\n EMBEDDINGS: \"/v1/embeddings\",\n} as const\n\n/** Capability type → default endpoints for legacy models without `supported_endpoints` */\nconst LEGACY_ENDPOINTS: Record<string, Array<string>> = {\n chat: [ENDPOINT.CHAT_COMPLETIONS],\n completion: [ENDPOINT.CHAT_COMPLETIONS],\n embeddings: [ENDPOINT.EMBEDDINGS],\n}\n\n/**\n * Get the effective endpoint list for a model.\n *\n * Returns `supported_endpoints` when present, otherwise infers from\n * `capabilities.type` for legacy models that predate the field.\n */\nexport function getEffectiveEndpoints(model: Model): Array<string> | undefined {\n if (model.supported_endpoints) return model.supported_endpoints\n const type = model.capabilities?.type\n if (type) return LEGACY_ENDPOINTS[type]\n return undefined\n}\n\n// ============================================================================\n// Endpoint support checks\n// ============================================================================\n\n/**\n * Check if a model supports a given API endpoint.\n *\n * When `supported_endpoints` is absent (legacy models like gpt-4, gemini),\n * we assume all endpoints are supported — these models predate the field\n * and rely on /chat/completions as a universal fallback.\n */\nexport function isEndpointSupported(model: Model | undefined, endpoint: string): boolean {\n if (!model?.supported_endpoints) return true\n return model.supported_endpoints.includes(endpoint)\n}\n\n/**\n * Assert that a model supports a given endpoint, throwing a descriptive error if not.\n * Returns the validated model for chaining.\n */\nexport function assertEndpointSupported(model: Model | undefined, endpoint: string): void {\n if (isEndpointSupported(model, endpoint)) return\n\n const modelId = model?.id ?? \"unknown\"\n const supported = model?.supported_endpoints?.join(\", \") ?? \"none\"\n const msg = `Model \"${modelId}\" does not support ${endpoint}. Supported endpoints: ${supported}`\n throw new Error(msg)\n}\n","/**\n * Proxy configuration: HTTP/HTTPS and SOCKS5/5h proxy support.\n *\n * Priority: explicit proxy URL (CLI --proxy or config.yaml) > env vars (--http-proxy-from-env).\n * On Node.js, proxying works via undici's global dispatcher.\n * On Bun, HTTP proxies are set via env vars (Bun handles them natively); SOCKS5 is not supported.\n */\n\nimport consola from \"consola\"\nimport tls from \"node:tls\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { SocksClient, type SocksProxy } from \"socks\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport interface ProxyOptions {\n /** Explicit proxy URL (from CLI --proxy or config.yaml proxy) */\n url?: string\n /** Fall back to HTTP_PROXY/HTTPS_PROXY environment variables */\n fromEnv: boolean\n}\n\n/**\n * Initialize proxy for all outgoing fetch requests.\n *\n * On Node.js: sets undici's global dispatcher.\n * On Bun: sets process.env.HTTP_PROXY/HTTPS_PROXY for HTTP proxies (Bun handles natively).\n *\n * Must be called before any network requests.\n */\nexport function initProxy(options: ProxyOptions): void {\n if (typeof Bun !== \"undefined\") {\n initProxyBun(options)\n return\n }\n\n initProxyNode(options)\n}\n\n/** Format a proxy URL for display (strip credentials) */\nexport function formatProxyDisplay(proxyUrl: string): string {\n try {\n const u = new URL(proxyUrl)\n const auth = u.username ? `${u.username}:***@` : \"\"\n return `${u.protocol}//${auth}${u.host}`\n } catch {\n return proxyUrl\n }\n}\n\n// ============================================================================\n// Node.js implementation (undici dispatchers)\n// ============================================================================\n\nfunction initProxyNode(options: ProxyOptions): void {\n try {\n if (options.url) {\n const dispatcher = createDispatcherForUrl(options.url)\n setGlobalDispatcher(dispatcher)\n consola.debug(`Proxy configured: ${formatProxyDisplay(options.url)}`)\n return\n }\n\n if (options.fromEnv) {\n const dispatcher = new EnvProxyDispatcher()\n setGlobalDispatcher(dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n }\n } catch (err) {\n consola.error(\"Proxy setup failed:\", err)\n throw err\n }\n}\n\n/** Create the appropriate undici dispatcher for a proxy URL scheme */\nexport function createDispatcherForUrl(proxyUrl: string): Dispatcher {\n const url = new URL(proxyUrl)\n const protocol = url.protocol.toLowerCase()\n\n if (protocol === \"http:\" || protocol === \"https:\") {\n return new ProxyAgent(proxyUrl)\n }\n\n if (protocol === \"socks5:\" || protocol === \"socks5h:\") {\n return createSocksAgent(url)\n }\n\n throw new Error(`Unsupported proxy protocol: ${protocol}. Supported: http, https, socks5, socks5h`)\n}\n\n// ============================================================================\n// SOCKS5/5h agent\n// ============================================================================\n\n/**\n * Create an undici Agent that routes connections through a SOCKS5/5h proxy.\n *\n * For socks5h:// the proxy performs DNS resolution (hostname passed as-is).\n * For socks5:// the hostname is also passed to the proxy (proxy resolves).\n * Both protocols support username/password authentication via URL credentials.\n */\nfunction createSocksAgent(proxyUrl: URL): Agent {\n const proxy: SocksProxy = {\n host: proxyUrl.hostname,\n port: Number(proxyUrl.port) || 1080,\n type: 5,\n }\n\n // Support username/password authentication\n if (proxyUrl.username) {\n proxy.userId = decodeURIComponent(proxyUrl.username)\n proxy.password = proxyUrl.password ? decodeURIComponent(proxyUrl.password) : undefined\n }\n\n return new Agent({\n connect(opts, callback) {\n const destPort = Number(opts.port) || (opts.protocol === \"https:\" ? 443 : 80)\n\n SocksClient.createConnection({\n proxy,\n command: \"connect\",\n destination: {\n host: opts.hostname,\n port: destPort,\n },\n })\n .then(({ socket }) => {\n if (opts.protocol === \"https:\") {\n // Upgrade to TLS for HTTPS destinations\n const tlsSocket = tls.connect({\n socket,\n servername: opts.servername ?? opts.hostname,\n })\n callback(null, tlsSocket)\n } else {\n callback(null, socket)\n }\n })\n .catch((err) => {\n callback(err instanceof Error ? err : new Error(String(err)), null)\n })\n },\n })\n}\n\n// ============================================================================\n// Environment variable proxy dispatcher (existing behavior)\n// ============================================================================\n\n/**\n * Custom dispatcher that routes requests through proxies based on environment variables.\n * Uses proxy-from-env to resolve HTTP_PROXY/HTTPS_PROXY/NO_PROXY per-URL.\n */\nclass EnvProxyDispatcher extends Agent {\n private proxies = new Map<string, ProxyAgent>()\n\n dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {\n try {\n const origin = this.getOriginUrl(options.origin)\n const proxyUrl = this.getProxyUrl(origin)\n\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return super.dispatch(options, handler)\n }\n\n const agent = this.getOrCreateProxyAgent(proxyUrl)\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${formatProxyDisplay(proxyUrl)}`)\n return agent.dispatch(options, handler)\n } catch {\n return super.dispatch(options, handler)\n }\n }\n\n private getOriginUrl(origin: Dispatcher.DispatchOptions[\"origin\"]): URL {\n return typeof origin === \"string\" ? new URL(origin) : (origin as URL)\n }\n\n private getProxyUrl(origin: URL): string | undefined {\n const raw = getProxyForUrl(origin.toString())\n return raw && raw.length > 0 ? raw : undefined\n }\n\n private getOrCreateProxyAgent(proxyUrl: string): ProxyAgent {\n let agent = this.proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n this.proxies.set(proxyUrl, agent)\n }\n return agent\n }\n\n override async close(): Promise<void> {\n await super.close()\n await Promise.all([...this.proxies.values()].map((p) => p.close()))\n this.proxies.clear()\n }\n\n override destroy(err?: Error | null): Promise<void>\n override destroy(callback: () => void): void\n override destroy(err: Error | null, callback: () => void): void\n override destroy(errOrCallback?: Error | null | (() => void), callback?: () => void): Promise<void> | void {\n // Clean up proxy agents (fire-and-forget, errors are ignored)\n for (const agent of this.proxies.values()) {\n if (typeof errOrCallback === \"function\") {\n agent.destroy(errOrCallback)\n } else if (callback) {\n agent.destroy(errOrCallback ?? null, callback)\n } else {\n agent.destroy(errOrCallback ?? null).catch(() => {\n // Ignore cleanup errors\n })\n }\n }\n this.proxies.clear()\n\n // Call super with appropriate overload\n if (typeof errOrCallback === \"function\") {\n super.destroy(errOrCallback)\n return\n } else if (callback) {\n super.destroy(errOrCallback ?? null, callback)\n return\n } else {\n return super.destroy(errOrCallback ?? null)\n }\n }\n}\n\n// ============================================================================\n// Bun implementation\n// ============================================================================\n\n/**\n * Initialize proxy for Bun runtime.\n * Bun handles HTTP_PROXY/HTTPS_PROXY env vars natively.\n * SOCKS5 proxies are not supported on Bun.\n */\nfunction initProxyBun(options: ProxyOptions): void {\n if (!options.url) return\n\n const url = new URL(options.url)\n const protocol = url.protocol.toLowerCase()\n\n if (protocol === \"socks5:\" || protocol === \"socks5h:\") {\n throw new Error(\"SOCKS5 proxy is not supported on Bun runtime. Use Node.js or an HTTP proxy instead.\")\n }\n\n // Set env vars for Bun's native HTTP proxy support\n process.env.HTTP_PROXY = options.url\n process.env.HTTPS_PROXY = options.url\n consola.debug(`Proxy configured (Bun env): ${formatProxyDisplay(options.url)}`)\n}\n","import type { Context } from \"hono\"\n\nimport {\n clearHistory,\n deleteSession,\n exportHistory,\n getEntry,\n getHistorySummaries,\n getSession,\n getSessionEntries,\n getSessions,\n getStats,\n isHistoryEnabled,\n type EndpointType,\n type QueryOptions,\n} from \"~/lib/history\"\n\nexport function handleGetEntries(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const query = c.req.query()\n const options: QueryOptions = {\n page: query.page ? Number.parseInt(query.page, 10) : undefined,\n limit: query.limit ? Number.parseInt(query.limit, 10) : undefined,\n model: query.model || undefined,\n endpoint: query.endpoint as EndpointType | undefined,\n success: query.success ? query.success === \"true\" : undefined,\n from: query.from ? Number.parseInt(query.from, 10) : undefined,\n to: query.to ? Number.parseInt(query.to, 10) : undefined,\n search: query.search || undefined,\n sessionId: query.sessionId || undefined,\n }\n\n const result = getHistorySummaries(options)\n return c.json(result)\n}\n\nexport function handleGetEntry(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const entry = getEntry(id)\n\n if (!entry) {\n return c.json({ error: \"Entry not found\" }, 404)\n }\n\n return c.json(entry)\n}\n\nexport function handleDeleteEntries(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n clearHistory()\n return c.json({ success: true, message: \"History cleared\" })\n}\n\nexport function handleGetStats(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const stats = getStats()\n return c.json(stats)\n}\n\nexport function handleExport(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const format = (c.req.query(\"format\") || \"json\") as \"json\" | \"csv\"\n const data = exportHistory(format)\n\n if (format === \"csv\") {\n c.header(\"Content-Type\", \"text/csv\")\n c.header(\"Content-Disposition\", \"attachment; filename=history.csv\")\n } else {\n c.header(\"Content-Type\", \"application/json\")\n c.header(\"Content-Disposition\", \"attachment; filename=history.json\")\n }\n\n return c.body(data)\n}\n\n/** Session management endpoints */\nexport function handleGetSessions(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const result = getSessions()\n return c.json(result)\n}\n\nexport function handleGetSession(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const session = getSession(id)\n\n if (!session) {\n return c.json({ error: \"Session not found\" }, 404)\n }\n\n // Include paginated entries in the session response\n const query = c.req.query()\n const result = getSessionEntries(id, {\n page: query.page ? Number.parseInt(query.page, 10) : undefined,\n limit: query.limit ? Number.parseInt(query.limit, 10) : undefined,\n })\n\n return c.json({\n ...session,\n ...result,\n })\n}\n\nexport function handleDeleteSession(c: Context) {\n if (!isHistoryEnabled()) {\n return c.json({ error: \"History recording is not enabled\" }, 400)\n }\n\n const id = c.req.param(\"id\")\n const success = deleteSession(id)\n\n if (!success) {\n return c.json({ error: \"Session not found\" }, 404)\n }\n\n return c.json({ success: true, message: \"Session deleted\" })\n}\n","/** MIME type helper for history UI static assets */\nexport function getMimeType(path: string): string {\n if (path.endsWith(\".html\")) return \"text/html\"\n if (path.endsWith(\".js\")) return \"application/javascript\"\n if (path.endsWith(\".css\")) return \"text/css\"\n if (path.endsWith(\".json\")) return \"application/json\"\n if (path.endsWith(\".svg\")) return \"image/svg+xml\"\n if (path.endsWith(\".png\")) return \"image/png\"\n if (path.endsWith(\".ico\")) return \"image/x-icon\"\n return \"application/octet-stream\"\n}\n","import type { UpgradeWebSocket } from \"hono/ws\"\nimport type { Server as NodeHttpServer } from \"node:http\"\n\nimport consola from \"consola\"\nimport { Hono } from \"hono\"\nimport { existsSync } from \"node:fs\"\nimport { access, constants } from \"node:fs/promises\"\nimport { readFile } from \"node:fs/promises\"\nimport { join, resolve } from \"node:path\"\n\nimport { addClient, removeClient } from \"~/lib/history\"\n\nimport {\n handleDeleteEntries,\n handleDeleteSession,\n handleExport,\n handleGetEntries,\n handleGetEntry,\n handleGetSession,\n handleGetSessions,\n handleGetStats,\n} from \"./api\"\nimport { getMimeType } from \"./assets\"\n\nexport const historyRoutes = new Hono()\n\n/** API endpoints */\nhistoryRoutes.get(\"/api/entries\", handleGetEntries)\nhistoryRoutes.get(\"/api/entries/:id\", handleGetEntry)\nhistoryRoutes.delete(\"/api/entries\", handleDeleteEntries)\nhistoryRoutes.get(\"/api/stats\", handleGetStats)\nhistoryRoutes.get(\"/api/export\", handleExport)\n\n/** Session endpoints */\nhistoryRoutes.get(\"/api/sessions\", handleGetSessions)\nhistoryRoutes.get(\"/api/sessions/:id\", handleGetSession)\nhistoryRoutes.delete(\"/api/sessions/:id\", handleDeleteSession)\n\n/**\n * Initialize WebSocket support for history real-time updates.\n * Registers the /ws route on historyRoutes using the appropriate WebSocket\n * adapter for the current runtime (hono/bun for Bun, @hono/node-ws for Node.js).\n *\n * @param rootApp - The root Hono app instance (needed by @hono/node-ws to match upgrade requests)\n * @returns An `injectWebSocket` function that must be called with the Node.js HTTP server\n * after the server is created. Returns `undefined` under Bun (no injection needed).\n */\nexport async function initHistoryWebSocket(rootApp: Hono): Promise<((server: NodeHttpServer) => void) | undefined> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let upgradeWs: UpgradeWebSocket<any>\n let injectFn: ((server: NodeHttpServer) => void) | undefined\n\n if (typeof globalThis.Bun !== \"undefined\") {\n // Bun runtime: use hono/bun adapter\n const { upgradeWebSocket } = await import(\"hono/bun\")\n upgradeWs = upgradeWebSocket\n } else {\n // Node.js runtime: use @hono/node-ws adapter\n const { createNodeWebSocket } = await import(\"@hono/node-ws\")\n const nodeWs = createNodeWebSocket({ app: rootApp })\n upgradeWs = nodeWs.upgradeWebSocket\n injectFn = (server: NodeHttpServer) => nodeWs.injectWebSocket(server)\n }\n\n // Register on the root app directly — historyRoutes sub-app has already been\n // mounted via app.route(\"/history\", historyRoutes) at import time, so adding\n // routes to historyRoutes here won't be visible on the root app.\n rootApp.get(\n \"/history/ws\",\n upgradeWs(() => ({\n onOpen(_event, ws) {\n addClient(ws.raw as unknown as WebSocket)\n },\n onClose(_event, ws) {\n removeClient(ws.raw as unknown as WebSocket)\n },\n onMessage(_event, _ws) {\n // Currently we don't process messages from clients\n },\n onError(event, ws) {\n consola.debug(\"WebSocket error:\", event)\n removeClient(ws.raw as unknown as WebSocket)\n },\n })),\n )\n\n return injectFn\n}\n\n/**\n * Resolve a UI directory that exists at runtime.\n * In dev mode this file lives at src/routes/history/ — 3 levels below project root.\n * In bundled mode (dist/main.mjs) — 1 level below project root.\n * We try both and fall back to the first candidate.\n */\nfunction resolveUiDir(subpath: string): string {\n const candidates = [\n join(import.meta.dirname, \"../../..\", \"ui\", subpath), // dev: src/routes/history/ → root\n join(import.meta.dirname, \"..\", \"ui\", subpath), // bundled: dist/ → root\n ]\n return candidates.find((c) => existsSync(c)) ?? candidates[0]\n}\n\n/** Static assets for legacy UI v1 */\nconst v1Dir = resolveUiDir(\"history-v1\")\n\n/** v1 root serves index.html directly */\nhistoryRoutes.get(\"/v1\", (c) => {\n return c.redirect(\"/history/v1/index.html\")\n})\n\n/** v1 static assets (CSS, JS) - no caching for development */\nhistoryRoutes.get(\"/v1/*\", async (c) => {\n const filePath = c.req.path.replace(\"/history/v1\", \"\")\n if (!filePath) return c.notFound()\n const fullPath = resolve(join(v1Dir, filePath))\n // Prevent path traversal\n if (!fullPath.startsWith(v1Dir)) return c.notFound()\n try {\n await access(fullPath, constants.R_OK)\n } catch {\n return c.notFound()\n }\n const content = await readFile(fullPath, \"utf8\")\n return new Response(content, {\n headers: {\n \"Content-Type\": getMimeType(filePath),\n \"Cache-Control\": \"no-cache\",\n },\n })\n})\n\n/** Static assets and routes for Vue UI v3 */\nconst v3Dir = resolveUiDir(\"history-v3/dist\")\n\nhistoryRoutes.get(\"/v3\", async (c) => {\n try {\n await access(join(v3Dir, \"index.html\"), constants.R_OK)\n const content = await readFile(join(v3Dir, \"index.html\"), \"utf8\")\n return c.html(content)\n } catch {\n return c.notFound()\n }\n})\n\nhistoryRoutes.get(\"/v3/assets/*\", async (c) => {\n const filePath = c.req.path.replace(\"/history/v3\", \"\")\n if (!filePath) return c.notFound()\n const fullPath = resolve(join(v3Dir, filePath))\n if (!fullPath.startsWith(v3Dir)) return c.notFound()\n try {\n await access(fullPath, constants.R_OK)\n const content = await readFile(fullPath)\n return new Response(content, {\n headers: {\n \"Content-Type\": getMimeType(filePath),\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n })\n } catch {\n return c.notFound()\n }\n})\n\nhistoryRoutes.get(\"/\", (c) => {\n return c.redirect(\"/history/v1\")\n})\n\nhistoryRoutes.get(\"/index.html\", (c) => {\n return c.redirect(\"/history/\")\n})\n","/**\n * Conversion utilities for Responses API data structures.\n *\n * Converts between OpenAI Responses API input/output formats and the\n * unified MessageContent format used for history storage and display.\n */\n\nimport type { MessageContent } from \"~/lib/history\"\nimport type { ResponsesInputItem, ResponsesOutputItem } from \"~/types/api/openai-responses\"\n\n// ============================================================================\n// Input conversion\n// ============================================================================\n\n/**\n * Convert Responses API input items to MessageContent format for history storage.\n * Maps Responses-specific item types to the unified MessageContent structure\n * that the history UI understands.\n */\nexport function responsesInputToMessages(input: string | Array<ResponsesInputItem>): Array<MessageContent> {\n if (typeof input === \"string\") {\n return [{ role: \"user\", content: input }]\n }\n\n const messages: Array<MessageContent> = []\n for (const item of input) {\n switch (item.type) {\n case \"message\":\n case undefined: {\n // Regular message — convert content parts to Anthropic-style blocks\n const role = item.role ?? \"user\"\n let content: string | Array<unknown> | null\n\n if (typeof item.content === \"string\") {\n content = item.content\n } else if (Array.isArray(item.content)) {\n content = item.content.map((part) => {\n switch (part.type) {\n case \"input_text\": {\n return { type: \"text\", text: part.text }\n }\n case \"output_text\": {\n return { type: \"text\", text: part.text }\n }\n case \"input_image\": {\n return { type: \"image\", source: { type: \"url\", url: part.image_url } }\n }\n case \"input_file\": {\n return { type: \"file\", file_id: part.file_id, filename: part.filename }\n }\n default: {\n return part\n }\n }\n })\n } else {\n content = null\n }\n\n messages.push({ role, content })\n break\n }\n\n case \"function_call\": {\n // Function call from assistant — convert to OpenAI tool_calls format\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? item.id ?? \"\",\n type: \"function\",\n function: { name: item.name ?? \"\", arguments: item.arguments ?? \"\" },\n },\n ],\n })\n break\n }\n\n case \"function_call_output\": {\n // Function output — convert to OpenAI tool response format\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id ?? \"\",\n })\n break\n }\n\n case \"item_reference\": {\n // Item reference — store as informational marker\n messages.push({\n role: \"system\",\n content: `[item_reference: ${item.id ?? \"unknown\"}]`,\n })\n break\n }\n\n default: {\n // Skip unknown item types silently\n break\n }\n }\n }\n\n return messages\n}\n\n// ============================================================================\n// Output conversion\n// ============================================================================\n\n/**\n * Convert Responses API output items to a unified MessageContent for history storage.\n * Extracts text content and function calls from the output array.\n */\nexport function responsesOutputToContent(output: Array<ResponsesOutputItem>): MessageContent | null {\n const textParts: Array<string> = []\n const toolCalls: Array<{ id: string; type: string; function: { name: string; arguments: string } }> = []\n\n for (const item of output) {\n if (item.type === \"message\") {\n for (const part of item.content) {\n if (part.type === \"output_text\") textParts.push(part.text)\n if (part.type === \"refusal\") textParts.push(`[Refusal: ${part.refusal}]`)\n }\n }\n if (item.type === \"function_call\") {\n toolCalls.push({\n id: item.call_id,\n type: \"function\",\n function: { name: item.name, arguments: item.arguments },\n })\n }\n }\n\n if (textParts.length === 0 && toolCalls.length === 0) return null\n\n return {\n role: \"assistant\",\n content: textParts.join(\"\") || null,\n ...(toolCalls.length > 0 && { tool_calls: toolCalls }),\n }\n}\n","/**\n * Stream accumulator for Responses API format.\n * Accumulates semantic SSE events into a final state for history/tracking.\n */\n\nimport type { BaseStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\nimport type { ResponsesStreamEvent } from \"~/types/api/openai-responses\"\n\n/** Internal tool call accumulator using string array to avoid O(n²) concatenation */\ninterface ToolCallAccumulator {\n id: string\n callId: string\n name: string\n argumentParts: Array<string>\n}\n\n/** Stream accumulator for Responses API format */\nexport interface ResponsesStreamAccumulator extends BaseStreamAccumulator {\n status: string\n responseId: string\n toolCalls: Array<{ id: string; callId: string; name: string; arguments: string }>\n /** Tool call accumulators indexed by output_index */\n toolCallMap: Map<number, ToolCallAccumulator>\n /** Text content parts for O(1) accumulation, joined on read via finalContent() */\n contentParts: Array<string>\n}\n\nexport function createResponsesStreamAccumulator(): ResponsesStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n content: \"\",\n status: \"\",\n responseId: \"\",\n toolCalls: [],\n toolCallMap: new Map(),\n contentParts: [],\n }\n}\n\n/** Get the final accumulated content string */\nexport function finalizeResponsesContent(acc: ResponsesStreamAccumulator): string {\n if (acc.contentParts.length > 0) {\n acc.content = acc.contentParts.join(\"\")\n acc.contentParts = []\n }\n return acc.content\n}\n\n/** Accumulate a single parsed Responses API event into the accumulator */\nexport function accumulateResponsesStreamEvent(event: ResponsesStreamEvent, acc: ResponsesStreamAccumulator) {\n switch (event.type) {\n case \"response.created\":\n case \"response.in_progress\": {\n if (event.response.model) acc.model = event.response.model\n if (event.response.id) acc.responseId = event.response.id\n break\n }\n\n case \"response.completed\": {\n acc.status = event.response.status\n if (event.response.model) acc.model = event.response.model\n if (event.response.usage) {\n acc.inputTokens = event.response.usage.input_tokens\n acc.outputTokens = event.response.usage.output_tokens\n }\n break\n }\n\n case \"response.failed\":\n case \"response.incomplete\": {\n acc.status = event.response.status\n break\n }\n\n case \"response.output_item.added\": {\n if (event.item.type === \"function_call\") {\n acc.toolCallMap.set(event.output_index, {\n id: event.item.id,\n callId: \"call_id\" in event.item ? event.item.call_id : \"\",\n name: \"name\" in event.item ? event.item.name : \"\",\n argumentParts: [],\n })\n }\n break\n }\n\n case \"response.output_text.delta\": {\n acc.contentParts.push(event.delta)\n break\n }\n\n case \"response.function_call_arguments.delta\": {\n const tcAcc = acc.toolCallMap.get(event.output_index)\n if (tcAcc) {\n tcAcc.argumentParts.push(event.delta)\n }\n break\n }\n\n case \"response.function_call_arguments.done\": {\n const tcAcc = acc.toolCallMap.get(event.output_index)\n if (tcAcc) {\n acc.toolCalls.push({\n id: tcAcc.id,\n callId: tcAcc.callId,\n name: tcAcc.name,\n arguments: tcAcc.argumentParts.join(\"\"),\n })\n }\n break\n }\n\n case \"response.output_item.done\": {\n // Final output item — if it's a function call that wasn't already finalized\n // via arguments.done, finalize it now\n if (event.item.type === \"function_call\") {\n const existing = acc.toolCalls.find((tc) => tc.id === event.item.id)\n if (!existing) {\n acc.toolCalls.push({\n id: event.item.id,\n callId: \"call_id\" in event.item ? event.item.call_id : \"\",\n name: \"name\" in event.item ? event.item.name : \"\",\n arguments: \"arguments\" in event.item ? event.item.arguments : \"\",\n })\n }\n }\n break\n }\n\n // Other events don't need accumulation\n default: {\n break\n }\n }\n}\n","/**\n * Request execution pipeline with pluggable retry strategies.\n *\n * Unifies the retry loop pattern shared by all API handlers:\n * messages, chat-completions, and responses.\n */\n\nimport consola from \"consola\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { ApiError } from \"~/lib/error\"\nimport type { EndpointType } from \"~/lib/history/store\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { classifyError } from \"~/lib/error\"\n\n// --- FormatAdapter ---\n\nexport interface SanitizeResult<TPayload> {\n payload: TPayload\n /** Convenience: total blocks removed (sum of orphans + empty text) */\n removedCount: number\n /** Convenience: number of system reminder tags removed */\n systemReminderRemovals: number\n /** Structured breakdown of what was removed/modified — format-specific detail */\n stats?: Record<string, number>\n}\n\nexport interface FormatAdapter<TPayload> {\n readonly format: EndpointType\n sanitize(payload: TPayload): SanitizeResult<TPayload>\n /** Execute API call — raw execution without rate limiting wrapper */\n execute(payload: TPayload): Promise<{ result: unknown; queueWaitMs: number }>\n logPayloadSize(payload: TPayload): void | Promise<void>\n}\n\n// --- RetryStrategy ---\n\nexport interface RetryContext<TPayload> {\n attempt: number\n originalPayload: TPayload\n model: Model | undefined\n maxRetries: number\n}\n\nexport type RetryAction<TPayload> =\n | { action: \"retry\"; payload: TPayload; waitMs?: number; meta?: Record<string, unknown> }\n | { action: \"abort\"; error: ApiError }\n\nexport interface RetryStrategy<TPayload> {\n readonly name: string\n /** Check if this strategy can handle the given error */\n canHandle(error: ApiError): boolean\n /** Handle the error and decide whether to retry or abort */\n handle(error: ApiError, payload: TPayload, context: RetryContext<TPayload>): Promise<RetryAction<TPayload>>\n}\n\n// --- Pipeline ---\n\nexport interface PipelineResult {\n response: unknown\n effectivePayload: unknown\n queueWaitMs: number\n totalRetries: number\n}\n\nexport interface PipelineOptions<TPayload> {\n adapter: FormatAdapter<TPayload>\n strategies: Array<RetryStrategy<TPayload>>\n payload: TPayload\n originalPayload: TPayload\n model: Model | undefined\n maxRetries?: number\n /** Optional request context for lifecycle tracking */\n requestContext?: RequestContext\n /** Called before each attempt (for tracking tags, etc.) */\n onBeforeAttempt?: (attempt: number, payload: TPayload) => void\n /** Called after successful truncation retry (for recording rewrites, etc.) */\n onRetry?: (attempt: number, strategyName: string, newPayload: TPayload, meta?: Record<string, unknown>) => void\n}\n\n/**\n * Execute a request through the pipeline with retry strategies.\n *\n * Flow:\n * 1. Execute API call with the current payload\n * 2. On success → return response\n * 3. On failure → classify error → find first matching strategy → handle\n * - retry → use new payload, loop back to step 1\n * - abort or no strategy → throw error\n */\nexport async function executeRequestPipeline<TPayload>(opts: PipelineOptions<TPayload>): Promise<PipelineResult> {\n const { adapter, strategies, originalPayload, model, maxRetries = 3, requestContext, onBeforeAttempt, onRetry } = opts\n\n let effectivePayload = opts.payload\n let lastError: unknown = null\n let totalQueueWaitMs = 0\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n onBeforeAttempt?.(attempt, effectivePayload)\n requestContext?.beginAttempt({ strategy: attempt > 0 ? \"retry\" : undefined })\n requestContext?.transition(\"executing\")\n\n try {\n const { result: response, queueWaitMs } = await adapter.execute(effectivePayload)\n totalQueueWaitMs += queueWaitMs\n requestContext?.addQueueWaitMs(queueWaitMs)\n\n return {\n response,\n effectivePayload,\n queueWaitMs: totalQueueWaitMs,\n totalRetries: attempt,\n }\n } catch (error) {\n lastError = error\n\n // Classify and record the error on the current attempt (always, including final attempt)\n const apiError = classifyError(error)\n requestContext?.setAttemptError(apiError)\n\n // Don't retry if we've exhausted attempts\n if (attempt >= maxRetries) break\n\n // Find first strategy that can handle this error\n let handled = false\n for (const strategy of strategies) {\n if (!strategy.canHandle(apiError)) continue\n\n const retryContext: RetryContext<TPayload> = {\n attempt,\n originalPayload,\n model,\n maxRetries,\n }\n\n try {\n const action = await strategy.handle(apiError, effectivePayload, retryContext)\n\n if (action.action === \"retry\") {\n consola.debug(\n `[Pipeline] Strategy \"${strategy.name}\" requests retry ` + `(attempt ${attempt + 1}/${maxRetries + 1})`,\n )\n\n if (action.waitMs && action.waitMs > 0) {\n totalQueueWaitMs += action.waitMs\n requestContext?.addQueueWaitMs(action.waitMs)\n }\n\n effectivePayload = action.payload\n onRetry?.(attempt, strategy.name, action.payload, action.meta)\n handled = true\n break\n }\n\n // action === \"abort\": fall through to break\n break\n } catch (strategyError) {\n consola.warn(\n `[Pipeline] Strategy \"${strategy.name}\" failed on attempt ${attempt + 1}:`,\n strategyError instanceof Error ? strategyError.message : strategyError,\n )\n // Strategy itself failed, break out to throw original error\n break\n }\n }\n\n if (!handled) break\n }\n }\n\n // If we exit the loop, it means all retries failed or no strategy handled the error\n if (lastError) {\n // Log payload size info for 413 errors\n const apiError = classifyError(lastError)\n if (apiError.type === \"payload_too_large\") {\n await adapter.logPayloadSize(effectivePayload)\n }\n\n throw lastError instanceof Error ? lastError : new Error(\"Unknown error\")\n }\n\n // Should not reach here\n throw new Error(\"Unexpected state in pipeline retry loop\")\n}\n","/**\n * Response utilities for request handlers.\n */\n\nimport type { ChatCompletionResponse } from \"~/types/api/openai-chat-completions\"\n\n/** Type guard for non-streaming responses */\nexport function isNonStreaming(\n response: ChatCompletionResponse | AsyncIterable<unknown>,\n): response is ChatCompletionResponse {\n return Object.hasOwn(response, \"choices\")\n}\n\n/** Parse a JSON string to object, returning the value as-is if already an object */\nexport function safeParseJson(input: string | Record<string, unknown>): Record<string, unknown> {\n if (typeof input !== \"string\") return input\n try {\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\n/** Prepend a marker string to the first text content block of an Anthropic-format response */\nexport function prependMarkerToResponse<T extends { content: Array<{ type: string; text?: string }> }>(\n response: T,\n marker: string,\n): T {\n if (!marker) return response\n\n // Find first text block and prepend, or add new text block at start\n const content = [...response.content]\n const firstTextIndex = content.findIndex((block) => block.type === \"text\")\n\n if (firstTextIndex !== -1) {\n const textBlock = content[firstTextIndex]\n if (textBlock.type === \"text\") {\n content[firstTextIndex] = {\n ...textBlock,\n text: marker + (textBlock.text ?? \"\"),\n }\n }\n } else {\n // No text block found, add one at the beginning\n content.unshift({ type: \"text\", text: marker } as (typeof content)[number])\n }\n\n return { ...response, content }\n}\n","/** Shared recording utilities for streaming responses */\n\nimport consola from \"consola\"\n\nimport type { AnthropicStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\nimport type { ResponseData } from \"~/lib/context/request\"\nimport type { ResponsesStreamAccumulator } from \"~/lib/openai/responses-stream-accumulator\"\nimport type { OpenAIStreamAccumulator } from \"~/lib/openai/stream-accumulator\"\n\nimport { finalizeResponsesContent } from \"~/lib/openai/responses-stream-accumulator\"\n\nimport { safeParseJson } from \"./response\"\n\n/**\n * Map Anthropic content blocks to history-friendly format.\n */\nfunction mapAnthropicContentBlocks(acc: AnthropicStreamAccumulator): Array<unknown> {\n return acc.contentBlocks.map((block) => {\n // Generic (unknown) blocks are passed through as-is\n if (\"_generic\" in block) {\n const { _generic: _, ...rest } = block\n return rest\n }\n\n switch (block.type) {\n case \"text\": {\n return { type: \"text\" as const, text: block.text }\n }\n case \"thinking\": {\n return { type: \"thinking\" as const, thinking: block.thinking }\n }\n case \"redacted_thinking\": {\n return { type: \"redacted_thinking\" as const }\n }\n case \"tool_use\":\n case \"server_tool_use\": {\n return {\n type: block.type as string,\n id: block.id,\n name: block.name,\n input: safeParseJson(block.input),\n }\n }\n case \"web_search_tool_result\": {\n return {\n type: \"web_search_tool_result\" as const,\n tool_use_id: block.tool_use_id,\n content: block.content,\n }\n }\n default: {\n const unknown = block as { type: string }\n consola.warn(`[recording] Unhandled content block type in stream result: ${unknown.type}`)\n return { type: unknown.type }\n }\n }\n })\n}\n\n/**\n * Build a ResponseData from a completed Anthropic stream accumulator.\n * Does not include durationMs or queueWaitMs — those are tracked by RequestContext.\n */\nexport function buildAnthropicResponseData(acc: AnthropicStreamAccumulator, fallbackModel: string): ResponseData {\n const contentBlocks = mapAnthropicContentBlocks(acc)\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cacheReadTokens > 0 && { cache_read_input_tokens: acc.cacheReadTokens }),\n ...(acc.cacheCreationTokens > 0 && { cache_creation_input_tokens: acc.cacheCreationTokens }),\n },\n stop_reason: acc.stopReason || undefined,\n content: contentBlocks.length > 0 ? { role: \"assistant\", content: contentBlocks } : null,\n }\n}\n\n/**\n * Build a ResponseData from a completed OpenAI stream accumulator.\n * Does not include durationMs or queueWaitMs — those are tracked by RequestContext.\n */\nexport function buildOpenAIResponseData(acc: OpenAIStreamAccumulator, fallbackModel: string): ResponseData {\n // Collect tool calls from map, joining accumulated argument parts\n for (const tc of acc.toolCallMap.values()) {\n if (tc.id && tc.name) acc.toolCalls.push({ id: tc.id, name: tc.name, arguments: tc.argumentParts.join(\"\") })\n }\n\n const toolCalls = acc.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }))\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n ...(acc.cachedTokens > 0 && { cache_read_input_tokens: acc.cachedTokens }),\n },\n stop_reason: acc.finishReason || undefined,\n content: {\n role: \"assistant\",\n content: acc.content,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n },\n }\n}\n\n/**\n * Build a ResponseData from a completed Responses API stream accumulator.\n * Converts tool calls from Responses format (callId) to OpenAI Chat Completions format (id).\n */\nexport function buildResponsesResponseData(acc: ResponsesStreamAccumulator, fallbackModel: string): ResponseData {\n // Finalize tool calls from the accumulator map\n for (const tcAcc of acc.toolCallMap.values()) {\n const existing = acc.toolCalls.find((tc) => tc.id === tcAcc.id)\n if (!existing && tcAcc.id && tcAcc.name) {\n acc.toolCalls.push({\n id: tcAcc.id,\n callId: tcAcc.callId,\n name: tcAcc.name,\n arguments: tcAcc.argumentParts.join(\"\"),\n })\n }\n }\n\n const finalContent = finalizeResponsesContent(acc)\n\n const toolCalls = acc.toolCalls.map((tc) => ({\n id: tc.callId || tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }))\n\n return {\n success: true,\n model: acc.model || fallbackModel,\n usage: {\n input_tokens: acc.inputTokens,\n output_tokens: acc.outputTokens,\n },\n stop_reason: acc.status || undefined,\n content:\n finalContent || toolCalls.length > 0 ?\n {\n role: \"assistant\",\n content: finalContent || null,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n }\n : null,\n }\n}\n","/**\n * Generic stream utilities for SSE-based streaming proxying.\n *\n * These utilities are protocol-agnostic — they work with any async iterator\n * and are used by Anthropic, OpenAI Chat Completions, and Responses handlers.\n */\n\n// ============================================================================\n// Stream idle timeout\n// ============================================================================\n\n/** Error thrown when no SSE event arrives within the configured idle timeout window */\nexport class StreamIdleTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`Stream idle timeout: no event received within ${timeoutMs / 1000}s`)\n this.name = \"StreamIdleTimeoutError\"\n }\n}\n\n// ============================================================================\n// Abort signal utilities\n// ============================================================================\n\n/** Sentinel value returned when shutdown abort signal fires during iterator.next() */\nexport const STREAM_ABORTED = Symbol(\"STREAM_ABORTED\")\n\n/**\n * Combine multiple abort signals into one.\n * Returns undefined if no valid signals provided. Returns the single signal\n * if only one is valid. Otherwise uses AbortSignal.any() to merge.\n */\nexport function combineAbortSignals(...signals: Array<AbortSignal | undefined>): AbortSignal | undefined {\n const valid = signals.filter((s): s is AbortSignal => s !== undefined)\n if (valid.length === 0) return undefined\n if (valid.length === 1) return valid[0]\n return AbortSignal.any(valid)\n}\n\n// ============================================================================\n// Iterator racing\n// ============================================================================\n\n/**\n * Race `iterator.next()` against idle timeout and/or shutdown abort signal.\n *\n * Without this, `await iterator.next()` blocks indefinitely when the upstream\n * connection is alive but sends no data — the shutdown signal check at the top\n * of the loop never gets reached. This function ensures the abort signal can\n * interrupt the wait.\n *\n * Returns `STREAM_ABORTED` when the abort signal fires (caller should break).\n * Rejects with `StreamIdleTimeoutError` if idle timeout fires first.\n */\nexport function raceIteratorNext<T>(\n promise: Promise<IteratorResult<T>>,\n opts: { idleTimeoutMs: number; abortSignal?: AbortSignal },\n): Promise<IteratorResult<T> | typeof STREAM_ABORTED> {\n const { idleTimeoutMs, abortSignal } = opts\n\n // Fast path: already aborted\n if (abortSignal?.aborted) return Promise.resolve(STREAM_ABORTED)\n\n // Build the set of racing promises\n const racers: Array<Promise<IteratorResult<T> | typeof STREAM_ABORTED>> = [promise]\n const cleanups: Array<() => void> = []\n\n // Idle timeout racer\n if (idleTimeoutMs > 0) {\n let timeoutId: ReturnType<typeof setTimeout>\n racers.push(\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new StreamIdleTimeoutError(idleTimeoutMs)), idleTimeoutMs)\n }),\n )\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n cleanups.push(() => clearTimeout(timeoutId!))\n }\n\n // Abort signal racer — resolves (not rejects) with sentinel so the caller\n // can distinguish shutdown from errors and complete gracefully\n if (abortSignal && !abortSignal.aborted) {\n let onAbort: () => void\n racers.push(\n new Promise<typeof STREAM_ABORTED>((resolve) => {\n onAbort = () => resolve(STREAM_ABORTED)\n abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }),\n )\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n cleanups.push(() => abortSignal.removeEventListener(\"abort\", onAbort!))\n }\n\n return Promise.race(racers).finally(() => {\n for (const cleanup of cleanups) cleanup()\n })\n}\n","/**\n * System Prompt Manager: config-based overrides.\n *\n * - **Overrides**: Applies per-line replacement rules from config.yaml.\n * Always active.\n */\n\nimport type { TextBlockParam } from \"~/types/api/anthropic\"\nimport type { ContentPart, Message } from \"~/types/api/openai-chat-completions\"\n\nimport { applyConfigToState } from \"./config/config\"\nimport { state, type CompiledRewriteRule } from \"./state\"\n\n// ============================================================================\n// Override Application\n// ============================================================================\n\n/**\n * Apply overrides to a text block.\n * - line: split by newlines, if a trimmed line matches trimmed `from`, replace that line with `to`\n * - regex: apply regex on the entire text with gms flags (multiline: ^$ match line boundaries, dotAll: . matches \\n)\n */\nexport function applyOverrides(text: string, rules: Array<CompiledRewriteRule>, model?: string): string {\n let result = text\n for (const rule of rules) {\n // Skip rule if it has a model filter and the model doesn't match\n if (rule.modelPattern && (!model || !rule.modelPattern.test(model))) continue\n if (rule.method === \"line\") {\n const lines = result.split(\"\\n\")\n result = lines.map((line) => (line.trim() === (rule.from as string).trim() ? rule.to : line)).join(\"\\n\")\n } else {\n result = result.replace(rule.from as RegExp, rule.to)\n }\n }\n return result\n}\n\n// ============================================================================\n// Core: Plain Text System Prompt Processing\n// ============================================================================\n\n/**\n * Process a plain-text system prompt: apply overrides, prepend, and append.\n *\n * Shared core logic used by all format-specific processors (Anthropic, OpenAI, Responses).\n */\nexport async function processSystemPromptText(text: string, model?: string): Promise<string> {\n const config = await applyConfigToState()\n\n let result = text\n if (state.systemPromptOverrides.length > 0) {\n result = applyOverrides(result, state.systemPromptOverrides, model)\n }\n if (config.system_prompt_prepend) {\n result = config.system_prompt_prepend + \"\\n\\n\" + result\n }\n if (config.system_prompt_append) {\n result = result + \"\\n\\n\" + config.system_prompt_append\n }\n return result\n}\n\n// ============================================================================\n// Public API: Anthropic\n// ============================================================================\n\nexport async function processAnthropicSystem(\n system: string | Array<TextBlockParam> | undefined,\n model?: string,\n): Promise<string | Array<TextBlockParam> | undefined> {\n if (!system) return system\n\n // String system prompt — delegate to shared core\n if (typeof system === \"string\") {\n return processSystemPromptText(system, model)\n }\n\n // TextBlockParam[] — apply overrides, prepend, append per block\n const config = await applyConfigToState()\n const prepend = config.system_prompt_prepend\n const append = config.system_prompt_append\n\n let result: Array<TextBlockParam> = system\n if (state.systemPromptOverrides.length > 0) {\n result = result.map((block) => ({\n ...block,\n text: applyOverrides(block.text, state.systemPromptOverrides, model),\n }))\n }\n\n if (prepend) {\n result = [{ type: \"text\" as const, text: prepend }, ...result]\n }\n\n if (append) {\n result = [...result, { type: \"text\" as const, text: append }]\n }\n\n return result\n}\n\n// ============================================================================\n// Public API: OpenAI\n// ============================================================================\n\nexport async function processOpenAIMessages(\n messages: Array<Message>,\n model?: string,\n): Promise<Array<Message>> {\n // Extract system/developer messages\n const systemMessages = messages.filter((m) => m.role === \"system\" || m.role === \"developer\")\n if (systemMessages.length === 0) {\n // Even with no system messages, we may need to prepend/append\n const config = await applyConfigToState()\n let result = messages\n if (config.system_prompt_prepend) {\n result = [{ role: \"system\" as const, content: config.system_prompt_prepend }, ...result]\n }\n if (config.system_prompt_append) {\n result = [...result, { role: \"system\" as const, content: config.system_prompt_append }]\n }\n return result\n }\n\n // Load config (also applies to state, populating systemPromptOverrides)\n const config = await applyConfigToState()\n const prepend = config.system_prompt_prepend\n const append = config.system_prompt_append\n\n // Apply overrides to system/developer messages\n let result =\n state.systemPromptOverrides.length > 0 ?\n messages.map((msg) => {\n if (msg.role !== \"system\" && msg.role !== \"developer\") return msg\n\n if (typeof msg.content === \"string\") {\n return { ...msg, content: applyOverrides(msg.content, state.systemPromptOverrides, model) }\n }\n\n if (Array.isArray(msg.content)) {\n return {\n ...msg,\n content: msg.content.map((part: ContentPart) => {\n if (part.type === \"text\") {\n return { ...part, text: applyOverrides(part.text, state.systemPromptOverrides, model) }\n }\n return part\n }),\n }\n }\n\n return msg\n })\n : messages\n\n // Apply prepend — insert a system message at the beginning\n if (prepend) {\n result = [{ role: \"system\" as const, content: prepend }, ...result]\n }\n\n // Apply append — insert a system message at the end\n if (append) {\n result = [...result, { role: \"system\" as const, content: append }]\n }\n\n return result\n}\n\n// ============================================================================\n// Public API: Responses\n// ============================================================================\n\n/**\n * Process Responses API `instructions` field (system prompt equivalent).\n *\n * Applies the same overrides, prepend, and append as Anthropic/OpenAI system prompts.\n * Preserves null/undefined pass-through — only processes non-empty strings.\n */\nexport async function processResponsesInstructions(\n instructions: string | null | undefined,\n model?: string,\n): Promise<string | null | undefined> {\n if (!instructions) return instructions\n return processSystemPromptText(instructions, model)\n}\n","import { state } from \"~/lib/state\"\n\n/**\n * Create an AbortSignal for fetch timeout if configured.\n * Controls the time from request start to receiving response headers.\n * Returns undefined if fetchTimeout is 0 (disabled).\n */\nexport function createFetchSignal(): AbortSignal | undefined {\n return state.fetchTimeout > 0 ? AbortSignal.timeout(state.fetchTimeout * 1000) : undefined\n}\n","/**\n * Responses API client for Copilot /responses endpoint.\n * Follows the same pattern as client.ts but targets the /responses endpoint.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport type { ResponsesPayload, ResponsesResponse, ResponsesInputItem } from \"~/types/api/openai-responses\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\n/** Call Copilot /responses endpoint */\nexport const createResponses = async (\n payload: ResponsesPayload,\n): Promise<ResponsesResponse | AsyncGenerator<ServerSentEventMessage>> => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n // Check for vision content in input\n const enableVision = hasVisionContent(payload.input)\n\n // Determine if this is an agent call (has assistant or function_call items in history)\n const isAgentCall =\n Array.isArray(payload.input)\n && payload.input.some(\n (item) => item.role === \"assistant\" || item.type === \"function_call\" || item.type === \"function_call_output\",\n )\n\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/responses`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create responses\", response)\n throw await HTTPError.fromResponse(\"Failed to create responses\", response, payload.model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ResponsesResponse\n}\n\n/** Check if the input contains any image content */\nfunction hasVisionContent(input: string | Array<ResponsesInputItem>): boolean {\n if (typeof input === \"string\") return false\n return input.some(\n (item) => Array.isArray(item.content) && item.content.some((part) => \"type\" in part && part.type === \"input_image\"),\n )\n}\n","/**\n * Network error retry strategy.\n *\n * Handles transient network errors (ECONNRESET, ETIMEDOUT, socket closures, etc.)\n * by retrying once after a brief delay. These errors are typically caused by\n * connection pool issues, transient network glitches, or upstream resets,\n * and a single retry usually succeeds.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\n\nimport type { RetryAction, RetryContext, RetryStrategy } from \"../pipeline\"\n\n/** Default delay before network retry (ms) */\nconst NETWORK_RETRY_DELAY_MS = 1000\n\n/**\n * Create a network error retry strategy.\n *\n * On `network_error` (ECONNRESET, ETIMEDOUT, socket closures, DNS timeouts, etc.),\n * waits briefly then retries with the same payload.\n * Only retries once per pipeline execution to avoid prolonged retry loops\n * on persistent network failures.\n */\nexport function createNetworkRetryStrategy<TPayload>(): RetryStrategy<TPayload> {\n // Track whether we've already attempted a network retry.\n // A second network error after retry means the problem is persistent.\n let hasRetried = false\n\n return {\n name: \"network-retry\",\n\n canHandle(error: ApiError): boolean {\n return error.type === \"network_error\" && !hasRetried\n },\n\n handle(error: ApiError, currentPayload: TPayload, context: RetryContext<TPayload>): Promise<RetryAction<TPayload>> {\n consola.info(\n `[NetworkRetry] Attempt ${context.attempt + 1}/${context.maxRetries + 1}: `\n + `Network error \"${error.message}\", retrying in ${NETWORK_RETRY_DELAY_MS}ms...`,\n )\n\n hasRetried = true\n\n return Promise.resolve({\n action: \"retry\",\n payload: currentPayload,\n waitMs: NETWORK_RETRY_DELAY_MS,\n meta: { networkRetry: true },\n })\n },\n }\n}\n","/**\n * Token refresh retry strategy.\n *\n * Handles 401/403 errors by refreshing the Copilot token and retrying.\n * When the Copilot token expires between scheduled refreshes, this strategy\n * triggers an immediate refresh so the request can be retried transparently.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\n\nimport { getCopilotTokenManager } from \"~/lib/token\"\n\nimport type { RetryAction, RetryContext, RetryStrategy } from \"../pipeline\"\n\n/**\n * Refresh the Copilot token via the global manager.\n * Returns true on success, false on failure.\n */\nasync function refreshCopilotToken(): Promise<boolean> {\n const manager = getCopilotTokenManager()\n if (!manager) return false\n const result = await manager.refresh()\n return result !== null\n}\n\n/**\n * Create a token refresh retry strategy.\n *\n * On `auth_expired` errors (401/403), refreshes the Copilot token via\n * `CopilotTokenManager.refresh()`, then retries with the same payload.\n * Only retries once per pipeline execution to avoid infinite refresh loops.\n */\nexport function createTokenRefreshStrategy<TPayload>(): RetryStrategy<TPayload> {\n // Track whether we've already attempted a refresh in this pipeline execution.\n // A second 401 after refresh means the problem isn't a stale token.\n let hasRefreshed = false\n\n return {\n name: \"token-refresh\",\n\n canHandle(error: ApiError): boolean {\n return error.type === \"auth_expired\" && !hasRefreshed\n },\n\n async handle(\n error: ApiError,\n currentPayload: TPayload,\n context: RetryContext<TPayload>,\n ): Promise<RetryAction<TPayload>> {\n consola.info(\n `[TokenRefresh] Attempt ${context.attempt + 1}/${context.maxRetries + 1}: `\n + `Got ${error.status}, refreshing Copilot token...`,\n )\n\n const success = await refreshCopilotToken()\n hasRefreshed = true\n\n if (!success) {\n consola.error(\"[TokenRefresh] Token refresh failed, aborting request\")\n return { action: \"abort\", error }\n }\n\n consola.info(\"[TokenRefresh] Token refreshed, retrying request\")\n\n // Retry with the same payload — the new token is in global state,\n // which the adapter reads when constructing the Authorization header\n return {\n action: \"retry\",\n payload: currentPayload,\n meta: { tokenRefreshed: true },\n }\n },\n }\n}\n","/**\n * Shared pipeline configuration for the Responses API.\n *\n * Both the HTTP handler (handler.ts) and WebSocket handler (ws.ts)\n * use identical adapter and strategy configuration. This module\n * centralizes that configuration to avoid duplication.\n */\n\nimport consola from \"consola\"\n\nimport type { FormatAdapter } from \"~/lib/request/pipeline\"\nimport type { ResponsesPayload } from \"~/types/api/openai-responses\"\n\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { createResponses } from \"~/lib/openai/responses-client\"\nimport { createNetworkRetryStrategy } from \"~/lib/request/strategies/network-retry\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\n\n/** Create the FormatAdapter for Responses API pipeline execution */\nexport function createResponsesAdapter(): FormatAdapter<ResponsesPayload> {\n return {\n format: \"openai-responses\",\n sanitize: (p) => ({ payload: p, removedCount: 0, systemReminderRemovals: 0 }),\n execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p)),\n logPayloadSize: (p) => {\n const count = typeof p.input === \"string\" ? 1 : p.input.length\n consola.debug(`Responses payload: ${count} input item(s), model: ${p.model}`)\n },\n }\n}\n\n/** Create the retry strategies for Responses API pipeline execution */\nexport function createResponsesStrategies() {\n return [createNetworkRetryStrategy<ResponsesPayload>(), createTokenRefreshStrategy<ResponsesPayload>()]\n}\n","/**\n * WebSocket transport for the Responses API.\n *\n * Accepts WebSocket connections on GET /v1/responses (and /responses).\n * Clients send `{ type: \"response.create\", response: { model, input, ... } }`\n * and receive streaming events as JSON frames (same data as SSE events).\n *\n * This bridges the WebSocket transport to our existing HTTP pipeline:\n * WebSocket message → extract payload → pipeline → SSE events → WS JSON frames.\n */\n\nimport type { Hono } from \"hono\"\nimport type { UpgradeWebSocket, WSContext } from \"hono/ws\"\nimport type { Server as NodeHttpServer } from \"node:http\"\n\nimport consola from \"consola\"\n\nimport type { ResponsesPayload, ResponsesStreamEvent } from \"~/types/api/openai-responses\"\n\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { responsesInputToMessages } from \"~/lib/openai/responses-conversion\"\nimport {\n accumulateResponsesStreamEvent,\n createResponsesStreamAccumulator,\n} from \"~/lib/openai/responses-stream-accumulator\"\nimport { executeRequestPipeline } from \"~/lib/request/pipeline\"\nimport { buildResponsesResponseData } from \"~/lib/request/recording\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { STREAM_ABORTED, raceIteratorNext } from \"~/lib/stream\"\nimport { processResponsesInstructions } from \"~/lib/system-prompt\"\nimport { tuiLogger } from \"~/lib/tui\"\n\nimport { createResponsesAdapter, createResponsesStrategies } from \"./pipeline\"\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Terminal event types that signal the end of a response */\nconst TERMINAL_EVENTS = new Set([\"response.completed\", \"response.failed\", \"response.incomplete\", \"error\"])\n\n// ============================================================================\n// Payload extraction\n// ============================================================================\n\n/**\n * Extract ResponsesPayload from a response.create WebSocket message.\n *\n * Supports two formats:\n * - OpenAI SDK style: `{ type: \"response.create\", response: { model, input, ... } }`\n * - Flat style: `{ type: \"response.create\", model, input, ... }`\n *\n * WebSocket transport always streams, so `stream` is forced to `true`.\n */\nfunction extractPayload(message: unknown): ResponsesPayload | null {\n if (typeof message !== \"object\" || message === null) return null\n const msg = message as Record<string, unknown>\n\n if (msg.type !== \"response.create\") return null\n\n // OpenAI SDK wraps payload in \"response\" key\n let payload: Record<string, unknown>\n if (msg.response && typeof msg.response === \"object\") {\n payload = msg.response as Record<string, unknown>\n } else {\n const { type: _type, ...rest } = msg\n payload = rest\n }\n\n // WebSocket transport always streams\n payload.stream = true\n\n if (!payload.model || typeof payload.model !== \"string\") return null\n if (!payload.input) return null\n\n return payload as unknown as ResponsesPayload\n}\n\n// ============================================================================\n// Error helpers\n// ============================================================================\n\n/** Send an error frame and close the WebSocket */\nfunction sendErrorAndClose(ws: WSContext, message: string, code?: string): void {\n try {\n ws.send(\n JSON.stringify({\n type: \"error\",\n error: { type: code ?? \"server_error\", message },\n }),\n )\n } catch {\n // WebSocket might already be closed\n }\n try {\n ws.close(1011, message.slice(0, 123)) // WS close reason max 123 bytes\n } catch {\n // Already closed\n }\n}\n\n// ============================================================================\n// Core handler\n// ============================================================================\n\n/** Handle a response.create message over WebSocket */\nasync function handleResponseCreate(ws: WSContext, payload: ResponsesPayload): Promise<void> {\n const requestedModel = payload.model\n const resolvedModel = resolveModelName(requestedModel)\n payload.model = resolvedModel\n\n // Check endpoint support\n const selectedModel = state.modelIndex.get(resolvedModel)\n if (!isEndpointSupported(selectedModel, ENDPOINT.RESPONSES)) {\n sendErrorAndClose(ws, `Model \"${resolvedModel}\" does not support the Responses API`, \"invalid_request_error\")\n return\n }\n\n // Process system prompt (overrides, prepend, append from config)\n payload.instructions = await processResponsesInstructions(payload.instructions, payload.model)\n\n // TUI logging — use \"WS\" as method indicator\n const tuiLogId = tuiLogger.startRequest({\n method: \"WS\",\n path: \"/v1/responses\",\n model: resolvedModel,\n })\n\n // Create request context for tracking\n const reqCtx = getRequestContextManager().create({ endpoint: \"openai-responses\", tuiLogId })\n\n reqCtx.setOriginalRequest({\n model: requestedModel,\n messages: responsesInputToMessages(payload.input),\n stream: true,\n tools: payload.tools,\n system: payload.instructions ?? undefined,\n payload,\n })\n\n // Update TUI with resolved model (if different from requested)\n if (requestedModel !== resolvedModel) {\n tuiLogger.updateRequest(tuiLogId, {\n model: resolvedModel,\n clientModel: requestedModel,\n })\n }\n\n // Build pipeline adapter and strategies (shared with HTTP handler)\n const adapter = createResponsesAdapter()\n const strategies = createResponsesStrategies()\n\n try {\n // Execute pipeline (model resolution, token refresh, rate limiting)\n const pipelineResult = await executeRequestPipeline({\n adapter,\n strategies,\n payload,\n originalPayload: payload,\n model: selectedModel,\n maxRetries: 1,\n })\n\n const response = pipelineResult.response\n\n // Stream SSE events → WebSocket JSON frames\n // The pipeline returns an AsyncIterable<ServerSentEventMessage> for streaming\n const iterator = (response as AsyncIterable<{ data?: string; event?: string }>)[Symbol.asyncIterator]()\n const acc = createResponsesStreamAccumulator()\n const idleTimeoutMs = state.streamIdleTimeout > 0 ? state.streamIdleTimeout * 1000 : 0\n const shutdownSignal = getShutdownSignal()\n let eventsReceived = 0\n\n while (true) {\n const result = await raceIteratorNext(iterator.next(), {\n idleTimeoutMs,\n abortSignal: shutdownSignal ?? undefined,\n })\n\n if (result === STREAM_ABORTED || result.done) break\n\n const sseEvent = result.value\n if (!sseEvent.data || sseEvent.data === \"[DONE]\") continue\n\n try {\n const parsed = JSON.parse(sseEvent.data) as ResponsesStreamEvent\n accumulateResponsesStreamEvent(parsed, acc)\n\n // Forward event as WebSocket JSON frame\n ws.send(sseEvent.data)\n eventsReceived++\n\n // Update TUI with stream progress\n tuiLogger.updateRequest(tuiLogId, { streamEventsIn: eventsReceived })\n\n // Check for terminal events\n if (TERMINAL_EVENTS.has(parsed.type)) break\n } catch {\n consola.debug(\"[WS] Skipping unparseable SSE event\")\n }\n }\n\n // Record to history\n const responseData = buildResponsesResponseData(acc, resolvedModel)\n reqCtx.complete(responseData)\n\n // Close WebSocket gracefully\n ws.close(1000, \"done\")\n } catch (error) {\n reqCtx.fail(resolvedModel, error)\n\n const message = error instanceof Error ? error.message : String(error)\n consola.error(`[WS] Responses API error: ${message}`)\n sendErrorAndClose(ws, message)\n }\n}\n\n// ============================================================================\n// WebSocket route registration\n// ============================================================================\n\n/**\n * Initialize WebSocket routes for the Responses API.\n *\n * Registers GET /v1/responses and GET /responses on the root Hono app\n * with WebSocket upgrade handling. Follows the same pattern as\n * initHistoryWebSocket in src/routes/history/route.ts.\n *\n * @returns An inject function for Node.js HTTP server (undefined for Bun)\n */\nexport async function initResponsesWebSocket(rootApp: Hono): Promise<((server: NodeHttpServer) => void) | undefined> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let upgradeWs: UpgradeWebSocket<any>\n let injectFn: ((server: NodeHttpServer) => void) | undefined\n\n if (typeof globalThis.Bun !== \"undefined\") {\n const { upgradeWebSocket } = await import(\"hono/bun\")\n upgradeWs = upgradeWebSocket\n } else {\n const { createNodeWebSocket } = await import(\"@hono/node-ws\")\n const nodeWs = createNodeWebSocket({ app: rootApp })\n upgradeWs = nodeWs.upgradeWebSocket\n injectFn = (server: NodeHttpServer) => {\n nodeWs.injectWebSocket(server)\n }\n }\n\n // Create the WebSocket handler\n const wsHandler = upgradeWs(() => ({\n onOpen(_event: Event, _ws: WSContext) {\n consola.debug(\"[WS] Responses API WebSocket connected\")\n },\n\n onClose(_event: Event, _ws: WSContext) {\n consola.debug(\"[WS] Responses API WebSocket disconnected\")\n },\n\n async onMessage(event: MessageEvent, ws: WSContext) {\n // Parse the incoming message\n let message: unknown\n try {\n const raw = typeof event.data === \"string\" ? event.data : String(event.data)\n message = JSON.parse(raw)\n } catch {\n sendErrorAndClose(ws, \"Invalid JSON message\", \"invalid_request_error\")\n return\n }\n\n // Extract and validate payload\n const payload = extractPayload(message)\n if (!payload) {\n sendErrorAndClose(\n ws,\n 'Invalid message: expected { type: \"response.create\", response: { model, input, ... } }',\n \"invalid_request_error\",\n )\n return\n }\n\n // Handle the response creation\n await handleResponseCreate(ws, payload)\n },\n\n onError(event: Event, ws: WSContext) {\n consola.error(\"[WS] Responses API WebSocket error:\", event)\n try {\n ws.close(1011, \"Internal error\")\n } catch {\n // Already closed\n }\n },\n }))\n\n // Register on both paths (GET for WebSocket upgrade, coexists with POST for HTTP)\n rootApp.get(\"/v1/responses\", wsHandler)\n rootApp.get(\"/responses\", wsHandler)\n\n consola.debug(\"[WS] Responses API WebSocket routes registered\")\n\n return injectFn\n}\n","import type { ChatCompletionsPayload, ContentPart, Message, Tool, ToolCall } from \"~/types/api/openai-chat-completions\"\n\nimport type { Model } from \"./client\"\n\n// ============================================================================\n// GPT Encoder Support\n// ============================================================================\n\n/** Encoder type mapping */\nconst ENCODING_MAP = {\n o200k_base: () => import(\"gpt-tokenizer/encoding/o200k_base\"),\n cl100k_base: () => import(\"gpt-tokenizer/encoding/cl100k_base\"),\n p50k_base: () => import(\"gpt-tokenizer/encoding/p50k_base\"),\n p50k_edit: () => import(\"gpt-tokenizer/encoding/p50k_edit\"),\n r50k_base: () => import(\"gpt-tokenizer/encoding/r50k_base\"),\n} as const\n\ntype SupportedEncoding = keyof typeof ENCODING_MAP\n\n/** Encoder interface for tokenization */\ninterface Encoder {\n encode: (text: string) => Array<number>\n}\n\n/** Cache loaded encoders to avoid repeated imports */\nconst encodingCache = new Map<string, Encoder>()\n\n/**\n * Calculate tokens for tool calls\n */\nconst calculateToolCallsTokens = (\n toolCalls: Array<ToolCall>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let tokens = 0\n for (const toolCall of toolCalls) {\n tokens += constants.funcInit\n tokens += encoder.encode(JSON.stringify(toolCall)).length\n }\n tokens += constants.funcEnd\n return tokens\n}\n\n/**\n * Calculate tokens for content parts\n */\nconst calculateContentPartsTokens = (contentParts: Array<ContentPart>, encoder: Encoder): number => {\n let tokens = 0\n for (const part of contentParts) {\n if (part.type === \"image_url\") {\n // Image URLs incur ~85 tokens overhead for the image processing metadata\n // This is an approximation based on OpenAI's image token calculation\n tokens += encoder.encode(part.image_url.url).length + 85\n } else if (part.text) {\n tokens += encoder.encode(part.text).length\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens for a single message\n */\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n // Each message incurs 3 tokens overhead for role/metadata framing\n // Based on OpenAI's token counting methodology\n const tokensPerMessage = 3\n // Additional token when a \"name\" field is present\n const tokensPerName = 1\n let tokens = tokensPerMessage\n for (const [key, value] of Object.entries(message)) {\n if (typeof value === \"string\") {\n tokens += encoder.encode(value).length\n }\n if (key === \"name\") {\n tokens += tokensPerName\n }\n if (key === \"tool_calls\") {\n tokens += calculateToolCallsTokens(value as Array<ToolCall>, encoder, constants)\n }\n if (key === \"content\" && Array.isArray(value)) {\n tokens += calculateContentPartsTokens(value as Array<ContentPart>, encoder)\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens using custom algorithm\n */\nconst calculateTokens = (\n messages: Array<Message>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (messages.length === 0) {\n return 0\n }\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n // every reply is primed with <|start|>assistant<|message|> (3 tokens)\n numTokens += 3\n return numTokens\n}\n\n/**\n * Get the corresponding encoder module based on encoding type\n */\nconst getEncodeChatFunction = async (encoding: string): Promise<Encoder> => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) {\n return cached\n }\n }\n\n const supportedEncoding = encoding as SupportedEncoding\n const rawModule =\n supportedEncoding in ENCODING_MAP ? await ENCODING_MAP[supportedEncoding]() : await ENCODING_MAP.o200k_base()\n\n // Wrap encode to disable special token checks.\n // gpt-tokenizer defaults to disallowedSpecial='all', which throws on\n // tokens like <|im_start|> that appear in tool_result content.\n const encoder: Encoder = {\n encode: (text: string) => rawModule.encode(text, { disallowedSpecial: new Set() }),\n }\n\n encodingCache.set(encoding, encoder)\n return encoder\n}\n\n/**\n * Get tokenizer type from model information\n */\nexport const getTokenizerFromModel = (model: Model): string => {\n return model.capabilities?.tokenizer || \"o200k_base\"\n}\n\n/**\n * Count tokens in a text string using the model's tokenizer.\n * This is a simple wrapper for counting tokens in plain text.\n */\nexport const countTextTokens = async (text: string, model: Model): Promise<number> => {\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n return encoder.encode(text).length\n}\n\n/**\n * Get model-specific constants for token calculation.\n * These values are empirically determined based on OpenAI's function calling token overhead.\n * - funcInit: Tokens for initializing a function definition\n * - propInit: Tokens for initializing the properties section\n * - propKey: Tokens per property key\n * - enumInit: Token adjustment when enum is present (negative because type info is replaced)\n * - enumItem: Tokens per enum value\n * - funcEnd: Tokens for closing the function definition\n */\nconst getModelConstants = (model: Model) => {\n return model.id === \"gpt-3.5-turbo\" || model.id === \"gpt-4\" ?\n {\n funcInit: 10,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n : {\n funcInit: 7,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n}\n\n/**\n * Calculate tokens for a single parameter\n */\nconst calculateParameterTokens = (\n key: string,\n prop: unknown,\n context: {\n encoder: Encoder\n constants: ReturnType<typeof getModelConstants>\n },\n): number => {\n const { encoder, constants } = context\n let tokens = constants.propKey\n\n // Early return if prop is not an object\n if (typeof prop !== \"object\" || prop === null) {\n return tokens\n }\n\n // Type assertion for parameter properties\n const param = prop as {\n type?: string\n description?: string\n enum?: Array<unknown>\n [key: string]: unknown\n }\n\n const paramName = key\n const paramType = param.type || \"string\"\n let paramDesc = param.description || \"\"\n\n // Handle enum values\n if (param.enum && Array.isArray(param.enum)) {\n tokens += constants.enumInit\n for (const item of param.enum) {\n tokens += constants.enumItem\n tokens += encoder.encode(String(item)).length\n }\n }\n\n // Clean up description\n if (paramDesc.endsWith(\".\")) {\n paramDesc = paramDesc.slice(0, -1)\n }\n\n // Encode the main parameter line\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n // Handle additional properties (excluding standard ones)\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (!excludedKeys.has(propertyName)) {\n const propertyValue = param[propertyName]\n const propertyText = typeof propertyValue === \"string\" ? propertyValue : JSON.stringify(propertyValue)\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for function parameters\n */\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n if (!parameters || typeof parameters !== \"object\") {\n return 0\n }\n\n const params = parameters as Record<string, unknown>\n let tokens = 0\n\n for (const [key, value] of Object.entries(params)) {\n if (key === \"properties\") {\n const properties = value as Record<string, unknown>\n if (Object.keys(properties).length > 0) {\n tokens += constants.propInit\n for (const propKey of Object.keys(properties)) {\n tokens += calculateParameterTokens(propKey, properties[propKey], {\n encoder,\n constants,\n })\n }\n }\n } else {\n const paramText = typeof value === \"string\" ? value : JSON.stringify(value)\n tokens += encoder.encode(`${key}:${paramText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for a single tool\n */\nconst calculateToolTokens = (tool: Tool, encoder: Encoder, constants: ReturnType<typeof getModelConstants>): number => {\n let tokens = constants.funcInit\n const func = tool.function\n const fName = func.name\n let fDesc = func.description || \"\"\n if (fDesc.endsWith(\".\")) {\n fDesc = fDesc.slice(0, -1)\n }\n const line = `${fName}:${fDesc}`\n tokens += encoder.encode(line).length\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typeof null === \"object\" in JavaScript\n if (typeof func.parameters === \"object\" && func.parameters !== null) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\n\n/**\n * Calculate token count for tools based on model\n */\nexport const numTokensForTools = (\n tools: Array<Tool>,\n encoder: Encoder,\n constants: ReturnType<typeof getModelConstants>,\n): number => {\n let funcTokenCount = 0\n for (const tool of tools) {\n funcTokenCount += calculateToolTokens(tool, encoder, constants)\n }\n funcTokenCount += constants.funcEnd\n return funcTokenCount\n}\n\n// ============================================================================\n// Main Token Count API\n// ============================================================================\n\n/**\n * Calculate the token count of messages.\n * Uses the tokenizer specified by the GitHub Copilot API model info.\n * All models (including Claude) use GPT tokenizers (o200k_base or cl100k_base).\n */\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: Model,\n): Promise<{ input: number; output: number }> => {\n // Use the tokenizer specified by the API (defaults to o200k_base)\n const tokenizer = getTokenizerFromModel(model)\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const simplifiedMessages = payload.messages\n const inputMessages = simplifiedMessages.filter((msg) => msg.role !== \"assistant\")\n const outputMessages = simplifiedMessages.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 {\n input: inputTokens,\n output: outputTokens,\n }\n}\n","/**\n * OpenAI orphaned tool block filtering.\n *\n * Filters orphaned tool messages and tool_calls from OpenAI messages\n * to ensure API compatibility.\n */\n\nimport consola from \"consola\"\n\nimport type { Message, ToolCall } from \"~/types/api/openai-chat-completions\"\n\n/**\n * Get tool_call IDs from an OpenAI assistant message.\n */\nexport function getOpenAIToolCallIds(msg: Message): Array<string> {\n if (msg.role === \"assistant\" && msg.tool_calls) {\n return msg.tool_calls.map((tc: ToolCall) => tc.id)\n }\n return []\n}\n\n/**\n * Get tool_result IDs from OpenAI tool messages.\n */\nexport function getOpenAIToolResultIds(messages: Array<Message>): Set<string> {\n const ids = new Set<string>()\n for (const msg of messages) {\n if (msg.role === \"tool\" && msg.tool_call_id) {\n ids.add(msg.tool_call_id)\n }\n }\n return ids\n}\n\n/**\n * Filter orphaned tool messages from OpenAI messages.\n */\nexport function filterOpenAIOrphanedToolResults(messages: Array<Message>): Array<Message> {\n // Collect all available tool_call IDs\n const toolCallIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getOpenAIToolCallIds(msg)) {\n toolCallIds.add(id)\n }\n }\n\n // Filter out orphaned tool messages\n let removedCount = 0\n const filtered = messages.filter((msg) => {\n if (msg.role === \"tool\" && msg.tool_call_id && !toolCallIds.has(msg.tool_call_id)) {\n removedCount++\n return false\n }\n return true\n })\n\n if (removedCount > 0) {\n consola.debug(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool_result`)\n }\n\n return filtered\n}\n\n/**\n * Filter orphaned tool_calls from OpenAI assistant messages.\n */\nexport function filterOpenAIOrphanedToolUse(messages: Array<Message>): Array<Message> {\n const toolResultIds = getOpenAIToolResultIds(messages)\n\n // Filter out orphaned tool_calls from assistant messages\n const result: Array<Message> = []\n let removedCount = 0\n\n for (const msg of messages) {\n if (msg.role === \"assistant\" && msg.tool_calls) {\n const filteredToolCalls = msg.tool_calls.filter((tc: ToolCall) => {\n if (!toolResultIds.has(tc.id)) {\n removedCount++\n return false\n }\n return true\n })\n\n // If all tool_calls were removed but there's still content, keep the message\n if (filteredToolCalls.length === 0) {\n if (msg.content) {\n result.push({ ...msg, tool_calls: undefined })\n }\n // Skip message entirely if no content and no tool_calls\n continue\n }\n\n result.push({ ...msg, tool_calls: filteredToolCalls })\n continue\n }\n\n result.push(msg)\n }\n\n if (removedCount > 0) {\n consola.debug(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool_use`)\n }\n\n return result\n}\n\n/**\n * Ensure OpenAI messages start with a user message.\n */\nexport function ensureOpenAIStartsWithUser(messages: Array<Message>): Array<Message> {\n let startIndex = 0\n while (startIndex < messages.length && messages[startIndex].role !== \"user\") {\n startIndex++\n }\n\n if (startIndex > 0) {\n consola.debug(`[Sanitizer:OpenAI] Skipped ${startIndex} leading non-user messages`)\n }\n\n return messages.slice(startIndex)\n}\n\n/**\n * Extract system/developer messages from the beginning of OpenAI messages.\n */\nexport function extractOpenAISystemMessages(messages: Array<Message>): {\n systemMessages: Array<Message>\n conversationMessages: Array<Message>\n} {\n let splitIndex = 0\n while (splitIndex < messages.length) {\n const role = messages[splitIndex].role\n if (role !== \"system\" && role !== \"developer\") break\n splitIndex++\n }\n\n return {\n systemMessages: messages.slice(0, splitIndex),\n conversationMessages: messages.slice(splitIndex),\n }\n}\n","/**\n * Auto-truncate module: Automatically truncates conversation history\n * when it exceeds token limits (OpenAI format).\n *\n * Key features:\n * - Binary search for optimal truncation point\n * - Token limit enforcement with learned calibration\n * - Preserves system messages\n * - Filters orphaned tool_result and tool_use messages\n * - Optional smart compression of old tool_result content\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\n\nimport { getTokenCount } from \"~/lib/models/tokenizer\"\nimport { state } from \"~/lib/state\"\nimport { bytesToKB } from \"~/lib/utils\"\n\nimport type { AutoTruncateConfig } from \"../auto-truncate\"\nimport type { ChatCompletionsPayload, Message } from \"~/types/api/openai-chat-completions\"\n\nimport {\n DEFAULT_AUTO_TRUNCATE_CONFIG,\n LARGE_TOOL_RESULT_THRESHOLD,\n calibrate,\n compressToolResultContent,\n computeSafetyMargin,\n getLearnedLimits,\n} from \"../auto-truncate\"\nimport {\n ensureOpenAIStartsWithUser,\n extractOpenAISystemMessages,\n filterOpenAIOrphanedToolResults,\n filterOpenAIOrphanedToolUse,\n} from \"./orphan-filter\"\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/** Result of auto-truncate operation */\nexport interface OpenAIAutoTruncateResult {\n payload: ChatCompletionsPayload\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\n/** Result of needs-compaction check */\nexport interface OpenAICompactionCheckResult {\n needed: boolean\n currentTokens: number\n tokenLimit: number\n reason?: \"tokens\"\n}\n\n// ============================================================================\n// Limit Calculation\n// ============================================================================\n\n/**\n * Calculate the effective token limit for auto-truncate.\n * Uses explicit target if provided, otherwise learned limits with calibration,\n * otherwise model capabilities with safety margin.\n *\n * Returns undefined when no limit information is available — the caller\n * should skip truncation rather than guess with a hardcoded default.\n */\nfunction calculateTokenLimit(model: Model, config: AutoTruncateConfig): number | undefined {\n // Use explicit target if provided (reactive retry — caller already applied margin)\n if (config.targetTokenLimit !== undefined) {\n return config.targetTokenLimit\n }\n\n // Check for learned limits (adjusted based on previous errors)\n const learned = getLearnedLimits(model.id)\n if (learned) {\n const margin = computeSafetyMargin(learned.sampleCount)\n return Math.floor(learned.tokenLimit * (1 - margin))\n }\n\n // Use model capabilities with static safety margin\n const rawTokenLimit =\n model.capabilities?.limits?.max_context_window_tokens\n ?? model.capabilities?.limits?.max_prompt_tokens\n\n if (rawTokenLimit === undefined) return undefined\n\n return Math.floor(rawTokenLimit * (1 - config.safetyMarginPercent / 100))\n}\n\n// ============================================================================\n// Message Utilities\n// ============================================================================\n\n/** Estimate tokens for a single message (fast approximation) */\nfunction estimateMessageTokens(msg: Message): number {\n let charCount = 0\n\n if (typeof msg.content === \"string\") {\n charCount = msg.content.length\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.type === \"text\") {\n charCount += part.text.length\n } else if (\"image_url\" in part) {\n // Base64 images are large but compressed in token counting\n charCount += Math.min(part.image_url.url.length, 10000)\n }\n }\n }\n\n if (msg.tool_calls) {\n charCount += JSON.stringify(msg.tool_calls).length\n }\n\n // ~4 chars per token + message overhead\n return Math.ceil(charCount / 4) + 10\n}\n\n/** Calculate cumulative token sums from the end of the message array */\nfunction calculateCumulativeSums(messages: Array<Message>): { cumTokens: Array<number> } {\n const n = messages.length\n const cumTokens = Array.from<number>({ length: n + 1 }).fill(0)\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n return { cumTokens }\n}\n\n/**\n * Clean up orphaned tool messages and ensure valid conversation start.\n * Loops until stable since each pass may create new orphans.\n */\nfunction cleanupMessages(messages: Array<Message>): Array<Message> {\n let result = messages\n let prevLength: number\n do {\n prevLength = result.length\n result = filterOpenAIOrphanedToolResults(result)\n result = filterOpenAIOrphanedToolUse(result)\n result = ensureOpenAIStartsWithUser(result)\n } while (result.length !== prevLength)\n return result\n}\n\n// ============================================================================\n// Smart Tool Result Compression\n// ============================================================================\n\n/**\n * Smart compression strategy for OpenAI format:\n * 1. Calculate tokens from the end until reaching preservePercent of limit\n * 2. Messages before that threshold get their tool content compressed\n * 3. Returns compressed messages and stats\n *\n * @param preservePercent - Percentage of context to preserve uncompressed (0.0-1.0)\n */\nfunction smartCompressToolResults(\n messages: Array<Message>,\n tokenLimit: number,\n preservePercent: number,\n): {\n messages: Array<Message>\n compressedCount: number\n compressThresholdIndex: number\n} {\n // Calculate cumulative token size from the end\n const n = messages.length\n const { cumTokens } = calculateCumulativeSums(messages)\n\n // Find the threshold index where we've used the preserve percentage of the limit\n const preserveTokenLimit = Math.floor(tokenLimit * preservePercent)\n\n let thresholdIndex = n\n for (let i = n - 1; i >= 0; i--) {\n if (cumTokens[i] > preserveTokenLimit) {\n thresholdIndex = i + 1\n break\n }\n thresholdIndex = i\n }\n\n // If threshold is at the end, nothing to compress\n if (thresholdIndex >= n) {\n return { messages, compressedCount: 0, compressThresholdIndex: n }\n }\n\n // Compress tool messages before threshold\n const result: Array<Message> = []\n let compressedCount = 0\n\n for (const [i, msg] of messages.entries()) {\n if (\n i < thresholdIndex\n && msg.role === \"tool\"\n && typeof msg.content === \"string\"\n && msg.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n compressedCount++\n result.push({\n ...msg,\n content: compressToolResultContent(msg.content),\n })\n continue\n }\n result.push(msg)\n }\n\n return {\n messages: result,\n compressedCount,\n compressThresholdIndex: thresholdIndex,\n }\n}\n\n// ============================================================================\n// Binary Search Algorithm\n// ============================================================================\n\ninterface PreserveSearchParams {\n messages: Array<Message>\n systemTokens: number\n tokenLimit: number\n}\n\n/**\n * Find the optimal index from which to preserve messages.\n * Uses binary search with pre-calculated cumulative sums.\n * Returns the smallest index where the preserved portion fits within limits.\n */\nfunction findOptimalPreserveIndex(params: PreserveSearchParams): number {\n const { messages, systemTokens, tokenLimit } = params\n\n if (messages.length === 0) return 0\n\n // Account for truncation marker (~50 tokens)\n const markerTokens = 50\n\n const availableTokens = tokenLimit - systemTokens - markerTokens\n\n if (availableTokens <= 0) {\n return messages.length // Cannot fit any messages\n }\n\n // Pre-calculate cumulative sums from the end\n const n = messages.length\n const { cumTokens } = calculateCumulativeSums(messages)\n\n // Binary search for the smallest index where token limit is satisfied\n let left = 0\n let right = n\n\n while (left < right) {\n const mid = (left + right) >>> 1\n if (cumTokens[mid] <= availableTokens) {\n right = mid // Can keep more messages\n } else {\n left = mid + 1 // Need to remove more\n }\n }\n\n return left\n}\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Check if payload needs compaction based on learned model limits.\n * Returns early with `needed: false` when no limits are known for the model.\n */\nexport async function checkNeedsCompactionOpenAI(\n payload: ChatCompletionsPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<OpenAICompactionCheckResult> {\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n\n // If no learned limits and no explicit target, skip pre-check\n // (we don't know the real limit yet — let the server tell us)\n const learned = getLearnedLimits(model.id)\n if (!learned && cfg.targetTokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // Defensive: should not reach here without a resolvable limit (guarded by early return above),\n // but satisfy the type system\n if (tokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const tokenCount = await getTokenCount(payload, model)\n const rawTokens = tokenCount.input\n\n // Apply calibration to adjust the GPT tokenizer estimate\n const currentTokens = learned && learned.sampleCount > 0 ? calibrate(model.id, rawTokens) : rawTokens\n\n const exceedsTokens = cfg.checkTokenLimit && currentTokens > tokenLimit\n\n return {\n needed: exceedsTokens,\n currentTokens,\n tokenLimit,\n reason: exceedsTokens ? \"tokens\" : undefined,\n }\n}\n\n/**\n * Generate a summary of removed messages for context.\n * Extracts key information like tool calls and topics.\n */\nfunction generateRemovedMessagesSummary(removedMessages: Array<Message>): string {\n const toolCalls: Array<string> = []\n let userMessageCount = 0\n let assistantMessageCount = 0\n\n for (const msg of removedMessages) {\n if (msg.role === \"user\") {\n userMessageCount++\n } else if (msg.role === \"assistant\") {\n assistantMessageCount++\n }\n\n // Extract tool call names\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n if (tc.function.name) {\n toolCalls.push(tc.function.name)\n }\n }\n }\n }\n\n // Build summary parts\n const parts: Array<string> = []\n\n // Message breakdown\n if (userMessageCount > 0 || assistantMessageCount > 0) {\n const breakdown = []\n if (userMessageCount > 0) breakdown.push(`${userMessageCount} user`)\n if (assistantMessageCount > 0) breakdown.push(`${assistantMessageCount} assistant`)\n parts.push(`Messages: ${breakdown.join(\", \")}`)\n }\n\n // Tool calls\n if (toolCalls.length > 0) {\n // Deduplicate and limit\n const uniqueTools = [...new Set(toolCalls)]\n const displayTools =\n uniqueTools.length > 5 ? [...uniqueTools.slice(0, 5), `+${uniqueTools.length - 5} more`] : uniqueTools\n parts.push(`Tools used: ${displayTools.join(\", \")}`)\n }\n\n return parts.join(\". \")\n}\n\n/**\n * Add a compression notice to the system message.\n * Informs the model that some tool content has been compressed.\n */\nfunction addCompressionNotice(payload: ChatCompletionsPayload, compressedCount: number): ChatCompletionsPayload {\n const notice =\n `\\n\\n[CONTEXT NOTE]\\n`\n + `${compressedCount} large tool results have been compressed to reduce context size.\\n`\n + `The compressed results show the beginning and end of the content with an omission marker.\\n`\n + `If you need the full content, you can re-read the file or re-run the tool.\\n`\n + `[END NOTE]`\n\n // Find last system message and append notice\n const messages = [...payload.messages]\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role === \"system\" || msg.role === \"developer\") {\n if (typeof msg.content === \"string\") {\n messages[i] = { ...msg, content: msg.content + notice }\n }\n break\n }\n }\n\n return { ...payload, messages }\n}\n\n/**\n * Create truncation context to append to system messages.\n */\nfunction createTruncationSystemContext(removedCount: number, compressedCount: number, summary: string): string {\n let context = `\\n\\n[CONVERSATION CONTEXT]\\n`\n\n if (removedCount > 0) {\n context += `${removedCount} earlier messages have been removed due to context window limits.\\n`\n }\n\n if (compressedCount > 0) {\n context += `${compressedCount} large tool results have been compressed.\\n`\n }\n\n if (summary) {\n context += `Summary of removed content: ${summary}\\n`\n }\n\n context +=\n `If you need earlier context, ask the user or check available tools for conversation history access.\\n`\n + `[END CONTEXT]`\n\n return context\n}\n\n/** Create a truncation marker message (fallback when no system message) */\nfunction createTruncationMarker(removedCount: number, compressedCount: number, summary: string): Message {\n const parts: Array<string> = []\n\n if (removedCount > 0) {\n parts.push(`${removedCount} earlier messages removed`)\n }\n if (compressedCount > 0) {\n parts.push(`${compressedCount} tool results compressed`)\n }\n\n let content = `[CONTEXT MODIFIED: ${parts.join(\", \")} to fit context limits]`\n if (summary) {\n content += `\\n[Summary: ${summary}]`\n }\n return {\n role: \"user\",\n content,\n }\n}\n\n// ============================================================================\n// Truncation Steps\n// ============================================================================\n\n/** Shared context for truncation operations */\ninterface TruncationContext {\n payload: ChatCompletionsPayload\n model: Model\n cfg: AutoTruncateConfig\n tokenLimit: number\n originalTokens: number\n originalBytes: number\n startTime: number\n}\n\nfunction buildTimedResult(\n ctx: TruncationContext,\n result: Omit<OpenAIAutoTruncateResult, \"processingTimeMs\">,\n): OpenAIAutoTruncateResult {\n return { ...result, processingTimeMs: Math.round(performance.now() - ctx.startTime) }\n}\n\n/**\n * Step 1: Try compressing tool results to fit within limits.\n * First compresses old tool results, then all if needed.\n * Returns early result if compression alone is sufficient.\n */\nasync function tryCompressToolResults(\n ctx: TruncationContext,\n): Promise<{ workingMessages: Array<Message>; compressedCount: number; earlyResult?: OpenAIAutoTruncateResult }> {\n if (!state.compressToolResultsBeforeTruncate) {\n return { workingMessages: ctx.payload.messages, compressedCount: 0 }\n }\n\n // Step 1a: Compress old tool messages\n const compressionResult = smartCompressToolResults(\n ctx.payload.messages,\n ctx.tokenLimit,\n ctx.cfg.preserveRecentPercent,\n )\n let workingMessages = compressionResult.messages\n let compressedCount = compressionResult.compressedCount\n\n // Check if compression alone was enough\n const compressedPayload = { ...ctx.payload, messages: workingMessages }\n const compressedBytes = JSON.stringify(compressedPayload).length\n const compressedTokenCount = await getTokenCount(compressedPayload, ctx.model)\n\n if (compressedTokenCount.input <= ctx.tokenLimit) {\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${compressedTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(compressedBytes)}KB `\n + `(compressed ${compressedCount} tool_results) [${elapsedMs}ms]`,\n )\n\n const noticePayload = addCompressionNotice(compressedPayload, compressedCount)\n // Estimate notice token overhead instead of full recount (~150 chars / 4 + framing)\n const noticeTokenOverhead = Math.ceil(150 / 4) + 10\n\n return {\n workingMessages,\n compressedCount,\n earlyResult: buildTimedResult(ctx, {\n payload: noticePayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: compressedTokenCount.input + noticeTokenOverhead,\n removedMessageCount: 0,\n }),\n }\n }\n\n // Step 1b: Compress ALL tool messages (including recent ones)\n const allCompression = smartCompressToolResults(\n workingMessages,\n ctx.tokenLimit,\n 0.0, // preservePercent=0 means compress all messages\n )\n if (allCompression.compressedCount > 0) {\n workingMessages = allCompression.messages\n compressedCount += allCompression.compressedCount\n\n // Check if compressing all was enough\n const allCompressedPayload = { ...ctx.payload, messages: workingMessages }\n const allCompressedBytes = JSON.stringify(allCompressedPayload).length\n const allCompressedTokenCount = await getTokenCount(allCompressedPayload, ctx.model)\n\n if (allCompressedTokenCount.input <= ctx.tokenLimit) {\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${allCompressedTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(allCompressedBytes)}KB `\n + `(compressed ${compressedCount} tool_results, including recent) [${elapsedMs}ms]`,\n )\n\n const noticePayload = addCompressionNotice(allCompressedPayload, compressedCount)\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 10\n\n return {\n workingMessages,\n compressedCount,\n earlyResult: buildTimedResult(ctx, {\n payload: noticePayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: allCompressedTokenCount.input + noticeTokenOverhead,\n removedMessageCount: 0,\n }),\n }\n }\n }\n\n return { workingMessages, compressedCount }\n}\n\n/**\n * Step 2: Remove messages to fit within limits using binary search.\n * Handles orphan cleanup, summary generation, and result assembly.\n */\nasync function truncateByMessageRemoval(\n ctx: TruncationContext,\n workingMessages: Array<Message>,\n compressedCount: number,\n): Promise<OpenAIAutoTruncateResult> {\n // Extract system messages from working messages\n const { systemMessages, conversationMessages } = extractOpenAISystemMessages(workingMessages)\n\n // Calculate system message token sizes\n const systemTokens = systemMessages.reduce((sum, m) => sum + estimateMessageTokens(m), 0)\n\n // Find optimal preserve index\n const preserveIndex = findOptimalPreserveIndex({\n messages: conversationMessages,\n systemTokens,\n tokenLimit: ctx.tokenLimit,\n })\n\n // Check if we can compact\n if (preserveIndex >= conversationMessages.length) {\n consola.warn(\"[AutoTruncate:OpenAI] Would need to remove all messages\")\n return buildTimedResult(ctx, {\n payload: ctx.payload,\n wasTruncated: false,\n originalTokens: ctx.originalTokens,\n compactedTokens: ctx.originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Build preserved messages and clean up orphans\n let preserved = conversationMessages.slice(preserveIndex)\n preserved = cleanupMessages(preserved)\n\n if (preserved.length === 0) {\n consola.warn(\"[AutoTruncate:OpenAI] All messages filtered out after cleanup\")\n return buildTimedResult(ctx, {\n payload: ctx.payload,\n wasTruncated: false,\n originalTokens: ctx.originalTokens,\n compactedTokens: ctx.originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Calculate removed messages and generate summary\n const removedMessages = conversationMessages.slice(0, preserveIndex)\n const removedCount = conversationMessages.length - preserved.length\n const summary = generateRemovedMessagesSummary(removedMessages)\n\n // Build new payload with truncation context\n let newSystemMessages = systemMessages\n let newMessages = preserved\n\n // Prefer adding context to last system message (cleaner for the model)\n if (systemMessages.length > 0) {\n const truncationContext = createTruncationSystemContext(removedCount, compressedCount, summary)\n const lastSystemIdx = systemMessages.length - 1\n const lastSystem = systemMessages[lastSystemIdx]\n\n // Append context to last system message\n const updatedSystem: Message = {\n ...lastSystem,\n\n content: typeof lastSystem.content === \"string\" ? lastSystem.content + truncationContext : lastSystem.content, // Can't append to array content\n }\n newSystemMessages = [...systemMessages.slice(0, lastSystemIdx), updatedSystem]\n } else {\n // No system messages, use marker message\n const marker = createTruncationMarker(removedCount, compressedCount, summary)\n newMessages = [marker, ...preserved]\n }\n\n const newPayload: ChatCompletionsPayload = {\n ...ctx.payload,\n messages: [...newSystemMessages, ...newMessages],\n }\n\n // Verify the result\n const newBytes = JSON.stringify(newPayload).length\n const newTokenCount = await getTokenCount(newPayload, ctx.model)\n\n // Log single line summary\n const actions: Array<string> = []\n if (removedCount > 0) actions.push(`removed ${removedCount} msgs`)\n if (compressedCount > 0) actions.push(`compressed ${compressedCount} tool_results`)\n const actionInfo = actions.length > 0 ? ` (${actions.join(\", \")})` : \"\"\n\n const elapsedMs = Math.round(performance.now() - ctx.startTime)\n consola.info(\n `[AutoTruncate:OpenAI] tokens: ${ctx.originalTokens}→${newTokenCount.input}, `\n + `${bytesToKB(ctx.originalBytes)}→${bytesToKB(newBytes)}KB${actionInfo} [${elapsedMs}ms]`,\n )\n\n // Warn if still over token limit\n if (newTokenCount.input > ctx.tokenLimit) {\n consola.warn(`[AutoTruncate:OpenAI] Result still over token limit (${newTokenCount.input} > ${ctx.tokenLimit})`)\n }\n\n return buildTimedResult(ctx, {\n payload: newPayload,\n wasTruncated: true,\n originalTokens: ctx.originalTokens,\n compactedTokens: newTokenCount.input,\n removedMessageCount: removedCount,\n })\n}\n\n// ============================================================================\n// Public Entry Points\n// ============================================================================\n\n/**\n * Perform auto-truncation on a payload that exceeds limits.\n * Uses binary search to find the optimal truncation point.\n *\n * Pipeline:\n * 1. Check if compaction is needed\n * 2. Try compressing tool results (old first, then all)\n * 3. If still over limit, remove messages via binary search\n */\nexport async function autoTruncateOpenAI(\n payload: ChatCompletionsPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<OpenAIAutoTruncateResult> {\n const startTime = performance.now()\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // No limit information available — skip truncation and let the server decide\n if (tokenLimit === undefined) {\n return {\n payload,\n wasTruncated: false,\n originalTokens: 0,\n compactedTokens: 0,\n removedMessageCount: 0,\n processingTimeMs: Math.round(performance.now() - startTime),\n }\n }\n\n // Measure original size\n const originalBytes = JSON.stringify(payload).length\n const originalTokens = (await getTokenCount(payload, model)).input\n\n const ctx: TruncationContext = {\n payload,\n model,\n cfg,\n tokenLimit,\n originalTokens,\n originalBytes,\n startTime,\n }\n\n // Check if compaction is needed\n if (originalTokens <= tokenLimit) {\n return buildTimedResult(ctx, {\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Step 1: Try tool result compression\n const { workingMessages, compressedCount, earlyResult } = await tryCompressToolResults(ctx)\n if (earlyResult) return earlyResult\n\n // Step 2: Message removal via binary search\n return await truncateByMessageRemoval(ctx, workingMessages, compressedCount)\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n */\nexport function createTruncationResponseMarkerOpenAI(result: OpenAIAutoTruncateResult): string {\n if (!result.wasTruncated) return \"\"\n\n const reduction = result.originalTokens - result.compactedTokens\n const percentage = Math.round((reduction / result.originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${result.removedMessageCount} messages removed, `\n + `${result.originalTokens} → ${result.compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n","import type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\nimport type { ChatCompletionsPayload, ChatCompletionResponse } from \"~/types/api/openai-chat-completions\"\n\nexport const createChatCompletions = async (\n payload: ChatCompletionsPayload,\n): Promise<ChatCompletionResponse | AsyncGenerator<ServerSentEventMessage>> => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = payload.messages.some(\n (x) => typeof x.content !== \"string\" && x.content?.some((x) => x.type === \"image_url\"),\n )\n\n // Agent/user check for X-Initiator header\n // Determine if any message is from an agent (\"assistant\" or \"tool\")\n const isAgentCall = payload.messages.some((msg) => [\"assistant\", \"tool\"].includes(msg.role))\n\n // Build headers and add X-Initiator\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create chat completions\", response)\n throw await HTTPError.fromResponse(\"Failed to create chat completions\", response, payload.model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ChatCompletionResponse\n}\n","/**\n * OpenAI message sanitization orchestrator.\n *\n * Combines system-reminder removal, orphan filtering, and empty block cleanup\n * into a single sanitization pipeline for OpenAI messages.\n */\n\nimport consola from \"consola\"\n\nimport type { SanitizeResult } from \"~/lib/request/pipeline\"\nimport { removeSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\n\nimport type { ChatCompletionsPayload, Message } from \"~/types/api/openai-chat-completions\"\n\nimport {\n extractOpenAISystemMessages,\n filterOpenAIOrphanedToolResults,\n filterOpenAIOrphanedToolUse,\n} from \"./orphan-filter\"\n\n// ============================================================================\n// Message Content Sanitization\n// ============================================================================\n\n/**\n * Remove system-reminder tags from OpenAI message content.\n * Handles both string content and array of content parts.\n *\n * NOTE: System prompt overrides are handled by\n * system-prompt.ts via config.yaml.\n */\nfunction sanitizeOpenAIMessageContent(msg: Message): Message {\n if (typeof msg.content === \"string\") {\n const sanitized = removeSystemReminderTags(msg.content)\n if (sanitized !== msg.content) {\n // Don't return empty content — keep original if sanitized is empty\n return sanitized ? { ...msg, content: sanitized } : msg\n }\n return msg\n }\n\n // Handle array of content parts (TextPart | ImagePart)\n if (Array.isArray(msg.content)) {\n const result = msg.content.reduce<{\n parts: Array<\n | { type: \"text\"; text: string }\n | {\n type: \"image_url\"\n image_url: { url: string; detail?: \"low\" | \"high\" | \"auto\" }\n }\n >\n modified: boolean\n }>(\n (acc, part) => {\n if (part.type === \"text\" && typeof part.text === \"string\") {\n const sanitized = removeSystemReminderTags(part.text)\n if (sanitized !== part.text) {\n if (sanitized) {\n acc.parts.push({ ...part, text: sanitized })\n }\n acc.modified = true\n return acc\n }\n }\n acc.parts.push(part)\n return acc\n },\n { parts: [], modified: false },\n )\n\n if (result.modified) {\n return { ...msg, content: result.parts }\n }\n }\n\n return msg\n}\n\n/**\n * Remove system-reminder tags from all OpenAI messages.\n */\nexport function removeOpenAISystemReminders(messages: Array<Message>): {\n messages: Array<Message>\n modifiedCount: number\n} {\n let modifiedCount = 0\n const result = messages.map((msg) => {\n const sanitized = sanitizeOpenAIMessageContent(msg)\n if (sanitized !== msg) modifiedCount++\n return sanitized\n })\n return { messages: result, modifiedCount }\n}\n\n// ============================================================================\n// Main Orchestrator\n// ============================================================================\n\n/**\n * Sanitize OpenAI messages by filtering orphaned tool messages and system reminders.\n *\n * @returns Sanitized payload and count of removed items\n */\nexport function sanitizeOpenAIMessages(payload: ChatCompletionsPayload): SanitizeResult<ChatCompletionsPayload> {\n const { systemMessages, conversationMessages } = extractOpenAISystemMessages(payload.messages)\n\n // Remove system-reminder tags from all messages\n const convResult = removeOpenAISystemReminders(conversationMessages)\n let messages = convResult.messages\n const sysResult = removeOpenAISystemReminders(systemMessages)\n const sanitizedSystemMessages = sysResult.messages\n const systemReminderRemovals = convResult.modifiedCount + sysResult.modifiedCount\n\n const originalCount = messages.length\n\n // Filter orphaned tool_result and tool_use messages\n messages = filterOpenAIOrphanedToolResults(messages)\n messages = filterOpenAIOrphanedToolUse(messages)\n\n // Final safety net: remove empty/whitespace-only text parts from array content\n const allMessages = [...sanitizedSystemMessages, ...messages].map((msg) => {\n if (!Array.isArray(msg.content)) return msg\n const filtered = msg.content.filter((part) => {\n if (part.type === \"text\") return part.text.trim() !== \"\"\n return true\n })\n if (filtered.length === msg.content.length) return msg\n return { ...msg, content: filtered }\n })\n\n const removedCount = originalCount - messages.length\n\n if (removedCount > 0) {\n consola.info(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool messages`)\n }\n\n return {\n payload: {\n ...payload,\n messages: allMessages,\n },\n removedCount,\n systemReminderRemovals,\n }\n}\n","/**\n * Stream accumulator for OpenAI format responses.\n * Handles accumulating ChatCompletionChunk events for history recording and tracking.\n */\n\nimport type { BaseStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\n\nimport type { ChatCompletionChunk } from \"~/types/api/openai-chat-completions\"\n\n/** Internal tool call accumulator using string array to avoid O(n²) concatenation */\ninterface ToolCallAccumulator {\n id: string\n name: string\n argumentParts: Array<string>\n}\n\n/** Stream accumulator for OpenAI format */\nexport interface OpenAIStreamAccumulator extends BaseStreamAccumulator {\n cachedTokens: number\n finishReason: string\n toolCalls: Array<{ id: string; name: string; arguments: string }>\n toolCallMap: Map<number, ToolCallAccumulator>\n}\n\nexport function createOpenAIStreamAccumulator(): OpenAIStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n cachedTokens: 0,\n finishReason: \"\",\n content: \"\",\n toolCalls: [],\n toolCallMap: new Map(),\n }\n}\n\n/** Accumulate a single parsed OpenAI chunk into the accumulator */\nexport function accumulateOpenAIStreamEvent(parsed: ChatCompletionChunk, acc: OpenAIStreamAccumulator) {\n if (parsed.model && !acc.model) acc.model = parsed.model\n\n if (parsed.usage) {\n acc.inputTokens = parsed.usage.prompt_tokens\n acc.outputTokens = parsed.usage.completion_tokens\n if (parsed.usage.prompt_tokens_details?.cached_tokens !== undefined) {\n acc.cachedTokens = parsed.usage.prompt_tokens_details.cached_tokens\n }\n }\n\n const choice = parsed.choices[0] as (typeof parsed.choices)[0] | undefined\n if (choice) {\n if (choice.delta.content) acc.content += choice.delta.content\n if (choice.delta.tool_calls) {\n for (const tc of choice.delta.tool_calls) {\n const idx = tc.index\n if (!acc.toolCallMap.has(idx)) {\n acc.toolCallMap.set(idx, {\n id: tc.id ?? \"\",\n name: tc.function?.name ?? \"\",\n argumentParts: [],\n })\n }\n const item = acc.toolCallMap.get(idx)\n if (item) {\n if (tc.id) item.id = tc.id\n if (tc.function?.name) item.name = tc.function.name\n if (tc.function?.arguments) item.argumentParts.push(tc.function.arguments)\n }\n }\n }\n if (choice.finish_reason) acc.finishReason = choice.finish_reason\n }\n}\n","/**\n * Payload utilities for request handlers.\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\nimport type { ChatCompletionsPayload } from \"~/types/api/openai-chat-completions\"\nimport type { MessagesPayload } from \"~/types/api/anthropic\"\n\nimport { getTokenCount } from \"~/lib/models/tokenizer\"\nimport { bytesToKB } from \"~/lib/utils\"\n\n/**\n * Log helpful debugging information when a 413 error occurs.\n *\n * @param precomputedBytes - Optional pre-computed payload byte size to avoid redundant JSON.stringify\n */\nexport async function logPayloadSizeInfo(\n payload: ChatCompletionsPayload,\n model: Model | undefined,\n precomputedBytes?: number,\n) {\n const messageCount = payload.messages.length\n const bodySize = precomputedBytes ?? JSON.stringify(payload).length\n const bodySizeKB = bytesToKB(bodySize)\n\n // Count images and large messages\n let imageCount = 0\n let largeMessages = 0\n let totalImageSize = 0\n\n for (const msg of payload.messages) {\n if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.type === \"image_url\") {\n imageCount++\n if (part.image_url.url.startsWith(\"data:\")) {\n totalImageSize += part.image_url.url.length\n }\n }\n }\n }\n\n const msgSize = typeof msg.content === \"string\" ? msg.content.length : JSON.stringify(msg.content).length\n if (msgSize > 50000) largeMessages++\n }\n\n consola.info(\"\")\n consola.info(\"╭─────────────────────────────────────────────────────────╮\")\n consola.info(\"│ 413 Request Entity Too Large │\")\n consola.info(\"╰─────────────────────────────────────────────────────────╯\")\n consola.info(\"\")\n consola.info(` Request body size: ${bodySizeKB} KB (${bodySize.toLocaleString()} bytes)`)\n consola.info(` Message count: ${messageCount}`)\n\n if (model) {\n try {\n const tokenCount = await getTokenCount(payload, model)\n const limit = model.capabilities?.limits?.max_prompt_tokens ?? 128000\n consola.info(` Estimated tokens: ${tokenCount.input.toLocaleString()} / ${limit.toLocaleString()}`)\n } catch (error) {\n consola.debug(\"Token count estimation failed:\", error)\n }\n }\n\n if (imageCount > 0) {\n const imageSizeKB = bytesToKB(totalImageSize)\n consola.info(` Images: ${imageCount} (${imageSizeKB} KB base64 data)`)\n }\n if (largeMessages > 0) {\n consola.info(` Large messages (>50KB): ${largeMessages}`)\n }\n\n consola.info(\"\")\n consola.info(\" Suggestions:\")\n if (imageCount > 0) {\n consola.info(\" • Remove or resize large images in the conversation\")\n }\n consola.info(\" • Start a new conversation with /clear or /reset\")\n consola.info(\" • Reduce conversation history by deleting old messages\")\n consola.info(\"\")\n}\n\n/** Log payload size info for Anthropic format when a 413 error occurs */\nexport function logPayloadSizeInfoAnthropic(payload: MessagesPayload, model: Model | undefined) {\n const payloadSize = JSON.stringify(payload).length\n const messageCount = payload.messages.length\n const toolCount = payload.tools?.length ?? 0\n const systemSize = payload.system ? JSON.stringify(payload.system).length : 0\n\n consola.info(\n `[Anthropic 413] Payload size: ${bytesToKB(payloadSize)}KB, `\n + `messages: ${messageCount}, tools: ${toolCount}, system: ${bytesToKB(systemSize)}KB`,\n )\n\n if (model?.capabilities?.limits) {\n const limits = model.capabilities.limits\n consola.info(\n `[Anthropic 413] Model limits: context=${limits.max_context_window_tokens}, `\n + `prompt=${limits.max_prompt_tokens}, output=${limits.max_output_tokens}`,\n )\n }\n}\n","/**\n * Auto-truncate retry strategy.\n *\n * Handles token limit errors by truncating the message payload and retrying.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\nimport type { Model } from \"~/lib/models/client\"\n\nimport { AUTO_TRUNCATE_RETRY_FACTOR, tryParseAndLearnLimit } from \"~/lib/auto-truncate\"\nimport { HTTPError } from \"~/lib/error\"\n\nimport type { RetryAction, RetryContext, RetryStrategy, SanitizeResult } from \"../pipeline\"\n\n/** Result from a truncation operation */\nexport interface TruncateResult<TPayload> {\n wasTruncated: boolean\n payload: TPayload\n removedMessageCount: number\n originalTokens: number\n compactedTokens: number\n processingTimeMs: number\n}\n\n/** Options passed to the truncation function */\nexport interface TruncateOptions {\n checkTokenLimit: boolean\n targetTokenLimit?: number\n}\n\n/**\n * Create an auto-truncate retry strategy.\n *\n * @param truncate - Format-specific truncation function\n * @param resanitize - Format-specific re-sanitization after truncation\n * @param isEnabled - Check if auto-truncate is enabled (typically reads state.autoTruncate)\n */\nexport function createAutoTruncateStrategy<TPayload>(opts: {\n truncate: (payload: TPayload, model: Model, options: TruncateOptions) => Promise<TruncateResult<TPayload>>\n resanitize: (payload: TPayload) => SanitizeResult<TPayload>\n isEnabled: () => boolean\n label: string\n}): RetryStrategy<TPayload> {\n const { truncate, resanitize, isEnabled, label } = opts\n\n return {\n name: \"auto-truncate\",\n\n canHandle(error: ApiError): boolean {\n if (!isEnabled()) return false\n return error.type === \"payload_too_large\" || error.type === \"token_limit\"\n },\n\n async handle(\n error: ApiError,\n currentPayload: TPayload,\n context: RetryContext<TPayload>,\n ): Promise<RetryAction<TPayload>> {\n const { attempt, originalPayload, model, maxRetries } = context\n\n if (!model) {\n return { action: \"abort\", error }\n }\n\n // Extract the raw error to get HTTP details for tryParseAndLearnLimit\n const rawError = error.raw\n if (!(rawError instanceof HTTPError)) {\n return { action: \"abort\", error }\n }\n\n // Estimate tokens using GPT tokenizer for calibration feedback\n const payloadJson = JSON.stringify(currentPayload)\n const estimatedTokens = Math.ceil(payloadJson.length / 4)\n\n const parsed = tryParseAndLearnLimit(rawError, model.id, true, estimatedTokens)\n\n if (!parsed) {\n // For 413 errors without parseable limit info, still retry with truncation\n if (rawError.status === 413) {\n consola.info(\n `[${label}] Attempt ${attempt + 1}/${maxRetries + 1}: ` + `413 Body too large, retrying with truncation...`,\n )\n\n const truncateResult = await truncate(originalPayload, model, {\n checkTokenLimit: true,\n })\n\n if (!truncateResult.wasTruncated) {\n return { action: \"abort\", error }\n }\n\n const sanitizeResult = resanitize(truncateResult.payload)\n return {\n action: \"retry\",\n payload: sanitizeResult.payload,\n meta: {\n truncateResult,\n sanitization: sanitizeResult.stats ?? {\n totalBlocksRemoved: sanitizeResult.removedCount,\n systemReminderRemovals: sanitizeResult.systemReminderRemovals,\n },\n attempt: attempt + 1,\n },\n }\n }\n\n return { action: \"abort\", error }\n }\n\n // Calculate target token limit based on error info\n let targetTokenLimit: number | undefined\n\n if (parsed.limit) {\n targetTokenLimit = Math.floor(parsed.limit * AUTO_TRUNCATE_RETRY_FACTOR)\n consola.info(\n `[${label}] Attempt ${attempt + 1}/${maxRetries + 1}: `\n + `Token limit error (${parsed.current}>${parsed.limit}), `\n + `retrying with limit ${targetTokenLimit}...`,\n )\n }\n\n // Truncate from original payload (not from already-truncated)\n const truncateResult = await truncate(originalPayload, model, {\n checkTokenLimit: true,\n targetTokenLimit,\n })\n\n if (!truncateResult.wasTruncated) {\n // Truncation didn't help\n return { action: \"abort\", error }\n }\n\n // Re-sanitize the truncated payload\n const sanitizeResult = resanitize(truncateResult.payload)\n\n return {\n action: \"retry\",\n payload: sanitizeResult.payload,\n meta: {\n truncateResult,\n sanitization: sanitizeResult.stats ?? {\n totalBlocksRemoved: sanitizeResult.removedCount,\n systemReminderRemovals: sanitizeResult.systemReminderRemovals,\n },\n attempt: attempt + 1,\n },\n }\n },\n }\n}\n","/**\n * Truncation marker utilities.\n */\n\n/** Minimal truncate result info needed for usage adjustment and markers */\nexport interface TruncateResultInfo {\n wasTruncated: boolean\n originalTokens?: number\n compactedTokens?: number\n removedMessageCount?: number\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n * Works with both OpenAI and Anthropic truncate results.\n */\nexport function createTruncationMarker(result: TruncateResultInfo): string {\n if (!result.wasTruncated) return \"\"\n\n const { originalTokens, compactedTokens, removedMessageCount } = result\n\n if (originalTokens === undefined || compactedTokens === undefined || removedMessageCount === undefined) {\n return `\\n\\n---\\n[Auto-truncated: conversation history was reduced to fit context limits]`\n }\n\n const reduction = originalTokens - compactedTokens\n const percentage = Math.round((reduction / originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${removedMessageCount} messages removed, `\n + `${originalTokens} → ${compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n","import type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { SSEStreamingApi, streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { MessageContent } from \"~/lib/history\"\nimport type { Model } from \"~/lib/models/client\"\nimport type { ChatCompletionChunk, ChatCompletionResponse, ChatCompletionsPayload } from \"~/types/api/openai-chat-completions\"\nimport type { FormatAdapter } from \"~/lib/request/pipeline\"\n\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { MAX_AUTO_TRUNCATE_RETRIES } from \"~/lib/auto-truncate\"\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport {\n autoTruncateOpenAI,\n createTruncationResponseMarkerOpenAI,\n type OpenAIAutoTruncateResult,\n} from \"~/lib/openai/auto-truncate\"\nimport { createChatCompletions } from \"~/lib/openai/client\"\nimport { sanitizeOpenAIMessages } from \"~/lib/openai/sanitize\"\nimport { createOpenAIStreamAccumulator, accumulateOpenAIStreamEvent } from \"~/lib/openai/stream-accumulator\"\nimport { buildOpenAIResponseData, isNonStreaming, logPayloadSizeInfo } from \"~/lib/request\"\nimport { executeRequestPipeline } from \"~/lib/request/pipeline\"\nimport { createAutoTruncateStrategy, type TruncateResult } from \"~/lib/request/strategies/auto-truncate\"\nimport { createNetworkRetryStrategy } from \"~/lib/request/strategies/network-retry\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { STREAM_ABORTED, StreamIdleTimeoutError, combineAbortSignals, raceIteratorNext } from \"~/lib/stream\"\nimport { processOpenAIMessages } from \"~/lib/system-prompt\"\nimport { tuiLogger } from \"~/lib/tui\"\nimport { isNullish } from \"~/lib/utils\"\n\nexport async function handleChatCompletion(c: Context) {\n const originalPayload = await c.req.json<ChatCompletionsPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n const clientModel = originalPayload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n originalPayload.model = resolvedModel\n }\n\n // Find the selected model and validate endpoint support\n const selectedModel = state.modelIndex.get(originalPayload.model)\n if (!isEndpointSupported(selectedModel, ENDPOINT.CHAT_COMPLETIONS)) {\n const msg = `Model \"${originalPayload.model}\" does not support the ${ENDPOINT.CHAT_COMPLETIONS} endpoint`\n throw new HTTPError(msg, 400, msg)\n }\n\n // System prompt collection + config-based overrides (always active)\n originalPayload.messages = await processOpenAIMessages(originalPayload.messages, originalPayload.model)\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Create request context — triggers \"created\" event → history consumer inserts entry\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"openai-chat-completions\", tuiLogId })\n reqCtx.setOriginalRequest({\n // Use client's original model name (before resolution/overrides)\n model: clientModel,\n messages: originalPayload.messages as unknown as Array<MessageContent>,\n stream: originalPayload.stream ?? false,\n tools: originalPayload.tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n })),\n payload: originalPayload,\n })\n\n // Update TUI tracker with model info (immediate feedback)\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: originalPayload.model,\n ...(clientModel !== originalPayload.model && { clientModel }),\n })\n }\n\n // Sanitize messages (filter orphaned tool blocks, system-reminders)\n const { payload: sanitizedPayload } = sanitizeOpenAIMessages(originalPayload)\n\n const finalPayload =\n isNullish(sanitizedPayload.max_tokens) ?\n {\n ...sanitizedPayload,\n max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens,\n }\n : sanitizedPayload\n\n if (isNullish(originalPayload.max_tokens)) {\n consola.debug(\"Set max_tokens to:\", JSON.stringify(finalPayload.max_tokens))\n }\n\n // Execute request with reactive retry pipeline\n return executeRequest({\n c,\n payload: finalPayload,\n originalPayload,\n selectedModel,\n reqCtx,\n })\n}\n\n/** Options for executeRequest */\ninterface ExecuteRequestOptions {\n c: Context\n payload: ChatCompletionsPayload\n originalPayload: ChatCompletionsPayload\n selectedModel: Model | undefined\n reqCtx: RequestContext\n}\n\n/**\n * Execute the API call with reactive retry pipeline.\n * Handles 413 and token limit errors with auto-truncation.\n */\nasync function executeRequest(opts: ExecuteRequestOptions) {\n const { c, payload, originalPayload, selectedModel, reqCtx } = opts\n\n // Build adapter and strategy for the pipeline\n const adapter: FormatAdapter<ChatCompletionsPayload> = {\n format: \"openai-chat-completions\",\n sanitize: (p) => sanitizeOpenAIMessages(p),\n execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p)),\n logPayloadSize: (p) => logPayloadSizeInfo(p, selectedModel),\n }\n\n const strategies = [\n createNetworkRetryStrategy<ChatCompletionsPayload>(),\n createTokenRefreshStrategy<ChatCompletionsPayload>(),\n createAutoTruncateStrategy<ChatCompletionsPayload>({\n truncate: (p, model, truncOpts) =>\n autoTruncateOpenAI(p, model, truncOpts) as Promise<TruncateResult<ChatCompletionsPayload>>,\n resanitize: (p) => sanitizeOpenAIMessages(p),\n isEnabled: () => state.autoTruncate,\n label: \"Completions\",\n }),\n ]\n\n // Track truncation result for non-streaming response marker\n let truncateResult: OpenAIAutoTruncateResult | undefined\n\n try {\n const result = await executeRequestPipeline({\n adapter,\n strategies,\n payload,\n originalPayload,\n model: selectedModel,\n maxRetries: MAX_AUTO_TRUNCATE_RETRIES,\n requestContext: reqCtx,\n onRetry: (attempt, _strategyName, _newPayload, meta) => {\n // Capture truncation result for response marker\n const retryTruncateResult = meta?.truncateResult as OpenAIAutoTruncateResult | undefined\n if (retryTruncateResult) {\n truncateResult = retryTruncateResult\n }\n\n // Update tracking tags\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, { tags: [\"truncated\", `retry-${attempt + 1}`] })\n }\n },\n })\n\n const response = result.response\n\n if (isNonStreaming(response as ChatCompletionResponse | AsyncIterable<unknown>)) {\n return handleNonStreamingResponse(c, response as ChatCompletionResponse, reqCtx, truncateResult)\n }\n\n consola.debug(\"Streaming response\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n await handleStreamingResponse({\n stream,\n response: response as AsyncIterable<ServerSentEventMessage>,\n payload,\n reqCtx,\n truncateResult,\n clientAbortSignal: clientAbort.signal,\n })\n })\n } catch (error) {\n reqCtx.fail(payload.model, error)\n throw error\n }\n}\n\n// Handle non-streaming response\nfunction handleNonStreamingResponse(\n c: Context,\n originalResponse: ChatCompletionResponse,\n reqCtx: RequestContext,\n truncateResult: OpenAIAutoTruncateResult | undefined,\n) {\n // Prepend truncation marker if auto-truncate was performed (only in verbose mode)\n let response = originalResponse\n if (state.verbose && truncateResult?.wasTruncated && response.choices[0]?.message.content) {\n const marker = createTruncationResponseMarkerOpenAI(truncateResult)\n const firstChoice = response.choices[0]\n response = {\n ...response,\n choices: [\n { ...firstChoice, message: { ...firstChoice.message, content: `${marker}${firstChoice.message.content}` } },\n ...response.choices.slice(1),\n ],\n }\n }\n\n const choice = response.choices[0]\n const usage = response.usage\n\n reqCtx.complete({\n success: true,\n model: response.model,\n usage: {\n input_tokens: usage?.prompt_tokens ?? 0,\n output_tokens: usage?.completion_tokens ?? 0,\n ...(usage?.prompt_tokens_details?.cached_tokens !== undefined && {\n cache_read_input_tokens: usage.prompt_tokens_details.cached_tokens,\n }),\n },\n stop_reason: choice.finish_reason ?? undefined,\n content: choice.message,\n })\n\n return c.json(response)\n}\n\n/** Options for handleStreamingResponse */\ninterface StreamingOptions {\n stream: SSEStreamingApi\n response: AsyncIterable<ServerSentEventMessage>\n payload: ChatCompletionsPayload\n reqCtx: RequestContext\n truncateResult: OpenAIAutoTruncateResult | undefined\n /** Abort signal that fires when the downstream client disconnects */\n clientAbortSignal?: AbortSignal\n}\n\n// Handle streaming response\nasync function handleStreamingResponse(opts: StreamingOptions) {\n const { stream, response, payload, reqCtx, truncateResult, clientAbortSignal } = opts\n const acc = createOpenAIStreamAccumulator()\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n\n // Streaming metrics for TUI footer\n let bytesIn = 0\n let eventsIn = 0\n\n try {\n // Prepend truncation marker as first chunk if auto-truncate was performed (only in verbose mode)\n if (state.verbose && truncateResult?.wasTruncated) {\n const marker = createTruncationResponseMarkerOpenAI(truncateResult)\n const markerChunk: ChatCompletionChunk = {\n id: `truncation-marker-${Date.now()}`,\n object: \"chat.completion.chunk\",\n created: Math.floor(Date.now() / 1000),\n model: payload.model,\n choices: [\n {\n index: 0,\n delta: { content: marker },\n finish_reason: null,\n logprobs: null,\n },\n ],\n }\n await stream.writeSSE({\n data: JSON.stringify(markerChunk),\n event: \"message\",\n })\n acc.content += marker\n }\n\n const iterator = response[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbortSignal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n if (result === STREAM_ABORTED) break\n if (result.done) break\n\n const rawEvent = result.value\n\n bytesIn += rawEvent.data?.length ?? 0\n eventsIn++\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n })\n }\n\n // Parse and accumulate for history/tracking (skip [DONE] and empty data)\n if (rawEvent.data && rawEvent.data !== \"[DONE]\") {\n try {\n const chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk\n accumulateOpenAIStreamEvent(chunk, acc)\n } catch {\n // Ignore parse errors\n }\n }\n\n // Forward every event to client — proxy preserves upstream data\n await stream.writeSSE({\n data: rawEvent.data ?? \"\",\n event: rawEvent.event,\n id: rawEvent.id !== undefined ? String(rawEvent.id) : undefined,\n retry: rawEvent.retry,\n })\n }\n\n const responseData = buildOpenAIResponseData(acc, payload.model)\n reqCtx.complete(responseData)\n } catch (error) {\n consola.error(\"[ChatCompletions] Stream error:\", error)\n reqCtx.fail(acc.model || payload.model, error)\n\n // Send error to client as final SSE event (consistent with Anthropic path)\n const errorMessage = error instanceof Error ? error.message : String(error)\n await stream.writeSSE({\n data: JSON.stringify({\n error: {\n message: errorMessage,\n type: error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"server_error\",\n },\n }),\n event: \"error\",\n })\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleChatCompletion } from \"./handler\"\n\nexport const chatCompletionRoutes = new Hono()\n\nchatCompletionRoutes.post(\"/\", async (c) => {\n try {\n return await handleChatCompletion(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { copilotHeaders, copilotBaseUrl } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createEmbeddings = async (payload: EmbeddingRequest) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n // Normalize input to array — some API providers reject bare string input\n const normalizedPayload = {\n ...payload,\n input: typeof payload.input === \"string\" ? [payload.input] : payload.input,\n }\n\n const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(normalizedPayload),\n })\n\n if (!response.ok) throw await HTTPError.fromResponse(\"Failed to create embeddings\", response)\n\n return (await response.json()) as EmbeddingResponse\n}\n\nexport interface EmbeddingRequest {\n input: string | Array<string>\n model: string\n encoding_format?: \"float\" | \"base64\"\n dimensions?: number\n}\n\nexport interface Embedding {\n object: string\n embedding: Array<number>\n index: number\n}\n\nexport interface EmbeddingResponse {\n object: string\n data: Array<Embedding>\n model: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { createEmbeddings, type EmbeddingRequest } from \"~/lib/openai/embeddings\"\n\nexport const embeddingsRoutes = new Hono()\n\nembeddingsRoutes.post(\"/\", async (c) => {\n try {\n const payload = await c.req.json<EmbeddingRequest>()\n const response = await createEmbeddings(payload)\n\n return c.json(response)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nexport const eventLoggingRoutes = new Hono()\n\n// Anthropic SDK sends telemetry to this endpoint\n// Return 200 OK to prevent errors in the SDK\neventLoggingRoutes.post(\"/batch\", (c) => {\n return c.text(\"OK\", 200)\n})\n","/**\n * Anthropic API Types\n *\n * Content block types, stream events, and response types are imported from\n * the `@anthropic-ai/sdk`. Request payload and tool types remain our own\n * definitions since Copilot proxies arbitrary model names (not SDK's literal\n * union) and adds extensions (context_management, copilot_annotations).\n */\n\n// ============================================================================\n// Re-export SDK types\n// ============================================================================\n\n// Response content blocks\nexport type {\n ContentBlock,\n RedactedThinkingBlock,\n ServerToolUseBlock,\n TextBlock,\n ThinkingBlock,\n ToolUseBlock,\n WebSearchToolResultBlock,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Request content blocks\nexport type {\n ContentBlockParam,\n ImageBlockParam,\n TextBlockParam,\n ToolResultBlockParam,\n ToolUseBlockParam,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Messages\nexport type { Message, MessageParam } from \"@anthropic-ai/sdk/resources/messages\"\n\n// Thinking & cache\nexport type { CacheControlEphemeral, ThinkingConfigParam } from \"@anthropic-ai/sdk/resources/messages\"\n\n// Stream events\nexport type {\n RawContentBlockDelta,\n RawContentBlockStartEvent,\n RawContentBlockStopEvent,\n RawMessageDeltaEvent,\n RawMessageStartEvent,\n RawMessageStopEvent,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// Internal-only SDK imports (not re-exported)\nimport type {\n ContentBlock,\n ContentBlockParam,\n TextBlockParam,\n MessageParam,\n ThinkingConfigParam,\n CacheControlEphemeral,\n WebSearchToolResultBlock,\n ToolResultBlockParam,\n RawContentBlockDeltaEvent,\n RawMessageStartEvent,\n RawMessageStopEvent,\n RawMessageDeltaEvent,\n RawContentBlockStartEvent,\n RawContentBlockStopEvent,\n} from \"@anthropic-ai/sdk/resources/messages\"\n\n// ============================================================================\n// Request payload (our own — SDK uses Model literal union, we proxy strings)\n// ============================================================================\n\nexport interface MessagesPayload {\n model: string\n max_tokens: number\n messages: Array<MessageParam>\n system?: string | Array<TextBlockParam>\n temperature?: number\n top_p?: number\n top_k?: number\n stop_sequences?: Array<string>\n stream?: boolean\n tools?: Array<Tool>\n tool_choice?: ToolChoice\n thinking?: ThinkingConfigParam\n metadata?: { user_id?: string }\n context_management?: Record<string, unknown>\n}\n\nexport interface Tool {\n name: string\n description?: string\n input_schema?: Record<string, unknown>\n cache_control?: CacheControlEphemeral\n type?: string\n defer_loading?: boolean\n}\n\nexport type ToolChoice = { type: \"auto\" } | { type: \"any\" } | { type: \"none\" } | { type: \"tool\"; name: string }\n\n// ============================================================================\n// Message subtypes (narrow role for cast convenience)\n// ============================================================================\n\nexport interface UserMessage {\n role: \"user\"\n content: string | Array<ContentBlockParam>\n}\n\nexport interface AssistantMessage {\n role: \"assistant\"\n content: string | Array<ContentBlockParam>\n}\n\n// ============================================================================\n// Copilot Extensions (not part of the Anthropic API)\n// ============================================================================\n\nexport interface CopilotIPCodeCitation {\n url: string\n license: string\n repository: string\n start_line: number\n end_line: number\n}\n\n/** Copilot-specific annotations attached to SSE content block deltas */\nexport interface CopilotAnnotations {\n ip_code_citations?: Array<CopilotIPCodeCitation>\n}\n\n/** Content block delta event with Copilot annotations extension */\ntype CopilotContentBlockDeltaEvent = RawContentBlockDeltaEvent & {\n copilot_annotations?: CopilotAnnotations\n}\n\nexport interface StreamPingEvent {\n type: \"ping\"\n}\n\nexport interface StreamErrorEvent {\n type: \"error\"\n error: { type: string; message: string }\n}\n\n/** Stream event union — replaces SDK's delta event with our Copilot-extended version */\nexport type StreamEvent =\n | RawMessageStartEvent\n | RawMessageStopEvent\n | RawMessageDeltaEvent\n | RawContentBlockStartEvent\n | RawContentBlockStopEvent\n | CopilotContentBlockDeltaEvent\n | StreamPingEvent\n | StreamErrorEvent\n\n// ============================================================================\n// Type guards\n// ============================================================================\n\n/** Type guard for ToolResultBlockParam */\nexport function isToolResultBlock(block: ContentBlockParam): block is ToolResultBlockParam {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- type guard pattern requires cast\n return (block as ToolResultBlockParam).type === \"tool_result\"\n}\n\n/** Type guard for server-side tool result blocks (web_search, tool_search, code_execution, etc.) */\nexport function isServerToolResultBlock(block: ContentBlockParam | ContentBlock): block is WebSearchToolResultBlock {\n // Cast to string to allow matching beyond the SDK's narrow literal type union.\n // Server tool results include: web_search_tool_result, tool_search_tool_result,\n // code_execution_tool_result, etc. They all end with \"_tool_result\" and carry a tool_use_id.\n // Exclude plain \"tool_result\" which is the standard user-side tool result.\n const type = (block as unknown as Record<string, unknown>).type as string | undefined\n if (!type) return false\n return type !== \"tool_result\" && type.endsWith(\"_tool_result\") && \"tool_use_id\" in block\n}\n","/**\n * Anthropic message sanitization orchestrator.\n *\n * Combines system-reminder removal, orphan filtering, and empty block cleanup\n * into a single sanitization pipeline for Anthropic messages.\n */\n\nimport consola from \"consola\"\n\nimport type { SanitizeResult } from \"~/lib/request/pipeline\"\nimport type {\n ContentBlock,\n AssistantMessage,\n MessageParam,\n MessagesPayload,\n ContentBlockParam,\n UserMessage,\n} from \"~/types/api/anthropic\"\n\nimport { removeSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\nimport { extractLeadingSystemReminderTags, extractTrailingSystemReminderTags } from \"~/lib/sanitize-system-reminder\"\nimport { state } from \"~/lib/state\"\nimport { isServerToolResultBlock } from \"~/types/api/anthropic\"\n\n// ============================================================================\n// Shared: Sanitize text blocks in an array\n// ============================================================================\n\n/**\n * Remove system-reminder tags from text blocks in an array.\n * Drops blocks whose text becomes empty after sanitization.\n * Returns the original array reference if nothing changed (for cheap identity checks).\n */\nfunction sanitizeTextBlocksInArray<T extends { type: string }>(\n blocks: Array<T>,\n getText: (b: T) => string | undefined,\n setText: (b: T, text: string) => T,\n): { blocks: Array<T>; modified: boolean } {\n let modified = false\n const result: Array<T> = []\n\n for (const block of blocks) {\n const text = getText(block)\n if (text !== undefined) {\n const sanitized = removeSystemReminderTags(text)\n if (sanitized !== text) {\n modified = true\n if (sanitized) {\n result.push(setText(block, sanitized))\n }\n continue\n }\n }\n result.push(block)\n }\n\n return { blocks: modified ? result : blocks, modified }\n}\n\n// ============================================================================\n// Tool Result Content Sanitization\n// ============================================================================\n\n/**\n * Sanitize tool_result content (can be string or array of text/image blocks).\n * Returns the sanitized content and whether it was modified.\n */\nfunction sanitizeToolResultContent(\n content: string | Array<{ type: \"text\"; text: string } | { type: \"image\"; source: unknown }>,\n): { content: typeof content; modified: boolean } {\n if (typeof content === \"string\") {\n const sanitized = removeSystemReminderTags(content)\n // Don't return empty content — keep original if sanitized is empty\n if (!sanitized && sanitized !== content) {\n return { content, modified: false }\n }\n return { content: sanitized, modified: sanitized !== content }\n }\n\n const { blocks, modified } = sanitizeTextBlocksInArray(\n content,\n (b) => (b.type === \"text\" ? b.text : undefined),\n (b, text) => ({ ...b, text }),\n )\n return { content: modified ? blocks : content, modified }\n}\n\n// ============================================================================\n// Message Content Sanitization\n// ============================================================================\n\n/**\n * Remove system-reminder tags from Anthropic message content.\n */\nfunction sanitizeMessageParamContent(msg: MessageParam): MessageParam {\n if (typeof msg.content === \"string\") {\n const sanitized = removeSystemReminderTags(msg.content)\n if (sanitized !== msg.content) {\n // Don't return empty content — keep original if sanitized is empty\n return sanitized ? { ...msg, content: sanitized } : msg\n }\n return msg\n }\n\n if (msg.role === \"user\") {\n // User messages: sanitize text blocks + tool_result content\n let modified = false\n const blocks: Array<ContentBlockParam> = []\n\n for (const block of msg.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n const sanitized = removeSystemReminderTags(block.text)\n if (sanitized !== block.text) {\n modified = true\n if (sanitized) blocks.push({ ...block, text: sanitized })\n continue\n }\n } else if (block.type === \"tool_result\" && block.content) {\n const sanitizedResult = sanitizeToolResultContent(\n block.content as Parameters<typeof sanitizeToolResultContent>[0],\n )\n if (sanitizedResult.modified) {\n modified = true\n blocks.push({ ...block, content: sanitizedResult.content } as ContentBlockParam)\n continue\n }\n }\n blocks.push(block)\n }\n\n return modified ? ({ role: \"user\", content: blocks } as UserMessage) : msg\n }\n\n // Assistant message: only sanitize text blocks\n const { blocks, modified } = sanitizeTextBlocksInArray(\n msg.content,\n (b) => (b.type === \"text\" && \"text\" in b ? (b as { text: string }).text : undefined),\n (b, text) => ({ ...b, text }) as ContentBlock,\n )\n return modified ? ({ role: \"assistant\", content: blocks } as AssistantMessage) : msg\n}\n\n/**\n * Remove system-reminder tags from all Anthropic messages.\n */\nexport function removeAnthropicSystemReminders(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n modifiedCount: number\n} {\n let modifiedCount = 0\n const result = messages.map((msg) => {\n const sanitized = sanitizeMessageParamContent(msg)\n if (sanitized !== msg) modifiedCount++\n return sanitized\n })\n // Return original array reference when nothing changed (avoids unnecessary allocation)\n return { messages: modifiedCount === 0 ? messages : result, modifiedCount }\n}\n\n// ============================================================================\n// System Prompt Sanitization\n// ============================================================================\n\n/**\n * Sanitize Anthropic system prompt (can be string or array of text blocks).\n * Only removes system-reminder tags here.\n *\n * NOTE: Restrictive statement filtering is handled separately by:\n * - system-prompt.ts (via config.yaml overrides)\n * This avoids duplicate processing of the system prompt.\n */\nfunction sanitizeAnthropicSystemPrompt(system: string | Array<{ type: \"text\"; text: string }> | undefined): {\n system: typeof system\n modified: boolean\n} {\n if (!system) {\n return { system, modified: false }\n }\n\n if (typeof system === \"string\") {\n const sanitized = removeSystemReminderTags(system)\n return { system: sanitized, modified: sanitized !== system }\n }\n\n const { blocks, modified } = sanitizeTextBlocksInArray(\n system,\n (b) => b.text,\n (b, text) => ({ ...b, text }),\n )\n return { system: modified ? blocks : system, modified }\n}\n\n// ============================================================================\n// Empty Block Cleanup\n// ============================================================================\n\n/**\n * Final pass: remove any empty/whitespace-only text content blocks from Anthropic messages.\n * This is a safety net that catches empty blocks regardless of how they were produced\n * (original input, sanitization, truncation, etc.).\n * Anthropic API rejects text blocks with empty text: \"text content blocks must be non-empty\"\n */\nfunction filterEmptyAnthropicTextBlocks(messages: Array<MessageParam>): Array<MessageParam> {\n return messages.map((msg) => {\n if (typeof msg.content === \"string\") return msg\n\n // Never modify assistant messages that contain thinking/redacted_thinking blocks.\n // The API validates thinking block signatures against the original response —\n // even removing an adjacent empty text block causes the content array to change,\n // which can trigger \"thinking blocks cannot be modified\" errors after\n // context_management truncation changes which message becomes the \"latest\".\n if (msg.role === \"assistant\" && msg.content.some((b) => b.type === \"thinking\" || b.type === \"redacted_thinking\")) {\n return msg\n }\n\n const filtered = msg.content.filter((block) => {\n if (block.type === \"text\" && \"text\" in block) {\n return block.text.trim() !== \"\"\n }\n return true\n })\n\n if (filtered.length === msg.content.length) return msg\n return { ...msg, content: filtered } as MessageParam\n })\n}\n\n/**\n * Final pass: remove any empty/whitespace-only text blocks from Anthropic system prompt.\n */\nfunction filterEmptySystemTextBlocks(system: MessagesPayload[\"system\"]): MessagesPayload[\"system\"] {\n if (!system || typeof system === \"string\") return system\n return system.filter((block) => block.text.trim() !== \"\")\n}\n\n// ============================================================================\n// Combined Tool Block Processing\n// ============================================================================\n\n/**\n * Parse a potentially stringified JSON input into a proper object.\n * Handles double-serialized strings (e.g., \"\\\"{ ... }\\\"\") by parsing iteratively.\n */\nfunction parseStringifiedInput(input: unknown): Record<string, unknown> {\n if (typeof input !== \"string\") return input as Record<string, unknown>\n try {\n let parsed: unknown = input\n while (typeof parsed === \"string\") {\n parsed = JSON.parse(parsed)\n }\n return (typeof parsed === \"object\" && parsed !== null ? parsed : {}) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\n/**\n * Process all tool-related operations in a single pass:\n * 1. Fix tool_use name casing\n * 2. Filter orphaned tool_result blocks\n * 3. Filter orphaned tool_use blocks\n *\n * This combines what were previously three separate operations (each with their own iterations)\n * into two passes through the messages array for better performance.\n */\nexport function processToolBlocks(\n messages: Array<MessageParam>,\n tools: Array<{ name: string }> | undefined,\n): {\n messages: Array<MessageParam>\n fixedNameCount: number\n orphanedToolUseCount: number\n orphanedToolResultCount: number\n} {\n // Build case-insensitive tool name map if tools are provided\n const nameMap = new Map<string, string>()\n if (tools && tools.length > 0) {\n for (const tool of tools) {\n nameMap.set(tool.name.toLowerCase(), tool.name)\n }\n }\n\n // Pass 1: Collect all tool_use/server_tool_use and tool_result/web_search_tool_result IDs\n const toolUseIds = new Set<string>()\n const toolResultIds = new Set<string>()\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") continue\n\n if (msg.role === \"assistant\") {\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && block.id) {\n toolUseIds.add(block.id)\n }\n // Server tool results can appear in assistant messages (server-side execution).\n // Collect their IDs so the corresponding server_tool_use is not treated as orphaned.\n if (isServerToolResultBlock(block)) {\n toolResultIds.add(block.tool_use_id)\n }\n }\n } else {\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && block.tool_use_id) {\n toolResultIds.add(block.tool_use_id)\n } else if (isServerToolResultBlock(block)) {\n toolResultIds.add(block.tool_use_id)\n }\n }\n }\n }\n\n // Pass 2: Process messages - fix names and filter orphans\n const result: Array<MessageParam> = []\n let fixedNameCount = 0\n let orphanedToolUseCount = 0\n let orphanedToolResultCount = 0\n // Track tool_use IDs that were filtered (orphaned) so their tool_results are also filtered\n const filteredToolUseIds = new Set<string>()\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n if (msg.role === \"assistant\") {\n // Process assistant messages: fix tool names and filter orphaned tool_use/server_tool_use.\n // IMPORTANT: Only create a new message object when content is actually modified.\n // Assistant messages may contain thinking/redacted_thinking blocks with signatures\n // that the API validates. Creating a new object (even with identical content) can\n // trigger \"thinking blocks cannot be modified\" errors after context_management\n // truncation changes which message the API considers the \"latest assistant message\".\n const newContent: Array<ContentBlockParam> = []\n let modified = false\n\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n // Check if orphaned (no corresponding tool_result)\n if (!toolResultIds.has(block.id)) {\n orphanedToolUseCount++\n filteredToolUseIds.add(block.id)\n modified = true\n continue // Skip orphaned tool_use\n }\n\n // Apply fixes: name casing and input deserialization\n const correctName = nameMap.get(block.name.toLowerCase())\n const needsNameFix = correctName !== undefined && correctName !== block.name\n const needsInputFix = typeof block.input === \"string\"\n\n if (needsNameFix || needsInputFix) {\n modified = true\n const fixed = { ...block } as typeof block\n if (needsNameFix) {\n fixedNameCount++\n ;(fixed as { name: string }).name = correctName\n }\n if (needsInputFix) {\n ;(fixed as { input: Record<string, unknown> }).input = parseStringifiedInput(block.input)\n }\n newContent.push(fixed)\n } else {\n newContent.push(block)\n }\n } else if (block.type === \"server_tool_use\") {\n // Check if orphaned (no corresponding web_search_tool_result)\n if (!toolResultIds.has(block.id)) {\n orphanedToolUseCount++\n filteredToolUseIds.add(block.id)\n modified = true\n continue // Skip orphaned server_tool_use\n }\n // Ensure input is an object (clients may send it as a JSON string from stream accumulation)\n if (typeof block.input === \"string\") {\n modified = true\n newContent.push({ ...block, input: parseStringifiedInput(block.input) })\n } else {\n newContent.push(block)\n }\n } else {\n // For server tool results in assistant messages (e.g., tool_search_tool_result),\n // check if their corresponding server_tool_use is still present\n if (\n isServerToolResultBlock(block)\n && (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id))\n ) {\n orphanedToolResultCount++\n modified = true\n continue // Skip orphaned server tool result\n }\n newContent.push(block as ContentBlockParam)\n }\n }\n\n // Skip message if all content was removed\n if (newContent.length === 0) continue\n\n // Preserve original message object when no modifications were made — this is\n // critical for messages with thinking blocks whose signatures must not change\n result.push(modified ? { ...msg, content: newContent } : msg)\n } else {\n // Process user messages: filter orphaned tool_result/web_search_tool_result\n const newContent: Array<ContentBlockParam> = []\n\n for (const block of msg.content) {\n if (block.type === \"tool_result\") {\n // Check if orphaned (no corresponding tool_use) or tool_use was filtered\n if (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id)) {\n orphanedToolResultCount++\n continue // Skip orphaned tool_result\n }\n } else if (isServerToolResultBlock(block)) {\n // Check if orphaned (no corresponding server_tool_use) or server_tool_use was filtered\n if (!toolUseIds.has(block.tool_use_id) || filteredToolUseIds.has(block.tool_use_id)) {\n orphanedToolResultCount++\n continue // Skip orphaned server tool result\n }\n } else if (\n (block as unknown as Record<string, unknown>).type !== \"text\"\n && (block as unknown as Record<string, unknown>).type !== \"image\"\n ) {\n // Unknown block type without tool_use_id (e.g., corrupted server tool result\n // from older history where tool_use_id was lost during conversion).\n // Filter it out to prevent API errors.\n orphanedToolResultCount++\n continue\n }\n newContent.push(block)\n }\n\n // Skip message if all content was removed\n if (newContent.length === 0) continue\n\n result.push({ ...msg, content: newContent })\n }\n }\n\n return {\n messages: result,\n fixedNameCount,\n orphanedToolUseCount,\n orphanedToolResultCount,\n }\n}\n\n// ============================================================================\n// Phase 1: One-time Preprocessing (幂等预处理,路由前执行一次)\n// ============================================================================\n// These operations are idempotent — once processed, re-running produces no\n// further changes. They do NOT need to re-run after auto-truncate retries.\n//\n// Includes:\n// - deduplicateToolCalls: remove repeated tool_use/tool_result pairs\n// - stripReadToolResultTags: strip injected <system-reminder> from Read results\n// ============================================================================\n\n// Dedup Tool Calls\n// ----------------------------------------------------------------------------\n\n/**\n * Remove duplicate tool_use/tool_result pairs, keeping only the last occurrence\n * of each matching combination.\n *\n * Claude Code sometimes enters a \"read loop\" where it repeatedly reads the same\n * files without progressing, causing prompt inflation. This removes redundant\n * earlier calls while preserving the most recent result for each unique call.\n *\n * @param mode - `\"input\"`: match by (tool_name, input).\n * `\"result\"`: match by (tool_name, input, result_content) — only dedup\n * when the result is also identical.\n *\n * After removal, empty messages are dropped and consecutive same-role messages\n * are merged (Anthropic requires strict user/assistant alternation).\n */\nexport function deduplicateToolCalls(\n messages: Array<MessageParam>,\n mode: \"input\" | \"result\" = \"input\",\n): {\n messages: Array<MessageParam>\n dedupedCount: number\n /** Per-tool breakdown of how many duplicate calls were removed */\n dedupedByTool: Record<string, number>\n} {\n // Step 1: Build tool_use name+input map from assistant messages\n // Map: tool_use id → (name, JSON(input)) key (may be extended with result content below)\n const toolUseKeys = new Map<string, string>()\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n const key = `${block.name}:${JSON.stringify(block.input)}`\n toolUseKeys.set(block.id, key)\n }\n }\n }\n\n // Step 1.5 (\"result\" mode): Extend keys with tool_result content.\n // This makes the dedup key (name, input, result) so calls with identical input\n // but different results are NOT considered duplicates.\n if (mode === \"result\") {\n // Collect tool_result content by tool_use_id\n const resultContentById = new Map<string, string>()\n for (const msg of messages) {\n if (msg.role !== \"user\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && toolUseKeys.has(block.tool_use_id)) {\n const resultStr = typeof block.content === \"string\" ? block.content : JSON.stringify(block.content)\n resultContentById.set(block.tool_use_id, resultStr)\n }\n }\n }\n\n // Extend each tool_use key with its result content\n for (const [id, baseKey] of toolUseKeys) {\n const resultContent = resultContentById.get(id)\n if (resultContent !== undefined) {\n toolUseKeys.set(id, `${baseKey}::${resultContent}`)\n }\n }\n }\n\n // Step 2: Reverse scan to find the LAST occurrence of each key (the keeper)\n const keeperIds = new Set<string>()\n const seenKeys = new Set<string>()\n\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (let j = msg.content.length - 1; j >= 0; j--) {\n const block = msg.content[j]\n if (block.type === \"tool_use\") {\n const key = toolUseKeys.get(block.id)\n if (!key) continue\n if (!seenKeys.has(key)) {\n seenKeys.add(key)\n keeperIds.add(block.id)\n }\n }\n }\n }\n\n // Step 2.5: Protect tool_use IDs in messages with thinking/redacted_thinking blocks.\n // The API validates thinking block signatures against the original response —\n // modifying the content array (even removing an adjacent tool_use) can trigger\n // \"thinking blocks cannot be modified\" errors.\n const protectedIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n const hasThinking = msg.content.some((b) => b.type === \"thinking\" || b.type === \"redacted_thinking\")\n if (!hasThinking) continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n protectedIds.add(block.id)\n }\n }\n }\n\n // Step 3: Forward scan — remove non-keeper tool_use blocks, collect their IDs\n const removedIds = new Set<string>()\n\n for (const [id, key] of toolUseKeys) {\n // If this ID has a key that was seen (i.e., has duplicates) but is not the keeper,\n // AND is not in a message with thinking blocks (which must not be modified)\n if (seenKeys.has(key) && !keeperIds.has(id) && !protectedIds.has(id)) {\n removedIds.add(id)\n }\n }\n\n if (removedIds.size === 0) {\n return { messages, dedupedCount: 0, dedupedByTool: {} }\n }\n\n // Build per-tool breakdown from removed IDs\n const dedupedByTool: Record<string, number> = {}\n for (const id of removedIds) {\n const key = toolUseKeys.get(id)\n if (key) {\n const toolName = key.slice(0, key.indexOf(\":\"))\n dedupedByTool[toolName] = (dedupedByTool[toolName] ?? 0) + 1\n }\n }\n\n // Step 4: Filter out removed tool_use and tool_result blocks\n const filtered: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n filtered.push(msg)\n continue\n }\n\n if (msg.role === \"assistant\") {\n const newContent = msg.content.filter((block) => block.type !== \"tool_use\" || !removedIds.has(block.id))\n if (newContent.length > 0) {\n // Preserve original object if nothing removed (thinking block signatures)\n if (newContent.length === msg.content.length) {\n filtered.push(msg)\n } else {\n filtered.push({ ...msg, content: newContent } as MessageParam)\n }\n }\n } else {\n const newContent = msg.content.filter(\n (block) => block.type !== \"tool_result\" || !removedIds.has(block.tool_use_id),\n )\n if (newContent.length > 0) {\n if (newContent.length === msg.content.length) {\n filtered.push(msg)\n } else {\n filtered.push({ ...msg, content: newContent } as MessageParam)\n }\n }\n }\n }\n\n // Step 5: Merge consecutive same-role messages (Anthropic requires alternation)\n const merged: Array<MessageParam> = []\n for (const msg of filtered) {\n const prev = merged.at(-1)\n if (prev && prev.role === msg.role) {\n // Merge content arrays\n const prevContent =\n typeof prev.content === \"string\" ? [{ type: \"text\" as const, text: prev.content }] : prev.content\n const currContent = typeof msg.content === \"string\" ? [{ type: \"text\" as const, text: msg.content }] : msg.content\n merged[merged.length - 1] = {\n ...prev,\n content: [...prevContent, ...currContent],\n } as MessageParam\n } else {\n merged.push(msg)\n }\n }\n\n return { messages: merged, dedupedCount: removedIds.size, dedupedByTool }\n}\n\n// Strip Read Tool Result Tags\n// ----------------------------------------------------------------------------\n\n/**\n * Strip ALL `<system-reminder>` tags from Read tool results.\n *\n * Claude Code injects system-reminder tags (TodoWrite reminders, Plan mode\n * reminders, etc.) into every tool_result. For Read tool results, these tags\n * are pure noise — they repeat every time the same file is read and inflate\n * context by 7-14%.\n *\n * Unlike the main `removeSystemReminderTags` (which only removes tags matching\n * enabled filters), this function removes ALL system-reminder tags regardless\n * of content, since Read results should contain only file content.\n */\nexport function stripReadToolResultTags(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n strippedCount: number\n tagPreviews: Array<string>\n} {\n // Step 1: Collect all Read tool_use IDs\n const readToolUseIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.name === \"Read\") {\n readToolUseIds.add(block.id)\n }\n }\n }\n\n if (readToolUseIds.size === 0) {\n return { messages, strippedCount: 0, tagPreviews: [] }\n }\n\n // Step 2: Strip tags from matching tool_result blocks\n let strippedCount = 0\n const allPreviews: Array<string> = []\n const result = messages.map((msg) => {\n if (msg.role !== \"user\" || typeof msg.content === \"string\") return msg\n\n let modified = false\n const newContent = msg.content.map((block) => {\n if (block.type !== \"tool_result\" || !readToolUseIds.has(block.tool_use_id)) {\n return block\n }\n\n const stripped = stripAllSystemReminderTags(block.content as string | Array<{ type: string; text?: string }>)\n if (stripped.modified) {\n modified = true\n strippedCount += stripped.tagCount\n allPreviews.push(...stripped.tagPreviews)\n return { ...block, content: stripped.content } as ContentBlockParam\n }\n return block\n })\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `modified` is set inside .map() callback\n return modified ? ({ ...msg, content: newContent } as UserMessage) : msg\n })\n\n return { messages: strippedCount > 0 ? result : messages, strippedCount, tagPreviews: allPreviews }\n}\n\n/**\n * Strip ALL system-reminder tags from tool_result content (string or array form).\n * Returns the cleaned content and whether anything was modified.\n */\nfunction stripAllSystemReminderTags(content: string | Array<{ type: string; text?: string }>): {\n content: typeof content\n modified: boolean\n tagCount: number\n tagPreviews: Array<string>\n} {\n if (typeof content === \"string\") {\n return stripAllTagsFromString(content)\n }\n\n let totalTagCount = 0\n const allPreviews: Array<string> = []\n let modified = false\n const result = content.map((block) => {\n if (block.type !== \"text\" || !block.text) return block\n const stripped = stripAllTagsFromString(block.text)\n if (stripped.modified) {\n modified = true\n totalTagCount += stripped.tagCount\n allPreviews.push(...stripped.tagPreviews)\n return { ...block, text: stripped.content }\n }\n return block\n })\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `modified` is set inside .map() callback\n return { content: modified ? result : content, modified, tagCount: totalTagCount, tagPreviews: allPreviews }\n}\n\n/**\n * Remove ALL system-reminder tags from a string, keeping only the main content.\n */\nfunction stripAllTagsFromString(text: string): {\n content: string\n modified: boolean\n tagCount: number\n tagPreviews: Array<string>\n} {\n let tagCount = 0\n const tagPreviews: Array<string> = []\n\n // Extract trailing tags\n const trailing = extractTrailingSystemReminderTags(text)\n tagCount += trailing.tags.length\n for (const tag of trailing.tags) {\n tagPreviews.push(tag.content.slice(0, 80))\n }\n\n // Extract leading tags from the remaining content\n const mainSlice = text.slice(0, trailing.mainContentEnd)\n const leading = extractLeadingSystemReminderTags(mainSlice)\n tagCount += leading.tags.length\n for (const tag of leading.tags) {\n tagPreviews.push(tag.content.slice(0, 80))\n }\n\n if (tagCount === 0) {\n return { content: text, modified: false, tagCount: 0, tagPreviews: [] }\n }\n\n const content = mainSlice.slice(leading.mainContentStart)\n return { content, modified: true, tagCount, tagPreviews }\n}\n\n// Preprocess Orchestrator\n// ----------------------------------------------------------------------------\n\n/**\n * One-time preprocessing of Anthropic messages.\n *\n * Runs idempotent operations that reduce context noise before the request\n * enters the routing / retry pipeline. These do NOT need to re-run after\n * auto-truncate retries because truncation cannot introduce new duplicates\n * or new system-reminder tags.\n */\nexport function preprocessAnthropicMessages(messages: Array<MessageParam>): {\n messages: Array<MessageParam>\n strippedReadTagCount: number\n dedupedToolCallCount: number\n} {\n let result = messages\n let strippedReadTagCount = 0\n let dedupedToolCallCount = 0\n\n // Strip injected <system-reminder> tags from Read tool results\n if (state.truncateReadToolResult) {\n const strip = stripReadToolResultTags(result)\n result = strip.messages\n strippedReadTagCount = strip.strippedCount\n if (strippedReadTagCount > 0) {\n consola.info(\n `[Preprocess] Stripped ${strippedReadTagCount} system-reminder tags from Read results:\\n`\n + strip.tagPreviews.map((p) => ` - \"${p}${p.length >= 80 ? \"…\" : \"\"}\"`).join(\"\\n\"),\n )\n }\n }\n\n // Deduplicate repeated tool_use/tool_result pairs (keep last occurrence)\n if (state.dedupToolCalls) {\n const dedup = deduplicateToolCalls(result, state.dedupToolCalls)\n result = dedup.messages\n dedupedToolCallCount = dedup.dedupedCount\n if (dedupedToolCallCount > 0) {\n const breakdown = Object.entries(dedup.dedupedByTool)\n .map(([name, count]) => `${name}×${count}`)\n .join(\", \")\n consola.info(`[Preprocess] Deduped ${dedupedToolCallCount} tool calls [${state.dedupToolCalls}] (${breakdown})`)\n }\n }\n\n return { messages: result, strippedReadTagCount, dedupedToolCallCount }\n}\n\n// ============================================================================\n// Phase 2: Repeatable Sanitization (可重复清洗,truncate 后需重新执行)\n// ============================================================================\n// These operations must re-run after every auto-truncate retry because\n// truncation can break tool_use/tool_result pairing and produce empty blocks.\n//\n// Includes:\n// - sanitizeAnthropicSystemPrompt: clean system prompt system-reminders\n// - removeAnthropicSystemReminders: clean message system-reminders\n// - processToolBlocks: fix tool names + filter orphaned tool blocks\n// - filterEmptyAnthropicTextBlocks: safety net for empty text blocks\n// ============================================================================\n\n/**\n * Count total content blocks in Anthropic messages.\n */\nfunction countAnthropicContentBlocks(messages: Array<MessageParam>): number {\n let count = 0\n for (const msg of messages) {\n count += typeof msg.content === \"string\" ? 1 : msg.content.length\n }\n return count\n}\n\nexport interface SanitizationStats {\n orphanedToolUseCount: number\n orphanedToolResultCount: number\n fixedNameCount: number\n emptyTextBlocksRemoved: number\n systemReminderRemovals: number\n totalBlocksRemoved: number\n}\n\n/**\n * Sanitize Anthropic messages by filtering orphaned tool blocks and system reminders.\n *\n * Returns both convenience totals (removedCount, systemReminderRemovals) for backward\n * compatibility, and structured stats for callers that need detail.\n */\nexport function sanitizeAnthropicMessages(\n payload: MessagesPayload,\n): SanitizeResult<MessagesPayload> & { stats: SanitizationStats } {\n let messages = payload.messages\n const originalBlocks = countAnthropicContentBlocks(messages)\n\n // Remove system-reminder tags from system prompt\n const { system: sanitizedSystem } = sanitizeAnthropicSystemPrompt(payload.system)\n\n // Remove system-reminder tags from all messages\n const reminderResult = removeAnthropicSystemReminders(messages)\n messages = reminderResult.messages\n const systemReminderRemovals = reminderResult.modifiedCount\n\n // Process all tool-related operations in a single pass:\n // - Fix tool_use name casing (e.g., \"bash\" → \"Bash\")\n // - Filter orphaned tool_result blocks\n // - Filter orphaned tool_use blocks\n const toolResult = processToolBlocks(messages, payload.tools)\n messages = toolResult.messages\n\n if (toolResult.fixedNameCount > 0) {\n consola.debug(`[Sanitizer:Anthropic] Fixed ${toolResult.fixedNameCount} tool name casing mismatches`)\n }\n\n // Final safety net: remove any remaining empty/whitespace-only text blocks\n // This catches empty blocks from any source (input, sanitization, truncation)\n messages = filterEmptyAnthropicTextBlocks(messages)\n const finalSystem = filterEmptySystemTextBlocks(sanitizedSystem)\n\n const newBlocks = countAnthropicContentBlocks(messages)\n const totalBlocksRemoved = originalBlocks - newBlocks\n const emptyTextBlocksRemoved =\n totalBlocksRemoved - toolResult.orphanedToolUseCount - toolResult.orphanedToolResultCount\n\n if (totalBlocksRemoved > 0 && (toolResult.orphanedToolUseCount > 0 || toolResult.orphanedToolResultCount > 0)) {\n const parts: Array<string> = []\n if (toolResult.orphanedToolUseCount > 0) parts.push(`${toolResult.orphanedToolUseCount} orphaned tool_use`)\n if (toolResult.orphanedToolResultCount > 0) parts.push(`${toolResult.orphanedToolResultCount} orphaned tool_result`)\n if (emptyTextBlocksRemoved > 0) parts.push(`${emptyTextBlocksRemoved} empty text blocks`)\n consola.info(`[Sanitizer:Anthropic] Removed ${totalBlocksRemoved} content blocks (${parts.join(\", \")})`)\n }\n\n return {\n payload: { ...payload, system: finalSystem, messages },\n removedCount: totalBlocksRemoved,\n systemReminderRemovals,\n stats: {\n orphanedToolUseCount: toolResult.orphanedToolUseCount,\n orphanedToolResultCount: toolResult.orphanedToolResultCount,\n fixedNameCount: toolResult.fixedNameCount,\n emptyTextBlocksRemoved: Math.max(0, emptyTextBlocksRemoved),\n systemReminderRemovals,\n totalBlocksRemoved,\n },\n }\n}\n","/**\n * Auto-truncate module for Anthropic-style messages.\n *\n * This module handles automatic truncation of Anthropic message format\n * when it exceeds token limits.\n *\n * Key features:\n * - Binary search for optimal truncation point\n * - Token limit enforcement with learned calibration\n * - Preserves system messages\n * - Filters orphaned tool_result and tool_use messages\n * - Smart compression of old tool_result content (e.g., Read tool results)\n */\n\nimport consola from \"consola\"\n\nimport type { Model } from \"~/lib/models/client\"\nimport type { ContentBlock, MessageParam, MessagesPayload, ContentBlockParam } from \"~/types/api/anthropic\"\n\nimport { countTextTokens } from \"~/lib/models/tokenizer\"\nimport { state } from \"~/lib/state\"\nimport { bytesToKB } from \"~/lib/utils\"\nimport { isServerToolResultBlock, isToolResultBlock } from \"~/types/api/anthropic\"\n\nimport type { AutoTruncateConfig } from \"../auto-truncate\"\n\nimport {\n DEFAULT_AUTO_TRUNCATE_CONFIG,\n LARGE_TOOL_RESULT_THRESHOLD,\n calibrate,\n compressCompactedReadResult,\n compressToolResultContent,\n computeSafetyMargin,\n getLearnedLimits,\n} from \"../auto-truncate\"\nimport { processToolBlocks } from \"./sanitize\"\n\n// ============================================================================\n// Orphan Filtering Utilities (specific to auto-truncate)\n// ============================================================================\n\n/**\n * Get tool_use IDs from an Anthropic assistant message.\n */\nexport function getAnthropicToolUseIds(msg: MessageParam): Array<string> {\n if (msg.role !== \"assistant\") return []\n if (typeof msg.content === \"string\") return []\n\n const ids: Array<string> = []\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && block.id) {\n ids.push(block.id)\n }\n }\n return ids\n}\n\n/**\n * Get tool_result IDs from an Anthropic message.\n * Checks both user messages (regular tool_result) and assistant messages\n * (server tool results like tool_search_tool_result which appear inline).\n */\nexport function getAnthropicToolResultIds(msg: MessageParam): Array<string> {\n if (typeof msg.content === \"string\") return []\n\n const ids: Array<string> = []\n for (const block of msg.content) {\n if (isToolResultBlock(block)) {\n ids.push(block.tool_use_id)\n } else if (isServerToolResultBlock(block)) {\n ids.push(block.tool_use_id)\n }\n }\n return ids\n}\n\n/**\n * Ensure Anthropic messages start with a user message.\n * Drops leading non-user messages (e.g., orphaned assistant messages after truncation).\n */\nexport function ensureAnthropicStartsWithUser(messages: Array<MessageParam>): Array<MessageParam> {\n let startIndex = 0\n while (startIndex < messages.length && messages[startIndex].role !== \"user\") {\n startIndex++\n }\n\n if (startIndex > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Skipped ${startIndex} leading non-user messages`)\n }\n\n return messages.slice(startIndex)\n}\n\n/**\n * Filter orphaned tool_result blocks (no matching tool_use).\n */\nexport function filterAnthropicOrphanedToolResults(messages: Array<MessageParam>): Array<MessageParam> {\n const toolUseIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolUseIds(msg)) {\n toolUseIds.add(id)\n }\n }\n\n let removed = 0\n const result: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n const filtered = msg.content.filter((block) => {\n if (isToolResultBlock(block) && !toolUseIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n if (isServerToolResultBlock(block) && !toolUseIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n return true\n })\n\n if (filtered.length === 0) continue\n if (filtered.length === msg.content.length) {\n result.push(msg)\n } else {\n result.push({ ...msg, content: filtered } as MessageParam)\n }\n }\n\n if (removed > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Filtered ${removed} orphaned tool results`)\n }\n\n return result\n}\n\n/**\n * Filter orphaned tool_use blocks (no matching tool_result).\n */\nexport function filterAnthropicOrphanedToolUse(messages: Array<MessageParam>): Array<MessageParam> {\n const toolResultIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolResultIds(msg)) {\n toolResultIds.add(id)\n }\n }\n\n const toolUseIds = new Set<string>()\n for (const msg of messages) {\n for (const id of getAnthropicToolUseIds(msg)) {\n toolUseIds.add(id)\n }\n }\n\n let removed = 0\n const result: Array<MessageParam> = []\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") {\n result.push(msg)\n continue\n }\n\n const survivingIds = new Set<string>()\n for (const block of msg.content) {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && toolResultIds.has(block.id)) {\n survivingIds.add(block.id)\n }\n }\n\n const filtered = msg.content.filter((block) => {\n if ((block.type === \"tool_use\" || block.type === \"server_tool_use\") && !toolResultIds.has(block.id)) {\n removed++\n return false\n }\n if (isServerToolResultBlock(block) && !survivingIds.has(block.tool_use_id)) {\n removed++\n return false\n }\n return true\n })\n\n if (filtered.length === 0) continue\n if (filtered.length === msg.content.length) {\n result.push(msg)\n } else {\n result.push({ ...msg, content: filtered } as MessageParam)\n }\n }\n\n if (removed > 0) {\n consola.debug(`[AutoTruncate:Anthropic] Filtered ${removed} orphaned tool blocks`)\n }\n\n return result\n}\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\nexport interface AnthropicAutoTruncateResult {\n payload: MessagesPayload\n wasTruncated: boolean\n originalTokens: number\n compactedTokens: number\n removedMessageCount: number\n /** Processing time in milliseconds */\n processingTimeMs: number\n}\n\n// ============================================================================\n// Token Counting (using official Anthropic tokenizer)\n// ============================================================================\n\n/**\n * Convert Anthropic message content to text for token counting.\n * @param options.includeThinking Whether to include thinking blocks (default: true)\n */\nexport function contentToText(content: MessageParam[\"content\"], options?: { includeThinking?: boolean }): string {\n if (typeof content === \"string\") {\n return content\n }\n\n const includeThinking = options?.includeThinking ?? true\n const parts: Array<string> = []\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n parts.push(block.text)\n break\n }\n case \"tool_use\": {\n parts.push(`[tool_use: ${block.name}]`, JSON.stringify(block.input))\n break\n }\n case \"tool_result\": {\n if (typeof block.content === \"string\") {\n parts.push(block.content)\n } else if (Array.isArray(block.content)) {\n for (const inner of block.content) {\n if (inner.type === \"text\") {\n parts.push(inner.text)\n }\n // Images are not counted as text tokens\n }\n }\n break\n }\n case \"thinking\": {\n if (includeThinking) {\n parts.push(block.thinking)\n }\n break\n }\n case \"redacted_thinking\": {\n // Redacted thinking blocks have opaque data, not text — skip for token counting\n break\n }\n case \"server_tool_use\": {\n parts.push(`[server_tool_use: ${block.name}]`, JSON.stringify(block.input))\n break\n }\n case \"web_search_tool_result\": {\n parts.push(`[web_search_tool_result]`)\n break\n }\n default: {\n // Handle generic server tool results (e.g., tool_search_tool_result)\n // Cast to Record to bypass type narrowing — API may return unknown block types\n const genericBlock = block as unknown as Record<string, unknown>\n if (\"tool_use_id\" in genericBlock && genericBlock.type !== \"image\") {\n parts.push(`[${String(genericBlock.type)}]`)\n break\n }\n // Images and other binary content are not counted as text tokens\n break\n }\n }\n }\n\n return parts.join(\"\\n\")\n}\n\n/**\n * Estimate tokens for a message (fast, synchronous).\n * Uses ~4 chars per token approximation for internal calculations.\n * The final result is verified with the accurate tokenizer.\n */\nfunction estimateMessageTokens(msg: MessageParam): number {\n const text = contentToText(msg.content)\n // ~4 chars per token + message framing overhead\n return Math.ceil(text.length / 4) + 4\n}\n\n/**\n * Count tokens for an Anthropic message using the model's tokenizer.\n */\nexport async function countMessageTokens(\n msg: MessageParam,\n model: Model,\n options?: { includeThinking?: boolean },\n): Promise<number> {\n const text = contentToText(msg.content, options)\n // Add message framing overhead (role + structure)\n return (await countTextTokens(text, model)) + 4\n}\n\n/**\n * Count tokens for system prompt.\n */\nexport async function countSystemTokens(system: MessagesPayload[\"system\"], model: Model): Promise<number> {\n if (!system) return 0\n if (typeof system === \"string\") {\n return (await countTextTokens(system, model)) + 4\n }\n const text = system.map((block) => block.text).join(\"\\n\")\n return (await countTextTokens(text, model)) + 4\n}\n\n/**\n * Count tokens for just the messages array.\n * Used internally to avoid re-counting system/tools tokens that don't change.\n */\nexport async function countMessagesTokens(messages: Array<MessageParam>, model: Model): Promise<number> {\n let total = 0\n for (const msg of messages) {\n total += await countMessageTokens(msg, model)\n }\n return total\n}\n\n/**\n * Count tokens for system + tools (the parts that don't change during truncation).\n * Returns the combined fixed overhead token count.\n */\nexport async function countFixedTokens(payload: MessagesPayload, model: Model): Promise<number> {\n let total = await countSystemTokens(payload.system, model)\n if (payload.tools) {\n const toolsText = JSON.stringify(payload.tools)\n total += await countTextTokens(toolsText, model)\n }\n return total\n}\n\n/**\n * Count total tokens for the payload using the model's tokenizer.\n * Includes thinking blocks — used by auto-truncate decisions.\n */\nexport async function countTotalTokens(payload: MessagesPayload, model: Model): Promise<number> {\n const fixed = await countFixedTokens(payload, model)\n const msgs = await countMessagesTokens(payload.messages, model)\n return fixed + msgs\n}\n\n/**\n * Count total input tokens for the payload, excluding thinking blocks\n * from assistant messages per Anthropic token counting spec.\n *\n * Per Anthropic docs: \"Thinking blocks from previous assistant turns are\n * ignored (don't count toward input tokens).\"\n *\n * This function is designed for the /v1/messages/count_tokens endpoint.\n * For auto-truncate decisions, use countTotalTokens instead (which includes\n * thinking blocks since they affect actual payload size).\n */\nexport async function countTotalInputTokens(payload: MessagesPayload, model: Model): Promise<number> {\n let total = await countSystemTokens(payload.system, model)\n for (const msg of payload.messages) {\n // Exclude thinking blocks from assistant messages\n const skipThinking = msg.role === \"assistant\"\n total += await countMessageTokens(msg, model, {\n includeThinking: !skipThinking,\n })\n }\n // Add overhead for tools\n if (payload.tools) {\n const toolsText = JSON.stringify(payload.tools)\n total += await countTextTokens(toolsText, model)\n }\n return total\n}\n\n// ============================================================================\n// Thinking Block Stripping\n// ============================================================================\n\n/**\n * Strip thinking/redacted_thinking blocks from old assistant messages.\n *\n * Per Anthropic docs, thinking blocks from previous turns don't count toward\n * input tokens (for billing), but they DO consume space in the request body.\n * Stripping them from older messages frees up context for actual content.\n *\n * @param messages - The message array to process\n * @param preserveRecentCount - Number of recent messages to preserve (keep thinking in recent messages)\n * @returns Object with stripped messages and count of removed blocks\n */\nfunction stripThinkingBlocks(\n messages: Array<MessageParam>,\n preserveRecentCount: number,\n): { messages: Array<MessageParam>; strippedCount: number } {\n const n = messages.length\n const stripBefore = Math.max(0, n - preserveRecentCount)\n let strippedCount = 0\n\n const result = messages.map((msg, i) => {\n if (i >= stripBefore || msg.role !== \"assistant\" || !Array.isArray(msg.content)) {\n return msg\n }\n\n const hasThinking = msg.content.some((block) => block.type === \"thinking\" || block.type === \"redacted_thinking\")\n if (!hasThinking) return msg\n\n const filtered = msg.content.filter((block): block is ContentBlock => {\n if (block.type === \"thinking\" || block.type === \"redacted_thinking\") {\n strippedCount++\n return false\n }\n return true\n })\n\n // If all content was thinking blocks, replace with empty text to preserve message structure\n if (filtered.length === 0) {\n return { ...msg, content: [{ type: \"text\" as const, text: \"\" }] }\n }\n\n return { ...msg, content: filtered }\n })\n\n return { messages: result, strippedCount }\n}\n\n// ============================================================================\n// Smart Tool Result Compression\n// ============================================================================\n\n/**\n * Compress a tool_result block in an Anthropic message.\n */\nfunction compressToolResultBlock(block: ContentBlockParam): ContentBlockParam {\n if (\n block.type === \"tool_result\"\n && typeof block.content === \"string\"\n && block.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n return {\n ...block,\n content: compressToolResultContent(block.content),\n }\n }\n return block\n}\n\n/**\n * Smart compression strategy:\n * 1. Calculate tokens from the end until reaching preservePercent of limit\n * 2. Messages before that threshold get their tool_results compressed\n * 3. Returns compressed messages and stats\n *\n * @param preservePercent - Percentage of context to preserve uncompressed (0.0-1.0)\n */\nfunction smartCompressToolResults(\n messages: Array<MessageParam>,\n tokenLimit: number,\n preservePercent: number,\n): {\n messages: Array<MessageParam>\n compressedCount: number\n compressThresholdIndex: number\n} {\n // Calculate cumulative token size from the end\n const n = messages.length\n const cumTokens: Array<number> = Array.from({ length: n + 1 }, () => 0)\n\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n\n // Find the threshold index where we've used the preserve percentage of the limit\n const preserveTokenLimit = Math.floor(tokenLimit * preservePercent)\n\n let thresholdIndex = n\n for (let i = n - 1; i >= 0; i--) {\n if (cumTokens[i] > preserveTokenLimit) {\n thresholdIndex = i + 1\n break\n }\n thresholdIndex = i\n }\n\n // If threshold is at the end, nothing to compress\n if (thresholdIndex >= n) {\n return { messages, compressedCount: 0, compressThresholdIndex: n }\n }\n\n // Compress tool_results and compacted text blocks in messages before threshold\n const result: Array<MessageParam> = []\n let compressedCount = 0\n\n for (const [i, msg] of messages.entries()) {\n if (i < thresholdIndex && msg.role === \"user\" && Array.isArray(msg.content)) {\n // Directly attempt compression on each block, avoiding a separate `some` pre-check\n let hadCompression = false\n const compressedContent = msg.content.map((block) => {\n if (\n block.type === \"tool_result\"\n && typeof block.content === \"string\"\n && block.content.length > LARGE_TOOL_RESULT_THRESHOLD\n ) {\n compressedCount++\n hadCompression = true\n return compressToolResultBlock(block)\n }\n if (block.type === \"text\" && block.text.length > LARGE_TOOL_RESULT_THRESHOLD) {\n const compressed = compressCompactedReadResult(block.text)\n if (compressed) {\n compressedCount++\n hadCompression = true\n return { ...block, text: compressed }\n }\n }\n return block\n })\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hadCompression set in synchronous .map() callback\n if (hadCompression) {\n result.push({ ...msg, content: compressedContent })\n continue\n }\n }\n result.push(msg)\n }\n\n return {\n messages: result,\n compressedCount,\n compressThresholdIndex: thresholdIndex,\n }\n}\n\n// ============================================================================\n// Limit Calculation\n// ============================================================================\n\n/**\n * Calculate the effective token limit for auto-truncate.\n * Uses explicit target if provided, otherwise learned limits with calibration,\n * otherwise model capabilities with safety margin.\n *\n * Returns undefined when no limit information is available — the caller\n * should skip truncation rather than guess with a hardcoded default.\n */\nfunction calculateTokenLimit(model: Model, config: AutoTruncateConfig): number | undefined {\n // Use explicit target if provided (reactive retry — caller already applied margin)\n if (config.targetTokenLimit !== undefined) {\n return config.targetTokenLimit\n }\n\n // Check for learned limits (adjusted based on previous errors)\n const learned = getLearnedLimits(model.id)\n if (learned) {\n const margin = computeSafetyMargin(learned.sampleCount)\n return Math.floor(learned.tokenLimit * (1 - margin))\n }\n\n // Use model capabilities with static safety margin\n const rawTokenLimit =\n model.capabilities?.limits?.max_context_window_tokens\n ?? model.capabilities?.limits?.max_prompt_tokens\n\n if (rawTokenLimit === undefined) return undefined\n\n return Math.floor(rawTokenLimit * (1 - config.safetyMarginPercent / 100))\n}\n\n// ============================================================================\n// Binary Search Algorithm\n// ============================================================================\n\ninterface PreserveSearchParams {\n messages: Array<MessageParam>\n systemTokens: number\n tokenLimit: number\n}\n\nfunction findOptimalPreserveIndex(params: PreserveSearchParams): number {\n const { messages, systemTokens, tokenLimit } = params\n\n if (messages.length === 0) return 0\n\n // Account for truncation marker\n const markerTokens = 50\n\n const availableTokens = tokenLimit - systemTokens - markerTokens\n\n if (availableTokens <= 0) {\n return messages.length\n }\n\n // Pre-calculate cumulative token sums from the end\n const n = messages.length\n const cumTokens: Array<number> = Array.from({ length: n + 1 }, () => 0)\n\n for (let i = n - 1; i >= 0; i--) {\n cumTokens[i] = cumTokens[i + 1] + estimateMessageTokens(messages[i])\n }\n\n // Binary search for the smallest index where token limit is satisfied\n let left = 0\n let right = n\n\n while (left < right) {\n const mid = (left + right) >>> 1\n if (cumTokens[mid] <= availableTokens) {\n right = mid\n } else {\n left = mid + 1\n }\n }\n\n return left\n}\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Generate a summary of removed messages for context.\n * Extracts key information like tool calls and topics.\n */\nfunction generateRemovedMessagesSummary(removedMessages: Array<MessageParam>): string {\n const toolCalls: Array<string> = []\n let userMessageCount = 0\n let assistantMessageCount = 0\n\n for (const msg of removedMessages) {\n if (msg.role === \"user\") {\n userMessageCount++\n } else {\n assistantMessageCount++\n }\n\n // Extract tool use names\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n toolCalls.push(block.name)\n }\n if (block.type === \"server_tool_use\") {\n toolCalls.push(block.name)\n }\n }\n }\n }\n\n // Build summary parts\n const parts: Array<string> = []\n\n // Message breakdown\n if (userMessageCount > 0 || assistantMessageCount > 0) {\n const breakdown = []\n if (userMessageCount > 0) breakdown.push(`${userMessageCount} user`)\n if (assistantMessageCount > 0) breakdown.push(`${assistantMessageCount} assistant`)\n parts.push(`Messages: ${breakdown.join(\", \")}`)\n }\n\n // Tool calls\n if (toolCalls.length > 0) {\n // Deduplicate and limit\n const uniqueTools = [...new Set(toolCalls)]\n const displayTools =\n uniqueTools.length > 5 ? [...uniqueTools.slice(0, 5), `+${uniqueTools.length - 5} more`] : uniqueTools\n parts.push(`Tools used: ${displayTools.join(\", \")}`)\n }\n\n return parts.join(\". \")\n}\n\n/**\n * Add a compression notice to the system prompt.\n * Informs the model that some tool_result content has been compressed.\n */\nfunction addCompressionNotice(payload: MessagesPayload, compressedCount: number): MessagesPayload {\n const notice =\n `[CONTEXT NOTE]\\n`\n + `${compressedCount} large tool_result blocks have been compressed to reduce context size.\\n`\n + `The compressed results show the beginning and end of the content with an omission marker.\\n`\n + `If you need the full content, you can re-read the file or re-run the tool.\\n`\n + `[END NOTE]\\n\\n`\n\n let newSystem: MessagesPayload[\"system\"]\n if (typeof payload.system === \"string\") {\n newSystem = notice + payload.system\n } else if (Array.isArray(payload.system)) {\n newSystem = [{ type: \"text\" as const, text: notice }, ...payload.system]\n } else {\n newSystem = notice\n }\n\n return { ...payload, system: newSystem }\n}\n\n/**\n * Create truncation context to prepend to system prompt.\n */\nfunction createTruncationSystemContext(removedCount: number, compressedCount: number, summary: string): string {\n let context = `[CONVERSATION CONTEXT]\\n`\n\n if (removedCount > 0) {\n context += `${removedCount} earlier messages have been removed due to context window limits.\\n`\n }\n\n if (compressedCount > 0) {\n context += `${compressedCount} large tool_result blocks have been compressed.\\n`\n }\n\n if (summary) {\n context += `Summary of removed content: ${summary}\\n`\n }\n\n context +=\n `If you need earlier context, ask the user or check available tools for conversation history access.\\n`\n + `[END CONTEXT]\\n\\n`\n\n return context\n}\n\n/**\n * Create a truncation marker message (fallback when no system prompt).\n */\nfunction createTruncationMarker(removedCount: number, compressedCount: number, summary: string): MessageParam {\n const parts: Array<string> = []\n\n if (removedCount > 0) {\n parts.push(`${removedCount} earlier messages removed`)\n }\n if (compressedCount > 0) {\n parts.push(`${compressedCount} tool_result blocks compressed`)\n }\n\n let content = `[CONTEXT MODIFIED: ${parts.join(\", \")} to fit context limits]`\n if (summary) {\n content += `\\n[Summary: ${summary}]`\n }\n return {\n role: \"user\",\n content,\n }\n}\n\n/**\n * Perform auto-truncation on an Anthropic payload that exceeds limits.\n */\nexport async function autoTruncateAnthropic(\n payload: MessagesPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<AnthropicAutoTruncateResult> {\n const startTime = performance.now()\n\n // Helper to build result with timing\n const buildResult = (result: Omit<AnthropicAutoTruncateResult, \"processingTimeMs\">): AnthropicAutoTruncateResult => ({\n ...result,\n processingTimeMs: Math.round(performance.now() - startTime),\n })\n\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // No limit information available — skip truncation and let the server decide\n if (tokenLimit === undefined) {\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens: 0,\n compactedTokens: 0,\n removedMessageCount: 0,\n })\n }\n\n // Compute fixed overhead tokens (system + tools) once — these don't change during truncation\n const fixedTokens = await countFixedTokens(payload, model)\n\n // Measure original size\n const originalMsgTokens = await countMessagesTokens(payload.messages, model)\n const originalTokens = fixedTokens + originalMsgTokens\n\n // Check if compaction is needed\n if (originalTokens <= tokenLimit) {\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Step 1: Strip thinking blocks from old assistant messages\n // These don't count as input tokens per Anthropic docs, but they consume request body space.\n // Preserve thinking in the last 4 messages (2 exchanges) for context continuity.\n const { messages: thinkingStripped, strippedCount: thinkingStrippedCount } = stripThinkingBlocks(payload.messages, 4)\n let workingMessages = thinkingStripped\n\n // Check if stripping alone was enough\n if (thinkingStrippedCount > 0) {\n const strippedMsgTokens = await countMessagesTokens(workingMessages, model)\n const strippedTokens = fixedTokens + strippedMsgTokens\n\n if (strippedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${strippedTokens} `\n + `(stripped ${thinkingStrippedCount} thinking blocks) [${elapsedMs}ms]`,\n )\n\n return buildResult({\n payload: { ...payload, messages: workingMessages },\n wasTruncated: true,\n originalTokens,\n compactedTokens: strippedTokens,\n removedMessageCount: 0,\n })\n }\n }\n\n // Step 2: Smart compress old tool_results (if enabled)\n // Compress tool_results in messages that are beyond the preserve threshold\n let compressedCount = 0\n\n if (state.compressToolResultsBeforeTruncate) {\n const compressionResult = smartCompressToolResults(workingMessages, tokenLimit, cfg.preserveRecentPercent)\n workingMessages = compressionResult.messages\n compressedCount = compressionResult.compressedCount\n\n // Check if compression alone was enough\n const compressedMsgTokens = await countMessagesTokens(workingMessages, model)\n const compressedTokens = fixedTokens + compressedMsgTokens\n\n if (compressedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${compressedTokens} `\n + `(compressed ${compressedCount} tool_results) [${elapsedMs}ms]`,\n )\n\n // Add compression notice to system prompt\n const compressedPayload = { ...payload, messages: workingMessages }\n const noticePayload = addCompressionNotice(compressedPayload, compressedCount)\n\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 4 // ~150 chars in notice text\n return buildResult({\n payload: noticePayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: compressedTokens + noticeTokenOverhead,\n removedMessageCount: 0,\n })\n }\n\n // Step 2.5: Compress ALL tool_results (including recent ones)\n // If compressing only old tool_results wasn't enough, try compressing all of them\n // before resorting to message removal\n const allCompression = smartCompressToolResults(\n workingMessages,\n tokenLimit,\n 0.0, // preservePercent=0 means compress all messages\n )\n if (allCompression.compressedCount > 0) {\n workingMessages = allCompression.messages\n compressedCount += allCompression.compressedCount\n\n // Check if compressing all was enough\n const allCompressedMsgTokens = await countMessagesTokens(workingMessages, model)\n const allCompressedTokens = fixedTokens + allCompressedMsgTokens\n\n if (allCompressedTokens <= tokenLimit) {\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${allCompressedTokens} `\n + `(compressed ${compressedCount} tool_results, including recent) [${elapsedMs}ms]`,\n )\n\n const allCompressedPayload = { ...payload, messages: workingMessages }\n const noticePayload = addCompressionNotice(allCompressedPayload, compressedCount)\n\n // Estimate notice token overhead instead of full recount\n const noticeTokenOverhead = Math.ceil(150 / 4) + 4\n return buildResult({\n payload: noticePayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: allCompressedTokens + noticeTokenOverhead,\n removedMessageCount: 0,\n })\n }\n }\n }\n\n // Step 3: Compression wasn't enough (or disabled), proceed with message removal\n // Use working messages (compressed if enabled, original otherwise)\n\n // Calculate system tokens for the binary search\n const systemTokens = await countSystemTokens(payload.system, model)\n\n // Find optimal preserve index on working messages\n const preserveIndex = findOptimalPreserveIndex({\n messages: workingMessages,\n systemTokens,\n tokenLimit,\n })\n\n // Check if we can compact\n if (preserveIndex >= workingMessages.length) {\n consola.warn(\"[AutoTruncate:Anthropic] Would need to remove all messages\")\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Build preserved messages from working (compressed) messages\n let preserved = workingMessages.slice(preserveIndex)\n\n // Clean up the message list - filter orphaned tool blocks in two passes\n // (one pass to collect IDs, one to filter), then ensure starts with user\n let { messages: cleaned } = processToolBlocks(preserved, undefined)\n cleaned = ensureAnthropicStartsWithUser(cleaned)\n // Run again after ensuring starts with user, in case skipping leading messages created new orphans\n ;({ messages: cleaned } = processToolBlocks(cleaned, undefined))\n preserved = cleaned\n\n if (preserved.length === 0) {\n consola.warn(\"[AutoTruncate:Anthropic] All messages filtered out after cleanup\")\n return buildResult({\n payload,\n wasTruncated: false,\n originalTokens,\n compactedTokens: originalTokens,\n removedMessageCount: 0,\n })\n }\n\n // Calculate removed messages and generate summary\n // Use original messages for summary (uncompressed content is more informative)\n const removedMessages = payload.messages.slice(0, preserveIndex)\n const removedCount = workingMessages.length - preserved.length\n const summary = generateRemovedMessagesSummary(removedMessages)\n\n // Build new payload with truncation context\n let newSystem = payload.system\n let newMessages = preserved\n\n // Prefer adding context to system prompt (cleaner for the model)\n if (payload.system !== undefined) {\n const truncationContext = createTruncationSystemContext(removedCount, compressedCount, summary)\n if (typeof payload.system === \"string\") {\n newSystem = truncationContext + payload.system\n } else if (Array.isArray(payload.system)) {\n // Prepend as first text block\n newSystem = [{ type: \"text\" as const, text: truncationContext }, ...payload.system]\n }\n } else {\n // No system prompt, use marker message\n const marker = createTruncationMarker(removedCount, compressedCount, summary)\n newMessages = [marker, ...preserved]\n }\n\n const newPayload: MessagesPayload = {\n ...payload,\n system: newSystem,\n messages: newMessages,\n }\n\n // Verify the result — only count messages (reuse cached fixed tokens)\n const newBytes = JSON.stringify(newPayload).length\n const newMsgTokens = await countMessagesTokens(newMessages, model)\n // Re-count system tokens if system was modified (truncation context added)\n const newSystemTokens = newSystem !== payload.system ? await countSystemTokens(newSystem, model) : systemTokens\n const toolsTokens = fixedTokens - (await countSystemTokens(payload.system, model))\n const newTokens = newSystemTokens + toolsTokens + newMsgTokens\n\n // Log single line summary\n const actions: Array<string> = []\n if (removedCount > 0) actions.push(`removed ${removedCount} msgs`)\n if (thinkingStrippedCount > 0) actions.push(`stripped ${thinkingStrippedCount} thinking blocks`)\n if (compressedCount > 0) actions.push(`compressed ${compressedCount} tool_results`)\n const actionInfo = actions.length > 0 ? ` (${actions.join(\", \")})` : \"\"\n\n const elapsedMs = Math.round(performance.now() - startTime)\n consola.info(\n `[AutoTruncate:Anthropic] tokens: ${originalTokens}→${newTokens}, `\n + `${bytesToKB(newBytes)}KB${actionInfo} [${elapsedMs}ms]`,\n )\n\n // Warn if still over token limit\n if (newTokens > tokenLimit) {\n consola.warn(`[AutoTruncate:Anthropic] Result still over token limit (${newTokens} > ${tokenLimit})`)\n }\n\n return buildResult({\n payload: newPayload,\n wasTruncated: true,\n originalTokens,\n compactedTokens: newTokens,\n removedMessageCount: removedCount,\n })\n}\n\n/**\n * Create a marker to prepend to responses indicating auto-truncation occurred.\n */\nexport function createTruncationResponseMarkerAnthropic(result: AnthropicAutoTruncateResult): string {\n if (!result.wasTruncated) return \"\"\n\n const reduction = result.originalTokens - result.compactedTokens\n const percentage = Math.round((reduction / result.originalTokens) * 100)\n\n return (\n `\\n\\n---\\n[Auto-truncated: ${result.removedMessageCount} messages removed, `\n + `${result.originalTokens} → ${result.compactedTokens} tokens (${percentage}% reduction)]`\n )\n}\n\n/**\n * Check if payload needs compaction based on learned model limits.\n * Returns early with `needed: false` when no limits are known for the model.\n */\nexport async function checkNeedsCompactionAnthropic(\n payload: MessagesPayload,\n model: Model,\n config: Partial<AutoTruncateConfig> = {},\n): Promise<{\n needed: boolean\n currentTokens: number\n tokenLimit: number\n reason?: \"tokens\"\n}> {\n const cfg = { ...DEFAULT_AUTO_TRUNCATE_CONFIG, ...config }\n\n // If no learned limits and no explicit target, skip pre-check\n // (we don't know the real limit yet — let the server tell us)\n const learned = getLearnedLimits(model.id)\n if (!learned && cfg.targetTokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const tokenLimit = calculateTokenLimit(model, cfg)\n\n // Defensive: should not reach here without a resolvable limit (guarded by early return above),\n // but satisfy the type system\n if (tokenLimit === undefined) {\n return {\n needed: false,\n currentTokens: 0,\n tokenLimit: 0,\n }\n }\n\n const rawTokens = await countTotalTokens(payload, model)\n\n // Apply calibration to adjust the GPT tokenizer estimate\n const currentTokens = learned && learned.sampleCount > 0 ? calibrate(model.id, rawTokens) : rawTokens\n\n const exceedsTokens = cfg.checkTokenLimit && currentTokens > tokenLimit\n\n return {\n needed: exceedsTokens,\n currentTokens,\n tokenLimit,\n reason: exceedsTokens ? \"tokens\" : undefined,\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { checkNeedsCompactionAnthropic, countTotalInputTokens } from \"~/lib/anthropic/auto-truncate\"\nimport { hasKnownLimits } from \"~/lib/auto-truncate\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { state } from \"~/lib/state\"\nimport { tuiLogger } from \"~/lib/tui\"\nimport { type MessagesPayload } from \"~/types/api/anthropic\"\n\n/**\n * Handles token counting for Anthropic /v1/messages/count_tokens endpoint.\n *\n * Counts tokens directly on the Anthropic payload using native counting functions.\n *\n * Per Anthropic docs:\n * - Returns { input_tokens: N } where N is the total input tokens\n * - Thinking blocks from previous assistant turns don't count as input tokens\n * - The count is an estimate\n */\nexport async function handleCountTokens(c: Context) {\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n try {\n const anthropicPayload = await c.req.json<MessagesPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n anthropicPayload.model = resolveModelName(anthropicPayload.model)\n\n // Update tracker with model name\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { model: anthropicPayload.model })\n }\n\n const selectedModel = state.modelIndex.get(anthropicPayload.model)\n\n if (!selectedModel) {\n consola.warn(`[count_tokens] Model \"${anthropicPayload.model}\" not found, returning input_tokens=1`)\n return c.json({ input_tokens: 1 })\n }\n\n // Check if auto-truncate would be triggered (only for models with known limits)\n // If so, return an inflated token count to encourage Claude Code auto-compact\n if (state.autoTruncate && hasKnownLimits(selectedModel.id)) {\n const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel, {\n checkTokenLimit: true,\n })\n\n if (truncateCheck.needed) {\n const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 200000\n const inflatedTokens = Math.floor(contextWindow * 0.95)\n\n consola.info(\n `[count_tokens] Prompt too long: `\n + `${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit} limit, `\n + `returning inflated count ${inflatedTokens} to trigger client-side compaction`,\n )\n\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { inputTokens: inflatedTokens })\n }\n\n return c.json({ input_tokens: inflatedTokens })\n }\n }\n\n // Count tokens directly on Anthropic payload\n // Excludes thinking blocks from assistant messages per Anthropic spec\n const inputTokens = await countTotalInputTokens(anthropicPayload, selectedModel)\n\n consola.debug(\n `[count_tokens] ${inputTokens} tokens (native Anthropic) `\n + `(tokenizer: ${selectedModel.capabilities?.tokenizer ?? \"o200k_base\"})`,\n )\n\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, { inputTokens })\n }\n\n return c.json({ input_tokens: inputTokens })\n } catch (error) {\n consola.error(\"[count_tokens] Error counting tokens:\", error)\n return c.json({ input_tokens: 1 })\n }\n}\n","/**\n * Anthropic model feature detection and request header construction.\n *\n * Mirrors VSCode Copilot Chat's feature detection logic from:\n * - anthropic.ts: modelSupportsInterleavedThinking, modelSupportsContextEditing, modelSupportsToolSearch\n * - chatEndpoint.ts: getExtraHeaders (anthropic-beta, capi-beta-1)\n * - anthropic.ts: buildContextManagement\n */\n\nimport { normalizeForMatching } from \"~/lib/models/resolver\"\n\n// ============================================================================\n// Model Feature Detection\n// ============================================================================\n\n/**\n * Interleaved thinking is supported by:\n * - Claude Sonnet 4/4.5\n * - Claude Haiku 4.5\n * - Claude Opus 4.5/4.6\n *\n * Notably, claude-opus-4 and claude-opus-4-1 do NOT support interleaved thinking.\n */\nexport function modelSupportsInterleavedThinking(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return (\n normalized.startsWith(\"claude-sonnet-4-5\")\n || normalized.startsWith(\"claude-sonnet-4\")\n || normalized.startsWith(\"claude-haiku-4-5\")\n || normalized.startsWith(\"claude-opus-4-5\")\n || normalized.startsWith(\"claude-opus-4-6\")\n )\n}\n\n/**\n * Context editing is supported by a broader set of models:\n * - Claude Haiku 4.5\n * - Claude Sonnet 4/4.5\n * - Claude Opus 4/4.1/4.5/4.6\n */\nexport function modelSupportsContextEditing(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return (\n normalized.startsWith(\"claude-haiku-4-5\")\n || normalized.startsWith(\"claude-sonnet-4-5\")\n || normalized.startsWith(\"claude-sonnet-4\")\n || normalized.startsWith(\"claude-opus-4-5\")\n || normalized.startsWith(\"claude-opus-4-6\")\n || normalized.startsWith(\"claude-opus-4-1\")\n || normalized.startsWith(\"claude-opus-4\")\n )\n}\n\n/**\n * Tool search is supported by:\n * - Claude Opus 4.5/4.6\n */\nexport function modelSupportsToolSearch(modelId: string): boolean {\n const normalized = normalizeForMatching(modelId)\n return normalized.startsWith(\"claude-opus-4-5\") || normalized.startsWith(\"claude-opus-4-6\")\n}\n\n// ============================================================================\n// Anthropic Beta Headers\n// ============================================================================\n\nexport interface AnthropicBetaHeaders {\n /** Comma-separated beta feature identifiers */\n \"anthropic-beta\"?: string\n /** Fallback for models without interleaved thinking support */\n \"capi-beta-1\"?: string\n}\n\n/**\n * Build anthropic-beta and capi-beta-1 headers based on model capabilities.\n *\n * Logic from chatEndpoint.ts:166-201:\n * - If model supports interleaved thinking → add \"interleaved-thinking-2025-05-14\"\n * - Otherwise → set \"capi-beta-1: true\"\n * - If model supports context editing → add \"context-management-2025-06-27\"\n * - If model supports tool search → add \"advanced-tool-use-2025-11-20\"\n */\nexport function buildAnthropicBetaHeaders(modelId: string): AnthropicBetaHeaders {\n const headers: AnthropicBetaHeaders = {}\n const betaFeatures: Array<string> = []\n\n if (modelSupportsInterleavedThinking(modelId)) {\n betaFeatures.push(\"interleaved-thinking-2025-05-14\")\n } else {\n headers[\"capi-beta-1\"] = \"true\"\n }\n\n if (modelSupportsContextEditing(modelId)) {\n betaFeatures.push(\"context-management-2025-06-27\")\n }\n\n if (modelSupportsToolSearch(modelId)) {\n betaFeatures.push(\"advanced-tool-use-2025-11-20\")\n }\n\n if (betaFeatures.length > 0) {\n headers[\"anthropic-beta\"] = betaFeatures.join(\",\")\n }\n\n return headers\n}\n\n// ============================================================================\n// Context Management\n// ============================================================================\n\ninterface ContextManagementEdit {\n type: string\n trigger?: { type: string; value: number }\n keep?: { type: string; value: number }\n clear_at_least?: { type: string; value: number }\n exclude_tools?: Array<string>\n clear_tool_inputs?: boolean\n}\n\nexport interface ContextManagement {\n edits: Array<ContextManagementEdit>\n}\n\n/**\n * Build context_management config for the request body.\n *\n * From anthropic.ts:270-329 (buildContextManagement + getContextManagementFromConfig):\n * - clear_thinking: keep last N thinking turns\n * - clear_tool_uses: triggered by input_tokens threshold, keep last N tool uses\n */\nexport function buildContextManagement(modelId: string, hasThinking: boolean): ContextManagement | undefined {\n if (!modelSupportsContextEditing(modelId)) {\n return undefined\n }\n\n // Default config from getContextManagementFromConfig\n const triggerType = \"input_tokens\"\n const triggerValue = 100_000\n const keepCount = 3\n const thinkingKeepTurns = 1\n\n const edits: Array<ContextManagementEdit> = []\n\n // Add clear_thinking only if thinking is enabled\n if (hasThinking) {\n edits.push({\n type: \"clear_thinking_20251015\",\n keep: { type: \"thinking_turns\", value: Math.max(1, thinkingKeepTurns) },\n })\n }\n\n // Always add clear_tool_uses\n edits.push({\n type: \"clear_tool_uses_20250919\",\n trigger: { type: triggerType, value: triggerValue },\n keep: { type: \"tool_uses\", value: keepCount },\n })\n\n return { edits }\n}\n","/**\n * Anthropic Messages API tool preprocessing pipeline.\n *\n * Prepares the tools array before sending to Copilot's /v1/messages endpoint:\n * - Injects Claude Code official tool stubs for missing tools\n * - Applies tool_search / defer_loading based on model capabilities\n * - Ensures all custom tools have input_schema\n * - Injects stubs for tools referenced in message history\n * - Converts server-side tools (web_search, etc.) to custom tool equivalents\n *\n * Must be called BEFORE sanitize — processToolBlocks (in sanitize) uses\n * the tools array to validate tool_use references in messages.\n */\n\nimport consola from \"consola\"\n\nimport type { MessageParam, MessagesPayload, Tool } from \"~/types/api/anthropic\"\n\nimport { state } from \"~/lib/state\"\n\nimport { modelSupportsToolSearch } from \"./features\"\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Claude Code official tool names that must always be present in the tools array.\n * If any of these are missing from the request, they will be injected as stub definitions.\n */\nconst CLAUDE_CODE_OFFICIAL_TOOLS = [\n \"Task\",\n \"TaskOutput\",\n \"Bash\",\n \"Glob\",\n \"Grep\",\n \"Read\",\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n \"WebFetch\",\n \"TodoWrite\",\n \"KillShell\",\n \"AskUserQuestion\",\n \"Skill\",\n \"EnterPlanMode\",\n \"ExitPlanMode\",\n]\n\n/** Tool names that should NOT be deferred (core tools always available) */\nconst NON_DEFERRED_TOOL_NAMES = new Set([\n // VSCode Copilot Chat original tool names (snake_case)\n \"read_file\",\n \"list_dir\",\n \"grep_search\",\n \"semantic_search\",\n \"file_search\",\n \"replace_string_in_file\",\n \"multi_replace_string_in_file\",\n \"insert_edit_into_file\",\n \"apply_patch\",\n \"create_file\",\n \"run_in_terminal\",\n \"get_terminal_output\",\n \"get_errors\",\n \"manage_todo_list\",\n \"runSubagent\",\n \"search_subagent\",\n \"runTests\",\n \"ask_questions\",\n \"switch_agent\",\n // Claude Code official tool names (PascalCase)\n ...CLAUDE_CODE_OFFICIAL_TOOLS,\n])\n\nconst TOOL_SEARCH_TOOL_NAME = \"tool_search_tool_regex\"\nconst TOOL_SEARCH_TOOL_TYPE = \"tool_search_tool_regex_20251119\"\n\nconst EMPTY_INPUT_SCHEMA = { type: \"object\", properties: {}, required: [] } as const\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\n/** Ensure a tool has input_schema — required by Anthropic API for custom tools. */\nfunction ensureInputSchema(tool: Tool): Tool {\n return tool.input_schema ? tool : { ...tool, input_schema: EMPTY_INPUT_SCHEMA }\n}\n\n/**\n * Collect tool names referenced in message history via tool_use blocks.\n *\n * When tool_search is enabled, deferred tools must be \"loaded\" via\n * tool_search_tool_regex before they can be called. But in multi-turn\n * conversations, message history may already contain tool_use blocks\n * referencing tools that were loaded in earlier turns. If we mark those\n * tools as deferred again, the API rejects the request because the\n * historical tool_use references a tool that isn't \"loaded\" in this turn.\n *\n * By collecting all tool names from history, we ensure those tools remain\n * non-deferred (immediately available) — preserving the tool_use/tool_result\n * pairing that the API requires.\n */\nfunction collectHistoryToolNames(messages: Array<MessageParam>): Set<string> {\n const names = new Set<string>()\n for (const msg of messages) {\n if (msg.role !== \"assistant\" || typeof msg.content === \"string\") continue\n for (const block of msg.content) {\n if (block.type === \"tool_use\") {\n names.add(block.name)\n }\n }\n }\n return names\n}\n\n/**\n * Build minimal tool stubs for tools referenced in message history.\n *\n * Used when the request has no tools but messages contain tool_use blocks.\n * Only needed when tool search is enabled (advanced-tool-use beta),\n * which enforces tool reference validation.\n */\nfunction buildHistoryToolStubs(historyToolNames: Set<string>): Array<Tool> {\n return Array.from(historyToolNames).map((name) => ({\n name,\n description: `Stub for tool referenced in conversation history`,\n input_schema: EMPTY_INPUT_SCHEMA,\n }))\n}\n\n// ============================================================================\n// Tool pipeline\n// ============================================================================\n\n/**\n * Process tools through the full pipeline:\n * 1. Inject missing Claude Code official tool stubs\n * 2. If model supports tool search: prepend search tool, mark non-core as deferred\n * 3. Ensure all custom tools have input_schema (skip API-defined typed tools)\n *\n * Returns a new array — never mutates the input.\n */\nfunction processToolPipeline(tools: Array<Tool>, modelId: string, messages: Array<MessageParam>): Array<Tool> {\n const existingNames = new Set(tools.map((t) => t.name))\n const toolSearchEnabled = modelSupportsToolSearch(modelId)\n\n // Collect tool names already referenced in message history — these must\n // stay non-deferred to avoid \"Tool reference not found\" errors\n const historyToolNames = toolSearchEnabled ? collectHistoryToolNames(messages) : undefined\n\n const result: Array<Tool> = []\n\n // Prepend tool_search_tool_regex if model supports it\n if (toolSearchEnabled) {\n result.push({\n name: TOOL_SEARCH_TOOL_NAME,\n type: TOOL_SEARCH_TOOL_TYPE,\n defer_loading: false,\n })\n }\n\n // Process existing tools: ensure input_schema, apply defer_loading\n for (const tool of tools) {\n // Tools with a `type` field are API-defined (tool_search, memory, web_search) —\n // schema is managed server-side, don't touch input_schema\n const normalized = tool.type ? tool : ensureInputSchema(tool)\n\n // Respect explicit defer_loading: false from retry strategies (deferred-tool-retry\n // sets this when a tool was rejected as \"not found in available tools\")\n const shouldDefer =\n toolSearchEnabled\n && tool.defer_loading !== false\n && !NON_DEFERRED_TOOL_NAMES.has(tool.name)\n && !historyToolNames?.has(tool.name)\n\n result.push(shouldDefer ? { ...normalized, defer_loading: true } : normalized)\n }\n\n // Inject stubs for any missing Claude Code official tools\n for (const name of CLAUDE_CODE_OFFICIAL_TOOLS) {\n if (!existingNames.has(name)) {\n const stub: Tool = {\n name,\n description: `Claude Code ${name} tool`,\n input_schema: EMPTY_INPUT_SCHEMA,\n }\n // Official tools are always non-deferred, no defer_loading needed\n result.push(stub)\n }\n }\n\n // Inject minimal stubs for tools referenced in message history but missing\n // from the tools array. This happens when MCP tools were available in earlier\n // turns but not included in the current request. Without these stubs, the API\n // rejects the request because the historical tool_use references a tool that\n // doesn't exist in the tools list at all.\n if (historyToolNames) {\n const allResultNames = new Set(result.map((t) => t.name))\n for (const name of historyToolNames) {\n if (!allResultNames.has(name)) {\n consola.debug(`[ToolPipeline] Injecting stub for history-referenced tool: ${name}`)\n result.push({\n name,\n description: `Stub for tool referenced in conversation history`,\n input_schema: EMPTY_INPUT_SCHEMA,\n })\n }\n }\n }\n\n const deferredCount = result.filter((t) => t.defer_loading === true).length\n const injectedCount = result.length - tools.length\n if (deferredCount > 0 || injectedCount > 0) {\n consola.debug(\n `[ToolPipeline] ${result.length} tools`\n + ` (${deferredCount} deferred, ${injectedCount} injected, tool_search: ${toolSearchEnabled})`,\n )\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Preprocess tools for an Anthropic request.\n *\n * Must be called BEFORE sanitize — because processToolBlocks (in sanitize)\n * uses the tools array to validate tool_use references. If we run sanitize\n * first with the original (incomplete) tools, it may incorrectly filter\n * tool_use blocks that processToolPipeline would have later provided stubs for.\n *\n * Handles two scenarios:\n * 1. Request has tools → run through processToolPipeline (defer_loading, stubs, tool_search)\n * 2. Request has NO tools but messages have tool_use → inject minimal stubs\n *\n * Returns a new payload (never mutates input).\n */\nexport function preprocessTools(payload: MessagesPayload): MessagesPayload {\n const tools = payload.tools\n const model = payload.model\n const messages = payload.messages\n\n if (tools && tools.length > 0) {\n return { ...payload, tools: processToolPipeline(tools, model, messages) }\n }\n\n // No tools in request — but if tool search is enabled and history has tool_use\n // references, we need stubs to satisfy API validation\n if (modelSupportsToolSearch(model)) {\n const historyToolNames = collectHistoryToolNames(messages)\n if (historyToolNames.size > 0) {\n consola.debug(\n `[ToolPipeline] Injecting ${historyToolNames.size} tool stubs for`\n + ` history references (no tools in request): ${[...historyToolNames].join(\", \")}`,\n )\n return { ...payload, tools: buildHistoryToolStubs(historyToolNames) }\n }\n }\n\n return payload\n}\n\n// ============================================================================\n// Server Tool Rewriting\n// ============================================================================\n\n/**\n * Server-side tool type prefixes that need special handling.\n * These tools have a special `type` field (e.g., \"web_search_20250305\")\n * and are normally executed by Anthropic's servers.\n */\ninterface ServerToolConfig {\n description: string\n input_schema: Record<string, unknown>\n /** If true, this tool will be removed from the request and Claude won't see it */\n remove?: boolean\n /** Error message to show if the tool is removed */\n removalReason?: string\n}\n\nconst SERVER_TOOL_CONFIGS: Record<string, ServerToolConfig> = {\n web_search: {\n description:\n \"Search the web for current information. \"\n + \"Returns web search results that can help answer questions about recent events, \"\n + \"current data, or information that may have changed since your knowledge cutoff.\",\n input_schema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"The search query\" },\n },\n required: [\"query\"],\n },\n },\n web_fetch: {\n description:\n \"Fetch content from a URL. \"\n + \"NOTE: This is a client-side tool - the client must fetch the URL and return the content.\",\n input_schema: {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"The URL to fetch\" },\n },\n required: [\"url\"],\n },\n },\n code_execution: {\n description: \"Execute code in a sandbox. \" + \"NOTE: This is a client-side tool - the client must execute the code.\",\n input_schema: {\n type: \"object\",\n properties: {\n code: { type: \"string\", description: \"The code to execute\" },\n language: { type: \"string\", description: \"The programming language\" },\n },\n required: [\"code\"],\n },\n },\n computer: {\n description:\n \"Control computer desktop. \" + \"NOTE: This is a client-side tool - the client must handle computer control.\",\n input_schema: {\n type: \"object\",\n properties: {\n action: { type: \"string\", description: \"The action to perform\" },\n },\n required: [\"action\"],\n },\n },\n}\n\n// Match tool.type (e.g., \"web_search_20250305\") to a server tool config\nfunction findServerToolConfig(type: string | undefined): ServerToolConfig | null {\n if (!type) return null\n for (const [prefix, config] of Object.entries(SERVER_TOOL_CONFIGS)) {\n if (type.startsWith(prefix)) return config\n }\n return null\n}\n\n/**\n * Convert server-side tools to custom tools, or pass them through unchanged.\n * Only converts when state.convertServerTools is enabled.\n */\nexport function convertServerToolsToCustom(tools: Array<Tool> | undefined): Array<Tool> | undefined {\n if (!tools) return undefined\n\n // When conversion is disabled, pass all tools through unchanged\n if (!state.convertServerToolsToCustom) return tools\n\n const result: Array<Tool> = []\n\n for (const tool of tools) {\n const config = findServerToolConfig(tool.type)\n if (!config) {\n result.push(tool)\n continue\n }\n\n if (config.remove) {\n consola.warn(`[DirectAnthropic] Removing server tool: ${tool.name}. Reason: ${config.removalReason}`)\n continue\n }\n\n consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`)\n result.push({\n name: tool.name,\n description: config.description,\n input_schema: config.input_schema,\n })\n }\n\n return result.length > 0 ? result : undefined\n}\n","/**\n * Direct Anthropic-style message API for Copilot.\n *\n * Owns the HTTP request lifecycle: wire payload construction, header building,\n * model-aware request enrichment (beta headers, context management),\n * and HTTP execution against Copilot's /v1/messages endpoint.\n *\n * Tool preprocessing lives in ./message-tools.ts and must be called\n * before createAnthropicMessages().\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport type { MessagesPayload, Message as AnthropicResponse, Tool } from \"~/types/api/anthropic\"\n\nimport { copilotBaseUrl, copilotHeaders } from \"~/lib/copilot-api\"\nimport { HTTPError } from \"~/lib/error\"\nimport { createFetchSignal } from \"~/lib/fetch-utils\"\nimport { state } from \"~/lib/state\"\n\nimport { buildAnthropicBetaHeaders, buildContextManagement } from \"./features\"\nimport { convertServerToolsToCustom } from \"./message-tools\"\n\n/** Re-export the response type for consumers */\nexport type AnthropicMessageResponse = AnthropicResponse\n\n// ============================================================================\n// Wire payload construction\n// ============================================================================\n\n/**\n * Fields known to be rejected by Copilot's Anthropic API endpoint\n * with \"Extra inputs are not permitted\".\n *\n * We use a blacklist instead of a whitelist so that new Anthropic fields\n * are forwarded by default — no code change needed when Copilot adds support.\n */\nconst COPILOT_REJECTED_FIELDS = new Set([\"output_config\", \"inference_geo\"])\n\n/**\n * Build the wire payload: strip rejected fields and convert server tools.\n * Returns a plain record — the wire format may carry fields beyond what\n * MessagesPayload declares (e.g. context_management), so we don't pretend\n * it's a typed struct.\n */\nfunction buildWirePayload(payload: MessagesPayload): Record<string, unknown> {\n const wire: Record<string, unknown> = {}\n const rejectedFields: Array<string> = []\n\n for (const [key, value] of Object.entries(payload)) {\n if (COPILOT_REJECTED_FIELDS.has(key)) {\n rejectedFields.push(key)\n } else {\n wire[key] = value\n }\n }\n\n if (rejectedFields.length > 0) {\n consola.debug(`[DirectAnthropic] Stripped rejected fields: ${rejectedFields.join(\", \")}`)\n }\n\n // Convert server-side tools (web_search, etc.) to custom tool equivalents\n if (wire.tools) {\n wire.tools = convertServerToolsToCustom(wire.tools as Array<Tool>)\n }\n\n return wire\n}\n\n/**\n * Ensure max_tokens > budget_tokens when thinking is enabled.\n *\n * Anthropic API requires max_tokens > thinking.budget_tokens. Some clients\n * send budget_tokens >= max_tokens. We cap budget_tokens to max_tokens - 1,\n * matching the approach in vscode-copilot-chat (messagesApi.ts:132).\n */\nfunction adjustThinkingBudget(wire: Record<string, unknown>): void {\n const thinking = wire.thinking as MessagesPayload[\"thinking\"]\n if (!thinking || thinking.type === \"disabled\" || thinking.type === \"adaptive\") return\n\n const budgetTokens = thinking.budget_tokens\n if (!budgetTokens) return\n\n const maxTokens = wire.max_tokens as number\n if (budgetTokens >= maxTokens) {\n const adjusted = maxTokens - 1\n ;(wire.thinking as { budget_tokens: number }).budget_tokens = adjusted\n consola.debug(\n `[DirectAnthropic] Capped thinking.budget_tokens: ${budgetTokens} → ${adjusted} ` + `(max_tokens=${maxTokens})`,\n )\n }\n}\n\n// ============================================================================\n// Main entry point — createAnthropicMessages\n// ============================================================================\n\n/**\n * Create messages using Anthropic-style API directly.\n * Calls Copilot's native Anthropic endpoint for Anthropic-vendor models.\n */\nexport async function createAnthropicMessages(\n payload: MessagesPayload,\n): Promise<AnthropicMessageResponse | AsyncGenerator<ServerSentEventMessage>> {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const wire = buildWirePayload(payload)\n adjustThinkingBudget(wire)\n\n // Destructure known fields for typed access\n const model = wire.model as string\n const messages = wire.messages as MessagesPayload[\"messages\"]\n const tools = wire.tools as Array<Tool> | undefined\n const thinking = wire.thinking as MessagesPayload[\"thinking\"]\n\n // Check for vision content\n const enableVision = messages.some((msg) => {\n if (typeof msg.content === \"string\") return false\n return msg.content.some((block) => block.type === \"image\")\n })\n\n // Agent/user check for X-Initiator header\n const isAgentCall = messages.some((msg) => msg.role === \"assistant\")\n\n const headers: Record<string, string> = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n \"anthropic-version\": \"2023-06-01\",\n ...buildAnthropicBetaHeaders(model),\n }\n\n // Add context_management if model supports it and payload doesn't already have one\n if (!wire.context_management) {\n const hasThinking = Boolean(thinking && thinking.type !== \"disabled\")\n const contextManagement = buildContextManagement(model, hasThinking)\n if (contextManagement) {\n wire.context_management = contextManagement\n consola.debug(\"[DirectAnthropic] Added context_management:\", JSON.stringify(contextManagement))\n }\n }\n\n // Tools should already be preprocessed by preprocessTools() before reaching here.\n // No further tool processing needed — wire.tools is used as-is.\n\n consola.debug(\"Sending direct Anthropic request to Copilot /v1/messages\")\n\n // Apply fetch timeout if configured (connection + response headers)\n const fetchSignal = createFetchSignal()\n\n const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(wire),\n signal: fetchSignal,\n })\n\n if (!response.ok) {\n consola.debug(\"Request failed:\", {\n model,\n max_tokens: wire.max_tokens,\n stream: wire.stream,\n toolCount: tools?.length ?? 0,\n thinking,\n messageCount: messages.length,\n })\n throw await HTTPError.fromResponse(\"Failed to create Anthropic messages\", response, model)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as AnthropicMessageResponse\n}\n","/**\n * Stream accumulator for Anthropic format responses.\n * Handles accumulating stream events for history recording and tracking.\n */\n\nimport consola from \"consola\"\n\nimport type { CopilotAnnotations, StreamEvent, RawMessageStartEvent } from \"~/types/api/anthropic\"\n\n// ============================================================================\n// Accumulated content block types\n// ============================================================================\n\n/**\n * A single content block accumulated from the stream, preserving original order.\n * Known block types have typed variants; unknown types are stored via\n * AccumulatedGenericBlock with all original fields preserved.\n */\nexport type AccumulatedContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"thinking\"; thinking: string; signature?: string }\n | { type: \"redacted_thinking\"; data: string }\n | { type: \"tool_use\"; id: string; name: string; input: string }\n | { type: \"server_tool_use\"; id: string; name: string; input: string }\n | { type: \"web_search_tool_result\"; tool_use_id: string; content: unknown }\n | AccumulatedGenericBlock\n\n/**\n * Generic block for unknown/future content block types.\n * Branded with `_generic` to distinguish from known types in discriminated unions.\n */\nexport interface AccumulatedGenericBlock {\n type: string\n _generic: true\n [key: string]: unknown\n}\n\n// ============================================================================\n// Base accumulator interface (shared with OpenAI accumulator)\n// ============================================================================\n\n/** Minimal accumulator contract for tracking and error recording */\nexport interface BaseStreamAccumulator {\n model: string\n inputTokens: number\n outputTokens: number\n /** Plain text content for error recording fallback */\n content: string\n}\n\n// ============================================================================\n// Anthropic stream accumulator\n// ============================================================================\n\n/** Stream accumulator for Anthropic format */\nexport interface AnthropicStreamAccumulator extends BaseStreamAccumulator {\n cacheReadTokens: number\n cacheCreationTokens: number\n stopReason: string\n /** Content blocks in stream order, indexed by the event's `index` field. */\n contentBlocks: Array<AccumulatedContentBlock>\n /** Copilot-specific: IP code citations collected from stream events */\n copilotAnnotations: Array<CopilotAnnotations>\n /** Error received from stream, if any */\n streamError?: { type: string; message: string }\n}\n\n/** Create a fresh Anthropic stream accumulator */\nexport function createAnthropicStreamAccumulator(): AnthropicStreamAccumulator {\n return {\n model: \"\",\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n stopReason: \"\",\n content: \"\",\n contentBlocks: [],\n copilotAnnotations: [],\n }\n}\n\n// ============================================================================\n// Event processing\n// ============================================================================\n\n/** Accumulate a single Anthropic stream event into the accumulator */\nexport function accumulateAnthropicStreamEvent(event: StreamEvent, acc: AnthropicStreamAccumulator) {\n switch (event.type) {\n case \"message_start\": {\n handleMessageStart(event.message, acc)\n break\n }\n case \"content_block_start\": {\n handleContentBlockStart(event.index, event.content_block as AccContentBlock, acc)\n break\n }\n case \"content_block_delta\": {\n handleContentBlockDelta(event.index, event.delta as AccDelta, acc, event.copilot_annotations)\n break\n }\n case \"content_block_stop\": {\n // Nothing to do — block is already stored by index, no state to clear\n break\n }\n case \"message_delta\": {\n handleMessageDelta(event.delta as MessageDelta, event.usage as MessageDeltaUsage, acc)\n break\n }\n case \"message_stop\": {\n // Nothing to do — stop_reason is provided in message_delta, no state to clear\n break\n }\n case \"ping\": {\n // No accumulation needed for ping events, but could track last ping time if desired\n break\n }\n case \"error\": {\n const err = (event as { error?: { type?: string; message?: string } }).error\n acc.streamError = {\n type: err?.type ?? \"unknown_error\",\n message: err?.message ?? \"Unknown stream error\",\n }\n break\n }\n default: {\n consola.warn(`[stream-accumulator] Unknown event type: ${(event as { type: string }).type}`)\n break\n }\n }\n}\n\n// ============================================================================\n// message_start handler\n// ============================================================================\n\n/**\n * Handle message_start event.\n * This is where input_tokens, model, and cache stats are first reported.\n */\nfunction handleMessageStart(message: RawMessageStartEvent[\"message\"], acc: AnthropicStreamAccumulator) {\n if (message.model) acc.model = message.model\n acc.inputTokens = message.usage.input_tokens\n acc.outputTokens = message.usage.output_tokens\n if (message.usage.cache_read_input_tokens) {\n acc.cacheReadTokens = message.usage.cache_read_input_tokens\n }\n if (message.usage.cache_creation_input_tokens) {\n acc.cacheCreationTokens = message.usage.cache_creation_input_tokens\n }\n}\n\n// ============================================================================\n// content_block handlers\n// ============================================================================\n\n/** Content block delta types (local — looser than SDK's RawContentBlockDelta for proxy use) */\ntype AccDelta =\n | { type: \"text_delta\"; text: string }\n | { type: \"input_json_delta\"; partial_json: string }\n | { type: \"thinking_delta\"; thinking: string }\n | { type: \"signature_delta\"; signature: string }\n\n/** Content block start types (local — looser than SDK's ContentBlock for proxy use) */\ntype AccContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"tool_use\"\n id: string\n name: string\n input: Record<string, unknown>\n }\n | { type: \"thinking\"; thinking: string; signature?: string }\n | { type: \"redacted_thinking\"; data: string }\n | { type: \"server_tool_use\"; id: string; name: string }\n | { type: \"web_search_tool_result\"; tool_use_id: string; content: unknown }\n\nfunction handleContentBlockStart(index: number, block: AccContentBlock, acc: AnthropicStreamAccumulator) {\n let newBlock: AccumulatedContentBlock\n\n switch (block.type) {\n case \"text\": {\n newBlock = { type: \"text\", text: \"\" }\n break\n }\n case \"thinking\": {\n newBlock = { type: \"thinking\", thinking: \"\", signature: undefined }\n break\n }\n case \"redacted_thinking\": {\n // Complete at block_start, no subsequent deltas\n newBlock = { type: \"redacted_thinking\", data: block.data }\n break\n }\n case \"tool_use\": {\n newBlock = { type: \"tool_use\", id: block.id, name: block.name, input: \"\" }\n break\n }\n case \"server_tool_use\": {\n newBlock = { type: \"server_tool_use\", id: block.id, name: block.name, input: \"\" }\n break\n }\n case \"web_search_tool_result\": {\n // Complete at block_start, no subsequent deltas\n newBlock = {\n type: \"web_search_tool_result\",\n tool_use_id: block.tool_use_id,\n content: block.content,\n }\n break\n }\n default: {\n // Unknown block type — store all fields as-is for forward compatibility.\n // Cast needed because TypeScript narrows to `never` after exhaustive cases,\n // but runtime data from the API may contain types not yet in our definitions.\n const unknownBlock = block as unknown as Record<string, unknown>\n consola.warn(`[stream-accumulator] Unknown content block type: ${String(unknownBlock.type)}`)\n newBlock = { ...unknownBlock, _generic: true } as AccumulatedGenericBlock\n break\n }\n }\n\n acc.contentBlocks[index] = newBlock\n}\n\nfunction handleContentBlockDelta(\n index: number,\n delta: AccDelta,\n acc: AnthropicStreamAccumulator,\n copilotAnnotations?: CopilotAnnotations,\n) {\n const block = acc.contentBlocks[index]\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive: index from untrusted SSE data\n if (!block) return\n\n switch (delta.type) {\n case \"text_delta\": {\n const b = block as { text: string }\n b.text += delta.text\n acc.content += delta.text // Sync BaseStreamAccumulator.content for error fallback\n break\n }\n case \"thinking_delta\": {\n const b = block as { thinking: string }\n b.thinking += delta.thinking\n break\n }\n case \"input_json_delta\": {\n const b = block as { input: string }\n b.input += delta.partial_json\n break\n }\n case \"signature_delta\": {\n // signature_delta is part of the thinking block integrity, it's not accumulated actually (it, not content)\n const b = block as { signature?: string }\n if (b.signature) {\n consola.error(\n \"[stream-accumulator] Received unexpected signature_delta for a block that already has a signature. Overwriting existing signature.\",\n )\n }\n b.signature = delta.signature\n break\n }\n default: {\n consola.warn(`[stream-accumulator] Unknown delta type: ${(delta as { type: string }).type}`)\n break\n }\n }\n\n // Collect Copilot-specific IP code citations\n if (copilotAnnotations?.ip_code_citations?.length) {\n acc.copilotAnnotations.push(copilotAnnotations)\n }\n}\n\n// ============================================================================\n// message_delta handler\n// ============================================================================\n\n// Message delta types\ninterface MessageDelta {\n stop_reason?: string | null\n stop_sequence?: string | null\n}\n\ninterface MessageDeltaUsage {\n input_tokens?: number\n output_tokens: number\n cache_creation_input_tokens?: number\n cache_read_input_tokens?: number\n}\n\n/**\n * Handle message_delta event.\n * output_tokens is the final count here (replaces message_start's 0).\n * input_tokens may or may not be present — only update if provided.\n */\nfunction handleMessageDelta(\n delta: MessageDelta,\n usage: MessageDeltaUsage | undefined,\n acc: AnthropicStreamAccumulator,\n) {\n if (delta.stop_reason) acc.stopReason = delta.stop_reason\n if (usage) {\n // output_tokens in message_delta is the final output count\n acc.outputTokens = usage.output_tokens\n // input_tokens in message_delta is optional; only override if explicitly present\n if (usage.input_tokens !== undefined) {\n acc.inputTokens = usage.input_tokens\n }\n // Accumulate cache stats if present (may complement message_start values)\n if (usage.cache_read_input_tokens !== undefined) {\n acc.cacheReadTokens = usage.cache_read_input_tokens\n }\n if (usage.cache_creation_input_tokens !== undefined) {\n acc.cacheCreationTokens = usage.cache_creation_input_tokens\n }\n }\n}\n\n// ============================================================================\n// Convenience extractors\n// ============================================================================\n\n/** Get concatenated text content from all text blocks */\nexport function getTextContent(acc: AnthropicStreamAccumulator): string {\n return acc.contentBlocks\n .filter((b): b is AccumulatedContentBlock & { type: \"text\" } => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\")\n}\n\n/** Get concatenated thinking content from all thinking blocks */\nexport function getThinkingContent(acc: AnthropicStreamAccumulator): string {\n return acc.contentBlocks\n .filter((b): b is AccumulatedContentBlock & { type: \"thinking\" } => b.type === \"thinking\")\n .map((b) => b.thinking)\n .join(\"\")\n}\n\n/** Get count of redacted_thinking blocks */\nexport function getRedactedThinkingCount(acc: AnthropicStreamAccumulator): number {\n return acc.contentBlocks.filter((b) => b.type === \"redacted_thinking\").length\n}\n","/**\n * Anthropic API routing and stream processing utilities.\n *\n * Reusable components shared across route handlers and tests:\n * - API routing decisions (vendor/endpoint validation)\n * - SSE stream processing (parse, accumulate, shutdown-aware iteration)\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\n\nimport consola from \"consola\"\n\nimport type { StreamEvent } from \"~/types/api/anthropic\"\n\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { combineAbortSignals, raceIteratorNext, STREAM_ABORTED } from \"~/lib/stream\"\n\nimport {\n type AnthropicStreamAccumulator,\n accumulateAnthropicStreamEvent,\n createAnthropicStreamAccumulator,\n} from \"./stream-accumulator\"\n\n// ============================================================================\n// API routing\n// ============================================================================\n\nexport interface ApiRoutingDecision {\n supported: boolean\n reason: string\n}\n\n/**\n * Check if a model supports direct Anthropic API.\n * Returns a decision with reason so callers can log/display the routing rationale.\n */\nexport function supportsDirectAnthropicApi(modelId: string): ApiRoutingDecision {\n const model = state.modelIndex.get(modelId)\n if (model?.vendor !== \"Anthropic\") {\n return { supported: false, reason: `vendor is \"${model?.vendor ?? \"unknown\"}\", not Anthropic` }\n }\n\n if (!isEndpointSupported(model, ENDPOINT.MESSAGES)) {\n return { supported: false, reason: \"model does not support /v1/messages endpoint\" }\n }\n\n return { supported: true, reason: \"Anthropic vendor with /v1/messages support\" }\n}\n\n// ============================================================================\n// Stream processing\n// ============================================================================\n\n/** Processed event from the Anthropic stream */\nexport interface ProcessedAnthropicEvent {\n /** Original SSE message for forwarding */\n raw: ServerSentEventMessage\n /** Parsed event for accumulation (undefined for keepalives / [DONE]) */\n parsed?: StreamEvent\n}\n\n/**\n * Process raw Anthropic SSE stream: parse events, accumulate, check shutdown.\n * Yields each event for the caller to forward to the client.\n *\n * Each iteration races `iterator.next()` against idle timeout (if configured)\n * and the shutdown abort signal — so a stalled upstream connection can be\n * interrupted by either mechanism without waiting for the next event.\n */\nexport async function* processAnthropicStream(\n response: AsyncIterable<ServerSentEventMessage>,\n acc: AnthropicStreamAccumulator,\n clientAbortSignal?: AbortSignal,\n): AsyncGenerator<ProcessedAnthropicEvent> {\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n const iterator = response[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbortSignal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n // Shutdown abort signal fired while waiting for the next event\n if (result === STREAM_ABORTED) break\n\n if (result.done) break\n\n const rawEvent = result.value\n\n // No data — keepalive, nothing to accumulate\n if (!rawEvent.data) {\n consola.debug(\"SSE event with no data (keepalive):\", rawEvent.event ?? \"(no event type)\")\n yield { raw: rawEvent }\n continue\n }\n\n // [DONE] is not part of the SSE spec - it's an OpenAI convention.\n // Copilot's gateway injects it at the end of all streams, including Anthropic.\n // see refs/vscode-copilot-chat/src/platform/endpoint/node/messagesApi.ts:326\n if (rawEvent.data === \"[DONE]\") break\n\n // Try to parse and accumulate for history/tracking\n let parsed: StreamEvent | undefined\n try {\n parsed = JSON.parse(rawEvent.data) as StreamEvent\n accumulateAnthropicStreamEvent(parsed, acc)\n } catch (parseError) {\n consola.error(\"Failed to parse Anthropic stream event:\", parseError, rawEvent.data)\n }\n\n yield { raw: rawEvent, parsed }\n\n // Error event is terminal — Anthropic sends no more events after this\n if (parsed?.type === \"error\") break\n }\n}\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\n// Stream accumulator — re-exported for convenience\nexport { createAnthropicStreamAccumulator, type AnthropicStreamAccumulator }\n\n// Stream utilities — re-exported for backward compatibility.\n// New code should import directly from \"~/lib/stream\".\nexport { combineAbortSignals, raceIteratorNext, STREAM_ABORTED, StreamIdleTimeoutError } from \"~/lib/stream\"\n","/**\n * Message mapping utilities for correlating original and rewritten message arrays.\n *\n * Used by Anthropic handlers to track which rewritten messages correspond\n * to which original messages.\n */\n\nimport type { MessageParam } from \"~/types/api/anthropic\"\n\n/**\n * Check if two messages likely correspond to the same original message.\n * Used by buildMessageMapping to handle cases where sanitization removes\n * content blocks within a message (changing its shape) or removes entire messages.\n */\nexport function messagesMatch(orig: MessageParam, rewritten: MessageParam): boolean {\n if (orig.role !== rewritten.role) return false\n\n // String content: compare prefix\n if (typeof orig.content === \"string\" && typeof rewritten.content === \"string\")\n return (\n rewritten.content.startsWith(orig.content.slice(0, 100))\n || orig.content.startsWith(rewritten.content.slice(0, 100))\n )\n\n // Array content: compare first block's type and id\n const origBlocks = Array.isArray(orig.content) ? orig.content : []\n const rwBlocks = Array.isArray(rewritten.content) ? rewritten.content : []\n\n if (origBlocks.length === 0 || rwBlocks.length === 0) return true\n\n const ob = origBlocks[0]\n const rb = rwBlocks[0]\n if (ob.type !== rb.type) return false\n if (ob.type === \"tool_use\" && rb.type === \"tool_use\") return ob.id === rb.id\n if (ob.type === \"tool_result\" && rb.type === \"tool_result\") return ob.tool_use_id === rb.tool_use_id\n return true\n}\n\n/**\n * Build messageMapping (rwIdx → origIdx) for the direct Anthropic path.\n * Uses a two-pointer approach since rewritten messages maintain the same relative\n * order as originals (all transformations are deletions, never reorderings).\n */\nexport function buildMessageMapping(original: Array<MessageParam>, rewritten: Array<MessageParam>): Array<number> {\n const mapping: Array<number> = []\n let origIdx = 0\n\n for (const element of rewritten) {\n while (origIdx < original.length) {\n if (messagesMatch(original[origIdx], element)) {\n mapping.push(origIdx)\n origIdx++\n break\n }\n origIdx++\n }\n }\n\n // If matching missed some (shouldn't happen), fill with -1\n while (mapping.length < rewritten.length) {\n mapping.push(-1)\n }\n\n return mapping\n}\n","/**\n * Stream repetition detector.\n *\n * Uses the KMP failure function (prefix function) to detect repeated patterns\n * in streaming text output. When a model gets stuck in a repetitive loop,\n * it wastes tokens producing the same content over and over. This detector\n * identifies such loops early so the caller can take action (log warning,\n * abort stream, etc.).\n *\n * The algorithm works by maintaining a sliding buffer of recent text and\n * computing the longest proper prefix that is also a suffix — if this\n * length exceeds `(text.length - period) >= minRepetitions * period`,\n * it means a pattern of length `period` has repeated enough times.\n */\n\nimport consola from \"consola\"\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface RepetitionDetectorConfig {\n /** Minimum pattern length in characters to consider as repetition (default: 10) */\n minPatternLength: number\n /** Minimum number of full repetitions to trigger detection (default: 3) */\n minRepetitions: number\n /** Maximum buffer size in characters; older text is discarded (default: 5000) */\n maxBufferSize: number\n}\n\nconst DEFAULT_CONFIG: RepetitionDetectorConfig = {\n minPatternLength: 10,\n minRepetitions: 3,\n maxBufferSize: 5000,\n}\n\n// ============================================================================\n// Repetition Detector\n// ============================================================================\n\nexport class RepetitionDetector {\n private buffer = \"\"\n private readonly config: RepetitionDetectorConfig\n private detected = false\n\n constructor(config?: Partial<RepetitionDetectorConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * Feed a text chunk into the detector.\n * Returns `true` if repetition has been detected (now or previously).\n * Once detected, subsequent calls return `true` without further analysis.\n */\n feed(text: string): boolean {\n if (this.detected) return true\n if (!text) return false\n\n this.buffer += text\n\n // Trim buffer to maxBufferSize (keep the tail)\n if (this.buffer.length > this.config.maxBufferSize) {\n this.buffer = this.buffer.slice(-this.config.maxBufferSize)\n }\n\n // Only check when we have enough data for at least minRepetitions of the minimum pattern\n const minRequired = this.config.minPatternLength * this.config.minRepetitions\n if (this.buffer.length < minRequired) return false\n\n // Check for repetition in the buffer\n this.detected = detectRepetition(this.buffer, this.config.minPatternLength, this.config.minRepetitions)\n return this.detected\n }\n\n /** Reset detector state for a new stream */\n reset(): void {\n this.buffer = \"\"\n this.detected = false\n }\n\n /** Whether repetition has been detected */\n get isDetected(): boolean {\n return this.detected\n }\n}\n\n// ============================================================================\n// Core detection algorithm\n// ============================================================================\n\n/**\n * Detect if the tail of `text` contains a repeating pattern.\n *\n * Uses the KMP prefix function: for a string S, the prefix function π[i]\n * gives the length of the longest proper prefix of S[0..i] that is also\n * a suffix. If π[n-1] ≥ (n - period) where period = n - π[n-1], then\n * the string is composed of a repeating unit of length `period`.\n *\n * We check the suffix of the buffer (last `checkLength` chars) to detect\n * if a pattern of at least `minPatternLength` chars repeats at least\n * `minRepetitions` times.\n */\nfunction detectRepetition(text: string, minPatternLength: number, minRepetitions: number): boolean {\n // We check progressively larger windows to find repetitions\n // Start from the minimum detectable size\n const minWindow = minPatternLength * minRepetitions\n const maxWindow = Math.min(text.length, 2000) // Cap analysis window for performance\n\n // Check the tail of the text with increasing window sizes\n // Use a few strategic window sizes rather than checking every size\n const windowSizes = [minWindow, Math.floor(maxWindow * 0.5), maxWindow].filter(\n (w) => w >= minWindow && w <= text.length,\n )\n\n for (const windowSize of windowSizes) {\n const window = text.slice(-windowSize)\n const period = findRepeatingPeriod(window)\n\n if (period >= minPatternLength) {\n const repetitions = Math.floor(window.length / period)\n if (repetitions >= minRepetitions) {\n return true\n }\n }\n }\n\n return false\n}\n\n/**\n * Find the shortest repeating period in a string using KMP prefix function.\n * Returns the period length, or the string length if no repetition found.\n */\nfunction findRepeatingPeriod(s: string): number {\n const n = s.length\n if (n === 0) return 0\n\n // Compute KMP prefix function (failure function)\n const pi = new Int32Array(n)\n // pi[0] = 0 implicitly\n\n for (let i = 1; i < n; i++) {\n let j = pi[i - 1] ?? 0\n while (j > 0 && s[i] !== s[j]) {\n j = pi[j - 1] ?? 0\n }\n if (s[i] === s[j]) {\n j++\n }\n pi[i] = j\n }\n\n // The period of the string is n - pi[n-1]\n // If n is divisible by the period, the string is fully periodic\n const period = n - pi[n - 1]\n if (period < n && n % period === 0) {\n return period\n }\n\n // Check if the string is approximately periodic\n // (allows for a partial final repetition)\n if (period < n && pi[n - 1] >= period) {\n return period\n }\n\n return n // No repetition found\n}\n\n// ============================================================================\n// Convenience: Stream integration helper\n// ============================================================================\n\n/**\n * Create a repetition detector callback for use in stream processing.\n * Returns a function that accepts text deltas and logs a warning on first detection.\n */\nexport function createStreamRepetitionChecker(\n label: string,\n config?: Partial<RepetitionDetectorConfig>,\n): (textDelta: string) => boolean {\n const detector = new RepetitionDetector(config)\n let warned = false\n\n return (textDelta: string): boolean => {\n const isRepetitive = detector.feed(textDelta)\n if (isRepetitive && !warned) {\n warned = true\n consola.warn(`[RepetitionDetector] ${label}: Repetitive output detected in stream`)\n }\n return isRepetitive\n }\n}\n","/**\n * Deferred tool retry strategy.\n *\n * Handles 400 errors caused by deferred tools being referenced in the request\n * (e.g., in message history) before they've been loaded via tool_search.\n *\n * When context_management clears older tool_search activations but keeps\n * tool_use/tool_result pairs, or when the client compacts history, a deferred\n * tool may appear in the conversation without its tool_search \"load\" record.\n * The API then rejects the request with:\n * \"Tool reference 'X' not found in available tools\"\n *\n * This strategy parses the tool name from the error, marks it as non-deferred\n * (defer_loading: false) in the payload's tools array, and retries.\n */\n\nimport consola from \"consola\"\n\nimport type { ApiError } from \"~/lib/error\"\nimport type { Tool } from \"~/types/api/anthropic\"\n\nimport type { RetryAction, RetryContext, RetryStrategy } from \"../pipeline\"\n\n// ============================================================================\n// Error parsing\n// ============================================================================\n\n/** Pattern: \"Tool reference 'tool_name' not found in available tools\" */\nconst TOOL_REFERENCE_NOT_FOUND_PATTERN = /Tool reference '([^']+)' not found in available tools/\n\n/**\n * Extract tool name from a \"Tool reference not found\" error.\n * Returns the tool name or null if the error doesn't match.\n */\nexport function parseToolReferenceError(message: string): string | null {\n const match = TOOL_REFERENCE_NOT_FOUND_PATTERN.exec(message)\n return match?.[1] ?? null\n}\n\n// ============================================================================\n// Strategy\n// ============================================================================\n\n/**\n * Create a deferred tool retry strategy.\n *\n * When the API rejects a request because a deferred tool is referenced\n * in the message history, this strategy un-defers that tool and retries.\n */\nexport function createDeferredToolRetryStrategy<TPayload extends { tools?: Array<Tool> }>(): RetryStrategy<TPayload> {\n // Track tool names that have already been un-deferred across retries\n // to avoid infinite retry loops on the same tool\n const undeferredTools = new Set<string>()\n\n return {\n name: \"deferred-tool-retry\",\n\n canHandle(error: ApiError): boolean {\n if (error.type !== \"bad_request\" || error.status !== 400) return false\n\n const raw = error.raw\n if (!raw || typeof raw !== \"object\" || !(\"responseText\" in raw)) return false\n\n const responseText = (raw as { responseText: string }).responseText\n const toolName = parseToolReferenceFromResponse(responseText)\n if (!toolName) return false\n\n // Only handle if we haven't already retried for this tool\n return !undeferredTools.has(toolName)\n },\n\n handle(error: ApiError, currentPayload: TPayload, context: RetryContext<TPayload>): Promise<RetryAction<TPayload>> {\n const raw = error.raw as { responseText: string }\n const toolName = parseToolReferenceFromResponse(raw.responseText)\n\n if (!toolName || !currentPayload.tools) {\n return Promise.resolve({ action: \"abort\", error })\n }\n\n consola.debug(\n `[DeferredToolRetry] Tool \"${toolName}\" error.`\n + ` Payload has ${currentPayload.tools.length} tools: [${currentPayload.tools.map((t) => t.name).join(\", \")}]`,\n )\n\n // Find the tool in the payload\n const toolIndex = currentPayload.tools.findIndex((t) => t.name === toolName)\n\n if (toolIndex === -1) {\n // Safety net: tool may not be in the pipeline-visible payload\n // (e.g. preprocessTools wasn't called, or sanitize removed it).\n // Inject a minimal stub with no defer_loading so the API accepts\n // the tool_use reference on retry.\n consola.debug(`[DeferredToolRetry] Tool \"${toolName}\" not in payload, injecting non-deferred stub`)\n undeferredTools.add(toolName)\n\n const newTools = [\n ...currentPayload.tools,\n {\n name: toolName,\n description: \"Tool referenced in conversation history\",\n input_schema: { type: \"object\" as const, properties: {} },\n },\n ]\n\n return Promise.resolve({\n action: \"retry\",\n payload: { ...currentPayload, tools: newTools } as TPayload,\n })\n }\n\n // Mark as un-deferred and track it\n undeferredTools.add(toolName)\n\n const newTools = [...currentPayload.tools]\n newTools[toolIndex] = { ...newTools[toolIndex], defer_loading: false }\n\n consola.info(\n `[DeferredToolRetry] Attempt ${context.attempt + 1}/${context.maxRetries + 1}: `\n + `Un-deferring tool \"${toolName}\" and retrying`,\n )\n\n return Promise.resolve({\n action: \"retry\",\n payload: { ...currentPayload, tools: newTools },\n meta: { undeferredTool: toolName },\n })\n },\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Parse tool name from the error response JSON */\nfunction parseToolReferenceFromResponse(responseText: string): string | null {\n try {\n const parsed = JSON.parse(responseText) as { error?: { message?: string } }\n const message = parsed.error?.message\n if (!message) return null\n return parseToolReferenceError(message)\n } catch {\n // Try raw text match as fallback\n return parseToolReferenceError(responseText)\n }\n}\n","/**\n * Anthropic /v1/messages route handler.\n * Parses payload, resolves model, processes system prompt,\n * and orchestrates completion (streaming / non-streaming).\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { SSEStreamingApi, streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { MessageContent, ToolDefinition } from \"~/lib/history\"\nimport type { SseEventRecord } from \"~/lib/history/store\"\nimport type { MessagesPayload } from \"~/types/api/anthropic\"\n\nimport { executeWithAdaptiveRateLimit } from \"~/lib/adaptive-rate-limiter\"\nimport { type AnthropicAutoTruncateResult, autoTruncateAnthropic } from \"~/lib/anthropic/auto-truncate\"\nimport { createAnthropicMessages, type AnthropicMessageResponse } from \"~/lib/anthropic/client\"\nimport { preprocessTools } from \"~/lib/anthropic/message-tools\"\nimport { processAnthropicStream, supportsDirectAnthropicApi } from \"~/lib/anthropic/handlers\"\nimport { buildMessageMapping } from \"~/lib/anthropic/message-mapping\"\nimport { preprocessAnthropicMessages, sanitizeAnthropicMessages, type SanitizationStats } from \"~/lib/anthropic/sanitize\"\nimport { createAnthropicStreamAccumulator } from \"~/lib/anthropic/stream-accumulator\"\nimport { MAX_AUTO_TRUNCATE_RETRIES } from \"~/lib/auto-truncate\"\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { createStreamRepetitionChecker } from \"~/lib/repetition-detector\"\nimport { buildAnthropicResponseData, createTruncationMarker, prependMarkerToResponse } from \"~/lib/request\"\nimport { logPayloadSizeInfoAnthropic } from \"~/lib/request/payload\"\nimport { executeRequestPipeline, type FormatAdapter } from \"~/lib/request/pipeline\"\nimport { createAutoTruncateStrategy, type TruncateResult } from \"~/lib/request/strategies/auto-truncate\"\nimport { createDeferredToolRetryStrategy } from \"~/lib/request/strategies/deferred-tool-retry\"\nimport { createNetworkRetryStrategy } from \"~/lib/request/strategies/network-retry\"\nimport { createTokenRefreshStrategy } from \"~/lib/request/strategies/token-refresh\"\nimport { state } from \"~/lib/state\"\nimport { StreamIdleTimeoutError } from \"~/lib/stream\"\nimport { processAnthropicSystem } from \"~/lib/system-prompt\"\nimport { tuiLogger } from \"~/lib/tui\"\n\n// ============================================================================\n// Main entry point — Anthropic /v1/messages completion\n// ============================================================================\n\n/**\n * Handle an Anthropic /v1/messages request.\n * Parses payload, resolves model name, processes system prompt,\n * creates RequestContext, and routes to direct Anthropic API.\n */\nexport async function handleMessages(c: Context) {\n const anthropicPayload = await c.req.json<MessagesPayload>()\n\n // Resolve model name aliases and date-suffixed versions\n // e.g., \"haiku\" → \"claude-haiku-4.5\", \"claude-sonnet-4-20250514\" → \"claude-sonnet-4\"\n const clientModel = anthropicPayload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n anthropicPayload.model = resolvedModel\n }\n const clientModelName = clientModel !== resolvedModel ? clientModel : undefined\n\n // System prompt collection + config-based overrides (always active)\n if (anthropicPayload.system) {\n anthropicPayload.system = await processAnthropicSystem(anthropicPayload.system, anthropicPayload.model)\n }\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Route validation BEFORE creating RequestContext — prevents dangling history entries\n // when routing fails (reqCtx.create() triggers history insertion, and a subsequent throw\n // without reqCtx.fail() would leave an entry with no response)\n const routingDecision = supportsDirectAnthropicApi(anthropicPayload.model)\n if (!routingDecision.supported) {\n const msg = `Model \"${anthropicPayload.model}\" does not support /v1/messages: ${routingDecision.reason}`\n throw new HTTPError(msg, 400, msg)\n }\n consola.debug(`[AnthropicRouting] ${anthropicPayload.model}: ${routingDecision.reason}`)\n\n // Create request context — this triggers the \"created\" event → history consumer inserts entry\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"anthropic-messages\", tuiLogId })\n reqCtx.setOriginalRequest({\n // Use client's original model name (before resolution/overrides)\n model: clientModelName ?? anthropicPayload.model,\n messages: anthropicPayload.messages as unknown as Array<MessageContent>,\n stream: anthropicPayload.stream ?? false,\n tools: anthropicPayload.tools as Array<ToolDefinition> | undefined,\n system: anthropicPayload.system,\n payload: anthropicPayload,\n })\n\n // Update TUI tracker with model info (immediate feedback, don't wait for event loop)\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: anthropicPayload.model,\n ...(clientModelName && { clientModel: clientModelName }),\n })\n }\n\n // Phase 1: One-time preprocessing (idempotent, before routing)\n const preprocessed = preprocessAnthropicMessages(anthropicPayload.messages)\n anthropicPayload.messages = preprocessed.messages\n reqCtx.setPreprocessInfo({\n strippedReadTagCount: preprocessed.strippedReadTagCount,\n dedupedToolCallCount: preprocessed.dedupedToolCallCount,\n })\n\n return handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx)\n}\n\n// ============================================================================\n// Direct Anthropic completion orchestration\n// ============================================================================\n\n// Handle completion using direct Anthropic API (no translation needed)\nasync function handleDirectAnthropicCompletion(c: Context, anthropicPayload: MessagesPayload, reqCtx: RequestContext) {\n consola.debug(\"Using direct Anthropic API path for model:\", anthropicPayload.model)\n\n // Find model for auto-truncate and usage adjustment\n const selectedModel = state.modelIndex.get(anthropicPayload.model)\n\n // Preprocess tools: inject stubs for history-referenced tools, set defer_loading,\n // add tool_search. Must run BEFORE sanitize — processToolBlocks (in sanitize) uses\n // the tools array to validate tool_use references in messages.\n const toolPreprocessed = preprocessTools(anthropicPayload)\n\n // Always sanitize messages to filter orphaned tool_result/tool_use blocks\n const { payload: initialSanitized, stats: sanitizationStats } = sanitizeAnthropicMessages(toolPreprocessed)\n reqCtx.addSanitizationInfo(toSanitizationInfo(sanitizationStats))\n\n // Record sanitization/preprocessing if anything was modified\n const hasPreprocessing =\n reqCtx.preprocessInfo ?\n reqCtx.preprocessInfo.dedupedToolCallCount > 0 || reqCtx.preprocessInfo.strippedReadTagCount > 0\n : false\n if (\n sanitizationStats.totalBlocksRemoved > 0\n || sanitizationStats.systemReminderRemovals > 0\n || sanitizationStats.fixedNameCount > 0\n || hasPreprocessing\n ) {\n const messageMapping = buildMessageMapping(anthropicPayload.messages, initialSanitized.messages)\n reqCtx.setRewrites({\n rewrittenMessages: initialSanitized.messages as unknown as Array<MessageContent>,\n rewrittenSystem: typeof initialSanitized.system === \"string\" ? initialSanitized.system : undefined,\n messageMapping,\n })\n }\n\n // Set initial tracking tags for log display\n if (reqCtx.tuiLogId) {\n const tags: Array<string> = []\n if (initialSanitized.thinking && initialSanitized.thinking.type !== \"disabled\")\n tags.push(`thinking:${initialSanitized.thinking.type}`)\n if (tags.length > 0) tuiLogger.updateRequest(reqCtx.tuiLogId, { tags })\n }\n\n // Build adapter and strategy for the pipeline\n const adapter: FormatAdapter<MessagesPayload> = {\n format: \"anthropic-messages\",\n sanitize: (p) => sanitizeAnthropicMessages(preprocessTools(p)),\n execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p)),\n logPayloadSize: (p) => logPayloadSizeInfoAnthropic(p, selectedModel),\n }\n\n const strategies = [\n createNetworkRetryStrategy<MessagesPayload>(),\n createTokenRefreshStrategy<MessagesPayload>(),\n createDeferredToolRetryStrategy<MessagesPayload>(),\n createAutoTruncateStrategy<MessagesPayload>({\n truncate: (p, model, opts) => autoTruncateAnthropic(p, model, opts) as Promise<TruncateResult<MessagesPayload>>,\n resanitize: (p) => sanitizeAnthropicMessages(preprocessTools(p)),\n isEnabled: () => state.autoTruncate,\n label: \"Anthropic\",\n }),\n ]\n\n // Track truncation result for non-streaming response marker\n let truncateResult: AnthropicAutoTruncateResult | undefined\n\n try {\n const result = await executeRequestPipeline({\n adapter,\n strategies,\n payload: initialSanitized,\n originalPayload: anthropicPayload,\n model: selectedModel,\n maxRetries: MAX_AUTO_TRUNCATE_RETRIES,\n requestContext: reqCtx,\n onRetry: (_attempt, _strategyName, newPayload, meta) => {\n // Capture truncation result for response marker\n const retryTruncateResult = meta?.truncateResult as AnthropicAutoTruncateResult | undefined\n if (retryTruncateResult) {\n truncateResult = retryTruncateResult\n }\n\n // Record rewrites for the retried payload\n const retrySanitization = meta?.sanitization as SanitizationStats | undefined\n if (retrySanitization) {\n reqCtx.addSanitizationInfo(toSanitizationInfo(retrySanitization))\n }\n const retryMessageMapping = buildMessageMapping(anthropicPayload.messages, newPayload.messages)\n reqCtx.setRewrites({\n truncation:\n retryTruncateResult ?\n {\n removedMessageCount: retryTruncateResult.removedMessageCount,\n originalTokens: retryTruncateResult.originalTokens,\n compactedTokens: retryTruncateResult.compactedTokens,\n processingTimeMs: retryTruncateResult.processingTimeMs,\n }\n : undefined,\n rewrittenMessages: newPayload.messages as unknown as Array<MessageContent>,\n rewrittenSystem: typeof newPayload.system === \"string\" ? newPayload.system : undefined,\n messageMapping: retryMessageMapping,\n })\n\n // Update tracking tags\n if (reqCtx.tuiLogId) {\n const retryAttempt = (meta?.attempt as number | undefined) ?? 1\n const retryTags = [\"truncated\", `retry-${retryAttempt}`]\n if (newPayload.thinking && newPayload.thinking.type !== \"disabled\")\n retryTags.push(`thinking:${newPayload.thinking.type}`)\n tuiLogger.updateRequest(reqCtx.tuiLogId, { tags: retryTags })\n }\n },\n })\n\n const response = result.response\n const effectivePayload = result.effectivePayload as MessagesPayload\n\n // Check if response is streaming (AsyncIterable)\n if (Symbol.asyncIterator in (response as object)) {\n consola.debug(\"Streaming response from Copilot (direct Anthropic)\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n await handleDirectAnthropicStreamingResponse({\n stream,\n response: response as AsyncIterable<ServerSentEventMessage>,\n anthropicPayload: effectivePayload,\n reqCtx,\n clientAbortSignal: clientAbort.signal,\n })\n })\n }\n\n // Non-streaming response\n return handleDirectAnthropicNonStreamingResponse(c, response as AnthropicMessageResponse, reqCtx, truncateResult)\n } catch (error) {\n reqCtx.fail(anthropicPayload.model, error)\n throw error\n }\n}\n\n// ============================================================================\n// Response handlers (streaming / non-streaming)\n// ============================================================================\n\n/** Options for handleDirectAnthropicStreamingResponse */\ninterface DirectAnthropicStreamHandlerOptions {\n stream: SSEStreamingApi\n response: AsyncIterable<ServerSentEventMessage>\n anthropicPayload: MessagesPayload\n reqCtx: RequestContext\n /** Abort signal that fires when the downstream client disconnects */\n clientAbortSignal?: AbortSignal\n}\n\n/** Handle streaming direct Anthropic response (passthrough SSE events) */\nasync function handleDirectAnthropicStreamingResponse(opts: DirectAnthropicStreamHandlerOptions) {\n const { stream, response, anthropicPayload, reqCtx, clientAbortSignal } = opts\n const acc = createAnthropicStreamAccumulator()\n\n // Repetition detection — feed text deltas and log warning on first detection\n const checkRepetition = createStreamRepetitionChecker(anthropicPayload.model)\n\n // SSE event recording for debugging (excludes high-volume content_block_delta and ping)\n const sseEvents: Array<SseEventRecord> = []\n\n // Streaming metrics for TUI footer and debug timing\n const streamStartMs = Date.now()\n let bytesIn = 0\n let eventsIn = 0\n let currentBlockType = \"\"\n let firstEventLogged = false\n\n try {\n for await (const { raw: rawEvent, parsed } of processAnthropicStream(response, acc, clientAbortSignal)) {\n const dataLen = rawEvent.data?.length ?? 0\n bytesIn += dataLen\n eventsIn++\n\n // Record non-delta SSE events for history debugging\n if (parsed && parsed.type !== \"content_block_delta\" && parsed.type !== \"ping\") {\n sseEvents.push({\n offsetMs: Date.now() - streamStartMs,\n type: parsed.type,\n data: parsed,\n })\n }\n\n // Debug: log first event arrival (measures TTFB from stream perspective)\n if (!firstEventLogged) {\n const eventType = parsed?.type ?? \"keepalive\"\n consola.debug(`[Stream] First event at +${Date.now() - streamStartMs}ms (${eventType})`)\n firstEventLogged = true\n }\n\n // Debug: log content block boundaries with timing\n if (parsed?.type === \"content_block_start\") {\n currentBlockType = (parsed.content_block as { type: string }).type\n consola.debug(`[Stream] Block #${parsed.index} start: ${currentBlockType} at +${Date.now() - streamStartMs}ms`)\n } else if (parsed?.type === \"content_block_stop\") {\n const offset = Date.now() - streamStartMs\n consola.debug(\n `[Stream] Block #${parsed.index} stop (${currentBlockType}) at +${offset}ms, cumulative ↓${bytesIn}B ${eventsIn}ev`,\n )\n currentBlockType = \"\"\n }\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n streamBlockType: currentBlockType,\n })\n }\n\n // Check for repetitive output in text deltas\n if (parsed?.type === \"content_block_delta\") {\n const delta = parsed.delta as { type: string; text?: string }\n if (delta.type === \"text_delta\" && delta.text) {\n checkRepetition(delta.text)\n }\n }\n\n // Forward every event to client — proxy preserves upstream data\n await stream.writeSSE({\n data: rawEvent.data ?? \"\",\n event: rawEvent.event,\n id: rawEvent.id !== undefined ? String(rawEvent.id) : undefined,\n retry: rawEvent.retry,\n })\n }\n\n // Debug: stream completion summary\n consola.debug(`[Stream] Completed: ↓${bytesIn}B ${eventsIn}ev in ${Date.now() - streamStartMs}ms`)\n\n // Record SSE events for history debugging (must be before complete/fail which calls toHistoryEntry)\n reqCtx.setSseEvents(sseEvents)\n\n if (acc.streamError) {\n reqCtx.fail(acc.model || anthropicPayload.model, new Error(`${acc.streamError.type}: ${acc.streamError.message}`))\n } else {\n const responseData = buildAnthropicResponseData(acc, anthropicPayload.model)\n reqCtx.complete(responseData)\n }\n } catch (error) {\n consola.error(\"Direct Anthropic stream error:\", error)\n reqCtx.fail(acc.model || anthropicPayload.model, error)\n\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorType = error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"api_error\"\n await stream.writeSSE({\n event: \"error\",\n data: JSON.stringify({\n type: \"error\",\n error: { type: errorType, message: errorMessage },\n }),\n })\n }\n}\n\n/** Handle non-streaming direct Anthropic response */\nfunction handleDirectAnthropicNonStreamingResponse(\n c: Context,\n response: AnthropicMessageResponse,\n reqCtx: RequestContext,\n truncateResult: AnthropicAutoTruncateResult | undefined,\n) {\n reqCtx.complete({\n success: true,\n model: response.model,\n usage: {\n input_tokens: response.usage.input_tokens,\n output_tokens: response.usage.output_tokens,\n cache_read_input_tokens: response.usage.cache_read_input_tokens ?? undefined,\n cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? undefined,\n },\n stop_reason: response.stop_reason ?? undefined,\n content: { role: \"assistant\", content: response.content },\n })\n\n // Add truncation marker to response if verbose mode and truncation occurred\n let finalResponse = response\n if (state.verbose && truncateResult?.wasTruncated) {\n const marker = createTruncationMarker(truncateResult)\n finalResponse = prependMarkerToResponse(response, marker)\n }\n\n return c.json(finalResponse)\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Convert SanitizationStats to the format expected by rewrites */\nfunction toSanitizationInfo(stats: SanitizationStats) {\n return {\n totalBlocksRemoved: stats.totalBlocksRemoved,\n orphanedToolUseCount: stats.orphanedToolUseCount,\n orphanedToolResultCount: stats.orphanedToolResultCount,\n fixedNameCount: stats.fixedNameCount,\n emptyTextBlocksRemoved: stats.emptyTextBlocksRemoved,\n systemReminderRemovals: stats.systemReminderRemovals,\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCountTokens } from \"./count-tokens-handler\"\nimport { handleMessages } from \"./handler\"\n\nexport const messagesRoutes = new Hono()\n\nmessagesRoutes.post(\"/\", async (c) => {\n try {\n return await handleMessages(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n\nmessagesRoutes.post(\"/count_tokens\", async (c) => {\n try {\n return await handleCountTokens(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport type { Model } from \"~/lib/models/client\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { cacheModels } from \"~/lib/models/client\"\nimport { state } from \"~/lib/state\"\n\nexport const modelsRoutes = new Hono()\n\nconst EPOCH_ISO = new Date(0).toISOString()\n\nfunction formatModel(model: Model) {\n return {\n id: model.id,\n object: \"model\" as const,\n type: \"model\" as const,\n created: 0, // No date available from source\n created_at: EPOCH_ISO, // No date available from source\n owned_by: model.vendor,\n display_name: model.name,\n capabilities: model.capabilities,\n }\n}\n\nmodelsRoutes.get(\"/\", async (c) => {\n try {\n if (!state.models) {\n // This should be handled by startup logic, but as a fallback.\n await cacheModels()\n }\n\n const models = state.models?.data.map((m) => formatModel(m))\n\n return c.json({\n object: \"list\",\n data: models,\n has_more: false,\n })\n } catch (error) {\n return forwardError(c, error)\n }\n})\n\nmodelsRoutes.get(\"/:model\", async (c) => {\n try {\n if (!state.models) {\n await cacheModels()\n }\n\n const modelId = c.req.param(\"model\")\n const model = state.modelIndex.get(modelId)\n\n if (!model) {\n return c.json(\n {\n error: {\n message: `The model '${modelId}' does not exist`,\n type: \"invalid_request_error\",\n param: \"model\",\n code: \"model_not_found\",\n },\n },\n 404,\n )\n }\n\n return c.json(formatModel(model))\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","/**\n * Handler for inbound OpenAI Responses API requests.\n * Routes directly to Copilot /responses endpoint.\n * Models that do not support /responses get a 400 error.\n */\n\nimport type { ServerSentEventMessage } from \"fetch-event-stream\"\nimport type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { streamSSE } from \"hono/streaming\"\n\nimport type { RequestContext } from \"~/lib/context/request\"\nimport type { ResponsesPayload, ResponsesResponse, ResponsesStreamEvent } from \"~/types/api/openai-responses\"\n\nimport { getRequestContextManager } from \"~/lib/context/manager\"\nimport { HTTPError } from \"~/lib/error\"\nimport { ENDPOINT, isEndpointSupported } from \"~/lib/models/endpoint\"\nimport { resolveModelName } from \"~/lib/models/resolver\"\nimport { responsesInputToMessages, responsesOutputToContent } from \"~/lib/openai/responses-conversion\"\nimport {\n accumulateResponsesStreamEvent,\n createResponsesStreamAccumulator,\n} from \"~/lib/openai/responses-stream-accumulator\"\nimport { executeRequestPipeline } from \"~/lib/request/pipeline\"\nimport { buildResponsesResponseData } from \"~/lib/request/recording\"\nimport { getShutdownSignal } from \"~/lib/shutdown\"\nimport { state } from \"~/lib/state\"\nimport { STREAM_ABORTED, StreamIdleTimeoutError, combineAbortSignals, raceIteratorNext } from \"~/lib/stream\"\nimport { processResponsesInstructions } from \"~/lib/system-prompt\"\nimport { tuiLogger } from \"~/lib/tui\"\n\nimport { createResponsesAdapter, createResponsesStrategies } from \"./pipeline\"\n\n// Re-export conversion functions (other modules may import from ./handler)\n\n/** Handle an inbound Responses API request */\nexport async function handleResponses(c: Context) {\n const payload = await c.req.json<ResponsesPayload>()\n\n // Resolve model name aliases\n const clientModel = payload.model\n const resolvedModel = resolveModelName(clientModel)\n if (resolvedModel !== clientModel) {\n consola.debug(`Model name resolved: ${clientModel} → ${resolvedModel}`)\n payload.model = resolvedModel\n }\n\n // Validate that the model supports /responses endpoint\n const selectedModel = state.modelIndex.get(payload.model)\n if (!isEndpointSupported(selectedModel, ENDPOINT.RESPONSES)) {\n const msg = `Model \"${payload.model}\" does not support the ${ENDPOINT.RESPONSES} endpoint`\n throw new HTTPError(msg, 400, msg)\n }\n\n // Process system prompt (overrides, prepend, append from config)\n payload.instructions = await processResponsesInstructions(payload.instructions, payload.model)\n\n // Get tracking ID\n const tuiLogId = c.get(\"tuiLogId\") as string | undefined\n\n // Create request context (Responses API is a distinct OpenAI-format endpoint)\n const manager = getRequestContextManager()\n const reqCtx = manager.create({ endpoint: \"openai-responses\", tuiLogId })\n\n // Record original request for history\n reqCtx.setOriginalRequest({\n model: clientModel,\n messages: responsesInputToMessages(payload.input),\n stream: payload.stream ?? false,\n tools: payload.tools,\n system: payload.instructions ?? undefined,\n payload,\n })\n\n // Update TUI tracker with model info\n if (tuiLogId) {\n tuiLogger.updateRequest(tuiLogId, {\n model: payload.model,\n ...(clientModel !== payload.model && { clientModel }),\n })\n }\n\n return handleDirectResponses({ c, payload, reqCtx })\n}\n\n// ============================================================================\n// Direct passthrough to /responses endpoint\n// ============================================================================\n\ninterface ResponsesHandlerOptions {\n c: Context\n payload: ResponsesPayload\n reqCtx: RequestContext\n}\n\n/** Pass through to Copilot /responses endpoint directly */\nasync function handleDirectResponses(opts: ResponsesHandlerOptions) {\n const { c, payload, reqCtx } = opts\n\n const adapter = createResponsesAdapter()\n const strategies = createResponsesStrategies()\n const selectedModel = state.modelIndex.get(payload.model)\n\n try {\n const pipelineResult = await executeRequestPipeline({\n adapter,\n strategies,\n payload,\n originalPayload: payload,\n model: selectedModel,\n maxRetries: 1,\n requestContext: reqCtx,\n })\n\n const response = pipelineResult.response\n reqCtx.addQueueWaitMs(pipelineResult.queueWaitMs)\n\n // Determine streaming vs non-streaming based on the request payload,\n // not by inspecting the response shape (isNonStreaming checks for \"choices\"\n // which only exists in Chat Completions format, not Responses format)\n if (!payload.stream) {\n // Non-streaming response — build content from output items\n const responsesResponse = response as ResponsesResponse\n const content = responsesOutputToContent(responsesResponse.output)\n\n reqCtx.complete({\n success: true,\n model: responsesResponse.model,\n usage: {\n input_tokens: responsesResponse.usage?.input_tokens ?? 0,\n output_tokens: responsesResponse.usage?.output_tokens ?? 0,\n ...(responsesResponse.usage?.input_tokens_details?.cached_tokens && {\n cache_read_input_tokens: responsesResponse.usage.input_tokens_details.cached_tokens,\n }),\n },\n stop_reason: responsesResponse.status,\n content,\n })\n return c.json(responsesResponse)\n }\n\n // Streaming response — forward Responses SSE events directly\n consola.debug(\"Streaming response (/responses)\")\n reqCtx.transition(\"streaming\")\n\n return streamSSE(c, async (stream) => {\n const clientAbort = new AbortController()\n stream.onAbort(() => clientAbort.abort())\n\n const acc = createResponsesStreamAccumulator()\n const idleTimeoutMs = state.streamIdleTimeout * 1000\n\n // Streaming metrics for TUI footer\n let bytesIn = 0\n let eventsIn = 0\n\n try {\n const iterator = (response as AsyncIterable<ServerSentEventMessage>)[Symbol.asyncIterator]()\n const abortSignal = combineAbortSignals(getShutdownSignal(), clientAbort.signal)\n\n for (;;) {\n const result = await raceIteratorNext(iterator.next(), { idleTimeoutMs, abortSignal })\n\n if (result === STREAM_ABORTED) break\n if (result.done) break\n\n const rawEvent = result.value\n\n if (rawEvent.data && rawEvent.data !== \"[DONE]\") {\n bytesIn += rawEvent.data.length\n eventsIn++\n\n // Update TUI footer with streaming progress\n if (reqCtx.tuiLogId) {\n tuiLogger.updateRequest(reqCtx.tuiLogId, {\n streamBytesIn: bytesIn,\n streamEventsIn: eventsIn,\n })\n }\n\n try {\n const event = JSON.parse(rawEvent.data) as ResponsesStreamEvent\n accumulateResponsesStreamEvent(event, acc)\n\n // Forward the event as-is\n await stream.writeSSE({ data: rawEvent.data })\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n // Use shared recording utility for consistent response data\n const responseData = buildResponsesResponseData(acc, payload.model)\n reqCtx.complete(responseData)\n } catch (error) {\n consola.error(\"[Responses] Stream error:\", error)\n reqCtx.fail(acc.model || payload.model, error)\n\n // Send error to client as final SSE event\n const errorMessage = error instanceof Error ? error.message : String(error)\n await stream.writeSSE({\n data: JSON.stringify({\n error: {\n message: errorMessage,\n type: error instanceof StreamIdleTimeoutError ? \"timeout_error\" : \"server_error\",\n },\n }),\n })\n }\n })\n } catch (error) {\n reqCtx.fail(payload.model, error)\n throw error\n }\n}\n\nexport { responsesInputToMessages, responsesOutputToContent } from \"~/lib/openai/responses-conversion\"\n","/**\n * OpenAI Responses API route definition.\n * Handles POST /responses and POST /v1/responses.\n */\n\nimport { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleResponses } from \"./handler\"\n\nexport const responsesRoutes = new Hono()\n\nresponsesRoutes.post(\"/\", async (c) => {\n try {\n return await handleResponses(c)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const tokenRoutes = new Hono()\n\ntokenRoutes.get(\"/\", (c) => {\n try {\n return c.json({\n token: state.copilotToken,\n })\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { getCopilotUsage } from \"~/lib/token/copilot-client\"\n\nexport const usageRoutes = new Hono()\n\nusageRoutes.get(\"/\", async (c) => {\n try {\n const usage = await getCopilotUsage()\n return c.json(usage)\n } catch (error) {\n return forwardError(c, error)\n }\n})\n","/**\n * Centralized route registration.\n * All API routes are registered here instead of scattered in server.ts.\n */\n\nimport type { Hono } from \"hono\"\n\nimport { chatCompletionRoutes } from \"./chat-completions/route\"\nimport { embeddingsRoutes } from \"./embeddings/route\"\nimport { eventLoggingRoutes } from \"./event-logging/route\"\nimport { historyRoutes } from \"./history/route\"\nimport { messagesRoutes } from \"./messages/route\"\nimport { modelsRoutes } from \"./models/route\"\nimport { responsesRoutes } from \"./responses/route\"\nimport { tokenRoutes } from \"./token/route\"\nimport { usageRoutes } from \"./usage/route\"\n\n/**\n * Register all API routes on the given Hono app.\n */\nexport function registerRoutes(app: Hono) {\n // OpenAI-compatible endpoints\n app.route(\"/chat/completions\", chatCompletionRoutes)\n app.route(\"/models\", modelsRoutes)\n app.route(\"/embeddings\", embeddingsRoutes)\n app.route(\"/responses\", responsesRoutes)\n\n // OpenAI-compatible with /v1 prefix\n app.route(\"/v1/chat/completions\", chatCompletionRoutes)\n app.route(\"/v1/models\", modelsRoutes)\n app.route(\"/v1/embeddings\", embeddingsRoutes)\n app.route(\"/v1/responses\", responsesRoutes)\n\n // Anthropic-compatible endpoints\n app.route(\"/v1/messages\", messagesRoutes)\n app.route(\"/api/event_logging\", eventLoggingRoutes)\n\n app.route(\"/usage\", usageRoutes)\n app.route(\"/token\", tokenRoutes)\n\n // History viewer (optional, enabled with --history flag)\n app.route(\"/history\", historyRoutes)\n}\n","import consola from \"consola\"\nimport { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport { trimTrailingSlash } from \"hono/trailing-slash\"\n\nimport { applyConfigToState } from \"./lib/config/config\"\nimport { forwardError } from \"./lib/error\"\nimport { state } from \"./lib/state\"\nimport { tuiMiddleware } from \"./lib/tui\"\nimport { registerRoutes } from \"./routes\"\n\nexport const server = new Hono()\n\n// Global error handler - catches any unhandled errors from route handlers\nserver.onError((error, c) => {\n // WebSocket errors after upgrade - connection is already upgraded,\n // cannot send HTTP response; log at debug level since these are normal\n // (e.g. client disconnect)\n if (c.req.header(\"upgrade\")?.toLowerCase() === \"websocket\") {\n consola.debug(\"WebSocket error:\", error)\n return c.text(\"\", 500)\n }\n\n consola.error(`Unhandled route error in ${c.req.method} ${c.req.path}:`, error)\n return forwardError(c, error)\n})\n\n// Browser auto-requests (favicon, devtools config) — return 204 silently\n// to avoid [FAIL] 404 noise in TUI logs.\nconst browserProbePaths = new Set([\"/favicon.ico\", \"/.well-known/appspecific/com.chrome.devtools.json\"])\n\nserver.notFound((c) => {\n if (browserProbePaths.has(c.req.path)) {\n return c.body(null, 204)\n }\n return c.json({ error: \"Not Found\" }, 404)\n})\n\n// Config hot-reload: re-apply config.yaml settings before each request.\n// loadConfig() is mtime-cached — only costs one stat() syscall when config is unchanged.\nserver.use(async (_c, next) => {\n await applyConfigToState()\n await next()\n})\n\nserver.use(tuiMiddleware())\nserver.use(cors())\nserver.use(trimTrailingSlash())\n\nserver.get(\"/\", (c) => c.text(\"Server running\"))\n\n// Health check endpoint for container orchestration (Docker, Kubernetes)\nserver.get(\"/health\", (c) => {\n const healthy = Boolean(state.copilotToken && state.githubToken)\n return c.json(\n {\n status: healthy ? \"healthy\" : \"unhealthy\",\n checks: {\n copilotToken: Boolean(state.copilotToken),\n githubToken: Boolean(state.githubToken),\n models: Boolean(state.models),\n },\n },\n healthy ? 200 : 503,\n )\n})\n\n// Register all API routes\nregisterRoutes(server)\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport pc from \"picocolors\"\nimport { serve, type ServerHandler } from \"srvx\"\n\nimport type { Model } from \"./lib/models/client\"\n\nimport packageJson from \"../package.json\"\nimport { initAdaptiveRateLimiter } from \"./lib/adaptive-rate-limiter\"\nimport { loadPersistedLimits } from \"./lib/auto-truncate\"\nimport { applyConfigToState } from \"./lib/config/config\"\nimport { ensurePaths } from \"./lib/config/paths\"\nimport { registerContextConsumers } from \"./lib/context/consumers\"\nimport { initRequestContextManager } from \"./lib/context/manager\"\nimport { cacheVSCodeVersion } from \"./lib/copilot-api\"\nimport { initHistory } from \"./lib/history\"\nimport { cacheModels } from \"./lib/models/client\"\nimport { getEffectiveEndpoints } from \"./lib/models/endpoint\"\nimport { resolveModelName } from \"./lib/models/resolver\"\nimport { formatProxyDisplay, initProxy } from \"./lib/proxy\"\nimport { setServerInstance, setupShutdownHandlers, waitForShutdown } from \"./lib/shutdown\"\nimport { state } from \"./lib/state\"\nimport { initTokenManagers } from \"./lib/token\"\nimport { initTuiLogger } from \"./lib/tui\"\nimport { initHistoryWebSocket } from \"./routes/history/route\"\nimport { initResponsesWebSocket } from \"./routes/responses/ws\"\nimport { server } from \"./server\"\n\n/** Format limit values as \"Xk\" or \"?\" if not available */\nfunction formatLimit(value?: number): string {\n return value ? `${Math.round(value / 1000)}k` : \"?\"\n}\n\n/**\n * Format a model as 3 lines: main info, features, and supported endpoints.\n *\n * Example output:\n * - claude-opus-4.6-1m (Anthropic) ctx:1000k prp: 936k out: 64k\n * features: adaptive-thinking, thinking, streaming, vision, tool-calls\n * endpoints: /v1/messages, /chat/completions\n */\nfunction formatModelInfo(model: Model): string {\n const limits = model.capabilities?.limits\n const supports = model.capabilities?.supports\n\n const contextK = formatLimit(limits?.max_context_window_tokens)\n const promptK = formatLimit(limits?.max_prompt_tokens)\n const outputK = formatLimit(limits?.max_output_tokens)\n\n // Main line: model id (vendor) + token limits\n const label = `${model.id} (${model.vendor})`\n const padded = label.length > 45 ? `${label.slice(0, 42)}...` : label.padEnd(45)\n const mainLine =\n ` - ${padded} ` + `ctx:${contextK.padStart(5)} ` + `prp:${promptK.padStart(5)} ` + `out:${outputK.padStart(5)}`\n\n // Features line: boolean capabilities + inferred features\n const features = [\n ...Object.entries(supports ?? {})\n .filter(([, value]) => value === true)\n .map(([key]) => key.replaceAll(\"_\", \"-\")),\n supports?.max_thinking_budget && \"thinking\",\n model.capabilities?.type === \"embeddings\" && \"embeddings\",\n model.preview && \"preview\",\n ]\n .filter(Boolean)\n .join(\", \")\n const featLine = features ? pc.dim(` features: ${features}`) : \"\"\n\n // Endpoints line: from supported_endpoints or inferred from capabilities.type\n const endpoints = formatEndpoints(getEffectiveEndpoints(model))\n const endpLine = pc.dim(` endpoints: ${endpoints}`)\n\n return [mainLine, featLine, endpLine].filter(Boolean).join(\"\\n\")\n}\n\n/** Format endpoint paths for display */\nfunction formatEndpoints(endpoints?: Array<string>): string {\n if (!endpoints || endpoints.length === 0) return \"(unknown)\"\n return endpoints.join(\", \")\n}\n\n/** Parse an integer from a string, returning a default if the result is NaN. */\nfunction parseIntOrDefault(value: string, defaultValue: number): number {\n const parsed = Number.parseInt(value, 10)\n return Number.isFinite(parsed) ? parsed : defaultValue\n}\n\ninterface RunServerOptions {\n port: number\n host?: string\n verbose: boolean\n accountType: \"individual\" | \"business\" | \"enterprise\"\n // Adaptive rate limiting (disabled if rateLimit is false)\n rateLimit: boolean\n githubToken?: string\n showGitHubToken: boolean\n /** Explicit proxy URL (CLI --proxy). Takes precedence over config.yaml and env vars. */\n proxy?: string\n httpProxyFromEnv: boolean\n autoTruncate: boolean\n}\n\nexport async function runServer(options: RunServerOptions): Promise<void> {\n // ===========================================================================\n // Phase 1: Logging and Verbose Mode\n // ===========================================================================\n if (options.verbose) {\n consola.level = 5\n state.verbose = true\n }\n\n // ===========================================================================\n // Phase 2: Version and Configuration Display\n // ===========================================================================\n consola.info(`copilot-api v${packageJson.version}`)\n\n // Set global state from CLI options\n state.accountType = options.accountType\n state.showGitHubToken = options.showGitHubToken\n state.autoTruncate = options.autoTruncate\n\n // ===========================================================================\n // Phase 2.5: Load config.yaml and apply runtime settings\n // ===========================================================================\n // ensurePaths must run first so the config directory exists\n await ensurePaths()\n\n const config = await applyConfigToState()\n\n // ===========================================================================\n // Phase 2.6: Initialize proxy (must be before any network requests)\n // ===========================================================================\n // Priority: CLI --proxy > config.yaml proxy > env vars (--http-proxy-from-env)\n const proxyUrl = options.proxy ?? config.proxy\n initProxy({ url: proxyUrl, fromEnv: !proxyUrl && options.httpProxyFromEnv })\n\n // Log configuration status for all features\n // Source labels: --flag for CLI options, [key] for config.yaml options\n const configLines: Array<string> = []\n const on = (source: string, label: string, detail?: string) =>\n configLines.push(` ${pc.dim(source)} ${label}: ON${detail ? ` ${pc.dim(`(${detail})`)}` : \"\"}`)\n const off = (source: string, label: string) => configLines.push(pc.dim(` ${source} ${label}: OFF`))\n const toggle = (flag: boolean | undefined, source: string, label: string, detail?: string) =>\n flag ? on(source, label, detail) : off(source, label)\n\n toggle(options.verbose, \"--verbose\", \"Verbose logging\")\n configLines.push(` ${pc.dim(\"--account-type\")} Account type: ${options.accountType}`)\n\n // Rate limiter: merge CLI flag (enable/disable) with config.yaml sub-parameters\n const rlConfig = config.rate_limiter\n const rlRetryInterval = rlConfig?.retry_interval ?? 10\n const rlRequestInterval = rlConfig?.request_interval ?? 10\n const rlRecoveryTimeout = rlConfig?.recovery_timeout ?? 10\n const rlConsecutiveSuccesses = rlConfig?.consecutive_successes ?? 5\n\n if (options.rateLimit) {\n on(\n \"--rate-limit\",\n \"Rate limiter\",\n `retry=${rlRetryInterval}s interval=${rlRequestInterval}s recovery=${rlRecoveryTimeout}m successes=${rlConsecutiveSuccesses}`,\n )\n } else {\n off(\"--rate-limit\", \"Rate limiter\")\n }\n\n if (options.autoTruncate) {\n const detail = state.compressToolResultsBeforeTruncate ? \"reactive, compress\" : \"reactive\"\n on(\"--auto-truncate\", \"Auto-truncate\", detail)\n } else {\n off(\"--auto-truncate\", \"Auto-truncate\")\n }\n\n if (state.compressToolResultsBeforeTruncate && !options.autoTruncate) {\n // Only show separately if auto-truncate is off but compress is on (unusual)\n on(\"[compress_tool_results_before_truncate]\", \"Compress tool results\")\n }\n toggle(state.convertServerToolsToCustom, \"[anthropic.convert_server_tools_to_custom]\", \"Convert server tools\")\n if (proxyUrl) {\n on(options.proxy ? \"--proxy\" : \"[proxy]\", \"Proxy\", formatProxyDisplay(proxyUrl))\n } else if (options.httpProxyFromEnv) {\n on(\"--http-proxy-from-env\", \"Proxy\", \"from env\")\n } else {\n off(\"--http-proxy-from-env\", \"Proxy\")\n }\n toggle(options.showGitHubToken, \"--show-github-token\", \"Show GitHub token\")\n const overrideEntries = Object.entries(state.modelOverrides)\n if (overrideEntries.length > 0) {\n on(\"[model_overrides]\", \"Model overrides\")\n } else {\n off(\"[model_overrides]\", \"Model overrides\")\n }\n if (state.dedupToolCalls) {\n on(\"[anthropic.dedup_tool_calls]\", \"Dedup tool calls\", `mode: ${state.dedupToolCalls}`)\n } else {\n off(\"[anthropic.dedup_tool_calls]\", \"Dedup tool calls\")\n }\n toggle(state.truncateReadToolResult, \"[anthropic.truncate_read_tool_result]\", \"Truncate Read tool result\")\n if (state.rewriteSystemReminders === true) {\n on(\"[anthropic.rewrite_system_reminders]\", \"Rewrite system reminders\", \"remove all\")\n } else if (state.rewriteSystemReminders === false) {\n off(\"[anthropic.rewrite_system_reminders]\", \"Rewrite system reminders\")\n } else {\n on(\n \"[anthropic.rewrite_system_reminders]\",\n \"Rewrite system reminders\",\n `${state.rewriteSystemReminders.length} rules`,\n )\n }\n\n // Show timeout settings (always show since streamIdleTimeout defaults to 300)\n {\n const parts: Array<string> = []\n if (state.fetchTimeout > 0) parts.push(`fetch=${state.fetchTimeout}s`)\n parts.push(`stream-idle=${state.streamIdleTimeout}s`)\n if (state.staleRequestMaxAge > 0) parts.push(`stale-reaper=${state.staleRequestMaxAge}s`)\n on(\"[timeouts]\", \"Timeouts\", parts.join(\", \"))\n }\n\n const historyLimitText = state.historyLimit === 0 ? \"unlimited\" : `max=${state.historyLimit}`\n on(\"[history_limit]\", \"History\", historyLimitText)\n\n consola.info(`Configuration:\\n${configLines.join(\"\\n\")}`)\n\n // ===========================================================================\n // Phase 3: Initialize Internal Services (rate limiter, history)\n // ===========================================================================\n if (options.rateLimit) {\n initAdaptiveRateLimiter({\n baseRetryIntervalSeconds: rlRetryInterval,\n requestIntervalSeconds: rlRequestInterval,\n recoveryTimeoutMinutes: rlRecoveryTimeout,\n consecutiveSuccessesForRecovery: rlConsecutiveSuccesses,\n })\n }\n\n initHistory(true, state.historyLimit)\n\n // Initialize request context manager and register event consumers\n // Must be after initHistory so history store is ready to receive events\n const contextManager = initRequestContextManager()\n registerContextConsumers(contextManager)\n\n // Start stale request reaper (periodic cleanup of stuck active contexts)\n contextManager.startReaper()\n\n // Initialize TUI request tracking (renderer was created in main.ts via initConsolaReporter)\n initTuiLogger()\n\n // ===========================================================================\n // Phase 4: External Dependencies (network)\n // ===========================================================================\n // cacheVSCodeVersion is independent network call\n await cacheVSCodeVersion()\n\n // Initialize token management and authenticate\n await initTokenManagers({ cliToken: options.githubToken })\n\n // Fetch available models from Copilot API\n try {\n await cacheModels()\n } catch (error) {\n consola.warn(\"Failed to fetch models from Copilot API:\", error instanceof Error ? error.message : error)\n }\n\n consola.info(`Available models:\\n${state.models?.data.map((m) => formatModelInfo(m)).join(\"\\n\")}`)\n\n // Load previously learned auto-truncate limits (calibration + token limits)\n await loadPersistedLimits()\n\n // Show resolved model overrides after models are fetched\n const availableIds = state.models?.data.map((m) => m.id) ?? []\n const overrideLines = Object.entries(state.modelOverrides)\n .map(([from, to]) => {\n const resolved = resolveModelName(from)\n const colorize = (name: string) => (availableIds.includes(name) ? name : pc.red(name))\n if (resolved !== to) {\n return ` - ${from} → ${to} ${pc.dim(`(→ ${colorize(resolved)})`)}`\n }\n return ` - ${from} → ${colorize(to)}`\n })\n .join(\"\\n\")\n if (overrideLines) {\n consola.info(`Model overrides:\\n${overrideLines}`)\n }\n\n // ===========================================================================\n // Phase 5: Start Server\n // ===========================================================================\n const displayHost = options.host ?? \"localhost\"\n const serverUrl = `http://${displayHost}:${options.port}`\n\n // Bun + srvx workaround: srvx's bun adapter wraps the fetch handler and\n // drops the `server` parameter that Bun.serve normally passes as the second\n // arg to fetch. hono/bun's upgradeWebSocket needs `c.env.server` to call\n // server.upgrade(). srvx stores the server on `request.runtime.bun.server`,\n // so we forward it into Hono's env via middleware.\n // Must be registered BEFORE any WebSocket routes so Hono dispatches it first.\n if (typeof globalThis.Bun !== \"undefined\") {\n server.use(\"*\", async (c, next) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const runtime = (c.req.raw as any).runtime as { bun?: { server?: unknown } } | undefined\n if (runtime?.bun?.server) {\n c.env = { server: runtime.bun.server }\n }\n await next()\n })\n }\n\n // Initialize WebSocket support (registers WS upgrade routes)\n // Must be called after the Bun server injection middleware above, and before\n // the srvx serve() call so routes are ready when the server starts.\n const injectHistoryWs = await initHistoryWebSocket(server)\n const injectResponsesWs = await initResponsesWebSocket(server)\n\n consola.box(\n `Web UI:\\n🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage\\n📜 History UI: ${serverUrl}/history`,\n )\n\n // Import hono/bun websocket handler for Bun's WebSocket support.\n // Bun.serve() requires an explicit `websocket` handler object alongside `fetch`\n // for WebSocket upgrades to work. Without this, server.upgrade() in\n // hono/bun's upgradeWebSocket middleware silently fails.\n const bunWebSocket = typeof globalThis.Bun !== \"undefined\" ? (await import(\"hono/bun\")).websocket : undefined\n\n let serverInstance\n try {\n serverInstance = serve({\n fetch: server.fetch as ServerHandler,\n port: options.port,\n hostname: options.host,\n reusePort: true,\n // Disable srvx's built-in graceful shutdown — we have our own\n // multi-phase shutdown handler (see lib/shutdown.ts) that provides\n // request draining, abort signaling, and WebSocket cleanup.\n gracefulShutdown: false,\n bun: {\n // Default idleTimeout is 10s, too short for LLM streaming responses\n idleTimeout: 255, // seconds (Bun max)\n ...(bunWebSocket && { websocket: bunWebSocket }),\n },\n })\n } catch (error) {\n consola.error(`Failed to start server on port ${options.port}. Is the port already in use?`, error)\n process.exit(1)\n }\n\n // Store server instance and register signal handlers for graceful shutdown.\n // Order matters: setServerInstance must be called before setupShutdownHandlers\n // so the handler has access to the server instance when closing.\n setServerInstance(serverInstance)\n setupShutdownHandlers()\n\n // Inject WebSocket upgrade handlers into Node.js HTTP server (no-op under Bun)\n if (injectHistoryWs || injectResponsesWs) {\n const nodeServer = serverInstance.node?.server\n if (nodeServer && \"on\" in nodeServer) {\n const ns = nodeServer as import(\"node:http\").Server\n injectHistoryWs?.(ns)\n injectResponsesWs?.(ns)\n }\n }\n\n // Block until a shutdown signal (SIGINT/SIGTERM) is received.\n // This prevents runMain() from returning, which would trigger\n // process.exit(0) in main.ts (needed for one-shot commands).\n await waitForShutdown()\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the Copilot API server\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port to listen on\",\n },\n host: {\n alias: \"H\",\n type: \"string\",\n description: \"Host/interface to bind to (e.g., 127.0.0.1 for localhost only, 0.0.0.0 for all interfaces)\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n \"rate-limit\": {\n type: \"boolean\",\n default: true,\n description: \"Adaptive rate limiting (disable with --no-rate-limit)\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description: \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"show-github-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token in logs (use --verbose for Copilot token refresh logs)\",\n },\n proxy: {\n type: \"string\",\n description:\n \"Proxy URL for all outgoing requests (http://, https://, socks5://, socks5h://). Overrides config.yaml and env vars.\",\n },\n \"http-proxy-from-env\": {\n type: \"boolean\",\n default: true,\n description: \"Use HTTP proxy from environment variables (disable with --no-http-proxy-from-env)\",\n },\n \"auto-truncate\": {\n type: \"boolean\",\n default: true,\n description:\n \"Reactive auto-truncate: retries with truncated payload on limit errors (disable with --no-auto-truncate)\",\n },\n },\n run({ args }) {\n // Check for unknown arguments\n // Known args include both kebab-case (as defined) and camelCase (citty auto-converts)\n const knownArgs = new Set([\n \"_\",\n // port\n \"port\",\n \"p\",\n // host\n \"host\",\n \"H\",\n // verbose\n \"verbose\",\n \"v\",\n // account-type\n \"account-type\",\n \"accountType\",\n \"a\",\n // rate-limit (citty handles --no-rate-limit via built-in negation)\n \"rate-limit\",\n \"rateLimit\",\n // github-token\n \"github-token\",\n \"githubToken\",\n \"g\",\n // show-github-token\n \"show-github-token\",\n \"showGithubToken\",\n // proxy\n \"proxy\",\n // http-proxy-from-env (citty handles --no-http-proxy-from-env via built-in negation)\n \"http-proxy-from-env\",\n \"httpProxyFromEnv\",\n // auto-truncate (citty handles --no-auto-truncate via built-in negation)\n \"auto-truncate\",\n \"autoTruncate\",\n ])\n const unknownArgs = Object.keys(args).filter((key) => !knownArgs.has(key))\n if (unknownArgs.length > 0) {\n consola.warn(`Unknown argument(s): ${unknownArgs.map((a) => `--${a}`).join(\", \")}`)\n }\n\n return runServer({\n port: parseIntOrDefault(args.port, 4141),\n host: args.host,\n verbose: args.verbose,\n accountType: args[\"account-type\"] as \"individual\" | \"business\" | \"enterprise\",\n rateLimit: args[\"rate-limit\"],\n githubToken: args[\"github-token\"],\n showGitHubToken: args[\"show-github-token\"],\n proxy: args.proxy,\n httpProxyFromEnv: args[\"http-proxy-from-env\"],\n autoTruncate: args[\"auto-truncate\"],\n })\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand, runMain } from \"citty\"\nimport consola from \"consola\"\n\nimport { auth } from \"./auth\"\nimport { checkUsage } from \"./check-usage\"\nimport { debug } from \"./debug\"\nimport { initConsolaReporter } from \"./lib/tui\"\nimport { listClaudeCode } from \"./list-claude-code\"\nimport { logout } from \"./logout\"\nimport { setupClaudeCode } from \"./setup-claude-code\"\nimport { start } from \"./start\"\n\n// Initialize console reporter before any logging\ninitConsolaReporter()\n\n// Global error handlers - catch errors from timers, callbacks, etc.\n// that would otherwise cause a silent process exit\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nprocess.on(\"unhandledRejection\", (reason) => {\n consola.error(\"Unhandled rejection:\", reason)\n process.exit(1)\n})\n\nconst main = defineCommand({\n meta: {\n name: \"copilot-api\",\n description: \"A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools.\",\n },\n subCommands: {\n auth,\n logout,\n start,\n \"check-usage\": checkUsage,\n debug,\n \"list-claude-code\": listClaudeCode,\n \"setup-claude-code\": setupClaudeCode,\n },\n})\n\nawait runMain(main)\n\n// When runMain() returns, the command has finished.\n// The `start` subcommand keeps the event loop alive (HTTP server),\n// so this line only executes for one-shot commands (debug, auth, etc.).\n// Explicit exit is needed because `bun run --watch` keeps the process alive otherwise.\nprocess.exit(0)\n"],"mappings":"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"}