@aliou/pi-dev-kit 0.6.5 → 0.7.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.
@@ -1,67 +1,114 @@
1
1
  # State Management
2
2
 
3
- Extensions can persist state in the session history. In modern Pi extensions, the usual pattern is to store reconstructible state in tool result `details` and rebuild it from the current branch on `session_start`.
3
+ Prefer reconstructible state. Pi sessions can branch, fork, compact, reload, and resume; extension state must follow the active branch.
4
4
 
5
- ## Recommended Pattern: Store State in Tool Result Details
5
+ ## Preferred Pattern: Tool Result `details`
6
6
 
7
- When a tool changes extension state, return the latest state in `details`. That keeps the state aligned with normal tool history, branching, and reconstruction.
7
+ When a tool changes state, return the latest snapshot in `details`. Rebuild in-memory state from the current branch on `session_start`.
8
8
 
9
9
  ```typescript
10
- export default function (pi: ExtensionAPI) {
11
- let items: string[] = [];
10
+ interface TodoState {
11
+ items: Array<{ id: number; text: string; done: boolean }>;
12
+ nextId: number;
13
+ }
14
+
15
+ export default function toolsExtension(pi: ExtensionAPI) {
16
+ let state: TodoState = { items: [], nextId: 1 };
12
17
 
13
18
  pi.on("session_start", async (_event, ctx) => {
14
- items = [];
19
+ state = { items: [], nextId: 1 };
15
20
  for (const entry of ctx.sessionManager.getBranch()) {
16
- if (entry.type === "message" && entry.message.role === "toolResult") {
17
- if (entry.message.toolName === "todo") {
18
- items = entry.message.details?.items ?? [];
19
- }
21
+ if (entry.type !== "message") continue;
22
+ if (entry.message.role !== "toolResult") continue;
23
+ if (entry.message.toolName !== "todo") continue;
24
+
25
+ const details = entry.message.details as Partial<TodoState> | undefined;
26
+ if (details?.items && typeof details.nextId === "number") {
27
+ state = { items: details.items, nextId: details.nextId };
20
28
  }
21
29
  }
22
30
  });
23
31
 
24
- pi.registerTool({
32
+ const todoTool = defineTool({
25
33
  name: "todo",
26
- // ...
27
- async execute() {
28
- items.push("Buy groceries");
34
+ label: "Todo",
35
+ description: "Manage session todos.",
36
+ parameters,
37
+ async execute(_toolCallId, params) {
38
+ state.items.push({ id: state.nextId++, text: params.text, done: false });
29
39
  return {
30
- content: [{ type: "text", text: "Added todo item" }],
31
- details: { items: [...items] },
40
+ content: [{ type: "text", text: `Added todo: ${params.text}` }],
41
+ details: { ...state },
32
42
  };
33
43
  },
34
44
  });
45
+
46
+ pi.registerTool(todoTool);
35
47
  }
36
48
  ```
37
49
 
38
- ## Reconstructing State from Session
50
+ Why this works:
51
+
52
+ - Forks and tree navigation naturally select different branches.
53
+ - Tool results are already ordered in session history.
54
+ - State is visible to renderers without separate storage.
39
55
 
40
- When a session loads, reconstruct state in `session_start` by iterating over the current branch or full session through `ctx.sessionManager`:
56
+ ## `appendEntry()`
57
+
58
+ Use `pi.appendEntry(customType, data)` for extension-specific state or audit entries that should persist but should not enter LLM context.
41
59
 
42
60
  ```typescript
43
- pi.on("session_start", async (_event, ctx) => {
44
- todoItems = [];
61
+ pi.appendEntry("my-extension-state", {
62
+ enabled: true,
63
+ selectedProfile: "default",
64
+ });
65
+ ```
45
66
 
67
+ Reconstruct from custom entries on `session_start` when the state is not tied to one tool result.
68
+
69
+ ```typescript
70
+ pi.on("session_start", (_event, ctx) => {
46
71
  for (const entry of ctx.sessionManager.getBranch()) {
47
- if (entry.type === "message" && entry.message.role === "toolResult") {
48
- if (entry.message.toolName === "todo") {
49
- todoItems = entry.message.details?.items ?? [];
50
- }
72
+ if (entry.type === "custom" && entry.customType === "my-extension-state") {
73
+ // Rebuild state from entry.data.
51
74
  }
52
75
  }
53
76
  });
54
77
  ```
55
78
 
56
- This pattern makes state survive session reloads, forks, and compactions (as long as the entries are included in the compaction summary).
79
+ ## `sendMessage()`
80
+
81
+ Use `pi.sendMessage()` for persistent user-visible messages that may be rendered with `registerMessageRenderer` and can enter model context depending on delivery.
82
+
83
+ ```typescript
84
+ pi.sendMessage({
85
+ customType: "my-extension-summary",
86
+ content: "Summary text the LLM may see",
87
+ display: true,
88
+ details: { source: "quota-check" },
89
+ });
90
+ ```
91
+
92
+ Do not use `sendMessage` for hidden internal state. Use tool `details` or `appendEntry`.
93
+
94
+ ## Choosing a State Store
95
+
96
+ | Store | LLM sees | Branch-aware | Best for |
97
+ |---|---:|---:|---|
98
+ | Tool result `details` | No (`content` yes) | Yes | State caused by tool calls. |
99
+ | `appendEntry` | No | Yes if reconstructing from branch | Internal extension state/history. |
100
+ | `sendMessage` | Yes when delivered | Yes | Persistent user-visible context. |
101
+ | Config file | No | No | User settings, credentials, defaults. |
102
+ | In-memory only | No | No | Caches that can be rebuilt. |
103
+
104
+ ## Compaction
57
105
 
58
- ## When to Use appendEntry vs sendMessage
106
+ Compaction may remove old detailed entries from active model context, but session history still exists. If state must survive compacted/forked workflows, keep reconstruction logic robust and consider adding summaries via custom messages only when the LLM needs that state.
59
107
 
60
- | | `appendEntry` | `sendMessage` |
61
- |---|---|---|
62
- | Rendered as | Tool call/result pair | Assistant message |
63
- | Custom renderer | Tool's `renderResult` | `registerMessageRenderer` |
64
- | Use for | State changes, action logs | Information display, command results |
65
- | LLM sees | The `output` field | The `content` field |
108
+ ## Guidelines
66
109
 
67
- Use tool result `details` when the state naturally belongs to a tool call and should follow normal conversation branching. Use `appendEntry` for extension-specific state/history that does not fit a normal tool result. Use `sendMessage` when you are displaying a one-time result.
110
+ - Store snapshots, not deltas, unless replaying deltas is simple and reliable.
111
+ - Rebuild state from `ctx.sessionManager.getBranch()`, not from all entries, when branch behavior matters.
112
+ - Keep persisted `details` small and serializable.
113
+ - Do not store secrets in session entries.
114
+ - Reinitialize in-memory state on `session_start` and clean up resources on `session_shutdown`.