/fetch https://developers.cloudflare.com/agents/llms-full.txt
Write me a basic Cloudflare Agent.
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Simple Cloudflare Agent Chat</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| #chat-container { | |
| border: 1px solid #ccc; | |
| border-radius: 5px; | |
| height: 400px; | |
| overflow-y: auto; | |
| padding: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .message { | |
| margin-bottom: 10px; | |
| padding: 8px 12px; | |
| border-radius: 5px; | |
| } | |
| .user { | |
| background-color: #e6f7ff; | |
| text-align: right; | |
| margin-left: 20%; | |
| } | |
| .assistant { | |
| background-color: #f0f0f0; | |
| margin-right: 20%; | |
| } | |
| #message-form { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| #message-input { | |
| flex: 1; | |
| padding: 8px; | |
| } | |
| .typing-indicator { | |
| color: #888; | |
| font-style: italic; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Simple Agent Chat</h1> | |
| <div> | |
| <label for="name-input">Your Name:</label> | |
| <input type="text" id="name-input" value="Guest"> | |
| <button id="update-name">Update</button> | |
| </div> | |
| <div id="connection-status">Connecting...</div> | |
| <div id="chat-container"></div> | |
| <form id="message-form"> | |
| <input type="text" id="message-input" placeholder="Type a message..." autocomplete="off"> | |
| <button type="submit">Send</button> | |
| </form> | |
| <script> | |
| // Get the unique agent ID from URL or generate one | |
| const agentId = new URL(window.location.href).searchParams.get('id') || | |
| `user-${Math.random().toString(36).substring(2, 10)}`; | |
| // Add ID to URL without page reload | |
| if (!window.location.href.includes('id=')) { | |
| const url = new URL(window.location.href); | |
| url.searchParams.set('id', agentId); | |
| window.history.pushState({}, '', url); | |
| } | |
| // Set up WebSocket connection to agent | |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; | |
| const wsUrl = `${protocol}//${window.location.host}/agents/my-simple-agent/${agentId}`; | |
| let socket; | |
| let reconnectAttempts = 0; | |
| function connectWebSocket() { | |
| socket = new WebSocket(wsUrl); | |
| socket.onopen = () => { | |
| document.getElementById('connection-status').textContent = 'Connected'; | |
| document.getElementById('connection-status').style.color = 'green'; | |
| reconnectAttempts = 0; | |
| }; | |
| socket.onclose = () => { | |
| document.getElementById('connection-status').textContent = 'Disconnected'; | |
| document.getElementById('connection-status').style.color = 'red'; | |
| // Attempt to reconnect with exponential backoff | |
| if (reconnectAttempts < 5) { | |
| const timeout = Math.pow(2, reconnectAttempts) * 1000; | |
| reconnectAttempts++; | |
| setTimeout(connectWebSocket, timeout); | |
| } | |
| }; | |
| socket.onerror = (error) => { | |
| console.error('WebSocket error:', error); | |
| }; | |
| let typingIndicator = null; | |
| socket.onmessage = (event) => { | |
| try { | |
| const data = JSON.parse(event.data); | |
| switch (data.type) { | |
| case 'state_update': | |
| // Render existing chat history | |
| const chatContainer = document.getElementById('chat-container'); | |
| chatContainer.innerHTML = ''; | |
| data.state.messages.forEach(message => { | |
| addMessageToUI(message.role, message.content); | |
| }); | |
| // Update name input | |
| document.getElementById('name-input').value = data.state.userName; | |
| break; | |
| case 'chunk': | |
| // Handle streaming message chunks | |
| if (!typingIndicator) { | |
| typingIndicator = document.createElement('div'); | |
| typingIndicator.className = 'message assistant typing-indicator'; | |
| typingIndicator.textContent = ''; | |
| document.getElementById('chat-container').appendChild(typingIndicator); | |
| } | |
| typingIndicator.textContent += data.content; | |
| document.getElementById('chat-container').scrollTop = document.getElementById('chat-container').scrollHeight; | |
| break; | |
| case 'done': | |
| // Finalize streaming message | |
| if (typingIndicator) { | |
| typingIndicator.className = 'message assistant'; | |
| typingIndicator = null; | |
| } | |
| break; | |
| case 'error': | |
| console.error('Agent returned error:', data.message); | |
| addMessageToUI('assistant', `Error: ${data.message}`); | |
| break; | |
| case 'name_updated': | |
| console.log(`Name updated to: ${data.name}`); | |
| break; | |
| } | |
| } catch (error) { | |
| console.error('Error processing message:', error); | |
| } | |
| }; | |
| } | |
| function addMessageToUI(role, content) { | |
| const chatContainer = document.getElementById('chat-container'); | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = `message ${role}`; | |
| messageElement.textContent = content; | |
| chatContainer.appendChild(messageElement); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| // Handle form submission | |
| document.getElementById('message-form').addEventListener('submit', (event) => { | |
| event.preventDefault(); | |
| const messageInput = document.getElementById('message-input'); | |
| const message = messageInput.value.trim(); | |
| if (message && socket.readyState === WebSocket.OPEN) { | |
| // Add message to UI immediately | |
| addMessageToUI('user', message); | |
| // Send to agent | |
| socket.send(JSON.stringify({ | |
| type: 'chat_message', | |
| content: message | |
| })); | |
| // Clear input | |
| messageInput.value = ''; | |
| } | |
| }); | |
| // Handle name updates | |
| document.getElementById('update-name').addEventListener('click', () => { | |
| const nameInput = document.getElementById('name-input'); | |
| const name = nameInput.value.trim(); | |
| if (name && socket.readyState === WebSocket.OPEN) { | |
| socket.send(JSON.stringify({ | |
| type: 'set_name', | |
| name: name | |
| })); | |
| } | |
| }); | |
| // Initial connection | |
| connectWebSocket(); | |
| </script> | |
| </body> | |
| </html> |
| import { Agent, Connection, ConnectionContext, WSMessage, routeAgentRequest } from "agents-sdk"; | |
| import { OpenAI } from "openai"; | |
| // Define environment type with our needed bindings | |
| interface Env { | |
| OPENAI_API_KEY: string; | |
| MySimpleAgent: any; | |
| } | |
| // Define chat message type for our state | |
| interface ChatMessage { | |
| role: "user" | "assistant"; | |
| content: string; | |
| timestamp: number; | |
| } | |
| // Define our agent state structure | |
| interface AgentState { | |
| messages: ChatMessage[]; | |
| userName: string; | |
| } | |
| // Our Agent implementation | |
| export class MySimpleAgent extends Agent<Env, AgentState> { | |
| // Initialize state when the agent is first created | |
| async onStart() { | |
| if (!this.state) { | |
| await this.setState({ | |
| messages: [], | |
| userName: "Guest", | |
| }); | |
| } | |
| console.log('Agent started with ID:', this.id); | |
| } | |
| // Handle WebSocket connections | |
| async onConnect(connection: Connection, ctx: ConnectionContext) { | |
| console.log("New client connected:", connection.id); | |
| // You could authenticate users here with tokens from request headers | |
| // const token = ctx.request.headers.get('Authorization'); | |
| // Accept the connection | |
| connection.accept(); | |
| // Send the current state to the newly connected client | |
| connection.send(JSON.stringify({ | |
| type: "state_update", | |
| state: this.state | |
| })); | |
| } | |
| // Handle incoming WebSocket messages | |
| async onMessage(connection: Connection, message: WSMessage) { | |
| try { | |
| const data = JSON.parse(message as string); | |
| // Handle different message types | |
| switch (data.type) { | |
| case "chat_message": | |
| await this.handleChatMessage(connection, data.content); | |
| break; | |
| case "set_name": | |
| await this.updateUserName(connection, data.name); | |
| break; | |
| default: | |
| connection.send(JSON.stringify({ | |
| type: "error", | |
| message: "Unknown message type" | |
| })); | |
| } | |
| } catch (error) { | |
| console.error("Error processing message:", error); | |
| connection.send(JSON.stringify({ | |
| type: "error", | |
| message: "Failed to process message" | |
| })); | |
| } | |
| } | |
| // Handle WebSocket errors | |
| async onError(connection: Connection, error: unknown): Promise<void> { | |
| console.error(`WebSocket error for client ${connection.id}:`, error); | |
| } | |
| // Handle WebSocket close | |
| async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise<void> { | |
| console.log(`Client ${connection.id} disconnected. Code: ${code}, Reason: ${reason}, Clean: ${wasClean}`); | |
| } | |
| // Handle HTTP requests directly | |
| async onRequest(request: Request): Promise<Response> { | |
| const url = new URL(request.url); | |
| // Simple API endpoint to get chat history | |
| if (url.pathname.endsWith('/history')) { | |
| return Response.json({ | |
| messages: this.state.messages | |
| }); | |
| } | |
| // Default response | |
| return new Response("Agent is running. Connect via WebSocket for chat functionality.", { | |
| headers: { "Content-Type": "text/plain" } | |
| }); | |
| } | |
| // Handle state updates | |
| async onStateUpdate(state: AgentState): Promise<void> { | |
| console.log("State updated:", state); | |
| } | |
| // Custom method to handle chat messages | |
| private async handleChatMessage(connection: Connection, content: string) { | |
| // Add the user message to state | |
| const userMessage: ChatMessage = { | |
| role: "user", | |
| content, | |
| timestamp: Date.now() | |
| }; | |
| // Update state with new user message | |
| await this.setState({ | |
| ...this.state, | |
| messages: [...this.state.messages, userMessage] | |
| }); | |
| // Generate a response using OpenAI | |
| try { | |
| const client = new OpenAI({ | |
| apiKey: this.env.OPENAI_API_KEY, | |
| }); | |
| // Start a stream for real-time responses | |
| const stream = await client.chat.completions.create({ | |
| model: "gpt-3.5-turbo", | |
| messages: [ | |
| { role: "system", content: `You are a helpful assistant named SimpleAgent. You're chatting with ${this.state.userName}.` }, | |
| ...this.state.messages.map(msg => ({ role: msg.role, content: msg.content })) | |
| ], | |
| stream: true, | |
| }); | |
| // Buffer to accumulate the complete response | |
| let assistantResponse = ''; | |
| // Stream chunks back to the client | |
| for await (const chunk of stream) { | |
| const content = chunk.choices[0]?.delta?.content || ''; | |
| if (content) { | |
| assistantResponse += content; | |
| // Send streaming updates | |
| connection.send(JSON.stringify({ | |
| type: "chunk", | |
| content | |
| })); | |
| } | |
| } | |
| // Create the complete message | |
| const assistantMessage: ChatMessage = { | |
| role: "assistant", | |
| content: assistantResponse, | |
| timestamp: Date.now() | |
| }; | |
| // Update state with assistant response | |
| await this.setState({ | |
| ...this.state, | |
| messages: [...this.state.messages, assistantMessage] | |
| }); | |
| // Send completion signal | |
| connection.send(JSON.stringify({ | |
| type: "done" | |
| })); | |
| } catch (error) { | |
| console.error("Error generating response:", error); | |
| connection.send(JSON.stringify({ | |
| type: "error", | |
| message: "Failed to generate response" | |
| })); | |
| } | |
| } | |
| // Custom method to update the user name | |
| private async updateUserName(connection: Connection, name: string) { | |
| if (typeof name === 'string' && name.trim()) { | |
| await this.setState({ | |
| ...this.state, | |
| userName: name.trim() | |
| }); | |
| connection.send(JSON.stringify({ | |
| type: "name_updated", | |
| name: name.trim() | |
| })); | |
| } else { | |
| connection.send(JSON.stringify({ | |
| type: "error", | |
| message: "Invalid name provided" | |
| })); | |
| } | |
| } | |
| // Helper method to schedule a reminder | |
| async scheduleReminder(minutes: number, message: string) { | |
| await this.schedule(minutes * 60, "sendReminder", { message }); | |
| } | |
| // Method called by the scheduler | |
| async sendReminder(data: { message: string }) { | |
| // Add reminder to chat history | |
| const reminderMessage: ChatMessage = { | |
| role: "assistant", | |
| content: `REMINDER: ${data.message}`, | |
| timestamp: Date.now() | |
| }; | |
| await this.setState({ | |
| ...this.state, | |
| messages: [...this.state.messages, reminderMessage] | |
| }); | |
| // Broadcast to all connected clients | |
| // In a real implementation, you would track active connections | |
| } | |
| } | |
| // Worker entry point | |
| export default { | |
| async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { | |
| // Route requests to the appropriate agent instance | |
| return (await routeAgentRequest(request, env)) || | |
| new Response("Not Found", { status: 404 }); | |
| } | |
| }; |
/fetch https://developers.cloudflare.com/agents/llms-full.txt
Write me a basic Cloudflare Agent.
| { | |
| "$schema": "https://json.schemastore.org/wrangler.json", | |
| "name": "my-simple-agent", | |
| "main": "src/index.ts", | |
| "compatibility_date": "2024-03-01", | |
| "compatibility_flags": ["nodejs_compat"], | |
| "durable_objects": { | |
| "bindings": [ | |
| { | |
| "name": "MySimpleAgent", | |
| "class_name": "MySimpleAgent" | |
| } | |
| ] | |
| }, | |
| "migrations": [ | |
| { | |
| "tag": "v1", | |
| "new_classes": ["MySimpleAgent"], | |
| "new_sqlite_classes": ["MySimpleAgent"] | |
| } | |
| ], | |
| "vars": { | |
| "OPENAI_API_KEY": "" | |
| }, | |
| "observability": { | |
| "enabled": true | |
| } | |
| } |