omp packages

@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)

xiaosat Published 10 days ago MIT ~1.1K downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

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