Skip to content

Commit 185ecc0

Browse files
committed
Simplify integration and improve documentation
Simplified the codebase and reorganized documentation: - Removed server.ts abstraction - adapter is sufficient - Removed outdated examples folder - will add fresh examples after testing - Streamlined code comments to focus on essentials - Added comprehensive docs/ folder: - CLIENT.md - WebSocket client usage and architecture - ADAPTER.md - Adapter patterns and streaming examples - README.md - Quick start guide - Fixed security issues: URL validation, event listener cleanup - Reduced inline commentary (618→266 lines in client.ts) Total reduction: ~2000 lines removed Still testing the integration manually.
1 parent eeeba98 commit 185ecc0

File tree

16 files changed

+1417
-692
lines changed

16 files changed

+1417
-692
lines changed

integrations/community/cloudflare-agents/typescript/README.md

Lines changed: 240 additions & 323 deletions
Large diffs are not rendered by default.

integrations/community/cloudflare-agents/typescript/docs/ADAPTER.md

Lines changed: 508 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# CloudflareAgentsClient
2+
3+
WebSocket-based client for connecting to deployed Cloudflare Agents from AG-UI applications.
4+
5+
## Overview
6+
7+
`CloudflareAgentsClient` extends `AbstractAgent` and provides a WebSocket connection to Cloudflare Workers running the Cloudflare Agents SDK. It handles:
8+
9+
- WebSocket connection management (connect, reconnect, disconnect)
10+
- Event transformation (Cloudflare → AG-UI protocol)
11+
- Message ID tracking across streaming chunks
12+
- State synchronization from server `this.setState()` calls
13+
- Error handling and resilient message parsing
14+
15+
## Basic Usage
16+
17+
```typescript
18+
import { CloudflareAgentsClient } from "@ag-ui/cloudflare-agents";
19+
20+
const agent = new CloudflareAgentsClient({
21+
url: "wss://your-worker.workers.dev"
22+
});
23+
24+
const subscription = agent.runAgent({
25+
messages: [{ role: "user", content: "Hello!" }],
26+
threadId: "thread-123",
27+
runId: "run-456"
28+
}).subscribe({
29+
next: (event) => {
30+
console.log(event.type, event);
31+
},
32+
error: (err) => {
33+
console.error("Error:", err);
34+
},
35+
complete: () => {
36+
console.log("Stream completed");
37+
}
38+
});
39+
40+
// Later, to abort:
41+
agent.abortRun();
42+
// or
43+
subscription.unsubscribe();
44+
```
45+
46+
## Event Flow
47+
48+
```
49+
Client → WebSocket → Cloudflare Worker
50+
51+
RUN_STARTED
52+
53+
TEXT_MESSAGE_CHUNK (streaming)
54+
55+
STATE_SNAPSHOT (if server calls setState)
56+
57+
RUN_FINISHED
58+
```
59+
60+
## Event Transformation
61+
62+
The client automatically transforms Cloudflare SDK events to AG-UI events:
63+
64+
| Cloudflare Event | AG-UI Event | Description |
65+
|-----------------|-------------|-------------|
66+
| `TEXT_CHUNK` | `TEXT_MESSAGE_CHUNK` | Streaming text from LLM |
67+
| `cf_agent_state` | `STATE_SNAPSHOT` | State updates from `this.setState()` |
68+
| `READY`, `PONG` | *(ignored)* | Connection lifecycle events |
69+
70+
### TEXT_CHUNK → TEXT_MESSAGE_CHUNK
71+
72+
When the server sends text chunks:
73+
74+
```json
75+
{ "type": "TEXT_CHUNK", "text": "Hello", "messageId": "msg-1" }
76+
```
77+
78+
The client transforms it to:
79+
80+
```json
81+
{
82+
"type": "TEXT_MESSAGE_CHUNK",
83+
"messageId": "msg-1",
84+
"role": "assistant",
85+
"delta": "Hello",
86+
"timestamp": 1234567890
87+
}
88+
```
89+
90+
AG-UI automatically expands this to:
91+
1. `TEXT_MESSAGE_START` (first chunk only)
92+
2. `TEXT_MESSAGE_CONTENT` (each chunk)
93+
3. `TEXT_MESSAGE_END` (when messageId changes or stream ends)
94+
95+
### cf_agent_state → STATE_SNAPSHOT
96+
97+
When the server calls `this.setState()`, Cloudflare Agents SDK automatically broadcasts:
98+
99+
```json
100+
{
101+
"type": "cf_agent_state",
102+
"state": { "count": 1, "user": "alice" }
103+
}
104+
```
105+
106+
The client transforms it to:
107+
108+
```json
109+
{
110+
"type": "STATE_SNAPSHOT",
111+
"state": { "count": 1, "user": "alice" },
112+
"timestamp": 1234567890
113+
}
114+
```
115+
116+
## Message ID Tracking
117+
118+
The client intelligently tracks message IDs across chunks:
119+
120+
1. **Server provides ID**: Uses it (allows server to control message boundaries)
121+
2. **No current ID**: Generates new UUID (first chunk of new message)
122+
3. **Has current ID**: Reuses it (continuation of current message)
123+
124+
This ensures all chunks belonging to the same message share the same `messageId`, which AG-UI requires for proper grouping.
125+
126+
## WebSocket Connection
127+
128+
### Browser Environment
129+
130+
Uses native `WebSocket` API:
131+
132+
```typescript
133+
const agent = new CloudflareAgentsClient({
134+
url: "wss://your-worker.workers.dev"
135+
});
136+
```
137+
138+
### Node.js Environment
139+
140+
Requires the `ws` package:
141+
142+
```bash
143+
npm install ws
144+
```
145+
146+
```typescript
147+
import WebSocket from "ws";
148+
globalThis.WebSocket = WebSocket;
149+
150+
const agent = new CloudflareAgentsClient({
151+
url: "wss://your-worker.workers.dev"
152+
});
153+
```
154+
155+
## Error Handling
156+
157+
### Parse Errors
158+
159+
If a message fails to parse, the client:
160+
1. Logs the error for debugging
161+
2. Emits `RUN_ERROR` event with code `"PARSE_ERROR"`
162+
3. Continues processing subsequent messages (doesn't crash the stream)
163+
164+
```typescript
165+
agent.runAgent({ messages }).subscribe({
166+
next: (event) => {
167+
if (event.type === "RUN_ERROR" && event.code === "PARSE_ERROR") {
168+
console.warn("Malformed message from server");
169+
}
170+
}
171+
});
172+
```
173+
174+
### WebSocket Errors
175+
176+
Fatal connection errors (network issues, server unavailable):
177+
178+
```typescript
179+
agent.runAgent({ messages }).subscribe({
180+
error: (err) => {
181+
// WebSocket connection failed
182+
console.error("Connection error:", err);
183+
}
184+
});
185+
```
186+
187+
## Advanced Usage
188+
189+
### State Tracking
190+
191+
Listen for state updates from the server:
192+
193+
```typescript
194+
agent.runAgent({ messages }).subscribe({
195+
next: (event) => {
196+
if (event.type === "STATE_SNAPSHOT") {
197+
console.log("Server state:", event.state);
198+
// Update your UI with new state
199+
}
200+
}
201+
});
202+
```
203+
204+
### Cloning Agents
205+
206+
Create a new instance with the same configuration:
207+
208+
```typescript
209+
const agent1 = new CloudflareAgentsClient({ url: "wss://..." });
210+
const agent2 = agent1.clone(); // Same config, fresh state
211+
212+
// Both can run independently
213+
agent1.runAgent({ messages: [...] });
214+
agent2.runAgent({ messages: [...] });
215+
```
216+
217+
### Aborting Runs
218+
219+
```typescript
220+
const agent = new CloudflareAgentsClient({ url: "wss://..." });
221+
222+
// Start a run
223+
const subscription = agent.runAgent({ messages }).subscribe(...);
224+
225+
// Abort via agent
226+
agent.abortRun(); // Closes WebSocket, emits RUN_FINISHED, completes observable
227+
228+
// OR abort via subscription
229+
subscription.unsubscribe(); // Same effect
230+
```
231+
232+
## Connection Lifecycle
233+
234+
```
235+
new CloudflareAgentsClient() → Not connected
236+
237+
runAgent() → Connecting
238+
239+
WebSocket Open → Connected
240+
241+
Send INIT message
242+
243+
Receive events → Processing
244+
245+
WebSocket Close → RUN_FINISHED
246+
247+
Observable Complete
248+
```
249+
250+
## Security
251+
252+
### URL Validation
253+
254+
The client validates and normalizes URLs:
255+
256+
```typescript
257+
// These are equivalent:
258+
new CloudflareAgentsClient({ url: "https://worker.dev" });
259+
new CloudflareAgentsClient({ url: "wss://worker.dev" });
260+
261+
// Both convert to: wss://worker.dev
262+
```
263+
264+
### Event Listener Cleanup
265+
266+
The client properly removes event listeners on cleanup to prevent memory leaks:
267+
268+
- When observable completes
269+
- When observable errors
270+
- When subscription is unsubscribed
271+
272+
## Troubleshooting
273+
274+
### "WebSocket not available" Error
275+
276+
**Cause**: Running in Node.js without `ws` package.
277+
278+
**Solution**:
279+
```bash
280+
npm install ws
281+
```
282+
283+
```typescript
284+
import WebSocket from "ws";
285+
globalThis.WebSocket = WebSocket;
286+
```
287+
288+
### Connection Closes Immediately
289+
290+
**Cause**: Server might not be handling WebSocket upgrade properly.
291+
292+
**Solution**: Ensure your Cloudflare Worker uses `routeAgentRequest()` from the Agents SDK.
293+
294+
### No Events Received
295+
296+
**Cause**: Server might not be sending AG-UI compatible events.
297+
298+
**Solution**: Use `AgentsToAGUIAdapter` on the server side to convert Vercel AI SDK streams to AG-UI events.
299+
300+
### Parse Errors
301+
302+
**Cause**: Server sending non-JSON or malformed messages.
303+
304+
**Solution**: Check server logs. All messages must be valid JSON strings.
305+
306+
## API Reference
307+
308+
### Constructor
309+
310+
```typescript
311+
new CloudflareAgentsClient(config: CloudflareAgentsClientConfig)
312+
```
313+
314+
**Config:**
315+
- `url: string` - WebSocket URL to deployed Cloudflare Agent
316+
- `agentId?: string` - Optional agent identifier
317+
- `description?: string` - Optional agent description
318+
- `threadId?: string` - Optional thread ID
319+
- `initialMessages?: Message[]` - Optional initial messages
320+
- `initialState?: any` - Optional initial state
321+
- `debug?: boolean` - Enable debug logging
322+
323+
### Methods
324+
325+
#### `run(input: RunAgentInput): Observable<BaseEvent>`
326+
327+
Connect to agent and stream events.
328+
329+
**Parameters:**
330+
- `input.messages: Message[]` - Message history
331+
- `input.threadId?: string` - Thread ID
332+
- `input.runId?: string` - Run ID
333+
- `input.parentRunId?: string` - Parent run ID for branching
334+
- `input.state?: any` - Initial state
335+
- `input.tools?: Tool[]` - Available tools
336+
- `input.context?: any[]` - Additional context
337+
338+
**Returns:** Observable stream of AG-UI events
339+
340+
#### `abortRun(): void`
341+
342+
Abort the current run and close WebSocket connection.
343+
344+
#### `clone(): CloudflareAgentsClient`
345+
346+
Create a new instance with the same configuration.

0 commit comments

Comments
 (0)