Merge pull request 'fix: per-provider health checks in status display' (#44) from fix/per-provider-health-check into main
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / test (push) Waiting to run
CI / build (push) Blocked by required conditions
CI / package (push) Blocked by required conditions

This commit was merged in pull request #44.
This commit is contained in:
2026-02-25 02:25:28 +00:00
2 changed files with 44 additions and 14 deletions

View File

@@ -17,6 +17,7 @@ const CLEAR_LINE = '\x1b[2K\r';
interface ProvidersInfo {
providers: string[];
tiers: { fast: string[]; heavy: string[] };
health: Record<string, boolean>;
}
export interface StatusCommandDeps {
@@ -234,13 +235,17 @@ export function createStatusCommand(deps?: Partial<StatusCommandDeps>): 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<StatusCommandDeps>): 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)') {

View File

@@ -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<string, boolean> = {};
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,
});
});