mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 02:34:32 +00:00
openai-codex: bridge OAuth profiles into pi auth.json for model discovery (#15184)
This commit is contained in:
@@ -91,6 +91,9 @@ export async function loadModelCatalog(params?: {
|
|||||||
try {
|
try {
|
||||||
const cfg = params?.config ?? loadConfig();
|
const cfg = params?.config ?? loadConfig();
|
||||||
await ensureOpenClawModelsJson(cfg);
|
await ensureOpenClawModelsJson(cfg);
|
||||||
|
await (
|
||||||
|
await import("./pi-auth-json.js")
|
||||||
|
).ensurePiAuthJsonFromAuthProfiles(resolveOpenClawAgentDir());
|
||||||
// IMPORTANT: keep the dynamic import *inside* the try/catch.
|
// IMPORTANT: keep the dynamic import *inside* the try/catch.
|
||||||
// If this fails once (e.g. during a pnpm install that temporarily swaps node_modules),
|
// If this fails once (e.g. during a pnpm install that temporarily swaps node_modules),
|
||||||
// we must not poison the cache with a rejected promise (otherwise all channel handlers
|
// we must not poison the cache with a rejected promise (otherwise all channel handlers
|
||||||
|
|||||||
42
src/agents/pi-auth-json.test.ts
Normal file
42
src/agents/pi-auth-json.test.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { saveAuthProfileStore } from "./auth-profiles.js";
|
||||||
|
import { ensurePiAuthJsonFromAuthProfiles } from "./pi-auth-json.js";
|
||||||
|
|
||||||
|
describe("ensurePiAuthJsonFromAuthProfiles", () => {
|
||||||
|
it("writes openai-codex oauth credentials into auth.json for pi-coding-agent discovery", async () => {
|
||||||
|
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||||
|
|
||||||
|
saveAuthProfileStore(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
profiles: {
|
||||||
|
"openai-codex:default": {
|
||||||
|
type: "oauth",
|
||||||
|
provider: "openai-codex",
|
||||||
|
access: "access-token",
|
||||||
|
refresh: "refresh-token",
|
||||||
|
expires: Date.now() + 60_000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
agentDir,
|
||||||
|
);
|
||||||
|
|
||||||
|
const first = await ensurePiAuthJsonFromAuthProfiles(agentDir);
|
||||||
|
expect(first.wrote).toBe(true);
|
||||||
|
|
||||||
|
const authPath = path.join(agentDir, "auth.json");
|
||||||
|
const auth = JSON.parse(await fs.readFile(authPath, "utf8")) as Record<string, unknown>;
|
||||||
|
expect(auth["openai-codex"]).toMatchObject({
|
||||||
|
type: "oauth",
|
||||||
|
access: "access-token",
|
||||||
|
refresh: "refresh-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
const second = await ensurePiAuthJsonFromAuthProfiles(agentDir);
|
||||||
|
expect(second.wrote).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
100
src/agents/pi-auth-json.ts
Normal file
100
src/agents/pi-auth-json.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js";
|
||||||
|
|
||||||
|
type AuthJsonCredential =
|
||||||
|
| {
|
||||||
|
type: "api_key";
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "oauth";
|
||||||
|
access: string;
|
||||||
|
refresh: string;
|
||||||
|
expires: number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AuthJsonShape = Record<string, AuthJsonCredential>;
|
||||||
|
|
||||||
|
async function readAuthJson(filePath: string): Promise<AuthJsonShape> {
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(filePath, "utf8");
|
||||||
|
const parsed = JSON.parse(raw) as unknown;
|
||||||
|
if (!parsed || typeof parsed !== "object") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return parsed as AuthJsonShape;
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pi-coding-agent's ModelRegistry/AuthStorage expects OAuth credentials in auth.json.
|
||||||
|
*
|
||||||
|
* OpenClaw stores OAuth credentials in auth-profiles.json instead. This helper
|
||||||
|
* bridges a subset of credentials into agentDir/auth.json so pi-coding-agent can
|
||||||
|
* (a) consider the provider authenticated and (b) include built-in models in its
|
||||||
|
* registry/catalog output.
|
||||||
|
*
|
||||||
|
* Currently used for openai-codex.
|
||||||
|
*/
|
||||||
|
export async function ensurePiAuthJsonFromAuthProfiles(agentDir: string): Promise<{
|
||||||
|
wrote: boolean;
|
||||||
|
authPath: string;
|
||||||
|
}> {
|
||||||
|
const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||||
|
const codexProfiles = listProfilesForProvider(store, "openai-codex");
|
||||||
|
if (codexProfiles.length === 0) {
|
||||||
|
return { wrote: false, authPath: path.join(agentDir, "auth.json") };
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileId = codexProfiles[0];
|
||||||
|
const cred = profileId ? store.profiles[profileId] : undefined;
|
||||||
|
if (!cred || cred.type !== "oauth") {
|
||||||
|
return { wrote: false, authPath: path.join(agentDir, "auth.json") };
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessRaw = (cred as { access?: unknown }).access;
|
||||||
|
const refreshRaw = (cred as { refresh?: unknown }).refresh;
|
||||||
|
const expiresRaw = (cred as { expires?: unknown }).expires;
|
||||||
|
|
||||||
|
const access = typeof accessRaw === "string" ? accessRaw.trim() : "";
|
||||||
|
const refresh = typeof refreshRaw === "string" ? refreshRaw.trim() : "";
|
||||||
|
const expires = typeof expiresRaw === "number" ? expiresRaw : Number.NaN;
|
||||||
|
|
||||||
|
if (!access || !refresh || !Number.isFinite(expires) || expires <= 0) {
|
||||||
|
return { wrote: false, authPath: path.join(agentDir, "auth.json") };
|
||||||
|
}
|
||||||
|
|
||||||
|
const authPath = path.join(agentDir, "auth.json");
|
||||||
|
const next = await readAuthJson(authPath);
|
||||||
|
|
||||||
|
const existing = next["openai-codex"];
|
||||||
|
const desired: AuthJsonCredential = {
|
||||||
|
type: "oauth",
|
||||||
|
access,
|
||||||
|
refresh,
|
||||||
|
expires,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSame =
|
||||||
|
existing &&
|
||||||
|
typeof existing === "object" &&
|
||||||
|
(existing as { type?: unknown }).type === "oauth" &&
|
||||||
|
(existing as { access?: unknown }).access === access &&
|
||||||
|
(existing as { refresh?: unknown }).refresh === refresh &&
|
||||||
|
(existing as { expires?: unknown }).expires === expires;
|
||||||
|
|
||||||
|
if (isSame) {
|
||||||
|
return { wrote: false, authPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
next["openai-codex"] = desired;
|
||||||
|
|
||||||
|
await fs.mkdir(agentDir, { recursive: true, mode: 0o700 });
|
||||||
|
await fs.writeFile(authPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
||||||
|
|
||||||
|
return { wrote: true, authPath };
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
resolveEnvApiKey,
|
resolveEnvApiKey,
|
||||||
} from "../../agents/model-auth.js";
|
} from "../../agents/model-auth.js";
|
||||||
import { ensureOpenClawModelsJson } from "../../agents/models-config.js";
|
import { ensureOpenClawModelsJson } from "../../agents/models-config.js";
|
||||||
|
import { ensurePiAuthJsonFromAuthProfiles } from "../../agents/pi-auth-json.js";
|
||||||
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
|
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
|
||||||
import { modelKey } from "./shared.js";
|
import { modelKey } from "./shared.js";
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ const hasAuthForProvider = (provider: string, cfg: OpenClawConfig, authStore: Au
|
|||||||
export async function loadModelRegistry(cfg: OpenClawConfig) {
|
export async function loadModelRegistry(cfg: OpenClawConfig) {
|
||||||
await ensureOpenClawModelsJson(cfg);
|
await ensureOpenClawModelsJson(cfg);
|
||||||
const agentDir = resolveOpenClawAgentDir();
|
const agentDir = resolveOpenClawAgentDir();
|
||||||
|
await ensurePiAuthJsonFromAuthProfiles(agentDir);
|
||||||
const authStorage = discoverAuthStorage(agentDir);
|
const authStorage = discoverAuthStorage(agentDir);
|
||||||
const registry = discoverModels(authStorage, agentDir);
|
const registry = discoverModels(authStorage, agentDir);
|
||||||
const models = registry.getAll();
|
const models = registry.getAll();
|
||||||
|
|||||||
Reference in New Issue
Block a user