SDK
pi can help you use the SDK. Ask it to build an integration for your use case.
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.
Example use cases:
- Build a custom UI (web, desktop, mobile)
- Integrate agent capabilities into existing applications
- Create automated pipelines with agent reasoning
- Build custom tools that spawn sub-agents
- Test agent behavior programmatically
See examples/sdk/ for working examples from minimal to full control.
Quick Start
Section titled “Quick Start”import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
// Set up credential storage and model registryconst authStorage = AuthStorage.create();const modelRegistry = ModelRegistry.create(authStorage);
const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(), authStorage, modelRegistry,});
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?");Installation
Section titled “Installation”npm install @earendil-works/pi-coding-agentThe SDK is included in the main package. No separate installation needed.
Core Concepts
Section titled “Core Concepts”createAgentSession()
Section titled “createAgentSession()”The main factory function for a single AgentSession.
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.
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
// Minimal: defaults with DefaultResourceLoaderconst { session } = await createAgentSession();
// Custom: override specific optionsconst { session } = await createAgentSession({ model: myModel, tools: ["read", "bash"], sessionManager: SessionManager.inMemory(),});AgentSession
Section titled “AgentSession”The session manages agent lifecycle, message history, model state, compaction, and event streaming.
interface AgentSession { // Send a prompt and wait for completion prompt(text: string, options?: PromptOptions): Promise<void>;
// Queue messages during streaming steer(text: string): Promise<void>; followUp(text: string): Promise<void>;
// Subscribe to events (returns unsubscribe function) subscribe(listener: (event: AgentSessionEvent) => void): () => void;
// Session info sessionFile: string | undefined; sessionId: string;
// Model control setModel(model: Model): Promise<void>; setThinkingLevel(level: ThinkingLevel): void; cycleModel(): Promise<ModelCycleResult | undefined>; cycleThinkingLevel(): ThinkingLevel | undefined;
// State access agent: Agent; model: Model | undefined; thinkingLevel: ThinkingLevel; messages: AgentMessage[]; isStreaming: boolean;
// In-place tree navigation within the current session file navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
// Compaction compact(customInstructions?: string): Promise<CompactionResult>; abortCompaction(): void;
// Abort current operation abort(): Promise<void>;
// Cleanup dispose(): void;}Session replacement APIs such as new-session, resume, fork, and import live on AgentSessionRuntime, not on AgentSession.
createAgentSessionRuntime() and AgentSessionRuntime
Section titled “createAgentSessionRuntime() and AgentSessionRuntime”Use the runtime API when you need to replace the active session and rebuild cwd-bound runtime state. This is the same layer used by the built-in interactive, print, and RPC modes.
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.
import { type CreateAgentSessionRuntimeFactory, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager,} from "@earendil-works/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => { const services = await createAgentSessionServices({ cwd }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, })), services, diagnostics: services.diagnostics, };};
const runtime = await createAgentSessionRuntime(createRuntime, { cwd: process.cwd(), agentDir: getAgentDir(), sessionManager: SessionManager.create(process.cwd()),});AgentSessionRuntime owns replacement of the active runtime across:
newSession()switchSession()fork()- clone flows via
fork(entryId, { position: "at" }) importFromJsonl()
Important behavior:
runtime.sessionchanges after those operations- event subscriptions are attached to a specific
AgentSession, so re-subscribe after replacement - if you use extensions, call
runtime.session.bindExtensions(...)again for the new session - creation returns diagnostics on
runtime.diagnostics - if runtime creation or replacement fails, the method throws and the caller decides how to handle it
let session = runtime.session;let unsubscribe = session.subscribe(() => {});
await runtime.newSession();
unsubscribe();session = runtime.session;unsubscribe = session.subscribe(() => {});Prompting and Message Queueing
Section titled “Prompting and Message Queueing”PromptOptions controls prompt expansion, queueing behavior while streaming, and prompt preflight notifications:
interface PromptOptions { expandPromptTemplates?: boolean; images?: ImageContent[]; streamingBehavior?: "steer" | "followUp"; source?: InputSource; preflightResult?: (success: boolean) => void;}preflightResult is called once per prompt() invocation:
truewhen the prompt was accepted, queued, or handled immediatelyfalsewhen prompt preflight rejected before acceptance
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).
The prompt() method handles prompt templates, extension commands, and message sending:
// Basic prompt (when not streaming)await session.prompt("What files are here?");
// With imagesawait session.prompt("What's in this image?", { images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]});
// During streaming: must specify how to queue the messageawait session.prompt("Stop and do this instead", { streamingBehavior: "steer" });await session.prompt("After you're done, also check X", { streamingBehavior: "followUp" });Behavior:
- Extension commands (e.g.,
/mycommand): Execute immediately, even during streaming. They manage their own LLM interaction viapi.sendMessage(). - File-based prompt templates (from
.mdfiles): Expanded to their content before sending or queueing. - During streaming without
streamingBehavior: Throws an error. Usesteer()orfollowUp()directly, or specify the option. preflightResult(true): Means the prompt was accepted, queued, or handled immediately.preflightResult(false): Means preflight rejected before acceptance.
For explicit queueing during streaming:
// Queue a steering message for delivery after the current assistant turn finishes its tool callsawait session.steer("New instruction");
// Wait for agent to finish (delivered only when agent stops)await session.followUp("After you're done, also do this");Both steer() and followUp() expand file-based prompt templates but error on extension commands (extension commands cannot be queued).
Agent and AgentState
Section titled “Agent and AgentState”The Agent class (from @earendil-works/pi-agent-core) handles the core LLM interaction. Access it via session.agent.
// Access current stateconst state = session.agent.state;
// state.messages: AgentMessage[] - conversation history// state.model: Model - current model// state.thinkingLevel: ThinkingLevel - current thinking level// state.systemPrompt: string - system prompt// state.tools: AgentTool[] - available tools// state.streamingMessage?: AgentMessage - current partial assistant message// state.errorMessage?: string - latest assistant error
// Replace messages (useful for branching or restoration)session.agent.state.messages = messages; // copies the top-level array
// Replace toolssession.agent.state.tools = tools; // copies the top-level array
// Wait for agent to finish processingawait session.agent.waitForIdle();Events
Section titled “Events”Subscribe to events to receive streaming output and lifecycle notifications.
session.subscribe((event) => { switch (event.type) { // Streaming text from assistant case "message_update": if (event.assistantMessageEvent.type === "text_delta") { process.stdout.write(event.assistantMessageEvent.delta); } if (event.assistantMessageEvent.type === "thinking_delta") { // Thinking output (if thinking enabled) } break;
// Tool execution case "tool_execution_start": console.log(`Tool: ${event.toolName}`); break; case "tool_execution_update": // Streaming tool output break; case "tool_execution_end": console.log(`Result: ${event.isError ? "error" : "success"}`); break;
// Message lifecycle case "message_start": // New message starting break; case "message_end": // Message complete break;
// Agent lifecycle case "agent_start": // Agent started processing prompt break; case "agent_end": // Agent finished (event.messages contains new messages) break;
// Turn lifecycle (one LLM response + tool calls) case "turn_start": break; case "turn_end": // event.message: assistant response // event.toolResults: tool results from this turn break;
// Session events (queue, compaction, retry) case "queue_update": console.log(event.steering, event.followUp); break; case "compaction_start": case "compaction_end": case "auto_retry_start": case "auto_retry_end": break; }});Options Reference
Section titled “Options Reference”Directories
Section titled “Directories”const { session } = await createAgentSession({ // Working directory for DefaultResourceLoader discovery cwd: process.cwd(), // default
// Global config directory agentDir: "~/.pi/agent", // default (expands ~)});cwd is used by DefaultResourceLoader for:
- Project extensions (
.pi/extensions/) - Project skills:
.pi/skills/.agents/skills/incwdand ancestor directories (up to git repo root, or filesystem root when not in a repo)
- Project prompts (
.pi/prompts/) - Context files (
AGENTS.mdwalking up from cwd) - Session directory naming
agentDir is used by DefaultResourceLoader for:
- Global extensions (
extensions/) - Global skills:
skills/underagentDir(for example~/.pi/agent/skills/)~/.agents/skills/
- Global prompts (
prompts/) - Global context file (
AGENTS.md) - Settings (
settings.json) - Custom models (
models.json) - Credentials (
auth.json) - Sessions (
sessions/)
When you pass a custom ResourceLoader, cwd and agentDir no longer control resource discovery. They still influence session naming and tool path resolution.
import { getModel } from "@earendil-works/pi-ai";import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
const authStorage = AuthStorage.create();const modelRegistry = ModelRegistry.create(authStorage);
// Find specific built-in model (doesn't check if API key exists)const opus = getModel("anthropic", "claude-opus-4-5");if (!opus) throw new Error("Model not found");
// Find any model by provider/id, including custom models from models.json// (doesn't check if API key exists)const customModel = modelRegistry.find("my-provider", "my-model");
// Get only models that have valid API keys configuredconst available = await modelRegistry.getAvailable();
const { session } = await createAgentSession({ model: opus, thinkingLevel: "medium", // off, minimal, low, medium, high, xhigh
// Models for cycling (Ctrl+P in interactive mode) scopedModels: [ { model: opus, thinkingLevel: "high" }, { model: haiku, thinkingLevel: "off" }, ],
authStorage, modelRegistry,});If no model is provided:
- Tries to restore from session (if continuing)
- Uses default from settings
- Falls back to first available model
API Keys and OAuth
Section titled “API Keys and OAuth”API key resolution priority (handled by AuthStorage):
- Runtime overrides (via
setRuntimeApiKey, not persisted) - Stored credentials in
auth.json(API keys or OAuth tokens) - Environment variables (
ANTHROPIC_API_KEY,OPENAI_API_KEY, etc.) - Fallback resolver (for custom provider keys from
models.json)
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.jsonconst authStorage = AuthStorage.create();const modelRegistry = ModelRegistry.create(authStorage);
const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(), authStorage, modelRegistry,});
// Runtime API key override (not persisted to disk)authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
// Custom auth storage locationconst customAuth = AuthStorage.create("/my/app/auth.json");const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(), authStorage: customAuth, modelRegistry: customRegistry,});
// No custom models.json (built-in models only)const simpleRegistry = ModelRegistry.inMemory(authStorage);System Prompt
Section titled “System Prompt”Use a ResourceLoader to override the system prompt:
import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({ systemPromptOverride: () => "You are a helpful assistant.",});await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });Specify which built-in tools to enable:
- Built-in tool names:
read,bash,edit,write,grep,find,ls - Default built-ins:
read,bash,edit,write noTools: "all"disables all toolsnoTools: "builtin"disables default built-ins while keeping extension and custom tools enabledexcludeToolsdisables specific built-in, extension, or custom tool names after anytoolsallowlist is applied
The edit tool returns details.diff for Pi’s TUI display and details.patch as a standard unified patch for SDK consumers.
import { createAgentSession } from "@earendil-works/pi-coding-agent";
// Read-only modeconst { session } = await createAgentSession({ tools: ["read", "grep", "find", "ls"],});
// Pick specific toolsconst { session } = await createAgentSession({ tools: ["read", "bash", "grep"],});
// Disable one tool while keeping the rest availableconst { session } = await createAgentSession({ excludeTools: ["ask_question"],});Tools with Custom cwd
Section titled “Tools with Custom cwd”When you pass a custom cwd, createAgentSession() builds selected built-in tools for that cwd.
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
const cwd = "/path/to/project";
// Use default tools for custom cwdconst { session } = await createAgentSession({ cwd, sessionManager: SessionManager.inMemory(cwd),});
// Or pick specific tools for custom cwdconst { session } = await createAgentSession({ cwd, tools: ["read", "bash", "grep"], sessionManager: SessionManager.inMemory(cwd),});Custom Tools
Section titled “Custom Tools”import { Type } from "typebox";import { createAgentSession, defineTool } from "@earendil-works/pi-coding-agent";
// Inline custom toolconst myTool = defineTool({ name: "my_tool", label: "My Tool", description: "Does something useful", parameters: Type.Object({ input: Type.String({ description: "Input value" }), }), execute: async (_toolCallId, params) => ({ content: [{ type: "text", text: `Result: ${params.input}` }], details: {}, }),});
// Pass custom tools directlyconst { session } = await createAgentSession({ customTools: [myTool],});Use defineTool() for standalone definitions and arrays like customTools: [myTool]. Inline pi.registerTool({ ... }) already infers parameter types correctly.
Custom tools passed via customTools are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via pi.registerTool().
If you pass tools, include each custom or extension tool name you want enabled, for example tools: ["read", "bash", "my_tool"].
Extensions
Section titled “Extensions”Extensions are loaded by the ResourceLoader. DefaultResourceLoader discovers extensions from ~/.pi/agent/extensions/, .pi/extensions/, and settings.json extension sources.
import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({ additionalExtensionPaths: ["/path/to/my-extension.ts"], extensionFactories: [ (pi) => { pi.on("agent_start", () => { console.log("[Inline Extension] Agent starting"); }); }, ],});await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });Extensions can register tools, subscribe to events, add commands, and more. See extensions.md for the full API.
Event Bus: Extensions can communicate via pi.events. Pass a shared eventBus to DefaultResourceLoader if you need to emit or listen from outside:
import { createEventBus, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const eventBus = createEventBus();const loader = new DefaultResourceLoader({ eventBus,});await loader.reload();
eventBus.on("my-extension:status", (data) => console.log(data));Skills
Section titled “Skills”import { createAgentSession, DefaultResourceLoader, type Skill,} from "@earendil-works/pi-coding-agent";
const customSkill: Skill = { name: "my-skill", description: "Custom instructions", filePath: "/path/to/SKILL.md", baseDir: "/path/to", source: "custom",};
const loader = new DefaultResourceLoader({ skillsOverride: (current) => ({ skills: [...current.skills, customSkill], diagnostics: current.diagnostics, }),});await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });Context Files
Section titled “Context Files”import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({ agentsFilesOverride: (current) => ({ agentsFiles: [ ...current.agentsFiles, { path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" }, ], }),});await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });Slash Commands
Section titled “Slash Commands”import { createAgentSession, DefaultResourceLoader, type PromptTemplate,} from "@earendil-works/pi-coding-agent";
const customCommand: PromptTemplate = { name: "deploy", description: "Deploy the application", source: "(custom)", content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",};
const loader = new DefaultResourceLoader({ promptsOverride: (current) => ({ prompts: [...current.prompts, customCommand], diagnostics: current.diagnostics, }),});await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });Session Management
Section titled “Session Management”Sessions use a tree structure with id/parentId linking, enabling in-place branching.
import { type CreateAgentSessionRuntimeFactory, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager,} from "@earendil-works/pi-coding-agent";
// In-memory (no persistence)const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(),});
// New persistent sessionconst { session: persisted } = await createAgentSession({ sessionManager: SessionManager.create(process.cwd()),});
// Continue most recentconst { session: continued, modelFallbackMessage } = await createAgentSession({ sessionManager: SessionManager.continueRecent(process.cwd()),});if (modelFallbackMessage) { console.log("Note:", modelFallbackMessage);}
// Open specific fileconst { session: opened } = await createAgentSession({ sessionManager: SessionManager.open("/path/to/session.jsonl"),});
// List sessionsconst currentProjectSessions = await SessionManager.list(process.cwd());const allSessions = await SessionManager.listAll(process.cwd());
// Session replacement API for /new, /resume, /fork, /clone, and import flows.const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => { const services = await createAgentSessionServices({ cwd }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, })), services, diagnostics: services.diagnostics, };};
const runtime = await createAgentSessionRuntime(createRuntime, { cwd: process.cwd(), agentDir: getAgentDir(), sessionManager: SessionManager.create(process.cwd()),});
// Replace the active session with a fresh oneawait runtime.newSession();
// Replace the active session with another saved sessionawait runtime.switchSession("/path/to/session.jsonl");
// Replace the active session with a fork from a specific user entryawait runtime.fork("entry-id");
// Clone the active path through a specific entryawait runtime.fork("entry-id", { position: "at" });SessionManager tree API:
const sm = SessionManager.open("/path/to/session.jsonl");
// Session listingconst currentProjectSessions = await SessionManager.list(process.cwd());const allSessions = await SessionManager.listAll(process.cwd());
// Tree traversalconst entries = sm.getEntries(); // All entries (excludes header)const tree = sm.getTree(); // Full tree structureconst path = sm.getPath(); // Path from root to current leafconst leaf = sm.getLeafEntry(); // Current leaf entryconst entry = sm.getEntry(id); // Get entry by IDconst children = sm.getChildren(id); // Direct children of entry
// Labelsconst label = sm.getLabel(id); // Get label for entrysm.appendLabelChange(id, "checkpoint"); // Set label
// Branchingsm.branch(entryId); // Move leaf to earlier entrysm.branchWithSummary(id, "Summary..."); // Branch with context summarysm.createBranchedSession(leafId); // Extract path to new fileSettings Management
Section titled “Settings Management”import { createAgentSession, SettingsManager, SessionManager } from "@earendil-works/pi-coding-agent";
// Default: loads from files (global + project merged)const { session } = await createAgentSession({ settingsManager: SettingsManager.create(),});
// With overridesconst settingsManager = SettingsManager.create();settingsManager.applyOverrides({ compaction: { enabled: false }, retry: { enabled: true, maxRetries: 5 },});const { session } = await createAgentSession({ settingsManager });
// In-memory (no file I/O, for testing)const { session } = await createAgentSession({ settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }), sessionManager: SessionManager.inMemory(),});
// Custom directoriesconst { session } = await createAgentSession({ settingsManager: SettingsManager.create("/custom/cwd", "/custom/agent"),});Static factories:
SettingsManager.create(cwd?, agentDir?)- Load from filesSettingsManager.inMemory(settings?)- No file I/O
Project-specific settings:
Settings load from two locations and merge:
- Global:
~/.pi/agent/settings.json - Project:
<cwd>/.pi/settings.json
Project overrides global. Nested objects merge keys. Setters modify global settings by default.
Persistence and error handling semantics:
- Settings getters/setters are synchronous for in-memory state.
- Setters enqueue persistence writes asynchronously.
- Call
await settingsManager.flush()when you need a durability boundary (for example, before process exit or before asserting file contents in tests). SettingsManagerdoes not print settings I/O errors. UsesettingsManager.drainErrors()and report them in your app layer.
ResourceLoader
Section titled “ResourceLoader”Use DefaultResourceLoader to discover extensions, skills, prompts, themes, and context files.
import { DefaultResourceLoader, getAgentDir,} from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({ cwd, agentDir: getAgentDir(),});await loader.reload();
const extensions = loader.getExtensions();const skills = loader.getSkills();const prompts = loader.getPrompts();const themes = loader.getThemes();const contextFiles = loader.getAgentsFiles().agentsFiles;Return Value
Section titled “Return Value”createAgentSession() returns:
interface CreateAgentSessionResult { // The session session: AgentSession;
// Extensions result (for runner setup) extensionsResult: LoadExtensionsResult;
// Warning if session model couldn't be restored modelFallbackMessage?: string;}
interface LoadExtensionsResult { extensions: Extension[]; errors: Array<{ path: string; error: string }>; runtime: ExtensionRuntime;}Complete Example
Section titled “Complete Example”import { getModel } from "@earendil-works/pi-ai";import { Type } from "typebox";import { AuthStorage, createAgentSession, DefaultResourceLoader, defineTool, ModelRegistry, SessionManager, SettingsManager,} from "@earendil-works/pi-coding-agent";
// Set up auth storage (custom location)const authStorage = AuthStorage.create("/custom/agent/auth.json");
// Runtime API key override (not persisted)if (process.env.MY_KEY) { authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);}
// Model registry (no custom models.json)const modelRegistry = ModelRegistry.create(authStorage);
// Inline toolconst statusTool = defineTool({ name: "status", label: "Status", description: "Get system status", parameters: Type.Object({}), execute: async () => ({ content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }], details: {}, }),});
const model = getModel("anthropic", "claude-opus-4-5");if (!model) throw new Error("Model not found");
// In-memory settings with overridesconst settingsManager = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: true, maxRetries: 2 },});
const loader = new DefaultResourceLoader({ cwd: process.cwd(), agentDir: "/custom/agent", settingsManager, systemPromptOverride: () => "You are a minimal assistant. Be concise.",});await loader.reload();
const { session } = await createAgentSession({ cwd: process.cwd(), agentDir: "/custom/agent",
model, thinkingLevel: "off", authStorage, modelRegistry,
tools: ["read", "bash", "status"], customTools: [statusTool], resourceLoader: loader,
sessionManager: SessionManager.inMemory(), settingsManager,});
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.");Run Modes
Section titled “Run Modes”The SDK exports run mode utilities for building custom interfaces on top of createAgentSession():
InteractiveMode
Section titled “InteractiveMode”Full TUI interactive mode with editor, chat history, and all built-in commands:
import { type CreateAgentSessionRuntimeFactory, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, InteractiveMode, SessionManager,} from "@earendil-works/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => { const services = await createAgentSessionServices({ cwd }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })), services, diagnostics: services.diagnostics, };};const runtime = await createAgentSessionRuntime(createRuntime, { cwd: process.cwd(), agentDir: getAgentDir(), sessionManager: SessionManager.create(process.cwd()),});
const mode = new InteractiveMode(runtime, { migratedProviders: [], modelFallbackMessage: undefined, initialMessage: "Hello", initialImages: [], initialMessages: [],});
await mode.run();runPrintMode
Section titled “runPrintMode”Single-shot mode: send prompts, output result, exit:
import { type CreateAgentSessionRuntimeFactory, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, runPrintMode, SessionManager,} from "@earendil-works/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => { const services = await createAgentSessionServices({ cwd }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })), services, diagnostics: services.diagnostics, };};const runtime = await createAgentSessionRuntime(createRuntime, { cwd: process.cwd(), agentDir: getAgentDir(), sessionManager: SessionManager.create(process.cwd()),});
await runPrintMode(runtime, { mode: "text", initialMessage: "Hello", initialImages: [], messages: ["Follow up"],});runRpcMode
Section titled “runRpcMode”JSON-RPC mode for subprocess integration:
import { type CreateAgentSessionRuntimeFactory, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, runRpcMode, SessionManager,} from "@earendil-works/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => { const services = await createAgentSessionServices({ cwd }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })), services, diagnostics: services.diagnostics, };};const runtime = await createAgentSessionRuntime(createRuntime, { cwd: process.cwd(), agentDir: getAgentDir(), sessionManager: SessionManager.create(process.cwd()),});
await runRpcMode(runtime);See RPC documentation for the JSON protocol.
RPC Mode Alternative
Section titled “RPC Mode Alternative”For subprocess-based integration without building with the SDK, use the CLI directly:
pi --mode rpc --no-sessionSee RPC documentation for the JSON protocol.
The SDK is preferred when:
- You want type safety
- You’re in the same Node.js process
- You need direct access to agent state
- You want to customize tools/extensions programmatically
RPC mode is preferred when:
- You’re integrating from another language
- You want process isolation
- You’re building a language-agnostic client
Exports
Section titled “Exports”The main entry point exports:
// FactorycreateAgentSessioncreateAgentSessionRuntimeAgentSessionRuntime
// Auth and ModelsAuthStorageModelRegistry
// Resource loadingDefaultResourceLoadertype ResourceLoadercreateEventBus
// Constants and helpersCONFIG_DIR_NAMEdefineToolgetAgentDirgetPackageDirgetReadmePathgetDocsPathgetExamplesPath
// Session managementSessionManagerSettingsManager
// Tool factoriescreateCodingToolscreateReadOnlyToolscreateReadTool, createBashTool, createEditTool, createWriteToolcreateGrepTool, createFindTool, createLsTool
// Typestype CreateAgentSessionOptionstype CreateAgentSessionResulttype ExtensionFactorytype ExtensionAPItype ToolDefinitiontype Skilltype PromptTemplatetype ToolFor extension types, see extensions.md for the full API.