cloudflare
references/agents-sdk/patterns.md
.md 193 lines
Content
# Patterns & Use Cases
## AI Chat w/Tools
**Server (AIChatAgent):**
```ts
import { AIChatAgent } from "agents";
import { openai } from "@ai-sdk/openai";
import { tool } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages, // Auto-managed
tools: {
getWeather: tool({
description: "Get current weather",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => `Weather in ${city}: Sunny, 72°F`
}),
searchDocs: tool({
description: "Search documentation",
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => JSON.stringify(
this.sql<{title, content}>`SELECT title, content FROM docs WHERE content LIKE ${'%' + query + '%'}`
)
})
},
onFinish,
});
}
}
```
**Client (React):**
```tsx
import { useAgent } from "agents/react";
import { useAgentChat } from "agents/ai-react";
function ChatUI() {
const agent = useAgent({ agent: "ChatAgent" });
const { messages, input, handleInputChange, handleSubmit, isLoading } = useAgentChat({ agent });
return (
<div>
{messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} disabled={isLoading} />
<button disabled={isLoading}>Send</button>
</form>
</div>
);
}
```
## Human-in-the-Loop (Client Tools)
Server defines tool, client executes:
```ts
// Server
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages,
tools: {
confirmAction: tool({
description: "Ask user to confirm",
parameters: z.object({ action: z.string() }),
execute: "client", // Client-side execution
})
},
onFinish,
});
}
}
// Client
const { messages } = useAgentChat({
agent,
onToolCall: async (toolCall) => {
if (toolCall.toolName === "confirmAction") {
return { confirmed: window.confirm(`Confirm: ${toolCall.args.action}?`) };
}
}
});
```
## Task Queue & Scheduled Processing
```ts
export class TaskAgent extends Agent<Env> {
onStart() {
this.schedule("*/5 * * * *", "processQueue", {}); // Every 5 min
this.schedule("0 0 * * *", "dailyCleanup", {}); // Daily
}
async onRequest(req: Request) {
await this.queue("processVideo", { videoId: (await req.json()).videoId });
return Response.json({ queued: true });
}
async processQueue() {
const tasks = await this.dequeue(10);
for (const task of tasks) {
if (task.name === "processVideo") await this.processVideo(task.data.videoId);
}
}
async dailyCleanup() {
this.sql`DELETE FROM logs WHERE created_at < ${Date.now() - 86400000}`;
}
}
```
## Manual WebSocket Chat
Custom protocols (non-AI):
```ts
export class ChatAgent extends Agent<Env> {
async onConnect(conn: Connection, ctx: ConnectionContext) {
conn.accept();
conn.setState({userId: ctx.request.headers.get("X-User-ID") || "anon"});
conn.send(JSON.stringify({type: "history", messages: this.state.messages}));
}
async onMessage(conn: Connection, msg: WSMessage) {
const newMsg = {userId: conn.state.userId, text: JSON.parse(msg as string).text, timestamp: Date.now()};
this.setState({messages: [...this.state.messages, newMsg]});
this.connections.forEach(c => c.send(JSON.stringify(newMsg)));
}
}
```
## Email Processing w/AI
```ts
export class EmailAgent extends Agent<Env> {
async onEmail(email: AgentEmail) {
const [text, from, subject] = [await email.text(), email.from, email.headers.get("subject") || ""];
this.sql`INSERT INTO emails (from_addr, subject, body) VALUES (${from}, ${subject}, ${text})`;
const { text: summary } = await generateText({
model: openai("gpt-4o-mini"), prompt: `Summarize: ${subject}\n\n${text}`
});
this.connections.forEach(c => c.send(JSON.stringify({type: "new_email", from, summary})));
if (summary.includes("urgent")) await this.schedule(0, "sendAutoReply", { to: from });
}
}
```
## Real-time Collaboration
```ts
export class GameAgent extends Agent<Env> {
initialState = { players: [], gameStarted: false };
async onConnect(conn: Connection, ctx: ConnectionContext) {
conn.accept();
const playerId = ctx.request.headers.get("X-Player-ID") || crypto.randomUUID();
conn.setState({ playerId });
const newPlayer = { id: playerId, score: 0 };
this.setState({...this.state, players: [...this.state.players, newPlayer]});
this.connections.forEach(c => c.send(JSON.stringify({type: "player_joined", player: newPlayer})));
}
async onMessage(conn: Connection, msg: WSMessage) {
const m = JSON.parse(msg as string);
if (m.type === "move") {
this.setState({
...this.state,
players: this.state.players.map(p => p.id === conn.state.playerId ? {...p, score: p.score + m.points} : p)
});
this.connections.forEach(c => c.send(JSON.stringify({type: "player_moved", playerId: conn.state.playerId})));
}
if (m.type === "start" && this.state.players.length >= 2) {
this.setState({...this.state, gameStarted: true});
this.connections.forEach(c => c.send(JSON.stringify({type: "game_started"})));
}
}
}
```