@taylorsatula/pi-timestamp 0.1.0 → 0.2.0
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 +38 -26
- package/extensions/timestamp.ts +39 -28
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,31 +4,37 @@ Timestamp injection extension for [Pi](https://pi.dev) — the model always know
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Hooks `before_provider_request` and appends
|
|
8
|
-
|
|
9
|
-
```jsonc
|
|
10
|
-
[
|
|
11
|
-
{
|
|
12
|
-
"role": "assistant",
|
|
13
|
-
"tool_calls": [
|
|
14
|
-
{
|
|
15
|
-
"id": "ts_1756220400000",
|
|
16
|
-
"type": "function",
|
|
17
|
-
"function": { "name": "__timestamp", "arguments": "{\"timestamp\":\"[3:00pm - 8/26/25]\"}" }
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{ "role": "tool", "tool_call_id": "ts_1756220400000", "content": "ok" }
|
|
22
|
-
]
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Injection happens only at **turn boundaries**, not on every provider call:
|
|
7
|
+
Hooks `before_provider_request` and appends the current local wall-clock time as plain text
|
|
8
|
+
to the tail of the last message:
|
|
26
9
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
```
|
|
11
|
+
They're injected poorly if they're that annoying.
|
|
12
|
+
[11:34pm - 9/1/26]
|
|
13
|
+
```
|
|
30
14
|
|
|
31
|
-
|
|
15
|
+
Injection happens only at **turn boundaries**, never mid-tool-loop:
|
|
16
|
+
|
|
17
|
+
- Last message is a **user message** → append the timestamp to its content.
|
|
18
|
+
- Last message is an **assistant message with text content** (a vocalization between tool
|
|
19
|
+
steps) → append to its content.
|
|
20
|
+
- Last message is an assistant message carrying `tool_calls`, or a tool result (mid-turn
|
|
21
|
+
churn) → skip.
|
|
22
|
+
|
|
23
|
+
## Why plain-text tail append (design notes)
|
|
24
|
+
|
|
25
|
+
- **Cache safety.** The tail of the message list is the only cache-safe injection point:
|
|
26
|
+
mutating the system prompt would invalidate the KV-cache prefix on every request; a
|
|
27
|
+
tail mutation re-processes only the last message. The system prompt is never touched.
|
|
28
|
+
- **No synthetic tool calls.** Appending after an assistant message that carries a
|
|
29
|
+
`tool_call` would require fabricating a tool-call pair (a call must be answered by its
|
|
30
|
+
result) — so those positions are simply never injected. In the positions that are
|
|
31
|
+
injected, no disguise is needed, so none is used. The model never appears to call a
|
|
32
|
+
nonexistent tool, and transcripts never render phantom calls.
|
|
33
|
+
- **Idempotent.** A previously appended tag is stripped before the fresh one is written,
|
|
34
|
+
so re-fires and retries cannot stack timestamps.
|
|
35
|
+
- **Content-shape aware.** String content concatenates directly; block-array content
|
|
36
|
+
(Anthropic-shaped) appends into or after the last text block; unknown shapes are left
|
|
37
|
+
untouched rather than guessed at.
|
|
32
38
|
|
|
33
39
|
## Installation
|
|
34
40
|
|
|
@@ -46,9 +52,15 @@ Or add to `~/.pi/agent/settings.json` under `packages`:
|
|
|
46
52
|
|
|
47
53
|
## Notes
|
|
48
54
|
|
|
49
|
-
- Timestamps use the machine's **local time**, formatted `h:mmam/pm - M/D/YY`
|
|
50
|
-
|
|
51
|
-
-
|
|
55
|
+
- Timestamps use the machine's **local time**, formatted `h:mmam/pm - M/D/YY`
|
|
56
|
+
(e.g. `[3:07pm - 8/26/25]`).
|
|
57
|
+
- No tools, commands, shortcuts, or settings are registered — the extension is invisible
|
|
58
|
+
except for the appended tag.
|
|
59
|
+
- The tag is added to the outgoing payload only; it is not persisted to the session
|
|
60
|
+
transcript.
|
|
61
|
+
- Version 0.2.0 changed the mechanism (plain-text tail append replacing the synthetic
|
|
62
|
+
tool-call pair). Sessions started before the upgrade may still show one old-style pair
|
|
63
|
+
until restart.
|
|
52
64
|
|
|
53
65
|
## Development
|
|
54
66
|
|
package/extensions/timestamp.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
// Cache-correct tail injection: mutating the system prompt would invalidate the
|
|
4
|
+
// KV-cache prefix on every request; the last message is the only cache-safe
|
|
5
|
+
// injection point, and only when no tool call is pending.
|
|
6
|
+
//
|
|
7
|
+
// Design:
|
|
8
|
+
// - Inject into user messages and text-only assistant vocalizations by
|
|
9
|
+
// appending plain text to the message content. No fabricated tool-call
|
|
10
|
+
// pair — the pair disguise is only structurally required in tool-call
|
|
11
|
+
// positions, which we never inject into.
|
|
12
|
+
// - Never inject after an assistant message carrying tool_calls (appending
|
|
13
|
+
// there would require wedging a pair between the call and its result) and
|
|
14
|
+
// never after tool results (mid-loop churn).
|
|
15
|
+
// - Idempotent per request: a previously appended tag is stripped before the
|
|
16
|
+
// fresh one is written.
|
|
17
|
+
|
|
18
|
+
const TS_STRIP = /\n\[\d{1,2}:\d{2}(am|pm) - \d{1,2}\/\d{1,2}\/\d{2}\]\s*$/;
|
|
19
|
+
|
|
3
20
|
function formatTimestamp(): string {
|
|
4
21
|
const now = new Date();
|
|
5
22
|
let hours = now.getHours();
|
|
@@ -13,52 +30,46 @@ function formatTimestamp(): string {
|
|
|
13
30
|
return `[${hours}:${String(minutes).padStart(2, "0")}${ampm} - ${month}/${day}/${year}]`;
|
|
14
31
|
}
|
|
15
32
|
|
|
16
|
-
function
|
|
33
|
+
function injectable(msgs: Array<Record<string, unknown>>): boolean {
|
|
17
34
|
const last = msgs[msgs.length - 1];
|
|
18
35
|
if (!last) return false;
|
|
19
36
|
|
|
20
|
-
// User message → inject
|
|
21
37
|
if (last.role === "user") return true;
|
|
22
38
|
|
|
23
|
-
// Assistant with text content (vocalization) → inject
|
|
24
|
-
// Assistant with only tool_calls (intermediate) → skip
|
|
25
39
|
if (last.role === "assistant") {
|
|
26
40
|
const hasToolCalls = Array.isArray(last.tool_calls) && last.tool_calls.length > 0;
|
|
27
41
|
const hasContent = last.content != null && last.content !== "";
|
|
28
|
-
return !hasToolCalls && hasContent;
|
|
42
|
+
return !hasToolCalls && hasContent; // vocalization → inject; tool call → never
|
|
29
43
|
}
|
|
30
44
|
|
|
31
|
-
//
|
|
32
|
-
return false;
|
|
45
|
+
return false; // tool results → mid-loop, skip
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
export default function (pi: ExtensionAPI) {
|
|
36
49
|
pi.on("before_provider_request", (event) => {
|
|
37
50
|
const payload = event.payload as Record<string, unknown>;
|
|
38
51
|
const msgs = payload.messages as Array<Record<string, unknown>>;
|
|
52
|
+
if (!injectable(msgs)) return;
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const timestamp = formatTimestamp();
|
|
43
|
-
const callId = `ts_${Date.now()}`;
|
|
54
|
+
const ts = `\n${formatTimestamp()}`;
|
|
55
|
+
const last = msgs[msgs.length - 1];
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
],
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
57
|
+
if (typeof last.content === "string") {
|
|
58
|
+
last.content = last.content.replace(TS_STRIP, "") + ts;
|
|
59
|
+
} else if (Array.isArray(last.content)) {
|
|
60
|
+
// Provider-shaped block arrays (e.g. Anthropic): normalize the last
|
|
61
|
+
// text block in place, or append a text block when none exists.
|
|
62
|
+
const blocks = [...(last.content as Array<Record<string, unknown>>)];
|
|
63
|
+
const idx = blocks.length - 1;
|
|
64
|
+
const lastBlock = blocks[idx] as { type?: string; text?: string } | undefined;
|
|
65
|
+
if (lastBlock && lastBlock.type === "text" && typeof lastBlock.text === "string") {
|
|
66
|
+
blocks[idx] = { ...lastBlock, text: lastBlock.text.replace(TS_STRIP, "") + ts };
|
|
67
|
+
} else {
|
|
68
|
+
blocks.push({ type: "text", text: ts });
|
|
69
|
+
}
|
|
70
|
+
last.content = blocks;
|
|
71
|
+
}
|
|
72
|
+
// Non-string, non-array content shapes: leave untouched rather than guess.
|
|
62
73
|
|
|
63
74
|
return event.payload;
|
|
64
75
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taylorsatula/pi-timestamp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Timestamp injection extension for Pi — appends the current wall-clock time
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Timestamp injection extension for Pi — appends the current wall-clock time to the tail of user messages and vocalizations just-in-time (cache-safe, no synthetic tool calls), so the model always knows what time it is.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
7
7
|
],
|