@mrclrchtr/supi-context 2.6.1 → 2.7.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 +51 -57
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +8 -5
- package/src/analysis.ts +146 -118
- package/src/capacity.ts +81 -0
- package/src/context.ts +39 -27
- package/src/entry-renderer.ts +23 -0
- package/src/format-helpers.ts +3 -5
- package/src/format-sections.ts +9 -13
- package/src/format-summary.ts +37 -31
- package/src/format.ts +6 -2
- package/src/report-component.ts +4 -3
- package/src/snapshot-component.ts +75 -0
- package/src/tool/guidance.ts +5 -2
- package/src/tool/output.ts +51 -0
- package/src/tool/render.ts +42 -42
- package/src/renderer.ts +0 -15
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# @mrclrchtr/supi-context
|
|
10
10
|
|
|
11
|
-
Adds
|
|
11
|
+
Adds context-capacity observability to the [pi coding agent](https://github.com/earendil-works/pi).
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -22,56 +22,58 @@ For local development:
|
|
|
22
22
|
pi install ./packages/supi-context
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
## Two concepts, two surfaces
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
- A **Context Pressure Snapshot** is a small point-in-time capacity reading for deciding whether a session has room for another operation.
|
|
28
|
+
- A **Context Usage Report** is a diagnostic account of where the session's context is spent.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
### Human: Context Usage Report
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
- `/supi-context full` — render the same report with the full guideline and tool-definition lists instead of previews
|
|
33
|
-
- `supi_context` — agent-callable tool (disabled by default; see Configuration)
|
|
32
|
+
In interactive TUI mode, pi gets these commands:
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
- `/supi-context` — render a report with guideline and tool previews
|
|
35
|
+
- `/supi-context full` — render the same report with complete guideline and tool lists
|
|
36
36
|
|
|
37
|
-
The
|
|
37
|
+
The command appends a durable `supi-context` custom entry and uses a dedicated entry renderer. The report remains in the transcript but never enters LLM context.
|
|
38
|
+
|
|
39
|
+
### Agent: Context Pressure Snapshot
|
|
40
|
+
|
|
41
|
+
`supi_context` is agent-callable when enabled in configuration. It defaults to a one-line, constant-shape JSON **Context Pressure Snapshot**:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface ContextPressureSnapshot {
|
|
45
|
+
modelName: string;
|
|
46
|
+
contextWindow: number | null;
|
|
47
|
+
usedTokens: number;
|
|
48
|
+
usagePercent: number | null;
|
|
49
|
+
compactionEnabled: boolean;
|
|
50
|
+
reserveTokens: number;
|
|
51
|
+
headroomTokens: number | null;
|
|
52
|
+
pressurePercent: number | null;
|
|
53
|
+
compacted: boolean;
|
|
54
|
+
approximationNote: string | null;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Use `supi_context({ mode: "full" })` only when diagnostic attribution is needed. Full mode returns compact JSON for the Context Usage Report. If it exceeds Pi's normal tool-output limits, it returns a small valid-JSON envelope with the path to a temporary file containing the complete report.
|
|
59
|
+
|
|
60
|
+
The tool TUI never shows raw agent JSON: concise results render as a dense snapshot that expands into its metrics; full results expand into the diagnostic report.
|
|
38
61
|
|
|
39
62
|
## What the report shows
|
|
40
63
|
|
|
41
|
-
The
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
It includes:
|
|
49
|
-
|
|
50
|
-
- model name, context-window size, and total token usage
|
|
51
|
-
- approximation or pending-usage notes when exact usage data is not available yet
|
|
52
|
-
- a visual usage bar for system prompt, user messages, assistant messages, tool calls, tool results, other, autocompact buffer, and free space
|
|
53
|
-
- a category breakdown table for the same usage buckets
|
|
54
|
-
- a system-prompt composition breakdown for:
|
|
55
|
-
- base prompt content
|
|
56
|
-
- instruction files (`AGENTS.md`, `CLAUDE.md`, etc.)
|
|
57
|
-
- other context files loaded into the system prompt
|
|
58
|
-
- active skills
|
|
59
|
-
- guidelines
|
|
60
|
-
- tool snippets
|
|
61
|
-
- append text
|
|
62
|
-
- instruction-file details with token cost, line count, and detected origin (`project` vs `global`)
|
|
63
|
-
- legacy injected subdirectory context files from older `supi-claude-md` sessions, when present
|
|
64
|
-
- active skill names with per-skill token counts
|
|
65
|
-
- guideline bullet previews, plus source attribution for PI defaults, known built-in tools (`read`, `write`, `edit`), and `other`
|
|
66
|
-
- active tool definitions with per-tool definition token counts and snippet-token columns when available
|
|
67
|
-
- a compaction note when older turns were summarized
|
|
68
|
-
- extra provider sections from extensions registered through the shared context-provider registry in `@mrclrchtr/supi-core`
|
|
64
|
+
The Context Usage Report includes:
|
|
65
|
+
|
|
66
|
+
- model name, context-window size, used tokens, usage percentage, pressure percentage, and **Headroom**
|
|
67
|
+
- the effective **Compaction reserve** and factual presence of compaction on the active branch
|
|
68
|
+
- approximation or pending-usage notes when exact usage is not available
|
|
69
|
+
- a visual usage bar and attribution-category breakdown for system prompt, messages, tool calls, tool results, and other context
|
|
70
|
+
- system-prompt composition, instruction/context files, skills, guideline sources, tool definitions, injected files, and registered provider sections
|
|
69
71
|
|
|
70
72
|
## Configuration
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
The human `/supi-context` command needs no configuration.
|
|
73
75
|
|
|
74
|
-
To enable the `supi_context`
|
|
76
|
+
To enable the agent-callable `supi_context` tool, set `agentToolEnabled` to `true` in your supi config:
|
|
75
77
|
|
|
76
78
|
```json
|
|
77
79
|
{
|
|
@@ -87,26 +89,18 @@ The tool is disabled by default and requires a `/reload` or restart after toggli
|
|
|
87
89
|
|
|
88
90
|
## Notes
|
|
89
91
|
|
|
90
|
-
- The
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
- If no model is selected yet, the report can still render, but the context-window bar cannot show capacity.
|
|
92
|
+
- The extension caches the latest `systemPromptOptions` from `before_agent_start`. If those are missing or incomplete, it backfills context files and skills from the current system prompt.
|
|
93
|
+
- Exact usage comes from Pi's current context-usage data when available. Otherwise the extension estimates usage and preserves an approximation note.
|
|
94
|
+
- The Active Context Limit is the auto-compaction threshold while auto-compaction is enabled, otherwise the raw model context window. Headroom and pressure use that limit.
|
|
94
95
|
|
|
95
96
|
## Source
|
|
96
97
|
|
|
97
|
-
- `src/context.ts` —
|
|
98
|
-
- `src/
|
|
99
|
-
- `src/
|
|
100
|
-
- `src/
|
|
101
|
-
- `src/format
|
|
102
|
-
- `src/
|
|
103
|
-
- `src/
|
|
104
|
-
- `src/format-sections.ts` — instruction file, context file, skill, guideline, tool, compaction, and provider sections
|
|
105
|
-
- `src/prompt-inference.ts` — fallback recovery of context files, skills, and guideline sections from the live system prompt
|
|
106
|
-
- `src/renderer.ts` — custom renderer for `supi-context` messages
|
|
107
|
-
- `src/report-component.ts` — shared width-aware report component for message and tool renderers
|
|
108
|
-
- `src/tool/guidance.ts` — tool description, prompt snippet, and guidelines for the agent tool
|
|
109
|
-
- `src/tool/render.ts` — TUI call/result renderer for the agent tool
|
|
110
|
-
- `src/utils.ts` — token and plural-format helpers
|
|
98
|
+
- `src/context.ts` — surface registration and cached prompt-option handling
|
|
99
|
+
- `src/capacity.ts` — shared capacity analysis and snapshot shape
|
|
100
|
+
- `src/analysis.ts` — diagnostic attribution and report data assembly
|
|
101
|
+
- `src/entry-renderer.ts` — TUI-only custom-entry renderer for the human command
|
|
102
|
+
- `src/format*.ts` and `src/report-component.ts` — Context Usage Report rendering
|
|
103
|
+
- `src/snapshot-component.ts` — compact, width-safe snapshot rendering
|
|
104
|
+
- `src/tool/guidance.ts`, `src/tool/output.ts`, and `src/tool/render.ts` — agent-tool guidance, safe full JSON output, and tool rendering
|
|
111
105
|
|
|
112
106
|
Tests live under `__tests__/unit/`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-context",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "SuPi Context extension —
|
|
3
|
+
"version": "2.7.0",
|
|
4
|
+
"description": "SuPi Context extension — agent pressure snapshots and TUI usage reports",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -32,16 +32,20 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"typebox": "*",
|
|
35
|
-
"@mrclrchtr/supi-core": "2.
|
|
35
|
+
"@mrclrchtr/supi-core": "2.7.0"
|
|
36
36
|
},
|
|
37
37
|
"bundledDependencies": [
|
|
38
38
|
"@mrclrchtr/supi-core"
|
|
39
39
|
],
|
|
40
40
|
"peerDependencies": {
|
|
41
|
+
"@earendil-works/pi-ai": "*",
|
|
41
42
|
"@earendil-works/pi-coding-agent": "*",
|
|
42
43
|
"@earendil-works/pi-tui": "*"
|
|
43
44
|
},
|
|
44
45
|
"peerDependenciesMeta": {
|
|
46
|
+
"@earendil-works/pi-ai": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
45
49
|
"@earendil-works/pi-coding-agent": {
|
|
46
50
|
"optional": true
|
|
47
51
|
},
|
|
@@ -52,8 +56,7 @@
|
|
|
52
56
|
"pi": {
|
|
53
57
|
"extensions": [
|
|
54
58
|
"./src/extension.ts"
|
|
55
|
-
]
|
|
56
|
-
"image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-context.png"
|
|
59
|
+
]
|
|
57
60
|
},
|
|
58
61
|
"main": "src/api.ts",
|
|
59
62
|
"exports": {
|
package/src/analysis.ts
CHANGED
|
@@ -5,13 +5,18 @@ import {
|
|
|
5
5
|
buildSessionContext,
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
type ExtensionContext,
|
|
8
|
-
|
|
8
|
+
estimateTokens,
|
|
9
9
|
formatSkillsForPrompt,
|
|
10
10
|
getLatestCompactionEntry,
|
|
11
11
|
SettingsManager,
|
|
12
12
|
} from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { getRegisteredContextProviders } from "@mrclrchtr/supi-core/context";
|
|
14
14
|
|
|
15
|
+
import {
|
|
16
|
+
analyzeContextCapacity,
|
|
17
|
+
type ContextPressureSnapshot,
|
|
18
|
+
createContextPressureSnapshot,
|
|
19
|
+
} from "./capacity.ts";
|
|
15
20
|
import { deriveOptionsFromSystemPrompt, extractGuidelinesSection } from "./prompt-inference.ts";
|
|
16
21
|
|
|
17
22
|
type AgentMessage = Parameters<typeof estimateTokens>[0];
|
|
@@ -69,17 +74,9 @@ export interface ContextProviderSection {
|
|
|
69
74
|
data: Record<string, string | number>;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
export interface ContextAnalysis {
|
|
73
|
-
modelName: string;
|
|
74
|
-
contextWindow: number;
|
|
75
|
-
totalTokens: number | null;
|
|
77
|
+
export interface ContextAnalysis extends ContextPressureSnapshot {
|
|
76
78
|
scaled: boolean;
|
|
77
|
-
|
|
78
|
-
full: boolean;
|
|
79
|
-
categories: CategoryTokens & {
|
|
80
|
-
autocompactBuffer: number;
|
|
81
|
-
freeSpace: number;
|
|
82
|
-
};
|
|
79
|
+
categories: CategoryTokens;
|
|
83
80
|
systemPromptBreakdown: {
|
|
84
81
|
base: number;
|
|
85
82
|
instructionFiles: ContextFileInfo[];
|
|
@@ -98,7 +95,6 @@ export interface ContextAnalysis {
|
|
|
98
95
|
guidelineSources: GuidelineSourceInfo[];
|
|
99
96
|
toolSnippetDetails: ToolSnippetInfo[];
|
|
100
97
|
toolDefinitions: { count: number; tokens: number; tools: ToolInfo[] };
|
|
101
|
-
compaction: { summarizedTurns: number } | null;
|
|
102
98
|
providerSections: ContextProviderSection[];
|
|
103
99
|
}
|
|
104
100
|
|
|
@@ -106,39 +102,6 @@ export function estimateTextTokens(text: string): number {
|
|
|
106
102
|
return Math.ceil(text.length / 4);
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
function estimateGenericContent(content: unknown): number {
|
|
110
|
-
if (typeof content === "string") {
|
|
111
|
-
return estimateTextTokens(content);
|
|
112
|
-
}
|
|
113
|
-
if (Array.isArray(content)) {
|
|
114
|
-
let chars = 0;
|
|
115
|
-
for (const block of content as Array<{ type?: string; text?: string }>) {
|
|
116
|
-
if (block.type === "text" && block.text) {
|
|
117
|
-
chars += block.text.length;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return Math.ceil(chars / 4);
|
|
121
|
-
}
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function estimateUserMessage(msg: Extract<AgentMessage, { role: "user" }>): number {
|
|
126
|
-
const content = msg.content;
|
|
127
|
-
if (typeof content === "string") {
|
|
128
|
-
return estimateTextTokens(content);
|
|
129
|
-
}
|
|
130
|
-
if (Array.isArray(content)) {
|
|
131
|
-
let chars = 0;
|
|
132
|
-
for (const block of content) {
|
|
133
|
-
if (block.type === "text" && block.text) {
|
|
134
|
-
chars += block.text.length;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return Math.ceil(chars / 4);
|
|
138
|
-
}
|
|
139
|
-
return 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
105
|
function estimateAssistantMessage(msg: Extract<AgentMessage, { role: "assistant" }>): {
|
|
143
106
|
text: number;
|
|
144
107
|
toolCalls: number;
|
|
@@ -169,7 +132,7 @@ function estimateMessageByCategory(msg: AgentMessage): {
|
|
|
169
132
|
} {
|
|
170
133
|
if (msg.role === "user") {
|
|
171
134
|
return {
|
|
172
|
-
user:
|
|
135
|
+
user: estimateTokens(msg),
|
|
173
136
|
assistantText: 0,
|
|
174
137
|
toolCalls: 0,
|
|
175
138
|
toolResult: 0,
|
|
@@ -185,7 +148,7 @@ function estimateMessageByCategory(msg: AgentMessage): {
|
|
|
185
148
|
user: 0,
|
|
186
149
|
assistantText: 0,
|
|
187
150
|
toolCalls: 0,
|
|
188
|
-
toolResult:
|
|
151
|
+
toolResult: estimateTokens(msg),
|
|
189
152
|
other: 0,
|
|
190
153
|
};
|
|
191
154
|
}
|
|
@@ -194,10 +157,14 @@ function estimateMessageByCategory(msg: AgentMessage): {
|
|
|
194
157
|
assistantText: 0,
|
|
195
158
|
toolCalls: 0,
|
|
196
159
|
toolResult: 0,
|
|
197
|
-
other:
|
|
160
|
+
other: estimateTokens(msg),
|
|
198
161
|
};
|
|
199
162
|
}
|
|
200
163
|
|
|
164
|
+
function estimateMessageTokens(msg: AgentMessage): number {
|
|
165
|
+
return estimateTokens(msg);
|
|
166
|
+
}
|
|
167
|
+
|
|
201
168
|
function computeMessageCategories(messages: AgentMessage[]): CategoryTokens {
|
|
202
169
|
const categories: CategoryTokens = {
|
|
203
170
|
systemPrompt: 0,
|
|
@@ -220,6 +187,26 @@ function computeMessageCategories(messages: AgentMessage[]): CategoryTokens {
|
|
|
220
187
|
return categories;
|
|
221
188
|
}
|
|
222
189
|
|
|
190
|
+
interface ScalingResult {
|
|
191
|
+
categories: CategoryTokens;
|
|
192
|
+
scaled: boolean;
|
|
193
|
+
approximationNote: string | null;
|
|
194
|
+
usedTokens: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type CurrentContextUsage = ReturnType<ExtensionContext["getContextUsage"]>;
|
|
198
|
+
|
|
199
|
+
function hasMeasuredTokens(tokens: number | null | undefined): tokens is number {
|
|
200
|
+
return typeof tokens === "number" && tokens > 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getApproximationNote(contextUsage: CurrentContextUsage): string | null {
|
|
204
|
+
if (contextUsage === undefined) return "Approximate (no usage data available)";
|
|
205
|
+
return hasMeasuredTokens(contextUsage.tokens)
|
|
206
|
+
? null
|
|
207
|
+
: "Token count pending — send a message to refresh";
|
|
208
|
+
}
|
|
209
|
+
|
|
223
210
|
function applyScaling(
|
|
224
211
|
categories: CategoryTokens,
|
|
225
212
|
actualTokens: number | null,
|
|
@@ -227,20 +214,13 @@ function applyScaling(
|
|
|
227
214
|
contextUsage:
|
|
228
215
|
| { tokens: number | null; contextWindow: number; percent: number | null }
|
|
229
216
|
| undefined,
|
|
230
|
-
): {
|
|
231
|
-
categories: CategoryTokens;
|
|
232
|
-
scaled: boolean;
|
|
233
|
-
approximationNote: string | null;
|
|
234
|
-
totalTokens: number;
|
|
235
|
-
} {
|
|
217
|
+
): ScalingResult {
|
|
236
218
|
let scaled = false;
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
const
|
|
219
|
+
const hasActualTotal = hasMeasuredTokens(actualTokens);
|
|
220
|
+
const usedTokens = hasActualTotal ? actualTokens : rawTotal;
|
|
221
|
+
const approximationNote = getApproximationNote(contextUsage);
|
|
240
222
|
|
|
241
|
-
if (
|
|
242
|
-
approximationNote = "Approximate (no usage data available)";
|
|
243
|
-
} else if (hasActualTotal && rawTotal > 0) {
|
|
223
|
+
if (hasActualTotal && rawTotal > 0) {
|
|
244
224
|
const scale = actualTokens / rawTotal;
|
|
245
225
|
categories.systemPrompt = Math.round(categories.systemPrompt * scale);
|
|
246
226
|
categories.userMessages = Math.round(categories.userMessages * scale);
|
|
@@ -249,11 +229,9 @@ function applyScaling(
|
|
|
249
229
|
categories.toolResults = Math.round(categories.toolResults * scale);
|
|
250
230
|
categories.other = Math.round(categories.other * scale);
|
|
251
231
|
scaled = true;
|
|
252
|
-
} else if (actualTokens === null || actualTokens === 0) {
|
|
253
|
-
approximationNote = "Token count pending — send a message to refresh";
|
|
254
232
|
}
|
|
255
233
|
|
|
256
|
-
return { categories, scaled, approximationNote,
|
|
234
|
+
return { categories, scaled, approximationNote, usedTokens };
|
|
257
235
|
}
|
|
258
236
|
|
|
259
237
|
/**
|
|
@@ -508,18 +486,10 @@ function computeToolDefinitions(pi: ExtensionAPI): {
|
|
|
508
486
|
};
|
|
509
487
|
}
|
|
510
488
|
|
|
511
|
-
function
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (!compactionEntry) return null;
|
|
516
|
-
|
|
517
|
-
const index = branch.findIndex((e) => e.id === compactionEntry.id);
|
|
518
|
-
const messagesBefore = branch
|
|
519
|
-
.slice(0, Math.max(0, index))
|
|
520
|
-
.filter((e) => e.type === "message").length;
|
|
521
|
-
const summarizedTurns = Math.floor(messagesBefore / 2);
|
|
522
|
-
return { summarizedTurns };
|
|
489
|
+
function hasCompactionOnActiveBranch(
|
|
490
|
+
branch: ReturnType<ExtensionContext["sessionManager"]["getBranch"]>,
|
|
491
|
+
): boolean {
|
|
492
|
+
return getLatestCompactionEntry(branch) !== null;
|
|
523
493
|
}
|
|
524
494
|
|
|
525
495
|
export function extractInjectedContextFiles(messages: AgentMessage[]): InjectedFileInfo[] {
|
|
@@ -556,22 +526,93 @@ export function extractInjectedContextFiles(messages: AgentMessage[]): InjectedF
|
|
|
556
526
|
return Array.from(seen.values()).sort((a, b) => a.turn - b.turn || a.file.localeCompare(b.file));
|
|
557
527
|
}
|
|
558
528
|
|
|
559
|
-
|
|
529
|
+
interface ContextFallback {
|
|
530
|
+
messages: AgentMessage[];
|
|
531
|
+
systemPromptText: string;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
interface CapacityObservation {
|
|
535
|
+
branch: ReturnType<ExtensionContext["sessionManager"]["getBranch"]>;
|
|
536
|
+
contextUsage: CurrentContextUsage;
|
|
537
|
+
snapshot: ContextPressureSnapshot;
|
|
538
|
+
fallback?: ContextFallback;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
interface ContextObservation extends ContextFallback {
|
|
542
|
+
scaling: ScalingResult;
|
|
543
|
+
snapshot: ContextPressureSnapshot;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function estimateContextTokens(fallback: ContextFallback): number {
|
|
547
|
+
return (
|
|
548
|
+
estimateTextTokens(fallback.systemPromptText) +
|
|
549
|
+
fallback.messages.reduce((total, message) => total + estimateMessageTokens(message), 0)
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function collectContextFallback(
|
|
560
554
|
ctx: ExtensionContext,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
555
|
+
branch: ReturnType<ExtensionContext["sessionManager"]["getBranch"]>,
|
|
556
|
+
): ContextFallback {
|
|
557
|
+
return {
|
|
558
|
+
messages: buildSessionContext(branch).messages,
|
|
559
|
+
systemPromptText: ctx.getSystemPrompt(),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Observe the small shared capacity seam. It only walks messages when Pi has
|
|
565
|
+
* no measured usage total and an aggregate estimate is genuinely necessary.
|
|
566
|
+
*/
|
|
567
|
+
function observeCapacity(ctx: ExtensionContext): CapacityObservation {
|
|
565
568
|
const branch = ctx.sessionManager.getBranch();
|
|
566
|
-
const apiView = buildSessionContext(branch);
|
|
567
569
|
const contextUsage = ctx.getContextUsage();
|
|
568
|
-
|
|
569
|
-
|
|
570
|
+
let fallback: ContextFallback | undefined;
|
|
571
|
+
let usedTokens: number;
|
|
572
|
+
if (hasMeasuredTokens(contextUsage?.tokens)) {
|
|
573
|
+
usedTokens = contextUsage.tokens;
|
|
574
|
+
} else {
|
|
575
|
+
fallback = collectContextFallback(ctx, branch);
|
|
576
|
+
usedTokens = estimateContextTokens(fallback);
|
|
577
|
+
}
|
|
578
|
+
const settings = SettingsManager.create(ctx.cwd, undefined, {
|
|
579
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
580
|
+
});
|
|
581
|
+
const capacity = analyzeContextCapacity({
|
|
582
|
+
contextWindow: contextUsage?.contextWindow ?? null,
|
|
583
|
+
usedTokens,
|
|
584
|
+
compactionEnabled: settings.getCompactionEnabled(),
|
|
585
|
+
configuredReserveTokens: settings.getCompactionReserveTokens(),
|
|
586
|
+
compacted: hasCompactionOnActiveBranch(branch),
|
|
587
|
+
approximationNote: getApproximationNote(contextUsage),
|
|
588
|
+
});
|
|
570
589
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
590
|
+
return {
|
|
591
|
+
branch,
|
|
592
|
+
contextUsage,
|
|
593
|
+
fallback,
|
|
594
|
+
snapshot: createContextPressureSnapshot(
|
|
595
|
+
ctx.model?.name ?? ctx.model?.id ?? "No model selected",
|
|
596
|
+
capacity,
|
|
597
|
+
),
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Return a constant-shape Context Pressure Snapshot without diagnostic attribution. */
|
|
602
|
+
export function analyzeContextPressure(ctx: ExtensionContext): ContextPressureSnapshot {
|
|
603
|
+
return observeCapacity(ctx).snapshot;
|
|
604
|
+
}
|
|
574
605
|
|
|
606
|
+
/** Compose a full Context Usage Report from shared capacity and diagnostic attribution. */
|
|
607
|
+
export function analyzeContext(
|
|
608
|
+
ctx: ExtensionContext,
|
|
609
|
+
pi: ExtensionAPI,
|
|
610
|
+
cachedOptions: BuildSystemPromptOptions | undefined,
|
|
611
|
+
): ContextAnalysis {
|
|
612
|
+
const capacity = observeCapacity(ctx);
|
|
613
|
+
const fallback = capacity.fallback ?? collectContextFallback(ctx, capacity.branch);
|
|
614
|
+
const categories = computeMessageCategories(fallback.messages);
|
|
615
|
+
categories.systemPrompt = estimateTextTokens(fallback.systemPromptText);
|
|
575
616
|
const rawTotal =
|
|
576
617
|
categories.systemPrompt +
|
|
577
618
|
categories.userMessages +
|
|
@@ -579,45 +620,33 @@ export function analyzeContext(
|
|
|
579
620
|
categories.toolCalls +
|
|
580
621
|
categories.toolResults +
|
|
581
622
|
categories.other;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
scaling.categories.toolResults +
|
|
593
|
-
scaling.categories.other;
|
|
594
|
-
const freeSpace = Math.max(0, contextWindow - used - autocompactBuffer);
|
|
595
|
-
|
|
623
|
+
const observation: ContextObservation = {
|
|
624
|
+
...fallback,
|
|
625
|
+
scaling: applyScaling(
|
|
626
|
+
categories,
|
|
627
|
+
capacity.contextUsage?.tokens ?? null,
|
|
628
|
+
rawTotal,
|
|
629
|
+
capacity.contextUsage,
|
|
630
|
+
),
|
|
631
|
+
snapshot: capacity.snapshot,
|
|
632
|
+
};
|
|
596
633
|
const promptOptions = deriveOptionsFromSystemPrompt(ctx, cachedOptions);
|
|
597
634
|
const breakdown = computeSystemPromptBreakdown(
|
|
598
635
|
promptOptions,
|
|
599
|
-
systemPromptText,
|
|
600
|
-
scaling.categories.systemPrompt,
|
|
636
|
+
observation.systemPromptText,
|
|
637
|
+
observation.scaling.categories.systemPrompt,
|
|
601
638
|
ctx.cwd,
|
|
602
639
|
);
|
|
603
|
-
const injectedFiles = extractInjectedContextFiles(
|
|
640
|
+
const injectedFiles = extractInjectedContextFiles(observation.messages);
|
|
604
641
|
const toolDefinitions = computeToolDefinitions(pi);
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
642
|
+
const guidelineBullets = extractGuidelineBullets(
|
|
643
|
+
extractGuidelinesSection(observation.systemPromptText),
|
|
644
|
+
);
|
|
608
645
|
|
|
609
646
|
return {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
scaled: scaling.scaled,
|
|
614
|
-
approximationNote: scaling.approximationNote,
|
|
615
|
-
full,
|
|
616
|
-
categories: {
|
|
617
|
-
...scaling.categories,
|
|
618
|
-
autocompactBuffer,
|
|
619
|
-
freeSpace,
|
|
620
|
-
},
|
|
647
|
+
...observation.snapshot,
|
|
648
|
+
scaled: observation.scaling.scaled,
|
|
649
|
+
categories: observation.scaling.categories,
|
|
621
650
|
systemPromptBreakdown: breakdown,
|
|
622
651
|
injectedFiles,
|
|
623
652
|
skills: breakdown.skills,
|
|
@@ -626,7 +655,6 @@ export function analyzeContext(
|
|
|
626
655
|
guidelineSources: breakdown.guidelineSources,
|
|
627
656
|
toolSnippetDetails: breakdown.toolSnippetDetails,
|
|
628
657
|
toolDefinitions,
|
|
629
|
-
compaction,
|
|
630
658
|
providerSections: collectProviderData(),
|
|
631
659
|
};
|
|
632
660
|
}
|
package/src/capacity.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, point-in-time capacity reading without diagnostic attribution.
|
|
3
|
+
*/
|
|
4
|
+
export interface ContextCapacity {
|
|
5
|
+
contextWindow: number | null;
|
|
6
|
+
usedTokens: number;
|
|
7
|
+
usagePercent: number | null;
|
|
8
|
+
compactionEnabled: boolean;
|
|
9
|
+
reserveTokens: number;
|
|
10
|
+
headroomTokens: number | null;
|
|
11
|
+
pressurePercent: number | null;
|
|
12
|
+
compacted: boolean;
|
|
13
|
+
approximationNote: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Agent-facing, constant-shape reading of current context pressure. */
|
|
17
|
+
export interface ContextPressureSnapshot extends ContextCapacity {
|
|
18
|
+
modelName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Inputs that the shared capacity analysis derives from session state. */
|
|
22
|
+
export interface ContextCapacityInput {
|
|
23
|
+
contextWindow: number | null;
|
|
24
|
+
usedTokens: number;
|
|
25
|
+
compactionEnabled: boolean;
|
|
26
|
+
configuredReserveTokens: number;
|
|
27
|
+
compacted: boolean;
|
|
28
|
+
approximationNote: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function roundPercentage(value: number): number {
|
|
32
|
+
return Math.round(value * 10) / 10;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Derive reserve-adjusted capacity without inspecting diagnostic inventories.
|
|
37
|
+
*
|
|
38
|
+
* The configured reserve affects capacity only while auto-compaction is enabled.
|
|
39
|
+
* This lets report and snapshot callers share exactly the same Active Context
|
|
40
|
+
* Limit, Headroom, and Pressure Percentage semantics.
|
|
41
|
+
*/
|
|
42
|
+
export function analyzeContextCapacity(input: ContextCapacityInput): ContextCapacity {
|
|
43
|
+
const contextWindow = input.contextWindow && input.contextWindow > 0 ? input.contextWindow : null;
|
|
44
|
+
const reserveTokens = input.compactionEnabled ? Math.max(0, input.configuredReserveTokens) : 0;
|
|
45
|
+
const activeLimit = contextWindow === null ? null : contextWindow - reserveTokens;
|
|
46
|
+
const hasUsableActiveLimit = activeLimit !== null && activeLimit > 0;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
contextWindow,
|
|
50
|
+
usedTokens: input.usedTokens,
|
|
51
|
+
usagePercent:
|
|
52
|
+
contextWindow === null ? null : roundPercentage((input.usedTokens / contextWindow) * 100),
|
|
53
|
+
compactionEnabled: input.compactionEnabled,
|
|
54
|
+
reserveTokens,
|
|
55
|
+
headroomTokens: activeLimit === null ? null : Math.max(0, activeLimit - input.usedTokens),
|
|
56
|
+
pressurePercent: hasUsableActiveLimit
|
|
57
|
+
? roundPercentage((input.usedTokens / activeLimit) * 100)
|
|
58
|
+
: null,
|
|
59
|
+
compacted: input.compacted,
|
|
60
|
+
approximationNote: input.approximationNote,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Create the exact agent-facing Context Pressure Snapshot shape. */
|
|
65
|
+
export function createContextPressureSnapshot(
|
|
66
|
+
modelName: string,
|
|
67
|
+
capacity: ContextCapacity,
|
|
68
|
+
): ContextPressureSnapshot {
|
|
69
|
+
return {
|
|
70
|
+
modelName,
|
|
71
|
+
contextWindow: capacity.contextWindow,
|
|
72
|
+
usedTokens: capacity.usedTokens,
|
|
73
|
+
usagePercent: capacity.usagePercent,
|
|
74
|
+
compactionEnabled: capacity.compactionEnabled,
|
|
75
|
+
reserveTokens: capacity.reserveTokens,
|
|
76
|
+
headroomTokens: capacity.headroomTokens,
|
|
77
|
+
pressurePercent: capacity.pressurePercent,
|
|
78
|
+
compacted: capacity.compacted,
|
|
79
|
+
approximationNote: capacity.approximationNote,
|
|
80
|
+
};
|
|
81
|
+
}
|