mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 06:20:04 +00:00
feat: add gpt-5.5 to codex model catalogs
This commit is contained in:
@@ -18,6 +18,7 @@ const KNOWN_CODEX_MODELS = new Set(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const FREE_PLAN_FALLBACKS = new Map<string, string>([
|
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', FREE_SAFE_DEFAULT_MODEL],
|
||||||
['gpt-5.3-codex-spark', FREE_SAFE_FAST_MODEL],
|
['gpt-5.3-codex-spark', FREE_SAFE_FAST_MODEL],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -188,6 +188,19 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
|||||||
displayName: 'Copilot Codex',
|
displayName: 'Copilot Codex',
|
||||||
defaultModel: 'gpt-5.4',
|
defaultModel: 'gpt-5.4',
|
||||||
models: [
|
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',
|
id: 'gpt-5.4',
|
||||||
name: 'GPT-5.4',
|
name: 'GPT-5.4',
|
||||||
|
|||||||
@@ -45,6 +45,48 @@ async function importCompatibilityModule(cacheTag: string) {
|
|||||||
const identity = (message: string) => message;
|
const identity = (message: string) => message;
|
||||||
|
|
||||||
describe('codex plan compatibility reconcile', () => {
|
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 () => {
|
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 { tmpDir, settingsPath } = createCodexSettingsFixture('gpt-5.3-codex-spark');
|
||||||
const errorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ describe('codex plan compatibility', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('maps paid-only free-plan models to safe fallbacks', () => {
|
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')).toBe('gpt-5.4');
|
||||||
expect(getFreePlanFallbackCodexModel('gpt-5.3-codex-xhigh')).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');
|
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', () => {
|
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')).toBe('xhigh');
|
||||||
expect(getModelMaxLevel('codex', 'gpt-5.4-mini')).toBe('high');
|
expect(getModelMaxLevel('codex', 'gpt-5.4-mini')).toBe('high');
|
||||||
expect(getModelMaxLevel('codex', 'gpt-5-codex')).toBe('xhigh');
|
expect(getModelMaxLevel('codex', 'gpt-5-codex')).toBe('xhigh');
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ describe('Model Catalog', () => {
|
|||||||
const { MODEL_CATALOG } = modelCatalog;
|
const { MODEL_CATALOG } = modelCatalog;
|
||||||
const ids = MODEL_CATALOG.codex.models.map((m) => m.id);
|
const ids = MODEL_CATALOG.codex.models.map((m) => m.id);
|
||||||
assert.deepStrictEqual(ids, [
|
assert.deepStrictEqual(ids, [
|
||||||
|
'gpt-5.5',
|
||||||
'gpt-5.4',
|
'gpt-5.4',
|
||||||
'gpt-5.4-mini',
|
'gpt-5.4-mini',
|
||||||
'gpt-5.3-codex',
|
'gpt-5.3-codex',
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function TopLevelControlsForm({
|
|||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setDraft((current) => ({ ...current, model: event.target.value || null }))
|
setDraft((current) => ({ ...current, model: event.target.value || null }))
|
||||||
}
|
}
|
||||||
placeholder="gpt-5.4"
|
placeholder="gpt-5.5"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,7 +312,9 @@ function TopLevelControlsForm({
|
|||||||
className="text-[10px] uppercase tracking-[0.16em] text-muted-foreground"
|
className="text-[10px] uppercase tracking-[0.16em] text-muted-foreground"
|
||||||
>
|
>
|
||||||
{/* TODO i18n: missing keys codex.gpt54Selected / codex.gpt54Reference */}
|
{/* 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>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
@@ -500,16 +502,16 @@ function TopLevelControlsForm({
|
|||||||
{/* TODO i18n: missing key codex.docs */}
|
{/* TODO i18n: missing key codex.docs */}
|
||||||
<span className="text-[10px] uppercase tracking-[0.14em]">Docs</span>
|
<span className="text-[10px] uppercase tracking-[0.14em]">Docs</span>
|
||||||
<a
|
<a
|
||||||
href="https://developers.openai.com/api/docs/models/gpt-5.4"
|
href="https://platform.openai.com/docs/models"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="underline underline-offset-2 hover:text-foreground"
|
className="underline underline-offset-2 hover:text-foreground"
|
||||||
>
|
>
|
||||||
{/* TODO i18n: missing key codex.gpt54ModelPage */}
|
{/* TODO i18n: missing key codex.gptModelGuide */}
|
||||||
GPT-5.4 model page
|
GPT-5 model guide
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://openai.com/index/introducing-gpt-5-4/"
|
href="https://openai.com/index/introducing-gpt-5-5/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="underline underline-offset-2 hover:text-foreground"
|
className="underline underline-offset-2 hover:text-foreground"
|
||||||
|
|||||||
@@ -259,6 +259,19 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
|||||||
displayName: 'Codex',
|
displayName: 'Codex',
|
||||||
defaultModel: 'gpt-5-codex',
|
defaultModel: 'gpt-5-codex',
|
||||||
models: [
|
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',
|
id: 'gpt-5-codex',
|
||||||
name: 'GPT-5 Codex',
|
name: 'GPT-5 Codex',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('CodexTopLevelControlsCard', () => {
|
|||||||
const saveButton = screen.getByRole('button', { name: 'Save top-level settings' });
|
const saveButton = screen.getByRole('button', { name: 'Save top-level settings' });
|
||||||
expect(saveButton).toBeDisabled();
|
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();
|
expect(saveButton).toBeEnabled();
|
||||||
|
|
||||||
await userEvent.click(saveButton);
|
await userEvent.click(saveButton);
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
|||||||
describe('codex model catalog defaults', () => {
|
describe('codex model catalog defaults', () => {
|
||||||
it('uses gpt-5-codex-mini as the haiku mapping for cross-plan codex presets', () => {
|
it('uses gpt-5-codex-mini as the haiku mapping for cross-plan codex presets', () => {
|
||||||
const codexCatalog = MODEL_CATALOGS.codex;
|
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 codex53 = codexCatalog.models.find((model) => model.id === 'gpt-5.3-codex');
|
||||||
const codex52 = codexCatalog.models.find((model) => model.id === 'gpt-5.2-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');
|
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(codex53?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
|
||||||
expect(codex52?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
|
expect(codex52?.presetMapping?.haiku).toBe('gpt-5-codex-mini');
|
||||||
expect(codex53?.codexMaxEffort).toBe('xhigh');
|
expect(codex53?.codexMaxEffort).toBe('xhigh');
|
||||||
|
|||||||
Reference in New Issue
Block a user