pi-context 1.0.4 → 1.1.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/README.md +3 -3
- package/package.json +4 -3
- package/src/context.ts +153 -0
- package/src/index.ts +358 -440
- package/src/utils.ts +5 -0
- package/dist/index.js +0 -443
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pi Context
|
|
1
|
+
# Pi Context: Agentic Context Management for the Pi
|
|
2
2
|
|
|
3
3
|
A Git-like context management tool that allows AI agents to proactively manage their context.
|
|
4
4
|
|
|
@@ -17,10 +17,10 @@ pi install npm:pi-context
|
|
|
17
17
|
|
|
18
18
|
### For Humans
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Run the command to enable ACM (**A**gentic **C**ontext **M**anagement) for the current session.
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
/
|
|
23
|
+
/acm
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
View detailed context window usage and token distribution with a visual dashboard. (like `claude code /context`)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-context",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Agentic Context Management for the Pi",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"pi": {
|
|
31
31
|
"extensions": [
|
|
32
|
-
"./src/index.ts"
|
|
32
|
+
"./src/index.ts",
|
|
33
|
+
"./src/context.ts"
|
|
33
34
|
],
|
|
34
35
|
"skills": [
|
|
35
36
|
"./skills"
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionAPI,
|
|
3
|
+
type SessionManager,
|
|
4
|
+
DynamicBorder,
|
|
5
|
+
} from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import { Container, Text, Spacer } from "@mariozechner/pi-tui";
|
|
7
|
+
import { formatTokens } from "./utils.js";
|
|
8
|
+
|
|
9
|
+
export default function (pi: ExtensionAPI) {
|
|
10
|
+
pi.registerCommand("context", {
|
|
11
|
+
description: "Show context usage visualization",
|
|
12
|
+
handler: async (args, ctx) => {
|
|
13
|
+
const usage = await ctx.getContextUsage();
|
|
14
|
+
if (!usage) {
|
|
15
|
+
ctx.ui.notify("Context usage info not available.", "warning");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sm = ctx.sessionManager as SessionManager;
|
|
20
|
+
const branch = sm.getBranch();
|
|
21
|
+
const systemPrompt = ctx.getSystemPrompt();
|
|
22
|
+
const tools = pi.getActiveTools();
|
|
23
|
+
const allTools = pi.getAllTools();
|
|
24
|
+
const activeToolDefs = allTools.filter(t => tools.includes(t.name));
|
|
25
|
+
|
|
26
|
+
const estimateTokens = (text: string) => Math.ceil(text.length / 4);
|
|
27
|
+
|
|
28
|
+
let msgTokensRaw = 0;
|
|
29
|
+
let toolUseTokensRaw = 0;
|
|
30
|
+
let toolResultTokensRaw = 0;
|
|
31
|
+
|
|
32
|
+
for (const entry of branch) {
|
|
33
|
+
if (entry.type === "message") {
|
|
34
|
+
const m = entry.message;
|
|
35
|
+
if (m.role === "user") {
|
|
36
|
+
if (typeof m.content === "string") msgTokensRaw += estimateTokens(m.content);
|
|
37
|
+
else if (Array.isArray(m.content)) {
|
|
38
|
+
for (const p of m.content) if (p.type === "text") msgTokensRaw += estimateTokens(p.text);
|
|
39
|
+
}
|
|
40
|
+
} else if (m.role === "assistant") {
|
|
41
|
+
if (typeof m.content === "string") msgTokensRaw += estimateTokens(m.content);
|
|
42
|
+
else if (Array.isArray(m.content)) {
|
|
43
|
+
for (const p of m.content) {
|
|
44
|
+
if (p.type === "text") msgTokensRaw += estimateTokens(p.text);
|
|
45
|
+
if (p.type === "toolCall") toolUseTokensRaw += estimateTokens(JSON.stringify(p));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (m.role === "toolResult") {
|
|
49
|
+
if (Array.isArray(m.content)) {
|
|
50
|
+
for (const p of m.content) if (p.type === "text") toolResultTokensRaw += estimateTokens(p.text);
|
|
51
|
+
}
|
|
52
|
+
} else if (m.role === "bashExecution") {
|
|
53
|
+
toolUseTokensRaw += estimateTokens(m.command || "");
|
|
54
|
+
}
|
|
55
|
+
} else if (entry.type === "branch_summary" || entry.type === "compaction") {
|
|
56
|
+
msgTokensRaw += estimateTokens(entry.summary || "");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const systemTokensRaw = estimateTokens(systemPrompt);
|
|
61
|
+
const toolDefTokensRaw = estimateTokens(JSON.stringify(activeToolDefs));
|
|
62
|
+
const totalActual = usage.tokens;
|
|
63
|
+
const limit = usage.contextWindow;
|
|
64
|
+
|
|
65
|
+
const totalRaw = systemTokensRaw + toolDefTokensRaw + msgTokensRaw + toolUseTokensRaw + toolResultTokensRaw;
|
|
66
|
+
const ratio = totalRaw > 0 ? (totalActual / totalRaw) : 1;
|
|
67
|
+
|
|
68
|
+
const systemTokens = Math.round(systemTokensRaw * ratio);
|
|
69
|
+
const toolDefTokens = Math.round(toolDefTokensRaw * ratio);
|
|
70
|
+
const msgTokens = Math.round(msgTokensRaw * ratio);
|
|
71
|
+
const toolUseTokens = Math.round(toolUseTokensRaw * ratio);
|
|
72
|
+
const toolResultTokens = Math.round(toolResultTokensRaw * ratio);
|
|
73
|
+
|
|
74
|
+
await ctx.ui.custom((tui, theme, kb, done) => {
|
|
75
|
+
const container = new Container();
|
|
76
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
77
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(" Context Usage")), 1, 0));
|
|
78
|
+
container.addChild(new Spacer(1));
|
|
79
|
+
|
|
80
|
+
// Grouped by function and color
|
|
81
|
+
const categories = [
|
|
82
|
+
{ label: "System Prompt", value: systemTokens, color: "muted" },
|
|
83
|
+
{ label: "System Tools", value: toolDefTokens, color: "dim" },
|
|
84
|
+
{ label: "Tool Call", value: toolUseTokens + toolResultTokens, color: "success" },
|
|
85
|
+
{ label: "Messages", value: msgTokens, color: "accent" },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const otherTokens = Math.max(0, totalActual - (systemTokens + toolDefTokens + msgTokens + toolUseTokens + toolResultTokens));
|
|
89
|
+
if (otherTokens > 10) categories.push({ label: "Other", value: otherTokens, color: "dim" });
|
|
90
|
+
|
|
91
|
+
categories.push({ label: "Available", value: Math.max(0, limit - totalActual), color: "borderMuted" });
|
|
92
|
+
|
|
93
|
+
const gridWidth = 10;
|
|
94
|
+
const gridHeight = 5;
|
|
95
|
+
const totalBlocks = gridWidth * gridHeight;
|
|
96
|
+
|
|
97
|
+
const blocks: { color: string, filled: boolean }[] = [];
|
|
98
|
+
categories.forEach((cat) => {
|
|
99
|
+
if (cat.label === "Available") return;
|
|
100
|
+
let count = Math.round((cat.value / limit) * totalBlocks);
|
|
101
|
+
if (count === 0 && cat.value > 0) count = 1;
|
|
102
|
+
for (let i = 0; i < count && blocks.length < totalBlocks; i++) {
|
|
103
|
+
blocks.push({ color: cat.color, filled: true });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
while (blocks.length < totalBlocks) {
|
|
108
|
+
blocks.push({ color: "borderMuted", filled: false });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const gridLines: string[] = [];
|
|
112
|
+
for (let r = 0; r < gridHeight; r++) {
|
|
113
|
+
let rowStr = "";
|
|
114
|
+
for (let c = 0; c < gridWidth; c++) {
|
|
115
|
+
const b = blocks[r * gridWidth + c];
|
|
116
|
+
rowStr += theme.fg(b.color as any, b.filled ? "■ " : "□ ");
|
|
117
|
+
}
|
|
118
|
+
gridLines.push(rowStr.trimEnd());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const totalUsageTitle = `${theme.fg("text", theme.bold("Total Usage".padEnd(16)))} ${theme.fg("text", theme.bold(formatTokens(totalActual).padStart(7)))} ${theme.fg("text", theme.bold(`(${usage.percent.toFixed(1).padStart(5)}%)`))}`;
|
|
122
|
+
|
|
123
|
+
const catDetailLines = categories.map(cat => {
|
|
124
|
+
const labelStr = cat.label.padEnd(14);
|
|
125
|
+
const valStr = formatTokens(cat.value).padStart(7);
|
|
126
|
+
const rowPercent = ((cat.value / limit) * 100).toFixed(1).padStart(5);
|
|
127
|
+
const icon = cat.label === "Available" ? "□" : "■";
|
|
128
|
+
return `${theme.fg(cat.color as any, icon)} ${theme.fg("text", labelStr)} ${theme.fg("accent", valStr)} (${rowPercent}%)`;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const allDetailLines = [totalUsageTitle, "", ...catDetailLines];
|
|
132
|
+
|
|
133
|
+
const leftSideWidth = 20;
|
|
134
|
+
const maxH = Math.max(gridLines.length, allDetailLines.length);
|
|
135
|
+
for (let i = 0; i < maxH; i++) {
|
|
136
|
+
const left = (gridLines[i] || "").padEnd(leftSideWidth);
|
|
137
|
+
const right = allDetailLines[i] || "";
|
|
138
|
+
container.addChild(new Text(` ${left} ${right}`, 1, 0));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
container.addChild(new Spacer(1));
|
|
142
|
+
container.addChild(new Text(theme.fg("dim", " Press any key to close"), 1, 0));
|
|
143
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
render: (w) => container.render(w),
|
|
147
|
+
invalidate: () => container.invalidate(),
|
|
148
|
+
handleInput: (data) => done(undefined),
|
|
149
|
+
};
|
|
150
|
+
}, { overlay: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|