@xynogen/pix-data 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,13 +19,21 @@ Pi coding agent extension — shared model data layer. Fetches and caches [model
19
19
  ## Install
20
20
 
21
21
  ```bash
22
- pi install git:github.com/xynogen/pix-data
22
+ pi install npm:@xynogen/pix-data
23
23
  ```
24
24
 
25
25
  ## How it works
26
26
 
27
27
  On session start the extension fires two parallel background fetches (`modelsDev.get()` + `benchmark.get()`). If the cache is fresh the fetches are skipped. Both cache files live in `~/.cache/pi/` — any Pi extension using the same `DataSource` + cache paths will share data automatically.
28
28
 
29
+ ## Full distro
30
+
31
+ To install the complete pix suite (all packages + Pi itself):
32
+
33
+ ```bash
34
+ curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
35
+ ```
36
+
29
37
  ## License
30
38
 
31
39
  MIT
package/package.json CHANGED
@@ -1,36 +1,44 @@
1
1
  {
2
- "name": "@xynogen/pix-data",
3
- "version": "0.1.0",
4
- "description": "Pi extension — shared model data layer (models.dev + BenchLM), cached at ~/.cache/pi",
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "scripts": {
8
- "test": "bun test"
9
- },
10
- "files": [
11
- "src",
12
- "README.md",
13
- "LICENSE"
14
- ],
15
- "pi": {
16
- "extensions": ["src/index.ts"]
17
- },
18
- "keywords": ["pi", "pi-package", "pi-extension", "models.dev", "benchlm"],
19
- "author": "xynogen",
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/xynogen/pix-mono.git",
24
- "directory": "packages/pix-data"
25
- },
26
- "homepage": "https://github.com/xynogen/pix-mono/tree/main/packages/pix-data#readme",
27
- "bugs": {
28
- "url": "https://github.com/xynogen/pix-mono/issues"
29
- },
30
- "publishConfig": {
31
- "access": "public"
32
- },
33
- "peerDependencies": {
34
- "@earendil-works/pi-coding-agent": "*"
35
- }
2
+ "name": "@xynogen/pix-data",
3
+ "version": "0.1.2",
4
+ "description": "Pi extension — shared model data layer (models.dev + BenchLM), cached at ~/.cache/pi",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "scripts": {
8
+ "test": "bun test"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "pi": {
16
+ "extensions": [
17
+ "src/index.ts"
18
+ ]
19
+ },
20
+ "keywords": [
21
+ "pi",
22
+ "pi-package",
23
+ "pi-extension",
24
+ "models.dev",
25
+ "benchlm"
26
+ ],
27
+ "author": "xynogen",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/xynogen/pix-mono.git",
32
+ "directory": "packages/pix-data"
33
+ },
34
+ "homepage": "https://github.com/xynogen/pix-mono/tree/main/packages/pix-data#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/xynogen/pix-mono/issues"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "@earendil-works/pi-coding-agent": "*"
43
+ }
36
44
  }
package/src/data.test.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
2
  import {
3
+ type BenchmarkEntry,
4
+ benchmark,
3
5
  buildModelsDevIndex,
6
+ lookupBenchmark,
4
7
  lookupInIndex,
5
8
  lookupModelsDev,
6
- lookupBenchmark,
7
- modelsDev,
8
- benchmark,
9
9
  type ModelsDevApi,
10
10
  type ModelsDevModel,
11
- type BenchmarkEntry,
11
+ modelsDev,
12
12
  } from "./data.ts";
13
13
 
14
14
  // ── buildModelsDevIndex ──────────────────────────────────────────────────────
@@ -17,13 +17,24 @@ describe("buildModelsDevIndex", () => {
17
17
  const api: ModelsDevApi = {
18
18
  anthropic: {
19
19
  models: {
20
- "claude-sonnet-4-5": { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
21
- "claude-opus-4": { id: "claude-opus-4", name: "Claude Opus 4", reasoning: true },
20
+ "claude-sonnet-4-5": {
21
+ id: "claude-sonnet-4-5",
22
+ name: "Claude Sonnet 4.5",
23
+ },
24
+ "claude-opus-4": {
25
+ id: "claude-opus-4",
26
+ name: "Claude Opus 4",
27
+ reasoning: true,
28
+ },
22
29
  },
23
30
  },
24
31
  openai: {
25
32
  models: {
26
- "gpt-4o": { id: "gpt-4o", name: "GPT-4o", modalities: { input: ["text", "image"] } },
33
+ "gpt-4o": {
34
+ id: "gpt-4o",
35
+ name: "GPT-4o",
36
+ modalities: { input: ["text", "image"] },
37
+ },
27
38
  },
28
39
  },
29
40
  };
@@ -39,7 +50,10 @@ describe("buildModelsDevIndex", () => {
39
50
  const a: ModelsDevApi = {
40
51
  anthropic: {
41
52
  models: {
42
- "claude-sonnet-4-5-20250514": { id: "claude-sonnet-4-5-20250514", name: "Claude Sonnet 4.5" },
53
+ "claude-sonnet-4-5-20250514": {
54
+ id: "claude-sonnet-4-5-20250514",
55
+ name: "Claude Sonnet 4.5",
56
+ },
43
57
  },
44
58
  },
45
59
  };
@@ -73,7 +87,10 @@ describe("lookupInIndex", () => {
73
87
  index = buildModelsDevIndex({
74
88
  anthropic: {
75
89
  models: {
76
- "claude-sonnet-4-5": { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
90
+ "claude-sonnet-4-5": {
91
+ id: "claude-sonnet-4-5",
92
+ name: "Claude Sonnet 4.5",
93
+ },
77
94
  "claude-opus-4": { id: "claude-opus-4", name: "Claude Opus 4" },
78
95
  },
79
96
  },
@@ -87,23 +104,33 @@ describe("lookupInIndex", () => {
87
104
  });
88
105
 
89
106
  it("finds exact match", () => {
90
- expect(lookupInIndex("claude-sonnet-4-5", index)?.name).toBe("Claude Sonnet 4.5");
107
+ expect(lookupInIndex("claude-sonnet-4-5", index)?.name).toBe(
108
+ "Claude Sonnet 4.5",
109
+ );
91
110
  });
92
111
 
93
112
  it("strips provider prefix (provider/model)", () => {
94
- expect(lookupInIndex("anthropic/claude-opus-4", index)?.name).toBe("Claude Opus 4");
113
+ expect(lookupInIndex("anthropic/claude-opus-4", index)?.name).toBe(
114
+ "Claude Opus 4",
115
+ );
95
116
  });
96
117
 
97
118
  it("strips deep prefix (cc/model)", () => {
98
- expect(lookupInIndex("cc/claude-opus-4", index)?.name).toBe("Claude Opus 4");
119
+ expect(lookupInIndex("cc/claude-opus-4", index)?.name).toBe(
120
+ "Claude Opus 4",
121
+ );
99
122
  });
100
123
 
101
124
  it("strips date suffix", () => {
102
- expect(lookupInIndex("claude-sonnet-4-5-20250514", index)?.name).toBe("Claude Sonnet 4.5");
125
+ expect(lookupInIndex("claude-sonnet-4-5-20250514", index)?.name).toBe(
126
+ "Claude Sonnet 4.5",
127
+ );
103
128
  });
104
129
 
105
130
  it("strips provider prefix + date suffix", () => {
106
- expect(lookupInIndex("anthropic/claude-sonnet-4-5-20250514", index)?.name).toBe("Claude Sonnet 4.5");
131
+ expect(
132
+ lookupInIndex("anthropic/claude-sonnet-4-5-20250514", index)?.name,
133
+ ).toBe("Claude Sonnet 4.5");
107
134
  });
108
135
 
109
136
  it("returns undefined for unknown model", () => {
@@ -123,7 +150,10 @@ describe("lookupModelsDev", () => {
123
150
  (modelsDev as any)._mem = {
124
151
  anthropic: {
125
152
  models: {
126
- "claude-sonnet-4-5": { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
153
+ "claude-sonnet-4-5": {
154
+ id: "claude-sonnet-4-5",
155
+ name: "Claude Sonnet 4.5",
156
+ },
127
157
  },
128
158
  },
129
159
  openai: {
@@ -139,7 +169,9 @@ describe("lookupModelsDev", () => {
139
169
  });
140
170
 
141
171
  it("finds by exact provider + id", () => {
142
- expect(lookupModelsDev("anthropic", "claude-sonnet-4-5")?.name).toBe("Claude Sonnet 4.5");
172
+ expect(lookupModelsDev("anthropic", "claude-sonnet-4-5")?.name).toBe(
173
+ "Claude Sonnet 4.5",
174
+ );
143
175
  });
144
176
 
145
177
  it("falls back across providers when provider miss", () => {
@@ -147,9 +179,9 @@ describe("lookupModelsDev", () => {
147
179
  });
148
180
 
149
181
  it("strips path prefix from id", () => {
150
- expect(lookupModelsDev("anthropic", "anthropic/claude-sonnet-4-5")?.name).toBe(
151
- "Claude Sonnet 4.5",
152
- );
182
+ expect(
183
+ lookupModelsDev("anthropic", "anthropic/claude-sonnet-4-5")?.name,
184
+ ).toBe("Claude Sonnet 4.5");
153
185
  });
154
186
 
155
187
  it("returns undefined for unknown model", () => {
@@ -161,9 +193,30 @@ describe("lookupModelsDev", () => {
161
193
 
162
194
  describe("lookupBenchmark", () => {
163
195
  const entries: BenchmarkEntry[] = [
164
- { rank: 1, model: "Claude Sonnet 4.5", creator: "Anthropic", overallScore: 95, inputPrice: 3, outputPrice: 15 },
165
- { rank: 2, model: "GPT-4o", creator: "OpenAI", overallScore: 90, inputPrice: 5, outputPrice: 15 },
166
- { rank: 3, model: "Gemini 1.5 Pro", creator: "Google", overallScore: 88, inputPrice: 3.5, outputPrice: 10.5 },
196
+ {
197
+ rank: 1,
198
+ model: "Claude Sonnet 4.5",
199
+ creator: "Anthropic",
200
+ overallScore: 95,
201
+ inputPrice: 3,
202
+ outputPrice: 15,
203
+ },
204
+ {
205
+ rank: 2,
206
+ model: "GPT-4o",
207
+ creator: "OpenAI",
208
+ overallScore: 90,
209
+ inputPrice: 5,
210
+ outputPrice: 15,
211
+ },
212
+ {
213
+ rank: 3,
214
+ model: "Gemini 1.5 Pro",
215
+ creator: "Google",
216
+ overallScore: 88,
217
+ inputPrice: 3.5,
218
+ outputPrice: 10.5,
219
+ },
167
220
  ];
168
221
 
169
222
  beforeEach(() => {
package/src/data.ts CHANGED
@@ -20,8 +20,8 @@
20
20
  * import { lookupModelsDev, lookupBenchmark } from "./data.ts";
21
21
  */
22
22
 
23
- import { mkdir, readFile, writeFile } from "node:fs/promises";
24
23
  import { existsSync, readFileSync } from "node:fs";
24
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
25
25
  import { homedir } from "node:os";
26
26
  import { dirname, join } from "node:path";
27
27
 
@@ -131,7 +131,11 @@ export class DataSource<T> {
131
131
  try {
132
132
  const url =
133
133
  typeof this.opts.url === "function" ? this.opts.url() : this.opts.url;
134
- const response = await fetchWithTimeout(url, this.opts.timeoutMs, this.opts.headers());
134
+ const response = await fetchWithTimeout(
135
+ url,
136
+ this.opts.timeoutMs,
137
+ this.opts.headers(),
138
+ );
135
139
  if (!response.ok)
136
140
  throw new Error(`${this.opts.label} fetch failed: ${response.status}`);
137
141
  const raw = await response.json();
@@ -142,7 +146,9 @@ export class DataSource<T> {
142
146
  } catch (error) {
143
147
  const msg = error instanceof Error ? error.message : String(error);
144
148
  if (cached !== undefined) {
145
- console.warn(`${this.opts.label} fetch failed, using stale cache: ${msg}`);
149
+ console.warn(
150
+ `${this.opts.label} fetch failed, using stale cache: ${msg}`,
151
+ );
146
152
  const val = this.opts.parseCache(cached.data);
147
153
  this._mem = val;
148
154
  return val;
@@ -152,7 +158,9 @@ export class DataSource<T> {
152
158
  }
153
159
  }
154
160
 
155
- private async _readCache(): Promise<{ ts: number; data: unknown } | undefined> {
161
+ private async _readCache(): Promise<
162
+ { ts: number; data: unknown } | undefined
163
+ > {
156
164
  try {
157
165
  const raw = await readFile(this.opts.cachePath, "utf8");
158
166
  const parsed = JSON.parse(raw) as { ts: number; data: unknown };
@@ -166,7 +174,10 @@ export class DataSource<T> {
166
174
  private async _writeCache(data: unknown): Promise<void> {
167
175
  try {
168
176
  await mkdir(dirname(this.opts.cachePath), { recursive: true });
169
- await writeFile(this.opts.cachePath, JSON.stringify({ ts: Date.now(), data }));
177
+ await writeFile(
178
+ this.opts.cachePath,
179
+ JSON.stringify({ ts: Date.now(), data }),
180
+ );
170
181
  } catch {
171
182
  // Write failure is non-fatal — stale cache used on next run
172
183
  }
@@ -226,7 +237,9 @@ function stripPrefix(id: string): string {
226
237
  return i >= 0 ? id.slice(i + 1) : id;
227
238
  }
228
239
 
229
- export function buildModelsDevIndex(api: ModelsDevApi): Map<string, ModelsDevModel> {
240
+ export function buildModelsDevIndex(
241
+ api: ModelsDevApi,
242
+ ): Map<string, ModelsDevModel> {
230
243
  const index = new Map<string, ModelsDevModel>();
231
244
  for (const provider of Object.values(api)) {
232
245
  if (!provider?.models) continue;
@@ -254,7 +267,10 @@ export function lookupInIndex(
254
267
  return undefined;
255
268
  }
256
269
 
257
- export function lookupModelsDev(provider: string, id: string): ModelsDevModel | undefined {
270
+ export function lookupModelsDev(
271
+ provider: string,
272
+ id: string,
273
+ ): ModelsDevModel | undefined {
258
274
  const data = modelsDev.getCached();
259
275
  const canonical = id.includes("/") ? id.slice(id.lastIndexOf("/") + 1) : id;
260
276
  const exact = data[provider]?.models?.[canonical];
@@ -266,12 +282,18 @@ export function lookupModelsDev(provider: string, id: string): ModelsDevModel |
266
282
  return undefined;
267
283
  }
268
284
 
269
- export async function fetchModelsDevIndex(): Promise<Map<string, ModelsDevModel>> {
285
+ export async function fetchModelsDevIndex(): Promise<
286
+ Map<string, ModelsDevModel>
287
+ > {
270
288
  return buildModelsDevIndex(await modelsDev.get());
271
289
  }
272
290
 
273
291
  function normBench(s: string): string {
274
- return s.toLowerCase().replace(/[-_.]+/g, " ").replace(/\s+/g, " ").trim();
292
+ return s
293
+ .toLowerCase()
294
+ .replace(/[-_.]+/g, " ")
295
+ .replace(/\s+/g, " ")
296
+ .trim();
275
297
  }
276
298
 
277
299
  export function lookupBenchmark(modelName: string): BenchmarkEntry | undefined {
package/src/index.ts CHANGED
@@ -8,7 +8,27 @@
8
8
  */
9
9
 
10
10
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
- import { modelsDev, benchmark } from "./data.ts";
11
+ import { benchmark, modelsDev } from "./data.ts";
12
+
13
+ export type {
14
+ BenchmarkEntry,
15
+ ModelsDevApi,
16
+ ModelsDevModel,
17
+ } from "./data.ts";
18
+ // Public data API — single source of truth for the shared model data layer.
19
+ // Consumers (pix-core, pix-9router, …) import these instead of duplicating
20
+ // the DataSource implementation and models.dev/BenchLM lookups.
21
+ export {
22
+ benchmark,
23
+ buildModelsDevIndex,
24
+ CACHE_DIR,
25
+ DataSource,
26
+ fetchModelsDevIndex,
27
+ lookupBenchmark,
28
+ lookupInIndex,
29
+ lookupModelsDev,
30
+ modelsDev,
31
+ } from "./data.ts";
12
32
 
13
33
  export default function (_pi: ExtensionAPI): void {
14
34
  void modelsDev.get();