@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
@@ -8,10 +8,10 @@
8
8
  * The onSession callback reconstructs state by scanning past tool results.
9
9
  */
10
10
 
11
- import { Type } from "@sinclair/typebox";
12
11
  import { StringEnum } from "@mariozechner/pi-ai";
13
- import { Text } from "@mariozechner/pi-tui";
14
12
  import type { CustomAgentTool, CustomToolFactory, ToolSessionEvent } from "@mariozechner/pi-coding-agent";
13
+ import { Text } from "@mariozechner/pi-tui";
14
+ import { Type } from "@sinclair/typebox";
15
15
 
16
16
  interface Todo {
17
17
  id: number;
@@ -76,11 +76,18 @@ const factory: CustomToolFactory = (_pi) => {
76
76
  switch (params.action) {
77
77
  case "list":
78
78
  return {
79
- content: [{ type: "text", text: todos.length ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n") : "No todos" }],
79
+ content: [
80
+ {
81
+ type: "text",
82
+ text: todos.length
83
+ ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n")
84
+ : "No todos",
85
+ },
86
+ ],
80
87
  details: { action: "list", todos: [...todos], nextId },
81
88
  };
82
89
 
83
- case "add":
90
+ case "add": {
84
91
  if (!params.text) {
85
92
  return {
86
93
  content: [{ type: "text", text: "Error: text required for add" }],
@@ -93,8 +100,9 @@ const factory: CustomToolFactory = (_pi) => {
93
100
  content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }],
94
101
  details: { action: "add", todos: [...todos], nextId },
95
102
  };
103
+ }
96
104
 
97
- case "toggle":
105
+ case "toggle": {
98
106
  if (params.id === undefined) {
99
107
  return {
100
108
  content: [{ type: "text", text: "Error: id required for toggle" }],
@@ -113,8 +121,9 @@ const factory: CustomToolFactory = (_pi) => {
113
121
  content: [{ type: "text", text: `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}` }],
114
122
  details: { action: "toggle", todos: [...todos], nextId },
115
123
  };
124
+ }
116
125
 
117
- case "clear":
126
+ case "clear": {
118
127
  const count = todos.length;
119
128
  todos = [];
120
129
  nextId = 1;
@@ -122,6 +131,7 @@ const factory: CustomToolFactory = (_pi) => {
122
131
  content: [{ type: "text", text: `Cleared ${count} todos` }],
123
132
  details: { action: "clear", todos: [], nextId: 1 },
124
133
  };
134
+ }
125
135
 
126
136
  default:
127
137
  return {
@@ -133,8 +143,8 @@ const factory: CustomToolFactory = (_pi) => {
133
143
 
134
144
  renderCall(args, theme) {
135
145
  let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
136
- if (args.text) text += " " + theme.fg("dim", `"${args.text}"`);
137
- if (args.id !== undefined) text += " " + theme.fg("accent", `#${args.id}`);
146
+ if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
147
+ if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
138
148
  return new Text(text, 0, 0);
139
149
  },
140
150
 
@@ -153,7 +163,7 @@ const factory: CustomToolFactory = (_pi) => {
153
163
  const todoList = details.todos;
154
164
 
155
165
  switch (details.action) {
156
- case "list":
166
+ case "list": {
157
167
  if (todoList.length === 0) {
158
168
  return new Text(theme.fg("dim", "No todos"), 0, 0);
159
169
  }
@@ -162,16 +172,24 @@ const factory: CustomToolFactory = (_pi) => {
162
172
  for (const t of display) {
163
173
  const check = t.done ? theme.fg("success", "✓") : theme.fg("dim", "○");
164
174
  const itemText = t.done ? theme.fg("dim", t.text) : theme.fg("muted", t.text);
165
- listText += "\n" + check + " " + theme.fg("accent", `#${t.id}`) + " " + itemText;
175
+ listText += `\n${check} ${theme.fg("accent", `#${t.id}`)} ${itemText}`;
166
176
  }
167
177
  if (!expanded && todoList.length > 5) {
168
- listText += "\n" + theme.fg("dim", `... ${todoList.length - 5} more`);
178
+ listText += `\n${theme.fg("dim", `... ${todoList.length - 5} more`)}`;
169
179
  }
170
180
  return new Text(listText, 0, 0);
181
+ }
171
182
 
172
183
  case "add": {
173
184
  const added = todoList[todoList.length - 1];
174
- return new Text(theme.fg("success", "✓ Added ") + theme.fg("accent", `#${added.id}`) + " " + theme.fg("muted", added.text), 0, 0);
185
+ return new Text(
186
+ theme.fg("success", "✓ Added ") +
187
+ theme.fg("accent", `#${added.id}`) +
188
+ " " +
189
+ theme.fg("muted", added.text),
190
+ 0,
191
+ 0,
192
+ );
175
193
  }
176
194
 
177
195
  case "toggle": {
@@ -10,7 +10,7 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
10
10
  export default function (pi: HookAPI) {
11
11
  pi.on("session", async (event, ctx) => {
12
12
  // Only handle before_* events (the ones that can be cancelled)
13
- if (event.reason === "before_clear") {
13
+ if (event.reason === "before_new") {
14
14
  if (!ctx.hasUI) return;
15
15
 
16
16
  const confirmed = await ctx.ui.confirm(
@@ -28,9 +28,7 @@ export default function (pi: HookAPI) {
28
28
  if (!ctx.hasUI) return;
29
29
 
30
30
  // Check if there are unsaved changes (messages since last assistant response)
31
- const hasUnsavedWork = event.entries.some(
32
- (e) => e.type === "message" && e.message.role === "user",
33
- );
31
+ const hasUnsavedWork = event.entries.some((e) => e.type === "message" && e.message.role === "user");
34
32
 
35
33
  if (hasUnsavedWork) {
36
34
  const confirmed = await ctx.ui.confirm(
@@ -48,10 +46,10 @@ export default function (pi: HookAPI) {
48
46
  if (event.reason === "before_branch") {
49
47
  if (!ctx.hasUI) return;
50
48
 
51
- const choice = await ctx.ui.select(
52
- `Branch from turn ${event.targetTurnIndex}?`,
53
- ["Yes, create branch", "No, stay in current session"],
54
- );
49
+ const choice = await ctx.ui.select(`Branch from turn ${event.targetTurnIndex}?`, [
50
+ "Yes, create branch",
51
+ "No, stay in current session",
52
+ ]);
55
53
 
56
54
  if (choice !== "Yes, create branch") {
57
55
  ctx.ui.notify("Branch cancelled", "info");
@@ -13,8 +13,8 @@
13
13
  * pi --hook examples/hooks/custom-compaction.ts
14
14
  */
15
15
 
16
- import { complete } from "@mariozechner/pi-ai";
17
- import { findModel, messageTransformer } from "@mariozechner/pi-coding-agent";
16
+ import { complete, getModel } from "@mariozechner/pi-ai";
17
+ import { messageTransformer } from "@mariozechner/pi-coding-agent";
18
18
  import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
19
19
 
20
20
  export default function (pi: HookAPI) {
@@ -23,13 +23,13 @@ export default function (pi: HookAPI) {
23
23
 
24
24
  ctx.ui.notify("Custom compaction hook triggered", "info");
25
25
 
26
- const { messagesToSummarize, messagesToKeep, previousSummary, tokensBefore, resolveApiKey, entries, signal } = event;
26
+ const { messagesToSummarize, messagesToKeep, previousSummary, tokensBefore, resolveApiKey, entries, signal } =
27
+ event;
27
28
 
28
29
  // Use Gemini Flash for summarization (cheaper/faster than most conversation models)
29
- // findModel searches both built-in models and custom models from models.json
30
- const { model, error } = findModel("google", "gemini-2.5-flash");
31
- if (error || !model) {
32
- ctx.ui.notify(`Could not find Gemini Flash model: ${error}, using default compaction`, "warning");
30
+ const model = getModel("google", "gemini-2.5-flash");
31
+ if (!model) {
32
+ ctx.ui.notify(`Could not find Gemini Flash model, using default compaction`, "warning");
33
33
  return;
34
34
  }
35
35
 
@@ -10,11 +10,7 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
10
10
  export default function (pi: HookAPI) {
11
11
  pi.on("session", async (event, ctx) => {
12
12
  // Only guard destructive actions
13
- if (
14
- event.reason !== "before_clear" &&
15
- event.reason !== "before_switch" &&
16
- event.reason !== "before_branch"
17
- ) {
13
+ if (event.reason !== "before_new" && event.reason !== "before_switch" && event.reason !== "before_branch") {
18
14
  return;
19
15
  }
20
16
 
@@ -40,16 +36,12 @@ export default function (pi: HookAPI) {
40
36
  const changedFiles = stdout.trim().split("\n").filter(Boolean).length;
41
37
 
42
38
  const action =
43
- event.reason === "before_clear"
44
- ? "clear session"
45
- : event.reason === "before_switch"
46
- ? "switch session"
47
- : "branch";
48
-
49
- const choice = await ctx.ui.select(
50
- `You have ${changedFiles} uncommitted file(s). ${action} anyway?`,
51
- ["Yes, proceed anyway", "No, let me commit first"],
52
- );
39
+ event.reason === "before_new" ? "new session" : event.reason === "before_switch" ? "switch session" : "branch";
40
+
41
+ const choice = await ctx.ui.select(`You have ${changedFiles} uncommitted file(s). ${action} anyway?`, [
42
+ "Yes, proceed anyway",
43
+ "No, let me commit first",
44
+ ]);
53
45
 
54
46
  if (choice !== "Yes, proceed anyway") {
55
47
  ctx.ui.notify("Commit your changes first", "warning");
@@ -8,11 +8,7 @@
8
8
  import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
9
9
 
10
10
  export default function (pi: HookAPI) {
11
- const dangerousPatterns = [
12
- /\brm\s+(-rf?|--recursive)/i,
13
- /\bsudo\b/i,
14
- /\b(chmod|chown)\b.*777/i,
15
- ];
11
+ const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
16
12
 
17
13
  pi.on("tool_call", async (event, ctx) => {
18
14
  if (event.toolName !== "bash") return undefined;
@@ -4,16 +4,27 @@
4
4
  * Shows how to select a specific model and thinking level.
5
5
  */
6
6
 
7
- import { createAgentSession, findModel, discoverAvailableModels } from "../../src/index.js";
7
+ import { getModel } from "@mariozechner/pi-ai";
8
+ import { createAgentSession, discoverAuthStorage, discoverModels } from "../../src/index.js";
8
9
 
9
- // Option 1: Find a specific model by provider/id
10
- const { model: sonnet } = findModel("anthropic", "claude-sonnet-4-20250514");
11
- if (sonnet) {
12
- console.log(`Found model: ${sonnet.provider}/${sonnet.id}`);
10
+ // Set up auth storage and model registry
11
+ const authStorage = discoverAuthStorage();
12
+ const modelRegistry = discoverModels(authStorage);
13
+
14
+ // Option 1: Find a specific built-in model by provider/id
15
+ const opus = getModel("anthropic", "claude-opus-4-5");
16
+ if (opus) {
17
+ console.log(`Found model: ${opus.provider}/${opus.id}`);
18
+ }
19
+
20
+ // Option 2: Find model via registry (includes custom models from models.json)
21
+ const customModel = modelRegistry.find("my-provider", "my-model");
22
+ if (customModel) {
23
+ console.log(`Found custom model: ${customModel.provider}/${customModel.id}`);
13
24
  }
14
25
 
15
- // Option 2: Pick from available models (have valid API keys)
16
- const available = await discoverAvailableModels();
26
+ // Option 3: Pick from available models (have valid API keys)
27
+ const available = await modelRegistry.getAvailable();
17
28
  console.log(
18
29
  "Available models:",
19
30
  available.map((m) => `${m.provider}/${m.id}`),
@@ -23,6 +34,8 @@ if (available.length > 0) {
23
34
  const { session } = await createAgentSession({
24
35
  model: available[0],
25
36
  thinkingLevel: "medium", // off, low, medium, high
37
+ authStorage,
38
+ modelRegistry,
26
39
  });
27
40
 
28
41
  session.subscribe((event) => {
@@ -27,7 +27,7 @@ const customSkill: Skill = {
27
27
  };
28
28
 
29
29
  // Use filtered + custom skills
30
- const { session } = await createAgentSession({
30
+ await createAgentSession({
31
31
  skills: [...filteredSkills, customSkill],
32
32
  sessionManager: SessionManager.inMemory(),
33
33
  });
@@ -10,31 +10,28 @@
10
10
 
11
11
  import { Type } from "@sinclair/typebox";
12
12
  import {
13
+ bashTool, // read, bash, edit, write - uses process.cwd()
14
+ type CustomAgentTool,
13
15
  createAgentSession,
14
- discoverCustomTools,
15
- SessionManager,
16
- codingTools, // read, bash, edit, write - uses process.cwd()
17
- readOnlyTools, // read, grep, find, ls - uses process.cwd()
18
- createCodingTools, // Factory: creates tools for specific cwd
19
- createReadOnlyTools, // Factory: creates tools for specific cwd
20
- createReadTool,
21
16
  createBashTool,
17
+ createCodingTools, // Factory: creates tools for specific cwd
22
18
  createGrepTool,
23
- readTool,
24
- bashTool,
19
+ createReadTool,
25
20
  grepTool,
26
- type CustomAgentTool,
21
+ readOnlyTools, // read, grep, find, ls - uses process.cwd()
22
+ readTool,
23
+ SessionManager,
27
24
  } from "../../src/index.js";
28
25
 
29
26
  // Read-only mode (no edit/write) - uses process.cwd()
30
- const { session: readOnly } = await createAgentSession({
27
+ await createAgentSession({
31
28
  tools: readOnlyTools,
32
29
  sessionManager: SessionManager.inMemory(),
33
30
  });
34
31
  console.log("Read-only session created");
35
32
 
36
33
  // Custom tool selection - uses process.cwd()
37
- const { session: custom } = await createAgentSession({
34
+ await createAgentSession({
38
35
  tools: [readTool, bashTool, grepTool],
39
36
  sessionManager: SessionManager.inMemory(),
40
37
  });
@@ -42,7 +39,7 @@ console.log("Custom tools session created");
42
39
 
43
40
  // With custom cwd - MUST use factory functions!
44
41
  const customCwd = "/path/to/project";
45
- const { session: customCwdSession } = await createAgentSession({
42
+ await createAgentSession({
46
43
  cwd: customCwd,
47
44
  tools: createCodingTools(customCwd), // Tools resolve paths relative to customCwd
48
45
  sessionManager: SessionManager.inMemory(),
@@ -50,7 +47,7 @@ const { session: customCwdSession } = await createAgentSession({
50
47
  console.log("Custom cwd session created");
51
48
 
52
49
  // Or pick specific tools for custom cwd
53
- const { session: specificTools } = await createAgentSession({
50
+ await createAgentSession({
54
51
  cwd: customCwd,
55
52
  tools: [createReadTool(customCwd), createBashTool(customCwd), createGrepTool(customCwd)],
56
53
  sessionManager: SessionManager.inMemory(),
@@ -4,7 +4,7 @@
4
4
  * Hooks intercept agent events for logging, blocking, or modification.
5
5
  */
6
6
 
7
- import { createAgentSession, discoverHooks, SessionManager, type HookFactory } from "../../src/index.js";
7
+ import { createAgentSession, type HookFactory, SessionManager } from "../../src/index.js";
8
8
 
9
9
  // Logging hook
10
10
  const loggingHook: HookFactory = (api) => {
@@ -14,7 +14,7 @@ for (const file of discovered) {
14
14
  }
15
15
 
16
16
  // Use custom context files
17
- const { session } = await createAgentSession({
17
+ await createAgentSession({
18
18
  contextFiles: [
19
19
  ...discovered,
20
20
  {
@@ -4,7 +4,7 @@
4
4
  * File-based commands that inject content when invoked with /commandname.
5
5
  */
6
6
 
7
- import { createAgentSession, discoverSlashCommands, SessionManager, type FileSlashCommand } from "../../src/index.js";
7
+ import { createAgentSession, discoverSlashCommands, type FileSlashCommand, SessionManager } from "../../src/index.js";
8
8
 
9
9
  // Discover commands from cwd/.pi/commands/ and ~/.pi/agent/commands/
10
10
  const discovered = discoverSlashCommands();
@@ -21,12 +21,12 @@ const deployCommand: FileSlashCommand = {
21
21
  content: `# Deploy Instructions
22
22
 
23
23
  1. Build: npm run build
24
- 2. Test: npm test
24
+ 2. Test: npm test
25
25
  3. Deploy: npm run deploy`,
26
26
  };
27
27
 
28
28
  // Use discovered + custom commands
29
- const { session } = await createAgentSession({
29
+ await createAgentSession({
30
30
  slashCommands: [...discovered, deployCommand],
31
31
  sessionManager: SessionManager.inMemory(),
32
32
  });
@@ -1,45 +1,55 @@
1
1
  /**
2
2
  * API Keys and OAuth
3
3
  *
4
- * Configure API key resolution. Default checks: models.json, OAuth, env vars.
4
+ * Configure API key resolution via AuthStorage and ModelRegistry.
5
5
  */
6
6
 
7
7
  import {
8
+ AuthStorage,
8
9
  createAgentSession,
9
- configureOAuthStorage,
10
- defaultGetApiKey,
10
+ discoverAuthStorage,
11
+ discoverModels,
12
+ ModelRegistry,
11
13
  SessionManager,
12
14
  } from "../../src/index.js";
13
- import { getAgentDir } from "../../src/config.js";
14
15
 
15
- // Default: uses env vars (ANTHROPIC_API_KEY, etc.), OAuth, and models.json
16
- const { session: defaultSession } = await createAgentSession({
16
+ // Default: discoverAuthStorage() uses ~/.pi/agent/auth.json
17
+ // discoverModels() loads built-in + custom models from ~/.pi/agent/models.json
18
+ const authStorage = discoverAuthStorage();
19
+ const modelRegistry = discoverModels(authStorage);
20
+
21
+ await createAgentSession({
17
22
  sessionManager: SessionManager.inMemory(),
23
+ authStorage,
24
+ modelRegistry,
18
25
  });
19
- console.log("Session with default API key resolution");
26
+ console.log("Session with default auth storage and model registry");
27
+
28
+ // Custom auth storage location
29
+ const customAuthStorage = new AuthStorage("/tmp/my-app/auth.json");
30
+ const customModelRegistry = new ModelRegistry(customAuthStorage, "/tmp/my-app/models.json");
20
31
 
21
- // Custom resolver
22
- const { session: customSession } = await createAgentSession({
23
- getApiKey: async (model) => {
24
- // Custom logic (secrets manager, database, etc.)
25
- if (model.provider === "anthropic") {
26
- return process.env.MY_ANTHROPIC_KEY;
27
- }
28
- // Fall back to default
29
- return defaultGetApiKey()(model);
30
- },
32
+ await createAgentSession({
31
33
  sessionManager: SessionManager.inMemory(),
34
+ authStorage: customAuthStorage,
35
+ modelRegistry: customModelRegistry,
32
36
  });
33
- console.log("Session with custom API key resolver");
37
+ console.log("Session with custom auth storage location");
34
38
 
35
- // Use OAuth from ~/.pi/agent while customizing everything else
36
- configureOAuthStorage(getAgentDir()); // Must call before createAgentSession
39
+ // Runtime API key override (not persisted to disk)
40
+ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
41
+ await createAgentSession({
42
+ sessionManager: SessionManager.inMemory(),
43
+ authStorage,
44
+ modelRegistry,
45
+ });
46
+ console.log("Session with runtime API key override");
37
47
 
38
- const { session: hybridSession } = await createAgentSession({
39
- agentDir: "/tmp/custom-config", // Custom config location
40
- // But OAuth tokens still come from ~/.pi/agent/oauth.json
41
- systemPrompt: "You are helpful.",
42
- skills: [],
48
+ // No models.json - only built-in models
49
+ const simpleRegistry = new ModelRegistry(authStorage); // null = no models.json
50
+ await createAgentSession({
43
51
  sessionManager: SessionManager.inMemory(),
52
+ authStorage,
53
+ modelRegistry: simpleRegistry,
44
54
  });
45
- console.log("Session with OAuth from default location, custom config elsewhere");
55
+ console.log("Session with only built-in models");
@@ -17,7 +17,7 @@ settingsManager.applyOverrides({
17
17
  retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
18
18
  });
19
19
 
20
- const { session } = await createAgentSession({
20
+ await createAgentSession({
21
21
  settingsManager,
22
22
  sessionManager: SessionManager.inMemory(),
23
23
  });
@@ -30,7 +30,7 @@ const inMemorySettings = SettingsManager.inMemory({
30
30
  retry: { enabled: false },
31
31
  });
32
32
 
33
- const { session: testSession } = await createAgentSession({
33
+ await createAgentSession({
34
34
  settingsManager: inMemorySettings,
35
35
  sessionManager: SessionManager.inMemory(),
36
36
  });
@@ -2,38 +2,36 @@
2
2
  * Full Control
3
3
  *
4
4
  * Replace everything - no discovery, explicit configuration.
5
- * Still uses OAuth from ~/.pi/agent for convenience.
6
5
  *
7
6
  * IMPORTANT: When providing `tools` with a custom `cwd`, use the tool factory
8
7
  * functions (createReadTool, createBashTool, etc.) to ensure tools resolve
9
8
  * paths relative to your cwd.
10
9
  */
11
10
 
11
+ import { getModel } from "@mariozechner/pi-ai";
12
12
  import { Type } from "@sinclair/typebox";
13
13
  import {
14
+ AuthStorage,
15
+ type CustomAgentTool,
14
16
  createAgentSession,
15
- configureOAuthStorage,
16
- defaultGetApiKey,
17
- findModel,
18
- SessionManager,
19
- SettingsManager,
20
- createReadTool,
21
17
  createBashTool,
18
+ createReadTool,
22
19
  type HookFactory,
23
- type CustomAgentTool,
20
+ ModelRegistry,
21
+ SessionManager,
22
+ SettingsManager,
24
23
  } from "../../src/index.js";
25
- import { getAgentDir } from "../../src/config.js";
26
24
 
27
- // Use OAuth from default location
28
- configureOAuthStorage(getAgentDir());
25
+ // Custom auth storage location
26
+ const authStorage = new AuthStorage("/tmp/my-agent/auth.json");
29
27
 
30
- // Custom API key with fallback
31
- const getApiKey = async (model: { provider: string }) => {
32
- if (model.provider === "anthropic" && process.env.MY_ANTHROPIC_KEY) {
33
- return process.env.MY_ANTHROPIC_KEY;
34
- }
35
- return defaultGetApiKey()(model as any);
36
- };
28
+ // Runtime API key override (not persisted)
29
+ if (process.env.MY_ANTHROPIC_KEY) {
30
+ authStorage.setRuntimeApiKey("anthropic", process.env.MY_ANTHROPIC_KEY);
31
+ }
32
+
33
+ // Model registry with no custom models.json
34
+ const modelRegistry = new ModelRegistry(authStorage);
37
35
 
38
36
  // Inline hook
39
37
  const auditHook: HookFactory = (api) => {
@@ -55,7 +53,7 @@ const statusTool: CustomAgentTool = {
55
53
  }),
56
54
  };
57
55
 
58
- const { model } = findModel("anthropic", "claude-sonnet-4-20250514");
56
+ const model = getModel("anthropic", "claude-opus-4-5");
59
57
  if (!model) throw new Error("Model not found");
60
58
 
61
59
  // In-memory settings with overrides
@@ -73,7 +71,8 @@ const { session } = await createAgentSession({
73
71
 
74
72
  model,
75
73
  thinkingLevel: "off",
76
- getApiKey,
74
+ authStorage,
75
+ modelRegistry,
77
76
 
78
77
  systemPrompt: `You are a minimal assistant.
79
78
  Available: read, bash, status. Be concise.`,
@@ -29,50 +29,63 @@ npx tsx examples/sdk/01-minimal.ts
29
29
  ## Quick Reference
30
30
 
31
31
  ```typescript
32
+ import { getModel } from "@mariozechner/pi-ai";
32
33
  import {
34
+ AuthStorage,
33
35
  createAgentSession,
34
- configureOAuthStorage,
36
+ discoverAuthStorage,
37
+ discoverModels,
35
38
  discoverSkills,
36
39
  discoverHooks,
37
40
  discoverCustomTools,
38
41
  discoverContextFiles,
39
42
  discoverSlashCommands,
40
- discoverAvailableModels,
41
- findModel,
42
- defaultGetApiKey,
43
43
  loadSettings,
44
44
  buildSystemPrompt,
45
+ ModelRegistry,
45
46
  SessionManager,
46
47
  codingTools,
47
48
  readOnlyTools,
48
49
  readTool, bashTool, editTool, writeTool,
49
50
  } from "@mariozechner/pi-coding-agent";
50
51
 
52
+ // Auth and models setup
53
+ const authStorage = discoverAuthStorage();
54
+ const modelRegistry = discoverModels(authStorage);
55
+
51
56
  // Minimal
52
- const { session } = await createAgentSession();
57
+ const { session } = await createAgentSession({ authStorage, modelRegistry });
53
58
 
54
59
  // Custom model
55
- const { model } = findModel("anthropic", "claude-sonnet-4-20250514");
56
- const { session } = await createAgentSession({ model, thinkingLevel: "high" });
60
+ const model = getModel("anthropic", "claude-opus-4-5");
61
+ const { session } = await createAgentSession({ model, thinkingLevel: "high", authStorage, modelRegistry });
57
62
 
58
63
  // Modify prompt
59
64
  const { session } = await createAgentSession({
60
65
  systemPrompt: (defaultPrompt) => defaultPrompt + "\n\nBe concise.",
66
+ authStorage,
67
+ modelRegistry,
61
68
  });
62
69
 
63
70
  // Read-only
64
- const { session } = await createAgentSession({ tools: readOnlyTools });
71
+ const { session } = await createAgentSession({ tools: readOnlyTools, authStorage, modelRegistry });
65
72
 
66
73
  // In-memory
67
74
  const { session } = await createAgentSession({
68
75
  sessionManager: SessionManager.inMemory(),
76
+ authStorage,
77
+ modelRegistry,
69
78
  });
70
79
 
71
80
  // Full control
72
- configureOAuthStorage(); // Use OAuth from ~/.pi/agent
81
+ const customAuth = new AuthStorage("/my/app/auth.json");
82
+ customAuth.setRuntimeApiKey("anthropic", process.env.MY_KEY!);
83
+ const customRegistry = new ModelRegistry(customAuth);
84
+
73
85
  const { session } = await createAgentSession({
74
86
  model,
75
- getApiKey: async (m) => process.env.MY_KEY,
87
+ authStorage: customAuth,
88
+ modelRegistry: customRegistry,
76
89
  systemPrompt: "You are helpful.",
77
90
  tools: [readTool, bashTool],
78
91
  customTools: [{ tool: myTool }],
@@ -81,7 +94,6 @@ const { session } = await createAgentSession({
81
94
  contextFiles: [],
82
95
  slashCommands: [],
83
96
  sessionManager: SessionManager.inMemory(),
84
- settings: { compaction: { enabled: false } },
85
97
  });
86
98
 
87
99
  // Run prompts
@@ -97,11 +109,12 @@ await session.prompt("Hello");
97
109
 
98
110
  | Option | Default | Description |
99
111
  |--------|---------|-------------|
112
+ | `authStorage` | `discoverAuthStorage()` | Credential storage |
113
+ | `modelRegistry` | `discoverModels(authStorage)` | Model registry |
100
114
  | `cwd` | `process.cwd()` | Working directory |
101
115
  | `agentDir` | `~/.pi/agent` | Config directory |
102
116
  | `model` | From settings/first available | Model to use |
103
117
  | `thinkingLevel` | From settings/"off" | off, low, medium, high |
104
- | `getApiKey` | Built-in resolver | API key function |
105
118
  | `systemPrompt` | Discovered | String or `(default) => modified` |
106
119
  | `tools` | `codingTools` | Built-in tools |
107
120
  | `customTools` | Discovered | Replaces discovery |
@@ -112,7 +125,7 @@ await session.prompt("Hello");
112
125
  | `contextFiles` | Discovered | AGENTS.md files |
113
126
  | `slashCommands` | Discovered | File commands |
114
127
  | `sessionManager` | `SessionManager.create(cwd)` | Persistence |
115
- | `settings` | From agentDir | Overrides |
128
+ | `settingsManager` | From agentDir | Settings overrides |
116
129
 
117
130
  ## Events
118
131