feat(openai): add gpt-5.4 support for API and Codex OAuth (#36590)

* feat(openai): add gpt-5.4 support and priority processing

* feat(openai-codex): add gpt-5.4 oauth support

* fix(openai): preserve provider overrides in gpt-5.4 fallback

* fix(openai-codex): keep xhigh for gpt-5.4 default

* fix(models): preserve configured overrides in list output

* fix(models): close gpt-5.4 integration gaps

* fix(openai): scope service tier to public api

* fix(openai): complete prep followups for gpt-5.4 support (#36590) (thanks @dorukardahan)

---------

Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
This commit is contained in:
dorukardahan
2026-03-06 08:01:37 +03:00
committed by GitHub
parent 8c85ad540a
commit 5d4b04040d
27 changed files with 913 additions and 178 deletions

View File

@@ -4,6 +4,15 @@ import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
import { normalizeModelCompat } from "./model-compat.js";
import { normalizeProviderId } from "./model-selection.js";
const OPENAI_GPT_54_MODEL_ID = "gpt-5.4";
const OPENAI_GPT_54_PRO_MODEL_ID = "gpt-5.4-pro";
const OPENAI_GPT_54_CONTEXT_TOKENS = 1_050_000;
const OPENAI_GPT_54_MAX_TOKENS = 128_000;
const OPENAI_GPT_54_TEMPLATE_MODEL_IDS = ["gpt-5.2"] as const;
const OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS = ["gpt-5.2-pro", "gpt-5.2"] as const;
const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4";
const OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2-codex"] as const;
const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex";
const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
@@ -25,6 +34,58 @@ const GEMINI_3_1_FLASH_PREFIX = "gemini-3.1-flash";
const GEMINI_3_1_PRO_TEMPLATE_IDS = ["gemini-3-pro-preview"] as const;
const GEMINI_3_1_FLASH_TEMPLATE_IDS = ["gemini-3-flash-preview"] as const;
function resolveOpenAIGpt54ForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
const normalizedProvider = normalizeProviderId(provider);
if (normalizedProvider !== "openai") {
return undefined;
}
const trimmedModelId = modelId.trim();
const lower = trimmedModelId.toLowerCase();
let templateIds: readonly string[];
if (lower === OPENAI_GPT_54_MODEL_ID) {
templateIds = OPENAI_GPT_54_TEMPLATE_MODEL_IDS;
} else if (lower === OPENAI_GPT_54_PRO_MODEL_ID) {
templateIds = OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS;
} else {
return undefined;
}
return (
cloneFirstTemplateModel({
normalizedProvider,
trimmedModelId,
templateIds: [...templateIds],
modelRegistry,
patch: {
api: "openai-responses",
provider: normalizedProvider,
baseUrl: "https://api.openai.com/v1",
reasoning: true,
input: ["text", "image"],
contextWindow: OPENAI_GPT_54_CONTEXT_TOKENS,
maxTokens: OPENAI_GPT_54_MAX_TOKENS,
},
}) ??
normalizeModelCompat({
id: trimmedModelId,
name: trimmedModelId,
api: "openai-responses",
provider: normalizedProvider,
baseUrl: "https://api.openai.com/v1",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: OPENAI_GPT_54_CONTEXT_TOKENS,
maxTokens: OPENAI_GPT_54_MAX_TOKENS,
} as Model<Api>)
);
}
function cloneFirstTemplateModel(params: {
normalizedProvider: string;
trimmedModelId: string;
@@ -48,23 +109,35 @@ function cloneFirstTemplateModel(params: {
return undefined;
}
const CODEX_GPT54_ELIGIBLE_PROVIDERS = new Set(["openai-codex"]);
const CODEX_GPT53_ELIGIBLE_PROVIDERS = new Set(["openai-codex", "github-copilot"]);
function resolveOpenAICodexGpt53FallbackModel(
function resolveOpenAICodexForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
const normalizedProvider = normalizeProviderId(provider);
const trimmedModelId = modelId.trim();
if (!CODEX_GPT53_ELIGIBLE_PROVIDERS.has(normalizedProvider)) {
return undefined;
}
if (trimmedModelId.toLowerCase() !== OPENAI_CODEX_GPT_53_MODEL_ID) {
const lower = trimmedModelId.toLowerCase();
let templateIds: readonly string[];
let eligibleProviders: Set<string>;
if (lower === OPENAI_CODEX_GPT_54_MODEL_ID) {
templateIds = OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS;
eligibleProviders = CODEX_GPT54_ELIGIBLE_PROVIDERS;
} else if (lower === OPENAI_CODEX_GPT_53_MODEL_ID) {
templateIds = OPENAI_CODEX_TEMPLATE_MODEL_IDS;
eligibleProviders = CODEX_GPT53_ELIGIBLE_PROVIDERS;
} else {
return undefined;
}
for (const templateId of OPENAI_CODEX_TEMPLATE_MODEL_IDS) {
if (!eligibleProviders.has(normalizedProvider)) {
return undefined;
}
for (const templateId of templateIds) {
const template = modelRegistry.find(normalizedProvider, templateId) as Model<Api> | null;
if (!template) {
continue;
@@ -248,7 +321,8 @@ export function resolveForwardCompatModel(
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
return (
resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ??
resolveOpenAIGpt54ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveOpenAICodexForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAnthropicSonnet46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??