@hyav/pi-provider 0.1.7 → 0.2.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.
@@ -1,11 +1,11 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { LiveCheckDiagnostics } from "./live-check-manager.ts";
3
+ import type { PiCatalogModelMeta } from "./pi-model-metadata.ts";
3
4
  import type { PreflightAdapter, PreflightDiagnostics } from "./preflight-manager.ts";
4
5
  import type { StatusDiagnostics } from "./status-manager.ts";
5
6
  import type {
6
7
  ModelFieldSource,
7
8
  ModelMetadataStatus,
8
- ModelQualityScore,
9
9
  ProviderAdapter,
10
10
  ProviderCost,
11
11
  ProviderModelMetadata,
@@ -39,6 +39,10 @@ export interface StatusReportOptions {
39
39
  showLiveCheckScope?: boolean;
40
40
  modelMetadata?: ProviderModelMetadata;
41
41
  metadataStatus?: ModelMetadataStatus;
42
+ piCatalogMatch?: {
43
+ matchedModel?: PiCatalogModelMeta;
44
+ matchType: string;
45
+ };
42
46
  }
43
47
 
44
48
  interface ReportIssue {
@@ -91,7 +95,10 @@ function formatTokens(count: number | undefined): string {
91
95
 
92
96
  function formatNumber(value: number): string {
93
97
  if (!Number.isFinite(value)) return "unknown";
94
- return value.toFixed(2).replace(/\.?(0+)$/, "");
98
+ const normalized = Object.is(value, -0) ? 0 : value;
99
+ const formatted = normalized.toFixed(2).replace(/\.?(0+)$/, "");
100
+ if (formatted === "-0" || formatted === "") return "0";
101
+ return formatted;
95
102
  }
96
103
 
97
104
  function formatAge(now: number, timestamp: number): string {
@@ -130,89 +137,102 @@ function formatRate(value: number): string {
130
137
  return `$${value.toFixed(decimals).replace(/\.?(0+)$/, "")}`;
131
138
  }
132
139
 
133
- function formatPricing(model: ActiveModel, metadata?: ProviderModelMetadata): string {
140
+ const HEADER_LABEL_WIDTH = 10;
141
+ const CHECK_LABEL_WIDTH = 14;
142
+ const MODEL_LABEL_WIDTH = 14;
143
+
144
+ function formatHeaderRow(label: string, value: string): string {
145
+ return `${label.padEnd(HEADER_LABEL_WIDTH)}${value}`;
146
+ }
147
+
148
+ function formatCheckRow(label: string, value: string): string {
149
+ return ` ${label.padEnd(CHECK_LABEL_WIDTH)}${value}`;
150
+ }
151
+
152
+ function formatCheckDetail(value: string): string {
153
+ return ` ${"".padEnd(CHECK_LABEL_WIDTH)}${value}`;
154
+ }
155
+
156
+ function formatModelRow(label: string, value: string): string {
157
+ return ` ${label.padEnd(MODEL_LABEL_WIDTH)}${value}`;
158
+ }
159
+
160
+ function formatModelContinuation(value: string): string {
161
+ return formatModelRow("", value);
162
+ }
163
+
164
+ function formatCostRateLines(cost: ProviderCost): string[] {
165
+ const primary = [`input ${formatRate(cost.input)}`, `output ${formatRate(cost.output)}`];
166
+ const cache: string[] = [];
167
+ if (cost.cacheRead > 0) cache.push(`cache read ${formatRate(cost.cacheRead)}`);
168
+ if (cost.cacheWrite > 0) cache.push(`cache write ${formatRate(cost.cacheWrite)}`);
169
+ return [primary.join(" · "), ...(cache.length > 0 ? [cache.join(" · ")] : [])];
170
+ }
171
+
172
+ function formatPricingLines(
173
+ model: ActiveModel,
174
+ metadata: ProviderModelMetadata | undefined,
175
+ pricingSource: string,
176
+ ): string[] {
134
177
  const cost = model.cost;
135
178
  const knownFree = metadata?.pricing.known === true && cost !== undefined;
136
179
  if (
137
180
  !cost ||
138
181
  (!knownFree && cost.input === 0 && cost.output === 0 && cost.cacheRead === 0 && cost.cacheWrite === 0)
139
182
  ) {
140
- return "unavailable";
183
+ return [formatModelRow("Pricing", `unavailable${pricingSource}`)];
141
184
  }
142
- const rates = [`${formatRate(cost.input)} input`, `${formatRate(cost.output)} output`];
143
- if (cost.cacheRead > 0) rates.push(`${formatRate(cost.cacheRead)} cache read`);
144
- if (cost.cacheWrite > 0) rates.push(`${formatRate(cost.cacheWrite)} cache write`);
145
- return `${rates.join(" / ")} per 1M tokens`;
146
- }
147
-
148
- function formatPricingTier(tier: NonNullable<ProviderCost["tiers"]>[number]): string {
149
- const rates = [`${formatRate(tier.input)} input`, `${formatRate(tier.output)} output`];
150
- if (tier.cacheRead > 0) rates.push(`${formatRate(tier.cacheRead)} cache read`);
151
- if (tier.cacheWrite > 0) rates.push(`${formatRate(tier.cacheWrite)} cache write`);
152
- return `above ${formatTokens(tier.inputTokensAbove)} · ${rates.join(" / ")} per 1M tokens`;
185
+ const rateLines = formatCostRateLines(cost);
186
+ return [
187
+ formatModelRow("Pricing", rateLines[0] ?? "unavailable"),
188
+ ...rateLines.slice(1).map(formatModelContinuation),
189
+ formatModelContinuation(`per 1M tokens${pricingSource}`),
190
+ ];
153
191
  }
154
192
 
155
- function formatQuality(quality: ModelQualityScore[], status: ModelMetadataStatus | undefined, now: number): string[] {
156
- const scores = quality.filter(
157
- (score) => score.source === "artificial-analysis" && score.benchmark === "Artificial Analysis",
158
- );
159
- if (scores.length === 0) return [];
160
- const statusParts = [`Status: ${status?.state ?? "available"}`];
161
- if (status?.updatedAt !== undefined) statusParts.push(formatAge(now, status.updatedAt));
193
+ function formatPricingTierLines(tier: NonNullable<ProviderCost["tiers"]>[number]): string[] {
194
+ const rateLines = formatCostRateLines(tier);
162
195
  return [
163
- statusParts.join(" · "),
164
- `Source: ${status?.source ?? "AA/OpenRouter"}`,
165
- `Indices: ${scores.map((score) => `${score.category} ${formatNumber(score.value)}`).join(" · ")}`,
196
+ formatModelRow(`Tier >${formatTokens(tier.inputTokensAbove)}`, rateLines[0] ?? "unknown"),
197
+ ...rateLines.slice(1).map(formatModelContinuation),
198
+ formatModelContinuation("per 1M tokens"),
166
199
  ];
167
200
  }
168
201
 
169
202
  function formatModelFieldSource(source: ModelFieldSource | undefined): string {
170
- if (source === undefined) return "";
203
+ if (source === undefined || source === "provider") return "";
171
204
  const label =
172
205
  source === "native"
173
206
  ? "Pi native"
174
- : source === "provider"
175
- ? "Provider catalog"
176
- : source === "official"
177
- ? "OpenRouter"
178
- : source === "fallback"
179
- ? "Provider fallback"
180
- : "Pi default";
207
+ : source === "pi"
208
+ ? "Pi catalog"
209
+ : source === "fallback"
210
+ ? "Provider fallback"
211
+ : source === "normalized"
212
+ ? "Normalized catalog value"
213
+ : source === "mixed"
214
+ ? "mixed"
215
+ : "Pi default";
181
216
  return ` · ${label}`;
182
217
  }
183
218
 
184
219
  function formatPricingSource(metadata: ProviderModelMetadata): string {
185
220
  const source =
186
- metadata.pricing.source === "provider"
187
- ? "Provider catalog"
188
- : metadata.pricing.source === "fallback"
189
- ? "Provider fallback"
190
- : metadata.pricing.source === "official"
191
- ? "OpenRouter"
221
+ metadata.pricing.source === "fallback"
222
+ ? "Provider fallback"
223
+ : metadata.pricing.source === "pi"
224
+ ? "Pi catalog"
225
+ : metadata.pricing.source === "mixed"
226
+ ? "mixed"
192
227
  : metadata.pricing.source === "native"
193
228
  ? "Pi native"
194
229
  : undefined;
195
230
  const parts = source ? [source] : [];
196
231
  if (metadata.pricing.adjustment) parts.push(metadata.pricing.adjustment.label);
197
- if (metadata.pricing.known) parts.push("estimate");
232
+ if (metadata.pricing.known && metadata.pricing.source !== "provider") parts.push("estimate");
198
233
  return parts.length > 0 ? ` · ${parts.join(" · ")}` : "";
199
234
  }
200
235
 
201
- function formatQualitySource(
202
- quality: ModelQualityScore[] | undefined,
203
- status: ModelMetadataStatus | undefined,
204
- now: number,
205
- ): string[] {
206
- const scores = quality ? formatQuality(quality, status, now) : [];
207
- if (scores.length > 0) return scores;
208
- if (!status?.source) return [];
209
- return [
210
- status.source === "AA/OpenRouter"
211
- ? "Status: unavailable · no AA/OpenRouter metric"
212
- : "Status: unavailable · no public score",
213
- ];
214
- }
215
-
216
236
  const REASONING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
217
237
 
218
238
  function getSupportedReasoningLevels(model: ActiveModel): string[] {
@@ -224,10 +244,22 @@ function getSupportedReasoningLevels(model: ActiveModel): string[] {
224
244
  });
225
245
  }
226
246
 
227
- function formatReasoning(model: ActiveModel): string {
228
- if (!model.reasoning) return "not supported";
247
+ function formatThinkingLevelsLine(model: ActiveModel, metadata?: ProviderModelMetadata): string {
248
+ const fieldSources = metadata?.fieldSources;
249
+ if (model.reasoning === false) {
250
+ return formatModelRow("Thinking", `not supported${formatModelFieldSource(fieldSources?.reasoning)}`);
251
+ }
229
252
  const levels = getSupportedReasoningLevels(model);
230
- return levels.length > 0 ? `supported (${levels.join(", ")})` : "supported";
253
+ if (levels.length > 0) {
254
+ return formatModelRow(
255
+ "Thinking",
256
+ `${levels.join(", ")}${formatModelFieldSource(fieldSources?.thinkingLevelMap)}`,
257
+ );
258
+ }
259
+ if (model.reasoning === true) {
260
+ return formatModelRow("Thinking", `supported${formatModelFieldSource(fieldSources?.reasoning)}`);
261
+ }
262
+ return formatModelRow("Thinking", "unavailable");
231
263
  }
232
264
 
233
265
  export function resolveNativeProvider(modelRegistry: NativeProviderRegistry, providerId: string): NativeProviderLookup {
@@ -262,24 +294,43 @@ function formatCatalog(
262
294
  nativeProvider: NativeProvider | undefined,
263
295
  nativeLookupAvailable: boolean,
264
296
  now: number,
265
- ): { lines: string[]; issue: ReportIssue } {
297
+ ): { summary: string; detailLines: string[]; issue: ReportIssue } {
266
298
  if (!adapter) {
267
- if (!nativeLookupAvailable) return { lines: ["Status: not managed by Pi Provider"], issue: { level: "none" } };
268
- if (!nativeProvider) return { lines: ["Status: unavailable in Pi"], issue: { level: "none" } };
299
+ if (!nativeLookupAvailable) {
300
+ return { summary: "not managed by Pi Provider", detailLines: [], issue: { level: "none" } };
301
+ }
302
+ if (!nativeProvider) {
303
+ return { summary: "unavailable in Pi", detailLines: [], issue: { level: "none" } };
304
+ }
269
305
  const count = getNativeModelCount(nativeProvider);
306
+ const countStr = count === undefined ? "unknown" : `${count} ${count === 1 ? "model" : "models"}`;
270
307
  return {
271
- lines: ["Status: static · Pi native", `Models: ${count === undefined ? "unknown" : count}`],
308
+ summary: `static · Pi native · ${countStr}`,
309
+ detailLines: [],
272
310
  issue: { level: "none" },
273
311
  };
274
312
  }
275
313
  const catalog = adapter.catalog;
276
314
  const count = catalog?.modelCount ?? adapter.provider.models.length;
315
+ const countStr = `${count} ${count === 1 ? "model" : "models"}`;
277
316
  const source = catalog?.source ?? "static";
278
317
  const freshness = catalog?.lastError ? "stale" : catalog?.updatedAt !== undefined ? "fresh" : undefined;
279
- const statusParts = freshness ? [freshness, source] : [source];
318
+ const statusParts = freshness ? [freshness, source, countStr] : [source, countStr];
280
319
  if (catalog?.updatedAt !== undefined) statusParts.push(formatAge(now, catalog.updatedAt));
281
- const lines = [`Status: ${statusParts.join(" · ")}`, `Models: ${count}`];
282
- if (catalog?.lastError) lines.push(`Error: ${catalog.lastError}`);
320
+ const summary = statusParts.join(" · ");
321
+ const detailLines: string[] = [];
322
+ const rejectedCount = catalog?.rejectedCount ?? 0;
323
+ const duplicateCount = catalog?.duplicateCount ?? 0;
324
+ if (rejectedCount > 0 || duplicateCount > 0) {
325
+ detailLines.push(`Skipped: ${rejectedCount} invalid · ${duplicateCount} duplicate`);
326
+ }
327
+ if (catalog?.lastError) detailLines.push(`Error: ${catalog.lastError}`);
328
+ if (catalog?.lastError && catalog.nextRetryAt !== undefined) {
329
+ const failures = catalog.consecutiveFailures ?? 1;
330
+ detailLines.push(
331
+ `Retry: ${formatUntil(now, catalog.nextRetryAt)} · ${failures} consecutive failure${failures === 1 ? "" : "s"}`,
332
+ );
333
+ }
283
334
  const issue =
284
335
  catalog?.lastError !== undefined
285
336
  ? {
@@ -287,7 +338,7 @@ function formatCatalog(
287
338
  key: `catalog:${catalog.lastError}`,
288
339
  }
289
340
  : { level: "none" as const };
290
- return { lines, issue };
341
+ return { summary, detailLines, issue };
291
342
  }
292
343
 
293
344
  function classifyError(code: string, httpStatus: number | undefined): StatusWarningLevel {
@@ -315,8 +366,8 @@ function scopeIssue(issue: ReportIssue, scope: string): ReportIssue {
315
366
  return issue.level === "none" ? issue : { ...issue, key: `${scope}:${issue.key ?? issue.level}` };
316
367
  }
317
368
 
318
- function indentLines(lines: string[]): string[] {
319
- return lines.map((line) => (line === "" ? "" : ` ${line}`));
369
+ function stripSummaryPrefix(value: string, prefix: string): string {
370
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
320
371
  }
321
372
 
322
373
  function formatStatusAmount(entry: StatusAmountEntry): string {
@@ -332,116 +383,57 @@ function formatStatusEntry(entry: StatusEntry): string {
332
383
  return `${entry.label}: ${remaining}${entry.resetAt !== undefined ? ` · reset at ${formatDateTime(entry.resetAt)}` : ""}`;
333
384
  }
334
385
 
335
- function appendStatusReport(
336
- lines: string[],
386
+ function formatAccount(
337
387
  status: StatusAdapter | undefined,
338
388
  diagnostics: StatusDiagnostics | undefined,
339
389
  authConfigured: boolean,
340
390
  now: number,
341
- ): ReportIssue {
391
+ ): { summary: string; detailLines: string[]; issue: ReportIssue } {
342
392
  if (!status) {
343
- lines.push("Status: not supported");
344
- return { level: "none" };
393
+ return { summary: "Account: not supported", detailLines: [], issue: { level: "none" } };
345
394
  }
346
395
  if (!authConfigured) {
347
- lines.push("Status: unavailable · auth missing");
348
- return { level: "none" };
396
+ return { summary: "Account: unavailable · auth missing", detailLines: [], issue: { level: "none" } };
349
397
  }
398
+ let summary: string;
399
+ const detailLines: string[] = [];
400
+ let issue: ReportIssue = { level: "none" };
401
+
350
402
  if (diagnostics?.snapshot) {
351
403
  const expired = now - diagnostics.snapshot.updatedAt >= status.cacheTtlMs;
352
404
  const stale = diagnostics.lastError !== undefined || expired;
353
- lines.push(`Status: ${stale ? "stale" : "fresh"} · ${formatAge(now, diagnostics.snapshot.updatedAt)}`);
354
- for (const entry of diagnostics.snapshot.entries) lines.push(formatStatusEntry(entry));
355
- } else if (diagnostics?.pending) {
356
- lines.push("Status: checking");
357
- } else {
358
- lines.push("Status: unavailable");
359
- }
360
- if (!diagnostics?.lastError) return { level: "none" };
361
- const httpStatus =
362
- diagnostics.lastError.httpStatus !== undefined ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}` : "";
363
- lines.push(`Error: ${diagnostics.lastError.code}${httpStatus}`);
364
- if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
365
- lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
366
- }
367
- return errorIssue("status", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
368
- }
369
-
370
- function appendPreflightReport(
371
- lines: string[],
372
- preflight: PreflightAdapter | undefined,
373
- diagnostics: PreflightDiagnostics | undefined,
374
- nativePreflight: NativePreflightStatus | undefined,
375
- authConfigured: boolean,
376
- now: number,
377
- ): ReportIssue {
378
- if (!preflight) {
379
- if (!nativePreflight) {
380
- lines.push("Preflight: not configured");
381
- return { level: "none" };
382
- }
383
- if (!nativePreflight.providerAvailable) {
384
- lines.push("Preflight: failed · Pi provider unavailable");
385
- return { level: "hard", key: "preflight:provider-unavailable" };
386
- }
387
- if (!authConfigured) {
388
- lines.push("Preflight: native · provider/catalog · auth missing");
389
- return { level: "none" };
390
- }
391
- if (!nativePreflight.modelMatched) {
392
- lines.push("Preflight: failed · native/provider/auth/catalog");
393
- lines.push("Preflight detail: model not in Pi catalog");
394
- return { level: "hard", key: "preflight:model-not-in-catalog" };
395
- }
396
- lines.push("Preflight: native · provider/auth/catalog");
397
- return { level: "none" };
398
- }
399
- if (!authConfigured) {
400
- lines.push("Preflight: skipped · auth missing");
401
- return { level: "none" };
402
- }
403
- if (!diagnostics?.snapshot) {
404
- if (diagnostics?.pending) {
405
- lines.push("Preflight: checking");
406
- return { level: "none" };
407
- }
408
- if (diagnostics?.lastError) {
409
- const httpStatus =
410
- diagnostics.lastError.httpStatus !== undefined
411
- ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
412
- : "";
413
- lines.push(`Preflight: unavailable · error ${diagnostics.lastError.code}${httpStatus}`);
414
- if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
415
- lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
405
+ const freshness = stale ? "stale" : "fresh";
406
+ const age = formatAge(now, diagnostics.snapshot.updatedAt);
407
+ const entries = diagnostics.snapshot.entries;
408
+ if (entries.length === 1 && entries[0]?.kind === "amount") {
409
+ summary = `Account: ${freshness} · ${entries[0].label.toLowerCase()} ${formatStatusAmount(entries[0])} · ${age}`;
410
+ } else if (entries.length === 0) {
411
+ summary = `Account: ${freshness} · ${age}`;
412
+ } else {
413
+ summary = `Account: ${freshness} · ${age}`;
414
+ for (const entry of entries) {
415
+ detailLines.push(formatStatusEntry(entry));
416
416
  }
417
- return errorIssue("preflight", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
418
417
  }
419
- lines.push("Preflight: not checked");
420
- return { level: "none" };
418
+ } else if (diagnostics?.pending) {
419
+ summary = "Account: checking";
420
+ } else {
421
+ summary = "Account: unavailable";
421
422
  }
422
423
 
423
- const expired = now - diagnostics.snapshot.updatedAt >= preflight.cacheTtlMs;
424
- const stale = expired || diagnostics.lastError !== undefined;
425
- const state = diagnostics.snapshot.passed ? "passed" : "failed";
426
- const freshness = stale ? "stale" : "fresh";
427
- const checks = diagnostics.snapshot.checks.length > 0 ? ` · ${diagnostics.snapshot.checks.join("/")}` : "";
428
- lines.push(`Preflight: ${state}${checks} · ${freshness} · ${formatAge(now, diagnostics.snapshot.updatedAt)}`);
429
- if (diagnostics.lastError) {
424
+ if (diagnostics?.lastError) {
430
425
  const httpStatus =
431
426
  diagnostics.lastError.httpStatus !== undefined
432
427
  ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
433
428
  : "";
434
- lines.push(`Preflight error: ${diagnostics.lastError.code}${httpStatus}`);
429
+ detailLines.push(`Error: ${diagnostics.lastError.code}${httpStatus}`);
435
430
  if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
436
- lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
431
+ detailLines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
437
432
  }
433
+ issue = errorIssue("status", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
438
434
  }
439
- if (!diagnostics.snapshot.passed) {
440
- return { level: "hard", key: "preflight:failed" };
441
- }
442
- return diagnostics.lastError
443
- ? errorIssue("preflight", diagnostics.lastError.code, diagnostics.lastError.httpStatus)
444
- : { level: "none" };
435
+
436
+ return { summary, detailLines, issue };
445
437
  }
446
438
 
447
439
  function formatLatency(latencyMs: number): string {
@@ -470,54 +462,154 @@ function formatHttpStatus(status: number): string {
470
462
  return `HTTP ${status}${labels[status] ? ` ${labels[status]}` : ""}`;
471
463
  }
472
464
 
473
- function appendLiveCheckReport(
474
- lines: string[],
475
- diagnostics: LiveCheckDiagnostics | undefined,
465
+ function formatHealth(
466
+ preflight: PreflightAdapter | undefined,
467
+ preflightDiagnostics: PreflightDiagnostics | undefined,
468
+ nativePreflight: NativePreflightStatus | undefined,
469
+ liveCheckDiagnostics: LiveCheckDiagnostics | undefined,
476
470
  authConfigured: boolean,
477
471
  now: number,
478
- options: { requested?: boolean; showScope?: boolean } = {},
479
- ): ReportIssue {
480
- if (!authConfigured) {
481
- lines.push("Availability: skipped · auth missing");
482
- return { level: "none" };
483
- }
484
- if (options.showScope) {
485
- lines.push("Live check scope: streamSimple() · Pi Provider tuners only (other hooks not replayed)");
472
+ options: { liveCheckRequested?: boolean; showLiveCheckScope?: boolean } = {},
473
+ ): {
474
+ preflightSummary: string;
475
+ availabilitySummary: string;
476
+ preflightDetailLines: string[];
477
+ availabilityDetailLines: string[];
478
+ preflightIssue: ReportIssue;
479
+ liveCheckIssue: ReportIssue;
480
+ } {
481
+ let preflightSummary: string;
482
+ const preflightDetailLines: string[] = [];
483
+ let preflightIssue: ReportIssue = { level: "none" };
484
+
485
+ if (!preflight) {
486
+ if (!nativePreflight) {
487
+ preflightSummary = authConfigured ? "preflight not configured" : "preflight skipped · auth missing";
488
+ } else if (!nativePreflight.providerAvailable) {
489
+ preflightSummary = "preflight failed · Pi provider unavailable";
490
+ preflightIssue = { level: "hard", key: "preflight:provider-unavailable" };
491
+ } else if (!authConfigured) {
492
+ preflightSummary = "preflight native · provider/catalog · auth missing";
493
+ } else if (!nativePreflight.modelMatched) {
494
+ preflightSummary = "preflight failed · native/provider/auth/catalog";
495
+ preflightDetailLines.push("Preflight detail: model not in Pi catalog");
496
+ preflightIssue = { level: "hard", key: "preflight:model-not-in-catalog" };
497
+ } else {
498
+ preflightSummary = "preflight native · provider/auth/catalog";
499
+ }
500
+ } else if (!authConfigured) {
501
+ preflightSummary = "preflight skipped · auth missing";
502
+ } else if (!preflightDiagnostics?.snapshot) {
503
+ if (preflightDiagnostics?.pending) {
504
+ preflightSummary = "preflight checking";
505
+ } else if (preflightDiagnostics?.lastError) {
506
+ const httpStatus =
507
+ preflightDiagnostics.lastError.httpStatus !== undefined
508
+ ? ` · ${formatHttpStatus(preflightDiagnostics.lastError.httpStatus)}`
509
+ : "";
510
+ preflightSummary = `preflight unavailable · error ${preflightDiagnostics.lastError.code}${httpStatus}`;
511
+ if (preflightDiagnostics.lastError.retryAt !== undefined && preflightDiagnostics.lastError.retryAt > now) {
512
+ preflightDetailLines.push(`Retry: ${formatUntil(now, preflightDiagnostics.lastError.retryAt)}`);
513
+ }
514
+ preflightIssue = errorIssue(
515
+ "preflight",
516
+ preflightDiagnostics.lastError.code,
517
+ preflightDiagnostics.lastError.httpStatus,
518
+ );
519
+ } else {
520
+ preflightSummary = "preflight not checked";
521
+ }
522
+ } else {
523
+ const expired = now - preflightDiagnostics.snapshot.updatedAt >= preflight.cacheTtlMs;
524
+ const stale = expired || preflightDiagnostics.lastError !== undefined;
525
+ const state = preflightDiagnostics.snapshot.passed ? "passed" : "failed";
526
+ const freshness = stale ? "stale" : "fresh";
527
+ const checks =
528
+ preflightDiagnostics.snapshot.checks.length > 0 ? ` · ${preflightDiagnostics.snapshot.checks.join("/")}` : "";
529
+ preflightSummary = `preflight ${state}${checks} · ${freshness} · ${formatAge(now, preflightDiagnostics.snapshot.updatedAt)}`;
530
+ if (preflightDiagnostics.lastError) {
531
+ const httpStatus =
532
+ preflightDiagnostics.lastError.httpStatus !== undefined
533
+ ? ` · ${formatHttpStatus(preflightDiagnostics.lastError.httpStatus)}`
534
+ : "";
535
+ preflightDetailLines.push(`Preflight error: ${preflightDiagnostics.lastError.code}${httpStatus}`);
536
+ if (preflightDiagnostics.lastError.retryAt !== undefined && preflightDiagnostics.lastError.retryAt > now) {
537
+ preflightDetailLines.push(`Retry: ${formatUntil(now, preflightDiagnostics.lastError.retryAt)}`);
538
+ }
539
+ }
540
+ if (!preflightDiagnostics.snapshot.passed) {
541
+ preflightIssue = { level: "hard", key: "preflight:failed" };
542
+ } else if (preflightDiagnostics.lastError) {
543
+ preflightIssue = errorIssue(
544
+ "preflight",
545
+ preflightDiagnostics.lastError.code,
546
+ preflightDiagnostics.lastError.httpStatus,
547
+ );
548
+ }
486
549
  }
487
- if (diagnostics?.pending) lines.push("Availability: checking");
488
- else if (!diagnostics?.snapshot && !diagnostics?.lastError) lines.push("Availability: not checked");
489
- else if (!diagnostics?.snapshot && diagnostics.lastError) {
550
+
551
+ let availabilitySummary: string;
552
+ const liveCheckDetailLines: string[] = [];
553
+ let liveCheckIssue: ReportIssue = { level: "none" };
554
+
555
+ if (!authConfigured) {
556
+ availabilitySummary = "availability skipped · auth missing";
557
+ } else if (liveCheckDiagnostics?.pending) {
558
+ availabilitySummary = "availability checking";
559
+ } else if (!liveCheckDiagnostics?.snapshot && !liveCheckDiagnostics?.lastError) {
560
+ availabilitySummary = "availability not checked";
561
+ } else if (!liveCheckDiagnostics?.snapshot && liveCheckDiagnostics.lastError) {
490
562
  const status =
491
- diagnostics.lastError.httpStatus !== undefined
492
- ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
563
+ liveCheckDiagnostics.lastError.httpStatus !== undefined
564
+ ? ` · ${formatHttpStatus(liveCheckDiagnostics.lastError.httpStatus)}`
493
565
  : "";
494
- lines.push(`Availability: failed${status}`);
495
- lines.push(`Live check error: ${diagnostics.lastError.code}`);
496
- if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
497
- lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
566
+ availabilitySummary = `availability failed${status}`;
567
+ liveCheckDetailLines.push(`Live check error: ${liveCheckDiagnostics.lastError.code}`);
568
+ if (liveCheckDiagnostics.lastError.retryAt !== undefined && liveCheckDiagnostics.lastError.retryAt > now) {
569
+ liveCheckDetailLines.push(`Retry: ${formatUntil(now, liveCheckDiagnostics.lastError.retryAt)}`);
498
570
  }
499
- return options.requested ? { level: "hard", key: `live-check:${diagnostics.lastError.code}` } : { level: "soft" };
500
- } else if (diagnostics?.snapshot) {
501
- const stale = diagnostics.lastError !== undefined;
502
- lines.push(`Availability: ${stale ? "stale" : "verified"} · ${formatAge(now, diagnostics.snapshot.checkedAt)}`);
571
+ liveCheckIssue = options.liveCheckRequested
572
+ ? { level: "hard", key: `live-check:${liveCheckDiagnostics.lastError.code}` }
573
+ : { level: "soft" };
574
+ } else if (liveCheckDiagnostics?.snapshot) {
575
+ const stale = liveCheckDiagnostics.lastError !== undefined;
576
+ availabilitySummary = `availability ${stale ? "stale" : "verified"} · ${formatAge(now, liveCheckDiagnostics.snapshot.checkedAt)}`;
503
577
  const httpStatus =
504
- diagnostics.snapshot.httpStatus !== undefined
505
- ? formatHttpStatus(diagnostics.snapshot.httpStatus)
578
+ liveCheckDiagnostics.snapshot.httpStatus !== undefined
579
+ ? formatHttpStatus(liveCheckDiagnostics.snapshot.httpStatus)
506
580
  : "HTTP status unknown";
507
- lines.push(
508
- `Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(diagnostics.snapshot.latencyMs)}`,
581
+ liveCheckDetailLines.push(
582
+ `Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(liveCheckDiagnostics.snapshot.latencyMs)}`,
509
583
  );
510
- if (diagnostics.lastError) {
511
- lines.push(`Live check error: ${diagnostics.lastError.code}`);
512
- if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
513
- lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
584
+ if (liveCheckDiagnostics.lastError) {
585
+ liveCheckDetailLines.push(`Live check error: ${liveCheckDiagnostics.lastError.code}`);
586
+ if (liveCheckDiagnostics.lastError.retryAt !== undefined && liveCheckDiagnostics.lastError.retryAt > now) {
587
+ liveCheckDetailLines.push(`Retry: ${formatUntil(now, liveCheckDiagnostics.lastError.retryAt)}`);
514
588
  }
515
- return options.requested
516
- ? { level: "hard", key: `live-check:${diagnostics.lastError.code}` }
589
+ liveCheckIssue = options.liveCheckRequested
590
+ ? { level: "hard", key: `live-check:${liveCheckDiagnostics.lastError.code}` }
517
591
  : { level: "soft" };
518
592
  }
593
+ } else {
594
+ availabilitySummary = "availability not checked";
595
+ }
596
+
597
+ const availabilityDetailLines: string[] = [];
598
+ if (options.showLiveCheckScope) {
599
+ availabilityDetailLines.push(
600
+ "Live check scope: streamSimple() · Pi Provider tuners only (other hooks not replayed)",
601
+ );
519
602
  }
520
- return { level: "none" };
603
+ availabilityDetailLines.push(...liveCheckDetailLines);
604
+
605
+ return {
606
+ preflightSummary,
607
+ availabilitySummary,
608
+ preflightDetailLines,
609
+ availabilityDetailLines,
610
+ preflightIssue,
611
+ liveCheckIssue,
612
+ };
521
613
  }
522
614
 
523
615
  export function formatProviderStatus(
@@ -537,51 +629,57 @@ export function formatProviderStatus(
537
629
  ): { report: string; warningKey?: string; warningLevel: StatusWarningLevel } {
538
630
  const catalog = formatCatalog(provider, nativeProvider, nativeLookupAvailable, now);
539
631
  const catalogIssue = scopeIssue(catalog.issue, `catalog:${model.provider}`);
540
- const healthLines: string[] = [];
541
- const preflightIssue = scopeIssue(
542
- appendPreflightReport(healthLines, preflight, preflightDiagnostics, nativePreflight, auth.configured, now),
543
- `preflight:${model.provider}/${model.id}`,
544
- );
545
- const liveCheckIssue = scopeIssue(
546
- appendLiveCheckReport(healthLines, liveCheckDiagnostics, auth.configured, now, {
547
- requested: options.liveCheckRequested,
548
- showScope: options.showLiveCheckScope,
549
- }),
550
- `live-check:${model.provider}/${model.id}`,
551
- );
552
- const accountLines: string[] = [];
553
- const statusIssue = scopeIssue(
554
- appendStatusReport(accountLines, status, diagnostics, auth.configured, now),
555
- `status:${model.provider}`,
632
+ const health = formatHealth(
633
+ preflight,
634
+ preflightDiagnostics,
635
+ nativePreflight,
636
+ liveCheckDiagnostics,
637
+ auth.configured,
638
+ now,
639
+ {
640
+ liveCheckRequested: options.liveCheckRequested,
641
+ showLiveCheckScope: options.showLiveCheckScope,
642
+ },
556
643
  );
644
+ const preflightIssue = scopeIssue(health.preflightIssue, `preflight:${model.provider}/${model.id}`);
645
+ const liveCheckIssue = scopeIssue(health.liveCheckIssue, `live-check:${model.provider}/${model.id}`);
646
+ const account = formatAccount(status, diagnostics, auth.configured, now);
647
+ const statusIssue = scopeIssue(account.issue, `status:${model.provider}`);
557
648
  const fieldSources = options.modelMetadata?.fieldSources;
558
- const qualityLines = formatQualitySource(options.modelMetadata?.quality, options.metadataStatus, now);
559
649
  const pricingSource = options.modelMetadata?.pricing ? formatPricingSource(options.modelMetadata) : "";
560
650
  const lines = [
561
- `Provider: ${model.provider}`,
562
- `Model: ${model.id}`,
563
- `Auth: ${auth.configured ? `configured${auth.source ? ` (${auth.source})` : ""}` : "missing"}`,
564
- "",
565
- "Catalog:",
566
- ...indentLines(catalog.lines),
567
- "",
568
- "Health:",
569
- ...indentLines(healthLines),
651
+ formatHeaderRow("Provider:", model.provider),
652
+ formatHeaderRow("Model:", model.id),
653
+ formatHeaderRow("Auth:", auth.configured ? `configured${auth.source ? ` (${auth.source})` : ""}` : "missing"),
570
654
  "",
571
- "Account:",
572
- ...indentLines(accountLines),
655
+ "Checks:",
656
+ formatCheckRow("Catalog", stripSummaryPrefix(catalog.summary, "Catalog: ")),
657
+ ...catalog.detailLines.map(formatCheckDetail),
658
+ formatCheckRow("Preflight", stripSummaryPrefix(health.preflightSummary, "preflight ")),
659
+ ...health.preflightDetailLines.map(formatCheckDetail),
660
+ formatCheckRow("Availability", stripSummaryPrefix(health.availabilitySummary, "availability ")),
661
+ ...health.availabilityDetailLines.map(formatCheckDetail),
662
+ formatCheckRow("Account", stripSummaryPrefix(account.summary, "Account: ")),
663
+ ...account.detailLines.map(formatCheckDetail),
573
664
  "",
574
665
  "Model details:",
575
- ` API: ${model.api ?? provider?.provider.api ?? "managed by Pi"}`,
576
- ` Endpoint: ${model.baseUrl ?? provider?.provider.baseUrl ?? "managed by Pi"}`,
577
- ` Context: ${formatTokens(model.contextWindow)}${formatModelFieldSource(fieldSources?.contextWindow)}`,
578
- ` Max output: ${formatTokens(model.maxTokens)}${formatModelFieldSource(fieldSources?.maxTokens)}`,
579
- ` Input: ${model.input?.join(", ") || "unknown"}${formatModelFieldSource(fieldSources?.input)}`,
580
- ` Reasoning: ${formatReasoning(model)}${formatModelFieldSource(fieldSources?.reasoning)}`,
581
- ` Pricing: ${formatPricing(model, options.modelMetadata)}${pricingSource}`,
582
- ...(model.cost?.tiers?.map((tier) => ` Pricing tier: ${formatPricingTier(tier)}`) ?? []),
583
- ...(options.modelMetadata?.pricing?.note ? [` Pricing note: ${options.modelMetadata.pricing.note}`] : []),
584
- ...(qualityLines.length > 0 ? ["", "Quality:", ...indentLines(qualityLines)] : []),
666
+ formatModelRow("API", model.api ?? provider?.provider.api ?? "managed by Pi"),
667
+ formatModelRow("Endpoint", model.baseUrl ?? provider?.provider.baseUrl ?? "managed by Pi"),
668
+ formatModelRow(
669
+ "Context",
670
+ `${formatTokens(model.contextWindow)}${formatModelFieldSource(fieldSources?.contextWindow)}`,
671
+ ),
672
+ formatModelRow(
673
+ "Max output",
674
+ `${formatTokens(model.maxTokens)}${formatModelFieldSource(fieldSources?.maxTokens)}`,
675
+ ),
676
+ formatModelRow("Input", `${model.input?.join(", ") || "unknown"}${formatModelFieldSource(fieldSources?.input)}`),
677
+ formatThinkingLevelsLine(model, options.modelMetadata),
678
+ ...formatPricingLines(model, options.modelMetadata, pricingSource),
679
+ ...(model.cost?.tiers?.flatMap(formatPricingTierLines) ?? []),
680
+ ...(options.modelMetadata?.pricing?.note
681
+ ? [formatModelRow("Pricing note", options.modelMetadata.pricing.note)]
682
+ : []),
585
683
  ];
586
684
  const issue = combineIssues(catalogIssue, preflightIssue, liveCheckIssue, statusIssue);
587
685
  return {