2026-01-22 22:43:27 +09:00
import { log } from "./logger"
import { fuzzyMatchModel } from "./model-availability"
import type { FallbackEntry } from "./model-requirements"
2026-01-26 11:53:41 +09:00
import { readConnectedProvidersCache } from "./connected-providers-cache"
2026-01-22 22:43:27 +09:00
2026-01-17 12:51:03 -05:00
export type ModelResolutionInput = {
2026-01-22 22:43:27 +09:00
userModel? : string
inheritedModel? : string
systemDefault : string
}
export type ModelSource =
| "override"
| "provider-fallback"
| "system-default"
export type ModelResolutionResult = {
model : string
source : ModelSource
2026-01-23 02:20:32 +09:00
variant? : string
2026-01-22 22:43:27 +09:00
}
export type ExtendedModelResolutionInput = {
userModel? : string
fallbackChain? : FallbackEntry [ ]
availableModels : Set < string >
systemDefaultModel : string
}
2026-01-17 12:51:03 -05:00
function normalizeModel ( model? : string ) : string | undefined {
2026-01-22 22:43:27 +09:00
const trimmed = model ? . trim ( )
return trimmed || undefined
2026-01-17 12:51:03 -05:00
}
export function resolveModel ( input : ModelResolutionInput ) : string {
2026-01-22 22:43:27 +09:00
return (
normalizeModel ( input . userModel ) ? ?
normalizeModel ( input . inheritedModel ) ? ?
input . systemDefault
)
}
export function resolveModelWithFallback (
input : ExtendedModelResolutionInput ,
) : ModelResolutionResult {
const { userModel , fallbackChain , availableModels , systemDefaultModel } = input
// Step 1: Override
const normalizedUserModel = normalizeModel ( userModel )
if ( normalizedUserModel ) {
log ( "Model resolved via override" , { model : normalizedUserModel } )
return { model : normalizedUserModel , source : "override" }
}
// Step 2: Provider fallback chain (with availability check)
if ( fallbackChain && fallbackChain . length > 0 ) {
2026-01-23 15:38:54 +09:00
if ( availableModels . size === 0 ) {
2026-01-26 11:53:41 +09:00
const connectedProviders = readConnectedProvidersCache ( )
const connectedSet = connectedProviders ? new Set ( connectedProviders ) : null
for ( const entry of fallbackChain ) {
for ( const provider of entry . providers ) {
if ( connectedSet === null || connectedSet . has ( provider ) ) {
const model = ` ${ provider } / ${ entry . model } `
log ( "Model resolved via fallback chain (no model cache, using connected provider)" , {
provider ,
model : entry.model ,
variant : entry.variant ,
hasConnectedCache : connectedSet !== null
} )
return { model , source : "provider-fallback" , variant : entry.variant }
}
}
}
2026-01-23 15:38:54 +09:00
const firstEntry = fallbackChain [ 0 ]
const firstProvider = firstEntry . providers [ 0 ]
const model = ` ${ firstProvider } / ${ firstEntry . model } `
2026-01-26 11:53:41 +09:00
log ( "Model resolved via fallback chain (no cache at all, using first entry)" , { provider : firstProvider , model : firstEntry.model , variant : firstEntry.variant } )
2026-01-23 15:38:54 +09:00
return { model , source : "provider-fallback" , variant : firstEntry.variant }
}
2026-01-22 22:43:27 +09:00
for ( const entry of fallbackChain ) {
for ( const provider of entry . providers ) {
const fullModel = ` ${ provider } / ${ entry . model } `
const match = fuzzyMatchModel ( fullModel , availableModels , [ provider ] )
if ( match ) {
2026-01-23 02:20:32 +09:00
log ( "Model resolved via fallback chain (availability confirmed)" , { provider , model : entry.model , match , variant : entry.variant } )
return { model : match , source : "provider-fallback" , variant : entry.variant }
2026-01-22 22:43:27 +09:00
}
}
}
2026-01-23 10:55:42 +09:00
log ( "No available model found in fallback chain, falling through to system default" )
2026-01-22 22:43:27 +09:00
}
// Step 4: System default
log ( "Model resolved via system default" , { model : systemDefaultModel } )
return { model : systemDefaultModel , source : "system-default" }
2026-01-17 12:51:03 -05:00
}