Human-in-the-loop
当 agents 调用处理敏感操作的工具(例如删除资源或运行长时间进程)时,有时需要与 workflows 相同的 human-in-the-loop (HITL) 监督机制。借助 human-in-the-loop,你可以在工具调用执行前将其挂起,以便人工批准或拒绝;也可以让工具自行挂起,以向用户请求额外上下文。
何时使用 human-in-the-loop何时使用 human-in-the-loop的直接链接
- 破坏性或不可逆的操作,例如删除记录或发送电子邮件,或者处理付款。
- 成本高昂的操作,例如调用昂贵的第三方 API,你希望先验证参数。
- 条件确认,即工具开始执行后才发现需要用户确认或提供额外数据才能完成。
快速开始快速开始的直接链接
为工具标记 requireApproval: true,然后在流中检查 tool-call-approval 片段,以批准或拒绝该操作:
import { Agent } from '@mastra/core/agent'
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
const deleteTool = createTool({
id: 'delete-record',
description: 'Delete a record by ID',
inputSchema: z.object({ id: z.string() }),
outputSchema: z.object({ deleted: z.boolean() }),
requireApproval: true,
execute: async ({ id }) => {
await db.delete(id)
return { deleted: true }
},
})
const agent = new Agent({
id: 'my-agent',
name: 'My Agent',
model: 'openai/gpt-5-mini',
tools: { deleteTool },
})
const stream = await agent.stream('Delete record abc-123')
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-approval') {
const approved = await agent.approveToolCall({ runId: stream.runId })
for await (const c of approved.textStream) process.stdout.write(c)
}
}
审批如何工作审批如何工作的直接链接
Mastra 提供两种不同的机制来暂停工具调用:执行前审批和运行时挂起。
执行前审批执行前审批的直接链接
执行前审批在工具的 execute 函数运行之前暂停工具调用。LLM 仍然决定调用哪个工具并提供参数,但 execute 只有在你明确批准后才会运行。
以下标志通过 OR 逻辑组合控制此行为。只要任意一个为 true,调用就会暂停:
| 标志 | 设置位置 | 作用域 |
|---|---|---|
requireToolApproval: true | stream() / generate() 选项 | 暂停该请求的每一次工具调用 |
requireApproval: true | createTool() 定义 | 暂停对该特定工具的调用 |
流会发出一个 tool-call-approval 片段,包含 toolCallId、toolName 和 args。使用流的 runId 调用 approveToolCall() 或 declineToolCall() 以继续:
const stream = await agent.stream("What's the weather in London?", {
requireToolApproval: true,
})
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-approval') {
console.log('Tool:', chunk.payload.toolName)
console.log('Args:', chunk.payload.args)
// Approve
const approved = await agent.approveToolCall({ runId: stream.runId })
for await (const c of approved.textStream) process.stdout.write(c)
// Or decline
const declined = await agent.declineToolCall({ runId: stream.runId })
for await (const c of declined.textStream) process.stdout.write(c)
}
}
解释拒绝原因解释拒绝原因的直接链接
declineToolCall()、declineToolCallGenerate() 和 declineNetworkToolCall() 接受一个可选的 reason。该原因会代替工具结果返回给模型,使模型可以调整策略,而不是盲目重试。它还会存储在工具调用的 approval 元数据中,因此对话被召回时仍然存在。
const declined = await agent.declineToolCall({
runId: stream.runId,
toolCallId,
reason: 'Reading other users PII is not allowed, ask the user for their own email instead',
})
如果不提供 reason,模型会收到默认消息 Tool call was not approved by the user。
使用函数进行条件审批使用函数进行条件审批的直接链接
requireToolApproval 也可以接受一个函数而不是布尔值,该函数会针对每次工具调用做出决定。它接收 toolName、模型传入的 args、requestContext 和 workspace。返回 true 表示该调用需要审批,返回 false 表示允许。这样你就可以在运行时控制审批,例如仅对名称匹配某个模式的工具要求审批:
const stream = await agent.stream('Clean up old records', {
requireToolApproval: ({ toolName }) => /^delete_/.test(toolName),
})
该函数可以是异步的。对于依赖工具名称和参数的决定,可以使用 classifier 来估计调用是否需要人工审核:
import { Classifier } from '@mastra/core/classifier'
import { typeSafeAi } from '@ai-sdk/typesafe-ai'
import { agent } from './agent'
const approvalClassifier = new Classifier({
id: 'tool-approval-classifier',
model: typeSafeAi.decisionModel('jev-latest'),
questions: {
requiresApproval: {
type: 'boolean',
instructions:
'Does this tool call need human approval because it is destructive, irreversible, expensive, or handles sensitive data?',
},
},
})
const stream = await agent.stream('Clean up old records', {
requireToolApproval: async ({ toolName, args }) => {
const result = await approvalClassifier.decide({
state: {
toolName,
argumentNames: Object.keys(args),
},
})
// A refused question has no probability, so it falls back to requiring approval.
return (result.answers.requiresApproval.probability ?? 1) >= 0.7
},
})
该示例发送的是参数名而不是参数值,因为 classifier 会将其状态转发给配置的决策模型。如果决策需要参数值,请先脱敏敏感数据,或使用满足受保护工具数据处理要求的决策模型和提供器。
返回 true 会暂停工具调用以等待人工审批,它不会自动批准或拒绝调用。请选择与 agent 可用工具的风险相匹配的阈值。如果该 requireToolApproval 函数抛出异常,则默认要求审批。
随后运行时会将此结果与工具的 requireApproval 设置组合。布尔值 true 表示要求审批,而 false 不会禁用由 requireToolApproval 要求的审批。工具级别的 requireApproval 函数具有权威性,会替换该工具的组合结果。
基于函数的 requireToolApproval 仅适用于常规的 stream() / generate() 调用。持久化 agents 和存储型 agents 会持久化其选项,而函数无法序列化,因此它们只接受布尔值。如果在这些上下文中传入函数,则回退为对每次工具调用都要求审批。
将审批绑定到确切的工具参数将审批绑定到确切的工具参数的直接链接
对于敏感工具,请将审批绑定到展示给审核者的确切工具名称和参数。如果这些参数在执行前发生变化,工具就不应在旧审批下运行。
tool-call-approval 片段已经包含 toolName、toolCallId 和 args。你可以在展示审批请求时对这些字段进行指纹计算。下面的示例使用 JSON 字符串作为指纹,但在生产环境中,你应该使用工具名称和参数的稳定哈希:
import { Agent } from '@mastra/core/agent'
// For your production usecase, build a stable hash of the tool name and args
function actionFingerprint(toolName: string, args: unknown) {
const payload = JSON.stringify({ toolName, args })
return `fingerprint-${payload}`
}
const sensitiveTools = new Set(['issue_refund', 'delete_record'])
const approvedFingerprints = new Set<string>()
export const approvalBoundAgent = new Agent({
id: 'approval-bound-agent',
name: 'Approval Bound Agent',
model: 'openai/gpt-5.6-sol',
tools: { issueRefundTool, deleteRecordTool },
hooks: {
beforeToolCall: ({ toolName, input }) => {
if (!sensitiveTools.has(toolName)) return
const fingerprint = actionFingerprint(toolName, input)
if (!approvedFingerprints.delete(fingerprint)) {
return {
proceed: false,
output: `Tool call blocked: approval did not match ${toolName} arguments.`,
}
}
},
},
})
const stream = await approvalBoundAgent.stream('Refund order ord-1042', {
requireToolApproval: ({ toolName }) => sensitiveTools.has(toolName),
})
async function consumeApprovalStream(currentStream: typeof stream) {
for await (const chunk of currentStream.fullStream) {
if (chunk.type === 'tool-call-approval') {
const { toolName, toolCallId, args } = chunk.payload
const fingerprint = actionFingerprint(toolName, args)
// Present toolName, args, and fingerprint to your approval UI.
const approved = await showApprovalDialog({ toolName, args, fingerprint })
const nextStream = approved
? await approveReviewedToolCall(currentStream.runId, toolCallId, fingerprint)
: await approvalBoundAgent.declineToolCall({ runId: currentStream.runId, toolCallId })
await consumeApprovalStream(nextStream)
}
}
}
async function approveReviewedToolCall(runId: string, toolCallId: string, fingerprint: string) {
approvedFingerprints.add(fingerprint)
return approvalBoundAgent.approveToolCall({ runId, toolCallId })
}
await consumeApprovalStream(stream)
在生产环境中,请将批准的指纹存储到持久化存储中,并按用户、运行、工具调用和策略版本划分作用域。上面的 Set 有意保持简单,以便边界清晰:审批只会被消费一次,且仅针对经过审核的相同的规范化工具参数。
使用 suspend() 进行运行时挂起使用-suspend-进行运行时挂起的直接链接
工具也可以在其 execute 函数执行期间调用 suspend() 来暂停。当工具开始运行后才发现需要额外的用户输入或确认才能完成时,这很有用。
流会发出一个 tool-call-suspended 片段,其自定义 payload 由工具的 suspendSchema 定义。你可以调用 resumeStream() 并提供匹配工具 resumeSchema 的数据来恢复。
const weatherTool = createTool({
id: 'get-weather',
inputSchema: z.object({
location: z.string().optional(),
}),
suspendSchema: z.object({
question: z.string(),
}),
resumeSchema: z.object({
location: z.string(),
}),
execute: async ({ location }, context) => {
if (!location) {
return await context?.agent?.suspend({
question: 'Which city would you like the weather for?',
})
}
return await fetchWeather(location)
},
})
suspend() 不会抛出异常,请在调用后立即返回(例如 return await suspend({ ... }))。await suspend(...) 之后的代码仍会在工具暂停前运行。
当工具需要用户提供结构化答案,并且还会在 subagent 中运行时,请使用 suspend() 而不是没有 execute 的工具:subagents 无法运行客户端工具,但挂起状态会通过 supervisor 传播。要从 AI SDK 的 useChat() 恢复,请参阅从 useChat() 恢复挂起的工具。
使用 generate() 进行工具审批使用-generate-进行工具审批的直接链接
工具审批也适用于非流式场景的 generate()。当工具需要审批时,generate() 会立即返回,带有 finishReason: 'suspended'、包含工具调用详情(toolCallId、toolName、args)的 suspendPayload,以及 runId:
const output = await agent.generate('Find user John', {
requireToolApproval: true,
})
if (output.finishReason === 'suspended') {
console.log('Tool requires approval:', output.suspendPayload.toolName)
// Approve
const result = await agent.approveToolCallGenerate({
runId: output.runId,
toolCallId: output.suspendPayload.toolCallId,
})
console.log('Final result:', result.text)
// Or decline
const result = await agent.declineToolCallGenerate({
runId: output.runId,
toolCallId: output.suspendPayload.toolCallId,
})
}
stream 与 generate 对比stream 与 generate 对比的直接链接
| 方面 | stream() | generate() |
|---|---|---|
| 响应类型 | 流式片段 | 完整响应 |
| 审批检测 | tool-call-approval 片段 | finishReason: 'suspended' |
| 批准方法 | approveToolCall({ runId }) | approveToolCallGenerate({ runId, toolCallId }) |
| 拒绝方法 | declineToolCall({ runId }) | declineToolCallGenerate({ runId, toolCallId }) |
| 结果 | 可迭代的流 | 完整输出对象 |
toolCallId 在所有四个方法中都是可选的。当可能有多个工具调用待处理时(在 supervisor agents 中很常见),请传入它。省略时,agent 会恢复最近挂起的工具调用。
工具级审批工具级审批的直接链接
除了在 agent 级别暂停每次工具调用,你还可以将单个工具标记为需要审批。这样你就能获得细粒度控制:只有特定工具会暂停,其他工具则立即执行。
使用 requireApproval 审批使用-requireapproval-审批的直接链接
在工具定义上设置 requireApproval: true。无论 agent 上是否设置了 requireToolApproval,该工具都会在执行前暂停:
export const testTool = createTool({
id: 'test-tool',
description: 'Fetches weather for a location',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
weather: z.string(),
}),
resumeSchema: z.object({
approved: z.boolean(),
}),
execute: async inputData => {
const response = await fetch(`https://wttr.in/${inputData.location}?format=3`)
const weather = await response.text()
return { weather }
},
requireApproval: true,
})
当 requireApproval 为 true 时,流会以与 agent 级审批相同的方式发出 tool-call-approval 片段。使用 approveToolCall() 或 declineToolCall() 继续:
const stream = await agent.stream("What's the weather in London?")
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-approval') {
console.log('Approval required for:', chunk.payload.toolName)
}
}
const handleApproval = async () => {
const approvedStream = await agent.approveToolCall({ runId: stream.runId })
for await (const chunk of approvedStream.textStream) {
process.stdout.write(chunk)
}
process.stdout.write('\n')
}
使用 suspend() 审批使用-suspend-审批的直接链接
使用这种方法时,agent 和工具都不使用 requireApproval。相反,工具的 execute 函数调用 suspend() 在特定位置暂停,并向用户返回上下文或确认提示。当审批取决于运行时条件而不是无条件要求时,这很有用。
export const testToolB = createTool({
id: 'test-tool-b',
description: 'Fetches weather for a location',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
weather: z.string(),
}),
resumeSchema: z.object({
approved: z.boolean(),
}),
suspendSchema: z.object({
reason: z.string(),
}),
execute: async (inputData, context) => {
const { resumeData: { approved } = {}, suspend } = context?.agent ?? {}
if (!approved) {
return suspend?.({ reason: 'Approval required.' })
}
const response = await fetch(`https://wttr.in/${inputData.location}?format=3`)
const weather = await response.text()
return { weather }
},
})
使用这种方法时,流中会包含一个 tool-call-suspended 片段,suspendPayload 包含工具 suspendSchema 定义的 reason。使用 resumeSchema 数据和 runId 调用 resumeStream 以继续:
const stream = await agent.stream("What's the weather in London?")
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-suspended') {
console.log(chunk.payload.suspendPayload)
}
}
const handleResume = async () => {
const resumedStream = await agent.resumeStream({ approved: true }, { runId: stream.runId })
for await (const chunk of resumedStream.textStream) {
process.stdout.write(chunk)
}
process.stdout.write('\n')
}
当挂起的或需要审批的工具调用恢复时,流会在该调用的 tool-result 之前发出一个具有相同 toolCallId 的 tool-call-resumed 片段。其 payload.kind 为 'suspension' 或 'approval',payload 会重复原始的 args、suspendPayload 和 resumeSchema。仅当真实的挂起或审批请求被清除时,才会发出该片段。
使用 @mastra/ai-sdk 时,它会以 data-tool-call-suspended 部分(审批则为 data-tool-call-approval 部分)到达,并带有 resumed: true,这会替换原始部分并保留其 payload。使用此信号将问题标记为已回答。不要从工具输出的形状推断这一点:当挂起来自 sub-agent 时,委派调用的输出是 sub-agent 的结果,而不是你的答案。
自动恢复工具自动恢复工具的直接链接
使用调用 suspend() 的工具时,你可以启用自动恢复,让 agent 根据用户的下一条消息恢复挂起的工具。在 agent 的默认选项中或每次请求时设置 autoResumeSuspendedTools 为 true:
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
const agent = new Agent({
id: 'my-agent',
name: 'My Agent',
instructions: 'You are a helpful assistant',
model: 'openai/gpt-5-mini',
tools: { weatherTool },
memory: new Memory(),
defaultOptions: {
autoResumeSuspendedTools: true,
},
})
启用后,agent 会在下一条用户消息时从消息历史中检测挂起的工具。它会根据工具的 resumeSchema 提取 resumeData,然后自动恢复工具。
自动恢复适用于携带数据的 suspend() 流程。它不会批准使用 requireApproval 的工具;这些工具会保持挂起,直到通过 approveToolCall()、declineToolCall()、resumeStream({ approved: boolean }) 或等效的 UI 或 API 操作提交明确的批准或拒绝。
以下示例展示了一个完整的对话流程:
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
const weatherTool = createTool({
id: 'weather-tool',
description: 'Fetches weather for a city',
inputSchema: z.object({
city: z.string(),
}),
outputSchema: z.object({
weather: z.string(),
}),
suspendSchema: z.object({
message: z.string(),
}),
resumeSchema: z.object({
city: z.string(),
}),
execute: async (inputData, context) => {
const { resumeData, suspend } = context?.agent ?? {}
// If no city provided, ask the user
if (!inputData.city && !resumeData?.city) {
return suspend?.({ message: 'What city do you want to know the weather for?' })
}
const city = resumeData?.city ?? inputData.city
const response = await fetch(`https://wttr.in/${city}?format=3`)
const weather = await response.text()
return { weather: `${city}: ${weather}` }
},
})
const stream = await agent.stream("What's the weather like?")
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-suspended') {
console.log(chunk.payload.suspendPayload)
}
}
// User sends follow-up on the same thread
const resumedStream = await agent.stream('San Francisco')
for await (const chunk of resumedStream.textStream) {
process.stdout.write(chunk)
}
User: "What's the weather like?"
Agent: "What city do you want to know the weather for?"
User: "San Francisco"
Agent: "The weather in San Francisco is: San Francisco: ☀️ +72°F"
第二条消息会自动恢复挂起的工具。agent 会从用户消息中提取 { city: "San Francisco" } 并将其作为 resumeData 传入。
要求要求的直接链接
要使自动工具恢复正常工作:
- 已配置 memory:agent 需要 memory 来跨消息跟踪挂起的工具
- 同一 thread:后续消息必须使用相同的 memory thread 和 resource 标识符
- 已定义
resumeSchema:工具必须定义resumeSchema,以便 agent 知道要从用户消息中提取什么数据结构
手动与自动恢复手动与自动恢复的直接链接
| 方式 | 使用场景 |
|---|---|
手动(resumeStream()) | 编程控制、webhooks、按钮点击、外部触发 |
自动(autoResumeSuspendedTools) | 用户以自然语言提供恢复数据的对话式流程 |
两种方式都适用于相同的工具定义。自动恢复仅在消息历史中存在挂起的工具且用户在同一 thread 上发送新消息时触发。
重启后恢复重启后恢复的直接链接
上面的示例在挂起和审批之间一直持有 stream.runId。这在进程保持存活时有效,但在生产环境中,审批往往来得更晚,例如页面刷新、服务器重启之后,或者位于负载均衡器后面的不同服务器实例上。
使用 listSuspendedRuns() 从存储中重新发现某个对话的待处理运行:
// In the request handler that receives the user's decision
const { runs } = await agent.listSuspendedRuns({
threadId: 'thread-123',
resourceId: 'user-456',
})
const run = runs[0]
const toolCall = run?.toolCalls[0]
if (run && toolCall) {
let stream
if (toolCall.requiresApproval) {
// Suspended by requireApproval — approve or decline the tool call
stream = await agent.approveToolCall({ runId: run.runId, toolCallId: toolCall.toolCallId })
} else {
// Suspended by suspend() — resume with the data the tool asked for
console.log('Tool asked:', toolCall.suspendPayload)
stream = await agent.resumeStream({ name: 'San Francisco' }, { runId: run.runId })
}
for await (const chunk of stream.textStream) process.stdout.write(chunk)
}
每个返回的运行都包含挂起的工具调用(toolCallId、toolName、args 和 requiresApproval)。审批挂起(requiresApproval: true)通过 approveToolCall() / declineToolCall() 应答,而基于 suspend() 的挂起带有其 suspendPayload,并期望使用恢复数据调用 resumeStream(),因此你可以为任一流程重建正确的 UI,而无需在内存中保留任何状态。
sendToolApproval() 会自动使用相同的基于存储的发现机制。如果 memory 中该 thread 没有活动运行,它会在失败前搜索存储中的挂起运行。当多个挂起的运行匹配该 thread 时,请传入 toolCallId。
相同的发现机制也通过 HTTP 以 GET /agents/:agentId/suspended-runs 提供,在客户端 SDK 中为 agent.listSuspendedRuns(),因此基于浏览器的审批 UI 可以直接重新发现待处理的运行。
只有当你的 Mastra 实例配置了持久化存储提供器时,挂起的运行才能在重启后存活。默认的内存存储会在进程退出时丢失快照。
工具审批:Supervisor agents工具审批:Supervisor agents的直接链接
supervisor agent 使用 .stream() 或 .generate() 协调多个 subagents。当 subagent 调用需要审批的工具时,请求会通过委派链向上传播,并在 supervisor 层出现:
- supervisor 将任务委派给 subagent。
- subagent 调用具有
requireApproval: true或使用suspend()的工具。 - 审批请求冒泡到 supervisor。
- 你在 supervisor 层批准或拒绝。
- 决定向下传播回 subagent。
工具审批也会通过多层委派传播。如果 supervisor 委派给 subagent A,A 又委派给拥有 requireApproval: true 工具的 subagent B,审批请求仍会出现在顶层 supervisor。
在 supervisor agents 中批准和拒绝在 supervisor agents 中批准和拒绝的直接链接
下面的示例创建了一个带有需要审批工具的 subagent。当工具触发审批请求时,它会在 supervisor 的流中作为 tool-call-approval 片段出现:
import { Agent } from '@mastra/core/agent'
import { createTool } from '@mastra/core/tools'
import { Memory } from '@mastra/memory'
import { z } from 'zod'
const findUserTool = createTool({
id: 'find-user',
description: 'Finds user by ID in the database',
inputSchema: z.object({
userId: z.string(),
}),
outputSchema: z.object({
user: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}),
}),
requireApproval: true,
execute: async input => {
const user = await database.findUser(input.userId)
return { user }
},
})
const dataAgent = new Agent({
id: 'data-agent',
name: 'Data Agent',
description: 'Handles database queries and user data retrieval',
model: 'openai/gpt-5-mini',
tools: { findUserTool },
})
const supervisorAgent = new Agent({
id: 'supervisor',
name: 'Supervisor Agent',
instructions: `You coordinate data retrieval tasks.
Delegate to data-agent for user lookups.`,
model: 'openai/gpt-5.6-sol',
agents: { dataAgent },
memory: new Memory(),
})
const stream = await supervisorAgent.stream('Find user with ID 12345')
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-approval') {
console.log('Tool requires approval:', chunk.payload.toolName)
console.log('Arguments:', chunk.payload.args)
// Approve the tool call
const resumeStream = await supervisorAgent.approveToolCall({
runId: stream.runId,
toolCallId: chunk.payload.toolCallId,
})
for await (const resumeChunk of resumeStream.textStream) {
process.stdout.write(resumeChunk)
}
// To decline instead, use:
const declineStream = await supervisorAgent.declineToolCall({
runId: stream.runId,
toolCallId: chunk.payload.toolCallId,
})
}
}
在 supervisor agents 中使用 suspend()在-supervisor-agents-中使用-suspend的直接链接
工具也可以使用 suspend() 暂停执行并向用户返回上下文。此方法通过 supervisor 委派链的工作方式与 requireApproval 相同:挂起状态会在 supervisor 层出现:
const conditionalTool = createTool({
id: 'conditional-operation',
description: 'Performs an operation that may require confirmation',
inputSchema: z.object({
operation: z.string(),
}),
suspendSchema: z.object({
message: z.string(),
}),
resumeSchema: z.object({
confirmed: z.boolean(),
}),
execute: async (input, context) => {
const { resumeData } = context?.agent ?? {}
if (!resumeData?.confirmed) {
return context?.agent?.suspend({
message: `Confirm: ${input.operation}?`,
})
}
// Proceed with operation
return await performOperation(input.operation)
},
})
// When using this tool through a subagent in supervisor agents
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-suspended') {
console.log('Tool suspended:', chunk.payload.suspendPayload.message)
// Resume with confirmation
const resumeStream = await supervisorAgent.resumeStream(
{ confirmed: true },
{ runId: stream.runId },
)
for await (const resumeChunk of resumeStream.textStream) {
process.stdout.write(resumeChunk)
}
}
}
使用 generate() 的 supervisor 审批使用-generate-的-supervisor-审批的直接链接
工具审批传播也适用于 supervisor agents 中的 generate():
const output = await supervisorAgent.generate('Find user with ID 12345', {
maxSteps: 10,
})
if (output.finishReason === 'suspended') {
console.log('Tool requires approval:', output.suspendPayload.toolName)
// Approve
const result = await supervisorAgent.approveToolCallGenerate({
runId: output.runId,
toolCallId: output.suspendPayload.toolCallId,
})
console.log('Final result:', result.text)
}