@mariozechner/pi-coding-agent 0.27.9 → 0.29.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +17 -18
  3. package/dist/cli/list-models.d.ts +2 -2
  4. package/dist/cli/list-models.d.ts.map +1 -1
  5. package/dist/cli/list-models.js +2 -7
  6. package/dist/cli/list-models.js.map +1 -1
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +3 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +6 -3
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +23 -25
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/auth-storage.d.ts +104 -0
  16. package/dist/core/auth-storage.d.ts.map +1 -0
  17. package/dist/core/auth-storage.js +232 -0
  18. package/dist/core/auth-storage.js.map +1 -0
  19. package/dist/core/custom-tools/types.d.ts +2 -2
  20. package/dist/core/custom-tools/types.d.ts.map +1 -1
  21. package/dist/core/custom-tools/types.js.map +1 -1
  22. package/dist/core/hooks/types.d.ts +3 -3
  23. package/dist/core/hooks/types.d.ts.map +1 -1
  24. package/dist/core/hooks/types.js.map +1 -1
  25. package/dist/core/model-registry.d.ts +50 -0
  26. package/dist/core/model-registry.d.ts.map +1 -0
  27. package/dist/core/model-registry.js +268 -0
  28. package/dist/core/model-registry.js.map +1 -0
  29. package/dist/core/model-resolver.d.ts +7 -7
  30. package/dist/core/model-resolver.d.ts.map +1 -1
  31. package/dist/core/model-resolver.js +12 -44
  32. package/dist/core/model-resolver.js.map +1 -1
  33. package/dist/core/sdk.d.ts +13 -26
  34. package/dist/core/sdk.d.ts.map +1 -1
  35. package/dist/core/sdk.js +24 -101
  36. package/dist/core/sdk.js.map +1 -1
  37. package/dist/core/settings-manager.d.ts +0 -5
  38. package/dist/core/settings-manager.d.ts.map +1 -1
  39. package/dist/core/settings-manager.js +0 -19
  40. package/dist/core/settings-manager.js.map +1 -1
  41. package/dist/core/skills.d.ts.map +1 -1
  42. package/dist/core/skills.js +15 -1
  43. package/dist/core/skills.js.map +1 -1
  44. package/dist/index.d.ts +3 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +4 -8
  47. package/dist/index.js.map +1 -1
  48. package/dist/main.d.ts.map +1 -1
  49. package/dist/main.js +37 -22
  50. package/dist/main.js.map +1 -1
  51. package/dist/modes/interactive/components/footer.d.ts +3 -1
  52. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  53. package/dist/modes/interactive/components/footer.js +4 -3
  54. package/dist/modes/interactive/components/footer.js.map +1 -1
  55. package/dist/modes/interactive/components/model-selector.d.ts +3 -1
  56. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/model-selector.js +21 -14
  58. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  59. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  60. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  61. package/dist/modes/interactive/components/oauth-selector.js +6 -6
  62. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  63. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  64. package/dist/modes/interactive/interactive-mode.js +56 -51
  65. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  66. package/docs/custom-tools.md +3 -3
  67. package/docs/hooks.md +9 -9
  68. package/docs/sdk.md +86 -61
  69. package/examples/custom-tools/hello/index.ts +15 -15
  70. package/examples/custom-tools/question/index.ts +3 -3
  71. package/examples/custom-tools/subagent/agents.ts +1 -2
  72. package/examples/custom-tools/subagent/index.ts +332 -125
  73. package/examples/custom-tools/todo/index.ts +30 -12
  74. package/examples/hooks/confirm-destructive.ts +6 -8
  75. package/examples/hooks/custom-compaction.ts +7 -7
  76. package/examples/hooks/dirty-repo-guard.ts +7 -15
  77. package/examples/hooks/permission-gate.ts +1 -5
  78. package/examples/sdk/02-custom-model.ts +20 -7
  79. package/examples/sdk/04-skills.ts +1 -1
  80. package/examples/sdk/05-tools.ts +11 -14
  81. package/examples/sdk/06-hooks.ts +1 -1
  82. package/examples/sdk/07-context-files.ts +1 -1
  83. package/examples/sdk/08-slash-commands.ts +3 -3
  84. package/examples/sdk/09-api-keys-and-oauth.ts +36 -26
  85. package/examples/sdk/10-settings.ts +2 -2
  86. package/examples/sdk/12-full-control.ts +19 -20
  87. package/examples/sdk/README.md +26 -13
  88. package/package.json +4 -5
  89. package/dist/core/model-config.d.ts +0 -58
  90. package/dist/core/model-config.d.ts.map +0 -1
  91. package/dist/core/model-config.js +0 -384
  92. package/dist/core/model-config.js.map +0 -1
  93. package/dist/core/oauth/index.d.ts +0 -41
  94. package/dist/core/oauth/index.d.ts.map +0 -1
  95. package/dist/core/oauth/index.js +0 -84
  96. package/dist/core/oauth/index.js.map +0 -1
@@ -186,7 +186,7 @@ interface ToolSessionEvent {
186
186
  entries: SessionEntry[]; // All session entries
187
187
  sessionFile: string | null; // Current session file
188
188
  previousSessionFile: string | null; // Previous session file
189
- reason: "start" | "switch" | "branch" | "clear";
189
+ reason: "start" | "switch" | "branch" | "new";
190
190
  }
191
191
  ```
192
192
 
@@ -194,7 +194,7 @@ interface ToolSessionEvent {
194
194
  - `start`: Initial session load on startup
195
195
  - `switch`: User switched to a different session (`/resume`)
196
196
  - `branch`: User branched from a previous message (`/branch`)
197
- - `clear`: User cleared the session (`/clear`)
197
+ - `new`: User started a new session (`/new`)
198
198
 
199
199
  ### State Management Pattern
200
200
 
@@ -250,7 +250,7 @@ const factory: CustomToolFactory = (pi) => {
250
250
  This pattern ensures:
251
251
  - When user branches, state is correct for that point in history
252
252
  - When user switches sessions, state matches that session
253
- - When user clears, state resets
253
+ - When user starts a new session, state resets
254
254
 
255
255
  ## Custom Rendering
256
256
 
package/docs/hooks.md CHANGED
@@ -125,10 +125,10 @@ user switches session (/resume)
125
125
  ├─► session (reason: "before_switch", can cancel)
126
126
  └─► session (reason: "switch", AFTER switch)
127
127
 
128
- user clears session (/clear)
128
+ user starts new session (/new)
129
129
 
130
- ├─► session (reason: "before_clear", can cancel)
131
- └─► session (reason: "clear", AFTER clear)
130
+ ├─► session (reason: "before_new", can cancel)
131
+ └─► session (reason: "new", AFTER new session starts)
132
132
 
133
133
  context compaction (auto or /compact)
134
134
 
@@ -151,12 +151,12 @@ pi.on("session", async (event, ctx) => {
151
151
  // event.entries: SessionEntry[] - all session entries
152
152
  // event.sessionFile: string | null - current session file (null with --no-session)
153
153
  // event.previousSessionFile: string | null - previous session file
154
- // event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" |
154
+ // event.reason: "start" | "before_switch" | "switch" | "before_new" | "new" |
155
155
  // "before_branch" | "branch" | "before_compact" | "compact" | "shutdown"
156
156
  // event.targetTurnIndex: number - only for "before_branch" and "branch"
157
157
 
158
158
  // Cancel a before_* action:
159
- if (event.reason === "before_clear") {
159
+ if (event.reason === "before_new") {
160
160
  return { cancel: true };
161
161
  }
162
162
 
@@ -171,7 +171,7 @@ pi.on("session", async (event, ctx) => {
171
171
  **Reasons:**
172
172
  - `start`: Initial session load on startup
173
173
  - `before_switch` / `switch`: User switched sessions (`/resume`)
174
- - `before_clear` / `clear`: User cleared the session (`/clear`)
174
+ - `before_new` / `new`: User started a new session (`/new`)
175
175
  - `before_branch` / `branch`: User branched the session (`/branch`)
176
176
  - `before_compact` / `compact`: Context compaction (auto or `/compact`)
177
177
  - `shutdown`: Process is exiting (double Ctrl+C, Ctrl+D, or SIGTERM)
@@ -848,9 +848,9 @@ Session switch:
848
848
 
849
849
  Clear:
850
850
  -> AgentSession.reset()
851
- -> hookRunner.emit({ type: "session", reason: "before_clear", ... }) # can cancel
852
- -> [if not cancelled: clear happens]
853
- -> hookRunner.emit({ type: "session", reason: "clear", ... })
851
+ -> hookRunner.emit({ type: "session", reason: "before_new", ... }) # can cancel
852
+ -> [if not cancelled: new session starts]
853
+ -> hookRunner.emit({ type: "session", reason: "new", ... })
854
854
 
855
855
  Shutdown (interactive mode):
856
856
  -> handleCtrlC() or handleCtrlD()
package/docs/sdk.md CHANGED
@@ -14,10 +14,16 @@ See [examples/sdk/](../examples/sdk/) for working examples from minimal to full
14
14
  ## Quick Start
15
15
 
16
16
  ```typescript
17
- import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
17
+ import { createAgentSession, discoverAuthStorage, discoverModels, SessionManager } from "@mariozechner/pi-coding-agent";
18
+
19
+ // Set up credential storage and model registry
20
+ const authStorage = discoverAuthStorage();
21
+ const modelRegistry = discoverModels(authStorage);
18
22
 
19
23
  const { session } = await createAgentSession({
20
24
  sessionManager: SessionManager.inMemory(),
25
+ authStorage,
26
+ modelRegistry,
21
27
  });
22
28
 
23
29
  session.subscribe((event) => {
@@ -220,32 +226,42 @@ const { session } = await createAgentSession({
220
226
  - Global commands (`commands/`)
221
227
  - Global context file (`AGENTS.md`)
222
228
  - Settings (`settings.json`)
223
- - Models (`models.json`)
224
- - OAuth tokens (`oauth.json`)
229
+ - Custom models (`models.json`)
230
+ - Credentials (`auth.json`)
225
231
  - Sessions (`sessions/`)
226
232
 
227
233
  ### Model
228
234
 
229
235
  ```typescript
230
- import { findModel, discoverAvailableModels } from "@mariozechner/pi-coding-agent";
236
+ import { getModel } from "@mariozechner/pi-ai";
237
+ import { discoverAuthStorage, discoverModels } from "@mariozechner/pi-coding-agent";
231
238
 
232
- // Find specific model (returns { model, error })
233
- const { model, error } = findModel("anthropic", "claude-sonnet-4-20250514");
234
- if (error) throw new Error(error);
235
- if (!model) throw new Error("Model not found");
239
+ const authStorage = discoverAuthStorage();
240
+ const modelRegistry = discoverModels(authStorage);
236
241
 
237
- // Or get all models with valid API keys
238
- const available = await discoverAvailableModels();
242
+ // Find specific built-in model (doesn't check if API key exists)
243
+ const opus = getModel("anthropic", "claude-opus-4-5");
244
+ if (!opus) throw new Error("Model not found");
245
+
246
+ // Find any model by provider/id, including custom models from models.json
247
+ // (doesn't check if API key exists)
248
+ const customModel = modelRegistry.find("my-provider", "my-model");
249
+
250
+ // Get only models that have valid API keys configured
251
+ const available = await modelRegistry.getAvailable();
239
252
 
240
253
  const { session } = await createAgentSession({
241
- model: model,
254
+ model: opus,
242
255
  thinkingLevel: "medium", // off, minimal, low, medium, high, xhigh
243
256
 
244
257
  // Models for cycling (Ctrl+P in interactive mode)
245
258
  scopedModels: [
246
- { model: sonnet, thinkingLevel: "high" },
259
+ { model: opus, thinkingLevel: "high" },
247
260
  { model: haiku, thinkingLevel: "off" },
248
261
  ],
262
+
263
+ authStorage,
264
+ modelRegistry,
249
265
  });
250
266
  ```
251
267
 
@@ -256,38 +272,42 @@ If no model is provided:
256
272
 
257
273
  > See [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)
258
274
 
259
- ### API Keys
275
+ ### API Keys and OAuth
260
276
 
261
- API key resolution priority:
262
- 1. `settings.json` apiKeys (e.g., `{ "apiKeys": { "anthropic": "sk-..." } }`)
263
- 2. Custom providers from `models.json`
264
- 3. OAuth credentials from `oauth.json`
265
- 4. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
277
+ API key resolution priority (handled by AuthStorage):
278
+ 1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
279
+ 2. Stored credentials in `auth.json` (API keys or OAuth tokens)
280
+ 3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
281
+ 4. Fallback resolver (for custom provider keys from `models.json`)
266
282
 
267
283
  ```typescript
268
- import { defaultGetApiKey, configureOAuthStorage } from "@mariozechner/pi-coding-agent";
284
+ import { AuthStorage, ModelRegistry, discoverAuthStorage, discoverModels } from "@mariozechner/pi-coding-agent";
269
285
 
270
- // Default: checks settings.json, models.json, OAuth, environment variables
271
- const { session } = await createAgentSession();
286
+ // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
287
+ const authStorage = discoverAuthStorage();
288
+ const modelRegistry = discoverModels(authStorage);
272
289
 
273
- // Custom resolver
274
290
  const { session } = await createAgentSession({
275
- getApiKey: async (model) => {
276
- // Custom logic (secrets manager, database, etc.)
277
- if (model.provider === "anthropic") {
278
- return process.env.MY_ANTHROPIC_KEY;
279
- }
280
- // Fall back to default (pass settingsManager for settings.json lookup)
281
- return defaultGetApiKey()(model);
282
- },
291
+ sessionManager: SessionManager.inMemory(),
292
+ authStorage,
293
+ modelRegistry,
283
294
  });
284
295
 
285
- // Use OAuth from ~/.pi/agent with custom agentDir for everything else
286
- configureOAuthStorage(); // Must call before createAgentSession
296
+ // Runtime API key override (not persisted to disk)
297
+ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
298
+
299
+ // Custom auth storage location
300
+ const customAuth = new AuthStorage("/my/app/auth.json");
301
+ const customRegistry = new ModelRegistry(customAuth, "/my/app/models.json");
302
+
287
303
  const { session } = await createAgentSession({
288
- agentDir: "/custom/config",
289
- // OAuth tokens still come from ~/.pi/agent/oauth.json
304
+ sessionManager: SessionManager.inMemory(),
305
+ authStorage: customAuth,
306
+ modelRegistry: customRegistry,
290
307
  });
308
+
309
+ // No custom models.json (built-in models only)
310
+ const simpleRegistry = new ModelRegistry(authStorage);
291
311
  ```
292
312
 
293
313
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
@@ -630,10 +650,12 @@ Project overrides global. Nested objects merge keys. Setters only modify global
630
650
  All discovery functions accept optional `cwd` and `agentDir` parameters.
631
651
 
632
652
  ```typescript
653
+ import { getModel } from "@mariozechner/pi-ai";
633
654
  import {
655
+ AuthStorage,
656
+ ModelRegistry,
657
+ discoverAuthStorage,
634
658
  discoverModels,
635
- discoverAvailableModels,
636
- findModel,
637
659
  discoverSkills,
638
660
  discoverHooks,
639
661
  discoverCustomTools,
@@ -643,10 +665,13 @@ import {
643
665
  buildSystemPrompt,
644
666
  } from "@mariozechner/pi-coding-agent";
645
667
 
646
- // Models
647
- const allModels = discoverModels();
648
- const available = await discoverAvailableModels();
649
- const { model, error } = findModel("anthropic", "claude-sonnet-4-20250514");
668
+ // Auth and Models
669
+ const authStorage = discoverAuthStorage(); // ~/.pi/agent/auth.json
670
+ const modelRegistry = discoverModels(authStorage); // + ~/.pi/agent/models.json
671
+ const allModels = modelRegistry.getAll(); // All models (built-in + custom)
672
+ const available = await modelRegistry.getAvailable(); // Only models with API keys
673
+ const model = modelRegistry.find("provider", "id"); // Find specific model
674
+ const builtIn = getModel("anthropic", "claude-opus-4-5"); // Built-in only
650
675
 
651
676
  // Skills
652
677
  const skills = discoverSkills(cwd, agentDir, skillsSettings);
@@ -698,12 +723,12 @@ interface CreateAgentSessionResult {
698
723
  ## Complete Example
699
724
 
700
725
  ```typescript
726
+ import { getModel } from "@mariozechner/pi-ai";
701
727
  import { Type } from "@sinclair/typebox";
702
728
  import {
729
+ AuthStorage,
703
730
  createAgentSession,
704
- configureOAuthStorage,
705
- defaultGetApiKey,
706
- findModel,
731
+ ModelRegistry,
707
732
  SessionManager,
708
733
  SettingsManager,
709
734
  readTool,
@@ -711,18 +736,17 @@ import {
711
736
  type HookFactory,
712
737
  type CustomAgentTool,
713
738
  } from "@mariozechner/pi-coding-agent";
714
- import { getAgentDir } from "@mariozechner/pi-coding-agent/config";
715
739
 
716
- // Use OAuth from default location
717
- configureOAuthStorage(getAgentDir());
740
+ // Set up auth storage (custom location)
741
+ const authStorage = new AuthStorage("/custom/agent/auth.json");
718
742
 
719
- // Custom API key with fallback
720
- const getApiKey = async (model: { provider: string }) => {
721
- if (model.provider === "anthropic" && process.env.MY_KEY) {
722
- return process.env.MY_KEY;
723
- }
724
- return defaultGetApiKey()(model as any);
725
- };
743
+ // Runtime API key override (not persisted)
744
+ if (process.env.MY_KEY) {
745
+ authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
746
+ }
747
+
748
+ // Model registry (no custom models.json)
749
+ const modelRegistry = new ModelRegistry(authStorage);
726
750
 
727
751
  // Inline hook
728
752
  const auditHook: HookFactory = (api) => {
@@ -744,8 +768,7 @@ const statusTool: CustomAgentTool = {
744
768
  }),
745
769
  };
746
770
 
747
- const { model, error } = findModel("anthropic", "claude-sonnet-4-20250514");
748
- if (error) throw new Error(error);
771
+ const model = getModel("anthropic", "claude-opus-4-5");
749
772
  if (!model) throw new Error("Model not found");
750
773
 
751
774
  // In-memory settings with overrides
@@ -760,7 +783,8 @@ const { session } = await createAgentSession({
760
783
 
761
784
  model,
762
785
  thinkingLevel: "off",
763
- getApiKey,
786
+ authStorage,
787
+ modelRegistry,
764
788
 
765
789
  systemPrompt: "You are a minimal assistant. Be concise.",
766
790
 
@@ -812,12 +836,14 @@ The main entry point exports:
812
836
  ```typescript
813
837
  // Factory
814
838
  createAgentSession
815
- configureOAuthStorage
816
839
 
817
- // Discovery
840
+ // Auth and Models
841
+ AuthStorage
842
+ ModelRegistry
843
+ discoverAuthStorage
818
844
  discoverModels
819
- discoverAvailableModels
820
- findModel
845
+
846
+ // Discovery
821
847
  discoverSkills
822
848
  discoverHooks
823
849
  discoverCustomTools
@@ -825,7 +851,6 @@ discoverContextFiles
825
851
  discoverSlashCommands
826
852
 
827
853
  // Helpers
828
- defaultGetApiKey
829
854
  loadSettings
830
855
  buildSystemPrompt
831
856
 
@@ -1,20 +1,20 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
2
+ import { Type } from "@sinclair/typebox";
3
3
 
4
- const factory: CustomToolFactory = (pi) => ({
5
- name: "hello",
6
- label: "Hello",
7
- description: "A simple greeting tool",
8
- parameters: Type.Object({
9
- name: Type.String({ description: "Name to greet" }),
10
- }),
4
+ const factory: CustomToolFactory = (_pi) => ({
5
+ name: "hello",
6
+ label: "Hello",
7
+ description: "A simple greeting tool",
8
+ parameters: Type.Object({
9
+ name: Type.String({ description: "Name to greet" }),
10
+ }),
11
11
 
12
- async execute(toolCallId, params) {
13
- return {
14
- content: [{ type: "text", text: `Hello, ${params.name}!` }],
15
- details: { greeted: params.name },
16
- };
17
- },
12
+ async execute(_toolCallId, params) {
13
+ return {
14
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
15
+ details: { greeted: params.name },
16
+ };
17
+ },
18
18
  });
19
19
 
20
- export default factory;
20
+ export default factory;
@@ -2,9 +2,9 @@
2
2
  * Question Tool - Let the LLM ask the user a question with options
3
3
  */
4
4
 
5
- import { Type } from "@sinclair/typebox";
6
- import { Text } from "@mariozechner/pi-tui";
7
5
  import type { CustomAgentTool, CustomToolFactory } from "@mariozechner/pi-coding-agent";
6
+ import { Text } from "@mariozechner/pi-tui";
7
+ import { Type } from "@sinclair/typebox";
8
8
 
9
9
  interface QuestionDetails {
10
10
  question: string;
@@ -57,7 +57,7 @@ const factory: CustomToolFactory = (pi) => {
57
57
  renderCall(args, theme) {
58
58
  let text = theme.fg("toolTitle", theme.bold("question ")) + theme.fg("muted", args.question);
59
59
  if (args.options?.length) {
60
- text += "\n" + theme.fg("dim", ` Options: ${args.options.join(", ")}`);
60
+ text += `\n${theme.fg("dim", ` Options: ${args.options.join(", ")}`)}`;
61
61
  }
62
62
  return new Text(text, 0, 0);
63
63
  },
@@ -129,8 +129,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
129
129
  const projectAgentsDir = findNearestProjectAgentsDir(cwd);
130
130
 
131
131
  const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
132
- const projectAgents =
133
- scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
132
+ const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
134
133
 
135
134
  const agentMap = new Map<string, AgentConfig>();
136
135