# 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` 的 `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` | `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` | `process.env` | 环境变量 | | `executable` | `'bun' \| 'deno' \| 'node'` | 自动检测 | JavaScript 运行时 | | `fallbackModel` | `string` | `undefined` | 主模型失败时使用的模型 | | `forkSession` | `boolean` | `false` | 恢复时,分叉到新的 session ID 而非继续原始会话 | | `hooks` | `Partial>` | `{}` | 事件钩子回调 | | `includePartialMessages` | `boolean` | `false` | 包含部分消息事件(流式) | | `maxBudgetUsd` | `number` | `undefined` | 查询的最大美元预算 | | `maxThinkingTokens` | `number` | `undefined` | 思考过程的最大 token 数 | | `maxTurns` | `number` | `undefined` | 最大对话轮次 | | `mcpServers` | `Record` | `{}` | 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 } | { type: 'sse'; url: string; headers?: Record } | { type: 'http'; url: string; headers?: Record } | { 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; 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`: ```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` 并自行管理协调 ### 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; ``` ### 钩子返回值 ```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 } | { 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 { interrupt(): Promise; // 停止当前执行(仅流式输入模式) rewindFiles(userMessageUuid: string): Promise; // 将文件恢复到消息时的状态(需要 enableFileCheckpointing) setPermissionMode(mode: PermissionMode): Promise; // 更改权限(仅流式输入模式) setModel(model?: string): Promise; // 更改模型(仅流式输入模式) setMaxThinkingTokens(max: number | null): Promise; // 更改思考 token 数(仅流式输入模式) supportedCommands(): Promise; // 可用的斜杠命令 supportedModels(): Promise; // 可用模型 mcpServerStatus(): Promise; // MCP 服务器连接状态 accountInfo(): Promise; // 已认证用户信息 } ``` 在 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( name: string, description: string, inputSchema: Schema, handler: (args: z.infer>, extra: unknown) => Promise ): SdkMcpToolDefinition ``` ### createSdkMcpServer() 创建进程内 MCP 服务器(我们改用 stdio 以支持子代理继承): ```typescript function createSdkMcpServer(options: { name: string; version?: string; tools?: Array>; }): 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 可执行文件(压缩的,作为子进程运行)