Skip to content

Conversation

@bendrucker
Copy link
Contributor

Adds tool approval integration for the Vercel AI adapter, enabling human-in-the-loop workflows with the AI SDK.

This PR builds on #3760 and implements the basic plumbing for tool approval. The integration is functional for emitting approval requests and parsing approval responses, but does not yet automatically wire up the full flow.

Changes

  • Adds ToolApprovalRequested and ToolApprovalResponded types to represent approval state
  • Adds approval field to all ToolUIPart and DynamicToolUIPart variants
  • Emits ToolApprovalRequestChunk when agent returns DeferredToolRequests
  • Adds deferred_tool_results property on VercelAIAdapter to extract approval responses from incoming tool parts
  • Adds extract_deferred_tool_results() class method for parsing approvals

How It Works

Approval Request (server → client):

  1. Agent tool has requires_approval=True
  2. When model calls the tool, agent returns DeferredToolRequests
  3. VercelAIEventStream.handle_run_result() emits tool-approval-request chunk
  4. Client receives chunk and shows approval UI

Approval Response (client → server):

  1. User approves or denies in the UI
  2. Client updates the tool part's approval field with {id, approved, reason?}
  3. Next SubmitMessage includes tool parts with approval responses
  4. VercelAIAdapter.deferred_tool_results extracts these into DeferredToolResults

Open Questions

1. Automatic vs Manual Integration

Currently, users must manually wire up the deferred tool results:

adapter = VercelAIAdapter(agent, request)
deferred = adapter.deferred_tool_results

# User must pass deferred_tool_results to run_stream
events = adapter.run_stream(deferred_tool_results=deferred)

Question: Should run_stream() automatically use self.deferred_tool_results when not explicitly provided? This would make the integration seamless but less explicit.

2. Approval ID Tracking

We generate a UUID for approval_id in ToolApprovalRequestChunk, but the AI SDK uses this ID to track the approval lifecycle. The ToolApprovalResponded.id field should match the original approval_id.

Question: Do we need to store/track these approval IDs to validate they match on response? Currently we don't validate this.

3. ToolOutputDeniedChunk

The ToolOutputDeniedChunk type exists but isn't emitted anywhere. In Pydantic AI, denied tool results flow as regular ToolReturnPart with the denial message as content.

Question: Should we emit ToolOutputDeniedChunk when the deferred tool result is a ToolDenied? This would require tracking which tool calls were denied through the agent run.

4. Message History Handling

When continuing with deferred tool results, the message history needs to include the original messages. Currently the adapter's messages property processes all incoming messages, but:

Question: Should tool parts with pending approvals (only ToolApprovalRequested, not ToolApprovalResponded) be filtered out or handled specially when building message history?

Testing

  • Added test for ToolApprovalRequestChunk emission when requires_approval=True tool is called
  • Added tests for extract_deferred_tool_results() covering approved, denied, and no-approval cases

References

When agent run result contains DeferredToolRequests with approvals,
emit ToolApprovalRequestChunk for each tool call requiring human
approval. Uses tool_call_id as both approval_id and tool_call_id.
- Use event.result.response instead of all_messages()
- Set finish_reason to error in on_error
- Remove tool approval emission (defer to separate PR)
- Update test snapshots for finishReason field
- Add ToolApproval types (ToolApprovalRequested, ToolApprovalResponded)
- Add approval field to all tool part types
- Emit ToolApprovalRequestChunk when DeferredToolRequests is returned
- Add deferred_tool_results property to extract approval responses
- Add tests for tool approval request emission and extraction
@bendrucker bendrucker closed this Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant