UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

1,269 lines (1,262 loc) 55.1 kB
'use strict'; var ink = require('ink'); var React = require('react'); var node_crypto = require('node:crypto'); var path = require('node:path'); var os = require('node:os'); var fs = require('node:fs/promises'); var axios = require('axios'); var persistence = require('./types-B_i6lpTn.cjs'); var createSessionMetadata = require('./createSessionMetadata-CVgp25Mn.cjs'); var index = require('./index-BMIckAk5.cjs'); var setupOfflineReconnection = require('./setupOfflineReconnection-CY4q78_S.cjs'); var optionsParser = require('./optionsParser-eyp1ynVN.cjs'); var capabilities = require('./capabilities-BsNjrlBG.cjs'); var cliRouting = require('./cliRouting-BIEQ5EVs.cjs'); var killSwitch = require('./killSwitch-DwcqKgA9.cjs'); var BasePermissionHandler = require('./BasePermissionHandler-DM5JDRsB.cjs'); require('chalk'); require('fs'); require('node:fs'); require('node:events'); require('socket.io-client'); require('zod'); require('tweetnacl'); require('child_process'); require('util'); require('fs/promises'); require('crypto'); require('path'); require('url'); require('os'); require('node:child_process'); require('node:module'); require('node:util'); require('expo-server-sdk'); require('node:readline'); require('node:url'); require('ps-list'); require('cross-spawn'); require('tmp'); require('qrcode-terminal'); require('open'); require('fastify'); require('fastify-type-provider-zod'); require('http'); require('@modelcontextprotocol/sdk/client/index.js'); require('@modelcontextprotocol/sdk/client/streamableHttp.js'); require('readline'); require('@modelcontextprotocol/sdk/server/mcp.js'); require('node:http'); require('@modelcontextprotocol/sdk/server/streamableHttp.js'); require('@agentclientprotocol/sdk'); require('libsodium-wrappers'); const CONSORTIUM_CODE_TIMEOUTS = { /** OpenCode boots fast, but first run performs a SQLite migration. 60s is safe. */ init: 6e4, /** Standard tool call timeout */ toolCall: 12e4, /** Idle detection after last message chunk */ idle: 500 }; const STDERR_NOISE_PATTERNS = [ "Performing one time database migration", "sqlite-migration:done", "Database migration complete", "Shell cwd was reset" ]; const UNSUPPORTED_SESSION_UPDATE_VARIANTS = /* @__PURE__ */ new Set([ "usage_update", "available_commands_update" ]); class ConsortiumCodeTransport { agentName = "consortium-code"; getInitTimeout() { return CONSORTIUM_CODE_TIMEOUTS.init; } getIdleTimeout() { return CONSORTIUM_CODE_TIMEOUTS.idle; } getToolCallTimeout() { return CONSORTIUM_CODE_TIMEOUTS.toolCall; } /** * Defensive stdout filter. With `--log-level ERROR --pure` OpenCode's stdout * is clean ACP JSON-RPC, but we still drop any non-JSON lines as a safety net * (matches the pattern used by GeminiTransport). * * We also drop OpenCode-specific notification variants that the * @agentclientprotocol/sdk validator doesn't know about — currently this is * `session/update` notifications with `sessionUpdate: 'usage_update'` (token * cost / context size info). Letting these reach the SDK causes a noisy * `Error handling notification` log without affecting functionality. We drop * them at the transport layer so the SDK never sees them. */ filterStdoutLine(line) { const trimmed = line.trim(); if (!trimmed) { return null; } if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) { return null; } let parsed; try { parsed = JSON.parse(trimmed); } catch { return null; } if (typeof parsed !== "object" || parsed === null) { return null; } const msg = parsed; if (msg.method === "session/update") { const variant = msg.params?.update?.sessionUpdate; if (variant && UNSUPPORTED_SESSION_UPDATE_VARIANTS.has(variant)) { return null; } } return line; } /** * Handle OpenCode stderr output. * * - Migration / shutdown lifecycle messages: suppress entirely * - Upstream provider rate limits (429): suppress (OpenCode handles retries) * - 401 / auth errors: surface as a user-visible error * - Everything else: pass through to logs without emitting a UI message */ handleStderr(text, _context) { const trimmed = text.trim(); if (!trimmed) { return { message: null, suppress: true }; } for (const pattern of STDERR_NOISE_PATTERNS) { if (trimmed.includes(pattern)) { return { message: null, suppress: true }; } } if (trimmed.includes("status 429") || trimmed.includes('"code":429') || trimmed.includes("rate limit") || trimmed.includes("Rate limit")) { return { message: null, suppress: false }; } if (trimmed.includes("status 401") || trimmed.includes('"code":401') || trimmed.includes("Unauthorized") || trimmed.includes("authentication required")) { const errorMessage = { type: "status", status: "error", detail: "Consortium Code authentication failed. Run `opencode auth login` to configure a provider, or set OPENAI_BASE_URL to point at the Consortium proxy." }; return { message: errorMessage }; } return { message: null }; } /** * Tool patterns. Empty for now — OpenCode's tool IDs appear to follow the * `tool_name-<id>` convention, so the default extractor in AcpBackend * handles the common case. Add specific patterns here as we observe tools * that need disambiguation. */ getToolPatterns() { return []; } } const consortiumCodeTransport = new ConsortiumCodeTransport(); function redactToken(token) { if (!token) return "(none)"; if (token.length <= 8) return "***"; return token.slice(0, 4) + "..." + token.slice(-4); } function validateNoTokensInArgs(args) { const sensitivePatterns = [ /^(sk-|pk-|key-|token-)/i, /^eyJ/ // JWT prefix ]; for (const arg of args) { for (const pattern of sensitivePatterns) { if (pattern.test(arg)) { throw new Error( "Security: sensitive token detected in command arguments. Tokens must be passed via environment variables, not CLI args." ); } } } } const SENSITIVE_KEYS = [ "CONSORTIUM_TOKEN", "CONSORTIUM_PROXY_TOKEN", "CONSORTIUM_ENCRYPTION_KEY", "CONSORTIUM_SESSION_ID", "CONSORTIUM_ENCRYPTION_VARIANT", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "GEMINI_API_KEY", "AZURE_OPENAI_API_KEY" ]; function safeEnvForLogging(env) { const safe = {}; for (const [key, value] of Object.entries(env)) { safe[key] = SENSITIVE_KEYS.includes(key) ? redactToken(value) : value; } return safe; } function isDirectZenModel(model) { return model.startsWith("opencode/"); } function createConsortiumCodeBackend(options) { const pure = options.pure ?? true; const logLevel = options.logLevel ?? "ERROR"; const useProxy = !!(options.proxyUrl && options.proxyTokens?.consortium); const consortiumMode = options.consortiumMode ?? useProxy; let command; try { command = cliRouting.resolveConsortiumCodeBinarySync(); } catch { persistence.logger.debug("[ConsortiumCode] Failed to resolve bundled binary, falling back to PATH lookup"); command = consortiumMode ? "consortium-code" : "opencode"; } const args = [ "acp", "--log-level", logLevel, "--port", "0", "--cwd", options.cwd ]; if (pure) args.push("--pure"); if (consortiumMode) args.push("--consortium-mode"); validateNoTokensInArgs(args); const env = { ...options.env }; const selectedModel = options.model ?? "opencode/big-pickle"; env.OPENCODE_MODEL = selectedModel; const directZen = isDirectZenModel(selectedModel); if (useProxy) { const configuredModel = selectedModel.includes("/") ? selectedModel : `opencode/${selectedModel}`; const tokens = options.proxyTokens; const provider = {}; if (!directZen) { provider.opencode = { options: { baseURL: `${options.proxyUrl}/v1/proxy/consortium`, apiKey: tokens.consortium } }; } if (tokens.anthropic) provider.anthropic = { options: { baseURL: `${options.proxyUrl}/v1/proxy/anthropic`, apiKey: tokens.anthropic } }; if (tokens.openai) provider.openai = { options: { baseURL: `${options.proxyUrl}/v1/proxy/openai`, apiKey: tokens.openai } }; if (tokens.google) provider.google = { options: { baseURL: `${options.proxyUrl}/v1/proxy/google`, apiKey: tokens.google } }; if (tokens.zai) provider.zai = { options: { baseURL: `${options.proxyUrl}/v1/proxy/zai`, apiKey: tokens.zai } }; env.OPENCODE_CONFIG_CONTENT = JSON.stringify({ model: configuredModel, provider }); } if (useProxy && !directZen) { env.CONSORTIUM_PROXY_URL = `${options.proxyUrl}/v1/proxy/consortium`; env.CONSORTIUM_PROXY_TOKEN = options.proxyTokens.consortium; } if (options.baseUrl) { env.OPENAI_BASE_URL = options.baseUrl; env.ANTHROPIC_BASE_URL = options.baseUrl; } if (options.apiKey) { env.OPENAI_API_KEY = options.apiKey; env.ANTHROPIC_API_KEY = options.apiKey; } const billingMode = directZen ? "direct-zen" : useProxy ? "proxy" : options.apiKey ? "byok" : "zen"; const backendOptions = { agentName: "consortium-code", cwd: options.cwd, command, args, env, mcpServers: options.mcpServers, permissionHandler: options.permissionHandler, transportHandler: consortiumCodeTransport }; persistence.logger.debug("[ConsortiumCode] Creating ACP backend:", { command, billingMode, cwd: backendOptions.cwd, args: backendOptions.args, model: options.model ?? "(default/zen)", pure, logLevel, consortiumMode, mcpServerCount: options.mcpServers ? Object.keys(options.mcpServers).length : 0, env: safeEnvForLogging(env) }); return new capabilities.AcpBackend(backendOptions); } function isWriteLikeTool(toolName) { const lower = toolName.toLowerCase(); if (lower === "other" || lower === "unknown tool" || lower === "unknown") return true; const patterns = [ "edit", "write", "patch", "delete", "remove", "create", "mkdir", "rename", "move", "copy", "exec", "bash", "shell", "run", "terminal" ]; return patterns.some((p) => lower === p || lower.includes(p)); } const ALWAYS_AUTO_APPROVE = ["change_title", "think", "save_memory"]; class ConsortiumCodePermissionHandler extends BasePermissionHandler.BasePermissionHandler { currentPermissionMode = "default"; constructor(session) { super(session); } getLogPrefix() { return "[ConsortiumCode]"; } updateSession(newSession) { super.updateSession(newSession); } setPermissionMode(mode) { this.currentPermissionMode = mode; persistence.logger.debug(`${this.getLogPrefix()} Permission mode set to: ${mode}`); } shouldAutoApprove(toolName, toolCallId) { if (ALWAYS_AUTO_APPROVE.some((n) => toolName.toLowerCase().includes(n))) return true; if (ALWAYS_AUTO_APPROVE.some((n) => toolCallId.toLowerCase().includes(n))) return true; switch (this.currentPermissionMode) { case "yolo": return true; case "safe-yolo": return !isWriteLikeTool(toolName); case "read-only": return !isWriteLikeTool(toolName); default: return false; } } async handleToolCall(toolCallId, toolName, input) { if (this.shouldAutoApprove(toolName, toolCallId)) { const decision = this.currentPermissionMode === "yolo" ? "approved_for_session" : "approved"; persistence.logger.debug(`${this.getLogPrefix()} Auto-approving tool ${toolName} (${toolCallId}) in ${this.currentPermissionMode} mode`); this.session.updateAgentState((currentState) => ({ ...currentState, completedRequests: { ...currentState.completedRequests, [toolCallId]: { tool: toolName, arguments: input, createdAt: Date.now(), completedAt: Date.now(), status: "approved", decision } } })); return { decision }; } const defaultOnTimeout = isWriteLikeTool(toolName) ? "denied" : "approved"; return this.createPendingRequest(toolCallId, toolName, input, { defaultOnTimeout }); } } async function runConsortiumCode(opts) { persistence.connectionState.setBackend("ConsortiumCode"); persistence.logger.debug(`[ConsortiumCode] Starting: startedBy=${opts.startedBy || "terminal"}`); let session; let permissionHandler; let isProcessingMessage = false; let pendingSessionSwap = null; const applyPendingSessionSwap = () => { if (pendingSessionSwap) { persistence.logger.debug("[ConsortiumCode] Applying pending session swap"); session = pendingSessionSwap; permissionHandler.updateSession(pendingSessionSwap); pendingSessionSwap = null; } }; const api = await persistence.ApiClient.create(opts.credentials); if (opts.existingSession) { session = opts.existingSession; } else { const sessionTag = opts.resumeTag ?? node_crypto.randomUUID(); const settings = await persistence.readSettings(); const machineId = settings?.machineId; if (!machineId) { console.error("[START] No machine ID found. Run consortium auth to set up."); process.exit(1); } await api.getOrCreateMachine({ machineId, metadata: index.initialMachineMetadata }); const { state, metadata } = createSessionMetadata.createSessionMetadata({ flavor: "consortium-code", machineId, startedBy: opts.startedBy }); const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state }); const { session: initialSession } = setupOfflineReconnection.setupOfflineReconnection({ api, sessionTag, metadata, state, response, onSessionSwap: (newSession) => { if (isProcessingMessage) { persistence.logger.debug("[ConsortiumCode] Session swap queued (processing in progress)"); pendingSessionSwap = newSession; } else { session = newSession; permissionHandler.updateSession(newSession); } } }); session = initialSession; if (response) { try { const result = await index.notifyDaemonSessionStarted(response.id, metadata); if (result.error) { persistence.logger.debug("[ConsortiumCode] Failed to report to daemon:", result.error); } } catch (error) { persistence.logger.debug("[ConsortiumCode] Failed to report to daemon:", error); } } } const consortiumServer = await index.startConsortiumServer(session, { sendArtifact: (msg) => session.sendAgentMessage("consortium-code", msg) }); permissionHandler = new ConsortiumCodePermissionHandler(session); let driveClient = null; if (opts.credentials.encryption?.type === "dataKey") { driveClient = new killSwitch.DriveClient( opts.credentials.token, { x25519SecretKey: opts.credentials.encryption.machineKey } ); } const scratchDir = new killSwitch.ScratchDir(session.sessionId, { workspaceDir: process.cwd() }); const attachmentResolver = driveClient ? new killSwitch.AttachmentResolver(driveClient, scratchDir) : null; const pendingMessageLocalIds = []; const pendingDriveNodeRefs = []; const pendingAttachmentIds = []; const messageQueue = opts.messageQueue ?? new index.MessageQueue2((mode) => index.hashObject({ permissionMode: mode.permissionMode, model: mode.model })); let currentPermissionMode = void 0; let currentModel = void 0; let currentBackendModel = void 0; let hasStoredFirstMessage = false; let isFirstMessage = true; session.onUserMessage((message) => { const text = message.content.text; const env = killSwitch.normalizeAttachmentEnvelope(message); if (driveClient && env.driveId && env.driveDek) { try { driveClient.setKnownDek(env.driveId, new Uint8Array(Buffer.from(env.driveDek, "base64"))); } catch (err) { persistence.logger.debug("[ConsortiumCode] setKnownDek failed:", err); } } if (env.messageLocalId) pendingMessageLocalIds.push(env.messageLocalId); if (env.driveId && env.driveNodeIds.length > 0) { pendingDriveNodeRefs.push({ driveId: env.driveId, nodeIds: env.driveNodeIds }); } if (env.bindingOnlyNodeIds.length > 0) { pendingAttachmentIds.push(...env.bindingOnlyNodeIds); } if (!hasStoredFirstMessage && text) { hasStoredFirstMessage = true; session.updateMetadata((m) => ({ ...m, firstMessage: text.substring(0, 100) })); } if (message.meta?.permissionMode) { const validModes = ["default", "read-only", "safe-yolo", "yolo"]; if (validModes.includes(message.meta.permissionMode)) { currentPermissionMode = message.meta.permissionMode; permissionHandler.setPermissionMode(currentPermissionMode); } } if (currentPermissionMode === void 0) { currentPermissionMode = "default"; permissionHandler.setPermissionMode("default"); } if (message.meta?.hasOwnProperty("model")) { if (message.meta.model === null) { currentModel = void 0; } else if (message.meta.model) { currentModel = message.meta.model; } } let fullPrompt = text; if (isFirstMessage && message.meta?.appendSystemPrompt) { fullPrompt = message.meta.appendSystemPrompt + "\n\n" + text; } isFirstMessage = false; const mode = { permissionMode: currentPermissionMode || "default", model: currentModel }; const special = index.parseSpecialCommand(text); if (special.type === "clear") { messageQueue.pushIsolateAndClear(fullPrompt, mode); } else { messageQueue.push(fullPrompt, mode); } }); const messageBuffer = opts.messageBuffer ?? new index.MessageBuffer(); const hasTTY = process.stdout.isTTY && process.stdin.isTTY; let inkInstance = null; if (hasTTY) { console.clear(); inkInstance = ink.render(React.createElement(index.RemoteModeDisplay, { messageBuffer, logPath: process.env.DEBUG ? persistence.logger.logFilePath : void 0, onExit: async () => { shouldExit = true; await handleAbort(); }, onSwitchToLocal: () => { persistence.logger.debug("[ConsortiumCode] Switching to local mode via double-space"); switchToLocal = true; shouldExit = true; handleAbort(); } }), { exitOnCtrlC: false, patchConsole: false }); } const agent = "consortium-code"; let thinking = false; let shouldExit = false; let switchToLocal = false; let abortController = new AbortController(); let acpSessionId = null; let backend = null; let currentModeHash = null; let accumulatedText = ""; let turnStartMs = 0; let emptyIndicatorTimer = null; const EMPTY_INDICATOR_DELAY_MS = 2500; const cancelEmptyIndicator = () => { if (emptyIndicatorTimer) { clearTimeout(emptyIndicatorTimer); emptyIndicatorTimer = null; } }; let sendPromptInFlight = false; const OPTIONS_MARKER = "<options"; const HOLDBACK_LEN = OPTIONS_MARKER.length - 1; let streamBuffer = ""; let streamFlushTimer = null; const STREAM_FLUSH_MS = 600; const STREAM_FLUSH_CHARS = 1500; let turnFinalized = true; let watchdog = null; const WATCHDOG_MS = 24e4; const clearWatchdog = () => { if (watchdog) { clearTimeout(watchdog); watchdog = null; } }; const resetWatchdog = () => { clearWatchdog(); if (!turnFinalized && sendPromptInFlight) { watchdog = setTimeout(() => { persistence.logger.debug("[ConsortiumCode] turn stalled (no backend activity for 90s); forcing finalize"); finalizeTurn("watchdog"); }, WATCHDOG_MS); } }; const startTurn = () => { turnFinalized = false; accumulatedText = ""; streamBuffer = ""; turnStartMs = Date.now(); if (streamFlushTimer) { clearTimeout(streamFlushTimer); streamFlushTimer = null; } cancelEmptyIndicator(); sendPromptInFlight = true; resetWatchdog(); }; const readLatestOpencodeError = async () => { try { const logDir = path.join(os.homedir(), ".local/share/opencode/log"); const entries = await fs.readdir(logDir).catch(() => []); let bestPath = null; let bestMtime = 0; for (const name of entries) { if (!name.endsWith(".log")) continue; const path$1 = path.join(logDir, name); const s = await fs.stat(path$1).catch(() => null); if (!s) continue; if (s.mtimeMs < turnStartMs - 2e3) continue; if (s.mtimeMs > bestMtime) { bestMtime = s.mtimeMs; bestPath = path$1; } } if (!bestPath) return null; const body = await fs.readFile(bestPath, "utf8").catch(() => null); if (!body) return null; const lines = body.split("\n"); for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i]; if (!line.includes("ERROR") || !line.includes("service=llm")) continue; const msgMatch = line.match(/"message":"((?:[^"\\]|\\.)*)"/); const statusMatch = line.match(/"statusCode":(\d+)/); const message = msgMatch?.[1]?.replace(/\\"/g, '"').replace(/\\\\/g, "\\") ?? null; const status = statusMatch?.[1] ?? null; if (message) { return status ? `HTTP ${status}: ${message}` : message; } } return null; } catch { return null; } }; const finalizeTurn = (reason) => { if (turnFinalized) return; turnFinalized = true; clearWatchdog(); flushStreamBuffer(); try { const tail = streamBuffer; streamBuffer = ""; let finalMessage = tail; const markerIdx = tail.indexOf(OPTIONS_MARKER); if (markerIdx !== -1) { const pre = tail.slice(0, markerIdx); const rest = tail.slice(markerIdx); const { text: optionsText, options } = optionsParser.parseOptionsFromText(rest); if (options.length > 0) { finalMessage = pre + optionsText + optionsParser.formatOptionsXml(options); persistence.logger.debug(`[ConsortiumCode] Found ${options.length} options in response:`, options); } else if (optionsParser.hasIncompleteOptions(rest)) { persistence.logger.debug("[ConsortiumCode] Warning: Incomplete options block detected"); finalMessage = pre + rest; } } if (finalMessage.trim()) { session.sendAgentMessage(agent, { type: "message", message: finalMessage, id: node_crypto.randomUUID() }); } else if (!accumulatedText.trim() && reason !== "error" && reason !== "watchdog") { cancelEmptyIndicator(); emptyIndicatorTimer = setTimeout(() => { emptyIndicatorTimer = null; if (accumulatedText.trim().length > 0 || streamBuffer.length > 0) return; session.sendAgentMessage(agent, { type: "message", message: "_(model returned no output \u2014 checking Consortium logs for details\u2026)_", id: node_crypto.randomUUID() }); readLatestOpencodeError().then((detail) => { if (!detail) return; session.sendAgentMessage(agent, { type: "message", message: `_(model error \u2014 ${detail})_`, id: node_crypto.randomUUID() }); }).catch(() => { }); }, EMPTY_INDICATOR_DELAY_MS); } } catch (e) { persistence.logger.debug("[ConsortiumCode] turn finalize flush failed:", e); } if (streamFlushTimer) { clearTimeout(streamFlushTimer); streamFlushTimer = null; } session.sendAgentMessage(agent, { type: "task_complete", id: node_crypto.randomUUID() }); thinking = false; session.keepAlive(thinking, "remote"); persistence.logger.debug(`[ConsortiumCode] turn finalized via ${reason}`); }; const flushStreamBuffer = () => { if (streamFlushTimer) { clearTimeout(streamFlushTimer); streamFlushTimer = null; } if (!streamBuffer) return; let cutAt = streamBuffer.length; const optIdx = streamBuffer.indexOf(OPTIONS_MARKER); if (optIdx !== -1) cutAt = optIdx; if (cutAt === streamBuffer.length && cutAt > HOLDBACK_LEN) { cutAt -= HOLDBACK_LEN; } const lookbackStart = Math.max(0, cutAt - 400); const lastBreak = streamBuffer.lastIndexOf("\n\n", cutAt); if (lastBreak >= lookbackStart) cutAt = lastBreak + 2; if (cutAt <= 0) return; const chunk = streamBuffer.slice(0, cutAt); streamBuffer = streamBuffer.slice(cutAt); if (!chunk) return; session.sendAgentMessage(agent, { type: "message", message: chunk, id: node_crypto.randomUUID() }); }; const streamModelChunk = (chunk) => { if (!chunk) return; cancelEmptyIndicator(); messageBuffer.addMessage(chunk, "assistant"); resetWatchdog(); if (sendPromptInFlight) accumulatedText += chunk; streamBuffer += chunk; if (streamBuffer.length >= STREAM_FLUSH_CHARS) { flushStreamBuffer(); } else if (!streamFlushTimer) { streamFlushTimer = setTimeout(flushStreamBuffer, STREAM_FLUSH_MS); } }; session.keepAlive(thinking, "remote"); const keepAliveInterval = setInterval(() => { if (!shouldExit) session.keepAlive(thinking, "remote"); }, 2e3); session.updateAgentState((state) => ({ ...state, controlledByUser: false })); index.startCaffeinate(); const sendReady = () => { session.sendSessionEvent({ type: "ready" }); try { api.push().sendToAllDevices( "It's ready!", "Consortium Code is waiting for your command", { sessionId: session.sessionId } ); } catch (pushError) { persistence.logger.debug("[ConsortiumCode] Failed to send ready push", pushError); } }; const spawnModelId = process.env.OPENCODE_MODEL || "opencode/big-pickle"; const byokProviderEnv = process.env.CONSORTIUM_CODE_BYOK_PROVIDER; const byokFlag = process.env.CONSORTIUM_CODE_BYOK === "1"; function inferProviderFromModel(id) { if (id.startsWith("custom:")) { const rest = id.slice("custom:".length); const ci = rest.indexOf(":"); return ci > 0 ? `custom:${rest.slice(0, ci)}` : `custom:${rest}`; } if (id.includes(":")) return id.slice(0, id.indexOf(":")); if (id.includes("/")) return id.split("/")[0]; return void 0; } const inferredScope = inferProviderFromModel(spawnModelId); const byokProviderId = byokProviderEnv ?? (byokFlag && inferredScope && inferredScope !== "opencode" && inferredScope !== "consortium" ? inferredScope : void 0); let byokActive = false; if (byokProviderId && byokProviderId !== "consortium") { const providerEntry = index.getProvider("consortium-code", byokProviderId); const opencodeEntry = index.findOpencodeProvider(byokProviderId); const envVarFromEnv = process.env.CONSORTIUM_CODE_BYOK_ENV_VAR; const envVar = providerEntry?.keyEnvVar ?? opencodeEntry?.env?.[0] ?? envVarFromEnv; if (envVar) { const existing = process.env[envVar]; let resolvedKey = existing && existing.length > 0 ? existing : null; if (!resolvedKey) { try { resolvedKey = await index.getProviderKeyViaDaemon(byokProviderId); } catch (err) { persistence.logger.debug(`[ConsortiumCode] BYOK getProviderKeyViaDaemon(${byokProviderId}) threw:`, err); } } if (resolvedKey) { process.env[envVar] = resolvedKey; delete process.env.OPENAI_BASE_URL; delete process.env.ANTHROPIC_BASE_URL; byokActive = true; const modelTail = (() => { if (spawnModelId.startsWith("custom:")) { const rest = spawnModelId.slice("custom:".length); const ci = rest.indexOf(":"); return ci > 0 ? rest.slice(ci + 1) : ""; } if (spawnModelId.includes(":")) return spawnModelId.slice(spawnModelId.indexOf(":") + 1); if (spawnModelId.includes("/")) return spawnModelId.split("/").slice(1).join("/"); return spawnModelId; })(); const opencodeModelId = `${byokProviderId}/${modelTail}`; const customBaseURL = process.env.CONSORTIUM_CODE_BYOK_BASE_URL; const config = { model: opencodeModelId }; if (byokProviderId.startsWith("custom:")) { if (customBaseURL && modelTail) { config.provider = { [byokProviderId]: { npm: "@ai-sdk/openai-compatible", options: { baseURL: customBaseURL, apiKey: resolvedKey }, models: { [modelTail]: {} } } }; } } else if (modelTail) { config.provider = { [byokProviderId]: { options: { apiKey: resolvedKey }, models: { [modelTail]: {} } } }; } process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config); persistence.logger.debug(`[ConsortiumCode] BYOK active for provider=${byokProviderId} envVar=${envVar} model=${opencodeModelId}; bypassing Consortium proxy mint.`); } else { persistence.logger.warn(`[ConsortiumCode] BYOK requested for provider=${byokProviderId} but no key found in env or daemon keystore; falling back to proxy path.`); } } else { persistence.logger.warn(`[ConsortiumCode] BYOK requested for unknown provider=${byokProviderId}; falling back to proxy path.`); } } const isFreeSlug = spawnModelId.startsWith("opencode/") || spawnModelId.startsWith("consortium/"); const PROVIDER_SCOPES = ["consortium", "anthropic", "openai", "google", "zai"]; const scopesToMint = isFreeSlug ? ["consortium"] : PROVIDER_SCOPES; let consortiumProxyUrl = process.env.CONSORTIUM_PROXY_URL; let proxyTokens; const extractAxiosError = (reason) => { const data = reason?.response?.data; const status = reason?.response?.status; const fromData = data?.error || data?.message; const body = typeof fromData === "string" ? fromData : fromData ? JSON.stringify(fromData) : void 0; const base = body || reason?.message || String(reason); return status ? `${status} ${base}` : base; }; const extractStructuredError = (reason) => { const data = reason?.response?.data; const code = typeof data?.error === "string" && data.error || typeof data?.code === "string" && data.code || reason?.code && String(reason.code) || "mint_failed"; const message = typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || reason?.message || String(reason); return { code, message }; }; const mintResults = byokActive ? [] : await Promise.allSettled(scopesToMint.map((providerId) => axios.post( `${persistence.configuration.serverUrl}/v1/billing/proxy-token`, { providerId, sessionId: session.sessionId, usageMode: "consortium-code" }, { headers: { Authorization: `Bearer ${opts.credentials.token}` }, timeout: 1e4 } ))); const failed = []; const scopeErrors = {}; const tokens = {}; let firstProxyBaseUrl; let anyNetworkSucceeded = false; mintResults.forEach((result, idx) => { const scope = scopesToMint[idx]; if (result.status === "fulfilled") { anyNetworkSucceeded = true; const data = result.value.data; if (data?.token && data?.proxyBaseUrl) { tokens[scope] = data.token; if (!firstProxyBaseUrl) { firstProxyBaseUrl = String(data.proxyBaseUrl).replace(/\/v1\/proxy\/[^/]+$/, ""); } } else { failed.push(`${scope}: empty response`); scopeErrors[scope] = { code: "empty_response", message: "Server returned no token" }; } } else { failed.push(`${scope}: ${extractAxiosError(result.reason)}`); scopeErrors[scope] = extractStructuredError(result.reason); } }); if (byokActive) { persistence.logger.debug("[ConsortiumCode] BYOK mode \u2014 skipping proxy mint + wallet preview."); } else if (failed.length === 0 && tokens.consortium) { proxyTokens = tokens; if (firstProxyBaseUrl) consortiumProxyUrl = firstProxyBaseUrl; persistence.logger.debug(`[ConsortiumCode] Acquired proxy tokens (${scopesToMint.length} provider${scopesToMint.length === 1 ? "" : "s"}) for session ${session.sessionId}`); const desiredModel = process.env.OPENCODE_MODEL; if (desiredModel) { const [scope, ...rest] = desiredModel.split("/"); const bareModelId = rest.length > 0 ? rest.join("/") : scope; const overrideProvider = rest.length === 0 || scope === "opencode" ? "consortium" : scope; try { await axios.post( `${persistence.configuration.serverUrl}/v1/proxy/model`, { providerId: overrideProvider, modelId: bareModelId }, { headers: { Authorization: `Bearer ${opts.credentials.token}` }, timeout: 5e3 } ); persistence.logger.debug(`[ConsortiumCode] Set proxy model override ${overrideProvider}/${bareModelId} for session ${session.sessionId}`); } catch (err) { persistence.logger.warn(`[ConsortiumCode] setModelOverride failed (binary may silently fall back to big-pickle): ${err?.message ?? err}`); } } } else if (!anyNetworkSucceeded) { const envTokens = { consortium: process.env.CONSORTIUM_PROXY_TOKEN_CONSORTIUM ?? process.env.CONSORTIUM_PROXY_TOKEN, anthropic: process.env.CONSORTIUM_PROXY_TOKEN_ANTHROPIC, openai: process.env.CONSORTIUM_PROXY_TOKEN_OPENAI, google: process.env.CONSORTIUM_PROXY_TOKEN_GOOGLE, zai: process.env.CONSORTIUM_PROXY_TOKEN_ZAI }; const haveAllPaid = !!(envTokens.consortium && envTokens.anthropic && envTokens.openai && envTokens.google && envTokens.zai); const haveJustConsortium = !!envTokens.consortium; if (isFreeSlug && haveJustConsortium || haveAllPaid) { proxyTokens = { consortium: envTokens.consortium, ...envTokens.anthropic ? { anthropic: envTokens.anthropic } : {}, ...envTokens.openai ? { openai: envTokens.openai } : {}, ...envTokens.google ? { google: envTokens.google } : {}, ...envTokens.zai ? { zai: envTokens.zai } : {} }; persistence.logger.warn(`[ConsortiumCode] Proxy-token mint failed (network); using CONSORTIUM_PROXY_TOKEN_* env-var fallback.`); } else { const consortiumError = scopeErrors.consortium ?? { code: "network_error", message: failed.join("; ") }; persistence.logger.warn(`[ConsortiumCode] Failed to mint proxy tokens for providers: ${failed.join("; ")}. Aborting (no upstream-Zen fallback).`); try { session.sendSessionEvent({ type: "message", message: `Cannot start session with ${spawnModelId}: ${consortiumError.message}`, errorCode: consortiumError.code, scope: "consortium" }); } catch { } const err = new Error(`mint_failed:consortium:${consortiumError.code}:${consortiumError.message}`); err.mintFailed = { scope: "consortium", errorCode: consortiumError.code, message: consortiumError.message }; throw err; } } else if (isFreeSlug && !tokens.consortium) { const consortiumError = scopeErrors.consortium ?? { code: "mint_failed", message: failed.join("; ") }; persistence.logger.warn(`[ConsortiumCode] Consortium-scope mint failed for free session (code=${consortiumError.code}): ${consortiumError.message}. No upstream-Zen fallback \u2014 refusing spawn.`); try { session.sendSessionEvent({ type: "message", message: `Cannot start session with ${spawnModelId}: ${consortiumError.message}`, // Pass structured fields verbatim so the app can route to the // right billing/upgrade UI without re-parsing strings. errorCode: consortiumError.code, scope: "consortium" }); } catch { } const err = new Error(`mint_failed:consortium:${consortiumError.code}:${consortiumError.message}`); err.mintFailed = { scope: "consortium", errorCode: consortiumError.code, message: consortiumError.message }; throw err; } else if (!isFreeSlug && tokens.consortium) { proxyTokens = tokens; if (firstProxyBaseUrl) consortiumProxyUrl = firstProxyBaseUrl; persistence.logger.warn(`[ConsortiumCode] Partial proxy-token mint (${failed.join("; ")}); paid native providers without tokens are unavailable this session.`); } else { const consortiumError = scopeErrors.consortium ?? { code: "mint_failed", message: failed.join("; ") }; persistence.logger.warn(`[ConsortiumCode] Failed to mint proxy tokens for providers: ${failed.join("; ")}`); try { session.sendSessionEvent({ type: "message", message: `Cannot start session with ${spawnModelId}: ${consortiumError.message}`, errorCode: consortiumError.code, scope: "consortium" }); } catch { } const err = new Error(`mint_failed:consortium:${consortiumError.code}:${consortiumError.message}`); err.mintFailed = { scope: "consortium", errorCode: consortiumError.code, message: consortiumError.message }; throw err; } if (!byokActive && spawnModelId !== "opencode/big-pickle" && spawnModelId.includes("/")) { const [providerScope, ...rest] = spawnModelId.split("/"); const bareModelId = rest.join("/"); try { const previewRes = await axios.get(`${persistence.configuration.serverUrl}/v1/billing/wallet-preview`, { params: { providerId: providerScope, modelId: bareModelId }, headers: { Authorization: `Bearer ${opts.credentials.token}` }, timeout: 7e3, validateStatus: () => true }); const data = previewRes.data ?? {}; if (previewRes.status === 200 && data.ok === false) { const reason = data.code; const friendly = reason === "insufficient_credits" ? `Your workspace needs credits to use ${spawnModelId}. Required ~${data.requiredCents}\xA2, available ${data.availableCents}\xA2.` : reason === "no_managed_key" ? `${providerScope} is not currently provisioned with a managed key on this server. Contact your operator.` : `Could not resolve a billable organization for this account.`; persistence.logger.warn(`[ConsortiumCode] Wallet preview blocked spawn for ${spawnModelId}: ${reason}`); try { session.sendSessionEvent({ type: "message", message: `Cannot start session with ${spawnModelId}: ${friendly}` }); } catch { } throw new Error(`wallet_preview_blocked:${reason}:${spawnModelId}`); } } catch (err) { if (typeof err?.message === "string" && err.message.startsWith("wallet_preview_blocked:")) { throw err; } persistence.logger.debug(`[ConsortiumCode] Wallet preview check skipped: ${err?.message ?? err}`); } } const ensureBackend = async (model) => { if (backend) { try { await backend.dispose(); } catch { } backend = null; acpSessionId = null; } const envModel = model || process.env.OPENCODE_MODEL; const envApiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY; const envBaseUrl = process.env.OPENAI_BASE_URL || process.env.ANTHROPIC_BASE_URL; const proxyUrl = consortiumProxyUrl; currentBackendModel = envModel ? envModel.includes("/") ? envModel : `opencode/${envModel}` : "opencode/big-pickle"; backend = createConsortiumCodeBackend({ cwd: process.cwd(), model: envModel, apiKey: envApiKey, baseUrl: envBaseUrl, proxyUrl, proxyTokens, // TEMP: DEBUG-level logging so we can see what the binary's // consortium provider does when resolving consortium/big-pickle. // Without this, "log-level ERROR" hides the catalog-fetch and // model-resolution failures we're trying to diagnose. Drop to // ERROR before production. logLevel: "DEBUG", permissionHandler, mcpServers: { consortium: { command: path.join(persistence.projectPath(), "bin", "consortium-mcp.mjs"), args: ["--url", consortiumServer.url] } } }); backend.setScratchDir?.(scratchDir); backend.setAttachmentCapability?.(capabilities.resolveCapability("consortium-code", currentModel ?? null)); backend.onMessage((msg) => { try { resetWatchdog(); switch (msg.type) { case "model-output": { const chunk = msg.textDelta ?? msg.fullText ?? ""; streamModelChunk(chunk); break; } case "status": resetWatchdog(); if (msg.status === "running") { thinking = true; session.keepAlive(thinking, "remote"); } else if (msg.status === "idle" || msg.status === "stopped") { if (accumulatedText.trim().length > 0) { finalizeTurn("backendIdle"); } else { persistence.logger.debug("[ConsortiumCode] backend idle with empty accumulatedText \u2014 deferring finalize to sendPromptResolved/watchdog"); } } else if (msg.status === "error") { const detail = typeof msg.detail === "object" ? JSON.stringify(msg.detail) : msg.detail ?? "unknown"; session.sendAgentMessage(agent, { type: "message", message: `Error: ${detail}`, id: node_crypto.randomUUID() }); messageBuffer.addMessage(`Error: ${detail}`, "status"); finalizeTurn("error"); } break; case "tool-call": session.sendAgentMessage(agent, { type: "tool-call", name: msg.toolName, callId: msg.callId, input: msg.args, id: node_crypto.randomUUID() }); messageBuffer.addMessage(`Executing: ${msg.toolName}`, "tool"); break; case "tool-result": session.sendAgentMessage(agent, { type: "tool-result", callId: msg.callId, output: msg.result, id: node_crypto.randomUUID() }); break; case "fs-edit": session.sendAgentMessage(agent, { type: "file-edit", description: msg.description, diff: msg.diff, filePath: msg.path ?? "unknown", id: node_crypto.randomUUID() }); messageBuffer.addMessage(`Edit: ${msg.path ?? "file"}`, "tool"); break; case "terminal-output": session.sendAgentMessage(agent, { type: "terminal-output", data: msg.data, callId: msg.callId ?? node_crypto.randomUUID() }); break; case "permission-request": session.sendAgentMessage(agent, msg); break; case "inline-media": (async () => { try { const m = msg; const bytes = Uint8Array.from(Buffer.from(m.base64, "base64")); const uploaded = await session.uploadArtifact({ bytes, mime: m.mime }); session.sendAgentMessage(agent, { type: "artifact", artifactId: uploaded.id, mime: uploaded.mime, name: m.name, sizeBytes: uploaded.sizeBytes, id: node_crypto.randomUUID() }); } catch (err) { persistence.logger.debug("[consortium-code] inline-media upload failed:", err); } })(); break; default: persistence.logger.debug("[ConsortiumCode] Unhandled message type:", msg.type); break; } } catch (err) { persistence.logger.debug("[ConsortiumCode] Error forwarding message:", err); } }); const resumeSessionId = process.env.CONSORTIUM_CODE_RESUME_SESSION_ID; if (resumeSessionId) { delete process.env.CONSORTIUM_CODE_RESUME_SESSION_ID; } try { let result; if (resumeSessionId && typeof backend.loadSession === "function") { persistence.logger.debug(`[ConsortiumCode] Resuming ACP session: ${resumeSessionId}`); result = await backend.loadSession(resumeSessionId); } else { result = await backend.startSession(); } acpSessionId = result.sessionId; persistence.logger.debug(`[ConsortiumCode] ACP session ${resumeSessionId ? "loaded" : "started"}: ${acpSessionId}`); session.updateMetadata((m) => ({ ...m, agentSessionId: acpSessionId ?? void 0 })); } catch (err) { persistence.logger.debug("[ConsortiumCode] Failed to start/load session:", err); throw err; } return backend; }; const handleAbort = async () => { persistence.logger.debug("[ConsortiumCode] Abort requested"); session.sendAgentMessage(agent, { type: "turn_aborted", id: node_crypto.randomUUID() }); permissionHandler.reset(); messageQueue.reset(); try { abortController.abort(); abortController = new AbortController(); if (acpSessionId && backend) await backend.cancel(acpSessionId); } catch (err) { persistence.logger.debug("[ConsortiumCode] Error during abort:", err); } }; const handleKillSession = async () => { persistence.logger.debug("[ConsortiumCode] Kill session requested"); shouldExit = true; await handleAbort(); try { session.updateMetadata((m) => ({ ...m, lifecycleState: "archived", lifecycleStateSince: Date.now(), archivedBy: "cli", archiveReason: "User terminated" })); session.sendSessionDeath(); await session.flush(); await session.close(); } catch (err) { persistence.logger.debug("[ConsortiumCode] Error closing session:", err); } finally { clearInterval(keepAliveInterval); index.stopCaffeinate(); consortiumServer.stop(); if (backend) try { await backend.dispose(); } catch { } inkInstance?.unmount(); if (!opts.existingSession) { process.exit(0); } } }; session.rpcHandlerManager.registerHandler("abort", handleAbort); index.registerKillSessionHandler(session.rpcHandlerManager, handleKillSession); if (!hasTTY) { console.log(" Consortium Code \u2014 waiting for prompts from the mobile app..."); console.log(" (Press Ctrl+C to exit)"); console.log(""); } try { while (!shouldExit) { applyPendingSessionSwap(); const batch = await messageQueue.waitForMessagesAndGetAsString(abortController.signal); if (!batch) { if (abortController.signal.aborted && !shouldExit) continue; break; } isProcessingMessage = true; messageBuffer.addMessage(batch.message.substring(0, 200), "user"); const special = index.parseSpecialCommand(batch.message); if (special.type === "clear") { messageBuffer.addMessage("Resetting session...", "status"); if (backend) try { await backend.dispose(); } catch { } backend = null; acpSessionId = null; currentModeHash = null; permissionHandler.reset(); thinking = false; session.keepAlive(thinking, "remote"); messageBuffer.addMessage("Session reset.", "status"); isProcessingMessage = false; sendReady(); continue; } if (currentModeHash && batch.hash !== currentModeHash) { persistence.logger.debug(`[ConsortiumCode] Mode hash changed: ${currentModeHash} \u2192 ${batch.hash}`); permissionHandler.setPermissionMode(batch.mode.permissionMode); } currentModeHash = batch.hash; thinking = true; session.keepAlive(thinking, "remote"); session.sendAgentMessage(agent, { type: "task_started", id: node_crypto.randomUUID() }); const requestedModel = batch.mode.model; const requestedFull = requestedModel ? requestedModel.includes("/") ? requestedModel : `opencode/${requestedModel}` : void 0; if (backend && acpSessionId) { if (requestedFull && requestedFull !== currentBackendModel) { try { await backend.setSessionModel(requestedFull); const [scope, ...rest] = requestedFull.split("/"); const bareModelId = rest.join("/"); const overrideProvider = scope === "opencode" ? "consortium" : scope; axios.post( `${persistence.configuration.serverUrl}/v1/proxy/model`, { providerId: overrideProvider, modelId: bareModelId }, { headers: { Authorization: `Bearer ${opts.credentials.token}` }, timeout: 5e3 } ).catch((err) => persistence.logger.warn(`[ConsortiumCode] in-session setModelOverride failed: ${err?.message ?? err}`)); currentBackendModel = requestedFull; persistence.logger.debug(`[ConsortiumCode] Switched session model to ${requestedFull} (context preserved)`); } catch (err) { persistence.logger.warn(`[ConsortiumCode] setSessionModel failed (${err}); respawning backend`); if (backend) try { await backend.dispose(); } catch { } backend = null; acpSessionId = null; try { await ensureBackend(requestedModel); } catch (rebuildErr) { persistence.logger.debug("[ConsortiumCode] Failed to rebuild backend after setSessionModel failure:", rebuildErr);