Merge pull request #870 from kaitranntt/kai/fix/869-codex-config-override-probe

fix: probe Codex config override support directly
This commit is contained in:
Kai (Tam Nhu) Tran
2026-04-01 10:20:31 -04:00
committed by GitHub
10 changed files with 449 additions and 48 deletions
+1
View File
@@ -87,6 +87,7 @@
"ui:build": "cd ui && bun run build",
"ui:preview": "cd ui && bun run preview",
"ui:validate": "cd ui && bun run validate",
"prepack": "bun run build:all",
"prepare": "husky",
"postinstall": "node scripts/postinstall.js"
},
+91 -3
View File
@@ -2,6 +2,7 @@ import { ChildProcess, spawn } from 'child_process';
import * as fs from 'fs';
import type { ProfileType } from '../types/profile';
import { runCleanup } from '../errors';
import { expandPath } from '../utils/helpers';
import { wireChildProcessSignals } from '../utils/signal-forwarder';
import { escapeShellArg, stripAnthropicEnv, stripCodexSessionEnv } from '../utils/shell-executor';
import type {
@@ -20,6 +21,7 @@ import {
const CODEX_RUNTIME_PROVIDER_ID = 'ccs_runtime';
const CODEX_RUNTIME_ENV_KEY = 'CCS_CODEX_API_KEY';
const CODEX_REASONING_LEVELS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
const CODEX_INFO_FLAGS = new Set(['--help', '-h', '--version', '-v']);
function formatTomlString(value: string): string {
return JSON.stringify(value);
@@ -83,6 +85,85 @@ function normalizeCodexReasoningOverride(value: string | number | undefined): st
);
}
function isInformationalCodexInvocation(args: string[]): boolean {
if (args.length === 1) {
return CODEX_INFO_FLAGS.has(args[0] || '');
}
if (args.length === 2) {
return CODEX_INFO_FLAGS.has(args[1] || '');
}
return false;
}
function normalizeExplicitCodexHomeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const rawCodexHome = env.CODEX_HOME;
if (rawCodexHome === undefined) {
return env;
}
const trimmedCodexHome = rawCodexHome.trim();
if (!trimmedCodexHome) {
const nextEnv = { ...env };
delete nextEnv.CODEX_HOME;
return nextEnv;
}
const normalizedCodexHome = expandPath(trimmedCodexHome);
if (normalizedCodexHome === rawCodexHome) {
return env;
}
return {
...env,
CODEX_HOME: normalizedCodexHome,
};
}
function prepareExplicitCodexHome(
env: NodeJS.ProcessEnv,
args: string[]
): { env: NodeJS.ProcessEnv; error?: string } {
const normalizedEnv = normalizeExplicitCodexHomeEnv(env);
const codexHome = normalizedEnv.CODEX_HOME;
if (!codexHome) {
return { env: normalizedEnv };
}
if (isInformationalCodexInvocation(args)) {
return { env: normalizedEnv };
}
try {
fs.mkdirSync(codexHome, { recursive: true });
} catch (err) {
const error = err as NodeJS.ErrnoException;
if (error.code !== 'EEXIST') {
return {
env: normalizedEnv,
error: `[X] Unable to initialize CODEX_HOME (${error.code || 'unknown'}): ${codexHome}`,
};
}
}
try {
if (!fs.statSync(codexHome).isDirectory()) {
return {
env: normalizedEnv,
error: `[X] CODEX_HOME path is not a directory: ${codexHome}`,
};
}
return { env: normalizedEnv };
} catch (err) {
const error = err as NodeJS.ErrnoException;
return {
env: normalizedEnv,
error: `[X] Unable to access CODEX_HOME (${error.code || 'unknown'}): ${codexHome}`,
};
}
}
export class CodexAdapter implements TargetAdapter {
readonly type: TargetType = 'codex';
readonly displayName = 'Codex CLI';
@@ -207,6 +288,13 @@ export class CodexAdapter implements TargetAdapter {
return exitWithCleanup(1);
}
const codexHomePreparation = prepareExplicitCodexHome(env, args);
if (codexHomePreparation.error) {
console.error(codexHomePreparation.error);
return exitWithCleanup(1);
}
const launchEnv = codexHomePreparation.env;
const isWindows = process.platform === 'win32';
const isPowerShellScript = isWindows && /\.ps1$/i.test(codexPath);
const needsShell = isWindows && /\.(cmd|bat)$/i.test(codexPath);
@@ -216,7 +304,7 @@ export class CodexAdapter implements TargetAdapter {
child = spawn(
'powershell.exe',
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', codexPath, ...args],
{ stdio: 'inherit', windowsHide: true, env }
{ stdio: 'inherit', windowsHide: true, env: launchEnv }
);
} else if (needsShell) {
const cmdString = [codexPath, ...args].map(escapeShellArg).join(' ');
@@ -224,10 +312,10 @@ export class CodexAdapter implements TargetAdapter {
stdio: 'inherit',
windowsHide: true,
shell: true,
env,
env: launchEnv,
});
} else {
child = spawn(codexPath, args, { stdio: 'inherit', windowsHide: true, env });
child = spawn(codexPath, args, { stdio: 'inherit', windowsHide: true, env: launchEnv });
}
wireChildProcessSignals(child, (err: NodeJS.ErrnoException) => {
+35 -7
View File
@@ -5,6 +5,23 @@ import { escapeShellArg } from '../utils/shell-executor';
import type { TargetBinaryInfo } from './target-adapter';
const CODEX_CONFIG_OVERRIDE_FEATURE = 'config-overrides';
const CODEX_CONFIG_OVERRIDE_PROBE_ARGS = ['-c', 'model="gpt-5"', '--version'];
function buildWindowsCodexCandidates(matches: string[]): string[] {
const shellCandidates = matches.filter((entry) => /\.(exe|cmd|bat|ps1)$/i.test(entry));
const bareCandidates = matches.filter((entry) => !/\.(exe|cmd|bat|ps1)$/i.test(entry));
const prioritized: string[] = [];
for (const entry of shellCandidates) {
if (/\.(cmd|bat)$/i.test(entry)) {
prioritized.push(entry.replace(/\.(cmd|bat)$/i, '.ps1'));
}
prioritized.push(entry);
}
prioritized.push(...bareCandidates);
return [...new Set(prioritized)];
}
function runCodexProbe(codexPath: string, args: string[]): string | undefined {
const isWindows = process.platform === 'win32';
@@ -49,9 +66,25 @@ export function readCodexVersion(codexPath: string): string | undefined {
return runCodexProbe(codexPath, ['--version'])?.trim();
}
function codexHelpAdvertisesConfigOverrides(helpText: string | undefined): boolean {
if (!helpText) {
return false;
}
return /(^|\n)\s*-c,\s*--config\b/m.test(helpText) || helpText.includes('--config <key=value>');
}
function codexSupportsConfigOverrideProbe(codexPath: string): boolean {
return !!runCodexProbe(codexPath, CODEX_CONFIG_OVERRIDE_PROBE_ARGS)?.trim();
}
function detectCodexFeatures(codexPath: string): readonly string[] {
if (codexSupportsConfigOverrideProbe(codexPath)) {
return [CODEX_CONFIG_OVERRIDE_FEATURE];
}
const helpText = runCodexProbe(codexPath, ['--help']);
return helpText?.includes('--config <key=value>') ? [CODEX_CONFIG_OVERRIDE_FEATURE] : [];
return codexHelpAdvertisesConfigOverrides(helpText) ? [CODEX_CONFIG_OVERRIDE_FEATURE] : [];
}
export function detectCodexCli(): string | null {
@@ -95,12 +128,7 @@ export function detectCodexCli(): string | null {
.map((entry) => entry.trim())
.filter(Boolean);
const candidates = isWindows
? [
...matches.filter((entry) => /\.(exe|cmd|bat|ps1)$/i.test(entry)),
...matches.filter((entry) => !/\.(exe|cmd|bat|ps1)$/i.test(entry)),
]
: matches;
const candidates = isWindows ? buildWindowsCodexCandidates(matches) : matches;
for (const candidate of candidates) {
try {
@@ -646,7 +646,7 @@ export function summarizeCodexMcpServers(value: unknown): CodexMcpServerDiagnost
.sort((left, right) => left.name.localeCompare(right.name));
}
function getCodexSupportMatrix(): CodexSupportMatrixEntry[] {
function getCodexSupportMatrix(supportsManagedRouting: boolean): CodexSupportMatrixEntry[] {
return [
{
id: 'default',
@@ -657,14 +657,18 @@ function getCodexSupportMatrix(): CodexSupportMatrixEntry[] {
{
id: 'cliproxy-provider-codex',
label: 'cliproxy provider=codex',
supported: true,
notes: 'Routed through the CLIProxy Codex Responses bridge.',
supported: supportsManagedRouting,
notes: supportsManagedRouting
? 'Routed through the CLIProxy Codex Responses bridge.'
: 'Requires a Codex build that exposes --config overrides.',
},
{
id: 'settings-with-bridge',
label: 'settings with bridge metadata',
supported: true,
notes: 'Supported when the resolved API profile points at a Codex CLIProxy bridge.',
supported: supportsManagedRouting,
notes: supportsManagedRouting
? 'Supported when the resolved API profile points at a Codex CLIProxy bridge.'
: 'Requires a Codex build that exposes --config overrides.',
},
{
id: 'cliproxy-composite',
@@ -696,6 +700,7 @@ function getCodexSupportMatrix(): CodexSupportMatrixEntry[] {
export async function getCodexDashboardDiagnostics(): Promise<CodexDashboardDiagnostics> {
const paths = resolveCodexConfigPaths();
const binaryInfo = getCodexBinaryInfo();
const supportsConfigOverrides = !!binaryInfo && codexBinarySupportsConfigOverrides(binaryInfo);
const docsReference = getCompatibleCliDocsReference('codex');
const fileProbe = await probeTomlObjectFile(
paths.configPath,
@@ -715,12 +720,12 @@ export async function getCodexDashboardDiagnostics(): Promise<CodexDashboardDiag
const features = summarizeCodexFeatureFlags(config?.features);
const projectTrust = summarizeCodexProjectTrust(config?.projects);
const mcpServers = summarizeCodexMcpServers(config?.mcp_servers);
const supportMatrix = getCodexSupportMatrix();
const supportMatrix = getCodexSupportMatrix(supportsConfigOverrides);
const warnings: string[] = [];
if (!binaryInfo) {
warnings.push('Codex binary is not detected in PATH or CCS_CODEX_PATH.');
} else if (!codexBinarySupportsConfigOverrides(binaryInfo)) {
} else if (!supportsConfigOverrides) {
warnings.push(
'This Codex build does not expose --config overrides required for CCS-backed Codex routing.'
);
@@ -766,7 +771,7 @@ export async function getCodexDashboardDiagnostics(): Promise<CodexDashboardDiag
source: process.env.CCS_CODEX_PATH ? 'CCS_CODEX_PATH' : binaryInfo ? 'PATH' : 'missing',
version: binaryInfo?.version ?? null,
overridePath: process.env.CCS_CODEX_PATH || null,
supportsConfigOverrides: codexBinarySupportsConfigOverrides(binaryInfo),
supportsConfigOverrides,
},
file: fileProbe.diagnostics,
workspacePath: process.cwd(),
+5
View File
@@ -175,6 +175,11 @@ describe('cross-platform', () => {
'dedicated ccsxp shortcut entrypoint should exist'
);
assert(packageJson.scripts, 'package.json should have scripts field');
assert.strictEqual(
packageJson.scripts.prepack,
'bun run build:all',
'prepack should rebuild packaged assets before npm pack/publish'
);
});
});
});
+66
View File
@@ -72,4 +72,70 @@ describe('codex-detector', () => {
execFileSyncSpy.mockRestore();
});
it('prefers a sibling PowerShell wrapper over cmd when Windows PATH only exposes codex.cmd', () => {
const fakeCmdCodex = path.join(tmpDir, 'codex.cmd');
const fakePsCodex = path.join(tmpDir, 'codex.ps1');
fs.writeFileSync(fakeCmdCodex, '');
fs.writeFileSync(fakePsCodex, '');
Object.defineProperty(process, 'platform', { value: 'win32' });
const execSyncSpy = spyOn(childProcess, 'execSync').mockImplementation(() => `${fakeCmdCodex}\n`);
expect(detectCodexCli()).toBe(fakePsCodex);
execSyncSpy.mockRestore();
});
it('falls back to a direct -c probe when help text omits the config flag', () => {
const fakeCodex = path.join(tmpDir, 'codex');
fs.writeFileSync(fakeCodex, '');
process.env.CCS_CODEX_PATH = fakeCodex;
const execFileSyncSpy = spyOn(childProcess, 'execFileSync').mockImplementation((command, args) => {
const joinedArgs = Array.isArray(args) ? args.join(' ') : '';
if (joinedArgs.includes('--help')) {
return 'Codex CLI\n';
}
if (joinedArgs.includes('-c') && joinedArgs.includes('--version')) {
return 'codex-cli 0.119.0-alpha.1';
}
return 'codex-cli 0.119.0-alpha.1';
});
const info = getCodexBinaryInfo();
expect(info?.features).toContain('config-overrides');
execFileSyncSpy.mockRestore();
});
it('still detects support from broader help text when the direct probe fails', () => {
const fakeCodex = path.join(tmpDir, 'codex');
fs.writeFileSync(fakeCodex, '');
process.env.CCS_CODEX_PATH = fakeCodex;
const execFileSyncSpy = spyOn(childProcess, 'execFileSync').mockImplementation((command, args) => {
const joinedArgs = Array.isArray(args) ? args.join(' ') : '';
if (joinedArgs.includes('--help')) {
return 'Codex CLI\n -c, --config <CONFIG_OVERRIDE>\n';
}
if (joinedArgs.includes('-c') && joinedArgs.includes('--version')) {
throw new Error('unsupported');
}
return 'codex-cli 0.119.0-alpha.1';
});
const info = getCodexBinaryInfo();
expect(info?.features).toContain('config-overrides');
execFileSyncSpy.mockRestore();
});
});
@@ -126,7 +126,17 @@ if (envOut) {
}) + '\\n'
);
}
if (cliArgs.includes('--version') || cliArgs.includes('-v')) {
const configFlagIndex = cliArgs.findIndex((arg) => arg === '-c' || arg === '--config');
if (process.env.CCS_TEST_CODEX_CONFIG_OVERRIDE_STATUS === 'unsupported' && configFlagIndex !== -1) {
process.stderr.write('codex: unknown option --config\\n');
process.exit(1);
}
if (
cliArgs.includes('--version') ||
cliArgs.includes('-v') ||
(configFlagIndex !== -1 &&
(cliArgs[configFlagIndex + 2] === '--version' || cliArgs[configFlagIndex + 2] === '-v'))
) {
process.stdout.write(process.env.CCS_TEST_CODEX_VERSION || 'codex-cli 0.118.0-alpha.3');
process.exit(0);
}
@@ -260,6 +270,120 @@ process.exit(0);
]);
});
it('creates an explicit CODEX_HOME directory before routed native Codex launches', () => {
if (process.platform === 'win32') return;
const freshCodexHome = path.join(tmpHome, 'fresh-codex-home');
const result = runCcs(['default', '--target', 'codex', '--effort', 'high', 'fix failing tests'], {
...process.env,
CI: '1',
NO_COLOR: '1',
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath,
CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test',
CODEX_HOME: freshCodexHome,
});
expect(result.status).toBe(0);
expect(fs.existsSync(freshCodexHome)).toBe(true);
expect(fs.statSync(freshCodexHome).isDirectory()).toBe(true);
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([
['-c', 'model="gpt-5"', '--version'],
['-c', 'model_reasoning_effort="high"', 'fix failing tests'],
]);
const loggedEnv = readLoggedCodexEnv(codexEnvLogPath);
expect(loggedEnv).toHaveLength(2);
expect(loggedEnv.map((entry) => entry.CODEX_HOME)).toEqual([freshCodexHome, freshCodexHome]);
expect(loggedEnv[1]).toEqual({
CODEX_HOME: freshCodexHome,
CODEX_CI: undefined,
CODEX_MANAGED_BY_BUN: undefined,
CODEX_THREAD_ID: undefined,
ANTHROPIC_BASE_URL: undefined,
});
});
it('fails with a clean error when routed launches receive a file CODEX_HOME path', () => {
if (process.platform === 'win32') return;
const invalidCodexHome = path.join(tmpHome, 'codex-home-file');
fs.writeFileSync(invalidCodexHome, 'not-a-directory');
const result = runCcs(
['default', '--target', 'codex', '--effort', 'high', 'fix failing tests'],
{
...process.env,
CI: '1',
NO_COLOR: '1',
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CODEX_HOME: invalidCodexHome,
}
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(`[X] CODEX_HOME path is not a directory: ${invalidCodexHome}`);
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['-c', 'model="gpt-5"', '--version']]);
});
it('keeps passthrough version launches aligned with native warning-only CODEX_HOME behavior', () => {
if (process.platform === 'win32') return;
const readOnlyRoot = path.join(tmpHome, 'readonly-root');
fs.mkdirSync(readOnlyRoot, { recursive: true });
fs.chmodSync(readOnlyRoot, 0o555);
try {
const result = runCodexAlias(['--version'], {
...process.env,
CI: '1',
NO_COLOR: '1',
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test',
CODEX_HOME: path.join(readOnlyRoot, 'missing-codex-home'),
});
expect(result.status).toBe(0);
expect(result.stdout).toContain('codex-cli 9.9.9-test');
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['--version']]);
} finally {
fs.chmodSync(readOnlyRoot, 0o755);
}
});
it('normalizes explicit CODEX_HOME before launching native Codex', () => {
if (process.platform === 'win32') return;
const result = runCodexAlias(['--version'], {
...process.env,
CI: '1',
NO_COLOR: '1',
HOME: tmpHome,
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath,
CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test',
CODEX_HOME: '~/.codex-lit',
});
expect(result.status).toBe(0);
expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([
{
CODEX_HOME: path.join(tmpHome, '.codex-lit'),
CODEX_CI: undefined,
CODEX_MANAGED_BY_BUN: undefined,
CODEX_THREAD_ID: undefined,
ANTHROPIC_BASE_URL: undefined,
},
]);
});
it('keeps ccsxp pinned to native Codex even when a user passes another --target override', () => {
if (process.platform === 'win32') return;
@@ -324,6 +448,7 @@ process.exit(0);
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_CONFIG_OVERRIDE_STATUS: 'unsupported',
CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test',
CCS_TEST_CODEX_HELP: ' -p, --profile <CONFIG_PROFILE>\\n',
}
@@ -333,7 +458,31 @@ process.exit(0);
expect(result.stderr).toContain('Codex CLI (codex-cli 9.9.9-test)');
expect(result.stderr).toContain('does not advertise --config overrides');
const calls = readLoggedCodexCalls(codexArgsLogPath);
expect(calls).toEqual([['--help'], ['--version']]);
expect(calls).toEqual([['-c', 'model="gpt-5"', '--version'], ['--help'], ['--version']]);
});
it('accepts native Codex reasoning overrides when the direct -c probe succeeds', () => {
if (process.platform === 'win32') return;
const result = runCcs(
['default', '--target', 'codex', '--effort', 'high', 'fix failing tests'],
{
...process.env,
CI: '1',
NO_COLOR: '1',
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test',
CCS_TEST_CODEX_HELP: ' -p, --profile <CONFIG_PROFILE>\\n',
}
);
expect(result.status).toBe(0);
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([
['-c', 'model="gpt-5"', '--version'],
['-c', 'model_reasoning_effort="high"', 'fix failing tests'],
]);
});
it('reports unsupported generic settings profiles before Codex install guidance', () => {
@@ -68,6 +68,13 @@ describe('Codex settings bridge launch', () => {
fs.writeFileSync(
fakeCodexPath,
`#!/bin/sh
if [ "$1" = "-c" ] || [ "$1" = "--config" ]; then
if [ "$3" = "--version" ] || [ "$3" = "-v" ]; then
echo "codex-cli 0.118.0-alpha.3"
exit 0
fi
fi
if [ "$1" = "--version" ]; then
echo "codex-cli 0.118.0-alpha.3"
exit 0
@@ -20,14 +20,29 @@ const testRoot = path.join(os.tmpdir(), `ccs-codex-dashboard-test-${Date.now()}`
const codexHome = path.join(testRoot, '.codex-home');
const codexStubPath = path.join(testRoot, 'codex');
function writeCodexStub(options?: { helpText?: string; version?: string }) {
function writeCodexStub(options?: {
helpText?: string;
version?: string;
supportsConfigOverrides?: boolean;
}) {
const helpText =
options?.helpText ?? ' -c, --config <key=value>\n -p, --profile <CONFIG_PROFILE>\n';
const version = options?.version ?? 'codex-cli 0.118.0-alpha.3';
const supportsConfigOverrides = options?.supportsConfigOverrides ?? true;
fs.writeFileSync(
codexStubPath,
`#!/bin/sh
if [ "$1" = "-c" ] || [ "$1" = "--config" ]; then
if [ "${supportsConfigOverrides ? '1' : '0'}" != "1" ]; then
printf '%s\\n' 'codex: unknown option --config' >&2
exit 1
fi
if [ "$3" = "--version" ] || [ "$3" = "-v" ]; then
printf '%s\\n' "${version}"
exit 0
fi
fi
if [ "$1" = "--version" ]; then
printf '%s\\n' "${version}"
exit 0
@@ -272,7 +287,10 @@ requires_openai_auth = true
});
it('warns when active profile is missing, config overrides are unavailable, or risky fields exist', async () => {
writeCodexStub({ helpText: ' -p, --profile <CONFIG_PROFILE>\n' });
writeCodexStub({
helpText: ' -p, --profile <CONFIG_PROFILE>\n',
supportsConfigOverrides: false,
});
fs.writeFileSync(
path.join(codexHome, 'config.toml'),
`profile = "missing-profile"
@@ -300,6 +318,12 @@ bearer_token = "secret"
expect(diagnostics.warnings.some((warning) => warning.includes('inline bearer_token'))).toBe(
true
);
expect(
diagnostics.supportMatrix.find((entry) => entry.id === 'cliproxy-provider-codex')?.supported
).toBe(false);
expect(
diagnostics.supportMatrix.find((entry) => entry.id === 'settings-with-bridge')?.supported
).toBe(false);
});
it('saves valid raw config content', async () => {
@@ -62,6 +62,7 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
const inspectProfileCommand = diagnostics.config.activeProfile
? `codex --profile ${diagnostics.config.activeProfile}`
: 'codex';
const supportsManagedRouting = diagnostics.binary.supportsConfigOverrides;
return (
<ScrollArea className="h-full">
@@ -142,29 +143,42 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>
There are two supported paths. Use <code>ccsxp</code> if you want the built-in CCS
Codex provider shortcut. Use the saved recipe below if you want plain{' '}
<code>codex</code> or a personal alias like <code>cxp</code> to default to CLIProxy.
</p>
<div className="rounded-md border bg-muted/20 p-3">
<p className="font-medium text-foreground">Saved native Codex recipe</p>
<pre className="mt-2 overflow-x-auto rounded-md bg-background p-3 text-xs text-foreground">
{CLIPROXY_NATIVE_CODEX_RECIPE}
</pre>
</div>
<div className="space-y-1">
{supportsManagedRouting ? (
<>
<p>
There are two supported paths. Use <code>ccsxp</code> if you want the built-in CCS
Codex provider shortcut. Use the saved recipe below if you want plain{' '}
<code>codex</code> or a personal alias like <code>cxp</code> to default to
CLIProxy.
</p>
<div className="rounded-md border bg-muted/20 p-3">
<p className="font-medium text-foreground">Saved native Codex recipe</p>
<pre className="mt-2 overflow-x-auto rounded-md bg-background p-3 text-xs text-foreground">
{CLIPROXY_NATIVE_CODEX_RECIPE}
</pre>
</div>
<div className="space-y-1">
<p>
1. Save a provider named <code>cliproxy</code> with the base URL and env key
above.
</p>
<p>
2. In <strong>Top-level settings</strong>, set <strong>Default provider</strong>{' '}
to <code>cliproxy</code>.
</p>
<p>
3. Export <code>CLIPROXY_API_KEY</code> in your shell before launching native
Codex.
</p>
</div>
</>
) : (
<p>
1. Save a provider named <code>cliproxy</code> with the base URL and env key above.
This Codex build can still use the native path, but CCS-backed Codex routing via{' '}
<code>ccsxp</code> or <code>ccs codex --target codex</code> stays unavailable until
the detected Codex binary exposes <code>--config</code> overrides.
</p>
<p>
2. In <strong>Top-level settings</strong>, set <strong>Default provider</strong> to{' '}
<code>cliproxy</code>.
</p>
<p>
3. Export <code>CLIPROXY_API_KEY</code> in your shell before launching native Codex.
</p>
</div>
)}
</CardContent>
</Card>
@@ -275,12 +289,16 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
{
label: 'CCS Codex shortcut',
command: 'ccsxp "your prompt"',
description: 'Run the built-in CCS Codex provider on native Codex.',
description: supportsManagedRouting
? 'Run the built-in CCS Codex provider on native Codex.'
: 'Requires a Codex build that exposes --config overrides.',
},
{
label: 'Explicit provider route',
command: 'ccs codex --target codex "your prompt"',
description: 'Use the explicit built-in Codex provider route.',
description: supportsManagedRouting
? 'Use the explicit built-in Codex provider route.'
: 'Requires a Codex build that exposes --config overrides.',
},
{
label: diagnostics.config.activeProfile
@@ -312,9 +330,19 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
<div className="rounded-md border p-3 text-sm">
<p className="font-medium">CCS Codex provider / bridge</p>
<p className="mt-1 text-muted-foreground">
Use <code>ccsxp</code> or <code>ccs codex --target codex</code> when you want the
built-in CCS Codex provider on native Codex. That path uses transient CCS-managed
overrides and is separate from the saved <code>cliproxy</code> recipe above.
{supportsManagedRouting ? (
<>
Use <code>ccsxp</code> or <code>ccs codex --target codex</code> when you want
the built-in CCS Codex provider on native Codex. That path uses transient
CCS-managed overrides and is separate from the saved <code>cliproxy</code>{' '}
recipe above.
</>
) : (
<>
The CCS Codex provider route is currently unavailable because the detected Codex
build does not expose <code>--config</code> overrides.
</>
)}
</p>
</div>
</CardContent>