Curated Skills
by lstudlo

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"})));
    }
  }
}
```