pi-open-tui 0.2.8 → 0.2.9
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 +1 -1
- package/extensions/open-tui/telemetry.ts +33 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ After each complete agent run, open-tui shows one transient notification. Tool-c
|
|
|
73
73
|
|
|
74
74
|
The notification uses the footer's icon mode and semantic theme colors. Configure its master switch and individual TPS, TTFT, duration, token, stall, and cost segments from the **Telemetry** tab in `/open-tui`.
|
|
75
75
|
|
|
76
|
-
TPS is the
|
|
76
|
+
TPS is the complete generation throughput for the agent run: all provider-reported assistant output tokens divided by the summed generation time of every LLM turn, measured from `turn_start` through the assistant `message_end`. This includes time-to-first-token, hidden reasoning, buffering, and stalls so the token count and timing cover the same interval. Tool execution between turns is excluded. A run with no output tokens or no measurable generation time is shown as `TPS —`. The `stall` segment shows occurrence count followed by accumulated duration. The optional `$ / M` value uses the model's list-price `usage.cost.total`; it is not the session's cumulative cost shown in the footer.
|
|
77
77
|
|
|
78
78
|
## Local development
|
|
79
79
|
|
|
@@ -14,8 +14,6 @@ import { resolveGlyphs } from "./icons.ts";
|
|
|
14
14
|
import { fmtTokens, formatDuration } from "./utils.ts";
|
|
15
15
|
|
|
16
16
|
const STALL_THRESHOLD_MS = 1000;
|
|
17
|
-
const MIN_STREAM_UPDATES = 2;
|
|
18
|
-
const MIN_MEASUREMENT_MS = 200;
|
|
19
17
|
|
|
20
18
|
type TelemetryEvent =
|
|
21
19
|
| AgentStartEvent
|
|
@@ -30,24 +28,16 @@ type AgentMessage = MessageStartEvent["message"];
|
|
|
30
28
|
type AssistantMessage = Extract<AgentMessage, { role: "assistant" }>;
|
|
31
29
|
|
|
32
30
|
interface MessageTiming {
|
|
33
|
-
startMs: number;
|
|
34
31
|
lastUpdateMs: number;
|
|
35
32
|
firstOutputMs: number | null;
|
|
36
|
-
streamUpdateCount: number;
|
|
37
33
|
inStall: boolean;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
interface MessageMeasurement {
|
|
41
|
-
outputTokens: number;
|
|
42
|
-
durationMs: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
36
|
interface TurnTiming {
|
|
46
37
|
startMs: number;
|
|
47
38
|
firstTokenMs: number | null;
|
|
48
39
|
currentMessage: MessageTiming | null;
|
|
49
40
|
messages: AssistantMessage[];
|
|
50
|
-
measurements: MessageMeasurement[];
|
|
51
41
|
generationMs: number;
|
|
52
42
|
stallMs: number;
|
|
53
43
|
stallCount: number;
|
|
@@ -68,11 +58,6 @@ export interface TurnTelemetry {
|
|
|
68
58
|
measurementMs: number | null;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
interface MeasuredTurn {
|
|
72
|
-
telemetry: TurnTelemetry;
|
|
73
|
-
measuredOutputTokens: number;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
61
|
function isAssistantMessage(message: AgentMessage): message is AssistantMessage {
|
|
77
62
|
return message.role === "assistant";
|
|
78
63
|
}
|
|
@@ -86,7 +71,7 @@ export class TurnTelemetryTracker {
|
|
|
86
71
|
private readonly now: () => number;
|
|
87
72
|
private turn: TurnTiming | undefined;
|
|
88
73
|
private agentStartMs: number | null = null;
|
|
89
|
-
private agentTurns:
|
|
74
|
+
private agentTurns: TurnTelemetry[] = [];
|
|
90
75
|
|
|
91
76
|
constructor(now: () => number = () => performance.now()) {
|
|
92
77
|
this.now = now;
|
|
@@ -127,7 +112,6 @@ export class TurnTelemetryTracker {
|
|
|
127
112
|
firstTokenMs: null,
|
|
128
113
|
currentMessage: null,
|
|
129
114
|
messages: [],
|
|
130
|
-
measurements: [],
|
|
131
115
|
generationMs: 0,
|
|
132
116
|
stallMs: 0,
|
|
133
117
|
stallCount: 0,
|
|
@@ -138,10 +122,8 @@ export class TurnTelemetryTracker {
|
|
|
138
122
|
if (!this.turn || !isAssistantMessage(message)) return;
|
|
139
123
|
const now = this.now();
|
|
140
124
|
this.turn.currentMessage = {
|
|
141
|
-
startMs: now,
|
|
142
125
|
lastUpdateMs: now,
|
|
143
126
|
firstOutputMs: null,
|
|
144
|
-
streamUpdateCount: 0,
|
|
145
127
|
inStall: false,
|
|
146
128
|
};
|
|
147
129
|
}
|
|
@@ -163,13 +145,10 @@ export class TurnTelemetryTracker {
|
|
|
163
145
|
if (current.firstOutputMs === null) {
|
|
164
146
|
current.firstOutputMs = now;
|
|
165
147
|
turn.firstTokenMs ??= now;
|
|
166
|
-
current.streamUpdateCount = 1;
|
|
167
148
|
current.lastUpdateMs = now;
|
|
168
149
|
return;
|
|
169
150
|
}
|
|
170
151
|
|
|
171
|
-
current.streamUpdateCount++;
|
|
172
|
-
|
|
173
152
|
const gap = now - current.lastUpdateMs;
|
|
174
153
|
if (gap >= STALL_THRESHOLD_MS) {
|
|
175
154
|
if (!current.inStall) turn.stallCount++;
|
|
@@ -188,30 +167,22 @@ export class TurnTelemetryTracker {
|
|
|
188
167
|
const current = turn.currentMessage;
|
|
189
168
|
if (current) {
|
|
190
169
|
const endMs = this.now();
|
|
191
|
-
turn.generationMs
|
|
170
|
+
turn.generationMs = endMs - turn.startMs;
|
|
192
171
|
if (current.firstOutputMs === null && message.usage.output > 0) {
|
|
193
172
|
turn.firstTokenMs ??= endMs;
|
|
194
173
|
}
|
|
195
|
-
const measurementMs = current.firstOutputMs === null ? 0 : endMs - current.firstOutputMs;
|
|
196
|
-
if (
|
|
197
|
-
message.usage.output > 0 &&
|
|
198
|
-
current.streamUpdateCount >= MIN_STREAM_UPDATES &&
|
|
199
|
-
measurementMs >= MIN_MEASUREMENT_MS
|
|
200
|
-
) {
|
|
201
|
-
turn.measurements.push({ outputTokens: message.usage.output, durationMs: measurementMs });
|
|
202
|
-
}
|
|
203
174
|
turn.currentMessage = null;
|
|
204
175
|
}
|
|
205
176
|
turn.messages.push(message);
|
|
206
177
|
}
|
|
207
178
|
|
|
208
179
|
private endTurnAndCollect(): TurnTelemetry | undefined {
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
return
|
|
180
|
+
const telemetry = this.endTurn();
|
|
181
|
+
if (telemetry && this.agentStartMs !== null) this.agentTurns.push(telemetry);
|
|
182
|
+
return telemetry;
|
|
212
183
|
}
|
|
213
184
|
|
|
214
|
-
private endTurn():
|
|
185
|
+
private endTurn(): TurnTelemetry | undefined {
|
|
215
186
|
const turn = this.turn;
|
|
216
187
|
this.turn = undefined;
|
|
217
188
|
if (!turn || turn.firstTokenMs === null || turn.messages.length === 0) return;
|
|
@@ -231,32 +202,27 @@ export class TurnTelemetryTracker {
|
|
|
231
202
|
throw new Error("Invalid assistant usage in turn telemetry");
|
|
232
203
|
}
|
|
233
204
|
|
|
234
|
-
const
|
|
235
|
-
const measuredMs = turn.measurements.reduce((sum, measurement) => sum + measurement.durationMs, 0);
|
|
236
|
-
const measurementMs = measuredMs > 0 ? measuredMs : null;
|
|
205
|
+
const measurementMs = outputTokens > 0 && turn.generationMs > 0 ? turn.generationMs : null;
|
|
237
206
|
const tps = measurementMs === null
|
|
238
207
|
? null
|
|
239
|
-
: round(
|
|
208
|
+
: round(outputTokens / (measurementMs / 1000), 1);
|
|
240
209
|
const validCost = Number.isFinite(costUsd) && costUsd > 0;
|
|
241
210
|
const validTokens = Number.isFinite(totalTokens) && totalTokens > 0;
|
|
242
211
|
return {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
measurementMs,
|
|
258
|
-
},
|
|
259
|
-
measuredOutputTokens,
|
|
212
|
+
tps,
|
|
213
|
+
ttftMs: turn.firstTokenMs - turn.startMs,
|
|
214
|
+
totalMs: endMs - turn.startMs,
|
|
215
|
+
inputTokens,
|
|
216
|
+
outputTokens,
|
|
217
|
+
stallMs: turn.stallMs,
|
|
218
|
+
stallCount: turn.stallCount,
|
|
219
|
+
rateUsdPerMTokens: validCost && validTokens
|
|
220
|
+
? round(costUsd / (totalTokens / 1_000_000), 2)
|
|
221
|
+
: null,
|
|
222
|
+
generationMs: turn.generationMs,
|
|
223
|
+
totalTokens,
|
|
224
|
+
costUsd: validCost ? costUsd : 0,
|
|
225
|
+
measurementMs,
|
|
260
226
|
};
|
|
261
227
|
}
|
|
262
228
|
|
|
@@ -267,29 +233,28 @@ export class TurnTelemetryTracker {
|
|
|
267
233
|
this.agentTurns = [];
|
|
268
234
|
if (startMs === null || turns.length === 0) return;
|
|
269
235
|
|
|
270
|
-
const outputTokens = turns.reduce((sum, turn) => sum + turn.
|
|
271
|
-
const inputTokens = turns.reduce((sum, turn) => sum + turn.
|
|
272
|
-
const totalTokens = turns.reduce((sum, turn) => sum + turn.
|
|
273
|
-
const costUsd = turns.reduce((sum, turn) => sum + turn.
|
|
274
|
-
const stallMs = turns.reduce((sum, turn) => sum + turn.
|
|
275
|
-
const stallCount = turns.reduce((sum, turn) => sum + turn.
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
const measurementMs = measuredMs > 0 ? measuredMs : null;
|
|
236
|
+
const outputTokens = turns.reduce((sum, turn) => sum + turn.outputTokens, 0);
|
|
237
|
+
const inputTokens = turns.reduce((sum, turn) => sum + turn.inputTokens, 0);
|
|
238
|
+
const totalTokens = turns.reduce((sum, turn) => sum + turn.totalTokens, 0);
|
|
239
|
+
const costUsd = turns.reduce((sum, turn) => sum + turn.costUsd, 0);
|
|
240
|
+
const stallMs = turns.reduce((sum, turn) => sum + turn.stallMs, 0);
|
|
241
|
+
const stallCount = turns.reduce((sum, turn) => sum + turn.stallCount, 0);
|
|
242
|
+
const generationMs = turns.reduce((sum, turn) => sum + turn.generationMs, 0);
|
|
243
|
+
const measurementMs = outputTokens > 0 && generationMs > 0 ? generationMs : null;
|
|
279
244
|
const tps = measurementMs === null
|
|
280
245
|
? null
|
|
281
|
-
: round(
|
|
246
|
+
: round(outputTokens / (measurementMs / 1000), 1);
|
|
282
247
|
const validRate = costUsd > 0 && totalTokens > 0;
|
|
283
248
|
return {
|
|
284
249
|
tps,
|
|
285
|
-
ttftMs: turns[0]!.
|
|
250
|
+
ttftMs: turns[0]!.ttftMs,
|
|
286
251
|
totalMs: this.now() - startMs,
|
|
287
252
|
inputTokens,
|
|
288
253
|
outputTokens,
|
|
289
254
|
stallMs,
|
|
290
255
|
stallCount,
|
|
291
256
|
rateUsdPerMTokens: validRate ? round(costUsd / (totalTokens / 1_000_000), 2) : null,
|
|
292
|
-
generationMs
|
|
257
|
+
generationMs,
|
|
293
258
|
totalTokens,
|
|
294
259
|
costUsd,
|
|
295
260
|
measurementMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-tui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "A polished TUI for Pi coding agent: animated logo header, Starship-style footer, rounded editor with model metadata, and prompt-box user messages. Combines the best of pi-haiku, pi-claude-code-tui, and pi-zentui.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|