@cnife/pi-footer 0.1.0 → 0.2.0
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/README.md +4 -2
- package/extensions/cnife-footer.ts +62 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,18 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
<目录> · <分支> ↑<ahead> ↓<behind> *<未提交数> · <会话名>
|
|
11
|
-
<provider>/<model_id> · <思考强度> · <已用>/<总量> (<百分比>) · $<花费>
|
|
11
|
+
<provider>/<model_id> · <思考强度> · <已用>/<总量> (<百分比>) · $<花费> · <tps>t/s
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
- 各项 ` · ` 分隔,空值自动省略(无 git 段、无会话名、cost=0 等均不显示)
|
|
14
|
+
- 各项 ` · ` 分隔,空值自动省略(无 git 段、无会话名、cost=0、无 tps 等均不显示)
|
|
15
15
|
- git:`main ↑1 ↓2 *3`,ahead/behind/未提交数为 0 时隐藏
|
|
16
16
|
- cost:`<0.01` 用 3 位小数,否则 2 位;为 0 时整项隐藏
|
|
17
17
|
- token:`<1000` 原值,`<1M` 用 `X.XK`,`≥1M` 用 `X.XM`
|
|
18
|
+
- tps:单条 assistant message 输出速率 `NN.Nt/s`,无数据时整项隐藏;口径 `usage.output / (message_end − before_provider_request)`,elapsed 含网络 + 排队 + 生成(对齐 pi 官方 `tps.ts`)
|
|
18
19
|
|
|
19
20
|
## 刷新
|
|
20
21
|
|
|
21
22
|
- git 状态:10s 定时器 + `tool_result`(agent 工具执行后即时)+ `user_bash`(`!`/`!!` 命令后 1.5s debounce)+ `onBranchChange`
|
|
22
23
|
- 其余字段随 pi 内部状态自动重渲染
|
|
24
|
+
- tps:`message_end` 后刷新并保持到下一条;切模型 / compact / 树导航 / 新会话时重置;`stopReason` 为 `aborted`/`error` 时丢弃
|
|
23
25
|
|
|
24
26
|
## 字符约束
|
|
25
27
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cnife-footer - 个人专属 pi footer。
|
|
3
3
|
* 两行:第一行工作区(目录 · git分支 ↑ahead ↓behind *未提交 · 会话名),
|
|
4
|
-
* 第二行模型(provider/id · thinking · ctx · $cost)。
|
|
4
|
+
* 第二行模型(provider/id · thinking · ctx · $cost · tps)。
|
|
5
5
|
* 全 dim,仅 ASCII + Unicode,无 nerd font,无配置。
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -31,6 +31,12 @@ function fmtCost(c: number): string {
|
|
|
31
31
|
return c < 0.01 ? `$${c.toFixed(3)}` : `$${c.toFixed(2)}`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Type guard: assistant message 才有 usage/stopReason,用于 message_end。 */
|
|
35
|
+
function isAssistantMessage(message: unknown): message is AssistantMessage {
|
|
36
|
+
if (!message || typeof message !== "object") return false;
|
|
37
|
+
return (message as { role?: unknown }).role === "assistant";
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
/** Parse `git status -b --porcelain` output into branch/ahead/behind/dirty. */
|
|
35
41
|
function parseGitStatus(stdout: string): GitState {
|
|
36
42
|
const lines = stdout.split("\n");
|
|
@@ -78,6 +84,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
84
|
let refreshing = false;
|
|
79
85
|
let cwd = "";
|
|
80
86
|
|
|
87
|
+
// TPS(输出速率):requestAt 配对 before_provider_request/message_end,
|
|
88
|
+
// lastTps 保存最近一次有效速率,render 时拼到第 2 行末尾。
|
|
89
|
+
let requestAt: number | null = null;
|
|
90
|
+
let lastTps: number | null = null;
|
|
91
|
+
|
|
81
92
|
const refreshGit = async () => {
|
|
82
93
|
if (refreshing) return;
|
|
83
94
|
refreshing = true;
|
|
@@ -96,9 +107,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
96
107
|
}
|
|
97
108
|
};
|
|
98
109
|
|
|
110
|
+
/** 清空 TPS 锚点与最近速率并重渲染(切模型/compact/树导航/无效结束时调用)。 */
|
|
111
|
+
const resetTps = () => {
|
|
112
|
+
requestAt = null;
|
|
113
|
+
lastTps = null;
|
|
114
|
+
activeTui?.requestRender();
|
|
115
|
+
};
|
|
116
|
+
|
|
99
117
|
pi.on("session_start", async (_event, ctx) => {
|
|
100
118
|
cwd = ctx.cwd;
|
|
101
119
|
gitState = { branch: null, ahead: 0, behind: 0, dirty: 0 };
|
|
120
|
+
requestAt = null;
|
|
121
|
+
lastTps = null;
|
|
102
122
|
|
|
103
123
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
104
124
|
activeTui = tui;
|
|
@@ -135,7 +155,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
135
155
|
const session = pi.getSessionName() ?? "";
|
|
136
156
|
const line1 = renderLine([dir, gitStr, session], width);
|
|
137
157
|
|
|
138
|
-
// Line 2: provider/id · thinking · ctx · $cost (cost=0 隐藏)
|
|
158
|
+
// Line 2: provider/id · thinking · ctx · $cost · tps (cost=0/无 tps 隐藏)
|
|
139
159
|
const model = ctx.model
|
|
140
160
|
? `${ctx.model.provider}/${ctx.model.id}`
|
|
141
161
|
: "no-model";
|
|
@@ -162,7 +182,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
const costStr = cost > 0 ? fmtCost(cost) : "";
|
|
165
|
-
const
|
|
185
|
+
const tpsStr = lastTps != null ? `${lastTps.toFixed(1)}t/s` : "";
|
|
186
|
+
const line2 = renderLine(
|
|
187
|
+
[model, thinking, ctxStr, costStr, tpsStr],
|
|
188
|
+
width,
|
|
189
|
+
);
|
|
166
190
|
|
|
167
191
|
return [line1, line2];
|
|
168
192
|
} catch {
|
|
@@ -178,6 +202,41 @@ export default function (pi: ExtensionAPI) {
|
|
|
178
202
|
}, GIT_INTERVAL_MS);
|
|
179
203
|
});
|
|
180
204
|
|
|
205
|
+
// TPS(输出速率):before_provider_request 记请求时刻,message_end 算 output/elapsed。
|
|
206
|
+
// 口径对齐 pi 官方 tps.ts:elapsed 含网络 + 排队 + 生成,不用 message.timestamp。
|
|
207
|
+
pi.on("before_provider_request", () => {
|
|
208
|
+
requestAt = Date.now();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
pi.on("message_end", (event) => {
|
|
212
|
+
if (!isAssistantMessage(event.message)) return;
|
|
213
|
+
const message = event.message as AssistantMessage;
|
|
214
|
+
// aborted/error 或无效数据时丢弃,不留旧值
|
|
215
|
+
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
216
|
+
resetTps();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const output = message.usage.output;
|
|
220
|
+
if (requestAt === null || output <= 0) {
|
|
221
|
+
resetTps();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const elapsedSeconds = (Date.now() - requestAt) / 1000;
|
|
225
|
+
if (elapsedSeconds <= 0) {
|
|
226
|
+
resetTps();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
lastTps = output / elapsedSeconds;
|
|
230
|
+
activeTui?.requestRender();
|
|
231
|
+
// 消费完毕,等下一条 message 的 before_provider_request
|
|
232
|
+
requestAt = null;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// 切模型 / compact / 树导航后重置 TPS
|
|
236
|
+
pi.on("session_tree", () => resetTps());
|
|
237
|
+
pi.on("session_compact", () => resetTps());
|
|
238
|
+
pi.on("model_select", () => resetTps());
|
|
239
|
+
|
|
181
240
|
// agent 工具执行后立即刷新 git
|
|
182
241
|
pi.on("tool_result", async () => {
|
|
183
242
|
void refreshGit();
|