import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; export interface RuntimePathInput { isPackaged: boolean; resourcesPath: string; appPath: string; platform: NodeJS.Platform; arch?: NodeJS.Architecture; } export interface OpencodeRuntimePaths { runtimeDir: string; binPath: string; packageJsonPath: string; } const OPENCODE_RUNTIME_PACKAGE = 'opencode-ai'; const nativePlatformNames: Partial> = { darwin: 'darwin', linux: 'linux', win32: 'windows', }; const nativeArchNames: Partial> = { x64: 'x64', arm64: 'arm64', arm: 'arm', }; function getDevelopmentNativePackageCandidates( platform: NodeJS.Platform, arch: NodeJS.Architecture, ): string[] { const platformName = nativePlatformNames[platform] ?? platform; const archName = nativeArchNames[arch] ?? arch; const base = `opencode-${platformName}-${archName}`; // The baseline x64 package runs on CPUs with and without AVX2, so prefer // it when available. This mirrors the packaged-runtime bundler while // avoiding opencode-ai's postinstall-generated wrapper in pnpm dev trees. return archName === 'x64' ? [`${base}-baseline`, base] : [base]; } function getNativeOpencodeBinName(platform: NodeJS.Platform): string { return platform === 'win32' ? 'opencode.exe' : 'opencode'; } function resolveDevelopmentNativeBinPath(input: RuntimePathInput): string | undefined { const arch = input.arch ?? process.arch; for (const packageName of getDevelopmentNativePackageCandidates(input.platform, arch)) { const binPath = join(input.appPath, 'node_modules', packageName, 'bin', getNativeOpencodeBinName(input.platform)); if (existsSync(binPath)) return binPath; } return undefined; } export function getOpencodeBinName( platform: NodeJS.Platform = process.platform, isPackaged = false, ): string { if (isPackaged && platform === 'win32') { return 'opencode.exe'; } return 'opencode'; } export function resolveOpencodeRuntimePaths(input: RuntimePathInput): OpencodeRuntimePaths { const runtimeDir = input.isPackaged ? join(input.resourcesPath, OPENCODE_RUNTIME_PACKAGE) : join(input.appPath, 'node_modules', OPENCODE_RUNTIME_PACKAGE); const developmentNativeBinPath = input.isPackaged ? undefined : resolveDevelopmentNativeBinPath(input); return { runtimeDir, binPath: developmentNativeBinPath ?? join(runtimeDir, 'bin', getOpencodeBinName(input.platform, input.isPackaged)), packageJsonPath: join(runtimeDir, 'package.json'), }; } export function readOpencodeRuntimeVersion(paths: OpencodeRuntimePaths): string | undefined { if (!existsSync(paths.packageJsonPath)) { return undefined; } const packageJson = JSON.parse(readFileSync(paths.packageJsonPath, 'utf8')) as { version?: unknown; }; return typeof packageJson.version === 'string' ? packageJson.version : undefined; }