@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.
- package/CHANGELOG.md +23 -0
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/core/adapter-extensions.ts +9 -2
- package/core/adapter-protocol.ts +4 -2
- package/core/adapter-validation.ts +14 -1
- package/core/catalog-preflight.ts +14 -1
- package/core/host.ts +43 -75
- package/core/model-catalog.ts +289 -0
- package/core/opencode-preflight.ts +6 -0
- package/core/pi-model-metadata.ts +739 -0
- package/core/provider-registration.ts +111 -56
- package/core/public-adapters.ts +16 -1
- package/core/runtime-config.ts +18 -38
- package/core/runtime-entry.ts +11 -6
- package/core/runtime.ts +26 -104
- package/core/status-report.ts +327 -229
- package/core/types.ts +37 -20
- package/index.ts +37 -18
- 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 +106 -236
- 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 -899
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,89 +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
|
-
:
|
|
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 === "
|
|
187
|
-
? "Provider
|
|
188
|
-
: metadata.pricing.source === "
|
|
189
|
-
? "
|
|
190
|
-
: metadata.pricing.source === "
|
|
191
|
-
? "
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
): {
|
|
297
|
+
): { summary: string; detailLines: string[]; issue: ReportIssue } {
|
|
266
298
|
if (!adapter) {
|
|
267
|
-
if (!nativeLookupAvailable)
|
|
268
|
-
|
|
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
|
-
|
|
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
|
|
282
|
-
|
|
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 {
|
|
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
|
|
319
|
-
return
|
|
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
|
|
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
|
-
|
|
344
|
-
return { level: "none" };
|
|
393
|
+
return { summary: "Account: not supported", detailLines: [], issue: { level: "none" } };
|
|
345
394
|
}
|
|
346
395
|
if (!authConfigured) {
|
|
347
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
420
|
-
|
|
418
|
+
} else if (diagnostics?.pending) {
|
|
419
|
+
summary = "Account: checking";
|
|
420
|
+
} else {
|
|
421
|
+
summary = "Account: unavailable";
|
|
421
422
|
}
|
|
422
423
|
|
|
423
|
-
|
|
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
|
-
|
|
429
|
+
detailLines.push(`Error: ${diagnostics.lastError.code}${httpStatus}`);
|
|
435
430
|
if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
|
|
436
|
-
|
|
431
|
+
detailLines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
|
|
437
432
|
}
|
|
433
|
+
issue = errorIssue("status", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
|
|
438
434
|
}
|
|
439
|
-
|
|
440
|
-
|
|
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
|
|
474
|
-
|
|
475
|
-
|
|
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: {
|
|
479
|
-
):
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
492
|
-
? ` · ${formatHttpStatus(
|
|
563
|
+
liveCheckDiagnostics.lastError.httpStatus !== undefined
|
|
564
|
+
? ` · ${formatHttpStatus(liveCheckDiagnostics.lastError.httpStatus)}`
|
|
493
565
|
: "";
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
505
|
-
? formatHttpStatus(
|
|
578
|
+
liveCheckDiagnostics.snapshot.httpStatus !== undefined
|
|
579
|
+
? formatHttpStatus(liveCheckDiagnostics.snapshot.httpStatus)
|
|
506
580
|
: "HTTP status unknown";
|
|
507
|
-
|
|
508
|
-
`Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(
|
|
581
|
+
liveCheckDetailLines.push(
|
|
582
|
+
`Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(liveCheckDiagnostics.snapshot.latencyMs)}`,
|
|
509
583
|
);
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
|
|
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
|
-
|
|
516
|
-
? { level: "hard", key: `live-check:${
|
|
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
|
-
|
|
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
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
"
|
|
572
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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 {
|