pi-free 2.0.13 → 2.0.14
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/CHANGELOG.md +12 -0
- package/README.md +4 -1
- package/config.ts +15 -0
- package/constants.ts +3 -0
- package/index.ts +135 -0
- package/lib/built-in-toggle.ts +4 -4
- package/lib/probe-cache.ts +86 -0
- package/lib/registry.ts +25 -3
- package/lib/telemetry.ts +328 -0
- package/lib/util.ts +10 -1
- package/package.json +1 -1
- package/provider-failover/benchmark-lookup.ts +94 -8
- package/provider-failover/benchmarks-chunk-0.ts +599 -890
- package/provider-failover/benchmarks-chunk-1.ts +655 -924
- package/provider-failover/benchmarks-chunk-2.ts +675 -966
- package/provider-failover/benchmarks-chunk-3.ts +676 -967
- package/provider-failover/benchmarks-chunk-4.ts +704 -954
- package/provider-failover/benchmarks-chunk-5.ts +1301 -0
- package/provider-failover/hardcoded-benchmarks.ts +9 -3
- package/providers/cline/cline-models.ts +196 -68
- package/providers/dynamic-built-in/index.ts +1 -1
- package/providers/kilo/kilo.ts +2 -2
- package/providers/model-fetcher.ts +3 -1
- package/providers/nvidia/nvidia.ts +47 -15
- package/providers/ollama/ollama.ts +103 -46
- package/providers/opencode-session.ts +398 -371
- package/providers/qwen/qwen.ts +2 -2
- package/providers/routeway/routeway.ts +213 -0
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
loadProviderCache,
|
|
40
40
|
saveProviderCache,
|
|
41
41
|
} from "../../lib/provider-cache.ts";
|
|
42
|
+
import {
|
|
43
|
+
getModelsDueForProbe,
|
|
44
|
+
recordModelProbeResults,
|
|
45
|
+
} from "../../lib/probe-cache.ts";
|
|
42
46
|
import { registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
43
47
|
import { fetchWithRetry, fetchWithTimeout } from "../../lib/util.ts";
|
|
44
48
|
import { createReRegister, enhanceWithCI } from "../../provider-helper.ts";
|
|
@@ -379,6 +383,80 @@ async function fetchAllModels(apiKey: string): Promise<ProviderModelConfig[]> {
|
|
|
379
383
|
return applyHidden(models, PROVIDER_OLLAMA);
|
|
380
384
|
}
|
|
381
385
|
|
|
386
|
+
async function runOllamaProbe(
|
|
387
|
+
apiKey: string,
|
|
388
|
+
modelsToTest: ProviderModelConfig[],
|
|
389
|
+
applyModels: (models: ProviderModelConfig[]) => void,
|
|
390
|
+
options: { useCache?: boolean } = {},
|
|
391
|
+
): Promise<string[]> {
|
|
392
|
+
const modelIdsToProbe = options.useCache
|
|
393
|
+
? new Set(
|
|
394
|
+
getModelsDueForProbe(
|
|
395
|
+
PROVIDER_OLLAMA,
|
|
396
|
+
modelsToTest.map((m) => m.id),
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
: undefined;
|
|
400
|
+
const probeCandidates = modelIdsToProbe
|
|
401
|
+
? modelsToTest.filter((m) => modelIdsToProbe.has(m.id))
|
|
402
|
+
: modelsToTest;
|
|
403
|
+
|
|
404
|
+
if (probeCandidates.length === 0) {
|
|
405
|
+
_logger.info("Auto-probe: Ollama probe cache is fresh");
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const notFound: string[] = [];
|
|
410
|
+
const cacheableResults: Array<{ modelId: string; status: "ok" | "broken" }> =
|
|
411
|
+
[];
|
|
412
|
+
const batchSize = 5;
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < probeCandidates.length; i += batchSize) {
|
|
415
|
+
const batch = probeCandidates.slice(i, i + batchSize);
|
|
416
|
+
const results = await Promise.all(
|
|
417
|
+
batch.map(async (m) => {
|
|
418
|
+
const status = await probeOllamaModel(apiKey, m.id);
|
|
419
|
+
return { id: m.id, status };
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
422
|
+
for (const r of results) {
|
|
423
|
+
if (r.status === "broken") notFound.push(r.id);
|
|
424
|
+
if (r.status !== "unknown") {
|
|
425
|
+
cacheableResults.push({ modelId: r.id, status: r.status });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
recordModelProbeResults(PROVIDER_OLLAMA, cacheableResults);
|
|
431
|
+
|
|
432
|
+
if (notFound.length === 0) {
|
|
433
|
+
_logger.info("Auto-probe: all checked Ollama models are accessible");
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Auto-hide 403 models in config (provider-scoped)
|
|
438
|
+
const config = loadConfigFile();
|
|
439
|
+
const existingHidden = new Set(config.hidden_models ?? []);
|
|
440
|
+
for (const id of notFound) existingHidden.add(`${PROVIDER_OLLAMA}/${id}`);
|
|
441
|
+
saveConfig({
|
|
442
|
+
hidden_models: Array.from(existingHidden),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Re-fetch and re-register so hidden models disappear immediately
|
|
446
|
+
try {
|
|
447
|
+
const fresh = await fetchAllModels(apiKey);
|
|
448
|
+
saveProviderCache(PROVIDER_OLLAMA, fresh);
|
|
449
|
+
applyModels(fresh);
|
|
450
|
+
} catch {
|
|
451
|
+
// If refresh fails, keep current models. The next refresh/probe will retry.
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
_logger.info(
|
|
455
|
+
`Auto-probe: found ${notFound.length} broken Ollama models (auto-hidden)`,
|
|
456
|
+
);
|
|
457
|
+
return notFound;
|
|
458
|
+
}
|
|
459
|
+
|
|
382
460
|
// =============================================================================
|
|
383
461
|
// Extension Entry Point
|
|
384
462
|
// =============================================================================
|
|
@@ -411,7 +489,7 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
411
489
|
|
|
412
490
|
// ── Register immediately with cached/fallback models ────────────
|
|
413
491
|
const freeModels = allModels;
|
|
414
|
-
|
|
492
|
+
const stored = { free: freeModels, all: allModels };
|
|
415
493
|
const hasKey = true;
|
|
416
494
|
|
|
417
495
|
const reRegister = createReRegister(pi, {
|
|
@@ -419,6 +497,12 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
419
497
|
baseUrl: BASE_URL_OLLAMA,
|
|
420
498
|
apiKey,
|
|
421
499
|
});
|
|
500
|
+
const applyModelList = (models: ProviderModelConfig[]) => {
|
|
501
|
+
allModels = models;
|
|
502
|
+
stored.free = models;
|
|
503
|
+
stored.all = models;
|
|
504
|
+
reRegister(models);
|
|
505
|
+
};
|
|
422
506
|
|
|
423
507
|
registerWithGlobalToggle(PROVIDER_OLLAMA, stored, reRegister, hasKey);
|
|
424
508
|
|
|
@@ -460,9 +544,7 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
460
544
|
try {
|
|
461
545
|
const fresh = await fetchAllModels(apiKey!);
|
|
462
546
|
saveProviderCache(PROVIDER_OLLAMA, fresh);
|
|
463
|
-
|
|
464
|
-
stored = { free: fresh, all: fresh };
|
|
465
|
-
reRegister(fresh);
|
|
547
|
+
applyModelList(fresh);
|
|
466
548
|
ctx.ui.notify(
|
|
467
549
|
`Registered ${fresh.length} Ollama Cloud models (refresh complete)`,
|
|
468
550
|
"info",
|
|
@@ -488,47 +570,17 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
488
570
|
const modelsToTest = allModels;
|
|
489
571
|
ctx.ui.notify(`Probing ${modelsToTest.length} Ollama models…`, "info");
|
|
490
572
|
|
|
491
|
-
const notFound
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const results = await Promise.all(
|
|
497
|
-
batch.map(async (m) => {
|
|
498
|
-
const ok = await probeOllamaModel(apiKey, m.id);
|
|
499
|
-
return { id: m.id, ok };
|
|
500
|
-
}),
|
|
501
|
-
);
|
|
502
|
-
for (const r of results) {
|
|
503
|
-
if (!r.ok) notFound.push(r.id);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
573
|
+
const notFound = await runOllamaProbe(
|
|
574
|
+
apiKey,
|
|
575
|
+
modelsToTest,
|
|
576
|
+
applyModelList,
|
|
577
|
+
);
|
|
506
578
|
|
|
507
579
|
if (notFound.length === 0) {
|
|
508
580
|
ctx.ui.notify("All Ollama models are accessible ✅", "info");
|
|
509
581
|
return;
|
|
510
582
|
}
|
|
511
583
|
|
|
512
|
-
// Auto-hide 403 models in config (provider-scoped)
|
|
513
|
-
const config = loadConfigFile();
|
|
514
|
-
const existingHidden = new Set(config.hidden_models ?? []);
|
|
515
|
-
for (const id of notFound) existingHidden.add(`${PROVIDER_OLLAMA}/${id}`);
|
|
516
|
-
saveConfig({
|
|
517
|
-
hidden_models: Array.from(existingHidden),
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
// Re-fetch and re-register so hidden models disappear immediately
|
|
521
|
-
try {
|
|
522
|
-
const fresh = await fetchAllModels(apiKey!);
|
|
523
|
-
saveProviderCache(PROVIDER_OLLAMA, fresh);
|
|
524
|
-
allModels = fresh;
|
|
525
|
-
stored = { free: fresh, all: fresh };
|
|
526
|
-
reRegister(fresh);
|
|
527
|
-
} catch {
|
|
528
|
-
// If refresh fails, just re-register current models
|
|
529
|
-
reRegister(allModels);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
584
|
ctx.ui.notify(
|
|
533
585
|
`Found ${notFound.length} broken models (auto-hidden):\n${notFound.join("\n")}`,
|
|
534
586
|
"warning",
|
|
@@ -560,10 +612,15 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
560
612
|
|
|
561
613
|
try {
|
|
562
614
|
const fresh = await refreshModels();
|
|
563
|
-
|
|
564
|
-
stored = { free: fresh, all: fresh };
|
|
565
|
-
reRegister(fresh);
|
|
615
|
+
applyModelList(fresh);
|
|
566
616
|
ctx.ui.notify(`Ollama Cloud: ${fresh.length} models ready`, "info");
|
|
617
|
+
runOllamaProbe(apiKey, fresh, applyModelList, { useCache: true }).catch(
|
|
618
|
+
(error) => {
|
|
619
|
+
_logger.warn("Auto-probe failed", {
|
|
620
|
+
error: error instanceof Error ? error.message : String(error),
|
|
621
|
+
});
|
|
622
|
+
},
|
|
623
|
+
);
|
|
567
624
|
} catch {
|
|
568
625
|
// Already logged in refreshModels()
|
|
569
626
|
}
|
|
@@ -576,12 +633,12 @@ export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
|
576
633
|
|
|
577
634
|
/**
|
|
578
635
|
* Probe a single Ollama model with a minimal chat request.
|
|
579
|
-
* Returns
|
|
636
|
+
* Returns "broken" only for deterministic 403s; network errors are unknown.
|
|
580
637
|
*/
|
|
581
638
|
async function probeOllamaModel(
|
|
582
639
|
apiKey: string,
|
|
583
640
|
modelId: string,
|
|
584
|
-
): Promise<
|
|
641
|
+
): Promise<"ok" | "broken" | "unknown"> {
|
|
585
642
|
try {
|
|
586
643
|
const response = await fetchWithTimeout(
|
|
587
644
|
`${BASE_URL_OLLAMA}/chat/completions`,
|
|
@@ -602,9 +659,9 @@ async function probeOllamaModel(
|
|
|
602
659
|
);
|
|
603
660
|
// 403 = access denied (model not provisioned)
|
|
604
661
|
// 200/400/401/etc = at least accessible
|
|
605
|
-
return response.status
|
|
662
|
+
return response.status === 403 ? "broken" : "ok";
|
|
606
663
|
} catch {
|
|
607
664
|
// Network errors / timeouts are not "access denied"
|
|
608
|
-
return
|
|
665
|
+
return "unknown";
|
|
609
666
|
}
|
|
610
667
|
}
|