Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/core/src/plugin/provider/openai-codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugin/provider/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export const OpenAIPlugin = define({
return
}
draft.cost = []
draft.limit = OpenAICodex.limits(draft.limit)
})
}
})
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/session/runner/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,17 @@ const codexModel = (
key: ReturnType<typeof Auth.value> | 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) =>
Expand Down
24 changes: 18 additions & 6 deletions packages/core/test/plugin/provider-openai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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)
}),
)
Expand All @@ -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({
Expand All @@ -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)
}),
)
Expand Down
7 changes: 6 additions & 1 deletion packages/core/test/session-runner-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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" }),
)
Expand All @@ -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 })
}),
)

Expand Down Expand Up @@ -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",
Expand All @@ -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")
Expand Down
Loading