diff --git a/packages/pi-coding-agent/src/core/sdk.ts b/packages/pi-coding-agent/src/core/sdk.ts index 12da38d16..1558b10b2 100644 --- a/packages/pi-coding-agent/src/core/sdk.ts +++ b/packages/pi-coding-agent/src/core/sdk.ts @@ -219,6 +219,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} time("resourceLoader.reload"); } + // Flush provider registrations queued during extension loading so that + // extension models (e.g. pi-claude-cli) are visible in the registry before + // findInitialModel() runs. bindCore() repeats this flush as a safety net + // for any late-arriving registrations. + const { runtime: extensionRuntime } = resourceLoader.getExtensions(); + for (const { name, config } of extensionRuntime.pendingProviderRegistrations) { + modelRegistry.registerProvider(name, config); + } + extensionRuntime.pendingProviderRegistrations = []; + // Check if session has existing data to restore const existingSession = sessionManager.buildSessionContext(); const hasExistingSession = existingSession.messages.length > 0; diff --git a/src/cli.ts b/src/cli.ts index b8a348e3e..29b7c804b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -131,6 +131,48 @@ function parseCliArgs(argv: string[]): CliFlags { return flags } +/** + * Validate the configured default model against the registry and reset it if + * it no longer exists. Must run AFTER extensions have registered their + * providers so that extension models (e.g. pi-claude-cli) are visible. + */ +function validateConfiguredModel( + modelRegistry: ModelRegistry, + settingsManager: SettingsManager, +): 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) + const configuredAvailable = configuredProvider && configuredModel && + availableModels.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') + } +} + const cliFlags = parseCliArgs(process.argv) const isPrintMode = cliFlags.print || cliFlags.mode !== undefined @@ -382,8 +424,23 @@ if (!isPrintMode && process.stdout.columns && process.stdout.columns < 40) { ) } -// --list-models: print available models and exit (no TTY needed) +// --list-models: load extensions so that extension-registered providers (e.g. +// pi-claude-cli) appear in the listing, then flush their pending registrations +// into the model registry before printing. if (cliFlags.listModels !== undefined) { + exitIfManagedResourcesAreNewer(agentDir) + initResources(agentDir) + const listModelsLoader = new DefaultResourceLoader({ + agentDir, + additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined, + }) + await listModelsLoader.reload() + const listModelsExtensions = listModelsLoader.getExtensions() + for (const { name, config } of listModelsExtensions.runtime.pendingProviderRegistrations) { + modelRegistry.registerProvider(name, config) + } + listModelsExtensions.runtime.pendingProviderRegistrations = [] + const models = modelRegistry.getAvailable() if (models.length === 0) { console.log('No models available. Set API keys in environment variables.') @@ -532,6 +589,11 @@ if (isPrintMode) { } } + // Validate configured model now that extension providers are registered. + // Must run after createAgentSession() which flushes pendingProviderRegistrations + // so extension models (e.g. pi-claude-cli) are visible in the registry. + validateConfiguredModel(modelRegistry, settingsManager) + // Apply --model override if specified if (cliFlags.model) { const available = modelRegistry.getAvailable() @@ -738,6 +800,11 @@ if (extensionsResult.errors.length > 0) { } } +// Validate configured model now that extension providers are registered. +// Must run after createAgentSession() which flushes pendingProviderRegistrations +// so extension models (e.g. pi-claude-cli) are visible in the registry. +validateConfiguredModel(modelRegistry, settingsManager) + // Restore scoped models from settings on startup. // The upstream InteractiveMode reads enabledModels from settings when /scoped-models is opened, // but doesn't apply them to the session at startup — so Ctrl+P cycles all models instead of