@hyav/pi-provider 0.1.0-oidc-bootstrap.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CONTRIBUTING.md +63 -0
  3. package/LICENSE +21 -0
  4. package/README.md +61 -0
  5. package/README.zh-CN.md +61 -0
  6. package/SECURITY.md +36 -0
  7. package/SUPPORT.md +25 -0
  8. package/core/adapter-extensions.ts +175 -0
  9. package/core/adapter-protocol.ts +120 -0
  10. package/core/adapter-validation.ts +241 -0
  11. package/core/deadline.ts +78 -0
  12. package/core/definition.ts +64 -0
  13. package/core/errors.ts +38 -0
  14. package/core/extension.ts +20 -0
  15. package/core/host.ts +462 -0
  16. package/core/live-check-manager.ts +263 -0
  17. package/core/official-pricing.ts +881 -0
  18. package/core/opencode-preflight.ts +66 -0
  19. package/core/preflight-manager.ts +251 -0
  20. package/core/pricing-adjustments.ts +118 -0
  21. package/core/provider-registration.ts +261 -0
  22. package/core/retry-after.ts +24 -0
  23. package/core/runtime-config.ts +95 -0
  24. package/core/runtime.ts +473 -0
  25. package/core/status-manager.ts +332 -0
  26. package/core/status-report.ts +592 -0
  27. package/core/tuner-manager.ts +34 -0
  28. package/core/types.ts +175 -0
  29. package/index.ts +108 -0
  30. package/package.json +81 -0
  31. package/preflight/charm-hyper.ts +62 -0
  32. package/preflight/deepseek.ts +73 -0
  33. package/preflight/google.ts +89 -0
  34. package/preflight/openai-codex.ts +88 -0
  35. package/preflight/opencode-go.ts +27 -0
  36. package/preflight/opencode.ts +27 -0
  37. package/providers/charm-hyper/constants.ts +31 -0
  38. package/providers/charm-hyper/oauth.ts +360 -0
  39. package/providers/charm-hyper.ts +536 -0
  40. package/status/charm-hyper.ts +76 -0
  41. package/status/deepseek.ts +102 -0
  42. package/status/openai-codex.ts +224 -0
  43. package/status/opencode-go.ts +133 -0
@@ -0,0 +1,592 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { LiveCheckDiagnostics } from "./live-check-manager.ts";
3
+ import type { PreflightAdapter, PreflightDiagnostics } from "./preflight-manager.ts";
4
+ import type { StatusDiagnostics } from "./status-manager.ts";
5
+ import type {
6
+ ModelFieldSource,
7
+ ModelMetadataStatus,
8
+ ModelQualityScore,
9
+ ProviderAdapter,
10
+ ProviderCost,
11
+ ProviderModelMetadata,
12
+ StatusAdapter,
13
+ StatusAmountEntry,
14
+ StatusEntry,
15
+ } from "./types.ts";
16
+
17
+ type ActiveModel = NonNullable<ExtensionContext["model"]>;
18
+ type ModelRegistry = ExtensionContext["modelRegistry"];
19
+ export type NativeProvider = NonNullable<ReturnType<ModelRegistry["getProvider"]>>;
20
+
21
+ export type NativeProviderRegistry = {
22
+ getProvider?: (provider: string) => NativeProvider | undefined;
23
+ };
24
+
25
+ export interface NativeProviderLookup {
26
+ available: boolean;
27
+ provider?: NativeProvider;
28
+ }
29
+
30
+ export interface NativePreflightStatus {
31
+ providerAvailable: boolean;
32
+ modelMatched: boolean;
33
+ }
34
+
35
+ export type StatusWarningLevel = "none" | "soft" | "hard";
36
+
37
+ export interface StatusReportOptions {
38
+ liveCheckRequested?: boolean;
39
+ showLiveCheckScope?: boolean;
40
+ modelMetadata?: ProviderModelMetadata;
41
+ metadataStatus?: ModelMetadataStatus;
42
+ }
43
+
44
+ interface ReportIssue {
45
+ level: StatusWarningLevel;
46
+ key?: string;
47
+ }
48
+
49
+ const STATUS_MODE_COMPLETIONS = [
50
+ {
51
+ value: "refresh",
52
+ label: "refresh",
53
+ description: "Refresh account status and free access checks; no model generation",
54
+ },
55
+ {
56
+ value: "check",
57
+ label: "check",
58
+ description: "Refresh free checks and run one live model check; may incur usage",
59
+ },
60
+ ];
61
+
62
+ export type StatusMode = "default" | "refresh" | "check";
63
+
64
+ export function getStatusModeCompletions(prefix: string) {
65
+ const normalizedPrefix = prefix.trim().toLowerCase();
66
+ const matches = STATUS_MODE_COMPLETIONS.filter(({ value }) => value.startsWith(normalizedPrefix));
67
+ return matches.length > 0 ? matches.map((item) => ({ ...item })) : null;
68
+ }
69
+
70
+ export function parseStatusMode(args: string): StatusMode | undefined {
71
+ switch (args.trim()) {
72
+ case "":
73
+ return "default";
74
+ case "refresh":
75
+ return "refresh";
76
+ case "check":
77
+ return "check";
78
+ default:
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ function formatTokens(count: number | undefined): string {
84
+ if (count === undefined || !Number.isFinite(count)) return "unknown";
85
+ if (count < 1_000) return count.toString();
86
+ if (count < 10_000) return `${(count / 1_000).toFixed(1)}k`;
87
+ if (count < 1_000_000) return `${Math.round(count / 1_000)}k`;
88
+ if (count < 10_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
89
+ return `${Math.round(count / 1_000_000)}M`;
90
+ }
91
+
92
+ function formatNumber(value: number): string {
93
+ if (!Number.isFinite(value)) return "unknown";
94
+ return value.toFixed(2).replace(/\.?(0+)$/, "");
95
+ }
96
+
97
+ function formatAge(now: number, timestamp: number): string {
98
+ const seconds = Math.max(0, Math.floor((now - timestamp) / 1_000));
99
+ if (seconds < 5) return "just now";
100
+ if (seconds < 60) return `${seconds}s ago`;
101
+ const minutes = Math.floor(seconds / 60);
102
+ if (minutes < 60) return `${minutes}m ago`;
103
+ const hours = Math.floor(minutes / 60);
104
+ if (hours < 24) return `${hours}h ago`;
105
+ return `${Math.floor(hours / 24)}d ago`;
106
+ }
107
+
108
+ function formatUntil(now: number, timestamp: number): string {
109
+ const seconds = Math.max(0, Math.ceil((timestamp - now) / 1_000));
110
+ if (seconds === 0) return "now";
111
+ if (seconds < 60) return `in ${seconds}s`;
112
+ const minutes = Math.ceil(seconds / 60);
113
+ if (minutes < 60) return `in ${minutes}m`;
114
+ const hours = Math.ceil(minutes / 60);
115
+ if (hours < 24) return `in ${hours}h`;
116
+ return `in ${Math.ceil(hours / 24)}d`;
117
+ }
118
+
119
+ function formatDateTime(timestamp: number): string {
120
+ const date = new Date(timestamp);
121
+ if (!Number.isFinite(timestamp) || !Number.isFinite(date.getTime())) return "unknown";
122
+ const pad = (value: number): string => value.toString().padStart(2, "0");
123
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
124
+ }
125
+
126
+ function formatRate(value: number): string {
127
+ if (!Number.isFinite(value)) return "unknown";
128
+ if (value === 0) return "$0";
129
+ const decimals = value < 1 ? 3 : 2;
130
+ return `$${value.toFixed(decimals).replace(/\.?(0+)$/, "")}`;
131
+ }
132
+
133
+ function formatPricing(model: ActiveModel, metadata?: ProviderModelMetadata): string {
134
+ const cost = model.cost;
135
+ const knownFree = metadata?.pricing.known === true && cost !== undefined;
136
+ if (
137
+ !cost ||
138
+ (!knownFree && cost.input === 0 && cost.output === 0 && cost.cacheRead === 0 && cost.cacheWrite === 0)
139
+ ) {
140
+ return "unavailable";
141
+ }
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`;
153
+ }
154
+
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));
162
+ return [
163
+ statusParts.join(" · "),
164
+ `Source: ${status?.source ?? "AA/OpenRouter"}`,
165
+ `Indices: ${scores.map((score) => `${score.category} ${formatNumber(score.value)}`).join(" · ")}`,
166
+ ];
167
+ }
168
+
169
+ function formatModelFieldSource(source: ModelFieldSource | undefined): string {
170
+ if (source === undefined) return "";
171
+ const label =
172
+ source === "native"
173
+ ? "Pi native"
174
+ : source === "provider"
175
+ ? "Provider catalog"
176
+ : source === "official"
177
+ ? "OpenRouter"
178
+ : source === "fallback"
179
+ ? "Provider fallback"
180
+ : "Pi default";
181
+ return ` · ${label}`;
182
+ }
183
+
184
+ function formatPricingSource(metadata: ProviderModelMetadata): string {
185
+ 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"
192
+ : metadata.pricing.source === "native"
193
+ ? "Pi native"
194
+ : undefined;
195
+ const parts = source ? [source] : [];
196
+ if (metadata.pricing.adjustment) parts.push(metadata.pricing.adjustment.label);
197
+ if (metadata.pricing.known) parts.push("estimate");
198
+ return parts.length > 0 ? ` · ${parts.join(" · ")}` : "";
199
+ }
200
+
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
+ const REASONING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
217
+
218
+ function getSupportedReasoningLevels(model: ActiveModel): string[] {
219
+ return REASONING_LEVELS.filter((level) => {
220
+ const mapped = model.thinkingLevelMap?.[level];
221
+ if (mapped === null) return false;
222
+ if (level === "xhigh" || level === "max") return mapped !== undefined;
223
+ return true;
224
+ });
225
+ }
226
+
227
+ function formatReasoning(model: ActiveModel): string {
228
+ if (!model.reasoning) return "not supported";
229
+ const levels = getSupportedReasoningLevels(model);
230
+ return levels.length > 0 ? `supported (${levels.join(", ")})` : "supported";
231
+ }
232
+
233
+ export function resolveNativeProvider(modelRegistry: NativeProviderRegistry, providerId: string): NativeProviderLookup {
234
+ if (typeof modelRegistry.getProvider !== "function") return { available: false };
235
+ try {
236
+ return { available: true, provider: modelRegistry.getProvider(providerId) };
237
+ } catch {
238
+ return { available: true };
239
+ }
240
+ }
241
+
242
+ function getNativeModelCount(provider: NativeProvider | undefined): number | undefined {
243
+ if (!provider) return undefined;
244
+ try {
245
+ return provider.getModels().length;
246
+ } catch {
247
+ return undefined;
248
+ }
249
+ }
250
+
251
+ export function nativeModelMatches(provider: NativeProvider | undefined, modelId: string): boolean {
252
+ if (!provider) return false;
253
+ try {
254
+ return provider.getModels().some(({ id }) => id === modelId);
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+
260
+ function formatCatalog(
261
+ adapter: ProviderAdapter | undefined,
262
+ nativeProvider: NativeProvider | undefined,
263
+ nativeLookupAvailable: boolean,
264
+ now: number,
265
+ ): { lines: string[]; issue: ReportIssue } {
266
+ if (!adapter) {
267
+ if (!nativeLookupAvailable) return { lines: ["Status: not managed by Provider Kit"], issue: { level: "none" } };
268
+ if (!nativeProvider) return { lines: ["Status: unavailable in Pi"], issue: { level: "none" } };
269
+ const count = getNativeModelCount(nativeProvider);
270
+ return {
271
+ lines: ["Status: static · Pi native", `Models: ${count === undefined ? "unknown" : count}`],
272
+ issue: { level: "none" },
273
+ };
274
+ }
275
+ const catalog = adapter.catalog;
276
+ const count = catalog?.modelCount ?? adapter.provider.models.length;
277
+ const source = catalog?.source ?? "static";
278
+ const freshness = catalog?.lastError ? "stale" : catalog?.updatedAt !== undefined ? "fresh" : undefined;
279
+ const statusParts = freshness ? [freshness, source] : [source];
280
+ 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}`);
283
+ const issue =
284
+ catalog?.lastError !== undefined
285
+ ? {
286
+ level: count > 0 ? ("soft" as const) : ("hard" as const),
287
+ key: `catalog:${catalog.lastError}`,
288
+ }
289
+ : { level: "none" as const };
290
+ return { lines, issue };
291
+ }
292
+
293
+ function classifyError(code: string, httpStatus: number | undefined): StatusWarningLevel {
294
+ if (code === "auth" || code === "config" || code === "badjson" || code === "unsupported") return "hard";
295
+ if (httpStatus !== undefined && httpStatus >= 400 && httpStatus < 500 && httpStatus !== 408 && httpStatus !== 429) {
296
+ return "hard";
297
+ }
298
+ return "soft";
299
+ }
300
+
301
+ function errorIssue(scope: string, code: string, httpStatus: number | undefined): ReportIssue {
302
+ return {
303
+ level: classifyError(code, httpStatus),
304
+ key: `${scope}:${code}`,
305
+ };
306
+ }
307
+
308
+ function combineIssues(...issues: ReportIssue[]): ReportIssue {
309
+ return (
310
+ issues.find(({ level }) => level === "hard") ?? issues.find(({ level }) => level === "soft") ?? { level: "none" }
311
+ );
312
+ }
313
+
314
+ function scopeIssue(issue: ReportIssue, scope: string): ReportIssue {
315
+ return issue.level === "none" ? issue : { ...issue, key: `${scope}:${issue.key ?? issue.level}` };
316
+ }
317
+
318
+ function indentLines(lines: string[]): string[] {
319
+ return lines.map((line) => (line === "" ? "" : ` ${line}`));
320
+ }
321
+
322
+ function formatStatusAmount(entry: StatusAmountEntry): string {
323
+ const unit = entry.unit.trim();
324
+ if (unit.toUpperCase() === "USD") return `$${entry.value.toFixed(2)}`;
325
+ return `${formatNumber(entry.value)} ${unit}`;
326
+ }
327
+
328
+ function formatStatusEntry(entry: StatusEntry): string {
329
+ if (entry.kind === "text") return `${entry.label}: ${entry.value}`;
330
+ if (entry.kind === "amount") return `${entry.label}: ${formatStatusAmount(entry)}`;
331
+ const remaining = `${formatNumber(entry.remainingPercent)}% remaining`;
332
+ return `${entry.label}: ${remaining}${entry.resetAt !== undefined ? ` · reset at ${formatDateTime(entry.resetAt)}` : ""}`;
333
+ }
334
+
335
+ function appendStatusReport(
336
+ lines: string[],
337
+ status: StatusAdapter | undefined,
338
+ diagnostics: StatusDiagnostics | undefined,
339
+ authConfigured: boolean,
340
+ now: number,
341
+ ): ReportIssue {
342
+ if (!status) {
343
+ lines.push("Status: not supported");
344
+ return { level: "none" };
345
+ }
346
+ if (!authConfigured) {
347
+ lines.push("Status: unavailable · auth missing");
348
+ return { level: "none" };
349
+ }
350
+ if (diagnostics?.snapshot) {
351
+ const expired = now - diagnostics.snapshot.updatedAt >= status.cacheTtlMs;
352
+ 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)}`);
416
+ }
417
+ return errorIssue("preflight", diagnostics.lastError.code, diagnostics.lastError.httpStatus);
418
+ }
419
+ lines.push("Preflight: not checked");
420
+ return { level: "none" };
421
+ }
422
+
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) {
430
+ const httpStatus =
431
+ diagnostics.lastError.httpStatus !== undefined
432
+ ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
433
+ : "";
434
+ lines.push(`Preflight error: ${diagnostics.lastError.code}${httpStatus}`);
435
+ if (diagnostics.lastError.retryAt !== undefined && diagnostics.lastError.retryAt > now) {
436
+ lines.push(`Retry: ${formatUntil(now, diagnostics.lastError.retryAt)}`);
437
+ }
438
+ }
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" };
445
+ }
446
+
447
+ function formatLatency(latencyMs: number): string {
448
+ if (!Number.isFinite(latencyMs)) return "unknown";
449
+ return `${Math.max(0, Math.round(latencyMs))}ms`;
450
+ }
451
+
452
+ function formatHttpStatus(status: number): string {
453
+ const labels: Record<number, string> = {
454
+ 200: "OK",
455
+ 201: "Created",
456
+ 202: "Accepted",
457
+ 204: "No Content",
458
+ 400: "Bad Request",
459
+ 401: "Unauthorized",
460
+ 403: "Forbidden",
461
+ 404: "Not Found",
462
+ 408: "Request Timeout",
463
+ 409: "Conflict",
464
+ 429: "Rate Limited",
465
+ 500: "Server Error",
466
+ 502: "Bad Gateway",
467
+ 503: "Service Unavailable",
468
+ 504: "Gateway Timeout",
469
+ };
470
+ return `HTTP ${status}${labels[status] ? ` ${labels[status]}` : ""}`;
471
+ }
472
+
473
+ function appendLiveCheckReport(
474
+ lines: string[],
475
+ diagnostics: LiveCheckDiagnostics | undefined,
476
+ authConfigured: boolean,
477
+ 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() · Provider Kit tuners only (other hooks not replayed)");
486
+ }
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) {
490
+ const status =
491
+ diagnostics.lastError.httpStatus !== undefined
492
+ ? ` · ${formatHttpStatus(diagnostics.lastError.httpStatus)}`
493
+ : "";
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)}`);
498
+ }
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)}`);
503
+ const httpStatus =
504
+ diagnostics.snapshot.httpStatus !== undefined
505
+ ? formatHttpStatus(diagnostics.snapshot.httpStatus)
506
+ : "HTTP status unknown";
507
+ lines.push(
508
+ `Live check: ${stale ? "last success" : "success"} · ${httpStatus} · ${formatLatency(diagnostics.snapshot.latencyMs)}`,
509
+ );
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)}`);
514
+ }
515
+ return options.requested
516
+ ? { level: "hard", key: `live-check:${diagnostics.lastError.code}` }
517
+ : { level: "soft" };
518
+ }
519
+ }
520
+ return { level: "none" };
521
+ }
522
+
523
+ export function formatProviderStatus(
524
+ model: ActiveModel,
525
+ provider: ProviderAdapter | undefined,
526
+ status: StatusAdapter | undefined,
527
+ preflight: PreflightAdapter | undefined,
528
+ nativePreflight: NativePreflightStatus | undefined,
529
+ auth: { configured: boolean; source?: string },
530
+ diagnostics: StatusDiagnostics | undefined,
531
+ preflightDiagnostics: PreflightDiagnostics | undefined,
532
+ liveCheckDiagnostics: LiveCheckDiagnostics | undefined,
533
+ nativeProvider: NativeProvider | undefined,
534
+ nativeLookupAvailable: boolean,
535
+ now: number,
536
+ options: StatusReportOptions = {},
537
+ ): { report: string; warningKey?: string; warningLevel: StatusWarningLevel } {
538
+ const catalog = formatCatalog(provider, nativeProvider, nativeLookupAvailable, now);
539
+ 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}`,
556
+ );
557
+ const fieldSources = options.modelMetadata?.fieldSources;
558
+ const qualityLines = formatQualitySource(options.modelMetadata?.quality, options.metadataStatus, now);
559
+ const pricingSource = options.modelMetadata?.pricing ? formatPricingSource(options.modelMetadata) : "";
560
+ 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),
570
+ "",
571
+ "Account:",
572
+ ...indentLines(accountLines),
573
+ "",
574
+ "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)] : []),
585
+ ];
586
+ const issue = combineIssues(catalogIssue, preflightIssue, liveCheckIssue, statusIssue);
587
+ return {
588
+ report: lines.join("\n"),
589
+ ...(issue.level === "hard" ? { warningKey: issue.key ?? "provider-status" } : {}),
590
+ warningLevel: issue.level,
591
+ };
592
+ }
@@ -0,0 +1,34 @@
1
+ import type { TunerAdapter, TunerContext } from "./types.ts";
2
+
3
+ function tunerPriority(tuner: TunerAdapter): number {
4
+ return tuner.priority ?? 0;
5
+ }
6
+
7
+ export function sortTunerAdapters(tuners: TunerAdapter[]): TunerAdapter[] {
8
+ return [...tuners].sort(
9
+ (left, right) =>
10
+ tunerPriority(left) - tunerPriority(right) || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
11
+ );
12
+ }
13
+
14
+ export async function applyTunerAdapters(
15
+ payload: unknown,
16
+ context: TunerContext,
17
+ tuners: TunerAdapter[],
18
+ ): Promise<unknown | undefined> {
19
+ let current = payload;
20
+ let changed = false;
21
+ for (const tuner of tuners) {
22
+ try {
23
+ if (!tuner.matches(context, current)) continue;
24
+ const transformed = await tuner.transform(current, context);
25
+ if (transformed !== undefined) {
26
+ current = transformed;
27
+ changed = true;
28
+ }
29
+ } catch (error) {
30
+ console.error(`[${tuner.id}] tuner failed:`, error);
31
+ }
32
+ }
33
+ return changed ? current : undefined;
34
+ }