fix(provider): align GPT-5 reasoning variants (#26268)
This commit is contained in:
@@ -500,6 +500,13 @@ export function topK(model: Provider.Model) {
|
|||||||
|
|
||||||
const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
|
const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
|
||||||
const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
|
const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
|
||||||
|
const OPENAI_GPT5_1_EFFORTS = ["none", ...WIDELY_SUPPORTED_EFFORTS]
|
||||||
|
const OPENAI_GPT5_2_PLUS_EFFORTS = [...OPENAI_GPT5_1_EFFORTS, "xhigh"]
|
||||||
|
const OPENAI_GPT5_PRO_EFFORTS = ["high"]
|
||||||
|
const OPENAI_GPT5_PRO_2_PLUS_EFFORTS = ["medium", "high", "xhigh"]
|
||||||
|
const OPENAI_GPT5_CHAT_EFFORTS = ["medium"]
|
||||||
|
const OPENAI_GPT5_CODEX_XHIGH_EFFORTS = [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
|
||||||
|
const OPENAI_GPT5_CODEX_3_PLUS_EFFORTS = ["none", ...OPENAI_GPT5_CODEX_XHIGH_EFFORTS]
|
||||||
|
|
||||||
// OpenAI rolled out the `none` reasoning_effort tier on this date (Responses API).
|
// OpenAI rolled out the `none` reasoning_effort tier on this date (Responses API).
|
||||||
// Models released before it 400 on `reasoning_effort: "none"`, so we only expose
|
// Models released before it 400 on `reasoning_effort: "none"`, so we only expose
|
||||||
@@ -513,17 +520,49 @@ const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04"
|
|||||||
// "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex".
|
// "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex".
|
||||||
// Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o".
|
// Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o".
|
||||||
const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/
|
const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/
|
||||||
|
const GPT5_VERSION_RE = /(?:^|\/)gpt-5[.-](\d+)(?:[.-]|$)/
|
||||||
|
const GPT5_PRO_RE = /(?:^|\/)gpt-5[.-]?pro(?:[.-]|$)/
|
||||||
|
const GPT5_VERSIONED_PRO_RE = /(?:^|\/)gpt-5[.-]\d+[.-]pro(?:[.-]|$)/
|
||||||
|
|
||||||
|
function gpt5Version(apiId: string) {
|
||||||
|
return Number(GPT5_VERSION_RE.exec(apiId)?.[1]) || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function versionedGpt5ReasoningEfforts(apiId: string) {
|
||||||
|
if (GPT5_VERSIONED_PRO_RE.test(apiId)) return OPENAI_GPT5_PRO_2_PLUS_EFFORTS
|
||||||
|
const version = gpt5Version(apiId)
|
||||||
|
if (version === undefined) return undefined
|
||||||
|
if (version === 1) return OPENAI_GPT5_1_EFFORTS
|
||||||
|
return OPENAI_GPT5_2_PLUS_EFFORTS
|
||||||
|
}
|
||||||
|
|
||||||
|
function gpt5CodexReasoningEfforts(apiId: string) {
|
||||||
|
if (!GPT5_FAMILY_RE.test(apiId) || !apiId.includes("codex")) return undefined
|
||||||
|
const version = gpt5Version(apiId)
|
||||||
|
if (version !== undefined && version >= 3) return OPENAI_GPT5_CODEX_3_PLUS_EFFORTS
|
||||||
|
if (apiId.includes("codex-max") || (version !== undefined && version >= 2)) return OPENAI_GPT5_CODEX_XHIGH_EFFORTS
|
||||||
|
return WIDELY_SUPPORTED_EFFORTS
|
||||||
|
}
|
||||||
|
|
||||||
|
function gpt5ChatReasoningEfforts(apiId: string) {
|
||||||
|
if (!GPT5_FAMILY_RE.test(apiId) || !apiId.includes("-chat")) return undefined
|
||||||
|
return gpt5Version(apiId) === undefined ? [] : OPENAI_GPT5_CHAT_EFFORTS
|
||||||
|
}
|
||||||
|
|
||||||
// Computes the reasoning_effort tiers an OpenAI (or OpenAI-compatible upstream
|
// Computes the reasoning_effort tiers an OpenAI (or OpenAI-compatible upstream
|
||||||
// routed through it, e.g. cf-ai-gateway) model exposes. Returns null for models
|
// routed through it, e.g. cf-ai-gateway) model exposes. Effort order: weakest
|
||||||
// with no tunable effort knob (gpt-5-pro). Effort order: weakest to strongest.
|
// to strongest.
|
||||||
function openaiReasoningEfforts(apiId: string, releaseDate: string): string[] | null {
|
function openaiReasoningEfforts(apiId: string, releaseDate: string) {
|
||||||
const id = apiId.toLowerCase()
|
const id = apiId.toLowerCase()
|
||||||
if (id === "gpt-5-pro" || id === "openai/gpt-5-pro") return null
|
const chatEfforts = gpt5ChatReasoningEfforts(id)
|
||||||
if (id.includes("codex")) {
|
if (chatEfforts) return chatEfforts
|
||||||
if (id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
|
if (GPT5_PRO_RE.test(id)) return OPENAI_GPT5_PRO_EFFORTS
|
||||||
return [...WIDELY_SUPPORTED_EFFORTS]
|
const codexEfforts = gpt5CodexReasoningEfforts(id)
|
||||||
}
|
if (codexEfforts) return codexEfforts
|
||||||
|
const versionedEfforts = versionedGpt5ReasoningEfforts(id)
|
||||||
|
// GPT-5.1 replaced GPT-5's `minimal` effort with `none`; GPT-5.2+
|
||||||
|
// additionally accepts `xhigh`. Model pages list the supported subset.
|
||||||
|
if (versionedEfforts) return versionedEfforts
|
||||||
const efforts = [...WIDELY_SUPPORTED_EFFORTS]
|
const efforts = [...WIDELY_SUPPORTED_EFFORTS]
|
||||||
if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal")
|
if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal")
|
||||||
if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none")
|
if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none")
|
||||||
@@ -531,6 +570,14 @@ function openaiReasoningEfforts(apiId: string, releaseDate: string): string[] |
|
|||||||
return efforts
|
return efforts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openaiCompatibleReasoningEfforts(id: string) {
|
||||||
|
const apiId = id.toLowerCase()
|
||||||
|
const chatEfforts = gpt5ChatReasoningEfforts(apiId)
|
||||||
|
if (chatEfforts) return chatEfforts
|
||||||
|
if (GPT5_PRO_RE.test(apiId)) return OPENAI_GPT5_PRO_EFFORTS
|
||||||
|
return gpt5CodexReasoningEfforts(apiId) ?? versionedGpt5ReasoningEfforts(apiId) ?? OPENAI_EFFORTS
|
||||||
|
}
|
||||||
|
|
||||||
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||||
if (["opus-4-7", "opus-4.7"].some((v) => apiId.includes(v))) {
|
if (["opus-4-7", "opus-4.7"].some((v) => apiId.includes(v))) {
|
||||||
return ["low", "medium", "high", "xhigh", "max"]
|
return ["low", "medium", "high", "xhigh", "max"]
|
||||||
@@ -577,8 +624,13 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
|
|
||||||
switch (model.api.npm) {
|
switch (model.api.npm) {
|
||||||
case "@openrouter/ai-sdk-provider":
|
case "@openrouter/ai-sdk-provider":
|
||||||
if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("claude")) return {}
|
if (!id.includes("gpt") && !id.includes("gemini-3") && !id.includes("claude")) return {}
|
||||||
return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning: { effort } }]))
|
return Object.fromEntries(
|
||||||
|
(id.includes("gpt") ? openaiCompatibleReasoningEfforts(id) : OPENAI_EFFORTS).map((effort) => [
|
||||||
|
effort,
|
||||||
|
{ reasoning: { effort } },
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
case "ai-gateway-provider": {
|
case "ai-gateway-provider": {
|
||||||
// Cloudflare AI Gateway routes every upstream through its OpenAI-compatible
|
// Cloudflare AI Gateway routes every upstream through its OpenAI-compatible
|
||||||
@@ -589,7 +641,6 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
// models that support it.
|
// models that support it.
|
||||||
if (model.api.id.startsWith("openai/")) {
|
if (model.api.id.startsWith("openai/")) {
|
||||||
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
||||||
if (!efforts) return {}
|
|
||||||
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
|
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||||
}
|
}
|
||||||
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||||
@@ -652,7 +703,9 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
return Object.fromEntries(
|
||||||
|
openaiCompatibleReasoningEfforts(model.api.id).map((effort) => [effort, { reasoningEffort: effort }]),
|
||||||
|
)
|
||||||
|
|
||||||
case "@ai-sdk/github-copilot":
|
case "@ai-sdk/github-copilot":
|
||||||
if (model.id.includes("gemini")) {
|
if (model.id.includes("gemini")) {
|
||||||
@@ -700,12 +753,11 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
case "@ai-sdk/azure":
|
case "@ai-sdk/azure":
|
||||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
|
||||||
if (id === "o1-mini") return {}
|
if (id === "o1-mini") return {}
|
||||||
const azureEfforts = ["low", "medium", "high"]
|
|
||||||
if (id.includes("gpt-5-") || id === "gpt-5") {
|
|
||||||
azureEfforts.unshift("minimal")
|
|
||||||
}
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
azureEfforts.map((effort) => [
|
(GPT5_FAMILY_RE.test(id) && gpt5Version(id) === undefined
|
||||||
|
? ["minimal", ...WIDELY_SUPPORTED_EFFORTS]
|
||||||
|
: WIDELY_SUPPORTED_EFFORTS
|
||||||
|
).map((effort) => [
|
||||||
effort,
|
effort,
|
||||||
{
|
{
|
||||||
reasoningEffort: effort,
|
reasoningEffort: effort,
|
||||||
@@ -717,7 +769,6 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
case "@ai-sdk/openai": {
|
case "@ai-sdk/openai": {
|
||||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
|
||||||
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
||||||
if (!efforts) return {}
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
efforts.map((effort) => [
|
efforts.map((effort) => [
|
||||||
effort,
|
effort,
|
||||||
@@ -1102,6 +1153,11 @@ export function smallOptions(model: Provider.Model) {
|
|||||||
model.api.npm === "@ai-sdk/github-copilot"
|
model.api.npm === "@ai-sdk/github-copilot"
|
||||||
) {
|
) {
|
||||||
if (model.api.id.includes("gpt-5")) {
|
if (model.api.id.includes("gpt-5")) {
|
||||||
|
if (model.api.id.includes("-chat")) {
|
||||||
|
if (gpt5Version(model.api.id) === undefined) return { store: false }
|
||||||
|
return { store: false, reasoningEffort: "medium" }
|
||||||
|
}
|
||||||
|
if (model.api.id.includes("search-api")) return { store: false }
|
||||||
if (model.api.id.includes("5.") || model.api.id.includes("5-mini")) {
|
if (model.api.id.includes("5.") || model.api.id.includes("5-mini")) {
|
||||||
return { store: false, reasoningEffort: "low" }
|
return { store: false, reasoningEffort: "low" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2464,6 +2464,32 @@ describe("ProviderTransform.variants", () => {
|
|||||||
expect(result.high).toEqual({ reasoning: { effort: "high" } })
|
expect(result.high).toEqual({ reasoning: { effort: "high" } })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for (const testCase of [
|
||||||
|
{ id: "openai/gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-pro", efforts: ["high"] },
|
||||||
|
{ id: "openai/gpt-5.5-pro", efforts: ["medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5.2-codex", efforts: ["low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5.3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5.3-codex-max", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-chat-latest", efforts: [] },
|
||||||
|
{ id: "openai/gpt-5.2-chat-latest", efforts: ["medium"] },
|
||||||
|
]) {
|
||||||
|
test(`${testCase.id} returns supported OpenAI reasoning efforts`, () => {
|
||||||
|
const result = ProviderTransform.variants(
|
||||||
|
createMockModel({
|
||||||
|
id: testCase.id,
|
||||||
|
providerID: "openrouter",
|
||||||
|
api: {
|
||||||
|
id: testCase.id,
|
||||||
|
url: "https://openrouter.ai",
|
||||||
|
npm: "@openrouter/ai-sdk-provider",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(Object.keys(result)).toEqual(testCase.efforts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
test("gemini-3 returns OPENAI_EFFORTS with reasoning", () => {
|
test("gemini-3 returns OPENAI_EFFORTS with reasoning", () => {
|
||||||
const model = createMockModel({
|
const model = createMockModel({
|
||||||
id: "openrouter/gemini-3-5-pro",
|
id: "openrouter/gemini-3-5-pro",
|
||||||
@@ -2651,6 +2677,32 @@ describe("ProviderTransform.variants", () => {
|
|||||||
expect(result.low).toEqual({ reasoningEffort: "low" })
|
expect(result.low).toEqual({ reasoningEffort: "low" })
|
||||||
expect(result.high).toEqual({ reasoningEffort: "high" })
|
expect(result.high).toEqual({ reasoningEffort: "high" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for (const testCase of [
|
||||||
|
{ id: "openai/gpt-5-5", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-pro", efforts: ["high"] },
|
||||||
|
{ id: "openai/gpt-5-5-pro", efforts: ["medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-2-codex", efforts: ["low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-3-codex-max", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-chat-latest", efforts: [] },
|
||||||
|
{ id: "openai/gpt-5-2-chat-latest", efforts: ["medium"] },
|
||||||
|
]) {
|
||||||
|
test(`${testCase.id} returns supported OpenAI reasoning efforts`, () => {
|
||||||
|
const result = ProviderTransform.variants(
|
||||||
|
createMockModel({
|
||||||
|
id: testCase.id,
|
||||||
|
providerID: "gateway",
|
||||||
|
api: {
|
||||||
|
id: testCase.id,
|
||||||
|
url: "https://gateway.ai",
|
||||||
|
npm: "@ai-sdk/gateway",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(Object.keys(result)).toEqual(testCase.efforts)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("@ai-sdk/github-copilot", () => {
|
describe("@ai-sdk/github-copilot", () => {
|
||||||
@@ -2929,10 +2981,27 @@ describe("ProviderTransform.variants", () => {
|
|||||||
const result = ProviderTransform.variants(model)
|
const result = ProviderTransform.variants(model)
|
||||||
expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
|
expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for (const id of ["gpt-5-4", "gpt-5-5"]) {
|
||||||
|
test(`${id} does not add minimal effort`, () => {
|
||||||
|
const result = ProviderTransform.variants(
|
||||||
|
createMockModel({
|
||||||
|
id,
|
||||||
|
providerID: "azure",
|
||||||
|
api: {
|
||||||
|
id,
|
||||||
|
url: "https://azure.com",
|
||||||
|
npm: "@ai-sdk/azure",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("@ai-sdk/openai", () => {
|
describe("@ai-sdk/openai", () => {
|
||||||
test("gpt-5-pro returns empty object", () => {
|
test("gpt-5-pro returns only high effort", () => {
|
||||||
const model = createMockModel({
|
const model = createMockModel({
|
||||||
id: "gpt-5-pro",
|
id: "gpt-5-pro",
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
@@ -2943,7 +3012,7 @@ describe("ProviderTransform.variants", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const result = ProviderTransform.variants(model)
|
const result = ProviderTransform.variants(model)
|
||||||
expect(result).toEqual({})
|
expect(Object.keys(result)).toEqual(["high"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("standard openai models return custom efforts with reasoningSummary", () => {
|
test("standard openai models return custom efforts with reasoningSummary", () => {
|
||||||
@@ -2983,10 +3052,10 @@ describe("ProviderTransform.variants", () => {
|
|||||||
|
|
||||||
test("models after 2025-12-04 include 'xhigh' effort", () => {
|
test("models after 2025-12-04 include 'xhigh' effort", () => {
|
||||||
const model = createMockModel({
|
const model = createMockModel({
|
||||||
id: "openai/gpt-5-chat",
|
id: "openai/gpt-5-reasoning",
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
api: {
|
api: {
|
||||||
id: "gpt-5-chat",
|
id: "gpt-5-reasoning",
|
||||||
url: "https://api.openai.com",
|
url: "https://api.openai.com",
|
||||||
npm: "@ai-sdk/openai",
|
npm: "@ai-sdk/openai",
|
||||||
},
|
},
|
||||||
@@ -2996,20 +3065,38 @@ describe("ProviderTransform.variants", () => {
|
|||||||
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
|
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("dotted gpt-5.x ids include 'minimal' (regression: matcher used to miss gpt-5.4)", () => {
|
for (const testCase of [
|
||||||
const model = createMockModel({
|
{ id: "gpt-5.1", releaseDate: "2025-11-13", efforts: ["none", "low", "medium", "high"] },
|
||||||
id: "gpt-5.4",
|
{ id: "gpt-5.4", releaseDate: "2026-03-05", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
providerID: "openai",
|
{ id: "gpt-5.5", modelID: "gpt-5-5", releaseDate: "2026-04-23", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
api: {
|
{ id: "gpt-5.4-pro", releaseDate: "2026-03-05", efforts: ["medium", "high", "xhigh"] },
|
||||||
id: "gpt-5.4",
|
{ id: "gpt-5.5-pro", releaseDate: "2026-04-23", efforts: ["medium", "high", "xhigh"] },
|
||||||
url: "https://api.openai.com",
|
{ id: "gpt-5-codex", releaseDate: "2025-09-23", efforts: ["low", "medium", "high"] },
|
||||||
npm: "@ai-sdk/openai",
|
{ id: "gpt-5.1-codex", releaseDate: "2025-11-13", efforts: ["low", "medium", "high"] },
|
||||||
},
|
{ id: "gpt-5.1-codex-max", releaseDate: "2025-11-13", efforts: ["low", "medium", "high", "xhigh"] },
|
||||||
release_date: "2026-03-05",
|
{ id: "gpt-5.2-codex", releaseDate: "2025-12-11", efforts: ["low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "gpt-5.3-codex", releaseDate: "2026-01-22", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "gpt-5.3-codex-max", releaseDate: "2026-01-22", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
|
{ id: "gpt-5-chat-latest", releaseDate: "2025-08-07", efforts: [] },
|
||||||
|
{ id: "gpt-5.1-chat-latest", releaseDate: "2025-11-13", efforts: ["medium"] },
|
||||||
|
{ id: "gpt-5.2-chat-latest", releaseDate: "2025-12-11", efforts: ["medium"] },
|
||||||
|
]) {
|
||||||
|
test(`${testCase.id} returns supported reasoning efforts`, () => {
|
||||||
|
const result = ProviderTransform.variants(
|
||||||
|
createMockModel({
|
||||||
|
id: testCase.modelID ?? testCase.id,
|
||||||
|
providerID: "openai",
|
||||||
|
api: {
|
||||||
|
id: testCase.id,
|
||||||
|
url: "https://api.openai.com",
|
||||||
|
npm: "@ai-sdk/openai",
|
||||||
|
},
|
||||||
|
release_date: testCase.releaseDate,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(Object.keys(result)).toEqual(testCase.efforts)
|
||||||
})
|
})
|
||||||
const result = ProviderTransform.variants(model)
|
}
|
||||||
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("gpt-50 (lookalike) does not get gpt-5 family treatment", () => {
|
test("gpt-50 (lookalike) does not get gpt-5 family treatment", () => {
|
||||||
const model = createMockModel({
|
const model = createMockModel({
|
||||||
@@ -3486,18 +3573,20 @@ describe("ProviderTransform.variants", () => {
|
|||||||
release_date: releaseDate,
|
release_date: releaseDate,
|
||||||
})
|
})
|
||||||
|
|
||||||
test("openai gpt-5.4 includes xhigh effort (regression: variant=xhigh used to be silently ignored)", () => {
|
for (const testCase of [
|
||||||
const result = ProviderTransform.variants(cfModel("openai/gpt-5.4", "2026-03-05"))
|
{ id: "openai/gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
expect(result.xhigh).toEqual({ reasoningEffort: "xhigh" })
|
{ id: "openai/gpt-5.2-codex", efforts: ["low", "medium", "high", "xhigh"] },
|
||||||
expect(result.high).toEqual({ reasoningEffort: "high" })
|
{ id: "openai/gpt-5.3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
|
||||||
expect(Object.keys(result)).toContain("minimal")
|
{ id: "openai/gpt-5-pro", efforts: ["high"] },
|
||||||
})
|
{ id: "openai/gpt-5.2-pro", efforts: ["medium", "high", "xhigh"] },
|
||||||
|
{ id: "openai/gpt-5-chat-latest", efforts: [] },
|
||||||
test("openai gpt-5.2-codex includes xhigh", () => {
|
{ id: "openai/gpt-5.2-chat-latest", efforts: ["medium"] },
|
||||||
const result = ProviderTransform.variants(cfModel("openai/gpt-5.2-codex", "2025-12-11"))
|
]) {
|
||||||
expect(result.xhigh).toEqual({ reasoningEffort: "xhigh" })
|
test(`${testCase.id} returns supported reasoning efforts`, () => {
|
||||||
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
|
const result = ProviderTransform.variants(cfModel(testCase.id, "2026-03-05"))
|
||||||
})
|
expect(Object.keys(result)).toEqual(testCase.efforts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
test("openai gpt-4o (no reasoning) returns empty", () => {
|
test("openai gpt-4o (no reasoning) returns empty", () => {
|
||||||
const model = cfModel("openai/gpt-4o")
|
const model = cfModel("openai/gpt-4o")
|
||||||
@@ -3517,6 +3606,30 @@ describe("ProviderTransform.variants", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
|
||||||
|
const createModel = (apiId: string) =>
|
||||||
|
({
|
||||||
|
id: `openai/${apiId}`,
|
||||||
|
providerID: "openai",
|
||||||
|
api: {
|
||||||
|
id: apiId,
|
||||||
|
url: "https://api.openai.com",
|
||||||
|
npm: "@ai-sdk/openai",
|
||||||
|
},
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
for (const testCase of [
|
||||||
|
{ id: "gpt-5-chat-latest", options: { store: false } },
|
||||||
|
{ id: "gpt-5.1-chat-latest", options: { store: false, reasoningEffort: "medium" } },
|
||||||
|
{ id: "gpt-5.2-chat-latest", options: { store: false, reasoningEffort: "medium" } },
|
||||||
|
{ id: "gpt-5-search-api", options: { store: false } },
|
||||||
|
]) {
|
||||||
|
test(`${testCase.id} returns only supported small options`, () => {
|
||||||
|
expect(ProviderTransform.smallOptions(createModel(testCase.id))).toEqual(testCase.options)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
|
describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
|
||||||
const createModel = (overrides: Partial<any> = {}) =>
|
const createModel = (overrides: Partial<any> = {}) =>
|
||||||
({
|
({
|
||||||
|
|||||||
Reference in New Issue
Block a user