From 3246c40319b0f9329c375ae3fe73ee02261579bb Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 14:57:56 -0400 Subject: [PATCH 1/5] feat(codex-dashboard): add manual long-context controls --- src/shared/compatible-cli-contracts.ts | 4 + .../services/codex-dashboard-service.ts | 14 ++ .../codex-dashboard-service.test.ts | 30 +++ .../codex-top-level-controls-card.tsx | 235 +++++++++++++++++- ui/src/lib/codex-config.ts | 4 + .../codex-overview-tab.test.tsx | 2 + .../codex-top-level-controls-card.test.tsx | 67 +++++ ui/tests/unit/hooks/use-codex.test.tsx | 2 + ui/tests/unit/ui/pages/codex-page.test.tsx | 2 + 9 files changed, 359 insertions(+), 1 deletion(-) diff --git a/src/shared/compatible-cli-contracts.ts b/src/shared/compatible-cli-contracts.ts index 0c0f1b42..7433d322 100644 --- a/src/shared/compatible-cli-contracts.ts +++ b/src/shared/compatible-cli-contracts.ts @@ -91,6 +91,8 @@ export interface CodexSupportMatrixEntry { export interface CodexUserConfigDiagnostics { model: string | null; modelReasoningEffort: string | null; + modelContextWindow: number | null; + modelAutoCompactTokenLimit: number | null; modelProvider: string | null; activeProfile: string | null; approvalPolicy: string | null; @@ -137,6 +139,8 @@ export interface CodexRawConfigResponse { export interface CodexTopLevelSettingsPatch { model?: string | null; modelReasoningEffort?: string | null; + modelContextWindow?: number | null; + modelAutoCompactTokenLimit?: number | null; modelProvider?: string | null; approvalPolicy?: string | null; sandboxMode?: string | null; diff --git a/src/web-server/services/codex-dashboard-service.ts b/src/web-server/services/codex-dashboard-service.ts index 725c0bc1..8bba991f 100644 --- a/src/web-server/services/codex-dashboard-service.ts +++ b/src/web-server/services/codex-dashboard-service.ts @@ -243,6 +243,18 @@ function applyTopLevelSettingsPatch( 'model_reasoning_effort' ); } + if (hasOwn(values, 'modelContextWindow')) { + setNumberField(target, 'model_context_window', values.modelContextWindow, { + integer: true, + min: 1, + }); + } + if (hasOwn(values, 'modelAutoCompactTokenLimit')) { + setNumberField(target, 'model_auto_compact_token_limit', values.modelAutoCompactTokenLimit, { + integer: true, + min: 1, + }); + } if (hasOwn(values, 'modelProvider')) { setStringField(target, 'model_provider', values.modelProvider); } @@ -778,6 +790,8 @@ export async function getCodexDashboardDiagnostics(): Promise { path.join(codexHome, 'config.toml'), `model = "gpt-5.4" profile = "work" +model_context_window = 800000 +model_auto_compact_token_limit = 700000 model_provider = "cliproxy" approval_policy = "never" sandbox_mode = "danger-full-access" @@ -217,6 +219,8 @@ model = "gpt-5.4" expect(diagnostics.binary.installed).toBe(true); expect(diagnostics.binary.supportsConfigOverrides).toBe(true); expect(diagnostics.config.model).toBe('gpt-5.4'); + expect(diagnostics.config.modelContextWindow).toBe(800000); + expect(diagnostics.config.modelAutoCompactTokenLimit).toBe(700000); expect(diagnostics.config.activeProfile).toBe('work'); expect(diagnostics.config.modelProvider).toBe('cliproxy'); expect(diagnostics.config.profileCount).toBe(1); @@ -376,6 +380,8 @@ bearer_token = "secret" values: { model: 'gpt-5.4', modelReasoningEffort: 'high', + modelContextWindow: 800000, + modelAutoCompactTokenLimit: 700000, approvalPolicy: 'never', sandboxMode: 'workspace-write', webSearch: 'cached', @@ -394,10 +400,14 @@ bearer_token = "secret" const diagnostics = await getCodexDashboardDiagnostics(); expect(diagnostics.config.model).toBe('gpt-5.4'); expect(diagnostics.config.modelReasoningEffort).toBe('high'); + expect(diagnostics.config.modelContextWindow).toBe(800000); + expect(diagnostics.config.modelAutoCompactTokenLimit).toBe(700000); expect(diagnostics.config.toolOutputTokenLimit).toBe(12000); expect(diagnostics.config.personality).toBe('friendly'); expect(diagnostics.config.projectTrust[0]?.path).toBe('/tmp/workspace-a'); expect(result.rawText).toContain('model = "gpt-5.4"'); + expect(result.rawText).toContain('model_context_window = 800000'); + expect(result.rawText).toContain('model_auto_compact_token_limit = 700000'); expect(result.config?.model).toBe('gpt-5.4'); }); @@ -665,4 +675,24 @@ bearer_token = "secret" }) ).rejects.toThrow(CodexRawConfigValidationError); }); + + it('rejects invalid long-context values in structured top-level patches', async () => { + await expect( + patchCodexConfig({ + kind: 'top-level', + values: { + modelContextWindow: 0, + }, + }) + ).rejects.toThrow(CodexRawConfigValidationError); + + await expect( + patchCodexConfig({ + kind: 'top-level', + values: { + modelAutoCompactTokenLimit: 1.5, + }, + }) + ).rejects.toThrow(CodexRawConfigValidationError); + }); }); diff --git a/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx b/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx index 15dc5927..2194b5b9 100644 --- a/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx +++ b/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { Loader2, SlidersHorizontal } from 'lucide-react'; +import { CircleAlert, Loader2, SlidersHorizontal } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { @@ -14,6 +15,11 @@ import type { CodexTopLevelSettingsView } from '@/lib/codex-config'; import { CodexConfigCardShell } from './codex-config-card-shell'; const UNSET = '__unset__'; +const GPT_54_MAX_CONTEXT_WINDOW = 1_050_000; +const GPT_54_STANDARD_CONTEXT_WINDOW = 272_000; +const CCS_GPT_54_STARTER_CONTEXT_WINDOW = 800_000; +const CCS_GPT_54_STARTER_AUTO_COMPACT_TOKEN_LIMIT = 700_000; +const INTEGER_FORMATTER = new Intl.NumberFormat('en-US'); interface CodexTopLevelControlsCardProps { values: CodexTopLevelSettingsView; @@ -32,6 +38,14 @@ function withCurrentValue(options: string[], current: string | null | undefined) return current && !options.includes(current) ? [current, ...options] : options; } +function formatInteger(value: number) { + return INTEGER_FORMATTER.format(value); +} + +function isGpt54ModelId(value: string | null | undefined) { + return value?.trim().toLowerCase().startsWith('gpt-5.4') ?? false; +} + function buildTopLevelPatch( initialValues: CodexTopLevelSettingsView, draft: CodexTopLevelSettingsView @@ -42,6 +56,12 @@ function buildTopLevelPatch( if (draft.modelReasoningEffort !== initialValues.modelReasoningEffort) { patch.modelReasoningEffort = draft.modelReasoningEffort; } + if (draft.modelContextWindow !== initialValues.modelContextWindow) { + patch.modelContextWindow = draft.modelContextWindow; + } + if (draft.modelAutoCompactTokenLimit !== initialValues.modelAutoCompactTokenLimit) { + patch.modelAutoCompactTokenLimit = draft.modelAutoCompactTokenLimit; + } if (draft.modelProvider !== initialValues.modelProvider) { patch.modelProvider = draft.modelProvider; } @@ -91,6 +111,11 @@ function TopLevelControlsForm({ const personalityOptions = withCurrentValue(['none', 'friendly', 'pragmatic'], draft.personality); const patch = buildTopLevelPatch(initialValues, draft); const hasChanges = Object.keys(patch).length > 0; + const isGpt54Selected = isGpt54ModelId(draft.model); + const parseOptionalInteger = (value: string) => { + const trimmed = value.trim(); + return trimmed.length > 0 ? Number(trimmed) : null; + }; return ( <> @@ -266,6 +291,214 @@ function TopLevelControlsForm({ +
+
+
+
+ +

Long context override

+ + Manual opt-in only + + + {isGpt54Selected ? 'GPT-5.4 selected' : 'GPT-5.4 reference'} + +
+

Draft values only. Nothing applies until Save.

+
+ +
+ + + +
+
+ +
+
+

+ Official max +

+

1.05M / 1M

+

GPT-5.4 context cap

+
+
+

+ Standard window +

+

+ {formatInteger(GPT_54_STANDARD_CONTEXT_WINDOW)} +

+

Normal usage window

+
+
+

+ Above 272K +

+

Counts 2x

+

Usage-limit cost above 272K

+
+
+ +
+
+
+

+ One cautious pair +

+
+ Context {formatInteger(CCS_GPT_54_STARTER_CONTEXT_WINDOW)} +
+
+ Auto-compact {formatInteger(CCS_GPT_54_STARTER_AUTO_COMPACT_TOKEN_LIMIT)} +
+
+
+ + Not official + + + Draft only + +
+
+
+ Quick-fill only. Review before saving. + {!isGpt54Selected && draft.model ? ( + + {draft.model} should be checked separately. + + ) : null} +
+
+ +
+
+

Model context window

+ + setDraft((current) => ({ + ...current, + modelContextWindow: parseOptionalInteger(event.target.value), + })) + } + placeholder="Unset" + disabled={disabled} + /> +

+ Writes model_context_window. Leave unset to keep Codex defaults. +

+
+ +
+

Auto-compact token limit

+ + setDraft((current) => ({ + ...current, + modelAutoCompactTokenLimit: parseOptionalInteger(event.target.value), + })) + } + placeholder="Unset" + disabled={disabled} + /> +

+ Writes model_auto_compact_token_limit. Leave unset to keep model + defaults. +

+
+
+ +
+ Docs + + GPT-5.4 model page + + + Release notes + + + Config reference + +
+
+
-

Draft values only. Nothing applies until Save.

+

+ Draft values only. Nothing applies until Save. +

From 7fd2acadbe727a839e44f99081d42687322ee791 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 15:44:06 -0400 Subject: [PATCH 4/5] docs(codex-dashboard): streamline Codex guidance copy --- .../compatible-cli/codex-docs-tab.tsx | 48 +++--- .../compatible-cli/codex-overview-tab.tsx | 148 ++++++++++-------- 2 files changed, 108 insertions(+), 88 deletions(-) diff --git a/ui/src/components/compatible-cli/codex-docs-tab.tsx b/ui/src/components/compatible-cli/codex-docs-tab.tsx index 1474155d..496390c5 100644 --- a/ui/src/components/compatible-cli/codex-docs-tab.tsx +++ b/ui/src/components/compatible-cli/codex-docs-tab.tsx @@ -107,25 +107,29 @@ export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) { -

- Use ccsxp if you want the built-in CCS Codex provider shortcut on native - Codex. Use the saved recipe below if you want plain codex or a personal - alias like cxp to default to CLIProxy. -

+
+

+ Built-in: Use ccsxp for the CCS provider shortcut. +

+

+ Native: Configure the recipe below to use CLIProxy directly with{' '} + codex. +

+
               {CLIPROXY_NATIVE_CODEX_RECIPE}
             
-
-

- 1. Save the cliproxy provider in your user config. -

-

- 2. Set top-level model_provider to cliproxy. -

-

- 3. Export CLIPROXY_API_KEY in your shell before launching native Codex. -

-
+
    +
  1. + Save the cliproxy provider in your user config. +
  2. +
  3. + Set top-level model_provider to cliproxy. +
  4. +
  5. + Export CLIPROXY_API_KEY before launching native Codex. +
  6. +
@@ -137,11 +141,13 @@ export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) { - {docsReference.notes.map((note, index) => ( -

- - {renderTextWithLinks(note)} -

- ))} + {docsReference.notes.length > 0 && ( +
    + {docsReference.notes.map((note, index) => ( +
  • {renderTextWithLinks(note)}
  • + ))} +
+ )}

Codex docs

diff --git a/ui/src/components/compatible-cli/codex-overview-tab.tsx b/ui/src/components/compatible-cli/codex-overview-tab.tsx index f74cd945..eb73f2eb 100644 --- a/ui/src/components/compatible-cli/codex-overview-tab.tsx +++ b/ui/src/components/compatible-cli/codex-overview-tab.tsx @@ -74,31 +74,23 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { How Codex works in CCS - -

Codex is a first-class runtime target in CCS, but it stays runtime-only in v1.

-

- ccs-codex and ccsx launch native Codex against your saved - native config, while ccsxp is the opinionated shortcut for{' '} - ccs codex --target codex. -

-

- Plain codex or a personal alias like cxp needs{' '} - model_provider = "cliproxy" plus a matching{' '} - [model_providers.cliproxy] entry if you want CLIProxy as the saved native - default. -

-

- Built-in openai and oss providers are also valid native - defaults and do not need a custom [model_providers] stanza. -

-

- Saved default targets for API profiles and variants still remain on Claude or Droid. -

-

- CCS-backed Codex launches can apply transient -c overrides and inject - CCS_CODEX_API_KEY, so effective runtime values may not match this file - exactly. -

+ +
    +
  • Codex is a first-class, runtime-only target in CCS v1.
  • +
  • + Native config: ccs-codex and ccsx launch + native Codex using your saved defaults. +
  • +
  • + Transient overrides: ccsxp (or{' '} + ccs codex --target codex) uses the CCS provider shortcut. +
  • +
  • + CLIProxy default: To make plain codex use CLIProxy, + set model_provider = "cliproxy" and add the recipe below. +
  • +
  • API profiles continue to default to Claude or Droid.
  • +
@@ -145,32 +137,40 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { {supportsManagedRouting ? ( <> -

- There are two supported paths. Use ccsxp if you want the built-in CCS - Codex provider shortcut. Use the saved recipe below if you want plain{' '} - codex or a personal alias like cxp to default to - CLIProxy. -

+
+

+ Two supported paths: +

+
    +
  • + Built-in: Use ccsxp for the CCS provider + shortcut. +
  • +
  • + Native: Configure the recipe below to use CLIProxy directly + with codex. +
  • +
+

Saved native Codex recipe

                     {CLIPROXY_NATIVE_CODEX_RECIPE}
                   
-
-

- 1. Save a provider named cliproxy with the base URL and env key - above. -

-

- 2. In Top-level settings, set Default provider{' '} - to cliproxy. -

-

- 3. Export CLIPROXY_API_KEY in your shell before launching native +

    +
  1. + Save a provider named cliproxy with the base URL and env key above. +
  2. +
  3. + In Top-level settings, set Default provider to{' '} + cliproxy. +
  4. +
  5. + Export CLIPROXY_API_KEY in your shell before launching native Codex. -

    -
+ + ) : (

@@ -320,30 +320,44 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { -

-

Native Codex runtime

-

- Use ccs-codex, ccsx, or --target codex when - you want the local Codex CLI to honor your saved native user config. -

+
+

Native Codex runtime

+
    +
  • + ccs-codex +
  • +
  • + ccsx +
  • +
  • + --target codex +
  • +
+ + Honors saved native user config +
-
-

CCS Codex provider / bridge

-

- {supportsManagedRouting ? ( - <> - Use ccsxp or ccs codex --target codex 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 cliproxy{' '} - recipe above. - - ) : ( - <> - The CCS Codex provider route is currently unavailable because the detected Codex - build does not expose --config overrides. - - )} -

+
+

CCS Codex provider / bridge

+ {supportsManagedRouting ? ( + <> +
    +
  • + ccsxp +
  • +
  • + ccs codex --target codex +
  • +
+ + Uses transient overrides + + + ) : ( +

+ Unavailable (Codex build lacks --config support). +

+ )}
From c1ab210095c723b8ae5c6cb88cf5f62867a6e84e Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:14:16 -0400 Subject: [PATCH 5/5] feat(codex-dashboard): refine control center boundary card --- .../codex-control-center-tab.tsx | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/ui/src/components/compatible-cli/codex-control-center-tab.tsx b/ui/src/components/compatible-cli/codex-control-center-tab.tsx index 9b701521..727a6e8d 100644 --- a/ui/src/components/compatible-cli/codex-control-center-tab.tsx +++ b/ui/src/components/compatible-cli/codex-control-center-tab.tsx @@ -1,11 +1,10 @@ -import { Route } from 'lucide-react'; +import { FileCode2, History, PenLine, Settings2 } from 'lucide-react'; import { CodexFeaturesCard } from '@/components/compatible-cli/codex-features-card'; import { CodexMcpServersCard } from '@/components/compatible-cli/codex-mcp-servers-card'; import { CodexModelProvidersCard } from '@/components/compatible-cli/codex-model-providers-card'; import { CodexProfilesCard } from '@/components/compatible-cli/codex-profiles-card'; import { CodexProjectTrustCard } from '@/components/compatible-cli/codex-project-trust-card'; import { CodexTopLevelControlsCard } from '@/components/compatible-cli/codex-top-level-controls-card'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import type { CodexConfigPatchInput, @@ -54,26 +53,60 @@ export function CodexControlCenterTab({ }: CodexControlCenterTabProps) { return ( -
- - - - - Structured controls boundary - - - -

- Guided controls write only the user-layer config.toml. They do not model - the full effective Codex runtime once trusted repo layers and CCS transient{' '} - -c overrides are involved. -

-

- Structured saves normalize TOML formatting and strip comments. Use the raw editor on - the right when exact layout matters. -

-
-
+
+
+
+ +
+
+
+
+ +
+
+

+ Structured controls boundary +

+
+
+ +
    +
  • + + + Writes exclusively to user-layer{' '} + + config.toml + + +
  • +
  • + + + Does not reflect repo trust layers or CLI overrides + +
  • +
+
+ +
+
+
+ +
+

+ Formatting Note +

+

+ Saves normalize TOML formatting and strip comments. Switch to the raw editor + if exact layout matters. +

+
+
+
+
+
+