This site is not affiliated with or endorsed by Cloudflare, Inc. It simply showcases experiments built using Cloudflare services.
Cloudflare Experiments
AI & Machine Learning

Chat Agent

Durable AI chat agent with SQLite-backed Durable Objects and optional Workers AI

Minimal durable AI chat agent using a SQLite-backed Durable Object. Workers AI is optional - without an AI binding the agent echoes agent: <message>.

Features

  • Per-session chat history in Durable Object SQLite
  • POST /chat for request/response turns
  • GET /chat for history
  • WebSocket upgrade on GET /ws for realtime updates
  • Optional Workers AI replies when the AI binding is available

API Reference

POST /chat

Send a user message and receive the updated message list (including the assistant reply).

sessionId string (required)

Session id (letters, numbers, _, -).

message string (required)

User message (max 4,000 characters).

Example Request

curl -X POST "https://your-worker.workers.dev/chat" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"demo","message":"Hello"}'

Success Response

{
  "sessionId": "demo",
  "messages": [
    { "role": "user", "content": "Hello" },
    { "role": "assistant", "content": "agent: Hello" }
  ]
}

GET /chat

Return chat history for a session.

sessionId string (required)

Example Request

curl "https://your-worker.workers.dev/chat?sessionId=demo"

GET /ws

WebSocket upgrade for realtime message updates. Send { "message": "..." } over the socket; receive { "type": "messages", "messages": [...] }.

sessionId string (required)

Requires Upgrade: websocket.

Error Codes

  • 400 - Invalid session, message, body, or missing WebSocket upgrade (INVALID_SESSION, INVALID_MESSAGE, INVALID_BODY, EXPECTED_WEBSOCKET)
  • 502 - Chat or Durable Object failure (CHAT_ERROR)

Use Cases

  • Learn Agents / Durable Object chat session patterns
  • Prototype multi-turn AI assistants with persistent history
  • Add WebSocket streaming alongside HTTP chat APIs
  • Compare stub echo replies vs Workers AI generation

Limitations

  • Without Workers AI, replies use the stub format agent: <message>
  • Message max 4,000 characters; no auth on sessions
  • Demo is single-agent per session id, not multi-user rooms
  • WebSocket clients must send JSON { "message": "..." }

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/chat-agent.

DependenciesNoneBindingsWorkers AI (AI binding), Durable ObjectsPlatformWebSockets, Fetch API, Workers AI, Durable Objects
import { AI_MODEL, MAX_MESSAGE_LENGTH, SESSION_ID_PATTERN } from "../constants/defaults";import type { ChatHistoryResponse, ChatMessage, ChatPostResponse } from "../types/chat";import type { Env } from "../types/env";export function validateSessionId(input: string | undefined): string | null {  if (!input || typeof input !== "string") return null;  const trimmed = input.trim();  if (!SESSION_ID_PATTERN.test(trimmed)) return null;  return trimmed;}export function validateMessage(input: string | undefined): string | null {  if (typeof input !== "string") return null;  const trimmed = input.trim();  if (!trimmed || trimmed.length > MAX_MESSAGE_LENGTH) return null;  return trimmed;}export function stubAgentReply(message: string): string {  return `agent: ${message}`;}type AiTextResponse = {  response?: string;};export async function generateReply(ai: Ai | undefined, message: string): Promise<string> {  if (!ai) {    return stubAgentReply(message);  }  try {    const result = (await ai.run(AI_MODEL, {      messages: [{ role: "user", content: message }],    })) as AiTextResponse;    const response = result.response?.trim();    if (response) return response;  } catch {    // Fall through to stub reply when AI is unavailable.  }  return stubAgentReply(message);}export function getChatStub(env: Env, sessionId: string): DurableObjectStub {  const id = env.CHAT_AGENT.idFromName(sessionId);  return env.CHAT_AGENT.get(id);}export async function postChatMessage(  env: Env,  sessionId: string,  message: string): Promise<ChatPostResponse> {  const stub = getChatStub(env, sessionId);  const response = await stub.fetch("https://chat-agent/chat", {    method: "POST",    headers: { "Content-Type": "application/json" },    body: JSON.stringify({ message }),  });  if (!response.ok) {    throw new Error(`Chat post failed with status ${response.status}`);  }  return (await response.json()) as ChatPostResponse;}export async function getChatHistory(env: Env, sessionId: string): Promise<ChatHistoryResponse> {  const stub = getChatStub(env, sessionId);  const response = await stub.fetch("https://chat-agent/chat");  if (!response.ok) {    throw new Error(`Chat history failed with status ${response.status}`);  }  return (await response.json()) as ChatHistoryResponse;}export async function upgradeChatWebSocket(  env: Env,  sessionId: string,  request: Request): Promise<Response> {  const stub = getChatStub(env, sessionId);  return stub.fetch("https://chat-agent/ws", request);}export function mapSqlRowsToMessages(  rows: Array<{ id: number; role: string; content: string; created_at: string }>): ChatMessage[] {  return rows.map((row) => ({    id: row.id,    role: row.role === "assistant" ? "assistant" : "user",    content: row.content,    createdAt: row.created_at,  }));}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Deploy

Wrangler binds CHAT_AGENT to class ChatAgent (SQLite migration v1) and AI for Workers AI.

Test your deployment

curl -X POST "https://your-worker.workers.dev/chat" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"demo","message":"Hello"}'

Local Development

cd apps/experiments/chat-agent
npm install
npm run dev
curl -X POST "http://localhost:8787/chat" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"demo","message":"Hello"}'

AI replies require a Workers AI binding. Without it, replies use the stub format.

Configuration

wrangler.json declares:

  • Durable Object binding CHAT_AGENT → ChatAgent
  • Migration v1 with new_sqlite_classes: ["ChatAgent"]
  • Workers AI binding AI

Cloudflare Features Used

On this page