
































@@ -0,0 +1,157 @@
1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import { resolveOpenClawAgentDir } from "openclaw/plugin-sdk/provider-auth";
5+import { prepareCodexAuthBridge } from "openclaw/plugin-sdk/provider-auth-runtime";
6+import { writePrivateSecretFileAtomic } from "openclaw/plugin-sdk/secret-file-runtime";
7+import type { PluginLogger } from "../runtime-api.js";
8+import type { ResolvedAcpxPluginConfig } from "./config.js";
9+10+const CODEX_AGENT_ID = "codex";
11+const DEFAULT_CODEX_AUTH_PROFILE_ID = "openai-codex:default";
12+const CODEX_AUTH_ENV_CLEAR_KEYS = ["OPENAI_API_KEY"];
13+14+type PreparedAcpxCodexAuth = {
15+codexHome: string;
16+clearEnv: string[];
17+};
18+19+function resolveSourceCodexHome(env: NodeJS.ProcessEnv = process.env): string {
20+const configured = env.CODEX_HOME?.trim();
21+if (configured) {
22+if (configured === "~") {
23+return os.homedir();
24+}
25+if (configured.startsWith("~/")) {
26+return path.join(os.homedir(), configured.slice(2));
27+}
28+return path.resolve(configured);
29+}
30+return path.join(os.homedir(), ".codex");
31+}
32+33+async function readOptionalFile(filePath: string): Promise<string | undefined> {
34+try {
35+return await fs.readFile(filePath, "utf8");
36+} catch (error) {
37+if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
38+return undefined;
39+}
40+throw error;
41+}
42+}
43+44+async function prepareCopiedCodexHome(params: {
45+agentDir: string;
46+sourceCodexHome: string;
47+}): Promise<PreparedAcpxCodexAuth | null> {
48+const authJson = await readOptionalFile(path.join(params.sourceCodexHome, "auth.json"));
49+if (!authJson) {
50+return null;
51+}
52+53+const codexHome = path.join(params.agentDir, "acp-auth", "codex-source");
54+await writePrivateSecretFileAtomic({
55+rootDir: params.agentDir,
56+filePath: path.join(codexHome, "auth.json"),
57+content: authJson,
58+});
59+60+const configToml = await readOptionalFile(path.join(params.sourceCodexHome, "config.toml"));
61+if (configToml) {
62+await writePrivateSecretFileAtomic({
63+rootDir: params.agentDir,
64+filePath: path.join(codexHome, "config.toml"),
65+content: configToml,
66+});
67+}
68+69+return {
70+ codexHome,
71+clearEnv: [...CODEX_AUTH_ENV_CLEAR_KEYS],
72+};
73+}
74+75+function shellArg(value: string): string {
76+return `'${value.replace(/'/g, `'\\''`)}'`;
77+}
78+79+async function writeCodexAcpWrapper(params: {
80+wrapperPath: string;
81+codexHome: string;
82+clearEnv: string[];
83+}): Promise<string> {
84+await fs.mkdir(path.dirname(params.wrapperPath), { recursive: true, mode: 0o700 });
85+const content = `#!/usr/bin/env node
86+import { spawn } from "node:child_process";
87+88+const env = { ...process.env, CODEX_HOME: ${JSON.stringify(params.codexHome)} };
89+for (const key of ${JSON.stringify(params.clearEnv)}) {
90+ delete env[key];
91+}
92+93+const child = spawn("npx", ["@zed-industries/codex-acp@^0.11.1"], {
94+ stdio: "inherit",
95+ env,
96+});
97+98+child.on("exit", (code, signal) => {
99+ if (signal) {
100+ process.kill(process.pid, signal);
101+ return;
102+ }
103+ process.exit(code ?? 1);
104+});
105+106+child.on("error", (error) => {
107+ console.error(error instanceof Error ? error.message : String(error));
108+ process.exit(1);
109+});
110+`;
111+await fs.writeFile(params.wrapperPath, content, { mode: 0o700 });
112+await fs.chmod(params.wrapperPath, 0o700);
113+return shellArg(params.wrapperPath);
114+}
115+116+export async function prepareAcpxCodexAuthConfig(params: {
117+pluginConfig: ResolvedAcpxPluginConfig;
118+stateDir: string;
119+logger?: PluginLogger;
120+}): Promise<ResolvedAcpxPluginConfig> {
121+if (params.pluginConfig.agents[CODEX_AGENT_ID]) {
122+return params.pluginConfig;
123+}
124+125+const agentDir = resolveOpenClawAgentDir();
126+const sourceCodexHome = resolveSourceCodexHome();
127+const bridge =
128+(await prepareCodexAuthBridge({
129+ agentDir,
130+bridgeDir: "acp-auth",
131+profileId: DEFAULT_CODEX_AUTH_PROFILE_ID,
132+ sourceCodexHome,
133+})) ??
134+(await prepareCopiedCodexHome({
135+ agentDir,
136+ sourceCodexHome,
137+}));
138+139+if (!bridge) {
140+params.logger?.debug?.("codex ACP auth bridge skipped: no Codex auth source found");
141+return params.pluginConfig;
142+}
143+144+const wrapperCommand = await writeCodexAcpWrapper({
145+wrapperPath: path.join(params.stateDir, "acpx", "codex-acp-wrapper.mjs"),
146+codexHome: bridge.codexHome,
147+clearEnv: bridge.clearEnv,
148+});
149+150+return {
151+ ...params.pluginConfig,
152+agents: {
153+ ...params.pluginConfig.agents,
154+[CODEX_AGENT_ID]: wrapperCommand,
155+},
156+};
157+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。