Files
oh-my-opencode/src/shared/model-resolver.ts
T
justsisyphus afbdf69037 fix(model-resolver): use first fallback entry when model cache unavailable
When availableModels is empty (no cache in CI), use the first entry
from fallbackChain directly instead of falling back to systemDefault.
This ensures categories and agents use their configured models even
when the model cache file doesn't exist.

Fixes:
- model-resolution check returning 'warn' instead of 'pass' in CI
- DEFAULT_CATEGORIES not being used when no cache available
- Unstable agent detection failing (models falling back to non-gemini)
2026-01-23 15:39:07 +09:00

83 lines
2.7 KiB
TypeScript

import { log } from "./logger"
import { fuzzyMatchModel } from "./model-availability"
import type { FallbackEntry } from "./model-requirements"
export type ModelResolutionInput = {
userModel?: string
inheritedModel?: string
systemDefault: string
}
export type ModelSource =
| "override"
| "provider-fallback"
| "system-default"
export type ModelResolutionResult = {
model: string
source: ModelSource
variant?: string
}
export type ExtendedModelResolutionInput = {
userModel?: string
fallbackChain?: FallbackEntry[]
availableModels: Set<string>
systemDefaultModel: string
}
function normalizeModel(model?: string): string | undefined {
const trimmed = model?.trim()
return trimmed || undefined
}
export function resolveModel(input: ModelResolutionInput): string {
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) {
// If availableModels is empty (no cache), use first fallback entry directly without availability check
if (availableModels.size === 0) {
const firstEntry = fallbackChain[0]
const firstProvider = firstEntry.providers[0]
const model = `${firstProvider}/${firstEntry.model}`
log("Model resolved via fallback chain (no cache, using first entry)", { provider: firstProvider, model: firstEntry.model, variant: firstEntry.variant })
return { model, source: "provider-fallback", variant: firstEntry.variant }
}
for (const entry of fallbackChain) {
for (const provider of entry.providers) {
const fullModel = `${provider}/${entry.model}`
const match = fuzzyMatchModel(fullModel, availableModels, [provider])
if (match) {
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 }
}
}
}
// No match found in fallback chain - fall through to system default
log("No available model found in fallback chain, falling through to system default")
}
// Step 4: System default
log("Model resolved via system default", { model: systemDefaultModel })
return { model: systemDefaultModel, source: "system-default" }
}