用户异步交互
在 Session 内处理审批、问题与等待用户响应的执行
用户异步交互
Interaction 表示当前 Turn 的执行需要用户参与。第一阶段支持 approval 与
question;它们使用同一套生命周期、查询和响应 API。
Interaction 是 Assistant Message 的 Part,不是新的顶层 Message,也不会创建新 Turn。 当 Interaction 来自 Tool 时,Tool 与 Interaction 在同一个 Assistant revision 中原子更新:
创建:Tool ready -> waiting-user + Interaction pending
响应:Interaction resolved + Tool running / failed
结束:Interaction expired / cancelled + Tool failed订阅并响应
const unsubscribe = session.subscribe((mutation) => {
if (
mutation.variant !== "part" ||
mutation.type !== "interaction" ||
mutation.part.status !== "pending"
) return;
render_interaction(mutation.part);
});
await session.respond({
interaction_id,
response: {
kind: "approval",
decision: "approved",
},
});subscribe() 只报告已经持久化的状态。respond() 先原子提交 Interaction 与关联 Tool,
成功后才恢复原执行。请求不存在、已经终结或响应类型不匹配时会抛出明确错误。
查询 pending Interaction
const pending = await session.interactions();
for (const { request } of pending) {
if (request.kind === "approval") {
render_approval(request);
} else {
render_questions(request.questions);
}
}Question 支持 text、single_select 与 multi_select。回答必须覆盖请求中的全部
question_id:
await session.respond({
interaction_id,
response: {
kind: "question",
answers: [
{ question_id: "region", value: "cn" },
{ question_id: "features", value: ["search", "files"] },
],
},
});模型主动提问
ask_question 是可选 Tool,不会由 Agent 默认注册。需要该能力时由调用方显式传入:
import { Agent, Workspace } from "@downcity/agent";
import { AskQuestionsTool } from "@downcity/agent/tools";
const agent = new Agent({
id: "repo-helper",
workspace: new Workspace({ path: "/path/to/project" }),
tools: {
ask_question: AskQuestionsTool,
},
});模型缺少会实质影响结果的信息时,返回标准 Tool Call:
{
"title": "选择部署区域",
"questions": [
{
"question": "需要部署到哪个区域?",
"type": "single_select",
"options": [
{ "value": "cn", "label": "中国" },
{ "value": "us", "label": "美国" }
]
}
]
}questions 中每个问题都必须显式提供 type,可选值为 text、single_select 和
multi_select。question_id 由 Session 自动生成;选择题必须提供至少一个同时包含
value 和 label 的 options,文本题不需要 options。
Session 会把该 Tool 与 Question Interaction 原子提交为 waiting-user / pending。调用方
使用上面的 session.respond(...) 提交完整回答后,Tool Result 返回:
{
"status": "resolved",
"answers": [{ "question_id": "question:generated-id", "value": "cn" }]
}模型随后在同一个 Turn 的下一 Step 继续执行。普通 assistant 文本中的问句不会进入该 生命周期:它会正常结束当前 Turn,用户的下一条消息会创建新 Turn。
普通 Tool 审批
需要在执行前获得用户许可的 Tool,直接使用 AI SDK 原生 needsApproval 声明:
import { tool } from "ai";
import { z } from "zod";
const delete_file = tool({
description: "删除工作区内的指定文件。",
inputSchema: z.object({
path: z.string(),
}),
needsApproval: true,
execute: async ({ path }) => {
await remove_workspace_file(path);
return { deleted: path };
},
});
const agent = new Agent({
id: "repo-helper",
workspace,
model,
tools: { delete_file },
});审批策略属于 Tool,不属于 Agent。模型产生 Tool Call 后,AI SDK 先完成 schema 校验;
Session 再创建 operation: "tool" 的 Approval Interaction,并等待 session.respond(...)。
批准后执行原 Tool Call,拒绝时 execute 不会运行。
普通 Tool 审批请求向 UI 提供真实调用信息:
source.tool_name与source.tool_call_id:实际 Tool 及调用标识。validated_input:schema 校验通过后的结构化输入。tool_description:Tool 定义中的稳定能力说明,可选。model_explanation:模型在当前调用中生成的解释,可选,不能作为安全判断依据。
调用方不需要配置审批 title 或 reason。UI 应根据 Tool、已校验输入和可选模型解释
组织展示内容,不能用客户端生成的摘要替代真实输入。
Shell 审批模式
const current = await session.status();
await session.set({
security: {
approval_mode: "always-allow",
},
});| 模式 | 行为 |
|---|---|
ask | unrestricted Shell 请求创建 approval Interaction。 |
always-allow | 当前 Session 后续 Shell 请求自动批准。 |
审批模式是 Shell Adapter 的执行策略,不是通用 Interaction 状态。它不改变 sandbox, 不影响其他 Session,也不会处理已经 pending 的 Interaction。
set() 会接受 configured 值,并把一个 SessionCommand 加入 Session 有序输入队列。
当前 Provider 请求与 Tool callback 继续使用已经捕获的策略,新模式在下一个 Session Step
检查点生效。如果当前 Turn 没有下一个 Step,该命令会在下一条 prompt 开始前提交。
通过 status() 读取状态:security.approval_mode 是 configured 值,
security.effective_approval_mode 是执行面当前值;两者不同时表示修改仍在排队。Command
成功提交后,Session 会持久化一条 Session configuration updated completed Action。
初始化或恢复审批模式时,可以通过 set() 的第二个参数关闭 Action 和 Mutation;配置仍会正常写入并在检查点生效:
await session.set(
{ security: { approval_mode: "always-allow" } },
{ persist_action: false, publish_mutation: false },
);相同审批模式的幂等设置不会生成新的配置 Action。审批模式会随 Session metadata 恢复。
UI 实现要点
- 使用
interaction_id作为交互卡片稳定 key。 source.type === "tool"时,用source.tool_call_id关联 Tool Part。- Tool 为
waiting-user时保持等待态,不要在客户端伪造running或成功。 resolved、expired、cancelled都是终态,不能重复响应。- Session 切换时清理旧交互 UI,不能把旧 Session 的
interaction_id发给新 Session。
完整 Part 状态见 Message 与 Part。