@xiaosat/pi-speed-sparkline1.5.1
pi extension: two-line status area with a live token-speed sparkline and an AI output-time timer (tool time excluded, shown as a separate breakdown)
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-speed-sparkline
English | 简体中文
npm 包: @xiaosat/pi-speed-sparkline · 作者: xiaosat · License: MIT
一个 pi 扩展包,在输入框下方提供两行状态区:第一行是 AI 输出耗时 + 输出累计 + 🔧 工具时长分项 + 💬 对话时长,第二行是 回答速度折线图 + 实时速度 + 统计。
⏱ 1m23s / 🤖 12m05s 🔧 3m40s 💬 45m
▁▂▅▇▆▃▂▁▅▇ 93.2 tok/s ↑128 ↓~61
主要功能
状态区布局
- 两行显示:pi 内置 footer 会把所有
setStatus项合并成同一行且过滤换行,无法分行;本扩展改用ctx.ui.setWidget()在输入框下方、footer 上方渲染多行状态区。 - 第 0 行:⏱ 输出耗时 / 🤖 输出累计(
agent-timer.ts) + 🔧 工具时长分项 + 💬 对话时长 + 行尾徽标(speed-sparkline.ts)。 - 第 1 行:速度折线图 + 实时/最近速度 + 最大/中位统计。
- 统一渲染:两个扩展通过
status-bus.ts各自注册「片段」(line / order),由总线合成多行;非 TUI 模式(RPC 之外)自动回退为单行setStatus。
速度折线图(speed-sparkline.ts)
- 速度折线图:每完成一次回答,记录该次平均速度(tok/s),折线图随之向左滚动,最多保留最近 10 次;图表固定占 10 格,不足 10 个样本时左侧用
▁(零高度)占位。速度按10 / 30 / 50 / 80 / 120 / 200档位映射到▁▂▃▄▅▆▇字符高度(7 档:低于 10 tok/s 为▁,200 tok/s 及以上为▇),一图看清趋势。 - 流式实时速度:回答生成过程中,状态区实时刷新当前速度(输出 token ÷ 经过时间),最右侧追加当次实时格与数字。
- 吐字间隙不空白:上一次下行的 token 结束后进入工具执行或等待新一轮回复时,保留上一次吐字速度持续展示,不再出现速度空白。
- 工具时长分项:第 0 行显示工具执行的累计时长(
🔧 3m40s),与 ⏱ 的纯输出耗时对照,一眼看出“AI 吐字”与“工具(code)执行”谁占用更多时间;工具正在执行时,行尾以⚙ bash显示工具名(状态,与 🔧 时长统计分开),充当“正在操作工具”标志;多个工具并行只计一次,等待你回答权限/选择题时暂停不计。 - 对话时长:本会话从创建到现在经过的时间(
💬 45m);恢复/重载会话时仍按会话创建时间计算。 - 持久化:工具累计时长与计时器一样写入会话记录(
speed-sparkline-tool),/reload、重启恢复后继续累加;新会话清零。 - 统计信息:空闲时附带显示最近 10 次的最大速度(
↑)与中位速度(↓~),兼顾峰值与典型水平。 - token 计数兜底:优先使用 provider 返回的
usage.output;缺失时按 text / thinking / toolCall 内容字符数估算(≈4 字符/token)。 - 多模型感知:记录每次速度对应的
provider/model,便于区分不同模型的速度表现。
AI 输出计时器(agent-timer.ts)
- 只计 AI 真正吐字的时间:每次计时区间为一条助手消息的
message_start → message_end(流式输出开始到结束),不包括工具执行——工具花多久都停着不走表。 - 工具后重新计时:每次调用工具后,模型的下一条输出会从 0 重新计时(每条助手消息都是新一轮计数)。
- 剔除等待用户的时间:输出途中,阻塞等待你回答的 UI 提问(权限确认、选择题、
ask_user_question等)不计入,此时计时冻结并显示⏸;两次输出之间(工具/输入空闲)同样不计入。 - 本次 / 会话累计:
⏱后左侧为当前(空闲时为最近一次)输出耗时,右侧🤖为本次会话每一次输出耗时的求和。 - 每秒刷新:输出中每秒重新渲染,时长实时增长;内容无变化时不重复刷 UI。
- 跨重载/重启保留:每次输出结算后用
pi.appendEntry()把累计值写入会话记录,/reload、重启并恢复会话后继续累加;新会话(/new)自动清零。 - 中断也计入:输出被打断、或退出/重载时输出尚未结束,已完成的那部分时间同样结算保留。
健壮性
- 状态展示失败静默忽略(会话重载等场景不报错);速度历史存于
globalThis符号键;session_shutdown时注销片段并清空状态区。 - 因为 pi 的扩展加载器使用
moduleCache: false,同一包内的扩展各有独立模块实例,status-bus的注册表因此放在globalThis的符号键上。
安装
pi install npm:@xiaosat/pi-speed-sparkline
重启 pi 即可在输入框下方看到两行状态区。
也可以用 pi -e npm:@xiaosat/pi-speed-sparkline 单次启动试用,不写入配置。
工作原理
多行状态区(status-bus)
pi.extensions 注册了两个扩展,它们都通过 status-bus.ts 提供片段:
| 扩展 | line | order | 内容 |
|---|---|---|---|
agent-timer |
0 | 0 | ⏱ 1m23s / 🤖 12m05s(等待用户时追加 ⏸) |
speed-sparkline |
0 | 10 | 🔧 3m40s(工具累计时长;执行中实时累加) |
speed-sparkline |
0 | 20 | 💬 45m(会话创建至今) |
speed-sparkline |
0 | 30 | ⚙ bash / ⚡ streaming / ⏳ waiting(行尾;各自仅在对应状态下显示,⚙ 表示工具执行中) |
speed-sparkline |
1 | 0 | ▁▂▅▇▆▃▂▁▅▇ 93.2 tok/s ↑128 ↓~61 |
总线按 line 分行、按 order 排序,同一行用两个空格连接,再通过 ctx.ui.setWidget(key, lines, { placement: "belowEditor" }) 渲染;内容无变化时跳过渲染。
速度折线图事件
| 事件 | 行为 |
|---|---|
session_start |
清空历史并注册片段;恢复工具累计时长(新会话清零);从会话头确定对话起始时间 |
before_provider_request |
标记"等待首个 token" |
message_start / message_update |
开始计时、实时刷新流式速度并清除工具标志 |
message_end |
结算该次平均速度,推入历史并左滚折线 |
tool_execution_start / tool_execution_end |
第 0 行行尾显示 ⚙ 工具名(执行结束即消失),累计工具时长(并行取并集),保留上一次吐字速度 |
ui_prompt_start / ui_prompt_end |
暂停/恢复工具时长计时 |
agent_settled |
结算工具累计时长并写入会话记录 |
agent_end / session_shutdown |
清理进行中状态;注销片段并清空状态区 |
AI 输出计时器事件
| 事件 | 行为 |
|---|---|
session_start |
从会话记录恢复累计值(新会话清零),注册片段,启动每秒刷新 |
message_start |
一条新的助手消息:从 0 重新计时(工具后的下一条输出由此重新开始) |
ui_prompt_start / ui_prompt_end |
开始/结束等待用户回答,期间暂停计时(⏸) |
message_end |
结算本次输出耗时,并入会话累计并写入会话记录 |
agent_settled |
兜底结算尚未结束的输出(异常中断),并落盘 |
session_shutdown |
清理定时器;输出中被打断时结算已耗时;注销片段 |
计时语义(工具执行不计时,每次输出重新从 0 开始):
输出1 开始 ──► 输出1 结束 ──► 工具执行(不计时) ──► 输出2 开始(重新计时) ──► ...
等待你回答的提问何时出现,都剔除不计,显示 ⏸
状态区图例
⏱ 1m23s / 🤖 12m05s 🔧 3m40s 💬 45m ← 第 0 行:输出耗时累计 + 工具时长分项 + 对话时长(行尾另有 ⚙ 工具名 / ⚡ / ⏳)
▁▂▅▇▆▃▂▁▅▇ 93.2 tok/s ↑128 ↓~61 ← 第 1 行:折线 + 速度 + 统计
~/proj · model · 12% ctx ← pi 内置 footer
| 显示 | 含义 |
|---|---|
⏱ 1m23s / 🤖 12m05s |
当前(空闲时为最近一次)输出耗时 / 会话内每一次输出耗时之和(不含工具执行) |
⏱ 1m23s ⏸ / 🤖 12m05s |
正等待你回答,计时已暂停冻结 |
🔧 3m40s |
工具(code)执行累计耗时,与 ⏱ 对照;工具执行中实时累加 |
⚙ bash |
正在执行工具 bash(行尾),执行结束即消失 |
⚡ streaming |
本次回答正在吐字(行尾);与 ⚙ 工具名 各自独立显示 |
⏳ waiting |
请求已发出、等待下一个首 token(行尾);与 ⚡ streaming 互斥,一次回答的周期为 ⏳ waiting → ⚡ streaming →(结束) |
💬 45m |
本对话从创建到现在经过的时间 |
▁▂▅▇▆▃▂▁▅▇ |
最近 10 次回答速度趋势(高度 = 速度档位;▁ 占位 = 尚无样本,固定 10 格) |
93.2 tok/s(第 1 行) |
实时速度(流式中)或空闲/等待时最近一次平均速度 |
…(第 1 行) |
工具执行中且暂无速度数据 |
↑128 ↓~61 |
最近 10 次:最大速度 / 中位速度 |
时长格式:3.4s(<10 秒) / 42s(<1 分) / 1m23s(<1 小时) / 2h05m。
自测
状态区与计时逻辑带有零依赖自测(用假 pi/ctx 按真实事件顺序驱动,校验暂停剔除、空闲剔除、持久化恢复、两行渲染等)。测试文件位于源码仓库中,npm 包不包含该文件:
node --experimental-strip-types agent-timer.test.ts
依赖
- pi 及官方包(
@earendil-works/pi-coding-agent、@earendil-works/pi-ai)以 peerDependencies 方式由运行时注入,无需额外安装。 - Node.js ≥ 22.19.0
更新日志
1.5.1
- 统计与状态拆分:
🔧片段只显示工具累计时长(🔧 3m40s,工具执行中仍实时累加);正在执行的工具名移到行尾,以状态徽标⚙ bash显示,仅在工具执行中出现,执行结束即消失。 - 文档:状态区表格与图例已同步到新布局。
1.5.0
- 计时语义重构:⏱ 只计模型真实吐字时间(每条助手消息
message_start → message_end),不再包含工具执行;工具花多久都不计入。 - 工具后重新计时:每次调用工具后,模型下一条输出从 0 重新计时。
- 🤖 会话累计 = 会话内每一次输出耗时的求和。
- 工具时长分项:第 0 行新增
🔧 3m40s(工具执行的累计时长),与 ⏱ 纯输出耗时对照;工具执行中同时显示工具名(如🔧 bash 1m23s)。 - 对话时长移位:💬 排在工具分项之后(新第 0 行顺序:
⏱ / 🤖 → 🔧 → 💬);行尾徽标仅⚡ streaming/⏳ waiting。
1.4.0
- 对话时长
💬 45m:第 0 行显示本对话从创建至今经过的时间;reload/恢复会话后按创建时间继续计算,/new清零。 - 吐字徽标
⚡ streaming:回答正在吐字时显示在第 0 行行尾;与🔧 <工具名>同时存在时并排显示。 - 等待徽标
⏳ waiting:请求已发出但首 token 未到达时显示在第 0 行行尾;与⚡ streaming互斥(一次回答周期为⏳ waiting → ⚡ streaming → 结束)。第 1 行不再显示等待状态。 - 固定长度折线:折线图始终渲染 10 格,不足部分从第一次回答起就用
▁占位。 - 工具时长统计:内部累计工具执行的墙钟时长(并行工具只计一次,等待你回答时暂停),并随会话持久化(
speed-sparkline-tool)。暂不在状态区展示。 - 图标变更:计时器的会话累计由
Σ改为🤖。 - 公共函数:
formatDuration移入status-bus.ts,两个扩展共用;agent-timer.ts仍保留导出以兼容旧引用。 - 文档:中英文 README 同步更新了状态区布局、事件表与图例。
License
MIT © xiaosat