Skip to content

Commit cc1707d

Browse files
committed
add UI example for tool approval response with options
1 parent 724d55b commit cc1707d

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { anthropic } from '@ai-sdk/anthropic';
2+
import { ToolLoopAgent, dynamicTool, createAgentUIStreamResponse } from 'ai';
3+
import { z } from 'zod';
4+
5+
function randomWeather() {
6+
const weatherOptions = ['sunny', 'cloudy', 'rainy', 'windy'];
7+
return weatherOptions[Math.floor(Math.random() * weatherOptions.length)];
8+
}
9+
10+
const weatherTool = dynamicTool({
11+
description: 'Get the weather in a location',
12+
inputSchema: z.object({ city: z.string() }),
13+
needsApproval: true,
14+
async *execute() {
15+
yield { state: 'loading' as const };
16+
await new Promise(resolve => setTimeout(resolve, 2000));
17+
yield {
18+
state: 'ready' as const,
19+
temperature: 72,
20+
weather: randomWeather(),
21+
};
22+
},
23+
});
24+
25+
const defaultInstructions =
26+
'You are a helpful weather assistant. ' +
27+
'When a tool execution is not approved by the user, do not retry it. ' +
28+
'Just say that the tool execution was not approved.';
29+
30+
export async function POST(request: Request) {
31+
const body = await request.json();
32+
33+
const systemInstruction: string | undefined = body.systemInstruction;
34+
35+
const agent = new ToolLoopAgent({
36+
model: anthropic('claude-sonnet-4-6'),
37+
instructions: systemInstruction ?? defaultInstructions,
38+
tools: { weather: weatherTool },
39+
});
40+
41+
return createAgentUIStreamResponse({
42+
agent,
43+
uiMessages: body.messages,
44+
});
45+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use client';
2+
3+
import ChatInput from '@/components/chat-input';
4+
import DynamicToolWithApprovalView from '@/components/tool/dynamic-tool-with-approval-view';
5+
import { useChat } from '@ai-sdk/react';
6+
import {
7+
ChatRequestOptions,
8+
DefaultChatTransport,
9+
lastAssistantMessageIsCompleteWithApprovalResponses,
10+
} from 'ai';
11+
import { useState } from 'react';
12+
13+
export default function TestToolApprovalOptions() {
14+
const [systemInstruction, setSystemInstruction] = useState('');
15+
16+
const requestOptions: ChatRequestOptions = {
17+
body: { systemInstruction: systemInstruction || undefined },
18+
};
19+
20+
const { status, sendMessage, messages, addToolApprovalResponse } = useChat({
21+
transport: new DefaultChatTransport({
22+
api: '/api/chat/tool-approval-options',
23+
}),
24+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
25+
});
26+
27+
return (
28+
<div className="flex flex-col py-24 mx-auto w-full max-w-md stretch">
29+
<h1 className="mb-4 text-xl font-bold">
30+
Tool Approval with Options Test
31+
</h1>
32+
33+
<label className="mb-4">
34+
<span className="block mb-1 text-sm font-medium">
35+
System Instruction (optional):
36+
</span>
37+
<input
38+
className="w-full p-2 border border-gray-300 rounded"
39+
placeholder="e.g. Always respond in French."
40+
value={systemInstruction}
41+
onChange={e => setSystemInstruction(e.target.value)}
42+
/>
43+
</label>
44+
45+
{messages.map(message => (
46+
<div key={message.id} className="whitespace-pre-wrap">
47+
{message.role === 'user' ? 'User: ' : 'AI: '}
48+
{message.parts.map((part, index) => {
49+
switch (part.type) {
50+
case 'text':
51+
return <div key={index}>{part.text}</div>;
52+
case 'dynamic-tool':
53+
return (
54+
<DynamicToolWithApprovalView
55+
key={index}
56+
invocation={part}
57+
addToolApprovalResponse={addToolApprovalResponse}
58+
requestOptions={requestOptions}
59+
/>
60+
);
61+
}
62+
})}
63+
</div>
64+
))}
65+
66+
<ChatInput
67+
status={status}
68+
onSubmit={text => sendMessage({ text }, requestOptions)}
69+
/>
70+
</div>
71+
);
72+
}

examples/ai-e2e-next/components/tool/dynamic-tool-with-approval-view.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import type { ChatAddToolApproveResponseFunction, DynamicToolUIPart } from 'ai';
1+
import type {
2+
ChatAddToolApproveResponseFunction,
3+
ChatRequestOptions,
4+
DynamicToolUIPart,
5+
} from 'ai';
26

37
export default function WeatherWithApprovalView({
48
invocation,
59
addToolApprovalResponse,
10+
requestOptions,
611
}: {
712
invocation: DynamicToolUIPart;
813
addToolApprovalResponse: ChatAddToolApproveResponseFunction;
14+
requestOptions?: ChatRequestOptions;
915
}) {
1016
switch (invocation.state) {
1117
case 'approval-requested':
@@ -26,6 +32,7 @@ export default function WeatherWithApprovalView({
2632
addToolApprovalResponse({
2733
id: invocation.approval.id,
2834
approved: true,
35+
options: requestOptions,
2936
})
3037
}
3138
>
@@ -37,6 +44,7 @@ export default function WeatherWithApprovalView({
3744
addToolApprovalResponse({
3845
id: invocation.approval.id,
3946
approved: false,
47+
options: requestOptions,
4048
})
4149
}
4250
>

0 commit comments

Comments
 (0)