Pify
Reference

API reference

A curated map of core and experimental Pi package entry points at version 0.85.0.

This curated integration reference omits exhaustive specialist and UI exports. It targets upstream commit 107d79f11072bbc8a3a757ed7fd69596bee7d68c, the package roots at 0.85.0, and Node.js 22.19 or newer.

  • @earendil-works/pi-ai owns provider collections, model metadata, authentication, messages, and LLM streams.
  • @earendil-works/pi-agent-core adds the agent loop, tool execution, state, queues, and lifecycle events.
  • @earendil-works/pi-coding-agent assembles sessions, settings, resources, extensions, coding tools, and CLI or SDK runtimes.
  • @earendil-works/pi-client, @earendil-works/pi-protocol, and @earendil-works/pi-server expose the optional experimental routed-service boundary.

The root of pi-ai is side-effect free. Provider factories live under providers/*, wire-protocol implementations under api/*, and the retired global catalog helpers under compat. New integrations should not use compat.

@earendil-works/pi-ai

Provider collections

createModels() returns an empty mutable Models collection. Add only the providers you ship, or use builtinModels() from providers/all when bundle size is not a concern.

catalog.ts
import { createModels } from "@earendil-works/pi-ai";
import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";

const models = createModels();
models.setProvider(anthropicProvider());

const model = models.getModel("anthropic", "claude-sonnet-4-6");
if (!model) throw new Error("Model is not in the registered catalog");

MutableModels adds setProvider(), deleteProvider(), and clearProviders() to the read and request methods on Models. Providers, not a process-wide registry, own catalogs and request routing.

Catalog lookup and authentication

getProviders(), getProvider(), getModels(), and getModel() are synchronous reads of the last-known catalog. refresh() restores or refreshes dynamic providers and returns { aborted, errors }; it does not reject merely because one provider failed. checkAuth(), getAuth(), and getAvailable() resolve provider-scoped availability. login() and logout() use the collection's credential store.

lookup.ts
import type { Api, Model } from "@earendil-works/pi-ai";
import { builtinModels } from "@earendil-works/pi-ai/providers/all";

const models = builtinModels();
const cached: readonly Model<Api>[] = models.getModels("anthropic");
const refresh = await models.refresh({
  providers: ["radius"],
  allowNetwork: false,
});
const available = await models.getAvailable("anthropic");

console.log(cached.length, refresh.errors.size, available.length);

Provider factories normally resolve stored credentials first and then provider environment variables or ambient credentials. Applications that need persistence supply a CredentialStore and, for dynamic catalogs, a ModelsStore to createModels().

Provider factories and adapters

Use a factory from providers/* to add an existing provider. Use createProvider() only when defining a provider or combining a catalog with a wire-protocol implementation. The following local OpenAI-compatible provider uses the public lazy API subpath and standard environment-key resolver.

custom-provider.ts
import {
  createModels,
  createProvider,
  envApiKeyAuth,
  type Model,
} from "@earendil-works/pi-ai";
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";

const localModel = {
  id: "local-chat",
  name: "Local Chat",
  api: "openai-completions",
  provider: "local",
  baseUrl: "http://127.0.0.1:8080/v1",
  reasoning: false,
  input: ["text"],
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
  contextWindow: 32_768,
  maxTokens: 4_096,
} satisfies Model<"openai-completions">;

const local = createProvider({
  id: "local",
  auth: { apiKey: envApiKeyAuth("Local API key", ["LOCAL_API_KEY"]) },
  models: [localModel],
  api: openAICompletionsApi(),
});

const models = createModels();
models.setProvider(local);

A native Provider supplies identity, auth, getModels(), optional refreshModels(), and stream methods. createProvider({ fetchModels }) handles a dynamic overlay. A new protocol adapter must return an AssistantMessageEventStream and follow its terminal-event contract.

Streaming and completion

stream() and complete() accept API-specific options. streamSimple() and completeSimple() accept portable reasoning, retry, transport, abort, and payload/response hooks, then translate those options for the selected API.

stream.ts
import type { Context } from "@earendil-works/pi-ai";
import { builtinModels } from "@earendil-works/pi-ai/providers/all";

const models = builtinModels();
const model = models.getModel("openai", "gpt-4o-mini");
if (!model) throw new Error("Model not found");

const context: Context = {
  messages: [{ role: "user", content: "Reply in one sentence.", timestamp: Date.now() }],
};
const stream = models.streamSimple(model, context);

for await (const event of stream) {
  if (event.type === "text_delta") process.stdout.write(event.delta);
}
const finalMessage = await stream.result();
console.log(finalMessage.stopReason, finalMessage.usage.cost.total);

The stream itself encodes request failures: inspect the final error event or the resolved message's stopReason and errorMessage. Pass an AbortSignal in the options to cancel a request.

Model metadata

Model<TApi> is the request descriptor. Required fields are id, name, api, provider, baseUrl, reasoning, supported input, per-million-token cost, contextWindow, and maxTokens. Optional metadata includes thinkingLevelMap, samplingParams, headers, and API-specific compat flags.

model.ts
import type { Model } from "@earendil-works/pi-ai";

const model = {
  id: "local-chat",
  name: "Local Chat",
  api: "openai-completions",
  provider: "local",
  baseUrl: "http://127.0.0.1:8080/v1",
  reasoning: false,
  input: ["text", "image"],
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
  contextWindow: 32_768,
  maxTokens: 4_096,
  compat: { supportsDeveloperRole: false },
} satisfies Model<"openai-completions">;

console.log(model.provider, model.contextWindow);

Google exposes two public thinking-level types from @earendil-works/pi-ai. GoogleApiThinkingLevel is the API-facing union "THINKING_LEVEL_UNSPECIFIED" | "MINIMAL" | "LOW" | "MEDIUM" | "HIGH", matching the enum-like values accepted by GoogleOptions.thinking.level and GoogleVertexOptions.thinking.level. ResolvedGoogleThinkingLevel is the normalized adapter union "minimal" | "low" | "medium" | "high"; it represents the result after model-level resolution and is appropriate for internal mapping tables, not request options.

google-thinking-types.ts
import type {
  GoogleApiThinkingLevel,
  GoogleOptions,
  ResolvedGoogleThinkingLevel,
} from "@earendil-works/pi-ai";

const apiLevel: GoogleApiThinkingLevel = "HIGH";
const options = {
  thinking: { enabled: true, level: apiLevel },
} satisfies GoogleOptions;

const normalizedBudgets: Record<ResolvedGoogleThinkingLevel, number> = {
  minimal: 1_024,
  low: 2_048,
  medium: 8_192,
  high: 16_384,
};

void [options, normalizedBudgets];

The following selected declarations preserve the exact optional member signatures published by Pi 0.85.0; they do not reproduce the interfaces' other members:

compatibility-types.ts
export interface OpenAICompletionsCompat {
  vllmPriority?: number;
}

export interface OpenAIResponsesCompat {
  supportsMaxOutputTokens?: boolean;
}

export interface AnthropicMessagesCompat {
  supportsMidConvoEffort?: boolean;
}

vllmPriority belongs only to OpenAICompletionsCompat: lower values are handled earlier, the vLLM server default is 0, and the field matters only with --scheduling-policy priority. It is off by default and is not set on the generated model catalog.

supportsMaxOutputTokens belongs to OpenAIResponsesCompat and defaults to true; set it to false when a Responses-compatible gateway rejects max_output_tokens. supportsMidConvoEffort belongs to AnthropicMessagesCompat and defaults to false. For built-in models in the Pi 0.85.0 generated catalog, automatic detection lowercases modelId first, then strips one optional prefix matching ^~?anthropic/ (anthropic/ or ~anthropic/). Pi auto-enables the flag only when provider is exactly anthropic or openrouter. The normalized ID must match exactly ^claude-opus-5(?:-\d{8})?$ or ^claude-(?:fable|mythos)-5(?:[.-]1)(?:-\d{8})?$. The exact supported model must still use a faithful Anthropic Messages transport; this is not support for all Anthropic-compatible providers or an API that merely imitates the Messages shape.

The accepted normalized variants are claude-opus-5, optionally followed by -YYYYMMDD; claude-fable-5.1 or claude-fable-5-1, each optionally dated; and claude-mythos-5.1 or claude-mythos-5-1, each optionally dated.

For a custom Model using anthropic-messages, set compat.supportsMidConvoEffort: true only after verifying both a faithful Anthropic Messages transport and the same exact compatible Claude model family. A provider name outside the generated-catalog allowlist does not by itself forbid manual configuration; never generalize this exception to arbitrary Anthropic-compatible providers or models.

Cost tiers, when present, compare input + cacheRead + cacheWrite with inputTokensAbove; the highest matching threshold prices the whole request.

Context, messages, and tools

Context contains an optional systemPrompt, Message[], and optional Tool[]. Message is the provider-facing union of user, assistant, and tool-result messages. Tool parameters are TypeBox schemas; validate arguments before executing tools when working below Agent Core.

context.ts
import { Type, type Context, type Tool } from "@earendil-works/pi-ai";

const search = {
  name: "search",
  description: "Search indexed documents",
  parameters: Type.Object({ query: Type.String() }),
} satisfies Tool;

const context: Context = {
  systemPrompt: "Cite the matching document.",
  messages: [{ role: "user", content: "Find the release note.", timestamp: Date.now() }],
  tools: [search],
};

console.log(context.tools?.[0]?.name);

AssistantMessage content contains text, thinking, or tool-call blocks and carries usage, cost, stop reason, and optional error or deferred-response metadata. Persist opaque signatures unchanged when replaying a conversation.

Stream events

AssistantMessageEventStream is both an async iterable and a holder for result(). A conforming stream starts once and terminates once.

EventPayload and use
startInitial partial assistant message
text_start / text_delta / text_endText block lifecycle and incremental text
thinking_start / thinking_delta / thinking_endThinking block lifecycle when the model emits it
toolcall_start / toolcall_delta / toolcall_endPartial arguments and the validated final tool call
doneSuccessful terminal event with reason stop, length, toolUse, or deferred
errorTerminal error or aborted assistant message

@earendil-works/pi-agent-core

Agent

Agent is the stateful wrapper for the low-level loop. It owns the transcript, tool execution, steering and follow-up queues, and event delivery. Its streamFn can be models.streamSimple.bind(models).

agent.ts
import { Agent } from "@earendil-works/pi-agent-core";
import { builtinModels } from "@earendil-works/pi-ai/providers/all";

const models = builtinModels();
const model = models.getModel("anthropic", "claude-sonnet-4-6");
if (!model) throw new Error("Model not found");

const agent = new Agent({
  initialState: { systemPrompt: "Be concise.", model },
  streamFn: models.streamSimple.bind(models),
});

await agent.prompt("Explain this module.");

prompt() starts a run; continue() resumes when the last message is user or tool-result. Use steer() for the next turn and followUp() after the loop would otherwise stop.

agentLoop()

agentLoop(prompts, context, config, signal, streamFn) is stateless with respect to your application. It returns EventStream<AgentEvent, AgentMessage[]>; iterate events, then await result() for the new messages. agentLoopContinue() reuses a context whose last message can continue.

agent-loop.ts
import { agentLoop, type AgentContext } from "@earendil-works/pi-agent-core";
import type { Message } from "@earendil-works/pi-ai";
import { builtinModels } from "@earendil-works/pi-ai/providers/all";

const models = builtinModels();
const model = models.getModel("openai", "gpt-4o-mini");
if (!model) throw new Error("Model not found");

const context: AgentContext = { systemPrompt: "Be exact.", messages: [], tools: [] };
const prompt = { role: "user" as const, content: "Summarize the API.", timestamp: Date.now() };
const events = agentLoop(
  [prompt],
  context,
  {
    model,
    convertToLlm: (messages) =>
      messages.filter(
        (message): message is Message =>
          message.role === "user" || message.role === "assistant" || message.role === "toolResult",
      ),
  },
  undefined,
  models.streamSimple.bind(models),
);

for await (const event of events) console.log(event.type);
const newMessages = await events.result();

AgentLoopConfig

The required fields are model and convertToLlm. Optional hooks transform context, resolve keys, prepare the next turn, stop after a completed turn, or intercept tool calls. Portable stream options, queue sources, retry limits, and toolExecution: "parallel" | "sequential" are also accepted.

loop-config.ts
import type { AgentLoopConfig } from "@earendil-works/pi-agent-core";
import type { Message } from "@earendil-works/pi-ai";
import { builtinModels } from "@earendil-works/pi-ai/providers/all";

const models = builtinModels();
const model = models.getModel("openai", "gpt-4o-mini");
if (!model) throw new Error("Model not found");

const config = {
  model,
  convertToLlm: (messages) =>
    messages.filter(
      (message): message is Message =>
        message.role === "user" || message.role === "assistant" || message.role === "toolResult",
    ),
  toolExecution: "parallel",
  shouldStopAfterTurn: ({ toolResults }) => toolResults.some((result) => result.isError),
} satisfies AgentLoopConfig;

console.log(config.toolExecution);

convertToLlm must filter or convert custom AgentMessage variants and must not reject. The same safe-fallback rule applies to transformContext and dynamic key resolution.

AgentTool

AgentTool extends the Pi AI tool schema with a UI label and execute(toolCallId, params, signal, onUpdate). Return model-visible content and structured details; throw to produce an error tool result. onUpdate emits partial progress.

tool.ts
import { Type } from "@earendil-works/pi-ai";
import type { AgentTool } from "@earendil-works/pi-agent-core";

const parameters = Type.Object({ path: Type.String() });

const inspectFile: AgentTool<typeof parameters, { path: string }> = {
  name: "inspect_file",
  label: "Inspect file",
  description: "Inspect one file",
  parameters,
  async execute(_toolCallId, { path }, signal, onUpdate) {
    signal?.throwIfAborted();
    onUpdate?.({ content: [{ type: "text", text: `Opening ${path}` }], details: { path } });
    return { content: [{ type: "text", text: `Inspected ${path}` }], details: { path } };
  },
};

console.log(inspectFile.name);

Multiple calls run in parallel by default, but a tool can request sequential execution. Completion events may arrive out of source order; final tool-result messages remain in assistant source order.

State, control, and events

agent.state exposes the current system prompt, model, thinking level, tools, messages, streaming message, pending tool-call IDs, and latest error. subscribe() accepts sync or async listeners and returns an unsubscribe function; Agent awaits listeners in registration order.

agent-events.ts
import type { Agent } from "@earendil-works/pi-agent-core";

export function observe(agent: Agent): () => void {
  return agent.subscribe(async (event, signal) => {
    if (signal.aborted) return;
    if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
      process.stdout.write(event.assistantMessageEvent.delta);
    }
    if (event.type === "tool_execution_end") console.log(event.toolCallId, event.isError);
  });
}

Runs emit agent_start/agent_end; each model turn emits turn_start/turn_end; messages emit start/update/end; tools emit execution start/update/end. abort() signals the active run. waitForIdle() settles after the final awaited listener. Agent Core also exports harness and session primitives; use Coding Agent's SessionManager when you need Pi's JSONL coding-session format.

@earendil-works/pi-coding-agent

createAgentSession() and AgentSession

createAgentSession(options?) resolves a ModelRuntime, SessionManager, SettingsManager, ResourceLoader, tools, and extensions, then returns { session, extensionsResult, modelFallbackMessage? }. Prompting requires a configured model and credential.

session.ts
import {
  createAgentSession,
  ModelRuntime,
  SessionManager,
} from "@earendil-works/pi-coding-agent";

const modelRuntime = await ModelRuntime.create();
const { session, modelFallbackMessage } = await createAgentSession({
  modelRuntime,
  sessionManager: SessionManager.inMemory(process.cwd()),
  tools: ["read"],
});

const unsubscribe = session.subscribe((event) => {
  if (event.type === "agent_settled") console.log("idle");
});
try {
  if (modelFallbackMessage) console.warn(modelFallbackMessage);
  await session.prompt("Describe the current directory.");
} finally {
  await session.abort();
  unsubscribe();
  session.dispose();
}

An AgentSession adds synchronous subscriptions, persistence, compaction, retries, bash execution, model selection, reload, and extension dispatch around Agent. Await abort() to cancel retry and agent work and wait for idle; call dispose() when the host is finished.

Persistence, settings, and resources

  • SessionManager.create(cwd, sessionDir?, options?), continueRecent(cwd, sessionDir?), open(path, sessionDir?, cwdOverride?), inMemory(cwd?, options?, entries?), forkFrom(sourcePath, targetCwd, sessionDir?, options?), and list(cwd, sessionDir?, onProgress?) manage append-only JSONL sessions and their trees. Use listAll(onProgress?) or listAll(sessionDir?, onProgress?) across projects.
  • SettingsManager.create(cwd, agentDir?) merges global and trusted project settings; SettingsManager.inMemory() is useful for embedded hosts and tests.
  • new DefaultResourceLoader({ cwd, agentDir, settingsManager? }) constructs a loader that discovers context files, system prompts, extensions, skills, prompt templates, and themes after reload().
  • ModelRuntime.create() owns the provider catalog and synchronized credentials used by Coding Agent.

External-session restoration

FileEntry, SessionHeader, SessionEntry, and NewSessionOptions are public package-root types. The declarations relevant to restoring application-owned entries are exactly:

export interface NewSessionOptions {
  id?: string;
  parentSession?: string;
}

export type FileEntry = SessionHeader | SessionEntry;

export declare class SessionManager {
  static inMemory(
    cwd?: string,
    options?: NewSessionOptions,
    entries?: FileEntry[],
  ): SessionManager;
}

Passing entries restores the parent-linked tree without enabling Pi file persistence. The caller owns validation, durable writes, and synchronization with its external store. parseSessionEntries() and migrateSessionEntries() are also exported, but parsing skips malformed JSON lines and migration mutates its array; neither is a general trust-boundary schema validator.

Compaction exports and failure boundary

The package root exports DEFAULT_COMPACTION_SETTINGS, shouldCompact(), compact(), generateSummary(), generateSummaryWithUsage(), generateBranchSummary(), and the related public result, settings, preparation, and file-operation types. It does not export the internal getSummarizationFailure() helper. Built-in compaction, turn-prefix, and branch-summary generation nevertheless apply that check internally: a response ending with stopReason: "length" is incomplete and is not persisted as a summary checkpoint. Branch summary generation now requests at most 4,096 output tokens, further bounded by a smaller positive model limit.

The Settings fragment below is a selected exact source-level internal settings.json shape; it is not exported or importable public API, while SettingsManager and selected settings types are the public importable surface of @earendil-works/pi-coding-agent 0.85.0.

thinking-settings-types.ts
interface Settings {
  defaultThinkingLevel?: ThinkingLevel;
  modelThinkingLevels?: Record<string, ThinkingLevel>;
  showCacheMissNotices?: boolean;
}

declare class SettingsManager {
  getDefaultThinkingLevel(): ThinkingLevel | undefined;
  setDefaultThinkingLevel(level: ThinkingLevel): void;
  getModelThinkingLevel(provider: string, modelId: string): ThinkingLevel | undefined;
  getAllModelThinkingLevels(): Record<string, ThinkingLevel>;
  setModelThinkingLevel(provider: string, modelId: string, level: ThinkingLevel): void;
  removeModelThinkingLevel(provider: string, modelId: string): void;
}

modelThinkingLevels stores per-model startup choices keyed by provider/modelId; the corresponding SettingsManager methods accept the provider and model ID separately. defaultThinkingLevel remains the global startup fallback, can be saved with Ctrl+S in /thinking, and is distinct from provider request fields.

When showCacheMissNotices is enabled, the transcript can also surface provider recovery diagnostics such as dropped Anthropic thinking blocks, in addition to significant cache misses and summary usage.

Direct SDK hosts own cwd, trust, storage, and cleanup policy. Do not mutate session JSONL while a manager is active, and do not assume that SettingsManager.create() reproduces CLI trust resolution without the host supplying that decision.

Image MIME detection

detectSupportedImageMimeTypeFromFile() is exported from the @earendil-works/pi-coding-agent package root with this exact published signature:

declare function detectSupportedImageMimeTypeFromFile(
  filePath: string,
): Promise<string | null>;

detectSupportedImageMimeTypeFromFile() opens the filename supplied by filePath, reads at most the first 4,100 bytes, and checks file-header signatures plus the limited structural fields needed to detect exactly image/jpeg, image/png for non-animated PNG, image/gif, image/webp, or image/bmp. It returns null for unsupported or undetectable content; filesystem open or read failures still reject the promise. This detection is based on file content, not the filename extension, and does not decode, resize, or fully validate the image. Use the result as an input-format gate, not as proof that an image is fully decodable or safe.

Extensions and managed tools

An extension is an ExtensionFactory receiving the exported ExtensionAPI. Register tools, commands, shortcuts, flags, providers, and event handlers through that object; there is no global registerExtension().

extension.ts
import { Type } from "@earendil-works/pi-ai";
import { defineTool, type ExtensionFactory } from "@earendil-works/pi-coding-agent";

const inspectPath = defineTool({
  name: "inspect_path",
  label: "Inspect path",
  description: "Return the requested path",
  parameters: Type.Object({ path: Type.String() }),
  async execute(_toolCallId, { path }) {
    return { content: [{ type: "text", text: path }], details: { path } };
  },
});

const extension: ExtensionFactory = (pi) => {
  pi.registerTool(inspectPath);
  pi.on("before_agent_start", (event) => ({
    systemPrompt: `${event.systemPrompt}\nKeep file paths exact.`,
  }));
};

export default extension;

pi.setModel() changes the current session's model. A successful selection is recorded in session history and restored when that session is resumed, but it does not change the configured defaultProvider or defaultModel used by new sessions. The Promise resolves to false when the selected provider lacks authentication.

pi.setThinkingLevel() computes the effective capability-clamped level and records a session-history change only when it differs from the current value; not every requested choice produces a history entry. Pi persists and restores that effective change for the current session, but it does not change the configured default used by new sessions.

The default editor automatically embeds its working indicator in the editor border. Custom editors built from CustomEditor keep the standalone working indicator unless they opt in: pass { embedWorkingStatus: true } as the fourth constructor argument to embed the same status in the border. The option changes status placement, not Agent settlement or Tool execution.

Managed toolAvailability
read, bash, edit, writeBuilt in and active by default unless settings or SDK options change the selection
powershellOptional Windows built-in; select it explicitly or use its exported factory
grep, find, lsBuilt in; activate through tools or use their exported factories
Extension or customTools entriesRegistered by the host; still filtered by tools, excludeTools, and noTools

Tool access is an application policy. The current SDK does not expose the baseline --yolo switch.

RPC queue and cancellation

The headless RPC protocol accepts an optional correlation ID. Its exact clear_queue request and successful response types are:

{ id?: string; type: "clear_queue" }
{
  id?: string;
  type: "response";
  command: "clear_queue";
  success: true;
  data: { steering: string[]; followUp: string[] };
}

clear_queue atomically removes queued work and returns the removed text, keeping steering and follow-up messages separate. RPC abort cancels the active operation, now including active manual compaction, and waits until the session is idle before responding. Queued work can continue unless clear_queue removed it; cancellation and queue disposal are distinct operations.

For an interactive Escape flow, issue clear_queue before abort, then decide whether to restore the returned steering and followUp strings in the editor. This ordering prevents queued continuation work from starting while the abort command waits for idle.

PowerShell Tool factory and operations

The package root publicly exports the PowerShell factory and types; no deep import into dist/ or src/ is required:

import {
  createPowerShellTool,
  type PowerShellOperations,
  type PowerShellToolOptions,
} from "@earendil-works/pi-coding-agent";

The published declaration is:

declare function createPowerShellTool(
  cwd: string,
  options?: PowerShellToolOptions,
): ReturnType<typeof createBashTool>;

In compact signature form, this is createPowerShellTool(cwd: string, options?: PowerShellToolOptions). The returned AgentTool accepts { command: string, timeout?: number }, streams partial results through the normal Tool update callback, and resolves to the shared shell-tool detail shape. Creating the Tool does not execute a command; execution begins only when its execute method is invoked by the host.

PowerShellToolOptions is declared as a Pick of the shared shell options. Its effective public shape is:

interface PowerShellToolOptions {
  operations?: PowerShellOperations;
  exposeSessionEnvironment?: boolean;
  spawnHook?: PowerShellSpawnHook;
}

PowerShellSpawnHook receives and returns { command: string; cwd: string; env: NodeJS.ProcessEnv }. The default exposeSessionEnvironment is true; the hook runs after Pi builds the command environment. Unlike BashToolOptions, this type does not expose commandPrefix or shellPath.

PowerShellOperations is a public alias of BashOperations, not a private PowerShell process class. A custom backend implements exactly one streamed method:

interface PowerShellOperations {
  exec: (
    command: string,
    cwd: string,
    options: {
      onData: (data: Buffer) => void;
      signal?: AbortSignal;
      timeout?: number;
      env?: NodeJS.ProcessEnv;
    },
  ) => Promise<{ exitCode: number | null }>;
}

The Tool wrapper owns argument validation, progress/result formatting, and bounded output. The operations backend owns actual execution: it must stream stdout/stderr bytes through onData, honor cancellation and timeout, return null when killed, and clean up process trees, transports, timers, and listeners. createLocalPowerShellOperations() is also public and supplies Pi's native Windows backend, but its executable discovery and process lifecycle are implementation behavior rather than an API for private process handles.

Runtime and CLI integration

For one fixed session, use createAgentSession(). For new, switch, fork, clone, or import flows, use createAgentSessionRuntime() and read runtime.session again after replacement; subscriptions belong to the old session. Lower-level hosts can use createAgentSessionServices(), createAgentSessionFromServices(), runPrintMode(), runRpcMode(), RpcClient, parseArgs(), or main().

CLI flagCurrent purpose
--provider, --model, --modelsSelect one model or a model-cycle scope
--thinkingSelect off, minimal, low, medium, high, xhigh, or max
--system-prompt <text>Replace the base prompt source with literal text or the contents of an existing file path
--append-system-prompt <text>Add literal text or an existing file as an ordered append source; repeat the flag to add more sources
--tools, --exclude-tools, --no-tools, --no-builtin-toolsSelect the initial tool surface
--session, --session-id, --session-dir, --continue, --resume, --fork, --no-sessionChoose persistence or restoration behavior
--extension, --no-extensions, --skill, --no-skills, --no-context-filesControl discovered or explicit resources
`--mode textjson

Extensions can register additional flags, so parseArgs() retains unknown flags for extension resolution. Use --help from the installed pi binary as the complete CLI inventory for that exact version.

Experimental routed-service packages

The following package-root exports are the current 0.85.0 boundary, not a stable remote-Agent recipe. Applications still own service contracts, transport authentication, Session discovery, worker lifecycle, and retry policy. Subpath exports such as @earendil-works/pi-client/unix, @earendil-works/pi-server/unix, and @earendil-works/pi-server/testing are separate from the roots summarized here.

@earendil-works/pi-client

The root exports Client and createClientServiceTransport; ClientDisposedError, DisconnectedError, and ServerError; the transport contracts ByteTransport, ByteTransportFactory, and ByteTransportHandlers; and the client types AttachmentChangeListener, ClientOptions, ConnectionState, ConnectionStateChange, ListenerErrorHandler, ServiceSubscription, and Unsubscribe.

Client is transport-neutral and operates on explicit RpcTarget values. createClientServiceTransport(client, getTarget) adapts a lazily resolved target to Chord's RemoteServiceTransport. It does not manufacture typed services. On disconnect or disposal, pending requests reject locally and the live attachment is cleared; the client does not reconnect or replay requests automatically, even though accepted work may finish remotely.

@earendil-works/pi-protocol

The root exports PROTOCOL_VERSION (value 8), isServerId, the message and target types ClientMessage, ServerMessage, RpcTarget, ServerId, and SessionTarget, plus the individual hello, request, cancellation, response, service-event, attachment, and protocol-error types. Encoding and validation entry points include parseClientMessage, parseServerMessage, encodeClientMessage, encodeServerMessage, ClientMessageDecoder, ServerMessageDecoder, isSupportedProtocolVersion, and ProtocolValidationError.

The same root re-exports CBOR and framing primitives: encodeCbor, decodeCbor, CborError, CBOR limit constants and options, encodeFrame, FrameDecoder, FrameError, FrameDecoderOptions, and DEFAULT_MAX_FRAME_LENGTH. These APIs validate strict envelopes, framing, and opaque strict-JSON values; Chord owns service-control parsing, subscriptions, bindings, and replicated-state semantics.

@earendil-works/pi-server

The root exports Server, ServerListener, ServerOptions, ServerHost, RoutedServerPresentation, RoutedServerServiceAttachment, RoutedServerServiceHost, RoutedSessionAttachment, RoutedSessionHandle, and MaybePromise. It also exports ServerError, WrongServerError, SessionNotFoundError, SessionAmbiguousError, SessionNotAttachedError, ServerDrainingError, and INTERNAL_SERVER_ERROR_MESSAGE.

The server routes server-scoped and attachment-scoped Session services; it does not export the application's service catalogue or move an open Session or Agent Harness over the wire. A server target is { serverId }, whereas a live Session target is { serverId, sessionId, attachmentId }. Constructing listeners and providing routed service hosts are application responsibilities, so this reference intentionally does not present an end-to-end launch recipe as stable.

The client, protocol, and server packages are experimental and have no compatibility guarantee. Pin their versions together and treat reconnect, replay, authentication, and lifecycle behavior as explicit application policy.

Next

  • Review runtime settings: Configuration reference
  • Review credentials and paths: Environment variables
  • Add a provider or protocol adapter: Plug in a new model
  • Build an Agent Core tool: Add a custom tool
  • Render event streams: Stream output
  • Store and branch coding sessions: Persist sessions

On this page