@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,881 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { withDeadline } from "./deadline.ts";
6
+ import type { ModelQualityScore, ProviderCost, ProviderModel, ProviderModelDraft } from "./types.ts";
7
+
8
+ export const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
9
+
10
+ const DEFAULT_PRICING_CACHE_TTL_MS = 60 * 60 * 1_000;
11
+ const DEFAULT_PRICING_MAX_STALE_MS = 24 * 60 * 60 * 1_000;
12
+ const PERSISTED_PRICING_CACHE_VERSION = 3 as const;
13
+ const REQUIRED_OPENROUTER_PRICE_FIELDS = ["prompt", "completion"] as const;
14
+ const OPTIONAL_OPENROUTER_PRICE_FIELDS = ["input_cache_read", "input_cache_write"] as const;
15
+
16
+ type ThinkingLevelMap = NonNullable<ProviderModel["thinkingLevelMap"]>;
17
+
18
+ export interface OfficialModelMeta {
19
+ cost: ProviderCost;
20
+ /** False when pricing is missing or invalid, including identity-only entries. */
21
+ costKnown?: boolean;
22
+ quality?: ModelQualityScore[];
23
+ name?: string;
24
+ contextWindow?: number;
25
+ maxTokens?: number;
26
+ reasoning?: boolean;
27
+ thinkingLevelMap?: ThinkingLevelMap;
28
+ input?: ("text" | "image")[];
29
+ compat?: {
30
+ supportsStore?: boolean;
31
+ supportsReasoningEffort?: boolean;
32
+ };
33
+ /** OpenRouter identity data used to resolve versioned aliases for read-only metadata. */
34
+ identity?: {
35
+ sourceId: string;
36
+ familyId: string;
37
+ canonicalId?: string;
38
+ aliasTargetId?: string;
39
+ version?: string;
40
+ created?: number;
41
+ latestAlias?: boolean;
42
+ };
43
+ }
44
+
45
+ export interface OfficialPricingFetchOptions {
46
+ /** Optional persistent cache file. Omit for process-only caching (for example, in unit tests). */
47
+ cachePath?: string;
48
+ /** Return the current snapshot immediately and refresh an expired/missing cache in the background. */
49
+ background?: boolean;
50
+ /** Observe the completed background snapshot without delaying the initial caller. */
51
+ onBackgroundRefresh?: (snapshot: Record<string, OfficialModelMeta>) => void;
52
+ }
53
+
54
+ interface PricingCacheEntry {
55
+ snapshot: Record<string, OfficialModelMeta>;
56
+ updatedAt: number;
57
+ }
58
+
59
+ interface PersistedPricingCache {
60
+ version: typeof PERSISTED_PRICING_CACHE_VERSION;
61
+ sourceUrl: string;
62
+ updatedAt: number;
63
+ snapshot: Record<string, OfficialModelMeta>;
64
+ }
65
+
66
+ const MAX_PRICING_CACHE_ENTRIES = 32;
67
+ const pricingCache = new Map<string, PricingCacheEntry>();
68
+ const pricingRequests = new Map<string, Promise<Record<string, OfficialModelMeta>>>();
69
+
70
+ /** Default cache for OpenRouter metadata, not Pi's native model catalog. */
71
+ export function getDefaultOpenRouterMetadataCachePath(): string {
72
+ const configuredAgentDir = process.env.PI_CODING_AGENT_DIR;
73
+ const agentDir =
74
+ configuredAgentDir && configuredAgentDir.trim() !== "" ? configuredAgentDir : join(homedir(), ".pi", "agent");
75
+ return join(agentDir, "provider-kit", "openrouter-model-metadata.json");
76
+ }
77
+
78
+ function cloneCost(cost: ProviderCost): ProviderCost {
79
+ return {
80
+ ...cost,
81
+ ...(cost.tiers ? { tiers: cost.tiers.map((tier) => ({ ...tier })) } : {}),
82
+ };
83
+ }
84
+
85
+ function cloneMeta(meta: OfficialModelMeta): OfficialModelMeta {
86
+ return {
87
+ ...meta,
88
+ cost: cloneCost(meta.cost),
89
+ ...(meta.costKnown === false ? { costKnown: false } : {}),
90
+ ...(meta.input ? { input: [...meta.input] } : {}),
91
+ ...(meta.thinkingLevelMap ? { thinkingLevelMap: { ...meta.thinkingLevelMap } } : {}),
92
+ ...(meta.compat ? { compat: { ...meta.compat } } : {}),
93
+ ...(meta.identity ? { identity: { ...meta.identity } } : {}),
94
+ ...(meta.quality
95
+ ? {
96
+ quality: meta.quality.map((score) => ({
97
+ ...score,
98
+ ...(score.confidenceInterval ? { confidenceInterval: { ...score.confidenceInterval } } : {}),
99
+ })),
100
+ }
101
+ : {}),
102
+ };
103
+ }
104
+
105
+ function cloneSnapshot(snapshot: Record<string, OfficialModelMeta>): Record<string, OfficialModelMeta> {
106
+ return Object.fromEntries(Object.entries(snapshot).map(([key, meta]) => [key, cloneMeta(meta)]));
107
+ }
108
+
109
+ export function setPricingCache(
110
+ cache: Record<string, OfficialModelMeta>,
111
+ pricingUrl = OPENROUTER_MODELS_URL,
112
+ updatedAt = Date.now(),
113
+ ): void {
114
+ pricingCache.delete(pricingUrl);
115
+ pricingCache.set(pricingUrl, { snapshot: cloneSnapshot(cache), updatedAt });
116
+ while (pricingCache.size > MAX_PRICING_CACHE_ENTRIES) {
117
+ const oldest = pricingCache.keys().next().value as string | undefined;
118
+ if (oldest === undefined) break;
119
+ pricingCache.delete(oldest);
120
+ }
121
+ }
122
+
123
+ export function getPricingCache(pricingUrl = OPENROUTER_MODELS_URL): Record<string, OfficialModelMeta> {
124
+ return cloneSnapshot(pricingCache.get(pricingUrl)?.snapshot ?? {});
125
+ }
126
+
127
+ export function getPricingCacheAge(pricingUrl = OPENROUTER_MODELS_URL, now = Date.now()): number | undefined {
128
+ const updatedAt = pricingCache.get(pricingUrl)?.updatedAt;
129
+ return updatedAt === undefined ? undefined : Math.max(0, now - updatedAt);
130
+ }
131
+
132
+ export function clearPricingCache(pricingUrl?: string): void {
133
+ if (pricingUrl === undefined) pricingCache.clear();
134
+ else pricingCache.delete(pricingUrl);
135
+ }
136
+
137
+ function isRecord(value: unknown): value is Record<string, unknown> {
138
+ return value !== null && typeof value === "object" && !Array.isArray(value);
139
+ }
140
+
141
+ function isFiniteNonNegative(value: unknown): value is number {
142
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
143
+ }
144
+
145
+ function isPersistableTier(value: unknown): boolean {
146
+ if (!isRecord(value)) return false;
147
+ return (
148
+ typeof value.inputTokensAbove === "number" &&
149
+ Number.isInteger(value.inputTokensAbove) &&
150
+ value.inputTokensAbove > 0 &&
151
+ isFiniteNonNegative(value.input) &&
152
+ isFiniteNonNegative(value.output) &&
153
+ isFiniteNonNegative(value.cacheRead) &&
154
+ isFiniteNonNegative(value.cacheWrite)
155
+ );
156
+ }
157
+
158
+ function isPersistableCost(value: unknown): value is ProviderCost {
159
+ if (!isRecord(value)) return false;
160
+ if (!["input", "output", "cacheRead", "cacheWrite"].every((key) => isFiniteNonNegative(value[key]))) return false;
161
+ return value.tiers === undefined || (Array.isArray(value.tiers) && value.tiers.every(isPersistableTier));
162
+ }
163
+
164
+ function isPersistableQuality(value: unknown): value is ModelQualityScore {
165
+ if (!isRecord(value)) return false;
166
+ if (typeof value.source !== "string" || value.source.trim() === "") return false;
167
+ if (typeof value.benchmark !== "string" || value.benchmark.trim() === "") return false;
168
+ if (typeof value.category !== "string" || value.category.trim() === "") return false;
169
+ if (value.metric !== "elo" && value.metric !== "rating" && value.metric !== "score" && value.metric !== "ips") {
170
+ return false;
171
+ }
172
+ if (!isFiniteNonNegative(value.value)) return false;
173
+ if (value.rank !== undefined && !isPositiveInteger(value.rank)) return false;
174
+ if (value.winRate !== undefined && !isFiniteNonNegative(value.winRate)) return false;
175
+ if (value.confidenceInterval !== undefined) {
176
+ if (!isRecord(value.confidenceInterval)) return false;
177
+ if (
178
+ !isFiniteNonNegative(value.confidenceInterval.lower) ||
179
+ !isFiniteNonNegative(value.confidenceInterval.upper)
180
+ ) {
181
+ return false;
182
+ }
183
+ }
184
+ return true;
185
+ }
186
+
187
+ function isPersistableIdentity(value: unknown): boolean {
188
+ if (!isRecord(value)) return false;
189
+ if (typeof value.sourceId !== "string" || value.sourceId.trim() === "") return false;
190
+ if (typeof value.familyId !== "string" || value.familyId.trim() === "") return false;
191
+ if (value.canonicalId !== undefined && typeof value.canonicalId !== "string") return false;
192
+ if (value.aliasTargetId !== undefined && typeof value.aliasTargetId !== "string") return false;
193
+ if (value.version !== undefined && typeof value.version !== "string") return false;
194
+ if (value.created !== undefined && !isFiniteNonNegative(value.created)) return false;
195
+ return value.latestAlias === undefined || typeof value.latestAlias === "boolean";
196
+ }
197
+
198
+ function isPersistableMeta(value: unknown): value is OfficialModelMeta {
199
+ if (!isRecord(value) || !isPersistableCost(value.cost)) return false;
200
+ if (value.identity !== undefined && !isPersistableIdentity(value.identity)) return false;
201
+ if (value.costKnown !== undefined && typeof value.costKnown !== "boolean") return false;
202
+ if (value.quality !== undefined && (!Array.isArray(value.quality) || !value.quality.every(isPersistableQuality))) {
203
+ return false;
204
+ }
205
+ if (value.name !== undefined && typeof value.name !== "string") return false;
206
+ if (value.contextWindow !== undefined && !isPositiveInteger(value.contextWindow)) return false;
207
+ if (value.maxTokens !== undefined && !isPositiveInteger(value.maxTokens)) return false;
208
+ if (value.reasoning !== undefined && typeof value.reasoning !== "boolean") return false;
209
+ if (
210
+ value.input !== undefined &&
211
+ (!Array.isArray(value.input) || value.input.some((item) => item !== "text" && item !== "image"))
212
+ ) {
213
+ return false;
214
+ }
215
+ if (value.thinkingLevelMap !== undefined) {
216
+ if (!isRecord(value.thinkingLevelMap)) return false;
217
+ if (Object.values(value.thinkingLevelMap).some((item) => item !== null && typeof item !== "string")) return false;
218
+ }
219
+ if (value.compat !== undefined) {
220
+ if (!isRecord(value.compat)) return false;
221
+ if (
222
+ (value.compat.supportsStore !== undefined && typeof value.compat.supportsStore !== "boolean") ||
223
+ (value.compat.supportsReasoningEffort !== undefined &&
224
+ typeof value.compat.supportsReasoningEffort !== "boolean")
225
+ ) {
226
+ return false;
227
+ }
228
+ }
229
+ return true;
230
+ }
231
+
232
+ function parsePersistedSnapshot(value: unknown): Record<string, OfficialModelMeta> | undefined {
233
+ if (!isRecord(value)) return undefined;
234
+ const entries: [string, OfficialModelMeta][] = [];
235
+ for (const [key, meta] of Object.entries(value)) {
236
+ if (key.trim() === "" || !isPersistableMeta(meta)) return undefined;
237
+ entries.push([key, meta]);
238
+ }
239
+ return Object.fromEntries(entries.map(([key, meta]) => [key, cloneMeta(meta)]));
240
+ }
241
+
242
+ async function readPersistedPricingCache(
243
+ cachePath: string | undefined,
244
+ pricingUrl: string,
245
+ ): Promise<PricingCacheEntry | undefined> {
246
+ if (!cachePath || cachePath.trim() === "") return undefined;
247
+ try {
248
+ const parsed: unknown = JSON.parse(await readFile(cachePath, "utf8"));
249
+ if (!isRecord(parsed)) return undefined;
250
+ if (
251
+ parsed.version !== PERSISTED_PRICING_CACHE_VERSION ||
252
+ parsed.sourceUrl !== pricingUrl ||
253
+ typeof parsed.updatedAt !== "number" ||
254
+ !Number.isFinite(parsed.updatedAt) ||
255
+ parsed.updatedAt < 0
256
+ ) {
257
+ return undefined;
258
+ }
259
+ const snapshot = parsePersistedSnapshot(parsed.snapshot);
260
+ return snapshot === undefined ? undefined : { snapshot, updatedAt: parsed.updatedAt };
261
+ } catch {
262
+ return undefined;
263
+ }
264
+ }
265
+
266
+ async function writePersistedPricingCache(
267
+ cachePath: string | undefined,
268
+ pricingUrl: string,
269
+ snapshot: Record<string, OfficialModelMeta>,
270
+ updatedAt: number,
271
+ ): Promise<void> {
272
+ if (!cachePath || cachePath.trim() === "") return;
273
+ const temporaryPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
274
+ try {
275
+ await mkdir(dirname(cachePath), { recursive: true, mode: 0o700 });
276
+ const persisted: PersistedPricingCache = {
277
+ version: PERSISTED_PRICING_CACHE_VERSION,
278
+ sourceUrl: pricingUrl,
279
+ updatedAt,
280
+ snapshot: cloneSnapshot(snapshot),
281
+ };
282
+ await writeFile(temporaryPath, `${JSON.stringify(persisted)}\n`, { encoding: "utf8", mode: 0o600 });
283
+ await rename(temporaryPath, cachePath);
284
+ } catch {
285
+ // Persistence is an optimization and must never make model registration fail.
286
+ } finally {
287
+ await unlink(temporaryPath).catch(() => undefined);
288
+ }
289
+ }
290
+
291
+ function parsePrice(value: unknown): number | undefined {
292
+ const num =
293
+ typeof value === "number"
294
+ ? value
295
+ : typeof value === "string" && value.trim() !== ""
296
+ ? Number(value.trim())
297
+ : Number.NaN;
298
+ if (!Number.isFinite(num) || num < 0) return undefined;
299
+ const scaled = num * 1_000_000;
300
+ return Number.isFinite(scaled) ? scaled : undefined;
301
+ }
302
+
303
+ function hasKnownOpenRouterPricing(pricing: Record<string, unknown> | undefined): boolean {
304
+ if (pricing === undefined) return false;
305
+ if (
306
+ REQUIRED_OPENROUTER_PRICE_FIELDS.some(
307
+ (key) => !Object.hasOwn(pricing, key) || parsePrice(pricing[key]) === undefined,
308
+ )
309
+ ) {
310
+ return false;
311
+ }
312
+ return OPTIONAL_OPENROUTER_PRICE_FIELDS.every(
313
+ (key) => !Object.hasOwn(pricing, key) || parsePrice(pricing[key]) !== undefined,
314
+ );
315
+ }
316
+
317
+ function priceOrZero(value: unknown): number {
318
+ return parsePrice(value) ?? 0;
319
+ }
320
+
321
+ function isPositiveInteger(value: unknown): value is number {
322
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
323
+ }
324
+
325
+ function setRecordValue<T>(record: Record<string, T>, key: string, value: T): void {
326
+ Object.defineProperty(record, key, {
327
+ configurable: true,
328
+ enumerable: true,
329
+ value,
330
+ writable: true,
331
+ });
332
+ }
333
+
334
+ function normalizeModelId(value: unknown): string | undefined {
335
+ if (typeof value !== "string") return undefined;
336
+ const normalized = value.trim().toLowerCase();
337
+ return normalized === "" ? undefined : normalized;
338
+ }
339
+
340
+ function splitModelId(value: string): { provider?: string; model: string } {
341
+ const slash = value.indexOf("/");
342
+ if (slash < 0) return { model: value };
343
+ return {
344
+ provider: value.slice(0, slash).replace(/^~/, ""),
345
+ model: value.slice(slash + 1),
346
+ };
347
+ }
348
+
349
+ function stripModelVariant(model: string): string {
350
+ return model
351
+ .replace(/:[^:]+$/, "")
352
+ .replace(/-(?:minimal|low|medium|high|xhigh|max|thinking|reasoning)$/i, "")
353
+ .replace(/-latest$/i, "")
354
+ .replace(/-(?:\d{8}|\d{4})$/i, "");
355
+ }
356
+
357
+ function getModelFamilyId(value: string): string {
358
+ const { provider, model } = splitModelId(value);
359
+ const family = stripModelVariant(model);
360
+ return provider ? `${provider}/${family}` : family;
361
+ }
362
+
363
+ function getModelVersion(value: string): string | undefined {
364
+ const { model } = splitModelId(value);
365
+ const normalized = model
366
+ .replace(/:[^:]+$/, "")
367
+ .replace(/-(?:minimal|low|medium|high|xhigh|max|thinking|reasoning)$/i, "")
368
+ .replace(/-latest$/i, "");
369
+ return normalized.match(/-(\d{8}|\d{4})$/)?.[1];
370
+ }
371
+
372
+ function parseOpenRouterIdentity(fullId: string, item: Record<string, unknown>): OfficialModelMeta["identity"] {
373
+ const canonicalId = normalizeModelId(item.canonical_slug);
374
+ const aliasTarget = isRecord(item.alias_target) ? normalizeModelId(item.alias_target.slug) : undefined;
375
+ const created = typeof item.created === "number" && Number.isFinite(item.created) ? item.created : undefined;
376
+ const latestAlias = splitModelId(fullId).model.endsWith("-latest");
377
+ const familyId = getModelFamilyId(aliasTarget ?? canonicalId ?? fullId);
378
+ const version = latestAlias ? undefined : getModelVersion(canonicalId ?? fullId);
379
+ return {
380
+ sourceId: fullId,
381
+ familyId,
382
+ ...(canonicalId ? { canonicalId } : {}),
383
+ ...(aliasTarget ? { aliasTargetId: aliasTarget } : {}),
384
+ ...(version ? { version } : {}),
385
+ ...(created !== undefined ? { created } : {}),
386
+ ...(latestAlias ? { latestAlias: true } : {}),
387
+ };
388
+ }
389
+
390
+ function parseArtificialAnalysisQuality(value: unknown): ModelQualityScore[] | undefined {
391
+ if (!isRecord(value)) return undefined;
392
+ const scores: ModelQualityScore[] = [];
393
+ const artificialAnalysis = isRecord(value.artificial_analysis) ? value.artificial_analysis : undefined;
394
+ for (const [category, key] of [
395
+ ["intelligence", "intelligence_index"],
396
+ ["coding", "coding_index"],
397
+ ["agentic", "agentic_index"],
398
+ ] as const) {
399
+ const score = artificialAnalysis?.[key];
400
+ if (!isFiniteNonNegative(score)) continue;
401
+ scores.push({
402
+ source: "artificial-analysis",
403
+ benchmark: "Artificial Analysis",
404
+ category,
405
+ metric: "score",
406
+ value: score,
407
+ });
408
+ }
409
+ return scores.length > 0 ? scores : undefined;
410
+ }
411
+
412
+ function parseThinkingLevelMap(reasoning: Record<string, unknown> | undefined): ThinkingLevelMap | undefined {
413
+ if (!Array.isArray(reasoning?.supported_efforts)) return undefined;
414
+ const supported = new Set(
415
+ reasoning.supported_efforts
416
+ .filter((effort): effort is string => typeof effort === "string")
417
+ .map((effort) => effort.trim().toLowerCase()),
418
+ );
419
+ if (supported.size === 0) return undefined;
420
+ return {
421
+ off: supported.has("none") ? "none" : null,
422
+ minimal: supported.has("minimal") ? "minimal" : null,
423
+ low: supported.has("low") ? "low" : null,
424
+ medium: supported.has("medium") ? "medium" : null,
425
+ high: supported.has("high") ? "high" : null,
426
+ xhigh: supported.has("xhigh") ? "xhigh" : null,
427
+ max: supported.has("max") ? "max" : null,
428
+ };
429
+ }
430
+
431
+ export function parseOpenRouterModels(payload: unknown): Record<string, OfficialModelMeta> {
432
+ if (!isRecord(payload) || !Array.isArray(payload.data)) return {};
433
+ const result: Record<string, OfficialModelMeta> = {};
434
+ const aliasCandidates = new Map<string, Map<string, OfficialModelMeta>>();
435
+
436
+ for (const item of payload.data) {
437
+ if (!isRecord(item) || typeof item.id !== "string" || item.id.trim() === "") continue;
438
+ const fullId = item.id.trim().toLowerCase();
439
+ const pricing = isRecord(item.pricing) ? item.pricing : undefined;
440
+ const quality = parseArtificialAnalysisQuality(item.benchmarks);
441
+ const costKnown = hasKnownOpenRouterPricing(pricing);
442
+
443
+ const input = priceOrZero(pricing?.prompt);
444
+ const output = priceOrZero(pricing?.completion);
445
+ const cacheRead = priceOrZero(pricing?.input_cache_read);
446
+ const cacheWrite = priceOrZero(pricing?.input_cache_write);
447
+ const cost: ProviderCost = { input, output, cacheRead, cacheWrite };
448
+
449
+ if (pricing && Array.isArray(pricing.overrides) && pricing.overrides.length > 0) {
450
+ const tiers: NonNullable<ProviderCost["tiers"]> = [];
451
+ for (const override of pricing.overrides) {
452
+ if (!isRecord(override)) continue;
453
+ const inputTokensAbove = isPositiveInteger(override.min_prompt_tokens) ? override.min_prompt_tokens : 0;
454
+ const inputPrice = parsePrice(override.prompt);
455
+ const outputPrice = parsePrice(override.completion);
456
+ const cacheReadPrice = Object.hasOwn(override, "input_cache_read")
457
+ ? parsePrice(override.input_cache_read)
458
+ : 0;
459
+ const cacheWritePrice = Object.hasOwn(override, "input_cache_write")
460
+ ? parsePrice(override.input_cache_write)
461
+ : 0;
462
+ if (
463
+ inputTokensAbove <= 0 ||
464
+ inputPrice === undefined ||
465
+ outputPrice === undefined ||
466
+ cacheReadPrice === undefined ||
467
+ cacheWritePrice === undefined
468
+ ) {
469
+ continue;
470
+ }
471
+ tiers.push({
472
+ inputTokensAbove,
473
+ input: inputPrice,
474
+ output: outputPrice,
475
+ cacheRead: cacheReadPrice,
476
+ cacheWrite: cacheWritePrice,
477
+ });
478
+ }
479
+
480
+ if (tiers.length > 0) cost.tiers = tiers;
481
+ }
482
+
483
+ const topProvider = isRecord(item.top_provider) ? item.top_provider : undefined;
484
+ const contextWindow = isPositiveInteger(item.context_length)
485
+ ? item.context_length
486
+ : topProvider && isPositiveInteger(topProvider.context_length)
487
+ ? topProvider.context_length
488
+ : undefined;
489
+ const maxTokens =
490
+ topProvider && isPositiveInteger(topProvider.max_completion_tokens)
491
+ ? topProvider.max_completion_tokens
492
+ : undefined;
493
+
494
+ const arch = isRecord(item.architecture) ? item.architecture : undefined;
495
+ const inputModalities = Array.isArray(arch?.input_modalities) ? arch.input_modalities : [];
496
+ const hasImage = inputModalities.includes("image");
497
+
498
+ const reasoningInfo = isRecord(item.reasoning) ? item.reasoning : undefined;
499
+ const thinkingLevelMap = parseThinkingLevelMap(reasoningInfo);
500
+ const supportedParams = Array.isArray(item.supported_parameters) ? item.supported_parameters : [];
501
+ const isReasoning =
502
+ reasoningInfo !== undefined ||
503
+ supportedParams.includes("reasoning") ||
504
+ supportedParams.includes("include_reasoning") ||
505
+ supportedParams.includes("reasoning_effort");
506
+ const supportsReasoningEffort =
507
+ supportedParams.includes("reasoning_effort") || supportedParams.includes("reasoning");
508
+
509
+ const name = typeof item.name === "string" && item.name.trim() !== "" ? item.name.trim() : undefined;
510
+ const identity = parseOpenRouterIdentity(fullId, item);
511
+ const meta: OfficialModelMeta = {
512
+ cost,
513
+ ...(costKnown ? {} : { costKnown: false }),
514
+ ...(quality ? { quality } : {}),
515
+ ...(identity ? { identity } : {}),
516
+ ...(name ? { name } : {}),
517
+ ...(contextWindow ? { contextWindow } : {}),
518
+ ...(maxTokens ? { maxTokens } : {}),
519
+ ...(isReasoning ? { reasoning: true } : {}),
520
+ ...(thinkingLevelMap ? { thinkingLevelMap } : {}),
521
+ input: hasImage ? ["text", "image"] : ["text"],
522
+ ...(supportsReasoningEffort ? { compat: { supportsReasoningEffort: true } } : {}),
523
+ };
524
+
525
+ setRecordValue(result, fullId, meta);
526
+
527
+ const strippedId = fullId.replace(/^[^/]+\//, "");
528
+ if (strippedId !== fullId) {
529
+ const candidates = aliasCandidates.get(strippedId) ?? new Map<string, OfficialModelMeta>();
530
+ candidates.set(fullId, meta);
531
+ aliasCandidates.set(strippedId, candidates);
532
+ }
533
+ }
534
+
535
+ for (const [strippedId, candidates] of aliasCandidates) {
536
+ const [meta] = candidates.values();
537
+ if (candidates.size === 1 && meta !== undefined && !Object.hasOwn(result, strippedId)) {
538
+ setRecordValue(result, strippedId, meta);
539
+ }
540
+ }
541
+
542
+ return result;
543
+ }
544
+
545
+ export function parseOpenRouterPricing(payload: unknown): Record<string, ProviderCost> {
546
+ const modelsMeta = parseOpenRouterModels(payload);
547
+ const res: Record<string, ProviderCost> = {};
548
+ for (const [key, meta] of Object.entries(modelsMeta)) {
549
+ if (meta.costKnown === false) continue;
550
+ res[key] = cloneCost(meta.cost);
551
+ }
552
+ return res;
553
+ }
554
+
555
+ function staleCache(
556
+ pricingUrl: string,
557
+ now: number,
558
+ maxStaleMs: number,
559
+ allowExpired = false,
560
+ ): Record<string, OfficialModelMeta> {
561
+ const cached = pricingCache.get(pricingUrl);
562
+ if (!cached) return {};
563
+ const age = Math.max(0, now - cached.updatedAt);
564
+ return allowExpired || age <= maxStaleMs ? cloneSnapshot(cached.snapshot) : {};
565
+ }
566
+
567
+ async function fetchOfficialPricingUncoalesced(
568
+ fetchFn: typeof globalThis.fetch,
569
+ pricingUrl: string,
570
+ timeoutMs: number,
571
+ cacheTtlMs: number,
572
+ maxStaleMs: number,
573
+ now: () => number,
574
+ cachePath?: string,
575
+ ): Promise<Record<string, OfficialModelMeta>> {
576
+ const persisted = await readPersistedPricingCache(cachePath, pricingUrl);
577
+ const allowPersistedStale = persisted !== undefined;
578
+ if (persisted !== undefined) {
579
+ const current = pricingCache.get(pricingUrl);
580
+ if (current === undefined || persisted.updatedAt > current.updatedAt) {
581
+ setPricingCache(persisted.snapshot, pricingUrl, persisted.updatedAt);
582
+ }
583
+ const cachedAge = getPricingCacheAge(pricingUrl, now());
584
+ if (cachedAge !== undefined && cachedAge <= cacheTtlMs) return getPricingCache(pricingUrl);
585
+ }
586
+
587
+ try {
588
+ const result = await withDeadline(async (signal) => {
589
+ const response = await fetchFn(pricingUrl, { signal });
590
+ if (!response.ok) return { ok: false as const };
591
+ const payload = await response.json();
592
+ return { ok: true as const, parsed: parseOpenRouterModels(payload) };
593
+ }, timeoutMs);
594
+ if (!result.ok) return staleCache(pricingUrl, now(), maxStaleMs, allowPersistedStale);
595
+ if (Object.keys(result.parsed).length > 0) {
596
+ const updatedAt = now();
597
+ setPricingCache(result.parsed, pricingUrl, updatedAt);
598
+ await writePersistedPricingCache(cachePath, pricingUrl, result.parsed, updatedAt);
599
+ return cloneSnapshot(result.parsed);
600
+ }
601
+ return staleCache(pricingUrl, now(), maxStaleMs, allowPersistedStale);
602
+ } catch {
603
+ return staleCache(pricingUrl, now(), maxStaleMs, allowPersistedStale);
604
+ }
605
+ }
606
+
607
+ function startPricingRequest(
608
+ fetchFn: typeof globalThis.fetch,
609
+ pricingUrl: string,
610
+ timeoutMs: number,
611
+ cacheTtlMs: number,
612
+ maxStaleMs: number,
613
+ now: () => number,
614
+ cachePath?: string,
615
+ ): Promise<Record<string, OfficialModelMeta>> {
616
+ const existing = pricingRequests.get(pricingUrl);
617
+ if (existing) return existing;
618
+
619
+ const request = fetchOfficialPricingUncoalesced(
620
+ fetchFn,
621
+ pricingUrl,
622
+ timeoutMs,
623
+ cacheTtlMs,
624
+ maxStaleMs,
625
+ now,
626
+ cachePath,
627
+ );
628
+ pricingRequests.set(pricingUrl, request);
629
+ void request.then(
630
+ () => {
631
+ if (pricingRequests.get(pricingUrl) === request) pricingRequests.delete(pricingUrl);
632
+ },
633
+ () => {
634
+ if (pricingRequests.get(pricingUrl) === request) pricingRequests.delete(pricingUrl);
635
+ },
636
+ );
637
+ return request;
638
+ }
639
+
640
+ function observeBackgroundRefresh(
641
+ request: Promise<Record<string, OfficialModelMeta>>,
642
+ callback: ((snapshot: Record<string, OfficialModelMeta>) => void) | undefined,
643
+ ): void {
644
+ if (!callback) return;
645
+ void request.then(
646
+ (snapshot) => {
647
+ try {
648
+ callback(snapshot);
649
+ } catch {
650
+ // A late metadata observer must never turn a completed fetch into an unhandled rejection.
651
+ }
652
+ },
653
+ () => undefined,
654
+ );
655
+ }
656
+
657
+ export async function fetchOfficialPricing(
658
+ fetchFn: typeof globalThis.fetch,
659
+ pricingUrl = OPENROUTER_MODELS_URL,
660
+ timeoutMs = 3_000,
661
+ cacheTtlMs = DEFAULT_PRICING_CACHE_TTL_MS,
662
+ maxStaleMs = DEFAULT_PRICING_MAX_STALE_MS,
663
+ now: () => number = Date.now,
664
+ options: OfficialPricingFetchOptions = {},
665
+ ): Promise<Record<string, OfficialModelMeta>> {
666
+ const currentTime = now();
667
+ const cachedAge = getPricingCacheAge(pricingUrl, currentTime);
668
+ if (cachedAge !== undefined && cachedAge <= cacheTtlMs) return getPricingCache(pricingUrl);
669
+
670
+ const existing = pricingRequests.get(pricingUrl);
671
+ if (existing) {
672
+ if (options.background === true) {
673
+ observeBackgroundRefresh(existing, options.onBackgroundRefresh);
674
+ return getPricingCache(pricingUrl);
675
+ }
676
+ return existing;
677
+ }
678
+
679
+ if (options.cachePath) {
680
+ const persisted = await readPersistedPricingCache(options.cachePath, pricingUrl);
681
+ if (persisted !== undefined) {
682
+ const current = pricingCache.get(pricingUrl);
683
+ if (current === undefined || persisted.updatedAt > current.updatedAt) {
684
+ setPricingCache(persisted.snapshot, pricingUrl, persisted.updatedAt);
685
+ }
686
+ const persistedAge = getPricingCacheAge(pricingUrl, now());
687
+ if (persistedAge !== undefined && persistedAge <= cacheTtlMs) return getPricingCache(pricingUrl);
688
+ }
689
+ }
690
+
691
+ const request = startPricingRequest(fetchFn, pricingUrl, timeoutMs, cacheTtlMs, maxStaleMs, now, options.cachePath);
692
+ if (options.background === true) {
693
+ observeBackgroundRefresh(request, options.onBackgroundRefresh);
694
+ void request.catch(() => undefined);
695
+ return getPricingCache(pricingUrl);
696
+ }
697
+ return request;
698
+ }
699
+
700
+ export function findOfficialCost(
701
+ modelId: string,
702
+ dynamicPricing: Record<string, OfficialModelMeta | ProviderCost> = {},
703
+ ): ProviderCost | undefined {
704
+ const meta = findOfficialMeta(modelId, dynamicPricing);
705
+ return meta && meta.costKnown !== false ? cloneCost(meta.cost) : undefined;
706
+ }
707
+
708
+ interface OfficialModelCandidate {
709
+ key: string;
710
+ meta: OfficialModelMeta;
711
+ provider?: string;
712
+ model: string;
713
+ familyId: string;
714
+ version?: string;
715
+ canonicalId?: string;
716
+ aliasTargetId?: string;
717
+ latestAlias: boolean;
718
+ created?: number;
719
+ }
720
+
721
+ function isOfficialModelMeta(value: OfficialModelMeta | ProviderCost): value is OfficialModelMeta {
722
+ return "cost" in value && typeof value.cost === "object";
723
+ }
724
+
725
+ function getOfficialMeta(value: OfficialModelMeta | ProviderCost): OfficialModelMeta {
726
+ return isOfficialModelMeta(value) ? cloneMeta(value) : { cost: cloneCost(value) };
727
+ }
728
+
729
+ function getOfficialCandidates(
730
+ dynamicPricing: Record<string, OfficialModelMeta | ProviderCost>,
731
+ ): OfficialModelCandidate[] {
732
+ const candidates: OfficialModelCandidate[] = [];
733
+ const seen = new Set<string>();
734
+ for (const [rawKey, value] of Object.entries(dynamicPricing)) {
735
+ const key = rawKey.toLowerCase().trim();
736
+ if (key === "") continue;
737
+ const meta = isOfficialModelMeta(value) ? value : { cost: value };
738
+ const sourceId = meta.identity?.sourceId ?? key;
739
+ if (seen.has(sourceId)) continue;
740
+ seen.add(sourceId);
741
+ const referenceId = meta.identity?.sourceId ?? key;
742
+ const { provider, model } = splitModelId(referenceId);
743
+ candidates.push({
744
+ key,
745
+ meta,
746
+ provider,
747
+ model,
748
+ familyId: meta.identity?.familyId ?? getModelFamilyId(referenceId),
749
+ version: meta.identity?.version ?? getModelVersion(meta.identity?.canonicalId ?? referenceId),
750
+ canonicalId: meta.identity?.canonicalId,
751
+ aliasTargetId: meta.identity?.aliasTargetId,
752
+ latestAlias: meta.identity?.latestAlias ?? model.endsWith("-latest"),
753
+ created: meta.identity?.created,
754
+ });
755
+ }
756
+ return candidates;
757
+ }
758
+
759
+ function hasExplicitModelVariant(model: string): boolean {
760
+ return (
761
+ getModelVersion(model) !== undefined ||
762
+ /:[^/]+$/.test(model) ||
763
+ /-(?:minimal|low|medium|high|xhigh|max|thinking|reasoning)$/i.test(model)
764
+ );
765
+ }
766
+
767
+ function compareCandidateAge(left: OfficialModelCandidate, right: OfficialModelCandidate): number {
768
+ if (left.version !== undefined && right.version !== undefined && left.version !== right.version) {
769
+ if (left.version.length !== right.version.length) return left.version.length - right.version.length;
770
+ return left.version.localeCompare(right.version);
771
+ }
772
+ if (left.created !== undefined && right.created !== undefined && left.created !== right.created) {
773
+ return left.created - right.created;
774
+ }
775
+ return left.key.localeCompare(right.key);
776
+ }
777
+
778
+ function candidateMatchesTarget(candidate: OfficialModelCandidate, target: string): boolean {
779
+ const normalizedTarget = target.toLowerCase().trim();
780
+ return [candidate.key, candidate.meta.identity?.sourceId, candidate.canonicalId]
781
+ .filter((value): value is string => value !== undefined)
782
+ .some((value) => value === normalizedTarget);
783
+ }
784
+
785
+ function chooseLatestCandidate(candidates: OfficialModelCandidate[]): OfficialModelCandidate | undefined {
786
+ for (const alias of candidates.filter(
787
+ (candidate) => candidate.latestAlias && candidate.aliasTargetId !== undefined,
788
+ )) {
789
+ const targetId = alias.aliasTargetId;
790
+ if (targetId === undefined) continue;
791
+ const target = candidates.find((candidate) => candidateMatchesTarget(candidate, targetId));
792
+ if (target !== undefined) return target;
793
+ }
794
+ return candidates.reduce<OfficialModelCandidate | undefined>(
795
+ (best, candidate) => (best === undefined || compareCandidateAge(candidate, best) > 0 ? candidate : best),
796
+ undefined,
797
+ );
798
+ }
799
+
800
+ function sameQualityVersion(left: OfficialModelCandidate, right: OfficialModelCandidate): boolean {
801
+ if (left.version === undefined && right.version === undefined) return true;
802
+ return left.version !== undefined && left.version === right.version;
803
+ }
804
+
805
+ function mergeQualityVariants(
806
+ selected: OfficialModelCandidate,
807
+ candidates: OfficialModelCandidate[],
808
+ ): ModelQualityScore[] | undefined {
809
+ const best = new Map<string, ModelQualityScore>();
810
+ for (const candidate of candidates) {
811
+ if (candidate.familyId !== selected.familyId || !sameQualityVersion(candidate, selected)) continue;
812
+ for (const score of candidate.meta.quality ?? []) {
813
+ const key = [score.source, score.benchmark, score.category, score.metric].join("\\0");
814
+ const previous = best.get(key);
815
+ if (previous === undefined || score.value > previous.value) {
816
+ best.set(key, {
817
+ ...score,
818
+ ...(score.confidenceInterval ? { confidenceInterval: { ...score.confidenceInterval } } : {}),
819
+ });
820
+ }
821
+ }
822
+ }
823
+ return best.size > 0 ? [...best.values()] : undefined;
824
+ }
825
+
826
+ export function findOfficialMeta(
827
+ modelId: string,
828
+ dynamicPricing: Record<string, OfficialModelMeta | ProviderCost> = {},
829
+ ): OfficialModelMeta | undefined {
830
+ const normalized = modelId.toLowerCase().trim();
831
+ if (normalized === "") return undefined;
832
+ const requested = splitModelId(normalized);
833
+ const requestedFamily = stripModelVariant(requested.model);
834
+ const candidates = getOfficialCandidates(dynamicPricing);
835
+ const exact = candidates.find((candidate) => candidate.key === normalized || candidate.canonicalId === normalized);
836
+ const matching = candidates.filter((candidate) => {
837
+ if (requested.provider !== undefined && candidate.provider !== requested.provider) return false;
838
+ return splitModelId(candidate.familyId).model === requestedFamily;
839
+ });
840
+ const providerCount = new Set(matching.map((candidate) => candidate.provider ?? "")).size;
841
+ if (requested.provider === undefined && providerCount > 1) return undefined;
842
+ if (matching.length === 0) return undefined;
843
+
844
+ const selected =
845
+ hasExplicitModelVariant(requested.model) && exact !== undefined ? exact : chooseLatestCandidate(matching);
846
+ if (selected === undefined) return undefined;
847
+ const result = getOfficialMeta(selected.meta);
848
+ const quality = mergeQualityVariants(selected, matching);
849
+ if (quality !== undefined) result.quality = quality;
850
+ return result;
851
+ }
852
+
853
+ export function applyOfficialModelCosts(
854
+ models: ProviderModelDraft[],
855
+ dynamicPricing: Record<string, OfficialModelMeta | ProviderCost> = {},
856
+ ): ProviderModelDraft[] {
857
+ return models.map((model) => {
858
+ const meta = findOfficialMeta(model.id, dynamicPricing);
859
+ if (!meta) return model;
860
+
861
+ const useOfficialCost =
862
+ meta.costKnown !== false && (model.cost === undefined || model.pricingSource === "official");
863
+ const merged: ProviderModelDraft = {
864
+ ...model,
865
+ ...(useOfficialCost ? { cost: cloneCost(meta.cost), pricingSource: "official" as const } : {}),
866
+ ...(model.cost !== undefined && !useOfficialCost ? { cost: cloneCost(model.cost) } : {}),
867
+ };
868
+ if (merged.name === undefined && meta.name !== undefined) merged.name = meta.name;
869
+ if (merged.contextWindow === undefined && meta.contextWindow !== undefined) {
870
+ merged.contextWindow = meta.contextWindow;
871
+ }
872
+ if (merged.maxTokens === undefined && meta.maxTokens !== undefined) merged.maxTokens = meta.maxTokens;
873
+ if (merged.reasoning === undefined && meta.reasoning !== undefined) merged.reasoning = meta.reasoning;
874
+ if (merged.thinkingLevelMap === undefined && meta.thinkingLevelMap !== undefined) {
875
+ merged.thinkingLevelMap = { ...meta.thinkingLevelMap };
876
+ }
877
+ if (merged.input === undefined && meta.input !== undefined) merged.input = [...meta.input];
878
+ if (meta.compat !== undefined) merged.compat = { ...meta.compat, ...merged.compat };
879
+ return merged;
880
+ });
881
+ }