Add SDK usage examples

12 examples showing increasing levels of customization:
- 01-minimal: all defaults
- 02-custom-model: model and thinking level
- 03-custom-prompt: replace or modify prompt
- 04-skills: discover, filter, merge skills
- 05-tools: built-in tools, custom tools
- 06-hooks: logging, blocking, result modification
- 07-context-files: AGENTS.md files
- 08-slash-commands: file-based commands
- 09-api-keys-and-oauth: API key resolution, OAuth config
- 10-settings: compaction, retry, terminal settings
- 11-sessions: persistence options
- 12-full-control: replace everything

Also exports FileSlashCommand type from index.ts
This commit is contained in:
Mario Zechner 2025-12-22 03:14:30 +01:00
parent 86cfe6a436
commit 56121dcac1
15 changed files with 723 additions and 0 deletions

View file

@ -0,0 +1,29 @@
# Examples
Example code for pi-coding-agent.
## Directories
### [sdk/](sdk/)
Programmatic usage via `createAgentSession()`. Shows how to customize models, prompts, tools, hooks, and session management.
### [hooks/](hooks/)
Example hooks for intercepting tool calls, adding safety gates, and integrating with external systems.
### [custom-tools/](custom-tools/)
Example custom tools that extend the agent's capabilities.
## Running Examples
```bash
cd packages/coding-agent
npx tsx examples/sdk/01-minimal.ts
npx tsx examples/hooks/permission-gate.ts
```
## Documentation
- [SDK Reference](sdk/README.md)
- [Hooks Documentation](../docs/hooks.md)
- [Custom Tools Documentation](../docs/custom-tools.md)
- [Skills Documentation](../docs/skills.md)

View file

@ -0,0 +1,22 @@
/**
* Minimal SDK Usage
*
* Uses all defaults: discovers skills, hooks, tools, context files
* from cwd and ~/.pi/agent. Model chosen from settings or first available.
*/
import { createAgentSession } from "../../src/index.js";
const { session } = await createAgentSession();
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are in the current directory?");
session.state.messages.forEach((msg) => {
console.log(msg);
});
console.log();

View file

@ -0,0 +1,36 @@
/**
* Custom Model Selection
*
* Shows how to select a specific model and thinking level.
*/
import { createAgentSession, findModel, discoverAvailableModels } from "../../src/index.js";
// Option 1: Find a specific model by provider/id
const { model: sonnet } = findModel("anthropic", "claude-sonnet-4-20250514");
if (sonnet) {
console.log(`Found model: ${sonnet.provider}/${sonnet.id}`);
}
// Option 2: Pick from available models (have valid API keys)
const available = await discoverAvailableModels();
console.log(
"Available models:",
available.map((m) => `${m.provider}/${m.id}`),
);
if (available.length > 0) {
const { session } = await createAgentSession({
model: available[0],
thinkingLevel: "medium", // off, low, medium, high
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Say hello in one sentence.");
console.log();
}

View file

@ -0,0 +1,44 @@
/**
* Custom System Prompt
*
* Shows how to replace or modify the default system prompt.
*/
import { createAgentSession, SessionManager } from "../../src/index.js";
// Option 1: Replace prompt entirely
const { session: session1 } = await createAgentSession({
systemPrompt: `You are a helpful assistant that speaks like a pirate.
Always end responses with "Arrr!"`,
sessionManager: SessionManager.inMemory(),
});
session1.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
console.log("=== Replace prompt ===");
await session1.prompt("What is 2 + 2?");
console.log("\n");
// Option 2: Modify default prompt (receives default, returns modified)
const { session: session2 } = await createAgentSession({
systemPrompt: (defaultPrompt) => `${defaultPrompt}
## Additional Instructions
- Always be concise
- Use bullet points when listing things`,
sessionManager: SessionManager.inMemory(),
});
session2.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
console.log("=== Modify prompt ===");
await session2.prompt("List 3 benefits of TypeScript.");
console.log();

View file

@ -0,0 +1,44 @@
/**
* Skills Configuration
*
* Skills provide specialized instructions loaded into the system prompt.
* Discover, filter, merge, or replace them.
*/
import { createAgentSession, discoverSkills, SessionManager, type Skill } from "../../src/index.js";
// Discover all skills from cwd/.pi/skills, ~/.pi/agent/skills, etc.
const allSkills = discoverSkills();
console.log(
"Discovered skills:",
allSkills.map((s) => s.name),
);
// Filter to specific skills
const filteredSkills = allSkills.filter((s) => s.name.includes("browser") || s.name.includes("search"));
// Or define custom skills inline
const customSkill: Skill = {
name: "my-skill",
description: "Custom project instructions",
filePath: "/virtual/SKILL.md",
baseDir: "/virtual",
source: "custom",
};
// Use filtered + custom skills
const { session } = await createAgentSession({
skills: [...filteredSkills, customSkill],
sessionManager: SessionManager.inMemory(),
});
console.log(`Session created with ${filteredSkills.length + 1} skills`);
// To disable all skills:
// skills: []
// To use discovery with filtering via settings:
// discoverSkills(process.cwd(), undefined, {
// ignoredSkills: ["browser-tools"], // glob patterns to exclude
// includeSkills: ["brave-*"], // glob patterns to include (empty = all)
// })

View file

@ -0,0 +1,67 @@
/**
* Tools Configuration
*
* Use built-in tool sets, individual tools, or add custom tools.
*/
import { Type } from "@sinclair/typebox";
import {
createAgentSession,
discoverCustomTools,
SessionManager,
codingTools, // read, bash, edit, write (default)
readOnlyTools, // read, bash
readTool,
bashTool,
grepTool,
type CustomAgentTool,
} from "../../src/index.js";
// Read-only mode (no edit/write)
const { session: readOnly } = await createAgentSession({
tools: readOnlyTools,
sessionManager: SessionManager.inMemory(),
});
console.log("Read-only session created");
// Custom tool selection
const { session: custom } = await createAgentSession({
tools: [readTool, bashTool, grepTool],
sessionManager: SessionManager.inMemory(),
});
console.log("Custom tools session created");
// Inline custom tool (needs TypeBox schema)
const weatherTool: CustomAgentTool = {
name: "get_weather",
label: "Get Weather",
description: "Get current weather for a city",
parameters: Type.Object({
city: Type.String({ description: "City name" }),
}),
execute: async (_toolCallId, params) => ({
content: [{ type: "text", text: `Weather in ${(params as { city: string }).city}: 22°C, sunny` }],
details: {},
}),
};
const { session } = await createAgentSession({
customTools: [{ tool: weatherTool }],
sessionManager: SessionManager.inMemory(),
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What's the weather in Tokyo?");
console.log();
// Merge with discovered tools from cwd/.pi/tools and ~/.pi/agent/tools:
// const discovered = await discoverCustomTools();
// customTools: [...discovered, { tool: myTool }]
// Or add paths without replacing discovery:
// additionalCustomToolPaths: ["/extra/tools"]

View file

@ -0,0 +1,61 @@
/**
* Hooks Configuration
*
* Hooks intercept agent events for logging, blocking, or modification.
*/
import { createAgentSession, discoverHooks, SessionManager, type HookFactory } from "../../src/index.js";
// Logging hook
const loggingHook: HookFactory = (api) => {
api.on("agent_start", async () => {
console.log("[Hook] Agent starting");
});
api.on("tool_call", async (event) => {
console.log(`[Hook] Tool: ${event.toolName}`);
return undefined; // Don't block
});
api.on("agent_end", async (event) => {
console.log(`[Hook] Done, ${event.messages.length} messages`);
});
};
// Blocking hook (returns { block: true, reason: "..." })
const safetyHook: HookFactory = (api) => {
api.on("tool_call", async (event) => {
if (event.toolName === "bash") {
const cmd = (event.input as { command?: string }).command ?? "";
if (cmd.includes("rm -rf")) {
return { block: true, reason: "Dangerous command blocked" };
}
}
return undefined;
});
};
// Use inline hooks
const { session } = await createAgentSession({
hooks: [{ factory: loggingHook }, { factory: safetyHook }],
sessionManager: SessionManager.inMemory(),
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("List files in the current directory.");
console.log();
// Disable all hooks:
// hooks: []
// Merge with discovered hooks:
// const discovered = await discoverHooks();
// hooks: [...discovered, { factory: myHook }]
// Add paths without replacing discovery:
// additionalHookPaths: ["/extra/hooks"]

View file

@ -0,0 +1,36 @@
/**
* Context Files (AGENTS.md)
*
* Context files provide project-specific instructions loaded into the system prompt.
*/
import { createAgentSession, discoverContextFiles, SessionManager } from "../../src/index.js";
// Discover AGENTS.md files walking up from cwd
const discovered = discoverContextFiles();
console.log("Discovered context files:");
for (const file of discovered) {
console.log(` - ${file.path} (${file.content.length} chars)`);
}
// Use custom context files
const { session } = await createAgentSession({
contextFiles: [
...discovered,
{
path: "/virtual/AGENTS.md",
content: `# Project Guidelines
## Code Style
- Use TypeScript strict mode
- No any types
- Prefer const over let`,
},
],
sessionManager: SessionManager.inMemory(),
});
console.log(`Session created with ${discovered.length + 1} context files`);
// Disable context files:
// contextFiles: []

View file

@ -0,0 +1,37 @@
/**
* Slash Commands
*
* File-based commands that inject content when invoked with /commandname.
*/
import { createAgentSession, discoverSlashCommands, SessionManager, type FileSlashCommand } from "../../src/index.js";
// Discover commands from cwd/.pi/commands/ and ~/.pi/agent/commands/
const discovered = discoverSlashCommands();
console.log("Discovered slash commands:");
for (const cmd of discovered) {
console.log(` /${cmd.name}: ${cmd.description}`);
}
// Define custom commands
const deployCommand: FileSlashCommand = {
name: "deploy",
description: "Deploy the application",
source: "(custom)",
content: `# Deploy Instructions
1. Build: npm run build
2. Test: npm test
3. Deploy: npm run deploy`,
};
// Use discovered + custom commands
const { session } = await createAgentSession({
slashCommands: [...discovered, deployCommand],
sessionManager: SessionManager.inMemory(),
});
console.log(`Session created with ${discovered.length + 1} slash commands`);
// Disable slash commands:
// slashCommands: []

View file

@ -0,0 +1,45 @@
/**
* API Keys and OAuth
*
* Configure API key resolution. Default checks: models.json, OAuth, env vars.
*/
import {
createAgentSession,
configureOAuthStorage,
defaultGetApiKey,
SessionManager,
} from "../../src/index.js";
import { getAgentDir } from "../../src/config.js";
// Default: uses env vars (ANTHROPIC_API_KEY, etc.), OAuth, and models.json
const { session: defaultSession } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
console.log("Session with default API key resolution");
// Custom resolver
const { session: customSession } = await createAgentSession({
getApiKey: async (model) => {
// Custom logic (secrets manager, database, etc.)
if (model.provider === "anthropic") {
return process.env.MY_ANTHROPIC_KEY;
}
// Fall back to default
return defaultGetApiKey()(model);
},
sessionManager: SessionManager.inMemory(),
});
console.log("Session with custom API key resolver");
// Use OAuth from ~/.pi/agent while customizing everything else
configureOAuthStorage(getAgentDir()); // Must call before createAgentSession
const { session: hybridSession } = await createAgentSession({
agentDir: "/tmp/custom-config", // Custom config location
// But OAuth tokens still come from ~/.pi/agent/oauth.json
systemPrompt: "You are helpful.",
skills: [],
sessionManager: SessionManager.inMemory(),
});
console.log("Session with OAuth from default location, custom config elsewhere");

View file

@ -0,0 +1,33 @@
/**
* Settings Configuration
*
* Override settings from agentDir/settings.json.
*/
import { createAgentSession, loadSettings, SessionManager } from "../../src/index.js";
// Load current settings
const settings = loadSettings();
console.log("Current settings:", JSON.stringify(settings, null, 2));
// Override specific settings
const { session } = await createAgentSession({
settings: {
// Disable auto-compaction
compaction: { enabled: false },
// Custom retry behavior
retry: {
enabled: true,
maxRetries: 5,
baseDelayMs: 1000,
},
// Terminal options
terminal: { showImages: true },
hideThinkingBlock: true,
},
sessionManager: SessionManager.inMemory(),
});
console.log("Session created with custom settings");

View file

@ -0,0 +1,46 @@
/**
* Session Management
*
* Control session persistence: in-memory, new file, continue, or open specific.
*/
import { createAgentSession, SessionManager } from "../../src/index.js";
// In-memory (no persistence)
const { session: inMemory } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
console.log("In-memory session:", inMemory.sessionFile ?? "(none)");
// New persistent session
const { session: newSession } = await createAgentSession({
sessionManager: SessionManager.create(process.cwd()),
});
console.log("New session file:", newSession.sessionFile);
// Continue most recent session (or create new if none)
const { session: continued, modelFallbackMessage } = await createAgentSession({
sessionManager: SessionManager.continueRecent(process.cwd()),
});
if (modelFallbackMessage) console.log("Note:", modelFallbackMessage);
console.log("Continued session:", continued.sessionFile);
// List and open specific session
const sessions = SessionManager.list(process.cwd());
console.log(`\nFound ${sessions.length} sessions:`);
for (const info of sessions.slice(0, 3)) {
console.log(` ${info.id.slice(0, 8)}... - "${info.firstMessage.slice(0, 30)}..."`);
}
if (sessions.length > 0) {
const { session: opened } = await createAgentSession({
sessionManager: SessionManager.open(sessions[0].path),
});
console.log(`\nOpened: ${opened.sessionId}`);
}
// Custom session directory
// const { session } = await createAgentSession({
// agentDir: "/custom/agent",
// sessionManager: SessionManager.create(process.cwd(), "/custom/agent"),
// });

View file

@ -0,0 +1,84 @@
/**
* Full Control
*
* Replace everything - no discovery, explicit configuration.
* Still uses OAuth from ~/.pi/agent for convenience.
*/
import { Type } from "@sinclair/typebox";
import {
createAgentSession,
configureOAuthStorage,
defaultGetApiKey,
findModel,
SessionManager,
readTool,
bashTool,
type HookFactory,
type CustomAgentTool,
} from "../../src/index.js";
import { getAgentDir } from "../../src/config.js";
// Use OAuth from default location
configureOAuthStorage(getAgentDir());
// Custom API key with fallback
const getApiKey = async (model: { provider: string }) => {
if (model.provider === "anthropic" && process.env.MY_ANTHROPIC_KEY) {
return process.env.MY_ANTHROPIC_KEY;
}
return defaultGetApiKey()(model as any);
};
// Inline hook
const auditHook: HookFactory = (api) => {
api.on("tool_call", async (event) => {
console.log(`[Audit] ${event.toolName}`);
return undefined;
});
};
// Inline custom tool
const statusTool: CustomAgentTool = {
name: "status",
label: "Status",
description: "Get system status",
parameters: Type.Object({}),
execute: async () => ({
content: [{ type: "text", text: `Uptime: ${process.uptime()}s, Node: ${process.version}` }],
details: {},
}),
};
const { model } = findModel("anthropic", "claude-sonnet-4-20250514");
if (!model) throw new Error("Model not found");
const { session } = await createAgentSession({
cwd: process.cwd(),
agentDir: "/tmp/my-agent",
model,
thinkingLevel: "off",
getApiKey,
systemPrompt: `You are a minimal assistant.
Available: read, bash, status. Be concise.`,
tools: [readTool, bashTool],
customTools: [{ tool: statusTool }],
hooks: [{ factory: auditHook }],
skills: [],
contextFiles: [],
slashCommands: [],
sessionManager: SessionManager.inMemory(),
settings: { compaction: { enabled: false } },
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Get status and list files.");
console.log();

View file

@ -0,0 +1,138 @@
# SDK Examples
Programmatic usage of pi-coding-agent via `createAgentSession()`.
## Examples
| File | Description |
|------|-------------|
| `01-minimal.ts` | Simplest usage with all defaults |
| `02-custom-model.ts` | Select model and thinking level |
| `03-custom-prompt.ts` | Replace or modify system prompt |
| `04-skills.ts` | Discover, filter, or replace skills |
| `05-tools.ts` | Built-in tools, custom tools |
| `06-hooks.ts` | Logging, blocking, result modification |
| `07-context-files.ts` | AGENTS.md context files |
| `08-slash-commands.ts` | File-based slash commands |
| `09-api-keys-and-oauth.ts` | API key resolution, OAuth config |
| `10-settings.ts` | Override compaction, retry, terminal settings |
| `11-sessions.ts` | In-memory, persistent, continue, list sessions |
| `12-full-control.ts` | Replace everything, no discovery |
## Running
```bash
cd packages/coding-agent
npx tsx examples/sdk/01-minimal.ts
```
## Quick Reference
```typescript
import {
createAgentSession,
configureOAuthStorage,
discoverSkills,
discoverHooks,
discoverCustomTools,
discoverContextFiles,
discoverSlashCommands,
discoverAvailableModels,
findModel,
defaultGetApiKey,
loadSettings,
buildSystemPrompt,
SessionManager,
codingTools,
readOnlyTools,
readTool, bashTool, editTool, writeTool,
} from "@mariozechner/pi-coding-agent";
// Minimal
const { session } = await createAgentSession();
// Custom model
const { model } = findModel("anthropic", "claude-sonnet-4-20250514");
const { session } = await createAgentSession({ model, thinkingLevel: "high" });
// Modify prompt
const { session } = await createAgentSession({
systemPrompt: (defaultPrompt) => defaultPrompt + "\n\nBe concise.",
});
// Read-only
const { session } = await createAgentSession({ tools: readOnlyTools });
// In-memory
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
// Full control
configureOAuthStorage(); // Use OAuth from ~/.pi/agent
const { session } = await createAgentSession({
model,
getApiKey: async (m) => process.env.MY_KEY,
systemPrompt: "You are helpful.",
tools: [readTool, bashTool],
customTools: [{ tool: myTool }],
hooks: [{ factory: myHook }],
skills: [],
contextFiles: [],
slashCommands: [],
sessionManager: SessionManager.inMemory(),
settings: { compaction: { enabled: false } },
});
// Run prompts
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Hello");
```
## Options
| Option | Default | Description |
|--------|---------|-------------|
| `cwd` | `process.cwd()` | Working directory |
| `agentDir` | `~/.pi/agent` | Config directory |
| `model` | From settings/first available | Model to use |
| `thinkingLevel` | From settings/"off" | off, low, medium, high |
| `getApiKey` | Built-in resolver | API key function |
| `systemPrompt` | Discovered | String or `(default) => modified` |
| `tools` | `codingTools` | Built-in tools |
| `customTools` | Discovered | Replaces discovery |
| `additionalCustomToolPaths` | `[]` | Merge with discovery |
| `hooks` | Discovered | Replaces discovery |
| `additionalHookPaths` | `[]` | Merge with discovery |
| `skills` | Discovered | Skills for prompt |
| `contextFiles` | Discovered | AGENTS.md files |
| `slashCommands` | Discovered | File commands |
| `sessionManager` | `SessionManager.create(cwd)` | Persistence |
| `settings` | From agentDir | Overrides |
## Events
```typescript
session.subscribe((event) => {
switch (event.type) {
case "message_update":
if (event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
break;
case "tool_execution_start":
console.log(`Tool: ${event.toolName}`);
break;
case "tool_execution_end":
console.log(`Result: ${event.result}`);
break;
case "agent_end":
console.log("Done");
break;
}
});
```