644 lines
24 KiB
Markdown
644 lines
24 KiB
Markdown
# Claude Agent SDK 深度剖析
|
||
|
||
对 `@anthropic-ai/claude-agent-sdk` v0.2.29–0.2.34 的反向工程发现,以理解 `query()` 如何工作、为什么 agent teams 子代理(subagents)被终止,以及如何修复。补充了官方 SDK 参考文档。
|
||
|
||
## 架构(Architecture)
|
||
|
||
```
|
||
Agent Runner (我们的代码)
|
||
└── query() → SDK (sdk.mjs)
|
||
└── 生成 CLI 子进程 (cli.js)
|
||
└── Claude API 调用、工具执行
|
||
└── Task 工具 → 生成子代理子进程
|
||
```
|
||
|
||
SDK 以子进程形式生成 `cli.js`,带有 `--output-format stream-json --input-format stream-json --print --verbose` 标志。通信通过 stdin/stdout 上的 JSON 行进行。
|
||
|
||
`query()` 返回一个扩展了 `AsyncGenerator<SDKMessage, void>` 的 `Query` 对象。内部机制:
|
||
|
||
- SDK 以子进程形式生成 CLI,通过 stdin/stdout JSON 行通信
|
||
- SDK 的 `readMessages()` 从 CLI stdout 读取,排入内部流
|
||
- `readSdkMessages()` 异步生成器从该流中产出
|
||
- `[Symbol.asyncIterator]` 返回 `readSdkMessages()`
|
||
- 迭代器仅在 CLI 关闭 stdout 时返回 `done: true`
|
||
|
||
V1 (`query()`) 和 V2 (`createSession`/`send`/`stream`) 都使用完全相同的三层架构:
|
||
|
||
```
|
||
SDK (sdk.mjs) CLI 进程 (cli.js)
|
||
-------------- --------------------
|
||
XX Transport ------> stdin 读取器 (bd1)
|
||
(生成 cli.js) |
|
||
$X Query <------ stdout 写入器
|
||
(JSON 行) |
|
||
EZ() 递归生成器
|
||
|
|
||
Anthropic Messages API
|
||
```
|
||
|
||
## 核心代理循环 (EZ)
|
||
|
||
在 CLI 内部,代理循环是一个名为 `EZ()` 的**递归异步生成器**,而非迭代 while 循环:
|
||
|
||
```
|
||
EZ({ messages, systemPrompt, canUseTool, maxTurns, turnCount=1, ... })
|
||
```
|
||
|
||
每次调用 = 一次对 Claude 的 API 调用(一个"轮次(turn)")。
|
||
|
||
### 每轮流程:
|
||
|
||
1. **准备消息**——裁剪上下文,必要时运行压缩(compaction)
|
||
2. **调用 Anthropic API**(通过 `mW1` 流式函数)
|
||
3. **从响应中提取 tool_use 块**
|
||
4. **分支:**
|
||
- 如果**没有 tool_use 块**→ 停止(运行停止钩子,返回)
|
||
- 如果**存在 tool_use 块** → 执行工具,递增 turnCount,递归
|
||
|
||
所有复杂逻辑——代理循环、工具执行、后台任务(background tasks)、队友(teammate)编排——都在 CLI 子进程内运行。`query()` 只是一个薄传输包装器。
|
||
|
||
## query() 选项
|
||
|
||
官方文档中的完整 `Options` 类型:
|
||
|
||
| 属性 | 类型 | 默认值 | 描述 |
|
||
|----------|------|---------|-------------|
|
||
| `abortController` | `AbortController` | `new AbortController()` | 用于取消操作的控制器 |
|
||
| `additionalDirectories` | `string[]` | `[]` | Claude 可访问的附加目录 |
|
||
| `agents` | `Record<string, AgentDefinition>` | `undefined` | 以编程方式定义子代理(不是 agent teams——无编排功能) |
|
||
| `allowDangerouslySkipPermissions` | `boolean` | `false` | 使用 `permissionMode: 'bypassPermissions'` 时必需 |
|
||
| `allowedTools` | `string[]` | 所有工具 | 允许的工具名称列表 |
|
||
| `betas` | `SdkBeta[]` | `[]` | Beta 功能(例如 `['context-1m-2025-08-07']` 用于 1M 上下文) |
|
||
| `canUseTool` | `CanUseTool` | `undefined` | 自定义工具使用权限函数 |
|
||
| `continue` | `boolean` | `false` | 继续最近的对话 |
|
||
| `cwd` | `string` | `process.cwd()` | 当前工作目录 |
|
||
| `disallowedTools` | `string[]` | `[]` | 禁用的工具名称列表 |
|
||
| `enableFileCheckpointing` | `boolean` | `false` | 启用文件更改追踪以支持回退 |
|
||
| `env` | `Dict<string>` | `process.env` | 环境变量 |
|
||
| `executable` | `'bun' \| 'deno' \| 'node'` | 自动检测 | JavaScript 运行时 |
|
||
| `fallbackModel` | `string` | `undefined` | 主模型失败时使用的模型 |
|
||
| `forkSession` | `boolean` | `false` | 恢复时,分叉到新的 session ID 而非继续原始会话 |
|
||
| `hooks` | `Partial<Record<HookEvent, HookCallbackMatcher[]>>` | `{}` | 事件钩子回调 |
|
||
| `includePartialMessages` | `boolean` | `false` | 包含部分消息事件(流式) |
|
||
| `maxBudgetUsd` | `number` | `undefined` | 查询的最大美元预算 |
|
||
| `maxThinkingTokens` | `number` | `undefined` | 思考过程的最大 token 数 |
|
||
| `maxTurns` | `number` | `undefined` | 最大对话轮次 |
|
||
| `mcpServers` | `Record<string, McpServerConfig>` | `{}` | MCP 服务器配置 |
|
||
| `model` | `string` | CLI 默认值 | 使用的 Claude 模型 |
|
||
| `outputFormat` | `{ type: 'json_schema', schema: JSONSchema }` | `undefined` | 结构化输出格式 |
|
||
| `pathToClaudeCodeExecutable` | `string` | 使用内置版本 | Claude Code 可执行文件路径 |
|
||
| `permissionMode` | `PermissionMode` | `'default'` | 权限模式 |
|
||
| `plugins` | `SdkPluginConfig[]` | `[]` | 从本地路径加载自定义插件 |
|
||
| `resume` | `string` | `undefined` | 要恢复的 session ID |
|
||
| `resumeSessionAt` | `string` | `undefined` | 在特定消息 UUID 处恢复会话 |
|
||
| `sandbox` | `SandboxSettings` | `undefined` | 沙箱行为配置 |
|
||
| `settingSources` | `SettingSource[]` | `[]`(无) | 要加载的文件系统设置。必须包含 `'project'` 才能加载 CLAUDE.md |
|
||
| `stderr` | `(data: string) => void` | `undefined` | stderr 输出回调 |
|
||
| `systemPrompt` | `string \| { type: 'preset'; preset: 'claude_code'; append?: string }` | `undefined` | 系统提示词。使用预设可获取 Claude Code 的提示词,可选 `append` |
|
||
| `tools` | `string[] \| { type: 'preset'; preset: 'claude_code' }` | `undefined` | 工具配置 |
|
||
|
||
### PermissionMode
|
||
|
||
```typescript
|
||
type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
|
||
```
|
||
|
||
### SettingSource
|
||
|
||
```typescript
|
||
type SettingSource = 'user' | 'project' | 'local';
|
||
// 'user' → ~/.claude/settings.json
|
||
// 'project' → .claude/settings.json(版本控制的)
|
||
// 'local' → .claude/settings.local.json(gitignored)
|
||
```
|
||
|
||
省略时,SDK 不加载任何文件系统设置(默认隔离)。优先级:local > project > user。编程方式提供的选项始终覆盖文件系统设置。
|
||
|
||
### AgentDefinition
|
||
|
||
以编程方式定义的子代理(不是 agent teams——这些更简单,无代理间协调):
|
||
|
||
```typescript
|
||
type AgentDefinition = {
|
||
description: string; // 何时使用此代理
|
||
tools?: string[]; // 允许的工具(省略则继承全部)
|
||
prompt: string; // 代理的系统提示词
|
||
model?: 'sonnet' | 'opus' | 'haiku' | 'inherit';
|
||
}
|
||
```
|
||
|
||
### McpServerConfig
|
||
|
||
```typescript
|
||
type McpServerConfig =
|
||
| { type?: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
||
| { type: 'sse'; url: string; headers?: Record<string, string> }
|
||
| { type: 'http'; url: string; headers?: Record<string, string> }
|
||
| { type: 'sdk'; name: string; instance: McpServer } // 进程内
|
||
```
|
||
|
||
### SdkBeta
|
||
|
||
```typescript
|
||
type SdkBeta = 'context-1m-2025-08-07';
|
||
// 为 Opus 4.6、Sonnet 4.5、Sonnet 4 启用 1M token 上下文窗口
|
||
```
|
||
|
||
### CanUseTool
|
||
|
||
```typescript
|
||
type CanUseTool = (
|
||
toolName: string,
|
||
input: ToolInput,
|
||
options: { signal: AbortSignal; suggestions?: PermissionUpdate[] }
|
||
) => Promise<PermissionResult>;
|
||
|
||
type PermissionResult =
|
||
| { behavior: 'allow'; updatedInput: ToolInput; updatedPermissions?: PermissionUpdate[] }
|
||
| { behavior: 'deny'; message: string; interrupt?: boolean };
|
||
```
|
||
|
||
## SDKMessage 类型
|
||
|
||
`query()` 可以产出 16 种消息类型。官方文档展示了一个简化的 7 种联合类型,但 `sdk.d.ts` 包含完整集合:
|
||
|
||
| 类型 | 子类型 | 用途 |
|
||
|------|---------|---------|
|
||
| `system` | `init` | 会话已初始化,包含 session_id、tools、model |
|
||
| `system` | `task_notification` | 后台代理已完成/失败/停止 |
|
||
| `system` | `compact_boundary` | 对话已压缩 |
|
||
| `system` | `status` | 状态变更(例如正在压缩) |
|
||
| `system` | `hook_started` | 钩子执行已开始 |
|
||
| `system` | `hook_progress` | 钩子进度输出 |
|
||
| `system` | `hook_response` | 钩子已完成 |
|
||
| `system` | `files_persisted` | 文件已保存 |
|
||
| `assistant` | — | Claude 的响应(文本 + 工具调用) |
|
||
| `user` | — | 用户消息(内部) |
|
||
| `user` (replay) | — | 恢复时重放的用户消息 |
|
||
| `result` | `success` / `error_*` | 提示词处理轮的最终结果 |
|
||
| `stream_event` | — | 部分流式内容(当 includePartialMessages 时) |
|
||
| `tool_progress` | — | 长时间运行工具进度 |
|
||
| `auth_status` | — | 认证状态变化 |
|
||
| `tool_use_summary` | — | 前置工具使用的摘要 |
|
||
|
||
### SDKTaskNotificationMessage (sdk.d.ts:1507)
|
||
|
||
```typescript
|
||
type SDKTaskNotificationMessage = {
|
||
type: 'system';
|
||
subtype: 'task_notification';
|
||
task_id: string;
|
||
status: 'completed' | 'failed' | 'stopped';
|
||
output_file: string;
|
||
summary: string;
|
||
uuid: UUID;
|
||
session_id: string;
|
||
};
|
||
```
|
||
|
||
### SDKResultMessage (sdk.d.ts:1375)
|
||
|
||
两种变体,共享字段:
|
||
|
||
```typescript
|
||
// 两种变体共享的字段:
|
||
// uuid, session_id, duration_ms, duration_api_ms, is_error, num_turns,
|
||
// total_cost_usd, usage: NonNullableUsage, modelUsage, permission_denials
|
||
|
||
// 成功:
|
||
type SDKResultSuccess = {
|
||
type: 'result';
|
||
subtype: 'success';
|
||
result: string;
|
||
structured_output?: unknown;
|
||
// ...共享字段
|
||
};
|
||
|
||
// 错误:
|
||
type SDKResultError = {
|
||
type: 'result';
|
||
subtype: 'error_during_execution' | 'error_max_turns' | 'error_max_budget_usd' | 'error_max_structured_output_retries';
|
||
errors: string[];
|
||
// ...共享字段
|
||
};
|
||
```
|
||
|
||
result 上有用的字段:`total_cost_usd`、`duration_ms`、`num_turns`、`modelUsage`(按模型细分的 `costUSD`、`inputTokens`、`outputTokens`、`contextWindow`)。
|
||
|
||
### SDKAssistantMessage
|
||
|
||
```typescript
|
||
type SDKAssistantMessage = {
|
||
type: 'assistant';
|
||
uuid: UUID;
|
||
session_id: string;
|
||
message: APIAssistantMessage; // 来自 Anthropic SDK
|
||
parent_tool_use_id: string | null; // 非空时来自子代理
|
||
};
|
||
```
|
||
|
||
### SDKSystemMessage (init)
|
||
|
||
```typescript
|
||
type SDKSystemMessage = {
|
||
type: 'system';
|
||
subtype: 'init';
|
||
uuid: UUID;
|
||
session_id: string;
|
||
apiKeySource: ApiKeySource;
|
||
cwd: string;
|
||
tools: string[];
|
||
mcp_servers: { name: string; status: string }[];
|
||
model: string;
|
||
permissionMode: PermissionMode;
|
||
slash_commands: string[];
|
||
output_style: string;
|
||
};
|
||
```
|
||
|
||
## 轮次行为:代理何时停止与继续
|
||
|
||
### 代理停止时(不再进行 API 调用)
|
||
|
||
**1. 响应中没有 tool_use 块(主要情况)**
|
||
|
||
Claude 仅以文本响应——它判定已完成任务。API 的 `stop_reason` 将为 `"end_turn"`。SDK 不做此决定——完全由 Claude 的模型输出驱动。
|
||
|
||
**2. 超过最大轮次**——导致 `SDKResultError`,`subtype: "error_max_turns"`。
|
||
|
||
**3. 中止信号**——通过 `abortController` 用户中断。
|
||
|
||
**4. 超出预算**——`totalCost >= maxBudgetUsd` → `"error_max_budget_usd"`。
|
||
|
||
**5. 停止钩子阻止继续**——钩子返回 `{preventContinuation: true}`。
|
||
|
||
### 代理继续时(进行另一次 API 调用)
|
||
|
||
**1. 响应包含 tool_use 块(主要情况)**——执行工具,递增 turnCount,递归进入 EZ。
|
||
|
||
**2. max_output_tokens 恢复**——最多 3 次重试,附带"将工作拆分为更小片段"的上下文消息。
|
||
|
||
**3. 停止钩子阻塞错误**——错误作为上下文消息反馈,循环继续。
|
||
|
||
**4. 模型回退**——使用回退模型重试(一次性)。
|
||
|
||
### 决策表
|
||
|
||
| 条件 | 动作 | 结果类型 |
|
||
|-----------|--------|-------------|
|
||
| 响应有 `tool_use` 块 | 执行工具,递归进入 `EZ` | 继续 |
|
||
| 响应没有 `tool_use` 块 | 运行停止钩子,返回 | `success` |
|
||
| `turnCount > maxTurns` | 产出 max_turns_reached | `error_max_turns` |
|
||
| `totalCost >= maxBudgetUsd` | 产出预算错误 | `error_max_budget_usd` |
|
||
| `abortController.signal.aborted` | 产出中断消息 | 取决于上下文 |
|
||
| `stop_reason === "max_tokens"`(输出) | 使用恢复提示词最多重试 3 次 | 继续 |
|
||
| 停止钩子 `preventContinuation` | 立即返回 | `success` |
|
||
| 停止钩子阻塞错误 | 反馈错误,递归 | 继续 |
|
||
| 模型回退错误 | 使用回退模型重试(一次性) | 继续 |
|
||
|
||
## 子代理执行模式
|
||
|
||
### 情况 1:同步子代理 (`run_in_background: false`)——阻塞
|
||
|
||
父代理调用 Task 工具 → `VR()` 为子代理运行 `EZ()` → 父代理等待完整结果 → 工具结果返回给父代理 → 父代理继续。
|
||
|
||
子代理运行完整的递归 EZ 循环。父代理的工具执行通过 `await` 挂起。存在一个执行中"提升"机制:同步子代理可以通过 `Promise.race()` 与 `backgroundSignal` promise 竞争被提升为后台。
|
||
|
||
### 情况 2:后台任务 (`run_in_background: true`)——不等待
|
||
|
||
- **Bash 工具:**命令启动,工具立即返回空结果 + `backgroundTaskId`
|
||
- **Task/Agent 工具:**子代理在即发即忘(fire-and-forget)包装器 (`g01()`) 中启动,工具立即返回 `status: "async_launched"` + `outputFile` 路径
|
||
|
||
在发出 `type: "result"` 消息之前,零"等待后台任务"逻辑。当后台任务完成时,单独发出 `SDKTaskNotificationMessage`。
|
||
|
||
### 情况 3:Agent Teams (TeammateTool / SendMessage)——先结果,后轮询
|
||
|
||
团队领导者运行其正常的 EZ 循环,其中包括生成队友。当领导者的 EZ 循环完成时,发出 `type: "result"`。然后领导者进入结果后轮询循环:
|
||
|
||
```javascript
|
||
while (true) {
|
||
// 检查是否没有活跃队友且没有正在运行的任务 → 跳出
|
||
// 检查来自队友的未读消息 → 作为新提示词重新注入,重启 EZ 循环
|
||
// 如果 stdin 关闭且有活跃队友 → 注入关闭提示词
|
||
// 每 500ms 轮询
|
||
}
|
||
```
|
||
|
||
从 SDK 消费者角度看:你收到初始的 `type: "result"`,但 AsyncGenerator 可能继续产出更多消息,因为团队领导者处理队友响应并重新进入代理循环。生成器只有在所有队友都已关闭时才真正完成。
|
||
|
||
## isSingleUserTurn 问题
|
||
|
||
来自 sdk.mjs:
|
||
|
||
```javascript
|
||
QK = typeof X === "string" // isSingleUserTurn = true,当 prompt 为字符串时
|
||
```
|
||
|
||
当 `isSingleUserTurn` 为 true 且第一个 `result` 消息到达时:
|
||
|
||
```javascript
|
||
if (this.isSingleUserTurn) {
|
||
this.transport.endInput(); // 向 CLI 关闭 stdin
|
||
}
|
||
```
|
||
|
||
这触发连锁反应:
|
||
|
||
1. SDK 关闭 CLI stdin
|
||
2. CLI 检测到 stdin 关闭
|
||
3. 轮询循环看到 `D = true`(stdin 关闭)且有活跃队友
|
||
4. 注入关闭提示词 → 领导者向所有队友发送 `shutdown_request`
|
||
5. **队友在搜索中途被杀死**
|
||
|
||
关闭提示词(通过在压缩的 cli.js 中的 `BGq` 变量找到):
|
||
|
||
```
|
||
You are running in non-interactive mode and cannot return a response
|
||
to the user until your team is shut down.
|
||
|
||
You MUST shut down your team before preparing your final response:
|
||
1. Use requestShutdown to ask each team member to shut down gracefully
|
||
2. Wait for shutdown approvals
|
||
3. Use the cleanup operation to clean up the team
|
||
4. Only then provide your final response to the user
|
||
```
|
||
|
||
### 实际问题
|
||
|
||
使用 V1 `query()` + 字符串 prompt + agent teams:
|
||
|
||
1. 领导者生成队友,他们开始搜索
|
||
2. 领导者的 EZ 循环结束("我已经派出了团队,他们正在处理此事")
|
||
3. 发出 `type: "result"`
|
||
4. SDK 看到 `isSingleUserTurn = true` → 立即关闭 stdin
|
||
5. 轮询循环检测到 stdin 关闭 + 活跃队友 → 注入关闭提示词
|
||
6. 领导者向所有队友发送 `shutdown_request`
|
||
7. **队友可能刚开始了 10 秒的 5 分钟搜索任务,就被要求停止**
|
||
|
||
## 修复方案:流式输入模式
|
||
|
||
不传递字符串 prompt(这会设置 `isSingleUserTurn = true`),而是传递一个 `AsyncIterable<SDKUserMessage>`:
|
||
|
||
```typescript
|
||
// 之前(对 agent teams 有问题的写法):
|
||
query({ prompt: "do something" })
|
||
|
||
// 之后(保持 CLI 存活):
|
||
query({ prompt: asyncIterableOfMessages })
|
||
```
|
||
|
||
当 prompt 是 `AsyncIterable` 时:
|
||
- `isSingleUserTurn = false`
|
||
- SDK 在第一个 result 后**不会**关闭 stdin
|
||
- CLI 保持活跃,继续处理
|
||
- 后台代理保持运行
|
||
- `task_notification` 消息流经迭代器
|
||
- 我们控制何时结束可迭代对象
|
||
|
||
### 额外好处:流式传入新消息
|
||
|
||
使用异步可迭代对象方案,我们可以在代理仍在工作时将新的 WhatsApp 消息推入可迭代对象。不用将消息排队直到容器退出再生成新容器,我们直接将它们流入正在运行的会话。
|
||
|
||
### 使用 Agent Teams 的预期生命周期
|
||
|
||
使用异步可迭代对象修复 (`isSingleUserTurn = false`),stdin 保持打开,因此 CLI 永远不会触发队友检查或关闭提示词注入:
|
||
|
||
```
|
||
1. system/init → 会话已初始化
|
||
2. assistant/user → Claude 推理、工具调用、工具结果
|
||
3. ... → 更多 assistant/user 轮次(生成子代理等)
|
||
4. result #1 → 领导代理的第一次响应(捕获)
|
||
5. task_notification(s) → 后台代理完成/失败/停止
|
||
6. assistant/user → 领导代理继续(处理子代理结果)
|
||
7. result #2 → 领导代理的后续响应(捕获)
|
||
8. [iterator done] → CLI 关闭 stdout,全部完成
|
||
```
|
||
|
||
所有 result 都有意义——捕获每一个,而不仅仅是第一个。
|
||
|
||
## V1 与 V2 API
|
||
|
||
### V1:`query()`——一次性异步生成器
|
||
|
||
```typescript
|
||
const q = query({ prompt: "...", options: {...} });
|
||
for await (const msg of q) { /* 处理事件 */ }
|
||
```
|
||
|
||
- 当 `prompt` 是字符串时:`isSingleUserTurn = true` → stdin 在第一个 result 后自动关闭
|
||
- 对于多轮次:必须传递 `AsyncIterable<SDKUserMessage>` 并自行管理协调
|
||
|
||
### V2:`createSession()` + `send()` / `stream()`——持久化会话
|
||
|
||
```typescript
|
||
await using session = unstable_v2_createSession({ model: "..." });
|
||
await session.send("first message");
|
||
for await (const msg of session.stream()) { /* 事件 */ }
|
||
await session.send("follow-up");
|
||
for await (const msg of session.stream()) { /* 事件 */ }
|
||
```
|
||
|
||
- `isSingleUserTurn = false` 始终 → stdin 保持打开
|
||
- `send()` 排入异步队列 (`QX`)
|
||
- `stream()` 从同一个消息生成器产出,遇到 `result` 类型时停止
|
||
- 多轮次很自然——只需交替 `send()` / `stream()`
|
||
- V2 在内部**不**调用 V1 `query()`——两者独立创建 Transport + Query
|
||
|
||
### 对比表
|
||
|
||
| 方面 | V1 | V2 |
|
||
|--------|----|----|
|
||
| `isSingleUserTurn` | 字符串 prompt 时为 `true` | 始终 `false` |
|
||
| 多轮次 | 需要管理 `AsyncIterable` | 只需调用 `send()`/`stream()` |
|
||
| stdin 生命周期 | 第一个 result 后自动关闭 | 保持打开直到 `close()` |
|
||
| 代理循环 | 相同的 `EZ()` | 相同的 `EZ()` |
|
||
| 停止条件 | 相同 | 相同 |
|
||
| 会话持久化 | 必须向新的 `query()` 传递 `resume` | 通过 session 对象内置 |
|
||
| API 稳定性 | 稳定 | 不稳定预览(`unstable_v2_*` 前缀) |
|
||
|
||
**关键发现:轮次行为零差异。**两者使用相同的 CLI 进程、相同的 `EZ()` 递归生成器和相同的决策逻辑。
|
||
|
||
## 钩子事件
|
||
|
||
```typescript
|
||
type HookEvent =
|
||
| 'PreToolUse' // 工具执行前
|
||
| 'PostToolUse' // 工具成功执行后
|
||
| 'PostToolUseFailure' // 工具执行失败后
|
||
| 'Notification' // 通知消息
|
||
| 'UserPromptSubmit' // 用户提示词已提交
|
||
| 'SessionStart' // 会话已启动(启动/恢复/清除/压缩)
|
||
| 'SessionEnd' // 会话已结束
|
||
| 'Stop' // 代理正在停止
|
||
| 'SubagentStart' // 子代理已生成
|
||
| 'SubagentStop' // 子代理已停止
|
||
| 'PreCompact' // 对话压缩前
|
||
| 'PermissionRequest'; // 正在请求权限
|
||
```
|
||
|
||
### 钩子配置
|
||
|
||
```typescript
|
||
interface HookCallbackMatcher {
|
||
matcher?: string; // 可选的工具名称匹配器
|
||
hooks: HookCallback[];
|
||
}
|
||
|
||
type HookCallback = (
|
||
input: HookInput,
|
||
toolUseID: string | undefined,
|
||
options: { signal: AbortSignal }
|
||
) => Promise<HookJSONOutput>;
|
||
```
|
||
|
||
### 钩子返回值
|
||
|
||
```typescript
|
||
type HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput;
|
||
|
||
type AsyncHookJSONOutput = { async: true; asyncTimeout?: number };
|
||
|
||
type SyncHookJSONOutput = {
|
||
continue?: boolean;
|
||
suppressOutput?: boolean;
|
||
stopReason?: string;
|
||
decision?: 'approve' | 'block';
|
||
systemMessage?: string;
|
||
reason?: string;
|
||
hookSpecificOutput?:
|
||
| { hookEventName: 'PreToolUse'; permissionDecision?: 'allow' | 'deny' | 'ask'; updatedInput?: Record<string, unknown> }
|
||
| { hookEventName: 'UserPromptSubmit'; additionalContext?: string }
|
||
| { hookEventName: 'SessionStart'; additionalContext?: string }
|
||
| { hookEventName: 'PostToolUse'; additionalContext?: string };
|
||
};
|
||
```
|
||
|
||
### 子代理钩子(来自 sdk.d.ts)
|
||
|
||
```typescript
|
||
type SubagentStartHookInput = BaseHookInput & {
|
||
hook_event_name: 'SubagentStart';
|
||
agent_id: string;
|
||
agent_type: string;
|
||
};
|
||
|
||
type SubagentStopHookInput = BaseHookInput & {
|
||
hook_event_name: 'SubagentStop';
|
||
stop_hook_active: boolean;
|
||
agent_id: string;
|
||
agent_transcript_path: string;
|
||
agent_type: string;
|
||
};
|
||
|
||
// BaseHookInput = { session_id, transcript_path, cwd, permission_mode? }
|
||
```
|
||
|
||
## Query 接口方法
|
||
|
||
`Query` 对象 (sdk.d.ts:931)。官方文档列出以下公开方法:
|
||
|
||
```typescript
|
||
interface Query extends AsyncGenerator<SDKMessage, void> {
|
||
interrupt(): Promise<void>; // 停止当前执行(仅流式输入模式)
|
||
rewindFiles(userMessageUuid: string): Promise<void>; // 将文件恢复到消息时的状态(需要 enableFileCheckpointing)
|
||
setPermissionMode(mode: PermissionMode): Promise<void>; // 更改权限(仅流式输入模式)
|
||
setModel(model?: string): Promise<void>; // 更改模型(仅流式输入模式)
|
||
setMaxThinkingTokens(max: number | null): Promise<void>; // 更改思考 token 数(仅流式输入模式)
|
||
supportedCommands(): Promise<SlashCommand[]>; // 可用的斜杠命令
|
||
supportedModels(): Promise<ModelInfo[]>; // 可用模型
|
||
mcpServerStatus(): Promise<McpServerStatus[]>; // MCP 服务器连接状态
|
||
accountInfo(): Promise<AccountInfo>; // 已认证用户信息
|
||
}
|
||
```
|
||
|
||
在 sdk.d.ts 中存在但官方文档中未出现(可能为内部方法):
|
||
- `streamInput(stream)`——流式传入额外用户消息
|
||
- `close()`——强制结束查询
|
||
- `setMcpServers(servers)`——动态添加/移除 MCP 服务器
|
||
|
||
## 沙箱配置
|
||
|
||
```typescript
|
||
type SandboxSettings = {
|
||
enabled?: boolean;
|
||
autoAllowBashIfSandboxed?: boolean;
|
||
excludedCommands?: string[];
|
||
allowUnsandboxedCommands?: boolean;
|
||
network?: {
|
||
allowLocalBinding?: boolean;
|
||
allowUnixSockets?: string[];
|
||
allowAllUnixSockets?: boolean;
|
||
httpProxyPort?: number;
|
||
socksProxyPort?: number;
|
||
};
|
||
ignoreViolations?: {
|
||
file?: string[];
|
||
network?: string[];
|
||
};
|
||
};
|
||
```
|
||
|
||
当 `allowUnsandboxedCommands` 为 true 时,模型可以在 Bash 工具输入中设置 `dangerouslyDisableSandbox: true`,这会回退到 `canUseTool` 权限处理器。
|
||
|
||
## MCP 服务器辅助函数
|
||
|
||
### tool()
|
||
|
||
使用 Zod schema 创建类型安全的 MCP 工具定义:
|
||
|
||
```typescript
|
||
function tool<Schema extends ZodRawShape>(
|
||
name: string,
|
||
description: string,
|
||
inputSchema: Schema,
|
||
handler: (args: z.infer<ZodObject<Schema>>, extra: unknown) => Promise<CallToolResult>
|
||
): SdkMcpToolDefinition<Schema>
|
||
```
|
||
|
||
### createSdkMcpServer()
|
||
|
||
创建进程内 MCP 服务器(我们改用 stdio 以支持子代理继承):
|
||
|
||
```typescript
|
||
function createSdkMcpServer(options: {
|
||
name: string;
|
||
version?: string;
|
||
tools?: Array<SdkMcpToolDefinition<any>>;
|
||
}): McpSdkServerConfigWithInstance
|
||
```
|
||
|
||
## 内部机制参考
|
||
|
||
### 关键的压缩标识符 (sdk.mjs)
|
||
|
||
| 压缩名 | 用途 |
|
||
|----------|---------|
|
||
| `s_` | V1 `query()` 导出 |
|
||
| `e_` | `unstable_v2_createSession` |
|
||
| `Xx` | `unstable_v2_resumeSession` |
|
||
| `Qx` | `unstable_v2_prompt` |
|
||
| `U9` | V2 Session 类 (`send`/`stream`/`close`) |
|
||
| `XX` | ProcessTransport(生成 cli.js) |
|
||
| `$X` | Query 类(JSON 行路由、异步可迭代) |
|
||
| `QX` | AsyncQueue(输入流缓冲区) |
|
||
|
||
### 关键的压缩标识符 (cli.js)
|
||
|
||
| 压缩名 | 用途 |
|
||
|----------|---------|
|
||
| `EZ` | 核心递归代理循环(异步生成器) |
|
||
| `_t4` | 停止钩子处理器(当没有 tool_use 块时运行) |
|
||
| `PU1` | 流式工具执行器(在 API 响应期间并行) |
|
||
| `TP6` | 标准工具执行器(API 响应之后) |
|
||
| `GU1` | 单个工具执行器 |
|
||
| `lTq` | SDK 会话运行器(直接调用 EZ) |
|
||
| `bd1` | stdin 读取器(来自传输层的 JSON 行) |
|
||
| `mW1` | Anthropic API 流式调用器 |
|
||
|
||
## 关键文件
|
||
|
||
- `sdk.d.ts` — 所有类型定义(1777 行)
|
||
- `sdk-tools.d.ts` — 工具输入 schema
|
||
- `sdk.mjs` — SDK 运行时(压缩的,376KB)
|
||
- `cli.js` — CLI 可执行文件(压缩的,作为子进程运行)
|