@xynogen/pix-models 0.2.4 → 0.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-models",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Pi extension — enhanced /models picker with BenchLM ranks",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "src",
16
+ "!src/**/*.test.*",
16
17
  "README.md",
17
18
  "LICENSE"
18
19
  ],
@@ -42,7 +43,7 @@
42
43
  "access": "public"
43
44
  },
44
45
  "dependencies": {
45
- "@xynogen/pix-data": "^0.4.3",
46
+ "@xynogen/pix-data": "^0.5.0",
46
47
  "@xynogen/pix-pretty": "^1.11.2",
47
48
  "@xynogen/pix-runtime": "^0.8.0"
48
49
  },
@@ -1,319 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- benchStars,
4
- filterModelItems,
5
- fmtCost,
6
- fmtCtx,
7
- type ModelSearchLookup,
8
- normalizeModelText,
9
- resolveContextWindow,
10
- sortModels,
11
- stepEffectiveThinkingLevel,
12
- stepThinkingLevel,
13
- THINKING_LEVELS,
14
- } from "./models.ts";
15
-
16
- describe("stepThinkingLevel", () => {
17
- it("steps up one notch", () => {
18
- expect(stepThinkingLevel("low", 1)).toBe("medium");
19
- expect(stepThinkingLevel("off", 1)).toBe("minimal");
20
- });
21
- it("steps down one notch", () => {
22
- expect(stepThinkingLevel("medium", -1)).toBe("low");
23
- expect(stepThinkingLevel("xhigh", -1)).toBe("high");
24
- });
25
- it("clamps at the low end (no wrap)", () => {
26
- expect(stepThinkingLevel("off", -1)).toBe("off");
27
- });
28
- it("clamps at the high end (no wrap)", () => {
29
- expect(stepThinkingLevel("xhigh", 1)).toBe("xhigh");
30
- });
31
- it("falls back to a medium-anchored step for unknown input", () => {
32
- expect(stepThinkingLevel("bogus", 1)).toBe("high");
33
- expect(stepThinkingLevel("", -1)).toBe("low");
34
- });
35
- it("exposes the six canonical levels ascending", () => {
36
- expect(THINKING_LEVELS).toEqual(["off", "minimal", "low", "medium", "high", "xhigh"]);
37
- });
38
- it("can walk the full ladder up and back down", () => {
39
- let lvl: string = "off";
40
- for (let i = 0; i < 10; i++) lvl = stepThinkingLevel(lvl, 1);
41
- expect(lvl).toBe("xhigh");
42
- for (let i = 0; i < 10; i++) lvl = stepThinkingLevel(lvl, -1);
43
- expect(lvl).toBe("off");
44
- });
45
- });
46
-
47
- describe("stepEffectiveThinkingLevel", () => {
48
- it("skips levels that clamp back to the current level", () => {
49
- const clamp = (requested: string) => {
50
- if (requested === "minimal" || requested === "low") return "off";
51
- return requested;
52
- };
53
- expect(stepEffectiveThinkingLevel("off", 1, clamp)).toBe("medium");
54
- });
55
-
56
- it("stays at an endpoint when every farther request clamps back", () => {
57
- expect(stepEffectiveThinkingLevel("high", 1, () => "high")).toBe("high");
58
- });
59
- });
60
-
61
- describe("resolveContextWindow", () => {
62
- it("prefers provider contextWindow over fallback", () => {
63
- expect(resolveContextWindow({ contextWindow: 200_000 }, 128_000)).toBe(200_000);
64
- });
65
- it("falls back to dev limit when provider is 0/missing", () => {
66
- expect(resolveContextWindow({ contextWindow: 0 }, 128_000)).toBe(128_000);
67
- expect(resolveContextWindow({}, 64_000)).toBe(64_000);
68
- });
69
- it("ignores non-finite and negative values", () => {
70
- expect(resolveContextWindow({ contextWindow: Number.NaN }, 128_000)).toBe(128_000);
71
- expect(resolveContextWindow({ contextWindow: Number.POSITIVE_INFINITY }, 50_000)).toBe(50_000);
72
- expect(resolveContextWindow({ contextWindow: -1 }, 32_000)).toBe(32_000);
73
- });
74
- it("returns 0 when both missing/invalid", () => {
75
- expect(resolveContextWindow({}, undefined)).toBe(0);
76
- expect(resolveContextWindow({ contextWindow: null }, null)).toBe(0);
77
- });
78
- });
79
-
80
- describe("fmtCtx", () => {
81
- it("formats 0 as 0", () => expect(fmtCtx(0)).toBe("0"));
82
- it("formats small numbers as-is", () => expect(fmtCtx(512)).toBe("512"));
83
- it("formats thousands as Nk", () => {
84
- expect(fmtCtx(128_000)).toBe("128k");
85
- expect(fmtCtx(8_192)).toBe("8k");
86
- });
87
- it("formats millions as NM", () => {
88
- expect(fmtCtx(1_000_000)).toBe("1M");
89
- expect(fmtCtx(2_000_000)).toBe("2M");
90
- expect(fmtCtx(1_500_000)).toBe("1.5M");
91
- });
92
- });
93
-
94
- describe("fmtCost", () => {
95
- it("returns — for undefined entry", () => expect(fmtCost(undefined)).toBe("—"));
96
- it("returns — when no cost field", () => expect(fmtCost({})).toBe("—"));
97
- it("returns free when both 0", () => {
98
- expect(fmtCost({ cost: { input: 0, output: 0 } })).toBe("free");
99
- });
100
- it("formats input/output costs", () => {
101
- expect(fmtCost({ cost: { input: 3, output: 15 } })).toBe("3.00/15.00");
102
- });
103
- it("handles missing input/output as 0", () => {
104
- expect(fmtCost({ cost: {} })).toBe("free");
105
- });
106
- });
107
-
108
- describe("benchStars", () => {
109
- it("gives 5 stars for score >= 90", () => {
110
- expect(benchStars(95).filled).toBe(5);
111
- expect(benchStars(90).filled).toBe(5);
112
- });
113
- it("gives 4 stars for 80-89", () => {
114
- expect(benchStars(85).filled).toBe(4);
115
- expect(benchStars(80).filled).toBe(4);
116
- });
117
- it("gives 3 stars for 70-79", () => {
118
- expect(benchStars(75).filled).toBe(3);
119
- });
120
- it("gives 2 stars for 50-69", () => {
121
- expect(benchStars(60).filled).toBe(2);
122
- expect(benchStars(50).filled).toBe(2);
123
- });
124
- it("gives 1 star for score < 50", () => {
125
- expect(benchStars(30).filled).toBe(1);
126
- expect(benchStars(0).filled).toBe(1);
127
- });
128
- it("gives 1 star for null/undefined", () => {
129
- expect(benchStars(null).filled).toBe(1);
130
- expect(benchStars(undefined).filled).toBe(1);
131
- });
132
- it("filled + empty always = 5", () => {
133
- for (const s of [0, 50, 70, 80, 90, 100]) {
134
- const { filled, empty } = benchStars(s);
135
- expect(filled + empty).toBe(5);
136
- }
137
- });
138
- });
139
-
140
- describe("sortModels", () => {
141
- const models = [
142
- { provider: "a", id: "m1", name: "Zebra", score: 80 },
143
- { provider: "a", id: "m2", name: "Alpha", score: 95 },
144
- { provider: "a", id: "m3", name: "Middle", score: null },
145
- { provider: "a", id: "m4", name: "Beta", score: 80 },
146
- ];
147
- type ModelEntry = (typeof models)[number];
148
-
149
- it("sorts by score descending", () => {
150
- const sorted = sortModels(models);
151
- expect((sorted[0] as ModelEntry).name).toBe("Alpha"); // score 95
152
- });
153
-
154
- it("breaks score ties alphabetically by name", () => {
155
- const sorted = sortModels(models);
156
- const tiedIdx = sorted.findIndex((m) => m.name === "Beta");
157
- const zebraIdx = sorted.findIndex((m) => m.name === "Zebra");
158
- expect(tiedIdx).toBeLessThan(zebraIdx); // Beta before Zebra, both score 80
159
- });
160
-
161
- it("puts null score models last", () => {
162
- const sorted = sortModels(models);
163
- expect((sorted[sorted.length - 1] as ModelEntry).name).toBe("Middle");
164
- });
165
-
166
- it("does not mutate the original array", () => {
167
- const original = [...models];
168
- sortModels(models);
169
- expect(models).toEqual(original);
170
- });
171
-
172
- it("puts null score models after all scored models regardless of source order", () => {
173
- const shuffled = [
174
- { provider: "a", id: "m1", name: "Zeta", score: null },
175
- { provider: "a", id: "m2", name: "Beta", score: 80 },
176
- { provider: "a", id: "m3", name: "Alpha", score: 95 },
177
- { provider: "a", id: "m4", name: "Gamma", score: null },
178
- { provider: "a", id: "m5", name: "Delta", score: 60 },
179
- ];
180
- const sorted = sortModels(shuffled);
181
- const lastTwo = sorted.slice(-2).map((m) => m.name);
182
- expect(lastTwo).toEqual(["Gamma", "Zeta"]); // nulls last, stable within
183
- });
184
-
185
- it("sinks tier 2 (off-catalog) below tier 1 (benched-but-unscored)", () => {
186
- // Mirrors the openrouter/owl-alpha bug: a model with no bench entry at
187
- // all must not interleave with benched models that happen to have a
188
- // null score.
189
- const mixed = [
190
- { provider: "a", id: "m1", name: "Alpha", score: 95, tier: 0 },
191
- { provider: "a", id: "m2", name: "Beta", score: null, tier: 1 },
192
- { provider: "a", id: "m3", name: "Gamma", score: undefined, tier: 2 },
193
- { provider: "a", id: "m4", name: "Delta", score: 60, tier: 0 },
194
- ];
195
- const sorted = sortModels(mixed);
196
- expect(sorted.map((m) => m.name)).toEqual(["Alpha", "Delta", "Beta", "Gamma"]);
197
- });
198
- });
199
-
200
- // ─── normalizeModelText ───────────────────────────────────────────────────────
201
-
202
- describe("normalizeModelText", () => {
203
- it("lowercases", () => {
204
- expect(normalizeModelText("GLM-5.2")).toBe("glm52");
205
- });
206
-
207
- it("strips hyphens", () => {
208
- expect(normalizeModelText("claude-opus-4-8")).toBe("claudeopus48");
209
- });
210
-
211
- it("strips dots", () => {
212
- expect(normalizeModelText("qwen3.7-max")).toBe("qwen37max");
213
- });
214
-
215
- it("strips all non-alphanumeric", () => {
216
- expect(normalizeModelText("a!b@c#d$e%f^g&h")).toBe("abcdefgh");
217
- });
218
-
219
- it("handles plain alphanumeric", () => {
220
- expect(normalizeModelText("abc123")).toBe("abc123");
221
- });
222
-
223
- it("handles empty string", () => {
224
- expect(normalizeModelText("")).toBe("");
225
- });
226
- });
227
-
228
- // ─── filterModelItems ─────────────────────────────────────────────────────────
229
-
230
- describe("filterModelItems", () => {
231
- // Fixture: models with ids matching the acceptance criteria.
232
- // Ranks are assigned so some overlap with digit-queries.
233
- const mk = (id: string, rank?: number) => ({
234
- value: `p/${id}`,
235
- _rank: rank,
236
- });
237
-
238
- type Fixture = ReturnType<typeof mk>;
239
-
240
- /** Build lookup maps exactly like production: haystack = `${id} ${name ?? ""}`. */
241
- function buildLookup(items: Fixture[]): ModelSearchLookup {
242
- const rankByValue = new Map<string, number>();
243
- const searchTextByValue = new Map<string, string>();
244
- const normalizedByValue = new Map<string, string>();
245
- for (const it of items) {
246
- // value is "p/glm-5.2" — extract the id part.
247
- const id = it.value.split("/")[1] ?? "";
248
- if (it._rank != null) rankByValue.set(it.value, it._rank);
249
- const text = `${id} ${""}`; // no name in test fixture
250
- searchTextByValue.set(it.value, text);
251
- normalizedByValue.set(it.value, normalizeModelText(text));
252
- }
253
- return { rankByValue, searchTextByValue, normalizedByValue };
254
- }
255
-
256
- // Full fixture with ranks: glm-5.2→3, minimax-m3→8, claude-opus-4-8→2.
257
- const allItems: Fixture[] = [
258
- mk("glm-5.2", 3),
259
- mk("glm-5.1"),
260
- mk("minimax-m3", 8),
261
- mk("claude-opus-4-8", 2),
262
- mk("claude-sonnet-4-6"),
263
- mk("qwen3.7-max"),
264
- ];
265
- const allLookup = buildLookup(allItems);
266
-
267
- function values(result: Fixture[]): string[] {
268
- return result.map((it) => it.value);
269
- }
270
-
271
- it("empty query returns all items in original order", () => {
272
- const result = filterModelItems(allItems, "", allLookup);
273
- expect(values(result)).toEqual(allItems.map((it) => it.value));
274
- });
275
-
276
- it("query '52' includes glm-5.2 via normalized substring", () => {
277
- const result = filterModelItems(allItems, "52", allLookup);
278
- expect(values(result)).toContain("p/glm-5.2");
279
- });
280
-
281
- it("query 'm3' includes minimax-m3 first (substring priority over fuzzy noise)", () => {
282
- const result = filterModelItems(allItems, "m3", allLookup);
283
- expect(values(result)[0]).toBe("p/minimax-m3");
284
- });
285
-
286
- it("query '48' includes claude-opus-4-8", () => {
287
- const result = filterModelItems(allItems, "48", allLookup);
288
- expect(values(result)).toContain("p/claude-opus-4-8");
289
- });
290
-
291
- it("exact id query 'glm-5.2' still matches glm-5.2", () => {
292
- const result = filterModelItems(allItems, "glm-5.2", allLookup);
293
- expect(values(result)).toContain("p/glm-5.2");
294
- });
295
-
296
- it("digit query matching a rank returns that ranked item first", () => {
297
- // glm-5.2 has rank 3 — query "3" should put it first.
298
- const result = filterModelItems(allItems, "3", allLookup);
299
- expect(values(result)[0]).toBe("p/glm-5.2");
300
- });
301
-
302
- it("dedupe: item matching both rank and substring appears once", () => {
303
- // claude-opus-4-8 has rank 2; query "2" matches by rank AND
304
- // normalized substring ("2" ⊂ "claudeopus48"). Should appear once.
305
- const result = filterModelItems(allItems, "2", allLookup);
306
- const occurrences = values(result).filter((v) => v === "p/claude-opus-4-8");
307
- expect(occurrences.length).toBe(1);
308
- });
309
-
310
- it("digit query '8' (rank 8) returns minimax-m3 as rank match first", () => {
311
- const result = filterModelItems(allItems, "8", allLookup);
312
- expect(values(result)[0]).toBe("p/minimax-m3");
313
- });
314
-
315
- it("whitespace query is treated as empty", () => {
316
- const result = filterModelItems(allItems, " ", allLookup);
317
- expect(values(result)).toEqual(allItems.map((it) => it.value));
318
- });
319
- });
@@ -1,101 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { stripBuiltinModelCommand } from "./patch-builtin.ts";
6
-
7
- const UNPATCHED = `export const BUILTIN_SLASH_COMMANDS = [
8
- { name: "settings", description: "Open settings menu" },
9
- { name: "model", description: "Select model (opens selector UI)" },
10
- { name: "login", description: "Configure provider authentication" },
11
- ];
12
- `;
13
-
14
- const CURRENT_PI = `export const BUILTIN_SLASH_COMMANDS = [
15
- { name: "settings", description: "Open settings menu" },
16
- { name: "model", description: "Select model (opens selector UI)", argumentHint: "<provider/model>" },
17
- { name: "scoped-models", description: "Enable/disable models for Ctrl+P cycling" },
18
- ];
19
- `;
20
-
21
- describe("patch-builtin /model removal", () => {
22
- it("removes the built-in /model line and keeps neighbors", () => {
23
- const out = stripBuiltinModelCommand(UNPATCHED);
24
- expect(out).not.toContain('name: "model"');
25
- expect(out).toContain('name: "settings"');
26
- expect(out).toContain('name: "login"');
27
- });
28
-
29
- it("is idempotent — second pass is a no-op", () => {
30
- const once = stripBuiltinModelCommand(UNPATCHED);
31
- const twice = stripBuiltinModelCommand(once);
32
- expect(twice).toBe(once);
33
- });
34
-
35
- it("leaves an already-clean file untouched", () => {
36
- const clean = `export const X = [\n { name: "login" },\n];\n`;
37
- expect(stripBuiltinModelCommand(clean)).toBe(clean);
38
- });
39
-
40
- it("does not strip the plural /models entry", () => {
41
- const withPlural = `export const BUILTIN_SLASH_COMMANDS = [
42
- { name: "models", description: "Enhanced picker" },
43
- { name: "model", description: "Select model (opens selector UI)" },
44
- ];`;
45
- const out = stripBuiltinModelCommand(withPlural);
46
- expect(out).toContain('name: "models"');
47
- expect(out).not.toContain('{ name: "model", description');
48
- });
49
-
50
- it("removes Pi's current /model form with an argument hint", () => {
51
- const out = stripBuiltinModelCommand(CURRENT_PI);
52
- expect(out).not.toContain('name: "model"');
53
- expect(out).toContain('name: "settings"');
54
- expect(out).toContain('name: "scoped-models"');
55
- });
56
-
57
- it("does not alter /model text outside the built-in command array", () => {
58
- const source = `const source = '{ name: "model" }';
59
- export const BUILTIN_SLASH_COMMANDS = [
60
- { name: "settings", description: "Open settings menu" },
61
- ];
62
- `;
63
- expect(stripBuiltinModelCommand(source)).toBe(source);
64
- });
65
-
66
- it("removes a multi-line /model command without touching adjacent entries", () => {
67
- const multiline = `export const BUILTIN_SLASH_COMMANDS = [
68
- { name: "settings", description: "Open settings menu" },
69
- {
70
- name: "model",
71
- description: "Select model (opens selector UI)",
72
- argumentHint: "<provider/model>",
73
- },
74
- { name: "login", description: "Configure provider authentication" },
75
- ];
76
- `;
77
- const out = stripBuiltinModelCommand(multiline);
78
- expect(out).not.toContain('name: "model"');
79
- expect(out).toContain('name: "settings"');
80
- expect(out).toContain('name: "login"');
81
- });
82
-
83
- it("removes /model from Pi's minified bundle chunk", () => {
84
- // Real form from dist/bundle/chunks/*.js: no `export const`, no spaces,
85
- // array followed immediately by other bundled statements.
86
- const minified = `var x=1;BUILTIN_SLASH_COMMANDS=[{name:"settings",description:"Open settings menu"},{name:"model",description:"Select model (opens selector UI)",argumentHint:"<provider/model>"},{name:"scoped-models",description:"Enable/disable models for Ctrl+P cycling"}];import path11 from"node:path";`;
87
- const out = stripBuiltinModelCommand(minified);
88
- expect(out).not.toContain('name:"model"');
89
- expect(out).toContain('name:"settings"');
90
- expect(out).toContain('name:"scoped-models"');
91
- expect(out).toContain('import path11 from"node:path"');
92
- });
93
-
94
- it("round-trips through disk", () => {
95
- const dir = mkdtempSync(join(tmpdir(), "pix-patch-"));
96
- const file = join(dir, "slash-commands.js");
97
- writeFileSync(file, CURRENT_PI, "utf8");
98
- writeFileSync(file, stripBuiltinModelCommand(readFileSync(file, "utf8")), "utf8");
99
- expect(readFileSync(file, "utf8")).not.toContain('name: "model"');
100
- });
101
- });