mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
refactor: share codex auth bridge
This commit is contained in:
@@ -1,54 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import {
|
||||
ensureAuthProfileStoreForLocalUpdate,
|
||||
type OAuthCredential,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { writePrivateSecretFileAtomic } from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
import { prepareCodexAuthBridge } from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
|
||||
const DEFAULT_CODEX_AUTH_PROFILE_ID = "openai-codex:default";
|
||||
const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const CODEX_AUTH_ENV_CLEAR_KEYS = ["OPENAI_API_KEY"] as const;
|
||||
|
||||
function isCodexBridgeableOAuthCredential(value: unknown): value is OAuthCredential {
|
||||
return Boolean(
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"type" in value &&
|
||||
"provider" in value &&
|
||||
"access" in value &&
|
||||
"refresh" in value &&
|
||||
value.type === "oauth" &&
|
||||
value.provider === OPENAI_CODEX_PROVIDER_ID &&
|
||||
typeof value.access === "string" &&
|
||||
value.access.trim().length > 0 &&
|
||||
typeof value.refresh === "string" &&
|
||||
value.refresh.trim().length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCodexBridgeHome(agentDir: string, profileId: string): string {
|
||||
const digest = crypto.createHash("sha256").update(profileId).digest("hex").slice(0, 16);
|
||||
return path.join(agentDir, "harness-auth", "codex", digest);
|
||||
}
|
||||
|
||||
function buildCodexAuthFile(credential: OAuthCredential): string {
|
||||
return `${JSON.stringify(
|
||||
{
|
||||
auth_mode: "chatgpt",
|
||||
tokens: {
|
||||
...(credential.idToken ? { id_token: credential.idToken } : {}),
|
||||
access_token: credential.access,
|
||||
refresh_token: credential.refresh,
|
||||
...(credential.accountId ? { account_id: credential.accountId } : {}),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`;
|
||||
}
|
||||
|
||||
export async function bridgeCodexAppServerStartOptions(params: {
|
||||
startOptions: CodexAppServerStartOptions;
|
||||
@@ -56,27 +9,21 @@ export async function bridgeCodexAppServerStartOptions(params: {
|
||||
authProfileId?: string;
|
||||
}): Promise<CodexAppServerStartOptions> {
|
||||
const profileId = params.authProfileId?.trim() || DEFAULT_CODEX_AUTH_PROFILE_ID;
|
||||
const store = ensureAuthProfileStoreForLocalUpdate(params.agentDir);
|
||||
const credential = store.profiles[profileId];
|
||||
if (!isCodexBridgeableOAuthCredential(credential)) {
|
||||
const bridge = await prepareCodexAuthBridge({
|
||||
agentDir: params.agentDir,
|
||||
bridgeDir: "harness-auth",
|
||||
profileId,
|
||||
});
|
||||
if (!bridge) {
|
||||
return params.startOptions;
|
||||
}
|
||||
|
||||
const codexHome = resolveCodexBridgeHome(params.agentDir, profileId);
|
||||
await writePrivateSecretFileAtomic({
|
||||
rootDir: params.agentDir,
|
||||
filePath: path.join(codexHome, "auth.json"),
|
||||
content: buildCodexAuthFile(credential),
|
||||
});
|
||||
|
||||
return {
|
||||
...params.startOptions,
|
||||
env: {
|
||||
...params.startOptions.env,
|
||||
CODEX_HOME: codexHome,
|
||||
CODEX_HOME: bridge.codexHome,
|
||||
},
|
||||
clearEnv: Array.from(
|
||||
new Set([...(params.startOptions.clearEnv ?? []), ...CODEX_AUTH_ENV_CLEAR_KEYS]),
|
||||
),
|
||||
clearEnv: Array.from(new Set([...(params.startOptions.clearEnv ?? []), ...bridge.clearEnv])),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,56 +1,8 @@
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import type {
|
||||
CliBackendPreparedExecution,
|
||||
CliBackendPrepareExecutionContext,
|
||||
} from "openclaw/plugin-sdk/cli-backend";
|
||||
import {
|
||||
ensureAuthProfileStoreForLocalUpdate,
|
||||
type OAuthCredential,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { writePrivateSecretFileAtomic } from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
|
||||
const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const CODEX_AUTH_ENV_CLEAR_KEYS = ["OPENAI_API_KEY"] as const;
|
||||
|
||||
function isCodexBridgeableOAuthCredential(value: unknown): value is OAuthCredential {
|
||||
return Boolean(
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"type" in value &&
|
||||
"provider" in value &&
|
||||
"access" in value &&
|
||||
"refresh" in value &&
|
||||
value.type === "oauth" &&
|
||||
value.provider === OPENAI_CODEX_PROVIDER_ID &&
|
||||
typeof value.access === "string" &&
|
||||
value.access.trim().length > 0 &&
|
||||
typeof value.refresh === "string" &&
|
||||
value.refresh.trim().length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCodexBridgeHome(agentDir: string, profileId: string): string {
|
||||
const digest = crypto.createHash("sha256").update(profileId).digest("hex").slice(0, 16);
|
||||
return path.join(agentDir, "cli-auth", "codex", digest);
|
||||
}
|
||||
|
||||
function buildCodexAuthFile(credential: OAuthCredential): string {
|
||||
return `${JSON.stringify(
|
||||
{
|
||||
auth_mode: "chatgpt",
|
||||
tokens: {
|
||||
...(credential.idToken ? { id_token: credential.idToken } : {}),
|
||||
access_token: credential.access,
|
||||
refresh_token: credential.refresh,
|
||||
...(credential.accountId ? { account_id: credential.accountId } : {}),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`;
|
||||
}
|
||||
import { prepareCodexAuthBridge } from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
|
||||
export async function prepareOpenAICodexCliExecution(
|
||||
ctx: CliBackendPrepareExecutionContext,
|
||||
@@ -59,23 +11,19 @@ export async function prepareOpenAICodexCliExecution(
|
||||
return null;
|
||||
}
|
||||
|
||||
const store = ensureAuthProfileStoreForLocalUpdate(ctx.agentDir);
|
||||
const credential = store.profiles[ctx.authProfileId];
|
||||
if (!isCodexBridgeableOAuthCredential(credential)) {
|
||||
const bridge = await prepareCodexAuthBridge({
|
||||
agentDir: ctx.agentDir,
|
||||
bridgeDir: "cli-auth",
|
||||
profileId: ctx.authProfileId,
|
||||
});
|
||||
if (!bridge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const codexHome = resolveCodexBridgeHome(ctx.agentDir, ctx.authProfileId);
|
||||
await writePrivateSecretFileAtomic({
|
||||
rootDir: ctx.agentDir,
|
||||
filePath: path.join(codexHome, "auth.json"),
|
||||
content: buildCodexAuthFile(credential),
|
||||
});
|
||||
|
||||
return {
|
||||
env: {
|
||||
CODEX_HOME: codexHome,
|
||||
CODEX_HOME: bridge.codexHome,
|
||||
},
|
||||
clearEnv: [...CODEX_AUTH_ENV_CLEAR_KEYS],
|
||||
clearEnv: bridge.clearEnv,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@ describe("plugin-sdk provider-auth-runtime", () => {
|
||||
it("exports the runtime-ready auth helper", () => {
|
||||
expect(typeof providerAuthRuntime.getRuntimeAuthForModel).toBe("function");
|
||||
});
|
||||
|
||||
it("exports the Codex auth bridge helper", () => {
|
||||
expect(typeof providerAuthRuntime.prepareCodexAuthBridge).toBe("function");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Public runtime auth helpers for provider plugins.
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { ensureAuthProfileStoreForLocalUpdate } from "../agents/auth-profiles/store.js";
|
||||
import type { OAuthCredential } from "../agents/auth-profiles/types.js";
|
||||
import { writePrivateSecretFileAtomic } from "../infra/secret-file.js";
|
||||
|
||||
export { resolveEnvApiKey } from "../agents/model-auth-env.js";
|
||||
export {
|
||||
@@ -18,6 +22,82 @@ export {
|
||||
export type { ProviderPreparedRuntimeAuth } from "../plugins/types.js";
|
||||
export type { ResolvedProviderRuntimeAuth } from "../plugins/runtime/model-auth-types.js";
|
||||
|
||||
export const CODEX_AUTH_ENV_CLEAR_KEYS = ["OPENAI_API_KEY"] as const;
|
||||
|
||||
const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
|
||||
export type PreparedCodexAuthBridge = {
|
||||
codexHome: string;
|
||||
clearEnv: string[];
|
||||
};
|
||||
|
||||
export function isCodexBridgeableOAuthCredential(value: unknown): value is OAuthCredential {
|
||||
return Boolean(
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"type" in value &&
|
||||
"provider" in value &&
|
||||
"access" in value &&
|
||||
"refresh" in value &&
|
||||
value.type === "oauth" &&
|
||||
value.provider === OPENAI_CODEX_PROVIDER_ID &&
|
||||
typeof value.access === "string" &&
|
||||
value.access.trim().length > 0 &&
|
||||
typeof value.refresh === "string" &&
|
||||
value.refresh.trim().length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveCodexAuthBridgeHome(params: {
|
||||
agentDir: string;
|
||||
bridgeDir: string;
|
||||
profileId: string;
|
||||
}): string {
|
||||
const digest = crypto.createHash("sha256").update(params.profileId).digest("hex").slice(0, 16);
|
||||
return path.join(params.agentDir, params.bridgeDir, "codex", digest);
|
||||
}
|
||||
|
||||
export function buildCodexAuthBridgeFile(credential: OAuthCredential): string {
|
||||
return `${JSON.stringify(
|
||||
{
|
||||
auth_mode: "chatgpt",
|
||||
tokens: {
|
||||
...(credential.idToken ? { id_token: credential.idToken } : {}),
|
||||
access_token: credential.access,
|
||||
refresh_token: credential.refresh,
|
||||
...(credential.accountId ? { account_id: credential.accountId } : {}),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`;
|
||||
}
|
||||
|
||||
export async function prepareCodexAuthBridge(params: {
|
||||
agentDir: string;
|
||||
bridgeDir: string;
|
||||
profileId: string;
|
||||
}): Promise<PreparedCodexAuthBridge | undefined> {
|
||||
const store = ensureAuthProfileStoreForLocalUpdate(params.agentDir);
|
||||
const credential = store.profiles[params.profileId];
|
||||
if (!isCodexBridgeableOAuthCredential(credential)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const codexHome = resolveCodexAuthBridgeHome(params);
|
||||
await writePrivateSecretFileAtomic({
|
||||
rootDir: params.agentDir,
|
||||
filePath: path.join(codexHome, "auth.json"),
|
||||
content: buildCodexAuthBridgeFile(credential),
|
||||
});
|
||||
|
||||
return {
|
||||
codexHome,
|
||||
clearEnv: [...CODEX_AUTH_ENV_CLEAR_KEYS],
|
||||
};
|
||||
}
|
||||
|
||||
type ResolveApiKeyForProvider = typeof import("../agents/model-auth.js").resolveApiKeyForProvider;
|
||||
type GetRuntimeAuthForModel =
|
||||
typeof import("../plugins/runtime/runtime-model-auth.runtime.js").getRuntimeAuthForModel;
|
||||
|
||||
Reference in New Issue
Block a user