@phi-code-admin/phi-code 0.75.3 → 0.75.5

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.
@@ -40,6 +40,7 @@ import {
40
40
  pingOpenCodeGo,
41
41
  validateOpenCodeGoApiKey,
42
42
  } from "./providers/opencode-go.js";
43
+ import { fetchLiveModels, pingProvider, toPersistedModel } from "./providers/live-models.js";
43
44
 
44
45
  // ─── Types ───────────────────────────────────────────────────────────────
45
46
 
@@ -176,22 +177,9 @@ function getProviderCatalog(): ProviderEntry[] {
176
177
  // ─── Helpers ─────────────────────────────────────────────────────────────
177
178
 
178
179
  async function probeLocalProvider(provider: ProviderEntry): Promise<string[]> {
179
- if (!provider.probeUrl) return [];
180
- const controller = new AbortController();
181
- const timeout = setTimeout(() => controller.abort(), 2_500);
182
- try {
183
- const res = await fetch(provider.probeUrl, {
184
- signal: controller.signal,
185
- headers: { Authorization: `Bearer ${provider.id === "ollama" ? "ollama" : "lm-studio"}` },
186
- });
187
- clearTimeout(timeout);
188
- if (!res.ok) return [];
189
- const data = (await res.json()) as { data?: Array<{ id?: string; name?: string }> };
190
- return (data.data ?? []).map((m) => m.id ?? m.name ?? "").filter(Boolean);
191
- } catch {
192
- clearTimeout(timeout);
193
- return [];
194
- }
180
+ if (!provider.local) return [];
181
+ const result = await fetchLiveModels(provider.id, { forceRefresh: true, timeoutMs: 2_500 });
182
+ return result.source === "live" ? result.models.map((m) => m.id) : [];
195
183
  }
196
184
 
197
185
  function buildStatusWidget(
@@ -442,21 +430,53 @@ async function configureGenericCloud(
442
430
  if (!proceed) return undefined;
443
431
  }
444
432
 
433
+ // Persist the key immediately so a downstream fetch failure cannot lose user input.
445
434
  store.setKey(provider.id, trimmed, {
446
435
  baseUrl: provider.baseUrl,
447
436
  api: provider.api,
448
- models: provider.staticModels.map((id) => ({
449
- id,
450
- name: id,
451
- reasoning: true,
452
- input: ["text"] as const,
453
- })),
454
437
  });
438
+
439
+ // Optional ping for early auth diagnostics.
440
+ ui.setStatus("setup-ping", `Pinging ${provider.displayName}...`);
441
+ const ping = await pingProvider(provider.id, trimmed, 5_000).catch((err) => ({
442
+ ok: false,
443
+ error: err instanceof Error ? err.message : String(err),
444
+ }));
445
+ ui.setStatus("setup-ping", undefined);
446
+ if (ping.ok) {
447
+ ui.notify(`${provider.displayName} ping OK (200).`, "info");
448
+ } else {
449
+ ui.notify(
450
+ `${provider.displayName} ping failed: ${ping.error ?? "unknown"}. Key saved; you can retry with \`/keys test ${provider.id}\`.`,
451
+ "warning",
452
+ );
453
+ }
454
+
455
+ // Live-fetch the model catalog (falls back to the static list when offline).
456
+ ui.setStatus("setup-fetch", `Fetching ${provider.displayName} model list...`);
457
+ const live = await fetchLiveModels(provider.id, {
458
+ apiKey: trimmed,
459
+ forceRefresh: true,
460
+ timeoutMs: 6_000,
461
+ });
462
+ ui.setStatus("setup-fetch", undefined);
463
+
464
+ const models = (live.models.length > 0
465
+ ? live.models
466
+ : provider.staticModels.map((id) => ({ id, name: id, reasoning: true }))
467
+ ).map(toPersistedModel);
468
+
469
+ store.setKey(provider.id, trimmed, {
470
+ baseUrl: provider.baseUrl,
471
+ api: provider.api,
472
+ models,
473
+ });
474
+
455
475
  ui.notify(
456
- `${provider.displayName} configured: \`${maskKeyForDisplay(trimmed)}\` (${provider.staticModels.length} models)`,
476
+ `${provider.displayName} configured: \`${maskKeyForDisplay(trimmed)}\` (${models.length} models, source: ${live.source}${live.error ? `, ${live.error}` : ""})`,
457
477
  "info",
458
478
  );
459
- return { providerId: provider.id, modelCount: provider.staticModels.length };
479
+ return { providerId: provider.id, modelCount: models.length };
460
480
  }
461
481
 
462
482
  async function configureLocal(
@@ -539,7 +559,23 @@ async function configureAssignments(
539
559
 
540
560
  // ─── Extension ───────────────────────────────────────────────────────────
541
561
 
562
+ // One-time global guard so a stray async rejection inside the wizard never kills the TUI.
563
+ let setupUnhandledGuard = false;
564
+ function installSetupUnhandledRejectionGuard(): void {
565
+ if (setupUnhandledGuard) return;
566
+ setupUnhandledGuard = true;
567
+ process.on("unhandledRejection", (reason) => {
568
+ const message = reason instanceof Error ? reason.message : String(reason);
569
+ try {
570
+ process.stderr.write(`[phi-setup] swallowed unhandledRejection: ${message}\n`);
571
+ } catch {
572
+ // no-op
573
+ }
574
+ });
575
+ }
576
+
542
577
  export default function setupExtension(pi: ExtensionAPI) {
578
+ installSetupUnhandledRejectionGuard();
543
579
  pi.registerCommand("setup", {
544
580
  description: "Phi Code setup wizard (refonte UX, replaces /phi-init)",
545
581
  handler: async (_args, ctx) => {
@@ -551,6 +587,7 @@ export default function setupExtension(pi: ExtensionAPI) {
551
587
  // empty file or missing, fine
552
588
  }
553
589
 
590
+ try {
554
591
  ui.notify(
555
592
  "**φ Phi Code Setup Wizard**\n\n" +
556
593
  "This wizard configures providers and assigns models to agent roles.\n" +
@@ -652,14 +689,21 @@ export default function setupExtension(pi: ExtensionAPI) {
652
689
  const provider = catalog[providerIndex];
653
690
 
654
691
  let result: { providerId: string; modelCount: number } | undefined;
655
- if (provider.id === "alibaba-codingplan") {
656
- result = await configureAlibaba(ui, store);
657
- } else if (provider.id === "opencode-go") {
658
- result = await configureOpenCodeGo(ui, store);
659
- } else if (provider.local) {
660
- result = await configureLocal(ui, store, provider);
661
- } else {
662
- result = await configureGenericCloud(ui, store, provider);
692
+ try {
693
+ if (provider.id === "alibaba-codingplan") {
694
+ result = await configureAlibaba(ui, store);
695
+ } else if (provider.id === "opencode-go") {
696
+ result = await configureOpenCodeGo(ui, store);
697
+ } else if (provider.local) {
698
+ result = await configureLocal(ui, store, provider);
699
+ } else {
700
+ result = await configureGenericCloud(ui, store, provider);
701
+ }
702
+ } catch (err) {
703
+ ui.notify(
704
+ `Provider configuration failed: ${err instanceof Error ? err.message : String(err)}`,
705
+ "error",
706
+ );
663
707
  }
664
708
 
665
709
  if (result) refreshAvailable();
@@ -681,12 +725,20 @@ export default function setupExtension(pi: ExtensionAPI) {
681
725
  "**Setup complete.**\n\n" +
682
726
  "Next steps:\n" +
683
727
  " - `/keys` to list/manage saved keys\n" +
728
+ " - `/models refresh` to re-fetch the catalog from each provider's API\n" +
684
729
  " - `/routing` to inspect routing\n" +
685
730
  " - `/agents` to list sub-agents\n" +
686
731
  " - `/skills` to list skills\n" +
687
732
  " - Edit `~/.phi/agent/models.json` or `routing.json` directly: hot-reload kicks in",
688
733
  "info",
689
734
  );
735
+ } catch (err) {
736
+ ui.setWidget("setup-status", undefined);
737
+ ui.notify(
738
+ `Setup wizard error: ${err instanceof Error ? err.message : String(err)}`,
739
+ "error",
740
+ );
741
+ }
690
742
  },
691
743
  });
692
744
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.75.3",
3
+ "version": "0.75.5",
4
4
  "description": "Coding agent CLI with persistent memory, sub-agents, intelligent routing, and orchestration",
5
5
  "type": "module",
6
6
  "piConfig": {