@firstpick/pi-extension-git-footer-status 0.2.0 → 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 +76 -27
- 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
|
@@ -5,10 +5,10 @@ import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import {
|
|
7
7
|
collectInitialPromptCalibration,
|
|
8
|
-
|
|
8
|
+
estimateInitialPromptForPiContext,
|
|
9
|
+
estimateInitialPromptFromPiExport,
|
|
9
10
|
estimateTokensFromCharCount,
|
|
10
11
|
formatTokens,
|
|
11
|
-
type InitialPromptToolInfo,
|
|
12
12
|
} from "@firstpick/pi-utils";
|
|
13
13
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
14
14
|
|
|
@@ -96,6 +96,11 @@ type LiveTokenSample = {
|
|
|
96
96
|
tokens: number;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
type PromptEstimateCache = {
|
|
100
|
+
key: string;
|
|
101
|
+
tokens: number;
|
|
102
|
+
};
|
|
103
|
+
|
|
99
104
|
function formatTokenSpeed(tokensPerSecond: number): string {
|
|
100
105
|
if (tokensPerSecond < 100) {
|
|
101
106
|
if (tokensPerSecond >= 10) return tokensPerSecond.toFixed(1);
|
|
@@ -361,6 +366,57 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
361
366
|
let currentAssistantLiveTokenSpeed: number | null = null;
|
|
362
367
|
let currentAssistantTokenSamples: LiveTokenSample[] = [];
|
|
363
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
|
+
};
|
|
364
420
|
|
|
365
421
|
const recordAssistantSpeed = (message: AssistantMessage, endMs = Date.now()): boolean => {
|
|
366
422
|
const outputTokens = message.usage?.output ?? 0;
|
|
@@ -419,11 +475,19 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
419
475
|
};
|
|
420
476
|
|
|
421
477
|
pi.on("session_start", async (_event, ctx) => {
|
|
478
|
+
promptEstimateCache = null;
|
|
479
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
480
|
+
|
|
422
481
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
423
|
-
const
|
|
482
|
+
const render = () => tui.requestRender();
|
|
483
|
+
requestFooterRender = render;
|
|
484
|
+
const unsub = footerData.onBranchChange(render);
|
|
424
485
|
|
|
425
486
|
return {
|
|
426
|
-
dispose
|
|
487
|
+
dispose() {
|
|
488
|
+
unsub();
|
|
489
|
+
if (requestFooterRender === render) requestFooterRender = null;
|
|
490
|
+
},
|
|
427
491
|
invalidate() {},
|
|
428
492
|
render(width: number): string[] {
|
|
429
493
|
let totalInput = 0;
|
|
@@ -489,28 +553,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
489
553
|
latestTokenSpeed = historicalTokenSpeed;
|
|
490
554
|
}
|
|
491
555
|
|
|
492
|
-
|
|
493
|
-
let allTools: InitialPromptToolInfo[] = [];
|
|
494
|
-
try {
|
|
495
|
-
activeTools = pi.getActiveTools();
|
|
496
|
-
} catch {
|
|
497
|
-
activeTools = [];
|
|
498
|
-
}
|
|
499
|
-
try {
|
|
500
|
-
allTools = pi.getAllTools().map((tool) => ({
|
|
501
|
-
name: tool.name,
|
|
502
|
-
description: tool.description,
|
|
503
|
-
parameters: tool.parameters,
|
|
504
|
-
}));
|
|
505
|
-
} catch {
|
|
506
|
-
allTools = [];
|
|
507
|
-
}
|
|
508
|
-
const promptInjectionTokens = estimateInitialPromptInput({
|
|
509
|
-
systemPrompt: ctx.getSystemPrompt(),
|
|
510
|
-
activeTools,
|
|
511
|
-
allTools,
|
|
512
|
-
calibration: collectInitialPromptCalibration(ctx.sessionManager.getSessionDir()),
|
|
513
|
-
}).total;
|
|
556
|
+
const currentPromptInjectionTokens = getFooterPromptInjectionTokens(ctx);
|
|
514
557
|
|
|
515
558
|
const contextUsage = ctx.getContextUsage();
|
|
516
559
|
const contextWindow = contextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
@@ -548,7 +591,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
548
591
|
const segments: string[] = [];
|
|
549
592
|
if (ioItems.length > 0) segments.push(`${theme.fg("muted", "🪙")} ${ioItems.join(` ${itemSep} `)}`);
|
|
550
593
|
if (cacheItems.length > 0) segments.push(`${theme.fg("muted", "💾")} ${cacheItems.join(` ${itemSep} `)}`);
|
|
551
|
-
segments.push(`PI: ${formatTokens(
|
|
594
|
+
segments.push(`PI: ${formatTokens(currentPromptInjectionTokens)} tok`);
|
|
552
595
|
if (latestTokenSpeed !== null) {
|
|
553
596
|
const livePrefix = liveOutputTokens > 0 ? `${formatTokens(liveOutputTokens)} tok @ ` : "";
|
|
554
597
|
segments.push(`⚡ ${livePrefix}${formatTokenSpeed(latestTokenSpeed)} tok/s`);
|
|
@@ -633,6 +676,10 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
633
676
|
await refresh(ctx);
|
|
634
677
|
});
|
|
635
678
|
|
|
679
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
680
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
681
|
+
});
|
|
682
|
+
|
|
636
683
|
pi.on("message_start", (event) => {
|
|
637
684
|
if (event.message.role === "assistant") {
|
|
638
685
|
currentAssistantStartMs = Date.now();
|
|
@@ -683,6 +730,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
683
730
|
recordAssistantSpeed(event.message as AssistantMessage);
|
|
684
731
|
resetLiveAssistantState();
|
|
685
732
|
}
|
|
733
|
+
void refreshPromptInjectionEstimate(ctx);
|
|
686
734
|
await refresh(ctx);
|
|
687
735
|
});
|
|
688
736
|
|
|
@@ -694,6 +742,7 @@ export default function gitFooterStatus(pi: ExtensionAPI) {
|
|
|
694
742
|
pi.registerCommand("git-footer-refresh", {
|
|
695
743
|
description: "Refresh git footer information",
|
|
696
744
|
handler: async (_args, ctx) => {
|
|
745
|
+
await refreshPromptInjectionEstimate(ctx);
|
|
697
746
|
await refresh(ctx);
|
|
698
747
|
ctx.ui.notify("Git footer refreshed", "info");
|
|
699
748
|
},
|
package/package.json
CHANGED