agents 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/{agent-tool-types-NofdbL9X.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +595 -137
- package/dist/agent-tool-types.d.ts +34 -18
- package/dist/agent-tool-types.js +20 -1
- package/dist/agent-tool-types.js.map +1 -0
- package/dist/agent-tools-BXlsuX0d.js +304 -0
- package/dist/agent-tools-BXlsuX0d.js.map +1 -0
- package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
- package/dist/agent-tools.d.ts +24 -18
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +1 -2
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +1 -1
- package/dist/ai-chat-v5-migration.js +1 -2
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +1 -7
- package/dist/ai-types.js +2 -8
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +1923 -76
- package/dist/chat/index.js +1784 -278
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +602 -0
- package/dist/chat/react.js +1506 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
- package/dist/{client-FUizKzj2.js → client-BZ-B3NhC.js} +19 -3
- package/dist/client-BZ-B3NhC.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +76 -57
- package/dist/client.js +33 -5
- package/dist/client.js.map +1 -1
- package/dist/{compaction-helpers-DVcu5lPN.d.ts → compaction-helpers-wUz6M3us.d.ts} +20 -1
- package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
- package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
- package/dist/email.js +1 -1
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +1 -1
- package/dist/experimental/memory/session/index.js +20 -2
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +1 -1
- package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
- package/dist/index.d.ts +91 -71
- package/dist/index.js +562 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +30 -30
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
- package/dist/message-builder-BymO4N_D.js.map +1 -0
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +123 -110
- package/dist/react.js +44 -11
- package/dist/react.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/compile.js +1 -1
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/index.js +5 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js +5 -5
- package/dist/vite.js.map +1 -1
- package/dist/wire-types-nflOzNuU.js +240 -0
- package/dist/wire-types-nflOzNuU.js.map +1 -0
- package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
- package/dist/workflow-types.d.ts +25 -21
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -21
- package/dist/workflows.js +31 -7
- package/dist/workflows.js.map +1 -1
- package/docs/adding-to-existing-project.md +450 -0
- package/docs/agent-class.md +503 -0
- package/docs/agent-tools.md +552 -0
- package/docs/browse-the-web.md +430 -0
- package/docs/callable-methods.md +627 -0
- package/docs/chat-agents.md +1687 -0
- package/docs/chat-sdk.md +181 -0
- package/docs/client-sdk.md +520 -0
- package/docs/client-tools-continuation.md +177 -0
- package/docs/codemode.md +440 -0
- package/docs/configuration.md +775 -0
- package/docs/cross-domain-authentication.md +171 -0
- package/docs/durable-execution.md +537 -0
- package/docs/email.md +663 -0
- package/docs/get-current-agent.md +204 -0
- package/docs/getting-started.md +305 -0
- package/docs/http-websockets.md +668 -0
- package/docs/human-in-the-loop.md +661 -0
- package/docs/index.md +151 -0
- package/docs/long-running-agents.md +730 -0
- package/docs/mcp-client.md +620 -0
- package/docs/mcp-servers.md +526 -0
- package/docs/mcp-transports.md +308 -0
- package/docs/migration-to-ai-sdk-v5.md +96 -0
- package/docs/migration-to-ai-sdk-v6.md +163 -0
- package/docs/observability.md +261 -0
- package/docs/push-notifications.md +367 -0
- package/docs/queue.md +329 -0
- package/docs/readonly-connections.md +278 -0
- package/docs/resumable-streaming.md +127 -0
- package/docs/retries.md +444 -0
- package/docs/routing.md +749 -0
- package/docs/scheduling.md +898 -0
- package/docs/securing-mcp-servers.md +359 -0
- package/docs/server-driven-messages.md +477 -0
- package/docs/sessions.md +1024 -0
- package/docs/state.md +512 -0
- package/docs/sub-agents.md +389 -0
- package/docs/webhooks.md +604 -0
- package/docs/workflows.md +877 -0
- package/package.json +47 -15
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DLquv-dp.d.ts +0 -14
- package/dist/client-FUizKzj2.js.map +0 -1
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
# HTTP & WebSockets
|
|
2
|
+
|
|
3
|
+
Agents handle both HTTP requests and WebSocket connections, giving you flexibility to build REST APIs, real-time applications, or hybrid architectures.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Every agent can respond to:
|
|
8
|
+
|
|
9
|
+
- **HTTP requests** via `onRequest()` - REST APIs, webhooks, file uploads
|
|
10
|
+
- **WebSocket connections** via `onConnect()`, `onMessage()`, `onClose()` - Real-time bidirectional communication
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Agent } from "agents";
|
|
14
|
+
|
|
15
|
+
export class MyAgent extends Agent {
|
|
16
|
+
// Handle HTTP requests
|
|
17
|
+
onRequest(request: Request): Response {
|
|
18
|
+
return new Response("Hello from HTTP!");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Handle WebSocket connections
|
|
22
|
+
onConnect(connection: Connection, ctx: ConnectionContext) {
|
|
23
|
+
connection.send("Welcome!");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onMessage(connection: Connection, message: WSMessage) {
|
|
27
|
+
// Echo back
|
|
28
|
+
connection.send(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Lifecycle Hooks
|
|
34
|
+
|
|
35
|
+
Agents have several lifecycle hooks that are called at different points:
|
|
36
|
+
|
|
37
|
+
| Hook | When Called |
|
|
38
|
+
| --------------------------------------------- | --------------------------------------------------------- |
|
|
39
|
+
| `onStart(props?)` | Once when the agent first starts (before any connections) |
|
|
40
|
+
| `onRequest(request)` | When an HTTP request is received (non-WebSocket) |
|
|
41
|
+
| `onConnect(connection, ctx)` | When a new WebSocket connection is established |
|
|
42
|
+
| `onMessage(connection, message)` | When a WebSocket message is received |
|
|
43
|
+
| `onClose(connection, code, reason, wasClean)` | When a WebSocket connection closes |
|
|
44
|
+
| `onError(connection, error)` | When a WebSocket error occurs |
|
|
45
|
+
| `onError(error)` | When a server error occurs (overloaded) |
|
|
46
|
+
|
|
47
|
+
### Lifecycle Flow
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Agent Created
|
|
51
|
+
↓
|
|
52
|
+
onStart() ←── Called once, before any connections
|
|
53
|
+
↓
|
|
54
|
+
┌─────────────────────────────────────┐
|
|
55
|
+
│ For each request: │
|
|
56
|
+
│ │
|
|
57
|
+
│ HTTP Request ──→ onRequest() │
|
|
58
|
+
│ │
|
|
59
|
+
│ WebSocket ──→ onConnect() │
|
|
60
|
+
│ ↓ │
|
|
61
|
+
│ Messages ──→ onMessage() (repeat) │
|
|
62
|
+
│ ↓ │
|
|
63
|
+
│ Disconnect ──→ onClose() │
|
|
64
|
+
└─────────────────────────────────────┘
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## HTTP Requests
|
|
68
|
+
|
|
69
|
+
Handle HTTP requests with `onRequest()`. This is called for any non-WebSocket request to your agent.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
export class ApiAgent extends Agent {
|
|
73
|
+
async onRequest(request: Request): Promise<Response> {
|
|
74
|
+
const url = new URL(request.url);
|
|
75
|
+
|
|
76
|
+
// Route by path
|
|
77
|
+
if (url.pathname.endsWith("/status")) {
|
|
78
|
+
return Response.json({
|
|
79
|
+
status: "ok",
|
|
80
|
+
connections: this.getConnections().length
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (url.pathname.endsWith("/data") && request.method === "POST") {
|
|
85
|
+
const data = await request.json();
|
|
86
|
+
// Process data...
|
|
87
|
+
return Response.json({ received: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new Response("Not found", { status: 404 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Common HTTP Patterns
|
|
96
|
+
|
|
97
|
+
**REST API with CORS:**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
onRequest(request: Request): Response {
|
|
101
|
+
// Handle preflight
|
|
102
|
+
if (request.method === "OPTIONS") {
|
|
103
|
+
return new Response(null, {
|
|
104
|
+
headers: {
|
|
105
|
+
"Access-Control-Allow-Origin": "*",
|
|
106
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
107
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return Response.json(
|
|
113
|
+
{ data: "..." },
|
|
114
|
+
{ headers: { "Access-Control-Allow-Origin": "*" } }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**File Upload:**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
async onRequest(request: Request): Promise<Response> {
|
|
123
|
+
if (request.method === "POST") {
|
|
124
|
+
const formData = await request.formData();
|
|
125
|
+
const file = formData.get("file") as File;
|
|
126
|
+
|
|
127
|
+
// Process file...
|
|
128
|
+
const content = await file.text();
|
|
129
|
+
|
|
130
|
+
return Response.json({ filename: file.name, size: file.size });
|
|
131
|
+
}
|
|
132
|
+
return new Response("Method not allowed", { status: 405 });
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## WebSocket Connections
|
|
137
|
+
|
|
138
|
+
WebSockets enable real-time bidirectional communication between clients and your agent.
|
|
139
|
+
|
|
140
|
+
### Connection Lifecycle
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
export class ChatAgent extends Agent {
|
|
144
|
+
// Called when a client connects
|
|
145
|
+
onConnect(connection: Connection, ctx: ConnectionContext) {
|
|
146
|
+
// ctx.request contains the original HTTP request (for auth, headers, etc.)
|
|
147
|
+
const url = new URL(ctx.request.url);
|
|
148
|
+
const token = url.searchParams.get("token");
|
|
149
|
+
|
|
150
|
+
console.log(`Client ${connection.id} connected`);
|
|
151
|
+
connection.send(JSON.stringify({ type: "welcome", id: connection.id }));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Called for each message from this connection
|
|
155
|
+
onMessage(connection: Connection, message: WSMessage) {
|
|
156
|
+
if (typeof message === "string") {
|
|
157
|
+
const data = JSON.parse(message);
|
|
158
|
+
// Handle message...
|
|
159
|
+
} else {
|
|
160
|
+
// Binary message (ArrayBuffer)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Called when connection closes
|
|
165
|
+
onClose(
|
|
166
|
+
connection: Connection,
|
|
167
|
+
code: number,
|
|
168
|
+
reason: string,
|
|
169
|
+
wasClean: boolean
|
|
170
|
+
) {
|
|
171
|
+
console.log(`Client ${connection.id} disconnected: ${code} ${reason}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Called on WebSocket errors
|
|
175
|
+
onError(connection: Connection, error: unknown) {
|
|
176
|
+
console.error(`Error on connection ${connection.id}:`, error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### The Connection Object
|
|
182
|
+
|
|
183
|
+
Each WebSocket connection is represented by a `Connection` object:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface Connection<TState = unknown> {
|
|
187
|
+
/** Unique connection identifier */
|
|
188
|
+
id: string;
|
|
189
|
+
|
|
190
|
+
/** The agent instance name this connection belongs to */
|
|
191
|
+
server: string;
|
|
192
|
+
|
|
193
|
+
/** Per-connection state (read-only, use setState to update) */
|
|
194
|
+
state: TState | null;
|
|
195
|
+
|
|
196
|
+
/** Update connection state */
|
|
197
|
+
setState(state: TState | ((prev: TState | null) => TState)): void;
|
|
198
|
+
|
|
199
|
+
/** Send a message to this connection */
|
|
200
|
+
send(message: string | ArrayBuffer): void;
|
|
201
|
+
|
|
202
|
+
/** Close this connection */
|
|
203
|
+
close(code?: number, reason?: string): void;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Message Types
|
|
208
|
+
|
|
209
|
+
Messages can be strings or binary:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
onMessage(connection: Connection, message: WSMessage) {
|
|
213
|
+
if (typeof message === "string") {
|
|
214
|
+
// Text message - usually JSON
|
|
215
|
+
const data = JSON.parse(message);
|
|
216
|
+
this.handleTextMessage(connection, data);
|
|
217
|
+
} else {
|
|
218
|
+
// Binary message - ArrayBuffer or ArrayBufferView
|
|
219
|
+
this.handleBinaryMessage(connection, message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Connection Management
|
|
225
|
+
|
|
226
|
+
### Getting Connections
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// Get all connections
|
|
230
|
+
const connections = this.getConnections();
|
|
231
|
+
|
|
232
|
+
// Get a specific connection by ID
|
|
233
|
+
const connection = this.getConnection("abc123");
|
|
234
|
+
|
|
235
|
+
// Get connections with a specific tag
|
|
236
|
+
const adminConnections = this.getConnections("admin");
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Broadcasting
|
|
240
|
+
|
|
241
|
+
Send a message to all connected clients:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Broadcast to everyone
|
|
245
|
+
this.broadcast(JSON.stringify({ type: "update", data: "..." }));
|
|
246
|
+
|
|
247
|
+
// Broadcast to everyone except specific connections
|
|
248
|
+
this.broadcast(
|
|
249
|
+
JSON.stringify({ type: "user-typing", userId: "123" }),
|
|
250
|
+
["connection-id-to-exclude"] // Don't send to the originator
|
|
251
|
+
);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Connection Tags
|
|
255
|
+
|
|
256
|
+
Tag connections for easy filtering. Override `getConnectionTags()` to assign tags:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
export class ChatAgent extends Agent {
|
|
260
|
+
// Called when a connection is established
|
|
261
|
+
getConnectionTags(connection: Connection, ctx: ConnectionContext): string[] {
|
|
262
|
+
const url = new URL(ctx.request.url);
|
|
263
|
+
const role = url.searchParams.get("role");
|
|
264
|
+
|
|
265
|
+
const tags: string[] = [];
|
|
266
|
+
if (role === "admin") tags.push("admin");
|
|
267
|
+
if (role === "moderator") tags.push("moderator");
|
|
268
|
+
|
|
269
|
+
return tags; // Up to 9 tags, max 256 chars each
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Later, broadcast only to admins
|
|
273
|
+
notifyAdmins(message: string) {
|
|
274
|
+
for (const conn of this.getConnections("admin")) {
|
|
275
|
+
conn.send(message);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Per-Connection State
|
|
282
|
+
|
|
283
|
+
Store data specific to each connection using `connection.state` and `connection.setState()`:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
type ConnectionState = {
|
|
287
|
+
username: string;
|
|
288
|
+
joinedAt: number;
|
|
289
|
+
messageCount: number;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export class ChatAgent extends Agent {
|
|
293
|
+
onConnect(connection: Connection<ConnectionState>, ctx: ConnectionContext) {
|
|
294
|
+
const url = new URL(ctx.request.url);
|
|
295
|
+
|
|
296
|
+
// Initialize connection state
|
|
297
|
+
connection.setState({
|
|
298
|
+
username: url.searchParams.get("username") || "Anonymous",
|
|
299
|
+
joinedAt: Date.now(),
|
|
300
|
+
messageCount: 0
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
onMessage(connection: Connection<ConnectionState>, message: WSMessage) {
|
|
305
|
+
// Update message count using functional update
|
|
306
|
+
connection.setState((prev) => ({
|
|
307
|
+
...prev!,
|
|
308
|
+
messageCount: (prev?.messageCount || 0) + 1
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
// Access current state
|
|
312
|
+
const { username, messageCount } = connection.state!;
|
|
313
|
+
console.log(`${username} sent message #${messageCount}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Important:** Connection state is:
|
|
319
|
+
|
|
320
|
+
- **Immutable** - Read via `connection.state`, update via `connection.setState()`
|
|
321
|
+
- **Per-connection** - Each connection has its own state
|
|
322
|
+
- **Persisted across hibernation** - Survives agent sleep/wake cycles
|
|
323
|
+
|
|
324
|
+
## The `onStart` Hook
|
|
325
|
+
|
|
326
|
+
`onStart()` is called once when the agent first starts, before any connections are established:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
export class MyAgent extends Agent {
|
|
330
|
+
private cache: Map<string, unknown> = new Map();
|
|
331
|
+
|
|
332
|
+
async onStart() {
|
|
333
|
+
// Initialize resources
|
|
334
|
+
console.log(`Agent ${this.name} starting...`);
|
|
335
|
+
|
|
336
|
+
// Load data from storage
|
|
337
|
+
const savedData = this.sql`SELECT * FROM cache`;
|
|
338
|
+
for (const row of savedData) {
|
|
339
|
+
this.cache.set(row.key, row.value);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Restore MCP connections, check workflows, etc.
|
|
343
|
+
// (Agent does this automatically, but you can add custom logic)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
onConnect(connection: Connection) {
|
|
347
|
+
// By the time connections arrive, onStart has completed
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Protocol Message Control
|
|
353
|
+
|
|
354
|
+
By default, when a WebSocket client connects, the agent sends protocol text frames (`CF_AGENT_IDENTITY`, `CF_AGENT_STATE`, `CF_AGENT_MCP_SERVERS`) to keep the client in sync. You can suppress these on a per-connection basis by overriding `shouldSendProtocolMessages`:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
export class MyAgent extends Agent {
|
|
358
|
+
shouldSendProtocolMessages(
|
|
359
|
+
connection: Connection,
|
|
360
|
+
ctx: ConnectionContext
|
|
361
|
+
): boolean {
|
|
362
|
+
// Suppress protocol frames for binary-only clients
|
|
363
|
+
const url = new URL(ctx.request.url);
|
|
364
|
+
return url.searchParams.get("protocol") !== "false";
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
When `shouldSendProtocolMessages` returns `false` for a connection:
|
|
370
|
+
|
|
371
|
+
- No `CF_AGENT_IDENTITY`, `CF_AGENT_STATE`, or `CF_AGENT_MCP_SERVERS` frames are sent on connect
|
|
372
|
+
- The connection is excluded from protocol broadcasts (state updates, MCP server changes)
|
|
373
|
+
- Regular messages via `connection.send()` and `this.broadcast()` still work normally
|
|
374
|
+
|
|
375
|
+
This is useful for IoT devices, binary-only clients, or lightweight consumers that only need raw messages.
|
|
376
|
+
|
|
377
|
+
### Checking Protocol Status
|
|
378
|
+
|
|
379
|
+
Use `isConnectionProtocolEnabled` to check whether a connection receives protocol messages:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const enabled = this.isConnectionProtocolEnabled(connection);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
This status persists across hibernation — a connection that was marked as no-protocol before hibernation remains no-protocol after waking up.
|
|
386
|
+
|
|
387
|
+
## Error Handling
|
|
388
|
+
|
|
389
|
+
Handle errors gracefully with `onError`:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
export class MyAgent extends Agent {
|
|
393
|
+
// WebSocket connection error
|
|
394
|
+
onError(connection: Connection, error: unknown): void {
|
|
395
|
+
console.error(`WebSocket error on ${connection.id}:`, error);
|
|
396
|
+
connection.send(
|
|
397
|
+
JSON.stringify({ type: "error", message: "Connection error" })
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Server error (overloaded signature - no connection parameter)
|
|
402
|
+
onError(error: unknown): void {
|
|
403
|
+
console.error("Server error:", error);
|
|
404
|
+
// Log to external service, etc.
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Hibernation
|
|
410
|
+
|
|
411
|
+
Agents support hibernation - they can sleep when inactive and wake when needed. This saves resources while maintaining WebSocket connections.
|
|
412
|
+
|
|
413
|
+
### Enabling Hibernation
|
|
414
|
+
|
|
415
|
+
Hibernation is enabled by default. To disable:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
export class AlwaysOnAgent extends Agent {
|
|
419
|
+
static options = { hibernate: false };
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### How Hibernation Works
|
|
424
|
+
|
|
425
|
+
1. Agent is active, handling connections
|
|
426
|
+
2. After ~10 seconds of no messages, agent hibernates (sleeps)
|
|
427
|
+
3. WebSocket connections remain open (handled by Cloudflare)
|
|
428
|
+
4. When a message arrives, agent wakes up
|
|
429
|
+
5. `onMessage` is called as normal
|
|
430
|
+
|
|
431
|
+
### What Persists Across Hibernation
|
|
432
|
+
|
|
433
|
+
| Persists | Does Not Persist |
|
|
434
|
+
| -------------------------- | ------------------- |
|
|
435
|
+
| `this.state` (agent state) | In-memory variables |
|
|
436
|
+
| `connection.state` | Timers/intervals |
|
|
437
|
+
| SQLite data (`this.sql`) | Promises in flight |
|
|
438
|
+
| Connection metadata | Local caches |
|
|
439
|
+
|
|
440
|
+
**Best Practice:** Store important data in `this.state` or SQLite, not in class properties:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
export class MyAgent extends Agent<Env, { counter: number }> {
|
|
444
|
+
initialState = { counter: 0 };
|
|
445
|
+
|
|
446
|
+
// ❌ Don't do this - lost on hibernation
|
|
447
|
+
private localCounter = 0;
|
|
448
|
+
|
|
449
|
+
onMessage(connection: Connection, message: WSMessage) {
|
|
450
|
+
// ✅ Do this - persists
|
|
451
|
+
this.setState({ counter: this.state.counter + 1 });
|
|
452
|
+
|
|
453
|
+
// ❌ Lost after hibernation
|
|
454
|
+
this.localCounter++;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Common Patterns
|
|
460
|
+
|
|
461
|
+
### Authentication on Connect
|
|
462
|
+
|
|
463
|
+
Validate users when they connect:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
export class SecureAgent extends Agent {
|
|
467
|
+
async onConnect(connection: Connection, ctx: ConnectionContext) {
|
|
468
|
+
const url = new URL(ctx.request.url);
|
|
469
|
+
const token = url.searchParams.get("token");
|
|
470
|
+
|
|
471
|
+
if (!token || !(await this.validateToken(token))) {
|
|
472
|
+
connection.close(4001, "Unauthorized");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const user = await this.getUserFromToken(token);
|
|
477
|
+
connection.setState({ userId: user.id, role: user.role });
|
|
478
|
+
|
|
479
|
+
connection.send(JSON.stringify({ type: "authenticated", user }));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private async validateToken(token: string): Promise<boolean> {
|
|
483
|
+
// Validate JWT, check database, etc.
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Chat Room with Broadcast
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
type Message = {
|
|
493
|
+
type: "message" | "join" | "leave";
|
|
494
|
+
user: string;
|
|
495
|
+
text?: string;
|
|
496
|
+
timestamp: number;
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
export class ChatRoom extends Agent {
|
|
500
|
+
onConnect(connection: Connection, ctx: ConnectionContext) {
|
|
501
|
+
const url = new URL(ctx.request.url);
|
|
502
|
+
const username = url.searchParams.get("username") || "Anonymous";
|
|
503
|
+
|
|
504
|
+
connection.setState({ username });
|
|
505
|
+
|
|
506
|
+
// Notify others
|
|
507
|
+
this.broadcast(
|
|
508
|
+
JSON.stringify({
|
|
509
|
+
type: "join",
|
|
510
|
+
user: username,
|
|
511
|
+
timestamp: Date.now()
|
|
512
|
+
} satisfies Message),
|
|
513
|
+
[connection.id] // Don't send to the joining user
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
onMessage(connection: Connection, message: WSMessage) {
|
|
518
|
+
if (typeof message !== "string") return;
|
|
519
|
+
|
|
520
|
+
const { username } = connection.state as { username: string };
|
|
521
|
+
|
|
522
|
+
// Broadcast to everyone
|
|
523
|
+
this.broadcast(
|
|
524
|
+
JSON.stringify({
|
|
525
|
+
type: "message",
|
|
526
|
+
user: username,
|
|
527
|
+
text: message,
|
|
528
|
+
timestamp: Date.now()
|
|
529
|
+
} satisfies Message)
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
onClose(connection: Connection) {
|
|
534
|
+
const { username } = (connection.state as { username: string }) || {};
|
|
535
|
+
if (username) {
|
|
536
|
+
this.broadcast(
|
|
537
|
+
JSON.stringify({
|
|
538
|
+
type: "leave",
|
|
539
|
+
user: username,
|
|
540
|
+
timestamp: Date.now()
|
|
541
|
+
} satisfies Message)
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Presence Tracking
|
|
549
|
+
|
|
550
|
+
Track who's online using per-connection state. This pattern is clean because connection state is automatically cleaned up when users disconnect:
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
type UserState = {
|
|
554
|
+
name: string;
|
|
555
|
+
joinedAt: number;
|
|
556
|
+
lastSeen: number;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
export class PresenceAgent extends Agent {
|
|
560
|
+
onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
|
|
561
|
+
const url = new URL(ctx.request.url);
|
|
562
|
+
const name = url.searchParams.get("name") || "Anonymous";
|
|
563
|
+
|
|
564
|
+
// Store user data on the connection itself
|
|
565
|
+
connection.setState({
|
|
566
|
+
name,
|
|
567
|
+
joinedAt: Date.now(),
|
|
568
|
+
lastSeen: Date.now()
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Send current presence to new user
|
|
572
|
+
connection.send(
|
|
573
|
+
JSON.stringify({
|
|
574
|
+
type: "presence",
|
|
575
|
+
users: this.getPresence()
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
// Notify others that someone joined
|
|
580
|
+
this.broadcastPresence();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
onClose(connection: Connection) {
|
|
584
|
+
// No manual cleanup needed - connection state is automatically gone
|
|
585
|
+
// Just broadcast updated presence to remaining users
|
|
586
|
+
this.broadcastPresence();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Heartbeat to update lastSeen
|
|
590
|
+
onMessage(connection: Connection<UserState>, message: WSMessage) {
|
|
591
|
+
if (message === "ping") {
|
|
592
|
+
connection.setState((prev) => ({
|
|
593
|
+
...prev!,
|
|
594
|
+
lastSeen: Date.now()
|
|
595
|
+
}));
|
|
596
|
+
connection.send("pong");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Build presence from all connections
|
|
601
|
+
private getPresence() {
|
|
602
|
+
const users: Record<string, { name: string; lastSeen: number }> = {};
|
|
603
|
+
for (const conn of this.getConnections<UserState>()) {
|
|
604
|
+
if (conn.state) {
|
|
605
|
+
users[conn.id] = {
|
|
606
|
+
name: conn.state.name,
|
|
607
|
+
lastSeen: conn.state.lastSeen
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return users;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private broadcastPresence() {
|
|
615
|
+
this.broadcast(
|
|
616
|
+
JSON.stringify({
|
|
617
|
+
type: "presence",
|
|
618
|
+
users: this.getPresence()
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
## API Reference
|
|
626
|
+
|
|
627
|
+
### Agent Lifecycle Methods
|
|
628
|
+
|
|
629
|
+
| Method | Signature | Description |
|
|
630
|
+
| ---------------------------- | --------------------------------------------------------------- | ------------------------------------------------------ |
|
|
631
|
+
| `onStart` | `(props?) => void \| Promise<void>` | Called once when agent starts |
|
|
632
|
+
| `onRequest` | `(request: Request) => Response \| Promise<Response>` | Handle HTTP requests |
|
|
633
|
+
| `onConnect` | `(connection, ctx) => void \| Promise<void>` | WebSocket connected |
|
|
634
|
+
| `onMessage` | `(connection, message) => void \| Promise<void>` | Message received |
|
|
635
|
+
| `onClose` | `(connection, code, reason, wasClean) => void \| Promise<void>` | Connection closed |
|
|
636
|
+
| `onError` | `(connection, error) => void \| Promise<void>` | WebSocket error |
|
|
637
|
+
| `onError` | `(error) => void \| Promise<void>` | Server error (overload) |
|
|
638
|
+
| `shouldSendProtocolMessages` | `(connection, ctx) => boolean` | Control per-connection protocol frames (default: true) |
|
|
639
|
+
|
|
640
|
+
### Connection Management Methods
|
|
641
|
+
|
|
642
|
+
| Method | Signature | Description |
|
|
643
|
+
| ----------------------------- | ----------------------------------------- | ---------------------------------------------- |
|
|
644
|
+
| `getConnections` | `(tag?: string) => Iterable<Connection>` | Get all connections, optionally by tag |
|
|
645
|
+
| `getConnection` | `(id: string) => Connection \| undefined` | Get connection by ID |
|
|
646
|
+
| `getConnectionTags` | `(connection, ctx) => string[]` | Override to tag connections |
|
|
647
|
+
| `broadcast` | `(message, without?: string[]) => void` | Send to all connections |
|
|
648
|
+
| `isConnectionProtocolEnabled` | `(connection) => boolean` | Check if connection receives protocol messages |
|
|
649
|
+
|
|
650
|
+
### Connection Object
|
|
651
|
+
|
|
652
|
+
| Property/Method | Type | Description |
|
|
653
|
+
| --------------- | ------------------------------------------ | -------------------------------- |
|
|
654
|
+
| `id` | `string` | Unique connection identifier |
|
|
655
|
+
| `server` | `string` | Agent instance name |
|
|
656
|
+
| `state` | `T \| null` | Per-connection state (read-only) |
|
|
657
|
+
| `setState` | `(state \| (prev) => state) => void` | Update connection state |
|
|
658
|
+
| `send` | `(message: string \| ArrayBuffer) => void` | Send message |
|
|
659
|
+
| `close` | `(code?, reason?) => void` | Close connection |
|
|
660
|
+
|
|
661
|
+
### Agent Properties
|
|
662
|
+
|
|
663
|
+
| Property | Type | Description |
|
|
664
|
+
| ------------ | -------------------- | ----------------------------------- |
|
|
665
|
+
| `this.name` | `string` | Agent instance name |
|
|
666
|
+
| `this.state` | `State` | Agent state (use with `setState()`) |
|
|
667
|
+
| `this.env` | `Env` | Environment bindings |
|
|
668
|
+
| `this.ctx` | `DurableObjectState` | Durable Object context |
|