@maheidem/model-discovery 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui-model.ts ADDED
@@ -0,0 +1,127 @@
1
+ import type { SelectItem } from "@earendil-works/pi-tui";
2
+ import { redactSecret } from "./providers.ts";
3
+ import { STORAGE_PATH, getStorageDiagnostic, type DiscoveredProvider } from "./storage.ts";
4
+
5
+ export type SourceAvailability = "ready" | "degraded" | "unavailable" | "unscanned";
6
+
7
+ export interface DiscoverySummary {
8
+ sourceCount: number;
9
+ modelCount: number;
10
+ readyCount: number;
11
+ degradedCount: number;
12
+ unavailableCount: number;
13
+ unscannedCount: number;
14
+ }
15
+
16
+ export function homeRelativePath(path: string, home = process.env.HOME): string {
17
+ if (!home) return path;
18
+ return path === home ? "~" : path.startsWith(`${home}/`) ? `~/${path.slice(home.length + 1)}` : path;
19
+ }
20
+
21
+ export function sourceAvailability(provider: DiscoveredProvider): SourceAvailability {
22
+ if (provider.lastScanError) return provider.cachedModels?.length ? "degraded" : "unavailable";
23
+ if (provider.lastScanned) return "ready";
24
+ return "unscanned";
25
+ }
26
+
27
+ export function summarizeSources(providers: readonly DiscoveredProvider[]): DiscoverySummary {
28
+ const summary: DiscoverySummary = {
29
+ sourceCount: providers.length,
30
+ modelCount: 0,
31
+ readyCount: 0,
32
+ degradedCount: 0,
33
+ unavailableCount: 0,
34
+ unscannedCount: 0,
35
+ };
36
+ for (const provider of providers) {
37
+ summary.modelCount += provider.cachedModels?.length ?? 0;
38
+ summary[`${sourceAvailability(provider)}Count`]++;
39
+ }
40
+ return summary;
41
+ }
42
+
43
+ export function buildHomeSummary(providers: readonly DiscoveredProvider[]): string[] {
44
+ const summary = summarizeSources(providers);
45
+ if (!summary.sourceCount) {
46
+ return ["No sources configured", "Add a source to discover models from a local OpenAI-compatible endpoint."];
47
+ }
48
+ const health: string[] = [];
49
+ if (summary.readyCount) health.push(`${summary.readyCount} ready`);
50
+ if (summary.degradedCount) health.push(`${summary.degradedCount} cached`);
51
+ if (summary.unavailableCount) health.push(`${summary.unavailableCount} unavailable`);
52
+ if (summary.unscannedCount) health.push(`${summary.unscannedCount} not scanned`);
53
+ return [
54
+ `${summary.sourceCount} source${summary.sourceCount === 1 ? "" : "s"} · ${summary.modelCount} cached model${summary.modelCount === 1 ? "" : "s"}`,
55
+ health.join(" · "),
56
+ ];
57
+ }
58
+
59
+ export function buildHomeItems(providers: readonly DiscoveredProvider[]): SelectItem[] {
60
+ const sourceItems = providers.map((provider) => {
61
+ const availability = sourceAvailability(provider);
62
+ const modelCount = provider.cachedModels?.length ?? 0;
63
+ return {
64
+ value: `provider:${provider.name}`,
65
+ label: provider.name,
66
+ description: `${availability} · ${provider.serverType ?? "unknown server"} · ${modelCount} model${modelCount === 1 ? "" : "s"} · ${provider.baseUrl}`,
67
+ };
68
+ });
69
+ return [
70
+ ...sourceItems,
71
+ { value: "add", label: "Add source", description: "Discover models from an OpenAI-compatible endpoint" },
72
+ ...(providers.length
73
+ ? [{ value: "rescan-all", label: "Re-scan all sources", description: "Refresh every catalogue; cached models remain on failure" }]
74
+ : []),
75
+ { value: "diagnostics", label: "Diagnostics", description: "Inspect storage, health, and cached models" },
76
+ { value: "quit", label: "Close" },
77
+ ];
78
+ }
79
+
80
+ function sourceStatusLine(provider: DiscoveredProvider): string {
81
+ const status = sourceAvailability(provider);
82
+ const modelCount = provider.cachedModels?.length ?? 0;
83
+ return `- ${provider.name}: ${status} · ${provider.serverType ?? "unknown"} · ${modelCount} model${modelCount === 1 ? "" : "s"} · ${provider.baseUrl}`;
84
+ }
85
+
86
+ export function formatDiscoveryStatus(
87
+ providers: readonly DiscoveredProvider[],
88
+ options: { storagePath?: string; home?: string } = {},
89
+ ): string {
90
+ const summary = summarizeSources(providers);
91
+ const path = homeRelativePath(options.storagePath ?? STORAGE_PATH, options.home ?? process.env.HOME);
92
+ const lines = [
93
+ "Model Discovery",
94
+ `Sources: ${summary.sourceCount}`,
95
+ `Cached models: ${summary.modelCount}`,
96
+ `Health: ${summary.readyCount} ready, ${summary.degradedCount} cached/degraded, ${summary.unavailableCount} unavailable, ${summary.unscannedCount} not scanned`,
97
+ `Storage: ${path}`,
98
+ ];
99
+ if (providers.length) lines.push(...providers.map(sourceStatusLine));
100
+ else lines.push("No sources configured. In TUI mode, run /discover and choose Add source.");
101
+ const diagnostic = getStorageDiagnostic();
102
+ if (diagnostic) lines.push(`Warning: ${diagnostic.message}`);
103
+ return lines.join("\n");
104
+ }
105
+
106
+ export function buildDiagnosticsLines(providers: readonly DiscoveredProvider[]): string[] {
107
+ const summary = summarizeSources(providers);
108
+ const lines = [
109
+ `Configuration: ${homeRelativePath(STORAGE_PATH)}`,
110
+ `Sources: ${summary.sourceCount} · cached models: ${summary.modelCount}`,
111
+ `Health: ${summary.readyCount} ready · ${summary.degradedCount} cached/degraded · ${summary.unavailableCount} unavailable · ${summary.unscannedCount} not scanned`,
112
+ ];
113
+ const diagnostic = getStorageDiagnostic();
114
+ if (diagnostic) lines.push(`Warning: ${diagnostic.message}`);
115
+ if (!providers.length) {
116
+ lines.push("", "No sources configured.");
117
+ return lines;
118
+ }
119
+ lines.push("", "Configured sources");
120
+ for (const provider of providers) {
121
+ lines.push(sourceStatusLine(provider));
122
+ lines.push(` authentication: ${provider.apiKey ? "API key configured" : "anonymous"}`);
123
+ if (provider.lastScanned) lines.push(` last successful scan: ${new Date(provider.lastScanned).toLocaleString()}`);
124
+ if (provider.lastScanError) lines.push(` latest error: ${redactSecret(provider.lastScanError, provider.apiKey)}`);
125
+ }
126
+ return lines;
127
+ }