pi-compact-transcript 0.1.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/README.md +3 -2
- package/extensions/compact-transcript.ts +112 -38
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
A compact transcript extension for [pi](https://pi.dev):
|
|
4
4
|
|
|
5
5
|
- Collapses hidden thinking blocks into `Thinking...` and consecutive thinking blocks into `Thinking... (Nx)`.
|
|
6
|
+
- Adds a short, visible next-step summary to collapsed thinking rows without exposing raw chain-of-thought.
|
|
6
7
|
- Collapses built-in tool calls/results into one-line previews.
|
|
7
|
-
- Consecutive tool uses are summarized as `Used N tools <latest tool preview>`.
|
|
8
|
+
- Consecutive tool uses in the same agent run are summarized as `Used N tools <latest tool preview>`.
|
|
8
9
|
- Minimizes vertical space so long agent runs do not scroll away as quickly.
|
|
9
10
|
|
|
10
11
|
## Install from GitHub
|
|
@@ -50,4 +51,4 @@ Set these in `~/.pi/agent/settings.json`, or use `/settings` in pi.
|
|
|
50
51
|
|
|
51
52
|
This extension overrides the built-in tool renderers for `bash`, `read`, `write`, `edit`, `grep`, `find`, and `ls`. It delegates execution to pi's original built-in tools; only display is changed.
|
|
52
53
|
|
|
53
|
-
The consecutive-thinking collapse
|
|
54
|
+
The consecutive-thinking collapse and compact self-rendered tool rows use pi's current internal TUI components. If pi changes those internal paths in a future release, the extension falls back to the normal hidden-thinking/tool rendering behavior.
|
|
@@ -28,6 +28,9 @@ type ToolInfo = {
|
|
|
28
28
|
const BUILT_INS: BuiltInName[] = ["bash", "read", "write", "edit", "grep", "find", "ls"];
|
|
29
29
|
const LABEL = "Thinking...";
|
|
30
30
|
|
|
31
|
+
let currentTools: ToolInfo[] = [];
|
|
32
|
+
let toolsById = new Map<string, ToolInfo>();
|
|
33
|
+
|
|
31
34
|
const toolCache = new Map<string, ReturnType<typeof createBuiltInTools>>();
|
|
32
35
|
|
|
33
36
|
function createBuiltInTools(cwd: string) {
|
|
@@ -119,7 +122,61 @@ function resultPreview(result: any): string {
|
|
|
119
122
|
return `${lines.length} lines`;
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
function toolCallName(block: any): string {
|
|
126
|
+
return block?.name ?? block?.toolName ?? block?.tool_name ?? block?.tool?.name ?? "tool";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toolCallArgs(block: any): any {
|
|
130
|
+
return block?.args ?? block?.input ?? block?.arguments ?? block?.tool?.args ?? {};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resetToolRun() {
|
|
134
|
+
currentTools = [];
|
|
135
|
+
toolsById = new Map();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function upsertToolInfo(id: string, name: string, args: any, invalidate?: () => void): ToolInfo {
|
|
139
|
+
let info = toolsById.get(id);
|
|
140
|
+
if (!info) {
|
|
141
|
+
info = { id, name, args, preview: previewFor(name, args) };
|
|
142
|
+
toolsById.set(id, info);
|
|
143
|
+
if (!currentTools.some((tool) => tool.id === id)) currentTools.push(info);
|
|
144
|
+
}
|
|
145
|
+
info.name = name;
|
|
146
|
+
info.args = args;
|
|
147
|
+
info.preview = previewFor(name, args);
|
|
148
|
+
if (invalidate) info.invalidate = invalidate;
|
|
149
|
+
return info;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function compactToolLine(toolCallId: string, name: string, args: any, theme: any, invalidate?: () => void, result?: any): string {
|
|
153
|
+
const info = upsertToolInfo(toolCallId, name, args, invalidate);
|
|
154
|
+
const suffix = resultPreview(result);
|
|
155
|
+
if (suffix) info.result = suffix;
|
|
156
|
+
|
|
157
|
+
if (currentTools[currentTools.length - 1]?.id !== toolCallId) return "";
|
|
158
|
+
|
|
159
|
+
const details = info.result ? `${info.preview} {${oneLine(info.result)}}` : info.preview;
|
|
160
|
+
if (currentTools.length === 1) return theme.fg("muted", limitPlain(details));
|
|
161
|
+
const prefix = `Used ${currentTools.length} tools `;
|
|
162
|
+
return theme.fg("toolTitle", prefix) + theme.fg("muted", limitPlain(details, Math.max(20, (process.stdout.columns || 100) - prefix.length - 6)));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function nextStepSummary(content: any[], fromIndex: number): string {
|
|
166
|
+
const tools = content.slice(fromIndex + 1).filter((c: any) => c?.type === "toolCall");
|
|
167
|
+
if (tools.length === 0) return " Next, I’ll continue from this reasoning and respond with the result. I’ll keep the visible output brief.";
|
|
168
|
+
|
|
169
|
+
const first = tools[0];
|
|
170
|
+
const preview = limitPlain(previewFor(toolCallName(first), toolCallArgs(first)), 90);
|
|
171
|
+
if (tools.length === 1) {
|
|
172
|
+
return ` Next, I’ll use ${preview}. I’ll inspect the result and decide the next visible step from there.`;
|
|
173
|
+
}
|
|
174
|
+
const last = tools[tools.length - 1];
|
|
175
|
+
const lastPreview = limitPlain(previewFor(toolCallName(last), toolCallArgs(last)), 70);
|
|
176
|
+
return ` Next, I’ll run ${tools.length} tool calls, starting with ${preview}. I’ll use the latest result to continue, ending this batch around ${lastPreview}.`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function patchInternalRenderers() {
|
|
123
180
|
try {
|
|
124
181
|
const require = createRequire(import.meta.url);
|
|
125
182
|
const piMain = require.resolve("@earendil-works/pi-coding-agent");
|
|
@@ -127,7 +184,48 @@ async function patchAssistantThinkingRenderer() {
|
|
|
127
184
|
const assistantModule = await import(
|
|
128
185
|
pathToFileURL(join(distDir, "modes/interactive/components/assistant-message.js")).href
|
|
129
186
|
);
|
|
187
|
+
const toolExecutionModule = await import(
|
|
188
|
+
pathToFileURL(join(distDir, "modes/interactive/components/tool-execution.js")).href
|
|
189
|
+
);
|
|
130
190
|
const themeModule = await import(pathToFileURL(join(distDir, "modes/interactive/theme/theme.js")).href);
|
|
191
|
+
|
|
192
|
+
const ToolExecutionComponent = toolExecutionModule.ToolExecutionComponent;
|
|
193
|
+
if (ToolExecutionComponent?.prototype && !ToolExecutionComponent.prototype.__compactTranscriptPatched) {
|
|
194
|
+
const originalRender = ToolExecutionComponent.prototype.render;
|
|
195
|
+
ToolExecutionComponent.prototype.updateDisplay = function () {
|
|
196
|
+
this.__compactTranscriptForceSelf = true;
|
|
197
|
+
this.hideComponent = false;
|
|
198
|
+
this.selfRenderContainer.clear();
|
|
199
|
+
this.imageComponents = [];
|
|
200
|
+
this.imageSpacers = [];
|
|
201
|
+
|
|
202
|
+
const line = compactToolLine(
|
|
203
|
+
this.toolCallId,
|
|
204
|
+
this.toolName,
|
|
205
|
+
this.args,
|
|
206
|
+
themeModule.theme,
|
|
207
|
+
() => {
|
|
208
|
+
this.invalidate();
|
|
209
|
+
this.ui?.requestRender?.();
|
|
210
|
+
},
|
|
211
|
+
this.result,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (!line) {
|
|
215
|
+
this.hideComponent = true;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.selfRenderContainer.addChild(new Text(line, 0, 0));
|
|
220
|
+
};
|
|
221
|
+
ToolExecutionComponent.prototype.render = function (width: number) {
|
|
222
|
+
if (this.hideComponent) return [];
|
|
223
|
+
if (this.__compactTranscriptForceSelf) return this.selfRenderContainer.render(width);
|
|
224
|
+
return originalRender.call(this, width);
|
|
225
|
+
};
|
|
226
|
+
ToolExecutionComponent.prototype.__compactTranscriptPatched = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
131
229
|
const Component = assistantModule.AssistantMessageComponent;
|
|
132
230
|
if (!Component?.prototype || Component.prototype.__compactTranscriptPatched) return;
|
|
133
231
|
const original = Component.prototype.updateContent;
|
|
@@ -161,8 +259,9 @@ async function patchAssistantThinkingRenderer() {
|
|
|
161
259
|
i += count - 1;
|
|
162
260
|
|
|
163
261
|
const label = count > 1 ? `${LABEL} (${count}x)` : LABEL;
|
|
262
|
+
const summary = nextStepSummary(message.content, i);
|
|
164
263
|
this.contentContainer.addChild(
|
|
165
|
-
new Text(themeModule.theme.italic(themeModule.theme.fg("thinkingText", label)), this.outputPad, 0),
|
|
264
|
+
new Text(themeModule.theme.italic(themeModule.theme.fg("thinkingText", limitPlain(`${label}${summary}`))), this.outputPad, 0),
|
|
166
265
|
);
|
|
167
266
|
|
|
168
267
|
const hasVisibleAfter = message.content
|
|
@@ -180,9 +279,7 @@ async function patchAssistantThinkingRenderer() {
|
|
|
180
279
|
}
|
|
181
280
|
|
|
182
281
|
export default async function (pi: ExtensionAPI) {
|
|
183
|
-
await
|
|
184
|
-
let current: ToolInfo[] = [];
|
|
185
|
-
let byId = new Map<string, ToolInfo>();
|
|
282
|
+
await patchInternalRenderers();
|
|
186
283
|
let consecutiveThinking = 0;
|
|
187
284
|
|
|
188
285
|
function thinkingLabel() {
|
|
@@ -190,8 +287,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
190
287
|
}
|
|
191
288
|
|
|
192
289
|
function resetTools() {
|
|
193
|
-
|
|
194
|
-
byId = new Map();
|
|
290
|
+
resetToolRun();
|
|
195
291
|
}
|
|
196
292
|
|
|
197
293
|
function applyThinkingLabel(ctx: ExtensionContext) {
|
|
@@ -203,8 +299,11 @@ export default async function (pi: ExtensionAPI) {
|
|
|
203
299
|
ctx.ui.setWorkingMessage(LABEL);
|
|
204
300
|
});
|
|
205
301
|
|
|
206
|
-
pi.on("
|
|
302
|
+
pi.on("agent_start", () => {
|
|
207
303
|
resetTools();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
208
307
|
consecutiveThinking = 0;
|
|
209
308
|
applyThinkingLabel(ctx);
|
|
210
309
|
});
|
|
@@ -219,49 +318,24 @@ export default async function (pi: ExtensionAPI) {
|
|
|
219
318
|
if (type && !type.startsWith("thinking_")) {
|
|
220
319
|
consecutiveThinking = 0;
|
|
221
320
|
applyThinkingLabel(ctx);
|
|
321
|
+
if (type === "text_delta" || type === "text_start") resetTools();
|
|
222
322
|
}
|
|
223
323
|
});
|
|
224
324
|
|
|
225
325
|
pi.on("tool_execution_start", (event: any) => {
|
|
226
|
-
const previous =
|
|
227
|
-
|
|
228
|
-
id: event.toolCallId,
|
|
229
|
-
name: event.toolName,
|
|
230
|
-
args: event.args,
|
|
231
|
-
preview: previewFor(event.toolName, event.args),
|
|
232
|
-
};
|
|
233
|
-
current.push(info);
|
|
234
|
-
byId.set(info.id, info);
|
|
326
|
+
const previous = currentTools[currentTools.length - 1];
|
|
327
|
+
upsertToolInfo(event.toolCallId, event.toolName, event.args);
|
|
235
328
|
previous?.invalidate?.();
|
|
236
329
|
});
|
|
237
330
|
|
|
238
331
|
pi.on("tool_execution_end", (event: any) => {
|
|
239
|
-
const info =
|
|
332
|
+
const info = toolsById.get(event.toolCallId);
|
|
240
333
|
if (!info) return;
|
|
241
334
|
const suffix = resultPreview(event.result);
|
|
242
335
|
if (suffix) info.result = suffix;
|
|
243
336
|
info.invalidate?.();
|
|
244
337
|
});
|
|
245
338
|
|
|
246
|
-
function compactLine(toolCallId: string, name: string, args: any, theme: any, context: any): string {
|
|
247
|
-
let info = byId.get(toolCallId);
|
|
248
|
-
if (!info) {
|
|
249
|
-
info = { id: toolCallId, name, args, preview: previewFor(name, args) };
|
|
250
|
-
byId.set(toolCallId, info);
|
|
251
|
-
if (!current.some((t) => t.id === toolCallId)) current.push(info);
|
|
252
|
-
}
|
|
253
|
-
info.invalidate = context.invalidate;
|
|
254
|
-
info.args = args;
|
|
255
|
-
info.preview = previewFor(name, args);
|
|
256
|
-
|
|
257
|
-
if (current[current.length - 1]?.id !== toolCallId) return "";
|
|
258
|
-
|
|
259
|
-
const details = info.result ? `${info.preview} {${oneLine(info.result)}}` : info.preview;
|
|
260
|
-
if (current.length === 1) return theme.fg("muted", limitPlain(details));
|
|
261
|
-
const prefix = `Used ${current.length} tools `;
|
|
262
|
-
return theme.fg("toolTitle", prefix) + theme.fg("muted", limitPlain(details, Math.max(20, (process.stdout.columns || 100) - prefix.length - 6)));
|
|
263
|
-
}
|
|
264
|
-
|
|
265
339
|
for (const name of BUILT_INS) {
|
|
266
340
|
const base = getBuiltInTools(process.cwd())[name] as any;
|
|
267
341
|
pi.registerTool({
|
|
@@ -275,7 +349,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
275
349
|
},
|
|
276
350
|
renderCall(args: any, theme: any, context: any) {
|
|
277
351
|
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
278
|
-
text.setText(
|
|
352
|
+
text.setText(compactToolLine(context.toolCallId, name, args, theme, context.invalidate));
|
|
279
353
|
return text;
|
|
280
354
|
},
|
|
281
355
|
renderResult(_result: any, _options: any, _theme: any, _context: any) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-compact-transcript",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "A compact transcript extension for pi: collapsed thinking and one-line tool previews.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "A compact transcript extension for pi: collapsed thinking summaries and one-line tool previews.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Alan Hagedorn <avhagedorn@gmail.com>",
|