From ae3564f3490ad54f28cad3555d7fc30d579f0f3c Mon Sep 17 00:00:00 2001 From: shuv Date: Wed, 22 Jul 2026 11:12:48 -0700 Subject: [PATCH] fix(core): correct Codex OAuth context limit --- .../core/src/plugin/provider/openai-codex.ts | 16 +++++++++++++ packages/core/src/plugin/provider/openai.ts | 1 + packages/core/src/session/runner/model.ts | 7 ++++-- .../core/test/plugin/provider-openai.test.ts | 24 ++++++++++++++----- .../core/test/session-runner-model.test.ts | 7 +++++- 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/src/plugin/provider/openai-codex.ts b/packages/core/src/plugin/provider/openai-codex.ts index 8d4389d969e9..d21421a6cb3e 100644 --- a/packages/core/src/plugin/provider/openai-codex.ts +++ b/packages/core/src/plugin/provider/openai-codex.ts @@ -12,6 +12,22 @@ export * as OpenAICodex from "./openai-codex" /** ChatGPT-plan requests must target the codex backend instead of the public API. */ export const baseURL = "https://chatgpt.com/backend-api/codex" +// ChatGPT Codex OAuth exposes a smaller window than the public Responses API. +// Cap instead of replacing so models with smaller windows retain their limit. +const contextWindow = 272_000 + +type Limits = { + readonly context: number + readonly input?: number + readonly output: number +} + +export const limits = (value: Limits): Limits => ({ + context: Math.min(value.context, contextWindow), + ...(value.input === undefined ? {} : { input: Math.min(value.input, contextWindow) }), + output: value.output, +}) + const methodIDs: readonly string[] = ["chatgpt-browser", "chatgpt-headless"] /** Structural credential shape so both core and plugin-facing credential types fit. */ diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index b6ff9e581064..59a81982abe7 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -206,6 +206,7 @@ export const OpenAIPlugin = define({ return } draft.cost = [] + draft.limit = OpenAICodex.limits(draft.limit) }) } }) diff --git a/packages/core/src/session/runner/model.ts b/packages/core/src/session/runner/model.ts index efb584fb43d4..3ffad3d9f738 100644 --- a/packages/core/src/session/runner/model.ts +++ b/packages/core/src/session/runner/model.ts @@ -295,14 +295,17 @@ const codexModel = ( key: ReturnType | undefined, ) => { const account = OpenAICodex.accountID(credential) - return withDefaults(model, OpenAIResponses.route) + const runtime = produce(model, (draft) => { + draft.limit = OpenAICodex.limits(draft.limit) + }) + return withDefaults(runtime, OpenAIResponses.route) .with({ endpoint: { baseURL: OpenAICodex.baseURL }, auth: (key === undefined ? Auth.none : Auth.bearer(key)).andThen( account === undefined ? Auth.none : Auth.headers({ "chatgpt-account-id": account }), ), }) - .model({ id: model.modelID ?? model.id }) + .model({ id: runtime.modelID ?? runtime.id }) } const unsupported = (model: ModelV2.Info) => diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index cd0c943442f8..e8213b031a9a 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -184,12 +184,17 @@ describe("OpenAIPlugin", () => { ] }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5-pro"), () => {}) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.3-codex-spark"), (model) => { + model.limit = { context: 128_000, input: 100_000, output: 16_000 } + }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5.4-pro"), (model) => { model.modelID = ModelV2.ID.make("gpt-5.4") model.body = { reasoning: { mode: "pro" } } }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6"), () => {}) - catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6-sol"), () => {}) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6-sol"), (model) => { + model.limit = { context: 1_050_000, input: 922_000, output: 128_000 } + }) catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {}) }) yield* credentials.create({ @@ -215,9 +220,12 @@ describe("OpenAIPlugin", () => { false, ) expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6"))).enabled).toBe(false) - expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).enabled).toBe( - true, - ) + const sol = required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))) + expect(sol.enabled).toBe(true) + expect(sol.limit).toEqual({ context: 272_000, input: 272_000, output: 128_000 }) + expect( + required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.3-codex-spark"))).limit, + ).toEqual({ context: 128_000, input: 100_000, output: 16_000 }) expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(false) }), ) @@ -234,7 +242,9 @@ describe("OpenAIPlugin", () => { catalog.provider.update(item.id, (draft) => { draft.package = item.package }) - catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5"), () => {}) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5"), (model) => { + model.limit = { context: 1_050_000, input: 922_000, output: 128_000 } + }) catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {}) }) yield* credentials.create({ @@ -243,7 +253,9 @@ describe("OpenAIPlugin", () => { }) yield* addPlugin() - expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5"))).enabled).toBe(true) + const gpt = required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5"))) + expect(gpt.enabled).toBe(true) + expect(gpt.limit).toEqual({ context: 1_050_000, input: 922_000, output: 128_000 }) expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(true) }), ) diff --git a/packages/core/test/session-runner-model.test.ts b/packages/core/test/session-runner-model.test.ts index 291cd66cc330..b1fe64894d90 100644 --- a/packages/core/test/session-runner-model.test.ts +++ b/packages/core/test/session-runner-model.test.ts @@ -16,6 +16,7 @@ import { it } from "./lib/effect" interface ModelOptions { readonly modelID?: string + readonly limit?: ModelV2.Info["limit"] readonly settings?: ModelV2.Info["settings"] readonly headers?: ModelV2.Info["headers"] readonly body?: ModelV2.Info["body"] @@ -38,7 +39,7 @@ const model = (packageName: string | undefined, options: ModelOptions = {}) => cost: [], status: "active", enabled: true, - limit: { context: 100, output: 20 }, + limit: options.limit ?? { context: 100, output: 20 }, }) describe("SessionRunnerModel", () => { @@ -297,6 +298,7 @@ describe("SessionRunnerModel", () => { settings: { baseURL: "https://openai.example/v1" }, headers: {}, body: {}, + limit: { context: 1_050_000, input: 922_000, output: 128_000 }, }), Credential.Key.make({ type: "key", key: "secret" }), ) @@ -310,6 +312,7 @@ describe("SessionRunnerModel", () => { }) expect(headers.authorization).toBe("Bearer secret") + expect(resolved.route.defaults.limits).toEqual({ context: 1_050_000, output: 128_000 }) }), ) @@ -366,6 +369,7 @@ describe("SessionRunnerModel", () => { settings: { baseURL: "https://openai.example/v1" }, headers: {}, body: {}, + limit: { context: 1_050_000, input: 922_000, output: 128_000 }, }), Credential.OAuth.make({ type: "oauth", @@ -388,6 +392,7 @@ describe("SessionRunnerModel", () => { expect(resolved.route).toMatchObject({ id: "openai-responses", endpoint: { baseURL: "https://chatgpt.com/backend-api/codex" }, + defaults: { limits: { context: 272_000, output: 128_000 } }, }) expect(headers.authorization).toBe("Bearer chatgpt-token") expect(headers["chatgpt-account-id"]).toBe("acct_123")