/** * v2.3 Phase 4 (design.md §8) — per-provider plaintext diagnostic report. * * Read-only by default: reports CACHED state (resolved registry def + the * available_agents row + the warm snapshot-cache entry) plus a `which`-style * PATH check for the launch binary. It does NOT spawn an ACP probe — §8 lists * the live initialize probe as optional, and the route defaults to cached state. * * A template string is the whole formatter (no Paseo diagnostic-utils port). */ import type { ResolvedProviderDef } from './provider-config-registry.js'; import type { ProviderSnapshotEntry, ProviderModel } from './provider-types.js'; import { isCommandAvailable } from './command-availability.js'; /** The subset of an `available_agents` row the diagnostic reads. */ export interface DiagnosticAgentRow { name: string; install_path: string | null; supports_acp?: boolean; models?: ProviderModel[] | null; last_probed_at?: string | Date | null; } interface DiagnosticOpts { /** Warm snapshot-cache entry (read-only peek) — source of the last probe error. */ cachedEntry?: ProviderSnapshotEntry; /** Injectable PATH check (defaults to the real `which`); stubbed in tests. */ checkAvailable?: (binary: string) => Promise; } /** Resolve the binary the dispatcher would launch (for the PATH check + report). */ function resolveBinary(resolved: ResolvedProviderDef, agentRow: DiagnosticAgentRow | undefined): string { return resolved.launchCommand?.[0] ?? agentRow?.install_path ?? resolved.id; } export async function getProviderDiagnostic( resolved: ResolvedProviderDef, agentRow: DiagnosticAgentRow | undefined, opts: DiagnosticOpts = {}, ): Promise { const checkAvailable = opts.checkAvailable ?? isCommandAvailable; const installed = agentRow?.install_path != null; const binary = resolveBinary(resolved, agentRow); // boocode is native (no binary to launch) — short-circuit the PATH check. const commandAvailable = resolved.transport === 'native' ? true : await checkAvailable(binary); const lastProbedAt = agentRow?.last_probed_at != null ? new Date(agentRow.last_probed_at).toISOString() : '(never)'; const modelCount = agentRow?.models?.length ?? 0; const launchCommand = resolved.launchCommand ? resolved.launchCommand.join(' ') : '(built-in default, resolved at dispatch)'; const lastError = opts.cachedEntry?.error ?? '(none recorded)'; return [ `provider: ${resolved.id}`, `label: ${resolved.configLabel ?? resolved.label}`, `transport: ${resolved.transport}`, `enabled: ${resolved.enabled}`, `builtin: ${resolved.isBuiltin}`, `customAcp: ${resolved.isCustomAcp}`, `installed: ${installed}`, `install_path: ${agentRow?.install_path ?? '(none)'}`, `binary: ${binary}`, `command_available: ${commandAvailable}`, `launch_command: ${launchCommand}`, `supports_acp: ${agentRow?.supports_acp ?? '(unknown)'}`, `last_probed_at: ${lastProbedAt}`, `models_in_db: ${modelCount}`, `last_probe_error: ${lastError}`, ].join('\n'); }