/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 | |
} | |
} |