feat(codex): add GPT-5.6 model catalog

Closes #1633
This commit is contained in:
Tam Nhu Tran committed 2026-07-10 08:54:48 -04:00
1 parent da6c99e3df
commit 24eae8ae8f
8 files changed
+111 -4

No files matched your search

@@ -247,6 +247,9 @@ describe('Model Catalog', () => {
const { MODEL_CATALOG } = modelCatalog;
const ids = MODEL_CATALOG.codex.models.map((m) => m.id);
assert.deepStrictEqual(ids, [
'gpt-5.6-sol',
'gpt-5.6-terra',
'gpt-5.6-luna',
'gpt-5.5',
'gpt-5.4',
'gpt-5.4-mini',
@@ -29,6 +29,9 @@ describe('codex plan compatibility', () => {
});
it('does not rewrite cross-plan or already-safe Codex models', () => {
expect(getFreePlanFallbackCodexModel('gpt-5.6-sol')).toBeNull();
expect(getFreePlanFallbackCodexModel('gpt-5.6-terra-high')).toBeNull();
expect(getFreePlanFallbackCodexModel('gpt-5.6-luna-fast')).toBeNull();
expect(getFreePlanFallbackCodexModel('gpt-5.4')).toBeNull();
expect(getFreePlanFallbackCodexModel('gpt-5.4-mini')).toBeNull();
expect(getFreePlanFallbackCodexModel('gpt-5.2')).toBeNull();
@@ -94,6 +97,9 @@ describe('codex plan compatibility', () => {
});
it('tracks Codex thinking caps for current safe defaults, paid models, and legacy aliases', () => {
expect(getModelMaxLevel('codex', 'gpt-5.6-sol')).toBe('xhigh');
expect(getModelMaxLevel('codex', 'gpt-5.6-terra')).toBe('xhigh');
expect(getModelMaxLevel('codex', 'gpt-5.6-luna')).toBe('xhigh');
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');
+36
View File
@@ -190,6 +190,42 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
displayName: 'Copilot Codex',
defaultModel: 'gpt-5.4',
models: [
{
id: 'gpt-5.6-sol',
name: 'GPT-5.6 Sol',
description: 'Latest frontier agentic coding model.',
thinking: {
type: 'levels',
levels: ['low', 'medium', 'high', 'xhigh'],
maxLevel: 'xhigh',
dynamicAllowed: false,
},
codexServiceTiers: ['fast'],
},
{
id: 'gpt-5.6-terra',
name: 'GPT-5.6 Terra',
description: 'Balanced agentic coding model for everyday work.',
thinking: {
type: 'levels',
levels: ['low', 'medium', 'high', 'xhigh'],
maxLevel: 'xhigh',
dynamicAllowed: false,
},
codexServiceTiers: ['fast'],
},
{
id: 'gpt-5.6-luna',
name: 'GPT-5.6 Luna',
description: 'Fast and affordable agentic coding model.',
thinking: {
type: 'levels',
levels: ['low', 'medium', 'high', 'xhigh'],
maxLevel: 'xhigh',
dynamicAllowed: false,
},
codexServiceTiers: ['fast'],
},
{
id: 'gpt-5.5',
name: 'GPT-5.5',
@@ -43,6 +43,8 @@ export interface ModelEntry {
};
/** Highest codex reasoning-effort suffix this model supports in the dashboard UI. */
codexMaxEffort?: CodexEffort;
/** Exact Codex reasoning-effort suffixes supported by this model in the dashboard UI. */
codexEfforts?: CodexEffort[];
/** Additional Codex service-tier suffixes supported by this model in the dashboard UI. */
codexServiceTiers?: CodexServiceTier[];
}
@@ -347,12 +349,13 @@ function getPreferredOptionValue(
function getModelOptionValues(
codexMaxEffort: CodexEffort | undefined,
codexEfforts: readonly CodexEffort[] | undefined,
codexServiceTiers: readonly CodexServiceTier[] | undefined,
optionValue: string,
isCodexProvider: boolean
): string[] {
return isCodexProvider
? getCodexEffortVariants(optionValue, codexMaxEffort, codexServiceTiers)
? getCodexEffortVariants(optionValue, codexMaxEffort, codexServiceTiers, codexEfforts)
: [optionValue];
}
@@ -395,6 +398,7 @@ export function FlexibleModelSelector({
resolvedCatalogModels.flatMap((model) =>
getModelOptionValues(
model.codexMaxEffort,
model.codexEfforts,
model.codexServiceTiers,
getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase())),
isCodexProvider
@@ -431,6 +435,7 @@ export function FlexibleModelSelector({
const routingHint = routingHints.get(model.id.toLowerCase());
const optionValues = getModelOptionValues(
model.codexMaxEffort,
model.codexEfforts,
model.codexServiceTiers,
getPreferredOptionValue(model.id, routingHint),
isCodexProvider
@@ -485,6 +490,7 @@ export function FlexibleModelSelector({
.flatMap((model) => {
const routingHint = routingHints.get(model.id.toLowerCase());
const optionValues = getModelOptionValues(
undefined,
undefined,
undefined,
getPreferredOptionValue(model.id, routingHint),
+4 -3
View File
@@ -69,7 +69,8 @@ export function applyCodexEffortSuffix(
export function getCodexEffortVariants(
modelId: string,
maxEffort: CodexEffort | undefined,
serviceTiers: readonly CodexServiceTier[] = []
serviceTiers: readonly CodexServiceTier[] = [],
efforts?: readonly CodexEffort[]
): string[] {
if (!maxEffort) {
const explicitEffort = parseCodexEffort(modelId);
@@ -91,9 +92,9 @@ export function getCodexEffortVariants(
appendVariantsForEffort(undefined);
for (const effort of CODEX_EFFORTS_IN_ORDER) {
for (const effort of efforts ?? CODEX_EFFORTS_IN_ORDER) {
appendVariantsForEffort(effort);
if (effort === maxEffort) {
if (!efforts && effort === maxEffort) {
break;
}
}
+42
View File
@@ -259,6 +259,48 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
displayName: 'Codex',
defaultModel: 'gpt-5.4',
models: [
{
id: 'gpt-5.6-sol',
name: 'GPT-5.6 Sol',
description: 'Latest frontier agentic coding model.',
codexMaxEffort: 'xhigh',
codexEfforts: ['low', 'medium', 'high', 'xhigh'],
codexServiceTiers: ['fast'],
presetMapping: {
default: 'gpt-5.6-sol',
opus: 'gpt-5.6-sol',
sonnet: 'gpt-5.6-sol',
haiku: 'gpt-5.4-mini',
},
},
{
id: 'gpt-5.6-terra',
name: 'GPT-5.6 Terra',
description: 'Balanced agentic coding model for everyday work.',
codexMaxEffort: 'xhigh',
codexEfforts: ['low', 'medium', 'high', 'xhigh'],
codexServiceTiers: ['fast'],
presetMapping: {
default: 'gpt-5.6-terra',
opus: 'gpt-5.6-terra',
sonnet: 'gpt-5.6-terra',
haiku: 'gpt-5.4-mini',
},
},
{
id: 'gpt-5.6-luna',
name: 'GPT-5.6 Luna',
description: 'Fast and affordable agentic coding model.',
codexMaxEffort: 'xhigh',
codexEfforts: ['low', 'medium', 'high', 'xhigh'],
codexServiceTiers: ['fast'],
presetMapping: {
default: 'gpt-5.6-luna',
opus: 'gpt-5.6-luna',
sonnet: 'gpt-5.6-luna',
haiku: 'gpt-5.4-mini',
},
},
{
id: 'gpt-5.5',
name: 'GPT-5.5',
@@ -191,6 +191,10 @@ describe('FlexibleModelSelector', () => {
expect(screen.getByText('gpt-5.3-codex-high')).toBeInTheDocument();
expect(screen.getByText('gpt-5.3-codex-xhigh')).toBeInTheDocument();
expect(screen.getByText('gpt-5.4-high-fast')).toBeInTheDocument();
expect(screen.getByText('gpt-5.6-sol-low')).toBeInTheDocument();
expect(screen.getByText('gpt-5.6-terra-xhigh')).toBeInTheDocument();
expect(screen.getByText('gpt-5.6-luna-fast')).toBeInTheDocument();
expect(screen.queryByText('gpt-5.6-sol-minimal')).not.toBeInTheDocument();
await userEvent.click(screen.getByText('gpt-5.3-codex-high'));
expect(onChange).toHaveBeenCalledWith('gpt-5.3-codex-high');
@@ -4,6 +4,9 @@ import { MODEL_CATALOGS } from '@/lib/model-catalogs';
describe('codex model catalog defaults', () => {
it('mirrors the current Codex runtime catalog and free-safe defaults', () => {
const codexCatalog = MODEL_CATALOGS.codex;
const codex56Sol = codexCatalog.models.find((model) => model.id === 'gpt-5.6-sol');
const codex56Terra = codexCatalog.models.find((model) => model.id === 'gpt-5.6-terra');
const codex56Luna = codexCatalog.models.find((model) => model.id === 'gpt-5.6-luna');
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');
@@ -11,6 +14,12 @@ describe('codex model catalog defaults', () => {
const codexMini = codexCatalog.models.find((model) => model.id === 'gpt-5.4-mini');
expect(codexCatalog.defaultModel).toBe('gpt-5.4');
for (const model of [codex56Sol, codex56Terra, codex56Luna]) {
expect(model?.tier).toBeUndefined();
expect(model?.codexEfforts).toEqual(['low', 'medium', 'high', 'xhigh']);
expect(model?.codexServiceTiers).toEqual(['fast']);
expect(model?.presetMapping?.haiku).toBe('gpt-5.4-mini');
}
expect(codex55?.tier).toBe('paid');
expect(codex55?.presetMapping?.haiku).toBe('gpt-5.4-mini');
expect(codex55?.codexMaxEffort).toBe('xhigh');