@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +81 -5
  3. 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
- - prompt-injection estimate (`PI: X tok`, compacted as `k` for thousands)
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 { formatTokens, estimatePromptInjectionTokens, estimateTokensFromCharCount } from "@firstpick/pi-utils";
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 unsub = footerData.onBranchChange(() => tui.requestRender());
482
+ const render = () => tui.requestRender();
483
+ requestFooterRender = render;
484
+ const unsub = footerData.onBranchChange(render);
418
485
 
419
486
  return {
420
- dispose: unsub,
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 promptInjectionTokens = estimatePromptInjectionTokens(ctx.getSystemPrompt());
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(promptInjectionTokens)} tok`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-extension-git-footer-status",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Enhanced Pi footer with git status, token usage, context usage, and model telemetry.",
5
5
  "license": "MIT",
6
6
  "keywords": [