@xynogen/pix-models 0.1.15 → 0.1.16
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 +1 -1
- package/src/models.test.ts +2 -8
- package/src/models.ts +70 -109
- package/src/patch-builtin.test.ts +2 -6
- package/src/patch-builtin.ts +3 -14
package/package.json
CHANGED
package/src/models.test.ts
CHANGED
|
@@ -16,8 +16,7 @@ describe("fmtCtx", () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
describe("fmtCost", () => {
|
|
19
|
-
it("returns — for undefined entry", () =>
|
|
20
|
-
expect(fmtCost(undefined)).toBe("—"));
|
|
19
|
+
it("returns — for undefined entry", () => expect(fmtCost(undefined)).toBe("—"));
|
|
21
20
|
it("returns — when no cost field", () => expect(fmtCost({})).toBe("—"));
|
|
22
21
|
it("returns free when both 0", () => {
|
|
23
22
|
expect(fmtCost({ cost: { input: 0, output: 0 } })).toBe("free");
|
|
@@ -118,11 +117,6 @@ describe("sortModels", () => {
|
|
|
118
117
|
{ provider: "a", id: "m4", name: "Delta", score: 60, tier: 0 },
|
|
119
118
|
];
|
|
120
119
|
const sorted = sortModels(mixed);
|
|
121
|
-
expect(sorted.map((m) => m.name)).toEqual([
|
|
122
|
-
"Alpha",
|
|
123
|
-
"Delta",
|
|
124
|
-
"Beta",
|
|
125
|
-
"Gamma",
|
|
126
|
-
]);
|
|
120
|
+
expect(sorted.map((m) => m.name)).toEqual(["Alpha", "Delta", "Beta", "Gamma"]);
|
|
127
121
|
});
|
|
128
122
|
});
|
package/src/models.ts
CHANGED
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
* Sorted by benchlm rank when available (best first), then alphabetical.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
12
|
-
ExtensionAPI,
|
|
13
|
-
ExtensionContext,
|
|
14
|
-
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
12
|
import {
|
|
16
13
|
fuzzyFilter,
|
|
17
14
|
Input,
|
|
@@ -20,11 +17,7 @@ import {
|
|
|
20
17
|
SelectList,
|
|
21
18
|
visibleWidth,
|
|
22
19
|
} from "@earendil-works/pi-tui";
|
|
23
|
-
import {
|
|
24
|
-
benchScoreColor,
|
|
25
|
-
lookupBenchmark,
|
|
26
|
-
lookupModelsDev,
|
|
27
|
-
} from "@xynogen/pix-data";
|
|
20
|
+
import { benchScoreColor, lookupBenchmark, lookupModelsDev } from "@xynogen/pix-data";
|
|
28
21
|
import { icon } from "@xynogen/pix-pretty/icon-catalog";
|
|
29
22
|
import { frameLines, modalWidth } from "@xynogen/pix-pretty/modal-frame";
|
|
30
23
|
import { patchOutBuiltinModelCommand } from "./patch-builtin";
|
|
@@ -40,9 +33,7 @@ export function fmtCtx(n: number): string {
|
|
|
40
33
|
return `${Math.round(n / 1_000)}k`;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
export function fmtCost(
|
|
44
|
-
entry: { cost?: { input?: number; output?: number } } | undefined,
|
|
45
|
-
): string {
|
|
36
|
+
export function fmtCost(entry: { cost?: { input?: number; output?: number } } | undefined): string {
|
|
46
37
|
if (!entry?.cost) return "\u2014";
|
|
47
38
|
const i = entry.cost.input ?? 0;
|
|
48
39
|
const o = entry.cost.output ?? 0;
|
|
@@ -96,10 +87,7 @@ export function sortModels<T extends SortableModel>(models: T[]): T[] {
|
|
|
96
87
|
});
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
async function showEnhancedPicker(
|
|
100
|
-
pi: ExtensionAPI,
|
|
101
|
-
ctx: ExtensionContext,
|
|
102
|
-
): Promise<void> {
|
|
90
|
+
async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
103
91
|
// Mirror the built-in /model selector, which calls refresh() then awaits
|
|
104
92
|
// getAvailable() (see model-selector.js). Without refresh(), this extension
|
|
105
93
|
// reads whatever `this.models` was last loaded into — which, depending on
|
|
@@ -182,9 +170,7 @@ async function showEnhancedPicker(
|
|
|
182
170
|
|
|
183
171
|
// Find max rank width across all benchmarked rows for # padding
|
|
184
172
|
const maxRankWidth = Math.max(
|
|
185
|
-
...dedupedRows.map((r) =>
|
|
186
|
-
r.localRank ? String(r.localRank).length : 0,
|
|
187
|
-
),
|
|
173
|
+
...dedupedRows.map((r) => (r.localRank ? String(r.localRank).length : 0)),
|
|
188
174
|
1,
|
|
189
175
|
);
|
|
190
176
|
|
|
@@ -198,84 +184,74 @@ async function showEnhancedPicker(
|
|
|
198
184
|
if (localRank) rankByValue.set(`${m.provider}/${m.id}`, localRank);
|
|
199
185
|
}
|
|
200
186
|
|
|
201
|
-
const items: SelectItem[] = dedupedRows.map(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
else if (s >= 70) filled = 3;
|
|
250
|
-
else if (s >= 50) filled = 2;
|
|
251
|
-
}
|
|
252
|
-
const starBar =
|
|
253
|
-
theme.fg(scoreColor, "★".repeat(filled)) +
|
|
254
|
-
mute("☆".repeat(5 - filled));
|
|
255
|
-
benchSeg = `⚡${theme.fg(scoreColor, String(score))} ${starBar}`;
|
|
187
|
+
const items: SelectItem[] = dedupedRows.map(({ m, dev, bench, localRank }) => {
|
|
188
|
+
const isCurrent = current && m.provider === current.provider && m.id === current.id;
|
|
189
|
+
|
|
190
|
+
// Label: marker + rank cell + accent-colored model name.
|
|
191
|
+
// Ranked models show muted '#' + colored rank. Unranked (no
|
|
192
|
+
// modelgrep entry) show a muted em-dash sized to the rank
|
|
193
|
+
// column, so the model name aligns across rows.
|
|
194
|
+
const marker = isCurrent ? theme.fg(accent, "▶") : " ";
|
|
195
|
+
let rankPrefix: string;
|
|
196
|
+
if (localRank) {
|
|
197
|
+
const rankStr = String(localRank).padEnd(maxRankWidth);
|
|
198
|
+
// Color rank by the model's bench score (same scale as ⚡score),
|
|
199
|
+
// not by list position — keeps the two colors consistent.
|
|
200
|
+
const rankColor = benchScoreColor(bench?.overallScore);
|
|
201
|
+
rankPrefix = mute("#") + theme.fg(rankColor, rankStr);
|
|
202
|
+
} else {
|
|
203
|
+
// Width = "#" + maxRankWidth chars (e.g. "# " or "#——" for 2-digit ranks).
|
|
204
|
+
const dash = "—".padEnd(maxRankWidth, " ");
|
|
205
|
+
rankPrefix = mute("#") + mute(dash);
|
|
206
|
+
}
|
|
207
|
+
// Display model id only; m.provider is routing provider, not part of id.
|
|
208
|
+
const idColored = theme.fg(accent, m.id);
|
|
209
|
+
const label = `${marker} ${rankPrefix} ${idColored}`;
|
|
210
|
+
|
|
211
|
+
// Description: ctx · cost · score stars
|
|
212
|
+
// Colors: ctx muted · cost success (free muted) · score+stars warning
|
|
213
|
+
const ctxRaw = fmtCtx(dev?.limit?.context ?? 0);
|
|
214
|
+
const ctxStr = mute(ctxRaw.padStart(4));
|
|
215
|
+
const rawCost = fmtCost(dev);
|
|
216
|
+
let costSeg: string;
|
|
217
|
+
if (rawCost === "—") {
|
|
218
|
+
costSeg = theme.fg("dim", "—".padEnd(10));
|
|
219
|
+
} else if (rawCost === "free") {
|
|
220
|
+
costSeg = mute("free".padEnd(10));
|
|
221
|
+
} else {
|
|
222
|
+
costSeg = theme.fg("success", rawCost.padEnd(10));
|
|
223
|
+
}
|
|
224
|
+
let benchSeg = "";
|
|
225
|
+
if (bench) {
|
|
226
|
+
const score = bench.overallScore ?? "?";
|
|
227
|
+
const s = bench.overallScore;
|
|
228
|
+
const scoreColor = benchScoreColor(s);
|
|
229
|
+
let filled = 1;
|
|
230
|
+
if (typeof s === "number") {
|
|
231
|
+
if (s >= 90) filled = 5;
|
|
232
|
+
else if (s >= 80) filled = 4;
|
|
233
|
+
else if (s >= 70) filled = 3;
|
|
234
|
+
else if (s >= 50) filled = 2;
|
|
256
235
|
}
|
|
257
|
-
const
|
|
236
|
+
const starBar = theme.fg(scoreColor, "★".repeat(filled)) + mute("☆".repeat(5 - filled));
|
|
237
|
+
benchSeg = `⚡${theme.fg(scoreColor, String(score))} ${starBar}`;
|
|
238
|
+
}
|
|
239
|
+
const desc = [ctxStr, costSeg, benchSeg].filter(Boolean).join(sep);
|
|
258
240
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
241
|
+
return {
|
|
242
|
+
value: `${m.provider}/${m.id}`,
|
|
243
|
+
label,
|
|
244
|
+
description: desc,
|
|
245
|
+
};
|
|
246
|
+
});
|
|
266
247
|
|
|
267
248
|
const currentIdx = current
|
|
268
|
-
? items.findIndex(
|
|
269
|
-
(it) => it.value === `${current.provider}/${current.id}`,
|
|
270
|
-
)
|
|
249
|
+
? items.findIndex((it) => it.value === `${current.provider}/${current.id}`)
|
|
271
250
|
: 0;
|
|
272
251
|
|
|
273
252
|
// Widest label (visible width, ANSI-stripped) so the model name
|
|
274
253
|
// column never truncates to "…". Add gap headroom.
|
|
275
|
-
const widestLabel = items.reduce(
|
|
276
|
-
(w, it) => Math.max(w, visibleWidth(it.label)),
|
|
277
|
-
0,
|
|
278
|
-
);
|
|
254
|
+
const widestLabel = items.reduce((w, it) => Math.max(w, visibleWidth(it.label)), 0);
|
|
279
255
|
|
|
280
256
|
const search = new Input();
|
|
281
257
|
const list = new SelectList(
|
|
@@ -313,15 +289,9 @@ async function showEnhancedPicker(
|
|
|
313
289
|
} else if (/^\d+$/.test(q)) {
|
|
314
290
|
// Pure number → match by benchlm rank, not name.
|
|
315
291
|
const wanted = Number(q);
|
|
316
|
-
next = internal.items.filter(
|
|
317
|
-
(it) => rankByValue.get(it.value) === wanted,
|
|
318
|
-
);
|
|
292
|
+
next = internal.items.filter((it) => rankByValue.get(it.value) === wanted);
|
|
319
293
|
} else {
|
|
320
|
-
next = fuzzyFilter(
|
|
321
|
-
internal.items,
|
|
322
|
-
q,
|
|
323
|
-
(it) => `${it.label} ${it.description ?? ""}`,
|
|
324
|
-
);
|
|
294
|
+
next = fuzzyFilter(internal.items, q, (it) => `${it.label} ${it.description ?? ""}`);
|
|
325
295
|
// Stable sort: ranked models (by rank asc) before unranked.
|
|
326
296
|
next = next
|
|
327
297
|
.map((it, i) => ({ it, i }))
|
|
@@ -343,21 +313,12 @@ async function showEnhancedPicker(
|
|
|
343
313
|
const mw = modalWidth(w);
|
|
344
314
|
const inner = mw - 4; // CHROME = 2 border + 2 padding
|
|
345
315
|
const lines: string[] = [
|
|
346
|
-
theme.fg(
|
|
347
|
-
|
|
348
|
-
theme.bold(`${icon("picker.model")} Select model`),
|
|
349
|
-
),
|
|
350
|
-
theme.fg(
|
|
351
|
-
"dim",
|
|
352
|
-
"context · pricing · coding rank & score from modelgrep.com",
|
|
353
|
-
),
|
|
316
|
+
theme.fg(accent, theme.bold(`${icon("picker.model")} Select model`)),
|
|
317
|
+
theme.fg("dim", "context · pricing · coding rank & score from modelgrep.com"),
|
|
354
318
|
theme.fg("muted", "Search:"),
|
|
355
319
|
...search.render(inner),
|
|
356
320
|
...list.render(inner),
|
|
357
|
-
theme.fg(
|
|
358
|
-
"dim",
|
|
359
|
-
"fuzzy search · ↑↓ navigate · enter select · esc cancel",
|
|
360
|
-
),
|
|
321
|
+
theme.fg("dim", "fuzzy search · ↑↓ navigate · enter select · esc cancel"),
|
|
361
322
|
];
|
|
362
323
|
return frameLines({
|
|
363
324
|
width: mw,
|
|
@@ -5,8 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
|
|
6
6
|
// Pure replacement tested in isolation (the exported fn resolves the host
|
|
7
7
|
// package, which isn't present in the test sandbox).
|
|
8
|
-
const MODEL_COMMAND_LINE =
|
|
9
|
-
'{ name: "model", description: "Select model (opens selector UI)" },';
|
|
8
|
+
const MODEL_COMMAND_LINE = '{ name: "model", description: "Select model (opens selector UI)" },';
|
|
10
9
|
|
|
11
10
|
function escapeRegExp(text: string): string {
|
|
12
11
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -14,10 +13,7 @@ function escapeRegExp(text: string): string {
|
|
|
14
13
|
|
|
15
14
|
function patchSource(source: string): string {
|
|
16
15
|
if (!source.includes(MODEL_COMMAND_LINE)) return source;
|
|
17
|
-
return source.replace(
|
|
18
|
-
new RegExp(`[ \\t]*${escapeRegExp(MODEL_COMMAND_LINE)}\\n?`),
|
|
19
|
-
"",
|
|
20
|
-
);
|
|
16
|
+
return source.replace(new RegExp(`[ \\t]*${escapeRegExp(MODEL_COMMAND_LINE)}\\n?`), "");
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
const UNPATCHED = `export const BUILTIN_SLASH_COMMANDS = [
|
package/src/patch-builtin.ts
CHANGED
|
@@ -19,8 +19,7 @@ import { createRequire } from "node:module";
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { dirname, join, resolve } from "node:path";
|
|
21
21
|
|
|
22
|
-
const MODEL_COMMAND_LINE =
|
|
23
|
-
'{ name: "model", description: "Select model (opens selector UI)" },';
|
|
22
|
+
const MODEL_COMMAND_LINE = '{ name: "model", description: "Select model (opens selector UI)" },';
|
|
24
23
|
|
|
25
24
|
/** Candidate slash-commands.js paths, most-specific first. */
|
|
26
25
|
function candidatePaths(): string[] {
|
|
@@ -51,14 +50,7 @@ function candidatePaths(): string[] {
|
|
|
51
50
|
];
|
|
52
51
|
for (const root of globalRoots) {
|
|
53
52
|
paths.push(
|
|
54
|
-
join(
|
|
55
|
-
root,
|
|
56
|
-
"@earendil-works",
|
|
57
|
-
"pi-coding-agent",
|
|
58
|
-
"dist",
|
|
59
|
-
"core",
|
|
60
|
-
"slash-commands.js",
|
|
61
|
-
),
|
|
53
|
+
join(root, "@earendil-works", "pi-coding-agent", "dist", "core", "slash-commands.js"),
|
|
62
54
|
);
|
|
63
55
|
}
|
|
64
56
|
|
|
@@ -99,10 +91,7 @@ export function patchOutBuiltinModelCommand(): void {
|
|
|
99
91
|
|
|
100
92
|
if (!source.includes(MODEL_COMMAND_LINE)) return; // already patched
|
|
101
93
|
|
|
102
|
-
const patched = source.replace(
|
|
103
|
-
new RegExp(`[ \\t]*${escapeRegExp(MODEL_COMMAND_LINE)}\\n?`),
|
|
104
|
-
"",
|
|
105
|
-
);
|
|
94
|
+
const patched = source.replace(new RegExp(`[ \\t]*${escapeRegExp(MODEL_COMMAND_LINE)}\\n?`), "");
|
|
106
95
|
if (patched === source) return;
|
|
107
96
|
|
|
108
97
|
try {
|