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.
Files changed (37) hide show
  1. package/dist/app/app.d.ts +0 -1
  2. package/dist/app/app.js +1 -4
  3. package/dist/app/conversation-entry-renderer.d.ts +0 -1
  4. package/dist/app/conversation-entry-renderer.js +2 -6
  5. package/dist/app/conversation-tool-renderer.js +2 -3
  6. package/dist/app/conversation-viewport.d.ts +0 -1
  7. package/dist/app/conversation-viewport.js +0 -1
  8. package/dist/config.d.ts +0 -3
  9. package/dist/config.js +0 -79
  10. package/dist/default-pix-config.js +2 -2
  11. package/dist/markdown-format.js +18 -1
  12. package/external/pi-tools-suite/README.md +4 -4
  13. package/external/pi-tools-suite/licenses/opencode-dynamic-context-pruning-AGPL-3.0.txt +619 -0
  14. package/external/pi-tools-suite/package.json +1 -1
  15. package/external/pi-tools-suite/src/config.ts +5 -1
  16. package/external/pi-tools-suite/src/{compress → dcp}/config.ts +10 -70
  17. package/external/pi-tools-suite/src/{compress → dcp}/index.ts +16 -66
  18. package/external/pi-tools-suite/src/dcp/ui.ts +45 -0
  19. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -2
  20. package/external/pi-tools-suite/src/index.ts +1 -1
  21. package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
  22. package/package.json +1 -1
  23. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +0 -498
  24. package/external/pi-tools-suite/src/compress/ui.ts +0 -308
  25. /package/external/pi-tools-suite/src/{compress → dcp}/commands.ts +0 -0
  26. /package/external/pi-tools-suite/src/{compress → dcp}/compress-tool.ts +0 -0
  27. /package/external/pi-tools-suite/src/{compress → dcp}/compression-blocks.ts +0 -0
  28. /package/external/pi-tools-suite/src/{compress → dcp}/prompts.ts +0 -0
  29. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-candidates.ts +0 -0
  30. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-compression-blocks.ts +0 -0
  31. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-message-ids.ts +0 -0
  32. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-metadata.ts +0 -0
  33. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-nudge.ts +0 -0
  34. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-tools.ts +0 -0
  35. /package/external/pi-tools-suite/src/{compress → dcp}/pruner-types.ts +0 -0
  36. /package/external/pi-tools-suite/src/{compress → dcp}/pruner.ts +0 -0
  37. /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