mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 18:15:40 +00:00
fix(codex): align cliproxy guidance with runtime behavior
This commit is contained in:
1 parent
0ffde4f3a2
commit
bd1ff02521
22 files changed
+342
-64
No files matched your search
@@ -297,6 +297,7 @@ export function ProviderEditor({
|
||||
defaultTarget={defaultTarget}
|
||||
data={data}
|
||||
authStatus={authStatus}
|
||||
supportsModelConfig={Boolean(catalog)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ interface ProviderInfoTabProps {
|
||||
defaultTarget?: CliTarget;
|
||||
data?: SettingsResponse;
|
||||
authStatus: AuthStatus;
|
||||
supportsModelConfig?: boolean;
|
||||
}
|
||||
|
||||
export function ProviderInfoTab({
|
||||
@@ -25,10 +26,16 @@ export function ProviderInfoTab({
|
||||
defaultTarget,
|
||||
data,
|
||||
authStatus,
|
||||
supportsModelConfig = false,
|
||||
}: ProviderInfoTabProps) {
|
||||
const resolvedTarget = defaultTarget || 'claude';
|
||||
const isDroidTarget = resolvedTarget === 'droid';
|
||||
const isCodexProvider = provider === 'codex';
|
||||
const managementPrefix =
|
||||
resolvedTarget === 'claude' ? `ccs ${provider}` : `ccs ${provider} --target claude`;
|
||||
const changeModelCommand = `${managementPrefix} --config`;
|
||||
const addAccountCommand = `${managementPrefix} --auth --add`;
|
||||
const listAccountsCommand = `${managementPrefix} --accounts`;
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
@@ -109,9 +116,30 @@ export function ProviderInfoTab({
|
||||
label={isDroidTarget ? 'Override to Claude' : 'Run on Droid (--target)'}
|
||||
command={`ccs ${provider} --target ${isDroidTarget ? 'claude' : 'droid'} "your prompt"`}
|
||||
/>
|
||||
<UsageCommand label="Change model" command={`ccs ${provider} --config`} />
|
||||
<UsageCommand label="Add account" command={`ccs ${provider} --add`} />
|
||||
<UsageCommand label="List accounts" command={`ccs ${provider} --accounts`} />
|
||||
{supportsModelConfig && (
|
||||
<UsageCommand
|
||||
label={
|
||||
resolvedTarget === 'claude' ? 'Change model' : 'Change model (Claude target)'
|
||||
}
|
||||
command={changeModelCommand}
|
||||
/>
|
||||
)}
|
||||
<UsageCommand
|
||||
label={resolvedTarget === 'claude' ? 'Add account' : 'Add account (Claude target)'}
|
||||
command={addAccountCommand}
|
||||
/>
|
||||
<UsageCommand
|
||||
label={
|
||||
resolvedTarget === 'claude' ? 'List accounts' : 'List accounts (Claude target)'
|
||||
}
|
||||
command={listAccountsCommand}
|
||||
/>
|
||||
{resolvedTarget !== 'claude' && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Account and model-management flags stay on Claude target. Codex and Droid runtime
|
||||
launches reject those CLIProxy management commands.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
CompatibleCliProviderDocLink,
|
||||
CodexDashboardDiagnostics,
|
||||
} from '@/hooks/use-codex-types';
|
||||
import { CLIPROXY_NATIVE_CODEX_RECIPE } from '@/lib/codex-config';
|
||||
|
||||
const DEFAULT_CODEX_DOC_LINKS = [
|
||||
{
|
||||
@@ -44,13 +45,6 @@ const DEFAULT_PROVIDER_DOCS: CompatibleCliProviderDocLink[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const CLIPROXY_NATIVE_CODEX_RECIPE = `model_provider = "cliproxy"
|
||||
|
||||
[model_providers.cliproxy]
|
||||
base_url = "http://127.0.0.1:8317/api/provider/codex"
|
||||
env_key = "CLIPROXY_API_KEY"
|
||||
wire_api = "responses"`;
|
||||
|
||||
function renderTextWithLinks(text: string): ReactNode[] {
|
||||
const urlPattern = /https?:\/\/[^\s)]+/g;
|
||||
const nodes: ReactNode[] = [];
|
||||
|
||||
@@ -22,15 +22,9 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import type { CodexDashboardDiagnostics } from '@/hooks/use-codex-types';
|
||||
import { CLIPROXY_NATIVE_CODEX_RECIPE } from '@/lib/codex-config';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const CLIPROXY_NATIVE_CODEX_RECIPE = `model_provider = "cliproxy"
|
||||
|
||||
[model_providers.cliproxy]
|
||||
base_url = "http://127.0.0.1:8317/api/provider/codex"
|
||||
env_key = "CLIPROXY_API_KEY"
|
||||
wire_api = "responses"`;
|
||||
|
||||
function formatTimestamp(value: number | null | undefined): string {
|
||||
if (!value || !Number.isFinite(value)) return 'N/A';
|
||||
return new Date(value).toLocaleString();
|
||||
@@ -65,6 +59,10 @@ interface CodexOverviewTabProps {
|
||||
}
|
||||
|
||||
export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
|
||||
const inspectProfileCommand = diagnostics.config.activeProfile
|
||||
? `codex --profile ${diagnostics.config.activeProfile}`
|
||||
: 'codex';
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="space-y-4 pr-1">
|
||||
@@ -88,6 +86,10 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
|
||||
<code>[model_providers.cliproxy]</code> entry if you want CLIProxy as the saved native
|
||||
default.
|
||||
</p>
|
||||
<p>
|
||||
Built-in <code>openai</code> and <code>oss</code> providers are also valid native
|
||||
defaults and do not need a custom <code>[model_providers]</code> stanza.
|
||||
</p>
|
||||
<p>
|
||||
Saved default targets for API profiles and variants still remain on Claude or Droid.
|
||||
</p>
|
||||
@@ -281,9 +283,13 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
|
||||
description: 'Use the explicit built-in Codex provider route.',
|
||||
},
|
||||
{
|
||||
label: 'Workspace trust',
|
||||
command: `codex --profile ${diagnostics.config.activeProfile || 'default'}`,
|
||||
description: 'Inspect the active profile directly in native Codex.',
|
||||
label: diagnostics.config.activeProfile
|
||||
? 'Inspect active profile'
|
||||
: 'Open native Codex',
|
||||
command: inspectProfileCommand,
|
||||
description: diagnostics.config.activeProfile
|
||||
? 'Inspect the active named profile directly in native Codex.'
|
||||
: 'Open native Codex without forcing a named profile overlay.',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -49,6 +49,13 @@ export interface CodexFeatureCatalogEntry {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const CLIPROXY_NATIVE_CODEX_RECIPE = `model_provider = "cliproxy"
|
||||
|
||||
[model_providers.cliproxy]
|
||||
base_url = "http://127.0.0.1:8317/api/provider/codex"
|
||||
env_key = "CLIPROXY_API_KEY"
|
||||
wire_api = "responses"`;
|
||||
|
||||
export const KNOWN_CODEX_FEATURES: CodexFeatureCatalogEntry[] = [
|
||||
{
|
||||
name: 'multi_agent',
|
||||
|
||||
+4
-4
@@ -950,7 +950,7 @@ const resources = {
|
||||
actionRequired: 'Action Required',
|
||||
done: 'Done',
|
||||
all: 'All',
|
||||
inboxTitle: 'Updates Inbox',
|
||||
inboxTitle: 'Updates Center',
|
||||
inboxSubtitle: 'Focus on actions, then mark updates done or dismissed.',
|
||||
needsAction: 'Needs Action',
|
||||
doneCount: 'Done',
|
||||
@@ -2118,7 +2118,7 @@ const resources = {
|
||||
actionRequired: '待处理',
|
||||
done: '已完成',
|
||||
all: '全部',
|
||||
inboxTitle: '更新收件箱',
|
||||
inboxTitle: '更新中心',
|
||||
inboxSubtitle: '先处理操作,再将更新标记为完成或忽略。',
|
||||
needsAction: '需处理',
|
||||
doneCount: '已完成',
|
||||
@@ -3344,7 +3344,7 @@ const resources = {
|
||||
actionRequired: 'Hành động bắt buộc',
|
||||
done: 'Xong',
|
||||
all: 'Tất cả',
|
||||
inboxTitle: 'Hộp thư cập nhật',
|
||||
inboxTitle: 'Trung tâm cập nhật',
|
||||
inboxSubtitle:
|
||||
'Tập trung vào các hành động, sau đó đánh dấu các cập nhật đã hoàn tất hoặc bị loại bỏ.',
|
||||
needsAction: 'Cần hành động',
|
||||
@@ -4580,7 +4580,7 @@ const resources = {
|
||||
actionRequired: '対応が必要',
|
||||
done: '完了',
|
||||
all: 'すべて',
|
||||
inboxTitle: '更新インボックス',
|
||||
inboxTitle: '更新センター',
|
||||
inboxSubtitle: '対応を優先し、その後で完了または非表示にします。',
|
||||
needsAction: '要対応',
|
||||
doneCount: '完了',
|
||||
|
||||
@@ -169,9 +169,9 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
|
||||
},
|
||||
{
|
||||
id: 'updates-center-launch',
|
||||
title: 'Updates inbox is available for rollout tasks',
|
||||
title: 'Updates Center is available for rollout tasks',
|
||||
summary:
|
||||
'A focused updates inbox exists for setup tasks and rollout guidance when you need it.',
|
||||
'A focused Updates Center exists for setup tasks and rollout guidance when you need it.',
|
||||
primaryAction:
|
||||
'Use this page only when needed for rollout tasks, then return to your normal workflow.',
|
||||
publishedAt: '2026-02-25',
|
||||
@@ -186,7 +186,7 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
|
||||
actions: [
|
||||
{
|
||||
id: 'open-updates-page',
|
||||
label: 'Open updates inbox when needed',
|
||||
label: 'Open Updates Center when needed',
|
||||
description: 'Review rollout tasks only when you want guided setup changes.',
|
||||
type: 'route',
|
||||
path: '/updates',
|
||||
@@ -199,7 +199,7 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
|
||||
command: 'ccs config',
|
||||
},
|
||||
],
|
||||
routes: [{ label: 'Updates Inbox', path: '/updates' }],
|
||||
routes: [{ label: 'Updates Center', path: '/updates' }],
|
||||
commands: ['ccs config'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render, screen } from '@tests/setup/test-utils';
|
||||
import { ProviderInfoTab } from '@/components/cliproxy/provider-editor/provider-info-tab';
|
||||
|
||||
const authenticatedStatus = {
|
||||
provider: 'codex',
|
||||
displayName: 'Codex',
|
||||
authenticated: true,
|
||||
lastAuth: null,
|
||||
tokenFiles: 1,
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
describe('ProviderInfoTab', () => {
|
||||
it('routes management commands through Claude target for codex-target variants', () => {
|
||||
render(
|
||||
<ProviderInfoTab
|
||||
provider="codex"
|
||||
displayName="Codex"
|
||||
defaultTarget="codex"
|
||||
authStatus={authenticatedStatus}
|
||||
supportsModelConfig
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('ccs codex --target claude --config')).toBeInTheDocument();
|
||||
expect(screen.getByText('ccs codex --target claude --auth --add')).toBeInTheDocument();
|
||||
expect(screen.getByText('ccs codex --target claude --accounts')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Codex and Droid runtime launches reject those CLIProxy management commands\./
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides unsupported model-config commands when the provider has no catalog support', () => {
|
||||
render(
|
||||
<ProviderInfoTab
|
||||
provider="custom-provider"
|
||||
displayName="Custom Provider"
|
||||
defaultTarget="claude"
|
||||
authStatus={{
|
||||
...authenticatedStatus,
|
||||
provider: 'custom-provider',
|
||||
displayName: 'Custom Provider',
|
||||
}}
|
||||
supportsModelConfig={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByText('Change model')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('ccs custom-provider --auth --add')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render, screen } from '@tests/setup/test-utils';
|
||||
import { CodexOverviewTab } from '@/components/compatible-cli/codex-overview-tab';
|
||||
import type { CodexDashboardDiagnostics } from '@/hooks/use-codex-types';
|
||||
|
||||
function buildDiagnostics(activeProfile: string | null): CodexDashboardDiagnostics {
|
||||
return {
|
||||
binary: {
|
||||
installed: true,
|
||||
path: '/tmp/codex',
|
||||
installDir: '/tmp',
|
||||
source: 'PATH',
|
||||
version: 'codex-cli 0.118.0-alpha.3',
|
||||
overridePath: null,
|
||||
supportsConfigOverrides: true,
|
||||
},
|
||||
file: {
|
||||
label: 'Codex user config',
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
isSymlink: false,
|
||||
isRegularFile: true,
|
||||
sizeBytes: 64,
|
||||
mtimeMs: 100,
|
||||
parseError: null,
|
||||
readError: null,
|
||||
},
|
||||
workspacePath: '/tmp/workspace',
|
||||
config: {
|
||||
model: 'gpt-5.4',
|
||||
modelReasoningEffort: null,
|
||||
modelProvider: 'openai',
|
||||
activeProfile,
|
||||
approvalPolicy: null,
|
||||
sandboxMode: null,
|
||||
webSearch: null,
|
||||
toolOutputTokenLimit: null,
|
||||
personality: null,
|
||||
topLevelKeys: ['model_provider'],
|
||||
profileCount: activeProfile ? 1 : 0,
|
||||
profileNames: activeProfile ? [activeProfile] : [],
|
||||
modelProviderCount: 0,
|
||||
modelProviders: [],
|
||||
featureCount: 0,
|
||||
enabledFeatures: [],
|
||||
disabledFeatures: [],
|
||||
trustedProjectCount: 0,
|
||||
untrustedProjectCount: 0,
|
||||
projectTrust: [],
|
||||
mcpServerCount: 0,
|
||||
mcpServers: [],
|
||||
},
|
||||
supportMatrix: [],
|
||||
warnings: [],
|
||||
docsReference: {
|
||||
providerValues: ['openai', 'oss', 'custom model_providers'],
|
||||
settingsHierarchy: [],
|
||||
notes: [],
|
||||
links: [],
|
||||
providerDocs: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('CodexOverviewTab', () => {
|
||||
it('uses a bare codex command when no active profile is set', () => {
|
||||
render(<CodexOverviewTab diagnostics={buildDiagnostics(null)} />);
|
||||
|
||||
expect(screen.getByText('Open native Codex')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('codex').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('codex --profile default')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getAllByText(
|
||||
(_, node) =>
|
||||
node?.textContent?.includes(
|
||||
'Built-in openai and oss providers are also valid native defaults'
|
||||
) ?? false
|
||||
).length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('uses the active named profile when one is selected', () => {
|
||||
render(<CodexOverviewTab diagnostics={buildDiagnostics('work')} />);
|
||||
|
||||
expect(screen.getByText('Inspect active profile')).toBeInTheDocument();
|
||||
expect(screen.getByText('codex --profile work')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -22,4 +22,18 @@ describe('support-updates catalog codex routing', () => {
|
||||
expect(entry).toBeDefined();
|
||||
expect(entry?.routes).toEqual([{ label: 'Codex CLI', path: '/codex' }]);
|
||||
});
|
||||
|
||||
it('uses Updates Center naming consistently for the rollout notice', () => {
|
||||
const notice = SUPPORT_NOTICES.find((entry) => entry.id === 'updates-center-launch');
|
||||
|
||||
expect(notice).toBeDefined();
|
||||
expect(notice?.title).toContain('Updates Center');
|
||||
expect(notice?.actions).toContainEqual(
|
||||
expect.objectContaining({
|
||||
id: 'open-updates-page',
|
||||
label: 'Open Updates Center when needed',
|
||||
})
|
||||
);
|
||||
expect(notice?.routes).toContainEqual({ label: 'Updates Center', path: '/updates' });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user