@badliveware/pi-footer-framework 0.2.2 → 0.3.0
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/index.ts +33 -49
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Added cache read/write token and cost fields to the footer `stats` source so custom renderers can show cache activity alongside input, output, totals, and cost.
|
|
6
|
+
- Improved footer render performance by caching session stats and text-width metadata for repeated renders of unchanged content.
|
|
7
|
+
- Reduced startup and render work by deferring slower extension probes and rendering styled footer text in contiguous cell runs instead of per-cell wrappers.
|
|
8
|
+
|
|
3
9
|
## 0.2.2
|
|
4
10
|
|
|
5
11
|
- Fixed custom footer rendering so layouts with all visible items on line 1 render as a single footer line instead of reserving an empty second line.
|
package/index.ts
CHANGED
|
@@ -2,9 +2,12 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import { Type } from "@
|
|
6
|
-
import { defineTool, type ExtensionAPI, type ExtensionContext } from "@
|
|
7
|
-
import { truncateToWidth, visibleWidth } from "@
|
|
5
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
6
|
+
import { defineTool, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
|
+
import { renderFooterCellRuns } from "./src/cell-runs.ts";
|
|
9
|
+
import { createFooterStatsCache, type FooterStats } from "./src/stats-cache.ts";
|
|
10
|
+
import { createFooterTextMetricsCache } from "./src/text-metrics-cache.ts";
|
|
8
11
|
|
|
9
12
|
type ChecksState = "pass" | "fail" | "running" | "unknown";
|
|
10
13
|
export type FooterAnchorMode = "gap" | "left" | "center" | "right" | "spread";
|
|
@@ -58,6 +61,8 @@ export interface FooterCell {
|
|
|
58
61
|
itemId?: string;
|
|
59
62
|
continuation?: boolean;
|
|
60
63
|
filler?: boolean;
|
|
64
|
+
prefix?: string;
|
|
65
|
+
suffix?: string;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
export interface FooterColumnItem {
|
|
@@ -148,7 +153,7 @@ export function footerCellsFromText(text: string, itemId?: string): FooterCell[]
|
|
|
148
153
|
pendingZeroWidthPlain += cluster;
|
|
149
154
|
}
|
|
150
155
|
} else {
|
|
151
|
-
cells.push({ raw: `${pendingZeroWidthRaw}${
|
|
156
|
+
cells.push({ raw: `${pendingZeroWidthRaw}${cluster}`, plainText: `${pendingZeroWidthPlain}${cluster}`, itemId, prefix: activeCellPrefix(state), suffix: activeCellSuffix(state) });
|
|
152
157
|
pendingZeroWidthRaw = "";
|
|
153
158
|
pendingZeroWidthPlain = "";
|
|
154
159
|
for (let i = 1; i < clusterWidth; i += 1) cells.push({ raw: "", plainText: "", itemId, continuation: true });
|
|
@@ -219,15 +224,7 @@ export function writeFooterText(cells: FooterCell[], start: number, text: string
|
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
export
|
|
223
|
-
let end = cells.length;
|
|
224
|
-
while (end > 0) {
|
|
225
|
-
const cell = cells[end - 1];
|
|
226
|
-
if (cell.continuation || !cell.filler) break;
|
|
227
|
-
end -= 1;
|
|
228
|
-
}
|
|
229
|
-
return cells.slice(0, end).map((cell) => (cell.continuation ? "" : cell.raw)).join("");
|
|
230
|
-
}
|
|
227
|
+
export const renderFooterCells = renderFooterCellRuns;
|
|
231
228
|
|
|
232
229
|
export function resolveFooterColumn(column: FooterColumn | undefined, width: number, itemWidth: number): number | undefined {
|
|
233
230
|
if (column === undefined) return undefined;
|
|
@@ -1823,11 +1820,11 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
1823
1820
|
return sorted.sort((a, b) => a.placement.order - b.placement.order || a.id.localeCompare(b.id));
|
|
1824
1821
|
}
|
|
1825
1822
|
|
|
1826
|
-
function renderVisibilityDiagnostics(items: FooterItem[], lines: Array<{ line: FooterLine; plainText: string }
|
|
1823
|
+
function renderVisibilityDiagnostics(items: FooterItem[], lines: Array<{ line: FooterLine; plainText: string }>, plainTextFor: (text: string) => string = plainFooterText): FooterRenderDiagnostic[] {
|
|
1827
1824
|
const lineTextByNumber = new Map(lines.map((line) => [line.line, line.plainText]));
|
|
1828
1825
|
const diagnostics: FooterRenderDiagnostic[] = [];
|
|
1829
1826
|
for (const item of items) {
|
|
1830
|
-
const itemPlainText =
|
|
1827
|
+
const itemPlainText = plainTextFor(item.text).trim();
|
|
1831
1828
|
if (!itemPlainText) continue;
|
|
1832
1829
|
const linePlainText = lineTextByNumber.get(item.placement.line);
|
|
1833
1830
|
if (linePlainText === undefined) {
|
|
@@ -1898,7 +1895,7 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
1898
1895
|
|
|
1899
1896
|
function buildPiSources(
|
|
1900
1897
|
footerData: { getGitBranch(): string | null; getExtensionStatuses(): ReadonlyMap<string, string> },
|
|
1901
|
-
stats:
|
|
1898
|
+
stats: FooterStats,
|
|
1902
1899
|
): Record<string, FooterAdapterSourceValue> {
|
|
1903
1900
|
const sources: Record<string, FooterAdapterSourceValue> = {
|
|
1904
1901
|
cwd: { label: "cwd", value: currentCtx?.cwd ?? "", tone: "muted", data: { path: currentCtx?.cwd ?? "" } },
|
|
@@ -2004,7 +2001,7 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
2004
2001
|
function collectItems(
|
|
2005
2002
|
theme: ExtensionContext["ui"]["theme"],
|
|
2006
2003
|
footerData: { getGitBranch(): string | null; getExtensionStatuses(): ReadonlyMap<string, string> },
|
|
2007
|
-
stats:
|
|
2004
|
+
stats: FooterStats,
|
|
2008
2005
|
diagnostics: FooterTemplateDiagnostic[],
|
|
2009
2006
|
): FooterItem[] {
|
|
2010
2007
|
const items: FooterItem[] = [];
|
|
@@ -2141,6 +2138,8 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
2141
2138
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
2142
2139
|
requestRender = () => tui.requestRender();
|
|
2143
2140
|
const unsubscribe = footerData.onBranchChange(() => tui.requestRender());
|
|
2141
|
+
const footerStats = createFooterStatsCache(formatTokens);
|
|
2142
|
+
const textMetrics = createFooterTextMetricsCache(plainFooterText, visibleWidth);
|
|
2144
2143
|
|
|
2145
2144
|
return {
|
|
2146
2145
|
dispose() {
|
|
@@ -2149,25 +2148,7 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
2149
2148
|
},
|
|
2150
2149
|
invalidate() {},
|
|
2151
2150
|
render(width: number): string[] {
|
|
2152
|
-
|
|
2153
|
-
let output = 0;
|
|
2154
|
-
let cost = 0;
|
|
2155
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
2156
|
-
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
2157
|
-
input += entry.message.usage.input;
|
|
2158
|
-
output += entry.message.usage.output;
|
|
2159
|
-
cost += entry.message.usage.cost.total;
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
const stats = {
|
|
2163
|
-
input,
|
|
2164
|
-
output,
|
|
2165
|
-
cost,
|
|
2166
|
-
inputText: formatTokens(input),
|
|
2167
|
-
outputText: formatTokens(output),
|
|
2168
|
-
costText: cost.toFixed(3),
|
|
2169
|
-
value: `↑${formatTokens(input)} ↓${formatTokens(output)} $${cost.toFixed(3)}`,
|
|
2170
|
-
};
|
|
2151
|
+
const stats = footerStats(ctx.sessionManager.getEntries());
|
|
2171
2152
|
const diagnostics: FooterTemplateDiagnostic[] = [];
|
|
2172
2153
|
const items = collectItems(theme, footerData, stats, diagnostics);
|
|
2173
2154
|
lastTemplateDiagnostics = diagnostics;
|
|
@@ -2175,21 +2156,24 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
2175
2156
|
const lineResults = Array.from({ length: maxLine }, (_, index) => {
|
|
2176
2157
|
const line = index + 1;
|
|
2177
2158
|
const result = renderFooterLine(theme, width, items, line, getLineAnchor(settings, line));
|
|
2178
|
-
return { line, text: result.line, plainText:
|
|
2159
|
+
return { line, text: result.line, plainText: textMetrics(result.line).plainText, layout: result.layout };
|
|
2179
2160
|
});
|
|
2180
2161
|
const line1Result = lineResults[0];
|
|
2181
2162
|
const line2Result = lineResults[1];
|
|
2182
|
-
const renderedItems = items.map((item) =>
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2163
|
+
const renderedItems = items.map((item) => {
|
|
2164
|
+
const metrics = textMetrics(item.text);
|
|
2165
|
+
return {
|
|
2166
|
+
id: item.id,
|
|
2167
|
+
line: item.placement.line,
|
|
2168
|
+
zone: item.placement.zone,
|
|
2169
|
+
order: item.placement.order,
|
|
2170
|
+
column: item.placement.column,
|
|
2171
|
+
width: metrics.width,
|
|
2172
|
+
plainText: metrics.plainText,
|
|
2173
|
+
renderSource: item.renderSource,
|
|
2174
|
+
tokens: item.tokens,
|
|
2175
|
+
};
|
|
2176
|
+
});
|
|
2193
2177
|
|
|
2194
2178
|
lastFooterSnapshot = {
|
|
2195
2179
|
width,
|
|
@@ -2202,7 +2186,7 @@ export default function footerFramework(pi: ExtensionAPI): void {
|
|
|
2202
2186
|
line2Layout: line2Result?.layout ?? renderFooterLine(theme, width, [], 2, getLineAnchor(settings, 2)).layout,
|
|
2203
2187
|
gitBranch: footerData.getGitBranch(),
|
|
2204
2188
|
renderedItems,
|
|
2205
|
-
renderDiagnostics: renderVisibilityDiagnostics(items, lineResults),
|
|
2189
|
+
renderDiagnostics: renderVisibilityDiagnostics(items, lineResults, (text) => textMetrics(text).plainText),
|
|
2206
2190
|
extensionStatuses: Array.from(footerData.getExtensionStatuses().entries()).map(([key, value]) => ({ key, value })),
|
|
2207
2191
|
model: ctx.model?.id ?? "no-model",
|
|
2208
2192
|
contextUsage: ctx.getContextUsage() ?? null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@badliveware/pi-footer-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Configurable footer framework extension for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
46
|
+
"@earendil-works/pi-ai": "*",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
48
|
+
"@earendil-works/pi-tui": "*"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=20"
|