Once a request reaches your Durable Object, the Lifecycle offers it to your capabilities, then calls your onRequest(). Use it for an HTTP API, or to stream a response with Server-Sent Events (SSE).
The object receives the full URL, so match on the end of the path:
import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
export class ApiAgent extends DurableObject {
lifecycle = Lifecycle.install(this);
async onRequest(request) {
const url = new URL(request.url);
if (url.pathname.endsWith("/status")) {
return Response.json({ name: this.lifecycle.name, status: "ok" });
}
if (url.pathname.endsWith("/action")) {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const { action } = await request.json();
await this.ctx.storage.put("lastAction", action);
return Response.json({ success: true });
}
return new Response("Not found", { status: 404 });
}
}import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
export class ApiAgent extends DurableObject<Env> {
readonly lifecycle = Lifecycle.install(this);
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname.endsWith("/status")) {
return Response.json({ name: this.lifecycle.name, status: "ok" });
}
if (url.pathname.endsWith("/action")) {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const { action } = await request.json<{ action: string }>();
await this.ctx.storage.put("lastAction", action);
return Response.json({ success: true });
}
return new Response("Not found", { status: 404 });
}
}Without onRequest(), a request that no capability handles returns 404. To reach the object from your Worker, refer to Routing.
Return a streaming response from onRequest(). The AI SDK builds one for you:
import { DurableObject } from "cloudflare:workers";
import { streamText } from "ai";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/ai-sdk";
export class ChatAgent extends DurableObject {
lifecycle = Lifecycle.install(this);
async onRequest(request) {
const { prompt } = await request.json();
const ai = createAI({ binding: this.env.AI });
const result = streamText({
model: ai("anthropic/claude-opus-5.5"),
prompt,
});
return result.toTextStreamResponse();
}
}import { DurableObject } from "cloudflare:workers";
import { streamText } from "ai";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/ai-sdk";
export class ChatAgent extends DurableObject<Env> {
readonly lifecycle = Lifecycle.install(this);
async onRequest(request: Request): Promise<Response> {
const { prompt } = await request.json<{ prompt: string }>();
const ai = createAI({ binding: this.env.AI });
const result = streamText({
model: ai("anthropic/claude-opus-5.5"),
prompt,
});
return result.toTextStreamResponse();
}
}To write SSE yourself, return a ReadableStream with the text/event-stream content type. Each event is one or more field: value lines followed by a blank line:
event: update
id: 42
data: {"count": 42}A stream from onRequest() ends when the client disconnects, and the client loses what it missed. The Streams capability stores each chunk, and sseResponse() serves a stored stream as SSE. A browser EventSource that reconnects sends Last-Event-ID, and the response continues from the next chunk:
import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { Streams, sseResponse } from "agents/streams";
export class ReportAgent extends DurableObject {
streams = new Streams();
lifecycle = Lifecycle.install(this).use(this.streams);
async onRequest(request) {
const id = new URL(request.url).searchParams.get("id") ?? "report";
return sseResponse(this.streams, id, { request });
}
}import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { Streams, sseResponse } from "agents/streams";
export class ReportAgent extends DurableObject<Env> {
readonly streams = new Streams();
readonly lifecycle = Lifecycle.install(this).use(this.streams);
async onRequest(request: Request): Promise<Response> {
const id = new URL(request.url).searchParams.get("id") ?? "report";
return sseResponse(this.streams, id, { request });
}
}Streams is experimental. For producing the chunks, refer to Streams.
| Feature | WebSockets | SSE |
|---|---|---|
| Direction | Both ways | Server to client |
| Protocol | ws:// or wss:// |
HTTP |
| Binary data | Yes | No, text only |
| Reconnection | Handled by useAgent and AgentClient |
Handled by the browser |
| Best for | Interactive apps, chat, callables | Streaming replies, notifications |
For WebSockets, install the WebSockets capability.
The Agent class calls the same onRequest(request) method, so these examples work on an Agent with extends Agent<Env> in place of the Lifecycle setup.