pi-tool-duration 0.2.1 → 0.2.2
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 +12 -0
- package/README.md +7 -3
- package/extensions/tool-duration/index.ts +42 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.2] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Keep duration markers out of terminal tool output after reload, resume, and branch navigation by storing timing in hidden session entries and annotating only model-request copies.
|
|
8
|
+
- Preserve model-visible timings across restoration, compaction summaries, and retained compaction history without changing tool content or details.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Verify recorded output, reload, restored timing, branch isolation, and repeated model requests in regression tests.
|
|
13
|
+
- Run clean-install checks on Node 22.19 and Node 24 in GitHub Actions.
|
|
14
|
+
|
|
3
15
|
## [0.2.1] - 2026-08-09
|
|
4
16
|
|
|
5
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -13,13 +13,17 @@ Pi already shows tool timing in the TUI (`Took Xs`), but that timing is UI-only.
|
|
|
13
13
|
|
|
14
14
|
## How it works
|
|
15
15
|
|
|
16
|
-
The extension measures from Pi's `tool_execution_start` through `tool_execution_end`. When elapsed time is at or above the configured threshold, or Pi marks the result as failed, it appends one text block to
|
|
16
|
+
The extension measures from Pi's `tool_execution_start` through `tool_execution_end`. When elapsed time is at or above the configured threshold, or Pi marks the result as failed, it saves the timing in a hidden session entry. Before each model request, it appends one text block to that request's copy of the tool result:
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
[duration: 5.0s]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Compaction summaries also receive annotated copies of tool results. Recorded tool output stays unchanged, including after reload, resume, and branch navigation. Timings remain available to the model across those transitions without duplicating Pi's native TUI timing such as `Took 5.0s`.
|
|
23
|
+
|
|
24
|
+
Markers already saved as tool text by versions through 0.2.1 remain unchanged. They cannot be safely distinguished from genuine tool output with the same text.
|
|
25
|
+
|
|
26
|
+
Scope: Pi tools that emit tool execution events, including built-ins and extension tools. Direct `!` / `!!` shell commands and RPC `bash` command messages are not tool results and are not annotated.
|
|
23
27
|
|
|
24
28
|
Pi starts these timers during sequential tool-call preflight. In a parallel batch, a call's elapsed time can therefore include time spent preparing later siblings. This mirrors Pi's TUI timing.
|
|
25
29
|
|
|
@@ -30,7 +34,7 @@ Requires Pi 0.84.0 or later.
|
|
|
30
34
|
```bash
|
|
31
35
|
pi install . # local, global settings
|
|
32
36
|
pi install -l --approve . # local, project settings
|
|
33
|
-
pi install npm:pi-tool-duration #
|
|
37
|
+
pi install npm:pi-tool-duration # published package
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
## Try without installing
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* how long a call actually took. pi already measures this for the TUI
|
|
6
6
|
* ("Took Xs") but the model does not see that timing.
|
|
7
7
|
*/
|
|
8
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { ContextEvent, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
const DEFAULT_THRESHOLD_MS = 1000;
|
|
11
|
+
const TIMING_ENTRY = "pi-tool-duration";
|
|
12
|
+
|
|
13
|
+
type SavedTiming = { toolCallId: string; timestamp: number; duration: string };
|
|
11
14
|
|
|
12
15
|
function parseThreshold(value: unknown): number | undefined {
|
|
13
16
|
if (typeof value !== "string" && typeof value !== "number") return undefined;
|
|
@@ -24,6 +27,30 @@ function thresholdMs(pi: ExtensionAPI): number {
|
|
|
24
27
|
);
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
function withDurations(messages: ContextEvent["messages"], ctx: ExtensionContext) {
|
|
31
|
+
const saved = new Map<string, string>();
|
|
32
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
33
|
+
if (entry.type !== "custom" || entry.customType !== TIMING_ENTRY) continue;
|
|
34
|
+
const timing = entry.data as SavedTiming | undefined;
|
|
35
|
+
if (
|
|
36
|
+
typeof timing?.toolCallId !== "string" ||
|
|
37
|
+
typeof timing.timestamp !== "number" ||
|
|
38
|
+
typeof timing.duration !== "string"
|
|
39
|
+
) continue;
|
|
40
|
+
saved.set(`${timing.timestamp}:${timing.toolCallId}`, timing.duration);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return messages.map((message) => {
|
|
44
|
+
if (message.role !== "toolResult") return message;
|
|
45
|
+
const duration = saved.get(`${message.timestamp}:${message.toolCallId}`);
|
|
46
|
+
if (!duration) return message;
|
|
47
|
+
return {
|
|
48
|
+
...message,
|
|
49
|
+
content: [...message.content, { type: "text" as const, text: duration }],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
export default function (pi: ExtensionAPI) {
|
|
28
55
|
const starts = new Map<string, number>();
|
|
29
56
|
const durations = new Map<string, string>();
|
|
@@ -54,12 +81,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
54
81
|
durations.delete(event.message.toolCallId);
|
|
55
82
|
if (!duration) return;
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
84
|
+
// Keep timing out of recorded tool content: Pi also uses it to rebuild the TUI.
|
|
85
|
+
pi.appendEntry<SavedTiming>(TIMING_ENTRY, {
|
|
86
|
+
toolCallId: event.message.toolCallId,
|
|
87
|
+
timestamp: event.message.timestamp,
|
|
88
|
+
duration,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
pi.on("context", (event, ctx) => ({ messages: withDurations(event.messages, ctx) }));
|
|
93
|
+
|
|
94
|
+
pi.on("session_before_compact", ({ preparation }, ctx) => {
|
|
95
|
+
// Native summarization bypasses context hooks. Replace its inputs, never the saved messages.
|
|
96
|
+
preparation.messagesToSummarize = withDurations(preparation.messagesToSummarize, ctx);
|
|
97
|
+
preparation.turnPrefixMessages = withDurations(preparation.turnPrefixMessages, ctx);
|
|
63
98
|
});
|
|
64
99
|
|
|
65
100
|
const clearTimings = () => {
|