pi-free 2.0.1 → 2.0.4

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.
@@ -1,152 +1,476 @@
1
- /**
2
- * NVIDIA NIM Provider Extension
3
- *
4
- * Provides access to NVIDIA-hosted large models via integrate.api.nvidia.com.
5
- * All models use NVIDIA's free credit system — requires NVIDIA_API_KEY.
6
- * Get a free key at: https://build.nvidia.com
7
- *
8
- * Small models (< 70B) are filtered out to keep the list focused on useful
9
- * chat/coding models. Non-chat models (embedding, speech-to-text, OCR,
10
- * image-gen) are filtered by their modalities (output must be ["text"],
11
- * input must include "text").
12
- *
13
- * Responds to global free-only filter for free/paid model filtering.
14
- */
15
-
16
- import type {
17
- ExtensionAPI,
18
- ProviderModelConfig,
19
- } from "@mariozechner/pi-coding-agent";
20
- import {
21
- applyHidden,
22
- getNvidiaApiKey,
23
- getNvidiaShowPaid,
24
- PROVIDER_NVIDIA,
25
- } from "../../config.ts";
26
- import {
27
- BASE_URL_NVIDIA,
28
- DEFAULT_FETCH_TIMEOUT_MS,
29
- NVIDIA_MIN_SIZE_B,
30
- URL_MODELS_DEV,
31
- } from "../../constants.ts";
32
- import { registerWithGlobalToggle } from "../../lib/registry.ts";
33
- import type { ModelsDevProvider } from "../../lib/types.ts";
34
- import { fetchWithRetry, isUsableModel } from "../../lib/util.ts";
35
- import { createReRegister, enhanceWithCI } from "../../provider-helper.ts";
36
-
37
- // =============================================================================
38
- // Fetch + map
39
- // =============================================================================
40
-
41
- async function fetchNvidiaModels(
42
- showPaid = false,
43
- ): Promise<ProviderModelConfig[]> {
44
- const response = await fetchWithRetry(
45
- URL_MODELS_DEV,
46
- {
47
- headers: { "User-Agent": "pi-free-providers" },
48
- },
49
- 3,
50
- 1000,
51
- DEFAULT_FETCH_TIMEOUT_MS,
52
- );
53
-
54
- if (!response.ok) {
55
- throw new Error(
56
- `Failed to fetch models.dev: ${response.status} ${response.statusText}`,
57
- );
58
- }
59
-
60
- const json = (await response.json()) as Record<string, ModelsDevProvider>;
61
- const provider = Object.values(json).find((p) => p?.id === "nvidia");
62
- if (!provider?.models)
63
- throw new Error("nvidia provider not found in models.dev");
64
-
65
- const result = applyHidden(
66
- Object.values(provider.models)
67
- .filter((m) => isUsableModel(m.id, NVIDIA_MIN_SIZE_B))
68
- .filter((m) => {
69
- // Filter non-chat models by modalities
70
- const modalities = m.modalities;
71
- if (modalities) {
72
- const output = modalities.output ?? [];
73
- const input = modalities.input ?? [];
74
- if (!output.includes("text")) return false;
75
- if (!input.includes("text")) return false;
76
- }
77
- return true;
78
- })
79
- .filter((m) => {
80
- // Filter by cost - free models have input cost of 0
81
- if (!showPaid && (m.cost?.input ?? 0) > 0) return false;
82
- return true;
83
- })
84
- .map(
85
- (m): ProviderModelConfig => ({
86
- id: m.id,
87
- name: m.name,
88
- reasoning: m.reasoning,
89
- input: m.modalities?.input?.includes("image")
90
- ? ["text", "image"]
91
- : ["text"],
92
- cost: {
93
- input: m.cost?.input ?? 0,
94
- output: m.cost?.output ?? 0,
95
- cacheRead: m.cost?.cache_read ?? 0,
96
- cacheWrite: m.cost?.cache_write ?? 0,
97
- },
98
- contextWindow: m.limit.context,
99
- maxTokens: m.limit.output,
100
- }),
101
- ),
102
- );
103
-
104
- return result;
105
- }
106
-
107
- // =============================================================================
108
- // Extension Entry Point
109
- // =============================================================================
110
-
111
- export default async function (pi: ExtensionAPI) {
112
- // Fetch both free and all models
113
- let freeModels: ProviderModelConfig[] = [];
114
- let allModels: ProviderModelConfig[] = [];
115
-
116
- try {
117
- freeModels = await fetchNvidiaModels(false);
118
- allModels = await fetchNvidiaModels(true);
119
- } catch (error) {
120
- console.error("[nvidia] Failed to fetch models at startup", error);
121
- return;
122
- }
123
-
124
- // Store both sets for global toggle
125
- const stored = { free: freeModels, all: allModels };
126
- const apiKey = getNvidiaApiKey();
127
- const hasKey = !!(apiKey || process.env.NVIDIA_API_KEY);
128
-
129
- // Create re-register function
130
- const reRegister = createReRegister(pi, {
131
- providerId: PROVIDER_NVIDIA,
132
- baseUrl: BASE_URL_NVIDIA,
133
- apiKey: apiKey || "NVIDIA_API_KEY",
134
- });
135
-
136
- // Register with global toggle system
137
- registerWithGlobalToggle(PROVIDER_NVIDIA, stored, reRegister, hasKey);
138
-
139
- // Register initial models (global toggle will apply filter if needed)
140
- const initialModels = getNvidiaShowPaid() ? allModels : freeModels;
141
- pi.registerProvider(PROVIDER_NVIDIA, {
142
- baseUrl: BASE_URL_NVIDIA,
143
- apiKey: apiKey || "NVIDIA_API_KEY",
144
- api: "openai-completions" as const,
145
- headers: {
146
- "User-Agent": "pi-free-providers",
147
- },
148
- models: enhanceWithCI(initialModels),
149
- });
150
-
151
- // Registration complete - models registered silently (use LOG_LEVEL=info to see details)
152
- }
1
+ /**
2
+ * NVIDIA NIM Provider Extension
3
+ *
4
+ * Provides access to NVIDIA-hosted large models via integrate.api.nvidia.com.
5
+ * All models use NVIDIA's free credit system — requires NVIDIA_API_KEY.
6
+ * Get a free key at: https://build.nvidia.com
7
+ *
8
+ * Small models (< 70B) are filtered out to keep the list focused on useful
9
+ * chat/coding models. Non-chat models (embedding, speech-to-text, OCR,
10
+ * image-gen) are filtered by their modalities (output must be ["text"],
11
+ * input must include "text").
12
+ *
13
+ * Responds to global free-only filter for free/paid model filtering.
14
+ */
15
+
16
+ import type {
17
+ ExtensionAPI,
18
+ ProviderModelConfig,
19
+ } from "@mariozechner/pi-coding-agent";
20
+ import {
21
+ applyHidden,
22
+ getNvidiaApiKey,
23
+ loadConfigFile,
24
+ PROVIDER_NVIDIA,
25
+ saveConfig,
26
+ } from "../../config.ts";
27
+ import {
28
+ BASE_URL_NVIDIA,
29
+ DEFAULT_FETCH_TIMEOUT_MS,
30
+ NVIDIA_MIN_SIZE_B,
31
+ URL_MODELS_DEV,
32
+ } from "../../constants.ts";
33
+ import { createLogger } from "../../lib/logger.ts";
34
+ import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
35
+ import type { ModelsDevModel, ModelsDevProvider } from "../../lib/types.ts";
36
+ import {
37
+ fetchWithRetry,
38
+ fetchWithTimeout,
39
+ isUsableModel,
40
+ } from "../../lib/util.ts";
41
+ import { createReRegister, enhanceWithCI } from "../../provider-helper.ts";
42
+
43
+ // =============================================================================
44
+ // Non-chat model heuristics for models not in models.dev
45
+ // =============================================================================
46
+
47
+ const NVIDIA_NON_CHAT_PATTERNS: RegExp[] = [
48
+ /embed(?!.*instruct)/i,
49
+ /whisper/i,
50
+ /reward/i,
51
+ /ocr(?!.*instruct)/i,
52
+ /safety-guard|content-safety|nemoguard/i,
53
+ /retriever-parse|nemotron-parse(?!.*instruct)/i,
54
+ /detector/i,
55
+ /deplot/i,
56
+ /nvclip/i,
57
+ /vila$/i,
58
+ /neva(?!.*instruct)/i,
59
+ /translate/i,
60
+ /cosmos-reason/i,
61
+ /kosmos/i,
62
+ /bge-/i,
63
+ /arctic-embed/i,
64
+ /gliner/i,
65
+ /nv-embed/i,
66
+ /embedqa/i,
67
+ /embedcode/i,
68
+ ];
69
+
70
+ /**
71
+ * Models that appear in NVIDIA's /v1/models but return 404 "Function not found"
72
+ * on /v1/chat/completions. These are listed but not actually provisioned for
73
+ * hosted chat inference. Community-reported; add new IDs as they surface.
74
+ *
75
+ * Users can also hide individual models via hidden_models in ~/.pi/free.json.
76
+ */
77
+ const NVIDIA_KNOWN_404_MODELS: ReadonlySet<string> = new Set([
78
+ "01-ai/yi-large",
79
+ "adept/fuyu-8b",
80
+ "ai21labs/jamba-1.5-large-instruct",
81
+ "aisingapore/sea-lion-7b-instruct",
82
+ "baai/bge-m3",
83
+ "bigcode/starcoder2-15b",
84
+ "databricks/dbrx-instruct",
85
+ "deepseek-ai/deepseek-coder-6.7b-instruct",
86
+ "google/codegemma-1.1-7b",
87
+ "google/codegemma-7b",
88
+ "google/deplot",
89
+ "google/gemma-2b",
90
+ "google/recurrentgemma-2b",
91
+ "ibm/granite-3.0-3b-a800m-instruct",
92
+ "ibm/granite-3.0-8b-instruct",
93
+ "ibm/granite-34b-code-instruct",
94
+ "ibm/granite-8b-code-instruct",
95
+ "meta/codellama-70b",
96
+ "meta/llama2-70b",
97
+ "microsoft/kosmos-2",
98
+ "microsoft/phi-3-vision-128k-instruct",
99
+ "microsoft/phi-3.5-moe-instruct",
100
+ "mistralai/codestral-22b-instruct-v0.1",
101
+ "mistralai/mistral-7b-instruct-v0.3",
102
+ "mistralai/mistral-large",
103
+ "mistralai/mistral-large-2-instruct",
104
+ "mistralai/mixtral-8x22b-v0.1",
105
+ "nv-mistralai/mistral-nemo-12b-instruct",
106
+ "nvidia/cosmos-reason2-8b",
107
+ "nvidia/embed-qa-4",
108
+ "nvidia/llama-3.1-nemotron-51b-instruct",
109
+ "nvidia/llama-3.1-nemotron-70b-instruct",
110
+ "nvidia/llama-3.1-nemotron-ultra-253b-v1",
111
+ "nvidia/llama-3.2-nemoretriever-1b-vlm-embed-v1",
112
+ "nvidia/llama-3.2-nemoretriever-300m-embed-v1",
113
+ "nvidia/llama-3.2-nv-embedqa-1b-v1",
114
+ "nvidia/llama-3.2-nv-embedqa-1b-v2",
115
+ "nvidia/llama-nemotron-embed-1b-v2",
116
+ "nvidia/llama-nemotron-embed-vl-1b-v2",
117
+ "nvidia/llama3-chatqa-1.5-70b",
118
+ "nvidia/mistral-nemo-minitron-8b-8k-instruct",
119
+ "nvidia/nemotron-4-340b-instruct",
120
+ "nvidia/nemotron-4-340b-reward",
121
+ "nvidia/nemotron-nano-3-30b-a3b",
122
+ "nvidia/neva-22b",
123
+ "nvidia/nv-embed-v1",
124
+ "nvidia/nv-embedcode-7b-v1",
125
+ "nvidia/nv-embedqa-e5-v5",
126
+ "nvidia/nv-embedqa-mistral-7b-v2",
127
+ "nvidia/nvclip",
128
+ "nvidia/riva-translate-4b-instruct",
129
+ "snowflake/arctic-embed-l",
130
+ "writer/palmyra-creative-122b",
131
+ "writer/palmyra-fin-70b-32k",
132
+ "writer/palmyra-med-70b",
133
+ "writer/palmyra-med-70b-32k",
134
+ "zyphra/zamba2-7b-instruct",
135
+ ]);
136
+
137
+ /**
138
+ * Infer model metadata from a NVIDIA model ID for models not present in
139
+ * models.dev. Returns null if the ID matches known non-chat patterns.
140
+ */
141
+ function inferModelFromId(id: string): ModelsDevModel | null {
142
+ for (const pattern of NVIDIA_NON_CHAT_PATTERNS) {
143
+ if (pattern.test(id)) return null;
144
+ }
145
+
146
+ const name = id
147
+ .split("/")
148
+ .pop()!
149
+ .replace(/-/g, " ")
150
+ .replace(/\b\w/g, (c) => c.toUpperCase())
151
+ .replace(/\b(\d+(?:\.\d+)?)b\b/gi, "$1B");
152
+
153
+ const hasVision = /vision|multimodal|vl/i.test(id);
154
+ const hasReasoning = /reason|r1|thinking/i.test(id);
155
+
156
+ return {
157
+ id,
158
+ name,
159
+ reasoning: hasReasoning,
160
+ limit: { context: 128_000, output: 4096 },
161
+ modalities: {
162
+ input: hasVision ? ["text", "image"] : ["text"],
163
+ output: ["text"],
164
+ },
165
+ cost: { input: 0, output: 0 },
166
+ };
167
+ }
168
+
169
+ // =============================================================================
170
+ // Fetch + map
171
+ // =============================================================================
172
+
173
+ async function fetchNvidiaModels(
174
+ apiKey?: string,
175
+ ): Promise<ProviderModelConfig[]> {
176
+ // ── 1. Query NVIDIA's actual API (source of truth) ─────────────────
177
+ let apiModelIds = new Set<string>();
178
+ if (apiKey) {
179
+ try {
180
+ const response = await fetchWithRetry(
181
+ `${BASE_URL_NVIDIA}/models`,
182
+ {
183
+ headers: {
184
+ Authorization: `Bearer ${apiKey}`,
185
+ "User-Agent": "pi-free-providers",
186
+ },
187
+ },
188
+ 3,
189
+ 1000,
190
+ DEFAULT_FETCH_TIMEOUT_MS,
191
+ );
192
+ if (response.ok) {
193
+ const json = (await response.json()) as {
194
+ data?: Array<{ id: string }>;
195
+ };
196
+ if (json.data) {
197
+ apiModelIds = new Set(json.data.map((m) => m.id));
198
+ }
199
+ }
200
+ } catch (error) {
201
+ console.error("[nvidia] Failed to fetch models from NVIDIA API", error);
202
+ }
203
+ }
204
+
205
+ // ── 2. Fetch models.dev for rich metadata (cost, limits, etc.) ─────
206
+ const devModels = new Map<string, ModelsDevModel>();
207
+ try {
208
+ const response = await fetchWithRetry(
209
+ URL_MODELS_DEV,
210
+ {
211
+ headers: { "User-Agent": "pi-free-providers" },
212
+ },
213
+ 3,
214
+ 1000,
215
+ DEFAULT_FETCH_TIMEOUT_MS,
216
+ );
217
+ if (response.ok) {
218
+ const json = (await response.json()) as Record<string, ModelsDevProvider>;
219
+ const provider = Object.values(json).find((p) => p?.id === "nvidia");
220
+ if (provider?.models) {
221
+ for (const m of Object.values(provider.models)) {
222
+ devModels.set(m.id, m);
223
+ }
224
+ }
225
+ }
226
+ } catch (error) {
227
+ console.error("[nvidia] Failed to fetch models.dev", error);
228
+ }
229
+
230
+ // ── 3. Build unified list (NVIDIA API wins; fallback to models.dev) ─
231
+ const modelIds =
232
+ apiModelIds.size > 0 ? [...apiModelIds] : [...devModels.keys()];
233
+
234
+ const result = applyHidden(
235
+ modelIds
236
+ .map((id) => {
237
+ const dev = devModels.get(id);
238
+ if (dev) return dev;
239
+ return inferModelFromId(id);
240
+ })
241
+ .filter((m): m is ModelsDevModel => m !== null)
242
+ .filter((m) => isUsableModel(m.id, NVIDIA_MIN_SIZE_B))
243
+ .filter((m) => {
244
+ const modalities = m.modalities;
245
+ if (modalities) {
246
+ const output = modalities.output ?? [];
247
+ const input = modalities.input ?? [];
248
+ if (!output.includes("text")) return false;
249
+ if (!input.includes("text")) return false;
250
+ }
251
+ return true;
252
+ })
253
+ // Filter out known 404 models (listed but not provisioned for chat)
254
+ .filter((m) => {
255
+ if (NVIDIA_KNOWN_404_MODELS.has(m.id)) {
256
+ return false;
257
+ }
258
+ return true;
259
+ })
260
+ // NVIDIA is freemium — all models are usable with free credits.
261
+ // No cost filtering applied.
262
+ .map(
263
+ (m): ProviderModelConfig => ({
264
+ id: m.id,
265
+ name: m.name,
266
+ reasoning: m.reasoning,
267
+ input: m.modalities?.input?.includes("image")
268
+ ? ["text", "image"]
269
+ : ["text"],
270
+ cost: {
271
+ input: m.cost?.input ?? 0,
272
+ output: m.cost?.output ?? 0,
273
+ cacheRead: m.cost?.cache_read ?? 0,
274
+ cacheWrite: m.cost?.cache_write ?? 0,
275
+ },
276
+ contextWindow: m.limit.context,
277
+ maxTokens: m.limit.output,
278
+ }),
279
+ ),
280
+ PROVIDER_NVIDIA,
281
+ );
282
+
283
+ return result;
284
+ }
285
+
286
+ // =============================================================================
287
+ // Extension Entry Point
288
+ // =============================================================================
289
+
290
+ /**
291
+ * Probe a single NVIDIA model with a minimal chat request.
292
+ * Returns true if the model is routable (not 404), false if it 404s.
293
+ */
294
+ async function probeNvidiaModel(
295
+ apiKey: string,
296
+ modelId: string,
297
+ ): Promise<boolean> {
298
+ try {
299
+ const response = await fetchWithTimeout(
300
+ `${BASE_URL_NVIDIA}/chat/completions`,
301
+ {
302
+ method: "POST",
303
+ headers: {
304
+ Authorization: `Bearer ${apiKey}`,
305
+ "Content-Type": "application/json",
306
+ "User-Agent": "pi-free-providers",
307
+ },
308
+ body: JSON.stringify({
309
+ model: modelId,
310
+ messages: [{ role: "user", content: "hi" }],
311
+ max_tokens: 1,
312
+ }),
313
+ },
314
+ 10000, // 10 second timeout
315
+ );
316
+ // 404 = function not found (model not provisioned)
317
+ // 200/400/401/etc = at least routable
318
+ return response.status !== 404;
319
+ } catch {
320
+ return true; // Network errors / timeouts are not "model not found"
321
+ }
322
+ }
323
+
324
+ const _nvidiaLogger = createLogger("nvidia");
325
+
326
+ /**
327
+ * Run probe on a list of models and auto-hide 404s.
328
+ * Shared between the /probe-nvidia command and auto-probe on session_start.
329
+ */
330
+ async function runNvidiaProbe(
331
+ apiKey: string,
332
+ modelsToTest: ProviderModelConfig[],
333
+ stored: { free: ProviderModelConfig[]; all: ProviderModelConfig[] },
334
+ reRegister: (models: ProviderModelConfig[]) => void,
335
+ ): Promise<void> {
336
+ const notFound: string[] = [];
337
+ const batchSize = 5;
338
+
339
+ for (let i = 0; i < modelsToTest.length; i += batchSize) {
340
+ const batch = modelsToTest.slice(i, i + batchSize);
341
+ const results = await Promise.all(
342
+ batch.map(async (m) => {
343
+ const ok = await probeNvidiaModel(apiKey, m.id);
344
+ return { id: m.id, ok };
345
+ }),
346
+ );
347
+ for (const r of results) {
348
+ if (!r.ok) notFound.push(r.id);
349
+ }
350
+ }
351
+
352
+ if (notFound.length === 0) {
353
+ _nvidiaLogger.info("Auto-probe: all NVIDIA models are routable");
354
+ return;
355
+ }
356
+
357
+ // Auto-hide 404 models in config (provider-scoped)
358
+ const cfg = loadConfigFile();
359
+ const existingHidden = new Set(cfg.hidden_models ?? []);
360
+ for (const id of notFound) existingHidden.add(`${PROVIDER_NVIDIA}/${id}`);
361
+ saveConfig({ hidden_models: Array.from(existingHidden) });
362
+
363
+ // Re-register so hidden models disappear immediately
364
+ const filtered = await fetchNvidiaModels(apiKey);
365
+ stored.free = filtered;
366
+ stored.all = filtered;
367
+ reRegister(filtered);
368
+
369
+ _nvidiaLogger.info(
370
+ `Auto-probe: found ${notFound.length} broken models (auto-hidden)`,
371
+ );
372
+ }
373
+
374
+ export default async function (pi: ExtensionAPI) {
375
+ const apiKey = getNvidiaApiKey();
376
+ const hasKey = !!apiKey;
377
+
378
+ let allModels: ProviderModelConfig[] = [];
379
+
380
+ try {
381
+ allModels = await fetchNvidiaModels(apiKey);
382
+ } catch (error) {
383
+ console.error("[nvidia] Failed to fetch models at startup", error);
384
+ return;
385
+ }
386
+
387
+ // Store both sets for global toggle using consistent isFreeModel helper
388
+ // NVIDIA uses Route B (name-based): only models with "free" in name are marked free
389
+ const freeModels = allModels.filter((m) =>
390
+ isFreeModel({ ...m, provider: PROVIDER_NVIDIA }),
391
+ );
392
+ const stored = { free: freeModels, all: allModels };
393
+
394
+ // Create re-register function
395
+ const reRegister = createReRegister(pi, {
396
+ providerId: PROVIDER_NVIDIA,
397
+ baseUrl: BASE_URL_NVIDIA,
398
+ apiKey: apiKey || "NVIDIA_API_KEY",
399
+ });
400
+
401
+ // Register with global toggle system
402
+ registerWithGlobalToggle(PROVIDER_NVIDIA, stored, reRegister, hasKey);
403
+
404
+ // Register initial models (global toggle will apply filter if needed)
405
+ const initialModels = allModels;
406
+ pi.registerProvider(PROVIDER_NVIDIA, {
407
+ baseUrl: BASE_URL_NVIDIA,
408
+ apiKey: apiKey || "NVIDIA_API_KEY",
409
+ api: "openai-completions" as const,
410
+ authHeader: true,
411
+ headers: {
412
+ "User-Agent": "pi-free-providers",
413
+ },
414
+ models: enhanceWithCI(initialModels),
415
+ });
416
+
417
+ // ── Lazy auto-probe on first session_start ──────────────────────
418
+ let _autoProbeDone = false;
419
+ pi.on("session_start", async () => {
420
+ if (_autoProbeDone || !apiKey) return;
421
+ _autoProbeDone = true;
422
+ _nvidiaLogger.info("Starting lazy auto-probe of NVIDIA models...");
423
+ runNvidiaProbe(apiKey, allModels, stored, reRegister).catch((err) => {
424
+ _nvidiaLogger.warn("Auto-probe failed", {
425
+ error: err instanceof Error ? err.message : String(err),
426
+ });
427
+ });
428
+ });
429
+
430
+ // ── Probe command: test all registered models for 404s ─────────────
431
+ pi.registerCommand("probe-nvidia", {
432
+ description: "Test all NVIDIA models for 404 'Function not found' errors",
433
+ handler: async (_args, ctx) => {
434
+ if (!apiKey) {
435
+ ctx.ui.notify("NVIDIA_API_KEY not set", "error");
436
+ return;
437
+ }
438
+
439
+ const modelsToTest = allModels;
440
+ ctx.ui.notify(`Probing ${modelsToTest.length} NVIDIA models…`, "info");
441
+
442
+ await runNvidiaProbe(apiKey, modelsToTest, stored, reRegister);
443
+
444
+ // Check if any were hidden (re-read config)
445
+ const cfgAfter = loadConfigFile();
446
+ const newHidden = (cfgAfter.hidden_models ?? []).filter((h) =>
447
+ h.startsWith(`${PROVIDER_NVIDIA}/`),
448
+ );
449
+ if (newHidden.length > 0) {
450
+ ctx.ui.notify(
451
+ `Found ${newHidden.length} broken models (auto-hidden):\n${newHidden.join("\n")}`,
452
+ "warning",
453
+ );
454
+ } else {
455
+ ctx.ui.notify("All NVIDIA models are routable ✅", "info");
456
+ }
457
+ },
458
+ });
459
+
460
+ // ── Status bar for provider selection ─────────────────────────
461
+
462
+ pi.on("model_select", (_event, ctx) => {
463
+ if (_event.model?.provider !== PROVIDER_NVIDIA) {
464
+ ctx.ui.setStatus(`${PROVIDER_NVIDIA}-status`, undefined);
465
+ return;
466
+ }
467
+
468
+ const count = allModels.length;
469
+ ctx.ui.setStatus(
470
+ `${PROVIDER_NVIDIA}-status`,
471
+ `nvidia: ${count} models (freemium)`,
472
+ );
473
+ });
474
+
475
+ // Registration complete - models registered silently (use LOG_LEVEL=info to see details)
476
+ }