@jameslovespancakes/pi-plus 1.0.0 → 1.0.1

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 (40) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +190 -190
  3. package/config/pi-plus.example.json +60 -60
  4. package/config/skills/model-routing/SKILL.md +86 -86
  5. package/images/pi-plus.svg +10 -10
  6. package/package.json +67 -67
  7. package/server/board-server.mjs +641 -641
  8. package/server/package.json +17 -17
  9. package/src/core/accounts/registry.ts +93 -93
  10. package/src/core/anthropic/client-identity.ts +241 -241
  11. package/src/core/catalog/quality.ts +314 -314
  12. package/src/core/config.ts +169 -169
  13. package/src/core/env.ts +58 -58
  14. package/src/core/exec/process.ts +146 -146
  15. package/src/core/exec/ssh-config.ts +157 -157
  16. package/src/core/policy/policy.ts +183 -183
  17. package/src/core/quota/pool.ts +64 -64
  18. package/src/core/quota/usage-source.ts +289 -289
  19. package/src/core/store.ts +43 -43
  20. package/src/domains/agents/board-setup.ts +409 -409
  21. package/src/domains/agents/index.ts +462 -462
  22. package/src/domains/models/catalog-tool.ts +361 -361
  23. package/src/domains/models/index.ts +14 -14
  24. package/src/domains/models/policy-gate.ts +169 -169
  25. package/src/domains/models/provider-picker.ts +207 -207
  26. package/src/domains/remote/config-path.ts +41 -41
  27. package/src/domains/remote/index.ts +866 -866
  28. package/src/domains/remote/setup.ts +425 -425
  29. package/src/domains/setup/index.ts +220 -220
  30. package/src/domains/subscriptions/accounts.ts +242 -242
  31. package/src/domains/subscriptions/footer.ts +182 -182
  32. package/src/domains/subscriptions/index.ts +42 -42
  33. package/src/domains/subscriptions/provider.ts +219 -219
  34. package/src/domains/subscriptions/providers/anthropic.ts +149 -149
  35. package/src/domains/subscriptions/providers/codex.ts +148 -148
  36. package/src/domains/subscriptions/routing.ts +72 -72
  37. package/src/services/usage-service.ts +186 -186
  38. package/src/ui/format.ts +73 -73
  39. package/src/ui/usage-bars.ts +154 -154
  40. package/src/vendor/anthropic.ts +109 -109
@@ -1,314 +1,314 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { env } from "../env.ts";
5
-
6
- /**
7
- * Complete Artificial Analysis model dataset, cached on disk.
8
- *
9
- * Every field the API returns is stored verbatim: all 17 benchmark
10
- * evaluations, all pricing fields, and all latency/throughput measurements.
11
- * Derived cost-efficiency ratios are computed on top. All network access
12
- * degrades to the cache so routing never blocks on the network.
13
- *
14
- * Refresh semantics deliberately mirror pi's own remote model catalog
15
- * (`dist/core/remote-catalog-provider.js`): restore from disk first, only go to
16
- * the network when the entry is older than the refresh interval, and revalidate
17
- * with `If-None-Match` so an unchanged dataset costs one 304 instead of a
18
- * multi-megabyte download.
19
- */
20
-
21
- const AA_URL = "https://artificialanalysis.ai/api/v2/data/llms/models";
22
- /** Matches pi's REMOTE_CATALOG_REFRESH_INTERVAL_MS. */
23
- export const REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
24
- const TIMEOUT_MS = 30_000;
25
-
26
- export type Confidence = "measured" | "inferred" | "unrated";
27
-
28
- /** Every evaluation key Artificial Analysis publishes, with display metadata. */
29
- export const EVALUATIONS = [
30
- { key: "artificial_analysis_intelligence_index", label: "Intelligence Index", scale: "index", group: "composite" },
31
- { key: "artificial_analysis_coding_index", label: "Coding Index", scale: "index", group: "composite" },
32
- { key: "artificial_analysis_math_index", label: "Math Index", scale: "index", group: "composite" },
33
- { key: "terminalbench_hard", label: "Terminal-Bench Hard", scale: "fraction", group: "agentic" },
34
- { key: "terminalbench_v2_1", label: "Terminal-Bench 2.1", scale: "fraction", group: "agentic" },
35
- { key: "tau2", label: "τ²-bench (agentic tools)", scale: "fraction", group: "agentic" },
36
- { key: "tau_banking", label: "τ-bench Banking", scale: "fraction", group: "agentic" },
37
- { key: "livecodebench", label: "LiveCodeBench", scale: "fraction", group: "coding" },
38
- { key: "scicode", label: "SciCode", scale: "fraction", group: "coding" },
39
- { key: "lcr", label: "Long Context Reasoning", scale: "fraction", group: "reasoning" },
40
- { key: "gpqa", label: "GPQA Diamond", scale: "fraction", group: "reasoning" },
41
- { key: "hle", label: "Humanity's Last Exam", scale: "fraction", group: "reasoning" },
42
- { key: "mmlu_pro", label: "MMLU-Pro", scale: "fraction", group: "reasoning" },
43
- { key: "ifbench", label: "IFBench (instruction following)", scale: "fraction", group: "reasoning" },
44
- { key: "aime_25", label: "AIME 2025", scale: "fraction", group: "math" },
45
- { key: "aime", label: "AIME", scale: "fraction", group: "math" },
46
- { key: "math_500", label: "MATH-500", scale: "fraction", group: "math" },
47
- ] as const;
48
-
49
- export type EvaluationKey = (typeof EVALUATIONS)[number]["key"];
50
-
51
- export interface QualityRecord {
52
- slug: string;
53
- name: string;
54
- creator: string;
55
- releaseDate?: string;
56
- evaluations: Partial<Record<EvaluationKey, number>>;
57
- pricing: {
58
- inputPer1M?: number;
59
- outputPer1M?: number;
60
- blended3to1Per1M?: number;
61
- };
62
- performance: {
63
- outputTokensPerSecond?: number;
64
- timeToFirstTokenSeconds?: number;
65
- timeToFirstAnswerTokenSeconds?: number;
66
- };
67
- }
68
-
69
- /**
70
- * Same shape as pi's ModelsStoreEntry, so the two caches can be reasoned about
71
- * (and debugged) identically.
72
- */
73
- interface QualityStore {
74
- records: Record<string, QualityRecord>;
75
- /** Unix timestamp of the last completed remote check. */
76
- checkedAt?: number;
77
- /** Unix timestamp from the remote dataset's Last-Modified header. */
78
- lastModified?: number;
79
- /** Opaque ETag validator, stored verbatim and echoed back as If-None-Match. */
80
- etag?: string;
81
- source: string;
82
- }
83
-
84
- let cache: QualityStore | undefined;
85
- let inFlight: Promise<string[]> | undefined;
86
-
87
- function agentDir(): string {
88
- return process.env.PI_AGENT_DIR ?? join(homedir(), ".pi", "agent");
89
- }
90
-
91
- function cachePath(): string {
92
- return join(agentDir(), "model-quality.json");
93
- }
94
-
95
- function readKey(): string | undefined {
96
- return env("ARTIFICIAL_ANALYSIS_API_KEY");
97
- }
98
-
99
- /** `openrouter/z-ai/glm-5.3` -> `glm-5-3`, `openai-codex/gpt-5.6-luna` -> `gpt-5-6-luna`. */
100
- export function normalizeSlug(modelId: string): string {
101
- const bare = modelId.includes("/") ? modelId.slice(modelId.lastIndexOf("/") + 1) : modelId;
102
- return bare.toLowerCase().replace(/[._]/g, "-").replace(/-latest$/, "").replace(/-\d{8}$/, "");
103
- }
104
-
105
- function num(value: unknown): number | undefined {
106
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
107
- }
108
-
109
- /** Fractions (0-1) are reported as percentages so every metric shares one scale. */
110
- export function normalizedScore(key: EvaluationKey, value: number | undefined): number | undefined {
111
- if (value === undefined) return undefined;
112
- const definition = EVALUATIONS.find((entry) => entry.key === key);
113
- return definition?.scale === "fraction" ? value * 100 : value;
114
- }
115
-
116
- function loadCache(): void {
117
- if (cache) return;
118
- try {
119
- const parsed = JSON.parse(readFileSync(cachePath(), "utf8")) as QualityStore & { fetchedAt?: number };
120
- if (!parsed?.records) return;
121
- // Migrate the pre-store format, which only had `fetchedAt`.
122
- cache = {
123
- records: parsed.records,
124
- checkedAt: parsed.checkedAt ?? parsed.fetchedAt,
125
- lastModified: parsed.lastModified,
126
- etag: parsed.etag,
127
- source: parsed.source ?? "artificial-analysis",
128
- };
129
- } catch { /* first run */ }
130
- }
131
-
132
- function persist(next: QualityStore): void {
133
- cache = next;
134
- try {
135
- writeFileSync(cachePath(), JSON.stringify(next), "utf8");
136
- } catch { /* cache write is best effort */ }
137
- }
138
-
139
- export function qualityAgeMs(): number | undefined {
140
- loadCache();
141
- return cache?.checkedAt !== undefined ? Date.now() - cache.checkedAt : undefined;
142
- }
143
-
144
- /** Cache metadata, for display by /model-info refresh. */
145
- export function qualityStatus(): { checkedAt?: number; lastModified?: number; records: number; hasEtag: boolean } {
146
- loadCache();
147
- return {
148
- checkedAt: cache?.checkedAt,
149
- lastModified: cache?.lastModified,
150
- records: Object.keys(cache?.records ?? {}).length,
151
- hasEtag: !!cache?.etag,
152
- };
153
- }
154
-
155
- export function qualityRecordCount(): number {
156
- loadCache();
157
- return Object.keys(cache?.records ?? {}).length;
158
- }
159
-
160
- export async function refreshQuality(force = false): Promise<string[]> {
161
- loadCache();
162
-
163
- // Restored from disk and still inside the window: nothing to do.
164
- const age = qualityAgeMs();
165
- if (!force && age !== undefined && age < REFRESH_INTERVAL_MS) return [];
166
- if (inFlight) return inFlight;
167
-
168
- inFlight = (async () => {
169
- const warnings: string[] = [];
170
- const key = readKey();
171
- if (!key) return ["artificial-analysis: no API key, run /model-info setup"];
172
-
173
- const stored = cache;
174
- // Only revalidate when a cached body backs the validator, so a 304 can never
175
- // leave the dataset empty.
176
- const validator = stored && Object.keys(stored.records).length > 0 ? stored.etag : undefined;
177
-
178
- try {
179
- const response = await fetch(AA_URL, {
180
- headers: {
181
- "x-api-key": key,
182
- ...(validator ? { "if-none-match": validator } : {}),
183
- },
184
- signal: AbortSignal.timeout(TIMEOUT_MS),
185
- });
186
-
187
- const checkedAt = Date.now();
188
-
189
- if (response.status === 304 && stored) {
190
- // Unchanged upstream: keep the dataset and the validator, stamp the check.
191
- persist({ ...stored, checkedAt });
192
- return [];
193
- }
194
-
195
- if (!response.ok) {
196
- // Drop the etag so the next attempt re-downloads rather than revalidating
197
- // against a validator we can no longer trust.
198
- if (stored) persist({ ...stored, checkedAt, etag: undefined });
199
- return [`artificial-analysis: HTTP ${response.status}`];
200
- }
201
-
202
- const body = await response.json() as { data?: any[] };
203
- const records: Record<string, QualityRecord> = {};
204
-
205
- for (const model of body.data ?? []) {
206
- const slug = String(model.slug ?? "").toLowerCase();
207
- if (!slug) continue;
208
- const source = model.evaluations ?? {};
209
- const evaluations: Partial<Record<EvaluationKey, number>> = {};
210
- for (const { key: evaluationKey } of EVALUATIONS) {
211
- const value = num(source[evaluationKey]);
212
- if (value !== undefined) evaluations[evaluationKey] = value;
213
- }
214
- const pricing = model.pricing ?? {};
215
- records[slug] = {
216
- slug,
217
- name: String(model.name ?? slug),
218
- creator: String(model.model_creator?.name ?? "unknown"),
219
- releaseDate: typeof model.release_date === "string" ? model.release_date : undefined,
220
- evaluations,
221
- pricing: {
222
- inputPer1M: num(pricing.price_1m_input_tokens),
223
- outputPer1M: num(pricing.price_1m_output_tokens),
224
- blended3to1Per1M: num(pricing.price_1m_blended_3_to_1),
225
- },
226
- performance: {
227
- outputTokensPerSecond: num(model.median_output_tokens_per_second),
228
- timeToFirstTokenSeconds: num(model.median_time_to_first_token_seconds),
229
- timeToFirstAnswerTokenSeconds: num(model.median_time_to_first_answer_token),
230
- },
231
- };
232
- }
233
-
234
- if (Object.keys(records).length === 0) {
235
- if (stored) persist({ ...stored, checkedAt, etag: undefined });
236
- return ["artificial-analysis: empty dataset"];
237
- }
238
-
239
- const lastModifiedHeader = response.headers.get("last-modified");
240
- const lastModified = lastModifiedHeader ? Date.parse(lastModifiedHeader) : undefined;
241
- persist({
242
- records,
243
- checkedAt,
244
- lastModified: Number.isNaN(lastModified) ? undefined : lastModified,
245
- etag: response.headers.get("etag") ?? undefined,
246
- source: "artificial-analysis",
247
- });
248
- } catch (error) {
249
- // Network failure keeps the old dataset usable but forces a full re-fetch
250
- // next time rather than trusting a stale validator.
251
- if (cache) persist({ ...cache, checkedAt: Date.now(), etag: undefined });
252
- warnings.push(`artificial-analysis: ${error instanceof Error ? error.message : String(error)}`);
253
- }
254
- return warnings;
255
- })().finally(() => { inFlight = undefined; });
256
-
257
- return inFlight;
258
- }
259
-
260
- export interface CostEfficiency {
261
- /** Intelligence index per dollar of blended 3:1 spend. */
262
- intelligencePerDollar?: number;
263
- codingPerDollar?: number;
264
- agenticPerDollar?: number;
265
- }
266
-
267
- export function costEfficiency(record: QualityRecord | undefined): CostEfficiency {
268
- if (!record) return {};
269
- const price = record.pricing.blended3to1Per1M;
270
- if (price === undefined || price <= 0) return {};
271
- const ratio = (value: number | undefined) => (value === undefined ? undefined : Number((value / price).toFixed(2)));
272
- return {
273
- intelligencePerDollar: ratio(record.evaluations.artificial_analysis_intelligence_index),
274
- codingPerDollar: ratio(record.evaluations.artificial_analysis_coding_index),
275
- agenticPerDollar: ratio(normalizedScore("terminalbench_hard", record.evaluations.terminalbench_hard)),
276
- };
277
- }
278
-
279
- export interface QualityLookup {
280
- record?: QualityRecord;
281
- efficiency: CostEfficiency;
282
- confidence: Confidence;
283
- basis: string;
284
- }
285
-
286
- /**
287
- * Resolve a pi model id, preferring the entry matching the requested thinking
288
- * level, then the base model, then the closest same-family sibling.
289
- */
290
- export function qualityFor(modelId: string, thinkingLevel?: string): QualityLookup {
291
- loadCache();
292
- const records = cache?.records ?? {};
293
- const base = normalizeSlug(modelId);
294
-
295
- const levelled = thinkingLevel && thinkingLevel !== "off" ? records[`${base}-${thinkingLevel}`] : undefined;
296
- if (levelled) return { record: levelled, efficiency: costEfficiency(levelled), confidence: "measured", basis: levelled.slug };
297
-
298
- const exact = records[base];
299
- if (exact) return { record: exact, efficiency: costEfficiency(exact), confidence: "measured", basis: exact.slug };
300
-
301
- let best: QualityRecord | undefined;
302
- for (const record of Object.values(records)) {
303
- if (!base.includes(record.slug) && !record.slug.includes(base)) continue;
304
- if (!best || record.slug.length > best.slug.length) best = record;
305
- }
306
- if (best) return { record: best, efficiency: costEfficiency(best), confidence: "inferred", basis: `nearest match ${best.slug}` };
307
-
308
- return { efficiency: {}, confidence: "unrated", basis: "no Artificial Analysis entry" };
309
- }
310
-
311
- export function allRecords(): QualityRecord[] {
312
- loadCache();
313
- return Object.values(cache?.records ?? {});
314
- }
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { env } from "../env.ts";
5
+
6
+ /**
7
+ * Complete Artificial Analysis model dataset, cached on disk.
8
+ *
9
+ * Every field the API returns is stored verbatim: all 17 benchmark
10
+ * evaluations, all pricing fields, and all latency/throughput measurements.
11
+ * Derived cost-efficiency ratios are computed on top. All network access
12
+ * degrades to the cache so routing never blocks on the network.
13
+ *
14
+ * Refresh semantics deliberately mirror pi's own remote model catalog
15
+ * (`dist/core/remote-catalog-provider.js`): restore from disk first, only go to
16
+ * the network when the entry is older than the refresh interval, and revalidate
17
+ * with `If-None-Match` so an unchanged dataset costs one 304 instead of a
18
+ * multi-megabyte download.
19
+ */
20
+
21
+ const AA_URL = "https://artificialanalysis.ai/api/v2/data/llms/models";
22
+ /** Matches pi's REMOTE_CATALOG_REFRESH_INTERVAL_MS. */
23
+ export const REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
24
+ const TIMEOUT_MS = 30_000;
25
+
26
+ export type Confidence = "measured" | "inferred" | "unrated";
27
+
28
+ /** Every evaluation key Artificial Analysis publishes, with display metadata. */
29
+ export const EVALUATIONS = [
30
+ { key: "artificial_analysis_intelligence_index", label: "Intelligence Index", scale: "index", group: "composite" },
31
+ { key: "artificial_analysis_coding_index", label: "Coding Index", scale: "index", group: "composite" },
32
+ { key: "artificial_analysis_math_index", label: "Math Index", scale: "index", group: "composite" },
33
+ { key: "terminalbench_hard", label: "Terminal-Bench Hard", scale: "fraction", group: "agentic" },
34
+ { key: "terminalbench_v2_1", label: "Terminal-Bench 2.1", scale: "fraction", group: "agentic" },
35
+ { key: "tau2", label: "τ²-bench (agentic tools)", scale: "fraction", group: "agentic" },
36
+ { key: "tau_banking", label: "τ-bench Banking", scale: "fraction", group: "agentic" },
37
+ { key: "livecodebench", label: "LiveCodeBench", scale: "fraction", group: "coding" },
38
+ { key: "scicode", label: "SciCode", scale: "fraction", group: "coding" },
39
+ { key: "lcr", label: "Long Context Reasoning", scale: "fraction", group: "reasoning" },
40
+ { key: "gpqa", label: "GPQA Diamond", scale: "fraction", group: "reasoning" },
41
+ { key: "hle", label: "Humanity's Last Exam", scale: "fraction", group: "reasoning" },
42
+ { key: "mmlu_pro", label: "MMLU-Pro", scale: "fraction", group: "reasoning" },
43
+ { key: "ifbench", label: "IFBench (instruction following)", scale: "fraction", group: "reasoning" },
44
+ { key: "aime_25", label: "AIME 2025", scale: "fraction", group: "math" },
45
+ { key: "aime", label: "AIME", scale: "fraction", group: "math" },
46
+ { key: "math_500", label: "MATH-500", scale: "fraction", group: "math" },
47
+ ] as const;
48
+
49
+ export type EvaluationKey = (typeof EVALUATIONS)[number]["key"];
50
+
51
+ export interface QualityRecord {
52
+ slug: string;
53
+ name: string;
54
+ creator: string;
55
+ releaseDate?: string;
56
+ evaluations: Partial<Record<EvaluationKey, number>>;
57
+ pricing: {
58
+ inputPer1M?: number;
59
+ outputPer1M?: number;
60
+ blended3to1Per1M?: number;
61
+ };
62
+ performance: {
63
+ outputTokensPerSecond?: number;
64
+ timeToFirstTokenSeconds?: number;
65
+ timeToFirstAnswerTokenSeconds?: number;
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Same shape as pi's ModelsStoreEntry, so the two caches can be reasoned about
71
+ * (and debugged) identically.
72
+ */
73
+ interface QualityStore {
74
+ records: Record<string, QualityRecord>;
75
+ /** Unix timestamp of the last completed remote check. */
76
+ checkedAt?: number;
77
+ /** Unix timestamp from the remote dataset's Last-Modified header. */
78
+ lastModified?: number;
79
+ /** Opaque ETag validator, stored verbatim and echoed back as If-None-Match. */
80
+ etag?: string;
81
+ source: string;
82
+ }
83
+
84
+ let cache: QualityStore | undefined;
85
+ let inFlight: Promise<string[]> | undefined;
86
+
87
+ function agentDir(): string {
88
+ return process.env.PI_AGENT_DIR ?? join(homedir(), ".pi", "agent");
89
+ }
90
+
91
+ function cachePath(): string {
92
+ return join(agentDir(), "model-quality.json");
93
+ }
94
+
95
+ function readKey(): string | undefined {
96
+ return env("ARTIFICIAL_ANALYSIS_API_KEY");
97
+ }
98
+
99
+ /** `openrouter/z-ai/glm-5.3` -> `glm-5-3`, `openai-codex/gpt-5.6-luna` -> `gpt-5-6-luna`. */
100
+ export function normalizeSlug(modelId: string): string {
101
+ const bare = modelId.includes("/") ? modelId.slice(modelId.lastIndexOf("/") + 1) : modelId;
102
+ return bare.toLowerCase().replace(/[._]/g, "-").replace(/-latest$/, "").replace(/-\d{8}$/, "");
103
+ }
104
+
105
+ function num(value: unknown): number | undefined {
106
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
107
+ }
108
+
109
+ /** Fractions (0-1) are reported as percentages so every metric shares one scale. */
110
+ export function normalizedScore(key: EvaluationKey, value: number | undefined): number | undefined {
111
+ if (value === undefined) return undefined;
112
+ const definition = EVALUATIONS.find((entry) => entry.key === key);
113
+ return definition?.scale === "fraction" ? value * 100 : value;
114
+ }
115
+
116
+ function loadCache(): void {
117
+ if (cache) return;
118
+ try {
119
+ const parsed = JSON.parse(readFileSync(cachePath(), "utf8")) as QualityStore & { fetchedAt?: number };
120
+ if (!parsed?.records) return;
121
+ // Migrate the pre-store format, which only had `fetchedAt`.
122
+ cache = {
123
+ records: parsed.records,
124
+ checkedAt: parsed.checkedAt ?? parsed.fetchedAt,
125
+ lastModified: parsed.lastModified,
126
+ etag: parsed.etag,
127
+ source: parsed.source ?? "artificial-analysis",
128
+ };
129
+ } catch { /* first run */ }
130
+ }
131
+
132
+ function persist(next: QualityStore): void {
133
+ cache = next;
134
+ try {
135
+ writeFileSync(cachePath(), JSON.stringify(next), "utf8");
136
+ } catch { /* cache write is best effort */ }
137
+ }
138
+
139
+ export function qualityAgeMs(): number | undefined {
140
+ loadCache();
141
+ return cache?.checkedAt !== undefined ? Date.now() - cache.checkedAt : undefined;
142
+ }
143
+
144
+ /** Cache metadata, for display by /model-info refresh. */
145
+ export function qualityStatus(): { checkedAt?: number; lastModified?: number; records: number; hasEtag: boolean } {
146
+ loadCache();
147
+ return {
148
+ checkedAt: cache?.checkedAt,
149
+ lastModified: cache?.lastModified,
150
+ records: Object.keys(cache?.records ?? {}).length,
151
+ hasEtag: !!cache?.etag,
152
+ };
153
+ }
154
+
155
+ export function qualityRecordCount(): number {
156
+ loadCache();
157
+ return Object.keys(cache?.records ?? {}).length;
158
+ }
159
+
160
+ export async function refreshQuality(force = false): Promise<string[]> {
161
+ loadCache();
162
+
163
+ // Restored from disk and still inside the window: nothing to do.
164
+ const age = qualityAgeMs();
165
+ if (!force && age !== undefined && age < REFRESH_INTERVAL_MS) return [];
166
+ if (inFlight) return inFlight;
167
+
168
+ inFlight = (async () => {
169
+ const warnings: string[] = [];
170
+ const key = readKey();
171
+ if (!key) return ["artificial-analysis: no API key, run /model-info setup"];
172
+
173
+ const stored = cache;
174
+ // Only revalidate when a cached body backs the validator, so a 304 can never
175
+ // leave the dataset empty.
176
+ const validator = stored && Object.keys(stored.records).length > 0 ? stored.etag : undefined;
177
+
178
+ try {
179
+ const response = await fetch(AA_URL, {
180
+ headers: {
181
+ "x-api-key": key,
182
+ ...(validator ? { "if-none-match": validator } : {}),
183
+ },
184
+ signal: AbortSignal.timeout(TIMEOUT_MS),
185
+ });
186
+
187
+ const checkedAt = Date.now();
188
+
189
+ if (response.status === 304 && stored) {
190
+ // Unchanged upstream: keep the dataset and the validator, stamp the check.
191
+ persist({ ...stored, checkedAt });
192
+ return [];
193
+ }
194
+
195
+ if (!response.ok) {
196
+ // Drop the etag so the next attempt re-downloads rather than revalidating
197
+ // against a validator we can no longer trust.
198
+ if (stored) persist({ ...stored, checkedAt, etag: undefined });
199
+ return [`artificial-analysis: HTTP ${response.status}`];
200
+ }
201
+
202
+ const body = await response.json() as { data?: any[] };
203
+ const records: Record<string, QualityRecord> = {};
204
+
205
+ for (const model of body.data ?? []) {
206
+ const slug = String(model.slug ?? "").toLowerCase();
207
+ if (!slug) continue;
208
+ const source = model.evaluations ?? {};
209
+ const evaluations: Partial<Record<EvaluationKey, number>> = {};
210
+ for (const { key: evaluationKey } of EVALUATIONS) {
211
+ const value = num(source[evaluationKey]);
212
+ if (value !== undefined) evaluations[evaluationKey] = value;
213
+ }
214
+ const pricing = model.pricing ?? {};
215
+ records[slug] = {
216
+ slug,
217
+ name: String(model.name ?? slug),
218
+ creator: String(model.model_creator?.name ?? "unknown"),
219
+ releaseDate: typeof model.release_date === "string" ? model.release_date : undefined,
220
+ evaluations,
221
+ pricing: {
222
+ inputPer1M: num(pricing.price_1m_input_tokens),
223
+ outputPer1M: num(pricing.price_1m_output_tokens),
224
+ blended3to1Per1M: num(pricing.price_1m_blended_3_to_1),
225
+ },
226
+ performance: {
227
+ outputTokensPerSecond: num(model.median_output_tokens_per_second),
228
+ timeToFirstTokenSeconds: num(model.median_time_to_first_token_seconds),
229
+ timeToFirstAnswerTokenSeconds: num(model.median_time_to_first_answer_token),
230
+ },
231
+ };
232
+ }
233
+
234
+ if (Object.keys(records).length === 0) {
235
+ if (stored) persist({ ...stored, checkedAt, etag: undefined });
236
+ return ["artificial-analysis: empty dataset"];
237
+ }
238
+
239
+ const lastModifiedHeader = response.headers.get("last-modified");
240
+ const lastModified = lastModifiedHeader ? Date.parse(lastModifiedHeader) : undefined;
241
+ persist({
242
+ records,
243
+ checkedAt,
244
+ lastModified: Number.isNaN(lastModified) ? undefined : lastModified,
245
+ etag: response.headers.get("etag") ?? undefined,
246
+ source: "artificial-analysis",
247
+ });
248
+ } catch (error) {
249
+ // Network failure keeps the old dataset usable but forces a full re-fetch
250
+ // next time rather than trusting a stale validator.
251
+ if (cache) persist({ ...cache, checkedAt: Date.now(), etag: undefined });
252
+ warnings.push(`artificial-analysis: ${error instanceof Error ? error.message : String(error)}`);
253
+ }
254
+ return warnings;
255
+ })().finally(() => { inFlight = undefined; });
256
+
257
+ return inFlight;
258
+ }
259
+
260
+ export interface CostEfficiency {
261
+ /** Intelligence index per dollar of blended 3:1 spend. */
262
+ intelligencePerDollar?: number;
263
+ codingPerDollar?: number;
264
+ agenticPerDollar?: number;
265
+ }
266
+
267
+ export function costEfficiency(record: QualityRecord | undefined): CostEfficiency {
268
+ if (!record) return {};
269
+ const price = record.pricing.blended3to1Per1M;
270
+ if (price === undefined || price <= 0) return {};
271
+ const ratio = (value: number | undefined) => (value === undefined ? undefined : Number((value / price).toFixed(2)));
272
+ return {
273
+ intelligencePerDollar: ratio(record.evaluations.artificial_analysis_intelligence_index),
274
+ codingPerDollar: ratio(record.evaluations.artificial_analysis_coding_index),
275
+ agenticPerDollar: ratio(normalizedScore("terminalbench_hard", record.evaluations.terminalbench_hard)),
276
+ };
277
+ }
278
+
279
+ export interface QualityLookup {
280
+ record?: QualityRecord;
281
+ efficiency: CostEfficiency;
282
+ confidence: Confidence;
283
+ basis: string;
284
+ }
285
+
286
+ /**
287
+ * Resolve a pi model id, preferring the entry matching the requested thinking
288
+ * level, then the base model, then the closest same-family sibling.
289
+ */
290
+ export function qualityFor(modelId: string, thinkingLevel?: string): QualityLookup {
291
+ loadCache();
292
+ const records = cache?.records ?? {};
293
+ const base = normalizeSlug(modelId);
294
+
295
+ const levelled = thinkingLevel && thinkingLevel !== "off" ? records[`${base}-${thinkingLevel}`] : undefined;
296
+ if (levelled) return { record: levelled, efficiency: costEfficiency(levelled), confidence: "measured", basis: levelled.slug };
297
+
298
+ const exact = records[base];
299
+ if (exact) return { record: exact, efficiency: costEfficiency(exact), confidence: "measured", basis: exact.slug };
300
+
301
+ let best: QualityRecord | undefined;
302
+ for (const record of Object.values(records)) {
303
+ if (!base.includes(record.slug) && !record.slug.includes(base)) continue;
304
+ if (!best || record.slug.length > best.slug.length) best = record;
305
+ }
306
+ if (best) return { record: best, efficiency: costEfficiency(best), confidence: "inferred", basis: `nearest match ${best.slug}` };
307
+
308
+ return { efficiency: {}, confidence: "unrated", basis: "no Artificial Analysis entry" };
309
+ }
310
+
311
+ export function allRecords(): QualityRecord[] {
312
+ loadCache();
313
+ return Object.values(cache?.records ?? {});
314
+ }