Skip to content

Commit 986068f

Browse files
authored
Add tool approval example: agent, backend, UI, and E2E tests (#226)
## 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 - [x] E2E approval test: approve flow executes tool and continues generation - [x] E2E approval test: deny flow produces denial acknowledgment from model - [x] Manual test: Send → Approve → clean completion (no stuck streaming) - [x] Manual test: Send → Deny → model acknowledges denial 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 5110045 + 700fab9 commit 986068f

14 files changed

Lines changed: 1286 additions & 30 deletions

File tree

example/convex/_generated/api.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
* @module
99
*/
1010

11+
import type * as agents_approval from "../agents/approval.js";
1112
import type * as agents_config from "../agents/config.js";
1213
import type * as agents_fashion from "../agents/fashion.js";
1314
import type * as agents_simple from "../agents/simple.js";
1415
import type * as agents_story from "../agents/story.js";
1516
import type * as agents_weather from "../agents/weather.js";
17+
import type * as chat_approval from "../chat/approval.js";
1618
import type * as chat_basic from "../chat/basic.js";
1719
import type * as chat_human from "../chat/human.js";
1820
import type * as chat_streamAbort from "../chat/streamAbort.js";
@@ -55,11 +57,13 @@ import type {
5557
} from "convex/server";
5658

5759
declare const fullApi: ApiFromModules<{
60+
"agents/approval": typeof agents_approval;
5861
"agents/config": typeof agents_config;
5962
"agents/fashion": typeof agents_fashion;
6063
"agents/simple": typeof agents_simple;
6164
"agents/story": typeof agents_story;
6265
"agents/weather": typeof agents_weather;
66+
"chat/approval": typeof chat_approval;
6367
"chat/basic": typeof chat_basic;
6468
"chat/human": typeof chat_human;
6569
"chat/streamAbort": typeof chat_streamAbort;

example/convex/agents/approval.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// See the docs at https://docs.convex.dev/agents/tool-approval
2+
import { Agent, createTool, stepCountIs } from "@convex-dev/agent";
3+
import { components } from "../_generated/api";
4+
import { defaultConfig } from "./config";
5+
import { z } from "zod/v4";
6+
7+
// Tool that always requires approval
8+
const deleteFileTool = createTool({
9+
description: "Delete a file from the system",
10+
inputSchema: z.object({
11+
filename: z.string().describe("The name of the file to delete"),
12+
}),
13+
needsApproval: () => true,
14+
execute: async (_ctx, input) => {
15+
return `Successfully deleted file: ${input.filename}`;
16+
},
17+
});
18+
19+
// Tool with conditional approval (requires approval for amounts > $100)
20+
const transferMoneyTool = createTool({
21+
description: "Transfer money to an account",
22+
inputSchema: z.object({
23+
amount: z.number().describe("The amount to transfer"),
24+
toAccount: z.string().describe("The destination account"),
25+
}),
26+
needsApproval: async (_ctx, input) => {
27+
return input.amount > 100;
28+
},
29+
execute: async (_ctx, input) => {
30+
return `Transferred $${input.amount} to account ${input.toAccount}`;
31+
},
32+
});
33+
34+
// Tool that doesn't need approval
35+
const checkBalanceTool = createTool({
36+
description: "Check the account balance",
37+
inputSchema: z.object({
38+
accountId: z.string().describe("The account to check"),
39+
}),
40+
execute: async (_ctx, _input) => {
41+
return `Balance: $1,234.56`;
42+
},
43+
});
44+
45+
export const approvalAgent = new Agent(components.agent, {
46+
name: "Approval Demo Agent",
47+
instructions:
48+
"You are a helpful assistant that can delete files, transfer money, and check account balances. " +
49+
"Always confirm what action you took after it completes.",
50+
tools: {
51+
deleteFile: deleteFileTool,
52+
transferMoney: transferMoneyTool,
53+
checkBalance: checkBalanceTool,
54+
},
55+
stopWhen: stepCountIs(5),
56+
...defaultConfig,
57+
callSettings: { temperature: 0 },
58+
});

0 commit comments

Comments
 (0)