@earendil-works/pi-coding-agent 0.87.0 → 0.87.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +25 -711
  3. package/dist/bundle/chunks/{anthropic-messages-MYU5ZMRF.js → anthropic-messages-J5WXPPPC.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-GV2E3GBU.js → chunk-65HAU2C5.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-4DKZACXI.js → chunk-OJP47DM6.js} +13 -13
  6. package/dist/bundle/chunks/github-copilot.js +1 -1
  7. package/dist/bundle/chunks/{openai-completions-XHML6MTL.js → openai-completions-OBX42CLD.js} +1 -1
  8. package/dist/bundle/chunks/{virtual-modules-BNWPZYDH.js → virtual-modules-VHMJYYWQ.js} +1 -1
  9. package/dist/bundle/cli-runtime.js +1 -1
  10. package/dist/bundle/index.js +1 -1
  11. package/dist/bundle/rpc-entry.js +1 -1
  12. package/dist/cli/args.d.ts.map +1 -1
  13. package/dist/cli/args.js +14 -4
  14. package/dist/cli/args.js.map +1 -1
  15. package/dist/core/compaction/compaction.d.ts.map +1 -1
  16. package/dist/core/compaction/compaction.js +9 -9
  17. package/dist/core/compaction/compaction.js.map +1 -1
  18. package/dist/core/model-resolver.d.ts.map +1 -1
  19. package/dist/core/model-resolver.js +1 -1
  20. package/dist/core/model-resolver.js.map +1 -1
  21. package/docs/cli-integration.md +106 -0
  22. package/docs/cli.md +268 -0
  23. package/docs/compaction.md +22 -22
  24. package/docs/configuration.md +45 -0
  25. package/docs/containerization.md +109 -82
  26. package/docs/custom-provider.md +132 -784
  27. package/docs/docs.json +139 -99
  28. package/docs/environment-variables.md +3 -5
  29. package/docs/extensions.md +134 -3020
  30. package/docs/how-pi-works.md +49 -0
  31. package/docs/images/interactive-mode.png +0 -0
  32. package/docs/index.md +24 -69
  33. package/docs/json.md +193 -65
  34. package/docs/keybindings.md +57 -102
  35. package/docs/llama-cpp.md +3 -3
  36. package/docs/message-types.md +261 -0
  37. package/docs/models.md +55 -565
  38. package/docs/packages.md +66 -167
  39. package/docs/prompt-templates.md +31 -68
  40. package/docs/providers.md +102 -240
  41. package/docs/quickstart.md +61 -106
  42. package/docs/rpc-commands.md +854 -0
  43. package/docs/rpc-extension-ui.md +200 -0
  44. package/docs/rpc.md +129 -1556
  45. package/docs/sdk.md +76 -1171
  46. package/docs/security.md +70 -32
  47. package/docs/session-format.md +10 -214
  48. package/docs/sessions.md +35 -141
  49. package/docs/settings.md +109 -387
  50. package/docs/shell-aliases.md +85 -5
  51. package/docs/skills.md +51 -190
  52. package/docs/slash-commands.md +60 -0
  53. package/docs/terminal-setup.md +105 -78
  54. package/docs/termux.md +74 -83
  55. package/docs/themes.md +68 -280
  56. package/docs/tmux.md +31 -39
  57. package/docs/tui.md +69 -923
  58. package/docs/usage.md +54 -272
  59. package/docs/windows.md +43 -17
  60. package/examples/README.md +13 -2
  61. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  62. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  63. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  64. package/examples/extensions/gondolin/package-lock.json +2 -2
  65. package/examples/extensions/gondolin/package.json +1 -1
  66. package/examples/extensions/sandbox/package-lock.json +2 -2
  67. package/examples/extensions/sandbox/package.json +1 -1
  68. package/examples/extensions/with-deps/package-lock.json +2 -2
  69. package/examples/extensions/with-deps/package.json +1 -1
  70. package/examples/rpc-client.ts +35 -0
  71. package/examples/rpc-extension-ui.ts +25 -5
  72. package/examples/sdk/README.md +1 -1
  73. package/npm-shrinkwrap.json +20 -20
  74. package/package.json +8 -8
  75. package/docs/development.md +0 -90
package/docs/sdk.md CHANGED
@@ -1,1237 +1,142 @@
1
- > pi can help you use the SDK. Ask it to build an integration for your use case.
2
-
3
1
  # SDK
4
2
 
5
- The SDK provides programmatic access to pi's agent capabilities. Use it to embed pi in other applications, build custom interfaces, or integrate with automated workflows.
6
-
7
- **Example use cases:**
8
- - Build a custom UI (web, desktop, mobile)
9
- - Integrate agent capabilities into existing applications
10
- - Create automated pipelines with agent reasoning
11
- - Build custom tools that spawn sub-agents
12
- - Test agent behavior programmatically
13
-
14
- See [examples/sdk/](../examples/sdk/) for working examples from minimal to full control.
15
-
16
- ## Quick Start
17
-
18
- ```typescript
19
- import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
20
-
21
- const modelRuntime = await ModelRuntime.create();
22
- const { session } = await createAgentSession({
23
- sessionManager: SessionManager.inMemory(),
24
- modelRuntime,
25
- });
26
-
27
- session.subscribe((event) => {
28
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
29
- process.stdout.write(event.assistantMessageEvent.delta);
30
- }
31
- });
32
-
33
- await session.prompt("What files are in the current directory?");
34
- ```
35
-
36
- ## Installation
37
-
38
- ```bash
39
- npm install @earendil-works/pi-coding-agent
40
- ```
41
-
42
- The SDK is included in the main package. No separate installation needed.
43
-
44
- ## Core Concepts
45
-
46
- ### createAgentSession()
3
+ `@earendil-works/pi-coding-agent` embeds Pi in a Node.js or Bun process. It provides direct TypeScript access to the agent, sessions, tools, models, and resources used by the command-line application.
47
4
 
48
- The main factory function for a single `AgentSession`.
49
-
50
- `createAgentSession()` uses a `ResourceLoader` to supply extensions, skills, prompt templates, themes, and context files. If you do not provide one, it uses `DefaultResourceLoader` with standard discovery.
5
+ Use the SDK for in-process TypeScript integration. For a language-independent or isolated subprocess, see [CLI Integration](cli-integration.md).
51
6
 
52
7
  ```typescript
53
- import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
8
+ import { createAgentSession } from "@earendil-works/pi-coding-agent";
54
9
 
55
- // Minimal: defaults with DefaultResourceLoader
56
10
  const { session } = await createAgentSession();
57
11
 
58
- // Custom: override specific options
59
- const { session } = await createAgentSession({
60
- model: myModel,
61
- tools: ["read", "bash"],
62
- sessionManager: SessionManager.inMemory(),
63
- });
64
- ```
65
-
66
- ### AgentSession
67
-
68
- The session manages agent lifecycle, message history, model state, compaction, and event streaming.
69
-
70
- ```typescript
71
- interface AgentSession {
72
- // Send a prompt and wait for completion
73
- prompt(text: string, options?: PromptOptions): Promise<void>;
74
-
75
- // Queue messages during streaming
76
- steer(text: string): Promise<void>;
77
- followUp(text: string): Promise<void>;
78
-
79
- // Subscribe to events (returns unsubscribe function)
80
- subscribe(listener: (event: AgentSessionEvent) => void): () => void;
81
-
82
- // Session info
83
- sessionFile: string | undefined;
84
- sessionId: string;
85
-
86
- // Model control
87
- setModel(model: Model): Promise<void>;
88
- setThinkingLevel(level: ThinkingLevel): void;
89
- cycleModel(): Promise<ModelCycleResult | undefined>;
90
- cycleThinkingLevel(): ThinkingLevel | undefined;
91
-
92
- // State access
93
- agent: Agent;
94
- sessionManager: SessionManager;
95
- refreshContext(): void;
96
- model: Model | undefined;
97
- thinkingLevel: ThinkingLevel;
98
- messages: AgentMessage[];
99
- isStreaming: boolean;
100
-
101
- // In-place tree navigation within the current session file
102
- navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
103
-
104
- // Compaction
105
- compact(customInstructions?: string): Promise<CompactionResult>;
106
- abortCompaction(): void;
107
-
108
- // Abort current operation
109
- abort(): Promise<void>;
110
-
111
- // Cleanup
112
- dispose(): void;
113
- }
114
- ```
115
-
116
- `session.navigateTree()` rejects while an agent response, manual or automatic compaction, or another tree navigation is active, even with `summarize: false`. It does not queue navigation or return `{ cancelled: true }` for these conflicts. Wait for the active operation to finish (for example, with `await session.waitForIdle()`) and retry. Rejection leaves the active branch unchanged.
117
-
118
- Session replacement APIs such as new-session, resume, fork, and import live on `AgentSessionRuntime`, not on `AgentSession`.
119
-
120
- ### createAgentSessionRuntime() and AgentSessionRuntime
121
-
122
- Use the runtime API when you need to replace the active session and rebuild cwd-bound runtime state.
123
- This is the same layer used by the built-in interactive, print, and RPC modes.
124
-
125
- `createAgentSessionRuntime()` takes a runtime factory plus the initial cwd/session target. The factory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and returns a full runtime result.
126
-
127
- ```typescript
128
- import {
129
- type CreateAgentSessionRuntimeFactory,
130
- createAgentSessionFromServices,
131
- createAgentSessionRuntime,
132
- createAgentSessionServices,
133
- getAgentDir,
134
- SessionManager,
135
- } from "@earendil-works/pi-coding-agent";
136
-
137
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
138
- const services = await createAgentSessionServices({ cwd });
139
- return {
140
- ...(await createAgentSessionFromServices({
141
- services,
142
- sessionManager,
143
- sessionStartEvent,
144
- })),
145
- services,
146
- diagnostics: services.diagnostics,
147
- };
148
- };
149
-
150
- const runtime = await createAgentSessionRuntime(createRuntime, {
151
- cwd: process.cwd(),
152
- agentDir: getAgentDir(),
153
- sessionManager: SessionManager.create(process.cwd()),
154
- });
155
- ```
156
-
157
- `AgentSessionRuntime` owns replacement of the active runtime across:
158
-
159
- - `newSession()`
160
- - `switchSession()`
161
- - `fork()`
162
- - clone flows via `fork(entryId, { position: "at" })`
163
- - `importFromJsonl()`
164
-
165
- Important behavior:
166
-
167
- - `runtime.session` changes after those operations
168
- - event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement
169
- - if you use extensions, call `runtime.session.bindExtensions(...)` again for the new session
170
- - creation returns diagnostics on `runtime.diagnostics`
171
- - if runtime creation or replacement fails, the method throws and the caller decides how to handle it
172
-
173
- ```typescript
174
- let session = runtime.session;
175
- let unsubscribe = session.subscribe(() => {});
176
-
177
- await runtime.newSession();
178
-
179
- unsubscribe();
180
- session = runtime.session;
181
- unsubscribe = session.subscribe(() => {});
182
- ```
183
-
184
- ### Prompting and Message Queueing
185
-
186
- `PromptOptions` controls prompt expansion, queueing behavior while streaming, and prompt preflight notifications:
187
-
188
- ```typescript
189
- interface PromptOptions {
190
- expandPromptTemplates?: boolean;
191
- images?: ImageContent[];
192
- streamingBehavior?: "steer" | "followUp";
193
- source?: InputSource;
194
- preflightResult?: (success: boolean) => void;
195
- }
196
- ```
197
-
198
- `preflightResult` is called once per `prompt()` invocation:
199
-
200
- - `true` when the prompt was accepted, queued, or handled immediately
201
- - `false` when prompt preflight rejected before acceptance
202
-
203
- It fires before `prompt()` resolves. `prompt()` still resolves only after the full accepted run finishes, including retries. Failures after acceptance are reported through the normal event and message stream, not through `preflightResult(false)`.
204
-
205
- The `prompt()` method handles prompt templates, extension commands, and message sending:
206
-
207
- ```typescript
208
- // Basic prompt (when not streaming)
209
- await session.prompt("What files are here?");
210
-
211
- // With images
212
- await session.prompt("What's in this image?", {
213
- images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]
214
- });
215
-
216
- // During streaming: must specify how to queue the message
217
- await session.prompt("Stop and do this instead", { streamingBehavior: "steer" });
218
- await session.prompt("After you're done, also check X", { streamingBehavior: "followUp" });
219
- ```
220
-
221
- **Behavior:**
222
- - **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `pi.sendMessage()`.
223
- - **File-based prompt templates** (from `.md` files): Expanded to their content before sending or queueing.
224
- - **During streaming without `streamingBehavior`**: Throws an error. Use `steer()` or `followUp()` directly, or specify the option.
225
- - **`preflightResult(true)`**: Means the prompt was accepted, queued, or handled immediately.
226
- - **`preflightResult(false)`**: Means preflight rejected before acceptance.
227
-
228
- For explicit queueing during streaming:
229
-
230
- ```typescript
231
- // Queue a steering message for delivery after the current assistant turn finishes its tool calls
232
- await session.steer("New instruction");
233
-
234
- // Wait for agent to finish (delivered only when agent stops)
235
- await session.followUp("After you're done, also do this");
236
- ```
237
-
238
- Both `steer()` and `followUp()` expand file-based prompt templates but error on extension commands (extension commands cannot be queued).
239
-
240
- ### Agent and AgentState
241
-
242
- The `Agent` class (from `@earendil-works/pi-agent-core`) handles the core LLM interaction. Access it via `session.agent`.
243
-
244
- ```typescript
245
- // Access current state
246
- const state = session.agent.state;
247
-
248
- // state.messages: AgentMessage[] - conversation history
249
- // state.model: Model - current model
250
- // state.thinkingLevel: ThinkingLevel - current thinking level
251
- // state.systemPrompt: string - read-only, replayed from the transcript's system messages
252
- // state.tools: AgentTool[] - executable tools; changes are declared to the model before the next request
253
- // state.streamingMessage?: AgentMessage - current partial assistant message
254
- // state.errorMessage?: string - latest assistant error
255
-
256
- // Model-visible messages are projected from session.sessionManager.
257
- // agent.state.messages is a refreshed inspection cache; do not assign it for restoration.
258
-
259
- // Replace tools
260
- session.agent.state.tools = tools; // copies the top-level array
261
-
262
- // Wait for agent to finish processing
263
- await session.agent.waitForIdle();
264
- ```
265
-
266
- Provider requests use `session.sessionManager` as the canonical finalized context. Assigning `session.agent.state.messages` does not replace persisted context and may be overwritten at the next request boundary. Restore externally stored history when constructing the session instead:
267
-
268
- ```typescript
269
- const restoredManager = SessionManager.inMemory(process.cwd(), { id: sessionId }, entries);
270
- const { session } = await createAgentSession({ sessionManager: restoredManager });
271
- ```
272
-
273
- For an existing session, use `session.navigateTree(entryId)` to move its active branch. Use `session.sessionManager.appendMessage(...)` plus `session.refreshContext()` only when intentionally appending externally managed entries.
274
-
275
- ### Events
276
-
277
- Subscribe to events to receive streaming output and lifecycle notifications.
278
-
279
- ```typescript
280
- session.subscribe((event) => {
281
- switch (event.type) {
282
- // Streaming text from assistant
283
- case "message_update":
284
- if (event.assistantMessageEvent.type === "text_delta") {
285
- process.stdout.write(event.assistantMessageEvent.delta);
286
- }
287
- if (event.assistantMessageEvent.type === "thinking_delta") {
288
- // Thinking output (if thinking enabled)
289
- }
290
- break;
291
-
292
- // Tool execution
293
- case "tool_execution_start":
294
- console.log(`Tool: ${event.toolName}`);
295
- break;
296
- case "tool_execution_update":
297
- // Streaming tool output
298
- break;
299
- case "tool_execution_end":
300
- console.log(`Result: ${event.isError ? "error" : "success"}`);
301
- break;
302
-
303
- // Message lifecycle
304
- case "message_start":
305
- // New message starting
306
- break;
307
- case "message_end":
308
- // Message complete
309
- break;
310
-
311
- // Agent lifecycle
312
- case "agent_start":
313
- // Agent started processing prompt
314
- break;
315
- case "agent_end":
316
- // Agent finished (event.messages contains new messages)
317
- break;
318
-
319
- // Turn lifecycle (one LLM response + tool calls)
320
- case "turn_start":
321
- break;
322
- case "turn_end":
323
- // event.message: assistant response
324
- // event.toolResults: tool results from this turn
325
- break;
326
-
327
- // Session events (queue, compaction, retry)
328
- case "queue_update":
329
- console.log(event.steering, event.followUp);
330
- break;
331
- case "compaction_start":
332
- case "compaction_end":
333
- case "auto_retry_start":
334
- case "auto_retry_end":
335
- case "summarization_retry_scheduled":
336
- case "summarization_retry_attempt_start":
337
- case "summarization_retry_finished":
338
- break;
339
- }
340
- });
341
- ```
342
-
343
- ## Options Reference
344
-
345
- ### Directories
346
-
347
- ```typescript
348
- const { session } = await createAgentSession({
349
- // Working directory for DefaultResourceLoader discovery
350
- cwd: process.cwd(), // default
351
-
352
- // Global config directory
353
- agentDir: "~/.pi/agent", // default (expands ~)
354
- });
355
- ```
356
-
357
- `cwd` is used by `DefaultResourceLoader` for:
358
- - Project extensions (`.pi/extensions/`)
359
- - Project skills:
360
- - `.pi/skills/`
361
- - `.agents/skills/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)
362
- - Project prompts (`.pi/prompts/`)
363
- - Context files (`AGENTS.md` walking up from cwd)
364
- - Session directory naming
365
-
366
- `agentDir` is used by `DefaultResourceLoader` for:
367
- - Global extensions (`extensions/`)
368
- - Global skills:
369
- - `skills/` under `agentDir` (for example `~/.pi/agent/skills/`)
370
- - `~/.agents/skills/`
371
- - Global prompts (`prompts/`)
372
- - Global context file (`AGENTS.md`)
373
- - Settings (`settings.json`)
374
- - Custom models (`models.json`)
375
- - Credentials (`auth.json`)
376
- - Sessions (`sessions/`)
377
-
378
- When you pass a custom `ResourceLoader`, `cwd` and `agentDir` no longer control resource discovery. They still influence session naming and tool path resolution.
379
-
380
- ### Model
381
-
382
- ```typescript
383
- import { getModel } from "@earendil-works/pi-ai";
384
- import { ModelRuntime } from "@earendil-works/pi-coding-agent";
385
-
386
- const modelRuntime = await ModelRuntime.create();
387
-
388
- // create() restores cached catalogs but does not refresh them from pi.dev by default.
389
- // Opt in to a create-time network refresh and bound how long it may take:
390
- const refreshedRuntime = await ModelRuntime.create({
391
- allowModelNetwork: true,
392
- modelRefreshTimeoutMs: 15_000,
393
- });
394
-
395
- // Find specific built-in model (doesn't check if API key exists)
396
- const opus = getModel("anthropic", "claude-opus-4-5");
397
- if (!opus) throw new Error("Model not found");
398
-
399
- // Find any model by provider/id, including custom models from models.json
400
- // (doesn't check if API key exists)
401
- const customModel = modelRuntime.getModel("my-provider", "my-model");
402
-
403
- // Get only models that have valid authentication configured
404
- const available = await modelRuntime.getAvailable();
405
-
406
- const { session } = await createAgentSession({
407
- model: opus,
408
- thinkingLevel: "medium", // off, minimal, low, medium, high, xhigh, max
409
-
410
- // Models for cycling (Ctrl+P in interactive mode)
411
- scopedModels: [
412
- { model: opus, thinkingLevel: "high" },
413
- { model: haiku, thinkingLevel: "off" },
414
- ],
415
-
416
- modelRuntime,
417
- });
418
- ```
419
-
420
- If no model is provided:
421
- 1. Tries to restore from session (if continuing)
422
- 2. Uses default from settings
423
- 3. Falls back to first available model
424
-
425
- Remote catalogs are persisted locally so later runtimes can restore them without a network request. The default file is `~/.pi/agent/models-store.json`; set `modelsStorePath` to choose another location, or inject `modelsStore` to control persistence. Network refreshes are throttled to once per provider every four hours unless forced. To force an immediate refresh, call `await modelRuntime.refresh({ allowNetwork: true, force: true, signal })`. Setting `PI_OFFLINE` disables model network access.
426
-
427
- To match CLI model parsing, use the exported resolver helpers:
428
-
429
- ```typescript
430
- import {
431
- resolveCliModel,
432
- resolveModelScopeWithDiagnostics,
433
- } from "@earendil-works/pi-coding-agent";
434
-
435
- const cliModel = resolveCliModel({
436
- cliModel: "anthropic/claude-opus-4-5:high",
437
- modelRuntime,
438
- });
439
- if (cliModel.error) throw new Error(cliModel.error);
440
- if (cliModel.warning) console.warn(cliModel.warning);
441
-
442
- const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(
443
- ["anthropic/*:high", "gpt-5"],
444
- modelRuntime,
445
- );
446
- for (const diagnostic of diagnostics) {
447
- console.warn(diagnostic.message);
448
- }
449
- ```
450
-
451
- `resolveCliModel()` uses all registered models so `--api-key` style first-time setup can resolve a model before stored auth exists. `resolveModelScopeWithDiagnostics()` matches `--models` and `enabledModels` semantics while returning warnings instead of printing them.
452
-
453
- > See [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)
454
-
455
- ### API Keys and OAuth
456
-
457
- Authentication resolution priority (handled by `ModelRuntime`):
458
- 1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
459
- 2. Stored credentials in `auth.json` (API keys or OAuth tokens)
460
- 3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
461
- 4. Fallback resolver (for custom provider keys from `models.json`)
462
-
463
- ```typescript
464
- import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
465
- import { createAgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
466
-
467
- // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
468
- const modelRuntime = await ModelRuntime.create();
469
-
470
- // Provider-owned auth methods and current status
471
- for (const provider of modelRuntime.getProviders()) {
472
- const status = await modelRuntime.checkAuth(provider.id);
473
- console.log(provider.name, provider.auth, status);
474
- }
475
-
476
- // Runtime API key override (not persisted to disk)
477
- await modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
478
-
479
- // Custom credential and model locations
480
- const customRuntime = await ModelRuntime.create({
481
- authPath: "/my/app/auth.json",
482
- modelsPath: "/my/app/models.json",
483
- });
484
-
485
- // Or inject any pi-ai CredentialStore
486
- const credentials = new InMemoryCredentialStore();
487
- const inMemoryRuntime = await ModelRuntime.create({ credentials });
488
-
489
- const { session } = await createAgentSession({
490
- modelRuntime: customRuntime,
491
- });
492
- ```
493
-
494
- `login()`, `logout()`, `setRuntimeApiKey()`, and `removeRuntimeApiKey()` resolve after the affected provider's cached/built-in catalog, composition, and availability snapshot are locally consistent. They do not wait for remote catalog freshness. If credentials were committed but local synchronization fails, they reject with the exported `CredentialSynchronizationError`; inspect its `providerId`, `operation`, `credential`, and `cause` fields instead of retrying the credential mutation blindly.
495
-
496
- Public model/auth operations and `ModelRuntime.create({ signal })` accept optional abort signals and are unbounded when omitted. SDK applications own deadline policy for remote catalog freshness:
497
-
498
- ```typescript
499
- const signal = AbortSignal.timeout(15_000);
500
- const result = await modelRuntime.refresh({
501
- providers: ["anthropic"],
502
- signal,
503
- });
504
- if (result.aborted) console.warn("Catalog refresh timed out; using cached models");
505
- for (const [providerId, error] of result.errors) {
506
- console.warn(`Could not refresh ${providerId}:`, error);
12
+ try {
13
+ await session.prompt("What files are in the current directory?");
14
+ console.log(session.getLastAssistantText());
15
+ } finally {
16
+ session.dispose();
507
17
  }
508
18
  ```
509
19
 
510
- A failed or timed-out network refresh does not undo a successful credential operation. `refresh()` starts a new provider generation, so it does not wait behind an older stalled refresh and stale generations cannot publish afterward.
511
-
512
- > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
513
-
514
- ### System Prompt
515
-
516
- Use a `ResourceLoader` to override the system prompt:
20
+ This uses the working directory, discovered resources, stored settings, and configured credentials. `prompt()` resolves when the run finishes.
517
21
 
518
- ```typescript
519
- import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
520
-
521
- const loader = new DefaultResourceLoader({
522
- systemPromptOverride: () => "You are a helpful assistant.",
523
- });
524
- await loader.reload();
22
+ The [complete minimal example](../examples/sdk/01-minimal.ts) also streams text events. All [SDK examples](../examples/sdk/) are typechecked with the repository.
525
23
 
526
- const { session } = await createAgentSession({ resourceLoader: loader });
527
- ```
24
+ <a id="session-management"></a>
528
25
 
529
- > See [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts)
26
+ ## Session lifecycle
530
27
 
531
- ### Tools
28
+ `createAgentSession()` creates an `AgentSession`. The session owns one conversation, its model and tools, queued messages, compaction state, and extension runtime.
532
29
 
533
- Specify which built-in tools to enable:
30
+ Read current state through `session.messages`, `session.model`, `session.thinkingLevel`, `session.systemPrompt`, and `session.getActiveToolNames()`.
534
31
 
535
- - Built-in tool names: `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, `ls`
536
- - Default built-ins: `read`, `bash`, `edit`, `write`
537
- - `noTools: "all"` disables all tools
538
- - `noTools: "builtin"` disables default built-ins while keeping extension and custom tools enabled
539
- - `excludeTools` disables specific built-in, extension, or custom tool names after any `tools` allowlist is applied
32
+ `session.systemPrompt` is read-only and returns the current effective system prompt, including changes that have not yet been sent to the model. Tool changes are declared to the model before the next request.
540
33
 
541
- The `edit` tool returns `details.diff` for Pi's TUI display and `details.patch` as a standard unified patch for SDK consumers.
34
+ <a id="sessionmanager-api"></a>
542
35
 
543
- ```typescript
544
- import { createAgentSession } from "@earendil-works/pi-coding-agent";
36
+ ### Session storage
545
37
 
546
- // Read-only mode
547
- const { session } = await createAgentSession({
548
- tools: ["read", "grep", "find", "ls"],
549
- });
550
-
551
- // Pick specific tools
552
- const { session } = await createAgentSession({
553
- tools: ["read", "bash", "grep"],
554
- });
555
-
556
- // Use PowerShell instead of Bash on Windows
557
- const { session } = await createAgentSession({
558
- tools: ["read", "powershell", "edit", "write"],
559
- });
560
-
561
- // Disable one tool while keeping the rest available
562
- const { session } = await createAgentSession({
563
- excludeTools: ["ask_question"],
564
- });
565
- ```
38
+ Sessions are persistent by default. `SessionManager` owns the persisted or in-memory entry tree and tracks its active leaf. Branching changes that leaf without deleting abandoned branches. When Pi reconstructs model context, the manager selects the active branch and applies compaction.
566
39
 
567
- #### Tools with Custom cwd
40
+ `SessionManager` is authoritative for finalized model context. Restore external history by constructing the session with a manager containing those entries. Assigning `session.agent.state.messages` does not replace persisted context.
568
41
 
569
- When you pass a custom `cwd`, `createAgentSession()` builds selected built-in tools for that cwd.
42
+ Use an in-memory manager when the host does not want session files:
570
43
 
571
44
  ```typescript
572
45
  import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
573
46
 
574
- const cwd = "/path/to/project";
575
-
576
- // Use default tools for custom cwd
577
- const { session } = await createAgentSession({
578
- cwd,
579
- sessionManager: SessionManager.inMemory(cwd),
580
- });
581
-
582
- // Or pick specific tools for custom cwd
583
- const { session } = await createAgentSession({
584
- cwd,
585
- tools: ["read", "bash", "grep"],
586
- sessionManager: SessionManager.inMemory(cwd),
587
- });
588
- ```
589
-
590
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
591
-
592
- ### Custom Tools
593
-
594
- ```typescript
595
- import { Type } from "typebox";
596
- import { createAgentSession, defineTool } from "@earendil-works/pi-coding-agent";
597
-
598
- // Inline custom tool
599
- const myTool = defineTool({
600
- name: "my_tool",
601
- label: "My Tool",
602
- description: "Does something useful",
603
- parameters: Type.Object({
604
- input: Type.String({ description: "Input value" }),
605
- }),
606
- execute: async (_toolCallId, params) => ({
607
- content: [{ type: "text", text: `Result: ${params.input}` }],
608
- details: {},
609
- }),
610
- });
611
-
612
- // Pass custom tools directly
613
- const { session } = await createAgentSession({
614
- customTools: [myTool],
615
- });
616
- ```
617
-
618
- Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
619
-
620
- Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
621
-
622
- If you pass `tools`, include each custom or extension tool name you want enabled, for example `tools: ["read", "bash", "my_tool"]`.
623
-
624
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
625
-
626
- ### Extensions
627
-
628
- Extensions are loaded by the `ResourceLoader`. `DefaultResourceLoader` discovers extensions from `~/.pi/agent/extensions/`, `.pi/extensions/`, and settings.json extension sources.
629
-
630
- ```typescript
631
- import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
632
-
633
- const loader = new DefaultResourceLoader({
634
- additionalExtensionPaths: ["/path/to/my-extension.ts"],
635
- extensionFactories: [
636
- (pi) => {
637
- pi.on("agent_start", () => {
638
- console.log("[Inline Extension] Agent starting");
639
- });
640
- },
641
- ],
642
- });
643
- await loader.reload();
644
-
645
- const { session } = await createAgentSession({ resourceLoader: loader });
646
- ```
647
-
648
- Extensions can register tools, subscribe to events, add commands, and more. See [extensions.md](extensions.md) for the full API.
649
-
650
- **Named inline extensions:** By default, inline factories display as `<inline:1>`, `<inline:2>`, etc. in the startup Extensions list. To show a descriptive name instead, wrap the factory:
651
-
652
- ```typescript
653
- import type { InlineExtension } from "@earendil-works/pi-coding-agent";
654
-
655
- const myProvider: InlineExtension = {
656
- name: "my-provider",
657
- factory: (pi) => {
658
- pi.on("agent_start", () => {
659
- console.log("[my-provider] Agent starting");
660
- });
661
- },
662
- };
663
-
664
- const loader = new DefaultResourceLoader({
665
- extensionFactories: [myProvider],
666
- });
667
- ```
668
-
669
- This displays as `<inline:my-provider>` instead of `<inline:1>`. Bare factory functions are still accepted for backward compatibility.
670
-
671
- **Event Bus:** Extensions can communicate via `pi.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
672
-
673
- ```typescript
674
- import { createEventBus, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
675
-
676
- const eventBus = createEventBus();
677
- const loader = new DefaultResourceLoader({
678
- eventBus,
679
- });
680
- await loader.reload();
681
-
682
- eventBus.on("my-extension:status", (data) => console.log(data));
683
- ```
684
-
685
- > See [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) and [docs/extensions.md](extensions.md)
686
-
687
- ### Skills
688
-
689
- ```typescript
690
- import {
691
- createAgentSession,
692
- DefaultResourceLoader,
693
- type Skill,
694
- } from "@earendil-works/pi-coding-agent";
695
-
696
- const customSkill: Skill = {
697
- name: "my-skill",
698
- description: "Custom instructions",
699
- filePath: "/path/to/SKILL.md",
700
- baseDir: "/path/to",
701
- source: "custom",
702
- };
703
-
704
- const loader = new DefaultResourceLoader({
705
- skillsOverride: (current) => ({
706
- skills: [...current.skills, customSkill],
707
- diagnostics: current.diagnostics,
708
- }),
709
- });
710
- await loader.reload();
711
-
712
- const { session } = await createAgentSession({ resourceLoader: loader });
713
- ```
714
-
715
- > See [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)
716
-
717
- ### Context Files
718
-
719
- ```typescript
720
- import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
721
-
722
- const loader = new DefaultResourceLoader({
723
- agentsFilesOverride: (current) => ({
724
- agentsFiles: [
725
- ...current.agentsFiles,
726
- { path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
727
- ],
728
- }),
729
- });
730
- await loader.reload();
731
-
732
- const { session } = await createAgentSession({ resourceLoader: loader });
733
- ```
734
-
735
- > See [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts)
736
-
737
- ### Slash Commands
738
-
739
- ```typescript
740
- import {
741
- createAgentSession,
742
- DefaultResourceLoader,
743
- type PromptTemplate,
744
- } from "@earendil-works/pi-coding-agent";
745
-
746
- const customCommand: PromptTemplate = {
747
- name: "deploy",
748
- description: "Deploy the application",
749
- source: "(custom)",
750
- content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",
751
- };
752
-
753
- const loader = new DefaultResourceLoader({
754
- promptsOverride: (current) => ({
755
- prompts: [...current.prompts, customCommand],
756
- diagnostics: current.diagnostics,
757
- }),
758
- });
759
- await loader.reload();
760
-
761
- const { session } = await createAgentSession({ resourceLoader: loader });
762
- ```
763
-
764
- > See [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts)
765
-
766
- ### Session Management
767
-
768
- Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
769
-
770
- ```typescript
771
- import {
772
- type CreateAgentSessionRuntimeFactory,
773
- createAgentSession,
774
- createAgentSessionFromServices,
775
- createAgentSessionRuntime,
776
- createAgentSessionServices,
777
- getAgentDir,
778
- SessionManager,
779
- } from "@earendil-works/pi-coding-agent";
780
-
781
- // In-memory (no persistence)
782
- const { session } = await createAgentSession({
783
- sessionManager: SessionManager.inMemory(),
784
- });
785
-
786
- // New persistent session
787
- const { session: persisted } = await createAgentSession({
788
- sessionManager: SessionManager.create(process.cwd()),
789
- });
790
-
791
- // Continue most recent
792
- const { session: continued, modelFallbackMessage } = await createAgentSession({
793
- sessionManager: SessionManager.continueRecent(process.cwd()),
794
- });
795
- if (modelFallbackMessage) {
796
- console.log("Note:", modelFallbackMessage);
797
- }
798
-
799
- // Open specific file
800
- const { session: opened } = await createAgentSession({
801
- sessionManager: SessionManager.open("/path/to/session.jsonl"),
802
- });
803
-
804
- // Resume a session kept outside the filesystem, e.g. in a database
805
- const { session: restored } = await createAgentSession({
806
- sessionManager: SessionManager.inMemory(process.cwd(), { id: sessionId }, entries),
807
- });
808
-
809
- // List sessions
810
- const currentProjectSessions = await SessionManager.list(process.cwd());
811
- const allSessions = await SessionManager.listAll(process.cwd());
812
-
813
- // Session replacement API for /new, /resume, /fork, /clone, and import flows.
814
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
815
- const services = await createAgentSessionServices({ cwd });
816
- return {
817
- ...(await createAgentSessionFromServices({
818
- services,
819
- sessionManager,
820
- sessionStartEvent,
821
- })),
822
- services,
823
- diagnostics: services.diagnostics,
824
- };
825
- };
826
-
827
- const runtime = await createAgentSessionRuntime(createRuntime, {
828
- cwd: process.cwd(),
829
- agentDir: getAgentDir(),
830
- sessionManager: SessionManager.create(process.cwd()),
831
- });
832
-
833
- // Replace the active session with a fresh one
834
- await runtime.newSession();
835
-
836
- // Replace the active session with another saved session
837
- await runtime.switchSession("/path/to/session.jsonl");
838
-
839
- // Replace the active session with a fork from a specific user entry
840
- await runtime.fork("entry-id");
841
-
842
- // Clone the active path through a specific entry
843
- await runtime.fork("entry-id", { position: "at" });
844
- ```
845
-
846
- **SessionManager tree API:**
847
-
848
- ```typescript
849
- const sm = SessionManager.open("/path/to/session.jsonl");
850
-
851
- // Session listing
852
- const currentProjectSessions = await SessionManager.list(process.cwd());
853
- const allSessions = await SessionManager.listAll(process.cwd());
854
-
855
- // Tree traversal
856
- const entries = sm.getEntries(); // All entries (excludes header)
857
- const tree = sm.getTree(); // Full tree structure
858
- const path = sm.getPath(); // Path from root to current leaf
859
- const leaf = sm.getLeafEntry(); // Current leaf entry
860
- const entry = sm.getEntry(id); // Get entry by ID
861
- const children = sm.getChildren(id); // Direct children of entry
862
-
863
- // Labels
864
- const label = sm.getLabel(id); // Get label for entry
865
- sm.appendLabelChange(id, "checkpoint"); // Set label
866
-
867
- // Branching
868
- sm.branch(entryId); // Move leaf to earlier entry
869
- sm.branchWithSummary(id, "Summary..."); // Branch with context summary
870
- sm.createBranchedSession(leafId); // Extract path to new file
871
- ```
872
-
873
- > See [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) and [Session Format](session-format.md)
874
-
875
- ### Settings Management
876
-
877
- ```typescript
878
- import { createAgentSession, SettingsManager, SessionManager } from "@earendil-works/pi-coding-agent";
879
-
880
- // Default: loads from files (global + project merged)
881
- const { session } = await createAgentSession({
882
- settingsManager: SettingsManager.create(),
883
- });
884
-
885
- // With overrides
886
- const settingsManager = SettingsManager.create();
887
- settingsManager.applyOverrides({
888
- compaction: { enabled: false },
889
- retry: { enabled: true, maxRetries: 5 },
890
- });
891
- const { session } = await createAgentSession({ settingsManager });
892
-
893
- // In-memory (no file I/O, for testing)
894
47
  const { session } = await createAgentSession({
895
- settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
896
48
  sessionManager: SessionManager.inMemory(),
897
49
  });
898
-
899
- // Custom directories
900
- const { session } = await createAgentSession({
901
- settingsManager: SettingsManager.create("/custom/cwd", "/custom/agent"),
902
- });
903
50
  ```
904
51
 
905
- **Static factories:**
906
- - `SettingsManager.create(cwd?, agentDir?)` - Load from files
907
- - `SettingsManager.inMemory(settings?)` - No file I/O
908
-
909
- **Project-specific settings:**
52
+ See the checked [sessions example](../examples/sdk/11-sessions.ts) for creating, opening, continuing, listing, and forking sessions. [Session File Format](session-format.md) defines the persisted JSONL contract, and [Message Types](message-types.md) defines transcript values. For exact methods and signatures, use the exported TypeScript declarations or [`session-manager.ts`](../src/core/session-manager.ts).
910
53
 
911
- Settings load from two locations and merge:
912
- 1. Global: `~/.pi/agent/settings.json`
913
- 2. Project: `<cwd>/.pi/settings.json`
54
+ `cwd` selects the workspace used for project resource discovery, context files, session grouping, and built-in tool paths. Pass it explicitly when the target differs from `process.cwd()`.
914
55
 
915
- Project overrides global. Nested objects merge keys. Setters modify global settings by default.
56
+ `session.dispose()` aborts active work, invalidates extension contexts, disconnects from the agent, and removes event listeners. Call it when the session is no longer needed.
916
57
 
917
- **Persistence and error handling semantics:**
58
+ `AgentSessionRuntime` adds `newSession()`, `switchSession()`, `fork()`, and `importFromJsonl()`. Each operation replaces the active `AgentSession` and recreates services for the target working directory.
918
59
 
919
- - Settings getters/setters are synchronous for in-memory state.
920
- - Setters enqueue persistence writes asynchronously.
921
- - Call `await settingsManager.flush()` when you need a durability boundary (for example, before process exit or before asserting file contents in tests).
922
- - `SettingsManager` does not print settings I/O errors. Use `settingsManager.drainErrors()` and report them in your app layer.
60
+ After a runtime replacement, subscriptions belong to the old `AgentSession` and must be rebound. See the [session runtime example](../examples/sdk/13-session-runtime.ts).
923
61
 
924
- > See [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)
62
+ ## Prompting
925
63
 
926
- ## ResourceLoader
64
+ `prompt()` handles extension commands and expands file-based prompt templates before ordinary user messages enter the agent. For an accepted agent run, it resolves after the run finishes, including automatic retries.
927
65
 
928
- Use `DefaultResourceLoader` to discover extensions, skills, prompts, themes, and context files.
66
+ A prompt sent while the session is already streaming must specify whether it should steer the current run or follow it. Calling `prompt()` without that choice rejects rather than guessing.
929
67
 
930
- ```typescript
931
- import {
932
- DefaultResourceLoader,
933
- getAgentDir,
934
- } from "@earendil-works/pi-coding-agent";
935
-
936
- const loader = new DefaultResourceLoader({
937
- cwd,
938
- agentDir: getAgentDir(),
939
- });
940
- await loader.reload();
68
+ A steering message enters after the current assistant turn and its tool calls. A follow-up enters after the current run finishes its pending work. `steer()` and `followUp()` expose those behaviors directly.
941
69
 
942
- const extensions = loader.getExtensions();
943
- const skills = loader.getSkills();
944
- const prompts = loader.getPrompts();
945
- const themes = loader.getThemes();
946
- const contextFiles = loader.getAgentsFiles().agentsFiles;
947
- ```
70
+ `abort()` stops the active operation and waits for the session to become idle. `waitForIdle()` waits without aborting it.
948
71
 
949
- ## Return Value
72
+ ## Subscribing to events
950
73
 
951
- `createAgentSession()` returns:
74
+ Subscribe before prompting when the host needs streamed output:
952
75
 
953
76
  ```typescript
954
- interface CreateAgentSessionResult {
955
- // The session
956
- session: AgentSession;
957
-
958
- // Extensions result (for runner setup)
959
- extensionsResult: LoadExtensionsResult;
960
-
961
- // Warning if session model couldn't be restored
962
- modelFallbackMessage?: string;
963
- }
964
-
965
- interface LoadExtensionsResult {
966
- extensions: Extension[];
967
- errors: Array<{ path: string; error: string }>;
968
- runtime: ExtensionRuntime;
969
- }
970
- ```
971
-
972
- ## Complete Example
973
-
974
- ```typescript
975
- import { getModel } from "@earendil-works/pi-ai";
976
- import { Type } from "typebox";
977
- import {
978
- createAgentSession,
979
- DefaultResourceLoader,
980
- defineTool,
981
- ModelRuntime,
982
- SessionManager,
983
- SettingsManager,
984
- } from "@earendil-works/pi-coding-agent";
985
-
986
- const modelRuntime = await ModelRuntime.create({
987
- authPath: "/custom/agent/auth.json",
988
- modelsPath: "/custom/agent/models.json",
989
- });
990
- if (process.env.MY_KEY) {
991
- await modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
992
- }
993
-
994
- // Inline tool
995
- const statusTool = defineTool({
996
- name: "status",
997
- label: "Status",
998
- description: "Get system status",
999
- parameters: Type.Object({}),
1000
- execute: async () => ({
1001
- content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
1002
- details: {},
1003
- }),
1004
- });
1005
-
1006
- const model = getModel("anthropic", "claude-opus-4-5");
1007
- if (!model) throw new Error("Model not found");
1008
-
1009
- // In-memory settings with overrides
1010
- const settingsManager = SettingsManager.inMemory({
1011
- compaction: { enabled: false },
1012
- retry: { enabled: true, maxRetries: 2 },
1013
- });
1014
-
1015
- const loader = new DefaultResourceLoader({
1016
- cwd: process.cwd(),
1017
- agentDir: "/custom/agent",
1018
- settingsManager,
1019
- systemPromptOverride: () => "You are a minimal assistant. Be concise.",
1020
- });
1021
- await loader.reload();
1022
-
1023
- const { session } = await createAgentSession({
1024
- cwd: process.cwd(),
1025
- agentDir: "/custom/agent",
1026
-
1027
- model,
1028
- thinkingLevel: "off",
1029
- modelRuntime,
1030
-
1031
- tools: ["read", "bash", "status"],
1032
- customTools: [statusTool],
1033
- resourceLoader: loader,
1034
-
1035
- sessionManager: SessionManager.inMemory(),
1036
- settingsManager,
1037
- });
1038
-
1039
- session.subscribe((event) => {
77
+ const unsubscribe = session.subscribe((event) => {
1040
78
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
1041
79
  process.stdout.write(event.assistantMessageEvent.delta);
1042
80
  }
1043
81
  });
1044
82
 
1045
- await session.prompt("Get status and list files.");
1046
- ```
1047
-
1048
- ## Run Modes
1049
-
1050
- The SDK exports run mode utilities for building custom interfaces on top of `createAgentSession()`:
1051
-
1052
- ### InteractiveMode
1053
-
1054
- Full TUI interactive mode with editor, chat history, and all built-in commands:
1055
-
1056
- ```typescript
1057
- import {
1058
- type CreateAgentSessionRuntimeFactory,
1059
- createAgentSessionFromServices,
1060
- createAgentSessionRuntime,
1061
- createAgentSessionServices,
1062
- getAgentDir,
1063
- InteractiveMode,
1064
- SessionManager,
1065
- } from "@earendil-works/pi-coding-agent";
1066
-
1067
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1068
- const services = await createAgentSessionServices({ cwd });
1069
- return {
1070
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1071
- services,
1072
- diagnostics: services.diagnostics,
1073
- };
1074
- };
1075
- const runtime = await createAgentSessionRuntime(createRuntime, {
1076
- cwd: process.cwd(),
1077
- agentDir: getAgentDir(),
1078
- sessionManager: SessionManager.create(process.cwd()),
1079
- });
1080
-
1081
- const mode = new InteractiveMode(runtime, {
1082
- migratedProviders: [],
1083
- modelFallbackMessage: undefined,
1084
- initialMessage: "Hello",
1085
- initialImages: [],
1086
- initialMessages: [],
1087
- });
1088
-
1089
- await mode.run();
1090
- ```
1091
-
1092
- ### runPrintMode
1093
-
1094
- Single-shot mode: send prompts, output result, exit:
1095
-
1096
- ```typescript
1097
- import {
1098
- type CreateAgentSessionRuntimeFactory,
1099
- createAgentSessionFromServices,
1100
- createAgentSessionRuntime,
1101
- createAgentSessionServices,
1102
- getAgentDir,
1103
- runPrintMode,
1104
- SessionManager,
1105
- } from "@earendil-works/pi-coding-agent";
1106
-
1107
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1108
- const services = await createAgentSessionServices({ cwd });
1109
- return {
1110
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1111
- services,
1112
- diagnostics: services.diagnostics,
1113
- };
1114
- };
1115
- const runtime = await createAgentSessionRuntime(createRuntime, {
1116
- cwd: process.cwd(),
1117
- agentDir: getAgentDir(),
1118
- sessionManager: SessionManager.create(process.cwd()),
1119
- });
1120
-
1121
- await runPrintMode(runtime, {
1122
- mode: "text",
1123
- initialMessage: "Hello",
1124
- initialImages: [],
1125
- messages: ["Follow up"],
1126
- });
1127
- ```
1128
-
1129
- ### runRpcMode
1130
-
1131
- JSON-RPC mode for subprocess integration:
1132
-
1133
- ```typescript
1134
- import {
1135
- type CreateAgentSessionRuntimeFactory,
1136
- createAgentSessionFromServices,
1137
- createAgentSessionRuntime,
1138
- createAgentSessionServices,
1139
- getAgentDir,
1140
- runRpcMode,
1141
- SessionManager,
1142
- } from "@earendil-works/pi-coding-agent";
1143
-
1144
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1145
- const services = await createAgentSessionServices({ cwd });
1146
- return {
1147
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1148
- services,
1149
- diagnostics: services.diagnostics,
1150
- };
1151
- };
1152
- const runtime = await createAgentSessionRuntime(createRuntime, {
1153
- cwd: process.cwd(),
1154
- agentDir: getAgentDir(),
1155
- sessionManager: SessionManager.create(process.cwd()),
1156
- });
1157
-
1158
- await runRpcMode(runtime);
83
+ try {
84
+ await session.prompt("Explain this repository");
85
+ } finally {
86
+ unsubscribe();
87
+ }
1159
88
  ```
1160
89
 
1161
- See [RPC documentation](rpc.md) for the JSON protocol.
90
+ Session events report message updates, tool execution, queues, compaction, retries, and run lifecycle changes.
1162
91
 
1163
- ## RPC Mode Alternative
92
+ `message_end` contains the authoritative completed message. `agent_end` marks the end of one low-level agent run, but automatic recovery or queued work can still follow.
1164
93
 
1165
- For subprocess-based integration without building with the SDK, use the CLI directly:
94
+ Use `agent_settled` when the host needs to know that Pi will not continue automatically.
1166
95
 
1167
- ```bash
1168
- pi --mode rpc --no-session
1169
- ```
96
+ ## Configuring a session
1170
97
 
1171
- See [RPC documentation](rpc.md) for the JSON protocol.
98
+ Without overrides, the factory creates a `ModelRuntime`, file-backed `SettingsManager`, persistent `SessionManager`, `DefaultResourceLoader`, and the configured default tools.
1172
99
 
1173
- The SDK is preferred when:
1174
- - You want type safety
1175
- - You're in the same Node.js process
1176
- - You need direct access to agent state
1177
- - You want to customize tools/extensions programmatically
100
+ Each boundary can be supplied explicitly:
1178
101
 
1179
- RPC mode is preferred when:
1180
- - You're integrating from another language
1181
- - You want process isolation
1182
- - You're building a language-agnostic client
102
+ - `modelRuntime`, `model`, `thinkingLevel`, and `scopedModels` control model access and selection.
103
+ - `settingsManager` supplies merged settings or an in-memory configuration.
104
+ - `sessionManager` supplies persistent or in-memory conversation history.
105
+ - `resourceLoader` supplies extensions, skills, prompt templates, themes, and context files.
106
+ - `tools`, `noTools`, `excludeTools`, and `customTools` control the active tool set.
1183
107
 
1184
- ## Exports
108
+ Use `DefaultResourceLoader` when you want standard discovery with selected overrides. Supply a custom `ResourceLoader` when the host owns resource storage and discovery completely.
1185
109
 
1186
- The main entry point exports:
110
+ <a id="inlineextension"></a>
1187
111
 
1188
- ```typescript
1189
- // Factory
1190
- createAgentSession
1191
- createAgentSessionRuntime
1192
- AgentSessionRuntime
112
+ Inline extension factories can be supplied through `DefaultResourceLoader`. Give one an `InlineExtension` name only when it needs a stable name in diagnostics and startup output.
1193
113
 
1194
- // Auth and Models
1195
- ModelRuntime // implements pi-ai Models and owns credential storage
1196
- ModelRegistry // synchronous extension compatibility facade
1197
- CredentialSynchronizationError
1198
- resolveCliModel
1199
- resolveModelScopeWithDiagnostics
114
+ See the focused examples for [models](../examples/sdk/02-custom-model.ts), [tools](../examples/sdk/05-tools.ts), [extensions](../examples/sdk/06-extensions.ts), and [full control](../examples/sdk/12-full-control.ts).
1200
115
 
1201
- // Resource loading
1202
- DefaultResourceLoader
1203
- type ResourceLoader
1204
- createEventBus
116
+ ## Examples
1205
117
 
1206
- // Constants and helpers
1207
- CONFIG_DIR_NAME
1208
- defineTool
1209
- getAgentDir
1210
- getPackageDir
1211
- getReadmePath
1212
- getDocsPath
1213
- getExamplesPath
118
+ | Example | Purpose |
119
+ |---|---|
120
+ | [Minimal](../examples/sdk/01-minimal.ts) | Create, prompt, observe, and dispose a session |
121
+ | [Custom model](../examples/sdk/02-custom-model.ts) | Select a model and thinking level |
122
+ | [System prompt](../examples/sdk/03-custom-prompt.ts) | Replace or append to the system prompt |
123
+ | [Skills](../examples/sdk/04-skills.ts) | Discover, filter, and add skills |
124
+ | [Tools](../examples/sdk/05-tools.ts) | Select built-in tools and their working directory |
125
+ | [Extensions](../examples/sdk/06-extensions.ts) | Load file-based and inline extensions |
126
+ | [Context files](../examples/sdk/07-context-files.ts) | Add or replace project instructions |
127
+ | [Prompt templates](../examples/sdk/08-prompt-templates.ts) | Add file-style prompt templates |
128
+ | [Credentials](../examples/sdk/09-api-keys-and-oauth.ts) | Configure credential and model storage |
129
+ | [Settings](../examples/sdk/10-settings.ts) | Supply file-backed or in-memory settings |
130
+ | [Sessions](../examples/sdk/11-sessions.ts) | Control session persistence and restoration |
131
+ | [Full control](../examples/sdk/12-full-control.ts) | Replace default discovery and state services |
132
+ | [Session runtime](../examples/sdk/13-session-runtime.ts) | Replace the active session safely |
1214
133
 
1215
- // Session management
1216
- SessionManager
1217
- SettingsManager
134
+ <a id="exports"></a>
1218
135
 
1219
- // Tool factories
1220
- createCodingTools
1221
- createReadOnlyTools
1222
- createReadTool, createBashTool, createPowerShellTool, createEditTool, createWriteTool
1223
- createGrepTool, createFindTool, createLsTool
1224
-
1225
- // Types
1226
- type CreateAgentSessionOptions
1227
- type CreateAgentSessionResult
1228
- type ExtensionFactory
1229
- type InlineExtension
1230
- type ExtensionAPI
1231
- type ToolDefinition
1232
- type Skill
1233
- type PromptTemplate
1234
- type Tool
1235
- ```
136
+ ## Resources
1236
137
 
1237
- For extension types, see [extensions.md](extensions.md) for the full API.
138
+ - [Choose a Model](models.md) covers model selection and compatible endpoints; [Provider Authentication](providers.md) covers credentials and cloud-provider setup.
139
+ - [Configuration](configuration.md) explains normal discovery and settings; [Settings](settings.md) lists every setting.
140
+ - [Sessions and Context](sessions.md) explains session behavior; [Session Format](session-format.md) defines persisted entries; [Message Types](message-types.md) defines shared transcript values.
141
+ - [Extensions](extensions.md), [Skills](skills.md), and [Prompt Templates](prompt-templates.md) document resources supplied through a `ResourceLoader`.
142
+ - [CLI Integration](cli-integration.md) covers print, JSON, and RPC alternatives to an in-process SDK integration.