Skip to main content
Glama
prompts.ts6.18 kB
/** * @fileoverview Interactive prompt logic for model selection */ import search, { Separator } from '@inquirer/search'; import chalk from 'chalk'; import { getAvailableModels } from '../../lib/model-management.js'; import { getCustomProviderOptions } from './custom-providers.js'; import type { CurrentModels, ModelChoice, ModelInfo, ModelRole, PromptData } from './types.js'; /** * Build prompt choices for a specific role */ export function buildPromptChoices( role: ModelRole, currentModels: CurrentModels, allowNone = false ): PromptData { const currentModel = currentModels[role]; const allModels = getAvailableModels(); // Group models by provider (filter out models without provider) const modelsByProvider = allModels .filter( (model): model is ModelInfo & { provider: string } => !!model.provider ) .reduce( (acc, model) => { if (!acc[model.provider]) { acc[model.provider] = []; } acc[model.provider].push(model); return acc; }, {} as Record<string, ModelInfo[]> ); // System options (cancel and no change) const systemOptions: ModelChoice[] = []; const cancelOption: ModelChoice = { name: '⏹ Cancel Model Setup', value: '__CANCEL__', short: 'Cancel' }; const noChangeOption: ModelChoice | null = currentModel?.modelId && currentModel?.provider ? { name: `✔ No change to current ${role} model (${currentModel.provider}/${currentModel.modelId})`, value: '__NO_CHANGE__', short: 'No change' } : null; if (noChangeOption) { systemOptions.push(noChangeOption); } systemOptions.push(cancelOption); // Build role-specific model choices const roleChoices: ModelChoice[] = Object.entries(modelsByProvider) .flatMap(([provider, models]) => { return models .filter((m) => m.allowed_roles && m.allowed_roles.includes(role)) .map((m) => { // Use model name if available, otherwise fall back to model ID const displayName = m.name || m.id; return { name: `${provider} / ${displayName} ${ m.cost_per_1m_tokens ? chalk.gray( `($${m.cost_per_1m_tokens.input.toFixed(2)} input | $${m.cost_per_1m_tokens.output.toFixed(2)} output)` ) : '' }`, value: { id: m.id, provider }, short: `${provider}/${displayName}` }; }); }) .filter((choice) => choice !== null); // Find current model index let currentChoiceIndex = -1; if (currentModel?.modelId && currentModel?.provider) { currentChoiceIndex = roleChoices.findIndex( (choice) => typeof choice.value === 'object' && choice.value !== null && 'id' in choice.value && choice.value.id === currentModel.modelId && choice.value.provider === currentModel.provider ); } // Get custom provider options const customProviderOptions = getCustomProviderOptions(); // Build final choices array const systemLength = systemOptions.length; let choices: (ModelChoice | Separator)[]; let defaultIndex: number; if (allowNone) { choices = [ ...systemOptions, new Separator('\n── Standard Models ──'), { name: '⚪ None (disable)', value: null, short: 'None' }, ...roleChoices, new Separator('\n── Custom Providers ──'), ...customProviderOptions ]; const noneOptionIndex = systemLength + 1; defaultIndex = currentChoiceIndex !== -1 ? currentChoiceIndex + systemLength + 2 : noneOptionIndex; } else { choices = [ ...systemOptions, new Separator('\n── Standard Models ──'), ...roleChoices, new Separator('\n── Custom Providers ──'), ...customProviderOptions ]; defaultIndex = currentChoiceIndex !== -1 ? currentChoiceIndex + systemLength + 1 : noChangeOption ? 1 : 0; } // Ensure defaultIndex is valid if (defaultIndex < 0 || defaultIndex >= choices.length) { defaultIndex = 0; console.warn( `Warning: Could not determine default model for role '${role}'. Defaulting to 'Cancel'.` ); } return { choices, default: defaultIndex }; } /** * Create search source for inquirer search prompt */ export function createSearchSource( choices: (ModelChoice | Separator)[], _defaultValue: number ) { return (searchTerm = '') => { const filteredChoices = choices.filter((choice) => { // Separators are always included if (choice instanceof Separator) return true; // Filter regular choices by search term (name and model ID) const mc = choice as ModelChoice; const displayText = mc.name || ''; const modelId = typeof mc.value === 'object' && mc.value !== null && 'id' in mc.value ? mc.value.id : ''; const searchText = `${displayText} ${modelId}`.toLowerCase(); return searchText.includes(searchTerm.toLowerCase()); }); // Map ModelChoice to the format inquirer expects return Promise.resolve( filteredChoices.map((choice) => { if (choice instanceof Separator) return choice; const mc = choice as ModelChoice; return { name: mc.name, value: mc.value, short: mc.short }; }) ); }; } /** * Display introductory message for interactive setup */ export function displaySetupIntro(): void { console.log(chalk.cyan('\n🎯 Interactive Model Setup')); console.log(chalk.gray('━'.repeat(50))); console.log(chalk.yellow('💡 Navigation tips:')); console.log(chalk.gray(' • Type to search and filter options')); console.log(chalk.gray(' • Use ↑↓ arrow keys to navigate results')); console.log( chalk.gray( ' • Standard models are listed first, custom providers at bottom' ) ); console.log(chalk.gray(' • Press Enter to select\n')); } /** * Prompt user to select a model for a specific role */ export async function promptForModel( role: ModelRole, promptData: PromptData ): Promise<string | { id: string; provider: string } | null> { const roleLabels = { main: 'main model for generation/updates', research: 'research model', fallback: 'fallback model (optional)' }; const answer = await search({ message: `Select the ${roleLabels[role]}:`, source: createSearchSource(promptData.choices, promptData.default), pageSize: 15 }); return answer; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server