fix(worker): codex prepareAuth skips auth.json for api_key profiles
This commit is contained in:
@@ -13,7 +13,11 @@ import {
|
||||
streamExecInContainer,
|
||||
} from './docker';
|
||||
import type { AdapterWorkspace } from './provider';
|
||||
import { codexModelArgs, providerEnvironment } from './provider';
|
||||
import {
|
||||
codexModelArgs,
|
||||
isCodexLoginProfile,
|
||||
providerEnvironment,
|
||||
} from './provider';
|
||||
|
||||
// Reused verbatim from the worker: normalize + pretty-print an auth JSON blob
|
||||
// before writing it with owner-only permissions.
|
||||
@@ -41,6 +45,11 @@ export const createCodexAdapter: AdapterFactory = (deps) => {
|
||||
const execStream = deps?.execStream ?? streamExecInContainer;
|
||||
|
||||
const prepareAuth = async (workspace: AdapterWorkspace) => {
|
||||
// API-key profiles (e.g. OpenAI api_key) authenticate purely via
|
||||
// OPENAI_API_KEY from providerEnvironment; only ChatGPT-login/JSON-snapshot
|
||||
// profiles need a Codex `auth.json` on disk. Mirror the Claude adapter,
|
||||
// which early-returns for non-OAuth profiles.
|
||||
if (!isCodexLoginProfile(workspace.claim)) return;
|
||||
const secret = workspace.claim.aiProviderProfile?.secret;
|
||||
if (!secret) {
|
||||
throw new Error('Codex auth profile is missing auth.json contents.');
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { mkdir, mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { NormalizedAgentEvent } from '../../src/agent-events';
|
||||
import type { ExecStreamFn } from '../../src/runtime/agent-runtime';
|
||||
import type { AdapterWorkspace, Claim } from '../../src/runtime/provider';
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
const pathExists = async (filePath: string): Promise<boolean> => {
|
||||
try {
|
||||
await stat(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadAdapter = async () => {
|
||||
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
|
||||
process.env.GITHUB_APP_ID = '123';
|
||||
@@ -51,7 +66,88 @@ const makeWorkspace = (): AdapterWorkspace => ({
|
||||
runtime: 'codex',
|
||||
});
|
||||
|
||||
const makeAuthWorkspace = async (
|
||||
profile: Record<string, unknown>,
|
||||
): Promise<AdapterWorkspace> => {
|
||||
const workdir = await mkdtemp(path.join(os.tmpdir(), 'spoon-codex-auth-'));
|
||||
tempDirs.push(workdir);
|
||||
const repoDir = path.join(workdir, 'Code', 'spoon');
|
||||
await mkdir(repoDir, { recursive: true });
|
||||
return {
|
||||
...makeWorkspace(),
|
||||
claim: { ...claim, aiProviderProfile: profile } as unknown as Claim,
|
||||
workdir,
|
||||
homeDir: workdir,
|
||||
repoDir,
|
||||
};
|
||||
};
|
||||
|
||||
describe('CodexAdapter', () => {
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
tempDirs.map((dir) => rm(dir, { force: true, recursive: true })),
|
||||
);
|
||||
tempDirs.length = 0;
|
||||
});
|
||||
|
||||
test('prepareAuth skips auth.json for an api_key profile', async () => {
|
||||
const { createCodexAdapter } = await loadAdapter();
|
||||
const adapter = createCodexAdapter();
|
||||
const workspace = await makeAuthWorkspace({
|
||||
id: 'p',
|
||||
name: 'OpenAI',
|
||||
provider: 'openai',
|
||||
authType: 'api_key',
|
||||
model: 'gpt-5',
|
||||
// A raw API key, which is deliberately NOT valid JSON.
|
||||
secret: 'sk-test',
|
||||
});
|
||||
|
||||
await expect(adapter.prepareAuth?.(workspace)).resolves.toBeUndefined();
|
||||
|
||||
const codexAuthPath = path.join(workspace.workdir, '.codex', 'auth.json');
|
||||
const openCodeAuthPath = path.join(
|
||||
workspace.workdir,
|
||||
'.local',
|
||||
'share',
|
||||
'opencode',
|
||||
'auth.json',
|
||||
);
|
||||
await expect(pathExists(codexAuthPath)).resolves.toBe(false);
|
||||
await expect(pathExists(openCodeAuthPath)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test('prepareAuth writes auth.json for a ChatGPT-login profile', async () => {
|
||||
const { createCodexAdapter } = await loadAdapter();
|
||||
const adapter = createCodexAdapter();
|
||||
const secret = JSON.stringify({ tokens: { access: 'abc' } });
|
||||
const workspace = await makeAuthWorkspace({
|
||||
id: 'p',
|
||||
name: 'Codex',
|
||||
provider: 'opencode_openai_login',
|
||||
authType: 'opencode_auth_json',
|
||||
model: 'gpt-5',
|
||||
secret,
|
||||
});
|
||||
|
||||
await adapter.prepareAuth?.(workspace);
|
||||
|
||||
const codexAuthPath = path.join(workspace.workdir, '.codex', 'auth.json');
|
||||
await expect(readFile(codexAuthPath, 'utf8')).resolves.toBe(
|
||||
`${JSON.stringify(JSON.parse(secret), null, 2)}\n`,
|
||||
);
|
||||
const openCodeAuthPath = path.join(
|
||||
workspace.workdir,
|
||||
'.local',
|
||||
'share',
|
||||
'opencode',
|
||||
'auth.json',
|
||||
);
|
||||
await expect(readFile(openCodeAuthPath, 'utf8')).resolves.toBe(
|
||||
`${JSON.stringify(JSON.parse(secret), null, 2)}\n`,
|
||||
);
|
||||
});
|
||||
|
||||
test('streams normalized events and marks the turn via buildMarkedCommand', async () => {
|
||||
const { createCodexAdapter } = await loadAdapter();
|
||||
const captured: unknown[] = [];
|
||||
|
||||
Reference in New Issue
Block a user