* fix: defer model validation until after extensions register (#2626) Extension-provided models (e.g. claude-code/claude-sonnet-4-6) were silently overwritten on every startup because the model validation ran before createAgentSession(), which is where extensions register their models in the ModelRegistry. At validation time, extension models did not exist in the registry, so the user's valid choice was replaced with a built-in fallback. Extract validation into validateConfiguredModel() and call it after createAgentSession() in both print-mode and interactive-mode paths. Closes #2626 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: align MinimalSettingsManager interface with SettingsManager The MinimalSettingsManager interface used `string` for thinking level types, but SettingsManager uses a specific union type and returns `undefined`. This caused TS2345 at cli.ts lines 448 and 587. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
/**
|
|
* Startup model validation — extracted from cli.ts so it can be called
|
|
* AFTER extensions register their models in the ModelRegistry.
|
|
*
|
|
* Before this extraction (bug #2626), the validation ran before
|
|
* createAgentSession(), meaning extension-provided models (e.g.
|
|
* claude-code/claude-sonnet-4-6) were not yet in the registry.
|
|
* configuredExists was always false for extension models, causing the
|
|
* user's valid choice to be silently overwritten with a built-in fallback.
|
|
*/
|
|
|
|
import { getPiDefaultModelAndProvider } from './pi-migration.js'
|
|
|
|
interface MinimalModel {
|
|
provider: string
|
|
id: string
|
|
}
|
|
|
|
interface MinimalModelRegistry {
|
|
getAll(): MinimalModel[]
|
|
getAvailable(): MinimalModel[]
|
|
}
|
|
|
|
type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
|
|
interface MinimalSettingsManager {
|
|
getDefaultProvider(): string | undefined
|
|
getDefaultModel(): string | undefined
|
|
getDefaultThinkingLevel(): ThinkingLevel | undefined
|
|
setDefaultModelAndProvider(provider: string, modelId: string): void
|
|
setDefaultThinkingLevel(level: ThinkingLevel): void
|
|
}
|
|
|
|
/**
|
|
* Validate the configured default model against the registry.
|
|
*
|
|
* If the configured model exists in the registry, this is a no-op — the
|
|
* user's choice is preserved. If it does not exist (stale settings from a
|
|
* prior install, or genuinely removed model), a fallback is selected and
|
|
* written to settings.
|
|
*
|
|
* IMPORTANT: Call this AFTER createAgentSession() so that extension-
|
|
* provided models have been registered in the ModelRegistry.
|
|
*/
|
|
export function validateConfiguredModel(
|
|
modelRegistry: MinimalModelRegistry,
|
|
settingsManager: MinimalSettingsManager,
|
|
): void {
|
|
const configuredProvider = settingsManager.getDefaultProvider()
|
|
const configuredModel = settingsManager.getDefaultModel()
|
|
const allModels = modelRegistry.getAll()
|
|
const availableModels = modelRegistry.getAvailable()
|
|
const configuredExists = configuredProvider && configuredModel &&
|
|
allModels.some((m) => m.provider === configuredProvider && m.id === configuredModel)
|
|
|
|
if (!configuredModel || !configuredExists) {
|
|
// Model not configured at all, or removed from registry — pick a fallback.
|
|
// Only fires when the model is genuinely unknown (not just temporarily unavailable).
|
|
const piDefault = getPiDefaultModelAndProvider()
|
|
const preferred =
|
|
(piDefault
|
|
? availableModels.find((m) => m.provider === piDefault.provider && m.id === piDefault.model)
|
|
: undefined) ||
|
|
availableModels.find((m) => m.provider === 'openai' && m.id === 'gpt-5.4') ||
|
|
availableModels.find((m) => m.provider === 'openai') ||
|
|
availableModels.find((m) => m.provider === 'anthropic' && m.id === 'claude-opus-4-6') ||
|
|
availableModels.find((m) => m.provider === 'anthropic' && m.id.includes('opus')) ||
|
|
availableModels.find((m) => m.provider === 'anthropic') ||
|
|
availableModels[0]
|
|
if (preferred) {
|
|
settingsManager.setDefaultModelAndProvider(preferred.provider, preferred.id)
|
|
}
|
|
}
|
|
|
|
if (settingsManager.getDefaultThinkingLevel() !== 'off' && !configuredExists) {
|
|
settingsManager.setDefaultThinkingLevel('off')
|
|
}
|
|
}
|