Skip to content

Commit 9ca662d

Browse files
committed
fix(provider): match ChatGPT reasoning efforts to the served catalog
The gpt-6 and gpt-6.1 models offered fewer levels than the Codex backend serves (xhigh, or max for gpt-6-astra), while gpt-5.6-luna was offered ultra, which the backend does not serve.
1 parent 0a7b2b3 commit 9ca662d

2 files changed

Lines changed: 63 additions & 27 deletions

File tree

‎src/provider/reasoning-effort.test.ts‎

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, describe, test, expect } from "bun:test";
2+
import { CODEX_DEFAULT_MODELS } from "../auth/codex/constants.js";
23
import {
34
isReasoningEffort,
45
normalizeProviderEfforts,
@@ -54,32 +55,53 @@ describe("supportedEfforts", () => {
5455
]);
5556
});
5657

57-
test("gpt-6-astra takes low through max on both API and Codex paths", () => {
58+
test("gpt-6-astra takes low through max on the API path", () => {
5859
expect(supportedEfforts("gpt-6-astra")).toEqual([
5960
"low",
6061
"medium",
6162
"high",
6263
"xhigh",
6364
"max",
6465
]);
65-
expect(supportedEfforts("gpt-6-astra", undefined, true)).toEqual([
66-
"low",
67-
"medium",
68-
"high",
69-
"xhigh",
70-
"max",
71-
]);
7266
});
7367

74-
test("gpt-5.6 family additionally takes max and ultra on the codex backend", () => {
75-
expect(supportedEfforts("gpt-5.6-sol", undefined, true)).toEqual([
76-
"low",
77-
"medium",
78-
"high",
79-
"xhigh",
80-
"max",
81-
"ultra",
82-
]);
68+
test("ChatGPT models the backend serves up to ultra offer max and ultra", () => {
69+
for (const model of [
70+
"gpt-6.1-sol",
71+
"gpt-6-astra",
72+
"gpt-6.1-terra",
73+
"gpt-6-sol",
74+
"gpt-5.6-sol",
75+
"gpt-5.6-terra",
76+
]) {
77+
expect(supportedEfforts(model, undefined, true)).toEqual([
78+
"low",
79+
"medium",
80+
"high",
81+
"xhigh",
82+
"max",
83+
"ultra",
84+
]);
85+
}
86+
});
87+
88+
test("ChatGPT Luna models stop at max", () => {
89+
for (const model of ["gpt-6-luna", "gpt-5.6-luna"]) {
90+
const efforts = supportedEfforts(model, undefined, true);
91+
expect(efforts).toEqual(["low", "medium", "high", "xhigh", "max"]);
92+
expect(efforts).not.toContain("ultra");
93+
}
94+
});
95+
96+
test("every built-in ChatGPT model has an assigned effort ladder", () => {
97+
// A model added to the fallback list without a ladder would silently
98+
// stop at xhigh. A model the backend really serves only up to xhigh
99+
// belongs here, so adding one is a decision rather than an accident.
100+
const servedOnlyUpToXhigh: readonly string[] = [];
101+
for (const model of CODEX_DEFAULT_MODELS) {
102+
if (servedOnlyUpToXhigh.includes(model)) continue;
103+
expect(supportedEfforts(model, undefined, true)).toContain("max");
104+
}
83105
});
84106

85107
test("the model name alone does not imply codex levels", () => {

‎src/provider/reasoning-effort.ts‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,30 @@ const FULL_EFFORT_MODELS: readonly string[] = [
5656
"gpt-5.1-codex-max",
5757
];
5858

59-
// Codex backend models take low/medium/high/xhigh — no `minimal`, no `none`.
59+
// ChatGPT (Codex backend) models not listed below: low/medium/high/xhigh,
60+
// no `minimal`, no `none`.
6061
const CODEX_EFFORTS: readonly ReasoningEffort[] = [
6162
"low",
6263
"medium",
6364
"high",
6465
"xhigh",
6566
];
6667

67-
// gpt-5.6 family adds `max` and `ultra`; older Codex models do not support
68-
// them.
69-
const MAX_EFFORT_CODEX_MODELS: readonly string[] = [
68+
// Per-model levels the Codex backend lists (`supported_reasoning_levels` in
69+
// GET /codex/models, client_version 0.165.0, 2026-10-28). Exact ids rather
70+
// than a name rule: an id not seen yet gets the conservative fallback above
71+
// instead of a guessed tier, because the adapter does not clamp and a level
72+
// the backend does not serve breaks the turn.
73+
const ULTRA_EFFORT_CODEX_MODELS: readonly string[] = [
74+
"gpt-6.1-sol",
75+
"gpt-6-astra",
76+
"gpt-6.1-terra",
77+
"gpt-6-sol",
7078
"gpt-5.6-sol",
7179
"gpt-5.6-terra",
80+
];
81+
const MAX_EFFORT_CODEX_MODELS: readonly string[] = [
82+
"gpt-6-luna",
7283
"gpt-5.6-luna",
7384
];
7485

@@ -96,8 +107,8 @@ function isMuseSparkModel(model: string): boolean {
96107
const GROK_46_AND_47_EFFORTS: readonly ReasoningEffort[] = CODEX_EFFORTS;
97108
const GROK_46_AND_47_MODELS: readonly string[] = ["grok-4.6", "grok-4.7"];
98109

99-
// GPT-6 Astra accepts low–max on both surfaces; not ultra (gpt-5.6
100-
// Codex-only), minimal, or none.
110+
// GPT-6 Astra on the API path accepts low–max; not ultra, minimal, or none.
111+
// On ChatGPT sign-in it follows the served-levels lists above.
101112
const GPT6_ASTRA_EFFORTS: readonly ReasoningEffort[] = [
102113
"low",
103114
"medium",
@@ -152,14 +163,17 @@ export function supportedEfforts(
152163
if (providerEfforts.length > 0) {
153164
return [...providerEfforts];
154165
}
155-
if (model === "gpt-6-astra") {
156-
return [...GPT6_ASTRA_EFFORTS];
157-
}
158166
if (isCodex) {
167+
if (ULTRA_EFFORT_CODEX_MODELS.includes(model)) {
168+
return [...CODEX_EFFORTS, "max", "ultra"];
169+
}
159170
return MAX_EFFORT_CODEX_MODELS.includes(model)
160-
? [...CODEX_EFFORTS, "max", "ultra"]
171+
? [...CODEX_EFFORTS, "max"]
161172
: [...CODEX_EFFORTS];
162173
}
174+
if (model === "gpt-6-astra") {
175+
return [...GPT6_ASTRA_EFFORTS];
176+
}
163177
if (FULL_EFFORT_MODELS.includes(model)) {
164178
return ["none", ...DEFAULT_EFFORTS, "xhigh"];
165179
}

0 commit comments

Comments
 (0)