Agents
Build AI agents with singular convention-based discovery
Agents
Koze auto-discovers .agent.ts files in src/server/. Each file becomes a first-class Worker export.
// src/server/session.agent.ts
import { Agent } from 'agents';
export class SessionAgent extends Agent {
async onRequest(request: Request) {
return Response.json({ ok: true });
}
}
Naming Convention
Agent files use the .agent.ts suffix (singular). Plural forms such as .agents.ts are rejected at dev/build time with a fix-it message.
| File | Export |
|---|---|
session.agent.ts |
SessionAgent |
chat.agent.ts |
ChatAgent |
assistant.agent.ts |
AssistantAgent |
Agent Lifecycle
Agents extend the base Agent class and implement lifecycle methods:
import { Agent } from 'agents';
export class ChatAgent extends Agent {
// Called on each request
async onRequest(request: Request) {
const url = new URL(request.url);
if (url.pathname === '/chat') {
return this.handleChat(request);
}
return new Response('Not found', { status: 404 });
}
// Called when agent starts
async onStart() {
console.log('Agent started');
}
// Called when agent stops
async onStop() {
console.log('Agent stopped');
}
}
State Management
Agents can maintain state across requests:
import { Agent } from 'agents';
export class CounterAgent extends Agent {
private count = 0;
async onRequest(request: Request) {
if (request.method === 'POST') {
this.count++;
}
return Response.json({ count: this.count });
}
}
WebSocket Support
Agents can handle WebSocket connections:
import { Agent } from 'agents';
export class RealtimeAgent extends Agent {
async onRequest(request: Request) {
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader === 'websocket') {
return this.handleWebSocket(request);
}
return new Response('Expected WebSocket', { status: 400 });
}
async handleWebSocket(request: Request) {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
server.accept();
server.addEventListener('message', (event) => {
server.send(`Echo: ${event.data}`);
});
return new Response(null, {
status: 101,
webSocket: client,
});
}
}
AI Integration
Agents work well with Cloudflare AI:
import { Agent } from 'agents';
export class AssistantAgent extends Agent {
async onRequest(request: Request) {
const { message } = await request.json();
const response = await this.env.AI.run('@cf/meta/llama-2-7b-chat-int8', {
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: message },
],
});
return Response.json({ response: response.response });
}
}
Routing to Agents
Use middleware to route requests to agents:
// src/middleware.ts
import { defineMiddleware } from 'koze:middleware';
export default defineMiddleware({
agents: {
async request(ctx, next) {
if (!ctx.url.pathname.startsWith('/agents/')) {
return next();
}
const agentName = ctx.url.pathname.split('/')[2];
const stub = ctx.env[`${agentName.toUpperCase()}_AGENT`];
if (!stub) {
return new Response('Agent not found', { status: 404 });
}
return stub.fetch(ctx.request);
},
},
});
Wrangler Configuration
Agent files are re-exported from src/worker.ts (via the koze:worker virtual module), but Wrangler needs matching bindings:
{
"durable_objects": {
"bindings": [
{
"name": "SESSION_AGENT",
"class_name": "SessionAgent"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": ["SessionAgent"]
}
]
}