@mrclrchtr/supi-context 2.6.1 → 2.8.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/README.md +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +14 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +31 -14
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- 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/src/format-summary.ts
CHANGED
|
@@ -15,13 +15,27 @@ import {
|
|
|
15
15
|
} from "./format-helpers.ts";
|
|
16
16
|
import { formatTokens } from "./utils.ts";
|
|
17
17
|
|
|
18
|
+
function formatPercentage(value: number | null): string {
|
|
19
|
+
return value === null ? "?" : `${value.toFixed(1)}%`;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
export function renderSummary(analysis: ContextAnalysis, theme: Theme, width: number): string[] {
|
|
19
|
-
const used = analysis.totalTokens ?? 0;
|
|
20
23
|
const health = theme.fg(healthColor(analysis), "●");
|
|
21
24
|
const usage =
|
|
22
|
-
analysis.contextWindow
|
|
23
|
-
? `${formatTokens(
|
|
24
|
-
: `${formatTokens(
|
|
25
|
+
analysis.contextWindow !== null
|
|
26
|
+
? `${formatTokens(analysis.usedTokens)} / ${formatTokens(analysis.contextWindow)} tokens (${formatPercentage(analysis.usagePercent)} usage)`
|
|
27
|
+
: `${formatTokens(analysis.usedTokens)} tokens`;
|
|
28
|
+
const capacityParts: string[] = [];
|
|
29
|
+
|
|
30
|
+
if (analysis.headroomTokens !== null) {
|
|
31
|
+
capacityParts.push(`Headroom ${formatTokens(analysis.headroomTokens)}`);
|
|
32
|
+
}
|
|
33
|
+
if (analysis.compactionEnabled) {
|
|
34
|
+
capacityParts.push(`Compaction reserve ${formatTokens(analysis.reserveTokens)}`);
|
|
35
|
+
}
|
|
36
|
+
if (analysis.pressurePercent !== null) {
|
|
37
|
+
capacityParts.push(`Pressure ${formatPercentage(analysis.pressurePercent)}`);
|
|
38
|
+
}
|
|
25
39
|
|
|
26
40
|
const lines = [
|
|
27
41
|
truncateToWidth(
|
|
@@ -30,6 +44,9 @@ export function renderSummary(analysis: ContextAnalysis, theme: Theme, width: nu
|
|
|
30
44
|
),
|
|
31
45
|
];
|
|
32
46
|
|
|
47
|
+
if (capacityParts.length > 0) {
|
|
48
|
+
lines.push(...wrapReportText(theme.fg("dim", capacityParts.join(" · ")), width));
|
|
49
|
+
}
|
|
33
50
|
if (analysis.approximationNote) {
|
|
34
51
|
lines.push(...wrapReportText(theme.fg("warning", analysis.approximationNote), width));
|
|
35
52
|
}
|
|
@@ -38,16 +55,16 @@ export function renderSummary(analysis: ContextAnalysis, theme: Theme, width: nu
|
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
export function renderUsageBar(analysis: ContextAnalysis, theme: Theme, width: number): string[] {
|
|
41
|
-
if (analysis.contextWindow
|
|
58
|
+
if (analysis.contextWindow === null) {
|
|
42
59
|
return [theme.fg("dim", "No model selected — usage bar unavailable")];
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
const percentLabel =
|
|
62
|
+
const percentLabel = formatPercentage(analysis.usagePercent);
|
|
46
63
|
const barWidth = Math.max(12, Math.min(48, width - visibleWidth(percentLabel) - 3));
|
|
47
64
|
const values = [
|
|
48
65
|
...CATEGORY_ORDER.map((key) => analysis.categories[key]),
|
|
49
|
-
analysis.
|
|
50
|
-
analysis.
|
|
66
|
+
analysis.reserveTokens,
|
|
67
|
+
analysis.headroomTokens ?? 0,
|
|
51
68
|
];
|
|
52
69
|
const counts = allocateBlocks(values, barWidth);
|
|
53
70
|
|
|
@@ -85,11 +102,11 @@ export function renderUsageBar(analysis: ContextAnalysis, theme: Theme, width: n
|
|
|
85
102
|
if (analysis.categories[key] <= 0) continue;
|
|
86
103
|
legendParts.push(`${theme.fg(CATEGORY_COLORS[key], "●")} ${CATEGORY_LABELS[key]}`);
|
|
87
104
|
}
|
|
88
|
-
if (analysis.
|
|
89
|
-
legendParts.push(`${theme.fg("warning", "▒")}
|
|
105
|
+
if (analysis.reserveTokens > 0) {
|
|
106
|
+
legendParts.push(`${theme.fg("warning", "▒")} Compaction reserve`);
|
|
90
107
|
}
|
|
91
|
-
if (analysis.
|
|
92
|
-
legendParts.push(`${theme.fg("dim", "░")}
|
|
108
|
+
if (analysis.headroomTokens !== null && analysis.headroomTokens > 0) {
|
|
109
|
+
legendParts.push(`${theme.fg("dim", "░")} Headroom`);
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
return [barLine, ...wrapReportText(legendParts.join(theme.fg("dim", " • ")), width)];
|
|
@@ -107,31 +124,20 @@ export function renderCategoryBreakdown(
|
|
|
107
124
|
label: string;
|
|
108
125
|
color: Parameters<Theme["fg"]>[0];
|
|
109
126
|
tokens: number;
|
|
110
|
-
}> =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})),
|
|
116
|
-
{
|
|
117
|
-
label: "Autocompact buffer",
|
|
118
|
-
color: "warning",
|
|
119
|
-
tokens: analysis.categories.autocompactBuffer,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
label: "Free space",
|
|
123
|
-
color: "dim",
|
|
124
|
-
tokens: analysis.categories.freeSpace,
|
|
125
|
-
},
|
|
126
|
-
];
|
|
127
|
+
}> = CATEGORY_ORDER.map((key) => ({
|
|
128
|
+
label: CATEGORY_LABELS[key],
|
|
129
|
+
color: CATEGORY_COLORS[key],
|
|
130
|
+
tokens: analysis.categories[key],
|
|
131
|
+
}));
|
|
127
132
|
|
|
128
133
|
const labelWidth = Math.max(18, Math.min(22, width - 22));
|
|
134
|
+
const total = analysis.contextWindow ?? 0;
|
|
129
135
|
for (const row of rows) {
|
|
130
|
-
if (row.tokens <= 0
|
|
136
|
+
if (row.tokens <= 0) continue;
|
|
131
137
|
const bullet = theme.fg(row.color, "●");
|
|
132
138
|
const label = padRight(row.label, labelWidth);
|
|
133
139
|
const tokens = padLeft(formatTokens(row.tokens), 8);
|
|
134
|
-
const percentage = padLeft(pct(row.tokens,
|
|
140
|
+
const percentage = padLeft(pct(row.tokens, total), 7);
|
|
135
141
|
lines.push(truncateToWidth(` ${bullet} ${label} ${tokens} ${percentage}`, width));
|
|
136
142
|
}
|
|
137
143
|
|
package/src/format.ts
CHANGED
|
@@ -18,10 +18,14 @@ import {
|
|
|
18
18
|
renderUsageBar,
|
|
19
19
|
} from "./format-summary.ts";
|
|
20
20
|
|
|
21
|
+
/** Presentation mode for a Context Usage Report. */
|
|
22
|
+
export type ContextReportMode = "preview" | "full";
|
|
23
|
+
|
|
21
24
|
export function formatContextReport(
|
|
22
25
|
analysis: ContextAnalysis,
|
|
23
26
|
theme: Theme,
|
|
24
27
|
width = 200,
|
|
28
|
+
mode: ContextReportMode = "preview",
|
|
25
29
|
): string[] {
|
|
26
30
|
const safeWidth = clampReportWidth(width);
|
|
27
31
|
const lines: string[] = [];
|
|
@@ -41,8 +45,8 @@ export function formatContextReport(
|
|
|
41
45
|
renderContextFilesSection(analysis, theme, safeWidth),
|
|
42
46
|
renderInjectedFilesSection(analysis, theme, safeWidth),
|
|
43
47
|
renderSkillsSection(analysis, theme, safeWidth),
|
|
44
|
-
renderGuidelinesSection(analysis, theme, safeWidth),
|
|
45
|
-
renderToolDefinitionsSection(analysis, theme, safeWidth),
|
|
48
|
+
renderGuidelinesSection(analysis, theme, safeWidth, mode === "full"),
|
|
49
|
+
renderToolDefinitionsSection(analysis, theme, safeWidth, mode === "full"),
|
|
46
50
|
renderCompactionNote(analysis, theme, safeWidth),
|
|
47
51
|
renderProviderSections(analysis, theme, safeWidth),
|
|
48
52
|
].filter((section) => section.length > 0);
|
package/src/report-component.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ContextAnalysis } from "./analysis.ts";
|
|
3
|
-
import { formatContextReport } from "./format.ts";
|
|
3
|
+
import { type ContextReportMode, formatContextReport } from "./format.ts";
|
|
4
4
|
|
|
5
|
-
/** Width-aware
|
|
5
|
+
/** Width-aware Context Usage Report shared by custom-entry and tool renderers. */
|
|
6
6
|
export class ContextReportComponent {
|
|
7
7
|
private cachedWidth?: number;
|
|
8
8
|
private cachedLines?: string[];
|
|
@@ -10,6 +10,7 @@ export class ContextReportComponent {
|
|
|
10
10
|
constructor(
|
|
11
11
|
private readonly analysis: ContextAnalysis,
|
|
12
12
|
private readonly theme: Theme,
|
|
13
|
+
private readonly mode: ContextReportMode = "preview",
|
|
13
14
|
) {}
|
|
14
15
|
|
|
15
16
|
render(width: number): string[] {
|
|
@@ -17,7 +18,7 @@ export class ContextReportComponent {
|
|
|
17
18
|
return this.cachedLines;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const lines = formatContextReport(this.analysis, this.theme, width);
|
|
21
|
+
const lines = formatContextReport(this.analysis, this.theme, width, this.mode);
|
|
21
22
|
this.cachedWidth = width;
|
|
22
23
|
this.cachedLines = lines;
|
|
23
24
|
return lines;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { ContextPressureSnapshot } from "./capacity.ts";
|
|
4
|
+
import { healthColor } from "./format-helpers.ts";
|
|
5
|
+
import { formatTokens } from "./utils.ts";
|
|
6
|
+
|
|
7
|
+
function formatPercentage(value: number | null): string {
|
|
8
|
+
return value === null ? "unavailable" : `${value.toFixed(1)}%`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatTokenValue(value: number | null): string {
|
|
12
|
+
return value === null ? "unavailable" : formatTokens(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Width-safe TUI renderer for the small Context Pressure Snapshot.
|
|
17
|
+
*
|
|
18
|
+
* The collapsed view is intentionally one dense line. Expansion exposes every
|
|
19
|
+
* snapshot field without falling back to the raw JSON returned to the agent.
|
|
20
|
+
*/
|
|
21
|
+
export class ContextPressureComponent implements Component {
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly snapshot: ContextPressureSnapshot,
|
|
24
|
+
private readonly theme: Theme,
|
|
25
|
+
private readonly expanded: boolean,
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
render(width: number): string[] {
|
|
29
|
+
return this.expanded ? this.renderExpanded(width) : [this.renderCollapsed(width)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
invalidate(): void {}
|
|
33
|
+
|
|
34
|
+
private renderCollapsed(width: number): string {
|
|
35
|
+
const usage =
|
|
36
|
+
this.snapshot.contextWindow === null
|
|
37
|
+
? `${formatTokens(this.snapshot.usedTokens)} used`
|
|
38
|
+
: `${formatTokens(this.snapshot.usedTokens)}/${formatTokens(this.snapshot.contextWindow)} used`;
|
|
39
|
+
const parts = [
|
|
40
|
+
`${this.theme.fg(healthColor(this.snapshot), "●")} ${this.theme.fg("text", usage)}`,
|
|
41
|
+
`${this.theme.fg("dim", "headroom")} ${this.theme.fg("muted", formatTokenValue(this.snapshot.headroomTokens))}`,
|
|
42
|
+
`${this.theme.fg("dim", "pressure")} ${this.theme.fg("muted", formatPercentage(this.snapshot.pressurePercent))}`,
|
|
43
|
+
this.theme.fg("muted", this.snapshot.modelName),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
return truncateToWidth(parts.join(` ${this.theme.fg("dim", "·")} `), width);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private renderExpanded(width: number): string[] {
|
|
50
|
+
const values: Array<[string, string]> = [
|
|
51
|
+
["Model", this.snapshot.modelName],
|
|
52
|
+
["Context window", formatTokenValue(this.snapshot.contextWindow)],
|
|
53
|
+
["Used", formatTokens(this.snapshot.usedTokens)],
|
|
54
|
+
["Usage", formatPercentage(this.snapshot.usagePercent)],
|
|
55
|
+
["Auto-compaction", this.snapshot.compactionEnabled ? "enabled" : "disabled"],
|
|
56
|
+
["Compaction reserve", formatTokens(this.snapshot.reserveTokens)],
|
|
57
|
+
["Headroom", formatTokenValue(this.snapshot.headroomTokens)],
|
|
58
|
+
["Pressure", formatPercentage(this.snapshot.pressurePercent)],
|
|
59
|
+
["Compacted", this.snapshot.compacted ? "yes" : "no"],
|
|
60
|
+
];
|
|
61
|
+
const labelWidth = Math.max(8, Math.min(18, width - 12));
|
|
62
|
+
const lines = values.map(([label, value]) =>
|
|
63
|
+
truncateToWidth(
|
|
64
|
+
`${this.theme.fg("dim", `${label.padEnd(labelWidth)} `)}${this.theme.fg("text", value)}`,
|
|
65
|
+
width,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (this.snapshot.approximationNote) {
|
|
70
|
+
lines.push(truncateToWidth(this.theme.fg("warning", this.snapshot.approximationNote), width));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return lines;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/tool/guidance.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
1
3
|
// Prompt guidance and tool description for the supi_context agent tool.
|
|
2
4
|
|
|
3
5
|
export const toolDescription =
|
|
4
|
-
"Report current
|
|
6
|
+
"Report current context capacity. Omit mode for a concise, constant-shape pressure snapshot; use mode: full only when diagnostic attribution is needed. Full output is compact JSON and is replaced with a temporary-file envelope above " +
|
|
7
|
+
`${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)}.`;
|
|
5
8
|
|
|
6
9
|
export const promptSnippet =
|
|
7
|
-
"supi_context — context
|
|
10
|
+
"supi_context — concise context-pressure snapshot (mode: full for diagnostics)";
|
|
8
11
|
|
|
9
12
|
export const promptGuidelines = [
|
|
10
13
|
"Use supi_context before large operations or when context usage is near the limit.",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MAX_BYTES,
|
|
6
|
+
DEFAULT_MAX_LINES,
|
|
7
|
+
truncateHead,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { ContextAnalysis } from "../analysis.ts";
|
|
10
|
+
|
|
11
|
+
interface TruncatedOutputEnvelope {
|
|
12
|
+
truncated: true;
|
|
13
|
+
fullOutputPath: string;
|
|
14
|
+
totalLines: number;
|
|
15
|
+
totalBytes: number;
|
|
16
|
+
maxLines: number;
|
|
17
|
+
maxBytes: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a diagnostic report without ever returning invalid partial JSON.
|
|
22
|
+
*
|
|
23
|
+
* When Pi's normal tool-output limits would truncate the report, the complete
|
|
24
|
+
* compact JSON is stored in a temporary file and the agent receives a small,
|
|
25
|
+
* valid JSON envelope pointing to it instead.
|
|
26
|
+
*/
|
|
27
|
+
export async function serializeFullContextAnalysis(analysis: ContextAnalysis): Promise<string> {
|
|
28
|
+
const fullJson = JSON.stringify(analysis);
|
|
29
|
+
const truncation = truncateHead(fullJson, {
|
|
30
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
31
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!truncation.truncated) {
|
|
35
|
+
return fullJson;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const outputDir = await mkdtemp(join(tmpdir(), "supi-context-"));
|
|
39
|
+
const fullOutputPath = join(outputDir, "context-usage-report.json");
|
|
40
|
+
await writeFile(fullOutputPath, fullJson, "utf8");
|
|
41
|
+
|
|
42
|
+
const envelope: TruncatedOutputEnvelope = {
|
|
43
|
+
truncated: true,
|
|
44
|
+
fullOutputPath,
|
|
45
|
+
totalLines: truncation.totalLines,
|
|
46
|
+
totalBytes: truncation.totalBytes,
|
|
47
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
48
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
49
|
+
};
|
|
50
|
+
return JSON.stringify(envelope);
|
|
51
|
+
}
|
package/src/tool/render.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ContextAnalysis } from "../analysis.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { type ContextPressureSnapshot, createContextPressureSnapshot } from "../capacity.ts";
|
|
5
5
|
import { ContextReportComponent } from "../report-component.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { ContextPressureComponent } from "../snapshot-component.ts";
|
|
7
7
|
|
|
8
|
-
export interface
|
|
8
|
+
export interface ContextToolConciseDetails {
|
|
9
|
+
mode: "concise";
|
|
10
|
+
snapshot: ContextPressureSnapshot;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ContextToolFullDetails {
|
|
14
|
+
mode: "full";
|
|
9
15
|
analysis: ContextAnalysis;
|
|
10
16
|
}
|
|
11
17
|
|
|
18
|
+
/** TUI data mirrors the mode-specific data returned to the agent. */
|
|
19
|
+
export type ContextToolDetails = ContextToolConciseDetails | ContextToolFullDetails;
|
|
20
|
+
|
|
12
21
|
interface ContextToolResult {
|
|
13
22
|
content: Array<{ type: string; text?: string }>;
|
|
14
23
|
details?: unknown;
|
|
@@ -20,8 +29,15 @@ interface ResultOptions {
|
|
|
20
29
|
isPartial: boolean;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
export function renderContextToolCall(
|
|
24
|
-
const
|
|
32
|
+
export function renderContextToolCall(args: unknown, theme: Theme): Text {
|
|
33
|
+
const mode =
|
|
34
|
+
args &&
|
|
35
|
+
typeof args === "object" &&
|
|
36
|
+
"mode" in args &&
|
|
37
|
+
(args as { mode?: string }).mode === "full"
|
|
38
|
+
? "full"
|
|
39
|
+
: "concise";
|
|
40
|
+
const content = `${theme.fg("toolTitle", "supi_context")} ${theme.fg("muted", mode)}`;
|
|
25
41
|
return new Text(content, 0, 0);
|
|
26
42
|
}
|
|
27
43
|
|
|
@@ -29,7 +45,7 @@ export function renderContextToolResult(
|
|
|
29
45
|
result: ContextToolResult | undefined,
|
|
30
46
|
options: ResultOptions,
|
|
31
47
|
theme: Theme,
|
|
32
|
-
): Text | ContextReportComponent {
|
|
48
|
+
): Text | ContextPressureComponent | ContextReportComponent {
|
|
33
49
|
if (options.isPartial) {
|
|
34
50
|
return new Text(theme.fg("warning", "Analyzing context…"), 0, 0);
|
|
35
51
|
}
|
|
@@ -38,53 +54,37 @@ export function renderContextToolResult(
|
|
|
38
54
|
return new Text(theme.fg("error", "supi_context failed"), 0, 0);
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
const
|
|
42
|
-
if (!
|
|
57
|
+
const details = extractDetails(result?.details);
|
|
58
|
+
if (!details) {
|
|
43
59
|
return new Text(theme.fg("dim", "No context analysis data"), 0, 0);
|
|
44
60
|
}
|
|
45
61
|
|
|
62
|
+
if (details.mode === "concise") {
|
|
63
|
+
return new ContextPressureComponent(details.snapshot, theme, options.expanded);
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
if (options.expanded) {
|
|
47
|
-
return new ContextReportComponent(analysis, theme);
|
|
67
|
+
return new ContextReportComponent(details.analysis, theme, "full");
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
return new
|
|
70
|
+
return new ContextPressureComponent(
|
|
71
|
+
createContextPressureSnapshot(details.analysis.modelName, details.analysis),
|
|
72
|
+
theme,
|
|
73
|
+
false,
|
|
74
|
+
);
|
|
51
75
|
}
|
|
52
76
|
|
|
53
|
-
function
|
|
54
|
-
if (!details || typeof details !== "object" || !("
|
|
77
|
+
function extractDetails(details: unknown): ContextToolDetails | undefined {
|
|
78
|
+
if (!details || typeof details !== "object" || !("mode" in details)) {
|
|
55
79
|
return undefined;
|
|
56
80
|
}
|
|
57
|
-
return (details as ContextToolDetails).analysis;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function formatCollapsedSummary(analysis: ContextAnalysis, theme: Theme): string {
|
|
61
|
-
const used = analysis.totalTokens ?? 0;
|
|
62
|
-
const usage =
|
|
63
|
-
analysis.contextWindow > 0
|
|
64
|
-
? `${formatTokens(used)} / ${formatTokens(analysis.contextWindow)} (${pct(used, analysis.contextWindow)})`
|
|
65
|
-
: `${formatTokens(used)} tokens`;
|
|
66
|
-
const dot = theme.fg("dim", "·");
|
|
67
|
-
const parts = [
|
|
68
|
-
`${theme.fg(healthColor(analysis), "●")} ${theme.fg("dim", "usage")} ${theme.fg("text", theme.bold(usage))}`,
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
if (analysis.contextWindow > 0) {
|
|
72
|
-
parts.push(
|
|
73
|
-
`${theme.fg("dim", "free")} ${theme.fg("muted", formatTokens(analysis.categories.freeSpace))}`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
);
|
|
82
|
+
const mode = (details as { mode?: unknown }).mode;
|
|
83
|
+
if (mode === "concise" && "snapshot" in details) {
|
|
84
|
+
return details as ContextToolConciseDetails;
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!analysis.approximationNote) {
|
|
86
|
-
return parts.join(` ${dot} `);
|
|
86
|
+
if (mode === "full" && "analysis" in details) {
|
|
87
|
+
return details as ContextToolFullDetails;
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
-
return `${parts.join(` ${dot} `)}\n${theme.fg("warning", analysis.approximationNote)}`;
|
|
89
|
+
return undefined;
|
|
90
90
|
}
|
package/src/renderer.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import type { ContextAnalysis } from "./analysis.ts";
|
|
4
|
-
import { ContextReportComponent } from "./report-component.ts";
|
|
5
|
-
|
|
6
|
-
export function registerContextRenderer(pi: ExtensionAPI): void {
|
|
7
|
-
pi.registerMessageRenderer("supi-context", (message, _renderOptions, theme) => {
|
|
8
|
-
const analysis = (message.details as { analysis?: ContextAnalysis } | undefined)?.analysis;
|
|
9
|
-
if (!analysis) {
|
|
10
|
-
return new Text(theme.fg("dim", "No context analysis data"), 1, 0);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return new ContextReportComponent(analysis, theme);
|
|
14
|
-
});
|
|
15
|
-
}
|