fix(model-capabilities): harden runtime capability handling
This commit is contained in:
@@ -229,4 +229,38 @@ describe("updateConnectedProvidersCache", () => {
|
|||||||
limit: { output: 128000 },
|
limit: { output: 128000 },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("keeps normalized fallback ids when raw metadata id is not a string", async () => {
|
||||||
|
const mockClient = {
|
||||||
|
provider: {
|
||||||
|
list: async () => ({
|
||||||
|
data: {
|
||||||
|
connected: ["openai"],
|
||||||
|
all: [
|
||||||
|
{
|
||||||
|
id: "openai",
|
||||||
|
models: {
|
||||||
|
"o3-mini": {
|
||||||
|
id: 123,
|
||||||
|
name: "o3-mini",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await testCacheStore.updateConnectedProvidersCache(mockClient)
|
||||||
|
const cache = testCacheStore.readProviderModelsCache()
|
||||||
|
|
||||||
|
expect(cache?.models.openai).toEqual([
|
||||||
|
{ id: "o3-mini", name: "o3-mini" },
|
||||||
|
])
|
||||||
|
expect(findProviderModelMetadata("openai", "o3-mini", cache)).toEqual({
|
||||||
|
id: "o3-mini",
|
||||||
|
name: "o3-mini",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -198,8 +198,8 @@ export function createConnectedProvidersCacheStore(
|
|||||||
: modelID
|
: modelID
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: normalizedID,
|
|
||||||
...rawMetadata,
|
...rawMetadata,
|
||||||
|
id: normalizedID,
|
||||||
} satisfies ModelMetadata
|
} satisfies ModelMetadata
|
||||||
})
|
})
|
||||||
if (modelMetadata.length > 0) {
|
if (modelMetadata.length > 0) {
|
||||||
|
|||||||
@@ -97,6 +97,37 @@ describe("model-capabilities-cache", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("merges repeated snapshot entries without materializing empty optional objects", () => {
|
||||||
|
const raw = {
|
||||||
|
openai: {
|
||||||
|
models: {
|
||||||
|
"gpt-5.4": {
|
||||||
|
id: "gpt-5.4",
|
||||||
|
family: "gpt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
models: {
|
||||||
|
"gpt-5.4-preview": {
|
||||||
|
id: "gpt-5.4",
|
||||||
|
reasoning: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = buildModelCapabilitiesSnapshotFromModelsDev(raw)
|
||||||
|
|
||||||
|
expect(snapshot.models["gpt-5.4"]).toEqual({
|
||||||
|
id: "gpt-5.4",
|
||||||
|
family: "gpt",
|
||||||
|
reasoning: true,
|
||||||
|
})
|
||||||
|
expect(snapshot.models["gpt-5.4"]).not.toHaveProperty("modalities")
|
||||||
|
expect(snapshot.models["gpt-5.4"]).not.toHaveProperty("limit")
|
||||||
|
})
|
||||||
|
|
||||||
test("refresh writes cache and preserves unrelated files in the cache directory", async () => {
|
test("refresh writes cache and preserves unrelated files in the cache directory", async () => {
|
||||||
//#given
|
//#given
|
||||||
const sentinelPath = join(testCacheDir, "keep-me.json")
|
const sentinelPath = join(testCacheDir, "keep-me.json")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json"
|
|||||||
const MODEL_CAPABILITIES_CACHE_FILE = "model-capabilities.json"
|
const MODEL_CAPABILITIES_CACHE_FILE = "model-capabilities.json"
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return typeof value === "object" && value !== null
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function readBoolean(value: unknown): boolean | undefined {
|
function readBoolean(value: unknown): boolean | undefined {
|
||||||
@@ -84,17 +84,24 @@ function mergeSnapshotEntries(
|
|||||||
return incoming
|
return incoming
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergedModalities = existing.modalities || incoming.modalities
|
||||||
|
? {
|
||||||
|
...existing.modalities,
|
||||||
|
...incoming.modalities,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
const mergedLimit = existing.limit || incoming.limit
|
||||||
|
? {
|
||||||
|
...existing.limit,
|
||||||
|
...incoming.limit,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...existing,
|
...existing,
|
||||||
...incoming,
|
...incoming,
|
||||||
modalities: {
|
...(mergedModalities ? { modalities: mergedModalities } : {}),
|
||||||
...existing.modalities,
|
...(mergedLimit ? { limit: mergedLimit } : {}),
|
||||||
...incoming.modalities,
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
...existing.limit,
|
|
||||||
...incoming.limit,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,53 @@ describe("getModelCapabilities", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("reads structured runtime capabilities from the SDK v2 shape", () => {
|
||||||
|
const result = getModelCapabilities({
|
||||||
|
providerID: "openai",
|
||||||
|
modelID: "gpt-5.4",
|
||||||
|
runtimeModel: {
|
||||||
|
capabilities: {
|
||||||
|
reasoning: true,
|
||||||
|
temperature: false,
|
||||||
|
toolcall: true,
|
||||||
|
input: {
|
||||||
|
text: true,
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
text: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bundledSnapshot,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
canonicalModelID: "gpt-5.4",
|
||||||
|
reasoning: true,
|
||||||
|
supportsThinking: true,
|
||||||
|
supportsTemperature: false,
|
||||||
|
toolCall: true,
|
||||||
|
modalities: {
|
||||||
|
input: ["text", "image"],
|
||||||
|
output: ["text"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("accepts runtime variant arrays without corrupting them into numeric keys", () => {
|
||||||
|
const result = getModelCapabilities({
|
||||||
|
providerID: "openai",
|
||||||
|
modelID: "gpt-5.4",
|
||||||
|
runtimeModel: {
|
||||||
|
variants: ["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
|
bundledSnapshot,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.variants).toEqual(["low", "medium", "high", "xhigh"])
|
||||||
|
})
|
||||||
|
|
||||||
test("normalizes thinking suffix aliases before snapshot lookup", () => {
|
test("normalizes thinking suffix aliases before snapshot lookup", () => {
|
||||||
const result = getModelCapabilities({
|
const result = getModelCapabilities({
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
@@ -156,4 +203,19 @@ describe("getModelCapabilities", () => {
|
|||||||
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("detects prefixed o-series model IDs through the heuristic fallback", () => {
|
||||||
|
const result = getModelCapabilities({
|
||||||
|
providerID: "azure-openai",
|
||||||
|
modelID: "openai/o3-mini",
|
||||||
|
bundledSnapshot,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
canonicalModelID: "openai/o3-mini",
|
||||||
|
family: "openai-reasoning",
|
||||||
|
variants: ["low", "medium", "high"],
|
||||||
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const MODEL_ID_OVERRIDES: Record<string, ModelCapabilityOverride> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return typeof value === "object" && value !== null
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLookupModelID(modelID: string): string {
|
function normalizeLookupModelID(modelID: string): string {
|
||||||
@@ -97,6 +97,11 @@ function readStringArray(value: unknown): string[] | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeVariantKeys(value: unknown): string[] | undefined {
|
function normalizeVariantKeys(value: unknown): string[] | undefined {
|
||||||
|
const arrayVariants = readStringArray(value)
|
||||||
|
if (arrayVariants) {
|
||||||
|
return arrayVariants.map((variant) => variant.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRecord(value)) {
|
if (!isRecord(value)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -105,13 +110,30 @@ function normalizeVariantKeys(value: unknown): string[] | undefined {
|
|||||||
return variants.length > 0 ? variants : undefined
|
return variants.length > 0 ? variants : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readModalityKeys(value: unknown): string[] | undefined {
|
||||||
|
const stringArray = readStringArray(value)
|
||||||
|
if (stringArray) {
|
||||||
|
return stringArray.map((entry) => entry.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled = Object.entries(value)
|
||||||
|
.filter(([, supported]) => supported === true)
|
||||||
|
.map(([modality]) => modality.toLowerCase())
|
||||||
|
|
||||||
|
return enabled.length > 0 ? enabled : undefined
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeModalities(value: unknown): ModelCapabilities["modalities"] | undefined {
|
function normalizeModalities(value: unknown): ModelCapabilities["modalities"] | undefined {
|
||||||
if (!isRecord(value)) {
|
if (!isRecord(value)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = readStringArray(value.input)
|
const input = readModalityKeys(value.input)
|
||||||
const output = readStringArray(value.output)
|
const output = readModalityKeys(value.output)
|
||||||
|
|
||||||
if (!input && !output) {
|
if (!input && !output) {
|
||||||
return undefined
|
return undefined
|
||||||
@@ -145,12 +167,18 @@ function getOverride(modelID: string): ModelCapabilityOverride | undefined {
|
|||||||
return MODEL_ID_OVERRIDES[normalizeLookupModelID(modelID)]
|
return MODEL_ID_OVERRIDES[normalizeLookupModelID(modelID)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelCapabilities(runtimeModel: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
|
||||||
|
return isRecord(runtimeModel?.capabilities) ? runtimeModel.capabilities : undefined
|
||||||
|
}
|
||||||
|
|
||||||
function readRuntimeModelLimitOutput(runtimeModel: Record<string, unknown> | undefined): number | undefined {
|
function readRuntimeModelLimitOutput(runtimeModel: Record<string, unknown> | undefined): number | undefined {
|
||||||
if (!runtimeModel) {
|
if (!runtimeModel) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = runtimeModel.limit
|
const limit = isRecord(runtimeModel.limit)
|
||||||
|
? runtimeModel.limit
|
||||||
|
: readRuntimeModelCapabilities(runtimeModel)?.limit
|
||||||
if (!isRecord(limit)) {
|
if (!isRecord(limit)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -163,11 +191,101 @@ function readRuntimeModelBoolean(runtimeModel: Record<string, unknown> | undefin
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = runtimeModel[key]
|
const value = runtimeModel[key]
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === "boolean") {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const capabilityValue = runtimeCapabilities?.[key]
|
||||||
|
if (typeof capabilityValue === "boolean") {
|
||||||
|
return capabilityValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelModalities(runtimeModel: Record<string, unknown> | undefined): ModelCapabilities["modalities"] | undefined {
|
||||||
|
if (!runtimeModel) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootModalities = normalizeModalities(runtimeModel.modalities)
|
||||||
|
if (rootModalities) {
|
||||||
|
return rootModalities
|
||||||
|
}
|
||||||
|
|
||||||
|
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
|
||||||
|
if (!runtimeCapabilities) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestedModalities = normalizeModalities(runtimeCapabilities.modalities)
|
||||||
|
if (nestedModalities) {
|
||||||
|
return nestedModalities
|
||||||
|
}
|
||||||
|
|
||||||
|
const capabilityModalities = normalizeModalities(runtimeCapabilities)
|
||||||
|
if (capabilityModalities) {
|
||||||
|
return capabilityModalities
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelVariants(runtimeModel: Record<string, unknown> | undefined): string[] | undefined {
|
||||||
|
if (!runtimeModel) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootVariants = normalizeVariantKeys(runtimeModel.variants)
|
||||||
|
if (rootVariants) {
|
||||||
|
return rootVariants
|
||||||
|
}
|
||||||
|
|
||||||
|
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
|
||||||
|
if (!runtimeCapabilities) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeVariantKeys(runtimeCapabilities.variants)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelTopPSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
|
||||||
|
return readRuntimeModelBoolean(runtimeModel, ["topP", "top_p"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelToolCallSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
|
||||||
|
return readRuntimeModelBoolean(runtimeModel, ["toolCall", "tool_call", "toolcall"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelReasoningSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
|
||||||
|
return readRuntimeModelBoolean(runtimeModel, ["reasoning"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelTemperatureSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
|
||||||
|
return readRuntimeModelBoolean(runtimeModel, ["temperature"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRuntimeModelThinkingSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
|
||||||
|
const capabilityValue = readRuntimeModelReasoningSupport(runtimeModel)
|
||||||
|
if (capabilityValue !== undefined) {
|
||||||
|
return capabilityValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
|
||||||
|
if (!runtimeCapabilities) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of ["thinking", "supportsThinking"] as const) {
|
||||||
|
const value = runtimeCapabilities[key]
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
@@ -194,7 +312,7 @@ export function getModelCapabilities(input: GetModelCapabilitiesInput): ModelCap
|
|||||||
const bundledSnapshot = input.bundledSnapshot ?? bundledModelCapabilitiesSnapshot
|
const bundledSnapshot = input.bundledSnapshot ?? bundledModelCapabilitiesSnapshot
|
||||||
const snapshotEntry = runtimeSnapshot?.models?.[canonicalModelID] ?? bundledSnapshot.models[canonicalModelID]
|
const snapshotEntry = runtimeSnapshot?.models?.[canonicalModelID] ?? bundledSnapshot.models[canonicalModelID]
|
||||||
const heuristicFamily = detectHeuristicModelFamily(canonicalModelID)
|
const heuristicFamily = detectHeuristicModelFamily(canonicalModelID)
|
||||||
const runtimeVariants = normalizeVariantKeys(runtimeModel?.variants)
|
const runtimeVariants = readRuntimeModelVariants(runtimeModel)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requestedModelID,
|
requestedModelID,
|
||||||
@@ -202,27 +320,27 @@ export function getModelCapabilities(input: GetModelCapabilitiesInput): ModelCap
|
|||||||
family: snapshotEntry?.family ?? heuristicFamily?.family,
|
family: snapshotEntry?.family ?? heuristicFamily?.family,
|
||||||
variants: runtimeVariants ?? override?.variants ?? heuristicFamily?.variants,
|
variants: runtimeVariants ?? override?.variants ?? heuristicFamily?.variants,
|
||||||
reasoningEfforts: override?.reasoningEfforts ?? heuristicFamily?.reasoningEfforts,
|
reasoningEfforts: override?.reasoningEfforts ?? heuristicFamily?.reasoningEfforts,
|
||||||
reasoning: readRuntimeModelBoolean(runtimeModel, ["reasoning"]) ?? snapshotEntry?.reasoning,
|
reasoning: readRuntimeModelReasoningSupport(runtimeModel) ?? snapshotEntry?.reasoning,
|
||||||
supportsThinking:
|
supportsThinking:
|
||||||
override?.supportsThinking
|
override?.supportsThinking
|
||||||
?? heuristicFamily?.supportsThinking
|
?? heuristicFamily?.supportsThinking
|
||||||
?? readRuntimeModelBoolean(runtimeModel, ["reasoning"])
|
?? readRuntimeModelThinkingSupport(runtimeModel)
|
||||||
?? snapshotEntry?.reasoning,
|
?? snapshotEntry?.reasoning,
|
||||||
supportsTemperature:
|
supportsTemperature:
|
||||||
readRuntimeModelBoolean(runtimeModel, ["temperature"])
|
readRuntimeModelTemperatureSupport(runtimeModel)
|
||||||
?? override?.supportsTemperature
|
?? override?.supportsTemperature
|
||||||
?? snapshotEntry?.temperature,
|
?? snapshotEntry?.temperature,
|
||||||
supportsTopP:
|
supportsTopP:
|
||||||
readRuntimeModelBoolean(runtimeModel, ["topP", "top_p"])
|
readRuntimeModelTopPSupport(runtimeModel)
|
||||||
?? override?.supportsTopP,
|
?? override?.supportsTopP,
|
||||||
maxOutputTokens:
|
maxOutputTokens:
|
||||||
readRuntimeModelLimitOutput(runtimeModel)
|
readRuntimeModelLimitOutput(runtimeModel)
|
||||||
?? snapshotEntry?.limit?.output,
|
?? snapshotEntry?.limit?.output,
|
||||||
toolCall:
|
toolCall:
|
||||||
readRuntimeModelBoolean(runtimeModel, ["toolCall", "tool_call"])
|
readRuntimeModelToolCallSupport(runtimeModel)
|
||||||
?? snapshotEntry?.toolCall,
|
?? snapshotEntry?.toolCall,
|
||||||
modalities:
|
modalities:
|
||||||
normalizeModalities(runtimeModel?.modalities)
|
readRuntimeModelModalities(runtimeModel)
|
||||||
?? snapshotEntry?.modalities,
|
?? snapshotEntry?.modalities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ export const HEURISTIC_MODEL_FAMILY_REGISTRY: ReadonlyArray<HeuristicModelFamily
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
family: "openai-reasoning",
|
family: "openai-reasoning",
|
||||||
pattern: /^o\d(?:$|-)/,
|
pattern: /(?:^|\/)o\d(?:$|-)/,
|
||||||
variants: ["low", "medium", "high"],
|
variants: ["low", "medium", "high"],
|
||||||
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
family: "gpt-5",
|
family: "gpt-5",
|
||||||
includes: ["gpt-5"],
|
includes: ["gpt-5"],
|
||||||
variants: ["low", "medium", "high", "xhigh", "max"],
|
variants: ["low", "medium", "high", "xhigh"],
|
||||||
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"],
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -324,6 +324,27 @@ describe("resolveCompatibleModelSettings", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("GPT-5 downgrades unsupported max variant to xhigh", () => {
|
||||||
|
const result = resolveCompatibleModelSettings({
|
||||||
|
providerID: "openai",
|
||||||
|
modelID: "gpt-5.4",
|
||||||
|
desired: { variant: "max" },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
variant: "xhigh",
|
||||||
|
reasoningEffort: undefined,
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
field: "variant",
|
||||||
|
from: "max",
|
||||||
|
to: "xhigh",
|
||||||
|
reason: "unsupported-by-model-family",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Reasoning effort: "none" and "minimal" are valid per Vercel AI SDK
|
// Reasoning effort: "none" and "minimal" are valid per Vercel AI SDK
|
||||||
test("GPT-5 keeps none reasoningEffort", () => {
|
test("GPT-5 keeps none reasoningEffort", () => {
|
||||||
const result = resolveCompatibleModelSettings({
|
const result = resolveCompatibleModelSettings({
|
||||||
|
|||||||
Reference in New Issue
Block a user