14 · 路线 C:SDK 驱动自己的代码研究助手¶
14.1 为什么这个场景适合 SDK¶
代码研究需要文件工具、资源、会话、压缩与恢复。SDK 已经实现这些边界,你可以把产品层放在它上面,避免重复开发。
本例功能:读取一个指定源码目录,查入口和调用链,流式输出中文证据,保存会话。它关闭自动 extension / skill / prompt / theme 与项目 context 注入,明确选择只读内建工具。
这是工具选择层的限制,不是 OS 沙箱;read 仍可读取账户允许的其他路径。如果给不可信用户使用,应把 worker 放隔离环境。
14.2 配置目录与工作目录分开¶
它不会自动借用个人 ~/.pi/agent/auth.json。Anthropic 的环境变量可用;若使用 models.json,应放入这个应用的 .agent。示例 .gitignore 不提交这些私密和历史目录。
这减少隐式依赖:你的应用和你每天使用的 Pi 配方可以不同。真正多用户时每个用户或服务账户需要自己的存储和权利边界。
14.3 资源加载的控制¶
const settingsManager = SettingsManager.inMemory({
defaultProvider: "anthropic", defaultModel: "claude-sonnet-4-6",
}, { projectTrusted: false });
const resourceLoader = new DefaultResourceLoader({
cwd, agentDir, settingsManager,
noExtensions: true,
noSkills: true,
noPromptTemplates: true,
noThemes: true,
noContextFiles: true,
systemPromptOverride: () => undefined,
agentsFilesOverride: () => ({ agentsFiles: [] }),
appendSystemPromptOverride: () => ["先读证据,给路径;不执行代码,不修改文件。"],
});
await resourceLoader.reload();
三个来源要分别处理:noContextFiles 关闭 AGENTS 等 context 文件,systemPromptOverride 覆盖发现到的 SYSTEM.md(返回 undefined 让 SDK 使用默认基础 prompt),appendSystemPromptOverride 只提供产品自己的追加规则。只清空 AGENTS 与 APPEND 仍可能导入项目 SYSTEM;SettingsManager.inMemory 本来默认项目受信,故本例显式 projectTrusted:false。
实际配置在 src/research-resources.ts,SDK 程序与隔离测试使用同一函数。测试真实创建带 AGENTS / SYSTEM / APPEND 与坏 extension 的目标目录,确认 loader 未导入,并断言发给 Faux Provider 的 messages 不含哨兵,产品研究指令仍存在。这个检查验证指令来源,不是文件系统沙箱。
noExtensions 关闭默认发现,但显式 additionalExtensionPaths 和 inline factories 仍可能加载;所以你的产品要控制整个 loader options 来源,不让用户任意传路径。
更彻底的做法是自己实现 ResourceLoader,所有 getters 都返回你管理的内容。官方 12-full-control.ts 提供配方。
14.4 创建和运行¶
const { session } = await createAgentSession({
cwd, agentDir, settingsManager, resourceLoader,
tools: ["read", "grep", "find", "ls"],
sessionManager: SessionManager.create(cwd, sessionDir),
});
工具名为 allowlist。当前 SDK createAgentSession 不是旧文档里的 tools: [readTool, bashTool]。
真实运行:
cd /home/debian/codex/learn/pi/examples
export ANTHROPIC_API_KEY='YOUR_API_KEY'
PI_LAB_CWD=../pi-1.0 npm run sdk -- "说明 Agent.prompt 到工具 execute 的调用链"
这是由你提供真实 key 后的实验入口;本次验收没有使用你的未知凭据。无 key 的行为是可预期前置错误,而不是装作得到真实回答。
14.5 恢复已有会话¶
把创建 manager 换成 SessionManager.open(file);或采用 SDK 官方 sessions / runtime 示例。调用前检查文件是否属于当前用户,不接受任意跨目录 file。
不要将前端传来的 messages 数组塞给 session.agent.state.messages 作为正式恢复。它不改变 authoritative persisted context,而且前端可能伪造 system 或 toolResult。
14.6 流式输出和清理¶
subscribe 在 prompt 前安装,text_delta 追加界面。完成后检查最终状态。定时器到期向 session.abort 发出取消请求,finally dispose;合作取消不保证忽略 signal 的工作立刻停止,硬 deadline 需外部进程监督。
订阅回调若启动异步写数据库,应自己管理写入完成;SDK public listener 不承诺 await 任意返回 Promise。用宿主任务持久化和正式 boundary 处理必须一致的业务提交。
14.7 给 SDK 加自己的工具¶
有两个入口:options.customTools,或 inline extension 的 registerTool。后者还可以注册 hooks / commands,通常适合完整工作流。
extensionFactories: [
(pi) => {
pi.registerTool({
name: "lookup_ticket",
label: "查询工单",
description: "按工单编号查询已授权项目的状态",
parameters: Type.Object({ ticket: Type.String() }),
async execute(_id, { ticket }, signal) {
signal?.throwIfAborted();
// 在这里调用你自己的、已认证的业务服务。
return { content: [{ type: "text", text: "这里必须替换为真实查询结果" }], details: { ticket } };
},
});
},
]
这是说明注册结构的模板,不是一个已经完成的真实工单集成。完整运行实验使用实际内存笔记工具;接真实业务时补齐客户端、异常、授权与测试。
14.8 新增自己的知识上下文¶
通过 loader 注入自己的 skills/context 或工具检索。关键选择:哪些常驻,哪些按需;哪些可信指令,哪些是不可信资料。
上下文窗口有限,因此不要每轮注入所有文档。skill description 负责「知道有这个能力」,工具检索负责「需要时获取证据」,持久业务状态负责「授权和进度不丢失」。
14.9 长驻服务的会话所有权¶
为每个 session 建 worker 或 queue。普通请求只在 idle 时 prompt;工作中必须显式 steer / followUp。用户取消应有已认证的所有权校验,不能凭知道 session ID 就 abort 别人的任务。
本例只是 CLI SDK 程序。第 15 章提供产品化设计;课程网站不执行这个后台流程。