@automatalabs/pi-acp

中文文档
0.9.4发布于 2026/09/25Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 8705

An in-process Agent Client Protocol server for the pi coding agent. It embeds pi through its published SDK and speaks ACP protocol version 1 over stdio. Standard output is reserved exclusively for ACP NDJSON.

原始使用文档
文档目录15 节
  1. Command line
  2. Side-effect-free library API
  3. Pi configuration and teardown
  4. AgentPrism built-in backend
  5. Thinking levels
  6. MCP and structured output
  7. Authentication
  8. Reserved tool namespaces
  9. Version 1 limitations
  10. Native session steering
  11. Loaded-session turn-terminal state ( _session/loaded_turn )
  12. Session system prompt ( _meta.systemPrompt )
  13. Development
  14. Built on pi — THIRD-PARTY notice
  15. Model discovery preferences

@automatalabs/pi-acp

An in-process Agent Client Protocol server for the pi coding agent. It embeds pi through its published SDK and speaks ACP protocol version 1 over stdio. Standard output is reserved exclusively for ACP NDJSON.

Command line

Run the server with npx @automatalabs/pi-acp or an installed pi-acp binary. pi-acp --version prints the package version without starting a server.

Side-effect-free library API

The package entry exports the value APIs runAcp(options?), PiAcpAgent, and resolveDeps, plus the TypeScript-only PiAcpDeps type. Importing it starts no server, opens no stdio connection, and does not mutate console or stdio. Call and await runAcp() explicitly to connect; pass a partial PiAcpDeps and/or an ACP stream for embedding and tests.

Pi configuration and teardown

Sessions load the operator's real pi configuration — settings, extensions, and pi's own MCP servers — from PiAcpDeps.agentDir, which defaults to pi's getAgentDir() ($PI_CODING_AGENT_DIR, else ~/.pi/agent). Override it to point pi at an isolated directory; embedders and tests that stub createAgentSession should, because the resource loader reads this path and starts the extensions it finds before the session factory is called.

Closing a session emits pi's session_shutdown event to those extensions before disposing the session, matching AgentSessionRuntime.dispose(). That event is pi's only extension-cleanup contract, so an extension that spawned a process releases it there. This matters more here than for out-of-process ACP backends: pi runs in-process, so anything it leaves behind is a live handle on the host's event loop rather than something the OS reaps with a child process tree.

AgentPrism built-in backend

@automatalabs/acp-agents ships pi-acp as the built-in pi backend. Route a call with the backend-only id to keep pi's configured default model, or include pi's exact provider/model-id after the routing prefix:

await runner.run("Review this change", { model: "pi" });
await runner.run("Review this change", { model: "pi/openrouter/vendor/model-id" });

The runner strips exactly the first pi/ segment and sends the remaining provider/model-id verbatim through ACP's reserved model config channel. Pi-acp advertises a configured model select populated from the completed credential- and provider-filter-aware Pi catalog; set requests refresh and require membership in that same catalog. An unknown model rejects with JSON-RPC -32602 and data.errorKind = "invalid_model".

Thinking levels

Pi-acp's thinkingLevel select is model-aware. Its visible choices are Pi's getSupportedThinkingLevels() result for the selected model, in Pi's order: a model capped at high does not advertise xhigh or max, and a non-reasoning model advertises only off. Before a model is selected, the option uses Pi's complete recognized domain as an unknown-model best-effort fallback. That domain is derived once from Pi's helper with a synthetic model that supports every level; pi-acp does not maintain a parallel hardcoded vocabulary.

The option carries the additive ACP metadata _meta["@automatalabs/agentprism"].recognizedValues. The visible choices remain the supported subset, while this metadata contains Pi's complete ordered domain so clients can distinguish an unsupported recognized value from garbage. A supported set request is applied unchanged. A recognized but unsupported request is clamped with Pi's clampThinkingLevel() and the response echoes the effective level. An unrecognized value fails loudly with JSON-RPC -32602 and data.errorKind = "invalid_config_value"; it is never handed to Pi's lowest-level fallback.

MCP and structured output

Pi-acp serves stdio, Streamable HTTP, and legacy SSE MCP servers and consumes stable tools, resources, prompts, completion, logging, pagination, progress, and dynamic tool registration. It also provides MCP client sampling, the workspace root, and form/URL elicitation. Client-hosted acp transport remains runner-owned. For agent({ schema }), Pi receives the runner's client-hosted HTTP StructuredOutput tool through this standard MCP path and retains the common prompt-embedded schema plus validated last-text fallback.

Authentication

Credentials are ambient. The server advertises exactly one method unconditionally:

idnamesource
pi-stored-credentialspi stored credentialspi's ~/.pi/agent/auth.json, or a provider API key in the server's environment (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY)

The per-provider API-key methods that used to sit alongside it were env_var-typed; ACP schema 1.21.0 (@agentclientprotocol/sdk 1.4.0) removed that variant from the protocol, so they are no longer advertised — the environment variables themselves are still honored. Authentication requests are no-op acknowledgements because the environment or pi's credential store supplies the secret. A selected or otherwise resolvable model whose credential is missing rejects with -32000 so clients can pause for authentication. Having no model at all is distinct: it rejects with -32602 / invalid_model.

Reserved tool namespaces

pi-acp owns the mcp__ prefix for injected MCP tools. Pi extensions must not register names in that namespace.

Version 1 limitations

Load replay is the active linear branch and excludes branch topology and compaction summaries. additionalDirectories is accepted but ignored because pi is not root-confined. ACP prompt audio is degraded to a text note. Terminal-login authentication is not exposed.

Native session steering

Pi advertises the _session/steering extension at top-level initialize metadata as _meta: { steering: { supported: true } }, with strict active-turn-only semantics. While a Pi prompt turn is live, the server converts the supplied ACP content with the same text/image conversion as session/prompt and calls native AgentSession.steer(text, images), returning { outcome: "injected" }; the original session/prompt still owns all output, usage, and settlement. When the session is idle — or a steer arrives just as the turn settles, before pi could consume it — the server removes any unconsumed native queue entry and returns { outcome: "promptRequired", reason: "noRunningTurn" }. It never calls prompt(), followUp(), or another turn-start helper from steering, so steering cannot create hidden work or prepend input to a later prompt. Cancellation races use the same promptRequired/noRunningTurn response when no active turn accepted the instruction. Unexpected internal failures reject the JSON-RPC request through the normal structured error path.

Loaded-session turn-terminal state (_session/loaded_turn)

Pi also advertises the _session/loaded_turn extension at top-level initialize metadata as _meta: { loadedTurn: { supported: true } } — the re-attach arm's AUTHORITATIVE completion evidence for a session re-opened with session/load (a restarted host's restore path). _session/loaded_turn/query { sessionId } answers whether the loaded session's founding turn is still running right now: running while a turn executes in this process (the client then waits for the _session/loaded_turn/ended push — sent with the turn's stop reason, or its error, when the turn finishes, arming and clearing a one-shot watch), completed when the session journal's last message entry is an assistant message (pi persists every complete LLM message atomically at message_end, so a completed turn always leaves an assistant leaf and the replay's trailing assistant message is the turn's FINAL message — authoritative, never a quiet-gap guess), and interrupted otherwise (the journal shows an interrupted/abandoned turn — nothing is running, so re-issue is safe). A query for an unknown session is the standard unknown_session error.

Session system prompt (_meta.systemPrompt)

Pi advertises a session system-prompt channel at top-level initialize metadata as _meta: { systemPrompt: { replace: true, append: true } }. A client sets _meta.systemPrompt on session/new, session/resume, session/load, or session/fork — the same bare key claude-agent-acp reads, so a client can keep one wire shape across the two servers — as either a string or an object:

  • a string, or { replace }, replaces pi's built-in system prompt: it takes pi's custom-prompt slot (the SYSTEM.md / --system-prompt slot) through the resource loader's systemPromptOverride, exactly as for an operator-configured prompt. With a custom prompt pi's buildSystemPrompt drops its default instruction text — the "Available tools" list with its snippets, its guidelines, and the pi docs/examples paths — and still appends, in this order, the append-system-prompt entries, the project context files (AGENTS.md and friends inside <project_context>), the skills block (when a read or bash tool is available), and the Current working directory line;
  • { append } is added as one more append-system-prompt entry after the operator's own configured entries (appendSystemPromptOverride), so it never displaces them;
  • { replace, append } does both.

The instructions are per session: they shape only the session the request opens and are read again on every reattach, so a session/resume without the key runs on pi's own configuration. A malformed value — a non-string/non-object, a blank string, a non-string field, or any field other than replace / append — is rejected with JSON-RPC -32602 and data.errorKind = "invalid_system_prompt" (data.field names the offender) before any journal or session state exists; an unusable instruction never runs under the default prompt. The @automatalabs/acp-agents pi backend drives this channel from the backend-neutral systemPrompt: { replace?, append? } option.

Development

The pnpm test script intentionally runs tsc -p tsconfig.type-tests.json before the runtime test suite. This is a small deviation from the test-script example in the frozen specification and ensures the T2b public type-contract check is enforced in local and CI test runs.

Run node scripts/smoke-pi-package.mjs from the repository root after changing Pi dependencies. It packs this adapter and checks its public SDK import in fresh npm and pnpm installations. Pi 0.87.0 keeps its experimental remote harness out of the published local SDK, so this adapter no longer needs to supply @earendil-works/pi-server.

Built on pi — THIRD-PARTY notice

This package depends on and embeds @earendil-works/pi-coding-agent, @earendil-works/pi-agent-core, and @earendil-works/pi-ai version 0.87.1. pi is Copyright Earendil Inc., Mario Zechner, and Armin Ronacher and is distributed under the MIT License. The dependency packages retain the full MIT copyright and license text. pi-acp itself is Apache-2.0.

Model discovery preferences

The ACP model catalog continues to expose every authenticated available model. Native merged Pi enabledModels settings add presentation metadata to the model config option at _meta["@automatalabs/agentprism.modelDiscovery"]: { source:"enabledModels", preferred:string[], unmatched:string[] }. Native pattern order and matching semantics are preserved, then preferences intersect the authenticated catalog. Unmatched patterns remain visible. Preferences do not restrict execution; without a configured preference list, the MCP summary shows available provider groups. The current model is distinct from preferred models, and thinkingLevel options must be discovered for the exact selected model.

版本与发布记录

@automatalabs/pi-acp 共发布 30 个版本,首次发布于 2026/07/17;过去 12 个月发布了 30 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.9.4latest2026/09/251 个月前
0.9.32026/09/221 个月前
0.9.22026/09/191 个月前
0.9.12026/09/171 个月前
0.9.02026/09/171 个月前
0.8.02026/09/091 个月前

关于 @automatalabs/pi-acp 的常见问题

@automatalabs/pi-acp 是什么?

An in-process Agent Client Protocol server for the pi coding agent. It embeds pi through its published SDK and speaks ACP protocol version 1 over stdio. Standard output is reserved exclusively for ACP NDJSON.

如何安装 @automatalabs/pi-acp?

在项目目录运行 npm install @automatalabs/pi-acp,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 pi-acp,可以用 npx pi-acp 直接运行。

@automatalabs/pi-acp 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@automatalabs/pi-acp 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@automatalabs/pi-acp 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.19.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@automatalabs/pi-acp 可以商用吗?

@automatalabs/pi-acp 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@automatalabs/pi-acp 的下载量有多少?

根据 npm 官方下载统计,@automatalabs/pi-acp 在 2026/10/07 – 2026/11/05 这 30 天内被下载 8,705 次,日均约 290 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@automatalabs/pi-acp 还在维护吗?

最新版本 0.9.4 发布于 2026/09/25(1 个月前);至今共发布 30 个版本,其中过去 12 个月发布了 30 个;首次发布于 2026/07/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。