feat(provider): use models.dev reasoning options

This commit is contained in:
Aiden Cline
2026-06-30 15:25:14 -05:00
parent ed20d69d80
commit 996a0628ef
5 changed files with 656 additions and 1299 deletions
+22
View File
@@ -44,6 +44,27 @@ const Cost = Schema.Struct({
),
})
const ReasoningEffortValue = Schema.Union([
Schema.Null,
Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh", "max", "default"]),
])
export const ReasoningOption = Schema.Union([
Schema.Struct({
type: Schema.Literal("effort"),
values: Schema.Array(ReasoningEffortValue),
}),
Schema.Struct({
type: Schema.Literal("toggle"),
}),
Schema.Struct({
type: Schema.Literal("budget_tokens"),
min: Schema.optional(Schema.Finite),
max: Schema.optional(Schema.Finite),
}),
])
export type ReasoningOption = typeof ReasoningOption.Type
export const Model = Schema.Struct({
id: Schema.String,
name: Schema.String,
@@ -51,6 +72,7 @@ export const Model = Schema.Struct({
release_date: Schema.String,
attachment: Schema.Boolean,
reasoning: Schema.Boolean,
reasoning_options: Schema.optional(Schema.Array(ReasoningOption)),
temperature: Schema.Boolean,
tool_call: Schema.Boolean,
interleaved: Schema.optional(
@@ -1022,6 +1022,7 @@ export const Model = Schema.Struct({
name: Schema.String,
family: optional(Schema.String),
capabilities: ProviderCapabilities,
reasoning_options: optional(Schema.Array(ModelsDev.ReasoningOption)),
cost: ProviderCost,
limit: ProviderLimit,
status: ModelStatus,
@@ -1185,6 +1186,13 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
return result
}
function reasoningOptions(model: ModelsDev.Model): Model["reasoning_options"] {
return model.reasoning_options?.map((option) => {
if (option.type === "effort") return { ...option, values: [...option.values] }
return { ...option }
})
}
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
const base: Model = {
id: ModelV2.ID.make(model.id),
@@ -1226,6 +1234,7 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
},
interleaved: model.interleaved ?? false,
},
reasoning_options: reasoningOptions(model),
release_date: model.release_date ?? "",
variants: {},
}
@@ -1456,6 +1465,7 @@ const layer = Layer.effect(
? { field: "reasoning_content" }
: false),
},
reasoning_options: existingModel?.reasoning_options,
cost: {
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
+295 -429
View File
@@ -3,7 +3,6 @@ import { mergeDeep, unique } from "remeda"
import type { JSONSchema7 } from "@ai-sdk/provider"
import type * as Provider from "./provider"
import type * as ModelsDev from "@opencode-ai/core/models-dev"
import { iife } from "@/util/iife"
type Modality = NonNullable<ModelsDev.Model["modalities"]>["input"][number]
@@ -515,134 +514,8 @@ export function topK(model: Provider.Model) {
}
const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
const ANTHROPIC_EFFORTS = [...WIDELY_SUPPORTED_EFFORTS, "xhigh", "max"]
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).
// Models released before it 400 on `reasoning_effort: "none"`, so we only expose
// it as a variant for models new enough to accept it.
const OPENAI_NONE_EFFORT_RELEASE_DATE = "2025-11-13"
// OpenAI rolled out the `xhigh` reasoning_effort tier on this date. Same reasoning.
const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04"
// Matches members of the gpt-5 family across the id formats we encounter:
// "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".
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
// routed through it, e.g. cf-ai-gateway) model exposes. Effort order: weakest
// to strongest.
function openaiReasoningEfforts(apiId: string, releaseDate: string) {
const id = apiId.toLowerCase()
if (id.includes("deep-research")) return ["medium"]
const chatEfforts = gpt5ChatReasoningEfforts(id)
if (chatEfforts) return chatEfforts
if (GPT5_PRO_RE.test(id)) return OPENAI_GPT5_PRO_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]
if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal")
if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none")
if (releaseDate >= OPENAI_XHIGH_EFFORT_RELEASE_DATE) efforts.push("xhigh")
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 anthropicOpus47OrLater(apiId: string) {
// Matches "opus-4.7" (Anthropic/Bedrock/Vertex) and "claude-4.7-opus" (SAP AI Core inverted).
// Greedy \d+ correctly extends to multi-digit majors (e.g. "claude-10.0-opus") for forward compatibility.
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)|claude-(\d+)[.-](\d+)-opus(?:[.@-]|$)/i.exec(apiId)
if (!version) return false
const major = Number(version[1] ?? version[3])
const minor = Number(version[2] ?? version[4])
return major > 4 || (major === 4 && minor >= 7)
}
function anthropicSonnet5OrLater(apiId: string) {
const version = /sonnet-(\d+)(?:[.@-]|$)|claude-(\d+)-sonnet(?:[.@-]|$)/i.exec(apiId)
if (!version) return false
return Number(version[1] ?? version[2]) >= 5
}
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
if (anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")) {
return ["low", "medium", "high", "xhigh", "max"]
}
if (
["opus-4-6", "opus-4.6", "4-6-opus", "4.6-opus", "sonnet-4-6", "sonnet-4.6", "4-6-sonnet", "4.6-sonnet"].some((v) =>
apiId.includes(v),
)
) {
return ["low", "medium", "high", "max"]
}
return null
}
function anthropicOmitsThinking(apiId: string) {
return anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")
}
function googleThinkingLevelEfforts(apiId: string) {
const id = apiId.toLowerCase()
if (!id.includes("gemini-3")) return ["low", "high"]
if (id.includes("flash-image")) return ["minimal", "high"]
if (id.includes("pro-image")) return ["high"]
if (id.includes("flash")) return ["minimal", "low", "medium", "high"]
return ["low", "medium", "high"]
}
function googleThinkingBudgetMax(apiId: string) {
const id = apiId.toLowerCase()
if (id.includes("2.5") && id.includes("pro") && !id.includes("flash")) return 32_768
return 24_576
}
// SAP's Zod schema drops unknown top-level keys; reasoning controls survive
// only via `modelParams` (catchall), forwarded verbatim by the SAP SDKs.
@@ -650,31 +523,289 @@ function wrapInSapModelParams(variants: Record<string, Record<string, any>>): Re
return Object.fromEntries(Object.entries(variants).map(([k, v]) => [k, { modelParams: v }]))
}
function googleThinkingVariants(model: Provider.Model): Record<string, Record<string, any>> {
const id = model.api.id.toLowerCase()
if (id.includes("2.5")) {
return {
high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
max: {
thinkingConfig: { includeThoughts: true, thinkingBudget: googleThinkingBudgetMax(id) },
},
}
}
function idIncludes(model: Provider.Model, value: string) {
return model.id.toLowerCase().includes(value) || model.api.id.toLowerCase().includes(value)
}
function anthropicAdaptiveVariants() {
return Object.fromEntries(
googleThinkingLevelEfforts(id).map((effort) => [
ANTHROPIC_EFFORTS.map((effort) => [
effort,
{
thinking: anthropicAdaptiveThinking(),
effort,
},
]),
)
}
function bedrockAnthropicAdaptiveVariants() {
return Object.fromEntries(
ANTHROPIC_EFFORTS.map((effort) => [
effort,
{
reasoningConfig: {
type: "adaptive",
maxReasoningEffort: effort,
display: "summarized",
},
},
]),
)
}
function sapAnthropicAdaptiveVariants() {
return wrapInSapModelParams(
Object.fromEntries(
ANTHROPIC_EFFORTS.map((effort) => [
effort,
{
thinking: anthropicAdaptiveThinking(),
output_config: { effort },
},
]),
),
)
}
function googleThinkingLevelVariants() {
return Object.fromEntries(
WIDELY_SUPPORTED_EFFORTS.map((effort) => [
effort,
{ thinkingConfig: { includeThoughts: true, thinkingLevel: effort } },
]),
)
}
function openAIReasoningEffortVariants() {
return Object.fromEntries(
OPENAI_EFFORTS.map((effort) => [
effort,
{
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
},
]),
)
}
type ReasoningOption = NonNullable<Provider.Model["reasoning_options"]>[number]
type ReasoningEffortOption = Extract<ReasoningOption, { type: "effort" }>
type ReasoningBudgetOption = Extract<ReasoningOption, { type: "budget_tokens" }>
type ReasoningEffortValue = ReasoningEffortOption["values"][number]
function reasoningOption<T extends ReasoningOption["type"]>(
model: Provider.Model,
type: T,
): Extract<ReasoningOption, { type: T }> | undefined {
return model.reasoning_options?.find((option): option is Extract<ReasoningOption, { type: T }> => option.type === type)
}
function reasoningBudgetVariants(
budget: ReasoningBudgetOption,
make: (budgetTokens: number) => Record<string, any>,
output: number,
) {
const max = Math.max(1, Math.min(budget.max ?? 31_999, (output || OUTPUT_TOKEN_MAX) - 1))
const min = Math.min(budget.min ?? 1, max)
return {
high: make(Math.max(min, Math.min(16_000, max))),
max: make(max),
}
}
function anthropicAdaptiveFromOptions(effort: ReasoningEffortOption) {
return effort.values.includes("max")
}
function anthropicAdaptiveThinking() {
return {
type: "adaptive",
display: "summarized",
}
}
function effortVariant(model: Provider.Model, effort: ReasoningEffortValue) {
switch (model.api.npm) {
case "@openrouter/ai-sdk-provider":
return { reasoning: { effort } }
case "ai-gateway-provider":
return { reasoningEffort: effort }
case "@ai-sdk/gateway":
if (idIncludes(model, "anthropic")) {
const option = reasoningOption(model, "effort")
if (option && anthropicAdaptiveFromOptions(option)) {
return {
thinking: anthropicAdaptiveThinking(),
effort,
}
}
return { effort }
}
if (idIncludes(model, "google")) return { includeThoughts: true, thinkingLevel: effort }
return { reasoningEffort: effort }
case "@ai-sdk/anthropic":
case "@ai-sdk/google-vertex/anthropic": {
const option = reasoningOption(model, "effort")
if (option && anthropicAdaptiveFromOptions(option)) {
return {
thinking: anthropicAdaptiveThinking(),
effort,
}
}
return { effort }
}
case "@ai-sdk/amazon-bedrock":
if (model.api.id.includes("anthropic")) {
const option = reasoningOption(model, "effort")
const adaptive = option ? anthropicAdaptiveFromOptions(option) : false
return {
reasoningConfig: {
type: adaptive ? "adaptive" : "enabled",
maxReasoningEffort: effort,
...(adaptive ? { display: "summarized" } : {}),
},
}
}
return { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }
case "@ai-sdk/google":
case "@ai-sdk/google-vertex":
return { thinkingConfig: { includeThoughts: true, thinkingLevel: effort } }
case "@jerome-benoit/sap-ai-provider-v2": {
if (!model.api.id.includes("anthropic")) return { modelParams: { reasoning_effort: effort } }
const option = reasoningOption(model, "effort")
const modelParams =
option && anthropicAdaptiveFromOptions(option)
? {
thinking: anthropicAdaptiveThinking(),
output_config: { effort },
}
: { output_config: { effort } }
return { modelParams }
}
case "@ai-sdk/azure":
return {
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
}
case "@ai-sdk/amazon-bedrock/mantle":
case "@ai-sdk/openai":
return {
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
}
case "@ai-sdk/github-copilot":
return model.id.includes("claude")
? { reasoningEffort: effort }
: {
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
}
case "@ai-sdk/cerebras":
case "@ai-sdk/togetherai":
case "@ai-sdk/xai":
case "@ai-sdk/deepinfra":
case "venice-ai-sdk-provider":
case "@ai-sdk/openai-compatible":
case "@ai-sdk/groq":
return { reasoningEffort: effort }
}
return undefined
}
function reasoningOptionVariants(model: Provider.Model): Record<string, Record<string, any>> | undefined {
if (!model.reasoning_options) return undefined
const effort = reasoningOption(model, "effort")
const budget = reasoningOption(model, "budget_tokens")
if (effort) {
return Object.fromEntries(
effort.values.flatMap((value): [string, Record<string, any>][] => {
// A null effort is a provider-specific escape hatch in models.dev. Skip
// exposing it as a selectable variant until variant IDs can represent it cleanly.
if (value === null) return []
const variant = effortVariant(model, value)
return variant ? [[value, variant]] : []
}),
)
}
// Toggle-only support needs a product decision about how to expose an on/off
// variant without clobbering users' current effort selection. Existing
// hand-authored toggle variants, like MiniMax M3 below, remain preserved.
if (!budget) return {}
switch (model.api.npm) {
case "@ai-sdk/gateway":
if (idIncludes(model, "anthropic")) {
return reasoningBudgetVariants(
budget,
(budgetTokens) => ({ thinking: { type: "enabled", budgetTokens } }),
model.limit.output,
)
}
if (idIncludes(model, "google")) {
return reasoningBudgetVariants(
budget,
(thinkingBudget) => ({ thinkingConfig: { includeThoughts: true, thinkingBudget } }),
model.limit.output,
)
}
break
case "@ai-sdk/anthropic":
case "@ai-sdk/google-vertex/anthropic":
return reasoningBudgetVariants(budget, (budgetTokens) => ({ thinking: { type: "enabled", budgetTokens } }), model.limit.output)
case "@ai-sdk/amazon-bedrock":
if (model.api.id.includes("anthropic")) {
return reasoningBudgetVariants(
budget,
(budgetTokens) => ({ reasoningConfig: { type: "enabled", budgetTokens } }),
model.limit.output,
)
}
break
case "@ai-sdk/google":
case "@ai-sdk/google-vertex":
return reasoningBudgetVariants(
budget,
(thinkingBudget) => ({ thinkingConfig: { includeThoughts: true, thinkingBudget } }),
model.limit.output,
)
case "@jerome-benoit/sap-ai-provider-v2":
if (model.api.id.includes("anthropic")) {
return wrapInSapModelParams(
reasoningBudgetVariants(budget, (budget_tokens) => ({ thinking: { type: "enabled", budget_tokens } }), model.limit.output),
)
}
break
}
return {}
}
export function variants(model: Provider.Model): Record<string, Record<string, any>> {
if (!model.capabilities.reasoning) return {}
const id = model.id.toLowerCase()
const glm52 = ["glm-5.2", "glm-5-2", "glm-5p2"].some(
(name) => id.includes(name) || model.api.id.toLowerCase().includes(name),
)
// Historical exception: MiniMax M3's Anthropic-compatible surface exposes an
// explicit thinking toggle that predates models.dev reasoning_options.
if (
model.api.id.toLowerCase().includes("minimax-m3") &&
["@ai-sdk/anthropic", "@ai-sdk/openai-compatible"].includes(model.api.npm)
@@ -684,161 +815,26 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
thinking: { thinking: { type: "adaptive" } },
}
}
const adaptiveThinkingOmitted = anthropicOmitsThinking(model.api.id)
const adaptiveEfforts = anthropicAdaptiveEfforts(model.api.id)
if (glm52 && model.api.npm === "@openrouter/ai-sdk-provider") {
// OpenRouter maps xhigh to GLM-5.2's native max effort.
return {
high: { reasoning: { effort: "high" } },
xhigh: { reasoning: { effort: "xhigh" } },
}
}
if (glm52 && model.api.npm === "@ai-sdk/openai-compatible") {
return {
high: { reasoningEffort: "high" },
max: { reasoningEffort: "max" },
}
}
if (glm52 && model.api.npm === "@ai-sdk/anthropic") {
return {
high: { effort: "high" },
max: { effort: "max" },
}
}
if (
id.includes("deepseek-chat") ||
id.includes("deepseek-reasoner") ||
id.includes("deepseek-r1") ||
id.includes("deepseek-v3") ||
id.includes("minimax") ||
(id.includes("glm") && !glm52) ||
id.includes("kimi") ||
id.includes("k2p") ||
id.includes("qwen") ||
id.includes("big-pickle")
)
return {}
// see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
if (id.includes("grok") && id.includes("grok-3-mini")) {
if (model.api.npm === "@openrouter/ai-sdk-provider") {
return {
low: { reasoning: { effort: "low" } },
high: { reasoning: { effort: "high" } },
}
}
return {
low: { reasoningEffort: "low" },
high: { reasoningEffort: "high" },
}
}
if (id.includes("grok")) return {}
const fromReasoningOptions = reasoningOptionVariants(model)
if (fromReasoningOptions) return fromReasoningOptions
switch (model.api.npm) {
case "@openrouter/ai-sdk-provider":
return Object.fromEntries(
(model.api.id.startsWith("openai/") || id.includes("gpt")
? openaiCompatibleReasoningEfforts(model.api.id)
: WIDELY_SUPPORTED_EFFORTS
).map((effort) => [effort, { reasoning: { effort } }]),
)
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoning: { effort } }]))
case "ai-gateway-provider": {
// Cloudflare AI Gateway routes every upstream through its OpenAI-compatible
// /v1/compat endpoint, so the body is always OAI-shaped. The gateway
// translates `reasoning_effort` to the upstream provider's native control
// (e.g. Anthropic thinking budgets) when needed. Variants therefore stay
// OAI-style for all upstreams, with an extended effort set for OpenAI
// models that support it.
if (model.api.id.startsWith("openai/")) {
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
}
case "ai-gateway-provider":
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
}
case "@ai-sdk/gateway":
if (model.id.includes("anthropic")) {
if (adaptiveEfforts) {
return Object.fromEntries(
adaptiveEfforts.map((effort) => [
effort,
{
thinking: {
type: "adaptive",
// Newer adaptive-only models default `display` to "omitted", which
// returns empty thinking blocks. Force "summarized" so summaries
// survive (4.6/Sonnet 4.6 already default to "summarized").
...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
},
effort,
},
]),
)
}
return {
high: {
thinking: {
type: "enabled",
budgetTokens: 16000,
},
},
max: {
thinking: {
type: "enabled",
budgetTokens: 31999,
},
},
}
}
if (model.id.includes("google")) {
if (id.includes("2.5")) {
return {
high: {
thinkingConfig: {
includeThoughts: true,
thinkingBudget: 16000,
},
},
max: {
thinkingConfig: {
includeThoughts: true,
thinkingBudget: googleThinkingBudgetMax(id),
},
},
}
}
return Object.fromEntries(
["low", "high"].map((effort) => [
effort,
{
includeThoughts: true,
thinkingLevel: effort,
},
]),
)
}
return Object.fromEntries(
openaiCompatibleReasoningEfforts(model.api.id).map((effort) => [effort, { reasoningEffort: effort }]),
)
if (idIncludes(model, "anthropic")) return anthropicAdaptiveVariants()
if (idIncludes(model, "google")) return googleThinkingLevelVariants()
if (idIncludes(model, "openai")) return openAIReasoningEffortVariants()
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
case "@ai-sdk/github-copilot":
if (model.id.includes("gemini")) {
// currently github copilot only returns thinking
return {}
}
if (model.id.includes("claude")) {
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
}
const copilotEfforts = iife(() => {
if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3"))
return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
const arr = [...WIDELY_SUPPORTED_EFFORTS]
if (id.includes("gpt-5") && model.release_date >= "2025-12-04") arr.push("xhigh")
return arr
})
return Object.fromEntries(
copilotEfforts.map((effort) => [
WIDELY_SUPPORTED_EFFORTS.map((effort) => [
effort,
{
reasoningEffort: effort,
@@ -859,125 +855,26 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
case "venice-ai-sdk-provider":
// https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort
case "@ai-sdk/openai-compatible":
if (model.api.id.toLowerCase().includes("north-mini-code")) {
return Object.fromEntries(["none", "high"].map((effort) => [effort, { reasoningEffort: effort }]))
}
const efforts = [...WIDELY_SUPPORTED_EFFORTS]
if (model.api.id.toLowerCase().includes("deepseek-v4")) {
efforts.push("max")
}
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
case "@ai-sdk/azure":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
if (id === "o1-mini") return {}
return Object.fromEntries(
openaiReasoningEfforts(id, model.release_date).map((effort) => [
effort,
{
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
},
]),
)
return openAIReasoningEffortVariants()
case "@ai-sdk/amazon-bedrock/mantle":
case "@ai-sdk/openai": {
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
return Object.fromEntries(
efforts.map((effort) => [
effort,
{
reasoningEffort: effort,
reasoningSummary: "auto",
include: INCLUDE_ENCRYPTED_REASONING,
},
]),
)
return openAIReasoningEffortVariants()
}
case "@ai-sdk/anthropic":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
case "@ai-sdk/google-vertex/anthropic":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
if (adaptiveEfforts) {
let efforts = [...adaptiveEfforts]
if (model.providerID === "github-copilot") {
if (model.api.id.includes("opus-4.7")) {
efforts = ["medium"]
}
// Efforts currently supported are: low, medium, high
efforts = efforts.filter((v) => v !== "max" && v !== "xhigh")
}
return Object.fromEntries(
efforts.map((effort) => [
effort,
{
thinking: {
type: "adaptive",
...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
},
effort,
},
]),
)
}
if (["opus-4-5", "opus-4.5"].some((v) => model.api.id.includes(v))) {
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { effort }]))
}
return {
high: {
thinking: {
type: "enabled",
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
},
},
max: {
thinking: {
type: "enabled",
budgetTokens: Math.min(31_999, model.limit.output - 1),
},
},
}
return anthropicAdaptiveVariants()
case "@ai-sdk/amazon-bedrock":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock
if (adaptiveEfforts) {
return Object.fromEntries(
adaptiveEfforts.map((effort) => [
effort,
{
reasoningConfig: {
type: "adaptive",
maxReasoningEffort: effort,
...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
},
},
]),
)
}
// For Anthropic models on Bedrock, use reasoningConfig with budgetTokens
if (model.api.id.includes("anthropic")) {
return {
high: {
reasoningConfig: {
type: "enabled",
budgetTokens: 16000,
},
},
max: {
reasoningConfig: {
type: "enabled",
budgetTokens: 31999,
},
},
}
}
// For Amazon Nova models, use reasoningConfig with maxReasoningEffort
if (idIncludes(model, "anthropic")) return bedrockAnthropicAdaptiveVariants()
return Object.fromEntries(
WIDELY_SUPPORTED_EFFORTS.map((effort) => [
effort,
@@ -994,21 +891,11 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex
case "@ai-sdk/google":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai
return googleThinkingVariants(model)
return googleThinkingLevelVariants()
case "@ai-sdk/mistral":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/mistral
// https://docs.mistral.ai/capabilities/reasoning/adjustable
if (!model.capabilities.reasoning) return {}
// Only Mistral Small 4 and Medium 3.5 support reasoning
const MISTRAL_REASONING_IDS = [
"mistral-small-2603",
"mistral-small-latest",
"mistral-medium-3.5",
"mistral-medium-2604",
]
const mistralId = model.api.id.toLowerCase()
if (!MISTRAL_REASONING_IDS.some((id) => mistralId.includes(id))) return {}
return {
high: { reasoningEffort: "high" },
}
@@ -1034,33 +921,12 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
return {}
case "@jerome-benoit/sap-ai-provider-v2": {
if (id.includes("anthropic")) {
if (adaptiveEfforts) {
// Bedrock adaptive splits `effort` out into `output_config` (vs Anthropic
// native which inlines it). Opus 4.7+ flipped `display` default to "omitted".
return wrapInSapModelParams(
Object.fromEntries(
adaptiveEfforts.map((effort) => [
effort,
{
thinking: { type: "adaptive", ...(adaptiveThinkingOmitted ? { display: "summarized" } : {}) },
output_config: { effort },
},
]),
),
)
}
return wrapInSapModelParams({
high: { thinking: { type: "enabled", budget_tokens: 16000 } },
max: { thinking: { type: "enabled", budget_tokens: 31999 } },
})
}
if (id.includes("gemini") && id.includes("2.5")) {
return wrapInSapModelParams(googleThinkingVariants(model))
}
if (id.includes("gpt") || /\bo[1-9]/.test(id)) {
const efforts = openaiReasoningEfforts(id, model.release_date)
return wrapInSapModelParams(Object.fromEntries(efforts.map((effort) => [effort, { reasoning_effort: effort }])))
if (idIncludes(model, "anthropic")) return sapAnthropicAdaptiveVariants()
if (idIncludes(model, "google")) return wrapInSapModelParams(googleThinkingLevelVariants())
if (idIncludes(model, "openai")) {
return wrapInSapModelParams(
Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning_effort: effort }])),
)
}
return wrapInSapModelParams(
Object.fromEntries(["low", "medium", "high"].map((effort) => [effort, { reasoning_effort: effort }])),
@@ -1426,6 +1426,30 @@ test("models.dev normalization fills required response fields", () => {
expect(model.release_date).toBe("")
})
test("models.dev normalization preserves reasoning options for variant generation", () => {
const provider = {
id: "custom",
name: "Custom",
env: [],
npm: "@ai-sdk/openai-compatible",
models: {
reasoner: {
id: "reasoner",
name: "Reasoner",
reasoning: true,
reasoning_options: [{ type: "effort", values: ["high", "xhigh"] }],
cost: { input: 1, output: 2 },
limit: { context: 128_000, output: 32_000 },
},
},
} as unknown as ModelsDev.Provider
const model = Provider.fromModelsDevProvider(provider).models.reasoner
expect(model.reasoning_options).toEqual([{ type: "effort", values: ["high", "xhigh"] }])
expect(Object.keys(model.variants!)).toEqual(["high", "xhigh"])
expect(model.variants!.xhigh).toEqual({ reasoningEffort: "xhigh" })
})
it.instance("model variants are generated for reasoning models", () =>
Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key")
@@ -1524,7 +1548,13 @@ it.instance(
anthropic: {
models: {
"claude-sonnet-4-20250514": {
variants: { high: { disabled: true }, max: { disabled: true } },
variants: {
low: { disabled: true },
medium: { disabled: true },
high: { disabled: true },
xhigh: { disabled: true },
max: { disabled: true },
},
},
},
},
+298 -869
View File
@@ -2827,6 +2827,132 @@ describe("ProviderTransform.variants", () => {
expect(result).toEqual({})
})
test("reasoning_options drive OpenAI-compatible effort variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "custom/new-reasoner",
providerID: "custom",
api: {
id: "new-reasoner",
url: "https://api.example.com",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [{ type: "effort", values: ["none", "high", "xhigh"] }],
}),
)
expect(Object.keys(result)).toEqual(["none", "high", "xhigh"])
expect(result.xhigh).toEqual({ reasoningEffort: "xhigh" })
})
test("empty reasoning_options suppress heuristic variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "anthropic/claude-sonnet-5",
providerID: "anthropic",
api: {
id: "claude-sonnet-5",
url: "https://api.anthropic.com",
npm: "@ai-sdk/anthropic",
},
reasoning_options: [],
}),
)
expect(result).toEqual({})
})
test("reasoning_options infer adaptive Anthropic variants without release heuristics", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "anthropic/claude-future",
providerID: "anthropic",
api: {
id: "claude-future",
url: "https://api.anthropic.com",
npm: "@ai-sdk/anthropic",
},
reasoning_options: [{ type: "toggle" }, { type: "effort", values: ["low", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "max"])
expect(result.max).toEqual({
thinking: { type: "adaptive", display: "summarized" },
effort: "max",
})
})
test("reasoning_options keep legacy Anthropic effort shape when budget tokens are also supported", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "anthropic/claude-opus-4-5",
providerID: "anthropic",
api: {
id: "claude-opus-4-5",
url: "https://api.anthropic.com",
npm: "@ai-sdk/anthropic",
},
reasoning_options: [
{ type: "effort", values: ["low", "medium", "high"] },
{ type: "budget_tokens", min: 1024 },
],
}),
)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.high).toEqual({ effort: "high" })
})
test("reasoning_options budget tokens generate budget variants when no effort is available", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "google/gemini-new",
providerID: "google",
api: {
id: "gemini-new",
url: "https://generativelanguage.googleapis.com",
npm: "@ai-sdk/google",
},
reasoning_options: [{ type: "toggle" }, { type: "budget_tokens", min: 128, max: 32768 }],
}),
)
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } })
expect(result.max).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 32768 } })
})
test("reasoning_options ignore toggle-only models", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "custom/toggle-only",
providerID: "custom",
api: {
id: "toggle-only",
url: "https://api.example.com",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [{ type: "toggle" }],
}),
)
expect(result).toEqual({})
})
test("reasoning_options do not override existing MiniMax M3 toggle variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "minimax/minimax-m3",
providerID: "minimax",
api: {
id: "MiniMax-M3",
url: "https://api.minimax.com/anthropic/v1",
npm: "@ai-sdk/anthropic",
},
reasoning_options: [],
}),
)
expect(result).toEqual({
none: { thinking: { type: "disabled" } },
thinking: { thinking: { type: "adaptive" } },
})
})
test("deepseek returns empty object", () => {
const model = createMockModel({
id: "deepseek/deepseek-chat",
@@ -2836,6 +2962,7 @@ describe("ProviderTransform.variants", () => {
url: "https://api.deepseek.com",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [],
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
@@ -2850,6 +2977,7 @@ describe("ProviderTransform.variants", () => {
url: "https://api.minimax.com",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [],
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
@@ -2888,21 +3016,7 @@ describe("ProviderTransform.variants", () => {
})
})
test("glm returns empty object", () => {
const model = createMockModel({
id: "glm/glm-4",
providerID: "glm",
api: {
id: "glm-4",
url: "https://api.glm.com",
npm: "@ai-sdk/openai-compatible",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
test("glm-5.2 returns native effort variants for openai-compatible providers", () => {
test("model-specific efforts are read from reasoning_options", () => {
const model = createMockModel({
id: "zhipuai/glm-5.2",
providerID: "zhipuai",
@@ -2911,6 +3025,7 @@ describe("ProviderTransform.variants", () => {
url: "https://open.bigmodel.cn/api/paas/v4",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [{ type: "effort", values: ["high", "max"] }],
})
expect(ProviderTransform.variants(model)).toEqual({
high: { reasoningEffort: "high" },
@@ -2918,86 +3033,6 @@ describe("ProviderTransform.variants", () => {
})
})
test("recognizes GLM-5.2 provider model IDs", () => {
for (const id of ["accounts/fireworks/models/glm-5p2", "zai-org-glm-5-2", "umans-glm-5.2"]) {
const model = createMockModel({
id: `test/${id}`,
api: {
id,
url: "https://api.test.com",
npm: "@ai-sdk/openai-compatible",
},
})
expect(ProviderTransform.variants(model)).toEqual({
high: { reasoningEffort: "high" },
max: { reasoningEffort: "max" },
})
}
})
test("recognizes GLM-5.2 from the API ID when the configured model ID is an alias", () => {
const model = createMockModel({
id: "custom/my-glm",
api: {
id: "accounts/fireworks/models/glm-5p2",
url: "https://api.fireworks.ai/inference/v1",
npm: "@ai-sdk/openai-compatible",
},
})
expect(ProviderTransform.variants(model)).toEqual({
high: { reasoningEffort: "high" },
max: { reasoningEffort: "max" },
})
})
test("glm-5.2 returns openrouter effort variants for openrouter", () => {
const model = createMockModel({
id: "openrouter/z-ai/glm-5.2",
providerID: "openrouter",
api: {
id: "z-ai/glm-5.2",
url: "https://openrouter.ai/api/v1",
npm: "@openrouter/ai-sdk-provider",
},
})
expect(ProviderTransform.variants(model)).toEqual({
high: { reasoning: { effort: "high" } },
xhigh: { reasoning: { effort: "xhigh" } },
})
})
test("glm-5.2 returns effort variants for anthropic-compatible providers", () => {
const model = createMockModel({
id: "zai-coding-plan/glm-5.2",
providerID: "zai-coding-plan",
api: {
id: "glm-5.2",
url: "https://api.z.ai/api/anthropic",
npm: "@ai-sdk/anthropic",
},
})
expect(ProviderTransform.variants(model)).toEqual({
high: { effort: "high" },
max: { effort: "max" },
})
})
test("glm-5.2 falls back to provider defaults for other packages", () => {
const model = createMockModel({
id: "test/glm-5.2",
api: {
id: "glm-5.2",
url: "https://api.test.com",
npm: "@ai-sdk/amazon-bedrock",
},
})
expect(ProviderTransform.variants(model)).toEqual({
low: { reasoningConfig: { type: "enabled", maxReasoningEffort: "low" } },
medium: { reasoningConfig: { type: "enabled", maxReasoningEffort: "medium" } },
high: { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } },
})
})
test("mistral models with reasoning support return variants", () => {
const model = createMockModel({
id: "mistral/mistral-small-latest",
@@ -3047,21 +3082,6 @@ describe("ProviderTransform.variants", () => {
expect(result).toEqual({})
})
test("mistral large with reasoning returns empty object (only small supports reasoning)", () => {
const model = createMockModel({
id: "mistral/mistral-large",
providerID: "mistral",
api: {
id: "mistral-large-latest",
url: "https://api.mistral.com",
npm: "@ai-sdk/mistral",
},
capabilities: { reasoning: true },
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
describe("@openrouter/ai-sdk-provider", () => {
test("returns widely supported efforts for other reasoning models", () => {
const model = createMockModel({
@@ -3078,286 +3098,26 @@ describe("ProviderTransform.variants", () => {
expect(result.medium).toEqual({ reasoning: { effort: "medium" } })
})
test("gpt models return OPENAI_EFFORTS with reasoning", () => {
const model = createMockModel({
id: "openrouter/gpt-4",
providerID: "openrouter",
api: {
id: "gpt-4",
url: "https://openrouter.ai",
npm: "@openrouter/ai-sdk-provider",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
expect(result.low).toEqual({ reasoning: { effort: "low" } })
expect(result.high).toEqual({ reasoning: { effort: "high" } })
})
for (const testCase of [
{ id: "openai/o3-mini", efforts: ["none", "minimal", "low", "medium", "high", "xhigh"] },
{ 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 widely supported efforts with reasoning", () => {
const model = createMockModel({
id: "openrouter/gemini-3-5-pro",
providerID: "openrouter",
api: {
id: "gemini-3-5-pro",
url: "https://openrouter.ai",
npm: "@openrouter/ai-sdk-provider",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
})
test("grok-4 returns empty object", () => {
const model = createMockModel({
id: "openrouter/grok-4",
providerID: "openrouter",
api: {
id: "grok-4",
url: "https://openrouter.ai",
npm: "@openrouter/ai-sdk-provider",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
test("grok-3-mini returns low and high with reasoning", () => {
const model = createMockModel({
id: "openrouter/grok-3-mini",
providerID: "openrouter",
api: {
id: "grok-3-mini",
url: "https://openrouter.ai",
npm: "@openrouter/ai-sdk-provider",
},
})
const result = ProviderTransform.variants(model)
test("model-specific OpenRouter efforts come from reasoning_options", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "openrouter/grok-3-mini",
providerID: "openrouter",
api: {
id: "grok-3-mini",
url: "https://openrouter.ai",
npm: "@openrouter/ai-sdk-provider",
},
reasoning_options: [{ type: "effort", values: ["low", "high"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "high"])
expect(result.low).toEqual({ reasoning: { effort: "low" } })
expect(result.high).toEqual({ reasoning: { effort: "high" } })
})
})
describe("@ai-sdk/gateway", () => {
test("anthropic sonnet 4.6 models return adaptive thinking options", () => {
const model = createMockModel({
id: "anthropic/claude-sonnet-4-6",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-4-6",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
expect(result.medium).toEqual({
thinking: {
type: "adaptive",
},
effort: "medium",
})
})
test("anthropic sonnet 4.6 dot-format models return adaptive thinking options", () => {
const model = createMockModel({
id: "anthropic/claude-sonnet-4-6",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-4.6",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
expect(result.medium).toEqual({
thinking: {
type: "adaptive",
},
effort: "medium",
})
})
test("anthropic opus 4.6 dot-format models return adaptive thinking options", () => {
const model = createMockModel({
id: "anthropic/claude-opus-4-6",
providerID: "gateway",
api: {
id: "anthropic/claude-opus-4.6",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
expect(result.high).toEqual({
thinking: {
type: "adaptive",
},
effort: "high",
})
})
test("anthropic opus 4.7 models return adaptive thinking options with xhigh", () => {
const model = createMockModel({
id: "anthropic/claude-opus-4-7",
providerID: "gateway",
api: {
id: "anthropic/claude-opus-4-7",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
expect(result.xhigh).toEqual({
thinking: {
type: "adaptive",
display: "summarized",
},
effort: "xhigh",
})
expect(result.max).toEqual({
thinking: {
type: "adaptive",
display: "summarized",
},
effort: "max",
})
})
test("anthropic opus 4.7 dot-format models return adaptive thinking options with xhigh", () => {
const model = createMockModel({
id: "anthropic/claude-opus-4-7",
providerID: "gateway",
api: {
id: "anthropic/claude-opus-4.7",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
})
test("anthropic opus 4.8 forces display summarized for adaptive reasoning", () => {
const model = createMockModel({
id: "anthropic/claude-opus-4-8",
providerID: "gateway",
api: {
id: "anthropic/claude-opus-4-8",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
expect(result.high).toEqual({
thinking: {
type: "adaptive",
display: "summarized",
},
effort: "high",
})
})
test("anthropic sonnet 5 returns adaptive thinking options with xhigh", () => {
const model = createMockModel({
id: "anthropic/claude-sonnet-5",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-5",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
expect(result.high).toEqual({
thinking: {
type: "adaptive",
display: "summarized",
},
effort: "high",
})
})
test("anthropic opus 4.6 omits display so it keeps the summarized default", () => {
const model = createMockModel({
id: "anthropic/claude-opus-4-6",
providerID: "gateway",
api: {
id: "anthropic/claude-opus-4-6",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
expect(result.high).toEqual({
thinking: {
type: "adaptive",
},
effort: "high",
})
})
test("anthropic models return anthropic thinking options", () => {
const model = createMockModel({
id: "anthropic/claude-sonnet-4",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-4",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({
thinking: {
type: "enabled",
budgetTokens: 16000,
},
})
expect(result.max).toEqual({
thinking: {
type: "enabled",
budgetTokens: 31999,
},
})
})
test("returns OPENAI_EFFORTS with reasoningEffort", () => {
test("falls back to widely-supported reasoningEffort variants", () => {
const model = createMockModel({
id: "gateway/gateway-model",
providerID: "gateway",
@@ -3368,36 +3128,66 @@ describe("ProviderTransform.variants", () => {
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.low).toEqual({ reasoningEffort: "low" })
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)
test("uses reasoning_options for Anthropic adaptive variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "anthropic/claude-sonnet-5",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-5",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
reasoning_options: [{ type: "effort", values: ["low", "high", "xhigh", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "high", "xhigh", "max"])
expect(result.high).toEqual({
thinking: { type: "adaptive", display: "summarized" },
effort: "high",
})
}
})
test("uses reasoning_options budget tokens for Anthropic variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "anthropic/claude-sonnet-4",
providerID: "gateway",
api: {
id: "anthropic/claude-sonnet-4",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
reasoning_options: [{ type: "budget_tokens", min: 1024, max: 32768 }],
}),
)
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({ thinking: { type: "enabled", budgetTokens: 16_000 } })
expect(result.max).toEqual({ thinking: { type: "enabled", budgetTokens: 32_768 } })
})
test("uses reasoning_options budget tokens for Google variants", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "google/gemini-2.5-pro",
providerID: "gateway",
api: {
id: "google/gemini-2.5-pro",
url: "https://gateway.ai",
npm: "@ai-sdk/gateway",
},
reasoning_options: [{ type: "budget_tokens", min: 128, max: 32768 }],
}),
)
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16_000 } })
expect(result.max).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 32_768 } })
})
})
describe("@ai-sdk/github-copilot", () => {
@@ -3420,109 +3210,6 @@ describe("ProviderTransform.variants", () => {
})
})
test("gpt-5.1-codex-max includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.1-codex-max",
providerID: "github-copilot",
api: {
id: "gpt-5.1-codex-max",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
})
test("gpt-5.1-codex-mini does not include xhigh", () => {
const model = createMockModel({
id: "gpt-5.1-codex-mini",
providerID: "github-copilot",
api: {
id: "gpt-5.1-codex-mini",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
})
test("gpt-5.1-codex does not include xhigh", () => {
const model = createMockModel({
id: "gpt-5.1-codex",
providerID: "github-copilot",
api: {
id: "gpt-5.1-codex",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
})
test("gpt-5.2 includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.2",
providerID: "github-copilot",
api: {
id: "gpt-5.2",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
expect(result.xhigh).toEqual({
reasoningEffort: "xhigh",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
})
})
test("gpt-5.2-codex includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.2-codex",
providerID: "github-copilot",
api: {
id: "gpt-5.2-codex",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
})
test("gpt-5.3-codex includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.3-codex",
providerID: "github-copilot",
api: {
id: "gpt-5.3-codex",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
})
test("gpt-5.4 includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.4",
release_date: "2026-03-05",
providerID: "github-copilot",
api: {
id: "gpt-5.4",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
})
})
describe("@ai-sdk/cerebras", () => {
@@ -3562,7 +3249,7 @@ describe("ProviderTransform.variants", () => {
})
describe("@ai-sdk/xai", () => {
test("grok-3 returns empty object", () => {
test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
const model = createMockModel({
id: "xai/grok-3",
providerID: "xai",
@@ -3573,23 +3260,8 @@ describe("ProviderTransform.variants", () => {
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
test("grok-3-mini returns low and high with reasoningEffort", () => {
const model = createMockModel({
id: "xai/grok-3-mini",
providerID: "xai",
api: {
id: "grok-3-mini",
url: "https://api.x.ai",
npm: "@ai-sdk/xai",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "high"])
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.low).toEqual({ reasoningEffort: "low" })
expect(result.high).toEqual({ reasoningEffort: "high" })
})
})
@@ -3628,17 +3300,19 @@ describe("ProviderTransform.variants", () => {
expect(result.high).toEqual({ reasoningEffort: "high" })
})
test("north-mini-code-1-0 returns only none and high", () => {
const model = createMockModel({
id: "cohere/north-mini-code-1-0",
providerID: "cohere",
api: {
id: "North-Mini-Code-1-0-latest",
url: "https://api.cohere.com/compatibility/v1",
npm: "@ai-sdk/openai-compatible",
},
})
const result = ProviderTransform.variants(model)
test("model-specific OpenAI-compatible efforts come from reasoning_options", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "cohere/north-mini-code-1-0",
providerID: "cohere",
api: {
id: "North-Mini-Code-1-0-latest",
url: "https://api.cohere.com/compatibility/v1",
npm: "@ai-sdk/openai-compatible",
},
reasoning_options: [{ type: "effort", values: ["none", "high"] }],
}),
)
expect(result).toEqual({
none: { reasoningEffort: "none" },
high: { reasoningEffort: "high" },
@@ -3647,21 +3321,7 @@ describe("ProviderTransform.variants", () => {
})
describe("@ai-sdk/azure", () => {
test("o1-mini returns empty object", () => {
const model = createMockModel({
id: "o1-mini",
providerID: "azure",
api: {
id: "o1-mini",
url: "https://azure.com",
npm: "@ai-sdk/azure",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
test("standard azure models return custom efforts with reasoningSummary", () => {
test("falls back to OpenAI-style efforts with reasoningSummary", () => {
const model = createMockModel({
id: "o1",
providerID: "azure",
@@ -3672,67 +3332,17 @@ describe("ProviderTransform.variants", () => {
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
expect(result.low).toEqual({
reasoningEffort: "low",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
})
})
test("gpt-5 adds minimal effort", () => {
const model = createMockModel({
id: "gpt-5",
providerID: "azure",
api: {
id: "gpt-5",
url: "https://azure.com",
npm: "@ai-sdk/azure",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
})
for (const testCase of [
{ id: "gpt-5-1", efforts: ["none", "low", "medium", "high"] },
{ id: "gpt-5-4", efforts: ["none", "low", "medium", "high", "xhigh"] },
{ id: "gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
{ id: "gpt-5-5", efforts: ["none", "low", "medium", "high", "xhigh"] },
]) {
test(`${testCase.id} returns supported Azure reasoning efforts`, () => {
const result = ProviderTransform.variants(
createMockModel({
id: testCase.id,
providerID: "azure",
api: {
id: testCase.id,
url: "https://azure.com",
npm: "@ai-sdk/azure",
},
}),
)
expect(Object.keys(result)).toEqual(testCase.efforts)
})
}
})
describe("@ai-sdk/openai", () => {
test("gpt-5-pro returns only high effort", () => {
const model = createMockModel({
id: "gpt-5-pro",
providerID: "openai",
api: {
id: "gpt-5-pro",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["high"])
})
test("standard openai models return custom efforts with reasoningSummary", () => {
test("falls back to OpenAI-style efforts with reasoningSummary", () => {
const model = createMockModel({
id: "gpt-5",
providerID: "openai",
@@ -3744,7 +3354,7 @@ describe("ProviderTransform.variants", () => {
release_date: "2024-06-01",
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
expect(result.low).toEqual({
reasoningEffort: "low",
reasoningSummary: "auto",
@@ -3752,95 +3362,20 @@ describe("ProviderTransform.variants", () => {
})
})
test("models after 2025-11-13 include 'none' effort", () => {
const model = createMockModel({
id: "gpt-5-nano",
providerID: "openai",
api: {
id: "gpt-5-nano",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
release_date: "2025-11-14",
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high"])
})
test("models after 2025-12-04 include 'xhigh' effort", () => {
const model = createMockModel({
id: "openai/gpt-5-reasoning",
providerID: "openai",
api: {
id: "gpt-5-reasoning",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
release_date: "2025-12-05",
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
})
for (const testCase of [
{ id: "o1", releaseDate: "2024-12-17", efforts: ["low", "medium", "high"] },
{ id: "o1-pro", releaseDate: "2025-03-19", efforts: ["low", "medium", "high"] },
{ id: "o3", releaseDate: "2025-04-16", efforts: ["low", "medium", "high"] },
{ id: "o3-mini", releaseDate: "2025-01-31", efforts: ["low", "medium", "high"] },
{ id: "o3-pro", releaseDate: "2025-06-10", efforts: ["low", "medium", "high"] },
{ id: "o4-mini", releaseDate: "2025-04-16", efforts: ["low", "medium", "high"] },
{ id: "o3-deep-research", releaseDate: "2025-06-26", efforts: ["medium"] },
{ id: "o4-mini-deep-research", releaseDate: "2025-06-26", efforts: ["medium"] },
{ id: "gpt-5.1", releaseDate: "2025-11-13", efforts: ["none", "low", "medium", "high"] },
{ id: "gpt-5.4", releaseDate: "2026-03-05", efforts: ["none", "low", "medium", "high", "xhigh"] },
{
id: "gpt-5.5",
modelID: "gpt-5-5",
releaseDate: "2026-04-23",
efforts: ["none", "low", "medium", "high", "xhigh"],
},
{ id: "gpt-5.4-pro", releaseDate: "2026-03-05", efforts: ["medium", "high", "xhigh"] },
{ id: "gpt-5.5-pro", releaseDate: "2026-04-23", efforts: ["medium", "high", "xhigh"] },
{ id: "gpt-5-codex", releaseDate: "2025-09-23", efforts: ["low", "medium", "high"] },
{ 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"] },
{ 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)
})
}
test("gpt-50 (lookalike) does not get gpt-5 family treatment", () => {
const model = createMockModel({
id: "gpt-50",
providerID: "openai",
api: {
id: "gpt-50",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
release_date: "2024-01-01",
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
test("model-specific OpenAI efforts come from reasoning_options", () => {
const result = ProviderTransform.variants(
createMockModel({
id: "gpt-5-pro",
providerID: "openai",
api: {
id: "gpt-5-pro",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
reasoning_options: [{ type: "effort", values: ["high"] }],
}),
)
expect(Object.keys(result)).toEqual(["high"])
})
})
@@ -3857,7 +3392,7 @@ describe("ProviderTransform.variants", () => {
release_date: "2026-04-23",
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"])
expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
expect(result.medium).toEqual({
reasoningEffort: "medium",
reasoningSummary: "auto",
@@ -3878,13 +3413,13 @@ describe("ProviderTransform.variants", () => {
name: "sonnet 4.6",
apiIds: ["claude-sonnet-4-6", "claude-sonnet-4.6"],
efforts: ["low", "medium", "high", "max"],
expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
},
{
name: "opus 4.6",
apiIds: ["claude-opus-4-6", "claude-opus-4.6"],
efforts: ["low", "medium", "high", "max"],
expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
},
{
name: "opus 4.7",
@@ -3917,42 +3452,21 @@ describe("ProviderTransform.variants", () => {
createMockModel({
id: `anthropic/${apiId}`,
providerID: "anthropic",
api: {
id: apiId,
url: "https://api.anthropic.com",
npm: "@ai-sdk/anthropic",
},
}),
)
api: {
id: apiId,
url: "https://api.anthropic.com",
npm: "@ai-sdk/anthropic",
},
reasoning_options: [{ type: "effort", values: testCase.efforts }],
}),
)
expect(Object.keys(result)).toEqual(testCase.efforts)
expect(result.high).toEqual(testCase.expectedHigh)
})
}
}
test("github copilot opus 4.7 returns only medium reasoning effort", () => {
const model = createMockModel({
id: "claude-opus-4.7",
providerID: "github-copilot",
api: {
id: "claude-opus-4.7",
url: "https://api.githubcopilot.com/v1",
npm: "@ai-sdk/anthropic",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({
medium: {
thinking: {
type: "adaptive",
display: "summarized",
},
effort: "medium",
},
})
})
test("returns high and max with thinking config", () => {
test("falls back to adaptive thinking efforts", () => {
const model = createMockModel({
id: "anthropic/claude-4",
providerID: "anthropic",
@@ -3963,18 +3477,10 @@ describe("ProviderTransform.variants", () => {
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["high", "max"])
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
expect(result.high).toEqual({
thinking: {
type: "enabled",
budgetTokens: 16000,
},
})
expect(result.max).toEqual({
thinking: {
type: "enabled",
budgetTokens: 31999,
},
thinking: { type: "adaptive", display: "summarized" },
effort: "high",
})
})
})
@@ -3990,6 +3496,7 @@ describe("ProviderTransform.variants", () => {
url: "https://us-central1-aiplatform.googleapis.com",
npm: "@ai-sdk/google-vertex/anthropic",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
@@ -4012,6 +3519,7 @@ describe("ProviderTransform.variants", () => {
url: "https://us-central1-aiplatform.googleapis.com",
npm: "@ai-sdk/google-vertex/anthropic",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
@@ -4035,6 +3543,7 @@ describe("ProviderTransform.variants", () => {
url: "https://bedrock.amazonaws.com",
npm: "@ai-sdk/amazon-bedrock",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "max"] }],
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
@@ -4042,6 +3551,7 @@ describe("ProviderTransform.variants", () => {
reasoningConfig: {
type: "adaptive",
maxReasoningEffort: "max",
display: "summarized",
},
})
})
@@ -4055,6 +3565,7 @@ describe("ProviderTransform.variants", () => {
url: "https://bedrock.amazonaws.com",
npm: "@ai-sdk/amazon-bedrock",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
@@ -4084,6 +3595,7 @@ describe("ProviderTransform.variants", () => {
url: "https://bedrock.amazonaws.com",
npm: "@ai-sdk/amazon-bedrock",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
@@ -4106,6 +3618,7 @@ describe("ProviderTransform.variants", () => {
url: "https://bedrock.amazonaws.com",
npm: "@ai-sdk/amazon-bedrock",
},
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
}),
)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
@@ -4198,6 +3711,9 @@ describe("ProviderTransform.variants", () => {
url: provider.url,
npm: provider.name,
},
reasoning_options: testCase.expectedMax
? [{ type: "budget_tokens", max: testCase.expectedMax.thinkingConfig.thinkingBudget }]
: [{ type: "effort", values: testCase.efforts }],
}),
)
expect(Object.keys(result)).toEqual(testCase.efforts)
@@ -4263,7 +3779,7 @@ describe("ProviderTransform.variants", () => {
})
describe("@jerome-benoit/sap-ai-provider-v2", () => {
const sapModel = (apiId: string, releaseDate = "2024-01-01") =>
const sapModel = (apiId: string, reasoning_options?: any[]) =>
createMockModel({
id: `sap-ai-core/${apiId}`,
providerID: "sap-ai-core",
@@ -4272,116 +3788,27 @@ describe("ProviderTransform.variants", () => {
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
release_date: releaseDate,
reasoning_options,
})
for (const testCase of [
{
name: "sonnet 4.6",
apiIds: ["anthropic--claude-sonnet-4-6"],
efforts: ["low", "medium", "high", "max"],
thinking: { type: "adaptive" },
},
{
name: "opus 4.6",
apiIds: ["anthropic--claude-4.6-opus", "anthropic--claude-4-6-opus"],
efforts: ["low", "medium", "high", "max"],
thinking: { type: "adaptive" },
},
{
name: "opus 4.7",
apiIds: ["anthropic--claude-4.7-opus", "anthropic--claude-4-7-opus"],
efforts: ["low", "medium", "high", "xhigh", "max"],
thinking: { type: "adaptive", display: "summarized" },
},
{
name: "opus 4.8",
apiIds: ["anthropic--claude-4.8-opus", "anthropic--claude-4-8-opus"],
efforts: ["low", "medium", "high", "xhigh", "max"],
thinking: { type: "adaptive", display: "summarized" },
},
{
name: "sonnet 5",
apiIds: ["anthropic--claude-sonnet-5", "anthropic--claude-5-sonnet"],
efforts: ["low", "medium", "high", "xhigh", "max"],
thinking: { type: "adaptive", display: "summarized" },
},
]) {
for (const apiId of testCase.apiIds) {
test(`${testCase.name} ${apiId} returns adaptive thinking variants under modelParams`, () => {
const result = ProviderTransform.variants(sapModel(apiId))
expect(Object.keys(result)).toEqual(testCase.efforts)
for (const effort of testCase.efforts) {
expect(result[effort]).toEqual({
modelParams: {
thinking: testCase.thinking,
output_config: { effort },
},
})
}
})
}
}
test("falls back to harmonized reasoning_effort variants", () => {
const result = ProviderTransform.variants(sapModel("generic-reasoner"))
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.high).toEqual({ modelParams: { reasoning_effort: "high" } })
})
for (const apiId of ["anthropic--claude-sonnet-4", "anthropic--claude-4.5-opus"]) {
test(`${apiId} returns budget_tokens variants under modelParams`, () => {
const result = ProviderTransform.variants(sapModel(apiId))
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({
modelParams: { thinking: { type: "enabled", budget_tokens: 16000 } },
})
expect(result.max).toEqual({
modelParams: { thinking: { type: "enabled", budget_tokens: 31999 } },
})
test("uses reasoning_options for Anthropic adaptive variants", () => {
const result = ProviderTransform.variants(
sapModel("anthropic--claude-sonnet-5", [{ type: "effort", values: ["low", "high", "max"] }]),
)
expect(Object.keys(result)).toEqual(["low", "high", "max"])
expect(result.max).toEqual({
modelParams: {
thinking: { type: "adaptive", display: "summarized" },
output_config: { effort: "max" },
},
})
}
for (const testCase of [
{ apiId: "gemini-2.5-pro", maxBudget: 32768 },
{ apiId: "gemini-2.5-flash", maxBudget: 24576 },
]) {
test(`${testCase.apiId} returns thinkingConfig variants under modelParams`, () => {
const result = ProviderTransform.variants(sapModel(testCase.apiId))
expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({
modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
})
expect(result.max).toEqual({
modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: testCase.maxBudget } },
})
})
}
for (const testCase of [
{ apiId: "gpt-5", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
{ apiId: "gpt-5-mini", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
{ apiId: "gpt-5-nano", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
{ apiId: "gpt-5.4", releaseDate: "2026-01-15", efforts: ["none", "low", "medium", "high", "xhigh"] },
{ apiId: "azure-openai--o3-mini", releaseDate: "2024-01-01", efforts: ["low", "medium", "high"] },
]) {
test(`${testCase.apiId} returns reasoning_effort variants under modelParams`, () => {
const result = ProviderTransform.variants(sapModel(testCase.apiId, testCase.releaseDate))
expect(Object.keys(result)).toEqual(testCase.efforts)
for (const effort of testCase.efforts) {
expect(result[effort]).toEqual({ modelParams: { reasoning_effort: effort } })
}
})
}
for (const apiId of [
"gemini-3.1-flash-lite",
"cohere--command-a-reasoning",
"sonar-deep-research",
"aws--llama-opus-4.7-fake",
]) {
test(`${apiId} falls through to harmonized reasoning_effort fallback`, () => {
const result = ProviderTransform.variants(sapModel(apiId))
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
for (const effort of ["low", "medium", "high"]) {
expect(result[effort]).toEqual({ modelParams: { reasoning_effort: effort } })
}
})
}
})
})
describe("ai-gateway-provider (cloudflare-ai-gateway)", () => {
@@ -4397,20 +3824,14 @@ describe("ProviderTransform.variants", () => {
release_date: releaseDate,
})
for (const testCase of [
{ id: "openai/gpt-5.4", efforts: ["none", "low", "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-pro", efforts: ["high"] },
{ id: "openai/gpt-5.2-pro", efforts: ["medium", "high", "xhigh"] },
{ id: "openai/gpt-5-chat-latest", efforts: [] },
{ id: "openai/gpt-5.2-chat-latest", efforts: ["medium"] },
]) {
test(`${testCase.id} returns supported reasoning efforts`, () => {
const result = ProviderTransform.variants(cfModel(testCase.id, "2026-03-05"))
expect(Object.keys(result)).toEqual(testCase.efforts)
test("falls back to widely-supported OAI efforts", () => {
const result = ProviderTransform.variants(cfModel("openai/gpt-future", "2026-03-05"))
expect(result).toEqual({
low: { reasoningEffort: "low" },
medium: { reasoningEffort: "medium" },
high: { reasoningEffort: "high" },
})
}
})
test("openai gpt-4o (no reasoning) returns empty", () => {
const model = cfModel("openai/gpt-4o")
@@ -4449,12 +3870,20 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
}
for (const testCase of [
{ id: "gpt-5-chat-latest", options: { store: false } },
{
id: "gpt-5-chat-latest",
options: {
store: false,
reasoningEffort: "none",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
},
},
{
id: "gpt-5.1-chat-latest",
options: {
store: false,
reasoningEffort: "medium",
reasoningEffort: "none",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
},
@@ -4463,7 +3892,7 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
id: "gpt-5.2-chat-latest",
options: {
store: false,
reasoningEffort: "medium",
reasoningEffort: "none",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
},
@@ -4520,14 +3949,14 @@ describe("ProviderTransform.smallOptions - google thinking controls", () => {
for (const testCase of [
{ id: "gemini-3-pro-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
{ id: "gemini-3-flash-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } } },
{ id: "gemini-3-flash-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
{
id: "gemini-3.1-flash-image-preview",
options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } },
options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } },
},
{ id: "gemini-3-pro-image-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } } },
{ id: "gemini-2.5-pro", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
{ id: "gemini-2.5-flash", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
{ id: "gemini-3-pro-image-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
{ id: "gemini-2.5-pro", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
{ id: "gemini-2.5-flash", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
]) {
test(`${testCase.id} returns supported small thinking options`, () => {
expect(ProviderTransform.smallOptions(createGoogleModel(testCase.id))).toEqual(testCase.options)