token-stats-timer 1.0.14 → 1.0.15

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/token-stats.ts +113 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-stats-timer",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "pi extension to display token usage and time spend.",
5
5
  "keywords": [
6
6
  "pi-package",
package/token-stats.ts CHANGED
@@ -13,8 +13,9 @@ import type {
13
13
  ExtensionContext,
14
14
  Theme,
15
15
  } from "@earendil-works/pi-coding-agent";
16
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
16
17
  import type { AssistantMessage } from "@earendil-works/pi-ai";
17
- import { Text } from "@earendil-works/pi-tui";
18
+ import { Markdown } from "@earendil-works/pi-tui";
18
19
  import {
19
20
  appendFile,
20
21
  mkdir,
@@ -212,6 +213,41 @@ function formatTokenSpeed(tokensPerSecond: number): string {
212
213
  return `${Math.round(tokensPerSecond / 1000000)}M`;
213
214
  }
214
215
 
216
+ /**
217
+ * 生成 GitHub 风格 markdown 表格源码(由 Markdown 组件负责渲染对齐/换行)。
218
+ *
219
+ * @param headers 表头
220
+ * @param rows 数据行
221
+ * @param opts.aligns 每列对齐方式,缺省左对齐
222
+ * @param opts.totalRow 底部合计行
223
+ */
224
+ function renderTable(
225
+ headers: string[],
226
+ rows: string[][],
227
+ opts?: {
228
+ aligns?: Array<"left" | "right" | "center">;
229
+ totalRow?: string[];
230
+ },
231
+ ): string[] {
232
+ const aligns = opts?.aligns ?? [];
233
+ // 转义单元格内的 | 与换行,防止破坏表格结构
234
+ const esc = (s: string) => s.replace(/\|/g, "\\|").replace(/\n/g, " ");
235
+ const alignMark = (i: number) => {
236
+ const a = aligns[i] ?? "left";
237
+ return a === "right" ? "---:" : a === "center" ? ":---:" : "---";
238
+ };
239
+
240
+ const lines = [
241
+ `| ${headers.map(esc).join(" | ")} |`,
242
+ `| ${headers.map((_, i) => alignMark(i)).join(" | ")} |`,
243
+ ...rows.map((r) => `| ${r.map(esc).join(" | ")} |`),
244
+ ];
245
+ if (opts?.totalRow) {
246
+ lines.push(`| ${opts.totalRow.map(esc).join(" | ")} |`);
247
+ }
248
+ return lines;
249
+ }
250
+
215
251
  function isReasonableTokenSpeed(tokensPerSecond: number): boolean {
216
252
  return Number.isFinite(tokensPerSecond) && tokensPerSecond > 0 && tokensPerSecond <= MAX_REASONABLE_TOKEN_SPEED;
217
253
  }
@@ -1317,23 +1353,25 @@ export function createTokenStats(
1317
1353
  return total > 0 ? (d.sumCacheRead / total) * 100 : 0;
1318
1354
  }
1319
1355
 
1320
- function renderDaySummary(daily: DailyRecord): string {
1356
+ function renderDaySummary(daily: DailyRecord): string[] {
1321
1357
  const d = daily;
1322
1358
  const avgInput = d.count > 0 ? d.sumInput / d.count : 0;
1323
1359
  const avgOutput = d.count > 0 ? d.sumOutput / d.count : 0;
1324
1360
  const totalPrompt = d.sumInput + d.sumCacheRead + d.sumCacheWrite;
1325
1361
  const cacheHitRate = weightedCacheHitRate(d);
1326
1362
 
1327
- const lines = [
1328
- `对话次数: ${d.count}`,
1329
- `新增输入: ${formatTokens(d.sumInput)} (平均 ${formatTokens(avgInput)}/次,未命中缓存)`,
1330
- `缓存输入: ${formatTokens(d.sumCacheRead)}`,
1331
- `总输出: ${formatTokens(d.sumOutput)} (平均 ${formatTokens(avgOutput)}/次)`,
1332
- `总token: ${formatTokens(totalPrompt)} (新增 + 缓存)`,
1333
- `缓存命中率: ${cacheHitRate.toFixed(1)}%`,
1334
- `平均速率: ${(d.sumTokensPerSec / d.count).toFixed(1)} t/s`,
1335
- ];
1336
- return lines.join("\n");
1363
+ return renderTable(
1364
+ ["指标", "数值"],
1365
+ [
1366
+ ["对话次数", String(d.count)],
1367
+ ["新增输入", `${formatTokens(d.sumInput)}(平均 ${formatTokens(avgInput)}/次,未命中缓存)`],
1368
+ ["缓存输入", formatTokens(d.sumCacheRead)],
1369
+ ["总输出", `${formatTokens(d.sumOutput)}(平均 ${formatTokens(avgOutput)}/次)`],
1370
+ ["总token", `${formatTokens(totalPrompt)}(新增 + 缓存)`],
1371
+ ["缓存命中率", `${cacheHitRate.toFixed(1)}%`],
1372
+ ["平均速率", `${(d.sumTokensPerSec / d.count).toFixed(1)} t/s`],
1373
+ ],
1374
+ );
1337
1375
  }
1338
1376
 
1339
1377
  async function showStats(
@@ -1341,9 +1379,7 @@ export function createTokenStats(
1341
1379
  title: string,
1342
1380
  ctx: ExtensionContext,
1343
1381
  ) {
1344
- const theme = ctx.ui.theme;
1345
- const text = `${theme.fg("accent", theme.bold(title))}\n${theme.fg("dim", "─".repeat(42))}\n` +
1346
- lines.map((l) => theme.fg("dim", l)).join("\n");
1382
+ const text = `## ${title}\n\n${lines.join("\n")}`;
1347
1383
  pi.sendMessage({
1348
1384
  customType: "token-stats",
1349
1385
  content: text,
@@ -1369,7 +1405,7 @@ export function createTokenStats(
1369
1405
  }
1370
1406
 
1371
1407
  await showStats(
1372
- renderDaySummary(daily).split("\n"),
1408
+ renderDaySummary(daily),
1373
1409
  `Token 统计 | ${date}`,
1374
1410
  ctx,
1375
1411
  );
@@ -1393,18 +1429,18 @@ export function createTokenStats(
1393
1429
 
1394
1430
  records.sort((a, b) => a.hour - b.hour);
1395
1431
 
1396
- const lines = [
1397
- "次数 输入 输出 命中率 速率",
1398
- "─".repeat(40),
1399
- ...records.map((r) =>
1400
- `${String(r.hour).padStart(2, "0")} ` +
1401
- `${String(r.count).padStart(3)} ` +
1402
- `${formatTokens(r.sumInput).padStart(7)} ` +
1403
- `${formatTokens(r.sumOutput).padStart(7)} ` +
1404
- `${weightedCacheHitRate(r).toFixed(1).padStart(5)}% ` +
1405
- `${(r.sumTokensPerSec / r.count).toFixed(1).padStart(5)}`,
1406
- ),
1407
- ];
1432
+ const lines = renderTable(
1433
+ ["时间", "次数", "输入", "输出", "命中率", "速率"],
1434
+ records.map((r) => [
1435
+ String(r.hour).padStart(2, "0"),
1436
+ String(r.count),
1437
+ formatTokens(r.sumInput),
1438
+ formatTokens(r.sumOutput),
1439
+ `${weightedCacheHitRate(r).toFixed(1)}%`,
1440
+ `${(r.sumTokensPerSec / r.count).toFixed(1)}`,
1441
+ ]),
1442
+ { aligns: ["left", "right", "right", "right", "right", "right"] },
1443
+ );
1408
1444
 
1409
1445
  await showStats(lines, `按小时分布 | ${date}`, ctx);
1410
1446
  }
@@ -1433,23 +1469,25 @@ export function createTokenStats(
1433
1469
  return;
1434
1470
  }
1435
1471
 
1436
- const lines = [
1437
- "日期 次数 新增输入 缓存输入 输出 总token 命中率 速率",
1438
- "─".repeat(70),
1439
- ...weekRecords.map((r) => {
1472
+ const lines = renderTable(
1473
+ ["日期", "次数", "新增输入", "缓存输入", "输出", "总token", "命中率", "速率"],
1474
+ weekRecords.map((r) => {
1440
1475
  const totalPrompt = r.sumInput + r.sumCacheRead + r.sumCacheWrite;
1441
- return (
1442
- `${r.date} ` +
1443
- `${String(r.count).padStart(3)} ` +
1444
- `${formatTokens(r.sumInput).padStart(7)} ` +
1445
- `${formatTokens(r.sumCacheRead).padStart(7)} ` +
1446
- `${formatTokens(r.sumOutput).padStart(7)} ` +
1447
- `${formatTokens(totalPrompt).padStart(7)} ` +
1448
- `${weightedCacheHitRate(r).toFixed(1).padStart(5)}% ` +
1449
- `${(r.sumTokensPerSec / r.count).toFixed(1).padStart(5)}`
1450
- );
1476
+ return [
1477
+ r.date,
1478
+ String(r.count),
1479
+ formatTokens(r.sumInput),
1480
+ formatTokens(r.sumCacheRead),
1481
+ formatTokens(r.sumOutput),
1482
+ formatTokens(totalPrompt),
1483
+ `${weightedCacheHitRate(r).toFixed(1)}%`,
1484
+ `${(r.sumTokensPerSec / r.count).toFixed(1)}`,
1485
+ ];
1451
1486
  }),
1452
- ];
1487
+ {
1488
+ aligns: ["left", "right", "right", "right", "right", "right", "right", "right"],
1489
+ },
1490
+ );
1453
1491
 
1454
1492
  await showStats(lines, "本周每天汇总", ctx);
1455
1493
  }
@@ -1495,40 +1533,46 @@ export function createTokenStats(
1495
1533
  const totalPrompt = total.sumInput + total.sumCacheRead + total.sumCacheWrite;
1496
1534
  const cacheHitRate = weightedCacheHitRate(total);
1497
1535
 
1498
- const lines = [
1499
- "日期 次数 新增输入 缓存输入 输出 总token 命中率 速率",
1500
- "─".repeat(70),
1501
- ...monthRecords.map((r) => {
1536
+ const lines = renderTable(
1537
+ ["日期", "次数", "新增输入", "缓存输入", "输出", "总token", "命中率", "速率"],
1538
+ monthRecords.map((r) => {
1502
1539
  const tp = r.sumInput + r.sumCacheRead + r.sumCacheWrite;
1503
- return (
1504
- `${r.date} ` +
1505
- `${String(r.count).padStart(3)} ` +
1506
- `${formatTokens(r.sumInput).padStart(7)} ` +
1507
- `${formatTokens(r.sumCacheRead).padStart(7)} ` +
1508
- `${formatTokens(r.sumOutput).padStart(7)} ` +
1509
- `${formatTokens(tp).padStart(7)} ` +
1510
- `${weightedCacheHitRate(r).toFixed(1).padStart(5)}% ` +
1511
- `${(r.sumTokensPerSec / r.count).toFixed(1).padStart(5)}`
1512
- );
1540
+ return [
1541
+ r.date,
1542
+ String(r.count),
1543
+ formatTokens(r.sumInput),
1544
+ formatTokens(r.sumCacheRead),
1545
+ formatTokens(r.sumOutput),
1546
+ formatTokens(tp),
1547
+ `${weightedCacheHitRate(r).toFixed(1)}%`,
1548
+ `${(r.sumTokensPerSec / r.count).toFixed(1)}`,
1549
+ ];
1513
1550
  }),
1514
- "",
1515
- `合计 ${String(total.count).padStart(3)} ` +
1516
- `${formatTokens(total.sumInput).padStart(7)} ` +
1517
- `${formatTokens(total.sumCacheRead).padStart(7)} ` +
1518
- `${formatTokens(total.sumOutput).padStart(7)} ` +
1519
- `${formatTokens(totalPrompt).padStart(7)} ` +
1520
- `${cacheHitRate.toFixed(1).padStart(5)}% ` +
1521
- `${(total.sumTokensPerSec / total.count).toFixed(1).padStart(5)}`,
1522
- ];
1551
+ {
1552
+ aligns: ["left", "right", "right", "right", "right", "right", "right", "right"],
1553
+ totalRow: [
1554
+ "合计",
1555
+ String(total.count),
1556
+ formatTokens(total.sumInput),
1557
+ formatTokens(total.sumCacheRead),
1558
+ formatTokens(total.sumOutput),
1559
+ formatTokens(totalPrompt),
1560
+ `${cacheHitRate.toFixed(1)}%`,
1561
+ `${(total.sumTokensPerSec / total.count).toFixed(1)}`,
1562
+ ],
1563
+ },
1564
+ );
1523
1565
 
1524
1566
  await showStats(lines, `${month} 月度汇总`, ctx);
1525
1567
  }
1526
1568
 
1527
1569
  // ── 事件注册 ─────────────────────────────────────────
1528
1570
 
1529
- // ── message renderer: 渲染 /stats 发出的消息 ─────────
1530
- pi.registerMessageRenderer("token-stats", (message, _options, _theme) => {
1531
- return new Text(message.content, 0, 0);
1571
+ // ── message renderer: 渲染 /stats 发出的消息(markdown 表格)──
1572
+ pi.registerMessageRenderer("token-stats", (message, _options, theme) => {
1573
+ return new Markdown(message.content, 0, 0, getMarkdownTheme(), {
1574
+ color: (text) => theme.fg("dim", text),
1575
+ });
1532
1576
  });
1533
1577
 
1534
1578
  // ── turn_start: 记录时间 + 检测供应商切换 ──────────