diff --git a/package-lock.json b/package-lock.json index 03d3be557..074d52fab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6402,6 +6402,128 @@ "node": ">=20.0" } }, + "node_modules/@openai/codex": { + "version": "0.114.0", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0.tgz", + "integrity": "sha512-HMo8LRR6CtfKkaa28xvFK6eOarmBFTDfsrS9GJtEoaspGGemFok494CpafDspiTZaHZZGHfSUe5SWTUxYq7OaA==", + "license": "Apache-2.0", + "bin": { + "codex": "bin/codex.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@openai/codex-darwin-arm64": "npm:@openai/codex@0.114.0-darwin-arm64", + "@openai/codex-darwin-x64": "npm:@openai/codex@0.114.0-darwin-x64", + "@openai/codex-linux-arm64": "npm:@openai/codex@0.114.0-linux-arm64", + "@openai/codex-linux-x64": "npm:@openai/codex@0.114.0-linux-x64", + "@openai/codex-win32-arm64": "npm:@openai/codex@0.114.0-win32-arm64", + "@openai/codex-win32-x64": "npm:@openai/codex@0.114.0-win32-x64" + } + }, + "node_modules/@openai/codex-darwin-arm64": { + "name": "@openai/codex", + "version": "0.114.0-darwin-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-darwin-arm64.tgz", + "integrity": "sha512-c9dlgo9O+66alr3s3G36fKE3KaO2+xrLZ/QcU3oujDruV86pqgVSEK2njHi+WIyyOa6m2AyWxdaHLEbKxPCNRg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-darwin-x64": { + "name": "@openai/codex", + "version": "0.114.0-darwin-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-darwin-x64.tgz", + "integrity": "sha512-oPAPeZSaJZ1AQneFPSJu44uqbZr63Bxut9FOTWZwuSqYx4YEees7i0HJmJcqQc0XnCbYtHJdqo/i07Uv374NLw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-arm64": { + "name": "@openai/codex", + "version": "0.114.0-linux-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-linux-arm64.tgz", + "integrity": "sha512-oUFZGZgMiEqmo85C+dfhckpVApH25alLWfwBgfKr2IRgdx/tovy8vN101f6kj01E6nDDe4LfJdNSZGtZht4fRA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-x64": { + "name": "@openai/codex", + "version": "0.114.0-linux-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-linux-x64.tgz", + "integrity": "sha512-MF6RhxfBoccccd5CYII2C7TL2TvYzombBQhanDZfQAajr6n7IuoazCmoHDlrFHS4AcSw9bYA4MRlrwEjbEjgsw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-win32-arm64": { + "name": "@openai/codex", + "version": "0.114.0-win32-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-win32-arm64.tgz", + "integrity": "sha512-CnRMHopj3en9aqQ2UaDW7EgpEGkxHdZVLLRq2cOy5D0HyuzF6Qb6595ADoFVJHEmPeN5Iz/KUbiGs5GLDjUwOA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-win32-x64": { + "name": "@openai/codex", + "version": "0.114.0-win32-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.114.0-win32-x64.tgz", + "integrity": "sha512-ztRsH5Z+gPVZ5ZInx6HzXsTFFkuEdjk3Ay0UGVOhRRCWvevXQi/SL4eU8hwysCYrs8K/WRq3mo4c95w9zQ2wLw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@opencode-ai/sdk": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.2.6.tgz", @@ -21560,6 +21682,161 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/opencode-ai": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-ai/-/opencode-ai-1.2.24.tgz", + "integrity": "sha512-LaSoATkVEF6jyXNnAPrkqYpmHsZfuf2uPLDSEGJkL4hT6HYFq8LkoknHDjCGIiE5Be3MmId4+l17eSIKDfmddw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "opencode": "bin/opencode" + }, + "optionalDependencies": { + "opencode-darwin-arm64": "1.2.24", + "opencode-darwin-x64": "1.2.24", + "opencode-darwin-x64-baseline": "1.2.24", + "opencode-linux-arm64": "1.2.24", + "opencode-linux-arm64-musl": "1.2.24", + "opencode-linux-x64": "1.2.24", + "opencode-linux-x64-baseline": "1.2.24", + "opencode-linux-x64-baseline-musl": "1.2.24", + "opencode-linux-x64-musl": "1.2.24", + "opencode-windows-x64": "1.2.24", + "opencode-windows-x64-baseline": "1.2.24" + } + }, + "node_modules/opencode-darwin-arm64": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-darwin-arm64/-/opencode-darwin-arm64-1.2.24.tgz", + "integrity": "sha512-mk5AkNbmATLwjnczd09Fu9dLTPcW4GSlRlIGWJBbLuGSPe7DWfbuhA5tj00VuaGeC09wDKVnW06BxWp/gRzgMQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/opencode-darwin-x64": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-darwin-x64/-/opencode-darwin-x64-1.2.24.tgz", + "integrity": "sha512-SuG7ItUejx42z9Bz46t0R3LLFIM34vZUcwJAkqqpS6MRd1KV9G4OQIJl9BR+Cwwvk70iqCnoDxj5NgFZjeeNOw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/opencode-darwin-x64-baseline": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-darwin-x64-baseline/-/opencode-darwin-x64-baseline-1.2.24.tgz", + "integrity": "sha512-+MT/ZkgXxeID/kOcT04SBn8Vg+4tk8CytKldaNDLgUnsi/78vTxDXWs0Cc21Ohc894r45fEtOOb5npSNIMOVfQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/opencode-linux-arm64": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-arm64/-/opencode-linux-arm64-1.2.24.tgz", + "integrity": "sha512-zSpbVCwVfUQ/Bey1MEGkJg3A52EiIPgnpS44HwmSANkiLPVXRBVuwbFYN2qJ7eJVAH939K9+QPmDOW4X8DWKeQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-linux-arm64-musl": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-arm64-musl/-/opencode-linux-arm64-musl-1.2.24.tgz", + "integrity": "sha512-LY28mspJOJ2s32E0AbrfbdWH5Bw+5YQkuB3ACJiVxW1MafjhRgPOsXjnw38WMiFjZ+NbhSPBOKnL0guKseVd4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-linux-x64": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-x64/-/opencode-linux-x64-1.2.24.tgz", + "integrity": "sha512-OM3umRJ2ZpDe4YtDOGp32cQCqvMPuFJBKKngfvWL4zz4EWDzLPttb0sNL7xdC1xR/XidtADRYcvtzc2rr7/4Rw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-linux-x64-baseline": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline/-/opencode-linux-x64-baseline-1.2.24.tgz", + "integrity": "sha512-yakykM+GxCMZ5WK2G0CQk5cGJkoKt09A/8Y0bhKy7l+Jc/QiTV2ZIlBwT5FO15dnuGlNH6zvukLyOQvOZinM+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-linux-x64-baseline-musl": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline-musl/-/opencode-linux-x64-baseline-musl-1.2.24.tgz", + "integrity": "sha512-XHPuf9Rj9nWkVX7JJZ5NtYx9UWT0hPiem/kAhZzYITseRGmQMMfFAhIOBYYvvlzJzgdhcrLi198kJHaKCdXjag==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-linux-x64-musl": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-linux-x64-musl/-/opencode-linux-x64-musl-1.2.24.tgz", + "integrity": "sha512-nFT4J4m4wKiZh4RsfgQJlW6W2w/VtMNnXnMiQk93FnncSf0nUXEB5Nam3P+vCjdR3i4cs355JiOvbshnMiJGSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/opencode-windows-x64": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-windows-x64/-/opencode-windows-x64-1.2.24.tgz", + "integrity": "sha512-7nkwFgS8Jn35kgw+6JIWRU6PmbggSuTDiQ+RnFGoySFgYW/Sz/R30Z4KmnbfJLElzEef0kX+nZQasfVl0pSf6A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/opencode-windows-x64-baseline": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/opencode-windows-x64-baseline/-/opencode-windows-x64-baseline-1.2.24.tgz", + "integrity": "sha512-hXp1nPnkrHgM6cid1AogRJvjz931UbZ/G165B6AAR1gOb5EqtIh2gZcT0IRUyjooEpwH7+oGFRhT+3Kql/M0WA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -27796,6 +28073,7 @@ "@lezer/markdown": "^1.6.2", "@lezer/python": "^1.1.18", "@modelcontextprotocol/sdk": "^1.20.1", + "@openai/codex": "^0.114.0", "@opencode-ai/sdk": "1.2.6", "@sctg/sentencepiece-js": "^1.1.0", "@xterm/headless": "^6.0.0", @@ -27810,6 +28088,7 @@ "node-pty": "1.2.0-beta.11", "onnxruntime-node": "^1.23.0", "openai": "^4.20.0", + "opencode-ai": "^1.2.24", "pino": "^10.2.0", "pino-pretty": "^13.1.3", "qrcode": "^1.5.4", diff --git a/packages/desktop/scripts/build-managed-runtime.mjs b/packages/desktop/scripts/build-managed-runtime.mjs index 6f3ba096a..08a60609f 100644 --- a/packages/desktop/scripts/build-managed-runtime.mjs +++ b/packages/desktop/scripts/build-managed-runtime.mjs @@ -409,12 +409,71 @@ async function pruneClaudeAgentSdk(runtimeRoot) { } } +async function pruneCodexCli(runtimeRoot) { + const codexTargetTriple = { + darwin: { arm64: "aarch64-apple-darwin", x64: "x86_64-apple-darwin" }, + linux: { arm64: "aarch64-unknown-linux-musl", x64: "x86_64-unknown-linux-musl" }, + win32: { arm64: "aarch64-pc-windows-msvc", x64: "x86_64-pc-windows-msvc" }, + }; + const codexPlatformPackages = [ + "@openai/codex-darwin-arm64", + "@openai/codex-darwin-x64", + "@openai/codex-linux-arm64", + "@openai/codex-linux-x64", + "@openai/codex-win32-arm64", + "@openai/codex-win32-x64", + ]; + const currentTriple = codexTargetTriple[process.platform]?.[process.arch]; + const currentPackage = currentTriple + ? `@openai/codex-${process.platform === "win32" ? "win32" : process.platform}-${process.arch}` + : null; + + const openaiScope = path.join(runtimeRoot, "node_modules", "@openai"); + for (const pkg of codexPlatformPackages) { + const pkgName = pkg.replace("@openai/", ""); + if (currentPackage && pkg === currentPackage) { + continue; + } + await removeIfExists(path.join(openaiScope, pkgName)); + } +} + +async function pruneOpenCodeCli(runtimeRoot) { + const opencodePlatformPackages = [ + "opencode-darwin-arm64", + "opencode-darwin-x64", + "opencode-linux-arm64", + "opencode-linux-x64", + "opencode-linux-x64-baseline", + "opencode-linux-arm64-musl", + "opencode-linux-x64-musl", + "opencode-linux-x64-baseline-musl", + "opencode-windows-x64", + "opencode-windows-arm64", + ]; + const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" }; + const currentPlatform = platformMap[process.platform]; + const currentPackage = currentPlatform + ? `opencode-${currentPlatform}-${process.arch}` + : null; + + const nodeModules = path.join(runtimeRoot, "node_modules"); + for (const pkg of opencodePlatformPackages) { + if (currentPackage && pkg === currentPackage) { + continue; + } + await removeIfExists(path.join(nodeModules, pkg)); + } +} + async function pruneManagedRuntime(runtimeRoot) { await Promise.all([ pruneNodeDistribution(runtimeRoot), pruneOnnxRuntime(runtimeRoot), pruneNodePty(runtimeRoot), pruneClaudeAgentSdk(runtimeRoot), + pruneCodexCli(runtimeRoot), + pruneOpenCodeCli(runtimeRoot), ]); } diff --git a/packages/server/package.json b/packages/server/package.json index 4db9014bb..b871d9942 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -62,6 +62,7 @@ "dependencies": { "@ai-sdk/openai": "2.0.52", "@anthropic-ai/claude-agent-sdk": "^0.2.11", + "@openai/codex": "^0.114.0", "@deepgram/sdk": "^3.4.0", "@getpaseo/relay": "0.1.25", "@lezer/common": "^1.5.0", @@ -87,6 +88,7 @@ "node-pty": "1.2.0-beta.11", "onnxruntime-node": "^1.23.0", "openai": "^4.20.0", + "opencode-ai": "^1.2.24", "pino": "^10.2.0", "pino-pretty": "^13.1.3", "qrcode": "^1.5.4", diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 33d642ce2..ca23f61b0 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -1,4 +1,4 @@ -import { execSync, spawn } from "node:child_process"; +import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import fs from "node:fs"; import { promises } from "node:fs"; @@ -69,8 +69,6 @@ import type { } from "../agent-sdk-types.js"; import { applyProviderEnv, - findExecutable, - isProviderCommandAvailable, type ProviderRuntimeSettings, } from "../provider-launch-config.js"; import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js"; @@ -358,22 +356,11 @@ type ClaudeAgentClientOptions = { type ClaudeAgentSessionOptions = { defaults?: { agents?: Record }; - claudePath: string | null; runtimeSettings?: ProviderRuntimeSettings; handle?: AgentPersistenceHandle; logger: Logger; }; -function resolveClaudeBinary(): string { - const claudePath = findExecutable("claude"); - if (claudePath) { - return claudePath; - } - throw new Error( - "Claude CLI not found. Install claude or configure agents.providers.claude.command.mode='replace'." - ); -} - function resolveClaudeSpawnCommand( spawnOptions: SpawnOptions, runtimeSettings?: ProviderRuntimeSettings @@ -1400,31 +1387,18 @@ export class ClaudeAgentClient implements AgentClient { private readonly defaults?: { agents?: Record }; private readonly logger: Logger; - private readonly claudePath: string | null; private readonly runtimeSettings?: ProviderRuntimeSettings; constructor(options: ClaudeAgentClientOptions) { this.defaults = options.defaults; this.logger = options.logger.child({ module: "agent", provider: "claude" }); this.runtimeSettings = options.runtimeSettings; - this.claudePath = findExecutable("claude"); - if (this.claudePath) { - try { - const version = execSync(`${this.claudePath} --version`, { encoding: "utf8" }).trim(); - this.logger.trace({ claudePath: this.claudePath, version }, "Resolved Claude binary"); - } catch { - this.logger.trace({ claudePath: this.claudePath }, "Resolved Claude binary (version unknown)"); - } - } else { - this.logger.trace("Claude binary not found in PATH; SDK will use bundled binary"); - } } async createSession(config: AgentSessionConfig): Promise { const claudeConfig = this.assertConfig(config); return new ClaudeAgentSession(claudeConfig, { defaults: this.defaults, - claudePath: this.claudePath, runtimeSettings: this.runtimeSettings, logger: this.logger, }); @@ -1443,7 +1417,6 @@ export class ClaudeAgentClient implements AgentClient { const claudeConfig = this.assertConfig(mergedConfig); return new ClaudeAgentSession(claudeConfig, { defaults: this.defaults, - claudePath: this.claudePath, runtimeSettings: this.runtimeSettings, handle, logger: this.logger, @@ -1478,11 +1451,11 @@ export class ClaudeAgentClient implements AgentClient { } async isAvailable(): Promise { - const commandConfig = this.runtimeSettings?.command; - if (commandConfig?.mode === "replace") { - return isProviderCommandAvailable(commandConfig, resolveClaudeBinary); + const command = this.runtimeSettings?.command; + if (command?.mode === "replace") { + return fs.existsSync(command.argv[0]); } - return this.claudePath !== null; + return true; } private assertConfig(config: AgentSessionConfig): ClaudeAgentConfig { @@ -1499,7 +1472,6 @@ class ClaudeAgentSession implements AgentSession { private readonly config: ClaudeAgentConfig; private readonly defaults?: { agents?: Record }; - private readonly claudePath: string | null; private readonly runtimeSettings?: ProviderRuntimeSettings; private readonly logger: Logger; private query: Query | null = null; @@ -1546,7 +1518,6 @@ class ClaudeAgentSession implements AgentSession { ) { this.config = config; this.defaults = options.defaults; - this.claudePath = options.claudePath; this.runtimeSettings = options.runtimeSettings; this.logger = options.logger; const handle = options.handle; @@ -2319,7 +2290,6 @@ class ClaudeAgentSession implements AgentSession { permissionMode: this.currentMode, agents: this.defaults?.agents, canUseTool: this.handlePermissionRequest, - ...(this.claudePath ? { pathToClaudeCodeExecutable: this.claudePath } : {}), // Use Claude Code preset system prompt and load CLAUDE.md files // Append provider-agnostic system prompt and orchestrator instructions for agents. systemPrompt: { diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 76589507a..849c4f2ab 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -27,8 +27,10 @@ import type { Logger } from "pino"; import { execSync, spawn } from "node:child_process"; import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import { Dirent } from "node:fs"; +import { createRequire } from "node:module"; import os from "node:os"; import path from "node:path"; import readline from "node:readline"; @@ -40,8 +42,6 @@ import { } from "./codex/tool-call-mapper.js"; import { applyProviderEnv, - findExecutable, - isProviderCommandAvailable, resolveProviderCommandPrefix, type ProviderRuntimeSettings, } from "../provider-launch-config.js"; @@ -150,13 +150,52 @@ function mergeCodexConfiguredDefaults( }; } +const CODEX_TARGET_TRIPLE: Record> = { + linux: { x64: "x86_64-unknown-linux-musl", arm64: "aarch64-unknown-linux-musl" }, + darwin: { x64: "x86_64-apple-darwin", arm64: "aarch64-apple-darwin" }, + win32: { x64: "x86_64-pc-windows-msvc", arm64: "aarch64-pc-windows-msvc" }, +}; + +const CODEX_PLATFORM_PACKAGE: Record = { + "x86_64-unknown-linux-musl": "@openai/codex-linux-x64", + "aarch64-unknown-linux-musl": "@openai/codex-linux-arm64", + "x86_64-apple-darwin": "@openai/codex-darwin-x64", + "aarch64-apple-darwin": "@openai/codex-darwin-arm64", + "x86_64-pc-windows-msvc": "@openai/codex-win32-x64", + "aarch64-pc-windows-msvc": "@openai/codex-win32-arm64", +}; + +function resolveBundledCodexBinary(): string | null { + const targetTriple = CODEX_TARGET_TRIPLE[process.platform]?.[process.arch]; + if (!targetTriple) { + return null; + } + const platformPackage = CODEX_PLATFORM_PACKAGE[targetTriple]; + if (!platformPackage) { + return null; + } + const binaryName = process.platform === "win32" ? "codex.exe" : "codex"; + try { + const require = createRequire(import.meta.url); + const packageJsonPath = require.resolve(`${platformPackage}/package.json`); + const vendorRoot = path.join(path.dirname(packageJsonPath), "vendor"); + const binaryPath = path.join(vendorRoot, targetTriple, "codex", binaryName); + if (existsSync(binaryPath)) { + return binaryPath; + } + } catch { + // Platform package not installed + } + return null; +} + function resolveCodexBinary(): string { - const codexPath = findExecutable("codex"); - if (codexPath) { - return codexPath; + const bundled = resolveBundledCodexBinary(); + if (bundled) { + return bundled; } throw new Error( - "Codex CLI not found. Please install codex globally: npm install -g @openai/codex" + `Bundled Codex binary not found for platform ${process.platform}-${process.arch}. Ensure the @openai/codex package and its platform-specific optional dependency are installed.` ); } @@ -3235,6 +3274,10 @@ export class CodexAppServerAgentClient implements AgentClient { } async isAvailable(): Promise { - return isProviderCommandAvailable(this.runtimeSettings?.command, resolveCodexBinary); + const command = this.runtimeSettings?.command; + if (command?.mode === "replace") { + return existsSync(command.argv[0]); + } + return true; } } diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index 0d362c6f0..7481a3b74 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -1,4 +1,8 @@ import { spawn, type ChildProcess } from "node:child_process"; +import { existsSync } from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import os from "node:os"; import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2/client"; import net from "node:net"; import type { Logger } from "pino"; @@ -29,8 +33,6 @@ import type { } from "../agent-sdk-types.js"; import { applyProviderEnv, - findExecutable, - isProviderCommandAvailable, resolveProviderCommandPrefix, type ProviderRuntimeSettings, } from "../provider-launch-config.js"; @@ -150,13 +152,40 @@ const OpencodeToolPartToTimelineItemSchema = OpencodeToolPartTimelineEnvelopeSch }) ); +const OPENCODE_PLATFORM_MAP: Record = { + darwin: "darwin", + linux: "linux", + win32: "windows", +}; + +function resolveBundledOpenCodeBinary(): string | null { + const platform = OPENCODE_PLATFORM_MAP[os.platform()]; + if (!platform) { + return null; + } + const arch = os.arch(); + const packageName = `opencode-${platform}-${arch}`; + const binaryName = platform === "windows" ? "opencode.exe" : "opencode"; + try { + const require = createRequire(import.meta.url); + const packageJsonPath = require.resolve(`${packageName}/package.json`); + const binaryPath = path.join(path.dirname(packageJsonPath), "bin", binaryName); + if (existsSync(binaryPath)) { + return binaryPath; + } + } catch { + // Platform package not installed + } + return null; +} + function resolveOpenCodeBinary(): string { - const opencodePath = findExecutable("opencode"); - if (opencodePath) { - return opencodePath; + const bundled = resolveBundledOpenCodeBinary(); + if (bundled) { + return bundled; } throw new Error( - "OpenCode CLI not found. Please install opencode globally so Paseo can launch the provider." + `Bundled OpenCode binary not found for platform ${os.platform()}-${os.arch()}. Ensure the opencode-ai package and its platform-specific optional dependency are installed.` ); } @@ -522,10 +551,11 @@ export class OpenCodeAgentClient implements AgentClient { } async isAvailable(): Promise { - return isProviderCommandAvailable( - this.runtimeSettings?.command, - resolveOpenCodeBinary - ); + const command = this.runtimeSettings?.command; + if (command?.mode === "replace") { + return existsSync(command.argv[0]); + } + return true; } private assertConfig(config: AgentSessionConfig): OpenCodeAgentConfig {