diff --git a/src/onboard.ts b/src/onboard.ts index d3faa9d..08bd825 100644 --- a/src/onboard.ts +++ b/src/onboard.ts @@ -462,7 +462,9 @@ async function stepModel(config: OnboardConfig, env: Record): Pr } spinner.start('Fetching models...'); - const modelOptions = await buildModelOptions({ billingTier, isSelfHosted }); + // Pass connected provider IDs so we show models from those providers + const connectedProviders = config.providers?.map(p => p.id) || []; + const modelOptions = await buildModelOptions({ billingTier, isSelfHosted, connectedProviders }); spinner.stop('Models loaded'); // Show appropriate message for free tier diff --git a/src/utils/model-selection.ts b/src/utils/model-selection.ts index 3a72330..cb6ea86 100644 --- a/src/utils/model-selection.ts +++ b/src/utils/model-selection.ts @@ -73,20 +73,32 @@ export function getDefaultModelForTier(billingTier?: string | null): string { return defaultModel?.handle ?? models[0]?.handle ?? 'anthropic/claude-sonnet-4-5-20250929'; } +// Map provider IDs to model handle prefixes +const PROVIDER_TO_MODEL_PREFIX: Record = { + 'anthropic': ['anthropic/'], + 'openai': ['openai/'], + 'gemini': ['google_ai/'], + 'zai': ['zai/'], + 'minimax': ['minimax/'], + 'openrouter': ['openrouter/'], +}; + /** - * Build model selection options based on billing tier + * Build model selection options based on billing tier and connected providers * Returns array ready for @clack/prompts select() * - * For free users: Show free models first, then BYOK option + * For free users: Show free models first, then connected BYOK models * For paid users: Show featured models first, then all models * For self-hosted: Fetch models from server */ export async function buildModelOptions(options?: { billingTier?: string | null; isSelfHosted?: boolean; + connectedProviders?: string[]; // Provider IDs like 'anthropic', 'minimax' }): Promise> { const billingTier = options?.billingTier; const isSelfHosted = options?.isSelfHosted; + const connectedProviders = options?.connectedProviders || []; const isFreeTier = billingTier?.toLowerCase() === 'free'; // For self-hosted servers, fetch models from server @@ -96,6 +108,14 @@ export async function buildModelOptions(options?: { const result: Array<{ value: string; label: string; hint: string }> = []; + // Helper to check if a model is from a connected provider + const isFromConnectedProvider = (handle: string) => { + return connectedProviders.some(providerId => { + const prefixes = PROVIDER_TO_MODEL_PREFIX[providerId] || []; + return prefixes.some(prefix => handle.startsWith(prefix)); + }); + }; + if (isFreeTier) { // Free tier: Show free models first const freeModels = models.filter(m => m.free); @@ -105,20 +125,22 @@ export async function buildModelOptions(options?: { hint: `🆓 Free - ${m.description}`, }))); - // Add BYOK header and options - result.push({ - value: '__byok_header__', - label: '── BYOK (Bring Your Own Key) ──', - hint: 'Connect your own API keys', - }); - - // Show featured non-free models as BYOK options - const byokModels = models.filter(m => m.isFeatured && !m.free); - result.push(...byokModels.map(m => ({ - value: m.handle, - label: m.label, - hint: `🔑 BYOK - ${m.description}`, - }))); + // If user has connected providers, show their models + if (connectedProviders.length > 0) { + result.push({ + value: '__byok_header__', + label: '── Your Connected Providers ──', + hint: 'Models from your API keys', + }); + + // Show models from connected providers only + const connectedModels = models.filter(m => !m.free && isFromConnectedProvider(m.handle)); + result.push(...connectedModels.map(m => ({ + value: m.handle, + label: m.label, + hint: `🔑 ${m.description}`, + }))); + } } else { // Paid tier: Show featured models first const featured = models.filter(m => m.isFeatured);