@danypops/pi-jittor 0.4.0 → 0.5.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.
- package/extension/src/index.ts +54 -27
- package/extension/src/jittor-command.ts +110 -0
- package/extension/src/jittor-shell.ts +338 -0
- package/extension/src/observability/cache-economics-view.ts +45 -21
- package/extension/src/observability/status.ts +116 -65
- package/extension/src/optimization/model-selection-panel.ts +61 -32
- package/extension/src/settings-tui.ts +112 -34
- package/extension/src/tui-prompts.ts +110 -0
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ export interface CacheEconomicsPanelTheme {
|
|
|
13
13
|
bold(text: string): string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
type CacheEconomicsPanelAction = "refresh" | "close";
|
|
16
|
+
export type CacheEconomicsPanelAction = "refresh" | "close";
|
|
17
17
|
|
|
18
18
|
const hostTextMeasure: TextMeasure = { visibleWidth, truncateToWidth };
|
|
19
19
|
|
|
@@ -88,25 +88,47 @@ export function renderCacheEconomicsView(summary: CacheEconomicsSummary): string
|
|
|
88
88
|
return lines;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** Shared by both notify/panel entry points below and the unified /jittor shell, so all three fetch identically. */
|
|
92
|
+
export async function fetchCacheEconomicsSummary(
|
|
93
|
+
client: CacheEconomicsPanelClient,
|
|
94
|
+
windowMs: number,
|
|
95
|
+
now: () => number = Date.now,
|
|
96
|
+
): Promise<CacheEconomicsSummary> {
|
|
97
|
+
const until = now();
|
|
98
|
+
const since = Math.max(0, until - windowMs);
|
|
99
|
+
return (await client.call("cache.economics", { since, until })) as CacheEconomicsSummary;
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
export async function showCacheEconomicsView(
|
|
92
103
|
ctx: ExtensionCommandContext,
|
|
93
104
|
client: CacheEconomicsPanelClient,
|
|
94
105
|
windowMs: number,
|
|
95
106
|
now: () => number = Date.now,
|
|
96
107
|
): Promise<void> {
|
|
97
|
-
const
|
|
98
|
-
const since = Math.max(0, until - windowMs);
|
|
99
|
-
const summary = (await client.call("cache.economics", { since, until })) as CacheEconomicsSummary;
|
|
108
|
+
const summary = await fetchCacheEconomicsSummary(client, windowMs, now);
|
|
100
109
|
ctx.ui.notify(renderCacheEconomicsView(summary).join("\n"), "info");
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The Cache panel's real chrome plus its own r/Esc key handling, wired to `onAction` rather than a
|
|
114
|
+
* `done` callback directly -- reusable by renderCacheEconomicsPanel/showCacheEconomicsPanel below
|
|
115
|
+
* and the unified /jittor shell. Defaults to a full-chrome standalone panel (`framed: true`); pass
|
|
116
|
+
* `framed: false` when nesting this as one tab's content inside another framed container.
|
|
117
|
+
*/
|
|
118
|
+
export function createCacheEconomicsPanel(
|
|
119
|
+
summary: CacheEconomicsSummary,
|
|
120
|
+
theme: CacheEconomicsPanelTheme,
|
|
121
|
+
onAction: (action: CacheEconomicsPanelAction) => void,
|
|
122
|
+
framed = true,
|
|
123
|
+
): BorderedSelectPanel {
|
|
106
124
|
const lines = renderCacheEconomicsView(summary);
|
|
107
125
|
const content = {
|
|
108
126
|
invalidate: () => {},
|
|
109
127
|
render: (availableWidth: number): string[] => lines.map((line) => truncateToWidth(line, availableWidth, "…")),
|
|
128
|
+
handleInput(data: string): void {
|
|
129
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) onAction("close");
|
|
130
|
+
else if (data === "r") onAction("refresh");
|
|
131
|
+
},
|
|
110
132
|
};
|
|
111
133
|
return new BorderedSelectPanel({
|
|
112
134
|
title: "Jittor Cache Economics",
|
|
@@ -118,7 +140,13 @@ export function renderCacheEconomicsPanel(summary: CacheEconomicsSummary, width:
|
|
|
118
140
|
help: (text) => theme.fg("dim", text),
|
|
119
141
|
},
|
|
120
142
|
measure: hostTextMeasure,
|
|
121
|
-
|
|
143
|
+
framed,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The same content as renderCacheEconomicsView, wrapped in a titled, bordered, scrollable frame -- mirrors optimization/model-selection-panel.ts's renderBenchmarkView/BorderedSelectPanel pattern. */
|
|
148
|
+
export function renderCacheEconomicsPanel(summary: CacheEconomicsSummary, width: number, theme: CacheEconomicsPanelTheme): string[] {
|
|
149
|
+
return createCacheEconomicsPanel(summary, theme, () => undefined).render(Math.max(1, width));
|
|
122
150
|
}
|
|
123
151
|
|
|
124
152
|
/**
|
|
@@ -133,23 +161,19 @@ export async function showCacheEconomicsPanel(
|
|
|
133
161
|
now: () => number = Date.now,
|
|
134
162
|
): Promise<void> {
|
|
135
163
|
for (;;) {
|
|
136
|
-
const
|
|
137
|
-
const since = Math.max(0, until - windowMs);
|
|
138
|
-
const summary = (await client.call("cache.economics", { since, until })) as CacheEconomicsSummary;
|
|
164
|
+
const summary = await fetchCacheEconomicsSummary(client, windowMs, now);
|
|
139
165
|
if (ctx.mode !== "tui") {
|
|
140
166
|
ctx.ui.notify(renderCacheEconomicsView(summary).join("\n"), "info");
|
|
141
167
|
return;
|
|
142
168
|
}
|
|
143
|
-
const action = await ctx.ui.custom<CacheEconomicsPanelAction>((_tui, theme, _keybindings, done) =>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
}));
|
|
169
|
+
const action = await ctx.ui.custom<CacheEconomicsPanelAction>((_tui, theme, _keybindings, done) => {
|
|
170
|
+
const panel = createCacheEconomicsPanel(summary, theme, done);
|
|
171
|
+
return {
|
|
172
|
+
invalidate: () => panel.invalidate(),
|
|
173
|
+
render: (width: number) => panel.render(width),
|
|
174
|
+
handleInput: (data: string) => panel.handleInput(data),
|
|
175
|
+
};
|
|
176
|
+
});
|
|
153
177
|
if (!action || action === "close") return;
|
|
154
178
|
}
|
|
155
179
|
}
|
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
TELEMETRY_STALE_AFTER_MS,
|
|
10
10
|
} from "@danypops/jittor";
|
|
11
11
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
12
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
13
|
+
import { BorderedSelectPanel, type TextMeasure } from "malevich-tui-components";
|
|
13
14
|
import { sessionSecretField } from "../session-identity.ts";
|
|
15
|
+
import { showConfirmDialog, showRouteOverrideMenu } from "../tui-prompts.ts";
|
|
14
16
|
import type { ProviderBudget } from "./footer.ts";
|
|
15
17
|
|
|
16
18
|
export interface JittorPanelClient {
|
|
@@ -32,7 +34,14 @@ export function providerBudgetMetricQuery(status: RouterStatus): MetricQuery | n
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
type PanelAction = "pause" | "resume" | "refresh" | "override" | "clear-override" | "close";
|
|
37
|
+
export type PanelAction = "pause" | "resume" | "refresh" | "override" | "clear-override" | "close";
|
|
38
|
+
|
|
39
|
+
export interface StatusPanelTheme {
|
|
40
|
+
fg(color: string, text: string): string;
|
|
41
|
+
bold(text: string): string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hostTextMeasure: TextMeasure = { visibleWidth, truncateToWidth };
|
|
36
45
|
|
|
37
46
|
function latest(
|
|
38
47
|
rows: StoredMetricObservation[],
|
|
@@ -326,10 +335,13 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
|
|
|
326
335
|
return lines;
|
|
327
336
|
}
|
|
328
337
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
export interface StatusPanelSnapshot {
|
|
339
|
+
status: RouterStatus;
|
|
340
|
+
metrics: StoredMetricObservation[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Shared by the standalone status panel below and the unified /jittor shell, so both fetch identically. */
|
|
344
|
+
export async function fetchStatusSnapshot(client: JittorPanelClient, sessionId: string): Promise<StatusPanelSnapshot> {
|
|
333
345
|
const status = (await client.call("router.status", { session_id: sessionId })) as RouterStatus;
|
|
334
346
|
const query = providerBudgetMetricQuery(status);
|
|
335
347
|
const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
|
|
@@ -341,74 +353,113 @@ async function chooseOverride(ctx: ExtensionCommandContext, routes: Route[]): Pr
|
|
|
341
353
|
ctx.ui.notify("Pi reports no authenticated routes for the current provider.", "warning");
|
|
342
354
|
return undefined;
|
|
343
355
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
return showRouteOverrideMenu(ctx, routes, routeText);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* The Status panel's real chrome (a BorderedSelectPanel, replacing the hand-rolled border lines
|
|
361
|
+
* this used to draw directly) plus its own r/p/o/c/Esc key handling, wired to `onAction` rather
|
|
362
|
+
* than a `done` callback directly -- reusable by the standalone panel below and the unified
|
|
363
|
+
* /jittor shell. Defaults to a full-chrome standalone panel (`framed: true`); pass `framed: false`
|
|
364
|
+
* when nesting this as one tab's content inside another framed container.
|
|
365
|
+
*/
|
|
366
|
+
export function createStatusPanel(
|
|
367
|
+
current: StatusPanelSnapshot,
|
|
368
|
+
theme: StatusPanelTheme,
|
|
369
|
+
onAction: (action: PanelAction) => void,
|
|
370
|
+
framed = true,
|
|
371
|
+
): BorderedSelectPanel {
|
|
372
|
+
const controls = current.status.paused
|
|
373
|
+
? "r refresh · p release emergency halt · o override · c clear override · Esc close"
|
|
374
|
+
: "r refresh · p emergency halt · o override · c clear override · Esc close";
|
|
375
|
+
const content = {
|
|
376
|
+
invalidate: () => {},
|
|
377
|
+
render: (width: number): string[] =>
|
|
378
|
+
buildStatusView(current.status, current.metrics).map((line) => truncateToWidth(` ${line}`, width, "…")),
|
|
379
|
+
handleInput(data: string): void {
|
|
380
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) onAction("close");
|
|
381
|
+
else if (data === "r") onAction("refresh");
|
|
382
|
+
else if (data === "p") onAction(current.status.paused ? "resume" : "pause");
|
|
383
|
+
else if (data === "o") onAction("override");
|
|
384
|
+
else if (data === "c") onAction("clear-override");
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
return new BorderedSelectPanel({
|
|
388
|
+
title: "Jittor",
|
|
389
|
+
list: content,
|
|
390
|
+
helpText: controls,
|
|
391
|
+
theme: {
|
|
392
|
+
border: (text) => theme.fg("borderMuted", text),
|
|
393
|
+
title: theme.bold,
|
|
394
|
+
help: (text) => theme.fg("dim", text),
|
|
395
|
+
},
|
|
396
|
+
measure: hostTextMeasure,
|
|
397
|
+
framed,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Performs the real side effect for one resolved status action -- confirmations and daemon calls.
|
|
403
|
+
* A no-op for "close" and "refresh" is handled by the caller (refresh is a plain re-poll with no
|
|
404
|
+
* confirmation). Shared by the standalone status panel below and the unified /jittor shell.
|
|
405
|
+
*/
|
|
406
|
+
export async function runStatusAction(
|
|
407
|
+
ctx: ExtensionCommandContext,
|
|
408
|
+
client: JittorPanelClient,
|
|
409
|
+
action: PanelAction,
|
|
410
|
+
current: StatusPanelSnapshot,
|
|
411
|
+
sessionId: string,
|
|
412
|
+
): Promise<void> {
|
|
413
|
+
if (action === "close") return;
|
|
414
|
+
if (action === "refresh") {
|
|
415
|
+
await client.call("telemetry.poll", {});
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (action === "pause" || action === "resume") {
|
|
419
|
+
if (
|
|
420
|
+
await showConfirmDialog(
|
|
421
|
+
ctx,
|
|
422
|
+
action === "pause" ? "Emergency-halt provider requests?" : "Release emergency halt?",
|
|
423
|
+
"This changes provider-request enforcement. Use /jittor off to disable blocking entirely.",
|
|
424
|
+
)
|
|
425
|
+
) {
|
|
426
|
+
await client.call(action === "pause" ? "router.pause" : "router.resume", { session_id: sessionId, ...sessionSecretField(sessionId) });
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (action === "clear-override") {
|
|
431
|
+
if (await showConfirmDialog(ctx, "Clear route override?", "Policy-controlled routing will resume."))
|
|
432
|
+
await client.call("router.clear_override", { session_id: sessionId, ...sessionSecretField(sessionId) });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const route = await chooseOverride(ctx, current.status.availableRoutes);
|
|
436
|
+
if (route && (await showConfirmDialog(ctx, "Apply route override?", `${routeText(route)} for one hour`))) {
|
|
437
|
+
await client.call("router.override", {
|
|
438
|
+
route,
|
|
439
|
+
expiresAt: Date.now() + 60 * 60 * 1_000,
|
|
440
|
+
session_id: sessionId,
|
|
441
|
+
...sessionSecretField(sessionId),
|
|
442
|
+
});
|
|
443
|
+
}
|
|
348
444
|
}
|
|
349
445
|
|
|
350
446
|
export async function showJittorPanel(ctx: ExtensionCommandContext, client: JittorPanelClient): Promise<void> {
|
|
351
447
|
const session_id = ctx.sessionManager.getSessionId();
|
|
352
448
|
for (;;) {
|
|
353
|
-
const current = await
|
|
449
|
+
const current = await fetchStatusSnapshot(client, session_id);
|
|
354
450
|
if (ctx.mode !== "tui") {
|
|
355
451
|
ctx.ui.notify(buildStatusView(current.status, current.metrics).join("\n"), "info");
|
|
356
452
|
return;
|
|
357
453
|
}
|
|
358
|
-
const action = await ctx.ui.custom<PanelAction>((_tui, theme, _keybindings, done) =>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
border,
|
|
367
|
-
truncateToWidth(theme.bold("Jittor"), width, ""),
|
|
368
|
-
border,
|
|
369
|
-
...buildStatusView(current.status, current.metrics).map((line) => truncateToWidth(` ${line}`, width, "…")),
|
|
370
|
-
border,
|
|
371
|
-
truncateToWidth(theme.fg("dim", controls), width, "…"),
|
|
372
|
-
border,
|
|
373
|
-
];
|
|
374
|
-
},
|
|
375
|
-
handleInput(data: string): void {
|
|
376
|
-
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) done("close");
|
|
377
|
-
else if (data === "r") done("refresh");
|
|
378
|
-
else if (data === "p") done(current.status.paused ? "resume" : "pause");
|
|
379
|
-
else if (data === "o") done("override");
|
|
380
|
-
else if (data === "c") done("clear-override");
|
|
381
|
-
},
|
|
382
|
-
}));
|
|
454
|
+
const action = await ctx.ui.custom<PanelAction>((_tui, theme, _keybindings, done) => {
|
|
455
|
+
const panel = createStatusPanel(current, theme, done);
|
|
456
|
+
return {
|
|
457
|
+
invalidate: () => panel.invalidate(),
|
|
458
|
+
render: (width: number) => panel.render(width),
|
|
459
|
+
handleInput: (data: string) => panel.handleInput(data),
|
|
460
|
+
};
|
|
461
|
+
});
|
|
383
462
|
if (!action || action === "close") return;
|
|
384
|
-
|
|
385
|
-
await client.call("telemetry.poll", {});
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (action === "pause" || action === "resume") {
|
|
389
|
-
if (
|
|
390
|
-
await ctx.ui.confirm(
|
|
391
|
-
action === "pause" ? "Emergency-halt provider requests?" : "Release emergency halt?",
|
|
392
|
-
"This changes provider-request enforcement. Use /jittor off to disable blocking entirely.",
|
|
393
|
-
)
|
|
394
|
-
) {
|
|
395
|
-
await client.call(action === "pause" ? "router.pause" : "router.resume", { session_id, ...sessionSecretField(session_id) });
|
|
396
|
-
}
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
if (action === "clear-override") {
|
|
400
|
-
if (await ctx.ui.confirm("Clear route override?", "Policy-controlled routing will resume."))
|
|
401
|
-
await client.call("router.clear_override", { session_id, ...sessionSecretField(session_id) });
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
const route = await chooseOverride(ctx, current.status.availableRoutes);
|
|
405
|
-
if (route && (await ctx.ui.confirm("Apply route override?", `${routeText(route)} for one hour`))) {
|
|
406
|
-
await client.call("router.override", {
|
|
407
|
-
route,
|
|
408
|
-
expiresAt: Date.now() + 60 * 60 * 1_000,
|
|
409
|
-
session_id,
|
|
410
|
-
...sessionSecretField(session_id),
|
|
411
|
-
});
|
|
412
|
-
}
|
|
463
|
+
await runStatusAction(ctx, client, action, current, session_id);
|
|
413
464
|
}
|
|
414
465
|
}
|
|
@@ -27,7 +27,7 @@ interface BenchmarkTheme {
|
|
|
27
27
|
bold(text: string): string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
type BenchmarkPanelAction = "refresh" | "close";
|
|
30
|
+
export type BenchmarkPanelAction = "refresh" | "close";
|
|
31
31
|
|
|
32
32
|
const COMPONENT_LABELS: Record<UtilityComponentName, string> = { quality: "Q", cost: "$", latency: "L", context: "C", reliability: "R" };
|
|
33
33
|
|
|
@@ -58,8 +58,19 @@ function benchmarkRows(shown: RankedModel[], currentIdentity: string): Record<st
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/**
|
|
62
|
+
* The Benchmarks panel's real chrome plus its own r/Esc key handling, wired to `onAction` rather
|
|
63
|
+
* than a `done` callback directly -- reusable by renderBenchmarkView/showBenchmarkPanel below and
|
|
64
|
+
* the unified /jittor shell. Defaults to a full-chrome standalone panel (`framed: true`); pass
|
|
65
|
+
* `framed: false` when nesting this as one tab's content inside another framed container.
|
|
66
|
+
*/
|
|
67
|
+
export function createBenchmarkPanel(
|
|
68
|
+
result: ModelRankingResult,
|
|
69
|
+
currentIdentity: string,
|
|
70
|
+
theme: BenchmarkTheme,
|
|
71
|
+
onAction: (action: BenchmarkPanelAction) => void,
|
|
72
|
+
framed = true,
|
|
73
|
+
): BorderedSelectPanel {
|
|
63
74
|
const shown = result.ranked.slice(0, BENCHMARK_TUI_MAX_CANDIDATES);
|
|
64
75
|
const currentIndex = result.ranked.findIndex((item) => item.identity.startsWith(`${currentIdentity}:`));
|
|
65
76
|
const recommended = result.ranked[0];
|
|
@@ -96,6 +107,10 @@ export function renderBenchmarkView(result: ModelRankingResult, currentIdentity:
|
|
|
96
107
|
: []),
|
|
97
108
|
...(result.scopeWarning ? [truncateToWidth(result.scopeWarning, availableWidth, "…")] : []),
|
|
98
109
|
],
|
|
110
|
+
handleInput(data: string): void {
|
|
111
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) onAction("close");
|
|
112
|
+
else if (data === "r") onAction("refresh");
|
|
113
|
+
},
|
|
99
114
|
};
|
|
100
115
|
return new BorderedSelectPanel({
|
|
101
116
|
title: "Jittor Benchmark Recommendations",
|
|
@@ -107,7 +122,40 @@ export function renderBenchmarkView(result: ModelRankingResult, currentIdentity:
|
|
|
107
122
|
help: (text) => theme.fg("dim", text),
|
|
108
123
|
},
|
|
109
124
|
measure: hostTextMeasure,
|
|
110
|
-
|
|
125
|
+
framed,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function renderBenchmarkView(result: ModelRankingResult, currentIdentity: string, width: number, theme: BenchmarkTheme): string[] {
|
|
130
|
+
return createBenchmarkPanel(result, currentIdentity, theme, () => undefined).render(Math.max(1, width));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Shared by the standalone benchmark panel below and the unified /jittor shell, so both fetch identically. */
|
|
134
|
+
export async function fetchBenchmarkRanking(
|
|
135
|
+
ctx: ExtensionCommandContext,
|
|
136
|
+
client: BenchmarkPanelClient,
|
|
137
|
+
candidates: ModelCandidate[],
|
|
138
|
+
domain: ModelTaskDomain,
|
|
139
|
+
type: ModelTaskType,
|
|
140
|
+
): Promise<ModelRankingResult> {
|
|
141
|
+
const session_id = ctx.sessionManager.getSessionId();
|
|
142
|
+
return (await client.call("models.rank", {
|
|
143
|
+
candidates,
|
|
144
|
+
session_id,
|
|
145
|
+
...sessionSecretField(session_id),
|
|
146
|
+
scopeAuthority: "available-models",
|
|
147
|
+
domain,
|
|
148
|
+
type,
|
|
149
|
+
budgetPressure: 0,
|
|
150
|
+
weights: {
|
|
151
|
+
quality: MODEL_RANKING_DEFAULT_QUALITY_WEIGHT,
|
|
152
|
+
cost: MODEL_RANKING_DEFAULT_COST_WEIGHT,
|
|
153
|
+
latency: MODEL_RANKING_DEFAULT_LATENCY_WEIGHT,
|
|
154
|
+
context: MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
|
|
155
|
+
reliability: MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
|
|
156
|
+
},
|
|
157
|
+
sourceIds: ["openrouter-models", "lmarena-hf", "artificial-analysis-direct", "openrouter-design-arena"],
|
|
158
|
+
})) as ModelRankingResult;
|
|
111
159
|
}
|
|
112
160
|
|
|
113
161
|
export async function showBenchmarkPanel(
|
|
@@ -119,24 +167,7 @@ export async function showBenchmarkPanel(
|
|
|
119
167
|
type: ModelTaskType,
|
|
120
168
|
): Promise<void> {
|
|
121
169
|
for (;;) {
|
|
122
|
-
const
|
|
123
|
-
const result = (await client.call("models.rank", {
|
|
124
|
-
candidates,
|
|
125
|
-
session_id,
|
|
126
|
-
...sessionSecretField(session_id),
|
|
127
|
-
scopeAuthority: "available-models",
|
|
128
|
-
domain,
|
|
129
|
-
type,
|
|
130
|
-
budgetPressure: 0,
|
|
131
|
-
weights: {
|
|
132
|
-
quality: MODEL_RANKING_DEFAULT_QUALITY_WEIGHT,
|
|
133
|
-
cost: MODEL_RANKING_DEFAULT_COST_WEIGHT,
|
|
134
|
-
latency: MODEL_RANKING_DEFAULT_LATENCY_WEIGHT,
|
|
135
|
-
context: MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
|
|
136
|
-
reliability: MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
|
|
137
|
-
},
|
|
138
|
-
sourceIds: ["openrouter-models", "lmarena-hf", "artificial-analysis-direct", "openrouter-design-arena"],
|
|
139
|
-
})) as ModelRankingResult;
|
|
170
|
+
const result = await fetchBenchmarkRanking(ctx, client, candidates, domain, type);
|
|
140
171
|
if (ctx.mode !== "tui") {
|
|
141
172
|
ctx.ui.notify(
|
|
142
173
|
renderBenchmarkView(result, currentIdentity, 100, { fg: (_color, text) => text, bold: (text) => text }).join("\n"),
|
|
@@ -144,16 +175,14 @@ export async function showBenchmarkPanel(
|
|
|
144
175
|
);
|
|
145
176
|
return;
|
|
146
177
|
}
|
|
147
|
-
const action = await ctx.ui.custom<BenchmarkPanelAction>((_tui, theme, _keybindings, done) =>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
},
|
|
156
|
-
}));
|
|
178
|
+
const action = await ctx.ui.custom<BenchmarkPanelAction>((_tui, theme, _keybindings, done) => {
|
|
179
|
+
const panel = createBenchmarkPanel(result, currentIdentity, theme, done);
|
|
180
|
+
return {
|
|
181
|
+
invalidate: () => panel.invalidate(),
|
|
182
|
+
render: (width: number) => panel.render(width),
|
|
183
|
+
handleInput: (data: string) => panel.handleInput(data),
|
|
184
|
+
};
|
|
185
|
+
});
|
|
157
186
|
if (!action || action === "close") return;
|
|
158
187
|
await client.call("benchmark.refresh", { force: true });
|
|
159
188
|
}
|
|
@@ -3,6 +3,7 @@ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
4
|
import { BorderedSelectPanel, Menu, type MenuTheme, type TextMeasure } from "malevich-tui-components";
|
|
5
5
|
import type { CodexRecoveryControl, EnforcementControl, UsageBudgetControl } from "./settings.ts";
|
|
6
|
+
import { showConfirmDialog } from "./tui-prompts.ts";
|
|
6
7
|
|
|
7
8
|
export interface SettingsSnapshot {
|
|
8
9
|
enforcementEnabled: boolean;
|
|
@@ -16,8 +17,8 @@ interface SettingsTheme {
|
|
|
16
17
|
bold(text: string): string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
type SettingsKey = "enforcement" | "footer" | "recovery" | `budget-${UsagePeriod}`;
|
|
20
|
-
type SettingsAction = { kind: "activate"; key: SettingsKey } | { kind: "close" };
|
|
20
|
+
export type SettingsKey = "enforcement" | "footer" | "recovery" | `budget-${UsagePeriod}`;
|
|
21
|
+
export type SettingsAction = { kind: "activate"; key: SettingsKey } | { kind: "close" };
|
|
21
22
|
|
|
22
23
|
export interface SettingsEffects {
|
|
23
24
|
setEnforcement(enabled: boolean): void | Promise<void>;
|
|
@@ -25,7 +26,21 @@ export interface SettingsEffects {
|
|
|
25
26
|
setRecovery(enabled: boolean): void | Promise<void>;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
// Enforcement -> Budget -> Providers -> UI: safety posture (the global kill-switch everything
|
|
30
|
+
// else is downstream of) leads, followed by spend limits, then provider-specific quirks (today,
|
|
31
|
+
// only Codex recovery -- grouped under its own header instead of sitting flat next to global
|
|
32
|
+
// switches, which read as "too Codex-oriented" with nothing to signal its narrower scope), then
|
|
33
|
+
// display preferences last.
|
|
34
|
+
const SETTINGS_KEYS: SettingsKey[] = ["enforcement", ...USAGE_PERIODS.map(({ id }) => `budget-${id}` as const), "recovery", "footer"];
|
|
35
|
+
|
|
36
|
+
type SettingsCategory = "Enforcement" | "Budget" | "Providers" | "UI";
|
|
37
|
+
|
|
38
|
+
function categoryOf(key: SettingsKey): SettingsCategory {
|
|
39
|
+
if (key === "enforcement") return "Enforcement";
|
|
40
|
+
if (key === "recovery") return "Providers";
|
|
41
|
+
if (key === "footer") return "UI";
|
|
42
|
+
return "Budget";
|
|
43
|
+
}
|
|
29
44
|
|
|
30
45
|
function state(enabled: boolean, theme: SettingsTheme): string {
|
|
31
46
|
return enabled ? theme.fg("success", "ON") : theme.fg("muted", "OFF");
|
|
@@ -69,11 +84,54 @@ function menuTheme(theme: SettingsTheme): MenuTheme {
|
|
|
69
84
|
};
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Wraps `menu` (already built over `SETTINGS_KEYS` in order, with no `title` of its own) so its
|
|
89
|
+
* rendered output gets a non-selectable category header line inserted before each contiguous run
|
|
90
|
+
* of same-category rows. `Menu` has no native header/divider item type, and keeps its selected
|
|
91
|
+
* index private with no getter, so headers are inserted into the *rendered lines*, never built as
|
|
92
|
+
* extra selectable items -- `menu`'s own input handling, selection state, and every existing
|
|
93
|
+
* keybinding stay completely untouched; this only changes what gets displayed.
|
|
94
|
+
*
|
|
95
|
+
* With no `title` set, `Menu.render` always returns exactly `[rule, ...itemLines, rule]` (see
|
|
96
|
+
* malevich-tui-components' Menu/renderFramedPanel). If that ever stops holding -- a future
|
|
97
|
+
* malevich release changing Menu's own frame shape -- this falls back to Menu's unmodified
|
|
98
|
+
* output rather than slicing the wrong lines into a garbled view.
|
|
99
|
+
*/
|
|
100
|
+
function groupedSettingsMenu(
|
|
101
|
+
menu: Menu,
|
|
102
|
+
theme: SettingsTheme,
|
|
103
|
+
): { invalidate(): void; handleInput(data: string): void; render(width: number): string[] } {
|
|
104
|
+
return {
|
|
105
|
+
invalidate: () => menu.invalidate(),
|
|
106
|
+
handleInput: (data: string) => menu.handleInput(data),
|
|
107
|
+
render(width: number): string[] {
|
|
108
|
+
const rendered = menu.render(width);
|
|
109
|
+
if (rendered.length !== SETTINGS_KEYS.length + 2) return rendered;
|
|
110
|
+
const itemLines = rendered.slice(1, 1 + SETTINGS_KEYS.length);
|
|
111
|
+
const out: string[] = [rendered[0]!];
|
|
112
|
+
let lastCategory: SettingsCategory | undefined;
|
|
113
|
+
for (const [index, key] of SETTINGS_KEYS.entries()) {
|
|
114
|
+
const category = categoryOf(key);
|
|
115
|
+
if (category !== lastCategory) {
|
|
116
|
+
if (lastCategory !== undefined) out.push("");
|
|
117
|
+
out.push(theme.bold(theme.fg("dim", category)));
|
|
118
|
+
lastCategory = category;
|
|
119
|
+
}
|
|
120
|
+
out.push(itemLines[index]!);
|
|
121
|
+
}
|
|
122
|
+
out.push(rendered.at(-1)!);
|
|
123
|
+
return out;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Defaults to a full-chrome standalone panel (`framed: true`); pass `framed: false` when nesting this as one tab's content inside another framed container (e.g. the unified /jittor shell's own outer border). */
|
|
129
|
+
export function createSettingsPanel(
|
|
73
130
|
snapshot: SettingsSnapshot,
|
|
74
131
|
theme: SettingsTheme,
|
|
75
132
|
onAction: (action: SettingsAction) => void,
|
|
76
133
|
selected = 0,
|
|
134
|
+
framed = true,
|
|
77
135
|
): BorderedSelectPanel {
|
|
78
136
|
const menu = new Menu({
|
|
79
137
|
items: SETTINGS_KEYS.map((key) => ({ label: rowText(key, snapshot, theme), action: () => onAction({ kind: "activate", key }) })),
|
|
@@ -91,7 +149,7 @@ function createSettingsPanel(
|
|
|
91
149
|
for (let index = 0; index < selected; index += 1) menu.handleInput("\x1b[B");
|
|
92
150
|
return new BorderedSelectPanel({
|
|
93
151
|
title: "Jittor Settings",
|
|
94
|
-
list: menu,
|
|
152
|
+
list: groupedSettingsMenu(menu, theme),
|
|
95
153
|
helpText: "Token budgets are user values; provider quotas remain separate. · ↑/↓ select · Enter edit · Esc close",
|
|
96
154
|
theme: {
|
|
97
155
|
border: (text) => theme.fg("borderMuted", text),
|
|
@@ -99,6 +157,7 @@ function createSettingsPanel(
|
|
|
99
157
|
help: (text) => theme.fg("dim", text),
|
|
100
158
|
},
|
|
101
159
|
measure: hostTextMeasure,
|
|
160
|
+
framed,
|
|
102
161
|
});
|
|
103
162
|
}
|
|
104
163
|
|
|
@@ -130,6 +189,53 @@ async function editBudget(ctx: ExtensionCommandContext, budgets: UsageBudgetCont
|
|
|
130
189
|
ctx.ui.notify(`${label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
|
|
131
190
|
}
|
|
132
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Performs the real side effect for one resolved settings action -- confirmations, effects calls,
|
|
194
|
+
* budget prompts. A no-op for "close". Shared by the standalone settings panel below and the
|
|
195
|
+
* unified /jittor shell, so the two interactive surfaces can never drift apart.
|
|
196
|
+
*/
|
|
197
|
+
export async function runSettingsAction(
|
|
198
|
+
ctx: ExtensionCommandContext,
|
|
199
|
+
action: SettingsAction,
|
|
200
|
+
enforcement: EnforcementControl,
|
|
201
|
+
recovery: CodexRecoveryControl,
|
|
202
|
+
budgets: UsageBudgetControl,
|
|
203
|
+
effects: SettingsEffects,
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
if (action.kind === "close") return;
|
|
206
|
+
if (action.key === "enforcement") {
|
|
207
|
+
if (enforcement.isEnabled()) {
|
|
208
|
+
if (
|
|
209
|
+
await showConfirmDialog(
|
|
210
|
+
ctx,
|
|
211
|
+
"Disable routing enforcement?",
|
|
212
|
+
"Jittor will remain monitor-only and will no longer block unsafe provider requests.",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
await effects.setEnforcement(false);
|
|
216
|
+
} else await effects.setEnforcement(true);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (action.key === "footer") {
|
|
220
|
+
await effects.setFooter(!enforcement.isFooterEnabled());
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (action.key === "recovery") {
|
|
224
|
+
if (!recovery.isCodexRecoveryEnabled()) {
|
|
225
|
+
if (
|
|
226
|
+
await showConfirmDialog(
|
|
227
|
+
ctx,
|
|
228
|
+
"Enable Codex recovery?",
|
|
229
|
+
"Jittor may start bounded hidden retries only after transient Codex failures fully settle.",
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
await effects.setRecovery(true);
|
|
233
|
+
} else await effects.setRecovery(false);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await editBudget(ctx, budgets, action.key.slice("budget-".length) as UsagePeriod);
|
|
237
|
+
}
|
|
238
|
+
|
|
133
239
|
export async function showSettingsPanel(
|
|
134
240
|
ctx: ExtensionCommandContext,
|
|
135
241
|
enforcement: EnforcementControl,
|
|
@@ -160,34 +266,6 @@ export async function showSettingsPanel(
|
|
|
160
266
|
};
|
|
161
267
|
});
|
|
162
268
|
if (!action || action.kind === "close") return;
|
|
163
|
-
|
|
164
|
-
if (enforcement.isEnabled()) {
|
|
165
|
-
if (
|
|
166
|
-
await ctx.ui.confirm(
|
|
167
|
-
"Disable routing enforcement?",
|
|
168
|
-
"Jittor will remain monitor-only and will no longer block unsafe provider requests.",
|
|
169
|
-
)
|
|
170
|
-
)
|
|
171
|
-
await effects.setEnforcement(false);
|
|
172
|
-
} else await effects.setEnforcement(true);
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (action.key === "footer") {
|
|
176
|
-
await effects.setFooter(!enforcement.isFooterEnabled());
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (action.key === "recovery") {
|
|
180
|
-
if (!recovery.isCodexRecoveryEnabled()) {
|
|
181
|
-
if (
|
|
182
|
-
await ctx.ui.confirm(
|
|
183
|
-
"Enable Codex recovery?",
|
|
184
|
-
"Jittor may start bounded hidden retries only after transient Codex failures fully settle.",
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
await effects.setRecovery(true);
|
|
188
|
-
} else await effects.setRecovery(false);
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
await editBudget(ctx, budgets, action.key.slice("budget-".length) as UsagePeriod);
|
|
269
|
+
await runSettingsAction(ctx, action, enforcement, recovery, budgets, effects);
|
|
192
270
|
}
|
|
193
271
|
}
|