pi-coding-agent について
pi-coding-agentは、TypeScriptやNodeのスクリプト、サービスからエージェントセッションを作成するSDKです。ストリーミング出力、カスタムツール、ツールセットの制限に対応し、専用エージェントや自動化処理に利用できます。
主な機能
- 01Nodeスクリプトからのセッション作成
- 02エージェント出力のストリーミング
- 03組み込みツールの選択と制限
- 04呼び出し可能なカスタムツールの定義
- 05リソースローダーによる拡張とプロンプトの設定
活用シーン
- 01専用のTypeScript CLIエージェントの構築
- 02Nodeサービスやジョブランナーへの組み込み
- 03スクリプト処理での一度限りのエージェント実行
- 04読み取り専用または特定領域のツールへの制限
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mizchi/ skills --skill pi-coding-agentインストール後、Claude Code で/pi-coding-agentを入力して読み込まれたか確認します。
同梱ファイルREADME.md
詳細
pi-coding-agent
@mariozechner/pi-coding-agent (upstream) は、pi の対話型 TUI と、フル機能の SDK を提供します。SDK を使うと、Node 20.6+ の任意のスクリプトからエージェントセッションを起動し、出力をストリーミングし、ツールの適用範囲を設定したり、独自のツールを追加したりできます。CLI をフォークする必要はありません。
使用する場面
- TypeScript / Node で単一目的のエージェント CLI を構築する場合(チャット製品ではないもの)
- より大きな Node サービスやジョブランナー内で pi をラップする場合
- スクリプトのパイプラインにエージェントの推論を組み込む場合(一度だけ実行し、完了時に終了する)
- 読み取り専用または特定のドメインに制限されたツールセットが必要な場合
- エージェントが呼び出せるカスタムツール(
deploy、query_db、…)を追加したい場合
次の場合は、このスキルを使わないでください。
- pi を対話的に使いたいだけの場合 → CLI から
piを実行してください。SDK は不要です - ホストが Node ではない場合 → 代わりに
pi --mode rpc(サブプロセス経由の JSON-RPC)を使用してください - Anthropic の公式 SDK のセマンティクスが必要な場合 → それは別パッケージの
@anthropic-ai/claude-agent-sdkです
注意点(最初にお読みください)
| 症状 | 原因 | 解決方法 |
|---|---|---|
require() で ERR_PACKAGE_PATH_NOT_EXPORTED が発生する | パッケージが ESM 専用です("type": "module"、CJS を含まない exports マップ) | ESM(import)を使用するか、tsx / --experimental-strip-types で実行してください |
| ツールが誤ったディレクトリを基準にパスを解決する | ビルド済みの readTool / bashTool は、インポート時の process.cwd() をキャプチャします | カスタムの cwd と明示的な tools の両方を渡す場合は、ファクトリー形式の createCodingTools(cwd) / createReadTool(cwd) を使用してください |
session.prompt(...) が「stream in progress」をスローする | 前のストリームが実行中に、再度 prompt() を呼び出しています | { streamingBehavior: "steer" } または "followUp" を渡すか、session.steer() / session.followUp() を使用してください |
ANTHROPIC_API_KEY を設定しているのに認証エラーが発生する | AuthStorage はランタイムのオーバーライド → auth.json → 環境変数の順に読み込みます。古い ~/.pi/agent/auth.json が環境変数を上書きしている可能性があります | 保存済みの認証情報を削除するか、createAgentSession() の前に authStorage.setRuntimeApiKey("anthropic", key) を呼び出してください |
| ストリーミング中に何も表示されない | 購読を忘れているか、text_delta だけを処理していて、モデルが思考のみを出力しています | message_update を購読し、text_delta と(必要に応じて)thinking_delta の両方を処理してください |
拡張機能が pi -e ./ext.ts では読み込まれるが、通常の実行では読み込まれない | -e は一度限りの指定であり、そのファイルは自動検出ディレクトリにありません | ~/.pi/agent/extensions/<name>.ts(グローバル)または <project>/.pi/extensions/<name>.ts(プロジェクト)に移動するか、settings.json の extensions: [...] に登録してください |
インストールした pi パッケージが、実行時に Cannot find module '@mariozechner/pi-coding-agent' で失敗する | peerDependencies ではなく dependencies に記載しています | 5 つのコアパッケージ(@mariozechner/pi-{ai,agent-core,coding-agent,tui}、typebox)は "*" の範囲で peerDependencies に指定し、バンドルしてはいけません。pi は読み込み時にこれらを提供します |
pi install は成功したが、推移的な依存関係が見つからない | デフォルトで npm install --omit=dev が使用されるため、実行時に devDependencies が存在しません | インポートを dependencies に移すか、カスタムの npm ラッパーが必要な場合は settings.json で npmCommand を設定してください |
| 拡張機能の変更が反映されない | TUI が前回の読み込み結果をキャッシュしています | pi 内で /reload を実行してください(自動検出される場所にある拡張機能であれば、どれでも有効です) |
インストール
pnpm add @mariozechner/pi-coding-agent
# Or one-off scripts:
npx tsx my-agent.ts
Node ≥ 20.6 と API キー(環境変数または ~/.pi/agent/auth.json)が必要です。
最小限のセッション
#!/usr/bin/env -S npx tsx
import {
AuthStorage,
ModelRegistry,
SessionManager,
createAgentSession,
} from "@mariozechner/pi-coding-agent";
const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(), // no .jsonl on disk
authStorage,
modelRegistry,
});
session.subscribe((event) => {
if (
event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta"
) {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt(process.argv.slice(2).join(" ") || "Hello");
実行方法:
export ANTHROPIC_API_KEY=sk-ant-...
npx tsx my-agent.ts "Explain the layout of src/"
オプションなしの createAgentSession() も使用できます。~/.pi/agent/ と cwd から、ツール、スキル、拡張機能、デフォルトモデルを検出します。デフォルトから変更する必要がある場合にのみ、オプションを渡してください。
ツールセットの制限
SDK には、組み込みのツール配列と文字列による名前指定の両方が用意されています。読みやすいほうを使ってください。
import { createAgentSession, readOnlyTools } from "@mariozechner/pi-coding-agent";
// Array of tool objects (use process.cwd())
await createAgentSession({ tools: readOnlyTools });
// Or by name — equivalent, more concise
await createAgentSession({ tools: ["read", "grep", "find", "ls"] });
ツール名のカタログ:read、bash、edit、write、grep、find、ls。事前定義済みのセット:
| セット | 内容 |
|---|---|
codingTools | read、bash、edit、write(デフォルト) |
readOnlyTools | read、grep、find、ls |
cwd × tools の落とし穴
事前構築済みのツールインスタンス(readOnlyTools、codingTools、readTool、bashTool、…)は、インポート時の process.cwd() を保持し、再バインドされません。カスタムの cwd と組み合わせると、エージェントは気づかないうちに誤ったディレクトリを読み込みます。
覚えておくべき組み合わせは次の3つです。
| 形式 | カスタムの cwd を指定した場合 | 理由 |
|---|---|---|
tools: readOnlyTools(定数) | NG | インポート時の process.cwd() を保持します |
tools: ["read", "grep", ...](文字列名) | OK | SDK が指定された cwd に基づいて、セッションごとに名前を解決します |
tools: createReadOnlyTools(cwd)(ファクトリー) | OK | cwd を明示的にバインドします |
tools を省略 | OK | SDK がデフォルトのセットを選び、cwd をバインドします |
ファクトリー群はすべての定数/インスタンスに対応しています。必要に応じて選んでください。
| 必要なもの… | ファクトリー |
|---|---|
cwd にバインドされた codingTools セット | createCodingTools(cwd) |
cwd にバインドされた readOnlyTools セット | createReadOnlyTools(cwd) |
cwd にバインドされた単一のツール | createReadTool(cwd)、createBashTool(cwd)、createEditTool(cwd)、createWriteTool(cwd)、createGrepTool(cwd)、createFindTool(cwd)、createLsTool(cwd) |
import {
createAgentSession,
createCodingTools,
createReadOnlyTools,
createGrepTool,
createReadTool,
} from "@mariozechner/pi-coding-agent";
const cwd = "/path/to/project";
// Whole set, bound to cwd
await createAgentSession({ cwd, tools: createCodingTools(cwd) });
await createAgentSession({ cwd, tools: createReadOnlyTools(cwd) });
// Or hand-picked, bound to cwd
await createAgentSession({ cwd, tools: [createReadTool(cwd), createGrepTool(cwd)] });
// Or — equivalent and shorter — name-based, which the SDK rebinds per session
await createAgentSession({ cwd, tools: ["read", "grep", "find", "ls"] });
デフォルト以外の cwd と明示的な tools の値を両方渡す場合にのみ、この点を考慮する必要があります。tools をまったく指定しなければ、SDK が cwd にバインドされたデフォルトのセットを構築します。
カスタムツール
import { Type } from "typebox";
import { createAgentSession, defineTool } from "@mariozechner/pi-coding-agent";
const deployTool = defineTool({
name: "deploy",
label: "Deploy",
description: "Deploy the current branch to staging",
parameters: Type.Object({
target: Type.Optional(Type.String({ description: "Target env (default: staging)" })),
}),
execute: async (_toolCallId, params) => {
// Your logic. Must return { content: [...], details: {...} }.
const target = params.target ?? "staging";
return {
content: [{ type: "text", text: `Deployed to ${target}` }],
details: { target },
};
},
});
const { session } = await createAgentSession({
customTools: [deployTool],
});
customTools は追加されるもので、指定した tools と統合されます。パラメーターには TypeBox を使用します。エージェントには JSON Schema の形式で表示され、execute には型付けされた params が渡されます。
カスタムシステムプロンプト
システムプロンプトを管理するのは createAgentSession() ではなく、ResourceLoader です。
import { DefaultResourceLoader, createAgentSession } from "@mariozechner/pi-coding-agent";
const resourceLoader = new DefaultResourceLoader({
systemPromptOverride: () => "You are a senior SRE. Answer tersely.",
});
await resourceLoader.reload();
const { session } = await createAgentSession({ resourceLoader });
同じローダーが、スキル、プロンプトテンプレート、拡張機能、仮想的な AGENTS.md コンテキストファイルを追加する際のエントリーポイントにもなります。オプションの全一覧については、docs/sdk.md を参照してください。
拡張機能(プラグインシステム)
拡張機能は、pi が起動時に読み込む TypeScript モジュールです。SDK 経由で渡す customTools と比べて、拡張機能には次の特徴があります。
- それを検出するすべての
piの呼び出しで再利用できます - ツールとコマンド(
/foo)、ショートカット、フッターウィジェット、プロバイダー、カスタムレンダリングを登録できます - ライフサイクルイベントをインターセプトできます(ツール呼び出しのブロック、コンテキストの変更、コンパクションのカスタマイズ)
ctx.uiを介してユーザーにプロンプトを表示できます(選択/確認/入力/通知)
Pi は jiti を使用して .ts を直接読み込みます。コンパイルは不要です。
セキュリティ: 拡張機能はシステムの全権限で実行され、任意のコードを実行できます。信頼できるソースからのみインストールしてください。
自動検出される場所
| パス | スコープ |
|---|---|
~/.pi/agent/extensions/<name>.ts | グローバル、単一ファイル |
~/.pi/agent/extensions/<name>/index.ts | グローバル、複数ファイル |
.pi/extensions/<name>.ts | プロジェクトローカル |
.pi/extensions/<name>/index.ts | プロジェクトローカル、複数ファイル |
これに加えて、settings.json で宣言されたパスも対象です(下記のインストールセクションを参照)。自動検出される場所にある拡張機能は、TUI 内で /reload を実行するとホットリロードできます。
最小限の拡張機能
~/.pi/agent/extensions/greet.ts として保存します。
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("greet extension loaded", "info");
});
// Block dangerous bash commands
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return;
const cmd = event.input.command as string;
if (/\brm\s+-rf?\b|\bsudo\b/.test(cmd)) {
const ok = ctx.hasUI ? await ctx.ui.confirm("Dangerous", `Allow:\n${cmd}`) : false;
if (!ok) return { block: true, reason: "blocked by greet extension" };
}
});
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({ name: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: `Hello, ${params.name}!` }], details: {} };
},
});
pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => ctx.ui.notify(`Hello ${args || "world"}!`, "info"),
});
}
どこにもインストールせずに試すには、次のようにします。
pi -e ./greet.ts
非同期ファクトリー(起動時の処理向け)
非同期のデフォルトエクスポートは、session_start が発生する前に完了を待機します。動的な設定(ローカル LLM のモデル一覧など)の取得に使用してください。
export default async function (pi: ExtensionAPI) {
const res = await fetch("http://localhost:1234/v1/models");
const { data } = (await res.json()) as { data: Array<{ id: string; context_window?: number; max_tokens?: number }> };
pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "LOCAL_OPENAI_API_KEY",
api: "openai-completions",
models: data.map((m) => ({
id: m.id, name: m.id, reasoning: false, input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: m.context_window ?? 128000,
maxTokens: m.max_tokens ?? 4096,
})),
});
}
SDK から拡張機能を読み込む
SDK コードでは、拡張機能は引き続き ResourceLoader が管理します。DefaultResourceLoader は上記と同じ自動検出ディレクトリを検出します。単発のパスには additionalExtensionPaths を、インラインファクトリーには extensionFactories を渡します。
import { DefaultResourceLoader, createAgentSession } from "@mariozechner/pi-coding-agent";
const resourceLoader = new DefaultResourceLoader({
additionalExtensionPaths: ["/abs/path/to/my-ext.ts"],
extensionFactories: [
(pi) => pi.on("agent_start", () => console.log("[inline] agent starting")),
],
});
await resourceLoader.reload();
const { session } = await createAgentSession({ resourceLoader });
npm 依存関係を含む複数ファイルの拡張機能
npm パッケージを使用する拡張機能の場合は、その隣に package.json を配置します。
~/.pi/agent/extensions/my-ext/
├── package.json
├── package-lock.json
├── node_modules/
└── src/
└── index.ts
// package.json
{
"name": "my-ext",
"dependencies": { "zod": "^3.0.0" },
"pi": { "extensions": ["./src/index.ts"] }
}
ディレクトリ内で一度 npm install を実行します。pi はローカルの node_modules/ を通じてインポートを解決します。
サードパーティパッケージのインストール
pi パッケージは配布単位です。1 つ以上の拡張機能、スキル、プロンプトテンプレート、テーマをまとめます。pi install は settings.json に書き込み、次回起動時に pi がパッケージで宣言されたすべての内容を自動で読み込みます。
pi install npm:@foo/[email protected] # npm, version-pinned
pi install npm:@foo/bar # npm, latest (re-pulled by `pi update`)
pi install git:github.com/user/repo@v1 # git, ref-pinned
pi install https://github.com/user/repo # raw URL also works
pi install ./local/package # local path, not copied
pi list # show installed
pi update # pull all non-pinned packages
pi remove npm:@foo/bar
デフォルトでは、~/.pi/agent/settings.json(グローバル)を変更します。代わりに <project>/.pi/settings.json に書き込むには -l を使用します。このファイルをコミットすると、チーム全員の pi が起動時に不足しているパッケージを自動インストールします。
設定を保存せずにパッケージを試すには、次のようにします。
pi -e npm:@foo/bar
pi -e git:github.com/user/repo
-e は、現在の実行時のみ使用するために一時ディレクトリへインストールします。
生成される settings.json は次のようになります。
{
"packages": [
"npm:@foo/[email protected]",
"git:github.com/user/repo@v1"
],
"extensions": [
"/absolute/path/to/local/extension.ts",
"/absolute/path/to/local/extension/dir"
]
}
packages(配列):ソース別の pi パッケージです。extensions(配列):パッケージ化されていない拡張機能のパスです。どちらの配列も、パッケージ内で対象を絞り込むオブジェクト形式に対応しています。例:{ source: "npm:my-pkg", extensions: ["extensions/*.ts", "!extensions/legacy.ts"], skills: [], prompts: [...] }。
公開可能な pi パッケージを作成する
package.json に pi マニフェストを追加し、pi-package キーワードを付けると、pi.dev/packages で検出されるようになります。
{
"name": "my-pi-pack",
"version": "1.0.0",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
},
"peerDependencies": {
"@mariozechner/pi-coding-agent": "*",
"@mariozechner/pi-ai": "*",
"@mariozechner/pi-agent-core": "*",
"@mariozechner/pi-tui": "*",
"typebox": "*"
},
"dependencies": {
"zod": "^3.0.0"
}
}
pi フィールドを省略すると、pi は慣例のディレクトリ(extensions/、skills/、prompts/、themes/)から自動検出します。パス配列では glob と !negation を使用できます。
依存関係のルール — ここを間違えると、インストール時にパッケージが壊れます。
- 5 つのコアパッケージ —
@mariozechner/pi-coding-agent、@mariozechner/pi-ai、@mariozechner/pi-agent-core、@mariozechner/pi-tui、typebox— は"*"を指定してpeerDependenciesに含める必要があります。pi は実行時にこれらを提供します。バンドルするとモジュール ID の不整合が発生します(独自のdefineToolは pi のdefineToolではありません)。 - それ以外(zod、chalk など)は
dependenciesに含めます。pi は取得後にnpm install --omit=devを実行するため、実行時にdevDependenciesは利用できません。 - 別の pi パッケージを埋め込むには、
dependenciesとbundledDependenciesの両方に記載し、node_modules/<name>/...を通じてそのリソースを参照します。
プリントモード(ワンショットのパイプライン)
runPrintMode は pi -p と同様に動作します。プロンプトを送信し、結果をストリーミングして終了します。シェルパイプラインや CI タスクで役立ちます。
import {
type CreateAgentSessionRuntimeFactory,
SessionManager,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
runPrintMode,
} from "@mariozechner/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.inMemory(),
});
await runPrintMode(runtime, {
mode: "text", // "text" | "json"
initialMessage: process.argv.slice(2).join(" "),
});
mode: "json" にすると、各ターンの出力が構造化された JSON エンベロープに切り替わります。別のプログラムにパイプで渡す場合に適しています。
実行中のストリームを操作する
session.prompt() はデフォルトで順次実行されます。実行中のターンに入力するには、次のようにします。
await session.steer("Stop and read README.md first"); // delivered after current tool calls
await session.followUp("Then summarise it"); // delivered after the agent finishes
どちらもファイルベースのプロンプトテンプレートを展開しますが、拡張機能コマンド(/...)は拒否します。ストリーミング中に session.prompt(text) を呼び出す場合は、{ streamingBehavior: "steer" | "followUp" } のいずれかを明示的に選択する必要があります。
適切なエントリーポイントの選び方
| 用途 | API |
|---|---|
| エンドツーエンドで制御する単発の CLI ツール | createAgentSession() + SessionManager.inMemory() |
| ディスク上にセッションを永続化する | SessionManager.create(cwd)(または .continueRecent、.open) |
| パイプに適したスクリプトを作成する | runPrintMode(runtime, { mode: "json" }) |
| ドメインに合わせたカスタムツール(1つのプロセス内、コード内で定義) | defineTool() + customTools: [...] |
このマシン上のすべての pi の実行で共有するツール/コマンド | pi.registerTool/pi.registerCommand を使用する ~/.pi/agent/extensions/<name>.ts の拡張機能 |
| 1つのプロジェクトに限定するツール/コマンド(コミットして共有) | <project>/.pi/extensions/<name>.ts の拡張機能と、<project>/.pi/settings.json(-l を使用) |
| 拡張機能/スキル/プロンプトをパッケージ化して共有する | Pi パッケージ:pi フィールドと pi-package キーワードを含む package.json |
| 他の人のパッケージをインストールする | pi install npm:<spec> または pi install git:<spec>(プロジェクトに限定する場合は -l を使用) |
実行時にアクティブなセッションを置き換える(/new、/fork、/resume) | createAgentSessionRuntime() を呼び出し、その後 runtime.newSession() などを実行します。置き換えるたびに再購読してください |
| 別の言語からエージェントを操作する | pi --mode rpc --no-session(stdio 経由の JSON-RPC)、SDK は不要 |
SDK と pi --mode rpc の比較
型安全性、プロセス内での制御、TypeScript で記述されたツールや拡張機能を登録する機能が必要な場合は、SDK を使用してください。ホストが Python、Go、Rust などの場合や、プロセス分離が必要な場合は、RPC モードを使用してください。どちらも同じエージェントを公開します。SDK では、内部状態やイベントストリームに直接アクセスできます。
参考資料
- 上流の SDK ガイド — SDK の正式なリファレンス(スキル、設定、拡張機能のオプション、実行時の置き換え)
- 拡張機能ガイド — イベントの全一覧、
ExtensionAPI/ExtensionContextのインターフェース、カスタム UI コンポーネント - パッケージガイド — パッケージ化、
pi installのソース、フィルタリング、スコープ/重複排除のルール - スキルガイド — エージェントが
SKILL.mdをどのように使用するか(このスキルリポジトリとは別) - 設定リファレンス —
~/.pi/agent/settings.jsonのスキーマ(コンパクション、リトライ、パッケージ、拡張機能) examples/sdk/— SDK のサンプル13件。node_modules/@mariozechner/pi-coding-agent/examples/sdk/にもインストールされていますexamples/extensions/— 拡張機能のクックブック:permission-gate.ts、protected-paths.ts、git-checkpoint.ts、カスタムプロバイダー、カスタム UI など- RPC モード — Node.js 以外のホストから pi を呼び出す際の JSON プロトコル