fix(cli-tools): send the API key Codex actually reads
Codex only authenticates a custom model provider from env_key, http_headers, env_http_headers or a token command — auth.json is read solely by the built-in openai provider. Writing OPENAI_API_KEY there left every request unauthenticated (401 Missing API key) while clobbering an existing ChatGPT login. Put the key in [model_providers.9router.http_headers] instead, and drop the auth.json write. Also move the subagent model to the agents.default_subagent_model scalar: agents.<role> now declares a custom role and requires a description, so the old [agents.subagent] table was discarded with a startup warning. DELETE still clears auth.json to repair machines configured by the previous version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
|
|||||||
if (modelMatch) setSelectedModel(modelMatch[1]);
|
if (modelMatch) setSelectedModel(modelMatch[1]);
|
||||||
|
|
||||||
// Parse subagent settings
|
// Parse subagent settings
|
||||||
const subagentModelMatch = codexStatus.config.match(/\[agents\.subagent\]\s*\n\s*model\s*=\s*"([^"]+)"/m);
|
const subagentModelMatch = codexStatus.config.match(/^default_subagent_model\s*=\s*"([^"]+)"/m);
|
||||||
if (subagentModelMatch) setSubagentModel(subagentModelMatch[1]);
|
if (subagentModelMatch) setSubagentModel(subagentModelMatch[1]);
|
||||||
}
|
}
|
||||||
}, [codexStatus]);
|
}, [codexStatus]);
|
||||||
@@ -172,24 +172,18 @@ name = "9Router"
|
|||||||
base_url = "${getEffectiveBaseUrl()}"
|
base_url = "${getEffectiveBaseUrl()}"
|
||||||
wire_api = "responses"
|
wire_api = "responses"
|
||||||
|
|
||||||
[agents.subagent]
|
[model_providers.9router.http_headers]
|
||||||
model = "${effectiveSubagentModel}"
|
Authorization = "Bearer ${keyToUse}"
|
||||||
`;
|
|
||||||
|
|
||||||
const authContent = JSON.stringify({
|
[agents]
|
||||||
auth_mode: "apikey",
|
default_subagent_model = "${effectiveSubagentModel}"
|
||||||
OPENAI_API_KEY: keyToUse
|
`;
|
||||||
}, null, 2);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
filename: "~/.codex/config.toml",
|
filename: "~/.codex/config.toml",
|
||||||
content: configContent,
|
content: configContent,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
filename: "~/.codex/auth.json",
|
|
||||||
content: authContent,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,7 +248,7 @@ model = "${effectiveSubagentModel}"
|
|||||||
<p className="text-text-muted">After installation, run <code className="px-1 bg-black/5 dark:bg-white/5 rounded">codex</code> to verify.</p>
|
<p className="text-text-muted">After installation, run <code className="px-1 bg-black/5 dark:bg-white/5 rounded">codex</code> to verify.</p>
|
||||||
<div className="pt-2 border-t border-border">
|
<div className="pt-2 border-t border-border">
|
||||||
<p className="text-text-muted text-xs">
|
<p className="text-text-muted text-xs">
|
||||||
Codex uses <code className="px-1 bg-black/5 dark:bg-white/5 rounded">~/.codex/auth.json</code> with <code className="px-1 bg-black/5 dark:bg-white/5 rounded">OPENAI_API_KEY</code>.
|
Codex reads custom providers from <code className="px-1 bg-black/5 dark:bg-white/5 rounded">~/.codex/config.toml</code>.
|
||||||
Click "Apply" to auto-configure.
|
Click "Apply" to auto-configure.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,35 +135,22 @@ export async function POST(request) {
|
|||||||
// Update or create 9router provider section (no api_key - Codex reads from auth.json)
|
// Update or create 9router provider section (no api_key - Codex reads from auth.json)
|
||||||
// Ensure /v1 suffix is added only once
|
// Ensure /v1 suffix is added only once
|
||||||
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
||||||
|
// Custom providers ignore auth.json - the key must travel as a static header
|
||||||
setNestedSection(parsed, "model_providers.9router", {
|
setNestedSection(parsed, "model_providers.9router", {
|
||||||
name: "9Router",
|
name: "9Router",
|
||||||
base_url: normalizedBaseUrl,
|
base_url: normalizedBaseUrl,
|
||||||
wire_api: "responses",
|
wire_api: "responses",
|
||||||
|
http_headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add subagent configuration
|
// Subagent model is a scalar under [agents]; agents.<role> now means a custom role
|
||||||
const effectiveSubagentModel = subagentModel || model;
|
deleteNestedSection(parsed, "agents.subagent");
|
||||||
setNestedSection(parsed, "agents.subagent", {
|
setNestedSection(parsed, "agents.default_subagent_model", subagentModel || model);
|
||||||
model: effectiveSubagentModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Write merged config
|
// Write merged config
|
||||||
const configContent = stringifyTOML(parsed);
|
const configContent = stringifyTOML(parsed);
|
||||||
await fs.writeFile(configPath, configContent);
|
await fs.writeFile(configPath, configContent);
|
||||||
|
|
||||||
// Update auth.json with OPENAI_API_KEY (Codex reads this first)
|
|
||||||
const authPath = getCodexAuthPath();
|
|
||||||
let authData = {};
|
|
||||||
try {
|
|
||||||
const existingAuth = await fs.readFile(authPath, "utf-8");
|
|
||||||
authData = JSON.parse(existingAuth);
|
|
||||||
} catch { /* No existing auth */ }
|
|
||||||
|
|
||||||
// Force apikey mode (keep existing tokens untouched for ChatGPT login reuse)
|
|
||||||
authData.OPENAI_API_KEY = apiKey;
|
|
||||||
authData.auth_mode = "apikey";
|
|
||||||
await fs.writeFile(authPath, JSON.stringify(authData, null, 2));
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Codex settings applied successfully!",
|
message: "Codex settings applied successfully!",
|
||||||
@@ -204,7 +191,8 @@ export async function DELETE() {
|
|||||||
// Remove 9router provider section
|
// Remove 9router provider section
|
||||||
deleteNestedSection(parsed, "model_providers.9router");
|
deleteNestedSection(parsed, "model_providers.9router");
|
||||||
|
|
||||||
// Remove subagent configuration
|
// Remove subagent configuration (both the current key and the legacy role form)
|
||||||
|
deleteNestedSection(parsed, "agents.default_subagent_model");
|
||||||
deleteNestedSection(parsed, "agents.subagent");
|
deleteNestedSection(parsed, "agents.subagent");
|
||||||
|
|
||||||
// Write updated config
|
// Write updated config
|
||||||
|
|||||||
Reference in New Issue
Block a user