Agent 在执行任务时,有两种情况需要用户参与:
无论
Agent 请求使用工具后,SDK 会先检查你的权限配置,然后出现以下三种结果:
下面只展示回调的配置方式。示例中的
TypeScript 的
允许工具执行时,返回工具参数:
拒绝时,请提供简短原因。Agent 可以根据原因换一种做法,或者向用户说明为什么没有执行:
如果拒绝后还要立即停止整个任务,可以设置
这些规则可以让同类操作在当前会话中不再重复询问。完整的
需要注意:
提交答案时仍返回
下面的示例同时处理问题和普通工具确认。实际应用可以把终端输入替换成对话框、Web 页面或审批服务。
Python 终端示例使用阻塞式
不要用
- Agent 想执行一个工具,需要用户确认是否允许。
- Agent 缺少必要信息,需要用户回答一个问题。
canUseTool(Python 为 can_use_tool)承接用户交互时,这两种请求都会进入同一个回调。你的应用需要显示确认框或问题,并把用户的选择返回给 SDK。
| 用户看到的内容 | 典型工具 | 用户需要做什么 | 应用返回什么 |
|---|---|---|---|
| 工具审批 | Bash、Write、MCP 工具 | 是否允许本次操作 | 原始或审核后的工具输入 |
| 澄清问题 | AskUserQuestion | 问题的实际答案 | questions 与 answers |
prompt 使用单消息输入还是流式输入,都可以配置这个回调。一次性 query() 也能显示工具确认和澄清问题。
canUseTool 何时触发
Agent 请求使用工具后,SDK 会先检查你的权限配置,然后出现以下三种结果:
- 已经允许:工具直接执行,不调用
canUseTool。 - 已经禁止:工具直接被拒绝,不调用
canUseTool。 - 需要用户确认:SDK 调用
canUseTool,等待你的应用返回允许或拒绝。
AskUserQuestion 与普通工具不同:它需要用户提供真实答案。已经配置 canUseTool / can_use_tool 时,只要没有明确禁止用户交互,SDK 就会调用回调来获取答案。
因此,canUseTool 看不到所有工具调用,不能用来记录完整的工具执行日志。要观察每次工具调用,请使用 hooks。权限配置的检查顺序见权限控制。
如果希望应用处理动态确认或 AskUserQuestion,必须配置 canUseTool / can_use_tool,或者使用外部 permission prompt tool。运行时发出权限请求而 SDK 没有对应回调时,SDK 会以失败关闭(fail closed)的方式报告错误,工具不会自动执行。
配置回调
下面只展示回调的配置方式。示例中的 showApprovalDialog / show_approval_dialog 代表宿主应用自己实现的确认界面,不是 SDK 提供的函数。可直接运行的终端版本见本文后面的完整示例。
context.signal 是 AbortSignal,Python 的 context.signal 是 asyncio.Event | None。如果任务已经取消,你的应用应关闭确认框并结束回调。TypeScript 示例返回 toolUseID,用于标记当前处理的是哪一次工具调用。
如果确认界面由你的应用提供,使用 canUseTool / can_use_tool。如果已有外部 permission prompt tool,使用 permissionPromptToolName / permission_prompt_tool_name。两种方式不能同时配置。
返回审批结果
允许一次
允许工具执行时,返回工具参数:
updatedInput / updated_input 是工具最终收到的参数。你可以原样返回,也可以在允许前删除危险选项、限制文件路径或修改其他字段。修改时应按照该工具的参数格式进行校验。
拒绝
拒绝时,请提供简短原因。Agent 可以根据原因换一种做法,或者向用户说明为什么没有执行:
interrupt: true(Python 为 interrupt=True)。
本会话始终允许
context.suggestions 可能包含 SDK 建议保存的权限规则。用户选择“本会话始终允许”时,把这些建议放进允许结果:
PermissionUpdate 类型和自定义规则方式见会话内更新权限。
权限模式如何影响回调
permissionMode / permission_mode 决定这一会话默认如何处理工具请求。它会影响用户是否看到确认框,也会影响 canUseTool 是否被调用。下表假设会话已经配置 canUseTool / can_use_tool,并且 AskUserQuestion 在 tools 中可见:
| 权限模式 | 普通工具会怎样 | AskUserQuestion 会怎样 |
|---|---|---|
default | 需要确认的工具会调用回调 | 调用回调,让用户回答 |
acceptEdits | 常见文件编辑直接允许;其他工具仍可能要求确认 | 调用回调,让用户回答 |
plan | 不执行实际修改;必要时仍可请求确认 | 可以提问,用来确认计划或需求 |
auto | SDK 自动允许或拒绝部分工具,不保证每次都调用回调 | 调用回调,让用户回答 |
dontAsk | 没有提前允许的工具直接被拒绝,不显示确认 | 问题也会被拒绝,无法等待用户回答 |
bypassPermissions / yolo | 普通工具直接执行,不显示确认 | 仍然调用回调;SDK 不会替用户回答 |
- 如果
disallowedTools/disallowed_tools或 deny 规则禁止了AskUserQuestion,Agent 就不能向用户提问。 - 如果显式设置了
tools,需要把AskUserQuestion加入列表,否则 Agent 看不到这个工具。 bypassPermissions和yolo只应在受信任环境使用。它们会跳过普通工具确认,但不会替用户回答问题。- 如果希望所有尚未允许的操作都显示在自己的确认界面中,通常选择
default,并且不要把这些工具加入allowedTools/allowed_tools。
处理 AskUserQuestion
AskUserQuestion 用于在继续当前任务前询问少量、结构化的问题,例如选择目标环境、实现方案或输出格式。它不是一条新的聊天消息;用户回答后,Agent 会继续当前任务。
当 canUseTool 收到 toolName === 'AskUserQuestion' 时,回调中的 input 使用下面的数据结构:
- 一次包含 1–4 个问题。
- 每个问题包含一个简短
header和 2–4 个选项。 multiSelect: false或省略时只能选择一项;为true时可以选择多项。- 你的界面应允许用户输入选项之外的自定义答案。
- 选项可能携带
preview。TypeScript 可通过toolConfig.askUserQuestion.previewFormat指定预览内容按markdown或html解释;Python SDK 当前没有对应的tool_config选项。
编写canUseTool回调时,请使用这里的questions/answers结构,不要按单个question/answer字段处理。
返回问题答案
提交答案时仍返回 behavior: 'allow'。这里的 allow 表示“接受这些答案并继续任务”,updatedInput 中需要包含问题和答案:
answers 的编码规则如下:
- 每个 key 必须使用对应问题完整的
question文本。 - 单选答案使用选项的
label;自定义答案直接使用用户输入的文本。 - 多选答案把多个 label 用
,连接成一个字符串。 - 如果用户取消问答,返回 deny 并说明原因。不要使用虚构或空白答案继续执行。
完整示例
下面的示例同时处理问题和普通工具确认。实际应用可以把终端输入替换成对话框、Web 页面或审批服务。
input(),因此任务取消后,要等当前输入返回才能检查 context.signal。Web 页面或桌面应用不应照搬这一限制:监听 context.signal,在信号触发时立即关闭对话框并结束回调。
适用边界
| 需求 | 推荐能力 | 原因 |
|---|---|---|
| 确认是否执行某个工具 | canUseTool / can_use_tool | 可以允许、拒绝,也可以先修改工具参数 |
| Agent 继续任务前需要 1–4 个简短答案 | AskUserQuestion | Agent 提供问题和选项,用户回答后继续当前任务 |
| 用户主动追问、补充长文本或改变任务方向 | 流式输入 | 这是一条新的用户消息 |
| 需要固定字段、严格校验、文件上传或复杂表单 | 自定义工具 | 应用可以自己定义固定的数据格式和界面 |
| MCP server 主动索取表单或授权信息 | MCP Elicitation | 这类请求使用 onElicitation / on_elicitation |
| 记录所有工具调用或统一拦截工具 | hooks 与权限控制 | canUseTool 不会收到已经自动允许或拒绝的调用 |
AskUserQuestion 代替多轮对话。用户主动发送的新消息应使用流式输入。也不要用普通文本回复代替工具确认,因为 SDK 需要收到明确的 allow 或 deny 结果。