@firstpick/pi-extension-git-footer-status 0.1.9 → 0.2.1
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/index.ts +81 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Enhanced Pi footer with git health and model/token telemetry.
|
|
|
8
8
|
|
|
9
9
|
- Shows compact runtime metrics in the footer:
|
|
10
10
|
- input/output/cache tokens
|
|
11
|
-
-
|
|
11
|
+
- export-backed initial prompt estimate (`PI: X tok`, same estimator as `/stats-pi`, compacted as `k` for thousands; falls back to live context data if Pi HTML export is unavailable)
|
|
12
12
|
- live output token counter + token output speed (`tok/s`) measured from assistant streaming lifecycle events, with a session-history fallback
|
|
13
13
|
- cost + context-window usage
|
|
14
14
|
- current model and reasoning level
|
package/index.ts
CHANGED
|
@@ -3,7 +3,13 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { isAbsolute, resolve, sep } from "node:path";
|
|
4
4
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
collectInitialPromptCalibration,
|
|
8
|
+
estimateInitialPromptForPiContext,
|
|
9
|
+
estimateInitialPromptFromPiExport,
|
|
10
|
+
estimateTokensFromCharCount,
|
|
11
|
+
formatTokens,
|
|
12
|
+
} from "@firstpick/pi-utils";
|
|
7
13
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
14
|
|
|
9
15
|
type GitSnapshot = {
|
|
@@ -90,6 +96,11 @@ type LiveTokenSample = {
|
|
|
90
96
|
tokens: number;
|
|
91
97
|
};
|
|
92
98
|
|
|
99
|
+
type PromptEstimateCache = {
|
|
100
|
+
key: string;
|
|
101
|
+
tokens: number;
|
|
102
|
+
};
|
|
103
|
+
|
|
93
104
|
function formatTokenSpeed(tokensPerSecond: number): string {
|
|
94
105
|
if (tokensPerSecond < 100) {
|
|
95
106
|
if (tokensPerSecond >= 10) return tokensPerSecond.toFixed(1);
|
|
@@ -355,6 +366,57 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
355
366
|
let currentAssistantLiveTokenSpeed: number | null = null;
|
|
356
367
|
let currentAssistantTokenSamples: LiveTokenSample[] = [];
|
|
357
368
|
let latestMeasuredTokenSpeed: number | null = null;
|
|
369
|
+
let promptEstimateCache: PromptEstimateCache | null = null;
|
|
370
|
+
let promptEstimateRequestId = 0;
|
|
371
|
+
let requestFooterRender: (() => void) | null = null;
|
|
372
|
+
|
|
373
|
+
const getPromptCalibration = (ctx: ExtensionContext) => collectInitialPromptCalibration(ctx.sessionManager.getSessionDir());
|
|
374
|
+
|
|
375
|
+
const getFallbackPromptEstimate = (ctx: ExtensionContext) => {
|
|
376
|
+
const calibration = getPromptCalibration(ctx);
|
|
377
|
+
const estimate = estimateInitialPromptForPiContext(pi, ctx.getSystemPrompt(), calibration);
|
|
378
|
+
const key = [
|
|
379
|
+
estimate.uncalibratedTotal,
|
|
380
|
+
estimate.promptText,
|
|
381
|
+
estimate.toolSchemas,
|
|
382
|
+
estimate.framing,
|
|
383
|
+
estimate.toolCount,
|
|
384
|
+
estimate.calibrationMultiplier,
|
|
385
|
+
estimate.calibrationSamples,
|
|
386
|
+
estimate.low,
|
|
387
|
+
estimate.high,
|
|
388
|
+
].join(":");
|
|
389
|
+
return { calibration, estimate, key };
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const refreshPromptInjectionEstimate = async (ctx: ExtensionContext) => {
|
|
393
|
+
const requestId = ++promptEstimateRequestId;
|
|
394
|
+
const fallback = getFallbackPromptEstimate(ctx);
|
|
395
|
+
promptEstimateCache = { key: fallback.key, tokens: fallback.estimate.total };
|
|
396
|
+
requestFooterRender?.();
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const result = await estimateInitialPromptFromPiExport(pi, ctx, fallback.calibration);
|
|
400
|
+
const latestFallback = getFallbackPromptEstimate(ctx);
|
|
401
|
+
if (requestId !== promptEstimateRequestId || latestFallback.key !== fallback.key) return;
|
|
402
|
+
promptEstimateCache = { key: fallback.key, tokens: result.estimate.total };
|
|
403
|
+
} catch {
|
|
404
|
+
if (requestId !== promptEstimateRequestId) return;
|
|
405
|
+
const latestFallback = getFallbackPromptEstimate(ctx);
|
|
406
|
+
promptEstimateCache = { key: latestFallback.key, tokens: latestFallback.estimate.total };
|
|
407
|
+
} finally {
|
|
408
|
+
if (requestId === promptEstimateRequestId) requestFooterRender?.();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const getFooterPromptInjectionTokens = (ctx: ExtensionContext): number => {
|
|
413
|
+
const fallback = getFallbackPromptEstimate(ctx);
|
|
414
|
+
if (!promptEstimateCache || promptEstimateCache.key !== fallback.key) {
|
|
415
|
+
promptEstimateCache = { key: fallback.key, tokens: fallback.estimate.total };
|
|
416
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
417
|
+
}
|
|
418
|
+
return promptEstimateCache.tokens;
|
|
419
|
+
};
|
|
358
420
|
|
|
359
421
|
const recordAssistantSpeed = (message: AssistantMessage, endMs = Date.now()): boolean => {
|
|
360
422
|
const outputTokens = message.usage?.output ?? 0;
|
|
@@ -413,11 +475,19 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
413
475
|
};
|
|
414
476
|
|
|
415
477
|
pi.on("session_start", async (_event, ctx) => {
|
|
478
|
+
promptEstimateCache = null;
|
|
479
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
480
|
+
|
|
416
481
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
417
|
-
const
|
|
482
|
+
const render = () => tui.requestRender();
|
|
483
|
+
requestFooterRender = render;
|
|
484
|
+
const unsub = footerData.onBranchChange(render);
|
|
418
485
|
|
|
419
486
|
return {
|
|
420
|
-
dispose
|
|
487
|
+
dispose() {
|
|
488
|
+
unsub();
|
|
489
|
+
if (requestFooterRender === render) requestFooterRender = null;
|
|
490
|
+
},
|
|
421
491
|
invalidate() {},
|
|
422
492
|
render(width: number): string[] {
|
|
423
493
|
let totalInput = 0;
|
|
@@ -483,7 +553,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
483
553
|
latestTokenSpeed = historicalTokenSpeed;
|
|
484
554
|
}
|
|
485
555
|
|
|
486
|
-
const
|
|
556
|
+
const currentPromptInjectionTokens = getFooterPromptInjectionTokens(ctx);
|
|
487
557
|
|
|
488
558
|
const contextUsage = ctx.getContextUsage();
|
|
489
559
|
const contextWindow = contextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
@@ -521,7 +591,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
521
591
|
const segments: string[] = [];
|
|
522
592
|
if (ioItems.length > 0) segments.push(`${theme.fg("muted", "🪙")} ${ioItems.join(` ${itemSep} `)}`);
|
|
523
593
|
if (cacheItems.length > 0) segments.push(`${theme.fg("muted", "💾")} ${cacheItems.join(` ${itemSep} `)}`);
|
|
524
|
-
segments.push(`PI: ${formatTokens(
|
|
594
|
+
segments.push(`PI: ${formatTokens(currentPromptInjectionTokens)} tok`);
|
|
525
595
|
if (latestTokenSpeed !== null) {
|
|
526
596
|
const livePrefix = liveOutputTokens > 0 ? `${formatTokens(liveOutputTokens)} tok @ ` : "";
|
|
527
597
|
segments.push(`⚡ ${livePrefix}${formatTokenSpeed(latestTokenSpeed)} tok/s`);
|
|
@@ -606,6 +676,10 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
606
676
|
await refresh(ctx);
|
|
607
677
|
});
|
|
608
678
|
|
|
679
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
680
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
681
|
+
});
|
|
682
|
+
|
|
609
683
|
pi.on("message_start", (event) => {
|
|
610
684
|
if (event.message.role === "assistant") {
|
|
611
685
|
currentAssistantStartMs = Date.now();
|
|
@@ -656,6 +730,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
656
730
|
recordAssistantSpeed(event.message as AssistantMessage);
|
|
657
731
|
resetLiveAssistantState();
|
|
658
732
|
}
|
|
733
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
659
734
|
await refresh(ctx);
|
|
660
735
|
});
|
|
661
736
|
|
|
@@ -667,6 +742,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
667
742
|
pi.registerCommand("git-footer-refresh", {
|
|
668
743
|
description: "Refresh git footer information",
|
|
669
744
|
handler: async (_args, ctx) => {
|
|
745
|
+
await refreshPromptInjectionEstimate(ctx);
|
|
670
746
|
await refresh(ctx);
|
|
671
747
|
ctx.ui.notify("Git footer refreshed", "info");
|
|
672
748
|
},
|
package/package.json
CHANGED