@danypops/pi-jittor 0.2.0 → 0.2.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.
- package/extension/src/benchmark-tui.ts +27 -12
- package/extension/src/capabilities/codex-recovery.ts +39 -23
- package/extension/src/capabilities/context-hub.ts +1 -5
- package/extension/src/capabilities/local-run-telemetry.ts +14 -10
- package/extension/src/capabilities/provider-response-telemetry.ts +20 -6
- package/extension/src/context-breakdown.ts +60 -29
- package/extension/src/context-report.ts +16 -5
- package/extension/src/context-view.ts +39 -7
- package/extension/src/footer.ts +56 -26
- package/extension/src/index.ts +228 -97
- package/extension/src/service-client.ts +1 -1
- package/extension/src/settings-tui.ts +35 -20
- package/extension/src/settings.ts +13 -10
- package/extension/src/tui.ts +148 -63
- package/extension/src/usage.ts +124 -42
- package/package.json +3 -3
package/extension/src/usage.ts
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
1
|
import {
|
|
2
|
+
buildCostGraph,
|
|
3
|
+
buildUsageGraph,
|
|
4
|
+
type CostGraph,
|
|
4
5
|
HUMAN_TEXT_FIELD_MAX_CHARACTERS,
|
|
6
|
+
resolveUsageWindow,
|
|
5
7
|
USAGE_CHART_HEIGHT,
|
|
6
8
|
USAGE_MAX_DISTINCT_SCOPES,
|
|
9
|
+
USAGE_PERIODS,
|
|
7
10
|
USAGE_RENDER_MAX_SERIES,
|
|
8
11
|
USAGE_Y_AXIS_WIDTH,
|
|
9
|
-
buildCostGraph,
|
|
10
|
-
buildUsageGraph,
|
|
11
|
-
resolveUsageWindow,
|
|
12
|
-
USAGE_PERIODS,
|
|
13
|
-
usagePeriod,
|
|
14
|
-
type CostGraph,
|
|
15
12
|
type UsageAggregateRow,
|
|
16
13
|
type UsageGraph,
|
|
17
14
|
type UsagePeriod,
|
|
15
|
+
usagePeriod,
|
|
18
16
|
} from "@danypops/jittor";
|
|
17
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
19
19
|
import type { UsageBudgetControl } from "./settings.ts";
|
|
20
20
|
import type { JittorPanelClient } from "./tui.ts";
|
|
21
21
|
|
|
22
22
|
type UsageAction = "period-prev" | "period-next" | "view-next" | "refresh" | "close";
|
|
23
23
|
type UsageColor =
|
|
24
|
-
| "accent"
|
|
25
|
-
| "
|
|
24
|
+
| "accent"
|
|
25
|
+
| "success"
|
|
26
|
+
| "warning"
|
|
27
|
+
| "error"
|
|
28
|
+
| "thinkingText"
|
|
29
|
+
| "muted"
|
|
30
|
+
| "dim"
|
|
31
|
+
| "borderMuted"
|
|
32
|
+
| "syntaxKeyword"
|
|
33
|
+
| "syntaxFunction"
|
|
34
|
+
| "syntaxVariable"
|
|
35
|
+
| "syntaxString"
|
|
36
|
+
| "syntaxNumber"
|
|
37
|
+
| "syntaxType"
|
|
38
|
+
| "syntaxOperator";
|
|
26
39
|
|
|
27
40
|
export interface UsageTheme {
|
|
28
41
|
fg(color: UsageColor, text: string): string;
|
|
@@ -53,15 +66,22 @@ export interface UsageTheme {
|
|
|
53
66
|
* on an additional channel rather than silently reusing an indistinguishable color).
|
|
54
67
|
*/
|
|
55
68
|
const SERIES_HUES: UsageColor[] = [
|
|
56
|
-
"accent",
|
|
57
|
-
"
|
|
69
|
+
"accent",
|
|
70
|
+
"syntaxFunction",
|
|
71
|
+
"syntaxString",
|
|
72
|
+
"syntaxNumber",
|
|
73
|
+
"syntaxKeyword",
|
|
74
|
+
"syntaxType",
|
|
75
|
+
"thinkingText",
|
|
76
|
+
"syntaxVariable",
|
|
77
|
+
"syntaxOperator",
|
|
58
78
|
];
|
|
59
79
|
|
|
60
80
|
/** Returns a style function for the Nth series: cycles hue, then adds bold once the palette wraps. */
|
|
61
81
|
function seriesStyle(index: number, theme: UsageTheme): (text: string) => string {
|
|
62
82
|
const hue = SERIES_HUES[index % SERIES_HUES.length]!;
|
|
63
83
|
const useBold = Math.floor(index / SERIES_HUES.length) % 2 === 1;
|
|
64
|
-
return (text: string) => useBold ? theme.bold(theme.fg(hue, text)) : theme.fg(hue, text);
|
|
84
|
+
return (text: string) => (useBold ? theme.bold(theme.fg(hue, text)) : theme.fg(hue, text));
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
const PARTIAL_BLOCKS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
@@ -73,16 +93,34 @@ function compact(value: number): string {
|
|
|
73
93
|
return String(Math.round(value));
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
interface RenderableSeries {
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
interface RenderableSeries {
|
|
97
|
+
key: string;
|
|
98
|
+
provider: string;
|
|
99
|
+
model: string;
|
|
100
|
+
total: number;
|
|
101
|
+
}
|
|
102
|
+
interface RenderableBucket {
|
|
103
|
+
start: number;
|
|
104
|
+
end: number;
|
|
105
|
+
total: number;
|
|
106
|
+
series: Record<string, number>;
|
|
107
|
+
}
|
|
108
|
+
interface RenderableChart {
|
|
109
|
+
period: UsagePeriod;
|
|
110
|
+
start: number;
|
|
111
|
+
end: number;
|
|
112
|
+
buckets: RenderableBucket[];
|
|
113
|
+
series: RenderableSeries[];
|
|
114
|
+
total: number;
|
|
115
|
+
truncated: boolean;
|
|
116
|
+
}
|
|
79
117
|
|
|
80
118
|
function mergeBuckets(buckets: RenderableBucket[], maximum: number): RenderableBucket[] {
|
|
81
119
|
if (buckets.length <= maximum) return buckets;
|
|
82
120
|
const result: RenderableBucket[] = [];
|
|
83
121
|
for (let index = 0; index < maximum; index += 1) {
|
|
84
|
-
const from = Math.floor(index * buckets.length / maximum);
|
|
85
|
-
const to = Math.max(from + 1, Math.floor((index + 1) * buckets.length / maximum));
|
|
122
|
+
const from = Math.floor((index * buckets.length) / maximum);
|
|
123
|
+
const to = Math.max(from + 1, Math.floor(((index + 1) * buckets.length) / maximum));
|
|
86
124
|
const selected = buckets.slice(from, to);
|
|
87
125
|
const series: Record<string, number> = {};
|
|
88
126
|
for (const bucket of selected) {
|
|
@@ -135,7 +173,11 @@ function axisLabels(start: number, end: number, period: UsagePeriod, width: numb
|
|
|
135
173
|
}
|
|
136
174
|
|
|
137
175
|
function displayIdentity(value: string): string {
|
|
138
|
-
return value
|
|
176
|
+
return value
|
|
177
|
+
.replace(/[\r\n\t]/g, " ")
|
|
178
|
+
.replace(/ +/g, " ")
|
|
179
|
+
.trim()
|
|
180
|
+
.slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS);
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
function plainTheme(): UsageTheme {
|
|
@@ -170,13 +212,14 @@ function renderChart(chart: RenderableChart, width: number, theme: UsageTheme, o
|
|
|
170
212
|
const budget = typeof options.budget === "number" && Number.isFinite(options.budget) && options.budget > 0 ? options.budget : undefined;
|
|
171
213
|
const maximum = Math.max(chart.total, budget ?? 0);
|
|
172
214
|
const observed = chart.truncated ? `at least ${formatValue(chart.total)}` : formatValue(chart.total);
|
|
173
|
-
const budgetState =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
215
|
+
const budgetState =
|
|
216
|
+
budget === undefined
|
|
217
|
+
? `${observed}${unitSuffix} · budget not configured${chart.truncated ? " · query limit reached" : ""}`
|
|
218
|
+
: chart.total > budget
|
|
219
|
+
? `${observed}${unitSuffix} / ${formatValue(budget)} budget · OVER BUDGET by ${chart.truncated ? "at least " : ""}${formatValue(chart.total - budget)}`
|
|
220
|
+
: chart.truncated
|
|
221
|
+
? `${observed}${unitSuffix} / ${formatValue(budget)} budget · state unknown · query limit reached`
|
|
222
|
+
: `${observed}${unitSuffix} / ${formatValue(budget)} budget · ${formatValue(budget - chart.total)} remaining`;
|
|
180
223
|
const lines = [
|
|
181
224
|
truncateToWidth(theme.bold(options.title), safeWidth, ""),
|
|
182
225
|
truncateToWidth(budgetState, safeWidth, "…"),
|
|
@@ -190,10 +233,16 @@ function renderChart(chart: RenderableChart, width: number, theme: UsageTheme, o
|
|
|
190
233
|
|
|
191
234
|
for (let row = 0; row < USAGE_CHART_HEIGHT; row += 1) {
|
|
192
235
|
const fromBottom = USAGE_CHART_HEIGHT - row - 1;
|
|
193
|
-
const lower = maximum * fromBottom / USAGE_CHART_HEIGHT;
|
|
194
|
-
const upper = maximum * (fromBottom + 1) / USAGE_CHART_HEIGHT;
|
|
236
|
+
const lower = (maximum * fromBottom) / USAGE_CHART_HEIGHT;
|
|
237
|
+
const upper = (maximum * (fromBottom + 1)) / USAGE_CHART_HEIGHT;
|
|
195
238
|
const thresholdRow = budget !== undefined && budget > lower && budget <= upper;
|
|
196
|
-
const label = thresholdRow
|
|
239
|
+
const label = thresholdRow
|
|
240
|
+
? formatValue(budget)
|
|
241
|
+
: row === 0
|
|
242
|
+
? formatValue(maximum)
|
|
243
|
+
: row === Math.floor(USAGE_CHART_HEIGHT / 2)
|
|
244
|
+
? formatValue(maximum / 2)
|
|
245
|
+
: "";
|
|
197
246
|
if (thresholdRow) {
|
|
198
247
|
const color = chart.total > budget ? "error" : "warning";
|
|
199
248
|
lines.push(`${label.padStart(USAGE_Y_AXIS_WIDTH - 2)} ${theme.fg("borderMuted", "│")}${theme.fg(color, "┄".repeat(plotWidth))}`);
|
|
@@ -201,14 +250,14 @@ function renderChart(chart: RenderableChart, width: number, theme: UsageTheme, o
|
|
|
201
250
|
}
|
|
202
251
|
let plot = "";
|
|
203
252
|
for (const bucket of buckets) {
|
|
204
|
-
const scaled = bucket.total / maximum * USAGE_CHART_HEIGHT;
|
|
253
|
+
const scaled = (bucket.total / maximum) * USAGE_CHART_HEIGHT;
|
|
205
254
|
const occupancy = Math.max(0, Math.min(1, scaled - fromBottom));
|
|
206
255
|
if (occupancy <= 0) {
|
|
207
256
|
plot += " ".repeat(barStep);
|
|
208
257
|
continue;
|
|
209
258
|
}
|
|
210
259
|
const block = PARTIAL_BLOCKS[Math.max(0, Math.ceil(occupancy * PARTIAL_BLOCKS.length) - 1)]!;
|
|
211
|
-
const valueHeight = Math.min(bucket.total, maximum * (fromBottom + Math.min(occupancy, 0.5)) / USAGE_CHART_HEIGHT);
|
|
260
|
+
const valueHeight = Math.min(bucket.total, (maximum * (fromBottom + Math.min(occupancy, 0.5))) / USAGE_CHART_HEIGHT);
|
|
212
261
|
plot += seriesStyle(seriesAt(bucket, chart, valueHeight), theme)(block) + (barStep === 2 ? " " : "");
|
|
213
262
|
}
|
|
214
263
|
lines.push(`${label.padStart(USAGE_Y_AXIS_WIDTH - 2)} ${theme.fg("borderMuted", "│")}${plot}`);
|
|
@@ -220,16 +269,32 @@ function renderChart(chart: RenderableChart, width: number, theme: UsageTheme, o
|
|
|
220
269
|
for (let index = 0; index < displayedSeries.length; index += 1) {
|
|
221
270
|
const series = displayedSeries[index]!;
|
|
222
271
|
const bullet = seriesStyle(index, theme)("■");
|
|
223
|
-
lines.push(
|
|
272
|
+
lines.push(
|
|
273
|
+
truncateToWidth(
|
|
274
|
+
`${bullet} ${displayIdentity(series.provider)}/${displayIdentity(series.model)} ${formatValue(series.total)}`,
|
|
275
|
+
safeWidth,
|
|
276
|
+
"…",
|
|
277
|
+
),
|
|
278
|
+
);
|
|
224
279
|
}
|
|
225
|
-
if (chart.series.length > displayedSeries.length)
|
|
226
|
-
|
|
280
|
+
if (chart.series.length > displayedSeries.length)
|
|
281
|
+
lines.push(truncateToWidth(theme.fg("muted", `… ${chart.series.length - displayedSeries.length} more series omitted`), safeWidth, "…"));
|
|
282
|
+
return lines.map((line) => (visibleWidth(line) <= safeWidth ? line : truncateToWidth(line, safeWidth, "…")));
|
|
227
283
|
}
|
|
228
284
|
|
|
229
285
|
export function renderUsageGraph(chart: UsageGraph, width: number, theme: UsageTheme, tokenBudget?: number): string[] {
|
|
230
286
|
return renderChart(
|
|
231
|
-
{
|
|
232
|
-
|
|
287
|
+
{
|
|
288
|
+
period: chart.period,
|
|
289
|
+
start: chart.start,
|
|
290
|
+
end: chart.end,
|
|
291
|
+
buckets: chart.buckets,
|
|
292
|
+
series: chart.series,
|
|
293
|
+
total: chart.totalTokens,
|
|
294
|
+
truncated: chart.truncated,
|
|
295
|
+
},
|
|
296
|
+
width,
|
|
297
|
+
theme,
|
|
233
298
|
{
|
|
234
299
|
title: `${usagePeriod(chart.period).label} token usage`,
|
|
235
300
|
formatValue: compact,
|
|
@@ -243,8 +308,17 @@ export function renderUsageGraph(chart: UsageGraph, width: number, theme: UsageT
|
|
|
243
308
|
|
|
244
309
|
export function renderCostGraph(chart: CostGraph, width: number, theme: UsageTheme, costBudget?: number): string[] {
|
|
245
310
|
return renderChart(
|
|
246
|
-
{
|
|
247
|
-
|
|
311
|
+
{
|
|
312
|
+
period: chart.period,
|
|
313
|
+
start: chart.start,
|
|
314
|
+
end: chart.end,
|
|
315
|
+
buckets: chart.buckets,
|
|
316
|
+
series: chart.series,
|
|
317
|
+
total: chart.totalUsd,
|
|
318
|
+
truncated: chart.truncated,
|
|
319
|
+
},
|
|
320
|
+
width,
|
|
321
|
+
theme,
|
|
248
322
|
{
|
|
249
323
|
title: `${usagePeriod(chart.period).label} cost`,
|
|
250
324
|
formatValue: formatUsd,
|
|
@@ -270,10 +344,18 @@ const USAGE_VIEWS: UsageViewKind[] = ["tokens", "cost"];
|
|
|
270
344
|
* recent rows alone. Aggregation has no such failure mode: result size scales with (scopes x
|
|
271
345
|
* metrics x buckets), never with raw event count.
|
|
272
346
|
*/
|
|
273
|
-
async function loadPiMetrics(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
347
|
+
async function loadPiMetrics(
|
|
348
|
+
client: JittorPanelClient,
|
|
349
|
+
window: ReturnType<typeof resolveUsageWindow>,
|
|
350
|
+
): Promise<{ rows: UsageAggregateRow[]; truncated: boolean }> {
|
|
351
|
+
const result = (await client.call("metrics.usage_series", {
|
|
352
|
+
source: "pi",
|
|
353
|
+
since: window.start,
|
|
354
|
+
until: window.end,
|
|
355
|
+
bucketSizeMs: window.bucketSizeMs,
|
|
356
|
+
bucketCount: window.bucketCount,
|
|
357
|
+
scopeLimit: USAGE_MAX_DISTINCT_SCOPES,
|
|
358
|
+
})) as { rows: UsageAggregateRow[]; truncated: boolean };
|
|
277
359
|
return result;
|
|
278
360
|
}
|
|
279
361
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-jittor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pi extension for Jittor: native routing enforcement, footer, settings, usage graphs, and benchmark panels backed by the @danypops/jittor daemon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["pi-package", "llm-router", "token-budget"],
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"extensions": ["extension/src/index.ts"]
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@danypops/vehicle-client": "^0.
|
|
15
|
+
"@danypops/vehicle-client": "^0.5.1",
|
|
16
16
|
"@danypops/jittor": "^0.14.0",
|
|
17
|
-
"malevich-tui-components": "^0.
|
|
17
|
+
"malevich-tui-components": "^0.16.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@earendil-works/pi-coding-agent": "*",
|