import {
Suspense,
useCallback,
useEffect,
useRef,
useState,
type ReactNode
} from "react";
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
import { isToolUIPart, getToolName } from "ai";
import {
Button,
Surface,
Text,
InputArea,
Empty,
Badge
} from "@cloudflare/kumo";
import {
PaperPlaneRightIcon,
TrashIcon,
GearIcon,
WrenchIcon,
GlobeIcon,
MonitorIcon,
CheckCircleIcon,
XCircleIcon,
LightningIcon,
ShieldCheckIcon,
BrowserIcon
} from "@phosphor-icons/react";
import { Streamdown } from "streamdown";
import { DemoWrapper } from "../../layout";
import {
ConnectionStatus,
CodeExplanation,
type CodeSection
} from "../../components";
import { useUserId } from "../../hooks";
const codeSections: CodeSection[] = [
{
title: "Define tools for the AI to call",
description:
"Use the AI SDK's tool() function to define tools with Zod schemas. Tools can run server-side (in the agent) or client-side (in the browser) via executions.",
code: `import { tool } from "ai";
import { z } from "zod";
const tools = {
getWeather: tool({
description: "Get the current weather for a location",
parameters: z.object({
city: z.string().describe("City name"),
}),
execute: async ({ city }) => {
return { temperature: 72, condition: "sunny", city };
},
}),
};`
},
{
title: "Client-side tool execution",
description:
"Some tools need to run in the browser — accessing the DOM, camera, or user interactions. Mark them with executions and handle them on the client with useAgentChat.",
code: `const { messages, addToolResult } = useAgentChat(agent, {
// Handle tool calls that need client-side execution
onToolCall: async ({ toolCall }) => {
if (toolCall.toolName === "getUserLocation") {
const position = await navigator.geolocation.getCurrentPosition();
return { lat: position.coords.latitude, lng: position.coords.longitude };
}
},
});`
}
];
const TOOL_META: Record<
string,
{ icon: ReactNode; label: string; type: string }
> = {
getWeather: {
icon: ,
label: "getWeather",
type: "Server"
},
rollDice: {
icon: ,
label: "rollDice",
type: "Server"
},
getUserTimezone: {
icon: ,
label: "getUserTimezone",
type: "Client"
},
getScreenSize: {
icon: ,
label: "getScreenSize",
type: "Client"
},
calculate: {
icon: ,
label: "calculate",
type: "Approval"
},
deleteFile: {
icon: ,
label: "deleteFile",
type: "Approval"
}
};
function typeBadgeVariant(
type: string
): "secondary" | "primary" | "destructive" {
if (type === "Server") return "secondary";
if (type === "Client") return "primary";
return "destructive";
}
function MessageBubble({
align,
variant,
children
}: {
align: "left" | "right";
variant: "user" | "assistant";
children: ReactNode;
}) {
const base = "max-w-[80%] rounded-2xl overflow-hidden";
const userStyle = `${base} rounded-br-md bg-kumo-contrast text-kumo-inverse`;
const assistantStyle = `${base} rounded-bl-md ring ring-kumo-line`;
return (
{variant === "user" ? (
{children}
) : (
{children}
)}
);
}
function ToolCard({
toolName,
state,
input: toolInput,
output,
approvalId,
onApprove,
onReject
}: {
toolName: string;
state: string;
input?: unknown;
output?: unknown;
approvalId?: string;
onApprove?: (id: string) => void;
onReject?: (id: string) => void;
}) {
const meta = TOOL_META[toolName] ?? {
icon: ,
label: toolName,
type: "Unknown"
};
const isApproval = state === "approval-requested";
const isDone = state === "output-available";
const isDenied = state === "output-denied";
const isError = state === "output-error";
const isRunning = state === "input-available" || state === "input-streaming";
return (
{meta.icon}
{meta.label}
{meta.type}
{isDone && (
Done
)}
{isRunning && (
Running
)}
{isApproval && Needs Approval}
{isDenied && (
Denied
)}
{isError && (
Error
)}
{toolInput != null && (
{JSON.stringify(toolInput, null, 2)}
)}
{isDone && output != null && (
{JSON.stringify(output, null, 2)}
)}
{isApproval && approvalId && onApprove && onReject && (
}
onClick={() => onApprove(approvalId)}
>
Approve
}
onClick={() => onReject(approvalId)}
>
Reject
)}
);
}
function ToolsUI() {
const userId = useUserId();
const [connectionStatus, setConnectionStatus] = useState<
"connected" | "connecting" | "disconnected"
>("connecting");
const [input, setInput] = useState("");
const messagesContainerRef = useRef(null);
const agent = useAgent({
agent: "ToolsAgent",
name: `tools-demo-${userId}`,
onOpen: useCallback(() => setConnectionStatus("connected"), []),
onClose: useCallback(() => setConnectionStatus("disconnected"), []),
onError: useCallback(() => setConnectionStatus("disconnected"), [])
});
const {
messages,
sendMessage,
clearHistory,
addToolApprovalResponse,
status
} = useAgentChat({
agent,
onToolCall: async ({ toolCall, addToolOutput }) => {
if (toolCall.toolName === "getUserTimezone") {
addToolOutput({
toolCallId: toolCall.toolCallId,
output: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
localTime: new Date().toLocaleTimeString()
}
});
}
if (toolCall.toolName === "getScreenSize") {
addToolOutput({
toolCallId: toolCall.toolCallId,
output: {
width: window.innerWidth,
height: window.innerHeight,
devicePixelRatio: window.devicePixelRatio
}
});
}
}
});
const isStreaming = status === "streaming";
const isConnected = connectionStatus === "connected";
useEffect(() => {
const el = messagesContainerRef.current;
if (el) el.scrollTop = el.scrollHeight;
}, [messages]);
const send = useCallback(() => {
const text = input.trim();
if (!text || isStreaming) return;
setInput("");
sendMessage({ role: "user", parts: [{ type: "text", text }] });
}, [input, isStreaming, sendMessage]);
const handleApprove = useCallback(
(id: string) => addToolApprovalResponse({ id, approved: true }),
[addToolApprovalResponse]
);
const handleReject = useCallback(
(id: string) => addToolApprovalResponse({ id, approved: false }),
[addToolApprovalResponse]
);
return (
AI agents can use tools — functions the model calls during a
conversation. Tools can run server-side (inside the agent),
client-side (in the browser, e.g. geolocation), or require human
approval before executing. Define them with Zod schemas for type-safe
argument validation.
>
}
statusIndicator={}
>
{/* Tool legend */}
Server
Auto-executed on server
Client
Runs in your browser
Approval
Needs your confirmation
{/* Messages area */}
{messages.length === 0 && (
}
title="Try the tools"
description={
'Try "What\'s the weather in Tokyo?", "Roll 3d20", ' +
'"What timezone am I in?", "What\'s my screen size?", ' +
'"What is 42 * 38?", "What is 5000 + 3000?", or "Delete /tmp/old.log"'
}
/>
)}
{messages.map((message, index) => {
const isUser = message.role === "user";
const isLastAssistant =
message.role === "assistant" && index === messages.length - 1;
return (
{message.parts.map((part, partIdx) => {
if (part.type === "text") {
if (!part.text || part.text.trim() === "") return null;
if (isUser) {
return (
{part.text}
);
}
return (
{part.text}
);
}
if (part.type === "reasoning") {
if (!part.text || part.text.trim() === "") return null;
return (
Thinking: {part.text}
);
}
if (isToolUIPart(part)) {
const toolName = getToolName(part);
const approvalId =
"approval" in part
? (part.approval as { id?: string })?.id
: undefined;
return (
);
}
return null;
})}
);
})}
{/* Input area */}
);
}
export function ToolsDemo() {
return (
Loading tools demo...
}
>
);
}