Fetch BYOK models from API instead of static list

This commit is contained in:
Sarah Wooders
2026-01-28 22:20:35 -08:00
parent e6038faf96
commit 77c8eb5b1e
2 changed files with 51 additions and 16 deletions

View File

@@ -462,7 +462,8 @@ async function stepModel(config: OnboardConfig, env: Record<string, string>): Pr
}
spinner.start('Fetching models...');
const modelOptions = await buildModelOptions({ billingTier, isSelfHosted });
const apiKey = config.apiKey || env.LETTA_API_KEY || process.env.LETTA_API_KEY;
const modelOptions = await buildModelOptions({ billingTier, isSelfHosted, apiKey });
spinner.stop('Models loaded');
// Show appropriate message for free tier

View File

@@ -73,17 +73,50 @@ export function getDefaultModelForTier(billingTier?: string | null): string {
return defaultModel?.handle ?? models[0]?.handle ?? 'anthropic/claude-sonnet-4-5-20250929';
}
interface ByokModel {
handle: string;
name: string;
display_name?: string;
provider_name: string;
provider_type: string;
}
/**
* Fetch BYOK models from Letta API
*/
async function fetchByokModels(apiKey?: string): Promise<ByokModel[]> {
try {
const key = apiKey || process.env.LETTA_API_KEY;
if (!key) return [];
const response = await fetch('https://api.letta.com/v1/models?provider_category=byok', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${key}`,
},
});
if (!response.ok) return [];
const models = await response.json() as ByokModel[];
return models;
} catch {
return [];
}
}
/**
* Build model selection options based on billing tier
* Returns array ready for @clack/prompts select()
*
* For free users: Show free models first, then all BYOK models
* For free users: Show free models first, then BYOK models from API
* 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;
apiKey?: string;
}): Promise<Array<{ value: string; label: string; hint: string }>> {
const billingTier = options?.billingTier;
const isSelfHosted = options?.isSelfHosted;
@@ -105,20 +138,21 @@ export async function buildModelOptions(options?: {
hint: `🆓 Free - ${m.description}`,
})));
// Show all BYOK models
result.push({
value: '__byok_header__',
label: '── BYOK Models ──',
hint: 'Requires provider API key',
});
// Show all non-free models as BYOK options
const byokModels = models.filter(m => !m.free);
result.push(...byokModels.map(m => ({
value: m.handle,
label: m.label,
hint: `🔑 ${m.description}`,
})));
// Fetch BYOK models from API
const byokModels = await fetchByokModels(options?.apiKey);
if (byokModels.length > 0) {
result.push({
value: '__byok_header__',
label: '── Your Connected Providers ──',
hint: 'Models from your API keys',
});
result.push(...byokModels.map(m => ({
value: m.handle,
label: m.display_name || m.name,
hint: `🔑 ${m.provider_name}`,
})));
}
} else {
// Paid tier: Show featured models first
const featured = models.filter(m => m.isFeatured);