pi-ui-extend 0.1.3 → 0.1.4
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/dist/app/app.d.ts +0 -1
- package/dist/app/app.js +1 -4
- package/dist/app/conversation-entry-renderer.d.ts +0 -1
- package/dist/app/conversation-entry-renderer.js +2 -6
- package/dist/app/conversation-tool-renderer.js +2 -3
- package/dist/app/conversation-viewport.d.ts +0 -1
- package/dist/app/conversation-viewport.js +0 -1
- package/dist/config.d.ts +0 -3
- package/dist/config.js +0 -79
- package/dist/default-pix-config.js +2 -2
- package/dist/markdown-format.js +18 -1
- package/external/pi-tools-suite/README.md +4 -4
- package/external/pi-tools-suite/licenses/opencode-dynamic-context-pruning-AGPL-3.0.txt +619 -0
- package/external/pi-tools-suite/package.json +1 -1
- package/external/pi-tools-suite/src/config.ts +5 -1
- package/external/pi-tools-suite/src/{compress → dcp}/config.ts +10 -70
- package/external/pi-tools-suite/src/{compress → dcp}/index.ts +16 -66
- package/external/pi-tools-suite/src/dcp/ui.ts +45 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -2
- package/external/pi-tools-suite/src/index.ts +1 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +1 -1
- package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +0 -498
- package/external/pi-tools-suite/src/compress/ui.ts +0 -308
- /package/external/pi-tools-suite/src/{compress → dcp}/commands.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/compress-tool.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/compression-blocks.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/prompts.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-candidates.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-compression-blocks.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-message-ids.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-metadata.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-nudge.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-tools.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-types.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/state.ts +0 -0
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext, ExtensionUIContext, Theme } from "@mariozechner/pi-coding-agent"
|
|
2
|
-
import type { DcpState } from "./state.js"
|
|
3
|
-
|
|
4
|
-
export interface DcpCompressionVisualDetails {
|
|
5
|
-
topic: string
|
|
6
|
-
blockIds: number[]
|
|
7
|
-
ranges: number
|
|
8
|
-
messages: number
|
|
9
|
-
itemCount: number
|
|
10
|
-
totalSummaryTokens: number
|
|
11
|
-
activeBlocks: number
|
|
12
|
-
totalBlocks: number
|
|
13
|
-
prunedTools: number
|
|
14
|
-
tokensSaved: number
|
|
15
|
-
contextTokens?: number | null
|
|
16
|
-
contextWindow?: number
|
|
17
|
-
contextPercent?: number | null
|
|
18
|
-
skippedMessages?: number
|
|
19
|
-
skippedMessageIssues?: string[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DcpContextUsage {
|
|
23
|
-
tokens: number | null
|
|
24
|
-
contextWindow: number
|
|
25
|
-
percent?: number | null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type RawDcpContextUsage = {
|
|
29
|
-
tokens?: number | null
|
|
30
|
-
contextWindow?: number
|
|
31
|
-
percent?: number | null
|
|
32
|
-
} | undefined
|
|
33
|
-
|
|
34
|
-
interface DcpVisualSnapshot {
|
|
35
|
-
manualMode: boolean
|
|
36
|
-
tokensSaved: number
|
|
37
|
-
prunedTools: number
|
|
38
|
-
activeBlocks: number
|
|
39
|
-
activeNudges: number
|
|
40
|
-
lastNudgeType?: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function fg(theme: Theme | undefined, color: string, text: string): string {
|
|
44
|
-
return theme ? theme.fg(color as any, text) : text
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function rgb(r: number, g: number, b: number, text: string): string {
|
|
48
|
-
return text ? `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m` : ""
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function contextUsageRgb(used: number, total: number): [number, number, number] {
|
|
52
|
-
const ratio = total > 0 ? Math.max(0, used) / total : 0
|
|
53
|
-
if (ratio <= 0.30) return [21, 128, 61]
|
|
54
|
-
if (ratio <= 0.50) return [161, 98, 7]
|
|
55
|
-
return [185, 28, 28]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function colorOccupiedContext(used: number, total: number, text: string): string {
|
|
59
|
-
return rgb(...contextUsageRgb(used, total), text)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function colorFreedContext(text: string): string {
|
|
63
|
-
return rgb(15, 118, 110, text)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function colorEmptyContext(text: string): string {
|
|
67
|
-
return rgb(55, 65, 81, text)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function formatDcpTokenCount(n: number): string {
|
|
71
|
-
const safe = Math.max(0, Math.round(n || 0))
|
|
72
|
-
if (safe >= 1_000_000) return `${(safe / 1_000_000).toFixed(1)}M`
|
|
73
|
-
if (safe >= 1_000) return `${(safe / 1_000).toFixed(1)}K`
|
|
74
|
-
return String(safe)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function snapshotFromState(state: DcpState): DcpVisualSnapshot {
|
|
78
|
-
const activeBlocks = state.compressionBlocks.filter((block) => block.active)
|
|
79
|
-
return {
|
|
80
|
-
manualMode: state.manualMode,
|
|
81
|
-
tokensSaved: state.tokensSaved,
|
|
82
|
-
prunedTools: state.prunedToolIds.size,
|
|
83
|
-
activeBlocks: activeBlocks.length,
|
|
84
|
-
activeNudges: state.nudgeAnchors.length,
|
|
85
|
-
lastNudgeType: state.lastNudge?.type,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatNudgeType(type: string | undefined): string | undefined {
|
|
90
|
-
if (!type) return undefined
|
|
91
|
-
if (type === "context-strong") return "context!"
|
|
92
|
-
if (type === "context-soft") return "context"
|
|
93
|
-
return type
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function normalizeDcpContextUsage(usage: RawDcpContextUsage): DcpContextUsage | undefined {
|
|
97
|
-
if (!usage || typeof usage.contextWindow !== "number" || usage.contextWindow <= 0) return undefined
|
|
98
|
-
const contextWindow = usage.contextWindow
|
|
99
|
-
const percent = typeof usage.percent === "number" && Number.isFinite(usage.percent)
|
|
100
|
-
? Math.max(0, usage.percent)
|
|
101
|
-
: usage.percent === null
|
|
102
|
-
? null
|
|
103
|
-
: undefined
|
|
104
|
-
const tokens = typeof percent === "number"
|
|
105
|
-
? Math.round((contextWindow * percent) / 100)
|
|
106
|
-
: typeof usage.tokens === "number"
|
|
107
|
-
? usage.tokens
|
|
108
|
-
: null
|
|
109
|
-
return { tokens, contextWindow, percent }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function usageTokens(usage: DcpContextUsage): number {
|
|
113
|
-
if (usage.contextWindow > 0 && typeof usage.percent === "number" && Number.isFinite(usage.percent)) {
|
|
114
|
-
return Math.round((usage.contextWindow * Math.max(0, usage.percent)) / 100)
|
|
115
|
-
}
|
|
116
|
-
return usage.tokens ?? 0
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function contextRatio(usage: DcpContextUsage | undefined): string | undefined {
|
|
120
|
-
if (!usage || typeof usage.contextWindow !== "number" || usage.contextWindow <= 0) return undefined
|
|
121
|
-
const used = usageTokens(usage)
|
|
122
|
-
return `${formatDcpTokenCount(used)}/${formatDcpTokenCount(usage.contextWindow)}`
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function compressionContextScale(details: Pick<DcpCompressionVisualDetails, "contextTokens" | "contextWindow" | "contextPercent">): string | undefined {
|
|
126
|
-
const total = Math.max(0, Math.round(details.contextWindow ?? 0))
|
|
127
|
-
if (total <= 0) return undefined
|
|
128
|
-
if (typeof details.contextPercent === "number" && Number.isFinite(details.contextPercent)) {
|
|
129
|
-
return `${Math.max(0, details.contextPercent).toFixed(1)}%/${formatDcpTokenCount(total)}`
|
|
130
|
-
}
|
|
131
|
-
return `${formatDcpTokenCount(compressionContextTokens(details))}/${formatDcpTokenCount(total)}`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function renderDcpStatusLabel(state: DcpState, theme?: Theme, usage?: DcpContextUsage): string {
|
|
135
|
-
const normalizedUsage = normalizeDcpContextUsage(usage)
|
|
136
|
-
const snapshot = snapshotFromState(state)
|
|
137
|
-
const parts: string[] = []
|
|
138
|
-
|
|
139
|
-
const ratio = contextRatio(normalizedUsage)
|
|
140
|
-
const headline = [
|
|
141
|
-
fg(theme, "accent", "DCP"),
|
|
142
|
-
ratio ? fg(theme, "customMessageText", ratio) : undefined,
|
|
143
|
-
normalizedUsage ? renderDcpUsageBar(theme, normalizedUsage, 14) : undefined,
|
|
144
|
-
snapshot.manualMode ? fg(theme, "warning", "manual") : undefined,
|
|
145
|
-
].filter(Boolean).join(" ")
|
|
146
|
-
parts.push(headline)
|
|
147
|
-
|
|
148
|
-
if (snapshot.tokensSaved > 0) {
|
|
149
|
-
parts.push(`${fg(theme, "success", `-${formatDcpTokenCount(snapshot.tokensSaved)}`)} ${fg(theme, "dim", "saved")}`)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (snapshot.prunedTools > 0) {
|
|
153
|
-
parts.push(`${fg(theme, "warning", String(snapshot.prunedTools))} ${fg(theme, "dim", "pruned")}`)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (snapshot.activeBlocks > 0) {
|
|
157
|
-
parts.push(`${fg(theme, "accent", String(snapshot.activeBlocks))} ${fg(theme, "dim", snapshot.activeBlocks === 1 ? "block" : "blocks")}`)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const nudgeLabel = formatNudgeType(snapshot.lastNudgeType)
|
|
161
|
-
if (snapshot.activeNudges > 0 && nudgeLabel) {
|
|
162
|
-
parts.push(`${fg(theme, "warning", `nudge ${nudgeLabel}`)} ${fg(theme, "dim", `(${snapshot.activeNudges})`)}`)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return parts.join(fg(theme, "dim", " │ "))
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function compressionContextTokens(details: Pick<DcpCompressionVisualDetails, "contextTokens" | "contextWindow" | "contextPercent">): number {
|
|
169
|
-
const total = Math.max(0, Math.round(details.contextWindow ?? 0))
|
|
170
|
-
if (total > 0 && typeof details.contextPercent === "number" && Number.isFinite(details.contextPercent)) {
|
|
171
|
-
return Math.round((total * Math.max(0, details.contextPercent)) / 100)
|
|
172
|
-
}
|
|
173
|
-
return Math.round(details.contextTokens ?? 0)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function contextParts(details: Pick<DcpCompressionVisualDetails, "contextTokens" | "contextWindow" | "contextPercent" | "tokensSaved">): { used: number; freed: number; empty: number; total: number } {
|
|
177
|
-
const total = Math.max(0, Math.round(details.contextWindow ?? 0))
|
|
178
|
-
if (total <= 0) {
|
|
179
|
-
const freed = Math.max(0, Math.round(details.tokensSaved ?? 0))
|
|
180
|
-
return { used: 0, freed, empty: 0, total: Math.max(1, freed) }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const footprint = Math.max(0, Math.min(total, compressionContextTokens(details)))
|
|
184
|
-
const freed = Math.max(0, Math.min(footprint, Math.round(details.tokensSaved ?? 0)))
|
|
185
|
-
const used = Math.max(0, footprint - freed)
|
|
186
|
-
const empty = Math.max(0, total - footprint)
|
|
187
|
-
return { used, freed, empty, total }
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function singleLine(value: string): string {
|
|
191
|
-
return value.replace(/\s+/g, " ").trim()
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function truncateLabel(value: string, maxChars: number): string {
|
|
195
|
-
const chars = Array.from(singleLine(value))
|
|
196
|
-
if (chars.length <= maxChars) return chars.join("")
|
|
197
|
-
if (maxChars <= 0) return ""
|
|
198
|
-
if (maxChars === 1) return "…"
|
|
199
|
-
return `${chars.slice(0, maxChars - 1).join("")}…`
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function distributeBar(width: number, parts: { used: number; freed: number; empty: number; total: number }): { used: number; freed: number; empty: number } {
|
|
203
|
-
const safeWidth = Math.max(1, width)
|
|
204
|
-
const footprint = Math.max(0, parts.used + parts.freed)
|
|
205
|
-
const occupied = footprint > 0
|
|
206
|
-
? Math.min(safeWidth, Math.max(1, Math.round((footprint / Math.max(1, parts.total)) * safeWidth)))
|
|
207
|
-
: 0
|
|
208
|
-
const empty = safeWidth - occupied
|
|
209
|
-
if (occupied === 0) return { used: 0, freed: 0, empty }
|
|
210
|
-
if (parts.used <= 0) return { used: 0, freed: occupied, empty }
|
|
211
|
-
if (parts.freed <= 0) return { used: occupied, freed: 0, empty }
|
|
212
|
-
if (occupied === 1) {
|
|
213
|
-
return parts.freed >= parts.used
|
|
214
|
-
? { used: 0, freed: 1, empty }
|
|
215
|
-
: { used: 1, freed: 0, empty }
|
|
216
|
-
}
|
|
217
|
-
const used = Math.min(occupied - 1, Math.max(1, Math.round((parts.used / footprint) * occupied)))
|
|
218
|
-
return { used, freed: occupied - used, empty }
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function renderDcpUsageBar(_theme: Theme | undefined, usage: DcpContextUsage, width: number): string {
|
|
222
|
-
const total = Math.max(0, Math.round(usage.contextWindow ?? 0))
|
|
223
|
-
const used = Math.max(0, Math.round(usageTokens(usage)))
|
|
224
|
-
const totalCells = Math.max(1, width)
|
|
225
|
-
const safeTotal = Math.max(1, total)
|
|
226
|
-
const clampedUsed = Math.max(0, Math.min(safeTotal, used))
|
|
227
|
-
const usedCells = clampedUsed > 0 ? Math.max(1, Math.round((clampedUsed / safeTotal) * totalCells)) : 0
|
|
228
|
-
const occupied = Math.min(totalCells, usedCells)
|
|
229
|
-
return colorOccupiedContext(used, total, "█".repeat(occupied)) + colorEmptyContext("░".repeat(totalCells - occupied))
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function renderDcpContextBar(_theme: Theme | undefined, details: Pick<DcpCompressionVisualDetails, "contextTokens" | "contextWindow" | "contextPercent" | "tokensSaved">, width: number): string {
|
|
233
|
-
const parts = contextParts(details)
|
|
234
|
-
const cells = distributeBar(width, parts)
|
|
235
|
-
return [
|
|
236
|
-
colorOccupiedContext(parts.used, parts.total, "█".repeat(cells.used)),
|
|
237
|
-
colorFreedContext("░".repeat(cells.freed)),
|
|
238
|
-
colorEmptyContext("░".repeat(cells.empty)),
|
|
239
|
-
].join("")
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function formatDcpCompressionMessageText(
|
|
243
|
-
details: DcpCompressionVisualDetails,
|
|
244
|
-
theme: Theme | undefined,
|
|
245
|
-
expanded: boolean,
|
|
246
|
-
): string {
|
|
247
|
-
const ratio = compressionContextScale(details)
|
|
248
|
-
const bar = renderDcpContextBar(theme, details, expanded ? 24 : 18)
|
|
249
|
-
return [
|
|
250
|
-
fg(theme, "success", "✓"),
|
|
251
|
-
fg(theme, "accent", "compressed"),
|
|
252
|
-
fg(theme, "success", `saved ${formatDcpTokenCount(details.tokensSaved)}`),
|
|
253
|
-
bar,
|
|
254
|
-
ratio ? fg(theme, "customMessageText", ratio) : undefined,
|
|
255
|
-
details.topic ? fg(theme, "dim", `· ${truncateLabel(details.topic, expanded ? 64 : 36)}`) : undefined,
|
|
256
|
-
].filter(Boolean).join(fg(theme, "dim", " "))
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function normalizeDcpCompressionDetails(content: unknown, details: unknown): DcpCompressionVisualDetails {
|
|
260
|
-
const raw = details && typeof details === "object" ? details as Partial<DcpCompressionVisualDetails> : {}
|
|
261
|
-
const topic = typeof raw.topic === "string"
|
|
262
|
-
? raw.topic
|
|
263
|
-
: typeof content === "string"
|
|
264
|
-
? content
|
|
265
|
-
: "Compressed context"
|
|
266
|
-
const blockIds = Array.isArray(raw.blockIds) ? raw.blockIds.filter((id): id is number => typeof id === "number") : []
|
|
267
|
-
const ranges = typeof raw.ranges === "number" ? raw.ranges : 0
|
|
268
|
-
const messages = typeof raw.messages === "number" ? raw.messages : 0
|
|
269
|
-
return {
|
|
270
|
-
topic,
|
|
271
|
-
blockIds,
|
|
272
|
-
ranges,
|
|
273
|
-
messages,
|
|
274
|
-
itemCount: typeof raw.itemCount === "number" ? raw.itemCount : ranges + messages,
|
|
275
|
-
totalSummaryTokens: typeof raw.totalSummaryTokens === "number" ? raw.totalSummaryTokens : 0,
|
|
276
|
-
activeBlocks: typeof raw.activeBlocks === "number" ? raw.activeBlocks : blockIds.length,
|
|
277
|
-
totalBlocks: typeof raw.totalBlocks === "number" ? raw.totalBlocks : blockIds.length,
|
|
278
|
-
prunedTools: typeof raw.prunedTools === "number" ? raw.prunedTools : 0,
|
|
279
|
-
tokensSaved: typeof raw.tokensSaved === "number" ? raw.tokensSaved : 0,
|
|
280
|
-
contextTokens: typeof raw.contextTokens === "number" || raw.contextTokens === null ? raw.contextTokens : undefined,
|
|
281
|
-
contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : undefined,
|
|
282
|
-
contextPercent: typeof raw.contextPercent === "number" || raw.contextPercent === null ? raw.contextPercent : undefined,
|
|
283
|
-
skippedMessages: typeof raw.skippedMessages === "number" ? raw.skippedMessages : undefined,
|
|
284
|
-
skippedMessageIssues: Array.isArray(raw.skippedMessageIssues)
|
|
285
|
-
? raw.skippedMessageIssues.filter((issue): issue is string => typeof issue === "string")
|
|
286
|
-
: undefined,
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export class DcpUiController {
|
|
291
|
-
constructor(_state: DcpState) {}
|
|
292
|
-
|
|
293
|
-
setUICtx(_ctx: ExtensionUIContext): void {
|
|
294
|
-
// DCP intentionally does not render extension UI/status/toasts. The only
|
|
295
|
-
// user-visible DCP output should be the `compress` tool result itself.
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
update(_ctx?: ExtensionContext): void {
|
|
299
|
-
// No-op by design; reminders are injected into model context, not rendered.
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
dispose(): void {
|
|
303
|
-
// No-op by design.
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export const __formatDcpCompressionMessageTextForTest = formatDcpCompressionMessageText
|
|
308
|
-
export const __renderDcpStatusLabelForTest = renderDcpStatusLabel
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|