system-prompt-omni 0.2.0 → 0.3.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 +23 -6
- package/extensions/pi-system-prompt-recorder.ts +16 -14
- package/lib/system-prompt-recorder.ts +74 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,18 +46,35 @@ confirmation. Run `/reload` after exporting to load the new file.
|
|
|
46
46
|
|
|
47
47
|
## Prompt recording
|
|
48
48
|
|
|
49
|
-
Before each provider request, the package compares the SHA-256
|
|
50
|
-
prompt with the latest snapshot on the active session
|
|
51
|
-
session entry with type `pi-system-prompt` records:
|
|
49
|
+
Before each provider request, the package compares the SHA-256 hashes of the effective system
|
|
50
|
+
prompt and serialized active tool definitions with the latest snapshot on the active session
|
|
51
|
+
branch. When either changes, a durable custom session entry with type `pi-system-prompt` records:
|
|
52
52
|
|
|
53
53
|
- the full effective system prompt, its hash, and UTF-8 byte length;
|
|
54
54
|
- provider, model, API, and thinking-level metadata;
|
|
55
|
-
-
|
|
55
|
+
- `serializedTools`, the tool-definition value copied from Pi's final provider payload and
|
|
56
|
+
encoded as a compact JSON string; and
|
|
56
57
|
- provider-level system/developer instructions extracted from the serialized request.
|
|
57
58
|
|
|
59
|
+
No second tool representation is stored. Tool definitions are not rebuilt or sorted, so array
|
|
60
|
+
order and provider-specific structure stay identical to the `before_provider_request` payload.
|
|
61
|
+
For OpenAI Chat Completions, for example, the value has the same shape as
|
|
62
|
+
`current_harness_tools.json`:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"serializedTools": "[{\"type\":\"function\",\"function\":{\"name\":\"read\",\"description\":\"Read files\",\"parameters\":{\"type\":\"object\"}}}]"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The exact representation follows the active API. OpenAI Responses, Anthropic, Google, Bedrock,
|
|
71
|
+
and `pi-messages` therefore retain their own transmitted tool schema rather than being converted
|
|
72
|
+
to the Chat Completions shape. Load this package after extensions that rewrite provider requests
|
|
73
|
+
so it observes their final tool order and definitions.
|
|
74
|
+
|
|
58
75
|
The recorder never stores the complete provider payload, never adds its custom entries to the
|
|
59
|
-
LLM context, and returns the payload unchanged.
|
|
60
|
-
|
|
76
|
+
LLM context, and returns the payload unchanged. Loading it last also ensures rewritten provider
|
|
77
|
+
instructions are included in the snapshot.
|
|
61
78
|
|
|
62
79
|
## Install
|
|
63
80
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Persist the effective system prompt when
|
|
2
|
+
* Persist the effective system prompt or provider tool definitions when either changes.
|
|
3
3
|
*
|
|
4
4
|
* Custom entries are durable but are not added to the LLM context. Load this
|
|
5
5
|
* extension last if another extension rewrites the serialized provider payload
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
extractSerializedTools,
|
|
11
12
|
extractProviderSystemInstructions,
|
|
12
|
-
|
|
13
|
+
latestSystemPromptSnapshotHashes,
|
|
13
14
|
sha256,
|
|
14
15
|
SYSTEM_PROMPT_ENTRY_SCHEMA_VERSION,
|
|
15
16
|
SYSTEM_PROMPT_ENTRY_TYPE,
|
|
@@ -19,22 +20,24 @@ export default function systemPromptRecorder(pi: ExtensionAPI): void {
|
|
|
19
20
|
pi.on("before_provider_request", (event, ctx) => {
|
|
20
21
|
const systemPrompt = ctx.getSystemPrompt();
|
|
21
22
|
const systemPromptSha256 = sha256(systemPrompt);
|
|
23
|
+
const serializedTools = extractSerializedTools(event.payload);
|
|
24
|
+
const serializedToolsSha256 =
|
|
25
|
+
serializedTools === undefined ? undefined : sha256(serializedTools);
|
|
22
26
|
|
|
23
|
-
// Compare with the latest snapshot on the
|
|
24
|
-
// duplicates after process restarts,
|
|
25
|
-
|
|
27
|
+
// Compare both prompt and tool definitions with the latest snapshot on the
|
|
28
|
+
// active branch. This also avoids duplicates after process restarts,
|
|
29
|
+
// session resume, and extension reloads.
|
|
30
|
+
const latestHashes = latestSystemPromptSnapshotHashes(ctx.sessionManager.getBranch());
|
|
31
|
+
if (
|
|
32
|
+
latestHashes?.systemPromptSha256 === systemPromptSha256 &&
|
|
33
|
+
latestHashes.capturesSerializedTools &&
|
|
34
|
+
latestHashes.serializedToolsSha256 === serializedToolsSha256
|
|
35
|
+
) {
|
|
26
36
|
return undefined;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
const providerSystemInstructions = extractProviderSystemInstructions(event.payload);
|
|
30
40
|
const model = ctx.model;
|
|
31
|
-
const activeTools = pi.getActiveTools();
|
|
32
|
-
const toolsByName = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
|
|
33
|
-
const tools = activeTools.flatMap((name) => {
|
|
34
|
-
const tool = toolsByName.get(name);
|
|
35
|
-
if (!tool) return [];
|
|
36
|
-
return [{ name: tool.name, description: tool.description, parameters: tool.parameters }];
|
|
37
|
-
});
|
|
38
41
|
|
|
39
42
|
pi.appendEntry(SYSTEM_PROMPT_ENTRY_TYPE, {
|
|
40
43
|
schemaVersion: SYSTEM_PROMPT_ENTRY_SCHEMA_VERSION,
|
|
@@ -47,8 +50,7 @@ export default function systemPromptRecorder(pi: ExtensionAPI): void {
|
|
|
47
50
|
modelId: model?.id,
|
|
48
51
|
api: model?.api,
|
|
49
52
|
thinkingLevel: pi.getThinkingLevel(),
|
|
50
|
-
|
|
51
|
-
tools,
|
|
53
|
+
...(serializedTools === undefined ? {} : { serializedTools }),
|
|
52
54
|
providerSystemInstructions,
|
|
53
55
|
});
|
|
54
56
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
export const SYSTEM_PROMPT_ENTRY_TYPE = "pi-system-prompt";
|
|
4
|
-
export const SYSTEM_PROMPT_ENTRY_SCHEMA_VERSION =
|
|
4
|
+
export const SYSTEM_PROMPT_ENTRY_SCHEMA_VERSION = 2;
|
|
5
|
+
|
|
6
|
+
export interface SystemPromptSnapshotHashes {
|
|
7
|
+
systemPromptSha256?: string;
|
|
8
|
+
serializedToolsSha256?: string;
|
|
9
|
+
capturesSerializedTools: boolean;
|
|
10
|
+
}
|
|
5
11
|
|
|
6
12
|
interface CustomEntryLike {
|
|
7
13
|
type?: unknown;
|
|
@@ -21,6 +27,45 @@ function jsonSnapshot(value: unknown): unknown {
|
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
function valueAtPath(root: Record<string, unknown>, path: readonly string[]): unknown {
|
|
31
|
+
let value: unknown = root;
|
|
32
|
+
for (const segment of path) {
|
|
33
|
+
if (!isRecord(value) || !Object.prototype.hasOwnProperty.call(value, segment)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
value = value[segment];
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Serialize the tool definitions from Pi's final provider payload without
|
|
43
|
+
* rebuilding, sorting, or normalizing them. The paths cover Pi's current
|
|
44
|
+
* provider transports, including OpenAI, Anthropic, Google, Bedrock, and the
|
|
45
|
+
* pi-messages API.
|
|
46
|
+
*/
|
|
47
|
+
export function extractSerializedTools(payload: unknown): string | undefined {
|
|
48
|
+
if (!isRecord(payload)) return undefined;
|
|
49
|
+
|
|
50
|
+
const toolPaths = [
|
|
51
|
+
["tools"],
|
|
52
|
+
["config", "tools"],
|
|
53
|
+
["toolConfig", "tools"],
|
|
54
|
+
["context", "tools"],
|
|
55
|
+
["body", "tools"],
|
|
56
|
+
["body", "config", "tools"],
|
|
57
|
+
["body", "toolConfig", "tools"],
|
|
58
|
+
["body", "context", "tools"],
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
for (const path of toolPaths) {
|
|
62
|
+
const tools = valueAtPath(payload, path);
|
|
63
|
+
if (tools === undefined) continue;
|
|
64
|
+
return JSON.stringify(tools);
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
function pushField(
|
|
25
70
|
result: Array<Record<string, unknown>>,
|
|
26
71
|
root: Record<string, unknown>,
|
|
@@ -109,3 +154,31 @@ export function latestSystemPromptSha256(entries: readonly CustomEntryLike[]): s
|
|
|
109
154
|
}
|
|
110
155
|
return undefined;
|
|
111
156
|
}
|
|
157
|
+
|
|
158
|
+
/** Return the hashes from the newest recorder snapshot on the active branch. */
|
|
159
|
+
export function latestSystemPromptSnapshotHashes(
|
|
160
|
+
entries: readonly CustomEntryLike[],
|
|
161
|
+
): SystemPromptSnapshotHashes | undefined {
|
|
162
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
163
|
+
const entry = entries[index];
|
|
164
|
+
if (entry?.type !== "custom" || entry.customType !== SYSTEM_PROMPT_ENTRY_TYPE) continue;
|
|
165
|
+
if (!isRecord(entry.data)) continue;
|
|
166
|
+
|
|
167
|
+
const systemPromptSha256 =
|
|
168
|
+
typeof entry.data.systemPromptSha256 === "string"
|
|
169
|
+
? entry.data.systemPromptSha256
|
|
170
|
+
: typeof entry.data.systemPrompt === "string"
|
|
171
|
+
? sha256(entry.data.systemPrompt)
|
|
172
|
+
: undefined;
|
|
173
|
+
const serializedToolsSha256 =
|
|
174
|
+
typeof entry.data.serializedTools === "string"
|
|
175
|
+
? sha256(entry.data.serializedTools)
|
|
176
|
+
: undefined;
|
|
177
|
+
const capturesSerializedTools =
|
|
178
|
+
typeof entry.data.schemaVersion === "number" &&
|
|
179
|
+
entry.data.schemaVersion >= SYSTEM_PROMPT_ENTRY_SCHEMA_VERSION;
|
|
180
|
+
|
|
181
|
+
return { systemPromptSha256, serializedToolsSha256, capturesSerializedTools };
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "system-prompt-omni",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Display, export, and record the effective pi coding agent system prompt",
|
|
6
6
|
"keywords": ["pi-package", "pi-extension", "system-prompt", "debugging", "recorder"],
|