@tetrixdev/ai-bridge 0.1.1 → 0.1.2
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/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { realpathSync } from "fs";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
|
|
@@ -11,7 +12,7 @@ import WebSocket from "ws";
|
|
|
11
12
|
|
|
12
13
|
// src/protocol/version.ts
|
|
13
14
|
var PROTOCOL_VERSION = "0.1";
|
|
14
|
-
var BRIDGE_VERSION = "0.1.
|
|
15
|
+
var BRIDGE_VERSION = "0.1.2";
|
|
15
16
|
|
|
16
17
|
// src/tools/manager.ts
|
|
17
18
|
import fs from "fs";
|
|
@@ -2707,10 +2708,21 @@ program.name("ai-bridge").description("Local CLI bridge for AI web apps \u2014 c
|
|
|
2707
2708
|
});
|
|
2708
2709
|
bridge.connect();
|
|
2709
2710
|
});
|
|
2710
|
-
|
|
2711
|
+
function isMainModule(argv1, moduleUrl) {
|
|
2712
|
+
if (!argv1) return false;
|
|
2713
|
+
try {
|
|
2714
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
2715
|
+
} catch {
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
if (isMainModule(process.argv[1], import.meta.url)) {
|
|
2711
2720
|
program.parseAsync(process.argv).catch((err) => {
|
|
2712
2721
|
log11.error("Fatal error", { error: err instanceof Error ? err.message : String(err) });
|
|
2713
2722
|
process.exit(1);
|
|
2714
2723
|
});
|
|
2715
2724
|
}
|
|
2725
|
+
export {
|
|
2726
|
+
isMainModule
|
|
2727
|
+
};
|
|
2716
2728
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/bridge.ts","../src/protocol/version.ts","../src/tools/manager.ts","../src/utils/logger.ts","../src/tools/resolver.ts","../src/tools/callback-server.ts","../src/session/store.ts","../src/utils/clamp.ts","../src/errors.ts","../src/providers/detector.ts","../src/providers/codex.ts","../src/providers/env.ts","../src/providers/base.ts","../src/tools/prompt.ts","../src/providers/claude.ts","../src/providers/gemini.ts","../src/test-mode.ts"],"sourcesContent":["/**\n * CLI Entry Point for @tetrixdev/ai-bridge\n *\n * Parses command-line arguments, detects local AI CLI providers,\n * creates a Bridge instance, and connects to the server.\n *\n * Usage:\n * npx @tetrixdev/ai-bridge --server wss://example.com/api/ai-bridge/ws --token <token>\n * AI_BRIDGE_TOKEN=xxx AI_BRIDGE_SERVER=wss://... npx @tetrixdev/ai-bridge\n * npx @tetrixdev/ai-bridge --server wss://... --token <token> --test\n */\n\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { Bridge, FatalBridgeError } from './bridge.js';\nimport { detectProviders } from './providers/detector.js';\nimport { CodexAdapter } from './providers/codex.js';\nimport { ClaudeAdapter } from './providers/claude.js';\nimport { GeminiAdapter } from './providers/gemini.js';\nimport type { ProviderAdapter } from './providers/base.js';\nimport { handleTestRequest } from './test-mode.js';\nimport { setDebug, createLogger } from './utils/logger.js';\nimport { BRIDGE_VERSION, PROTOCOL_VERSION } from './protocol/version.js';\n\nconst log = createLogger('CLI');\n\n// ---------------------------------------------------------------------------\n// CLI Definition\n// ---------------------------------------------------------------------------\n\nconst program = new Command();\n\nprogram\n .name('ai-bridge')\n .description('Local CLI bridge for AI web apps — connects Codex, Claude, and Gemini to your web application via WebSocket')\n .version(BRIDGE_VERSION)\n .option(\n '-t, --token <token>',\n 'Authentication token (or set AI_BRIDGE_TOKEN env var)',\n process.env['AI_BRIDGE_TOKEN'],\n )\n .option(\n '-s, --server <url>',\n 'WebSocket server URL (or set AI_BRIDGE_SERVER env var)',\n process.env['AI_BRIDGE_SERVER'],\n )\n .option(\n '-d, --debug',\n 'Enable verbose debug logging',\n false,\n )\n .option(\n '--test',\n 'Test mode — respond to AI requests with mock streaming data (--server and --token still required for the WebSocket connection)',\n false,\n )\n .action(async (opts: { token?: string; server?: string; debug: boolean; test: boolean }) => {\n // Enable debug logging if requested\n if (opts.debug) {\n setDebug(true);\n }\n\n log.info(`AI Bridge v${BRIDGE_VERSION} (protocol v${PROTOCOL_VERSION})`);\n\n if (opts.test) {\n log.info('Running in TEST MODE — AI requests will receive mock responses');\n }\n\n // Validate required options\n const token = opts.token;\n const serverUrl = opts.server;\n\n if (!token) {\n log.error('Authentication token is required. Use --token <token> or set AI_BRIDGE_TOKEN. Generate a token from your web application (see README for details).');\n process.exit(1);\n }\n\n if (!serverUrl) {\n log.error('Server URL is required. Use --server <url> or set AI_BRIDGE_SERVER. Use the wss:// address provided by your web application (e.g. wss://your-app.com/api/ai-bridge/ws).');\n process.exit(1);\n }\n\n // Validate server URL format\n if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {\n log.error('Server URL must start with ws:// or wss://');\n process.exit(1);\n }\n\n // Warn about unencrypted connections\n if (serverUrl.startsWith('ws://')) {\n log.warn('Connecting over unencrypted ws://. Use wss:// in production.');\n }\n\n // Reject URLs with username/password components to prevent URL authority\n // confusion (e.g. wss://legit.com@attacker.com/ws)\n try {\n const parsedUrl = new URL(serverUrl);\n if (parsedUrl.username || parsedUrl.password) {\n log.error('Server URL must not contain username or password components');\n process.exit(1);\n }\n } catch {\n log.error('Server URL is not a valid URL');\n process.exit(1);\n }\n\n // -----------------------------------------------------------------------\n // Detect providers\n // -----------------------------------------------------------------------\n\n const providers = await detectProviders();\n const availableProviders = providers.filter((p) => p.available);\n\n if (availableProviders.length === 0 && !opts.test) {\n // Warn BEFORE connecting so the user knows the bridge is non-functional.\n log.warn('No AI CLI tools detected. The bridge will NOT be able to execute requests.');\n log.warn('Install one of: codex (https://github.com/openai/codex), claude (https://claude.ai/download), gemini (https://github.com/google-gemini/gemini-cli)');\n log.warn('Or use --test flag to run in test mode with mock responses.');\n log.warn('AI requests will fail until a provider CLI is installed. See install links above.');\n log.warn('Connecting anyway so the server knows a bridge is present...');\n } else if (availableProviders.length > 0) {\n log.info(`Available providers: ${availableProviders.map((p) => `${p.name} (${p.version ?? 'unknown version'})`).join(', ')}`);\n }\n\n // -----------------------------------------------------------------------\n // Initialize adapters and populate model lists\n // -----------------------------------------------------------------------\n\n const adapterInstances: ProviderAdapter[] = [\n new CodexAdapter(),\n new ClaudeAdapter(),\n new GeminiAdapter(),\n ];\n\n const adapters = new Map<string, ProviderAdapter>();\n // Run listModels() concurrently across all available providers.\n const availableAdapters = adapterInstances.filter((adapter) => {\n const capability = providers.find((p) => p.name === adapter.providerName);\n return capability?.available === true;\n });\n\n await Promise.all(\n availableAdapters.map(async (adapter) => {\n const capability = providers.find((p) => p.name === adapter.providerName)!;\n adapters.set(adapter.providerName, adapter);\n try {\n const models = await adapter.listModels();\n capability.models = models;\n log.info(`${adapter.providerName} models: ${models.map((m) => m.id).join(', ')}`);\n } catch (err) {\n log.warn(`Failed to list models for ${adapter.providerName}`, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n log.debug('Registered adapter', { name: adapter.providerName });\n }),\n );\n\n // -----------------------------------------------------------------------\n // Create and connect bridge\n // Token goes in the URL query param (?token=...), NOT in the hello body\n // -----------------------------------------------------------------------\n\n const bridge = new Bridge({\n serverUrl,\n token,\n providers,\n adapters,\n testMode: opts.test,\n onTestRequest: opts.test ? handleTestRequest : undefined,\n });\n\n // Lifecycle logging\n bridge.on('connected', () => {\n log.info('Connected to server');\n });\n\n bridge.on('welcome', (sessionId) => {\n log.info(`Session established: ${sessionId}`);\n if (opts.test) {\n log.info('Test mode active — waiting for ai_request messages...');\n }\n });\n\n bridge.on('disconnected', (code, reason) => {\n log.warn(`Disconnected from server (code=${code}, reason=\"${reason}\")`);\n });\n\n bridge.on('error', (err) => {\n log.error('Bridge error', { error: err.message });\n\n if (err instanceof FatalBridgeError) {\n process.exit(1);\n }\n });\n\n bridge.on('request_start', (requestId, provider) => {\n log.info(`Processing request ${requestId} with ${provider}${opts.test ? ' (test mode)' : ''}`);\n });\n\n bridge.on('request_end', (requestId) => {\n log.info(`Request ${requestId} completed`);\n });\n\n // -----------------------------------------------------------------------\n // Graceful shutdown\n // -----------------------------------------------------------------------\n\n const shutdown = async (signal: string) => {\n log.info(`Received ${signal} — shutting down gracefully`);\n await bridge.disconnect();\n process.exit(0);\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n\n // Unhandled rejections leave the bridge in an unknown state — log, attempt\n // a graceful disconnect, then exit so the operator restarts the process.\n process.on('unhandledRejection', (reason) => {\n log.error('Unhandled rejection — bridge is in an unknown state, exiting (restart the bridge to recover)', {\n error: reason instanceof Error ? reason.message : String(reason),\n });\n // Best-effort disconnect (notify server we're going away)\n bridge.disconnect().catch(() => { /* ignore */ }).finally(() => {\n process.exit(1);\n });\n });\n\n // -----------------------------------------------------------------------\n // Connect\n // -----------------------------------------------------------------------\n\n bridge.connect();\n });\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\n// Guard execution so importing this module does not invoke the CLI. When run\n// directly via Node, import.meta.url matches process.argv[1].\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n program.parseAsync(process.argv).catch((err: unknown) => {\n log.error('Fatal error', { error: err instanceof Error ? err.message : String(err) });\n process.exit(1);\n });\n}\n","/**\n * Bridge — Core WebSocket management class\n *\n * Connects to the AI Bridge server, handles the protocol handshake,\n * routes AI requests to local provider adapters, and streams results\n * back over the WebSocket.\n *\n * Implements:\n * - WebSocket connection with exponential backoff reconnection\n * - Protocol handshake (hello -> welcome)\n * - AI request routing to providers\n * - Tool call resolution round-trip\n * - Heartbeat (ping/pong) — interval in SECONDS from server\n * - Tool script generation on welcome\n * - Local HTTP callback server for tool scripts\n * - Lifecycle event emission\n */\n\nimport { EventEmitter } from 'node:events';\nimport crypto from 'node:crypto';\nimport WebSocket from 'ws';\nimport type {\n ProviderCapability,\n BridgeToServerMessage,\n ServerToBridgeMessage,\n AiRequestMessage,\n SessionResetMessage,\n WelcomeMessage,\n ToolDefinition,\n ServerConfig,\n StreamEventType,\n StreamEventData,\n} from './protocol/types.js';\nimport { PROTOCOL_VERSION, BRIDGE_VERSION } from './protocol/version.js';\nimport { ProviderAdapter, type ExecutionContext, type AdapterStreamEvent } from './providers/base.js';\nimport { ToolManager } from './tools/manager.js';\nimport { ToolResolver } from './tools/resolver.js';\nimport { ToolCallbackServer } from './tools/callback-server.js';\nimport { SessionStore } from './session/store.js';\nimport { createLogger } from './utils/logger.js';\nimport { clampRequestTimeout, clampHeartbeat } from './utils/clamp.js';\nimport { FatalBridgeError } from './errors.js';\n\nexport { FatalBridgeError } from './errors.js';\n\nconst log = createLogger('Bridge');\n\n// ---------------------------------------------------------------------------\n// Configuration & Defaults\n// ---------------------------------------------------------------------------\n\nexport interface BridgeOptions {\n /** WebSocket server URL (wss://...) — token is appended as ?token= */\n serverUrl: string;\n /** Authentication token (placed in URL query param, NOT in hello body) */\n token: string;\n /** Detected provider capabilities */\n providers: ProviderCapability[];\n /** Provider adapter instances, keyed by provider name */\n adapters: Map<string, ProviderAdapter>;\n /** Whether to run in test mode (mock responses) */\n testMode?: boolean;\n /** Mock response handler for test mode */\n onTestRequest?: (request: AiRequestMessage, sendEvent: (event: StreamEventType, data: StreamEventData) => void) => Promise<void>;\n}\n\nconst DEFAULT_HEARTBEAT_SECONDS = 30;\nconst DEFAULT_REQUEST_TIMEOUT_SECONDS = 300;\n\n// Large-but-finite cap (~24 min of retries with backoff); infinite retry could\n// mask configuration errors.\nconst MAX_RECONNECT_ATTEMPTS = 100;\nconst BASE_RECONNECT_DELAY_MS = 1_000;\nconst MAX_RECONNECT_DELAY_MS = 15_000; // Cap at 15s per PROTOCOL.md\n\n// ---------------------------------------------------------------------------\n// Bridge Events\n// ---------------------------------------------------------------------------\n\nexport interface BridgeEvents {\n connected: [];\n disconnected: [code: number, reason: string];\n welcome: [sessionId: string];\n error: [error: Error];\n request_start: [requestId: string, provider: string];\n request_end: [requestId: string];\n}\n\n// ---------------------------------------------------------------------------\n// Helper: Escape XML characters in message content\n// ---------------------------------------------------------------------------\n\nfunction escapeXml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n// ---------------------------------------------------------------------------\n// Helper: Build a synthetic AiRequestMessage from a session reset\n// ---------------------------------------------------------------------------\n\n/**\n * Construct an AiRequestMessage from a SessionResetMessage by extracting\n * the last user message and folding prior history into the system prompt\n * using XML-tagged structure to prevent prompt injection.\n *\n * Returns null if no user message is found in history.\n */\nfunction buildSessionResetRequest(\n originalMsg: SessionResetMessage,\n currentSystemPrompt: string | null,\n): AiRequestMessage | null {\n const { request_id, conversation_id, provider, history, options } = originalMsg;\n\n // Validate history roles — warn if unexpected values encountered\n const validRoles = new Set(['user', 'assistant', 'system']);\n const unexpectedRoles = history\n .map((h) => h.role)\n .filter((role) => !validRoles.has(role));\n if (unexpectedRoles.length > 0) {\n log.warn('Session reset history contains unexpected role values', {\n unexpectedRoles: [...new Set(unexpectedRoles)],\n });\n }\n\n const lastUserIdx = history.findLastIndex((h) => h.role === 'user');\n if (lastUserIdx === -1) {\n return null;\n }\n\n const lastUserMessage = history[lastUserIdx];\n\n // Build conversation context from prior history (excluding the last user message)\n const rawPriorHistory = history.slice(0, lastUserIdx);\n\n // Exclude entries with unexpected roles to avoid widening the injection\n // surface (an unknown role could be treated as authoritative instructions).\n const priorHistory = rawPriorHistory.filter((h) => validRoles.has(h.role));\n\n // Wrap history in XML tags (with escaped content) to prevent prompt injection.\n let enhancedSystemPrompt: string | null;\n if (priorHistory.length > 0) {\n const historyXml = priorHistory\n .map((h) => `<message role=\"${escapeXml(h.role)}\">${escapeXml(h.content)}</message>`)\n .join('\\n');\n\n const historyBlock = `<conversation_history>\\n${historyXml}\\n</conversation_history>`;\n\n // If system_prompt is null/empty, use only the history context\n enhancedSystemPrompt = currentSystemPrompt\n ? `${currentSystemPrompt}\\n\\n${historyBlock}`\n : historyBlock;\n } else {\n enhancedSystemPrompt = currentSystemPrompt;\n }\n\n return {\n type: 'ai_request',\n request_id,\n conversation_id,\n provider,\n message: lastUserMessage.content,\n system_prompt: enhancedSystemPrompt,\n options,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge Class\n// ---------------------------------------------------------------------------\n\nexport class Bridge extends EventEmitter<BridgeEvents> {\n private ws: WebSocket | null = null;\n private readonly serverUrl: string;\n private readonly token: string;\n private readonly providers: ProviderCapability[];\n private readonly adapters: Map<string, ProviderAdapter>;\n private readonly toolManager = new ToolManager();\n private readonly toolResolver = new ToolResolver();\n private readonly sessionStore = new SessionStore();\n private readonly callbackServer: ToolCallbackServer;\n private readonly testMode: boolean;\n private readonly onTestRequest?: BridgeOptions['onTestRequest'];\n\n private sessionId: string | null = null;\n private serverConfig: ServerConfig = {\n heartbeat_interval: DEFAULT_HEARTBEAT_SECONDS,\n request_timeout: DEFAULT_REQUEST_TIMEOUT_SECONDS,\n };\n /** Timer to detect a missing welcome message after hello is sent. */\n private welcomeTimeoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private pongTimeoutTimer: ReturnType<typeof setTimeout> | null = null;\n private awaitingPong = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private isShuttingDown = false;\n private activeRequests = new Map<string, AbortController>();\n /**\n * Request IDs aborted because the WebSocket dropped while they were in\n * flight. No terminal event could be sent over the closed socket, so on the\n * next welcome these are replayed as session_expired errors to release the\n * browser's loading state.\n */\n private abortedRequestIds: string[] = [];\n /** Random secret for authenticating tool callback HTTP requests. */\n private readonly callbackSecret: string;\n\n constructor(options: BridgeOptions) {\n super();\n this.serverUrl = options.serverUrl;\n this.token = options.token;\n this.providers = options.providers;\n this.adapters = options.adapters;\n this.testMode = options.testMode ?? false;\n this.onTestRequest = options.onTestRequest;\n\n // Generate a random secret for callback server authentication\n this.callbackSecret = crypto.randomBytes(32).toString('hex');\n\n // Forwards HTTP-based tool calls from scripts over the WebSocket.\n const sendFn = (reqId: string, tcId: string, tName: string, tArgs: Record<string, unknown>) => {\n this.send({\n type: 'tool_call',\n request_id: reqId,\n tool_call_id: tcId,\n tool_name: tName,\n arguments: tArgs,\n });\n };\n\n this.callbackServer = new ToolCallbackServer(\n this.toolResolver,\n sendFn,\n new Set(this.toolManager.getAll().map((t) => t.name)),\n this.callbackSecret,\n );\n }\n\n // -------------------------------------------------------------------------\n // Connection Lifecycle\n // -------------------------------------------------------------------------\n\n /**\n * Initiate the WebSocket connection to the server.\n *\n * The token is sent as an Authorization: Bearer header so it does NOT appear\n * in server/proxy access logs. It is also kept in the URL query parameter as\n * a backward-compatible fallback for servers that have not yet adopted the\n * header-based flow.\n *\n * NOTE: When passing --token on the command line the value is still visible\n * in process listings (ps aux). Prefer the AI_BRIDGE_TOKEN environment\n * variable to avoid this.\n */\n connect(): void {\n if (this.ws) {\n log.warn('connect() called while already connected — ignoring');\n return;\n }\n\n // Keep token in query param for backward compatibility, but also send it\n // in the Authorization header as the primary (log-safe) channel.\n const url = new URL(this.serverUrl);\n url.searchParams.set('token', this.token);\n const wsUrl = url.toString();\n\n log.info('Connecting to server', { url: this.serverUrl });\n\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'User-Agent': `ai-bridge/${BRIDGE_VERSION}`,\n // Send token via Authorization header (not visible in proxy logs)\n 'Authorization': `Bearer ${this.token}`,\n },\n // Limit incoming message size to 10MB to prevent memory exhaustion\n maxPayload: 10 * 1024 * 1024,\n });\n\n this.ws.on('open', this.onOpen.bind(this));\n this.ws.on('message', this.onMessage.bind(this));\n this.ws.on('close', this.onClose.bind(this));\n this.ws.on('error', this.onError.bind(this));\n }\n\n /**\n * Gracefully disconnect from the server.\n */\n async disconnect(): Promise<void> {\n this.isShuttingDown = true;\n this.stopHeartbeat();\n this.clearReconnectTimer();\n this.toolResolver.cancelAll();\n this.toolManager.cleanupScripts();\n await this.callbackServer.stop();\n // Persist in-memory last_used_at updates so sessions don't appear stale\n // (premature TTL expiry) after a bridge restart.\n this.sessionStore.flush();\n\n // Cancel active requests\n for (const [id, controller] of this.activeRequests) {\n controller.abort();\n log.debug('Cancelled active request', { requestId: id });\n }\n this.activeRequests.clear();\n\n if (this.ws) {\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close(1000, 'Bridge shutting down');\n }\n this.ws = null;\n }\n\n log.info('Bridge disconnected');\n }\n\n /**\n * Returns true if the WebSocket is currently open.\n */\n isConnected(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n // -------------------------------------------------------------------------\n // WebSocket Event Handlers\n // -------------------------------------------------------------------------\n\n private onOpen(): void {\n log.info('WebSocket connected');\n this.reconnectAttempts = 0;\n this.emit('connected');\n this.sendHello();\n }\n\n private onMessage(data: WebSocket.RawData): void {\n let message: ServerToBridgeMessage;\n try {\n message = JSON.parse(data.toString()) as ServerToBridgeMessage;\n } catch (err) {\n log.error('Failed to parse server message', {\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n\n log.debug('Received message', { type: message.type });\n\n switch (message.type) {\n case 'welcome':\n this.handleWelcome(message);\n break;\n case 'ai_request':\n this.handleAiRequest(message);\n break;\n case 'session_reset':\n this.handleSessionReset(message);\n break;\n case 'tool_resolve':\n this.toolResolver.resolve(message.tool_call_id, message.result);\n break;\n case 'tool_error':\n this.toolResolver.reject(message.tool_call_id, message.error);\n break;\n case 'pong':\n log.debug('Pong received', { timestamp: message.timestamp });\n // Mark pong received and clear the timeout\n this.awaitingPong = false;\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n this.pongTimeoutTimer = null;\n }\n break;\n case 'error':\n this.handleServerError(message);\n break;\n default:\n log.warn('Unknown message type received', { type: (message as { type: string }).type });\n }\n }\n\n private onClose(code: number, reason: Buffer): void {\n const reasonStr = reason.toString();\n log.info('WebSocket closed', { code, reason: reasonStr });\n this.stopHeartbeat();\n // Clear welcome timeout if connection closes before welcome arrives\n if (this.welcomeTimeoutTimer) {\n clearTimeout(this.welcomeTimeoutTimer);\n this.welcomeTimeoutTimer = null;\n }\n this.ws = null;\n // Clear the stale session ID so the welcome-timeout guard on the next\n // reconnect correctly detects a missing welcome.\n this.sessionId = null;\n\n // Cancel all pending tool resolvers immediately on WebSocket close to fail\n // in-flight tool calls instead of letting them stall.\n this.toolResolver.cancelAll();\n\n // Abort all active AI requests on unexpected disconnect so their CLI\n // subprocesses are terminated; otherwise the server never receives a done\n // event and the conversation slot stays blocked until its timeout fires.\n if (!this.isShuttingDown) {\n for (const [id, controller] of this.activeRequests) {\n controller.abort();\n // Record the aborted request so it can be replayed as a\n // session_expired error after reconnect.\n this.abortedRequestIds.push(id);\n log.debug('Aborted active request on disconnect', { requestId: id });\n }\n this.activeRequests.clear();\n }\n\n this.emit('disconnected', code, reasonStr);\n\n if (!this.isShuttingDown) {\n // Check for authentication rejection — don't retry, exit immediately\n if (code === 4001) {\n // Clean up tool scripts and callback server before emitting fatal error\n this.toolManager.cleanupScripts();\n this.callbackServer.stop().catch(() => {\n // Best-effort cleanup; ignore errors during shutdown\n });\n\n this.isShuttingDown = true;\n this.emit(\n 'error',\n new FatalBridgeError(\n 'Connection rejected: invalid or expired token. Generate a new token from your application\\'s dashboard and restart the bridge.',\n ),\n );\n return;\n }\n\n this.scheduleReconnect();\n }\n }\n\n private onError(err: Error): void {\n log.error('WebSocket error', { error: err.message });\n this.emit('error', err);\n }\n\n // -------------------------------------------------------------------------\n // Protocol Handlers\n // -------------------------------------------------------------------------\n\n /**\n * Send hello message per PROTOCOL.md:\n * { type: \"hello\", version, bridge_version, providers[] }\n * NO token field — token is in the URL query param.\n * NO id field on providers — just name.\n */\n private sendHello(): void {\n const hello: BridgeToServerMessage = {\n type: 'hello',\n version: PROTOCOL_VERSION,\n bridge_version: BRIDGE_VERSION,\n providers: this.providers,\n };\n this.send(hello);\n log.info('Hello sent', {\n protocol: PROTOCOL_VERSION,\n providers: this.providers.filter((p) => p.available).map((p) => p.name),\n });\n\n // If no welcome arrives within 15s the server silently dropped our hello;\n // close and reconnect so we don't stall forever.\n this.welcomeTimeoutTimer = setTimeout(() => {\n this.welcomeTimeoutTimer = null;\n if (!this.sessionId && !this.isShuttingDown) {\n log.error('Welcome message not received within 15 seconds after hello — reconnecting');\n this.ws?.close(4000, 'Welcome timeout');\n }\n }, 15_000);\n }\n\n private async handleWelcome(message: WelcomeMessage): Promise<void> {\n // Cancel the welcome-timeout now that we've received the welcome.\n if (this.welcomeTimeoutTimer) {\n clearTimeout(this.welcomeTimeoutTimer);\n this.welcomeTimeoutTimer = null;\n }\n this.sessionId = message.session_id;\n this.serverConfig = message.config;\n\n // Check protocol version compatibility if the server provides one\n if (message.protocol_version) {\n const serverMajor = message.protocol_version.split('.')[0];\n const bridgeMajor = PROTOCOL_VERSION.split('.')[0];\n if (serverMajor !== bridgeMajor) {\n log.warn('Protocol version mismatch — major versions differ', {\n server: message.protocol_version,\n bridge: PROTOCOL_VERSION,\n });\n }\n }\n\n // Clamp request_timeout to a safe range (10–3600 s) before applying — a\n // malicious or misconfigured server could send 0 or a huge value.\n if (message.config.request_timeout) {\n const raw = message.config.request_timeout;\n const clamped = clampRequestTimeout(raw);\n if (clamped !== raw) {\n log.warn('Server request_timeout is outside safe range — clamping', {\n received: raw,\n clamped,\n });\n }\n this.toolResolver.setTimeoutMs(clamped * 1000);\n // Write the clamped value back so generateScripts (below) uses the same\n // timeout as the tool resolver.\n this.serverConfig.request_timeout = clamped;\n }\n\n // Register tools from the server\n this.toolManager.register(message.tools);\n\n // Update the callback server's validation set so it accepts tool calls\n // for the tools we just registered.\n this.callbackServer.setRegisteredToolNames(this.toolManager.getRegisteredNames());\n\n // Notify the server about any tools rejected during registration (unsafe\n // or reserved names) so it can surface a warning.\n const rejectedTools = this.toolManager.getRejectedToolNames();\n if (rejectedTools.length > 0) {\n this.send({\n type: 'error',\n request_id: 'setup',\n code: 'tool_rejected',\n message: `The following tools were rejected by the bridge due to unsafe or reserved names and will be unavailable: ${rejectedTools.join(', ')}`,\n fatal: false,\n });\n }\n\n // Generate tool wrapper scripts and start the callback server\n if (message.tools.length > 0) {\n try {\n await this.callbackServer.start();\n const port = this.callbackServer.getPort();\n if (port) {\n // Pass the server-configured request timeout so the bash script's\n // HTTP timeout matches the bridge-side tool resolver timeout.\n this.toolManager.generateScripts(port, this.callbackSecret, this.serverConfig.request_timeout * 1000);\n log.info('Tool scripts generated', {\n count: message.tools.length,\n callbackPort: port,\n scriptDir: this.toolManager.getScriptDir(),\n });\n }\n } catch (err) {\n log.error('Failed to set up tool callback server', {\n error: err instanceof Error ? err.message : String(err),\n });\n // Notify the server so it can warn the user — all tool calls will fail\n // for this session because the callback server could not start.\n this.send({\n type: 'error',\n request_id: 'setup',\n code: 'tool_setup_failed',\n message: `Tool callback server failed to start — tool calls will not work for this session: ${err instanceof Error ? err.message : String(err)}`,\n fatal: false,\n });\n }\n }\n\n // Start heartbeat — config.heartbeat_interval is in SECONDS. Clamp to a\n // safe range to prevent a ping flood or an excessively long dead-connection\n // window.\n const rawHeartbeat = message.config.heartbeat_interval;\n const clampedHeartbeat = clampHeartbeat(rawHeartbeat);\n if (clampedHeartbeat !== rawHeartbeat) {\n log.warn('Server heartbeat_interval is outside safe range — clamping', {\n received: rawHeartbeat,\n clamped: clampedHeartbeat,\n });\n }\n const intervalMs = clampedHeartbeat * 1000;\n this.startHeartbeat(intervalMs);\n\n log.info('Welcome received', {\n sessionId: this.sessionId,\n toolCount: message.tools.length,\n heartbeatSeconds: message.config.heartbeat_interval,\n });\n\n this.emit('welcome', this.sessionId);\n\n // Replay any requests aborted by a previous disconnect as session_expired\n // errors now that the connection is back, so the browser exits its loading\n // state instead of waiting for the server's own timeout.\n if (this.abortedRequestIds.length > 0) {\n const replayed = this.abortedRequestIds;\n this.abortedRequestIds = [];\n for (const requestId of replayed) {\n log.info('Replaying aborted request as session_expired error', { requestId });\n this.send({\n type: 'error',\n request_id: requestId,\n code: 'session_expired',\n message: 'Request aborted: the bridge connection dropped while the response was streaming.',\n fatal: false,\n });\n }\n }\n }\n\n /**\n * Handle an incoming ai_request: send ack, then execute.\n */\n private handleAiRequest(message: AiRequestMessage): void {\n const { request_id, provider, conversation_id } = message;\n\n // Look up existing CLI session for this conversation\n const existingCliSessionId = conversation_id\n ? this.sessionStore.get(conversation_id)\n : null;\n\n // If a conversation_id was provided but no session was found AND the\n // message has no system_prompt (a follow-up, not a new conversation), send\n // a session_expired error and return early. Continuing would produce two\n // conflicting signals: an error AND a streamed response from a context-less\n // CLI session. The server's session_reset flow recovers from this.\n if (conversation_id && !existingCliSessionId && !message.system_prompt) {\n log.warn('Session not found for conversation — notifying server', {\n conversationId: conversation_id,\n });\n this.send({\n type: 'error',\n request_id,\n code: 'session_expired',\n message: `No local session found for conversation ${conversation_id}`,\n fatal: false,\n });\n // Do NOT proceed: the server will send a session_reset with full history\n // to allow the bridge to reconstruct the context.\n return;\n }\n\n // Send ai_request_ack with the CLI session ID (null when there is no\n // existing session).\n this.send({\n type: 'ai_request_ack',\n request_id,\n cli_session_id: existingCliSessionId ?? null,\n });\n\n // Delegate to the internal handler (shared with session reset)\n this.executeAiRequestInternal(message, existingCliSessionId);\n }\n\n /**\n * Internal request execution logic shared by handleAiRequest and handleSessionReset.\n * Does NOT send ai_request_ack — the caller is responsible for that.\n */\n private executeAiRequestInternal(\n message: AiRequestMessage,\n existingCliSessionId?: string | null,\n ): void {\n const { request_id, provider } = message;\n\n const cliSessionId = existingCliSessionId ?? null;\n\n // Test mode: use mock handler\n if (this.testMode && this.onTestRequest) {\n this.emit('request_start', request_id, provider);\n const sendEvent = (event: StreamEventType, data: StreamEventData) => {\n this.sendStreamEvent(request_id, event, data);\n };\n this.onTestRequest(message, sendEvent)\n .catch((err) => {\n log.error('Test mode handler failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'error', {\n code: 'test_error',\n message: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'done', {});\n })\n .finally(() => {\n this.emit('request_end', request_id);\n });\n return;\n }\n\n // Find the adapter for the requested provider\n const adapter = this.adapters.get(provider);\n if (!adapter) {\n log.error('No adapter for requested provider', { provider });\n const installHints: Record<string, string> = {\n codex: ' Install the Codex CLI (https://github.com/openai/codex) on the machine running the bridge and restart it.',\n claude: ' Install the Claude CLI (https://claude.ai/download) on the machine running the bridge and restart it.',\n gemini: ' Install the Gemini CLI (https://github.com/google-gemini/gemini-cli) on the machine running the bridge and restart it.',\n };\n const hint = installHints[provider] ?? '';\n this.send({\n type: 'error',\n request_id,\n code: 'provider_unavailable',\n message: `Provider \"${provider}\" is not available on this bridge.${hint}`,\n fatal: true,\n });\n return;\n }\n\n this.emit('request_start', request_id, provider);\n\n // Execute asynchronously\n const controller = new AbortController();\n this.activeRequests.set(request_id, controller);\n\n this.executeRequest(adapter, message, cliSessionId, controller.signal)\n .catch((err) => {\n log.error('Request execution failed', {\n requestId: request_id,\n error: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'error', {\n code: 'provider_error',\n message: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'done', {});\n })\n .finally(() => {\n this.activeRequests.delete(request_id);\n this.emit('request_end', request_id);\n });\n }\n\n private async executeRequest(\n adapter: ProviderAdapter,\n request: AiRequestMessage,\n cliSessionId: string | null,\n signal: AbortSignal,\n ): Promise<void> {\n const { request_id, conversation_id } = request;\n\n // Build execution context\n const context: ExecutionContext = {\n request,\n requestId: request_id,\n tools: this.toolManager.getAll(),\n toolScriptDir: this.toolManager.getScriptDir(),\n onToolCall: async (toolCallId, toolName, args) => {\n return this.toolResolver.call(\n (reqId, tcId, tName, tArgs) => {\n this.send({\n type: 'tool_call',\n request_id: reqId,\n tool_call_id: tcId,\n tool_name: tName,\n arguments: tArgs,\n });\n },\n request_id,\n toolCallId,\n toolName,\n args,\n );\n },\n signal,\n cliSessionId,\n };\n\n // Wrap in try/finally to ensure the session mapping is persisted even if\n // the adapter throws after producing a session ID.\n let newCliSessionId: string | null = null;\n try {\n // Run the adapter — it returns the new CLI session ID\n newCliSessionId = await adapter.execute(context, (event: AdapterStreamEvent) => {\n this.sendStreamEvent(request_id, event.event, event.data);\n });\n } finally {\n // Store the session mapping for future resume. Also persist\n // system_prompt so session resets can restore it even when the server\n // omits it from the session_reset message.\n if (newCliSessionId && conversation_id) {\n this.sessionStore.set(\n conversation_id,\n newCliSessionId,\n adapter.providerName,\n request.system_prompt,\n );\n }\n }\n }\n\n /**\n * Handle a session_reset message by constructing a synthetic ai_request\n * from the conversation history and executing it without sending an ack.\n */\n private handleSessionReset(message: SessionResetMessage): void {\n const { request_id, conversation_id } = message;\n\n // Retrieve the stored system prompt BEFORE deleting the session, so it can\n // be used as a fallback if the server omits system_prompt.\n const storedSystemPrompt = this.sessionStore.getSystemPrompt(conversation_id);\n\n // Delete the old session so the adapter starts fresh\n const deleted = this.sessionStore.delete(conversation_id);\n log.info('Session reset', { conversationId: conversation_id, found: deleted, historyLength: message.history.length });\n\n // Prefer the server-provided system_prompt; fall back to the stored one if\n // the server omits it.\n const effectiveSystemPrompt = message.system_prompt ?? storedSystemPrompt;\n if (!message.system_prompt && storedSystemPrompt) {\n log.warn('session_reset has no system_prompt — using stored system prompt for this conversation', {\n conversationId: conversation_id,\n });\n }\n\n const syntheticRequest = buildSessionResetRequest(message, effectiveSystemPrompt);\n\n if (!syntheticRequest) {\n log.error('Session reset has no user message in history', { conversationId: conversation_id });\n this.send({\n type: 'error',\n request_id,\n code: 'session_reset_failed',\n message: 'No user message found in conversation history',\n fatal: true,\n });\n return;\n }\n\n log.info('Re-processing session_reset as new ai_request', { requestId: request_id, provider: message.provider });\n this.executeAiRequestInternal(syntheticRequest);\n }\n\n private handleServerError(message: { type: 'error'; code: string; message: string; fatal: boolean }): void {\n log.error('Server error', { code: message.code, message: message.message, fatal: message.fatal });\n if (message.fatal) {\n log.error('Fatal server error — disconnecting');\n this.isShuttingDown = true;\n // Clean up tool scripts and callback server (best-effort) before closing.\n this.toolManager.cleanupScripts();\n this.callbackServer.stop().catch(() => {\n // Best-effort cleanup; ignore errors during shutdown\n });\n this.ws?.close(1000, 'Fatal server error');\n }\n }\n\n // -------------------------------------------------------------------------\n // Heartbeat\n // -------------------------------------------------------------------------\n\n private startHeartbeat(intervalMs: number): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.isConnected()) {\n this.send({ type: 'ping', timestamp: Date.now() });\n log.debug('Ping sent');\n\n // Set a 10-second timeout for the pong response; if none arrives,\n // treat the connection as dead.\n this.awaitingPong = true;\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n }\n this.pongTimeoutTimer = setTimeout(() => {\n if (this.awaitingPong && this.isConnected()) {\n log.warn('Pong not received within 10s — connection presumed dead');\n this.ws?.close(4000, 'Pong timeout');\n }\n }, 10_000);\n }\n }, intervalMs);\n log.debug('Heartbeat started', { intervalMs });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n this.pongTimeoutTimer = null;\n }\n this.awaitingPong = false;\n }\n\n // -------------------------------------------------------------------------\n // Reconnection with Exponential Backoff\n // -------------------------------------------------------------------------\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n log.error('Maximum reconnection attempts reached — giving up', {\n attempts: this.reconnectAttempts,\n max: MAX_RECONNECT_ATTEMPTS,\n });\n this.emit(\n 'error',\n new FatalBridgeError(\n 'Maximum reconnection attempts reached. Check that the server URL is correct and the server is reachable, then restart the bridge.',\n ),\n );\n return;\n }\n\n // Per PROTOCOL.md: 1s, 2s, 4s, 8s, 15s cap\n const delay = Math.min(\n BASE_RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),\n MAX_RECONNECT_DELAY_MS,\n );\n\n this.reconnectAttempts++;\n log.info(`Reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, delay);\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n // -------------------------------------------------------------------------\n // Message Sending\n // -------------------------------------------------------------------------\n\n private send(message: BridgeToServerMessage): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n log.warn('Cannot send message — WebSocket not open', { type: message.type });\n return;\n }\n\n const payload = JSON.stringify(message);\n this.ws.send(payload);\n log.debug('Message sent', { type: message.type, bytes: payload.length });\n }\n\n /**\n * Send a stream event using the correct envelope format per PROTOCOL.md:\n * { type: \"stream\", request_id, event: \"<event_type>\", data: {...} }\n */\n private sendStreamEvent(requestId: string, event: StreamEventType, data: StreamEventData): void {\n this.send({\n type: 'stream',\n request_id: requestId,\n event,\n data,\n });\n }\n}\n","declare const __BRIDGE_VERSION__: string;\n\n/** Current AI Bridge Protocol version. */\nexport const PROTOCOL_VERSION = '0.1';\n\n/** Current bridge package version — injected at build time from package.json. */\nexport const BRIDGE_VERSION: string = __BRIDGE_VERSION__;\n","/**\n * Tool Manager\n *\n * Receives tool definitions from the server (via the WelcomeMessage)\n * and manages them for provider adapters. Generates temporary Bash\n * scripts that CLI tools can invoke; those scripts call back to the\n * bridge process which then routes the tool call over WebSocket.\n *\n * Flow:\n * Server -> Welcome (tools[]) -> ToolManager.register()\n * ToolManager generates wrapper scripts in a temp directory\n * Provider adapters add that temp dir to PATH when spawning CLIs\n * CLI invokes tool script -> script contacts bridge -> bridge sends tool_call\n * Server resolves -> bridge feeds result back to CLI\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ToolDefinition } from '../protocol/types.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('ToolManager');\n\n/** Strict pattern for safe tool names: must start with a letter, alphanumeric/underscore/hyphen only. */\nconst SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;\n\n/** Denylist of system binary names that must not be used as tool names. */\nconst RESERVED_TOOL_NAMES = new Set([\n 'curl', 'wget', 'node', 'npm', 'npx', 'bash', 'sh', 'zsh',\n 'python', 'python3', 'ruby', 'perl', 'git', 'ssh', 'scp',\n 'cat', 'ls', 'rm', 'cp', 'mv', 'chmod', 'chown', 'mkdir',\n 'kill', 'ps', 'env', 'sudo', 'su', 'tar', 'gzip', 'gunzip',\n 'openssl', 'nc', 'ncat', 'netcat', 'socat', 'find', 'grep',\n 'awk', 'sed', 'echo', 'printf', 'head', 'tail', 'wc', 'tee',\n 'test', 'true', 'false', 'xargs', 'sort', 'uniq', 'cut', 'tr',\n 'make',\n]);\n\nexport class ToolManager {\n private tools = new Map<string, ToolDefinition>();\n private scriptDir: string | null = null;\n /** Names of tools most recently rejected by register(). */\n private rejectedTools: string[] = [];\n\n /**\n * Register tool definitions received from the server.\n * Replaces any previously registered tools.\n * Tool names are validated against a safe pattern and denylist.\n */\n register(tools: ToolDefinition[]): void {\n this.tools.clear();\n const rejectedTools: string[] = [];\n\n for (const tool of tools) {\n // Validate tool name against regex pattern\n if (!SAFE_TOOL_NAME_PATTERN.test(tool.name)) {\n log.error('Rejected tool with unsafe name', {\n name: tool.name.substring(0, 100),\n reason: 'Tool names must start with a letter and be 1-64 chars of alphanumeric/underscore/hyphen',\n });\n rejectedTools.push(tool.name.substring(0, 100));\n continue;\n }\n\n // Check against the denylist case-insensitively — on case-insensitive\n // filesystems (macOS) a tool named 'Curl' would shadow the system 'curl'.\n if (RESERVED_TOOL_NAMES.has(tool.name.toLowerCase())) {\n log.error('Rejected tool with reserved name', {\n name: tool.name,\n reason: 'Tool name conflicts with a system binary',\n });\n rejectedTools.push(tool.name);\n continue;\n }\n\n this.tools.set(tool.name, tool);\n }\n\n // Expose rejected tool names so the bridge can notify the server.\n this.rejectedTools = rejectedTools;\n\n if (rejectedTools.length > 0) {\n log.warn('Tools rejected by name validation', {\n count: rejectedTools.length,\n names: rejectedTools,\n });\n }\n\n log.info('Registered tools', { accepted: this.tools.size, total: tools.length, names: Array.from(this.tools.keys()) });\n }\n\n /**\n * Return the list of tool names rejected during the last register() call.\n */\n getRejectedToolNames(): string[] {\n return this.rejectedTools;\n }\n\n /**\n * Get a tool definition by name.\n */\n get(name: string): ToolDefinition | undefined {\n return this.tools.get(name);\n }\n\n /**\n * Get all registered tool definitions.\n */\n getAll(): ToolDefinition[] {\n return Array.from(this.tools.values());\n }\n\n /**\n * Returns the number of registered tools.\n */\n count(): number {\n return this.tools.size;\n }\n\n /**\n * Returns the set of registered tool names.\n * Useful for passing to ToolCallbackServer for validation.\n */\n getRegisteredNames(): Set<string> {\n return new Set(this.tools.keys());\n }\n\n /**\n * Generate temporary Bash wrapper scripts for all registered tools.\n *\n * Each script, when invoked by a CLI tool, will:\n * 1. Collect arguments as JSON\n * 2. Send them to the bridge process via a local HTTP callback\n * 3. Wait for the result\n * 4. Print the result to stdout\n *\n * @param callbackPort The local HTTP port the bridge is listening on\n * for tool call callbacks from spawned scripts.\n * @param secret Optional bearer token for callback server auth.\n * @param timeoutMs HTTP timeout for the callback request in ms. Should\n * match the server-configured request_timeout so the\n * bash script does not outlive the bridge-side timeout.\n * Defaults to 300 000 ms (5 min).\n * @returns The path to the temporary directory containing the scripts.\n */\n generateScripts(callbackPort: number, secret?: string, timeoutMs: number = 300_000): string {\n // Clean up any previous script directory\n this.cleanupScripts();\n\n this.scriptDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-bridge-tools-'));\n log.debug('Created tool script directory', { dir: this.scriptDir });\n\n for (const tool of this.tools.values()) {\n const scriptPath = path.join(this.scriptDir, tool.name);\n const scriptContent = this.buildScript(tool, callbackPort, secret, timeoutMs);\n // 0o700 (owner-only) so other local users cannot read the embedded\n // callback bearer secret.\n fs.writeFileSync(scriptPath, scriptContent, { mode: 0o700 });\n log.debug('Generated tool script', { tool: tool.name, path: scriptPath });\n }\n\n return this.scriptDir;\n }\n\n /**\n * Get the directory containing generated tool scripts, or null if\n * scripts have not been generated yet.\n */\n getScriptDir(): string | null {\n return this.scriptDir;\n }\n\n /**\n * Remove all generated tool scripts and the temp directory.\n */\n cleanupScripts(): void {\n if (this.scriptDir) {\n try {\n fs.rmSync(this.scriptDir, { recursive: true, force: true });\n log.debug('Cleaned up tool script directory', { dir: this.scriptDir });\n } catch (err) {\n log.warn('Failed to clean up tool scripts', {\n dir: this.scriptDir,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n this.scriptDir = null;\n }\n }\n\n /**\n * Build the Bash script content for a single tool.\n *\n * Embeds the bearer token for callback server authentication. Tool names\n * are pre-validated by register() so they are safe to embed; tool\n * descriptions are NOT included to prevent injection.\n */\n private buildScript(tool: ToolDefinition, callbackPort: number, secret?: string, timeoutMs: number = 300_000): string {\n const secretArg = secret ?? '';\n return `#!/usr/bin/env bash\n# Auto-generated tool wrapper: ${tool.name}\n# DO NOT EDIT — regenerated on each bridge session.\n\nset -euo pipefail\n\n# Read the JSON payload from the first CLI argument when present; otherwise\n# fall back to stdin (some AI CLIs pass tool args one way, some the other).\nPAYLOAD_DATA=\"\"\nif [ \"$#\" -ge 1 ] && [ -n \"\\${1:-}\" ]; then\n PAYLOAD_DATA=\"$1\"\nelif [ ! -t 0 ]; then\n PAYLOAD_DATA=$(cat)\nfi\n\n# Single Node.js invocation for input parsing, payload building, HTTP call,\n# and output extraction.\nnode -e '\nconst http = require(\"http\");\nconst payloadData = process.argv[1];\nconst toolName = process.argv[2];\nconst toolCallId = process.argv[3];\nconst requestId = process.argv[4];\nconst secret = process.argv[5];\n\n// Parse the payload as JSON, fall back to wrapping as {input: ...}\nlet args = {};\nif (payloadData) {\n try { args = JSON.parse(payloadData); } catch { args = { input: payloadData }; }\n}\n\nconst payload = JSON.stringify({\n tool_name: toolName,\n tool_call_id: toolCallId,\n arguments: args,\n request_id: requestId\n});\n\nconst headers = { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(payload) };\nif (secret) headers[\"Authorization\"] = \"Bearer \" + secret;\n\nconst req = http.request({ hostname: \"127.0.0.1\", port: ${callbackPort}, path: \"/tool-call\", method: \"POST\", headers, timeout: ${timeoutMs} }, (res) => {\n let body = \"\";\n res.on(\"data\", (c) => { body += c; });\n res.on(\"end\", () => {\n try {\n const r = JSON.parse(body);\n if (r.error) { process.stderr.write(r.error + \"\\\\n\"); process.exit(1); }\n process.stdout.write(r.result != null ? String(r.result) : \"\");\n } catch {\n process.stderr.write(\"Tool call failed: invalid response from bridge\\\\n\");\n process.exit(1);\n }\n });\n});\nreq.on(\"error\", (e) => { process.stderr.write(\"Tool call failed: \" + e.message + \"\\\\n\"); process.exit(1); });\nreq.write(payload);\nreq.end();\n// The $RANDOM-based ID here is discarded — the callback server overrides it\n// with a cryptographically strong UUID before use.\n' \"$PAYLOAD_DATA\" \"${tool.name}\" \"tc_\\${RANDOM}\\${RANDOM}\" \"\\${AI_BRIDGE_REQUEST_ID:-}\" \"${secretArg}\"\n`;\n }\n}\n","/**\n * Structured logger for the AI Bridge.\n *\n * Supports debug / info / warn / error levels.\n * Debug output is suppressed unless `setDebug(true)` is called (via --debug flag).\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nconst LEVEL_LABEL: Record<LogLevel, string> = {\n debug: 'DBG',\n info: 'INF',\n warn: 'WRN',\n error: 'ERR',\n};\n\nlet currentLevel: LogLevel = 'info';\n\n/** Enable or disable debug-level output. */\nexport function setDebug(enabled: boolean): void {\n currentLevel = enabled ? 'debug' : 'info';\n}\n\n/** Returns true if debug logging is currently enabled. */\nexport function isDebugEnabled(): boolean {\n return currentLevel === 'debug';\n}\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];\n}\n\nfunction formatMessage(level: LogLevel, component: string, message: string, meta?: Record<string, unknown>): string {\n const ts = formatTimestamp();\n const label = LEVEL_LABEL[level];\n const metaStr = meta ? ' ' + JSON.stringify(meta) : '';\n return `${ts} [${label}] [${component}] ${message}${metaStr}`;\n}\n\n/**\n * Create a scoped logger for a specific component.\n *\n * ```ts\n * const log = createLogger('Bridge');\n * log.info('Connected', { url: 'wss://...' });\n * ```\n */\nexport function createLogger(component: string) {\n return {\n debug(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('debug')) {\n process.stderr.write(formatMessage('debug', component, message, meta) + '\\n');\n }\n },\n\n info(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('info')) {\n process.stderr.write(formatMessage('info', component, message, meta) + '\\n');\n }\n },\n\n warn(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('warn')) {\n process.stderr.write(formatMessage('warn', component, message, meta) + '\\n');\n }\n },\n\n error(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('error')) {\n process.stderr.write(formatMessage('error', component, message, meta) + '\\n');\n }\n },\n };\n}\n\nexport type Logger = ReturnType<typeof createLogger>;\n","/**\n * Tool Resolver\n *\n * Handles the WebSocket round-trip for tool calls:\n * 1. The provider adapter invokes a tool\n * 2. The resolver sends a tool_call message to the server\n * 3. The resolver waits for a matching tool_resolve or tool_error\n * 4. The result (or error) is returned to the provider adapter\n *\n * Pending tool calls are tracked by tool_call_id and resolved via\n * the `resolve()` / `reject()` methods called by the Bridge when\n * it receives the server's response.\n */\n\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('ToolResolver');\n\n/** A pending tool call awaiting resolution from the server. */\ninterface PendingToolCall {\n toolCallId: string;\n toolName: string;\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/** Callback to send a tool_call message over the WebSocket. */\nexport type SendToolCallFn = (\n requestId: string,\n toolCallId: string,\n toolName: string,\n args: Record<string, unknown>,\n) => void;\n\nexport class ToolResolver {\n private pending = new Map<string, PendingToolCall>();\n private timeoutMs: number;\n\n constructor(timeoutMs: number = 300_000) {\n this.timeoutMs = timeoutMs;\n }\n\n /**\n * Update the timeout duration (e.g. from the server's request_timeout config).\n */\n setTimeoutMs(ms: number): void {\n this.timeoutMs = ms;\n log.debug('Tool resolver timeout updated', { timeoutMs: ms });\n }\n\n /**\n * Initiate a tool call and wait for the server's response.\n *\n * @param sendFn Function to send the tool_call over WebSocket.\n * @param requestId The parent AI request ID.\n * @param toolCallId Unique ID for this tool invocation.\n * @param toolName Name of the tool being called.\n * @param args Tool arguments.\n * @returns The tool result from the server.\n * @throws If the server returns a tool_error or the call times out.\n */\n call(\n sendFn: SendToolCallFn,\n requestId: string,\n toolCallId: string,\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(toolCallId);\n const seconds = Math.round(this.timeoutMs / 1000);\n const minutes = Math.round(seconds / 60);\n const humanDuration = seconds >= 60 ? `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}` : `${seconds}s`;\n reject(new Error(`Tool call ${toolName} (${toolCallId}) timed out after ${humanDuration}`));\n }, this.timeoutMs);\n\n this.pending.set(toolCallId, {\n toolCallId,\n toolName,\n resolve,\n reject,\n timer,\n });\n\n log.debug('Sending tool call to server', { requestId, toolCallId, toolName });\n sendFn(requestId, toolCallId, toolName, args);\n });\n }\n\n /**\n * Resolve a pending tool call with a successful result.\n * Called by the Bridge when a `tool_resolve` message arrives.\n */\n resolve(toolCallId: string, result: unknown): boolean {\n const pending = this.pending.get(toolCallId);\n if (!pending) {\n log.warn('Received tool_resolve for unknown tool_call_id', { toolCallId });\n return false;\n }\n\n clearTimeout(pending.timer);\n this.pending.delete(toolCallId);\n log.debug('Tool call resolved', { toolCallId, toolName: pending.toolName });\n pending.resolve(result);\n return true;\n }\n\n /**\n * Reject a pending tool call with an error.\n * Called by the Bridge when a `tool_error` message arrives.\n */\n reject(toolCallId: string, error: string): boolean {\n const pending = this.pending.get(toolCallId);\n if (!pending) {\n log.warn('Received tool_error for unknown tool_call_id', { toolCallId });\n return false;\n }\n\n clearTimeout(pending.timer);\n this.pending.delete(toolCallId);\n log.debug('Tool call rejected', { toolCallId, toolName: pending.toolName, error });\n pending.reject(new Error(`Tool error (${pending.toolName}): ${error}`));\n return true;\n }\n\n /**\n * Cancel all pending tool calls (e.g., on disconnect).\n */\n cancelAll(): void {\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Tool call cancelled — bridge disconnected'));\n }\n const count = this.pending.size;\n this.pending.clear();\n if (count > 0) {\n log.info('Cancelled all pending tool calls', { count });\n }\n }\n\n /**\n * Returns the number of tool calls currently awaiting resolution.\n */\n pendingCount(): number {\n return this.pending.size;\n }\n}\n","/**\n * Tool Callback Server\n *\n * A minimal local HTTP server that tool wrapper scripts POST to\n * when a CLI invokes a tool. The server receives the tool call,\n * routes it through the ToolResolver (which sends it over WebSocket\n * to the server and waits for the result), then responds to the\n * HTTP request with the tool result.\n *\n * Flow:\n * 1. CLI invokes bash wrapper script for a tool\n * 2. Script POSTs to http://127.0.0.1:<port>/tool-call\n * 3. This server receives the request\n * 4. Routes through ToolResolver -> WebSocket -> server -> tool_resolve\n * 5. Returns the result to the bash script via HTTP response\n * 6. Bash script prints result to stdout for CLI to consume\n */\n\nimport http from 'node:http';\nimport crypto from 'node:crypto';\nimport { createLogger } from '../utils/logger.js';\nimport type { ToolResolver, SendToolCallFn } from './resolver.js';\n\nconst log = createLogger('ToolCallbackServer');\n\n/** Maximum request body size: 1 MB. */\nconst MAX_BODY_SIZE = 1048576;\n\nexport class ToolCallbackServer {\n private server: http.Server | null = null;\n private port: number | null = null;\n\n /** Set of registered tool names for validation. */\n private registeredToolNames: Set<string> | null = null;\n\n /** Shared secret for authenticating callback requests. */\n private readonly secret: string | null;\n\n constructor(\n private readonly toolResolver: ToolResolver,\n private readonly sendFn: SendToolCallFn,\n registeredToolNames?: Set<string>,\n secret?: string,\n ) {\n if (registeredToolNames) {\n this.registeredToolNames = registeredToolNames;\n }\n this.secret = secret ?? null;\n }\n\n /**\n * Set the registered tool names for validation.\n * Tool calls with names not in this set will be rejected.\n */\n setRegisteredToolNames(names: Set<string>): void {\n this.registeredToolNames = names;\n }\n\n /**\n * Start the local HTTP server on a random available port.\n */\n async start(): Promise<number> {\n if (this.server) {\n return this.port!;\n }\n\n return new Promise<number>((resolve, reject) => {\n this.server = http.createServer((req, res) => {\n this.handleRequest(req, res);\n });\n\n // Listen on random port, only on localhost\n this.server.listen(0, '127.0.0.1', () => {\n const addr = this.server!.address();\n if (addr && typeof addr === 'object') {\n this.port = addr.port;\n log.info('Tool callback server started', { port: this.port });\n resolve(this.port);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n\n this.server.on('error', (err) => {\n log.error('Tool callback server error', { error: err.message });\n reject(err);\n });\n });\n }\n\n /**\n * Stop the callback server.\n */\n async stop(): Promise<void> {\n if (!this.server) return;\n\n return new Promise<void>((resolve) => {\n this.server!.close(() => {\n log.info('Tool callback server stopped');\n this.server = null;\n this.port = null;\n resolve();\n });\n });\n }\n\n /**\n * Get the port the server is listening on, or null if not started.\n */\n getPort(): number | null {\n return this.port;\n }\n\n /**\n * Handle incoming HTTP requests from tool wrapper scripts.\n */\n private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n if (req.method !== 'POST' || req.url !== '/tool-call') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n // Verify bearer token if a secret is configured, using timingSafeEqual to\n // prevent timing-based token recovery.\n if (this.secret) {\n const authHeader = req.headers['authorization'];\n const expected = `Bearer ${this.secret}`;\n // Check lengths first (timingSafeEqual requires equal-length buffers)\n if (\n !authHeader ||\n authHeader.length !== expected.length ||\n !crypto.timingSafeEqual(Buffer.from(authHeader), Buffer.from(expected))\n ) {\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Unauthorized' }));\n return;\n }\n }\n\n // Enforce body size limit\n let body = '';\n let bodyLength = 0;\n\n req.on('data', (chunk: Buffer) => {\n bodyLength += chunk.length;\n if (bodyLength > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n\n req.on('end', () => {\n // If the request was already destroyed due to size limit, skip processing\n if (bodyLength > MAX_BODY_SIZE) return;\n\n this.processToolCall(body, res).catch((err) => {\n log.error('Failed to process tool call', {\n error: err instanceof Error ? err.message : String(err),\n });\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));\n });\n });\n }\n\n private async processToolCall(body: string, res: http.ServerResponse): Promise<void> {\n let parsed: { tool_name: string; tool_call_id?: string; arguments: Record<string, unknown>; request_id?: string };\n\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n return;\n }\n\n const { tool_name, arguments: args } = parsed;\n\n // request_id comes exclusively from the POST body\n const requestId = parsed.request_id;\n if (!requestId) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Missing request_id \\u2014 tool call cannot be routed' }));\n return;\n }\n\n // Validate tool_name against the registered set\n if (this.registeredToolNames && !this.registeredToolNames.has(tool_name)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `Unknown tool: ${tool_name}` }));\n return;\n }\n\n const toolCallId = `tc_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;\n\n log.debug('Tool call received via HTTP callback', { tool_name, toolCallId, requestId });\n\n try {\n // Route through the ToolResolver which sends over WebSocket and waits\n const result = await this.toolResolver.call(\n this.sendFn,\n requestId,\n toolCallId,\n tool_name,\n args ?? {},\n );\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ result: typeof result === 'string' ? result : JSON.stringify(result) }));\n } catch (err) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));\n }\n }\n}\n","/**\n * Session Store\n *\n * Persists conversation_id -> cli_session_id mappings to\n * ~/.ai-bridge/sessions.json so that conversations can be resumed\n * across bridge restarts.\n *\n * Includes TTL-based pruning (default: 7 days).\n *\n * Stored format per PROTOCOL.md:\n * {\n * \"conv_xyz789\": {\n * \"provider\": \"claude\",\n * \"cli_session_id\": \"session_def456\",\n * \"created_at\": \"2026-05-14T10:30:00Z\",\n * \"last_used_at\": \"2026-05-14T11:45:00Z\"\n * }\n * }\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('SessionStore');\n\n/** A single session record stored on disk. */\ninterface SessionRecord {\n cli_session_id: string;\n provider: string;\n created_at: string; // ISO 8601\n last_used_at: string; // ISO 8601\n /** Original system prompt, so session resets can reuse it even when the\n * server omits system_prompt from the session_reset message. */\n system_prompt?: string | null;\n}\n\n/** The full shape of the sessions.json file. */\ninterface SessionFile {\n [conversationId: string]: SessionRecord;\n}\n\nconst DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport class SessionStore {\n private readonly dir: string;\n private readonly filePath: string;\n private readonly ttlMs: number;\n private data: SessionFile;\n\n constructor(ttlMs: number = DEFAULT_TTL_MS) {\n this.dir = path.join(os.homedir(), '.ai-bridge');\n this.filePath = path.join(this.dir, 'sessions.json');\n this.ttlMs = ttlMs;\n this.data = {};\n this.load();\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /**\n * Look up the CLI session ID for a given conversation.\n * Returns null if not found or expired.\n */\n get(conversationId: string): string | null {\n const record = this.data[conversationId];\n if (!record) return null;\n\n if (this.isExpired(record)) {\n delete this.data[conversationId];\n this.persist();\n return null;\n }\n\n // Touch last_used_at in memory only — persist on set/delete/shutdown.\n record.last_used_at = new Date().toISOString();\n return record.cli_session_id;\n }\n\n /**\n * Store a mapping from conversation_id to cli_session_id.\n * @param systemPrompt The system prompt used for the first message in this\n * conversation. Stored so session resets can restore it even when the\n * server omits system_prompt from the session_reset message.\n */\n set(conversationId: string, cliSessionId: string, provider: string, systemPrompt?: string | null): void {\n const now = new Date().toISOString();\n const existing = this.data[conversationId];\n this.data[conversationId] = {\n cli_session_id: cliSessionId,\n provider,\n // Preserve the original created_at when updating an existing record —\n // every resumed request calls set() again.\n created_at: existing?.created_at ?? now,\n last_used_at: now,\n // Only overwrite system_prompt when a non-null value is supplied;\n // follow-up requests carry no system_prompt and must not erase it.\n system_prompt: systemPrompt ?? existing?.system_prompt ?? null,\n };\n this.persist();\n log.debug('Session stored', { conversationId, cliSessionId, provider });\n }\n\n /**\n * Retrieve the stored system prompt for a conversation.\n * Returns null if not found or if no system_prompt was stored.\n */\n getSystemPrompt(conversationId: string): string | null {\n return this.data[conversationId]?.system_prompt ?? null;\n }\n\n /**\n * Remove a specific conversation mapping.\n */\n delete(conversationId: string): boolean {\n if (this.data[conversationId]) {\n delete this.data[conversationId];\n this.persist();\n log.debug('Session deleted', { conversationId });\n return true;\n }\n return false;\n }\n\n /**\n * Flush the current in-memory state to disk immediately. Call on bridge\n * shutdown to persist last_used_at updates made in memory via get(), which\n * would otherwise be lost and could make active sessions appear expired.\n */\n flush(): void {\n this.persist();\n log.debug('Session store flushed to disk', { count: Object.keys(this.data).length });\n }\n\n /**\n * Remove all expired sessions. Returns the number of pruned entries.\n */\n prune(): number {\n const now = Date.now();\n let pruned = 0;\n for (const [id, record] of Object.entries(this.data)) {\n if (this.isExpired(record, now)) {\n delete this.data[id];\n pruned++;\n }\n }\n if (pruned > 0) {\n this.persist();\n log.info('Pruned expired sessions', { count: pruned });\n }\n return pruned;\n }\n\n /**\n * Returns the number of active (non-expired) sessions.\n */\n size(): number {\n const now = Date.now();\n return Object.values(this.data).filter((record) => !this.isExpired(record, now)).length;\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isExpired(record: SessionRecord, now: number = Date.now()): boolean {\n const lastUsed = new Date(record.last_used_at).getTime();\n return now - lastUsed > this.ttlMs;\n }\n\n private load(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n // Support both old versioned format and new flat format\n if (parsed && typeof parsed === 'object') {\n if ('version' in parsed && 'sessions' in parsed) {\n // Migrate from old format\n const oldSessions = parsed.sessions as Record<string, {\n cli_session_id: string;\n provider_id?: string;\n provider?: string;\n created_at: number | string;\n last_used_at: number | string;\n }>;\n let migrateSkipped = 0;\n for (const [id, rec] of Object.entries(oldSessions)) {\n // Validate against corrupted old-format files: an empty\n // cli_session_id or unparseable last_used_at (NaN never expires)\n // must be skipped.\n if (!rec.cli_session_id || typeof rec.cli_session_id !== 'string') {\n log.warn('Skipping migrated session record with missing cli_session_id', { id });\n migrateSkipped++;\n continue;\n }\n const rawLastUsed = typeof rec.last_used_at === 'number'\n ? rec.last_used_at\n : new Date(rec.last_used_at ?? '').getTime();\n if (Number.isNaN(rawLastUsed)) {\n log.warn('Skipping migrated session record with invalid last_used_at', { id, last_used_at: rec.last_used_at });\n migrateSkipped++;\n continue;\n }\n this.data[id] = {\n cli_session_id: rec.cli_session_id,\n provider: rec.provider ?? rec.provider_id ?? 'unknown',\n created_at: typeof rec.created_at === 'number' ? new Date(rec.created_at).toISOString() : rec.created_at,\n last_used_at: typeof rec.last_used_at === 'number' ? new Date(rec.last_used_at).toISOString() : rec.last_used_at,\n };\n }\n if (migrateSkipped > 0) {\n log.warn('Skipped invalid session records during migration', { count: migrateSkipped });\n }\n log.debug('Migrated sessions from old format', { count: Object.keys(this.data).length });\n // Persist the migrated data so the old-format file is replaced on\n // disk and not re-migrated on every restart.\n this.persist();\n } else {\n // Validate individual records before accepting them: a missing\n // cli_session_id or unparseable last_used_at (NaN never expires)\n // would otherwise be returned by get() indefinitely.\n const rawSessions = parsed as Record<string, unknown>;\n let skipped = 0;\n for (const [id, rec] of Object.entries(rawSessions)) {\n const r = rec as Partial<SessionRecord>;\n if (!r.cli_session_id || typeof r.cli_session_id !== 'string') {\n log.warn('Skipping session record with missing cli_session_id', { id });\n skipped++;\n continue;\n }\n const lastUsed = new Date(r.last_used_at ?? '').getTime();\n if (Number.isNaN(lastUsed)) {\n log.warn('Skipping session record with invalid last_used_at', { id, last_used_at: r.last_used_at });\n skipped++;\n continue;\n }\n this.data[id] = r as SessionRecord;\n }\n if (skipped > 0) {\n log.warn('Skipped invalid session records on load', { count: skipped });\n }\n log.debug('Sessions loaded from disk', { count: Object.keys(this.data).length });\n }\n }\n }\n } catch (err) {\n // Log at error level so operators notice sessions were lost, and preserve\n // the corrupted file as a .bak for recovery.\n log.error('Failed to load sessions file — starting with empty session store', {\n error: err instanceof Error ? err.message : String(err),\n });\n try {\n if (fs.existsSync(this.filePath)) {\n const bakPath = this.filePath + '.bak';\n fs.copyFileSync(this.filePath, bakPath);\n log.error('Corrupted sessions file saved as backup', { backupPath: bakPath });\n }\n } catch {\n // Best-effort — ignore errors during backup\n }\n }\n\n // Prune on load\n this.prune();\n }\n\n // persist() uses synchronous file I/O intentionally: it is called at most\n // once per completed request, and a single SSD write is negligible next to\n // the multi-second CLI invocations it bookends.\n private persist(): void {\n try {\n if (!fs.existsSync(this.dir)) {\n // Create directory with restricted permissions (owner-only)\n fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });\n }\n // Write sessions file with restricted permissions (owner read/write only)\n fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n } catch (err) {\n log.error('Failed to persist sessions file', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n","/**\n * Clamping utilities for server-provided configuration values.\n *\n * Shared by the bridge and the test suite so both exercise the same\n * constants and logic.\n */\n\n/** Accepted range for the server-provided request_timeout (seconds). */\nexport const REQUEST_TIMEOUT_MIN_S = 10;\nexport const REQUEST_TIMEOUT_MAX_S = 3600;\n\n/** Accepted range for the server-provided heartbeat_interval (seconds). */\nexport const HEARTBEAT_MIN_S = 5;\nexport const HEARTBEAT_MAX_S = 300;\n\n/**\n * Clamp a raw request_timeout value (in seconds) from the server welcome\n * message into the acceptable range [10, 3600].\n */\nexport function clampRequestTimeout(raw: number): number {\n return Math.min(Math.max(raw, REQUEST_TIMEOUT_MIN_S), REQUEST_TIMEOUT_MAX_S);\n}\n\n/**\n * Clamp a raw heartbeat_interval value (in seconds) from the server welcome\n * message into the acceptable range [5, 300].\n */\nexport function clampHeartbeat(raw: number): number {\n return Math.min(Math.max(raw, HEARTBEAT_MIN_S), HEARTBEAT_MAX_S);\n}\n","/**\n * Custom error types for the AI Bridge.\n */\n\n/**\n * A fatal error that cannot be recovered from and should cause the bridge\n * to exit. Used for conditions like invalid/expired tokens or reconnect\n * exhaustion where retrying would be pointless.\n */\nexport class FatalBridgeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FatalBridgeError';\n }\n}\n","/**\n * CLI Detector\n *\n * Probes the local system for known AI CLI tools by attempting to run\n * `<cli> --version`. Returns an array of ProviderCapability descriptors\n * indicating what is available.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ProviderCapability } from '../protocol/types.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('Detector');\nconst execFileAsync = promisify(execFile);\n\n/** CLI probing configuration for each known provider. */\ninterface CliProbe {\n name: string;\n binary: string;\n versionArgs: string[];\n /** Parse the version string from the CLI's stdout/stderr. */\n parseVersion: (output: string) => string | null;\n supports_streaming: boolean;\n supports_tools: boolean;\n supports_thinking: boolean;\n supports_session_resume: boolean;\n}\n\nconst CLI_PROBES: CliProbe[] = [\n {\n name: 'codex',\n binary: 'codex',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n // Codex supports server-defined bridge tools via wrapper scripts on PATH.\n // See codex.ts for the full mechanism.\n supports_tools: true,\n supports_thinking: true,\n supports_session_resume: true,\n },\n {\n name: 'claude',\n binary: 'claude',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n supports_tools: true,\n supports_thinking: true,\n supports_session_resume: true,\n },\n {\n name: 'gemini',\n binary: 'gemini',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n supports_tools: true,\n supports_thinking: false,\n supports_session_resume: true,\n },\n];\n\n/**\n * Try to extract a semver-like version string from CLI output.\n * Matches patterns like \"1.2.3\", \"v1.2.3\", \"0.1.0-beta\".\n */\nfunction extractVersion(output: string): string | null {\n const match = output.match(/v?(\\d+\\.\\d+\\.\\d+[\\w.-]*)/);\n return match ? match[1] : null;\n}\n\n/**\n * Probe a single CLI binary.\n */\nasync function probeOne(probe: CliProbe): Promise<ProviderCapability> {\n const capability: ProviderCapability = {\n name: probe.name,\n version: null,\n available: false,\n supports_streaming: probe.supports_streaming,\n supports_tools: probe.supports_tools,\n supports_thinking: probe.supports_thinking,\n supports_session_resume: probe.supports_session_resume,\n };\n\n try {\n // Remove bridge credential variables from the probe environment — version\n // probes run arbitrary binaries from PATH that must not see the token.\n const probeEnv = { ...process.env };\n delete probeEnv['AI_BRIDGE_TOKEN'];\n delete probeEnv['AI_BRIDGE_SERVER'];\n\n const { stdout, stderr } = await execFileAsync(probe.binary, probe.versionArgs, {\n timeout: 5_000,\n env: probeEnv,\n });\n const output = (stdout || '') + (stderr || '');\n capability.available = true;\n capability.version = probe.parseVersion(output.trim());\n log.info(`Detected ${probe.name}`, { version: capability.version });\n } catch (err) {\n log.debug(`${probe.name} not found or not executable`, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n\n return capability;\n}\n\n/**\n * Detect all known AI CLI tools installed on this system.\n * Probes run concurrently for speed.\n *\n * @returns Array of ProviderCapability for all known providers (both available and unavailable).\n */\nexport async function detectProviders(): Promise<ProviderCapability[]> {\n log.info('Detecting locally installed AI CLI tools...');\n const results = await Promise.all(CLI_PROBES.map(probeOne));\n const available = results.filter((r) => r.available);\n log.info(`Detection complete: ${available.length}/${results.length} providers available`);\n return results;\n}\n","/**\n * Codex CLI Adapter\n *\n * Wraps the OpenAI Codex CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: codex exec --json --skip-git-repo-check --ephemeral -m <model> \"<user message>\"\n * Resume session: codex exec resume <SESSION_ID> --json \"<user message>\"\n *\n * Output format (NDJSON):\n * {\"type\":\"thread.started\",\"thread_id\":\"...\"}\n * {\"type\":\"turn.started\"}\n * {\"type\":\"item.completed\",\"item\":{\"id\":\"item_0\",\"type\":\"reasoning\",\"text\":\"...\"}}\n * {\"type\":\"item.completed\",\"item\":{\"id\":\"item_1\",\"type\":\"agent_message\",\"text\":\"...\"}}\n * {\"type\":\"turn.completed\",\"usage\":{\"input_tokens\":...,\"output_tokens\":...}}\n * {\"type\":\"error\",\"message\":\"...\"}\n * {\"type\":\"turn.failed\",\"error\":{\"message\":\"...\"}}\n */\n\nimport { spawn } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { createInterface } from 'node:readline';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, buildCombinedPrompt, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\nconst log = createLogger('CodexAdapter');\n\n/**\n * Default model to use when no model is specified in the request options.\n *\n * gpt-5.2-codex (the Codex CLI default) is NOT available on ChatGPT Team\n * plans. gpt-5.3-codex is the best coding-optimized model that works\n * with both API key and ChatGPT auth modes.\n */\nconst DEFAULT_MODEL = 'gpt-5.3-codex';\n\nexport class CodexAdapter extends ProviderAdapter {\n readonly providerName = 'codex';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Codex request', { requestId });\n\n // Build CLI arguments\n let args: string[];\n\n // Use the model from request options, or fall back to the default\n const model = request.options?.model ?? DEFAULT_MODEL;\n\n if (cliSessionId) {\n // Resume an existing session\n args = [\n 'exec', 'resume', cliSessionId,\n '--json',\n ];\n // Pass model flag on resume if specified in request options\n if (request.options?.model) {\n args.push('-m', request.options.model);\n }\n log.debug('Resuming session', { cliSessionId });\n } else {\n // New session\n args = [\n 'exec',\n '--json',\n '--skip-git-repo-check',\n '--ephemeral',\n '-m', model,\n ];\n }\n\n // Server-defined bridge tools support. Codex's `exec` sandbox defaults to\n // read-only with no network access, which blocks the wrapper script's\n // loopback callback. When tools are present we run with danger-full-access\n // and approval_policy=never: the wrapper scripts execute directly (no\n // network restriction) and exec never stalls on an approval prompt.\n // workspace-write was not used because its bubblewrap sandbox requires\n // unprivileged user namespaces, which are unavailable in many container and\n // hardened-kernel environments — there the tool callback fails entirely.\n // This matches Claude (bypassPermissions) and Gemini (--yolo): the bridge\n // runs the CLI in the user's own trusted environment. Tool-less requests\n // keep Codex's safer default sandbox.\n const hasTools = context.tools.length > 0;\n if (hasTools) {\n args.push(\n '-s', 'danger-full-access',\n '-c', 'approval_policy=never',\n );\n }\n\n // Build the prompt positional argument. The prompt is appended LAST, after\n // every option flag, so Codex's argument parser never mistakes it for a\n // flag value.\n //\n // When server tools are present we append a note listing the available\n // tool command names so Codex knows it may run them as shell commands.\n // On a fresh session the system prompt is also prepended; on a resumed\n // session the original system prompt was already consumed by the first\n // turn, so the tool note is appended to the user message directly.\n const toolNote = hasTools ? '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir) : '';\n if (!cliSessionId && request.system_prompt) {\n args.push('--', buildCombinedPrompt(request.system_prompt, userMessage) + toolNote);\n } else if (toolNote) {\n args.push('--', userMessage + toolNote);\n } else {\n args.push(userMessage);\n }\n\n // Codex CLI does not support max_tokens directly — log a warning only; do\n // not emit a stream error (the server has no actionable response and it\n // would confuse users who see an error before a successful reply).\n if (request.options?.max_tokens) {\n log.warn('max_tokens option specified but Codex CLI does not support it directly — ignoring', {\n max_tokens: request.options.max_tokens,\n });\n }\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning codex', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n\n // Prepend the tool-script directory to PATH so the wrapper commands are\n // invocable by Codex's model-generated shell commands; pass null when\n // there are no tools so PATH is left untouched.\n if (hasTools) {\n log.info('Server-defined tools enabled for Codex request', {\n toolCount: context.tools.length,\n });\n }\n const env = buildSpawnEnv(hasTools ? context.toolScriptDir : null, context.requestId);\n\n const child = spawn('codex', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing codex process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'codex',\n terminalEvent: 'turn.completed',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n // ── thread.started ─────────────────────────────────\n if (type === 'thread.started') {\n sessionId = (parsed['thread_id'] as string) ?? null;\n log.debug('Thread started', { sessionId });\n return;\n }\n\n // ── turn.started ───────────────────────────────────\n if (type === 'turn.started') {\n log.debug('Turn started');\n return;\n }\n\n // ── item.completed ─────────────────────────────────\n if (type === 'item.completed') {\n const item = parsed['item'] as Record<string, unknown> | undefined;\n if (!item) return;\n\n const itemType = item['type'] as string;\n\n if (itemType === 'agent_message') {\n // Text response from the model\n const text = item['text'] as string;\n if (!text) return;\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'text' },\n });\n\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content: text },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n\n blockIndex++;\n } else if (itemType === 'reasoning') {\n // Thinking / reasoning from the model\n const text = (item['text'] as string) ?? '';\n if (!text) return;\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'thinking' },\n });\n\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content: text },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n\n blockIndex++;\n } else if (itemType === 'error') {\n // Error item\n const message = (item['message'] as string) ?? 'Unknown Codex error';\n log.warn('Codex error item', { message: message.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n // Emit done after error so the server always gets a terminal event.\n onEvent({ event: 'done', data: {} });\n settled = true;\n }\n // function_call and function_call_output items are produced by\n // Codex's own tool execution — we don't need to relay them as\n // stream events since Codex handles tools internally.\n return;\n }\n\n // ── turn.completed ─────────────────────────────────\n if (type === 'turn.completed') {\n // Guard against duplicate done events — an error item may already\n // have settled the stream before turn.completed arrives.\n if (settled) return;\n\n const usage = parsed['usage'] as Record<string, unknown> | undefined;\n const inputTokens = usage ? (usage['input_tokens'] as number) ?? null : null;\n const outputTokens = usage ? (usage['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n // ── turn.failed ────────────────────────────────────\n if (type === 'turn.failed') {\n const error = parsed['error'] as Record<string, unknown> | undefined;\n const message = (error?.['message'] as string) ?? 'Codex turn failed';\n log.warn('Codex turn failed', { message: message.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n\n onEvent({ event: 'done', data: {} });\n settled = true;\n return;\n }\n\n // ── error (top-level) ──────────────────────────────\n if (type === 'error') {\n const message = (parsed['message'] as string) ?? 'Unknown Codex error';\n log.warn('Codex error event', { message: message.substring(0, 200) });\n\n // Emit error + done so the server is always informed, even if\n // Codex exits with code 0 after this and no turn.failed follows.\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n onEvent({ event: 'done', data: {} });\n settled = true;\n return;\n }\n\n // Ignore other event types (response_item with session_meta, turn_context, etc.)\n log.debug('Unhandled Codex event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn codex', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'codex CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn codex: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Codex process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n try {\n const cachePath = join(homedir(), '.codex', 'models_cache.json');\n const raw = await readFile(cachePath, 'utf-8');\n const cache = JSON.parse(raw) as {\n models?: Array<{\n slug: string;\n display_name: string;\n description?: string;\n visibility?: string;\n }>;\n };\n\n if (!cache.models || !Array.isArray(cache.models)) {\n log.warn('Codex models cache is empty or invalid');\n return [];\n }\n\n return cache.models\n .filter((m) => m.visibility !== 'hide') // Exclude hidden models like codex-auto-review\n .map((m) => ({\n id: m.slug,\n name: m.display_name,\n description: m.description,\n is_default: m.slug === DEFAULT_MODEL,\n }));\n } catch (err) {\n log.warn('Failed to read Codex models cache. Run codex once to populate models cache. Showing default model only.', {\n error: err instanceof Error ? err.message : String(err),\n });\n // Fallback: return just the default model\n return [\n { id: DEFAULT_MODEL, name: DEFAULT_MODEL, is_default: true },\n ];\n }\n }\n}\n","/**\n * Shared utilities for provider adapters.\n *\n * Centralizes environment variable construction, prompt building, and\n * stderr buffering that would otherwise be duplicated across all three\n * adapter implementations.\n */\n\n/** Maximum stderr buffer size (10 KB). */\nconst MAX_STDERR_BYTES = 10 * 1024;\n\n/**\n * Build the environment variables for spawning a CLI subprocess.\n *\n * @param toolScriptDir Directory containing tool wrapper scripts to prepend\n * to PATH, or null to skip PATH modification (e.g. for\n * Codex which handles tools internally).\n * @param requestId Optional request ID to pass as AI_BRIDGE_REQUEST_ID\n * env var for concurrent-request correlation.\n * @returns A copy of process.env with the requested modifications applied.\n */\nexport function buildSpawnEnv(toolScriptDir: string | null, requestId?: string): NodeJS.ProcessEnv {\n const env = { ...process.env };\n if (toolScriptDir) {\n env['PATH'] = `${toolScriptDir}:${env['PATH'] ?? ''}`;\n }\n if (requestId) {\n env['AI_BRIDGE_REQUEST_ID'] = requestId;\n }\n // Remove bridge credential variables from the child process environment so\n // the token does not leak into /proc/<pid>/environ or the CLI's own logging.\n delete env['AI_BRIDGE_TOKEN'];\n delete env['AI_BRIDGE_SERVER'];\n return env;\n}\n\n/**\n * Build a combined prompt by prepending the system prompt to the user message.\n *\n * Used by providers (Gemini, Codex) whose CLIs lack a dedicated\n * --system-prompt flag, so system instructions must be concatenated\n * into the user-facing prompt string.\n *\n * @param systemPrompt The system-level instructions.\n * @param userMessage The user's actual request message.\n * @returns A single string with the system prompt followed by the user message.\n */\nexport function buildCombinedPrompt(systemPrompt: string, userMessage: string): string {\n return `${systemPrompt}\\n\\nUser request:\\n${userMessage}`;\n}\n\n/**\n * Append a chunk to a stderr buffer, capping at MAX_STDERR_BYTES (10 KB).\n *\n * Keeps the FIRST 10 KB rather than the last, because the beginning of CLI\n * stderr almost always contains the root-cause error while the tail tends to\n * be less useful stack traces.\n *\n * @param buffer Current buffer contents.\n * @param chunk New data to append.\n * @returns The updated (possibly truncated) buffer.\n */\nexport function appendStderr(buffer: string, chunk: string): string {\n // Once we have 10 KB, stop accumulating — root-cause is already there.\n if (buffer.length >= MAX_STDERR_BYTES) {\n return buffer;\n }\n buffer += chunk;\n if (buffer.length > MAX_STDERR_BYTES) {\n buffer = buffer.slice(0, MAX_STDERR_BYTES);\n }\n return buffer;\n}\n\n/**\n * Produce a user-friendly error message from raw CLI stderr output.\n *\n * Detects common known patterns (auth failures, rate limits) and returns a\n * clear actionable message. Strips ANSI escape codes and limits to the first\n * meaningful line for unrecognized errors.\n *\n * @param provider Provider name (e.g. \"claude\") used in fallback messages.\n * @param stderr Raw stderr output from the CLI.\n * @param exitCode Process exit code (for context).\n * @returns A user-facing error string.\n */\nexport function formatStderrMessage(provider: string, stderr: string, exitCode: number | null): string {\n // Strip ANSI escape sequences\n // eslint-disable-next-line no-control-regex\n const clean = stderr.replace(/\\x1b\\[[0-9;]*[mGKHFJSTsuABCDhl]/g, '').trim();\n\n if (!clean) {\n return `${provider} CLI exited with code ${exitCode ?? 'unknown'}`;\n }\n\n const lower = clean.toLowerCase();\n\n // Auth-related patterns\n if (\n lower.includes('401') ||\n lower.includes('403') ||\n lower.includes('unauthorized') ||\n lower.includes('unauthenticated') ||\n lower.includes('auth') ||\n lower.includes('login') ||\n lower.includes('authenticate') ||\n lower.includes('not logged in') ||\n lower.includes('sign in') ||\n lower.includes('credentials')\n ) {\n // Codex uses `codex login`; Claude and Gemini use `<provider> auth login`.\n const authCmd = provider === 'codex' ? `${provider} login` : `${provider} auth login`;\n return `Authentication required — run \\`${authCmd}\\` to re-authenticate.`;\n }\n\n // Rate limit patterns\n if (\n lower.includes('rate limit') ||\n lower.includes('ratelimit') ||\n lower.includes('too many requests') ||\n lower.includes('429')\n ) {\n return `Rate limit reached — please wait a moment and try again.`;\n }\n\n // Return the first non-empty line, capped to 500 characters\n const firstLine = clean.split('\\n').find((l) => l.trim()) ?? clean;\n return firstLine.substring(0, 500);\n}\n","/**\n * Abstract base class for AI CLI provider adapters.\n *\n * Each adapter wraps a specific CLI tool (Codex, Claude, Gemini) and\n * normalizes its output into the Bridge protocol's stream event format.\n */\n\nimport { ChildProcess } from 'node:child_process';\nimport { Interface as ReadlineInterface } from 'node:readline';\nimport type {\n ModelInfo,\n AiRequestMessage,\n ToolDefinition,\n StreamEventType,\n StreamEventData,\n} from '../protocol/types.js';\nimport { formatStderrMessage } from './env.js';\n\n/** A stream event emitted by the adapter. */\nexport interface AdapterStreamEvent {\n event: StreamEventType;\n data: StreamEventData;\n}\n\n/** Context passed to a provider when executing a request. */\nexport interface ExecutionContext {\n /** The full AI request from the server. */\n request: AiRequestMessage;\n /** The request ID for correlation with tool calls, stream events, and concurrent-request correlation (set by bridge). */\n requestId: string;\n /** Tool definitions that should be made available to the CLI. */\n tools: ToolDefinition[];\n /** Path to directory containing generated tool wrapper scripts. */\n toolScriptDir: string | null;\n /** Callback to resolve a tool call through the server. */\n onToolCall: (toolCallId: string, toolName: string, args: Record<string, unknown>) => Promise<unknown>;\n /** Abort signal for cancellation. */\n signal: AbortSignal;\n /** CLI session ID if resuming, or null for new session. */\n cliSessionId: string | null;\n}\n\n/**\n * Shared subprocess finalization helper.\n *\n * Manages the race between the readline 'close' event and the child process\n * 'close' event — the 'done' event must not be sent until both have fired.\n *\n * Returns an object containing:\n * - `onRlClose` — call from rl.on('close')\n * - `onChildClose` — call from child.on('close', code)\n *\n * @param providerName Provider name used in error messages.\n * @param terminalEvent Name of the expected terminal output event\n * (e.g. 'result', 'turn.completed') for logging.\n * @param getSettled Returns the current settled flag (read-only).\n * @param setSettled Sets the settled flag to true.\n * @param getSessionId Returns the current session ID (may be null).\n * @param getStderr Returns the current stderr buffer.\n * @param onEvent Adapter's onEvent callback.\n * @param resolve Promise resolve function.\n * @param signal AbortSignal (for listener cleanup).\n * @param onAbort Abort listener to remove on finalization.\n * @param onBeforeFinalize Optional callback for provider-specific pre-finalize\n * work (e.g. closing an open text block in Gemini).\n */\nexport function createFinalizer(opts: {\n providerName: string;\n terminalEvent: string;\n getSettled: () => boolean;\n setSettled: () => void;\n getSessionId: () => string | null;\n getStderr: () => string;\n onEvent: (event: AdapterStreamEvent) => void;\n resolve: (sessionId: string | null) => void;\n signal: AbortSignal;\n onAbort: () => void;\n onBeforeFinalize?: () => void;\n}): { onRlClose: () => void; onChildClose: (code: number | null) => void } {\n let rlClosed = false;\n let childExitCode: number | null = null;\n let childExited = false;\n\n const tryFinalize = () => {\n if (!rlClosed || !childExited) return;\n opts.signal.removeEventListener('abort', opts.onAbort);\n\n if (opts.getSettled()) {\n opts.resolve(opts.getSessionId());\n return;\n }\n opts.setSettled();\n\n // Provider-specific pre-finalize work (e.g. close an open text block)\n opts.onBeforeFinalize?.();\n\n if (childExitCode !== 0 && childExitCode !== null) {\n opts.onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: formatStderrMessage(opts.providerName, opts.getStderr(), childExitCode),\n },\n });\n opts.onEvent({ event: 'done', data: {} });\n } else {\n // Clean exit but no terminal event — emit a non-fatal error.\n opts.onEvent({\n event: 'error',\n data: {\n code: 'provider_empty_response',\n message: 'The AI returned no response. Please try again.',\n },\n });\n opts.onEvent({ event: 'done', data: {} });\n }\n\n opts.resolve(opts.getSessionId());\n };\n\n return {\n onRlClose: () => {\n rlClosed = true;\n tryFinalize();\n },\n onChildClose: (code: number | null) => {\n childExitCode = code;\n childExited = true;\n tryFinalize();\n },\n };\n}\n\n// Re-export child_process types needed by adapters that use createFinalizer\nexport type { ChildProcess, ReadlineInterface };\n\nexport abstract class ProviderAdapter {\n /** Provider name / identifier (e.g. \"codex\", \"claude\", \"gemini\"). */\n abstract readonly providerName: string;\n\n /**\n * Execute an AI request by invoking the local CLI.\n *\n * The adapter should call `onEvent` for each streaming chunk produced\n * by the CLI, normalizing the output into stream event format.\n *\n * Must send a final `done` event when the CLI exits.\n * Returns the CLI session ID for future resumption (or null).\n *\n * @param context Execution context with request, tools, and tool resolution callback.\n * @param onEvent Callback for each normalized stream event.\n * @returns The CLI session ID (for session resume) or null.\n */\n abstract execute(\n context: ExecutionContext,\n onEvent: (event: AdapterStreamEvent) => void,\n ): Promise<string | null>;\n\n /**\n * List available models for this provider.\n *\n * Returns model info from local CLI config/cache where possible,\n * or known model aliases as a fallback.\n */\n abstract listModels(): Promise<ModelInfo[]>;\n}\n","/**\n * Tool Prompt Manifest\n *\n * The bridge exposes each server-registered tool as a Bash wrapper script on\n * the AI CLI's PATH (filename = tool name). None of the supported CLIs\n * (claude / codex / gemini) have a protocol-level concept of these external\n * tools, so the model is never told they exist. This module builds a\n * human-readable markdown manifest describing the tools, their parameters,\n * and how to call them, which provider adapters append to the prompt the\n * model receives.\n */\n\nimport type { ToolDefinition } from '../protocol/types.js';\n\n/** Shape of a single JSON Schema property entry we care about. */\ninterface SchemaProperty {\n type?: unknown;\n description?: unknown;\n}\n\n/**\n * Build a markdown manifest describing the server-defined bridge tools.\n *\n * Each tool is documented as a shell command on PATH that takes a single\n * JSON-object argument and prints its result to stdout. When `tools` is empty\n * an empty string is returned so callers can append unconditionally.\n *\n * The function is intentionally defensive: a tool's `parameters` may be `{}`,\n * may be missing `properties`, and individual property entries may not be\n * objects. Anything malformed is described as best-effort rather than throwing.\n *\n * @param tools The server-defined tool definitions for this request.\n * @returns A markdown manifest, or an empty string when there are no tools.\n */\nexport function buildToolInstructions(\n tools: ToolDefinition[],\n toolScriptDir?: string | null,\n): string {\n if (tools.length === 0) return '';\n\n // Use the absolute path to each tool script in examples. Relying on PATH is\n // fragile — some CLIs run commands through a login shell (`bash -lc`) that\n // re-sources profile scripts and resets PATH, dropping the bridge's tool\n // directory. An absolute path always resolves.\n const cmd = (name: string): string =>\n toolScriptDir ? `${toolScriptDir}/${name}` : name;\n\n const sections: string[] = [\n '# Available Tools',\n '',\n 'You have access to the following tools. Each tool is an executable ' +\n 'script. Call a tool by running it as a shell command with a single ' +\n 'argument: a JSON object containing the parameters. The command prints ' +\n 'its result to stdout. Run each tool using the exact path shown in its ' +\n '\"Call it like\" example. Use these tools whenever they help fulfill the ' +\n 'request.',\n ];\n\n for (const tool of tools) {\n sections.push('', `## ${tool.name}`, '', tool.description);\n\n const parameters = (tool.parameters ?? {}) as Record<string, unknown>;\n const properties = (parameters['properties'] ?? {}) as Record<string, unknown>;\n const required = Array.isArray(parameters['required'])\n ? (parameters['required'] as unknown[]).map((r) => String(r))\n : [];\n const propNames = Object.keys(properties);\n\n if (propNames.length === 0) {\n sections.push('', 'Parameters: No parameters');\n sections.push('', `Call it like: ${cmd(tool.name)} '{}'`);\n continue;\n }\n\n sections.push('', 'Parameters:');\n for (const pname of propNames) {\n const raw = properties[pname];\n const prop: SchemaProperty =\n raw && typeof raw === 'object' ? (raw as SchemaProperty) : {};\n const type = typeof prop.type === 'string' ? prop.type : 'any';\n const requiredLabel = required.includes(pname) ? 'required' : 'optional';\n const description =\n typeof prop.description === 'string' && prop.description.length > 0\n ? prop.description\n : 'No description';\n sections.push(`- ${pname} (${type}, ${requiredLabel}): ${description}`);\n }\n\n // Example uses the first parameter so the model sees the calling shape.\n const exampleParam = propNames[0];\n sections.push(\n '',\n `Call it like: ${cmd(tool.name)} '{\"${exampleParam}\":\"<value>\"}'`,\n );\n }\n\n return sections.join('\\n');\n}\n","/**\n * Claude CLI Adapter\n *\n * Wraps the Anthropic Claude CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: claude -p --output-format stream-json --verbose \"user message\"\n * Resume session: claude -p --session-id <UUID> --output-format stream-json --verbose \"user message\"\n * System prompt: Passed via --system-prompt flag on first message\n *\n * Output format (NDJSON):\n * {\"type\":\"system\",\"subtype\":\"init\",\"session_id\":\"...\",\"model\":\"...\"}\n * {\"type\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"...\"}],...}}\n * {\"type\":\"result\",\"subtype\":\"success\",\"session_id\":\"...\",\"usage\":{...},\"total_cost_usd\":...}\n */\n\nimport { spawn } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\n/**\n * Known Claude CLI model aliases.\n *\n * Claude Code uses stable aliases (sonnet, opus, haiku) that resolve to the\n * latest version within each model family. The CLI has no dynamic model listing\n * command, so these aliases are the official user-facing interface.\n */\nconst CLAUDE_MODELS: ModelInfo[] = [\n { id: 'sonnet', name: 'Sonnet', description: 'Best balance of speed and intelligence', is_default: true },\n { id: 'opus', name: 'Opus', description: 'Highest intelligence, slower', is_default: false },\n { id: 'haiku', name: 'Haiku', description: 'Fastest and most cost-efficient', is_default: false },\n];\n\nconst log = createLogger('ClaudeAdapter');\n\nexport class ClaudeAdapter extends ProviderAdapter {\n readonly providerName = 'claude';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Claude request', { requestId });\n\n // Build CLI arguments\n const args: string[] = [\n '-p', // Print mode (non-interactive)\n '--output-format', 'stream-json', // NDJSON streaming output\n '--verbose', // Required for stream-json in print mode\n ];\n\n // Resume an existing session if we have a session ID\n if (cliSessionId) {\n args.push('--session-id', cliSessionId);\n log.debug('Resuming session', { cliSessionId });\n }\n\n // Add system prompt if provided (only on new sessions)\n if (request.system_prompt && !cliSessionId) {\n args.push('--system-prompt', request.system_prompt);\n }\n\n // Add model if specified in request options\n if (request.options?.model) {\n args.push('--model', request.options.model);\n }\n\n // Add max tokens if specified\n if (request.options?.max_tokens) {\n args.push('--max-tokens', String(request.options.max_tokens));\n }\n\n // Enable tool execution when server-defined tools are available. The wrapper\n // scripts are invoked through Claude's Bash tool; in headless (-p) mode every\n // Bash command would otherwise be denied with \"requires approval\" since there\n // is no interactive approver. bypassPermissions auto-approves tool use — the\n // bridge runs in the user's own trusted environment, mirroring Codex\n // (approval_policy=never) and Gemini (--yolo).\n if (context.tools.length > 0 && context.toolScriptDir) {\n args.push('--permission-mode', 'bypassPermissions');\n }\n\n // The user message is the final argument. When server-defined tools are\n // present, append the tool manifest so the model knows the tools exist and\n // how to call them — appended every turn (new and resumed sessions) since\n // Claude has no protocol-level concept of these external tools.\n let promptArg = userMessage;\n if (context.tools.length > 0) {\n promptArg += '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir);\n }\n args.push(promptArg);\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning claude', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve, reject) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n\n // Build env with tool scripts on PATH and request ID for correlation\n const env = buildSpawnEnv(context.toolScriptDir, context.requestId);\n // Claude CLI refuses to run if CLAUDECODE is set, even to empty string\n delete env['CLAUDECODE'];\n\n const child = spawn('claude', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'], // stdin must be 'ignore' — Claude CLI hangs if stdin is a pipe\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing claude process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'claude',\n terminalEvent: 'result',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n if (type === 'system' && (parsed as Record<string, unknown>)['subtype'] === 'init') {\n // Extract session ID from init event\n sessionId = (parsed['session_id'] as string) ?? null;\n log.debug('Session init', { sessionId, model: parsed['model'] });\n return;\n }\n\n if (type === 'assistant') {\n // A late readline-buffered assistant event can arrive after the\n // stream is already settled; log it for diagnosis.\n if (settled) {\n log.debug('Assistant event received after stream settled — block events would be emitted post-done', {\n sessionId,\n });\n }\n\n // The assistant message contains the content blocks\n const message = parsed['message'] as Record<string, unknown> | undefined;\n if (!message) return;\n\n const content = message['content'] as Array<Record<string, unknown>> | undefined;\n if (!content || !Array.isArray(content)) return;\n\n for (const block of content) {\n const blockType = block['type'] as string;\n\n if (blockType === 'text') {\n const text = block['text'] as string;\n if (!text) continue;\n\n // Emit block_start + block_delta + block_stop for text\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'text',\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: text,\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n } else if (blockType === 'thinking') {\n const thinking = block['thinking'] as string;\n if (!thinking) continue;\n\n // Emit thinking block\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'thinking',\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: thinking,\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n } else if (blockType === 'tool_use') {\n // Claude emits tool_use blocks when the model wants to call a tool\n const toolName = block['name'] as string;\n const toolId = block['id'] as string;\n const toolInput = block['input'] as Record<string, unknown> | undefined;\n\n if (!toolName || !toolId) continue;\n\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'tool_call',\n tool_name: toolName,\n tool_call_id: toolId,\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: JSON.stringify(toolInput ?? {}),\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n }\n }\n return;\n }\n\n if (type === 'result') {\n // Extract final session ID and usage from result\n sessionId = (parsed['session_id'] as string) ?? sessionId;\n\n const usage = parsed['usage'] as Record<string, unknown> | undefined;\n const inputTokens = usage ? (usage['input_tokens'] as number) ?? null : null;\n const outputTokens = usage ? (usage['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n // rate_limit_event is informational — Claude Code emits it to report\n // rate-limit status (often status \"allowed\") and continues streaming.\n // It must NOT be turned into a terminal error: doing so aborts the\n // request mid-stream. A genuine hard rate-limit surfaces through the\n // result event / non-zero exit, which the normal error path handles.\n if (type === 'rate_limit_event') {\n log.debug('Claude rate limit event (informational)', {\n status: (parsed['rate_limit_info'] as Record<string, unknown> | undefined)?.['status'],\n });\n return;\n }\n\n log.debug('Unhandled Claude event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn claude', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'claude CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn claude: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Claude process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n // Claude CLI has no dynamic model listing — return known aliases\n return CLAUDE_MODELS;\n }\n}\n","/**\n * Gemini CLI Adapter\n *\n * Wraps the Google Gemini CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: gemini --prompt \"user message\" --output-format stream-json\n * Resume session: gemini --prompt \"user message\" --resume <session-id> --output-format stream-json\n *\n * Output format (NDJSON):\n * {\"type\":\"init\",\"session_id\":\"...\",\"model\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"message\",\"role\":\"user\",\"content\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"message\",\"role\":\"assistant\",\"content\":\"...\",\"delta\":true,\"timestamp\":\"...\"}\n * {\"type\":\"tool_use\",\"tool_name\":\"...\",\"tool_id\":\"...\",\"parameters\":{...},\"timestamp\":\"...\"}\n * {\"type\":\"tool_result\",\"tool_id\":\"...\",\"status\":\"success|error\",\"output\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"error\",\"severity\":\"warning|error\",\"message\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"result\",\"status\":\"success|error\",\"stats\":{...},\"timestamp\":\"...\"}\n */\n\nimport { spawn } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, buildCombinedPrompt, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\n/**\n * Known Gemini CLI model aliases and models.\n *\n * Gemini CLI supports aliases (auto, pro, flash, flash-lite) that resolve to\n * concrete model versions. Like Claude, there is no `--list-models` command,\n * but the aliases are the official user-facing interface.\n */\nconst GEMINI_MODELS: ModelInfo[] = [\n { id: 'auto', name: 'Auto', description: 'Automatically selects the best model', is_default: true },\n { id: 'pro', name: 'Pro', description: 'Complex reasoning tasks (Gemini 2.5 Pro)', is_default: false },\n { id: 'flash', name: 'Flash', description: 'Fast and balanced (Gemini 2.5 Flash)', is_default: false },\n { id: 'flash-lite', name: 'Flash Lite', description: 'Fastest for simple tasks (Gemini 2.5 Flash Lite)', is_default: false },\n];\n\nconst log = createLogger('GeminiAdapter');\n\nexport class GeminiAdapter extends ProviderAdapter {\n readonly providerName = 'gemini';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Gemini request', { requestId });\n\n // Build the prompt — prepend system prompt if provided (Gemini CLI\n // has no dedicated --system-instruction flag, so we concatenate)\n let prompt = userMessage;\n if (request.system_prompt && !cliSessionId) {\n prompt = buildCombinedPrompt(request.system_prompt, userMessage);\n }\n\n // When server-defined tools are present, append the tool manifest so the\n // model knows the tools exist and how to call them — appended every turn\n // (new and resumed sessions) since Gemini has no protocol-level concept of\n // these external tools.\n const hasTools = context.tools.length > 0;\n if (hasTools) {\n prompt += '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir);\n }\n\n // Build CLI arguments\n const args: string[] = [\n '--prompt', prompt, // Non-interactive mode with prompt\n '--output-format', 'stream-json', // NDJSON streaming output\n '--skip-trust', // Required for headless/non-interactive mode\n ];\n\n // Server-defined bridge tools are invoked as shell commands. --skip-trust\n // only trusts the workspace folder; it does NOT auto-approve tool/shell\n // execution. In non-interactive --prompt mode Gemini's default approval\n // mode would block on an approval prompt the model cannot answer, stalling\n // the request. --yolo (approval-mode \"yolo\") auto-approves all tool calls\n // so the wrapper scripts can run. Only enabled when tools are present so\n // tool-less requests keep Gemini's safer default approval behavior.\n if (hasTools) {\n args.push('--yolo');\n }\n\n // Resume an existing session if we have a session ID\n if (cliSessionId) {\n args.push('--resume', cliSessionId);\n log.debug('Resuming session', { cliSessionId });\n }\n\n // Add model if specified in request options\n if (request.options?.model) {\n args.push('--model', request.options.model);\n }\n\n // Gemini CLI does not support max_tokens directly — log a warning only; do\n // not emit a stream error (the server has no actionable response and it\n // would confuse users who see an error before a successful reply).\n if (request.options?.max_tokens) {\n log.warn('max_tokens option specified but Gemini CLI does not support it directly — ignoring', {\n max_tokens: request.options.max_tokens,\n });\n }\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning gemini', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n let inTextBlock = false;\n\n // Build env with tool scripts on PATH and request ID for correlation\n const env = buildSpawnEnv(context.toolScriptDir, context.requestId);\n\n const child = spawn('gemini', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'], // stdin must be 'ignore' to prevent hanging\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing gemini process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'gemini',\n terminalEvent: 'result',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n // Gemini-specific: close any open text block before finalizing\n onBeforeFinalize: () => {\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n },\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n // ── init ─────────────────────────────────────────────\n if (type === 'init') {\n sessionId = (parsed['session_id'] as string) ?? null;\n log.debug('Session init', { sessionId, model: parsed['model'] });\n return;\n }\n\n // ── message ──────────────────────────────────────────\n if (type === 'message') {\n const role = parsed['role'] as string;\n\n // Skip the user message echo\n if (role === 'user') return;\n\n if (role === 'assistant') {\n const content = parsed['content'] as string;\n const isDelta = parsed['delta'] as boolean | undefined;\n\n if (!content) return;\n\n if (isDelta) {\n // Streaming delta — Gemini sends multiple delta messages\n // Open a text block if not already open\n if (!inTextBlock) {\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'text',\n },\n });\n inTextBlock = true;\n }\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content,\n },\n });\n } else {\n // Non-delta full message (rare in stream-json mode, but handle it)\n // Close any open block first\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'text' },\n });\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content },\n });\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n }\n }\n return;\n }\n\n // ── tool_use ─────────────────────────────────────────\n if (type === 'tool_use') {\n // Close any open text block before tool use\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n // Emit as a tool_call block\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'tool_call',\n tool_name: parsed['tool_name'] as string,\n tool_call_id: parsed['tool_id'] as string,\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: JSON.stringify(parsed['parameters'] ?? {}),\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n return;\n }\n\n // ── tool_result ──────────────────────────────────────\n if (type === 'tool_result') {\n const toolId = parsed['tool_id'] as string;\n const output = (parsed['output'] as string) ?? '';\n const status = parsed['status'] as string;\n\n onEvent({\n event: 'tool_result',\n data: {\n tool_call_id: toolId,\n result: status === 'error'\n ? `Error: ${(parsed['error'] as Record<string, unknown>)?.['message'] ?? output}`\n : output,\n },\n });\n return;\n }\n\n // ── error (non-fatal or fatal) ──────────────────────\n if (type === 'error') {\n const severity = parsed['severity'] as string;\n const message = parsed['message'] as string;\n log.warn('Gemini error event', { severity, message: message?.substring(0, 200) });\n\n // severity='error' and severity='warning' are both forwarded to the\n // server as stream events.\n if (severity === 'error') {\n onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: message ?? 'Unknown Gemini error',\n },\n });\n // Fatal errors terminate the response — emit done and mark settled.\n onEvent({ event: 'done', data: {} });\n settled = true;\n }\n // severity 'warning' is non-fatal and informational — Gemini keeps\n // streaming. It must NOT be emitted as a stream 'error' event: the\n // server treats every error as terminal and would abort the request\n // (dropping subsequent content and tool calls). Log it locally only.\n return;\n }\n\n // ── result (final) ───────────────────────────────────\n if (type === 'result') {\n // Guard against duplicate done events — a fatal 'error' event may\n // already have settled this request before result arrives.\n if (settled) return;\n\n // Close any open text block\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n const status = parsed['status'] as string;\n\n if (status === 'error') {\n const error = parsed['error'] as Record<string, unknown> | undefined;\n const errorMessage = (error?.['message'] as string) ?? 'Gemini request failed';\n log.warn('Gemini result error', { type: error?.['type'], message: errorMessage.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: errorMessage,\n },\n });\n }\n\n // Extract usage stats\n const stats = parsed['stats'] as Record<string, unknown> | undefined;\n const inputTokens = stats ? (stats['input_tokens'] as number) ?? null : null;\n const outputTokens = stats ? (stats['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n log.debug('Unhandled Gemini event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn gemini', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'gemini CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn gemini: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Gemini process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n // Gemini CLI has no dynamic model listing — return known aliases\n return GEMINI_MODELS;\n }\n}\n","/**\n * Test Mode\n *\n * When the bridge is started with --test, incoming ai_request messages\n * are answered with mock streaming data instead of invoking a real CLI.\n *\n * This is useful for testing the WebSocket connection and the streaming\n * protocol without needing a real AI CLI installed.\n *\n * Mock response flow:\n * block_start (thinking, index 0)\n * block_delta (thinking content)\n * block_stop (index 0)\n * block_start (text, index 1)\n * block_delta (text content, multiple chunks)\n * block_stop (index 1)\n * done (with mock usage)\n */\n\nimport type { AiRequestMessage, StreamEventType, StreamEventData } from './protocol/types.js';\nimport { createLogger } from './utils/logger.js';\n\nconst log = createLogger('TestMode');\n\n/** Small delay helper for simulating streaming. */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Handle an AI request with mock streaming data.\n *\n * @param request The incoming AI request from the server.\n * @param sendEvent Function to send a stream event back to the server.\n */\nexport async function handleTestRequest(\n request: AiRequestMessage,\n sendEvent: (event: StreamEventType, data: StreamEventData) => void,\n): Promise<void> {\n log.info('Test mode: handling mock request', {\n requestId: request.request_id,\n provider: request.provider,\n message: request.message.slice(0, 80),\n });\n\n // Simulate a short delay before starting\n await delay(100);\n\n // --- Thinking block (index 0) ---\n sendEvent('block_start', {\n block_index: 0,\n block_type: 'thinking',\n });\n await delay(50);\n\n const thinkingChunks = [\n 'Let me think about this request... ',\n `The user asked: \"${request.message.slice(0, 50)}\". `,\n 'I will provide a helpful mock response.',\n ];\n\n for (const chunk of thinkingChunks) {\n sendEvent('block_delta', {\n block_index: 0,\n content: chunk,\n });\n await delay(30);\n }\n\n sendEvent('block_stop', {\n block_index: 0,\n });\n await delay(50);\n\n // --- Text block (index 1) ---\n sendEvent('block_start', {\n block_index: 1,\n block_type: 'text',\n });\n await delay(50);\n\n const textChunks = [\n 'This is a **mock response** from ai-bridge test mode. ',\n 'The bridge is connected and streaming is working correctly. ',\n `Your request was routed to the \"${request.provider}\" provider. `,\n 'In production, this response would come from your local CLI tool. ',\n `\\n\\nOriginal message: \"${request.message.slice(0, 100)}\"`,\n ];\n\n for (const chunk of textChunks) {\n sendEvent('block_delta', {\n block_index: 1,\n content: chunk,\n });\n await delay(40);\n }\n\n sendEvent('block_stop', {\n block_index: 1,\n });\n await delay(50);\n\n // --- Done ---\n sendEvent('done', {\n usage: {\n input_tokens: 42,\n output_tokens: 108,\n },\n });\n\n log.info('Test mode: mock response complete', { requestId: request.request_id });\n}\n"],"mappings":";;;AAYA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;;;ACKxB,SAAS,oBAAoB;AAC7B,OAAOA,aAAY;AACnB,OAAO,eAAe;;;ACjBf,IAAM,mBAAmB;AAGzB,IAAM,iBAAyB;;;ACUtC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;;;ACTf,IAAM,iBAA2C;AAAA,EAC/C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,cAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAI,eAAyB;AAGtB,SAAS,SAAS,SAAwB;AAC/C,iBAAe,UAAU,UAAU;AACrC;AAGO,SAAS,iBAA0B;AACxC,SAAO,iBAAiB;AAC1B;AAEA,SAAS,kBAA0B;AACjC,UAAO,oBAAI,KAAK,GAAE,YAAY;AAChC;AAEA,SAAS,UAAU,OAA0B;AAC3C,SAAO,eAAe,KAAK,KAAK,eAAe,YAAY;AAC7D;AAEA,SAAS,cAAc,OAAiB,WAAmB,SAAiB,MAAwC;AAClH,QAAM,KAAK,gBAAgB;AAC3B,QAAM,QAAQ,YAAY,KAAK;AAC/B,QAAM,UAAU,OAAO,MAAM,KAAK,UAAU,IAAI,IAAI;AACpD,SAAO,GAAG,EAAE,KAAK,KAAK,MAAM,SAAS,KAAK,OAAO,GAAG,OAAO;AAC7D;AAUO,SAAS,aAAa,WAAmB;AAC9C,SAAO;AAAA,IACL,MAAM,SAAiB,MAAsC;AAC3D,UAAI,UAAU,OAAO,GAAG;AACtB,gBAAQ,OAAO,MAAM,cAAc,SAAS,WAAW,SAAS,IAAI,IAAI,IAAI;AAAA,MAC9E;AAAA,IACF;AAAA,IAEA,KAAK,SAAiB,MAAsC;AAC1D,UAAI,UAAU,MAAM,GAAG;AACrB,gBAAQ,OAAO,MAAM,cAAc,QAAQ,WAAW,SAAS,IAAI,IAAI,IAAI;AAAA,MAC7E;AAAA,IACF;AAAA,IAEA,KAAK,SAAiB,MAAsC;AAC1D,UAAI,UAAU,MAAM,GAAG;AACrB,gBAAQ,OAAO,MAAM,cAAc,QAAQ,WAAW,SAAS,IAAI,IAAI,IAAI;AAAA,MAC7E;AAAA,IACF;AAAA,IAEA,MAAM,SAAiB,MAAsC;AAC3D,UAAI,UAAU,OAAO,GAAG;AACtB,gBAAQ,OAAO,MAAM,cAAc,SAAS,WAAW,SAAS,IAAI,IAAI,IAAI;AAAA,MAC9E;AAAA,IACF;AAAA,EACF;AACF;;;AD9DA,IAAM,MAAM,aAAa,aAAa;AAGtC,IAAM,yBAAyB;AAG/B,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAM;AAAA,EACpD;AAAA,EAAU;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAO;AAAA,EACnD;AAAA,EAAO;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAS;AAAA,EAAS;AAAA,EACjD;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAO;AAAA,EAAQ;AAAA,EAClD;AAAA,EAAW;AAAA,EAAM;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAS;AAAA,EAAQ;AAAA,EACpD;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAM;AAAA,EACtD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EACzD;AACF,CAAC;AAEM,IAAM,cAAN,MAAkB;AAAA,EACf,QAAQ,oBAAI,IAA4B;AAAA,EACxC,YAA2B;AAAA;AAAA,EAE3B,gBAA0B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnC,SAAS,OAA+B;AACtC,SAAK,MAAM,MAAM;AACjB,UAAM,gBAA0B,CAAC;AAEjC,eAAW,QAAQ,OAAO;AAExB,UAAI,CAAC,uBAAuB,KAAK,KAAK,IAAI,GAAG;AAC3C,YAAI,MAAM,kCAAkC;AAAA,UAC1C,MAAM,KAAK,KAAK,UAAU,GAAG,GAAG;AAAA,UAChC,QAAQ;AAAA,QACV,CAAC;AACD,sBAAc,KAAK,KAAK,KAAK,UAAU,GAAG,GAAG,CAAC;AAC9C;AAAA,MACF;AAIA,UAAI,oBAAoB,IAAI,KAAK,KAAK,YAAY,CAAC,GAAG;AACpD,YAAI,MAAM,oCAAoC;AAAA,UAC5C,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,QACV,CAAC;AACD,sBAAc,KAAK,KAAK,IAAI;AAC5B;AAAA,MACF;AAEA,WAAK,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAChC;AAGA,SAAK,gBAAgB;AAErB,QAAI,cAAc,SAAS,GAAG;AAC5B,UAAI,KAAK,qCAAqC;AAAA,QAC5C,OAAO,cAAc;AAAA,QACrB,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,oBAAoB,EAAE,UAAU,KAAK,MAAM,MAAM,OAAO,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,EACvH;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,MAA0C;AAC5C,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,SAA2B;AACzB,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAgB;AACd,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAAkC;AAChC,WAAO,IAAI,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,gBAAgB,cAAsB,QAAiB,YAAoB,KAAiB;AAE1F,SAAK,eAAe;AAEpB,SAAK,YAAY,GAAG,YAAY,KAAK,KAAK,GAAG,OAAO,GAAG,kBAAkB,CAAC;AAC1E,QAAI,MAAM,iCAAiC,EAAE,KAAK,KAAK,UAAU,CAAC;AAElE,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,YAAM,aAAa,KAAK,KAAK,KAAK,WAAW,KAAK,IAAI;AACtD,YAAM,gBAAgB,KAAK,YAAY,MAAM,cAAc,QAAQ,SAAS;AAG5E,SAAG,cAAc,YAAY,eAAe,EAAE,MAAM,IAAM,CAAC;AAC3D,UAAI,MAAM,yBAAyB,EAAE,MAAM,KAAK,MAAM,MAAM,WAAW,CAAC;AAAA,IAC1E;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AACrB,QAAI,KAAK,WAAW;AAClB,UAAI;AACF,WAAG,OAAO,KAAK,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC1D,YAAI,MAAM,oCAAoC,EAAE,KAAK,KAAK,UAAU,CAAC;AAAA,MACvE,SAAS,KAAK;AACZ,YAAI,KAAK,mCAAmC;AAAA,UAC1C,KAAK,KAAK;AAAA,UACV,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY,MAAsB,cAAsB,QAAiB,YAAoB,KAAiB;AACpH,UAAM,YAAY,UAAU;AAC5B,WAAO;AAAA,iCACsB,KAAK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0DAwCgB,YAAY,2DAA2D,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAmBrH,KAAK,IAAI,6DAA6D,SAAS;AAAA;AAAA,EAElG;AACF;;;AEvPA,IAAMC,OAAM,aAAa,cAAc;AAmBhC,IAAM,eAAN,MAAmB;AAAA,EAChB,UAAU,oBAAI,IAA6B;AAAA,EAC3C;AAAA,EAER,YAAY,YAAoB,KAAS;AACvC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,IAAkB;AAC7B,SAAK,YAAY;AACjB,IAAAA,KAAI,MAAM,iCAAiC,EAAE,WAAW,GAAG,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,KACE,QACA,WACA,YACA,UACA,MACkB;AAClB,WAAO,IAAI,QAAiB,CAAC,SAAS,WAAW;AAC/C,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,QAAQ,OAAO,UAAU;AAC9B,cAAM,UAAU,KAAK,MAAM,KAAK,YAAY,GAAI;AAChD,cAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,cAAM,gBAAgB,WAAW,KAAK,GAAG,OAAO,IAAI,YAAY,IAAI,WAAW,SAAS,KAAK,GAAG,OAAO;AACvG,eAAO,IAAI,MAAM,aAAa,QAAQ,KAAK,UAAU,qBAAqB,aAAa,EAAE,CAAC;AAAA,MAC5F,GAAG,KAAK,SAAS;AAEjB,WAAK,QAAQ,IAAI,YAAY;AAAA,QAC3B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,MAAAA,KAAI,MAAM,+BAA+B,EAAE,WAAW,YAAY,SAAS,CAAC;AAC5E,aAAO,WAAW,YAAY,UAAU,IAAI;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,YAAoB,QAA0B;AACpD,UAAM,UAAU,KAAK,QAAQ,IAAI,UAAU;AAC3C,QAAI,CAAC,SAAS;AACZ,MAAAA,KAAI,KAAK,kDAAkD,EAAE,WAAW,CAAC;AACzE,aAAO;AAAA,IACT;AAEA,iBAAa,QAAQ,KAAK;AAC1B,SAAK,QAAQ,OAAO,UAAU;AAC9B,IAAAA,KAAI,MAAM,sBAAsB,EAAE,YAAY,UAAU,QAAQ,SAAS,CAAC;AAC1E,YAAQ,QAAQ,MAAM;AACtB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAoB,OAAwB;AACjD,UAAM,UAAU,KAAK,QAAQ,IAAI,UAAU;AAC3C,QAAI,CAAC,SAAS;AACZ,MAAAA,KAAI,KAAK,gDAAgD,EAAE,WAAW,CAAC;AACvE,aAAO;AAAA,IACT;AAEA,iBAAa,QAAQ,KAAK;AAC1B,SAAK,QAAQ,OAAO,UAAU;AAC9B,IAAAA,KAAI,MAAM,sBAAsB,EAAE,YAAY,UAAU,QAAQ,UAAU,MAAM,CAAC;AACjF,YAAQ,OAAO,IAAI,MAAM,eAAe,QAAQ,QAAQ,MAAM,KAAK,EAAE,CAAC;AACtE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAkB;AAChB,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,SAAS;AACxC,mBAAa,QAAQ,KAAK;AAC1B,cAAQ,OAAO,IAAI,MAAM,gDAA2C,CAAC;AAAA,IACvE;AACA,UAAM,QAAQ,KAAK,QAAQ;AAC3B,SAAK,QAAQ,MAAM;AACnB,QAAI,QAAQ,GAAG;AACb,MAAAA,KAAI,KAAK,oCAAoC,EAAE,MAAM,CAAC;AAAA,IACxD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK,QAAQ;AAAA,EACtB;AACF;;;AClIA,OAAO,UAAU;AACjB,OAAO,YAAY;AAInB,IAAMC,OAAM,aAAa,oBAAoB;AAG7C,IAAM,gBAAgB;AAEf,IAAM,qBAAN,MAAyB;AAAA,EAU9B,YACmB,cACA,QACjB,qBACA,QACA;AAJiB;AACA;AAIjB,QAAI,qBAAqB;AACvB,WAAK,sBAAsB;AAAA,IAC7B;AACA,SAAK,SAAS,UAAU;AAAA,EAC1B;AAAA,EATmB;AAAA,EACA;AAAA,EAXX,SAA6B;AAAA,EAC7B,OAAsB;AAAA;AAAA,EAGtB,sBAA0C;AAAA;AAAA,EAGjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBjB,uBAAuB,OAA0B;AAC/C,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAyB;AAC7B,QAAI,KAAK,QAAQ;AACf,aAAO,KAAK;AAAA,IACd;AAEA,WAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,WAAK,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC5C,aAAK,cAAc,KAAK,GAAG;AAAA,MAC7B,CAAC;AAGD,WAAK,OAAO,OAAO,GAAG,aAAa,MAAM;AACvC,cAAM,OAAO,KAAK,OAAQ,QAAQ;AAClC,YAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,eAAK,OAAO,KAAK;AACjB,UAAAA,KAAI,KAAK,gCAAgC,EAAE,MAAM,KAAK,KAAK,CAAC;AAC5D,kBAAQ,KAAK,IAAI;AAAA,QACnB,OAAO;AACL,iBAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA,QAClD;AAAA,MACF,CAAC;AAED,WAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,QAAAA,KAAI,MAAM,8BAA8B,EAAE,OAAO,IAAI,QAAQ,CAAC;AAC9D,eAAO,GAAG;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,OAAQ;AAElB,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,WAAK,OAAQ,MAAM,MAAM;AACvB,QAAAA,KAAI,KAAK,8BAA8B;AACvC,aAAK,SAAS;AACd,aAAK,OAAO;AACZ,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,UAAyB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,KAA2B,KAAgC;AAC/E,QAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,cAAc;AACrD,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAC9C;AAAA,IACF;AAIA,QAAI,KAAK,QAAQ;AACf,YAAM,aAAa,IAAI,QAAQ,eAAe;AAC9C,YAAM,WAAW,UAAU,KAAK,MAAM;AAEtC,UACE,CAAC,cACD,WAAW,WAAW,SAAS,UAC/B,CAAC,OAAO,gBAAgB,OAAO,KAAK,UAAU,GAAG,OAAO,KAAK,QAAQ,CAAC,GACtE;AACA,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACjD;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO;AACX,QAAI,aAAa;AAEjB,QAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,oBAAc,MAAM;AACpB,UAAI,aAAa,eAAe;AAC9B,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,CAAC;AAC3D,YAAI,QAAQ;AACZ;AAAA,MACF;AACA,cAAQ,MAAM,SAAS;AAAA,IACzB,CAAC;AAED,QAAI,GAAG,OAAO,MAAM;AAElB,UAAI,aAAa,cAAe;AAEhC,WAAK,gBAAgB,MAAM,GAAG,EAAE,MAAM,CAAC,QAAQ;AAC7C,QAAAA,KAAI,MAAM,+BAA+B;AAAA,UACvC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC,CAAC;AAAA,MACrF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,gBAAgB,MAAc,KAAyC;AACnF,QAAI;AAEJ,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACN,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACjD;AAAA,IACF;AAEA,UAAM,EAAE,WAAW,WAAW,KAAK,IAAI;AAGvC,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,WAAW;AACd,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,uDAAuD,CAAC,CAAC;AACzF;AAAA,IACF;AAGA,QAAI,KAAK,uBAAuB,CAAC,KAAK,oBAAoB,IAAI,SAAS,GAAG;AACxE,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iBAAiB,SAAS,GAAG,CAAC,CAAC;AAC/D;AAAA,IACF;AAEA,UAAM,aAAa,MAAM,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAE3E,IAAAA,KAAI,MAAM,wCAAwC,EAAE,WAAW,YAAY,UAAU,CAAC;AAEtF,QAAI;AAEF,YAAM,SAAS,MAAM,KAAK,aAAa;AAAA,QACrC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAEA,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM,EAAE,CAAC,CAAC;AAAA,IAClG,SAAS,KAAK;AACZ,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC,CAAC;AAAA,IACrF;AAAA,EACF;AACF;;;ACtMA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAGf,IAAMC,OAAM,aAAa,cAAc;AAkBvC,IAAM,iBAAiB,IAAI,KAAK,KAAK,KAAK;AAEnC,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAER,YAAY,QAAgB,gBAAgB;AAC1C,SAAK,MAAMC,MAAK,KAAKC,IAAG,QAAQ,GAAG,YAAY;AAC/C,SAAK,WAAWD,MAAK,KAAK,KAAK,KAAK,eAAe;AACnD,SAAK,QAAQ;AACb,SAAK,OAAO,CAAC;AACb,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,IAAI,gBAAuC;AACzC,UAAM,SAAS,KAAK,KAAK,cAAc;AACvC,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,KAAK,UAAU,MAAM,GAAG;AAC1B,aAAO,KAAK,KAAK,cAAc;AAC/B,WAAK,QAAQ;AACb,aAAO;AAAA,IACT;AAGA,WAAO,gBAAe,oBAAI,KAAK,GAAE,YAAY;AAC7C,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,gBAAwB,cAAsB,UAAkB,cAAoC;AACtG,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,WAAW,KAAK,KAAK,cAAc;AACzC,SAAK,KAAK,cAAc,IAAI;AAAA,MAC1B,gBAAgB;AAAA,MAChB;AAAA;AAAA;AAAA,MAGA,YAAY,UAAU,cAAc;AAAA,MACpC,cAAc;AAAA;AAAA;AAAA,MAGd,eAAe,gBAAgB,UAAU,iBAAiB;AAAA,IAC5D;AACA,SAAK,QAAQ;AACb,IAAAD,KAAI,MAAM,kBAAkB,EAAE,gBAAgB,cAAc,SAAS,CAAC;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,gBAAuC;AACrD,WAAO,KAAK,KAAK,cAAc,GAAG,iBAAiB;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,gBAAiC;AACtC,QAAI,KAAK,KAAK,cAAc,GAAG;AAC7B,aAAO,KAAK,KAAK,cAAc;AAC/B,WAAK,QAAQ;AACb,MAAAA,KAAI,MAAM,mBAAmB,EAAE,eAAe,CAAC;AAC/C,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,QAAQ;AACb,IAAAA,KAAI,MAAM,iCAAiC,EAAE,OAAO,OAAO,KAAK,KAAK,IAAI,EAAE,OAAO,CAAC;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAgB;AACd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,SAAS;AACb,eAAW,CAAC,IAAI,MAAM,KAAK,OAAO,QAAQ,KAAK,IAAI,GAAG;AACpD,UAAI,KAAK,UAAU,QAAQ,GAAG,GAAG;AAC/B,eAAO,KAAK,KAAK,EAAE;AACnB;AAAA,MACF;AAAA,IACF;AACA,QAAI,SAAS,GAAG;AACd,WAAK,QAAQ;AACb,MAAAA,KAAI,KAAK,2BAA2B,EAAE,OAAO,OAAO,CAAC;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAe;AACb,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,OAAO,OAAO,KAAK,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,UAAU,QAAQ,GAAG,CAAC,EAAE;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAU,QAAuB,MAAc,KAAK,IAAI,GAAY;AAC1E,UAAM,WAAW,IAAI,KAAK,OAAO,YAAY,EAAE,QAAQ;AACvD,WAAO,MAAM,WAAW,KAAK;AAAA,EAC/B;AAAA,EAEQ,OAAa;AACnB,QAAI;AACF,UAAIG,IAAG,WAAW,KAAK,QAAQ,GAAG;AAChC,cAAM,MAAMA,IAAG,aAAa,KAAK,UAAU,OAAO;AAClD,cAAM,SAAS,KAAK,MAAM,GAAG;AAE7B,YAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAI,aAAa,UAAU,cAAc,QAAQ;AAE/C,kBAAM,cAAc,OAAO;AAO3B,gBAAI,iBAAiB;AACrB,uBAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,WAAW,GAAG;AAInD,kBAAI,CAAC,IAAI,kBAAkB,OAAO,IAAI,mBAAmB,UAAU;AACjE,gBAAAH,KAAI,KAAK,gEAAgE,EAAE,GAAG,CAAC;AAC/E;AACA;AAAA,cACF;AACA,oBAAM,cAAc,OAAO,IAAI,iBAAiB,WAC5C,IAAI,eACJ,IAAI,KAAK,IAAI,gBAAgB,EAAE,EAAE,QAAQ;AAC7C,kBAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,gBAAAA,KAAI,KAAK,8DAA8D,EAAE,IAAI,cAAc,IAAI,aAAa,CAAC;AAC7G;AACA;AAAA,cACF;AACA,mBAAK,KAAK,EAAE,IAAI;AAAA,gBACd,gBAAgB,IAAI;AAAA,gBACpB,UAAU,IAAI,YAAY,IAAI,eAAe;AAAA,gBAC7C,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,KAAK,IAAI,UAAU,EAAE,YAAY,IAAI,IAAI;AAAA,gBAC9F,cAAc,OAAO,IAAI,iBAAiB,WAAW,IAAI,KAAK,IAAI,YAAY,EAAE,YAAY,IAAI,IAAI;AAAA,cACtG;AAAA,YACF;AACA,gBAAI,iBAAiB,GAAG;AACtB,cAAAA,KAAI,KAAK,oDAAoD,EAAE,OAAO,eAAe,CAAC;AAAA,YACxF;AACA,YAAAA,KAAI,MAAM,qCAAqC,EAAE,OAAO,OAAO,KAAK,KAAK,IAAI,EAAE,OAAO,CAAC;AAGvF,iBAAK,QAAQ;AAAA,UACf,OAAO;AAIL,kBAAM,cAAc;AACpB,gBAAI,UAAU;AACd,uBAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,WAAW,GAAG;AACnD,oBAAM,IAAI;AACV,kBAAI,CAAC,EAAE,kBAAkB,OAAO,EAAE,mBAAmB,UAAU;AAC7D,gBAAAA,KAAI,KAAK,uDAAuD,EAAE,GAAG,CAAC;AACtE;AACA;AAAA,cACF;AACA,oBAAM,WAAW,IAAI,KAAK,EAAE,gBAAgB,EAAE,EAAE,QAAQ;AACxD,kBAAI,OAAO,MAAM,QAAQ,GAAG;AAC1B,gBAAAA,KAAI,KAAK,qDAAqD,EAAE,IAAI,cAAc,EAAE,aAAa,CAAC;AAClG;AACA;AAAA,cACF;AACA,mBAAK,KAAK,EAAE,IAAI;AAAA,YAClB;AACA,gBAAI,UAAU,GAAG;AACf,cAAAA,KAAI,KAAK,2CAA2C,EAAE,OAAO,QAAQ,CAAC;AAAA,YACxE;AACA,YAAAA,KAAI,MAAM,6BAA6B,EAAE,OAAO,OAAO,KAAK,KAAK,IAAI,EAAE,OAAO,CAAC;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AAGZ,MAAAA,KAAI,MAAM,yEAAoE;AAAA,QAC5E,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AACD,UAAI;AACF,YAAIG,IAAG,WAAW,KAAK,QAAQ,GAAG;AAChC,gBAAM,UAAU,KAAK,WAAW;AAChC,UAAAA,IAAG,aAAa,KAAK,UAAU,OAAO;AACtC,UAAAH,KAAI,MAAM,2CAA2C,EAAE,YAAY,QAAQ,CAAC;AAAA,QAC9E;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAgB;AACtB,QAAI;AACF,UAAI,CAACG,IAAG,WAAW,KAAK,GAAG,GAAG;AAE5B,QAAAA,IAAG,UAAU,KAAK,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,MACzD;AAEA,MAAAA,IAAG,cAAc,KAAK,UAAU,KAAK,UAAU,KAAK,MAAM,MAAM,CAAC,GAAG,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AAAA,IACxG,SAAS,KAAK;AACZ,MAAAH,KAAI,MAAM,mCAAmC;AAAA,QAC3C,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACvRO,IAAM,wBAAwB;AAC9B,IAAM,wBAAwB;AAG9B,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AAMxB,SAAS,oBAAoB,KAAqB;AACvD,SAAO,KAAK,IAAI,KAAK,IAAI,KAAK,qBAAqB,GAAG,qBAAqB;AAC7E;AAMO,SAAS,eAAe,KAAqB;AAClD,SAAO,KAAK,IAAI,KAAK,IAAI,KAAK,eAAe,GAAG,eAAe;AACjE;;;ACpBO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;AR+BA,IAAMI,OAAM,aAAa,QAAQ;AAqBjC,IAAM,4BAA4B;AAClC,IAAM,kCAAkC;AAIxC,IAAM,yBAAyB;AAC/B,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAmB/B,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AAC/E;AAaA,SAAS,yBACP,aACA,qBACyB;AACzB,QAAM,EAAE,YAAY,iBAAiB,UAAU,SAAS,QAAQ,IAAI;AAGpE,QAAM,aAAa,oBAAI,IAAI,CAAC,QAAQ,aAAa,QAAQ,CAAC;AAC1D,QAAM,kBAAkB,QACrB,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,OAAO,CAAC,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC;AACzC,MAAI,gBAAgB,SAAS,GAAG;AAC9B,IAAAA,KAAI,KAAK,yDAAyD;AAAA,MAChE,iBAAiB,CAAC,GAAG,IAAI,IAAI,eAAe,CAAC;AAAA,IAC/C,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,QAAQ,cAAc,CAAC,MAAM,EAAE,SAAS,MAAM;AAClE,MAAI,gBAAgB,IAAI;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,QAAQ,WAAW;AAG3C,QAAM,kBAAkB,QAAQ,MAAM,GAAG,WAAW;AAIpD,QAAM,eAAe,gBAAgB,OAAO,CAAC,MAAM,WAAW,IAAI,EAAE,IAAI,CAAC;AAGzE,MAAI;AACJ,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,aAAa,aAChB,IAAI,CAAC,MAAM,kBAAkB,UAAU,EAAE,IAAI,CAAC,KAAK,UAAU,EAAE,OAAO,CAAC,YAAY,EACnF,KAAK,IAAI;AAEZ,UAAM,eAAe;AAAA,EAA2B,UAAU;AAAA;AAG1D,2BAAuB,sBACnB,GAAG,mBAAmB;AAAA;AAAA,EAAO,YAAY,KACzC;AAAA,EACN,OAAO;AACL,2BAAuB;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,gBAAgB;AAAA,IACzB,eAAe;AAAA,IACf;AAAA,EACF;AACF;AAMO,IAAM,SAAN,cAAqB,aAA2B;AAAA,EAC7C,KAAuB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc,IAAI,YAAY;AAAA,EAC9B,eAAe,IAAI,aAAa;AAAA,EAChC,eAAe,IAAI,aAAa;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAA2B;AAAA,EAC3B,eAA6B;AAAA,IACnC,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,EACnB;AAAA;AAAA,EAEQ,sBAA4D;AAAA,EAE5D,iBAAwD;AAAA,EACxD,mBAAyD;AAAA,EACzD,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,iBAAuD;AAAA,EACvD,iBAAiB;AAAA,EACjB,iBAAiB,oBAAI,IAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOlD,oBAA8B,CAAC;AAAA;AAAA,EAEtB;AAAA,EAEjB,YAAY,SAAwB;AAClC,UAAM;AACN,SAAK,YAAY,QAAQ;AACzB,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AACzB,SAAK,WAAW,QAAQ;AACxB,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,gBAAgB,QAAQ;AAG7B,SAAK,iBAAiBC,QAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAG3D,UAAM,SAAS,CAAC,OAAe,MAAc,OAAe,UAAmC;AAC7F,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,iBAAiB,IAAI;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA,IAAI,IAAI,KAAK,YAAY,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,MACpD,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,UAAgB;AACd,QAAI,KAAK,IAAI;AACX,MAAAD,KAAI,KAAK,0DAAqD;AAC9D;AAAA,IACF;AAIA,UAAM,MAAM,IAAI,IAAI,KAAK,SAAS;AAClC,QAAI,aAAa,IAAI,SAAS,KAAK,KAAK;AACxC,UAAM,QAAQ,IAAI,SAAS;AAE3B,IAAAA,KAAI,KAAK,wBAAwB,EAAE,KAAK,KAAK,UAAU,CAAC;AAExD,SAAK,KAAK,IAAI,UAAU,OAAO;AAAA,MAC7B,SAAS;AAAA,QACP,cAAc,aAAa,cAAc;AAAA;AAAA,QAEzC,iBAAiB,UAAU,KAAK,KAAK;AAAA,MACvC;AAAA;AAAA,MAEA,YAAY,KAAK,OAAO;AAAA,IAC1B,CAAC;AAED,SAAK,GAAG,GAAG,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC;AACzC,SAAK,GAAG,GAAG,WAAW,KAAK,UAAU,KAAK,IAAI,CAAC;AAC/C,SAAK,GAAG,GAAG,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AAC3C,SAAK,GAAG,GAAG,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,SAAK,iBAAiB;AACtB,SAAK,cAAc;AACnB,SAAK,oBAAoB;AACzB,SAAK,aAAa,UAAU;AAC5B,SAAK,YAAY,eAAe;AAChC,UAAM,KAAK,eAAe,KAAK;AAG/B,SAAK,aAAa,MAAM;AAGxB,eAAW,CAAC,IAAI,UAAU,KAAK,KAAK,gBAAgB;AAClD,iBAAW,MAAM;AACjB,MAAAA,KAAI,MAAM,4BAA4B,EAAE,WAAW,GAAG,CAAC;AAAA,IACzD;AACA,SAAK,eAAe,MAAM;AAE1B,QAAI,KAAK,IAAI;AACX,UAAI,KAAK,GAAG,eAAe,UAAU,MAAM;AACzC,aAAK,GAAG,MAAM,KAAM,sBAAsB;AAAA,MAC5C;AACA,WAAK,KAAK;AAAA,IACZ;AAEA,IAAAA,KAAI,KAAK,qBAAqB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK,IAAI,eAAe,UAAU;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAe;AACrB,IAAAA,KAAI,KAAK,qBAAqB;AAC9B,SAAK,oBAAoB;AACzB,SAAK,KAAK,WAAW;AACrB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEQ,UAAU,MAA+B;AAC/C,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,KAAK,SAAS,CAAC;AAAA,IACtC,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,kCAAkC;AAAA,QAC1C,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AACD;AAAA,IACF;AAEA,IAAAA,KAAI,MAAM,oBAAoB,EAAE,MAAM,QAAQ,KAAK,CAAC;AAEpD,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK;AACH,aAAK,cAAc,OAAO;AAC1B;AAAA,MACF,KAAK;AACH,aAAK,gBAAgB,OAAO;AAC5B;AAAA,MACF,KAAK;AACH,aAAK,mBAAmB,OAAO;AAC/B;AAAA,MACF,KAAK;AACH,aAAK,aAAa,QAAQ,QAAQ,cAAc,QAAQ,MAAM;AAC9D;AAAA,MACF,KAAK;AACH,aAAK,aAAa,OAAO,QAAQ,cAAc,QAAQ,KAAK;AAC5D;AAAA,MACF,KAAK;AACH,QAAAA,KAAI,MAAM,iBAAiB,EAAE,WAAW,QAAQ,UAAU,CAAC;AAE3D,aAAK,eAAe;AACpB,YAAI,KAAK,kBAAkB;AACzB,uBAAa,KAAK,gBAAgB;AAClC,eAAK,mBAAmB;AAAA,QAC1B;AACA;AAAA,MACF,KAAK;AACH,aAAK,kBAAkB,OAAO;AAC9B;AAAA,MACF;AACE,QAAAA,KAAI,KAAK,iCAAiC,EAAE,MAAO,QAA6B,KAAK,CAAC;AAAA,IAC1F;AAAA,EACF;AAAA,EAEQ,QAAQ,MAAc,QAAsB;AAClD,UAAM,YAAY,OAAO,SAAS;AAClC,IAAAA,KAAI,KAAK,oBAAoB,EAAE,MAAM,QAAQ,UAAU,CAAC;AACxD,SAAK,cAAc;AAEnB,QAAI,KAAK,qBAAqB;AAC5B,mBAAa,KAAK,mBAAmB;AACrC,WAAK,sBAAsB;AAAA,IAC7B;AACA,SAAK,KAAK;AAGV,SAAK,YAAY;AAIjB,SAAK,aAAa,UAAU;AAK5B,QAAI,CAAC,KAAK,gBAAgB;AACxB,iBAAW,CAAC,IAAI,UAAU,KAAK,KAAK,gBAAgB;AAClD,mBAAW,MAAM;AAGjB,aAAK,kBAAkB,KAAK,EAAE;AAC9B,QAAAA,KAAI,MAAM,wCAAwC,EAAE,WAAW,GAAG,CAAC;AAAA,MACrE;AACA,WAAK,eAAe,MAAM;AAAA,IAC5B;AAEA,SAAK,KAAK,gBAAgB,MAAM,SAAS;AAEzC,QAAI,CAAC,KAAK,gBAAgB;AAExB,UAAI,SAAS,MAAM;AAEjB,aAAK,YAAY,eAAe;AAChC,aAAK,eAAe,KAAK,EAAE,MAAM,MAAM;AAAA,QAEvC,CAAC;AAED,aAAK,iBAAiB;AACtB,aAAK;AAAA,UACH;AAAA,UACA,IAAI;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAEA,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,QAAQ,KAAkB;AAChC,IAAAA,KAAI,MAAM,mBAAmB,EAAE,OAAO,IAAI,QAAQ,CAAC;AACnD,SAAK,KAAK,SAAS,GAAG;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,YAAkB;AACxB,UAAM,QAA+B;AAAA,MACnC,MAAM;AAAA,MACN,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,WAAW,KAAK;AAAA,IAClB;AACA,SAAK,KAAK,KAAK;AACf,IAAAA,KAAI,KAAK,cAAc;AAAA,MACrB,UAAU;AAAA,MACV,WAAW,KAAK,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACxE,CAAC;AAID,SAAK,sBAAsB,WAAW,MAAM;AAC1C,WAAK,sBAAsB;AAC3B,UAAI,CAAC,KAAK,aAAa,CAAC,KAAK,gBAAgB;AAC3C,QAAAA,KAAI,MAAM,gFAA2E;AACrF,aAAK,IAAI,MAAM,KAAM,iBAAiB;AAAA,MACxC;AAAA,IACF,GAAG,IAAM;AAAA,EACX;AAAA,EAEA,MAAc,cAAc,SAAwC;AAElE,QAAI,KAAK,qBAAqB;AAC5B,mBAAa,KAAK,mBAAmB;AACrC,WAAK,sBAAsB;AAAA,IAC7B;AACA,SAAK,YAAY,QAAQ;AACzB,SAAK,eAAe,QAAQ;AAG5B,QAAI,QAAQ,kBAAkB;AAC5B,YAAM,cAAc,QAAQ,iBAAiB,MAAM,GAAG,EAAE,CAAC;AACzD,YAAM,cAAc,iBAAiB,MAAM,GAAG,EAAE,CAAC;AACjD,UAAI,gBAAgB,aAAa;AAC/B,QAAAA,KAAI,KAAK,0DAAqD;AAAA,UAC5D,QAAQ,QAAQ;AAAA,UAChB,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAIA,QAAI,QAAQ,OAAO,iBAAiB;AAClC,YAAM,MAAM,QAAQ,OAAO;AAC3B,YAAM,UAAU,oBAAoB,GAAG;AACvC,UAAI,YAAY,KAAK;AACnB,QAAAA,KAAI,KAAK,gEAA2D;AAAA,UAClE,UAAU;AAAA,UACV;AAAA,QACF,CAAC;AAAA,MACH;AACA,WAAK,aAAa,aAAa,UAAU,GAAI;AAG7C,WAAK,aAAa,kBAAkB;AAAA,IACtC;AAGA,SAAK,YAAY,SAAS,QAAQ,KAAK;AAIvC,SAAK,eAAe,uBAAuB,KAAK,YAAY,mBAAmB,CAAC;AAIhF,UAAM,gBAAgB,KAAK,YAAY,qBAAqB;AAC5D,QAAI,cAAc,SAAS,GAAG;AAC5B,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM;AAAA,QACN,SAAS,4GAA4G,cAAc,KAAK,IAAI,CAAC;AAAA,QAC7I,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAGA,QAAI,QAAQ,MAAM,SAAS,GAAG;AAC5B,UAAI;AACF,cAAM,KAAK,eAAe,MAAM;AAChC,cAAM,OAAO,KAAK,eAAe,QAAQ;AACzC,YAAI,MAAM;AAGR,eAAK,YAAY,gBAAgB,MAAM,KAAK,gBAAgB,KAAK,aAAa,kBAAkB,GAAI;AACpG,UAAAA,KAAI,KAAK,0BAA0B;AAAA,YACjC,OAAO,QAAQ,MAAM;AAAA,YACrB,cAAc;AAAA,YACd,WAAW,KAAK,YAAY,aAAa;AAAA,UAC3C,CAAC;AAAA,QACH;AAAA,MACF,SAAS,KAAK;AACZ,QAAAA,KAAI,MAAM,yCAAyC;AAAA,UACjD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAGD,aAAK,KAAK;AAAA,UACR,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,MAAM;AAAA,UACN,SAAS,0FAAqF,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UAC9I,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAKA,UAAM,eAAe,QAAQ,OAAO;AACpC,UAAM,mBAAmB,eAAe,YAAY;AACpD,QAAI,qBAAqB,cAAc;AACrC,MAAAA,KAAI,KAAK,mEAA8D;AAAA,QACrE,UAAU;AAAA,QACV,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,aAAa,mBAAmB;AACtC,SAAK,eAAe,UAAU;AAE9B,IAAAA,KAAI,KAAK,oBAAoB;AAAA,MAC3B,WAAW,KAAK;AAAA,MAChB,WAAW,QAAQ,MAAM;AAAA,MACzB,kBAAkB,QAAQ,OAAO;AAAA,IACnC,CAAC;AAED,SAAK,KAAK,WAAW,KAAK,SAAS;AAKnC,QAAI,KAAK,kBAAkB,SAAS,GAAG;AACrC,YAAM,WAAW,KAAK;AACtB,WAAK,oBAAoB,CAAC;AAC1B,iBAAW,aAAa,UAAU;AAChC,QAAAA,KAAI,KAAK,sDAAsD,EAAE,UAAU,CAAC;AAC5E,aAAK,KAAK;AAAA,UACR,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,MAAM;AAAA,UACN,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,SAAiC;AACvD,UAAM,EAAE,YAAY,UAAU,gBAAgB,IAAI;AAGlD,UAAM,uBAAuB,kBACzB,KAAK,aAAa,IAAI,eAAe,IACrC;AAOJ,QAAI,mBAAmB,CAAC,wBAAwB,CAAC,QAAQ,eAAe;AACtE,MAAAA,KAAI,KAAK,8DAAyD;AAAA,QAChE,gBAAgB;AAAA,MAClB,CAAC;AACD,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,SAAS,2CAA2C,eAAe;AAAA,QACnE,OAAO;AAAA,MACT,CAAC;AAGD;AAAA,IACF;AAIA,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,gBAAgB,wBAAwB;AAAA,IAC1C,CAAC;AAGD,SAAK,yBAAyB,SAAS,oBAAoB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,yBACN,SACA,sBACM;AACN,UAAM,EAAE,YAAY,SAAS,IAAI;AAEjC,UAAM,eAAe,wBAAwB;AAG7C,QAAI,KAAK,YAAY,KAAK,eAAe;AACvC,WAAK,KAAK,iBAAiB,YAAY,QAAQ;AAC/C,YAAM,YAAY,CAAC,OAAwB,SAA0B;AACnE,aAAK,gBAAgB,YAAY,OAAO,IAAI;AAAA,MAC9C;AACA,WAAK,cAAc,SAAS,SAAS,EAClC,MAAM,CAAC,QAAQ;AACd,QAAAA,KAAI,MAAM,4BAA4B;AAAA,UACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,aAAK,gBAAgB,YAAY,SAAS;AAAA,UACxC,MAAM;AAAA,UACN,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QAC1D,CAAC;AACD,aAAK,gBAAgB,YAAY,QAAQ,CAAC,CAAC;AAAA,MAC7C,CAAC,EACA,QAAQ,MAAM;AACb,aAAK,KAAK,eAAe,UAAU;AAAA,MACrC,CAAC;AACH;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,SAAS,IAAI,QAAQ;AAC1C,QAAI,CAAC,SAAS;AACZ,MAAAA,KAAI,MAAM,qCAAqC,EAAE,SAAS,CAAC;AAC3D,YAAM,eAAuC;AAAA,QAC3C,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AACA,YAAM,OAAO,aAAa,QAAQ,KAAK;AACvC,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,SAAS,aAAa,QAAQ,qCAAqC,IAAI;AAAA,QACvE,OAAO;AAAA,MACT,CAAC;AACD;AAAA,IACF;AAEA,SAAK,KAAK,iBAAiB,YAAY,QAAQ;AAG/C,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,eAAe,IAAI,YAAY,UAAU;AAE9C,SAAK,eAAe,SAAS,SAAS,cAAc,WAAW,MAAM,EAClE,MAAM,CAAC,QAAQ;AACd,MAAAA,KAAI,MAAM,4BAA4B;AAAA,QACpC,WAAW;AAAA,QACX,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AACD,WAAK,gBAAgB,YAAY,SAAS;AAAA,QACxC,MAAM;AAAA,QACN,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC1D,CAAC;AACD,WAAK,gBAAgB,YAAY,QAAQ,CAAC,CAAC;AAAA,IAC7C,CAAC,EACA,QAAQ,MAAM;AACb,WAAK,eAAe,OAAO,UAAU;AACrC,WAAK,KAAK,eAAe,UAAU;AAAA,IACrC,CAAC;AAAA,EACL;AAAA,EAEA,MAAc,eACZ,SACA,SACA,cACA,QACe;AACf,UAAM,EAAE,YAAY,gBAAgB,IAAI;AAGxC,UAAM,UAA4B;AAAA,MAChC;AAAA,MACA,WAAW;AAAA,MACX,OAAO,KAAK,YAAY,OAAO;AAAA,MAC/B,eAAe,KAAK,YAAY,aAAa;AAAA,MAC7C,YAAY,OAAO,YAAY,UAAU,SAAS;AAChD,eAAO,KAAK,aAAa;AAAA,UACvB,CAAC,OAAO,MAAM,OAAO,UAAU;AAC7B,iBAAK,KAAK;AAAA,cACR,MAAM;AAAA,cACN,YAAY;AAAA,cACZ,cAAc;AAAA,cACd,WAAW;AAAA,cACX,WAAW;AAAA,YACb,CAAC;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAIA,QAAI,kBAAiC;AACrC,QAAI;AAEF,wBAAkB,MAAM,QAAQ,QAAQ,SAAS,CAAC,UAA8B;AAC9E,aAAK,gBAAgB,YAAY,MAAM,OAAO,MAAM,IAAI;AAAA,MAC1D,CAAC;AAAA,IACH,UAAE;AAIA,UAAI,mBAAmB,iBAAiB;AACtC,aAAK,aAAa;AAAA,UAChB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,SAAoC;AAC7D,UAAM,EAAE,YAAY,gBAAgB,IAAI;AAIxC,UAAM,qBAAqB,KAAK,aAAa,gBAAgB,eAAe;AAG5E,UAAM,UAAU,KAAK,aAAa,OAAO,eAAe;AACxD,IAAAA,KAAI,KAAK,iBAAiB,EAAE,gBAAgB,iBAAiB,OAAO,SAAS,eAAe,QAAQ,QAAQ,OAAO,CAAC;AAIpH,UAAM,wBAAwB,QAAQ,iBAAiB;AACvD,QAAI,CAAC,QAAQ,iBAAiB,oBAAoB;AAChD,MAAAA,KAAI,KAAK,8FAAyF;AAAA,QAChG,gBAAgB;AAAA,MAClB,CAAC;AAAA,IACH;AAEA,UAAM,mBAAmB,yBAAyB,SAAS,qBAAqB;AAEhF,QAAI,CAAC,kBAAkB;AACrB,MAAAA,KAAI,MAAM,gDAAgD,EAAE,gBAAgB,gBAAgB,CAAC;AAC7F,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC;AACD;AAAA,IACF;AAEA,IAAAA,KAAI,KAAK,iDAAiD,EAAE,WAAW,YAAY,UAAU,QAAQ,SAAS,CAAC;AAC/G,SAAK,yBAAyB,gBAAgB;AAAA,EAChD;AAAA,EAEQ,kBAAkB,SAAiF;AACzG,IAAAA,KAAI,MAAM,gBAAgB,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,SAAS,OAAO,QAAQ,MAAM,CAAC;AAChG,QAAI,QAAQ,OAAO;AACjB,MAAAA,KAAI,MAAM,yCAAoC;AAC9C,WAAK,iBAAiB;AAEtB,WAAK,YAAY,eAAe;AAChC,WAAK,eAAe,KAAK,EAAE,MAAM,MAAM;AAAA,MAEvC,CAAC;AACD,WAAK,IAAI,MAAM,KAAM,oBAAoB;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,YAA0B;AAC/C,SAAK,cAAc;AACnB,SAAK,iBAAiB,YAAY,MAAM;AACtC,UAAI,KAAK,YAAY,GAAG;AACtB,aAAK,KAAK,EAAE,MAAM,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AACjD,QAAAA,KAAI,MAAM,WAAW;AAIrB,aAAK,eAAe;AACpB,YAAI,KAAK,kBAAkB;AACzB,uBAAa,KAAK,gBAAgB;AAAA,QACpC;AACA,aAAK,mBAAmB,WAAW,MAAM;AACvC,cAAI,KAAK,gBAAgB,KAAK,YAAY,GAAG;AAC3C,YAAAA,KAAI,KAAK,8DAAyD;AAClE,iBAAK,IAAI,MAAM,KAAM,cAAc;AAAA,UACrC;AAAA,QACF,GAAG,GAAM;AAAA,MACX;AAAA,IACF,GAAG,UAAU;AACb,IAAAA,KAAI,MAAM,qBAAqB,EAAE,WAAW,CAAC;AAAA,EAC/C;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IAC1B;AACA,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAA0B;AAChC,QAAI,KAAK,qBAAqB,wBAAwB;AACpD,MAAAA,KAAI,MAAM,0DAAqD;AAAA,QAC7D,UAAU,KAAK;AAAA,QACf,KAAK;AAAA,MACP,CAAC;AACD,WAAK;AAAA,QACH;AAAA,QACA,IAAI;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAGA,UAAME,SAAQ,KAAK;AAAA,MACjB,0BAA0B,KAAK,IAAI,GAAG,KAAK,iBAAiB;AAAA,MAC5D;AAAA,IACF;AAEA,SAAK;AACL,IAAAF,KAAI,KAAK,mBAAmBE,SAAQ,GAAI,cAAc,KAAK,iBAAiB,IAAI,sBAAsB,GAAG;AAEzG,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,WAAK,QAAQ;AAAA,IACf,GAAGA,MAAK;AAAA,EACV;AAAA,EAEQ,sBAA4B;AAClC,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,KAAK,SAAsC;AACjD,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACrD,MAAAF,KAAI,KAAK,iDAA4C,EAAE,MAAM,QAAQ,KAAK,CAAC;AAC3E;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,OAAO;AACtC,SAAK,GAAG,KAAK,OAAO;AACpB,IAAAA,KAAI,MAAM,gBAAgB,EAAE,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,CAAC;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,WAAmB,OAAwB,MAA6B;AAC9F,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AS/6BA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAI1B,IAAMG,OAAM,aAAa,UAAU;AACnC,IAAM,gBAAgB,UAAU,QAAQ;AAexC,IAAM,aAAyB;AAAA,EAC7B;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa,CAAC,WAAW;AAAA,IACzB,cAAc,CAAC,WAAW,eAAe,MAAM;AAAA,IAC/C,oBAAoB;AAAA;AAAA;AAAA,IAGpB,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,yBAAyB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa,CAAC,WAAW;AAAA,IACzB,cAAc,CAAC,WAAW,eAAe,MAAM;AAAA,IAC/C,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,yBAAyB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa,CAAC,WAAW;AAAA,IACzB,cAAc,CAAC,WAAW,eAAe,MAAM;AAAA,IAC/C,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,yBAAyB;AAAA,EAC3B;AACF;AAMA,SAAS,eAAe,QAA+B;AACrD,QAAM,QAAQ,OAAO,MAAM,0BAA0B;AACrD,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAKA,eAAe,SAAS,OAA8C;AACpE,QAAM,aAAiC;AAAA,IACrC,MAAM,MAAM;AAAA,IACZ,SAAS;AAAA,IACT,WAAW;AAAA,IACX,oBAAoB,MAAM;AAAA,IAC1B,gBAAgB,MAAM;AAAA,IACtB,mBAAmB,MAAM;AAAA,IACzB,yBAAyB,MAAM;AAAA,EACjC;AAEA,MAAI;AAGF,UAAM,WAAW,EAAE,GAAG,QAAQ,IAAI;AAClC,WAAO,SAAS,iBAAiB;AACjC,WAAO,SAAS,kBAAkB;AAElC,UAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,cAAc,MAAM,QAAQ,MAAM,aAAa;AAAA,MAC9E,SAAS;AAAA,MACT,KAAK;AAAA,IACP,CAAC;AACD,UAAM,UAAU,UAAU,OAAO,UAAU;AAC3C,eAAW,YAAY;AACvB,eAAW,UAAU,MAAM,aAAa,OAAO,KAAK,CAAC;AACrD,IAAAA,KAAI,KAAK,YAAY,MAAM,IAAI,IAAI,EAAE,SAAS,WAAW,QAAQ,CAAC;AAAA,EACpE,SAAS,KAAK;AACZ,IAAAA,KAAI,MAAM,GAAG,MAAM,IAAI,gCAAgC;AAAA,MACrD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAQA,eAAsB,kBAAiD;AACrE,EAAAA,KAAI,KAAK,6CAA6C;AACtD,QAAM,UAAU,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAQ,CAAC;AAC1D,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS;AACnD,EAAAA,KAAI,KAAK,uBAAuB,UAAU,MAAM,IAAI,QAAQ,MAAM,sBAAsB;AACxF,SAAO;AACT;;;ACxGA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,eAAe;AACxB,SAAS,YAAY;;;ACdrB,IAAM,mBAAmB,KAAK;AAYvB,SAAS,cAAc,eAA8B,WAAuC;AACjG,QAAM,MAAM,EAAE,GAAG,QAAQ,IAAI;AAC7B,MAAI,eAAe;AACjB,QAAI,MAAM,IAAI,GAAG,aAAa,IAAI,IAAI,MAAM,KAAK,EAAE;AAAA,EACrD;AACA,MAAI,WAAW;AACb,QAAI,sBAAsB,IAAI;AAAA,EAChC;AAGA,SAAO,IAAI,iBAAiB;AAC5B,SAAO,IAAI,kBAAkB;AAC7B,SAAO;AACT;AAaO,SAAS,oBAAoB,cAAsB,aAA6B;AACrF,SAAO,GAAG,YAAY;AAAA;AAAA;AAAA,EAAsB,WAAW;AACzD;AAaO,SAAS,aAAa,QAAgB,OAAuB;AAElE,MAAI,OAAO,UAAU,kBAAkB;AACrC,WAAO;AAAA,EACT;AACA,YAAU;AACV,MAAI,OAAO,SAAS,kBAAkB;AACpC,aAAS,OAAO,MAAM,GAAG,gBAAgB;AAAA,EAC3C;AACA,SAAO;AACT;AAcO,SAAS,oBAAoB,UAAkB,QAAgB,UAAiC;AAGrG,QAAM,QAAQ,OAAO,QAAQ,oCAAoC,EAAE,EAAE,KAAK;AAE1E,MAAI,CAAC,OAAO;AACV,WAAO,GAAG,QAAQ,yBAAyB,YAAY,SAAS;AAAA,EAClE;AAEA,QAAM,QAAQ,MAAM,YAAY;AAGhC,MACE,MAAM,SAAS,KAAK,KACpB,MAAM,SAAS,KAAK,KACpB,MAAM,SAAS,cAAc,KAC7B,MAAM,SAAS,iBAAiB,KAChC,MAAM,SAAS,MAAM,KACrB,MAAM,SAAS,OAAO,KACtB,MAAM,SAAS,cAAc,KAC7B,MAAM,SAAS,eAAe,KAC9B,MAAM,SAAS,SAAS,KACxB,MAAM,SAAS,aAAa,GAC5B;AAEA,UAAM,UAAU,aAAa,UAAU,GAAG,QAAQ,WAAW,GAAG,QAAQ;AACxE,WAAO,wCAAmC,OAAO;AAAA,EACnD;AAGA,MACE,MAAM,SAAS,YAAY,KAC3B,MAAM,SAAS,WAAW,KAC1B,MAAM,SAAS,mBAAmB,KAClC,MAAM,SAAS,KAAK,GACpB;AACA,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM,MAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK;AAC7D,SAAO,UAAU,UAAU,GAAG,GAAG;AACnC;;;AC9DO,SAAS,gBAAgB,MAY2C;AACzE,MAAI,WAAW;AACf,MAAI,gBAA+B;AACnC,MAAI,cAAc;AAElB,QAAM,cAAc,MAAM;AACxB,QAAI,CAAC,YAAY,CAAC,YAAa;AAC/B,SAAK,OAAO,oBAAoB,SAAS,KAAK,OAAO;AAErD,QAAI,KAAK,WAAW,GAAG;AACrB,WAAK,QAAQ,KAAK,aAAa,CAAC;AAChC;AAAA,IACF;AACA,SAAK,WAAW;AAGhB,SAAK,mBAAmB;AAExB,QAAI,kBAAkB,KAAK,kBAAkB,MAAM;AACjD,WAAK,QAAQ;AAAA,QACX,OAAO;AAAA,QACP,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,SAAS,oBAAoB,KAAK,cAAc,KAAK,UAAU,GAAG,aAAa;AAAA,QACjF;AAAA,MACF,CAAC;AACD,WAAK,QAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AAAA,IAC1C,OAAO;AAEL,WAAK,QAAQ;AAAA,QACX,OAAO;AAAA,QACP,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AACD,WAAK,QAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AAAA,IAC1C;AAEA,SAAK,QAAQ,KAAK,aAAa,CAAC;AAAA,EAClC;AAEA,SAAO;AAAA,IACL,WAAW,MAAM;AACf,iBAAW;AACX,kBAAY;AAAA,IACd;AAAA,IACA,cAAc,CAAC,SAAwB;AACrC,sBAAgB;AAChB,oBAAc;AACd,kBAAY;AAAA,IACd;AAAA,EACF;AACF;AAKO,IAAe,kBAAf,MAA+B;AA6BtC;;;ACnIO,SAAS,sBACd,OACA,eACQ;AACR,MAAI,MAAM,WAAW,EAAG,QAAO;AAM/B,QAAM,MAAM,CAAC,SACX,gBAAgB,GAAG,aAAa,IAAI,IAAI,KAAK;AAE/C,QAAM,WAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EAMF;AAEA,aAAW,QAAQ,OAAO;AACxB,aAAS,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,IAAI,KAAK,WAAW;AAEzD,UAAM,aAAc,KAAK,cAAc,CAAC;AACxC,UAAM,aAAc,WAAW,YAAY,KAAK,CAAC;AACjD,UAAM,WAAW,MAAM,QAAQ,WAAW,UAAU,CAAC,IAChD,WAAW,UAAU,EAAgB,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,IAC1D,CAAC;AACL,UAAM,YAAY,OAAO,KAAK,UAAU;AAExC,QAAI,UAAU,WAAW,GAAG;AAC1B,eAAS,KAAK,IAAI,2BAA2B;AAC7C,eAAS,KAAK,IAAI,iBAAiB,IAAI,KAAK,IAAI,CAAC,OAAO;AACxD;AAAA,IACF;AAEA,aAAS,KAAK,IAAI,aAAa;AAC/B,eAAW,SAAS,WAAW;AAC7B,YAAM,MAAM,WAAW,KAAK;AAC5B,YAAM,OACJ,OAAO,OAAO,QAAQ,WAAY,MAAyB,CAAC;AAC9D,YAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACzD,YAAM,gBAAgB,SAAS,SAAS,KAAK,IAAI,aAAa;AAC9D,YAAM,cACJ,OAAO,KAAK,gBAAgB,YAAY,KAAK,YAAY,SAAS,IAC9D,KAAK,cACL;AACN,eAAS,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,aAAa,MAAM,WAAW,EAAE;AAAA,IACxE;AAGA,UAAM,eAAe,UAAU,CAAC;AAChC,aAAS;AAAA,MACP;AAAA,MACA,iBAAiB,IAAI,KAAK,IAAI,CAAC,OAAO,YAAY;AAAA,IACpD;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,IAAI;AAC3B;;;AHnEA,IAAMC,OAAM,aAAa,cAAc;AASvC,IAAM,gBAAgB;AAEf,IAAM,eAAN,cAA2B,gBAAgB;AAAA,EACvC,eAAe;AAAA,EAExB,MAAM,QAAQ,SAA2B,SAAsE;AAC7G,UAAM,EAAE,SAAS,QAAQ,aAAa,IAAI;AAC1C,UAAM,YAAY,QAAQ;AAC1B,UAAM,cAAc,QAAQ;AAE5B,IAAAA,KAAI,KAAK,2BAA2B,EAAE,UAAU,CAAC;AAGjD,QAAI;AAGJ,UAAM,QAAQ,QAAQ,SAAS,SAAS;AAExC,QAAI,cAAc;AAEhB,aAAO;AAAA,QACL;AAAA,QAAQ;AAAA,QAAU;AAAA,QAClB;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,OAAO;AAC1B,aAAK,KAAK,MAAM,QAAQ,QAAQ,KAAK;AAAA,MACvC;AACA,MAAAA,KAAI,MAAM,oBAAoB,EAAE,aAAa,CAAC;AAAA,IAChD,OAAO;AAEL,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QAAM;AAAA,MACR;AAAA,IACF;AAaA,UAAM,WAAW,QAAQ,MAAM,SAAS;AACxC,QAAI,UAAU;AACZ,WAAK;AAAA,QACH;AAAA,QAAM;AAAA,QACN;AAAA,QAAM;AAAA,MACR;AAAA,IACF;AAWA,UAAM,WAAW,WAAW,SAAS,sBAAsB,QAAQ,OAAO,QAAQ,aAAa,IAAI;AACnG,QAAI,CAAC,gBAAgB,QAAQ,eAAe;AAC1C,WAAK,KAAK,MAAM,oBAAoB,QAAQ,eAAe,WAAW,IAAI,QAAQ;AAAA,IACpF,WAAW,UAAU;AACnB,WAAK,KAAK,MAAM,cAAc,QAAQ;AAAA,IACxC,OAAO;AACL,WAAK,KAAK,WAAW;AAAA,IACvB;AAKA,QAAI,QAAQ,SAAS,YAAY;AAC/B,MAAAA,KAAI,KAAK,0FAAqF;AAAA,QAC5F,YAAY,QAAQ,QAAQ;AAAA,MAC9B,CAAC;AAAA,IACH;AAGA,QAAI,eAAe,GAAG;AACpB,MAAAA,KAAI,MAAM,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,UAAU,GAAG,EAAE,IAAI,QAAQ,CAAC,EAAE,CAAC;AAAA,IACvG;AAEA,WAAO,IAAI,QAAuB,CAAC,YAAY;AAC7C,UAAI,YAA2B;AAC/B,UAAI,aAAa;AACjB,UAAI,UAAU;AAKd,UAAI,UAAU;AACZ,QAAAA,KAAI,KAAK,kDAAkD;AAAA,UACzD,WAAW,QAAQ,MAAM;AAAA,QAC3B,CAAC;AAAA,MACH;AACA,YAAM,MAAM,cAAc,WAAW,QAAQ,gBAAgB,MAAM,QAAQ,SAAS;AAEpF,YAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,QACjC;AAAA,QACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAClC,CAAC;AAGD,YAAM,UAAU,MAAM;AACpB,QAAAA,KAAI,KAAK,gDAA2C,EAAE,UAAU,CAAC;AACjE,cAAM,KAAK,SAAS;AAAA,MACtB;AACA,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAGxD,UAAI,eAAe;AAEnB,YAAM,YAAY,gBAAgB;AAAA,QAChC,cAAc;AAAA,QACd,eAAe;AAAA,QACf,YAAY,MAAM;AAAA,QAClB,YAAY,MAAM;AAAE,oBAAU;AAAA,QAAM;AAAA,QACpC,cAAc,MAAM;AAAA,QACpB,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAGD,YAAM,KAAK,gBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;AAElD,SAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,YAAI,CAAC,KAAK,KAAK,EAAG;AAElB,YAAI;AACJ,YAAI;AACF,mBAAS,KAAK,MAAM,IAAI;AAAA,QAC1B,QAAQ;AACN,UAAAA,KAAI,MAAM,0BAA0B,EAAE,MAAM,KAAK,UAAU,GAAG,GAAG,EAAE,CAAC;AACpE;AAAA,QACF;AAEA,cAAM,OAAO,OAAO,MAAM;AAG1B,YAAI,SAAS,kBAAkB;AAC7B,sBAAa,OAAO,WAAW,KAAgB;AAC/C,UAAAA,KAAI,MAAM,kBAAkB,EAAE,UAAU,CAAC;AACzC;AAAA,QACF;AAGA,YAAI,SAAS,gBAAgB;AAC3B,UAAAA,KAAI,MAAM,cAAc;AACxB;AAAA,QACF;AAGA,YAAI,SAAS,kBAAkB;AAC7B,gBAAM,OAAO,OAAO,MAAM;AAC1B,cAAI,CAAC,KAAM;AAEX,gBAAM,WAAW,KAAK,MAAM;AAE5B,cAAI,aAAa,iBAAiB;AAEhC,kBAAM,OAAO,KAAK,MAAM;AACxB,gBAAI,CAAC,KAAM;AAEX,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,YAAY,YAAY,OAAO;AAAA,YACtD,CAAC;AAED,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,YAAY,SAAS,KAAK;AAAA,YACjD,CAAC;AAED,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,WAAW;AAAA,YAClC,CAAC;AAED;AAAA,UACF,WAAW,aAAa,aAAa;AAEnC,kBAAM,OAAQ,KAAK,MAAM,KAAgB;AACzC,gBAAI,CAAC,KAAM;AAEX,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,YAAY,YAAY,WAAW;AAAA,YAC1D,CAAC;AAED,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,YAAY,SAAS,KAAK;AAAA,YACjD,CAAC;AAED,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,WAAW;AAAA,YAClC,CAAC;AAED;AAAA,UACF,WAAW,aAAa,SAAS;AAE/B,kBAAM,UAAW,KAAK,SAAS,KAAgB;AAC/C,YAAAA,KAAI,KAAK,oBAAoB,EAAE,SAAS,QAAQ,UAAU,GAAG,GAAG,EAAE,CAAC;AAEnE,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,MAAM,kBAAkB,QAAQ;AAAA,YAC1C,CAAC;AAED,oBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,sBAAU;AAAA,UACZ;AAIA;AAAA,QACF;AAGA,YAAI,SAAS,kBAAkB;AAG7B,cAAI,QAAS;AAEb,gBAAM,QAAQ,OAAO,OAAO;AAC5B,gBAAM,cAAc,QAAS,MAAM,cAAc,KAAgB,OAAO;AACxE,gBAAM,eAAe,QAAS,MAAM,eAAe,KAAgB,OAAO;AAE1E,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,OAAO;AAAA,gBACL,cAAc;AAAA,gBACd,eAAe;AAAA,cACjB;AAAA,YACF;AAAA,UACF,CAAC;AACD,oBAAU;AACV;AAAA,QACF;AAGA,YAAI,SAAS,eAAe;AAC1B,gBAAM,QAAQ,OAAO,OAAO;AAC5B,gBAAM,UAAW,QAAQ,SAAS,KAAgB;AAClD,UAAAA,KAAI,KAAK,qBAAqB,EAAE,SAAS,QAAQ,UAAU,GAAG,GAAG,EAAE,CAAC;AAEpE,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM,EAAE,MAAM,kBAAkB,QAAQ;AAAA,UAC1C,CAAC;AAED,kBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,oBAAU;AACV;AAAA,QACF;AAGA,YAAI,SAAS,SAAS;AACpB,gBAAM,UAAW,OAAO,SAAS,KAAgB;AACjD,UAAAA,KAAI,KAAK,qBAAqB,EAAE,SAAS,QAAQ,UAAU,GAAG,GAAG,EAAE,CAAC;AAIpE,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM,EAAE,MAAM,kBAAkB,QAAQ;AAAA,UAC1C,CAAC;AACD,kBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,oBAAU;AACV;AAAA,QACF;AAGA,QAAAA,KAAI,MAAM,8BAA8B,EAAE,KAAK,CAAC;AAAA,MAClD,CAAC;AAED,SAAG,GAAG,SAAS,UAAU,SAAS;AAGlC,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,uBAAe,aAAa,cAAc,MAAM,SAAS,CAAC;AAAA,MAC5D,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,QAA+B;AAChD,QAAAA,KAAI,MAAM,yBAAyB,EAAE,OAAO,IAAI,QAAQ,CAAC;AAEzD,cAAM,eAAe,IAAI,SAAS,WAC9B,kEACA,0BAA0B,IAAI,OAAO;AACzC,eAAO,oBAAoB,SAAS,OAAO;AAE3C,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AAAA,UACF,CAAC;AACD,kBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,QAAAA,KAAI,MAAM,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACrD,kBAAU,aAAa,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAmC;AACvC,QAAI;AACF,YAAM,YAAY,KAAK,QAAQ,GAAG,UAAU,mBAAmB;AAC/D,YAAM,MAAM,MAAM,SAAS,WAAW,OAAO;AAC7C,YAAM,QAAQ,KAAK,MAAM,GAAG;AAS5B,UAAI,CAAC,MAAM,UAAU,CAAC,MAAM,QAAQ,MAAM,MAAM,GAAG;AACjD,QAAAA,KAAI,KAAK,wCAAwC;AACjD,eAAO,CAAC;AAAA,MACV;AAEA,aAAO,MAAM,OACV,OAAO,CAAC,MAAM,EAAE,eAAe,MAAM,EACrC,IAAI,CAAC,OAAO;AAAA,QACX,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,aAAa,EAAE;AAAA,QACf,YAAY,EAAE,SAAS;AAAA,MACzB,EAAE;AAAA,IACN,SAAS,KAAK;AACZ,MAAAA,KAAI,KAAK,2GAA2G;AAAA,QAClH,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAED,aAAO;AAAA,QACL,EAAE,IAAI,eAAe,MAAM,eAAe,YAAY,KAAK;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACF;;;AIjYA,SAAS,SAAAC,cAAa;AACtB,SAAS,mBAAAC,wBAAuB;AAchC,IAAM,gBAA6B;AAAA,EACjC,EAAE,IAAI,UAAU,MAAM,UAAU,aAAa,0CAA0C,YAAY,KAAK;AAAA,EACxG,EAAE,IAAI,QAAQ,MAAM,QAAQ,aAAa,gCAAgC,YAAY,MAAM;AAAA,EAC3F,EAAE,IAAI,SAAS,MAAM,SAAS,aAAa,mCAAmC,YAAY,MAAM;AAClG;AAEA,IAAMC,OAAM,aAAa,eAAe;AAEjC,IAAM,gBAAN,cAA4B,gBAAgB;AAAA,EACxC,eAAe;AAAA,EAExB,MAAM,QAAQ,SAA2B,SAAsE;AAC7G,UAAM,EAAE,SAAS,QAAQ,aAAa,IAAI;AAC1C,UAAM,YAAY,QAAQ;AAC1B,UAAM,cAAc,QAAQ;AAE5B,IAAAA,KAAI,KAAK,4BAA4B,EAAE,UAAU,CAAC;AAGlD,UAAM,OAAiB;AAAA,MACrB;AAAA;AAAA,MACA;AAAA,MAAmB;AAAA;AAAA,MACnB;AAAA;AAAA,IACF;AAGA,QAAI,cAAc;AAChB,WAAK,KAAK,gBAAgB,YAAY;AACtC,MAAAA,KAAI,MAAM,oBAAoB,EAAE,aAAa,CAAC;AAAA,IAChD;AAGA,QAAI,QAAQ,iBAAiB,CAAC,cAAc;AAC1C,WAAK,KAAK,mBAAmB,QAAQ,aAAa;AAAA,IACpD;AAGA,QAAI,QAAQ,SAAS,OAAO;AAC1B,WAAK,KAAK,WAAW,QAAQ,QAAQ,KAAK;AAAA,IAC5C;AAGA,QAAI,QAAQ,SAAS,YAAY;AAC/B,WAAK,KAAK,gBAAgB,OAAO,QAAQ,QAAQ,UAAU,CAAC;AAAA,IAC9D;AAQA,QAAI,QAAQ,MAAM,SAAS,KAAK,QAAQ,eAAe;AACrD,WAAK,KAAK,qBAAqB,mBAAmB;AAAA,IACpD;AAMA,QAAI,YAAY;AAChB,QAAI,QAAQ,MAAM,SAAS,GAAG;AAC5B,mBAAa,SAAS,sBAAsB,QAAQ,OAAO,QAAQ,aAAa;AAAA,IAClF;AACA,SAAK,KAAK,SAAS;AAGnB,QAAI,eAAe,GAAG;AACpB,MAAAA,KAAI,MAAM,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,UAAU,GAAG,EAAE,IAAI,QAAQ,CAAC,EAAE,CAAC;AAAA,IACxG;AAEA,WAAO,IAAI,QAAuB,CAAC,SAAS,WAAW;AACrD,UAAI,YAA2B;AAC/B,UAAI,aAAa;AACjB,UAAI,UAAU;AAGd,YAAM,MAAM,cAAc,QAAQ,eAAe,QAAQ,SAAS;AAElE,aAAO,IAAI,YAAY;AAEvB,YAAM,QAAQC,OAAM,UAAU,MAAM;AAAA,QAClC;AAAA,QACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA;AAAA,MAClC,CAAC;AAGD,YAAM,UAAU,MAAM;AACpB,QAAAD,KAAI,KAAK,iDAA4C,EAAE,UAAU,CAAC;AAClE,cAAM,KAAK,SAAS;AAAA,MACtB;AACA,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAGxD,UAAI,eAAe;AAEnB,YAAM,YAAY,gBAAgB;AAAA,QAChC,cAAc;AAAA,QACd,eAAe;AAAA,QACf,YAAY,MAAM;AAAA,QAClB,YAAY,MAAM;AAAE,oBAAU;AAAA,QAAM;AAAA,QACpC,cAAc,MAAM;AAAA,QACpB,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAGD,YAAM,KAAKE,iBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;AAElD,SAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,YAAI,CAAC,KAAK,KAAK,EAAG;AAElB,YAAI;AACJ,YAAI;AACF,mBAAS,KAAK,MAAM,IAAI;AAAA,QAC1B,QAAQ;AACN,UAAAF,KAAI,MAAM,0BAA0B,EAAE,MAAM,KAAK,UAAU,GAAG,GAAG,EAAE,CAAC;AACpE;AAAA,QACF;AAEA,cAAM,OAAO,OAAO,MAAM;AAE1B,YAAI,SAAS,YAAa,OAAmC,SAAS,MAAM,QAAQ;AAElF,sBAAa,OAAO,YAAY,KAAgB;AAChD,UAAAA,KAAI,MAAM,gBAAgB,EAAE,WAAW,OAAO,OAAO,OAAO,EAAE,CAAC;AAC/D;AAAA,QACF;AAEA,YAAI,SAAS,aAAa;AAGxB,cAAI,SAAS;AACX,YAAAA,KAAI,MAAM,gGAA2F;AAAA,cACnG;AAAA,YACF,CAAC;AAAA,UACH;AAGA,gBAAM,UAAU,OAAO,SAAS;AAChC,cAAI,CAAC,QAAS;AAEd,gBAAM,UAAU,QAAQ,SAAS;AACjC,cAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO,EAAG;AAEzC,qBAAW,SAAS,SAAS;AAC3B,kBAAM,YAAY,MAAM,MAAM;AAE9B,gBAAI,cAAc,QAAQ;AACxB,oBAAM,OAAO,MAAM,MAAM;AACzB,kBAAI,CAAC,KAAM;AAGX,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,YAAY;AAAA,gBACd;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,SAAS;AAAA,gBACX;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,gBACf;AAAA,cACF,CAAC;AAED;AAAA,YACF,WAAW,cAAc,YAAY;AACnC,oBAAM,WAAW,MAAM,UAAU;AACjC,kBAAI,CAAC,SAAU;AAGf,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,YAAY;AAAA,gBACd;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,SAAS;AAAA,gBACX;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,gBACf;AAAA,cACF,CAAC;AAED;AAAA,YACF,WAAW,cAAc,YAAY;AAEnC,oBAAM,WAAW,MAAM,MAAM;AAC7B,oBAAM,SAAS,MAAM,IAAI;AACzB,oBAAM,YAAY,MAAM,OAAO;AAE/B,kBAAI,CAAC,YAAY,CAAC,OAAQ;AAE1B,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,YAAY;AAAA,kBACZ,WAAW;AAAA,kBACX,cAAc;AAAA,gBAChB;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb,SAAS,KAAK,UAAU,aAAa,CAAC,CAAC;AAAA,gBACzC;AAAA,cACF,CAAC;AAED,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,gBACf;AAAA,cACF,CAAC;AAED;AAAA,YACF;AAAA,UACF;AACA;AAAA,QACF;AAEA,YAAI,SAAS,UAAU;AAErB,sBAAa,OAAO,YAAY,KAAgB;AAEhD,gBAAM,QAAQ,OAAO,OAAO;AAC5B,gBAAM,cAAc,QAAS,MAAM,cAAc,KAAgB,OAAO;AACxE,gBAAM,eAAe,QAAS,MAAM,eAAe,KAAgB,OAAO;AAE1E,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,OAAO;AAAA,gBACL,cAAc;AAAA,gBACd,eAAe;AAAA,cACjB;AAAA,YACF;AAAA,UACF,CAAC;AACD,oBAAU;AACV;AAAA,QACF;AAOA,YAAI,SAAS,oBAAoB;AAC/B,UAAAA,KAAI,MAAM,2CAA2C;AAAA,YACnD,QAAS,OAAO,iBAAiB,IAA4C,QAAQ;AAAA,UACvF,CAAC;AACD;AAAA,QACF;AAEA,QAAAA,KAAI,MAAM,+BAA+B,EAAE,KAAK,CAAC;AAAA,MACnD,CAAC;AAED,SAAG,GAAG,SAAS,UAAU,SAAS;AAGlC,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,uBAAe,aAAa,cAAc,MAAM,SAAS,CAAC;AAAA,MAC5D,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,QAA+B;AAChD,QAAAA,KAAI,MAAM,0BAA0B,EAAE,OAAO,IAAI,QAAQ,CAAC;AAE1D,cAAM,eAAe,IAAI,SAAS,WAC9B,mEACA,2BAA2B,IAAI,OAAO;AAC1C,eAAO,oBAAoB,SAAS,OAAO;AAE3C,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AAAA,UACF,CAAC;AACD,kBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,QAAAA,KAAI,MAAM,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACtD,kBAAU,aAAa,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAmC;AAEvC,WAAO;AAAA,EACT;AACF;;;AChVA,SAAS,SAAAG,cAAa;AACtB,SAAS,mBAAAC,wBAAuB;AAchC,IAAM,gBAA6B;AAAA,EACjC,EAAE,IAAI,QAAQ,MAAM,QAAQ,aAAa,wCAAwC,YAAY,KAAK;AAAA,EAClG,EAAE,IAAI,OAAO,MAAM,OAAO,aAAa,4CAA4C,YAAY,MAAM;AAAA,EACrG,EAAE,IAAI,SAAS,MAAM,SAAS,aAAa,wCAAwC,YAAY,MAAM;AAAA,EACrG,EAAE,IAAI,cAAc,MAAM,cAAc,aAAa,oDAAoD,YAAY,MAAM;AAC7H;AAEA,IAAMC,OAAM,aAAa,eAAe;AAEjC,IAAM,gBAAN,cAA4B,gBAAgB;AAAA,EACxC,eAAe;AAAA,EAExB,MAAM,QAAQ,SAA2B,SAAsE;AAC7G,UAAM,EAAE,SAAS,QAAQ,aAAa,IAAI;AAC1C,UAAM,YAAY,QAAQ;AAC1B,UAAM,cAAc,QAAQ;AAE5B,IAAAA,KAAI,KAAK,4BAA4B,EAAE,UAAU,CAAC;AAIlD,QAAI,SAAS;AACb,QAAI,QAAQ,iBAAiB,CAAC,cAAc;AAC1C,eAAS,oBAAoB,QAAQ,eAAe,WAAW;AAAA,IACjE;AAMA,UAAM,WAAW,QAAQ,MAAM,SAAS;AACxC,QAAI,UAAU;AACZ,gBAAU,SAAS,sBAAsB,QAAQ,OAAO,QAAQ,aAAa;AAAA,IAC/E;AAGA,UAAM,OAAiB;AAAA,MACrB;AAAA,MAAY;AAAA;AAAA,MACZ;AAAA,MAAmB;AAAA;AAAA,MACnB;AAAA;AAAA,IACF;AASA,QAAI,UAAU;AACZ,WAAK,KAAK,QAAQ;AAAA,IACpB;AAGA,QAAI,cAAc;AAChB,WAAK,KAAK,YAAY,YAAY;AAClC,MAAAA,KAAI,MAAM,oBAAoB,EAAE,aAAa,CAAC;AAAA,IAChD;AAGA,QAAI,QAAQ,SAAS,OAAO;AAC1B,WAAK,KAAK,WAAW,QAAQ,QAAQ,KAAK;AAAA,IAC5C;AAKA,QAAI,QAAQ,SAAS,YAAY;AAC/B,MAAAA,KAAI,KAAK,2FAAsF;AAAA,QAC7F,YAAY,QAAQ,QAAQ;AAAA,MAC9B,CAAC;AAAA,IACH;AAGA,QAAI,eAAe,GAAG;AACpB,MAAAA,KAAI,MAAM,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,UAAU,GAAG,EAAE,IAAI,QAAQ,CAAC,EAAE,CAAC;AAAA,IACxG;AAEA,WAAO,IAAI,QAAuB,CAAC,YAAY;AAC7C,UAAI,YAA2B;AAC/B,UAAI,aAAa;AACjB,UAAI,UAAU;AACd,UAAI,cAAc;AAGlB,YAAM,MAAM,cAAc,QAAQ,eAAe,QAAQ,SAAS;AAElE,YAAM,QAAQC,OAAM,UAAU,MAAM;AAAA,QAClC;AAAA,QACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA;AAAA,MAClC,CAAC;AAGD,YAAM,UAAU,MAAM;AACpB,QAAAD,KAAI,KAAK,iDAA4C,EAAE,UAAU,CAAC;AAClE,cAAM,KAAK,SAAS;AAAA,MACtB;AACA,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAGxD,UAAI,eAAe;AAEnB,YAAM,YAAY,gBAAgB;AAAA,QAChC,cAAc;AAAA,QACd,eAAe;AAAA,QACf,YAAY,MAAM;AAAA,QAClB,YAAY,MAAM;AAAE,oBAAU;AAAA,QAAM;AAAA,QACpC,cAAc,MAAM;AAAA,QACpB,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAEA,kBAAkB,MAAM;AACtB,cAAI,aAAa;AACf,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,WAAW;AAAA,YAClC,CAAC;AACD;AACA,0BAAc;AAAA,UAChB;AAAA,QACF;AAAA,MACF,CAAC;AAGD,YAAM,KAAKE,iBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;AAElD,SAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,YAAI,CAAC,KAAK,KAAK,EAAG;AAElB,YAAI;AACJ,YAAI;AACF,mBAAS,KAAK,MAAM,IAAI;AAAA,QAC1B,QAAQ;AACN,UAAAF,KAAI,MAAM,0BAA0B,EAAE,MAAM,KAAK,UAAU,GAAG,GAAG,EAAE,CAAC;AACpE;AAAA,QACF;AAEA,cAAM,OAAO,OAAO,MAAM;AAG1B,YAAI,SAAS,QAAQ;AACnB,sBAAa,OAAO,YAAY,KAAgB;AAChD,UAAAA,KAAI,MAAM,gBAAgB,EAAE,WAAW,OAAO,OAAO,OAAO,EAAE,CAAC;AAC/D;AAAA,QACF;AAGA,YAAI,SAAS,WAAW;AACtB,gBAAM,OAAO,OAAO,MAAM;AAG1B,cAAI,SAAS,OAAQ;AAErB,cAAI,SAAS,aAAa;AACxB,kBAAM,UAAU,OAAO,SAAS;AAChC,kBAAM,UAAU,OAAO,OAAO;AAE9B,gBAAI,CAAC,QAAS;AAEd,gBAAI,SAAS;AAGX,kBAAI,CAAC,aAAa;AAChB,wBAAQ;AAAA,kBACN,OAAO;AAAA,kBACP,MAAM;AAAA,oBACJ,aAAa;AAAA,oBACb,YAAY;AAAA,kBACd;AAAA,gBACF,CAAC;AACD,8BAAc;AAAA,cAChB;AAEA,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM;AAAA,kBACJ,aAAa;AAAA,kBACb;AAAA,gBACF;AAAA,cACF,CAAC;AAAA,YACH,OAAO;AAGL,kBAAI,aAAa;AACf,wBAAQ;AAAA,kBACN,OAAO;AAAA,kBACP,MAAM,EAAE,aAAa,WAAW;AAAA,gBAClC,CAAC;AACD;AACA,8BAAc;AAAA,cAChB;AAEA,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM,EAAE,aAAa,YAAY,YAAY,OAAO;AAAA,cACtD,CAAC;AACD,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM,EAAE,aAAa,YAAY,QAAQ;AAAA,cAC3C,CAAC;AACD,sBAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM,EAAE,aAAa,WAAW;AAAA,cAClC,CAAC;AACD;AAAA,YACF;AAAA,UACF;AACA;AAAA,QACF;AAGA,YAAI,SAAS,YAAY;AAEvB,cAAI,aAAa;AACf,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,WAAW;AAAA,YAClC,CAAC;AACD;AACA,0BAAc;AAAA,UAChB;AAGA,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,aAAa;AAAA,cACb,YAAY;AAAA,cACZ,WAAW,OAAO,WAAW;AAAA,cAC7B,cAAc,OAAO,SAAS;AAAA,YAChC;AAAA,UACF,CAAC;AAED,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,aAAa;AAAA,cACb,SAAS,KAAK,UAAU,OAAO,YAAY,KAAK,CAAC,CAAC;AAAA,YACpD;AAAA,UACF,CAAC;AAED,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM,EAAE,aAAa,WAAW;AAAA,UAClC,CAAC;AACD;AACA;AAAA,QACF;AAGA,YAAI,SAAS,eAAe;AAC1B,gBAAM,SAAS,OAAO,SAAS;AAC/B,gBAAM,SAAU,OAAO,QAAQ,KAAgB;AAC/C,gBAAM,SAAS,OAAO,QAAQ;AAE9B,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,cAAc;AAAA,cACd,QAAQ,WAAW,UACf,UAAW,OAAO,OAAO,IAAgC,SAAS,KAAK,MAAM,KAC7E;AAAA,YACN;AAAA,UACF,CAAC;AACD;AAAA,QACF;AAGA,YAAI,SAAS,SAAS;AACpB,gBAAM,WAAW,OAAO,UAAU;AAClC,gBAAM,UAAU,OAAO,SAAS;AAChC,UAAAA,KAAI,KAAK,sBAAsB,EAAE,UAAU,SAAS,SAAS,UAAU,GAAG,GAAG,EAAE,CAAC;AAIhF,cAAI,aAAa,SAAS;AACxB,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,SAAS,WAAW;AAAA,cACtB;AAAA,YACF,CAAC;AAED,oBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,sBAAU;AAAA,UACZ;AAKA;AAAA,QACF;AAGA,YAAI,SAAS,UAAU;AAGrB,cAAI,QAAS;AAGb,cAAI,aAAa;AACf,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM,EAAE,aAAa,WAAW;AAAA,YAClC,CAAC;AACD;AACA,0BAAc;AAAA,UAChB;AAEA,gBAAM,SAAS,OAAO,QAAQ;AAE9B,cAAI,WAAW,SAAS;AACtB,kBAAM,QAAQ,OAAO,OAAO;AAC5B,kBAAM,eAAgB,QAAQ,SAAS,KAAgB;AACvD,YAAAA,KAAI,KAAK,uBAAuB,EAAE,MAAM,QAAQ,MAAM,GAAG,SAAS,aAAa,UAAU,GAAG,GAAG,EAAE,CAAC;AAElG,oBAAQ;AAAA,cACN,OAAO;AAAA,cACP,MAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,SAAS;AAAA,cACX;AAAA,YACF,CAAC;AAAA,UACH;AAGA,gBAAM,QAAQ,OAAO,OAAO;AAC5B,gBAAM,cAAc,QAAS,MAAM,cAAc,KAAgB,OAAO;AACxE,gBAAM,eAAe,QAAS,MAAM,eAAe,KAAgB,OAAO;AAE1E,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,OAAO;AAAA,gBACL,cAAc;AAAA,gBACd,eAAe;AAAA,cACjB;AAAA,YACF;AAAA,UACF,CAAC;AACD,oBAAU;AACV;AAAA,QACF;AAEA,QAAAA,KAAI,MAAM,+BAA+B,EAAE,KAAK,CAAC;AAAA,MACnD,CAAC;AAED,SAAG,GAAG,SAAS,UAAU,SAAS;AAGlC,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,uBAAe,aAAa,cAAc,MAAM,SAAS,CAAC;AAAA,MAC5D,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,QAA+B;AAChD,QAAAA,KAAI,MAAM,0BAA0B,EAAE,OAAO,IAAI,QAAQ,CAAC;AAE1D,cAAM,eAAe,IAAI,SAAS,WAC9B,mEACA,2BAA2B,IAAI,OAAO;AAC1C,eAAO,oBAAoB,SAAS,OAAO;AAE3C,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,kBAAQ;AAAA,YACN,OAAO;AAAA,YACP,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AAAA,UACF,CAAC;AACD,kBAAQ,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,CAAC;AACnC,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,QAAAA,KAAI,MAAM,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACtD,kBAAU,aAAa,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAmC;AAEvC,WAAO;AAAA,EACT;AACF;;;AClZA,IAAMG,QAAM,aAAa,UAAU;AAGnC,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAQA,eAAsB,kBACpB,SACA,WACe;AACf,EAAAA,MAAI,KAAK,oCAAoC;AAAA,IAC3C,WAAW,QAAQ;AAAA,IACnB,UAAU,QAAQ;AAAA,IAClB,SAAS,QAAQ,QAAQ,MAAM,GAAG,EAAE;AAAA,EACtC,CAAC;AAGD,QAAM,MAAM,GAAG;AAGf,YAAU,eAAe;AAAA,IACvB,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AACD,QAAM,MAAM,EAAE;AAEd,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,oBAAoB,QAAQ,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,aAAW,SAAS,gBAAgB;AAClC,cAAU,eAAe;AAAA,MACvB,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AACD,UAAM,MAAM,EAAE;AAAA,EAChB;AAEA,YAAU,cAAc;AAAA,IACtB,aAAa;AAAA,EACf,CAAC;AACD,QAAM,MAAM,EAAE;AAGd,YAAU,eAAe;AAAA,IACvB,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AACD,QAAM,MAAM,EAAE;AAEd,QAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,IACA,mCAAmC,QAAQ,QAAQ;AAAA,IACnD;AAAA,IACA;AAAA;AAAA,qBAA0B,QAAQ,QAAQ,MAAM,GAAG,GAAG,CAAC;AAAA,EACzD;AAEA,aAAW,SAAS,YAAY;AAC9B,cAAU,eAAe;AAAA,MACvB,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AACD,UAAM,MAAM,EAAE;AAAA,EAChB;AAEA,YAAU,cAAc;AAAA,IACtB,aAAa;AAAA,EACf,CAAC;AACD,QAAM,MAAM,EAAE;AAGd,YAAU,QAAQ;AAAA,IAChB,OAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,IACjB;AAAA,EACF,CAAC;AAED,EAAAA,MAAI,KAAK,qCAAqC,EAAE,WAAW,QAAQ,WAAW,CAAC;AACjF;;;AjBvFA,IAAMC,QAAM,aAAa,KAAK;AAM9B,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,WAAW,EAChB,YAAY,kHAA6G,EACzH,QAAQ,cAAc,EACtB;AAAA,EACC;AAAA,EACA;AAAA,EACA,QAAQ,IAAI,iBAAiB;AAC/B,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA,QAAQ,IAAI,kBAAkB;AAChC,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC,OAAO,OAAO,SAA6E;AAE1F,MAAI,KAAK,OAAO;AACd,aAAS,IAAI;AAAA,EACf;AAEA,EAAAA,MAAI,KAAK,cAAc,cAAc,eAAe,gBAAgB,GAAG;AAEvE,MAAI,KAAK,MAAM;AACb,IAAAA,MAAI,KAAK,qEAAgE;AAAA,EAC3E;AAGA,QAAM,QAAQ,KAAK;AACnB,QAAM,YAAY,KAAK;AAEvB,MAAI,CAAC,OAAO;AACV,IAAAA,MAAI,MAAM,oJAAoJ;AAC9J,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,WAAW;AACd,IAAAA,MAAI,MAAM,yKAAyK;AACnL,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,CAAC,UAAU,WAAW,OAAO,KAAK,CAAC,UAAU,WAAW,QAAQ,GAAG;AACrE,IAAAA,MAAI,MAAM,4CAA4C;AACtD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,UAAU,WAAW,OAAO,GAAG;AACjC,IAAAA,MAAI,KAAK,8DAA8D;AAAA,EACzE;AAIA,MAAI;AACF,UAAM,YAAY,IAAI,IAAI,SAAS;AACnC,QAAI,UAAU,YAAY,UAAU,UAAU;AAC5C,MAAAA,MAAI,MAAM,6DAA6D;AACvE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,QAAQ;AACN,IAAAA,MAAI,MAAM,+BAA+B;AACzC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAMA,QAAM,YAAY,MAAM,gBAAgB;AACxC,QAAM,qBAAqB,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS;AAE9D,MAAI,mBAAmB,WAAW,KAAK,CAAC,KAAK,MAAM;AAEjD,IAAAA,MAAI,KAAK,4EAA4E;AACrF,IAAAA,MAAI,KAAK,oJAAoJ;AAC7J,IAAAA,MAAI,KAAK,6DAA6D;AACtE,IAAAA,MAAI,KAAK,mFAAmF;AAC5F,IAAAA,MAAI,KAAK,8DAA8D;AAAA,EACzE,WAAW,mBAAmB,SAAS,GAAG;AACxC,IAAAA,MAAI,KAAK,wBAAwB,mBAAmB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,WAAW,iBAAiB,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EAC9H;AAMA,QAAM,mBAAsC;AAAA,IAC1C,IAAI,aAAa;AAAA,IACjB,IAAI,cAAc;AAAA,IAClB,IAAI,cAAc;AAAA,EACpB;AAEA,QAAM,WAAW,oBAAI,IAA6B;AAElD,QAAM,oBAAoB,iBAAiB,OAAO,CAAC,YAAY;AAC7D,UAAM,aAAa,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,YAAY;AACxE,WAAO,YAAY,cAAc;AAAA,EACnC,CAAC;AAED,QAAM,QAAQ;AAAA,IACZ,kBAAkB,IAAI,OAAO,YAAY;AACvC,YAAM,aAAa,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,YAAY;AACxE,eAAS,IAAI,QAAQ,cAAc,OAAO;AAC1C,UAAI;AACF,cAAM,SAAS,MAAM,QAAQ,WAAW;AACxC,mBAAW,SAAS;AACpB,QAAAA,MAAI,KAAK,GAAG,QAAQ,YAAY,YAAY,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,MAClF,SAAS,KAAK;AACZ,QAAAA,MAAI,KAAK,6BAA6B,QAAQ,YAAY,IAAI;AAAA,UAC5D,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AACA,MAAAA,MAAI,MAAM,sBAAsB,EAAE,MAAM,QAAQ,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH;AAOA,QAAM,SAAS,IAAI,OAAO;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,KAAK;AAAA,IACf,eAAe,KAAK,OAAO,oBAAoB;AAAA,EACjD,CAAC;AAGD,SAAO,GAAG,aAAa,MAAM;AAC3B,IAAAA,MAAI,KAAK,qBAAqB;AAAA,EAChC,CAAC;AAED,SAAO,GAAG,WAAW,CAAC,cAAc;AAClC,IAAAA,MAAI,KAAK,wBAAwB,SAAS,EAAE;AAC5C,QAAI,KAAK,MAAM;AACb,MAAAA,MAAI,KAAK,4DAAuD;AAAA,IAClE;AAAA,EACF,CAAC;AAED,SAAO,GAAG,gBAAgB,CAAC,MAAM,WAAW;AAC1C,IAAAA,MAAI,KAAK,kCAAkC,IAAI,aAAa,MAAM,IAAI;AAAA,EACxE,CAAC;AAED,SAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,IAAAA,MAAI,MAAM,gBAAgB,EAAE,OAAO,IAAI,QAAQ,CAAC;AAEhD,QAAI,eAAe,kBAAkB;AACnC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAED,SAAO,GAAG,iBAAiB,CAAC,WAAW,aAAa;AAClD,IAAAA,MAAI,KAAK,sBAAsB,SAAS,SAAS,QAAQ,GAAG,KAAK,OAAO,iBAAiB,EAAE,EAAE;AAAA,EAC/F,CAAC;AAED,SAAO,GAAG,eAAe,CAAC,cAAc;AACtC,IAAAA,MAAI,KAAK,WAAW,SAAS,YAAY;AAAA,EAC3C,CAAC;AAMD,QAAM,WAAW,OAAO,WAAmB;AACzC,IAAAA,MAAI,KAAK,YAAY,MAAM,kCAA6B;AACxD,UAAM,OAAO,WAAW;AACxB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,MAAM,SAAS,QAAQ,CAAC;AAC7C,UAAQ,GAAG,WAAW,MAAM,SAAS,SAAS,CAAC;AAI/C,UAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,IAAAA,MAAI,MAAM,qGAAgG;AAAA,MACxG,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,IACjE,CAAC;AAED,WAAO,WAAW,EAAE,MAAM,MAAM;AAAA,IAAe,CAAC,EAAE,QAAQ,MAAM;AAC9D,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH,CAAC;AAMD,SAAO,QAAQ;AACjB,CAAC;AAQH,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,UAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,QAAiB;AACvD,IAAAA,MAAI,MAAM,eAAe,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AACpF,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":["crypto","log","log","fs","path","os","log","path","os","fs","log","crypto","delay","log","log","spawn","createInterface","log","spawn","createInterface","spawn","createInterface","log","spawn","createInterface","log","log"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/bridge.ts","../src/protocol/version.ts","../src/tools/manager.ts","../src/utils/logger.ts","../src/tools/resolver.ts","../src/tools/callback-server.ts","../src/session/store.ts","../src/utils/clamp.ts","../src/errors.ts","../src/providers/detector.ts","../src/providers/codex.ts","../src/providers/env.ts","../src/providers/base.ts","../src/tools/prompt.ts","../src/providers/claude.ts","../src/providers/gemini.ts","../src/test-mode.ts"],"sourcesContent":["/**\n * CLI Entry Point for @tetrixdev/ai-bridge\n *\n * Parses command-line arguments, detects local AI CLI providers,\n * creates a Bridge instance, and connects to the server.\n *\n * Usage:\n * npx @tetrixdev/ai-bridge --server wss://example.com/api/ai-bridge/ws --token <token>\n * AI_BRIDGE_TOKEN=xxx AI_BRIDGE_SERVER=wss://... npx @tetrixdev/ai-bridge\n * npx @tetrixdev/ai-bridge --server wss://... --token <token> --test\n */\n\nimport { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { Bridge, FatalBridgeError } from './bridge.js';\nimport { detectProviders } from './providers/detector.js';\nimport { CodexAdapter } from './providers/codex.js';\nimport { ClaudeAdapter } from './providers/claude.js';\nimport { GeminiAdapter } from './providers/gemini.js';\nimport type { ProviderAdapter } from './providers/base.js';\nimport { handleTestRequest } from './test-mode.js';\nimport { setDebug, createLogger } from './utils/logger.js';\nimport { BRIDGE_VERSION, PROTOCOL_VERSION } from './protocol/version.js';\n\nconst log = createLogger('CLI');\n\n// ---------------------------------------------------------------------------\n// CLI Definition\n// ---------------------------------------------------------------------------\n\nconst program = new Command();\n\nprogram\n .name('ai-bridge')\n .description('Local CLI bridge for AI web apps — connects Codex, Claude, and Gemini to your web application via WebSocket')\n .version(BRIDGE_VERSION)\n .option(\n '-t, --token <token>',\n 'Authentication token (or set AI_BRIDGE_TOKEN env var)',\n process.env['AI_BRIDGE_TOKEN'],\n )\n .option(\n '-s, --server <url>',\n 'WebSocket server URL (or set AI_BRIDGE_SERVER env var)',\n process.env['AI_BRIDGE_SERVER'],\n )\n .option(\n '-d, --debug',\n 'Enable verbose debug logging',\n false,\n )\n .option(\n '--test',\n 'Test mode — respond to AI requests with mock streaming data (--server and --token still required for the WebSocket connection)',\n false,\n )\n .action(async (opts: { token?: string; server?: string; debug: boolean; test: boolean }) => {\n // Enable debug logging if requested\n if (opts.debug) {\n setDebug(true);\n }\n\n log.info(`AI Bridge v${BRIDGE_VERSION} (protocol v${PROTOCOL_VERSION})`);\n\n if (opts.test) {\n log.info('Running in TEST MODE — AI requests will receive mock responses');\n }\n\n // Validate required options\n const token = opts.token;\n const serverUrl = opts.server;\n\n if (!token) {\n log.error('Authentication token is required. Use --token <token> or set AI_BRIDGE_TOKEN. Generate a token from your web application (see README for details).');\n process.exit(1);\n }\n\n if (!serverUrl) {\n log.error('Server URL is required. Use --server <url> or set AI_BRIDGE_SERVER. Use the wss:// address provided by your web application (e.g. wss://your-app.com/api/ai-bridge/ws).');\n process.exit(1);\n }\n\n // Validate server URL format\n if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {\n log.error('Server URL must start with ws:// or wss://');\n process.exit(1);\n }\n\n // Warn about unencrypted connections\n if (serverUrl.startsWith('ws://')) {\n log.warn('Connecting over unencrypted ws://. Use wss:// in production.');\n }\n\n // Reject URLs with username/password components to prevent URL authority\n // confusion (e.g. wss://legit.com@attacker.com/ws)\n try {\n const parsedUrl = new URL(serverUrl);\n if (parsedUrl.username || parsedUrl.password) {\n log.error('Server URL must not contain username or password components');\n process.exit(1);\n }\n } catch {\n log.error('Server URL is not a valid URL');\n process.exit(1);\n }\n\n // -----------------------------------------------------------------------\n // Detect providers\n // -----------------------------------------------------------------------\n\n const providers = await detectProviders();\n const availableProviders = providers.filter((p) => p.available);\n\n if (availableProviders.length === 0 && !opts.test) {\n // Warn BEFORE connecting so the user knows the bridge is non-functional.\n log.warn('No AI CLI tools detected. The bridge will NOT be able to execute requests.');\n log.warn('Install one of: codex (https://github.com/openai/codex), claude (https://claude.ai/download), gemini (https://github.com/google-gemini/gemini-cli)');\n log.warn('Or use --test flag to run in test mode with mock responses.');\n log.warn('AI requests will fail until a provider CLI is installed. See install links above.');\n log.warn('Connecting anyway so the server knows a bridge is present...');\n } else if (availableProviders.length > 0) {\n log.info(`Available providers: ${availableProviders.map((p) => `${p.name} (${p.version ?? 'unknown version'})`).join(', ')}`);\n }\n\n // -----------------------------------------------------------------------\n // Initialize adapters and populate model lists\n // -----------------------------------------------------------------------\n\n const adapterInstances: ProviderAdapter[] = [\n new CodexAdapter(),\n new ClaudeAdapter(),\n new GeminiAdapter(),\n ];\n\n const adapters = new Map<string, ProviderAdapter>();\n // Run listModels() concurrently across all available providers.\n const availableAdapters = adapterInstances.filter((adapter) => {\n const capability = providers.find((p) => p.name === adapter.providerName);\n return capability?.available === true;\n });\n\n await Promise.all(\n availableAdapters.map(async (adapter) => {\n const capability = providers.find((p) => p.name === adapter.providerName)!;\n adapters.set(adapter.providerName, adapter);\n try {\n const models = await adapter.listModels();\n capability.models = models;\n log.info(`${adapter.providerName} models: ${models.map((m) => m.id).join(', ')}`);\n } catch (err) {\n log.warn(`Failed to list models for ${adapter.providerName}`, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n log.debug('Registered adapter', { name: adapter.providerName });\n }),\n );\n\n // -----------------------------------------------------------------------\n // Create and connect bridge\n // Token goes in the URL query param (?token=...), NOT in the hello body\n // -----------------------------------------------------------------------\n\n const bridge = new Bridge({\n serverUrl,\n token,\n providers,\n adapters,\n testMode: opts.test,\n onTestRequest: opts.test ? handleTestRequest : undefined,\n });\n\n // Lifecycle logging\n bridge.on('connected', () => {\n log.info('Connected to server');\n });\n\n bridge.on('welcome', (sessionId) => {\n log.info(`Session established: ${sessionId}`);\n if (opts.test) {\n log.info('Test mode active — waiting for ai_request messages...');\n }\n });\n\n bridge.on('disconnected', (code, reason) => {\n log.warn(`Disconnected from server (code=${code}, reason=\"${reason}\")`);\n });\n\n bridge.on('error', (err) => {\n log.error('Bridge error', { error: err.message });\n\n if (err instanceof FatalBridgeError) {\n process.exit(1);\n }\n });\n\n bridge.on('request_start', (requestId, provider) => {\n log.info(`Processing request ${requestId} with ${provider}${opts.test ? ' (test mode)' : ''}`);\n });\n\n bridge.on('request_end', (requestId) => {\n log.info(`Request ${requestId} completed`);\n });\n\n // -----------------------------------------------------------------------\n // Graceful shutdown\n // -----------------------------------------------------------------------\n\n const shutdown = async (signal: string) => {\n log.info(`Received ${signal} — shutting down gracefully`);\n await bridge.disconnect();\n process.exit(0);\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n\n // Unhandled rejections leave the bridge in an unknown state — log, attempt\n // a graceful disconnect, then exit so the operator restarts the process.\n process.on('unhandledRejection', (reason) => {\n log.error('Unhandled rejection — bridge is in an unknown state, exiting (restart the bridge to recover)', {\n error: reason instanceof Error ? reason.message : String(reason),\n });\n // Best-effort disconnect (notify server we're going away)\n bridge.disconnect().catch(() => { /* ignore */ }).finally(() => {\n process.exit(1);\n });\n });\n\n // -----------------------------------------------------------------------\n // Connect\n // -----------------------------------------------------------------------\n\n bridge.connect();\n });\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\n/**\n * Determine whether this module is being executed directly as the CLI entry\n * point (as opposed to being imported by another module or a test).\n *\n * npm installs the package `bin` entry as a symlink\n * (`node_modules/.bin/ai-bridge` -> `.../dist/cli.js`). When executed,\n * `process.argv[1]` is the *symlink* path while `import.meta.url` resolves to\n * the *real* file path, so a plain string comparison fails and the CLI exits\n * silently without doing anything. Comparing the resolved real paths makes the\n * check symlink-safe. `realpathSync` is wrapped in try/catch because either\n * path may not exist on disk (e.g. argv1 from an unusual launcher).\n *\n * @param argv1 The script path Node was invoked with (`process.argv[1]`).\n * @param moduleUrl This module's URL (`import.meta.url`).\n */\nexport function isMainModule(argv1: string | undefined, moduleUrl: string): boolean {\n if (!argv1) return false;\n try {\n return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));\n } catch {\n return false;\n }\n}\n\n// Guard execution so importing this module does not invoke the CLI.\nif (isMainModule(process.argv[1], import.meta.url)) {\n program.parseAsync(process.argv).catch((err: unknown) => {\n log.error('Fatal error', { error: err instanceof Error ? err.message : String(err) });\n process.exit(1);\n });\n}\n","/**\n * Bridge — Core WebSocket management class\n *\n * Connects to the AI Bridge server, handles the protocol handshake,\n * routes AI requests to local provider adapters, and streams results\n * back over the WebSocket.\n *\n * Implements:\n * - WebSocket connection with exponential backoff reconnection\n * - Protocol handshake (hello -> welcome)\n * - AI request routing to providers\n * - Tool call resolution round-trip\n * - Heartbeat (ping/pong) — interval in SECONDS from server\n * - Tool script generation on welcome\n * - Local HTTP callback server for tool scripts\n * - Lifecycle event emission\n */\n\nimport { EventEmitter } from 'node:events';\nimport crypto from 'node:crypto';\nimport WebSocket from 'ws';\nimport type {\n ProviderCapability,\n BridgeToServerMessage,\n ServerToBridgeMessage,\n AiRequestMessage,\n SessionResetMessage,\n WelcomeMessage,\n ToolDefinition,\n ServerConfig,\n StreamEventType,\n StreamEventData,\n} from './protocol/types.js';\nimport { PROTOCOL_VERSION, BRIDGE_VERSION } from './protocol/version.js';\nimport { ProviderAdapter, type ExecutionContext, type AdapterStreamEvent } from './providers/base.js';\nimport { ToolManager } from './tools/manager.js';\nimport { ToolResolver } from './tools/resolver.js';\nimport { ToolCallbackServer } from './tools/callback-server.js';\nimport { SessionStore } from './session/store.js';\nimport { createLogger } from './utils/logger.js';\nimport { clampRequestTimeout, clampHeartbeat } from './utils/clamp.js';\nimport { FatalBridgeError } from './errors.js';\n\nexport { FatalBridgeError } from './errors.js';\n\nconst log = createLogger('Bridge');\n\n// ---------------------------------------------------------------------------\n// Configuration & Defaults\n// ---------------------------------------------------------------------------\n\nexport interface BridgeOptions {\n /** WebSocket server URL (wss://...) — token is appended as ?token= */\n serverUrl: string;\n /** Authentication token (placed in URL query param, NOT in hello body) */\n token: string;\n /** Detected provider capabilities */\n providers: ProviderCapability[];\n /** Provider adapter instances, keyed by provider name */\n adapters: Map<string, ProviderAdapter>;\n /** Whether to run in test mode (mock responses) */\n testMode?: boolean;\n /** Mock response handler for test mode */\n onTestRequest?: (request: AiRequestMessage, sendEvent: (event: StreamEventType, data: StreamEventData) => void) => Promise<void>;\n}\n\nconst DEFAULT_HEARTBEAT_SECONDS = 30;\nconst DEFAULT_REQUEST_TIMEOUT_SECONDS = 300;\n\n// Large-but-finite cap (~24 min of retries with backoff); infinite retry could\n// mask configuration errors.\nconst MAX_RECONNECT_ATTEMPTS = 100;\nconst BASE_RECONNECT_DELAY_MS = 1_000;\nconst MAX_RECONNECT_DELAY_MS = 15_000; // Cap at 15s per PROTOCOL.md\n\n// ---------------------------------------------------------------------------\n// Bridge Events\n// ---------------------------------------------------------------------------\n\nexport interface BridgeEvents {\n connected: [];\n disconnected: [code: number, reason: string];\n welcome: [sessionId: string];\n error: [error: Error];\n request_start: [requestId: string, provider: string];\n request_end: [requestId: string];\n}\n\n// ---------------------------------------------------------------------------\n// Helper: Escape XML characters in message content\n// ---------------------------------------------------------------------------\n\nfunction escapeXml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n// ---------------------------------------------------------------------------\n// Helper: Build a synthetic AiRequestMessage from a session reset\n// ---------------------------------------------------------------------------\n\n/**\n * Construct an AiRequestMessage from a SessionResetMessage by extracting\n * the last user message and folding prior history into the system prompt\n * using XML-tagged structure to prevent prompt injection.\n *\n * Returns null if no user message is found in history.\n */\nfunction buildSessionResetRequest(\n originalMsg: SessionResetMessage,\n currentSystemPrompt: string | null,\n): AiRequestMessage | null {\n const { request_id, conversation_id, provider, history, options } = originalMsg;\n\n // Validate history roles — warn if unexpected values encountered\n const validRoles = new Set(['user', 'assistant', 'system']);\n const unexpectedRoles = history\n .map((h) => h.role)\n .filter((role) => !validRoles.has(role));\n if (unexpectedRoles.length > 0) {\n log.warn('Session reset history contains unexpected role values', {\n unexpectedRoles: [...new Set(unexpectedRoles)],\n });\n }\n\n const lastUserIdx = history.findLastIndex((h) => h.role === 'user');\n if (lastUserIdx === -1) {\n return null;\n }\n\n const lastUserMessage = history[lastUserIdx];\n\n // Build conversation context from prior history (excluding the last user message)\n const rawPriorHistory = history.slice(0, lastUserIdx);\n\n // Exclude entries with unexpected roles to avoid widening the injection\n // surface (an unknown role could be treated as authoritative instructions).\n const priorHistory = rawPriorHistory.filter((h) => validRoles.has(h.role));\n\n // Wrap history in XML tags (with escaped content) to prevent prompt injection.\n let enhancedSystemPrompt: string | null;\n if (priorHistory.length > 0) {\n const historyXml = priorHistory\n .map((h) => `<message role=\"${escapeXml(h.role)}\">${escapeXml(h.content)}</message>`)\n .join('\\n');\n\n const historyBlock = `<conversation_history>\\n${historyXml}\\n</conversation_history>`;\n\n // If system_prompt is null/empty, use only the history context\n enhancedSystemPrompt = currentSystemPrompt\n ? `${currentSystemPrompt}\\n\\n${historyBlock}`\n : historyBlock;\n } else {\n enhancedSystemPrompt = currentSystemPrompt;\n }\n\n return {\n type: 'ai_request',\n request_id,\n conversation_id,\n provider,\n message: lastUserMessage.content,\n system_prompt: enhancedSystemPrompt,\n options,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge Class\n// ---------------------------------------------------------------------------\n\nexport class Bridge extends EventEmitter<BridgeEvents> {\n private ws: WebSocket | null = null;\n private readonly serverUrl: string;\n private readonly token: string;\n private readonly providers: ProviderCapability[];\n private readonly adapters: Map<string, ProviderAdapter>;\n private readonly toolManager = new ToolManager();\n private readonly toolResolver = new ToolResolver();\n private readonly sessionStore = new SessionStore();\n private readonly callbackServer: ToolCallbackServer;\n private readonly testMode: boolean;\n private readonly onTestRequest?: BridgeOptions['onTestRequest'];\n\n private sessionId: string | null = null;\n private serverConfig: ServerConfig = {\n heartbeat_interval: DEFAULT_HEARTBEAT_SECONDS,\n request_timeout: DEFAULT_REQUEST_TIMEOUT_SECONDS,\n };\n /** Timer to detect a missing welcome message after hello is sent. */\n private welcomeTimeoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private pongTimeoutTimer: ReturnType<typeof setTimeout> | null = null;\n private awaitingPong = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private isShuttingDown = false;\n private activeRequests = new Map<string, AbortController>();\n /**\n * Request IDs aborted because the WebSocket dropped while they were in\n * flight. No terminal event could be sent over the closed socket, so on the\n * next welcome these are replayed as session_expired errors to release the\n * browser's loading state.\n */\n private abortedRequestIds: string[] = [];\n /** Random secret for authenticating tool callback HTTP requests. */\n private readonly callbackSecret: string;\n\n constructor(options: BridgeOptions) {\n super();\n this.serverUrl = options.serverUrl;\n this.token = options.token;\n this.providers = options.providers;\n this.adapters = options.adapters;\n this.testMode = options.testMode ?? false;\n this.onTestRequest = options.onTestRequest;\n\n // Generate a random secret for callback server authentication\n this.callbackSecret = crypto.randomBytes(32).toString('hex');\n\n // Forwards HTTP-based tool calls from scripts over the WebSocket.\n const sendFn = (reqId: string, tcId: string, tName: string, tArgs: Record<string, unknown>) => {\n this.send({\n type: 'tool_call',\n request_id: reqId,\n tool_call_id: tcId,\n tool_name: tName,\n arguments: tArgs,\n });\n };\n\n this.callbackServer = new ToolCallbackServer(\n this.toolResolver,\n sendFn,\n new Set(this.toolManager.getAll().map((t) => t.name)),\n this.callbackSecret,\n );\n }\n\n // -------------------------------------------------------------------------\n // Connection Lifecycle\n // -------------------------------------------------------------------------\n\n /**\n * Initiate the WebSocket connection to the server.\n *\n * The token is sent as an Authorization: Bearer header so it does NOT appear\n * in server/proxy access logs. It is also kept in the URL query parameter as\n * a backward-compatible fallback for servers that have not yet adopted the\n * header-based flow.\n *\n * NOTE: When passing --token on the command line the value is still visible\n * in process listings (ps aux). Prefer the AI_BRIDGE_TOKEN environment\n * variable to avoid this.\n */\n connect(): void {\n if (this.ws) {\n log.warn('connect() called while already connected — ignoring');\n return;\n }\n\n // Keep token in query param for backward compatibility, but also send it\n // in the Authorization header as the primary (log-safe) channel.\n const url = new URL(this.serverUrl);\n url.searchParams.set('token', this.token);\n const wsUrl = url.toString();\n\n log.info('Connecting to server', { url: this.serverUrl });\n\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'User-Agent': `ai-bridge/${BRIDGE_VERSION}`,\n // Send token via Authorization header (not visible in proxy logs)\n 'Authorization': `Bearer ${this.token}`,\n },\n // Limit incoming message size to 10MB to prevent memory exhaustion\n maxPayload: 10 * 1024 * 1024,\n });\n\n this.ws.on('open', this.onOpen.bind(this));\n this.ws.on('message', this.onMessage.bind(this));\n this.ws.on('close', this.onClose.bind(this));\n this.ws.on('error', this.onError.bind(this));\n }\n\n /**\n * Gracefully disconnect from the server.\n */\n async disconnect(): Promise<void> {\n this.isShuttingDown = true;\n this.stopHeartbeat();\n this.clearReconnectTimer();\n this.toolResolver.cancelAll();\n this.toolManager.cleanupScripts();\n await this.callbackServer.stop();\n // Persist in-memory last_used_at updates so sessions don't appear stale\n // (premature TTL expiry) after a bridge restart.\n this.sessionStore.flush();\n\n // Cancel active requests\n for (const [id, controller] of this.activeRequests) {\n controller.abort();\n log.debug('Cancelled active request', { requestId: id });\n }\n this.activeRequests.clear();\n\n if (this.ws) {\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close(1000, 'Bridge shutting down');\n }\n this.ws = null;\n }\n\n log.info('Bridge disconnected');\n }\n\n /**\n * Returns true if the WebSocket is currently open.\n */\n isConnected(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n // -------------------------------------------------------------------------\n // WebSocket Event Handlers\n // -------------------------------------------------------------------------\n\n private onOpen(): void {\n log.info('WebSocket connected');\n this.reconnectAttempts = 0;\n this.emit('connected');\n this.sendHello();\n }\n\n private onMessage(data: WebSocket.RawData): void {\n let message: ServerToBridgeMessage;\n try {\n message = JSON.parse(data.toString()) as ServerToBridgeMessage;\n } catch (err) {\n log.error('Failed to parse server message', {\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n\n log.debug('Received message', { type: message.type });\n\n switch (message.type) {\n case 'welcome':\n this.handleWelcome(message);\n break;\n case 'ai_request':\n this.handleAiRequest(message);\n break;\n case 'session_reset':\n this.handleSessionReset(message);\n break;\n case 'tool_resolve':\n this.toolResolver.resolve(message.tool_call_id, message.result);\n break;\n case 'tool_error':\n this.toolResolver.reject(message.tool_call_id, message.error);\n break;\n case 'pong':\n log.debug('Pong received', { timestamp: message.timestamp });\n // Mark pong received and clear the timeout\n this.awaitingPong = false;\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n this.pongTimeoutTimer = null;\n }\n break;\n case 'error':\n this.handleServerError(message);\n break;\n default:\n log.warn('Unknown message type received', { type: (message as { type: string }).type });\n }\n }\n\n private onClose(code: number, reason: Buffer): void {\n const reasonStr = reason.toString();\n log.info('WebSocket closed', { code, reason: reasonStr });\n this.stopHeartbeat();\n // Clear welcome timeout if connection closes before welcome arrives\n if (this.welcomeTimeoutTimer) {\n clearTimeout(this.welcomeTimeoutTimer);\n this.welcomeTimeoutTimer = null;\n }\n this.ws = null;\n // Clear the stale session ID so the welcome-timeout guard on the next\n // reconnect correctly detects a missing welcome.\n this.sessionId = null;\n\n // Cancel all pending tool resolvers immediately on WebSocket close to fail\n // in-flight tool calls instead of letting them stall.\n this.toolResolver.cancelAll();\n\n // Abort all active AI requests on unexpected disconnect so their CLI\n // subprocesses are terminated; otherwise the server never receives a done\n // event and the conversation slot stays blocked until its timeout fires.\n if (!this.isShuttingDown) {\n for (const [id, controller] of this.activeRequests) {\n controller.abort();\n // Record the aborted request so it can be replayed as a\n // session_expired error after reconnect.\n this.abortedRequestIds.push(id);\n log.debug('Aborted active request on disconnect', { requestId: id });\n }\n this.activeRequests.clear();\n }\n\n this.emit('disconnected', code, reasonStr);\n\n if (!this.isShuttingDown) {\n // Check for authentication rejection — don't retry, exit immediately\n if (code === 4001) {\n // Clean up tool scripts and callback server before emitting fatal error\n this.toolManager.cleanupScripts();\n this.callbackServer.stop().catch(() => {\n // Best-effort cleanup; ignore errors during shutdown\n });\n\n this.isShuttingDown = true;\n this.emit(\n 'error',\n new FatalBridgeError(\n 'Connection rejected: invalid or expired token. Generate a new token from your application\\'s dashboard and restart the bridge.',\n ),\n );\n return;\n }\n\n this.scheduleReconnect();\n }\n }\n\n private onError(err: Error): void {\n log.error('WebSocket error', { error: err.message });\n this.emit('error', err);\n }\n\n // -------------------------------------------------------------------------\n // Protocol Handlers\n // -------------------------------------------------------------------------\n\n /**\n * Send hello message per PROTOCOL.md:\n * { type: \"hello\", version, bridge_version, providers[] }\n * NO token field — token is in the URL query param.\n * NO id field on providers — just name.\n */\n private sendHello(): void {\n const hello: BridgeToServerMessage = {\n type: 'hello',\n version: PROTOCOL_VERSION,\n bridge_version: BRIDGE_VERSION,\n providers: this.providers,\n };\n this.send(hello);\n log.info('Hello sent', {\n protocol: PROTOCOL_VERSION,\n providers: this.providers.filter((p) => p.available).map((p) => p.name),\n });\n\n // If no welcome arrives within 15s the server silently dropped our hello;\n // close and reconnect so we don't stall forever.\n this.welcomeTimeoutTimer = setTimeout(() => {\n this.welcomeTimeoutTimer = null;\n if (!this.sessionId && !this.isShuttingDown) {\n log.error('Welcome message not received within 15 seconds after hello — reconnecting');\n this.ws?.close(4000, 'Welcome timeout');\n }\n }, 15_000);\n }\n\n private async handleWelcome(message: WelcomeMessage): Promise<void> {\n // Cancel the welcome-timeout now that we've received the welcome.\n if (this.welcomeTimeoutTimer) {\n clearTimeout(this.welcomeTimeoutTimer);\n this.welcomeTimeoutTimer = null;\n }\n this.sessionId = message.session_id;\n this.serverConfig = message.config;\n\n // Check protocol version compatibility if the server provides one\n if (message.protocol_version) {\n const serverMajor = message.protocol_version.split('.')[0];\n const bridgeMajor = PROTOCOL_VERSION.split('.')[0];\n if (serverMajor !== bridgeMajor) {\n log.warn('Protocol version mismatch — major versions differ', {\n server: message.protocol_version,\n bridge: PROTOCOL_VERSION,\n });\n }\n }\n\n // Clamp request_timeout to a safe range (10–3600 s) before applying — a\n // malicious or misconfigured server could send 0 or a huge value.\n if (message.config.request_timeout) {\n const raw = message.config.request_timeout;\n const clamped = clampRequestTimeout(raw);\n if (clamped !== raw) {\n log.warn('Server request_timeout is outside safe range — clamping', {\n received: raw,\n clamped,\n });\n }\n this.toolResolver.setTimeoutMs(clamped * 1000);\n // Write the clamped value back so generateScripts (below) uses the same\n // timeout as the tool resolver.\n this.serverConfig.request_timeout = clamped;\n }\n\n // Register tools from the server\n this.toolManager.register(message.tools);\n\n // Update the callback server's validation set so it accepts tool calls\n // for the tools we just registered.\n this.callbackServer.setRegisteredToolNames(this.toolManager.getRegisteredNames());\n\n // Notify the server about any tools rejected during registration (unsafe\n // or reserved names) so it can surface a warning.\n const rejectedTools = this.toolManager.getRejectedToolNames();\n if (rejectedTools.length > 0) {\n this.send({\n type: 'error',\n request_id: 'setup',\n code: 'tool_rejected',\n message: `The following tools were rejected by the bridge due to unsafe or reserved names and will be unavailable: ${rejectedTools.join(', ')}`,\n fatal: false,\n });\n }\n\n // Generate tool wrapper scripts and start the callback server\n if (message.tools.length > 0) {\n try {\n await this.callbackServer.start();\n const port = this.callbackServer.getPort();\n if (port) {\n // Pass the server-configured request timeout so the bash script's\n // HTTP timeout matches the bridge-side tool resolver timeout.\n this.toolManager.generateScripts(port, this.callbackSecret, this.serverConfig.request_timeout * 1000);\n log.info('Tool scripts generated', {\n count: message.tools.length,\n callbackPort: port,\n scriptDir: this.toolManager.getScriptDir(),\n });\n }\n } catch (err) {\n log.error('Failed to set up tool callback server', {\n error: err instanceof Error ? err.message : String(err),\n });\n // Notify the server so it can warn the user — all tool calls will fail\n // for this session because the callback server could not start.\n this.send({\n type: 'error',\n request_id: 'setup',\n code: 'tool_setup_failed',\n message: `Tool callback server failed to start — tool calls will not work for this session: ${err instanceof Error ? err.message : String(err)}`,\n fatal: false,\n });\n }\n }\n\n // Start heartbeat — config.heartbeat_interval is in SECONDS. Clamp to a\n // safe range to prevent a ping flood or an excessively long dead-connection\n // window.\n const rawHeartbeat = message.config.heartbeat_interval;\n const clampedHeartbeat = clampHeartbeat(rawHeartbeat);\n if (clampedHeartbeat !== rawHeartbeat) {\n log.warn('Server heartbeat_interval is outside safe range — clamping', {\n received: rawHeartbeat,\n clamped: clampedHeartbeat,\n });\n }\n const intervalMs = clampedHeartbeat * 1000;\n this.startHeartbeat(intervalMs);\n\n log.info('Welcome received', {\n sessionId: this.sessionId,\n toolCount: message.tools.length,\n heartbeatSeconds: message.config.heartbeat_interval,\n });\n\n this.emit('welcome', this.sessionId);\n\n // Replay any requests aborted by a previous disconnect as session_expired\n // errors now that the connection is back, so the browser exits its loading\n // state instead of waiting for the server's own timeout.\n if (this.abortedRequestIds.length > 0) {\n const replayed = this.abortedRequestIds;\n this.abortedRequestIds = [];\n for (const requestId of replayed) {\n log.info('Replaying aborted request as session_expired error', { requestId });\n this.send({\n type: 'error',\n request_id: requestId,\n code: 'session_expired',\n message: 'Request aborted: the bridge connection dropped while the response was streaming.',\n fatal: false,\n });\n }\n }\n }\n\n /**\n * Handle an incoming ai_request: send ack, then execute.\n */\n private handleAiRequest(message: AiRequestMessage): void {\n const { request_id, provider, conversation_id } = message;\n\n // Look up existing CLI session for this conversation\n const existingCliSessionId = conversation_id\n ? this.sessionStore.get(conversation_id)\n : null;\n\n // If a conversation_id was provided but no session was found AND the\n // message has no system_prompt (a follow-up, not a new conversation), send\n // a session_expired error and return early. Continuing would produce two\n // conflicting signals: an error AND a streamed response from a context-less\n // CLI session. The server's session_reset flow recovers from this.\n if (conversation_id && !existingCliSessionId && !message.system_prompt) {\n log.warn('Session not found for conversation — notifying server', {\n conversationId: conversation_id,\n });\n this.send({\n type: 'error',\n request_id,\n code: 'session_expired',\n message: `No local session found for conversation ${conversation_id}`,\n fatal: false,\n });\n // Do NOT proceed: the server will send a session_reset with full history\n // to allow the bridge to reconstruct the context.\n return;\n }\n\n // Send ai_request_ack with the CLI session ID (null when there is no\n // existing session).\n this.send({\n type: 'ai_request_ack',\n request_id,\n cli_session_id: existingCliSessionId ?? null,\n });\n\n // Delegate to the internal handler (shared with session reset)\n this.executeAiRequestInternal(message, existingCliSessionId);\n }\n\n /**\n * Internal request execution logic shared by handleAiRequest and handleSessionReset.\n * Does NOT send ai_request_ack — the caller is responsible for that.\n */\n private executeAiRequestInternal(\n message: AiRequestMessage,\n existingCliSessionId?: string | null,\n ): void {\n const { request_id, provider } = message;\n\n const cliSessionId = existingCliSessionId ?? null;\n\n // Test mode: use mock handler\n if (this.testMode && this.onTestRequest) {\n this.emit('request_start', request_id, provider);\n const sendEvent = (event: StreamEventType, data: StreamEventData) => {\n this.sendStreamEvent(request_id, event, data);\n };\n this.onTestRequest(message, sendEvent)\n .catch((err) => {\n log.error('Test mode handler failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'error', {\n code: 'test_error',\n message: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'done', {});\n })\n .finally(() => {\n this.emit('request_end', request_id);\n });\n return;\n }\n\n // Find the adapter for the requested provider\n const adapter = this.adapters.get(provider);\n if (!adapter) {\n log.error('No adapter for requested provider', { provider });\n const installHints: Record<string, string> = {\n codex: ' Install the Codex CLI (https://github.com/openai/codex) on the machine running the bridge and restart it.',\n claude: ' Install the Claude CLI (https://claude.ai/download) on the machine running the bridge and restart it.',\n gemini: ' Install the Gemini CLI (https://github.com/google-gemini/gemini-cli) on the machine running the bridge and restart it.',\n };\n const hint = installHints[provider] ?? '';\n this.send({\n type: 'error',\n request_id,\n code: 'provider_unavailable',\n message: `Provider \"${provider}\" is not available on this bridge.${hint}`,\n fatal: true,\n });\n return;\n }\n\n this.emit('request_start', request_id, provider);\n\n // Execute asynchronously\n const controller = new AbortController();\n this.activeRequests.set(request_id, controller);\n\n this.executeRequest(adapter, message, cliSessionId, controller.signal)\n .catch((err) => {\n log.error('Request execution failed', {\n requestId: request_id,\n error: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'error', {\n code: 'provider_error',\n message: err instanceof Error ? err.message : String(err),\n });\n this.sendStreamEvent(request_id, 'done', {});\n })\n .finally(() => {\n this.activeRequests.delete(request_id);\n this.emit('request_end', request_id);\n });\n }\n\n private async executeRequest(\n adapter: ProviderAdapter,\n request: AiRequestMessage,\n cliSessionId: string | null,\n signal: AbortSignal,\n ): Promise<void> {\n const { request_id, conversation_id } = request;\n\n // Build execution context\n const context: ExecutionContext = {\n request,\n requestId: request_id,\n tools: this.toolManager.getAll(),\n toolScriptDir: this.toolManager.getScriptDir(),\n onToolCall: async (toolCallId, toolName, args) => {\n return this.toolResolver.call(\n (reqId, tcId, tName, tArgs) => {\n this.send({\n type: 'tool_call',\n request_id: reqId,\n tool_call_id: tcId,\n tool_name: tName,\n arguments: tArgs,\n });\n },\n request_id,\n toolCallId,\n toolName,\n args,\n );\n },\n signal,\n cliSessionId,\n };\n\n // Wrap in try/finally to ensure the session mapping is persisted even if\n // the adapter throws after producing a session ID.\n let newCliSessionId: string | null = null;\n try {\n // Run the adapter — it returns the new CLI session ID\n newCliSessionId = await adapter.execute(context, (event: AdapterStreamEvent) => {\n this.sendStreamEvent(request_id, event.event, event.data);\n });\n } finally {\n // Store the session mapping for future resume. Also persist\n // system_prompt so session resets can restore it even when the server\n // omits it from the session_reset message.\n if (newCliSessionId && conversation_id) {\n this.sessionStore.set(\n conversation_id,\n newCliSessionId,\n adapter.providerName,\n request.system_prompt,\n );\n }\n }\n }\n\n /**\n * Handle a session_reset message by constructing a synthetic ai_request\n * from the conversation history and executing it without sending an ack.\n */\n private handleSessionReset(message: SessionResetMessage): void {\n const { request_id, conversation_id } = message;\n\n // Retrieve the stored system prompt BEFORE deleting the session, so it can\n // be used as a fallback if the server omits system_prompt.\n const storedSystemPrompt = this.sessionStore.getSystemPrompt(conversation_id);\n\n // Delete the old session so the adapter starts fresh\n const deleted = this.sessionStore.delete(conversation_id);\n log.info('Session reset', { conversationId: conversation_id, found: deleted, historyLength: message.history.length });\n\n // Prefer the server-provided system_prompt; fall back to the stored one if\n // the server omits it.\n const effectiveSystemPrompt = message.system_prompt ?? storedSystemPrompt;\n if (!message.system_prompt && storedSystemPrompt) {\n log.warn('session_reset has no system_prompt — using stored system prompt for this conversation', {\n conversationId: conversation_id,\n });\n }\n\n const syntheticRequest = buildSessionResetRequest(message, effectiveSystemPrompt);\n\n if (!syntheticRequest) {\n log.error('Session reset has no user message in history', { conversationId: conversation_id });\n this.send({\n type: 'error',\n request_id,\n code: 'session_reset_failed',\n message: 'No user message found in conversation history',\n fatal: true,\n });\n return;\n }\n\n log.info('Re-processing session_reset as new ai_request', { requestId: request_id, provider: message.provider });\n this.executeAiRequestInternal(syntheticRequest);\n }\n\n private handleServerError(message: { type: 'error'; code: string; message: string; fatal: boolean }): void {\n log.error('Server error', { code: message.code, message: message.message, fatal: message.fatal });\n if (message.fatal) {\n log.error('Fatal server error — disconnecting');\n this.isShuttingDown = true;\n // Clean up tool scripts and callback server (best-effort) before closing.\n this.toolManager.cleanupScripts();\n this.callbackServer.stop().catch(() => {\n // Best-effort cleanup; ignore errors during shutdown\n });\n this.ws?.close(1000, 'Fatal server error');\n }\n }\n\n // -------------------------------------------------------------------------\n // Heartbeat\n // -------------------------------------------------------------------------\n\n private startHeartbeat(intervalMs: number): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.isConnected()) {\n this.send({ type: 'ping', timestamp: Date.now() });\n log.debug('Ping sent');\n\n // Set a 10-second timeout for the pong response; if none arrives,\n // treat the connection as dead.\n this.awaitingPong = true;\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n }\n this.pongTimeoutTimer = setTimeout(() => {\n if (this.awaitingPong && this.isConnected()) {\n log.warn('Pong not received within 10s — connection presumed dead');\n this.ws?.close(4000, 'Pong timeout');\n }\n }, 10_000);\n }\n }, intervalMs);\n log.debug('Heartbeat started', { intervalMs });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.pongTimeoutTimer) {\n clearTimeout(this.pongTimeoutTimer);\n this.pongTimeoutTimer = null;\n }\n this.awaitingPong = false;\n }\n\n // -------------------------------------------------------------------------\n // Reconnection with Exponential Backoff\n // -------------------------------------------------------------------------\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n log.error('Maximum reconnection attempts reached — giving up', {\n attempts: this.reconnectAttempts,\n max: MAX_RECONNECT_ATTEMPTS,\n });\n this.emit(\n 'error',\n new FatalBridgeError(\n 'Maximum reconnection attempts reached. Check that the server URL is correct and the server is reachable, then restart the bridge.',\n ),\n );\n return;\n }\n\n // Per PROTOCOL.md: 1s, 2s, 4s, 8s, 15s cap\n const delay = Math.min(\n BASE_RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),\n MAX_RECONNECT_DELAY_MS,\n );\n\n this.reconnectAttempts++;\n log.info(`Reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, delay);\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n // -------------------------------------------------------------------------\n // Message Sending\n // -------------------------------------------------------------------------\n\n private send(message: BridgeToServerMessage): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n log.warn('Cannot send message — WebSocket not open', { type: message.type });\n return;\n }\n\n const payload = JSON.stringify(message);\n this.ws.send(payload);\n log.debug('Message sent', { type: message.type, bytes: payload.length });\n }\n\n /**\n * Send a stream event using the correct envelope format per PROTOCOL.md:\n * { type: \"stream\", request_id, event: \"<event_type>\", data: {...} }\n */\n private sendStreamEvent(requestId: string, event: StreamEventType, data: StreamEventData): void {\n this.send({\n type: 'stream',\n request_id: requestId,\n event,\n data,\n });\n }\n}\n","declare const __BRIDGE_VERSION__: string;\n\n/** Current AI Bridge Protocol version. */\nexport const PROTOCOL_VERSION = '0.1';\n\n/** Current bridge package version — injected at build time from package.json. */\nexport const BRIDGE_VERSION: string = __BRIDGE_VERSION__;\n","/**\n * Tool Manager\n *\n * Receives tool definitions from the server (via the WelcomeMessage)\n * and manages them for provider adapters. Generates temporary Bash\n * scripts that CLI tools can invoke; those scripts call back to the\n * bridge process which then routes the tool call over WebSocket.\n *\n * Flow:\n * Server -> Welcome (tools[]) -> ToolManager.register()\n * ToolManager generates wrapper scripts in a temp directory\n * Provider adapters add that temp dir to PATH when spawning CLIs\n * CLI invokes tool script -> script contacts bridge -> bridge sends tool_call\n * Server resolves -> bridge feeds result back to CLI\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ToolDefinition } from '../protocol/types.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('ToolManager');\n\n/** Strict pattern for safe tool names: must start with a letter, alphanumeric/underscore/hyphen only. */\nconst SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;\n\n/** Denylist of system binary names that must not be used as tool names. */\nconst RESERVED_TOOL_NAMES = new Set([\n 'curl', 'wget', 'node', 'npm', 'npx', 'bash', 'sh', 'zsh',\n 'python', 'python3', 'ruby', 'perl', 'git', 'ssh', 'scp',\n 'cat', 'ls', 'rm', 'cp', 'mv', 'chmod', 'chown', 'mkdir',\n 'kill', 'ps', 'env', 'sudo', 'su', 'tar', 'gzip', 'gunzip',\n 'openssl', 'nc', 'ncat', 'netcat', 'socat', 'find', 'grep',\n 'awk', 'sed', 'echo', 'printf', 'head', 'tail', 'wc', 'tee',\n 'test', 'true', 'false', 'xargs', 'sort', 'uniq', 'cut', 'tr',\n 'make',\n]);\n\nexport class ToolManager {\n private tools = new Map<string, ToolDefinition>();\n private scriptDir: string | null = null;\n /** Names of tools most recently rejected by register(). */\n private rejectedTools: string[] = [];\n\n /**\n * Register tool definitions received from the server.\n * Replaces any previously registered tools.\n * Tool names are validated against a safe pattern and denylist.\n */\n register(tools: ToolDefinition[]): void {\n this.tools.clear();\n const rejectedTools: string[] = [];\n\n for (const tool of tools) {\n // Validate tool name against regex pattern\n if (!SAFE_TOOL_NAME_PATTERN.test(tool.name)) {\n log.error('Rejected tool with unsafe name', {\n name: tool.name.substring(0, 100),\n reason: 'Tool names must start with a letter and be 1-64 chars of alphanumeric/underscore/hyphen',\n });\n rejectedTools.push(tool.name.substring(0, 100));\n continue;\n }\n\n // Check against the denylist case-insensitively — on case-insensitive\n // filesystems (macOS) a tool named 'Curl' would shadow the system 'curl'.\n if (RESERVED_TOOL_NAMES.has(tool.name.toLowerCase())) {\n log.error('Rejected tool with reserved name', {\n name: tool.name,\n reason: 'Tool name conflicts with a system binary',\n });\n rejectedTools.push(tool.name);\n continue;\n }\n\n this.tools.set(tool.name, tool);\n }\n\n // Expose rejected tool names so the bridge can notify the server.\n this.rejectedTools = rejectedTools;\n\n if (rejectedTools.length > 0) {\n log.warn('Tools rejected by name validation', {\n count: rejectedTools.length,\n names: rejectedTools,\n });\n }\n\n log.info('Registered tools', { accepted: this.tools.size, total: tools.length, names: Array.from(this.tools.keys()) });\n }\n\n /**\n * Return the list of tool names rejected during the last register() call.\n */\n getRejectedToolNames(): string[] {\n return this.rejectedTools;\n }\n\n /**\n * Get a tool definition by name.\n */\n get(name: string): ToolDefinition | undefined {\n return this.tools.get(name);\n }\n\n /**\n * Get all registered tool definitions.\n */\n getAll(): ToolDefinition[] {\n return Array.from(this.tools.values());\n }\n\n /**\n * Returns the number of registered tools.\n */\n count(): number {\n return this.tools.size;\n }\n\n /**\n * Returns the set of registered tool names.\n * Useful for passing to ToolCallbackServer for validation.\n */\n getRegisteredNames(): Set<string> {\n return new Set(this.tools.keys());\n }\n\n /**\n * Generate temporary Bash wrapper scripts for all registered tools.\n *\n * Each script, when invoked by a CLI tool, will:\n * 1. Collect arguments as JSON\n * 2. Send them to the bridge process via a local HTTP callback\n * 3. Wait for the result\n * 4. Print the result to stdout\n *\n * @param callbackPort The local HTTP port the bridge is listening on\n * for tool call callbacks from spawned scripts.\n * @param secret Optional bearer token for callback server auth.\n * @param timeoutMs HTTP timeout for the callback request in ms. Should\n * match the server-configured request_timeout so the\n * bash script does not outlive the bridge-side timeout.\n * Defaults to 300 000 ms (5 min).\n * @returns The path to the temporary directory containing the scripts.\n */\n generateScripts(callbackPort: number, secret?: string, timeoutMs: number = 300_000): string {\n // Clean up any previous script directory\n this.cleanupScripts();\n\n this.scriptDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-bridge-tools-'));\n log.debug('Created tool script directory', { dir: this.scriptDir });\n\n for (const tool of this.tools.values()) {\n const scriptPath = path.join(this.scriptDir, tool.name);\n const scriptContent = this.buildScript(tool, callbackPort, secret, timeoutMs);\n // 0o700 (owner-only) so other local users cannot read the embedded\n // callback bearer secret.\n fs.writeFileSync(scriptPath, scriptContent, { mode: 0o700 });\n log.debug('Generated tool script', { tool: tool.name, path: scriptPath });\n }\n\n return this.scriptDir;\n }\n\n /**\n * Get the directory containing generated tool scripts, or null if\n * scripts have not been generated yet.\n */\n getScriptDir(): string | null {\n return this.scriptDir;\n }\n\n /**\n * Remove all generated tool scripts and the temp directory.\n */\n cleanupScripts(): void {\n if (this.scriptDir) {\n try {\n fs.rmSync(this.scriptDir, { recursive: true, force: true });\n log.debug('Cleaned up tool script directory', { dir: this.scriptDir });\n } catch (err) {\n log.warn('Failed to clean up tool scripts', {\n dir: this.scriptDir,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n this.scriptDir = null;\n }\n }\n\n /**\n * Build the Bash script content for a single tool.\n *\n * Embeds the bearer token for callback server authentication. Tool names\n * are pre-validated by register() so they are safe to embed; tool\n * descriptions are NOT included to prevent injection.\n */\n private buildScript(tool: ToolDefinition, callbackPort: number, secret?: string, timeoutMs: number = 300_000): string {\n const secretArg = secret ?? '';\n return `#!/usr/bin/env bash\n# Auto-generated tool wrapper: ${tool.name}\n# DO NOT EDIT — regenerated on each bridge session.\n\nset -euo pipefail\n\n# Read the JSON payload from the first CLI argument when present; otherwise\n# fall back to stdin (some AI CLIs pass tool args one way, some the other).\nPAYLOAD_DATA=\"\"\nif [ \"$#\" -ge 1 ] && [ -n \"\\${1:-}\" ]; then\n PAYLOAD_DATA=\"$1\"\nelif [ ! -t 0 ]; then\n PAYLOAD_DATA=$(cat)\nfi\n\n# Single Node.js invocation for input parsing, payload building, HTTP call,\n# and output extraction.\nnode -e '\nconst http = require(\"http\");\nconst payloadData = process.argv[1];\nconst toolName = process.argv[2];\nconst toolCallId = process.argv[3];\nconst requestId = process.argv[4];\nconst secret = process.argv[5];\n\n// Parse the payload as JSON, fall back to wrapping as {input: ...}\nlet args = {};\nif (payloadData) {\n try { args = JSON.parse(payloadData); } catch { args = { input: payloadData }; }\n}\n\nconst payload = JSON.stringify({\n tool_name: toolName,\n tool_call_id: toolCallId,\n arguments: args,\n request_id: requestId\n});\n\nconst headers = { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(payload) };\nif (secret) headers[\"Authorization\"] = \"Bearer \" + secret;\n\nconst req = http.request({ hostname: \"127.0.0.1\", port: ${callbackPort}, path: \"/tool-call\", method: \"POST\", headers, timeout: ${timeoutMs} }, (res) => {\n let body = \"\";\n res.on(\"data\", (c) => { body += c; });\n res.on(\"end\", () => {\n try {\n const r = JSON.parse(body);\n if (r.error) { process.stderr.write(r.error + \"\\\\n\"); process.exit(1); }\n process.stdout.write(r.result != null ? String(r.result) : \"\");\n } catch {\n process.stderr.write(\"Tool call failed: invalid response from bridge\\\\n\");\n process.exit(1);\n }\n });\n});\nreq.on(\"error\", (e) => { process.stderr.write(\"Tool call failed: \" + e.message + \"\\\\n\"); process.exit(1); });\nreq.write(payload);\nreq.end();\n// The $RANDOM-based ID here is discarded — the callback server overrides it\n// with a cryptographically strong UUID before use.\n' \"$PAYLOAD_DATA\" \"${tool.name}\" \"tc_\\${RANDOM}\\${RANDOM}\" \"\\${AI_BRIDGE_REQUEST_ID:-}\" \"${secretArg}\"\n`;\n }\n}\n","/**\n * Structured logger for the AI Bridge.\n *\n * Supports debug / info / warn / error levels.\n * Debug output is suppressed unless `setDebug(true)` is called (via --debug flag).\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nconst LEVEL_LABEL: Record<LogLevel, string> = {\n debug: 'DBG',\n info: 'INF',\n warn: 'WRN',\n error: 'ERR',\n};\n\nlet currentLevel: LogLevel = 'info';\n\n/** Enable or disable debug-level output. */\nexport function setDebug(enabled: boolean): void {\n currentLevel = enabled ? 'debug' : 'info';\n}\n\n/** Returns true if debug logging is currently enabled. */\nexport function isDebugEnabled(): boolean {\n return currentLevel === 'debug';\n}\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];\n}\n\nfunction formatMessage(level: LogLevel, component: string, message: string, meta?: Record<string, unknown>): string {\n const ts = formatTimestamp();\n const label = LEVEL_LABEL[level];\n const metaStr = meta ? ' ' + JSON.stringify(meta) : '';\n return `${ts} [${label}] [${component}] ${message}${metaStr}`;\n}\n\n/**\n * Create a scoped logger for a specific component.\n *\n * ```ts\n * const log = createLogger('Bridge');\n * log.info('Connected', { url: 'wss://...' });\n * ```\n */\nexport function createLogger(component: string) {\n return {\n debug(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('debug')) {\n process.stderr.write(formatMessage('debug', component, message, meta) + '\\n');\n }\n },\n\n info(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('info')) {\n process.stderr.write(formatMessage('info', component, message, meta) + '\\n');\n }\n },\n\n warn(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('warn')) {\n process.stderr.write(formatMessage('warn', component, message, meta) + '\\n');\n }\n },\n\n error(message: string, meta?: Record<string, unknown>): void {\n if (shouldLog('error')) {\n process.stderr.write(formatMessage('error', component, message, meta) + '\\n');\n }\n },\n };\n}\n\nexport type Logger = ReturnType<typeof createLogger>;\n","/**\n * Tool Resolver\n *\n * Handles the WebSocket round-trip for tool calls:\n * 1. The provider adapter invokes a tool\n * 2. The resolver sends a tool_call message to the server\n * 3. The resolver waits for a matching tool_resolve or tool_error\n * 4. The result (or error) is returned to the provider adapter\n *\n * Pending tool calls are tracked by tool_call_id and resolved via\n * the `resolve()` / `reject()` methods called by the Bridge when\n * it receives the server's response.\n */\n\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('ToolResolver');\n\n/** A pending tool call awaiting resolution from the server. */\ninterface PendingToolCall {\n toolCallId: string;\n toolName: string;\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/** Callback to send a tool_call message over the WebSocket. */\nexport type SendToolCallFn = (\n requestId: string,\n toolCallId: string,\n toolName: string,\n args: Record<string, unknown>,\n) => void;\n\nexport class ToolResolver {\n private pending = new Map<string, PendingToolCall>();\n private timeoutMs: number;\n\n constructor(timeoutMs: number = 300_000) {\n this.timeoutMs = timeoutMs;\n }\n\n /**\n * Update the timeout duration (e.g. from the server's request_timeout config).\n */\n setTimeoutMs(ms: number): void {\n this.timeoutMs = ms;\n log.debug('Tool resolver timeout updated', { timeoutMs: ms });\n }\n\n /**\n * Initiate a tool call and wait for the server's response.\n *\n * @param sendFn Function to send the tool_call over WebSocket.\n * @param requestId The parent AI request ID.\n * @param toolCallId Unique ID for this tool invocation.\n * @param toolName Name of the tool being called.\n * @param args Tool arguments.\n * @returns The tool result from the server.\n * @throws If the server returns a tool_error or the call times out.\n */\n call(\n sendFn: SendToolCallFn,\n requestId: string,\n toolCallId: string,\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(toolCallId);\n const seconds = Math.round(this.timeoutMs / 1000);\n const minutes = Math.round(seconds / 60);\n const humanDuration = seconds >= 60 ? `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}` : `${seconds}s`;\n reject(new Error(`Tool call ${toolName} (${toolCallId}) timed out after ${humanDuration}`));\n }, this.timeoutMs);\n\n this.pending.set(toolCallId, {\n toolCallId,\n toolName,\n resolve,\n reject,\n timer,\n });\n\n log.debug('Sending tool call to server', { requestId, toolCallId, toolName });\n sendFn(requestId, toolCallId, toolName, args);\n });\n }\n\n /**\n * Resolve a pending tool call with a successful result.\n * Called by the Bridge when a `tool_resolve` message arrives.\n */\n resolve(toolCallId: string, result: unknown): boolean {\n const pending = this.pending.get(toolCallId);\n if (!pending) {\n log.warn('Received tool_resolve for unknown tool_call_id', { toolCallId });\n return false;\n }\n\n clearTimeout(pending.timer);\n this.pending.delete(toolCallId);\n log.debug('Tool call resolved', { toolCallId, toolName: pending.toolName });\n pending.resolve(result);\n return true;\n }\n\n /**\n * Reject a pending tool call with an error.\n * Called by the Bridge when a `tool_error` message arrives.\n */\n reject(toolCallId: string, error: string): boolean {\n const pending = this.pending.get(toolCallId);\n if (!pending) {\n log.warn('Received tool_error for unknown tool_call_id', { toolCallId });\n return false;\n }\n\n clearTimeout(pending.timer);\n this.pending.delete(toolCallId);\n log.debug('Tool call rejected', { toolCallId, toolName: pending.toolName, error });\n pending.reject(new Error(`Tool error (${pending.toolName}): ${error}`));\n return true;\n }\n\n /**\n * Cancel all pending tool calls (e.g., on disconnect).\n */\n cancelAll(): void {\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Tool call cancelled — bridge disconnected'));\n }\n const count = this.pending.size;\n this.pending.clear();\n if (count > 0) {\n log.info('Cancelled all pending tool calls', { count });\n }\n }\n\n /**\n * Returns the number of tool calls currently awaiting resolution.\n */\n pendingCount(): number {\n return this.pending.size;\n }\n}\n","/**\n * Tool Callback Server\n *\n * A minimal local HTTP server that tool wrapper scripts POST to\n * when a CLI invokes a tool. The server receives the tool call,\n * routes it through the ToolResolver (which sends it over WebSocket\n * to the server and waits for the result), then responds to the\n * HTTP request with the tool result.\n *\n * Flow:\n * 1. CLI invokes bash wrapper script for a tool\n * 2. Script POSTs to http://127.0.0.1:<port>/tool-call\n * 3. This server receives the request\n * 4. Routes through ToolResolver -> WebSocket -> server -> tool_resolve\n * 5. Returns the result to the bash script via HTTP response\n * 6. Bash script prints result to stdout for CLI to consume\n */\n\nimport http from 'node:http';\nimport crypto from 'node:crypto';\nimport { createLogger } from '../utils/logger.js';\nimport type { ToolResolver, SendToolCallFn } from './resolver.js';\n\nconst log = createLogger('ToolCallbackServer');\n\n/** Maximum request body size: 1 MB. */\nconst MAX_BODY_SIZE = 1048576;\n\nexport class ToolCallbackServer {\n private server: http.Server | null = null;\n private port: number | null = null;\n\n /** Set of registered tool names for validation. */\n private registeredToolNames: Set<string> | null = null;\n\n /** Shared secret for authenticating callback requests. */\n private readonly secret: string | null;\n\n constructor(\n private readonly toolResolver: ToolResolver,\n private readonly sendFn: SendToolCallFn,\n registeredToolNames?: Set<string>,\n secret?: string,\n ) {\n if (registeredToolNames) {\n this.registeredToolNames = registeredToolNames;\n }\n this.secret = secret ?? null;\n }\n\n /**\n * Set the registered tool names for validation.\n * Tool calls with names not in this set will be rejected.\n */\n setRegisteredToolNames(names: Set<string>): void {\n this.registeredToolNames = names;\n }\n\n /**\n * Start the local HTTP server on a random available port.\n */\n async start(): Promise<number> {\n if (this.server) {\n return this.port!;\n }\n\n return new Promise<number>((resolve, reject) => {\n this.server = http.createServer((req, res) => {\n this.handleRequest(req, res);\n });\n\n // Listen on random port, only on localhost\n this.server.listen(0, '127.0.0.1', () => {\n const addr = this.server!.address();\n if (addr && typeof addr === 'object') {\n this.port = addr.port;\n log.info('Tool callback server started', { port: this.port });\n resolve(this.port);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n\n this.server.on('error', (err) => {\n log.error('Tool callback server error', { error: err.message });\n reject(err);\n });\n });\n }\n\n /**\n * Stop the callback server.\n */\n async stop(): Promise<void> {\n if (!this.server) return;\n\n return new Promise<void>((resolve) => {\n this.server!.close(() => {\n log.info('Tool callback server stopped');\n this.server = null;\n this.port = null;\n resolve();\n });\n });\n }\n\n /**\n * Get the port the server is listening on, or null if not started.\n */\n getPort(): number | null {\n return this.port;\n }\n\n /**\n * Handle incoming HTTP requests from tool wrapper scripts.\n */\n private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n if (req.method !== 'POST' || req.url !== '/tool-call') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n // Verify bearer token if a secret is configured, using timingSafeEqual to\n // prevent timing-based token recovery.\n if (this.secret) {\n const authHeader = req.headers['authorization'];\n const expected = `Bearer ${this.secret}`;\n // Check lengths first (timingSafeEqual requires equal-length buffers)\n if (\n !authHeader ||\n authHeader.length !== expected.length ||\n !crypto.timingSafeEqual(Buffer.from(authHeader), Buffer.from(expected))\n ) {\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Unauthorized' }));\n return;\n }\n }\n\n // Enforce body size limit\n let body = '';\n let bodyLength = 0;\n\n req.on('data', (chunk: Buffer) => {\n bodyLength += chunk.length;\n if (bodyLength > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n\n req.on('end', () => {\n // If the request was already destroyed due to size limit, skip processing\n if (bodyLength > MAX_BODY_SIZE) return;\n\n this.processToolCall(body, res).catch((err) => {\n log.error('Failed to process tool call', {\n error: err instanceof Error ? err.message : String(err),\n });\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));\n });\n });\n }\n\n private async processToolCall(body: string, res: http.ServerResponse): Promise<void> {\n let parsed: { tool_name: string; tool_call_id?: string; arguments: Record<string, unknown>; request_id?: string };\n\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n return;\n }\n\n const { tool_name, arguments: args } = parsed;\n\n // request_id comes exclusively from the POST body\n const requestId = parsed.request_id;\n if (!requestId) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Missing request_id \\u2014 tool call cannot be routed' }));\n return;\n }\n\n // Validate tool_name against the registered set\n if (this.registeredToolNames && !this.registeredToolNames.has(tool_name)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `Unknown tool: ${tool_name}` }));\n return;\n }\n\n const toolCallId = `tc_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;\n\n log.debug('Tool call received via HTTP callback', { tool_name, toolCallId, requestId });\n\n try {\n // Route through the ToolResolver which sends over WebSocket and waits\n const result = await this.toolResolver.call(\n this.sendFn,\n requestId,\n toolCallId,\n tool_name,\n args ?? {},\n );\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ result: typeof result === 'string' ? result : JSON.stringify(result) }));\n } catch (err) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));\n }\n }\n}\n","/**\n * Session Store\n *\n * Persists conversation_id -> cli_session_id mappings to\n * ~/.ai-bridge/sessions.json so that conversations can be resumed\n * across bridge restarts.\n *\n * Includes TTL-based pruning (default: 7 days).\n *\n * Stored format per PROTOCOL.md:\n * {\n * \"conv_xyz789\": {\n * \"provider\": \"claude\",\n * \"cli_session_id\": \"session_def456\",\n * \"created_at\": \"2026-05-14T10:30:00Z\",\n * \"last_used_at\": \"2026-05-14T11:45:00Z\"\n * }\n * }\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('SessionStore');\n\n/** A single session record stored on disk. */\ninterface SessionRecord {\n cli_session_id: string;\n provider: string;\n created_at: string; // ISO 8601\n last_used_at: string; // ISO 8601\n /** Original system prompt, so session resets can reuse it even when the\n * server omits system_prompt from the session_reset message. */\n system_prompt?: string | null;\n}\n\n/** The full shape of the sessions.json file. */\ninterface SessionFile {\n [conversationId: string]: SessionRecord;\n}\n\nconst DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport class SessionStore {\n private readonly dir: string;\n private readonly filePath: string;\n private readonly ttlMs: number;\n private data: SessionFile;\n\n constructor(ttlMs: number = DEFAULT_TTL_MS) {\n this.dir = path.join(os.homedir(), '.ai-bridge');\n this.filePath = path.join(this.dir, 'sessions.json');\n this.ttlMs = ttlMs;\n this.data = {};\n this.load();\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /**\n * Look up the CLI session ID for a given conversation.\n * Returns null if not found or expired.\n */\n get(conversationId: string): string | null {\n const record = this.data[conversationId];\n if (!record) return null;\n\n if (this.isExpired(record)) {\n delete this.data[conversationId];\n this.persist();\n return null;\n }\n\n // Touch last_used_at in memory only — persist on set/delete/shutdown.\n record.last_used_at = new Date().toISOString();\n return record.cli_session_id;\n }\n\n /**\n * Store a mapping from conversation_id to cli_session_id.\n * @param systemPrompt The system prompt used for the first message in this\n * conversation. Stored so session resets can restore it even when the\n * server omits system_prompt from the session_reset message.\n */\n set(conversationId: string, cliSessionId: string, provider: string, systemPrompt?: string | null): void {\n const now = new Date().toISOString();\n const existing = this.data[conversationId];\n this.data[conversationId] = {\n cli_session_id: cliSessionId,\n provider,\n // Preserve the original created_at when updating an existing record —\n // every resumed request calls set() again.\n created_at: existing?.created_at ?? now,\n last_used_at: now,\n // Only overwrite system_prompt when a non-null value is supplied;\n // follow-up requests carry no system_prompt and must not erase it.\n system_prompt: systemPrompt ?? existing?.system_prompt ?? null,\n };\n this.persist();\n log.debug('Session stored', { conversationId, cliSessionId, provider });\n }\n\n /**\n * Retrieve the stored system prompt for a conversation.\n * Returns null if not found or if no system_prompt was stored.\n */\n getSystemPrompt(conversationId: string): string | null {\n return this.data[conversationId]?.system_prompt ?? null;\n }\n\n /**\n * Remove a specific conversation mapping.\n */\n delete(conversationId: string): boolean {\n if (this.data[conversationId]) {\n delete this.data[conversationId];\n this.persist();\n log.debug('Session deleted', { conversationId });\n return true;\n }\n return false;\n }\n\n /**\n * Flush the current in-memory state to disk immediately. Call on bridge\n * shutdown to persist last_used_at updates made in memory via get(), which\n * would otherwise be lost and could make active sessions appear expired.\n */\n flush(): void {\n this.persist();\n log.debug('Session store flushed to disk', { count: Object.keys(this.data).length });\n }\n\n /**\n * Remove all expired sessions. Returns the number of pruned entries.\n */\n prune(): number {\n const now = Date.now();\n let pruned = 0;\n for (const [id, record] of Object.entries(this.data)) {\n if (this.isExpired(record, now)) {\n delete this.data[id];\n pruned++;\n }\n }\n if (pruned > 0) {\n this.persist();\n log.info('Pruned expired sessions', { count: pruned });\n }\n return pruned;\n }\n\n /**\n * Returns the number of active (non-expired) sessions.\n */\n size(): number {\n const now = Date.now();\n return Object.values(this.data).filter((record) => !this.isExpired(record, now)).length;\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isExpired(record: SessionRecord, now: number = Date.now()): boolean {\n const lastUsed = new Date(record.last_used_at).getTime();\n return now - lastUsed > this.ttlMs;\n }\n\n private load(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n // Support both old versioned format and new flat format\n if (parsed && typeof parsed === 'object') {\n if ('version' in parsed && 'sessions' in parsed) {\n // Migrate from old format\n const oldSessions = parsed.sessions as Record<string, {\n cli_session_id: string;\n provider_id?: string;\n provider?: string;\n created_at: number | string;\n last_used_at: number | string;\n }>;\n let migrateSkipped = 0;\n for (const [id, rec] of Object.entries(oldSessions)) {\n // Validate against corrupted old-format files: an empty\n // cli_session_id or unparseable last_used_at (NaN never expires)\n // must be skipped.\n if (!rec.cli_session_id || typeof rec.cli_session_id !== 'string') {\n log.warn('Skipping migrated session record with missing cli_session_id', { id });\n migrateSkipped++;\n continue;\n }\n const rawLastUsed = typeof rec.last_used_at === 'number'\n ? rec.last_used_at\n : new Date(rec.last_used_at ?? '').getTime();\n if (Number.isNaN(rawLastUsed)) {\n log.warn('Skipping migrated session record with invalid last_used_at', { id, last_used_at: rec.last_used_at });\n migrateSkipped++;\n continue;\n }\n this.data[id] = {\n cli_session_id: rec.cli_session_id,\n provider: rec.provider ?? rec.provider_id ?? 'unknown',\n created_at: typeof rec.created_at === 'number' ? new Date(rec.created_at).toISOString() : rec.created_at,\n last_used_at: typeof rec.last_used_at === 'number' ? new Date(rec.last_used_at).toISOString() : rec.last_used_at,\n };\n }\n if (migrateSkipped > 0) {\n log.warn('Skipped invalid session records during migration', { count: migrateSkipped });\n }\n log.debug('Migrated sessions from old format', { count: Object.keys(this.data).length });\n // Persist the migrated data so the old-format file is replaced on\n // disk and not re-migrated on every restart.\n this.persist();\n } else {\n // Validate individual records before accepting them: a missing\n // cli_session_id or unparseable last_used_at (NaN never expires)\n // would otherwise be returned by get() indefinitely.\n const rawSessions = parsed as Record<string, unknown>;\n let skipped = 0;\n for (const [id, rec] of Object.entries(rawSessions)) {\n const r = rec as Partial<SessionRecord>;\n if (!r.cli_session_id || typeof r.cli_session_id !== 'string') {\n log.warn('Skipping session record with missing cli_session_id', { id });\n skipped++;\n continue;\n }\n const lastUsed = new Date(r.last_used_at ?? '').getTime();\n if (Number.isNaN(lastUsed)) {\n log.warn('Skipping session record with invalid last_used_at', { id, last_used_at: r.last_used_at });\n skipped++;\n continue;\n }\n this.data[id] = r as SessionRecord;\n }\n if (skipped > 0) {\n log.warn('Skipped invalid session records on load', { count: skipped });\n }\n log.debug('Sessions loaded from disk', { count: Object.keys(this.data).length });\n }\n }\n }\n } catch (err) {\n // Log at error level so operators notice sessions were lost, and preserve\n // the corrupted file as a .bak for recovery.\n log.error('Failed to load sessions file — starting with empty session store', {\n error: err instanceof Error ? err.message : String(err),\n });\n try {\n if (fs.existsSync(this.filePath)) {\n const bakPath = this.filePath + '.bak';\n fs.copyFileSync(this.filePath, bakPath);\n log.error('Corrupted sessions file saved as backup', { backupPath: bakPath });\n }\n } catch {\n // Best-effort — ignore errors during backup\n }\n }\n\n // Prune on load\n this.prune();\n }\n\n // persist() uses synchronous file I/O intentionally: it is called at most\n // once per completed request, and a single SSD write is negligible next to\n // the multi-second CLI invocations it bookends.\n private persist(): void {\n try {\n if (!fs.existsSync(this.dir)) {\n // Create directory with restricted permissions (owner-only)\n fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });\n }\n // Write sessions file with restricted permissions (owner read/write only)\n fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n } catch (err) {\n log.error('Failed to persist sessions file', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n","/**\n * Clamping utilities for server-provided configuration values.\n *\n * Shared by the bridge and the test suite so both exercise the same\n * constants and logic.\n */\n\n/** Accepted range for the server-provided request_timeout (seconds). */\nexport const REQUEST_TIMEOUT_MIN_S = 10;\nexport const REQUEST_TIMEOUT_MAX_S = 3600;\n\n/** Accepted range for the server-provided heartbeat_interval (seconds). */\nexport const HEARTBEAT_MIN_S = 5;\nexport const HEARTBEAT_MAX_S = 300;\n\n/**\n * Clamp a raw request_timeout value (in seconds) from the server welcome\n * message into the acceptable range [10, 3600].\n */\nexport function clampRequestTimeout(raw: number): number {\n return Math.min(Math.max(raw, REQUEST_TIMEOUT_MIN_S), REQUEST_TIMEOUT_MAX_S);\n}\n\n/**\n * Clamp a raw heartbeat_interval value (in seconds) from the server welcome\n * message into the acceptable range [5, 300].\n */\nexport function clampHeartbeat(raw: number): number {\n return Math.min(Math.max(raw, HEARTBEAT_MIN_S), HEARTBEAT_MAX_S);\n}\n","/**\n * Custom error types for the AI Bridge.\n */\n\n/**\n * A fatal error that cannot be recovered from and should cause the bridge\n * to exit. Used for conditions like invalid/expired tokens or reconnect\n * exhaustion where retrying would be pointless.\n */\nexport class FatalBridgeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FatalBridgeError';\n }\n}\n","/**\n * CLI Detector\n *\n * Probes the local system for known AI CLI tools by attempting to run\n * `<cli> --version`. Returns an array of ProviderCapability descriptors\n * indicating what is available.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ProviderCapability } from '../protocol/types.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('Detector');\nconst execFileAsync = promisify(execFile);\n\n/** CLI probing configuration for each known provider. */\ninterface CliProbe {\n name: string;\n binary: string;\n versionArgs: string[];\n /** Parse the version string from the CLI's stdout/stderr. */\n parseVersion: (output: string) => string | null;\n supports_streaming: boolean;\n supports_tools: boolean;\n supports_thinking: boolean;\n supports_session_resume: boolean;\n}\n\nconst CLI_PROBES: CliProbe[] = [\n {\n name: 'codex',\n binary: 'codex',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n // Codex supports server-defined bridge tools via wrapper scripts on PATH.\n // See codex.ts for the full mechanism.\n supports_tools: true,\n supports_thinking: true,\n supports_session_resume: true,\n },\n {\n name: 'claude',\n binary: 'claude',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n supports_tools: true,\n supports_thinking: true,\n supports_session_resume: true,\n },\n {\n name: 'gemini',\n binary: 'gemini',\n versionArgs: ['--version'],\n parseVersion: (output) => extractVersion(output),\n supports_streaming: true,\n supports_tools: true,\n supports_thinking: false,\n supports_session_resume: true,\n },\n];\n\n/**\n * Try to extract a semver-like version string from CLI output.\n * Matches patterns like \"1.2.3\", \"v1.2.3\", \"0.1.0-beta\".\n */\nfunction extractVersion(output: string): string | null {\n const match = output.match(/v?(\\d+\\.\\d+\\.\\d+[\\w.-]*)/);\n return match ? match[1] : null;\n}\n\n/**\n * Probe a single CLI binary.\n */\nasync function probeOne(probe: CliProbe): Promise<ProviderCapability> {\n const capability: ProviderCapability = {\n name: probe.name,\n version: null,\n available: false,\n supports_streaming: probe.supports_streaming,\n supports_tools: probe.supports_tools,\n supports_thinking: probe.supports_thinking,\n supports_session_resume: probe.supports_session_resume,\n };\n\n try {\n // Remove bridge credential variables from the probe environment — version\n // probes run arbitrary binaries from PATH that must not see the token.\n const probeEnv = { ...process.env };\n delete probeEnv['AI_BRIDGE_TOKEN'];\n delete probeEnv['AI_BRIDGE_SERVER'];\n\n const { stdout, stderr } = await execFileAsync(probe.binary, probe.versionArgs, {\n timeout: 5_000,\n env: probeEnv,\n });\n const output = (stdout || '') + (stderr || '');\n capability.available = true;\n capability.version = probe.parseVersion(output.trim());\n log.info(`Detected ${probe.name}`, { version: capability.version });\n } catch (err) {\n log.debug(`${probe.name} not found or not executable`, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n\n return capability;\n}\n\n/**\n * Detect all known AI CLI tools installed on this system.\n * Probes run concurrently for speed.\n *\n * @returns Array of ProviderCapability for all known providers (both available and unavailable).\n */\nexport async function detectProviders(): Promise<ProviderCapability[]> {\n log.info('Detecting locally installed AI CLI tools...');\n const results = await Promise.all(CLI_PROBES.map(probeOne));\n const available = results.filter((r) => r.available);\n log.info(`Detection complete: ${available.length}/${results.length} providers available`);\n return results;\n}\n","/**\n * Codex CLI Adapter\n *\n * Wraps the OpenAI Codex CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: codex exec --json --skip-git-repo-check --ephemeral -m <model> \"<user message>\"\n * Resume session: codex exec resume <SESSION_ID> --json \"<user message>\"\n *\n * Output format (NDJSON):\n * {\"type\":\"thread.started\",\"thread_id\":\"...\"}\n * {\"type\":\"turn.started\"}\n * {\"type\":\"item.completed\",\"item\":{\"id\":\"item_0\",\"type\":\"reasoning\",\"text\":\"...\"}}\n * {\"type\":\"item.completed\",\"item\":{\"id\":\"item_1\",\"type\":\"agent_message\",\"text\":\"...\"}}\n * {\"type\":\"turn.completed\",\"usage\":{\"input_tokens\":...,\"output_tokens\":...}}\n * {\"type\":\"error\",\"message\":\"...\"}\n * {\"type\":\"turn.failed\",\"error\":{\"message\":\"...\"}}\n */\n\nimport { spawn } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { createInterface } from 'node:readline';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, buildCombinedPrompt, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\nconst log = createLogger('CodexAdapter');\n\n/**\n * Default model to use when no model is specified in the request options.\n *\n * gpt-5.2-codex (the Codex CLI default) is NOT available on ChatGPT Team\n * plans. gpt-5.3-codex is the best coding-optimized model that works\n * with both API key and ChatGPT auth modes.\n */\nconst DEFAULT_MODEL = 'gpt-5.3-codex';\n\nexport class CodexAdapter extends ProviderAdapter {\n readonly providerName = 'codex';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Codex request', { requestId });\n\n // Build CLI arguments\n let args: string[];\n\n // Use the model from request options, or fall back to the default\n const model = request.options?.model ?? DEFAULT_MODEL;\n\n if (cliSessionId) {\n // Resume an existing session\n args = [\n 'exec', 'resume', cliSessionId,\n '--json',\n ];\n // Pass model flag on resume if specified in request options\n if (request.options?.model) {\n args.push('-m', request.options.model);\n }\n log.debug('Resuming session', { cliSessionId });\n } else {\n // New session\n args = [\n 'exec',\n '--json',\n '--skip-git-repo-check',\n '--ephemeral',\n '-m', model,\n ];\n }\n\n // Server-defined bridge tools support. Codex's `exec` sandbox defaults to\n // read-only with no network access, which blocks the wrapper script's\n // loopback callback. When tools are present we run with danger-full-access\n // and approval_policy=never: the wrapper scripts execute directly (no\n // network restriction) and exec never stalls on an approval prompt.\n // workspace-write was not used because its bubblewrap sandbox requires\n // unprivileged user namespaces, which are unavailable in many container and\n // hardened-kernel environments — there the tool callback fails entirely.\n // This matches Claude (bypassPermissions) and Gemini (--yolo): the bridge\n // runs the CLI in the user's own trusted environment. Tool-less requests\n // keep Codex's safer default sandbox.\n const hasTools = context.tools.length > 0;\n if (hasTools) {\n args.push(\n '-s', 'danger-full-access',\n '-c', 'approval_policy=never',\n );\n }\n\n // Build the prompt positional argument. The prompt is appended LAST, after\n // every option flag, so Codex's argument parser never mistakes it for a\n // flag value.\n //\n // When server tools are present we append a note listing the available\n // tool command names so Codex knows it may run them as shell commands.\n // On a fresh session the system prompt is also prepended; on a resumed\n // session the original system prompt was already consumed by the first\n // turn, so the tool note is appended to the user message directly.\n const toolNote = hasTools ? '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir) : '';\n if (!cliSessionId && request.system_prompt) {\n args.push('--', buildCombinedPrompt(request.system_prompt, userMessage) + toolNote);\n } else if (toolNote) {\n args.push('--', userMessage + toolNote);\n } else {\n args.push(userMessage);\n }\n\n // Codex CLI does not support max_tokens directly — log a warning only; do\n // not emit a stream error (the server has no actionable response and it\n // would confuse users who see an error before a successful reply).\n if (request.options?.max_tokens) {\n log.warn('max_tokens option specified but Codex CLI does not support it directly — ignoring', {\n max_tokens: request.options.max_tokens,\n });\n }\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning codex', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n\n // Prepend the tool-script directory to PATH so the wrapper commands are\n // invocable by Codex's model-generated shell commands; pass null when\n // there are no tools so PATH is left untouched.\n if (hasTools) {\n log.info('Server-defined tools enabled for Codex request', {\n toolCount: context.tools.length,\n });\n }\n const env = buildSpawnEnv(hasTools ? context.toolScriptDir : null, context.requestId);\n\n const child = spawn('codex', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing codex process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'codex',\n terminalEvent: 'turn.completed',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n // ── thread.started ─────────────────────────────────\n if (type === 'thread.started') {\n sessionId = (parsed['thread_id'] as string) ?? null;\n log.debug('Thread started', { sessionId });\n return;\n }\n\n // ── turn.started ───────────────────────────────────\n if (type === 'turn.started') {\n log.debug('Turn started');\n return;\n }\n\n // ── item.completed ─────────────────────────────────\n if (type === 'item.completed') {\n const item = parsed['item'] as Record<string, unknown> | undefined;\n if (!item) return;\n\n const itemType = item['type'] as string;\n\n if (itemType === 'agent_message') {\n // Text response from the model\n const text = item['text'] as string;\n if (!text) return;\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'text' },\n });\n\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content: text },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n\n blockIndex++;\n } else if (itemType === 'reasoning') {\n // Thinking / reasoning from the model\n const text = (item['text'] as string) ?? '';\n if (!text) return;\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'thinking' },\n });\n\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content: text },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n\n blockIndex++;\n } else if (itemType === 'error') {\n // Error item\n const message = (item['message'] as string) ?? 'Unknown Codex error';\n log.warn('Codex error item', { message: message.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n // Emit done after error so the server always gets a terminal event.\n onEvent({ event: 'done', data: {} });\n settled = true;\n }\n // function_call and function_call_output items are produced by\n // Codex's own tool execution — we don't need to relay them as\n // stream events since Codex handles tools internally.\n return;\n }\n\n // ── turn.completed ─────────────────────────────────\n if (type === 'turn.completed') {\n // Guard against duplicate done events — an error item may already\n // have settled the stream before turn.completed arrives.\n if (settled) return;\n\n const usage = parsed['usage'] as Record<string, unknown> | undefined;\n const inputTokens = usage ? (usage['input_tokens'] as number) ?? null : null;\n const outputTokens = usage ? (usage['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n // ── turn.failed ────────────────────────────────────\n if (type === 'turn.failed') {\n const error = parsed['error'] as Record<string, unknown> | undefined;\n const message = (error?.['message'] as string) ?? 'Codex turn failed';\n log.warn('Codex turn failed', { message: message.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n\n onEvent({ event: 'done', data: {} });\n settled = true;\n return;\n }\n\n // ── error (top-level) ──────────────────────────────\n if (type === 'error') {\n const message = (parsed['message'] as string) ?? 'Unknown Codex error';\n log.warn('Codex error event', { message: message.substring(0, 200) });\n\n // Emit error + done so the server is always informed, even if\n // Codex exits with code 0 after this and no turn.failed follows.\n onEvent({\n event: 'error',\n data: { code: 'provider_error', message },\n });\n onEvent({ event: 'done', data: {} });\n settled = true;\n return;\n }\n\n // Ignore other event types (response_item with session_meta, turn_context, etc.)\n log.debug('Unhandled Codex event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn codex', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'codex CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn codex: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Codex process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n try {\n const cachePath = join(homedir(), '.codex', 'models_cache.json');\n const raw = await readFile(cachePath, 'utf-8');\n const cache = JSON.parse(raw) as {\n models?: Array<{\n slug: string;\n display_name: string;\n description?: string;\n visibility?: string;\n }>;\n };\n\n if (!cache.models || !Array.isArray(cache.models)) {\n log.warn('Codex models cache is empty or invalid');\n return [];\n }\n\n return cache.models\n .filter((m) => m.visibility !== 'hide') // Exclude hidden models like codex-auto-review\n .map((m) => ({\n id: m.slug,\n name: m.display_name,\n description: m.description,\n is_default: m.slug === DEFAULT_MODEL,\n }));\n } catch (err) {\n log.warn('Failed to read Codex models cache. Run codex once to populate models cache. Showing default model only.', {\n error: err instanceof Error ? err.message : String(err),\n });\n // Fallback: return just the default model\n return [\n { id: DEFAULT_MODEL, name: DEFAULT_MODEL, is_default: true },\n ];\n }\n }\n}\n","/**\n * Shared utilities for provider adapters.\n *\n * Centralizes environment variable construction, prompt building, and\n * stderr buffering that would otherwise be duplicated across all three\n * adapter implementations.\n */\n\n/** Maximum stderr buffer size (10 KB). */\nconst MAX_STDERR_BYTES = 10 * 1024;\n\n/**\n * Build the environment variables for spawning a CLI subprocess.\n *\n * @param toolScriptDir Directory containing tool wrapper scripts to prepend\n * to PATH, or null to skip PATH modification (e.g. for\n * Codex which handles tools internally).\n * @param requestId Optional request ID to pass as AI_BRIDGE_REQUEST_ID\n * env var for concurrent-request correlation.\n * @returns A copy of process.env with the requested modifications applied.\n */\nexport function buildSpawnEnv(toolScriptDir: string | null, requestId?: string): NodeJS.ProcessEnv {\n const env = { ...process.env };\n if (toolScriptDir) {\n env['PATH'] = `${toolScriptDir}:${env['PATH'] ?? ''}`;\n }\n if (requestId) {\n env['AI_BRIDGE_REQUEST_ID'] = requestId;\n }\n // Remove bridge credential variables from the child process environment so\n // the token does not leak into /proc/<pid>/environ or the CLI's own logging.\n delete env['AI_BRIDGE_TOKEN'];\n delete env['AI_BRIDGE_SERVER'];\n return env;\n}\n\n/**\n * Build a combined prompt by prepending the system prompt to the user message.\n *\n * Used by providers (Gemini, Codex) whose CLIs lack a dedicated\n * --system-prompt flag, so system instructions must be concatenated\n * into the user-facing prompt string.\n *\n * @param systemPrompt The system-level instructions.\n * @param userMessage The user's actual request message.\n * @returns A single string with the system prompt followed by the user message.\n */\nexport function buildCombinedPrompt(systemPrompt: string, userMessage: string): string {\n return `${systemPrompt}\\n\\nUser request:\\n${userMessage}`;\n}\n\n/**\n * Append a chunk to a stderr buffer, capping at MAX_STDERR_BYTES (10 KB).\n *\n * Keeps the FIRST 10 KB rather than the last, because the beginning of CLI\n * stderr almost always contains the root-cause error while the tail tends to\n * be less useful stack traces.\n *\n * @param buffer Current buffer contents.\n * @param chunk New data to append.\n * @returns The updated (possibly truncated) buffer.\n */\nexport function appendStderr(buffer: string, chunk: string): string {\n // Once we have 10 KB, stop accumulating — root-cause is already there.\n if (buffer.length >= MAX_STDERR_BYTES) {\n return buffer;\n }\n buffer += chunk;\n if (buffer.length > MAX_STDERR_BYTES) {\n buffer = buffer.slice(0, MAX_STDERR_BYTES);\n }\n return buffer;\n}\n\n/**\n * Produce a user-friendly error message from raw CLI stderr output.\n *\n * Detects common known patterns (auth failures, rate limits) and returns a\n * clear actionable message. Strips ANSI escape codes and limits to the first\n * meaningful line for unrecognized errors.\n *\n * @param provider Provider name (e.g. \"claude\") used in fallback messages.\n * @param stderr Raw stderr output from the CLI.\n * @param exitCode Process exit code (for context).\n * @returns A user-facing error string.\n */\nexport function formatStderrMessage(provider: string, stderr: string, exitCode: number | null): string {\n // Strip ANSI escape sequences\n // eslint-disable-next-line no-control-regex\n const clean = stderr.replace(/\\x1b\\[[0-9;]*[mGKHFJSTsuABCDhl]/g, '').trim();\n\n if (!clean) {\n return `${provider} CLI exited with code ${exitCode ?? 'unknown'}`;\n }\n\n const lower = clean.toLowerCase();\n\n // Auth-related patterns\n if (\n lower.includes('401') ||\n lower.includes('403') ||\n lower.includes('unauthorized') ||\n lower.includes('unauthenticated') ||\n lower.includes('auth') ||\n lower.includes('login') ||\n lower.includes('authenticate') ||\n lower.includes('not logged in') ||\n lower.includes('sign in') ||\n lower.includes('credentials')\n ) {\n // Codex uses `codex login`; Claude and Gemini use `<provider> auth login`.\n const authCmd = provider === 'codex' ? `${provider} login` : `${provider} auth login`;\n return `Authentication required — run \\`${authCmd}\\` to re-authenticate.`;\n }\n\n // Rate limit patterns\n if (\n lower.includes('rate limit') ||\n lower.includes('ratelimit') ||\n lower.includes('too many requests') ||\n lower.includes('429')\n ) {\n return `Rate limit reached — please wait a moment and try again.`;\n }\n\n // Return the first non-empty line, capped to 500 characters\n const firstLine = clean.split('\\n').find((l) => l.trim()) ?? clean;\n return firstLine.substring(0, 500);\n}\n","/**\n * Abstract base class for AI CLI provider adapters.\n *\n * Each adapter wraps a specific CLI tool (Codex, Claude, Gemini) and\n * normalizes its output into the Bridge protocol's stream event format.\n */\n\nimport { ChildProcess } from 'node:child_process';\nimport { Interface as ReadlineInterface } from 'node:readline';\nimport type {\n ModelInfo,\n AiRequestMessage,\n ToolDefinition,\n StreamEventType,\n StreamEventData,\n} from '../protocol/types.js';\nimport { formatStderrMessage } from './env.js';\n\n/** A stream event emitted by the adapter. */\nexport interface AdapterStreamEvent {\n event: StreamEventType;\n data: StreamEventData;\n}\n\n/** Context passed to a provider when executing a request. */\nexport interface ExecutionContext {\n /** The full AI request from the server. */\n request: AiRequestMessage;\n /** The request ID for correlation with tool calls, stream events, and concurrent-request correlation (set by bridge). */\n requestId: string;\n /** Tool definitions that should be made available to the CLI. */\n tools: ToolDefinition[];\n /** Path to directory containing generated tool wrapper scripts. */\n toolScriptDir: string | null;\n /** Callback to resolve a tool call through the server. */\n onToolCall: (toolCallId: string, toolName: string, args: Record<string, unknown>) => Promise<unknown>;\n /** Abort signal for cancellation. */\n signal: AbortSignal;\n /** CLI session ID if resuming, or null for new session. */\n cliSessionId: string | null;\n}\n\n/**\n * Shared subprocess finalization helper.\n *\n * Manages the race between the readline 'close' event and the child process\n * 'close' event — the 'done' event must not be sent until both have fired.\n *\n * Returns an object containing:\n * - `onRlClose` — call from rl.on('close')\n * - `onChildClose` — call from child.on('close', code)\n *\n * @param providerName Provider name used in error messages.\n * @param terminalEvent Name of the expected terminal output event\n * (e.g. 'result', 'turn.completed') for logging.\n * @param getSettled Returns the current settled flag (read-only).\n * @param setSettled Sets the settled flag to true.\n * @param getSessionId Returns the current session ID (may be null).\n * @param getStderr Returns the current stderr buffer.\n * @param onEvent Adapter's onEvent callback.\n * @param resolve Promise resolve function.\n * @param signal AbortSignal (for listener cleanup).\n * @param onAbort Abort listener to remove on finalization.\n * @param onBeforeFinalize Optional callback for provider-specific pre-finalize\n * work (e.g. closing an open text block in Gemini).\n */\nexport function createFinalizer(opts: {\n providerName: string;\n terminalEvent: string;\n getSettled: () => boolean;\n setSettled: () => void;\n getSessionId: () => string | null;\n getStderr: () => string;\n onEvent: (event: AdapterStreamEvent) => void;\n resolve: (sessionId: string | null) => void;\n signal: AbortSignal;\n onAbort: () => void;\n onBeforeFinalize?: () => void;\n}): { onRlClose: () => void; onChildClose: (code: number | null) => void } {\n let rlClosed = false;\n let childExitCode: number | null = null;\n let childExited = false;\n\n const tryFinalize = () => {\n if (!rlClosed || !childExited) return;\n opts.signal.removeEventListener('abort', opts.onAbort);\n\n if (opts.getSettled()) {\n opts.resolve(opts.getSessionId());\n return;\n }\n opts.setSettled();\n\n // Provider-specific pre-finalize work (e.g. close an open text block)\n opts.onBeforeFinalize?.();\n\n if (childExitCode !== 0 && childExitCode !== null) {\n opts.onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: formatStderrMessage(opts.providerName, opts.getStderr(), childExitCode),\n },\n });\n opts.onEvent({ event: 'done', data: {} });\n } else {\n // Clean exit but no terminal event — emit a non-fatal error.\n opts.onEvent({\n event: 'error',\n data: {\n code: 'provider_empty_response',\n message: 'The AI returned no response. Please try again.',\n },\n });\n opts.onEvent({ event: 'done', data: {} });\n }\n\n opts.resolve(opts.getSessionId());\n };\n\n return {\n onRlClose: () => {\n rlClosed = true;\n tryFinalize();\n },\n onChildClose: (code: number | null) => {\n childExitCode = code;\n childExited = true;\n tryFinalize();\n },\n };\n}\n\n// Re-export child_process types needed by adapters that use createFinalizer\nexport type { ChildProcess, ReadlineInterface };\n\nexport abstract class ProviderAdapter {\n /** Provider name / identifier (e.g. \"codex\", \"claude\", \"gemini\"). */\n abstract readonly providerName: string;\n\n /**\n * Execute an AI request by invoking the local CLI.\n *\n * The adapter should call `onEvent` for each streaming chunk produced\n * by the CLI, normalizing the output into stream event format.\n *\n * Must send a final `done` event when the CLI exits.\n * Returns the CLI session ID for future resumption (or null).\n *\n * @param context Execution context with request, tools, and tool resolution callback.\n * @param onEvent Callback for each normalized stream event.\n * @returns The CLI session ID (for session resume) or null.\n */\n abstract execute(\n context: ExecutionContext,\n onEvent: (event: AdapterStreamEvent) => void,\n ): Promise<string | null>;\n\n /**\n * List available models for this provider.\n *\n * Returns model info from local CLI config/cache where possible,\n * or known model aliases as a fallback.\n */\n abstract listModels(): Promise<ModelInfo[]>;\n}\n","/**\n * Tool Prompt Manifest\n *\n * The bridge exposes each server-registered tool as a Bash wrapper script on\n * the AI CLI's PATH (filename = tool name). None of the supported CLIs\n * (claude / codex / gemini) have a protocol-level concept of these external\n * tools, so the model is never told they exist. This module builds a\n * human-readable markdown manifest describing the tools, their parameters,\n * and how to call them, which provider adapters append to the prompt the\n * model receives.\n */\n\nimport type { ToolDefinition } from '../protocol/types.js';\n\n/** Shape of a single JSON Schema property entry we care about. */\ninterface SchemaProperty {\n type?: unknown;\n description?: unknown;\n}\n\n/**\n * Build a markdown manifest describing the server-defined bridge tools.\n *\n * Each tool is documented as a shell command on PATH that takes a single\n * JSON-object argument and prints its result to stdout. When `tools` is empty\n * an empty string is returned so callers can append unconditionally.\n *\n * The function is intentionally defensive: a tool's `parameters` may be `{}`,\n * may be missing `properties`, and individual property entries may not be\n * objects. Anything malformed is described as best-effort rather than throwing.\n *\n * @param tools The server-defined tool definitions for this request.\n * @returns A markdown manifest, or an empty string when there are no tools.\n */\nexport function buildToolInstructions(\n tools: ToolDefinition[],\n toolScriptDir?: string | null,\n): string {\n if (tools.length === 0) return '';\n\n // Use the absolute path to each tool script in examples. Relying on PATH is\n // fragile — some CLIs run commands through a login shell (`bash -lc`) that\n // re-sources profile scripts and resets PATH, dropping the bridge's tool\n // directory. An absolute path always resolves.\n const cmd = (name: string): string =>\n toolScriptDir ? `${toolScriptDir}/${name}` : name;\n\n const sections: string[] = [\n '# Available Tools',\n '',\n 'You have access to the following tools. Each tool is an executable ' +\n 'script. Call a tool by running it as a shell command with a single ' +\n 'argument: a JSON object containing the parameters. The command prints ' +\n 'its result to stdout. Run each tool using the exact path shown in its ' +\n '\"Call it like\" example. Use these tools whenever they help fulfill the ' +\n 'request.',\n ];\n\n for (const tool of tools) {\n sections.push('', `## ${tool.name}`, '', tool.description);\n\n const parameters = (tool.parameters ?? {}) as Record<string, unknown>;\n const properties = (parameters['properties'] ?? {}) as Record<string, unknown>;\n const required = Array.isArray(parameters['required'])\n ? (parameters['required'] as unknown[]).map((r) => String(r))\n : [];\n const propNames = Object.keys(properties);\n\n if (propNames.length === 0) {\n sections.push('', 'Parameters: No parameters');\n sections.push('', `Call it like: ${cmd(tool.name)} '{}'`);\n continue;\n }\n\n sections.push('', 'Parameters:');\n for (const pname of propNames) {\n const raw = properties[pname];\n const prop: SchemaProperty =\n raw && typeof raw === 'object' ? (raw as SchemaProperty) : {};\n const type = typeof prop.type === 'string' ? prop.type : 'any';\n const requiredLabel = required.includes(pname) ? 'required' : 'optional';\n const description =\n typeof prop.description === 'string' && prop.description.length > 0\n ? prop.description\n : 'No description';\n sections.push(`- ${pname} (${type}, ${requiredLabel}): ${description}`);\n }\n\n // Example uses the first parameter so the model sees the calling shape.\n const exampleParam = propNames[0];\n sections.push(\n '',\n `Call it like: ${cmd(tool.name)} '{\"${exampleParam}\":\"<value>\"}'`,\n );\n }\n\n return sections.join('\\n');\n}\n","/**\n * Claude CLI Adapter\n *\n * Wraps the Anthropic Claude CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: claude -p --output-format stream-json --verbose \"user message\"\n * Resume session: claude -p --session-id <UUID> --output-format stream-json --verbose \"user message\"\n * System prompt: Passed via --system-prompt flag on first message\n *\n * Output format (NDJSON):\n * {\"type\":\"system\",\"subtype\":\"init\",\"session_id\":\"...\",\"model\":\"...\"}\n * {\"type\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"...\"}],...}}\n * {\"type\":\"result\",\"subtype\":\"success\",\"session_id\":\"...\",\"usage\":{...},\"total_cost_usd\":...}\n */\n\nimport { spawn } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\n/**\n * Known Claude CLI model aliases.\n *\n * Claude Code uses stable aliases (sonnet, opus, haiku) that resolve to the\n * latest version within each model family. The CLI has no dynamic model listing\n * command, so these aliases are the official user-facing interface.\n */\nconst CLAUDE_MODELS: ModelInfo[] = [\n { id: 'sonnet', name: 'Sonnet', description: 'Best balance of speed and intelligence', is_default: true },\n { id: 'opus', name: 'Opus', description: 'Highest intelligence, slower', is_default: false },\n { id: 'haiku', name: 'Haiku', description: 'Fastest and most cost-efficient', is_default: false },\n];\n\nconst log = createLogger('ClaudeAdapter');\n\nexport class ClaudeAdapter extends ProviderAdapter {\n readonly providerName = 'claude';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Claude request', { requestId });\n\n // Build CLI arguments\n const args: string[] = [\n '-p', // Print mode (non-interactive)\n '--output-format', 'stream-json', // NDJSON streaming output\n '--verbose', // Required for stream-json in print mode\n ];\n\n // Resume an existing session if we have a session ID\n if (cliSessionId) {\n args.push('--session-id', cliSessionId);\n log.debug('Resuming session', { cliSessionId });\n }\n\n // Add system prompt if provided (only on new sessions)\n if (request.system_prompt && !cliSessionId) {\n args.push('--system-prompt', request.system_prompt);\n }\n\n // Add model if specified in request options\n if (request.options?.model) {\n args.push('--model', request.options.model);\n }\n\n // Add max tokens if specified\n if (request.options?.max_tokens) {\n args.push('--max-tokens', String(request.options.max_tokens));\n }\n\n // Enable tool execution when server-defined tools are available. The wrapper\n // scripts are invoked through Claude's Bash tool; in headless (-p) mode every\n // Bash command would otherwise be denied with \"requires approval\" since there\n // is no interactive approver. bypassPermissions auto-approves tool use — the\n // bridge runs in the user's own trusted environment, mirroring Codex\n // (approval_policy=never) and Gemini (--yolo).\n if (context.tools.length > 0 && context.toolScriptDir) {\n args.push('--permission-mode', 'bypassPermissions');\n }\n\n // The user message is the final argument. When server-defined tools are\n // present, append the tool manifest so the model knows the tools exist and\n // how to call them — appended every turn (new and resumed sessions) since\n // Claude has no protocol-level concept of these external tools.\n let promptArg = userMessage;\n if (context.tools.length > 0) {\n promptArg += '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir);\n }\n args.push(promptArg);\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning claude', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve, reject) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n\n // Build env with tool scripts on PATH and request ID for correlation\n const env = buildSpawnEnv(context.toolScriptDir, context.requestId);\n // Claude CLI refuses to run if CLAUDECODE is set, even to empty string\n delete env['CLAUDECODE'];\n\n const child = spawn('claude', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'], // stdin must be 'ignore' — Claude CLI hangs if stdin is a pipe\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing claude process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'claude',\n terminalEvent: 'result',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n if (type === 'system' && (parsed as Record<string, unknown>)['subtype'] === 'init') {\n // Extract session ID from init event\n sessionId = (parsed['session_id'] as string) ?? null;\n log.debug('Session init', { sessionId, model: parsed['model'] });\n return;\n }\n\n if (type === 'assistant') {\n // A late readline-buffered assistant event can arrive after the\n // stream is already settled; log it for diagnosis.\n if (settled) {\n log.debug('Assistant event received after stream settled — block events would be emitted post-done', {\n sessionId,\n });\n }\n\n // The assistant message contains the content blocks\n const message = parsed['message'] as Record<string, unknown> | undefined;\n if (!message) return;\n\n const content = message['content'] as Array<Record<string, unknown>> | undefined;\n if (!content || !Array.isArray(content)) return;\n\n for (const block of content) {\n const blockType = block['type'] as string;\n\n if (blockType === 'text') {\n const text = block['text'] as string;\n if (!text) continue;\n\n // Emit block_start + block_delta + block_stop for text\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'text',\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: text,\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n } else if (blockType === 'thinking') {\n const thinking = block['thinking'] as string;\n if (!thinking) continue;\n\n // Emit thinking block\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'thinking',\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: thinking,\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n } else if (blockType === 'tool_use') {\n // Claude emits tool_use blocks when the model wants to call a tool\n const toolName = block['name'] as string;\n const toolId = block['id'] as string;\n const toolInput = block['input'] as Record<string, unknown> | undefined;\n\n if (!toolName || !toolId) continue;\n\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'tool_call',\n tool_name: toolName,\n tool_call_id: toolId,\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: JSON.stringify(toolInput ?? {}),\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: {\n block_index: blockIndex,\n },\n });\n\n blockIndex++;\n }\n }\n return;\n }\n\n if (type === 'result') {\n // Extract final session ID and usage from result\n sessionId = (parsed['session_id'] as string) ?? sessionId;\n\n const usage = parsed['usage'] as Record<string, unknown> | undefined;\n const inputTokens = usage ? (usage['input_tokens'] as number) ?? null : null;\n const outputTokens = usage ? (usage['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n // rate_limit_event is informational — Claude Code emits it to report\n // rate-limit status (often status \"allowed\") and continues streaming.\n // It must NOT be turned into a terminal error: doing so aborts the\n // request mid-stream. A genuine hard rate-limit surfaces through the\n // result event / non-zero exit, which the normal error path handles.\n if (type === 'rate_limit_event') {\n log.debug('Claude rate limit event (informational)', {\n status: (parsed['rate_limit_info'] as Record<string, unknown> | undefined)?.['status'],\n });\n return;\n }\n\n log.debug('Unhandled Claude event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn claude', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'claude CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn claude: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Claude process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n // Claude CLI has no dynamic model listing — return known aliases\n return CLAUDE_MODELS;\n }\n}\n","/**\n * Gemini CLI Adapter\n *\n * Wraps the Google Gemini CLI to produce normalized stream events.\n *\n * CLI invocation:\n * New session: gemini --prompt \"user message\" --output-format stream-json\n * Resume session: gemini --prompt \"user message\" --resume <session-id> --output-format stream-json\n *\n * Output format (NDJSON):\n * {\"type\":\"init\",\"session_id\":\"...\",\"model\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"message\",\"role\":\"user\",\"content\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"message\",\"role\":\"assistant\",\"content\":\"...\",\"delta\":true,\"timestamp\":\"...\"}\n * {\"type\":\"tool_use\",\"tool_name\":\"...\",\"tool_id\":\"...\",\"parameters\":{...},\"timestamp\":\"...\"}\n * {\"type\":\"tool_result\",\"tool_id\":\"...\",\"status\":\"success|error\",\"output\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"error\",\"severity\":\"warning|error\",\"message\":\"...\",\"timestamp\":\"...\"}\n * {\"type\":\"result\",\"status\":\"success|error\",\"stats\":{...},\"timestamp\":\"...\"}\n */\n\nimport { spawn } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport type { ModelInfo } from '../protocol/types.js';\nimport { ProviderAdapter, createFinalizer, type ExecutionContext, type AdapterStreamEvent } from './base.js';\nimport { buildSpawnEnv, buildCombinedPrompt, appendStderr, formatStderrMessage } from './env.js';\nimport { buildToolInstructions } from '../tools/prompt.js';\nimport { createLogger, isDebugEnabled } from '../utils/logger.js';\n\n/**\n * Known Gemini CLI model aliases and models.\n *\n * Gemini CLI supports aliases (auto, pro, flash, flash-lite) that resolve to\n * concrete model versions. Like Claude, there is no `--list-models` command,\n * but the aliases are the official user-facing interface.\n */\nconst GEMINI_MODELS: ModelInfo[] = [\n { id: 'auto', name: 'Auto', description: 'Automatically selects the best model', is_default: true },\n { id: 'pro', name: 'Pro', description: 'Complex reasoning tasks (Gemini 2.5 Pro)', is_default: false },\n { id: 'flash', name: 'Flash', description: 'Fast and balanced (Gemini 2.5 Flash)', is_default: false },\n { id: 'flash-lite', name: 'Flash Lite', description: 'Fastest for simple tasks (Gemini 2.5 Flash Lite)', is_default: false },\n];\n\nconst log = createLogger('GeminiAdapter');\n\nexport class GeminiAdapter extends ProviderAdapter {\n readonly providerName = 'gemini';\n\n async execute(context: ExecutionContext, onEvent: (event: AdapterStreamEvent) => void): Promise<string | null> {\n const { request, signal, cliSessionId } = context;\n const requestId = request.request_id;\n const userMessage = request.message;\n\n log.info('Executing Gemini request', { requestId });\n\n // Build the prompt — prepend system prompt if provided (Gemini CLI\n // has no dedicated --system-instruction flag, so we concatenate)\n let prompt = userMessage;\n if (request.system_prompt && !cliSessionId) {\n prompt = buildCombinedPrompt(request.system_prompt, userMessage);\n }\n\n // When server-defined tools are present, append the tool manifest so the\n // model knows the tools exist and how to call them — appended every turn\n // (new and resumed sessions) since Gemini has no protocol-level concept of\n // these external tools.\n const hasTools = context.tools.length > 0;\n if (hasTools) {\n prompt += '\\n\\n' + buildToolInstructions(context.tools, context.toolScriptDir);\n }\n\n // Build CLI arguments\n const args: string[] = [\n '--prompt', prompt, // Non-interactive mode with prompt\n '--output-format', 'stream-json', // NDJSON streaming output\n '--skip-trust', // Required for headless/non-interactive mode\n ];\n\n // Server-defined bridge tools are invoked as shell commands. --skip-trust\n // only trusts the workspace folder; it does NOT auto-approve tool/shell\n // execution. In non-interactive --prompt mode Gemini's default approval\n // mode would block on an approval prompt the model cannot answer, stalling\n // the request. --yolo (approval-mode \"yolo\") auto-approves all tool calls\n // so the wrapper scripts can run. Only enabled when tools are present so\n // tool-less requests keep Gemini's safer default approval behavior.\n if (hasTools) {\n args.push('--yolo');\n }\n\n // Resume an existing session if we have a session ID\n if (cliSessionId) {\n args.push('--resume', cliSessionId);\n log.debug('Resuming session', { cliSessionId });\n }\n\n // Add model if specified in request options\n if (request.options?.model) {\n args.push('--model', request.options.model);\n }\n\n // Gemini CLI does not support max_tokens directly — log a warning only; do\n // not emit a stream error (the server has no actionable response and it\n // would confuse users who see an error before a successful reply).\n if (request.options?.max_tokens) {\n log.warn('max_tokens option specified but Gemini CLI does not support it directly — ignoring', {\n max_tokens: request.options.max_tokens,\n });\n }\n\n // Only build the truncated arg array when debug logging is active\n if (isDebugEnabled()) {\n log.debug('Spawning gemini', { args: args.map((a) => a.length > 50 ? a.substring(0, 50) + '...' : a) });\n }\n\n return new Promise<string | null>((resolve) => {\n let sessionId: string | null = null;\n let blockIndex = 0;\n let settled = false;\n let inTextBlock = false;\n\n // Build env with tool scripts on PATH and request ID for correlation\n const env = buildSpawnEnv(context.toolScriptDir, context.requestId);\n\n const child = spawn('gemini', args, {\n env,\n stdio: ['ignore', 'pipe', 'pipe'], // stdin must be 'ignore' to prevent hanging\n });\n\n // Set up abort handling\n const onAbort = () => {\n log.info('Request aborted — killing gemini process', { requestId });\n child.kill('SIGTERM');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n\n // Track stderr in a variable so the finalizer closure can access it.\n let stderrBuffer = '';\n\n const finalizer = createFinalizer({\n providerName: 'gemini',\n terminalEvent: 'result',\n getSettled: () => settled,\n setSettled: () => { settled = true; },\n getSessionId: () => sessionId,\n getStderr: () => stderrBuffer,\n onEvent,\n resolve,\n signal,\n onAbort,\n // Gemini-specific: close any open text block before finalizing\n onBeforeFinalize: () => {\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n },\n });\n\n // Parse NDJSON from stdout line by line\n const rl = createInterface({ input: child.stdout });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(line) as Record<string, unknown>;\n } catch {\n log.debug('Skipping non-JSON line', { line: line.substring(0, 100) });\n return;\n }\n\n const type = parsed['type'] as string;\n\n // ── init ─────────────────────────────────────────────\n if (type === 'init') {\n sessionId = (parsed['session_id'] as string) ?? null;\n log.debug('Session init', { sessionId, model: parsed['model'] });\n return;\n }\n\n // ── message ──────────────────────────────────────────\n if (type === 'message') {\n const role = parsed['role'] as string;\n\n // Skip the user message echo\n if (role === 'user') return;\n\n if (role === 'assistant') {\n const content = parsed['content'] as string;\n const isDelta = parsed['delta'] as boolean | undefined;\n\n if (!content) return;\n\n if (isDelta) {\n // Streaming delta — Gemini sends multiple delta messages\n // Open a text block if not already open\n if (!inTextBlock) {\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'text',\n },\n });\n inTextBlock = true;\n }\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content,\n },\n });\n } else {\n // Non-delta full message (rare in stream-json mode, but handle it)\n // Close any open block first\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n onEvent({\n event: 'block_start',\n data: { block_index: blockIndex, block_type: 'text' },\n });\n onEvent({\n event: 'block_delta',\n data: { block_index: blockIndex, content },\n });\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n }\n }\n return;\n }\n\n // ── tool_use ─────────────────────────────────────────\n if (type === 'tool_use') {\n // Close any open text block before tool use\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n // Emit as a tool_call block\n onEvent({\n event: 'block_start',\n data: {\n block_index: blockIndex,\n block_type: 'tool_call',\n tool_name: parsed['tool_name'] as string,\n tool_call_id: parsed['tool_id'] as string,\n },\n });\n\n onEvent({\n event: 'block_delta',\n data: {\n block_index: blockIndex,\n content: JSON.stringify(parsed['parameters'] ?? {}),\n },\n });\n\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n return;\n }\n\n // ── tool_result ──────────────────────────────────────\n if (type === 'tool_result') {\n const toolId = parsed['tool_id'] as string;\n const output = (parsed['output'] as string) ?? '';\n const status = parsed['status'] as string;\n\n onEvent({\n event: 'tool_result',\n data: {\n tool_call_id: toolId,\n result: status === 'error'\n ? `Error: ${(parsed['error'] as Record<string, unknown>)?.['message'] ?? output}`\n : output,\n },\n });\n return;\n }\n\n // ── error (non-fatal or fatal) ──────────────────────\n if (type === 'error') {\n const severity = parsed['severity'] as string;\n const message = parsed['message'] as string;\n log.warn('Gemini error event', { severity, message: message?.substring(0, 200) });\n\n // severity='error' and severity='warning' are both forwarded to the\n // server as stream events.\n if (severity === 'error') {\n onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: message ?? 'Unknown Gemini error',\n },\n });\n // Fatal errors terminate the response — emit done and mark settled.\n onEvent({ event: 'done', data: {} });\n settled = true;\n }\n // severity 'warning' is non-fatal and informational — Gemini keeps\n // streaming. It must NOT be emitted as a stream 'error' event: the\n // server treats every error as terminal and would abort the request\n // (dropping subsequent content and tool calls). Log it locally only.\n return;\n }\n\n // ── result (final) ───────────────────────────────────\n if (type === 'result') {\n // Guard against duplicate done events — a fatal 'error' event may\n // already have settled this request before result arrives.\n if (settled) return;\n\n // Close any open text block\n if (inTextBlock) {\n onEvent({\n event: 'block_stop',\n data: { block_index: blockIndex },\n });\n blockIndex++;\n inTextBlock = false;\n }\n\n const status = parsed['status'] as string;\n\n if (status === 'error') {\n const error = parsed['error'] as Record<string, unknown> | undefined;\n const errorMessage = (error?.['message'] as string) ?? 'Gemini request failed';\n log.warn('Gemini result error', { type: error?.['type'], message: errorMessage.substring(0, 200) });\n\n onEvent({\n event: 'error',\n data: {\n code: 'provider_error',\n message: errorMessage,\n },\n });\n }\n\n // Extract usage stats\n const stats = parsed['stats'] as Record<string, unknown> | undefined;\n const inputTokens = stats ? (stats['input_tokens'] as number) ?? null : null;\n const outputTokens = stats ? (stats['output_tokens'] as number) ?? null : null;\n\n onEvent({\n event: 'done',\n data: {\n usage: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n },\n },\n });\n settled = true;\n return;\n }\n\n log.debug('Unhandled Gemini event type', { type });\n });\n\n rl.on('close', finalizer.onRlClose);\n\n // Capture stderr for error logging (capped at 10KB)\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBuffer = appendStderr(stderrBuffer, chunk.toString());\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n log.error('Failed to spawn gemini', { error: err.message });\n // Provide user-friendly message for ENOENT\n const errorMessage = err.code === 'ENOENT'\n ? 'gemini CLI not found. Install it or ensure it is on your PATH.'\n : `Failed to spawn gemini: ${err.message}`;\n signal.removeEventListener('abort', onAbort);\n\n if (!settled) {\n settled = true;\n onEvent({\n event: 'error',\n data: {\n code: 'provider_spawn_error',\n message: errorMessage,\n },\n });\n onEvent({ event: 'done', data: {} });\n resolve(null);\n }\n });\n\n child.on('close', (code) => {\n log.debug('Gemini process closed', { code, sessionId });\n finalizer.onChildClose(code);\n });\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n // Gemini CLI has no dynamic model listing — return known aliases\n return GEMINI_MODELS;\n }\n}\n","/**\n * Test Mode\n *\n * When the bridge is started with --test, incoming ai_request messages\n * are answered with mock streaming data instead of invoking a real CLI.\n *\n * This is useful for testing the WebSocket connection and the streaming\n * protocol without needing a real AI CLI installed.\n *\n * Mock response flow:\n * block_start (thinking, index 0)\n * block_delta (thinking content)\n * block_stop (index 0)\n * block_start (text, index 1)\n * block_delta (text content, multiple chunks)\n * block_stop (index 1)\n * done (with mock usage)\n */\n\nimport type { AiRequestMessage, StreamEventType, StreamEventData } from './protocol/types.js';\nimport { createLogger } from './utils/logger.js';\n\nconst log = createLogger('TestMode');\n\n/** Small delay helper for simulating streaming. */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Handle an AI request with mock streaming data.\n *\n * @param request The incoming AI request from the server.\n * @param sendEvent Function to send a stream event back to the server.\n */\nexport async function handleTestRequest(\n request: AiRequestMessage,\n sendEvent: (event: StreamEventType, data: StreamEventData) => void,\n): Promise<void> {\n log.info('Test mode: handling mock request', {\n requestId: request.request_id,\n provider: request.provider,\n message: request.message.slice(0, 80),\n });\n\n // Simulate a short delay before starting\n await delay(100);\n\n // --- Thinking block (index 0) ---\n sendEvent('block_start', {\n block_index: 0,\n block_type: 'thinking',\n });\n await delay(50);\n\n const thinkingChunks = [\n 'Let me think about this request... ',\n `The user asked: \"${request.message.slice(0, 50)}\". `,\n 'I will provide a helpful mock response.',\n ];\n\n for (const chunk of thinkingChunks) {\n sendEvent('block_delta', {\n block_index: 0,\n content: chunk,\n });\n await delay(30);\n }\n\n sendEvent('block_stop', {\n block_index: 0,\n });\n await delay(50);\n\n // --- Text block (index 1) ---\n sendEvent('block_start', {\n block_index: 1,\n block_type: 'text',\n });\n await delay(50);\n\n const textChunks = [\n 'This is a **mock response** from ai-bridge test mode. ',\n 'The bridge is connected and streaming is working correctly. ',\n `Your request was routed to the \"${request.provider}\" provider. `,\n 'In production, this response would come from your local CLI tool. ',\n `\\n\\nOriginal message: \"${request.message.slice(0, 100)}\"`,\n ];\n\n for (const chunk of textChunks) {\n sendEvent('block_delta', {\n block_index: 1,\n content: chunk,\n });\n await delay(40);\n }\n\n sendEvent('block_stop', {\n block_index: 1,\n });\n await delay(50);\n\n // --- Done ---\n sendEvent('done', {\n usage: {\n input_tokens: 42,\n output_tokens: 108,\n },\n });\n\n log.info('Test mode: mock response complete', { requestId: request.request_id });\n}\n"],"mappings":"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","names":["crypto","log","log","fs","path","os","log","path","os","fs","log","crypto","delay","log","log","spawn","createInterface","log","spawn","createInterface","spawn","createInterface","log","spawn","createInterface","log","log"]}
|
package/package.json
CHANGED