@hyav/pi-provider 0.1.8 → 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.
- package/CHANGELOG.md +11 -0
- package/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/core/adapter-extensions.ts +9 -2
- package/core/adapter-protocol.ts +4 -2
- package/core/catalog-preflight.ts +14 -1
- package/core/host.ts +43 -75
- package/core/model-catalog.ts +13 -1
- package/core/opencode-preflight.ts +6 -0
- package/core/pi-model-metadata.ts +739 -0
- package/core/provider-registration.ts +75 -78
- package/core/public-adapters.ts +9 -1
- package/core/runtime-config.ts +18 -38
- package/core/runtime-entry.ts +11 -6
- package/core/runtime.ts +24 -104
- package/core/status-report.ts +316 -236
- package/core/types.ts +28 -19
- package/index.ts +30 -21
- package/package.json +3 -1
- package/preflight/charm-hyper.ts +6 -2
- package/preflight/deepseek.ts +10 -1
- package/preflight/github-copilot.ts +4 -0
- package/preflight/google.ts +10 -1
- package/preflight/groq.ts +10 -1
- package/preflight/openai-codex.ts +10 -1
- package/preflight/openrouter.ts +11 -2
- package/preflight/vercel-ai-gateway.ts +11 -1
- package/preflight/xai.ts +18 -4
- package/providers/charm-hyper/oauth.ts +17 -11
- package/providers/charm-hyper.ts +26 -12
- package/status/huggingface.ts +2 -1
- package/status/openrouter.ts +8 -2
- package/status/vercel-ai-gateway.ts +1 -1
- package/core/official-pricing.ts +0 -923
package/core/status-report.ts
CHANGED
|
@@ -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
|
-
|
|
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,91 +137,102 @@ function formatRate(value: number): string {
|
|
|
130
137
|
return `$${value.toFixed(decimals).replace(/\.?(0+)$/, "")}`;
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
156
|
-
const
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 === "
|
|
175
|
-
? "
|
|
176
|
-
: source === "
|
|
177
|
-
? "
|
|
178
|
-
: source === "
|
|
179
|
-
? "
|
|
180
|
-
: source === "
|
|
181
|
-
? "
|
|
207
|
+
: source === "pi"
|
|
208
|
+
? "Pi catalog"
|
|
209
|
+
: source === "fallback"
|
|
210
|
+
? "Provider fallback"
|
|
211
|
+
: source === "normalized"
|
|
212
|
+
? "Normalized catalog value"
|
|
213
|
+
: source === "mixed"
|
|
214
|
+
? "mixed"
|
|
182
215
|
: "Pi default";
|
|
183
216
|
return ` · ${label}`;
|
|
184
217
|
}
|
|
185
218
|
|
|
186
219
|
function formatPricingSource(metadata: ProviderModelMetadata): string {
|
|
187
220
|
const source =
|
|
188
|
-
metadata.pricing.source === "
|
|
189
|
-
? "Provider
|
|
190
|
-
: metadata.pricing.source === "
|
|
191
|
-
? "
|
|
192
|
-
: metadata.pricing.source === "
|
|
193
|
-
? "
|
|
221
|
+
metadata.pricing.source === "fallback"
|
|
222
|
+
? "Provider fallback"
|
|
223
|
+
: metadata.pricing.source === "pi"
|
|
224
|
+
? "Pi catalog"
|
|
225
|
+
: metadata.pricing.source === "mixed"
|
|
226
|
+
? "mixed"
|
|
194
227
|
: metadata.pricing.source === "native"
|
|
195
228
|
? "Pi native"
|
|
196
229
|
: undefined;
|
|
197
230
|
const parts = source ? [source] : [];
|
|
198
231
|
if (metadata.pricing.adjustment) parts.push(metadata.pricing.adjustment.label);
|
|
199
|
-
if (metadata.pricing.known) parts.push("estimate");
|
|
232
|
+
if (metadata.pricing.known && metadata.pricing.source !== "provider") parts.push("estimate");
|
|
200
233
|
return parts.length > 0 ? ` · ${parts.join(" · ")}` : "";
|
|
201
234
|
}
|
|
202
235
|
|
|
203
|
-
function formatQualitySource(
|
|
204
|
-
quality: ModelQualityScore[] | undefined,
|
|
205
|
-
status: ModelMetadataStatus | undefined,
|
|
206
|
-
now: number,
|
|
207
|
-
): string[] {
|
|
208
|
-
const scores = quality ? formatQuality(quality, status, now) : [];
|
|
209
|
-
if (scores.length > 0) return scores;
|
|
210
|
-
if (!status?.source) return [];
|
|
211
|
-
return [
|
|
212
|
-
status.source === "AA/OpenRouter"
|
|
213
|
-
? "Status: unavailable · no AA/OpenRouter metric"
|
|
214
|
-
: "Status: unavailable · no public score",
|
|
215
|
-
];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
236
|
const REASONING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
219
237
|
|
|
220
238
|
function getSupportedReasoningLevels(model: ActiveModel): string[] {
|
|
@@ -226,10 +244,22 @@ function getSupportedReasoningLevels(model: ActiveModel): string[] {
|
|
|
226
244
|
});
|
|
227
245
|
}
|
|
228
246
|
|
|
229
|
-
function
|
|
230
|
-
|
|
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
|
+
}
|
|
231
252
|
const levels = getSupportedReasoningLevels(model);
|
|
232
|
-
|
|
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");
|
|
233
263
|
}
|
|
234
264
|
|
|
235
265
|
export function resolveNativeProvider(modelRegistry: NativeProviderRegistry, providerId: string): NativeProviderLookup {
|
|
@@ -264,32 +294,40 @@ function formatCatalog(
|
|
|
264
294
|
nativeProvider: NativeProvider | undefined,
|
|
265
295
|
nativeLookupAvailable: boolean,
|
|
266
296
|
now: number,
|
|
267
|
-
): {
|
|
297
|
+
): { summary: string; detailLines: string[]; issue: ReportIssue } {
|
|
268
298
|
if (!adapter) {
|
|
269
|
-
if (!nativeLookupAvailable)
|
|
270
|
-
|
|
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
|
+
}
|
|
271
305
|
const count = getNativeModelCount(nativeProvider);
|
|
306
|
+
const countStr = count === undefined ? "unknown" : `${count} ${count === 1 ? "model" : "models"}`;
|
|
272
307
|
return {
|
|
273
|
-
|
|
308
|
+
summary: `static · Pi native · ${countStr}`,
|
|
309
|
+
detailLines: [],
|
|
274
310
|
issue: { level: "none" },
|
|
275
311
|
};
|
|
276
312
|
}
|
|
277
313
|
const catalog = adapter.catalog;
|
|
278
314
|
const count = catalog?.modelCount ?? adapter.provider.models.length;
|
|
315
|
+
const countStr = `${count} ${count === 1 ? "model" : "models"}`;
|
|
279
316
|
const source = catalog?.source ?? "static";
|
|
280
317
|
const freshness = catalog?.lastError ? "stale" : catalog?.updatedAt !== undefined ? "fresh" : undefined;
|
|
281
|
-
const statusParts = freshness ? [freshness, source] : [source];
|
|
318
|
+
const statusParts = freshness ? [freshness, source, countStr] : [source, countStr];
|
|
282
319
|
if (catalog?.updatedAt !== undefined) statusParts.push(formatAge(now, catalog.updatedAt));
|
|
283
|
-
const
|
|
320
|
+
const summary = statusParts.join(" · ");
|
|
321
|
+
const detailLines: string[] = [];
|
|
284
322
|
const rejectedCount = catalog?.rejectedCount ?? 0;
|
|
285
323
|
const duplicateCount = catalog?.duplicateCount ?? 0;
|
|
286
324
|
if (rejectedCount > 0 || duplicateCount > 0) {
|
|
287
|
-
|
|
325
|
+
detailLines.push(`Skipped: ${rejectedCount} invalid · ${duplicateCount} duplicate`);
|
|
288
326
|
}
|
|
289
|
-
if (catalog?.lastError)
|
|
327
|
+
if (catalog?.lastError) detailLines.push(`Error: ${catalog.lastError}`);
|
|
290
328
|
if (catalog?.lastError && catalog.nextRetryAt !== undefined) {
|
|
291
329
|
const failures = catalog.consecutiveFailures ?? 1;
|
|
292
|
-
|
|
330
|
+
detailLines.push(
|
|
293
331
|
`Retry: ${formatUntil(now, catalog.nextRetryAt)} · ${failures} consecutive failure${failures === 1 ? "" : "s"}`,
|
|
294
332
|
);
|
|
295
333
|
}
|
|
@@ -300,7 +338,7 @@ function formatCatalog(
|
|
|
300
338
|
key: `catalog:${catalog.lastError}`,
|
|
301
339
|
}
|
|
302
340
|
: { level: "none" as const };
|
|
303
|
-
return {
|
|
341
|
+
return { summary, detailLines, issue };
|
|
304
342
|
}
|
|
305
343
|
|
|
306
344
|
function classifyError(code: string, httpStatus: number | undefined): StatusWarningLevel {
|
|
@@ -328,8 +366,8 @@ function scopeIssue(issue: ReportIssue, scope: string): ReportIssue {
|
|
|
328
366
|
return issue.level === "none" ? issue : { ...issue, key: `${scope}:${issue.key ?? issue.level}` };
|
|
329
367
|
}
|
|
330
368
|
|
|
331
|
-
function
|
|
332
|
-
return
|
|
369
|
+
function stripSummaryPrefix(value: string, prefix: string): string {
|
|
370
|
+
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
333
371
|
}
|
|
334
372
|
|
|
335
373
|
function formatStatusAmount(entry: StatusAmountEntry): string {
|
|
@@ -345,116 +383,57 @@ function formatStatusEntry(entry: StatusEntry): string {
|
|
|
345
383
|
return `${entry.label}: ${remaining}${entry.resetAt !== undefined ? ` · reset at ${formatDateTime(entry.resetAt)}` : ""}`;
|
|
346
384
|
}
|
|
347
385
|
|
|
348
|
-
function
|
|
349
|
-
lines: string[],
|
|
386
|
+
function formatAccount(
|
|
350
387
|
status: StatusAdapter | undefined,
|
|
351
388
|
diagnostics: StatusDiagnostics | undefined,
|
|
352
389
|
authConfigured: boolean,
|
|
353
390
|
now: number,
|
|
354
|
-
): ReportIssue {
|
|
391
|
+
): { summary: string; detailLines: string[]; issue: ReportIssue } {
|
|
355
392
|
if (!status) {
|
|
356
|
-
|
|
357
|
-
return { level: "none" };
|
|
393
|
+
return { summary: "Account: not supported", detailLines: [], issue: { level: "none" } };
|
|
358
394
|
}
|
|
359
395
|
if (!authConfigured) {
|
|
360
|
-
|
|
361
|
-
return { level: "none" };
|
|
396
|
+
return { summary: "Account: unavailable · auth missing", detailLines: [], issue: { level: "none" } };
|
|
362
397
|
}
|
|
398
|
+
let summary: string;
|
|
399
|
+
const detailLines: string[] = [];
|
|
400
|
+
let issue: ReportIssue = { level: "none" };
|
|
401
|
+
|
|
363
402
|
if (diagnostics?.snapshot) {
|
|
364
403
|
const expired = now - diagnostics.snapshot.updatedAt >= status.cacheTtlMs;
|
|
365
404
|
const stale = diagnostics.lastError !== undefined || expired;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
|
|
378
|
-
lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
|
|
379
|
-
}
|
|
380
|
-
return errorIssue("status", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function appendPreflightReport(
|
|
384
|
-
lines: string[],
|
|
385
|
-
preflight: PreflightAdapter | undefined,
|
|
386
|
-
diagnostics: PreflightDiagnostics | undefined,
|
|
387
|
-
nativePreflight: NativePreflightStatus | undefined,
|
|
388
|
-
authConfigured: boolean,
|
|
389
|
-
now: number,
|
|
390
|
-
): ReportIssue {
|
|
391
|
-
if (!preflight) {
|
|
392
|
-
if (!nativePreflight) {
|
|
393
|
-
lines.push("Preflight: not configured");
|
|
394
|
-
return { level: "none" };
|
|
395
|
-
}
|
|
396
|
-
if (!nativePreflight.providerAvailable) {
|
|
397
|
-
lines.push("Preflight: failed · Pi provider unavailable");
|
|
398
|
-
return { level: "hard", key: "preflight:provider-unavailable" };
|
|
399
|
-
}
|
|
400
|
-
if (!authConfigured) {
|
|
401
|
-
lines.push("Preflight: native · provider/catalog · auth missing");
|
|
402
|
-
return { level: "none" };
|
|
403
|
-
}
|
|
404
|
-
if (!nativePreflight.modelMatched) {
|
|
405
|
-
lines.push("Preflight: failed · native/provider/auth/catalog");
|
|
406
|
-
lines.push("Preflight detail: model not in Pi catalog");
|
|
407
|
-
return { level: "hard", key: "preflight:model-not-in-catalog" };
|
|
408
|
-
}
|
|
409
|
-
lines.push("Preflight: native · provider/auth/catalog");
|
|
410
|
-
return { level: "none" };
|
|
411
|
-
}
|
|
412
|
-
if (!authConfigured) {
|
|
413
|
-
lines.push("Preflight: skipped · auth missing");
|
|
414
|
-
return { level: "none" };
|
|
415
|
-
}
|
|
416
|
-
if (!diagnostics?.snapshot) {
|
|
417
|
-
if (diagnostics?.pending) {
|
|
418
|
-
lines.push("Preflight: checking");
|
|
419
|
-
return { level: "none" };
|
|
420
|
-
}
|
|
421
|
-
if (diagnostics?.lastError) {
|
|
422
|
-
const httpStatus =
|
|
423
|
-
diagnostics.lastError.httpStatus !== undefined
|
|
424
|
-
? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
|
|
425
|
-
: "";
|
|
426
|
-
lines.push(`Preflight: unavailable · error ${diagnostics.lastError.code}${httpStatus}`);
|
|
427
|
-
if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
|
|
428
|
-
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));
|
|
429
416
|
}
|
|
430
|
-
return errorIssue("preflight", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
|
|
431
417
|
}
|
|
432
|
-
|
|
433
|
-
|
|
418
|
+
} else if (diagnostics?.pending) {
|
|
419
|
+
summary = "Account: checking";
|
|
420
|
+
} else {
|
|
421
|
+
summary = "Account: unavailable";
|
|
434
422
|
}
|
|
435
423
|
|
|
436
|
-
|
|
437
|
-
const stale = expired || diagnostics.lastError !== undefined;
|
|
438
|
-
const state = diagnostics.snapshot.passed ? "passed" : "failed";
|
|
439
|
-
const freshness = stale ? "stale" : "fresh";
|
|
440
|
-
const checks = diagnostics.snapshot.checks.length > 0 ? ` · ${diagnostics.snapshot.checks.join("/")}` : "";
|
|
441
|
-
lines.push(`Preflight: ${state}${checks} · ${freshness} · ${formatAge(now, diagnostics.snapshot.updatedAt)}`);
|
|
442
|
-
if (diagnostics.lastError) {
|
|
424
|
+
if (diagnostics?.lastError) {
|
|
443
425
|
const httpStatus =
|
|
444
426
|
diagnostics.lastError.httpStatus !== undefined
|
|
445
427
|
? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
|
|
446
428
|
: "";
|
|
447
|
-
|
|
429
|
+
detailLines.push(`Error: ${diagnostics.lastError.code}${httpStatus}`);
|
|
448
430
|
if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
|
|
449
|
-
|
|
431
|
+
detailLines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
|
|
450
432
|
}
|
|
433
|
+
issue = errorIssue("status", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
|
|
451
434
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
return diagnostics.lastError
|
|
456
|
-
? errorIssue("preflight", diagnostics.lastError.code, diagnostics.lastError.httpStatus)
|
|
457
|
-
: { level: "none" };
|
|
435
|
+
|
|
436
|
+
return { summary, detailLines, issue };
|
|
458
437
|
}
|
|
459
438
|
|
|
460
439
|
function formatLatency(latencyMs: number): string {
|
|
@@ -483,54 +462,154 @@ function formatHttpStatus(status: number): string {
|
|
|
483
462
|
return `HTTP ${status}${labels[status] ? ` ${labels[status]}` : ""}`;
|
|
484
463
|
}
|
|
485
464
|
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
465
|
+
function formatHealth(
|
|
466
|
+
preflight: PreflightAdapter | undefined,
|
|
467
|
+
preflightDiagnostics: PreflightDiagnostics | undefined,
|
|
468
|
+
nativePreflight: NativePreflightStatus | undefined,
|
|
469
|
+
liveCheckDiagnostics: LiveCheckDiagnostics | undefined,
|
|
489
470
|
authConfigured: boolean,
|
|
490
471
|
now: number,
|
|
491
|
-
options: {
|
|
492
|
-
):
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
+
}
|
|
499
549
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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) {
|
|
503
562
|
const status =
|
|
504
|
-
|
|
505
|
-
? ` · ${formatHttpStatus(
|
|
563
|
+
liveCheckDiagnostics.lastError.httpStatus !== undefined
|
|
564
|
+
? ` · ${formatHttpStatus(liveCheckDiagnostics.lastError.httpStatus)}`
|
|
506
565
|
: "";
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (
|
|
510
|
-
|
|
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)}`);
|
|
511
570
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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)}`;
|
|
516
577
|
const httpStatus =
|
|
517
|
-
|
|
518
|
-
? formatHttpStatus(
|
|
578
|
+
liveCheckDiagnostics.snapshot.httpStatus !== undefined
|
|
579
|
+
? formatHttpStatus(liveCheckDiagnostics.snapshot.httpStatus)
|
|
519
580
|
: "HTTP status unknown";
|
|
520
|
-
|
|
521
|
-
`Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(
|
|
581
|
+
liveCheckDetailLines.push(
|
|
582
|
+
`Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(liveCheckDiagnostics.snapshot.latencyMs)}`,
|
|
522
583
|
);
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
if (
|
|
526
|
-
|
|
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)}`);
|
|
527
588
|
}
|
|
528
|
-
|
|
529
|
-
? { level: "hard", key: `live-check:${
|
|
589
|
+
liveCheckIssue = options.liveCheckRequested
|
|
590
|
+
? { level: "hard", key: `live-check:${liveCheckDiagnostics.lastError.code}` }
|
|
530
591
|
: { level: "soft" };
|
|
531
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
|
+
);
|
|
532
602
|
}
|
|
533
|
-
|
|
603
|
+
availabilityDetailLines.push(...liveCheckDetailLines);
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
preflightSummary,
|
|
607
|
+
availabilitySummary,
|
|
608
|
+
preflightDetailLines,
|
|
609
|
+
availabilityDetailLines,
|
|
610
|
+
preflightIssue,
|
|
611
|
+
liveCheckIssue,
|
|
612
|
+
};
|
|
534
613
|
}
|
|
535
614
|
|
|
536
615
|
export function formatProviderStatus(
|
|
@@ -550,56 +629,57 @@ export function formatProviderStatus(
|
|
|
550
629
|
): { report: string; warningKey?: string; warningLevel: StatusWarningLevel } {
|
|
551
630
|
const catalog = formatCatalog(provider, nativeProvider, nativeLookupAvailable, now);
|
|
552
631
|
const catalogIssue = scopeIssue(catalog.issue, `catalog:${model.provider}`);
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
);
|
|
565
|
-
const accountLines: string[] = [];
|
|
566
|
-
const statusIssue = scopeIssue(
|
|
567
|
-
appendStatusReport(accountLines, status, diagnostics, auth.configured, now),
|
|
568
|
-
`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
|
+
},
|
|
569
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}`);
|
|
570
648
|
const fieldSources = options.modelMetadata?.fieldSources;
|
|
571
|
-
const qualityLines = formatQualitySource(options.modelMetadata?.quality, options.metadataStatus, now);
|
|
572
649
|
const pricingSource = options.modelMetadata?.pricing ? formatPricingSource(options.modelMetadata) : "";
|
|
573
650
|
const lines = [
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
"",
|
|
578
|
-
"Catalog:",
|
|
579
|
-
...indentLines(catalog.lines),
|
|
580
|
-
"",
|
|
581
|
-
"Health:",
|
|
582
|
-
...indentLines(healthLines),
|
|
651
|
+
formatHeaderRow("Provider:", model.provider),
|
|
652
|
+
formatHeaderRow("Model:", model.id),
|
|
653
|
+
formatHeaderRow("Auth:", auth.configured ? `configured${auth.source ? ` (${auth.source})` : ""}` : "missing"),
|
|
583
654
|
"",
|
|
584
|
-
"
|
|
585
|
-
|
|
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),
|
|
586
664
|
"",
|
|
587
665
|
"Model details:",
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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)]
|
|
598
682
|
: []),
|
|
599
|
-
` Pricing: ${formatPricing(model, options.modelMetadata)}${pricingSource}`,
|
|
600
|
-
...(model.cost?.tiers?.map((tier) => ` Pricing tier: ${formatPricingTier(tier)}`) ?? []),
|
|
601
|
-
...(options.modelMetadata?.pricing?.note ? [` Pricing note: ${options.modelMetadata.pricing.note}`] : []),
|
|
602
|
-
...(qualityLines.length > 0 ? ["", "Quality:", ...indentLines(qualityLines)] : []),
|
|
603
683
|
];
|
|
604
684
|
const issue = combineIssues(catalogIssue, preflightIssue, liveCheckIssue, statusIssue);
|
|
605
685
|
return {
|