Merge pull request #1702 from paperclipai/fix/codex-auth-check
fix(codex): check native auth before warning about missing API key
This commit is contained in:
commit
fea892c8b3
3 changed files with 88 additions and 9 deletions
|
|
@ -107,8 +107,8 @@ function parsePlanAndEmailFromToken(idToken: string | null, accessToken: string
|
||||||
return { email: null, planType: null };
|
return { email: null, planType: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readCodexAuthInfo(): Promise<CodexAuthInfo | null> {
|
export async function readCodexAuthInfo(codexHome?: string): Promise<CodexAuthInfo | null> {
|
||||||
const authPath = path.join(codexHomeDir(), "auth.json");
|
const authPath = path.join(codexHome ?? codexHomeDir(), "auth.json");
|
||||||
let raw: string;
|
let raw: string;
|
||||||
try {
|
try {
|
||||||
raw = await fs.readFile(authPath, "utf8");
|
raw = await fs.readFile(authPath, "utf8");
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { parseCodexJsonl } from "./parse.js";
|
import { parseCodexJsonl } from "./parse.js";
|
||||||
|
import { codexHomeDir, readCodexAuthInfo } from "./quota.js";
|
||||||
|
|
||||||
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||||||
if (checks.some((check) => check.level === "error")) return "fail";
|
if (checks.some((check) => check.level === "error")) return "fail";
|
||||||
|
|
@ -108,12 +109,23 @@ export async function testEnvironment(
|
||||||
detail: `Detected in ${source}.`,
|
detail: `Detected in ${source}.`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
checks.push({
|
const codexHome = isNonEmpty(env.CODEX_HOME) ? env.CODEX_HOME : undefined;
|
||||||
code: "codex_openai_api_key_missing",
|
const codexAuth = await readCodexAuthInfo(codexHome).catch(() => null);
|
||||||
level: "warn",
|
if (codexAuth) {
|
||||||
message: "OPENAI_API_KEY is not set. Codex runs may fail until authentication is configured.",
|
checks.push({
|
||||||
hint: "Set OPENAI_API_KEY in adapter env, shell environment, or Codex auth configuration.",
|
code: "codex_native_auth_present",
|
||||||
});
|
level: "info",
|
||||||
|
message: "Codex is authenticated via its own auth configuration.",
|
||||||
|
detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${path.join(codexHome ?? codexHomeDir(), "auth.json")}.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
checks.push({
|
||||||
|
code: "codex_openai_api_key_missing",
|
||||||
|
level: "warn",
|
||||||
|
message: "OPENAI_API_KEY is not set. Codex runs may fail until authentication is configured.",
|
||||||
|
hint: "Set OPENAI_API_KEY in adapter env, shell environment, or run `codex auth` to log in.",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRunProbe =
|
const canRunProbe =
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
@ -7,6 +7,12 @@ import { testEnvironment } from "@paperclipai/adapter-codex-local/server";
|
||||||
const itWindows = process.platform === "win32" ? it : it.skip;
|
const itWindows = process.platform === "win32" ? it : it.skip;
|
||||||
|
|
||||||
describe("codex_local environment diagnostics", () => {
|
describe("codex_local environment diagnostics", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv("OPENAI_API_KEY", "");
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
});
|
||||||
it("creates a missing working directory when cwd is absolute", async () => {
|
it("creates a missing working directory when cwd is absolute", async () => {
|
||||||
const cwd = path.join(
|
const cwd = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
|
|
@ -32,6 +38,67 @@ describe("codex_local environment diagnostics", () => {
|
||||||
await fs.rm(path.dirname(cwd), { recursive: true, force: true });
|
await fs.rm(path.dirname(cwd), { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits codex_native_auth_present when ~/.codex/auth.json exists and OPENAI_API_KEY is unset", async () => {
|
||||||
|
const root = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`paperclip-codex-auth-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
const codexHome = path.join(root, ".codex");
|
||||||
|
const cwd = path.join(root, "workspace");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(codexHome, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(codexHome, "auth.json"),
|
||||||
|
JSON.stringify({ accessToken: "fake-token", accountId: "acct-1" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await testEnvironment({
|
||||||
|
companyId: "company-1",
|
||||||
|
adapterType: "codex_local",
|
||||||
|
config: {
|
||||||
|
command: process.execPath,
|
||||||
|
cwd,
|
||||||
|
env: { CODEX_HOME: codexHome },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.checks.some((check) => check.code === "codex_native_auth_present")).toBe(true);
|
||||||
|
expect(result.checks.some((check) => check.code === "codex_openai_api_key_missing")).toBe(false);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits codex_openai_api_key_missing when neither env var nor native auth exists", async () => {
|
||||||
|
const root = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`paperclip-codex-noauth-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
const codexHome = path.join(root, ".codex");
|
||||||
|
const cwd = path.join(root, "workspace");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(codexHome, { recursive: true });
|
||||||
|
// No auth.json written
|
||||||
|
|
||||||
|
const result = await testEnvironment({
|
||||||
|
companyId: "company-1",
|
||||||
|
adapterType: "codex_local",
|
||||||
|
config: {
|
||||||
|
command: process.execPath,
|
||||||
|
cwd,
|
||||||
|
env: { CODEX_HOME: codexHome },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.checks.some((check) => check.code === "codex_openai_api_key_missing")).toBe(true);
|
||||||
|
expect(result.checks.some((check) => check.code === "codex_native_auth_present")).toBe(false);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
itWindows("runs the hello probe when Codex is available via a Windows .cmd wrapper", async () => {
|
itWindows("runs the hello probe when Codex is available via a Windows .cmd wrapper", async () => {
|
||||||
const root = path.join(
|
const root = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue