feat(server): bundle Codex and OpenCode binaries instead of requiring global installs

This commit is contained in:
Mohamed Boudra
2026-03-12 10:25:53 +07:00
parent 586b48e150
commit 91dde29146
6 changed files with 435 additions and 52 deletions
+279
View File
@@ -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",
@@ -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),
]);
}
+2
View File
@@ -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",
@@ -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<string, AgentDefinition> };
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<string, AgentDefinition> };
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<AgentSession> {
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<boolean> {
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<string, AgentDefinition> };
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: {
@@ -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<string, Record<string, string>> = {
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<string, string> = {
"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<boolean> {
return isProviderCommandAvailable(this.runtimeSettings?.command, resolveCodexBinary);
const command = this.runtimeSettings?.command;
if (command?.mode === "replace") {
return existsSync(command.argv[0]);
}
return true;
}
}
@@ -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<string, string> = {
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<boolean> {
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 {