From 9f3893ba21c8b6baa02af3eb0516e7c5123f087c Mon Sep 17 00:00:00 2001 From: magoz Date: Wed, 29 Jul 2026 19:10:35 +0200 Subject: [PATCH] fix(core): align ChatGPT OAuth context limits --- packages/core/src/plugin/internal.ts | 5 ++ packages/core/src/plugin/provider/openai.ts | 28 +++++++- .../core/test/plugin/provider-openai.test.ts | 71 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index d4ab71cb6b04..53c64c9faca8 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -14,6 +14,7 @@ import { ConfigExternalPlugin } from "../config/plugin/external" import { ConfigProviderPlugin } from "../config/plugin/provider" import { ConfigReferencePlugin } from "../config/plugin/reference" import { ConfigSkillPlugin } from "../config/plugin/skill" +import { Credential } from "../credential" import { EventV2 } from "../event" import { FileSystem } from "../filesystem" import { FSUtil } from "../fs-util" @@ -39,6 +40,7 @@ export type Requirements = | Catalog.Service | CommandV2.Service | Config.Service + | Credential.Service | EventV2.Service | FileSystem.Service | FSUtil.Service @@ -68,6 +70,7 @@ const layer = Layer.effectDiscard( const integration = yield* Integration.Service const agents = yield* AgentV2.Service const config = yield* Config.Service + const credential = yield* Credential.Service const location = yield* Location.Service const modelsDev = yield* ModelsDev.Service const npm = yield* Npm.Service @@ -90,6 +93,7 @@ const layer = Layer.effectDiscard( Effect.provideService(Integration.Service, integration), Effect.provideService(AgentV2.Service, agents), Effect.provideService(Config.Service, config), + Effect.provideService(Credential.Service, credential), Effect.provideService(Location.Service, location), Effect.provideService(ModelsDev.Service, modelsDev), Effect.provideService(Npm.Service, npm), @@ -139,6 +143,7 @@ export const node = makeLocationNode({ Integration.node, AgentV2.node, Config.node, + Credential.node, Location.node, ModelsDev.node, Npm.node, diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 46553eaff429..118a59377809 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -1,9 +1,10 @@ import { createServer } from "node:http" import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration" import { define } from "@opencode-ai/plugin/v2/effect/plugin" -import { Deferred, Effect } from "effect" +import { Deferred, Effect, Stream } from "effect" import type { Scope } from "effect" import { Credential } from "../../credential" +import { EventV2 } from "../../event" import { InstallationVersion } from "../../installation/version" import { Integration } from "../../integration" import { ModelV2 } from "../../model" @@ -154,15 +155,35 @@ const headless = { export const OpenAIPlugin = define({ id: "openai", effect: Effect.fn(function* (ctx) { + const credentials = yield* Credential.Service + const events = yield* EventV2.Service yield* ctx.integration.transform((draft) => { draft.method.update(browser) draft.method.update(headless) }) yield* ctx.catalog.transform( Effect.fn(function* (evt) { + const connection = yield* ctx.integration.connection.active("openai") + const credential = + connection?.type === "credential" ? yield* credentials.get(Credential.ID.make(connection.id)) : undefined for (const item of evt.provider.list()) { if (item.provider.api.type !== "aisdk") continue if (item.provider.api.package !== "@ai-sdk/openai") continue + if (item.provider.id === "openai" && credential?.value.type === "oauth") { + // ChatGPT's Codex backend exposes smaller windows than the public API catalog. + // Keep these limits aligned with the V1 Codex OAuth provider transform. + for (const model of item.models.values()) { + const limit = model.id.includes("gpt-5.5") + ? { context: 400_000, input: 272_000, output: 128_000 } + : model.id.includes("gpt-5.6") + ? { context: 500_000, input: 372_000, output: 128_000 } + : undefined + if (!limit) continue + evt.model.update(item.provider.id, model.id, (draft) => { + draft.limit = limit + }) + } + } if (!item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) continue evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => { // OpenAIPlugin sends OpenAI models through Responses; this alias is a @@ -172,6 +193,11 @@ export const OpenAIPlugin = define({ } }), ) + yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe( + Stream.filter((event) => event.data.integrationID === Integration.ID.make("openai")), + Stream.runForEach(() => ctx.catalog.reload()), + Effect.forkScoped({ startImmediately: true }), + ) yield* ctx.aisdk.sdk( Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/openai") return diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index 31a80f93190e..16607abb0dbc 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -3,6 +3,7 @@ import { describe, expect } from "bun:test" import type { LanguageModelV3 } from "@ai-sdk/provider" import { Effect } from "effect" import { Catalog } from "@opencode-ai/core/catalog" +import { Credential } from "@opencode-ai/core/credential" import { Integration } from "@opencode-ai/core/integration" import { ModelV2 } from "@opencode-ai/core/model" import { PluginV2 } from "@opencode-ai/core/plugin" @@ -153,6 +154,76 @@ describe("OpenAIPlugin", () => { }), ) + it.effect("tracks ChatGPT OAuth context limits", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const credentials = yield* Credential.Service + yield* catalog.transform((catalog) => { + const item = ProviderV2.Info.make({ + ...ProviderV2.Info.empty(ProviderV2.ID.openai), + api: { type: "aisdk", package: "@ai-sdk/openai" }, + }) + catalog.provider.update(item.id, (draft) => { + draft.api = item.api + }) + 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-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-5.7-pro"), (model) => { + model.limit = { context: 1_050_000, input: 922_000, output: 128_000 } + }) + }) + yield* credentials.create({ + integrationID: Integration.ID.make("openai"), + value: Credential.Key.make({ type: "key", key: "key" }), + }) + yield* addPlugin() + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).limit).toEqual({ + context: 1_050_000, + input: 922_000, + output: 128_000, + }) + + const oauth = yield* credentials.create({ + integrationID: Integration.ID.make("openai"), + value: Credential.OAuth.make({ + type: "oauth", + methodID: Integration.MethodID.make("chatgpt-browser"), + refresh: "refresh", + access: "access", + expires: 0, + }), + }) + yield* catalog.reload() + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5"))).limit).toEqual({ + context: 400_000, + input: 272_000, + output: 128_000, + }) + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).limit).toEqual({ + context: 500_000, + input: 372_000, + output: 128_000, + }) + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.7-pro"))).limit).toEqual({ + context: 1_050_000, + input: 922_000, + output: 128_000, + }) + + yield* credentials.remove(oauth.id) + yield* catalog.reload() + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).limit).toEqual({ + context: 1_050_000, + input: 922_000, + output: 128_000, + }) + }), + ) + it.effect("does not disable gpt-5-chat-latest for non-OpenAI providers", () => Effect.gen(function* () { const catalog = yield* Catalog.Service