首页 / pi-agent 入门教程 / SDK 编程:用代码驱动 pi

pi-agent 入门教程

SDK 编程:用代码驱动 pi

本教程共 30 篇 · 第 21 篇 · 更新于 2026-08-10 · 约 8 分钟阅读

pi-agentSDK编程APITypeScript

本节目标:学会用 pi 的 TypeScript SDK 在代码里创建 Agent、发送消息、接收流式输出。学完你能写出一个”最小可用的 pi Agent 应用”,并知道什么时候选 SDK、什么时候选 RPC 模式。

为什么需要 SDK

上一章讲了 RPC 模式——通过子进程和 JSON 协议远程控制 pi。它通用性好,什么语言都能用,但有个明显的代价:你需要自己管理子进程生命周期、自己解析 JSON 事件流、自己处理错误和重连。

如果你恰好用 Node.js / TypeScript 写应用,pi 还提供了更优雅的方式:直接用 SDK

SDK 就是 @earendil-works/pi-coding-agent 这个 npm 包里导出的编程接口。你 import 进来,几个函数调用就能创建一个 Agent 会话,发消息,收回复。所有事件都有 TypeScript 类型,IDE 自动补全,不需要手工拼 JSON。

Tip

SDK 和 RPC 不互斥。复杂场景可以混用——比如主进程用 SDK 直接创建 Agent,同时对外暴露一个 RPC 接口给其他语言的后端服务调用。

装 SDK

SDK 包含在 pi 的主包里,不需要额外安装:

npm install @earendil-works/pi-coding-agent

如果你的项目已经有 type: "module" 或者用 tsx/ts-node 跑 TypeScript,直接用 ESM import。如果还是 CommonJS 项目,需要用 require() 或者改成 ESM。

三行代码跑起第一个 Agent

最精简的例子:

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

const modelRuntime = await ModelRuntime.create();
const { session } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  modelRuntime,
});

await session.prompt("当前目录下有哪些文件?列出前 5 个。");

这三步在做什么:

  1. ModelRuntime.create() 初始化模型运行时,读取你的 API Key 配置和环境变量
  2. createAgentSession() 创建一个 Agent 会话,SessionManager.inMemory() 表示不持久化
  3. session.prompt() 发送消息并等待 Agent 处理完成

如果你之前已经在 pi 里配好了 API Key(通过 /login 或环境变量),ModelRuntime.create() 会自动读到。

接收流式输出

上面的例子调用 session.prompt() 后会阻塞等待,直到 Agent 完成全部工作才返回。这在简单的脚本里够用,但如果你想让用户实时看到 Agent 在说什么、在想什么,就得订阅事件流:

session.subscribe((event) => {
  if (event.type === "message_update") {
    const e = event.assistantMessageEvent;
    if (e.type === "text_delta") {
      process.stdout.write(e.delta);
    }
    if (e.type === "thinking_delta") {
      // 思考内容,如果你想展示的话
      process.stderr.write(`[思考] ${e.delta}`);
    }
  }
});

subscribe() 的回调会收到各种事件——跟 RPC 模式的事件流完全对应,只是你现在拿到的是类型化的 TypeScript 对象,不是裸 JSON。

常用事件一览:

事件类型含义典型用途
message_update流式输出增量实时显示 AI 回复文字
tool_execution_start工具开始执行显示”正在执行 bash…”
tool_execution_end工具执行完成显示执行结果
agent_start / agent_endAgent 生命周期进度指示
compaction_start / compaction_end上下文压缩提示用户”处理中…”

每个 message_updateassistantMessageEvent 可能是 text_delta(回复文字)、thinking_delta(思考过程)、toolcall_delta(工具调用参数)等。组装起来就是完整的 Agent 输出。

指定模型和工具

创建会话时可以指定模型和工具集合:

import { getModel } from "@earendil-works/pi-ai";
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";

const modelRuntime = await ModelRuntime.create();

// 按 provider + model id 查找模型
const model = getModel("anthropic", "claude-sonnet-4-20250514");
if (!model) throw new Error("模型没找到,检查 API Key 和网络");

const { session } = await createAgentSession({
  model,
  thinkingLevel: "medium",
  tools: ["read", "bash", "edit", "write", "grep"],
  sessionManager: SessionManager.inMemory(),
  modelRuntime,
});

tools 数组列出启用的内置工具。默认只有 readbasheditwrite 四个。想加 grepfindls 需要显式列出。设为 ["read", "grep", "find", "ls"] 就是只读模式——能看不能改。

处理 Agent 运行中的交互

Agent 没跑完的时候你可能想插话——比如纠正方向、追加新任务。SDK 提供了 steer()followUp()

// 等 Agent 开始处理后,插一条纠正消息
await session.prompt("帮我重构这个项目");
// ... 看了几秒觉得方向不对 ...
await session.steer("停,不要动 node_modules,只重构 src 目录下的文件");

// Agent 跑完后追加后续任务
await session.followUp("重构完成后跑一遍测试,有失败就修");

steer() 在当前轮工具执行完后插入——Agent 处理完当前的工具调用结果,在下一次调用 LLM 之前看到你的纠正。followUp() 等 Agent 完全空闲后才发。

session.prompt() 时也可以传 streamingBehavior 达到同样效果:

await session.prompt("换个方案", { streamingBehavior: "steer" });

完整的自定义 Agent 应用

下面是一个可以直接跑的完整示例。启动后读 stdin 上的每一行作为用户消息,把 Agent 的回复流式输出到 stdout:

// my-pi-app.ts
import { createInterface } from "node:readline";
import { getModel } from "@earendil-works/pi-ai";
import {
  createAgentSession,
  ModelRuntime,
  SessionManager,
} from "@earendil-works/pi-coding-agent";

async function main() {
  // 1. 初始化模型运行时
  const modelRuntime = await ModelRuntime.create();
  const model = getModel("anthropic", "claude-sonnet-4-20250514");

  if (!model) {
    console.error("模型未找到。请确认 API Key 已配置。");
    process.exit(1);
  }

  // 2. 创建 Agent 会话
  const { session } = await createAgentSession({
    model,
    thinkingLevel: "medium",
    tools: ["read", "bash", "edit", "write", "grep", "find", "ls"],
    sessionManager: SessionManager.inMemory(),
    modelRuntime,
  });

  // 3. 订阅事件 - 实时输出 Agent 的回复
  let agentStarted = false;
  session.subscribe((event) => {
    switch (event.type) {
      case "agent_start":
        agentStarted = true;
        break;

      case "message_update": {
        const e = event.assistantMessageEvent;
        if (e.type === "text_delta") {
          process.stdout.write(e.delta);
        }
        break;
      }

      case "tool_execution_start":
        console.log(`\n🔧 正在执行: ${event.toolName}`);
        break;

      case "tool_execution_end":
        if (event.isError) {
          console.log(`❌ 工具 ${event.toolName} 执行出错`);
        }
        break;

      case "agent_settled":
        agentStarted = false;
        console.log("\n--- Agent 空闲,可以输入下一条消息 ---");
        promptUser();
        break;

      case "compaction_start":
        console.log("\n🔄 正在压缩上下文...");
        break;

      case "compaction_end":
        console.log("✅ 压缩完成");
        break;
    }
  });

  // 4. 交互循环 - 读用户输入
  const rl = createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  function promptUser() {
    if (agentStarted) return; // Agent 还在忙,不催用户
    rl.question("\n> ", async (input) => {
      const trimmed = input.trim();
      if (trimmed === "exit" || trimmed === "quit") {
        console.log("再见!");
        rl.close();
        process.exit(0);
      }
      if (!trimmed) {
        promptUser();
        return;
      }

      try {
        await session.prompt(trimmed);
        // prompt() 在 agent_settled 后才 resolve
        // 但我们在事件里已经处理了 promptUser()
      } catch (err) {
        console.error("发送消息出错:", err);
        promptUser();
      }
    });
  }

  console.log("pi Agent 已就绪。输入消息开始对话,输入 exit 退出。");
  promptUser();
}

main().catch((err) => {
  console.error("启动失败:", err);
  process.exit(1);
});

把这个文件保存为 my-pi-app.ts,用 tsx 跑:

npx tsx my-pi-app.ts

你会看到一个终端对话界面——你在 > 后面输入消息,Agent 流式输出回复,工具执行时显示进度。输入 exit 退出。

控制更多细节

SDK 提供了丰富的配置项。下面几个是日常开发中最常用的。

自定义系统提示词

import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";

const loader = new DefaultResourceLoader({
  systemPromptOverride: () => "你是一个专注于代码安全的审查助手。",
});
await loader.reload();

const { session } = await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});

注册自定义工具

import { Type } from "typebox";
import { createAgentSession, defineTool } from "@earendil-works/pi-coding-agent";

const timeTool = defineTool({
  name: "current_time",
  label: "当前时间",
  description: "获取当前系统时间",
  parameters: Type.Object({}),
  execute: async () => ({
    content: [{ type: "text", text: new Date().toISOString() }],
    details: {},
  }),
});

const { session } = await createAgentSession({
  customTools: [timeTool],
  tools: ["read", "bash", "current_time"],
  sessionManager: SessionManager.inMemory(),
});

current_time 这个名字同时出现在 customTools 数组和 tools 数组里——customTools 注册工具定义,tools 决定它是否启用。

持久化会话

import { SessionManager } from "@earendil-works/pi-coding-agent";

// 新建持久化会话
const { session } = await createAgentSession({
  sessionManager: SessionManager.create(process.cwd()),
  modelRuntime,
});

console.log(`会话已保存到: ${session.sessionFile}`);

// 下次启动时继续
const { session: resumed } = await createAgentSession({
  sessionManager: SessionManager.continueRecent(process.cwd()),
  modelRuntime,
});

SessionManager.create() 在项目目录下创建会话文件。continueRecent() 自动找到最近的会话继续对话。

SDK vs RPC:什么时候用哪个

两章讲完,你可能会纠结选哪个。一个简单的判断标准:

场景选 SDK选 RPC
主程序是 Node.js/TypeScript✅ 类型安全,代码简洁也可以用,但没必要
主程序是 Python/Go/Rust 等❌ 没法直接 import✅ 唯一选择
需要进程隔离理论上可以但没意义✅ pi 在独立进程
需要直接访问 Agent 内部状态session.agent.state 随便读需要通过 get_state 命令
写 CI 脚本太重了,要装 npm 依赖✅ 一条 pi --mode rpc 搞定
构建自定义前端 UI✅ 直接嵌入 Node 后端✅ Electron 里启动子进程也行
需要类型安全和 IDE 补全✅ 全 TypeScript 类型❌ 只有 JSON
Tip

如果你想从 Python 或 Go 调 pi,但又想要好一点的开发体验,可以自己写一个 Node.js RPC 中间层——Node 进程用 SDK 创建 Agent,暴露一个 HTTP/WebSocket 接口给其他语言调。pi 官方没有内置 HTTP 模式,但社区有人这么干过。

一步到位:SDK 还能干什么

SDK 能做的事情远不止发消息。它暴露了 pi 的完整能力:

  • session.agent.state 直接读写 Agent 内部状态(消息列表、模型、工具集)
  • session.compact() 手动触发上下文压缩
  • session.setModel() / session.setThinkingLevel() 运行时切换模型和思考级别
  • session.navigateTree() 在会话树中跳转到历史节点
  • session.abort() 中止当前 Agent 操作

还用到了 SettingsManager 管理配置、createAgentSessionRuntime() 做会话替换(新建/切换/fork)——这些是构建完整 pi 替代客户端的基础构件。如果你对更深入的用法感兴趣,官方仓库的 examples/sdk/ 目录下有从最小到完整控制的渐进式示例。