Files
oh-my-pi/docs/sdk.md
T
2026-04-30 06:47:01 +02:00

353 lines
9.8 KiB
Markdown

# SDK
The SDK is the in-process integration surface for `@oh-my-pi/pi-coding-agent`.
Use it when you want direct access to agent state, event streaming, tool wiring, and session control from your own Bun/Node process.
If you need cross-language/process isolation, use RPC mode instead.
## Installation
```bash
bun add @oh-my-pi/pi-coding-agent
```
## Entry points
`@oh-my-pi/pi-coding-agent` exports the SDK APIs from the package root (and also via `@oh-my-pi/pi-coding-agent/sdk`).
Core exports for embedders:
- `createAgentSession`
- `SessionManager`
- `Settings`
- `AuthStorage`
- `ModelRegistry`
- `discoverAuthStorage`
- Discovery helpers (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)
- Tool factory surface (`createTools`, `BUILTIN_TOOLS`, tool classes)
## Quick start (auto-discovery defaults)
```ts
import { createAgentSession } from "@oh-my-pi/pi-coding-agent";
const { session, modelFallbackMessage } = await createAgentSession();
if (modelFallbackMessage) {
process.stderr.write(`${modelFallbackMessage}\n`);
}
const unsubscribe = session.subscribe((event) => {
if (
event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta"
) {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Summarize this repository in 3 bullets.");
unsubscribe();
await session.dispose();
```
## What `createAgentSession()` discovers by default
`createAgentSession()` follows “provide to override, omit to discover”.
If omitted, it resolves:
- `cwd`: `getProjectDir()`
- `agentDir`: `~/.omp/agent` (via `getAgentDir()`)
- `authStorage`: `discoverAuthStorage(agentDir)`
- `modelRegistry`: `new ModelRegistry(authStorage)` + background `refreshInBackground()` when the registry is not provided
- `settings`: `await Settings.init({ cwd, agentDir })`
- `sessionManager`: `SessionManager.create(cwd)` (file-backed)
- skills/context files/prompt templates/slash commands/extensions/custom TS commands
- built-in tools via `createTools(...)`
- MCP tools (enabled by default)
- LSP integration (enabled by default)
- `eventBus`: new `EventBus()` unless supplied
### Required vs optional inputs
Typically you must provide only what you want to control:
- **Must provide**: nothing for a minimal session
- **Usually provide explicitly** in embedders:
- `sessionManager` (if you need in-memory or custom location)
- `authStorage` + `modelRegistry` (if you own credential/model lifecycle)
- `model` or `modelPattern` (if deterministic model selection matters)
- `settings` (if you need isolated/test config)
## Session manager behavior (persistent vs in-memory)
`AgentSession` always uses a `SessionManager`; behavior depends on which factory you use.
### File-backed (default)
```ts
import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
const { session } = await createAgentSession({
sessionManager: SessionManager.create(process.cwd()),
});
console.log(session.sessionFile); // absolute .jsonl path
```
- Persists conversation/messages/state deltas to session files.
- Supports resume/open/list/fork workflows.
- `session.sessionFile` is defined.
### In-memory
```ts
import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
console.log(session.sessionFile); // undefined
```
- No filesystem persistence.
- Useful for tests, ephemeral workers, request-scoped agents.
- Session methods still work, but persistence-specific behaviors (file resume/fork paths) are naturally limited.
### Resume/open/list helpers
```ts
import { SessionManager } from "@oh-my-pi/pi-coding-agent";
const recent = await SessionManager.continueRecent(process.cwd());
const listed = await SessionManager.list(process.cwd());
const opened = listed[0] ? await SessionManager.open(listed[0].path) : null;
```
## Model and auth wiring
`createAgentSession()` uses `ModelRegistry` + `AuthStorage` for model selection and API key resolution.
### Explicit wiring
```ts
import {
createAgentSession,
discoverAuthStorage,
ModelRegistry,
SessionManager,
} from "@oh-my-pi/pi-coding-agent";
const authStorage = await discoverAuthStorage();
const modelRegistry = new ModelRegistry(authStorage);
await modelRegistry.refresh();
const available = modelRegistry.getAvailable();
if (available.length === 0)
throw new Error("No authenticated models available");
const { session } = await createAgentSession({
authStorage,
modelRegistry,
model: available[0],
thinkingLevel: "medium",
sessionManager: SessionManager.inMemory(),
});
```
### Selection order when `model` is omitted
When no explicit `model`/`modelPattern` is provided:
1. restore model from existing session (if restorable + key available)
2. settings default model role (`default`)
3. first available model with valid auth
If restore fails, `modelFallbackMessage` explains fallback.
### Auth priority
`AuthStorage.getApiKey(...)` resolves in this order:
1. runtime override (`setRuntimeApiKey`)
2. stored credentials in `agent.db`
3. provider environment variables
4. custom-provider resolver fallback (if configured)
## Event subscription model
Subscribe with `session.subscribe(listener)`; it returns an unsubscribe function.
```ts
const unsubscribe = session.subscribe((event) => {
switch (event.type) {
case "agent_start":
case "turn_start":
case "tool_execution_start":
break;
case "message_update":
if (event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
break;
}
});
```
`AgentSessionEvent` includes core `AgentEvent` plus session-level events:
- `auto_compaction_start` / `auto_compaction_end`
- `auto_retry_start` / `auto_retry_end`
- `retry_fallback_applied` / `retry_fallback_succeeded`
- `ttsr_triggered`
- `todo_reminder` / `todo_auto_clear`
- `irc_message`
## Prompt lifecycle
`session.prompt(text, options?)` is the primary entry point.
Behavior:
1. optional command/template expansion (`/` commands, custom commands, file slash commands, prompt templates)
2. if currently streaming:
- requires `streamingBehavior: "steer" | "followUp"`
- queues instead of throwing work away
3. if idle:
- validates model + API key
- appends user message
- starts agent turn
Related APIs:
- `sendUserMessage(content, { deliverAs? })`
- `steer(text, images?)`
- `followUp(text, images?)`
- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`
- `abort()`
## Tools and extension integration
### Built-ins and filtering
- Built-ins come from `createTools(...)` and `BUILTIN_TOOLS`.
- `toolNames` acts as an allowlist for built-ins.
- `customTools` and extension-registered tools are still included.
- Hidden tools (for example `yield`) are opt-in unless required by options.
```ts
const { session } = await createAgentSession({
toolNames: ["read", "grep", "find", "write"],
requireYieldTool: true,
});
```
### Extensions
- `extensions`: inline `ExtensionFactory[]`
- `additionalExtensionPaths`: load extra extension files
- `disableExtensionDiscovery`: disable automatic extension scanning
- `preloadedExtensions`: reuse already loaded extension set
### Runtime tool set changes
`AgentSession` supports runtime activation updates:
- `getActiveToolNames()`
- `getAllToolNames()`
- `setActiveToolsByName(names)`
- `refreshMCPTools(mcpTools)`
System prompt is rebuilt to reflect active tool changes.
## Discovery helpers
Use these when you want partial control without recreating internal discovery logic:
- `discoverAuthStorage(agentDir?)`
- `discoverExtensions(cwd?)`
- `discoverSkills(cwd?, _agentDir?, settings?)`
- `discoverContextFiles(cwd?, _agentDir?)`
- `discoverPromptTemplates(cwd?, agentDir?)`
- `discoverSlashCommands(cwd?)`
- `discoverCustomTSCommands(cwd?, agentDir?)`
- `discoverMCPServers(cwd?)`
- `buildSystemPrompt(options?)`
## Subagent-oriented options
For SDK consumers building orchestrators (similar to task executor flow):
- `outputSchema`: passes structured output expectation into tool context
- `requireYieldTool`: forces `yield` tool inclusion
- `taskDepth`: recursion-depth context for nested task sessions
- `parentTaskPrefix`: artifact naming prefix for nested task outputs
These are optional for normal single-agent embedding.
## `createAgentSession()` return value
```ts
type CreateAgentSessionResult = {
session: AgentSession;
extensionsResult: LoadExtensionsResult;
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
mcpManager?: MCPManager;
modelFallbackMessage?: string;
lspServers?: Array<{
name: string;
status: "ready" | "error";
fileTypes: string[];
error?: string;
}>;
eventBus: EventBus;
};
```
Use `setToolUIContext(...)` only if your embedder provides UI capabilities that tools/extensions should call into.
## Minimal controlled embed example
```ts
import {
createAgentSession,
discoverAuthStorage,
ModelRegistry,
SessionManager,
Settings,
} from "@oh-my-pi/pi-coding-agent";
const authStorage = await discoverAuthStorage();
const modelRegistry = new ModelRegistry(authStorage);
await modelRegistry.refresh();
const settings = Settings.isolated({
"compaction.enabled": true,
"retry.enabled": true,
});
const { session } = await createAgentSession({
authStorage,
modelRegistry,
settings,
sessionManager: SessionManager.inMemory(),
toolNames: ["read", "grep", "find", "edit", "write"],
enableMCP: false,
enableLsp: true,
});
session.subscribe((event) => {
if (
event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta"
) {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Find all TODO comments in this repo and propose fixes.");
await session.dispose();
```