| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- import { access, realpath } from 'node:fs/promises';
- import { constants } from 'node:fs';
- import { createRequire } from 'node:module';
- import { dirname, join } from 'node:path';
- import { spawnSync } from 'node:child_process';
- import { getBaseDir } from 'ee-core/ps';
- /** 移植自 Noobi.ai src/main/codexLocator.ts,改动:去掉 ChatGPT.app 分支、改用 CJS 可用的 require 基准、增加不抛错的探测入口 */
- const TARGETS: Record<string, { packageName: string; triple: string; executable: string }> = {
- 'darwin-arm64': {
- packageName: '@openai/codex-darwin-arm64',
- triple: 'aarch64-apple-darwin',
- executable: 'codex',
- },
- 'darwin-x64': {
- packageName: '@openai/codex-darwin-x64',
- triple: 'x86_64-apple-darwin',
- executable: 'codex',
- },
- 'linux-arm64': {
- packageName: '@openai/codex-linux-arm64',
- triple: 'aarch64-unknown-linux-musl',
- executable: 'codex',
- },
- 'linux-x64': {
- packageName: '@openai/codex-linux-x64',
- triple: 'x86_64-unknown-linux-musl',
- executable: 'codex',
- },
- 'win32-arm64': {
- packageName: '@openai/codex-win32-arm64',
- triple: 'aarch64-pc-windows-msvc',
- executable: 'codex.exe',
- },
- 'win32-x64': {
- packageName: '@openai/codex-win32-x64',
- triple: 'x86_64-pc-windows-msvc',
- executable: 'codex.exe',
- },
- };
- export interface CodexBinaryProbe {
- available: boolean;
- binaryPath: string | null;
- version: string | null;
- reason: string | null;
- }
- /** 探测顺序:环境变量 → 平台包 vendor → 应用根 node_modules → 系统 PATH */
- function collectCandidates(): string[] {
- const candidates: string[] = [];
- if (process.env.ZSJZ_CODEX_BIN) candidates.push(process.env.ZSJZ_CODEX_BIN);
- const target = TARGETS[`${process.platform}-${process.arch}`];
- if (target) {
- // ee-bin 把主进程 bundle 成单文件 CJS,这里不能依赖 import.meta.url
- try {
- const packageJson = createRequire(__filename).resolve(`${target.packageName}/package.json`);
- candidates.push(join(dirname(packageJson), 'vendor', target.triple, 'bin', target.executable));
- } catch {
- // 平台包是 optionalDependency,缺失时继续尝试下面的候选
- }
- candidates.push(join(getBaseDir(), 'node_modules', target.packageName, 'vendor', target.triple, 'bin', target.executable));
- }
- const which = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['codex'], {
- encoding: 'utf8',
- timeout: 3_000,
- });
- if (which.status === 0 && which.stdout.trim()) {
- candidates.push(which.stdout.trim().split(/\r?\n/u)[0]!);
- }
- return deduplicate(candidates);
- }
- /** 打包后 node_modules 在 app.asar 内,可执行文件必须走 asar.unpacked(builder.json 的 asarUnpack) */
- export function unpackedAsarPath(value: string): string {
- return value.replace(/([/\\])app\.asar([/\\])/u, '$1app.asar.unpacked$2');
- }
- function deduplicate(values: string[]): string[] {
- return [...new Set(values.filter(Boolean))];
- }
- export async function locateCodexBinary(): Promise<string> {
- const failures: string[] = [];
- for (const lexical of collectCandidates()) {
- const candidate = unpackedAsarPath(lexical);
- try {
- await access(candidate, constants.X_OK);
- return await realpath(candidate);
- } catch (error) {
- failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
- }
- }
- throw new Error(
- `找不到可执行的 Codex 二进制,请设置 ZSJZ_CODEX_BIN。已检查 ${failures.length} 个候选路径。`,
- );
- }
- /** 供 ping 使用:任何失败都降级成 available:false + reason,不抛错 */
- export async function probeCodexBinary(): Promise<CodexBinaryProbe> {
- try {
- const binaryPath = await locateCodexBinary();
- return { available: true, binaryPath, version: readCodexVersion(binaryPath), reason: null };
- } catch (error) {
- return {
- available: false,
- binaryPath: null,
- version: null,
- reason: error instanceof Error ? error.message : String(error),
- };
- }
- }
- export function readCodexVersion(binaryPath: string): string | null {
- const result = spawnSync(binaryPath, ['--version'], { encoding: 'utf8', timeout: 5_000 });
- return result.status === 0 ? result.stdout.trim() || null : null;
- }
|