fix: codex thinking handling

This commit is contained in:
Ben Vargas 2026-01-05 11:40:44 -07:00 committed by Mario Zechner
parent 22870ae0c2
commit 02b72b49d5
23 changed files with 205 additions and 754 deletions

View file

@ -2781,6 +2781,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["medium","high"],
input: ["text", "image"],
cost: {
input: 0,
@ -2815,6 +2816,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["low","medium","high"],
input: ["text", "image"],
cost: {
input: 0,
@ -2832,40 +2834,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5-codex-mini-high": {
id: "gpt-5-codex-mini-high",
name: "gpt-5-codex-mini-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5-codex-mini-medium": {
id: "gpt-5-codex-mini-medium",
name: "gpt-5-codex-mini-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["medium","high"],
input: ["text", "image"],
cost: {
input: 0,
@ -2951,40 +2920,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-high": {
id: "gpt-5.1-codex-high",
name: "gpt-5.1-codex-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-low": {
id: "gpt-5.1-codex-low",
name: "gpt-5.1-codex-low",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["low","medium","high"],
input: ["text", "image"],
cost: {
input: 0,
@ -3002,91 +2938,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-max-high": {
id: "gpt-5.1-codex-max-high",
name: "gpt-5.1-codex-max-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-max-low": {
id: "gpt-5.1-codex-max-low",
name: "gpt-5.1-codex-max-low",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-max-medium": {
id: "gpt-5.1-codex-max-medium",
name: "gpt-5.1-codex-max-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-max-xhigh": {
id: "gpt-5.1-codex-max-xhigh",
name: "gpt-5.1-codex-max-xhigh",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-medium": {
id: "gpt-5.1-codex-medium",
name: "gpt-5.1-codex-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["low","medium","high","xhigh"],
input: ["text", "image"],
cost: {
input: 0,
@ -3104,108 +2956,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-mini-high": {
id: "gpt-5.1-codex-mini-high",
name: "gpt-5.1-codex-mini-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-codex-mini-medium": {
id: "gpt-5.1-codex-mini-medium",
name: "gpt-5.1-codex-mini-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-high": {
id: "gpt-5.1-high",
name: "gpt-5.1-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-low": {
id: "gpt-5.1-low",
name: "gpt-5.1-low",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-medium": {
id: "gpt-5.1-medium",
name: "gpt-5.1-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.1-none": {
id: "gpt-5.1-none",
name: "gpt-5.1-none",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["medium","high"],
input: ["text", "image"],
cost: {
input: 0,
@ -3240,159 +2991,7 @@ export const MODELS = {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-codex-high": {
id: "gpt-5.2-codex-high",
name: "gpt-5.2-codex-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-codex-low": {
id: "gpt-5.2-codex-low",
name: "gpt-5.2-codex-low",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-codex-medium": {
id: "gpt-5.2-codex-medium",
name: "gpt-5.2-codex-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-codex-xhigh": {
id: "gpt-5.2-codex-xhigh",
name: "gpt-5.2-codex-xhigh",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-high": {
id: "gpt-5.2-high",
name: "gpt-5.2-high",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-low": {
id: "gpt-5.2-low",
name: "gpt-5.2-low",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-medium": {
id: "gpt-5.2-medium",
name: "gpt-5.2-medium",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-none": {
id: "gpt-5.2-none",
name: "gpt-5.2-none",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-codex-responses">,
"gpt-5.2-xhigh": {
id: "gpt-5.2-xhigh",
name: "gpt-5.2-xhigh",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevels: ["low","medium","high","xhigh"],
input: ["text", "image"],
cost: {
input: 0,

View file

@ -1,13 +1,50 @@
import { MODELS } from "./models.generated.js";
import type { Api, KnownProvider, Model, Usage } from "./types.js";
import type { Api, KnownProvider, Model, ReasoningEffort, Usage } from "./types.js";
const modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
const CODEX_THINKING_SUFFIXES = ["-none", "-minimal", "-low", "-medium", "-high", "-xhigh"];
const CODEX_THINKING_LEVELS: Record<string, ReasoningEffort[]> = {
"gpt-5.2-codex": ["low", "medium", "high", "xhigh"],
"gpt-5.1-codex-max": ["low", "medium", "high", "xhigh"],
"gpt-5.1-codex": ["low", "medium", "high"],
"gpt-5.1-codex-mini": ["medium", "high"],
"codex-mini-latest": ["medium", "high"],
"gpt-5-codex-mini": ["medium", "high"],
"gpt-5-codex": ["low", "medium", "high"],
};
function isCodexThinkingVariant(modelId: string): boolean {
const normalized = modelId.toLowerCase();
return CODEX_THINKING_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
}
function normalizeCodexModelId(modelId: string): string {
const normalized = modelId.toLowerCase();
for (const suffix of CODEX_THINKING_SUFFIXES) {
if (normalized.endsWith(suffix)) {
return modelId.slice(0, modelId.length - suffix.length);
}
}
return modelId;
}
function applyCodexThinkingLevels<TApi extends Api>(model: Model<TApi>): Model<TApi> {
if (model.provider !== "openai-codex") return model;
const thinkingLevels = CODEX_THINKING_LEVELS[model.id];
if (!thinkingLevels) return model;
return { ...model, thinkingLevels };
}
// Initialize registry from MODELS on module load
for (const [provider, models] of Object.entries(MODELS)) {
const providerModels = new Map<string, Model<Api>>();
for (const [id, model] of Object.entries(models)) {
providerModels.set(id, model as Model<Api>);
const typedModel = model as Model<Api>;
if (provider === "openai-codex" && isCodexThinkingVariant(typedModel.id)) {
continue;
}
providerModels.set(id, applyCodexThinkingLevels(typedModel));
}
modelRegistry.set(provider, providerModels);
}
@ -21,7 +58,17 @@ export function getModel<TProvider extends KnownProvider, TModelId extends keyof
provider: TProvider,
modelId: TModelId,
): Model<ModelApi<TProvider, TModelId>> {
return modelRegistry.get(provider)?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;
const providerModels = modelRegistry.get(provider);
const direct = providerModels?.get(modelId as string);
if (direct) return direct as Model<ModelApi<TProvider, TModelId>>;
if (provider === "openai-codex") {
const normalized = normalizeCodexModelId(modelId as string);
const normalizedModel = providerModels?.get(normalized);
if (normalizedModel) {
return normalizedModel as Model<ModelApi<TProvider, TModelId>>;
}
}
return direct as unknown as Model<ModelApi<TProvider, TModelId>>;
}
export function getProviders(): KnownProvider[] {
@ -52,6 +99,9 @@ const XHIGH_MODELS = new Set(["gpt-5.1-codex-max", "gpt-5.2", "gpt-5.2-codex"]);
* Currently only certain OpenAI models support this.
*/
export function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {
if (model.thinkingLevels) {
return model.thinkingLevels.includes("xhigh");
}
return XHIGH_MODELS.has(model.id);
}

View file

@ -126,10 +126,12 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
context.systemPrompt,
);
const reasoningEffort = transformedBody.reasoning?.effort ?? null;
const headers = createCodexHeaders(model.headers, accountId, apiKey, transformedBody.prompt_cache_key);
logCodexDebug("codex request", {
url,
model: params.model,
reasoningEffort,
headers: redactHeaders(headers),
});

View file

@ -308,11 +308,15 @@ export async function transformRequestBody(
}
}
const reasoningConfig = getReasoningConfig(normalizedModel, options);
body.reasoning = {
...body.reasoning,
...reasoningConfig,
};
if (options.reasoningEffort !== undefined) {
const reasoningConfig = getReasoningConfig(normalizedModel, options);
body.reasoning = {
...body.reasoning,
...reasoningConfig,
};
} else {
delete body.reasoning;
}
body.text = {
...body.text,

View file

@ -210,6 +210,8 @@ export interface Model<TApi extends Api> {
provider: Provider;
baseUrl: string;
reasoning: boolean;
/** Supported reasoning levels for this model (excluding "off"). */
thinkingLevels?: ReasoningEffort[];
input: ("text" | "image")[];
cost: {
input: number; // $/million tokens