mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix(codex): bootstrap app-server auth fallback
This commit is contained in:
@@ -27,7 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/Codex: pass agent and workspace directories into provider stream wrappers so Codex native `web_search` activation can evaluate the correct auth context, and smoke-test the built status-message runtime by resolving the emitted bundle name. Carries forward #67843; refs #65909. Thanks @neilofneils404.
|
||||
- Cron/models: keep `payload.model` as a per-job primary that can use configured fallbacks, while still letting `payload.fallbacks: []` make cron runs strict and avoid hidden agent-primary retries. Refs #73023. Thanks @pavelyortho-cyber.
|
||||
- Models/fallbacks: treat user-selected session models as exact choices, so `/model ollama/...` and model-picker switches fail visibly when the selected provider is unreachable instead of answering from an unrelated configured fallback. Fixes #73023. Thanks @pavelyortho-cyber.
|
||||
- Codex harness: automatically clear inherited `OPENAI_API_KEY` from spawned Codex app-server processes when the harness is using ChatGPT subscription auth, while keeping explicit Codex API-key profiles and the manual `appServer.clearEnv` escape hatch available. Fixes #73057. Thanks @holgergruenhagen.
|
||||
- Codex harness: keep ChatGPT subscription app-server runs from inheriting `CODEX_API_KEY` or `OPENAI_API_KEY`, and fall back to `CODEX_API_KEY` / `OPENAI_API_KEY` app-server login only when no Codex account is available. Fixes #73057. Thanks @holgergruenhagen.
|
||||
- CLI/model probes: fail local `infer model run` probes when the provider returns no text output, so unreachable local providers and empty completions no longer look like successful smoke tests. Refs #73023. Thanks @pavelyortho-cyber.
|
||||
- CLI/Ollama: run local `infer model run` through the lean provider completion path and skip global model discovery for one-shot local probes, so Ollama smoke tests no longer pay full chat-agent/tool startup cost or hang before the native `/api/chat` request. Fixes #72851. Thanks @TotalRes2020.
|
||||
- Doctor/gateway services: ignore launchd/systemd companion services that only reference the gateway as a dependency, suppress inactive Linux extra-service warnings, and avoid rewriting a running systemd gateway command/entrypoint during doctor repair. Carries forward #39118. Thanks @therk.
|
||||
|
||||
@@ -179,15 +179,15 @@ Codex after changing config.
|
||||
- Codex app-server `0.125.0` or newer. The bundled plugin manages a compatible
|
||||
Codex app-server binary by default, so local `codex` commands on `PATH` do
|
||||
not affect normal harness startup.
|
||||
- Codex auth available to the app-server process.
|
||||
- Codex auth available to the app-server process or to OpenClaw's Codex auth
|
||||
bridge.
|
||||
|
||||
The plugin blocks older or unversioned app-server handshakes. That keeps
|
||||
OpenClaw on the protocol surface it has been tested against.
|
||||
|
||||
For live and Docker smoke tests, auth usually comes from `OPENAI_API_KEY`, plus
|
||||
optional Codex CLI files such as `~/.codex/auth.json` and
|
||||
`~/.codex/config.toml`. Use the same auth material your local Codex app-server
|
||||
uses.
|
||||
For live and Docker smoke tests, auth usually comes from the Codex CLI account,
|
||||
an OpenClaw `openai-codex` auth profile, or `CODEX_API_KEY` /
|
||||
`OPENAI_API_KEY` as a fallback when no account is present.
|
||||
|
||||
## Minimal config
|
||||
|
||||
@@ -508,16 +508,24 @@ For an already-running app-server, use WebSocket transport:
|
||||
}
|
||||
```
|
||||
|
||||
Stdio app-server launches inherit OpenClaw's process environment by default.
|
||||
When OpenClaw sees that the Codex harness is using a ChatGPT subscription-style
|
||||
auth profile, including the local Codex CLI login imported as
|
||||
`openai-codex:default`, it automatically removes `OPENAI_API_KEY` from the
|
||||
spawned Codex child process. That keeps Gateway-level API keys available for
|
||||
embeddings or direct OpenAI models without making native Codex app-server turns
|
||||
bill through the API by accident.
|
||||
Stdio app-server launches inherit OpenClaw's process environment by default,
|
||||
but OpenClaw owns the Codex app-server account bridge. Auth is selected in this
|
||||
order:
|
||||
|
||||
Explicit Codex API-key profiles are left alone. If a deployment needs additional
|
||||
environment isolation, add those variables to `appServer.clearEnv`:
|
||||
1. An explicit OpenClaw Codex auth profile for the agent.
|
||||
2. The app-server's existing account, such as a local Codex CLI ChatGPT sign-in.
|
||||
3. `CODEX_API_KEY`, then `OPENAI_API_KEY`, only when no app-server account is
|
||||
present and OpenAI auth is still required.
|
||||
|
||||
When OpenClaw sees a ChatGPT subscription-style Codex auth profile, it removes
|
||||
`CODEX_API_KEY` and `OPENAI_API_KEY` from the spawned Codex child process. That
|
||||
keeps Gateway-level API keys available for embeddings or direct OpenAI models
|
||||
without making native Codex app-server turns bill through the API by accident.
|
||||
Explicit Codex API-key profiles and env-key fallback use app-server login
|
||||
instead of inherited child-process env.
|
||||
|
||||
If a deployment needs additional environment isolation, add those variables to
|
||||
`appServer.clearEnv`:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -527,7 +535,7 @@ environment isolation, add those variables to `appServer.clearEnv`:
|
||||
enabled: true,
|
||||
config: {
|
||||
appServer: {
|
||||
clearEnv: ["OPENAI_API_KEY"],
|
||||
clearEnv: ["CODEX_API_KEY", "OPENAI_API_KEY"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -285,6 +285,24 @@ Choose your preferred auth method and follow the setup steps.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Native Codex app-server auth
|
||||
|
||||
The native Codex app-server harness uses `openai/*` model refs plus
|
||||
`agentRuntime.id: "codex"`, but its auth is still account-based. OpenClaw
|
||||
selects auth in this order:
|
||||
|
||||
1. An explicit OpenClaw `openai-codex` auth profile bound to the agent.
|
||||
2. The app-server's existing account, such as a local Codex CLI ChatGPT sign-in.
|
||||
3. `CODEX_API_KEY`, then `OPENAI_API_KEY`, only when the app-server reports no
|
||||
account and still requires OpenAI auth.
|
||||
|
||||
That means a local ChatGPT/Codex subscription sign-in is not replaced just
|
||||
because the gateway process also has `OPENAI_API_KEY` for direct OpenAI models
|
||||
or embeddings. API-key fallback is only the no-account path. When a
|
||||
subscription-style Codex profile is selected, OpenClaw also keeps
|
||||
`CODEX_API_KEY` and `OPENAI_API_KEY` out of the spawned stdio app-server child
|
||||
and sends the selected credentials through the app-server login RPC.
|
||||
|
||||
## Image generation
|
||||
|
||||
The bundled `openai` plugin registers image generation through the `image_generate` tool.
|
||||
|
||||
@@ -109,31 +109,26 @@ function createStartOptions(
|
||||
};
|
||||
}
|
||||
|
||||
async function writeCodexCliAuthFile(codexHome: string): Promise<void> {
|
||||
await fs.mkdir(codexHome, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(codexHome, "auth.json"),
|
||||
JSON.stringify({
|
||||
tokens: {
|
||||
access_token: "cli-access-token",
|
||||
refresh_token: "cli-refresh-token",
|
||||
account_id: "cli-account-123",
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("bridgeCodexAppServerStartOptions", () => {
|
||||
it("clears an inherited OpenAI API key when local Codex CLI OAuth is available", async () => {
|
||||
it("clears inherited API-key env vars when the default Codex profile is subscription auth", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const codexHome = path.join(agentDir, "codex-home");
|
||||
const startOptions = createStartOptions({
|
||||
env: { CODEX_HOME: "/tmp/source-codex-home", EXISTING: "1" },
|
||||
env: { EXISTING: "1" },
|
||||
clearEnv: ["FOO"],
|
||||
});
|
||||
vi.stubEnv("CODEX_HOME", codexHome);
|
||||
try {
|
||||
await writeCodexCliAuthFile(codexHome);
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
accountId: "account-123",
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
bridgeCodexAppServerStartOptions({
|
||||
@@ -142,7 +137,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
clearEnv: ["FOO", "OPENAI_API_KEY"],
|
||||
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
|
||||
});
|
||||
expect(startOptions.clearEnv).toEqual(["FOO"]);
|
||||
await expect(fs.access(path.join(agentDir, "harness-auth"))).rejects.toMatchObject({
|
||||
@@ -178,7 +173,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
clearEnv: ["FOO", "OPENAI_API_KEY"],
|
||||
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
@@ -207,7 +202,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
clearEnv: ["FOO", "OPENAI_API_KEY"],
|
||||
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
@@ -380,6 +375,105 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to CODEX_API_KEY when no auth profile and no Codex account is available", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "account/read") {
|
||||
return { account: null, requiresOpenaiAuth: true };
|
||||
}
|
||||
return { type: "apiKey" };
|
||||
});
|
||||
vi.stubEnv("CODEX_API_KEY", "codex-env-api-key");
|
||||
vi.stubEnv("OPENAI_API_KEY", "openai-env-api-key");
|
||||
try {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenNthCalledWith(1, "account/read", { refreshToken: false });
|
||||
expect(request).toHaveBeenNthCalledWith(2, "account/login/start", {
|
||||
type: "apiKey",
|
||||
apiKey: "codex-env-api-key",
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to OPENAI_API_KEY when CODEX_API_KEY is not set", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "account/read") {
|
||||
return { account: null, requiresOpenaiAuth: true };
|
||||
}
|
||||
return { type: "apiKey" };
|
||||
});
|
||||
vi.stubEnv("CODEX_API_KEY", "");
|
||||
vi.stubEnv("OPENAI_API_KEY", "openai-env-api-key");
|
||||
try {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenNthCalledWith(1, "account/read", { refreshToken: false });
|
||||
expect(request).toHaveBeenNthCalledWith(2, "account/login/start", {
|
||||
type: "apiKey",
|
||||
apiKey: "openai-env-api-key",
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps an existing app-server ChatGPT account over env API-key fallback", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "account/read") {
|
||||
return {
|
||||
account: { type: "chatgpt", email: "codex@example.test", planType: "plus" },
|
||||
requiresOpenaiAuth: true,
|
||||
};
|
||||
}
|
||||
return { type: "apiKey" };
|
||||
});
|
||||
vi.stubEnv("CODEX_API_KEY", "codex-env-api-key");
|
||||
try {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledTimes(1);
|
||||
expect(request).toHaveBeenCalledWith("account/read", { refreshToken: false });
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("skips env API-key fallback when app-server does not require OpenAI auth", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "account/read") {
|
||||
return { account: null, requiresOpenaiAuth: false };
|
||||
}
|
||||
return { type: "apiKey" };
|
||||
});
|
||||
vi.stubEnv("CODEX_API_KEY", "codex-env-api-key");
|
||||
try {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledTimes(1);
|
||||
expect(request).toHaveBeenCalledWith("account/read", { refreshToken: false });
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("applies an OpenAI Codex token profile backed by a secret ref", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const request = vi.fn(async () => ({ type: "chatgptAuthTokens" }));
|
||||
|
||||
@@ -10,11 +10,14 @@ import {
|
||||
import type { CodexAppServerClient } from "./client.js";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
import type { ChatgptAuthTokensRefreshResponse } from "./protocol-generated/typescript/v2/ChatgptAuthTokensRefreshResponse.js";
|
||||
import type { GetAccountResponse } from "./protocol-generated/typescript/v2/GetAccountResponse.js";
|
||||
import type { LoginAccountParams } from "./protocol-generated/typescript/v2/LoginAccountParams.js";
|
||||
|
||||
const CODEX_APP_SERVER_AUTH_PROVIDER = "openai-codex";
|
||||
const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai-codex:default";
|
||||
const CODEX_API_KEY_ENV_VAR = "CODEX_API_KEY";
|
||||
const OPENAI_API_KEY_ENV_VAR = "OPENAI_API_KEY";
|
||||
const CODEX_APP_SERVER_API_KEY_ENV_VARS = [CODEX_API_KEY_ENV_VAR, OPENAI_API_KEY_ENV_VAR];
|
||||
|
||||
export async function bridgeCodexAppServerStartOptions(params: {
|
||||
startOptions: CodexAppServerStartOptions;
|
||||
@@ -30,7 +33,7 @@ export async function bridgeCodexAppServerStartOptions(params: {
|
||||
authProfileId: params.authProfileId,
|
||||
});
|
||||
return shouldClearInheritedOpenAiApiKey
|
||||
? withClearedEnvironmentVariable(params.startOptions, OPENAI_API_KEY_ENV_VAR)
|
||||
? withClearedEnvironmentVariables(params.startOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS)
|
||||
: params.startOptions;
|
||||
}
|
||||
|
||||
@@ -44,6 +47,13 @@ export async function applyCodexAppServerAuthProfile(params: {
|
||||
authProfileId: params.authProfileId,
|
||||
});
|
||||
if (!loginParams) {
|
||||
const fallbackLoginParams = await resolveCodexAppServerEnvApiKeyLoginParams({
|
||||
client: params.client,
|
||||
env: process.env,
|
||||
});
|
||||
if (fallbackLoginParams) {
|
||||
await params.client.request("account/login/start", fallbackLoginParams);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await params.client.request("account/login/start", loginParams);
|
||||
@@ -105,6 +115,23 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params: {
|
||||
return loginParams;
|
||||
}
|
||||
|
||||
async function resolveCodexAppServerEnvApiKeyLoginParams(params: {
|
||||
client: CodexAppServerClient;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): Promise<LoginAccountParams | undefined> {
|
||||
const apiKey = readFirstNonEmptyEnv(params.env, CODEX_APP_SERVER_API_KEY_ENV_VARS);
|
||||
if (!apiKey) {
|
||||
return undefined;
|
||||
}
|
||||
const response = await params.client.request<GetAccountResponse>("account/read", {
|
||||
refreshToken: false,
|
||||
});
|
||||
if (response.account || !response.requiresOpenaiAuth) {
|
||||
return undefined;
|
||||
}
|
||||
return { type: "apiKey", apiKey };
|
||||
}
|
||||
|
||||
async function resolveLoginParamsForCredential(
|
||||
profileId: string,
|
||||
credential: AuthProfileCredential,
|
||||
@@ -189,20 +216,31 @@ function isCodexSubscriptionCredential(credential: AuthProfileCredential | undef
|
||||
return credential.type === "oauth" || credential.type === "token";
|
||||
}
|
||||
|
||||
function withClearedEnvironmentVariable(
|
||||
function withClearedEnvironmentVariables(
|
||||
startOptions: CodexAppServerStartOptions,
|
||||
envVar: string,
|
||||
envVars: readonly string[],
|
||||
): CodexAppServerStartOptions {
|
||||
const clearEnv = startOptions.clearEnv ?? [];
|
||||
if (clearEnv.includes(envVar)) {
|
||||
const missingEnvVars = envVars.filter((envVar) => !clearEnv.includes(envVar));
|
||||
if (missingEnvVars.length === 0) {
|
||||
return startOptions;
|
||||
}
|
||||
return {
|
||||
...startOptions,
|
||||
clearEnv: [...clearEnv, envVar],
|
||||
clearEnv: [...clearEnv, ...missingEnvVars],
|
||||
};
|
||||
}
|
||||
|
||||
function readFirstNonEmptyEnv(env: NodeJS.ProcessEnv, keys: readonly string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = env[key]?.trim();
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildChatgptAuthTokensParams(
|
||||
profileId: string,
|
||||
credential: AuthProfileCredential,
|
||||
|
||||
@@ -18,7 +18,6 @@ describe("Codex app-server config", () => {
|
||||
transport: "websocket",
|
||||
url: "ws://127.0.0.1:39175",
|
||||
headers: { "X-Test": "yes" },
|
||||
clearEnv: ["OPENAI_API_KEY"],
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "danger-full-access",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
@@ -41,12 +40,26 @@ describe("Codex app-server config", () => {
|
||||
transport: "websocket",
|
||||
url: "ws://127.0.0.1:39175",
|
||||
headers: { "X-Test": "yes" },
|
||||
clearEnv: ["OPENAI_API_KEY"],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores app-server environment clearing for websocket transports", () => {
|
||||
const runtime = resolveCodexAppServerRuntimeOptions({
|
||||
pluginConfig: {
|
||||
appServer: {
|
||||
transport: "websocket",
|
||||
url: "ws://127.0.0.1:39175",
|
||||
clearEnv: ["OPENAI_API_KEY"],
|
||||
},
|
||||
},
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(runtime.start).not.toHaveProperty("clearEnv");
|
||||
});
|
||||
|
||||
it("normalizes app-server environment variables to clear", () => {
|
||||
const runtime = resolveCodexAppServerRuntimeOptions({
|
||||
pluginConfig: {
|
||||
|
||||
@@ -214,7 +214,7 @@ export function resolveCodexAppServerRuntimeOptions(
|
||||
...(url ? { url } : {}),
|
||||
...(authToken ? { authToken } : {}),
|
||||
headers,
|
||||
...(clearEnv.length > 0 ? { clearEnv } : {}),
|
||||
...(transport === "stdio" && clearEnv.length > 0 ? { clearEnv } : {}),
|
||||
},
|
||||
requestTimeoutMs: normalizePositiveNumber(config.requestTimeoutMs, 60_000),
|
||||
approvalPolicy:
|
||||
|
||||
@@ -3,7 +3,10 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
import { resolveCodexAppServerSpawnInvocation } from "./transport-stdio.js";
|
||||
import {
|
||||
resolveCodexAppServerSpawnEnv,
|
||||
resolveCodexAppServerSpawnInvocation,
|
||||
} from "./transport-stdio.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
@@ -86,3 +89,26 @@ describe("resolveCodexAppServerSpawnInvocation", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveCodexAppServerSpawnEnv", () => {
|
||||
it("applies configured env overrides before clearing denied env vars", () => {
|
||||
expect(
|
||||
resolveCodexAppServerSpawnEnv(
|
||||
{
|
||||
env: {
|
||||
OPENAI_API_KEY: "configured-openai-key",
|
||||
KEEP: "override",
|
||||
},
|
||||
clearEnv: ["OPENAI_API_KEY", "CODEX_API_KEY", "MISSING"],
|
||||
},
|
||||
{
|
||||
OPENAI_API_KEY: "parent-openai-key",
|
||||
CODEX_API_KEY: "parent-codex-key",
|
||||
KEEP: "parent",
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
KEEP: "override",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,14 +41,22 @@ export function resolveCodexAppServerSpawnInvocation(
|
||||
};
|
||||
}
|
||||
|
||||
export function createStdioTransport(options: CodexAppServerStartOptions): CodexAppServerTransport {
|
||||
export function resolveCodexAppServerSpawnEnv(
|
||||
options: Pick<CodexAppServerStartOptions, "env" | "clearEnv">,
|
||||
baseEnv: NodeJS.ProcessEnv = process.env,
|
||||
): NodeJS.ProcessEnv {
|
||||
const env = {
|
||||
...process.env,
|
||||
...baseEnv,
|
||||
...options.env,
|
||||
};
|
||||
for (const key of options.clearEnv ?? []) {
|
||||
delete env[key];
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
export function createStdioTransport(options: CodexAppServerStartOptions): CodexAppServerTransport {
|
||||
const env = resolveCodexAppServerSpawnEnv(options);
|
||||
const invocation = resolveCodexAppServerSpawnInvocation(options, {
|
||||
platform: process.platform,
|
||||
env,
|
||||
|
||||
Reference in New Issue
Block a user