pi-editor-footer 0.6.1 → 0.6.2
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/CHANGELOG.md +6 -0
- package/dist/index.js +17 -9
- package/dist/live-border.js +62 -15
- package/dist/telemetry.js +10 -7
- package/package.json +2 -2
- package/src/index.ts +30 -21
- package/src/live-border.ts +68 -15
- package/src/telemetry.ts +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.2] - 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Live `↑` no longer exceeds context window or session total — `telemetry:peekAgentLive` and `endAgent` now use peak window (`max`) for `inputTokens` not sum (summing `50k+60k=110k` double-counted overlapping history `> 60k` window), `live-border` top `↑` now shows current window `peekLive` + per-agent `output`/`cost` capped to `contextUsage.tokens`, idle also via telemetry `max` capped, timeline `↑` prefers telemetry `max` capped
|
|
12
|
+
|
|
7
13
|
## [0.6.1] - 2026-08-25
|
|
8
14
|
|
|
9
15
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -631,7 +631,7 @@ export default function (pi) {
|
|
|
631
631
|
pi.on("turn_start", (e, ctx) => {
|
|
632
632
|
// Input is known at turn_start via context usage — seed live input so peekLive shows it during streaming (output already streams via liveDeltaChars)
|
|
633
633
|
const usageTokens = ctx // SAFETY: pi context seam — getContextUsage is ExtensionContext API
|
|
634
|
-
|
|
634
|
+
?.getContextUsage?.()?.tokens;
|
|
635
635
|
if (typeof usageTokens === "number" &&
|
|
636
636
|
Number.isFinite(usageTokens) &&
|
|
637
637
|
usageTokens > 0) {
|
|
@@ -715,21 +715,29 @@ export default function (pi) {
|
|
|
715
715
|
const cacheRate = totals.latestCacheHitRate ?? 0;
|
|
716
716
|
const cacheStr = `${glyphs.cacheHit} ${cacheRate.toFixed(1)}%`;
|
|
717
717
|
// Respect timeline.* toggles for specified metrics (wallTime/tokens/cost), but datetime/cache/turn/tools are always shown per user spec
|
|
718
|
-
// Timeline tokens per-agent:
|
|
719
|
-
//
|
|
718
|
+
// Timeline tokens per-agent: input is peak window (max), not sum — summing full prompts
|
|
719
|
+
// double-counts overlapping history (50k+60k=110k > window 60k). Prefer telemetry max
|
|
720
|
+
// (tel.inputTokens after fix) when available, else delta capped to context/total.
|
|
721
|
+
// Output/cost remain per-agent sum for billing.
|
|
720
722
|
let telInput;
|
|
721
723
|
let telOutput;
|
|
722
724
|
let telCost;
|
|
723
|
-
if (
|
|
724
|
-
telInput = Math.max(0, totals.input - agentBaselineTotals.input);
|
|
725
|
-
telOutput = Math.max(0, totals.output - agentBaselineTotals.output);
|
|
726
|
-
telCost = Math.max(0, totals.cost - agentBaselineTotals.cost);
|
|
727
|
-
}
|
|
728
|
-
else if (tel) {
|
|
725
|
+
if (tel) {
|
|
729
726
|
telInput = tel.inputTokens;
|
|
730
727
|
telOutput = tel.outputTokens;
|
|
731
728
|
telCost = tel.costUsd;
|
|
732
729
|
}
|
|
730
|
+
else if (agentBaselineTotals) {
|
|
731
|
+
telInput = Math.max(0, totals.input - agentBaselineTotals.input);
|
|
732
|
+
telOutput = Math.max(0, totals.output - agentBaselineTotals.output);
|
|
733
|
+
telCost = Math.max(0, totals.cost - agentBaselineTotals.cost);
|
|
734
|
+
// Cap input to not exceed context window (peak) or session total
|
|
735
|
+
const ctxTokens = lastSessionCtx?.getContextUsage?.()?.tokens;
|
|
736
|
+
if (typeof ctxTokens === "number" && Number.isFinite(ctxTokens))
|
|
737
|
+
telInput = Math.min(telInput, ctxTokens);
|
|
738
|
+
if (totals.input > 0)
|
|
739
|
+
telInput = Math.min(telInput, totals.input);
|
|
740
|
+
}
|
|
733
741
|
else {
|
|
734
742
|
telInput = totals.input;
|
|
735
743
|
telOutput = totals.output;
|
package/dist/live-border.js
CHANGED
|
@@ -197,38 +197,85 @@ export class LiveBorder {
|
|
|
197
197
|
let tokensText = "";
|
|
198
198
|
if (cfg.telemetry.enabled && cfg.telemetry.tokens) {
|
|
199
199
|
const isRunning = this.deps.runActivityTracker.isRunning();
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
200
|
+
// Live input: window occupancy (max), not sum. Summing full prompts double-counts
|
|
201
|
+
// overlapping history (50k+60k=110k > window 60k) and exceeds context/total.
|
|
202
|
+
// Idle shows per-agent max via telemetry (fallback to delta capped), running shows
|
|
203
|
+
// current turn window (peekLive) + per-agent output/cost, both capped to context/total.
|
|
203
204
|
if (!isRunning && this.agentBaseline) {
|
|
204
205
|
const cur = snapshot.totals;
|
|
205
206
|
const base = this.agentBaseline;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
//
|
|
207
|
+
// Idle per-agent display — input is peak window (max), not sum, to avoid
|
|
208
|
+
// double-count exceed (sum of prompts double-counts overlapping history).
|
|
209
|
+
// Prefer telemetry max when available, else delta capped to context/totals.
|
|
210
|
+
const trackerIdle = this.deps.telemetryTracker;
|
|
211
|
+
const telIdle = trackerIdle.peekAgentLive() ?? trackerIdle.getLastTelemetry();
|
|
212
|
+
let displayInput;
|
|
213
|
+
let displayOutput;
|
|
214
|
+
let displayCost;
|
|
215
|
+
if (telIdle) {
|
|
216
|
+
displayInput = telIdle.inputTokens;
|
|
217
|
+
displayOutput = telIdle.outputTokens;
|
|
218
|
+
displayCost = telIdle.costUsd;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
displayInput = Math.max(0, cur.input - base.input);
|
|
222
|
+
displayOutput = Math.max(0, cur.output - base.output);
|
|
223
|
+
displayCost = Math.max(0, cur.cost - base.cost);
|
|
224
|
+
}
|
|
225
|
+
// Guarantee invariant: live input never exceeds context window or session total
|
|
226
|
+
if (snapshot.contextUsage?.tokens)
|
|
227
|
+
displayInput = Math.min(displayInput, snapshot.contextUsage.tokens);
|
|
228
|
+
if (cur.input > 0)
|
|
229
|
+
displayInput = Math.min(displayInput, cur.input);
|
|
209
230
|
const deltaTel = {
|
|
210
231
|
tps: null,
|
|
211
232
|
ttftMs: 0,
|
|
212
233
|
totalMs: 0,
|
|
213
|
-
inputTokens:
|
|
214
|
-
outputTokens:
|
|
234
|
+
inputTokens: displayInput,
|
|
235
|
+
outputTokens: displayOutput,
|
|
215
236
|
stallMs: 0,
|
|
216
237
|
stallCount: 0,
|
|
217
238
|
rateUsdPerMTokens: null,
|
|
218
239
|
generationMs: 0,
|
|
219
|
-
totalTokens:
|
|
220
|
-
costUsd:
|
|
240
|
+
totalTokens: displayInput + displayOutput,
|
|
241
|
+
costUsd: displayCost,
|
|
221
242
|
measurementMs: null,
|
|
222
243
|
};
|
|
223
244
|
tokensText = formatTelemetryTokens(deltaTel, theme, cfg.telemetry, glyphs);
|
|
224
245
|
}
|
|
225
246
|
else {
|
|
226
|
-
// Live
|
|
227
|
-
//
|
|
247
|
+
// Live per-agent — input is current window (peekLive), not per-agent sum,
|
|
248
|
+
// to avoid sum(50k+60k)=110k > window 60k. Output/cost still per-agent sum.
|
|
228
249
|
const tracker = this.deps.telemetryTracker;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
const agentLive = tracker.peekAgentLive() ?? tracker.getLastTelemetry();
|
|
251
|
+
const liveTurn = tracker.peekLive();
|
|
252
|
+
let displayLive = agentLive;
|
|
253
|
+
if (agentLive &&
|
|
254
|
+
liveTurn &&
|
|
255
|
+
this.deps.runActivityTracker.isRunning()) {
|
|
256
|
+
// Input = current turn window (liveTurn), output/cost = per-agent sum
|
|
257
|
+
displayLive = {
|
|
258
|
+
...agentLive,
|
|
259
|
+
inputTokens: liveTurn.inputTokens,
|
|
260
|
+
totalTokens: liveTurn.inputTokens + agentLive.outputTokens,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (displayLive) {
|
|
264
|
+
let cappedInput = displayLive.inputTokens;
|
|
265
|
+
if (snapshot.contextUsage?.tokens)
|
|
266
|
+
cappedInput = Math.min(cappedInput, snapshot.contextUsage.tokens);
|
|
267
|
+
// Note: not capping to session totals during running — totals is authoritative
|
|
268
|
+
// (lagging) while liveTurn is predictive (current window). Capping to totals
|
|
269
|
+
// would make live stale (50k) during second turn streaming instead of showing
|
|
270
|
+
// current window 60k. After fix to max, live 60k == context 60k, not exceed context;
|
|
271
|
+
// live may still be > authoritative totals interim (60k > 50k) but will be <= totals
|
|
272
|
+
// after turn completes (60k <= 110k). This is expected predictive vs authoritative.
|
|
273
|
+
displayLive = {
|
|
274
|
+
...displayLive,
|
|
275
|
+
inputTokens: cappedInput,
|
|
276
|
+
totalTokens: cappedInput + displayLive.outputTokens,
|
|
277
|
+
};
|
|
278
|
+
tokensText = formatTelemetryTokens(displayLive, theme, cfg.telemetry, glyphs);
|
|
232
279
|
}
|
|
233
280
|
}
|
|
234
281
|
}
|
package/dist/telemetry.js
CHANGED
|
@@ -83,11 +83,12 @@ export class TurnTelemetryTracker {
|
|
|
83
83
|
let stallCount = 0;
|
|
84
84
|
let generationMs = 0;
|
|
85
85
|
let ttftMs = 0;
|
|
86
|
-
//
|
|
86
|
+
// input per turn is the full prompt (includes history), summing double-counts overlapping
|
|
87
|
+
// history and makes live input exceed context usage (e.g. 50k+60k=110k > window 60k).
|
|
88
|
+
// Display input as peak window occupancy (max), not sum. Output/cost still sum.
|
|
87
89
|
for (const t of this.agentTurns) {
|
|
88
|
-
inputTokens
|
|
90
|
+
inputTokens = Math.max(inputTokens, t.inputTokens);
|
|
89
91
|
outputTokens += t.outputTokens;
|
|
90
|
-
totalTokens += t.totalTokens;
|
|
91
92
|
costUsd += t.costUsd;
|
|
92
93
|
stallMs += t.stallMs;
|
|
93
94
|
stallCount += t.stallCount;
|
|
@@ -96,9 +97,8 @@ export class TurnTelemetryTracker {
|
|
|
96
97
|
if (this.agentTurns.length > 0)
|
|
97
98
|
ttftMs = this.agentTurns[0].ttftMs;
|
|
98
99
|
if (live) {
|
|
99
|
-
inputTokens
|
|
100
|
+
inputTokens = Math.max(inputTokens, live.inputTokens);
|
|
100
101
|
outputTokens += live.outputTokens;
|
|
101
|
-
totalTokens += live.totalTokens;
|
|
102
102
|
costUsd += live.costUsd;
|
|
103
103
|
stallMs += live.stallMs;
|
|
104
104
|
stallCount += live.stallCount;
|
|
@@ -106,6 +106,8 @@ export class TurnTelemetryTracker {
|
|
|
106
106
|
if (ttftMs === 0)
|
|
107
107
|
ttftMs = live.ttftMs;
|
|
108
108
|
}
|
|
109
|
+
// totalTokens is window input (max) + cumulative output, not sum of per-turn totals
|
|
110
|
+
totalTokens = inputTokens + outputTokens;
|
|
109
111
|
const now = this.now();
|
|
110
112
|
const totalMs = Math.max(0, now - this.agentStartMs);
|
|
111
113
|
const measurementMs = outputTokens > 0 && generationMs > 0 ? generationMs : null;
|
|
@@ -397,8 +399,9 @@ export class TurnTelemetryTracker {
|
|
|
397
399
|
if (startMs === null || turns.length === 0)
|
|
398
400
|
return;
|
|
399
401
|
const outputTokens = turns.reduce((sum, t) => sum + t.outputTokens, 0);
|
|
400
|
-
const inputTokens = turns.reduce((sum, t) => sum
|
|
401
|
-
|
|
402
|
+
const inputTokens = turns.reduce((sum, t) => Math.max(sum, t.inputTokens), 0);
|
|
403
|
+
// totalTokens is window input (max) + cumulative output, not sum of per-turn totals
|
|
404
|
+
const totalTokens = inputTokens + outputTokens;
|
|
402
405
|
const costUsd = turns.reduce((sum, t) => sum + t.costUsd, 0);
|
|
403
406
|
const stallMs = turns.reduce((sum, t) => sum + t.stallMs, 0);
|
|
404
407
|
const stallCount = turns.reduce((sum, t) => sum + t.stallCount, 0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-editor-footer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"description": "Pi TUI theme
|
|
4
|
+
"description": "Pi TUI theme \u2014 project-aware footer, model border, and skill detail window (TrackingEditor, live theme)",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --import tsx --test test/*.test.ts",
|
|
7
7
|
"typecheck": "tsc --noEmit",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@earendil-works/pi-coding-agent": "*",
|
|
19
19
|
"@earendil-works/pi-tui": "*"
|
|
20
20
|
},
|
|
21
|
-
"version": "0.6.
|
|
21
|
+
"version": "0.6.2",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"src",
|
package/src/index.ts
CHANGED
|
@@ -802,9 +802,8 @@ export default function (pi: ExtensionAPILike): void {
|
|
|
802
802
|
pi.on("turn_start", (e, ctx) => {
|
|
803
803
|
// Input is known at turn_start via context usage — seed live input so peekLive shows it during streaming (output already streams via liveDeltaChars)
|
|
804
804
|
const usageTokens = (
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
?.getContextUsage?.()?.tokens;
|
|
805
|
+
ctx as unknown as { getContextUsage?: () => { tokens?: number } } // SAFETY: pi context seam — getContextUsage is ExtensionContext API
|
|
806
|
+
)?.getContextUsage?.()?.tokens;
|
|
808
807
|
if (
|
|
809
808
|
typeof usageTokens === "number" &&
|
|
810
809
|
Number.isFinite(usageTokens) &&
|
|
@@ -895,24 +894,34 @@ export default function (pi: ExtensionAPILike): void {
|
|
|
895
894
|
const cacheRate = totals.latestCacheHitRate ?? 0;
|
|
896
895
|
const cacheStr = `${glyphs.cacheHit} ${cacheRate.toFixed(1)}%`;
|
|
897
896
|
// Respect timeline.* toggles for specified metrics (wallTime/tokens/cost), but datetime/cache/turn/tools are always shown per user spec
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
897
|
+
// Timeline tokens per-agent: input is peak window (max), not sum — summing full prompts
|
|
898
|
+
// double-counts overlapping history (50k+60k=110k > window 60k). Prefer telemetry max
|
|
899
|
+
// (tel.inputTokens after fix) when available, else delta capped to context/total.
|
|
900
|
+
// Output/cost remain per-agent sum for billing.
|
|
901
|
+
let telInput: number;
|
|
902
|
+
let telOutput: number;
|
|
903
|
+
let telCost: number;
|
|
904
|
+
if (tel) {
|
|
905
|
+
telInput = tel.inputTokens;
|
|
906
|
+
telOutput = tel.outputTokens;
|
|
907
|
+
telCost = tel.costUsd;
|
|
908
|
+
} else if (agentBaselineTotals) {
|
|
909
|
+
telInput = Math.max(0, totals.input - agentBaselineTotals.input);
|
|
910
|
+
telOutput = Math.max(0, totals.output - agentBaselineTotals.output);
|
|
911
|
+
telCost = Math.max(0, totals.cost - agentBaselineTotals.cost);
|
|
912
|
+
// Cap input to not exceed context window (peak) or session total
|
|
913
|
+
const ctxTokens = (
|
|
914
|
+
lastSessionCtx as unknown as { // SAFETY: pi seam — intentional unsafe cast, validated at runtime
|
|
915
|
+
getContextUsage?: () => { tokens?: number };
|
|
916
|
+
}
|
|
917
|
+
)?.getContextUsage?.()?.tokens;
|
|
918
|
+
if (typeof ctxTokens === "number" && Number.isFinite(ctxTokens)) telInput = Math.min(telInput, ctxTokens);
|
|
919
|
+
if (totals.input > 0) telInput = Math.min(telInput, totals.input);
|
|
920
|
+
} else {
|
|
921
|
+
telInput = totals.input;
|
|
922
|
+
telOutput = totals.output;
|
|
923
|
+
telCost = totals.cost;
|
|
924
|
+
}
|
|
916
925
|
const line1Parts: string[] = [dt];
|
|
917
926
|
if (currentConfig.timeline.wallTime) line1Parts.push(wallDur);
|
|
918
927
|
else line1Parts.push(wallDur); // wall time always per spec (11s)
|
package/src/live-border.ts
CHANGED
|
@@ -245,27 +245,50 @@ export class LiveBorder {
|
|
|
245
245
|
let tokensText = "";
|
|
246
246
|
if (cfg.telemetry.enabled && cfg.telemetry.tokens) {
|
|
247
247
|
const isRunning = this.deps.runActivityTracker.isRunning();
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
248
|
+
// Live input: window occupancy (max), not sum. Summing full prompts double-counts
|
|
249
|
+
// overlapping history (50k+60k=110k > window 60k) and exceeds context/total.
|
|
250
|
+
// Idle shows per-agent max via telemetry (fallback to delta capped), running shows
|
|
251
|
+
// current turn window (peekLive) + per-agent output/cost, both capped to context/total.
|
|
251
252
|
if (!isRunning && this.agentBaseline) {
|
|
252
253
|
const cur = snapshot.totals;
|
|
253
254
|
const base = this.agentBaseline;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
255
|
+
// Idle per-agent display — input is peak window (max), not sum, to avoid
|
|
256
|
+
// double-count exceed (sum of prompts double-counts overlapping history).
|
|
257
|
+
// Prefer telemetry max when available, else delta capped to context/totals.
|
|
258
|
+
const trackerIdle = this.deps.telemetryTracker as unknown as {
|
|
259
|
+
peekAgentLive(): import("./telemetry.js").TurnTelemetry | null;
|
|
260
|
+
getLastTelemetry(): import("./telemetry.js").TurnTelemetry | null;
|
|
261
|
+
};
|
|
262
|
+
const telIdle =
|
|
263
|
+
trackerIdle.peekAgentLive() ?? trackerIdle.getLastTelemetry();
|
|
264
|
+
let displayInput: number;
|
|
265
|
+
let displayOutput: number;
|
|
266
|
+
let displayCost: number;
|
|
267
|
+
if (telIdle) {
|
|
268
|
+
displayInput = telIdle.inputTokens;
|
|
269
|
+
displayOutput = telIdle.outputTokens;
|
|
270
|
+
displayCost = telIdle.costUsd;
|
|
271
|
+
} else {
|
|
272
|
+
displayInput = Math.max(0, cur.input - base.input);
|
|
273
|
+
displayOutput = Math.max(0, cur.output - base.output);
|
|
274
|
+
displayCost = Math.max(0, cur.cost - base.cost);
|
|
275
|
+
}
|
|
276
|
+
// Guarantee invariant: live input never exceeds context window or session total
|
|
277
|
+
if (snapshot.contextUsage?.tokens)
|
|
278
|
+
displayInput = Math.min(displayInput, snapshot.contextUsage.tokens);
|
|
279
|
+
if (cur.input > 0) displayInput = Math.min(displayInput, cur.input);
|
|
257
280
|
const deltaTel: TurnTelemetry = {
|
|
258
281
|
tps: null,
|
|
259
282
|
ttftMs: 0,
|
|
260
283
|
totalMs: 0,
|
|
261
|
-
inputTokens:
|
|
262
|
-
outputTokens:
|
|
284
|
+
inputTokens: displayInput,
|
|
285
|
+
outputTokens: displayOutput,
|
|
263
286
|
stallMs: 0,
|
|
264
287
|
stallCount: 0,
|
|
265
288
|
rateUsdPerMTokens: null,
|
|
266
289
|
generationMs: 0,
|
|
267
|
-
totalTokens:
|
|
268
|
-
costUsd:
|
|
290
|
+
totalTokens: displayInput + displayOutput,
|
|
291
|
+
costUsd: displayCost,
|
|
269
292
|
measurementMs: null,
|
|
270
293
|
};
|
|
271
294
|
tokensText = formatTelemetryTokens(
|
|
@@ -275,16 +298,46 @@ export class LiveBorder {
|
|
|
275
298
|
glyphs as never,
|
|
276
299
|
);
|
|
277
300
|
} else {
|
|
278
|
-
// Live
|
|
279
|
-
//
|
|
301
|
+
// Live per-agent — input is current window (peekLive), not per-agent sum,
|
|
302
|
+
// to avoid sum(50k+60k)=110k > window 60k. Output/cost still per-agent sum.
|
|
280
303
|
const tracker = this.deps.telemetryTracker as unknown as {
|
|
281
304
|
peekAgentLive(): import("./telemetry.js").TurnTelemetry | null;
|
|
305
|
+
peekLive(): import("./telemetry.js").TurnTelemetry | null;
|
|
282
306
|
getLastTelemetry(): import("./telemetry.js").TurnTelemetry | null;
|
|
283
307
|
};
|
|
284
|
-
const
|
|
285
|
-
|
|
308
|
+
const agentLive =
|
|
309
|
+
tracker.peekAgentLive() ?? tracker.getLastTelemetry();
|
|
310
|
+
const liveTurn = tracker.peekLive();
|
|
311
|
+
let displayLive = agentLive;
|
|
312
|
+
if (
|
|
313
|
+
agentLive &&
|
|
314
|
+
liveTurn &&
|
|
315
|
+
this.deps.runActivityTracker.isRunning()
|
|
316
|
+
) {
|
|
317
|
+
// Input = current turn window (liveTurn), output/cost = per-agent sum
|
|
318
|
+
displayLive = {
|
|
319
|
+
...agentLive,
|
|
320
|
+
inputTokens: liveTurn.inputTokens,
|
|
321
|
+
totalTokens: liveTurn.inputTokens + agentLive.outputTokens,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (displayLive) {
|
|
325
|
+
let cappedInput = displayLive.inputTokens;
|
|
326
|
+
if (snapshot.contextUsage?.tokens)
|
|
327
|
+
cappedInput = Math.min(cappedInput, snapshot.contextUsage.tokens);
|
|
328
|
+
// Note: not capping to session totals during running — totals is authoritative
|
|
329
|
+
// (lagging) while liveTurn is predictive (current window). Capping to totals
|
|
330
|
+
// would make live stale (50k) during second turn streaming instead of showing
|
|
331
|
+
// current window 60k. After fix to max, live 60k == context 60k, not exceed context;
|
|
332
|
+
// live may still be > authoritative totals interim (60k > 50k) but will be <= totals
|
|
333
|
+
// after turn completes (60k <= 110k). This is expected predictive vs authoritative.
|
|
334
|
+
displayLive = {
|
|
335
|
+
...displayLive,
|
|
336
|
+
inputTokens: cappedInput,
|
|
337
|
+
totalTokens: cappedInput + displayLive.outputTokens,
|
|
338
|
+
};
|
|
286
339
|
tokensText = formatTelemetryTokens(
|
|
287
|
-
|
|
340
|
+
displayLive,
|
|
288
341
|
theme as never,
|
|
289
342
|
cfg.telemetry,
|
|
290
343
|
glyphs as never,
|
package/src/telemetry.ts
CHANGED
|
@@ -185,11 +185,12 @@ export class TurnTelemetryTracker {
|
|
|
185
185
|
let stallCount = 0;
|
|
186
186
|
let generationMs = 0;
|
|
187
187
|
let ttftMs = 0;
|
|
188
|
-
//
|
|
188
|
+
// input per turn is the full prompt (includes history), summing double-counts overlapping
|
|
189
|
+
// history and makes live input exceed context usage (e.g. 50k+60k=110k > window 60k).
|
|
190
|
+
// Display input as peak window occupancy (max), not sum. Output/cost still sum.
|
|
189
191
|
for (const t of this.agentTurns) {
|
|
190
|
-
inputTokens
|
|
192
|
+
inputTokens = Math.max(inputTokens, t.inputTokens);
|
|
191
193
|
outputTokens += t.outputTokens;
|
|
192
|
-
totalTokens += t.totalTokens;
|
|
193
194
|
costUsd += t.costUsd;
|
|
194
195
|
stallMs += t.stallMs;
|
|
195
196
|
stallCount += t.stallCount;
|
|
@@ -197,15 +198,16 @@ export class TurnTelemetryTracker {
|
|
|
197
198
|
}
|
|
198
199
|
if (this.agentTurns.length > 0) ttftMs = this.agentTurns[0]!.ttftMs;
|
|
199
200
|
if (live) {
|
|
200
|
-
inputTokens
|
|
201
|
+
inputTokens = Math.max(inputTokens, live.inputTokens);
|
|
201
202
|
outputTokens += live.outputTokens;
|
|
202
|
-
totalTokens += live.totalTokens;
|
|
203
203
|
costUsd += live.costUsd;
|
|
204
204
|
stallMs += live.stallMs;
|
|
205
205
|
stallCount += live.stallCount;
|
|
206
206
|
generationMs += live.generationMs;
|
|
207
207
|
if (ttftMs === 0) ttftMs = live.ttftMs;
|
|
208
208
|
}
|
|
209
|
+
// totalTokens is window input (max) + cumulative output, not sum of per-turn totals
|
|
210
|
+
totalTokens = inputTokens + outputTokens;
|
|
209
211
|
const now = this.now();
|
|
210
212
|
const totalMs = Math.max(0, now - this.agentStartMs);
|
|
211
213
|
const measurementMs =
|
|
@@ -524,8 +526,12 @@ export class TurnTelemetryTracker {
|
|
|
524
526
|
if (startMs === null || turns.length === 0) return;
|
|
525
527
|
|
|
526
528
|
const outputTokens = turns.reduce((sum, t) => sum + t.outputTokens, 0);
|
|
527
|
-
const inputTokens = turns.reduce(
|
|
528
|
-
|
|
529
|
+
const inputTokens = turns.reduce(
|
|
530
|
+
(sum, t) => Math.max(sum, t.inputTokens),
|
|
531
|
+
0,
|
|
532
|
+
);
|
|
533
|
+
// totalTokens is window input (max) + cumulative output, not sum of per-turn totals
|
|
534
|
+
const totalTokens = inputTokens + outputTokens;
|
|
529
535
|
const costUsd = turns.reduce((sum, t) => sum + t.costUsd, 0);
|
|
530
536
|
const stallMs = turns.reduce((sum, t) => sum + t.stallMs, 0);
|
|
531
537
|
const stallCount = turns.reduce((sum, t) => sum + t.stallCount, 0);
|