From 0c43b270b9164349c04d5c35b56ec27c588fb8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Thu, 21 May 2026 14:15:09 +0800 Subject: [PATCH] fix: Align OpenAI Codex context windows ChatGPT Official runs through Codex OAuth, so context accounting should follow the Codex model catalog and its effective context window instead of public API limits. This keeps GPT-5.5 at the same usable window shown by Codex while preserving GPT-5.4's larger Codex window. Constraint: Codex OAuth model metadata applies effective_context_window_percent to raw catalog windows Rejected: Use public API GPT-5.5 1.05M context | Codex app and OAuth runtime clamp this model to the smaller effective window Confidence: high Scope-risk: narrow Directive: Keep ChatGPT Official context values aligned to Codex model catalog behavior, not generic OpenAI API docs Tested: bun test src/services/openaiAuth/models.test.ts src/services/compact/autoCompact.test.ts src/server/__tests__/providers.test.ts Tested: bun run check:server Tested: git diff --check Not-tested: Manual desktop screenshot after rebuilding the app --- src/server/__tests__/providers.test.ts | 6 +++ src/server/services/openaiOfficialProvider.ts | 9 ++-- src/services/compact/autoCompact.test.ts | 7 +++ src/services/openaiAuth/models.test.ts | 31 ++++++------- src/services/openaiAuth/models.ts | 44 +++++++++++++++---- src/utils/context.ts | 4 +- 6 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/server/__tests__/providers.test.ts b/src/server/__tests__/providers.test.ts index 8bd7fbed..8d215bbd 100644 --- a/src/server/__tests__/providers.test.ts +++ b/src/server/__tests__/providers.test.ts @@ -355,6 +355,12 @@ describe('ProviderService', () => { expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.4') expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex') expect(typeof env.CLAUDE_CODE_MODEL_CONTEXT_WINDOWS).toBe('string') + expect(JSON.parse(env.CLAUDE_CODE_MODEL_CONTEXT_WINDOWS)).toEqual({ + 'gpt-5.3-codex': 258_400, + 'gpt-5.4': 950_000, + 'gpt-5.5': 258_400, + 'gpt-5.4-mini': 258_400, + }) expect(env.ANTHROPIC_BASE_URL).toBeUndefined() expect(env.ANTHROPIC_API_KEY).toBeUndefined() expect(env.ANTHROPIC_AUTH_TOKEN).toBeUndefined() diff --git a/src/server/services/openaiOfficialProvider.ts b/src/server/services/openaiOfficialProvider.ts index 5d39cbc4..bfc05a06 100644 --- a/src/server/services/openaiOfficialProvider.ts +++ b/src/server/services/openaiOfficialProvider.ts @@ -1,9 +1,10 @@ import { OPENAI_CODEX_API_ENDPOINT } from '../../services/openaiAuth/client.js' import { + OPENAI_CODEX_MODEL_CATALOG, OPENAI_DEFAULT_HAIKU_MODEL, OPENAI_DEFAULT_MAIN_MODEL, OPENAI_DEFAULT_SONNET_MODEL, - getOpenAIContextWindowForModel, + getOpenAICodexContextWindowForModel, } from '../../services/openaiAuth/models.js' import { MODEL_CONTEXT_WINDOWS_ENV_KEY } from '../../utils/model/modelContextWindows.js' import { getHahaOpenAIOAuthFilePath } from './hahaOpenAIOAuthService.js' @@ -28,8 +29,10 @@ const openAIModels: SavedProvider['models'] = { } const modelContextWindows = Object.fromEntries( - Object.values(openAIModels) - .map((model) => [model, getOpenAIContextWindowForModel(model)] as const) + OPENAI_CODEX_MODEL_CATALOG.map( + ({ value }) => + [value, getOpenAICodexContextWindowForModel(value)] as const, + ) .filter((entry): entry is readonly [string, number] => entry[1] !== null), ) diff --git a/src/services/compact/autoCompact.test.ts b/src/services/compact/autoCompact.test.ts index e4c5853e..913f4ade 100644 --- a/src/services/compact/autoCompact.test.ts +++ b/src/services/compact/autoCompact.test.ts @@ -36,6 +36,13 @@ describe('model context window resolution', () => { expect(getContextWindowForModel('glm-4.5-air')).toBe(128_000) }) + test('uses Codex OAuth effective context windows for OpenAI GPT models', () => { + expect(getContextWindowForModel('gpt-5.5')).toBe(258_400) + expect(getContextWindowForModel('gpt-5.4')).toBe(950_000) + expect(getContextWindowForModel('gpt-5.4-mini')).toBe(258_400) + expect(getContextWindowForModel('gpt-5.3-codex-spark')).toBe(121_600) + }) + test('uses per-model provider overrides before built-in defaults', () => { process.env[MODEL_CONTEXT_WINDOWS_ENV_KEY] = JSON.stringify({ 'deepseek-v4-pro': 500_000, diff --git a/src/services/openaiAuth/models.test.ts b/src/services/openaiAuth/models.test.ts index 284bf158..b8bcf9b6 100644 --- a/src/services/openaiAuth/models.test.ts +++ b/src/services/openaiAuth/models.test.ts @@ -1,9 +1,10 @@ import { describe, expect, test } from 'bun:test' import { + OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW, + OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW, + OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW, OPENAI_DEFAULT_MAIN_MODEL, - OPENAI_GPT5_LARGE_CONTEXT_WINDOW, - OPENAI_GPT5_STANDARD_CONTEXT_WINDOW, - getOpenAIContextWindowForModel, + getOpenAICodexContextWindowForModel, isOpenAIResponsesModel, resolveOpenAICodexModel, } from './models.js' @@ -22,21 +23,21 @@ describe('openai auth model resolution', () => { expect(resolveOpenAICodexModel('opus')).toBe(OPENAI_DEFAULT_MAIN_MODEL) }) - test('maps large-context GPT-5 models to a 1.05M context window', () => { - expect(getOpenAIContextWindowForModel('gpt-5.5')).toBe( - OPENAI_GPT5_LARGE_CONTEXT_WINDOW, + test('maps Codex OAuth GPT models to effective Codex context windows', () => { + expect(getOpenAICodexContextWindowForModel('gpt-5.5')).toBe( + OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW, ) - expect(getOpenAIContextWindowForModel('gpt-5.4')).toBe( - OPENAI_GPT5_LARGE_CONTEXT_WINDOW, + expect(getOpenAICodexContextWindowForModel('gpt-5.4')).toBe( + OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW, ) - }) - - test('maps standard GPT-5 models to a 400k context window', () => { - expect(getOpenAIContextWindowForModel('gpt-5.3-codex')).toBe( - OPENAI_GPT5_STANDARD_CONTEXT_WINDOW, + expect(getOpenAICodexContextWindowForModel('gpt-5.3-codex')).toBe( + OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW, ) - expect(getOpenAIContextWindowForModel('gpt-5.4-mini')).toBe( - OPENAI_GPT5_STANDARD_CONTEXT_WINDOW, + expect(getOpenAICodexContextWindowForModel('gpt-5.4-mini')).toBe( + OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW, + ) + expect(getOpenAICodexContextWindowForModel('gpt-5.3-codex-spark')).toBe( + OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW, ) }) }) diff --git a/src/services/openaiAuth/models.ts b/src/services/openaiAuth/models.ts index b5ed2505..1222d6dd 100644 --- a/src/services/openaiAuth/models.ts +++ b/src/services/openaiAuth/models.ts @@ -1,8 +1,22 @@ export const OPENAI_DEFAULT_MAIN_MODEL = 'gpt-5.3-codex' export const OPENAI_DEFAULT_SONNET_MODEL = 'gpt-5.4' export const OPENAI_DEFAULT_HAIKU_MODEL = 'gpt-5.4-mini' -export const OPENAI_GPT5_STANDARD_CONTEXT_WINDOW = 400_000 -export const OPENAI_GPT5_LARGE_CONTEXT_WINDOW = 1_050_000 +export const OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT = 95 +export const OPENAI_CODEX_STANDARD_CONTEXT_WINDOW = 272_000 +export const OPENAI_CODEX_LARGE_CONTEXT_WINDOW = 1_000_000 +export const OPENAI_CODEX_SPARK_CONTEXT_WINDOW = 128_000 +export const OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW = Math.floor( + (OPENAI_CODEX_STANDARD_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) / + 100, +) +export const OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW = Math.floor( + (OPENAI_CODEX_LARGE_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) / + 100, +) +export const OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW = Math.floor( + (OPENAI_CODEX_SPARK_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) / + 100, +) export type OpenAIModelCatalogEntry = { value: string @@ -80,6 +94,8 @@ export function getOpenAIModelDisplayName(model: string): string | null { switch (model.trim().toLowerCase()) { case 'gpt-5.3-codex': return 'GPT-5.3 Codex' + case 'gpt-5.3-codex-spark': + return 'GPT-5.3 Codex Spark' case 'gpt-5.5': return 'GPT-5.5' case 'gpt-5.4': @@ -101,21 +117,28 @@ export function getOpenAIModelDisplayName(model: string): string | null { } } -export function getOpenAIContextWindowForModel(model: string): number | null { +export function getOpenAICodexContextWindowForModel( + model: string, +): number | null { const normalized = model.trim().toLowerCase() - // Current OpenAI model docs split GPT-5-family context windows into - // standard 400k models and larger 1.05M models. + // Codex OAuth follows the Codex app model catalog, not the public API model + // context limits. The catalog applies effective_context_window_percent=95, + // and the runtime /context display reports this effective window. if ( - normalized === 'gpt-5.5' || - normalized === 'gpt-5.5-pro' || normalized === 'gpt-5.4' || normalized === 'gpt-5.4-pro' ) { - return OPENAI_GPT5_LARGE_CONTEXT_WINDOW + return OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW + } + + if (normalized === 'gpt-5.3-codex-spark') { + return OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW } if ( + normalized === 'gpt-5.5' || + normalized === 'gpt-5.5-pro' || normalized === 'gpt-5.4-mini' || normalized === 'gpt-5.4-nano' || normalized === 'gpt-5.3-codex' || @@ -130,8 +153,11 @@ export function getOpenAIContextWindowForModel(model: string): number | null { normalized === 'gpt-5-mini' || normalized === 'gpt-5-nano' ) { - return OPENAI_GPT5_STANDARD_CONTEXT_WINDOW + return OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW } return null } + +export const getOpenAIContextWindowForModel = + getOpenAICodexContextWindowForModel diff --git a/src/utils/context.ts b/src/utils/context.ts index 8da26ad3..37045ed2 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -1,6 +1,6 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import { CONTEXT_1M_BETA_HEADER } from '../constants/betas.js' -import { getOpenAIContextWindowForModel } from '../services/openaiAuth/models.js' +import { getOpenAICodexContextWindowForModel } from '../services/openaiAuth/models.js' import { getGlobalConfig } from './config.js' import { isEnvTruthy } from './envUtils.js' import { getCanonicalName } from './model/model.js' @@ -84,7 +84,7 @@ export function getContextWindowForModel( return configuredWindow } - const openAIContextWindow = getOpenAIContextWindowForModel(model) + const openAIContextWindow = getOpenAICodexContextWindowForModel(model) if (openAIContextWindow) { return openAIContextWindow }