@quandev104/pi-style 0.2.0 → 0.2.1
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/CHANGELOG.md +6 -0
- package/dist/extensions/pi-style.js +691 -296
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/index.ts +3 -2
- package/extension-src/pi-style/app/runtime.ts +72 -84
- package/extension-src/pi-style/app/snapshot.ts +41 -2
- package/extension-src/pi-style/features/editor/index.ts +80 -61
- package/extension-src/pi-style/features/messages/index.ts +194 -56
- package/extension-src/pi-style/features/tools/bash-execution.ts +12 -1
- package/extension-src/pi-style/features/tools/boxed/bash.ts +99 -31
- package/extension-src/pi-style/features/tools/boxed/batch.ts +44 -10
- package/extension-src/pi-style/features/tools/boxed/edit.ts +30 -15
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +31 -15
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -14
- package/extension-src/pi-style/features/tools/boxed/shared.ts +25 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +1 -1
- package/extension-src/pi-style/pi/index.ts +26 -13
- package/extension-src/pi-style/pi/session-usage.ts +204 -21
- package/extension-src/pi-style/shared/box.ts +13 -2
- package/package.json +1 -1
|
@@ -18,27 +18,197 @@ interface UsageTotals {
|
|
|
18
18
|
cost: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
interface UsageContribution extends UsageTotals {
|
|
22
|
+
sawUsage: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface SessionUsageCacheState {
|
|
26
|
+
processedLength: number;
|
|
27
|
+
lastEntryId: string | undefined;
|
|
28
|
+
lastEntryRef: SessionEntry | undefined;
|
|
29
|
+
lastContribution: UsageContribution;
|
|
30
|
+
totals: UsageTotals;
|
|
31
|
+
cached: UsageSnapshot | undefined;
|
|
32
|
+
scannedEntries: number;
|
|
33
|
+
cacheHits: number;
|
|
34
|
+
rebuilds: number;
|
|
35
|
+
tailRefreshes: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SessionUsageCacheStats {
|
|
39
|
+
processedLength: number;
|
|
40
|
+
scannedEntries: number;
|
|
41
|
+
cacheHits: number;
|
|
42
|
+
rebuilds: number;
|
|
43
|
+
tailRefreshes: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let usageCache = new WeakMap<SessionUsageSource, SessionUsageCacheState>();
|
|
47
|
+
|
|
21
48
|
/**
|
|
22
|
-
* Aggregate cumulative token/cost usage from
|
|
23
|
-
* Pi's native footer
|
|
24
|
-
*
|
|
49
|
+
* Aggregate cumulative token/cost usage from finalized session entries,
|
|
50
|
+
* mirroring Pi's native footer. The result is cached incrementally so repeated
|
|
51
|
+
* reads only scan appended entries and refresh the finalized tail entry when it
|
|
52
|
+
* changes in place.
|
|
25
53
|
*/
|
|
26
54
|
export function usageFromSession(session: SessionUsageSource): UsageSnapshot | undefined {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
const entries = session.getEntries();
|
|
56
|
+
const state = usageCache.get(session) ?? createState();
|
|
57
|
+
usageCache.set(session, state);
|
|
58
|
+
|
|
59
|
+
if (entries.length === 0) {
|
|
60
|
+
state.cacheHits++;
|
|
61
|
+
state.processedLength = 0;
|
|
62
|
+
state.lastEntryId = undefined;
|
|
63
|
+
state.lastEntryRef = undefined;
|
|
64
|
+
state.lastContribution = emptyContribution();
|
|
65
|
+
state.totals = emptyTotals();
|
|
66
|
+
state.cached = undefined;
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (state.processedLength === 0) {
|
|
71
|
+
rebuildFrom(entries, state);
|
|
72
|
+
return state.cached;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (entries.length < state.processedLength) {
|
|
76
|
+
rebuildFrom(entries, state);
|
|
77
|
+
return state.cached;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const currentLast = entries.at(-1);
|
|
81
|
+
if (!currentLast) {
|
|
82
|
+
state.cacheHits++;
|
|
83
|
+
state.cached = undefined;
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (entries.length === state.processedLength) {
|
|
88
|
+
if (currentLast.id !== state.lastEntryId) {
|
|
89
|
+
rebuildFrom(entries, state);
|
|
90
|
+
return state.cached;
|
|
39
91
|
}
|
|
92
|
+
if (currentLast !== state.lastEntryRef) refreshTailEntry(currentLast, state);
|
|
93
|
+
else state.cacheHits++;
|
|
94
|
+
return state.cached;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const previousLast = entries[state.processedLength - 1];
|
|
98
|
+
if (!previousLast || previousLast.id !== state.lastEntryId) {
|
|
99
|
+
rebuildFrom(entries, state);
|
|
100
|
+
return state.cached;
|
|
40
101
|
}
|
|
41
|
-
if (
|
|
102
|
+
if (previousLast !== state.lastEntryRef) refreshTailEntry(previousLast, state);
|
|
103
|
+
for (const entry of entries.slice(state.processedLength)) appendEntry(entry, state);
|
|
104
|
+
return state.cached;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function resetUsageFromSessionCache(session?: SessionUsageSource): void {
|
|
108
|
+
if (session) {
|
|
109
|
+
usageCache.delete(session);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
usageCache = new WeakMap<SessionUsageSource, SessionUsageCacheState>();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getUsageFromSessionCacheStats(session: SessionUsageSource): SessionUsageCacheStats {
|
|
116
|
+
const state = usageCache.get(session) ?? createState();
|
|
117
|
+
if (!usageCache.has(session)) usageCache.set(session, state);
|
|
118
|
+
return {
|
|
119
|
+
processedLength: state.processedLength,
|
|
120
|
+
scannedEntries: state.scannedEntries,
|
|
121
|
+
cacheHits: state.cacheHits,
|
|
122
|
+
rebuilds: state.rebuilds,
|
|
123
|
+
tailRefreshes: state.tailRefreshes,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createState(): SessionUsageCacheState {
|
|
128
|
+
return {
|
|
129
|
+
processedLength: 0,
|
|
130
|
+
lastEntryId: undefined,
|
|
131
|
+
lastEntryRef: undefined,
|
|
132
|
+
lastContribution: emptyContribution(),
|
|
133
|
+
totals: emptyTotals(),
|
|
134
|
+
cached: undefined,
|
|
135
|
+
scannedEntries: 0,
|
|
136
|
+
cacheHits: 0,
|
|
137
|
+
rebuilds: 0,
|
|
138
|
+
tailRefreshes: 0,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function rebuildFrom(entries: readonly SessionEntry[], state: SessionUsageCacheState): void {
|
|
143
|
+
state.rebuilds++;
|
|
144
|
+
state.processedLength = 0;
|
|
145
|
+
state.lastEntryId = undefined;
|
|
146
|
+
state.lastEntryRef = undefined;
|
|
147
|
+
state.lastContribution = emptyContribution();
|
|
148
|
+
state.totals = emptyTotals();
|
|
149
|
+
state.cached = undefined;
|
|
150
|
+
for (const entry of entries) appendEntry(entry, state);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function refreshTailEntry(entry: SessionEntry, state: SessionUsageCacheState): void {
|
|
154
|
+
state.tailRefreshes++;
|
|
155
|
+
subtractContribution(state.totals, state.lastContribution);
|
|
156
|
+
const contribution = usageContribution(entry);
|
|
157
|
+
addContribution(state.totals, contribution);
|
|
158
|
+
state.lastEntryId = entry.id;
|
|
159
|
+
state.lastEntryRef = entry;
|
|
160
|
+
state.lastContribution = contribution;
|
|
161
|
+
state.cached = snapshotFromTotals(state.totals);
|
|
162
|
+
state.scannedEntries++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function appendEntry(entry: SessionEntry, state: SessionUsageCacheState): void {
|
|
166
|
+
const contribution = usageContribution(entry);
|
|
167
|
+
addContribution(state.totals, contribution);
|
|
168
|
+
state.processedLength++;
|
|
169
|
+
state.lastEntryId = entry.id;
|
|
170
|
+
state.lastEntryRef = entry;
|
|
171
|
+
state.lastContribution = contribution;
|
|
172
|
+
state.cached = snapshotFromTotals(state.totals);
|
|
173
|
+
state.scannedEntries++;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function usageContribution(entry: SessionEntry): UsageContribution {
|
|
177
|
+
if (entry.type === "message") {
|
|
178
|
+
if (entry.message.role !== "assistant" && entry.message.role !== "toolResult") return emptyContribution();
|
|
179
|
+
const usage = "usage" in entry.message ? entry.message.usage : undefined;
|
|
180
|
+
if (!usage) return emptyContribution();
|
|
181
|
+
return {
|
|
182
|
+
input: usage.input,
|
|
183
|
+
output: usage.output,
|
|
184
|
+
cacheRead: usage.cacheRead,
|
|
185
|
+
cacheWrite: usage.cacheWrite,
|
|
186
|
+
cost: usage.cost.total,
|
|
187
|
+
sawUsage: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
|
|
191
|
+
return {
|
|
192
|
+
input: entry.usage.input,
|
|
193
|
+
output: entry.usage.output,
|
|
194
|
+
cacheRead: entry.usage.cacheRead,
|
|
195
|
+
cacheWrite: entry.usage.cacheWrite,
|
|
196
|
+
cost: entry.usage.cost.total,
|
|
197
|
+
sawUsage: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return emptyContribution();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function snapshotFromTotals(totals: UsageTotals): UsageSnapshot | undefined {
|
|
204
|
+
if (
|
|
205
|
+
totals.input === 0 &&
|
|
206
|
+
totals.output === 0 &&
|
|
207
|
+
totals.cacheRead === 0 &&
|
|
208
|
+
totals.cacheWrite === 0 &&
|
|
209
|
+
totals.cost === 0
|
|
210
|
+
)
|
|
211
|
+
return undefined;
|
|
42
212
|
return {
|
|
43
213
|
inputTokens: totals.input,
|
|
44
214
|
outputTokens: totals.output,
|
|
@@ -50,13 +220,26 @@ export function usageFromSession(session: SessionUsageSource): UsageSnapshot | u
|
|
|
50
220
|
};
|
|
51
221
|
}
|
|
52
222
|
|
|
53
|
-
function
|
|
54
|
-
totals: UsageTotals,
|
|
55
|
-
usage: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: { total: number } },
|
|
56
|
-
): void {
|
|
223
|
+
function addContribution(totals: UsageTotals, usage: UsageContribution): void {
|
|
57
224
|
totals.input += usage.input;
|
|
58
225
|
totals.output += usage.output;
|
|
59
226
|
totals.cacheRead += usage.cacheRead;
|
|
60
227
|
totals.cacheWrite += usage.cacheWrite;
|
|
61
|
-
totals.cost += usage.cost
|
|
228
|
+
totals.cost += usage.cost;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function subtractContribution(totals: UsageTotals, usage: UsageContribution): void {
|
|
232
|
+
totals.input -= usage.input;
|
|
233
|
+
totals.output -= usage.output;
|
|
234
|
+
totals.cacheRead -= usage.cacheRead;
|
|
235
|
+
totals.cacheWrite -= usage.cacheWrite;
|
|
236
|
+
totals.cost -= usage.cost;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function emptyTotals(): UsageTotals {
|
|
240
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function emptyContribution(): UsageContribution {
|
|
244
|
+
return { ...emptyTotals(), sawUsage: false };
|
|
62
245
|
}
|
|
@@ -36,6 +36,17 @@ export interface BoxTheme {
|
|
|
36
36
|
getColorMode?(): string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const THEME_CACHE_KEYS = new WeakMap<object, number>();
|
|
40
|
+
let nextThemeCacheKey = 1;
|
|
41
|
+
|
|
42
|
+
export function themeCacheKey(theme: object): number {
|
|
43
|
+
const cached = THEME_CACHE_KEYS.get(theme);
|
|
44
|
+
if (cached !== undefined) return cached;
|
|
45
|
+
const created = nextThemeCacheKey++;
|
|
46
|
+
THEME_CACHE_KEYS.set(theme, created);
|
|
47
|
+
return created;
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export interface BoxedRenderOptions {
|
|
40
51
|
widthKey?: string;
|
|
41
52
|
/** Detail embedded in the top-border title after the tool name (e.g. the path). */
|
|
@@ -384,10 +395,10 @@ export function dimLine(text: string): string {
|
|
|
384
395
|
return `\x1b[2m${text}\x1b[22m`;
|
|
385
396
|
}
|
|
386
397
|
|
|
387
|
-
function boxText(
|
|
398
|
+
function boxText(_theme: BoxTheme, text: string): string {
|
|
388
399
|
return dimLine(text);
|
|
389
400
|
}
|
|
390
|
-
function boxFrameText(
|
|
401
|
+
function boxFrameText(_theme: BoxTheme, text: string): string {
|
|
391
402
|
return dimLine(text);
|
|
392
403
|
}
|
|
393
404
|
|