@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.
@@ -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" | "success" | "warning" | "error" | "thinkingText" | "muted" | "dim" | "borderMuted"
25
- | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator";
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", "syntaxFunction", "syntaxString", "syntaxNumber",
57
- "syntaxKeyword", "syntaxType", "thinkingText", "syntaxVariable", "syntaxOperator",
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 { key: string; provider: string; model: string; total: number }
77
- interface RenderableBucket { start: number; end: number; total: number; series: Record<string, number> }
78
- interface RenderableChart { period: UsagePeriod; start: number; end: number; buckets: RenderableBucket[]; series: RenderableSeries[]; total: number; truncated: boolean }
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.replace(/[\r\n\t]/g, " ").replace(/ +/g, " ").trim().slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS);
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 = budget === undefined
174
- ? `${observed}${unitSuffix} · budget not configured${chart.truncated ? " · query limit reached" : ""}`
175
- : chart.total > budget
176
- ? `${observed}${unitSuffix} / ${formatValue(budget)} budget · OVER BUDGET by ${chart.truncated ? "at least " : ""}${formatValue(chart.total - budget)}`
177
- : chart.truncated
178
- ? `${observed}${unitSuffix} / ${formatValue(budget)} budget · state unknown · query limit reached`
179
- : `${observed}${unitSuffix} / ${formatValue(budget)} budget · ${formatValue(budget - chart.total)} remaining`;
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 ? formatValue(budget) : row === 0 ? formatValue(maximum) : row === Math.floor(USAGE_CHART_HEIGHT / 2) ? formatValue(maximum / 2) : "";
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(truncateToWidth(`${bullet} ${displayIdentity(series.provider)}/${displayIdentity(series.model)} ${formatValue(series.total)}`, safeWidth, "…"));
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) lines.push(truncateToWidth(theme.fg("muted", `… ${chart.series.length - displayedSeries.length} more series omitted`), safeWidth, "…"));
226
- return lines.map((line) => visibleWidth(line) <= safeWidth ? line : truncateToWidth(line, safeWidth, "…"));
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
- { period: chart.period, start: chart.start, end: chart.end, buckets: chart.buckets, series: chart.series, total: chart.totalTokens, truncated: chart.truncated },
232
- width, theme,
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
- { period: chart.period, start: chart.start, end: chart.end, buckets: chart.buckets, series: chart.series, total: chart.totalUsd, truncated: chart.truncated },
247
- width, theme,
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(client: JittorPanelClient, window: ReturnType<typeof resolveUsageWindow>): Promise<{ rows: UsageAggregateRow[]; truncated: boolean }> {
274
- const result = await client.call("metrics.usage_series", {
275
- source: "pi", since: window.start, until: window.end, bucketSizeMs: window.bucketSizeMs, bucketCount: window.bucketCount, scopeLimit: USAGE_MAX_DISTINCT_SCOPES,
276
- }) as { rows: UsageAggregateRow[]; truncated: boolean };
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.0",
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.1.1",
15
+ "@danypops/vehicle-client": "^0.5.1",
16
16
  "@danypops/jittor": "^0.14.0",
17
- "malevich-tui-components": "^0.6.0"
17
+ "malevich-tui-components": "^0.16.1"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@earendil-works/pi-coding-agent": "*",