feat: add gpt-5.5 to codex model catalogs

This commit is contained in:
Tam Nhu Tran
2026-04-24 10:30:59 -04:00
parent a6170a967d
commit 4efd61ee34
9 changed files with 85 additions and 7 deletions
+1
View File
@@ -18,6 +18,7 @@ const KNOWN_CODEX_MODELS = new Set(
);
const FREE_PLAN_FALLBACKS = new Map<string, string>([
['gpt-5.5', FREE_SAFE_DEFAULT_MODEL],
['gpt-5.3-codex', FREE_SAFE_DEFAULT_MODEL],
['gpt-5.3-codex-spark', FREE_SAFE_FAST_MODEL],
]);
+13
View File
@@ -188,6 +188,19 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
displayName: 'Copilot Codex',
defaultModel: 'gpt-5.4',
models: [
{
id: 'gpt-5.5',
name: 'GPT-5.5',
tier: 'pro',
description:
'Newest Codex-released GPT-5 family model; falls back to GPT-5.4 on free plans',
thinking: {
type: 'levels',
levels: ['low', 'medium', 'high', 'xhigh'],
maxLevel: 'xhigh',
dynamicAllowed: false,
},
},
{
id: 'gpt-5.4',
name: 'GPT-5.4',
@@ -45,6 +45,48 @@ async function importCompatibilityModule(cacheTag: string) {
const identity = (message: string) => message;
describe('codex plan compatibility reconcile', () => {
it('warns about the GPT-5.5 free-plan fallback without rewriting saved settings', async () => {
const { tmpDir, settingsPath } = createCodexSettingsFixture();
const errorSpy = spyOn(console, 'error').mockImplementation(() => {});
try {
const { reconcileCodexModelForActivePlan } = await importCompatibilityModule('gpt-5-5-free');
await reconcileCodexModelForActivePlan(
{
currentModel: 'gpt-5.5',
verbose: false,
},
{
getDefaultAccount: () => ({ id: 'free@example.com' }) as never,
fetchCodexQuota: async () => ({
success: true,
windows: [],
coreUsage: { fiveHour: null, weekly: null },
planType: 'free',
lastUpdated: Date.now(),
accountId: 'free@example.com',
}),
formatInfo: identity,
formatWarn: identity,
}
);
const repaired = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')) as {
env: Record<string, string>;
};
expect(repaired.env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5.4-mini');
expect(errorSpy).toHaveBeenCalledWith(
'Codex free plan detected. Keeping saved model "gpt-5.5" in settings; runtime requests will fall back to "gpt-5.4" when needed.'
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it('keeps saved Codex settings intact and warns about runtime fallback for free-plan accounts', async () => {
const { tmpDir, settingsPath } = createCodexSettingsFixture('gpt-5.3-codex-spark');
const errorSpy = spyOn(console, 'error').mockImplementation(() => {});
@@ -14,6 +14,8 @@ describe('codex plan compatibility', () => {
});
it('maps paid-only free-plan models to safe fallbacks', () => {
expect(getFreePlanFallbackCodexModel('gpt-5.5')).toBe('gpt-5.4');
expect(getFreePlanFallbackCodexModel('gpt-5.5-xhigh')).toBe('gpt-5.4');
expect(getFreePlanFallbackCodexModel('gpt-5.3-codex')).toBe('gpt-5.4');
expect(getFreePlanFallbackCodexModel('gpt-5.3-codex-xhigh')).toBe('gpt-5.4');
expect(getFreePlanFallbackCodexModel('gpt-5.3-codex(high)')).toBe('gpt-5.4');
@@ -71,6 +73,7 @@ describe('codex plan compatibility', () => {
});
it('tracks Codex thinking caps for current safe defaults, paid models, and legacy aliases', () => {
expect(getModelMaxLevel('codex', 'gpt-5.5')).toBe('xhigh');
expect(getModelMaxLevel('codex', 'gpt-5.4')).toBe('xhigh');
expect(getModelMaxLevel('codex', 'gpt-5.4-mini')).toBe('high');
expect(getModelMaxLevel('codex', 'gpt-5-codex')).toBe('xhigh');
@@ -209,6 +209,7 @@ describe('Model Catalog', () => {
const { MODEL_CATALOG } = modelCatalog;
const ids = MODEL_CATALOG.codex.models.map((m) => m.id);
assert.deepStrictEqual(ids, [
'gpt-5.5',
'gpt-5.4',
'gpt-5.4-mini',
'gpt-5.3-codex',
@@ -130,7 +130,7 @@ function TopLevelControlsForm({
onChange={(event) =>
setDraft((current) => ({ ...current, model: event.target.value || null }))
}
placeholder="gpt-5.4"
placeholder="gpt-5.5"
disabled={disabled}
/>
</div>
@@ -312,7 +312,9 @@ function TopLevelControlsForm({
className="text-[10px] uppercase tracking-[0.16em] text-muted-foreground"
>
{/* TODO i18n: missing keys codex.gpt54Selected / codex.gpt54Reference */}
{isGpt54Selected ? 'GPT-5.4 selected' : 'GPT-5.4 reference'}
{isGpt54Selected
? 'GPT-5.4 long-context selected'
: 'GPT-5.4 long-context reference'}
</Badge>
</div>
<p className="text-xs text-muted-foreground">
@@ -500,16 +502,16 @@ function TopLevelControlsForm({
{/* TODO i18n: missing key codex.docs */}
<span className="text-[10px] uppercase tracking-[0.14em]">Docs</span>
<a
href="https://developers.openai.com/api/docs/models/gpt-5.4"
href="https://platform.openai.com/docs/models"
target="_blank"
rel="noreferrer"
className="underline underline-offset-2 hover:text-foreground"
>
{/* TODO i18n: missing key codex.gpt54ModelPage */}
GPT-5.4 model page
{/* TODO i18n: missing key codex.gptModelGuide */}
GPT-5 model guide
</a>
<a
href="https://openai.com/index/introducing-gpt-5-4/"
href="https://openai.com/index/introducing-gpt-5-5/"
target="_blank"
rel="noreferrer"
className="underline underline-offset-2 hover:text-foreground"
+13
View File
@@ -259,6 +259,19 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
displayName: 'Codex',
defaultModel: 'gpt-5-codex',
models: [
{
id: 'gpt-5.5',
name: 'GPT-5.5',
tier: 'paid',
description: 'Newest Codex-only GPT-5 family model',
codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5.5',
opus: 'gpt-5.5',
sonnet: 'gpt-5.5',
haiku: 'gpt-5-codex-mini',
},
},
{
id: 'gpt-5-codex',
name: 'GPT-5 Codex',
@@ -28,7 +28,7 @@ describe('CodexTopLevelControlsCard', () => {
const saveButton = screen.getByRole('button', { name: 'Save top-level settings' });
expect(saveButton).toBeDisabled();
await userEvent.type(screen.getByPlaceholderText('gpt-5.4'), 'gpt-5.4-mini');
await userEvent.type(screen.getByPlaceholderText('gpt-5.5'), 'gpt-5.4-mini');
expect(saveButton).toBeEnabled();
await userEvent.click(saveButton);
@@ -4,10 +4,13 @@ import { MODEL_CATALOGS } from '@/lib/model-catalogs';
describe('codex model catalog defaults', () => {
it('uses gpt-5-codex-mini as the haiku mapping for cross-plan codex presets', () => {
const codexCatalog = MODEL_CATALOGS.codex;
const codex55 = codexCatalog.models.find((model) => model.id === 'gpt-5.5');
const codex53 = codexCatalog.models.find((model) => model.id === 'gpt-5.3-codex');
const codex52 = codexCatalog.models.find((model) => model.id === 'gpt-5.2-codex');
const codexMini = codexCatalog.models.find((model) => model.id === 'gpt-5-codex-mini');
expect(codex55?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
expect(codex55?.codexMaxEffort).toBe('xhigh');
expect(codex53?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
expect(codex52?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
expect(codex53?.codexMaxEffort).toBe('xhigh');