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:
decolua
2026-08-27 18:52:32 +07:00
parent e6f5724b4b
commit 9c45b27cd7
2 changed files with 14 additions and 32 deletions

View File

@@ -57,7 +57,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
if (modelMatch) setSelectedModel(modelMatch[1]);
// 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]);
}
}, [codexStatus]);
@@ -172,24 +172,18 @@ name = "9Router"
base_url = "${getEffectiveBaseUrl()}"
wire_api = "responses"
[agents.subagent]
model = "${effectiveSubagentModel}"
`;
[model_providers.9router.http_headers]
Authorization = "Bearer ${keyToUse}"
const authContent = JSON.stringify({
auth_mode: "apikey",
OPENAI_API_KEY: keyToUse
}, null, 2);
[agents]
default_subagent_model = "${effectiveSubagentModel}"
`;
return [
{
filename: "~/.codex/config.toml",
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>
<div className="pt-2 border-t border-border">
<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 &quot;Apply&quot; to auto-configure.
</p>
</div>

View File

@@ -135,35 +135,22 @@ export async function POST(request) {
// Update or create 9router provider section (no api_key - Codex reads from auth.json)
// Ensure /v1 suffix is added only once
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", {
name: "9Router",
base_url: normalizedBaseUrl,
wire_api: "responses",
http_headers: { Authorization: `Bearer ${apiKey}` },
});
// Add subagent configuration
const effectiveSubagentModel = subagentModel || model;
setNestedSection(parsed, "agents.subagent", {
model: effectiveSubagentModel,
});
// Subagent model is a scalar under [agents]; agents.<role> now means a custom role
deleteNestedSection(parsed, "agents.subagent");
setNestedSection(parsed, "agents.default_subagent_model", subagentModel || model);
// Write merged config
const configContent = stringifyTOML(parsed);
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({
success: true,
message: "Codex settings applied successfully!",
@@ -204,7 +191,8 @@ export async function DELETE() {
// Remove 9router provider section
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");
// Write updated config