Skip to content

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: ~/.pisces/agent (via getAgentDir())
  • authStorage: discoverAuthStorage(agentDir)
  • modelRegistry: new ModelRegistry(authStorage) + await refresh()
  • 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)

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
  • ttsr_triggered
  • todo_reminder

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 submit_result) are opt-in unless required by options.
ts
const { session } = await createAgentSession({
	toolNames: ["read", "grep", "find", "write"],
	requireSubmitResultTool: 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
  • requireSubmitResultTool: forces submit_result 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 }>;
};

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();