diff --git a/src/cli/src/commands/status.ts b/src/cli/src/commands/status.ts index 2981786..f3bb39d 100644 --- a/src/cli/src/commands/status.ts +++ b/src/cli/src/commands/status.ts @@ -17,6 +17,7 @@ const CLEAR_LINE = '\x1b[2K\r'; interface ProvidersInfo { providers: string[]; tiers: { fast: string[]; heavy: string[] }; + health: Record; } export interface StatusCommandDeps { @@ -234,13 +235,17 @@ export function createStatusCommand(deps?: Partial): Command clearInterval(interval); if (providersInfo && (providersInfo.tiers.fast.length > 0 || providersInfo.tiers.heavy.length > 0)) { - // Tiered display + // Tiered display with per-provider health write(`${CLEAR_LINE}`); - if (providersInfo.tiers.fast.length > 0) { - log(`LLM (fast): ${providersInfo.tiers.fast.join(', ')} ${GREEN}✓${RESET}`); - } - if (providersInfo.tiers.heavy.length > 0) { - log(`LLM (heavy): ${providersInfo.tiers.heavy.join(', ')} ${GREEN}✓${RESET}`); + for (const tier of ['fast', 'heavy'] as const) { + const names = providersInfo.tiers[tier]; + if (names.length === 0) continue; + const label = tier === 'fast' ? 'LLM (fast): ' : 'LLM (heavy):'; + const parts = names.map((n) => { + const ok = providersInfo.health[n]; + return ok ? `${n} ${GREEN}✓${RESET}` : `${n} ${RED}✗${RESET}`; + }); + log(`${label} ${parts.join(', ')}`); } } else { // Legacy single provider display @@ -258,11 +263,15 @@ export function createStatusCommand(deps?: Partial): Command const [llmStatus, models, providersInfo] = await Promise.all([llmPromise, modelsPromise, providersPromise]); if (providersInfo && (providersInfo.tiers.fast.length > 0 || providersInfo.tiers.heavy.length > 0)) { - if (providersInfo.tiers.fast.length > 0) { - log(`LLM (fast): ${providersInfo.tiers.fast.join(', ')}`); - } - if (providersInfo.tiers.heavy.length > 0) { - log(`LLM (heavy): ${providersInfo.tiers.heavy.join(', ')}`); + for (const tier of ['fast', 'heavy'] as const) { + const names = providersInfo.tiers[tier]; + if (names.length === 0) continue; + const label = tier === 'fast' ? 'LLM (fast): ' : 'LLM (heavy):'; + const parts = names.map((n) => { + const ok = providersInfo.health[n]; + return ok ? `${n} ✓` : `${n} ✗`; + }); + log(`${label} ${parts.join(', ')}`); } } else { if (llmStatus === 'ok' || llmStatus === 'ok (key stored)') { diff --git a/src/mcplocal/src/http/server.ts b/src/mcplocal/src/http/server.ts index a296ecf..a9e29c6 100644 --- a/src/mcplocal/src/http/server.ts +++ b/src/mcplocal/src/http/server.ts @@ -140,19 +140,40 @@ export async function createHttpServer( } }); - // LLM providers — list all registered providers with tier assignments + // LLM providers — list all registered providers with tier assignments and health app.get('/llm/providers', async (_request, reply) => { const registry = deps.providerRegistry; if (!registry) { - reply.code(200).send({ providers: [], tiers: { fast: [], heavy: [] } }); + reply.code(200).send({ providers: [], tiers: { fast: [], heavy: [] }, health: {} }); return; } + + // Run isAvailable() on all providers in parallel (lightweight, no tokens burned) + const names = registry.list(); + const healthChecks = await Promise.all( + names.map(async (name) => { + const provider = registry.get(name); + if (!provider) return { name, available: false }; + try { + const available = await provider.isAvailable(); + return { name, available }; + } catch { + return { name, available: false }; + } + }), + ); + const health: Record = {}; + for (const check of healthChecks) { + health[check.name] = check.available; + } + reply.code(200).send({ - providers: registry.list(), + providers: names, tiers: { fast: registry.getTierProviders('fast'), heavy: registry.getTierProviders('heavy'), }, + health, }); });