import crypto from "node:crypto"; import type { CliReusableSession, PreparedCliRunContext } from "./types.js"; function buildCliLogArgs(params: { args: string[]; systemPromptArg?: string; modelArg?: string; imageArg?: string; argsPrompt?: string; }): string[] { const logArgs: string[] = []; for (let i = 0; i < params.args.length; i += 1) { const arg = params.args[i] ?? ""; if (arg === params.systemPromptArg) { const systemPromptValue = params.args[i + 1] ?? ""; logArgs.push(arg, ``); i += 1; continue; } if (arg === params.modelArg) { logArgs.push(arg, params.args[i + 1] ?? ""); i += 1; continue; } if (arg === params.imageArg) { logArgs.push(arg, ""); i += 1; continue; } logArgs.push(arg); } if (params.argsPrompt) { const promptIndex = logArgs.indexOf(params.argsPrompt); if (promptIndex >= 0) { logArgs[promptIndex] = ``; } } return logArgs; } const CLI_ENV_AUTH_LOG_KEYS = [ "AI_GATEWAY_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD", "ANTHROPIC_API_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "ANTHROPIC_CUSTOM_HEADERS", "ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_UNIX_SOCKET", "AZURE_OPENAI_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST", "OPENAI_API_KEY", "OPENAI_STEIPETE_API_KEY", "OPENROUTER_API_KEY", ] as const; const CLI_ENV_RUNTIME_LOG_KEYS = ["GEMINI_CLI_HOME", "GEMINI_CLI_SYSTEM_SETTINGS_PATH"] as const; export const CLAUDE_SELECTED_AUTH_ENV_KEYS = new Set([ "ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", ]); export const NODE_CLAUDE_FORWARD_ENV_KEYS = new Set([ "CLAUDE_CODE_AUTO_COMPACT_WINDOW", "CLAUDE_CODE_DISABLE_1M_CONTEXT", ]); export function resolveNodeClaudeAuthEnv(context: PreparedCliRunContext): Record { const secretInput = context.preparedBackend.secretInput; if (!secretInput) { return {}; } const descriptorEnv = context.preparedBackend.env ?? {}; const requestEnv = Object.hasOwn(descriptorEnv, "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR") ? "CLAUDE_CODE_OAUTH_TOKEN" : Object.hasOwn(descriptorEnv, "CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR") ? "ANTHROPIC_API_KEY" : undefined; if (!requestEnv) { return {}; } const data = secretInput.createData(); try { return { [requestEnv]: data.toString("utf8") }; } finally { data.fill(0); } } export const CLI_BACKEND_PRESERVE_ENV = "OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV"; export function parseCliBackendPreserveEnv(raw: string | undefined): Set { const trimmed = raw?.trim(); if (!trimmed) { return new Set(); } if (trimmed.startsWith("[")) { try { const parsed = JSON.parse(trimmed) as unknown; return new Set( Array.isArray(parsed) ? parsed.filter((entry): entry is string => typeof entry === "string") : [], ); } catch { return new Set(); } } return new Set( trimmed .split(/[,\s]+/) .map((entry) => entry.trim()) .filter((entry) => entry.length > 0), ); } function listPresentCliEnvKeys( env: Record, keys: readonly string[], ): string[] { return keys.filter((key) => { const value = env[key]; return typeof value === "string" && value.length > 0; }); } function formatCliEnvKeyList(keys: readonly string[]): string { return keys.length > 0 ? keys.join(",") : "none"; } function buildCliEnvMcpLog(childEnv: Record): string { return [ `token=${childEnv.OPENCLAW_MCP_TOKEN ? "set" : "missing"}`, `capture=${childEnv.OPENCLAW_MCP_CLI_CAPTURE_KEY ? "set" : "missing"}`, ].join(" "); } function fingerprintCliSessionId(sessionId?: string): string { const trimmed = sessionId?.trim(); if (!trimmed) { return "none"; } return crypto.createHash("sha256").update(trimmed).digest("hex").slice(0, 12); } function formatCliSessionReuseLogState(reusableSession: CliReusableSession): string { switch (reusableSession.mode) { case "reuse": return "reusable"; case "reuse-with-drift": return `reusable-drift:${reusableSession.drift.reasons.join(",")}`; case "invalidate": return `invalidated:${reusableSession.invalidatedReason}`; case "none": return "none"; } const exhaustive: never = reusableSession; return exhaustive; } /** Builds the compact execution summary logged before a CLI backend run. */ export function buildCliExecLogLine(params: { provider: string; model: string; promptChars: number; trigger?: string; useResume: boolean; cliSessionId?: string; resolvedSessionId?: string; reusableSession: CliReusableSession; hasHistoryPrompt: boolean; }): string { return [ `cli exec: provider=${params.provider}`, `model=${params.model}`, `promptChars=${params.promptChars}`, `trigger=${params.trigger ?? "unknown"}`, `useResume=${params.useResume ? "true" : "false"}`, `session=${params.cliSessionId ? "present" : "none"}`, `resumeSession=${params.useResume ? fingerprintCliSessionId(params.resolvedSessionId) : "none"}`, `reuse=${formatCliSessionReuseLogState(params.reusableSession)}`, `historyPrompt=${params.hasHistoryPrompt ? "present" : "none"}`, ].join(" "); } /** Summarizes auth-related env keys preserved or cleared for a CLI child process. */ function buildCliEnvAuthLog(childEnv: Record): string { const hostKeys = listPresentCliEnvKeys(process.env, CLI_ENV_AUTH_LOG_KEYS); const childKeys = listPresentCliEnvKeys(childEnv, CLI_ENV_AUTH_LOG_KEYS); const childKeySet = new Set(childKeys); const clearedKeys = hostKeys.filter((key) => !childKeySet.has(key)); const runtimeHostKeys = listPresentCliEnvKeys(process.env, CLI_ENV_RUNTIME_LOG_KEYS); const runtimeChildKeys = listPresentCliEnvKeys(childEnv, CLI_ENV_RUNTIME_LOG_KEYS); const runtimeChildKeySet = new Set(runtimeChildKeys); const runtimeClearedKeys = runtimeHostKeys.filter((key) => !runtimeChildKeySet.has(key)); return [ `host=${formatCliEnvKeyList(hostKeys)}`, `child=${formatCliEnvKeyList(childKeys)}`, `cleared=${formatCliEnvKeyList(clearedKeys)}`, `runtimeHost=${formatCliEnvKeyList(runtimeHostKeys)}`, `runtimeChild=${formatCliEnvKeyList(runtimeChildKeys)}`, `runtimeCleared=${formatCliEnvKeyList(runtimeClearedKeys)}`, ].join(" "); } export function logCliInvocation(params: { args: string[]; command: string; env: Record; systemPromptArg?: string; modelArg?: string; imageArg?: string; argsPrompt?: string; log: (message: string) => void; }): void { const logArgs = buildCliLogArgs(params); params.log(`cli argv: ${params.command} ${logArgs.join(" ")}`); params.log(`cli env auth: ${buildCliEnvAuthLog(params.env)}`); if (params.env.OPENCLAW_MCP_TOKEN) { params.log(`cli env mcp: ${buildCliEnvMcpLog(params.env)}`); } }