Skip to content

Add tool approval example: agent, backend, UI, and E2E tests#226

Merged
sethconvex merged 9 commits intomainfrom
elegant-tool-approval-example
Feb 20, 2026
Merged

Add tool approval example: agent, backend, UI, and E2E tests#226
sethconvex merged 9 commits intomainfrom
elegant-tool-approval-example

Conversation

@sethconvex
Copy link
Contributor

@sethconvex sethconvex commented Feb 11, 2026

Summary

Example implementation demonstrating the tool approval flow from #222:

  • Agent (example/convex/agents/approval.ts): delete_file tool with needsApproval: true
  • Backend (example/convex/chat/approval.ts): sendMessage, submitApproval, handleApprovalDecision
  • React UI (example/ui/chat/ChatApproval.tsx): Approve/Deny buttons with denial reason input, type-safe approval helpers
  • E2E tests (example/convex/approval.test.ts): Approve and deny flows through usageHandler

Test plan

  • E2E approval test: approve flow executes tool and continues generation
  • E2E approval test: deny flow produces denial acknowledgment from model
  • Manual test: Send → Approve → clean completion (no stuck streaming)
  • Manual test: Send → Deny → model acknowledges denial

🤖 Generated with Claude Code

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Warning

Rate limit exceeded

@sethconvex has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch elegant-tool-approval-example

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 11, 2026

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/agent/@convex-dev/agent@226

commit: b5e37ec

@sethconvex sethconvex force-pushed the elegant-tool-approval branch from c70a594 to ef701ff Compare February 11, 2026 05:31
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch 2 times, most recently from f46cd2c to 0c83dae Compare February 11, 2026 20:44
@sethconvex sethconvex marked this pull request as ready for review February 17, 2026 22:34
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch from 8f636a7 to 9a18c30 Compare February 20, 2026 06:34
@sethconvex sethconvex force-pushed the elegant-tool-approval branch from 52b4b56 to ffb13f8 Compare February 20, 2026 06:51
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch from 9a18c30 to dc6edb1 Compare February 20, 2026 06:51
@sethconvex sethconvex force-pushed the elegant-tool-approval branch from ffb13f8 to 3953862 Compare February 20, 2026 06:59
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch from dc6edb1 to 8bcb7bd Compare February 20, 2026 06:59
@sethconvex sethconvex force-pushed the elegant-tool-approval branch from 3953862 to 4e8cd39 Compare February 20, 2026 07:12
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch from 8bcb7bd to 1a12002 Compare February 20, 2026 07:12
@sethconvex sethconvex force-pushed the elegant-tool-approval branch from 4e8cd39 to 2bfbdd6 Compare February 20, 2026 07:23
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch 2 times, most recently from 972fcde to b5e37ec Compare February 20, 2026 07:24
Copy link
Contributor Author

sethconvex commented Feb 20, 2026

Merge activity

  • Feb 20, 7:25 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Feb 20, 7:32 AM UTC: Graphite rebased this pull request as part of a merge.
  • Feb 20, 7:33 AM UTC: @sethconvex merged this pull request with Graphite.

@sethconvex sethconvex changed the base branch from elegant-tool-approval to graphite-base/226 February 20, 2026 07:30
@sethconvex sethconvex changed the base branch from graphite-base/226 to main February 20, 2026 07:31
sethconvex and others added 6 commits February 20, 2026 07:32
Example implementation of the tool approval flow:
- Approval agent with a delete_file tool that requires approval
- Backend functions: sendMessage, submitApproval, handleApprovalDecision
- React UI component with approve/deny buttons and denial reason input
- E2E tests exercising approve and deny flows through usageHandler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the generateId override in startGeneration that caused all
AI SDK internal IDs (approval IDs, tool execution IDs, message IDs)
to be identical. The pending message is already linked via the
explicit pendingMessageId parameter in addMessages.

Document that approveToolCall/denyToolCall append at the end of the
thread, so intervening messages between tool calls and approvals
may cause provider errors (e.g., Anthropic's tool_use/tool_result
adjacency requirement). Recommend disabling chat input while
approvals are pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents users from sending messages that would interleave between
tool calls and their approval responses, which can cause errors with
providers that require tool_use/tool_result adjacency (e.g. Anthropic).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Approval responses are now positioned at the same order as the original
approval request by looking up the parent message ID. This preserves
tool_call/tool_result adjacency even when the user sends messages
between the tool call and the approval decision.

Also updates the example default prompt to trigger two approval-requiring
tool calls (deleteFile + transferMoney) for a better demo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When multiple tools need approval in the same step, the old approach
continued generation after each individual approval, causing the
unapproved tools to re-emit duplicate approval cards.

Now: submitApproval only saves the decision (returns messageId), and
the frontend triggers continuation only when all pending approvals
are resolved via a useEffect watching hasPendingApprovals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI SDK's collectToolApprovals() only examines the last tool message
for approval responses. When multiple approvals are saved as separate
messages (one per approveToolCall/denyToolCall call), earlier approvals
are invisible to the SDK, causing Anthropic to reject the request with
"tool_use ids found without tool_result blocks".

Fix: mergeApprovalResponseMessages() combines consecutive tool messages
containing tool-approval-response parts into a single message before
passing to the SDK.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sethconvex and others added 3 commits February 20, 2026 07:32
Cast tool message content to the expected shape to avoid TS2339
("approvalId does not exist on type") from the union type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…roval test

- Clone content array before .push() to avoid mutating original messages
- Add multi-tool approval E2E test (two tools needing approval in one step)
- Rename _finishStreamId to finishStreamId (was misleadingly prefixed as unused)
- Document order assumption in getApprovalRequestMessageId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sethconvex sethconvex force-pushed the elegant-tool-approval-example branch from b5e37ec to 700fab9 Compare February 20, 2026 07:32
@sethconvex sethconvex merged commit 986068f into main Feb 20, 2026
3 checks passed
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