pi-tool-duration 0.1.4 → 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 +20 -0
- package/README.md +14 -8
- package/extensions/tool-duration/index.ts +29 -36
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1] - 2026-08-09
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Use Pi's error status instead of guessing from arbitrary result text or metadata, eliminating false duration markers on successful tools.
|
|
8
|
+
- Append duration markers to finalized model-visible messages so the TUI keeps its single native timer, missing-content tools stay safe, blocked preflight failures are covered, and marker-like tool output is still measured.
|
|
9
|
+
- Keep the long threshold flag readable in `pi --help` and declare the documented Pi 0.84.0 host floor in package metadata.
|
|
10
|
+
- Keep the integration-test dependency lockfile installable from the public npm registry.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Added real Pi CLI integration coverage over a local HTTP model transport for timing, flags, failures, parallel calls, lifecycle cleanup, and result preservation.
|
|
15
|
+
|
|
16
|
+
## [0.2.0] - 2026-08-06
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Requires Pi 0.84.0 or later and refreshes the development lock against the released Pi 0.84.0 packages.
|
|
21
|
+
- Verified the tool timing hooks and lifecycle cleanup against Pi 0.84.0's extension events, emitted types, and runtime implementation.
|
|
22
|
+
|
|
3
23
|
## [0.1.4] - 2026-07-16
|
|
4
24
|
|
|
5
25
|
- Refreshed the local Pi development lock and validation baseline to Pi 0.80.9; the unified model runtime/provider changes do not affect tool timing.
|
package/README.md
CHANGED
|
@@ -13,16 +13,20 @@ 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
|
|
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 the finalized model-visible tool message:
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
[duration: 5.0s]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Scope: Pi tools that emit
|
|
22
|
+
This leaves Pi's TUI output unchanged, so built-in timing such as `Took 5.0s` is not duplicated. 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
|
+
|
|
24
|
+
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.
|
|
23
25
|
|
|
24
26
|
## Install
|
|
25
27
|
|
|
28
|
+
Requires Pi 0.84.0 or later.
|
|
29
|
+
|
|
26
30
|
```bash
|
|
27
31
|
pi install . # local, global settings
|
|
28
32
|
pi install -l --approve . # local, project settings
|
|
@@ -34,21 +38,23 @@ pi install npm:pi-tool-duration # after npm publish
|
|
|
34
38
|
From this repo:
|
|
35
39
|
|
|
36
40
|
```bash
|
|
37
|
-
pi -e .
|
|
41
|
+
pi --no-extensions -e .
|
|
38
42
|
# or
|
|
39
|
-
pi -e ./extensions/tool-duration/index.ts
|
|
43
|
+
pi --no-extensions -e ./extensions/tool-duration/index.ts
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
`--no-extensions` prevents a duplicate flag conflict when another copy is already installed.
|
|
47
|
+
|
|
42
48
|
## Configure
|
|
43
49
|
|
|
44
50
|
Default threshold: `1000` ms.
|
|
45
51
|
|
|
46
52
|
```bash
|
|
47
|
-
PI_TOOL_DURATION_THRESHOLD_MS=0 pi -e .
|
|
48
|
-
pi -e . --tool-duration-threshold-ms 500
|
|
53
|
+
PI_TOOL_DURATION_THRESHOLD_MS=0 pi --no-extensions -e . # annotate every tool result
|
|
54
|
+
pi --no-extensions -e . --tool-duration-threshold-ms 500 # annotate tools taking >= 500ms
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
Invalid
|
|
57
|
+
Invalid values are ignored. An invalid CLI value falls through to the environment value; an invalid environment value falls back to the default.
|
|
52
58
|
|
|
53
59
|
## Verify
|
|
54
60
|
|
|
@@ -65,7 +71,7 @@ hi
|
|
|
65
71
|
[duration: 5.0s]
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
A fast successful
|
|
74
|
+
A fast successful tool below the threshold stays unchanged. A failed tool result delivered to the model is always annotated, even below the threshold.
|
|
69
75
|
|
|
70
76
|
## License
|
|
71
77
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-tool-duration
|
|
3
3
|
*
|
|
4
|
-
* Appends `[duration: Xs]` to slow tool
|
|
5
|
-
* call actually took. pi already measures this for the TUI
|
|
6
|
-
* model does not see that timing.
|
|
4
|
+
* Appends `[duration: Xs]` to slow or failed tool messages so the model sees
|
|
5
|
+
* how long a call actually took. pi already measures this for the TUI
|
|
6
|
+
* ("Took Xs") but the model does not see that timing.
|
|
7
7
|
*/
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
const DEFAULT_THRESHOLD_MS = 1000;
|
|
11
|
-
const DURATION_RE = /^\[duration: \d+(?:\.\d+)?s\]$/;
|
|
12
11
|
|
|
13
12
|
function parseThreshold(value: unknown): number | undefined {
|
|
14
13
|
if (typeof value !== "string" && typeof value !== "number") return undefined;
|
|
@@ -25,56 +24,50 @@ function thresholdMs(pi: ExtensionAPI): number {
|
|
|
25
24
|
);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
function alreadyAnnotated(content: Array<{ type: string; text?: string }>): boolean {
|
|
29
|
-
const last = content.at(-1);
|
|
30
|
-
return last?.type === "text" && typeof last.text === "string" && DURATION_RE.test(last.text);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function nonZeroExitCode(event: { content: Array<{ type: string; text?: string }>; details?: unknown }): boolean {
|
|
34
|
-
const details = event.details as Record<string, unknown> | null;
|
|
35
|
-
const code = details && Number(details.exitCode ?? details.code ?? details.status);
|
|
36
|
-
if (code !== null && Number.isFinite(code)) return code !== 0;
|
|
37
|
-
|
|
38
|
-
return event.content.some(
|
|
39
|
-
(item) =>
|
|
40
|
-
item.type === "text" &&
|
|
41
|
-
typeof item.text === "string" &&
|
|
42
|
-
/(?:exited with code|exit code)\s+(-?[1-9]\d*)\b/i.test(item.text),
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
27
|
export default function (pi: ExtensionAPI) {
|
|
47
28
|
const starts = new Map<string, number>();
|
|
29
|
+
const durations = new Map<string, string>();
|
|
48
30
|
|
|
49
31
|
pi.registerFlag("tool-duration-threshold-ms", {
|
|
50
|
-
|
|
32
|
+
// Pi's help formatter adds no separator once this long flag exceeds its 30-column width.
|
|
33
|
+
description: " Minimum elapsed milliseconds before appending a model-visible duration",
|
|
51
34
|
type: "string",
|
|
52
35
|
});
|
|
53
36
|
|
|
54
|
-
pi.on("tool_execution_start",
|
|
37
|
+
pi.on("tool_execution_start", (event) => {
|
|
55
38
|
starts.set(event.toolCallId, performance.now());
|
|
56
39
|
});
|
|
57
40
|
|
|
58
|
-
pi.on("
|
|
41
|
+
pi.on("tool_execution_end", (event) => {
|
|
59
42
|
const startedAt = starts.get(event.toolCallId);
|
|
60
43
|
starts.delete(event.toolCallId);
|
|
61
44
|
if (startedAt === undefined) return;
|
|
62
45
|
|
|
63
46
|
const ms = performance.now() - startedAt;
|
|
64
|
-
if (!
|
|
65
|
-
|
|
47
|
+
if (!event.isError && ms < thresholdMs(pi)) return;
|
|
48
|
+
durations.set(event.toolCallId, `[duration: ${(ms / 1000).toFixed(1)}s]`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
pi.on("message_end", (event) => {
|
|
52
|
+
if (event.message.role !== "toolResult") return;
|
|
53
|
+
const duration = durations.get(event.message.toolCallId);
|
|
54
|
+
durations.delete(event.message.toolCallId);
|
|
55
|
+
if (!duration) return;
|
|
66
56
|
|
|
67
57
|
return {
|
|
68
|
-
|
|
69
|
-
...event.
|
|
70
|
-
{ type: "text" as const, text:
|
|
71
|
-
|
|
58
|
+
message: {
|
|
59
|
+
...event.message,
|
|
60
|
+
content: [...event.message.content, { type: "text" as const, text: duration }],
|
|
61
|
+
},
|
|
72
62
|
};
|
|
73
63
|
});
|
|
74
64
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pi.on("
|
|
65
|
+
const clearTimings = () => {
|
|
66
|
+
starts.clear();
|
|
67
|
+
durations.clear();
|
|
68
|
+
};
|
|
69
|
+
pi.on("session_start", clearTimings);
|
|
70
|
+
pi.on("session_shutdown", clearTimings);
|
|
71
|
+
pi.on("agent_end", clearTimings);
|
|
72
|
+
pi.on("agent_settled", clearTimings);
|
|
80
73
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-tool-duration",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Append model-visible durations to slow Pi tool results",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@earendil-works/pi-coding-agent": "
|
|
43
|
+
"@earendil-works/pi-coding-agent": "0.84.0",
|
|
44
|
+
"typebox": "1.3.7",
|
|
44
45
|
"typescript": "^5.9.3"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=0.84.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependenciesMeta": {
|
|
50
51
|
"@earendil-works/pi-coding-agent": {
|