agents 0.16.2 → 0.17.1
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-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
- 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-CSnyGvJ2.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 +2033 -77
- package/dist/chat/index.js +1828 -245
- 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-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
- 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/{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/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
- package/dist/index.d.ts +95 -73
- package/dist/index.js +694 -25
- 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/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +44 -1
- package/dist/retries.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 +1696 -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 +41 -14
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
- package/dist/client-BXJ9n2f7.js.map +0 -1
package/docs/chat-sdk.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Chat SDK state
|
|
2
|
+
|
|
3
|
+
Use `agents/chat-sdk` when you run the [Chat SDK](https://chat-sdk.dev/) inside an Agent and want the Chat SDK `StateAdapter` to use Agents sub-agents for durable state.
|
|
4
|
+
|
|
5
|
+
The adapter stores Chat SDK subscriptions, locks, queues, dedupe keys, thread state, channel state, callback metadata, transcript lists, and thread history in Durable Object SQLite. Each state shard is a `ChatSdkStateAgent` sub-agent under your ingress Agent.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install both packages in the Worker that hosts your messenger ingress:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install agents chat
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`agents/chat-sdk` itself does not provide a messenger adapter. Use it with any Chat SDK adapter, such as Telegram, Slack, Discord, Teams, or Google Chat.
|
|
16
|
+
|
|
17
|
+
## Basic setup
|
|
18
|
+
|
|
19
|
+
Create a parent Agent that owns your Chat SDK runtime. Pass `createChatSdkState()` as the Chat SDK `state` option.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Agent } from "agents";
|
|
23
|
+
import { Chat } from "chat";
|
|
24
|
+
import { createChatSdkState } from "agents/chat-sdk";
|
|
25
|
+
import { createTelegramAdapter } from "@chat-adapter/telegram";
|
|
26
|
+
|
|
27
|
+
export { ChatSdkStateAgent } from "agents/chat-sdk";
|
|
28
|
+
|
|
29
|
+
export class MessengerAgent extends Agent<Env> {
|
|
30
|
+
private chat!: Chat;
|
|
31
|
+
|
|
32
|
+
onStart() {
|
|
33
|
+
const telegram = createTelegramAdapter({
|
|
34
|
+
botToken: this.env.TELEGRAM_BOT_TOKEN,
|
|
35
|
+
mode: "webhook",
|
|
36
|
+
secretToken: this.env.TELEGRAM_WEBHOOK_SECRET_TOKEN,
|
|
37
|
+
userName: "my_bot"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.chat = new Chat({
|
|
41
|
+
adapters: { telegram },
|
|
42
|
+
userName: "my_bot",
|
|
43
|
+
state: createChatSdkState(),
|
|
44
|
+
concurrency: { strategy: "burst", debounceMs: 600 }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Add the parent Agent to your Durable Object migration:
|
|
51
|
+
|
|
52
|
+
```jsonc
|
|
53
|
+
{
|
|
54
|
+
"durable_objects": {
|
|
55
|
+
"bindings": [{ "name": "MessengerAgent", "class_name": "MessengerAgent" }]
|
|
56
|
+
},
|
|
57
|
+
"migrations": [
|
|
58
|
+
{
|
|
59
|
+
"tag": "v1",
|
|
60
|
+
"new_sqlite_classes": ["MessengerAgent"]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Export `ChatSdkStateAgent` from your Worker entry point so sub-agent routing can resolve it. When `createChatSdkState()` is called inside an Agent lifecycle method, it uses the current Agent as the parent and creates state shards with `this.subAgent()`.
|
|
67
|
+
|
|
68
|
+
## State sharding
|
|
69
|
+
|
|
70
|
+
By default, Chat SDK state is sharded by the first two colon-separated segments of a thread-like key.
|
|
71
|
+
|
|
72
|
+
For example, `telegram:-100123:456` and `telegram:-100123:789` share the same state shard, `telegram:-100123`.
|
|
73
|
+
|
|
74
|
+
The default key sharder recognizes these Chat SDK key prefixes:
|
|
75
|
+
|
|
76
|
+
- `thread-state:`
|
|
77
|
+
- `channel-state:`
|
|
78
|
+
- `msg-history:`
|
|
79
|
+
- `transcripts:user:`
|
|
80
|
+
|
|
81
|
+
Unknown keys use the adapter's default shard name, `default`.
|
|
82
|
+
|
|
83
|
+
## Custom sharding
|
|
84
|
+
|
|
85
|
+
Use `shardKey` to control how thread IDs map to state sub-agent names:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const state = createChatSdkState({
|
|
89
|
+
shardKey(threadId) {
|
|
90
|
+
return threadId.split(":").slice(0, 2).join(":");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `keyShard` when an adapter stores non-thread-shaped keys that should still route to a provider-specific shard:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const state = createChatSdkState({
|
|
99
|
+
keyShard(key) {
|
|
100
|
+
if (!key.startsWith("dedupe:telegram:")) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const chatId = key.slice("dedupe:telegram:".length).split(":")[0];
|
|
105
|
+
return chatId ? `telegram:${chatId}` : undefined;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Returning `undefined` falls back to the built-in key sharder and then to the default shard.
|
|
111
|
+
|
|
112
|
+
## API
|
|
113
|
+
|
|
114
|
+
### `createChatSdkState(options)`
|
|
115
|
+
|
|
116
|
+
Creates a Chat SDK `StateAdapter` backed by a `ChatSdkStateAgent` sub-agent.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { createChatSdkState } from "agents/chat-sdk";
|
|
120
|
+
|
|
121
|
+
export { ChatSdkStateAgent } from "agents/chat-sdk";
|
|
122
|
+
|
|
123
|
+
const state = createChatSdkState({
|
|
124
|
+
// parent: this // optional, defaults to the current Agent from `getCurrentAgent()`
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
|
|
130
|
+
- `agent`: Optional custom subclass of `ChatSdkStateAgent`. Defaults to `ChatSdkStateAgent`.
|
|
131
|
+
- `parent`: Optional parent Agent that will call `subAgent()` to create state shards. Defaults to the current Agent from `getCurrentAgent()` when called inside an Agent lifecycle method or request handler.
|
|
132
|
+
- `name`: Default shard name for keys that cannot be mapped. Defaults to `default`.
|
|
133
|
+
- `shardKey(threadId)`: Maps Chat SDK thread IDs and lock keys to a shard name.
|
|
134
|
+
- `keyShard(key)`: Maps generic Chat SDK cache/list keys to a shard name.
|
|
135
|
+
|
|
136
|
+
### `ChatSdkStateAgent`
|
|
137
|
+
|
|
138
|
+
The sub-agent class that stores state in SQLite. Export it from your Worker entry point so the runtime can create it.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
export { ChatSdkStateAgent } from "agents/chat-sdk";
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `ChatSdkStateAdapter`
|
|
145
|
+
|
|
146
|
+
The concrete `StateAdapter` implementation returned by `createChatSdkState()`. Most applications do not need to instantiate it directly.
|
|
147
|
+
|
|
148
|
+
## What is stored
|
|
149
|
+
|
|
150
|
+
The adapter implements the full Chat SDK `StateAdapter` interface:
|
|
151
|
+
|
|
152
|
+
- Subscriptions for `thread.subscribe()` and `thread.unsubscribe()`.
|
|
153
|
+
- Locks for per-thread or per-channel concurrency.
|
|
154
|
+
- Pending message queues for `queue`, `debounce`, and `burst` concurrency strategies.
|
|
155
|
+
- Generic key-value cache entries with optional TTL.
|
|
156
|
+
- Append-only lists with max-length trimming and list-level TTL refresh.
|
|
157
|
+
|
|
158
|
+
Chat SDK features built on these primitives include:
|
|
159
|
+
|
|
160
|
+
- Message deduplication.
|
|
161
|
+
- Thread and channel state.
|
|
162
|
+
- Persistent thread history for adapters that opt in to `persistThreadHistory`.
|
|
163
|
+
- Callback URL token storage.
|
|
164
|
+
- Modal context storage.
|
|
165
|
+
- Cross-platform transcripts.
|
|
166
|
+
|
|
167
|
+
## Cleanup behavior
|
|
168
|
+
|
|
169
|
+
TTL reads are strict: expired locks, cache values, queue entries, and list entries are ignored or deleted before they are returned.
|
|
170
|
+
|
|
171
|
+
Physical cleanup is lazy. `ChatSdkStateAgent` schedules one cleanup callback for the earliest known expiry and reschedules after cleanup runs. This keeps idle shards quiet while preventing expired rows from accumulating indefinitely.
|
|
172
|
+
|
|
173
|
+
## Example
|
|
174
|
+
|
|
175
|
+
See `examples/chat-sdk-messenger` for a complete Telegram bot that uses:
|
|
176
|
+
|
|
177
|
+
- `createChatSdkState()` for Chat SDK state.
|
|
178
|
+
- `ThinkMessengerStateAgent`, a Think-specific wrapper around
|
|
179
|
+
`ChatSdkStateAgent`, as a sub-agent.
|
|
180
|
+
- Chat SDK burst/debounce concurrency.
|
|
181
|
+
- Think-backed AI replies running in managed fibers.
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Client SDK
|
|
2
|
+
|
|
3
|
+
Connect to agents from any JavaScript runtime — browsers, Node.js, Deno, Bun, or edge functions — using WebSockets or HTTP. The SDK provides real-time state synchronization, RPC method calls, and streaming responses.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The client SDK offers two ways to connect with a websocket connection, and one way to make HTTP requests.
|
|
8
|
+
|
|
9
|
+
| Client | Use Case |
|
|
10
|
+
| ------------- | ----------------------------------------------------------- |
|
|
11
|
+
| `useAgent` | React hook with automatic reconnection and state management |
|
|
12
|
+
| `AgentClient` | Vanilla JavaScript/TypeScript class for any environment |
|
|
13
|
+
| `agentFetch` | HTTP requests when WebSocket isn't needed |
|
|
14
|
+
|
|
15
|
+
All clients provide:
|
|
16
|
+
|
|
17
|
+
- **Bidirectional state sync** - Push and receive state updates in real-time
|
|
18
|
+
- **RPC calls** - Call agent methods with typed arguments and return values
|
|
19
|
+
- **Streaming** - Handle chunked responses for AI completions
|
|
20
|
+
- **Auto-reconnection** - Built on [PartySocket](https://docs.partykit.io/reference/partysocket-api/) for reliable connections
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### React
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useAgent } from "agents/react";
|
|
28
|
+
|
|
29
|
+
function Chat() {
|
|
30
|
+
const agent = useAgent({
|
|
31
|
+
agent: "ChatAgent",
|
|
32
|
+
name: "room-123"
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const sendMessage = async () => {
|
|
36
|
+
const response = await agent.call("sendMessage", ["Hello!"]);
|
|
37
|
+
console.log("Response:", response);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<p>Messages: {agent.state?.messageCount ?? 0}</p>
|
|
43
|
+
<button onClick={sendMessage}>Send</button>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Vanilla JavaScript
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { AgentClient } from "agents/client";
|
|
53
|
+
|
|
54
|
+
const client = new AgentClient({
|
|
55
|
+
agent: "ChatAgent",
|
|
56
|
+
name: "room-123",
|
|
57
|
+
host: "your-worker.your-subdomain.workers.dev"
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await client.ready;
|
|
61
|
+
|
|
62
|
+
// Read state directly
|
|
63
|
+
console.log("Current state:", client.state);
|
|
64
|
+
|
|
65
|
+
// Call a method
|
|
66
|
+
const response = await client.call("sendMessage", ["Hello!"]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Connecting to Agents
|
|
70
|
+
|
|
71
|
+
### Agent Naming
|
|
72
|
+
|
|
73
|
+
The `agent` parameter is your agent class name. It's automatically converted from camelCase to kebab-case for the URL:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// These are equivalent:
|
|
77
|
+
useAgent({ agent: "ChatAgent" }); // → /agents/chat-agent/...
|
|
78
|
+
useAgent({ agent: "MyCustomAgent" }); // → /agents/my-custom-agent/...
|
|
79
|
+
useAgent({ agent: "LOUD_AGENT" }); // → /agents/loud-agent/...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Instance Names
|
|
83
|
+
|
|
84
|
+
The `name` parameter identifies a specific agent instance. If omitted, defaults to `"default"`:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Connect to a specific chat room
|
|
88
|
+
useAgent({ agent: "ChatAgent", name: "room-123" });
|
|
89
|
+
|
|
90
|
+
// Connect to a user's personal agent
|
|
91
|
+
useAgent({ agent: "UserAgent", name: userId });
|
|
92
|
+
|
|
93
|
+
// Uses "default" instance
|
|
94
|
+
useAgent({ agent: "ChatAgent" });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Connection Options
|
|
98
|
+
|
|
99
|
+
Both `useAgent` and `AgentClient` accept PartySocket options:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
useAgent({
|
|
103
|
+
agent: "ChatAgent",
|
|
104
|
+
name: "room-123",
|
|
105
|
+
|
|
106
|
+
// Connection settings
|
|
107
|
+
host: "my-worker.workers.dev", // Custom host (defaults to current origin)
|
|
108
|
+
path: "/custom/path", // Custom path prefix
|
|
109
|
+
|
|
110
|
+
// Query parameters (sent on connection)
|
|
111
|
+
query: {
|
|
112
|
+
token: "abc123",
|
|
113
|
+
version: "2"
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Event handlers
|
|
117
|
+
onOpen: () => console.log("Connected"),
|
|
118
|
+
onClose: () => console.log("Disconnected"),
|
|
119
|
+
onError: (error) => console.error("Error:", error)
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Async Query Parameters
|
|
124
|
+
|
|
125
|
+
For authentication tokens or other async data, pass a function that returns a Promise:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
useAgent({
|
|
129
|
+
agent: "ChatAgent",
|
|
130
|
+
name: "room-123",
|
|
131
|
+
|
|
132
|
+
// Async query - called before connecting
|
|
133
|
+
query: async () => {
|
|
134
|
+
const token = await getAuthToken();
|
|
135
|
+
return { token };
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Dependencies that trigger re-fetching the query
|
|
139
|
+
queryDeps: [userId],
|
|
140
|
+
|
|
141
|
+
// Cache TTL for the query result (default: 5 minutes)
|
|
142
|
+
cacheTtl: 60 * 1000 // 1 minute
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The query function is cached and only re-called when:
|
|
147
|
+
|
|
148
|
+
- `queryDeps` change
|
|
149
|
+
- `cacheTtl` expires
|
|
150
|
+
- The component remounts
|
|
151
|
+
|
|
152
|
+
## State Synchronization
|
|
153
|
+
|
|
154
|
+
Agents can maintain state that syncs bidirectionally with all connected clients. Both `useAgent` and `AgentClient` expose a `state` property that tracks the current agent state.
|
|
155
|
+
|
|
156
|
+
### Reading State
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
const agent = useAgent({
|
|
160
|
+
agent: "GameAgent",
|
|
161
|
+
name: "game-123"
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Read state directly — reactive in React (re-renders on change)
|
|
165
|
+
return <div>Score: {agent.state?.score}</div>;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`agent.state` starts as `undefined` and is populated when the server sends state on connect (from the agent's `initialState`). Use optional chaining for safe access.
|
|
169
|
+
|
|
170
|
+
### Pushing State Updates
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Update the agent's state from the client
|
|
174
|
+
agent.setState({ score: 100, level: 5 });
|
|
175
|
+
|
|
176
|
+
// Spread existing state for partial updates
|
|
177
|
+
agent.setState({ ...agent.state, score: agent.state.score + 10 });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
When you call `setState()`:
|
|
181
|
+
|
|
182
|
+
1. The state is sent to the agent over WebSocket
|
|
183
|
+
2. The agent's `onStateChanged()` method is called
|
|
184
|
+
3. The agent broadcasts the new state to all connected clients
|
|
185
|
+
4. `agent.state` updates on the next render (React) or immediately (`AgentClient`)
|
|
186
|
+
|
|
187
|
+
### Listening for State Changes
|
|
188
|
+
|
|
189
|
+
For side effects when state changes, use the `onStateUpdate` callback:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const agent = useAgent({
|
|
193
|
+
agent: "GameAgent",
|
|
194
|
+
name: "game-123",
|
|
195
|
+
onStateUpdate: (state, source) => {
|
|
196
|
+
// source: "server" (agent pushed) or "client" (you pushed)
|
|
197
|
+
console.log(`State updated from ${source}:`, state);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> For most use cases, reading `agent.state` directly is simpler than tracking state manually with `onStateUpdate`. Use `onStateUpdate` when you need to trigger side effects on state changes.
|
|
203
|
+
|
|
204
|
+
### State Flow
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
┌─────────┐ ┌─────────┐
|
|
208
|
+
│ Client │ ── setState() ────▶ │ Agent │
|
|
209
|
+
│ │ │ │
|
|
210
|
+
│ .state │ ◀── state update ── │ │
|
|
211
|
+
└─────────┘ (broadcast) └─────────┘
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Calling Agent Methods (RPC)
|
|
215
|
+
|
|
216
|
+
Call methods on your agent that are decorated with `@callable()`.
|
|
217
|
+
|
|
218
|
+
> **Note:** The `@callable()` decorator is only required for methods called from external runtimes (browsers, other services). When calling from within the same Worker, you can use standard [Durable Object RPC](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#invoke-rpc-methods) directly on the stub without the decorator.
|
|
219
|
+
|
|
220
|
+
### Using call()
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Basic call
|
|
224
|
+
const result = await agent.call("getUser", [userId]);
|
|
225
|
+
|
|
226
|
+
// Call with multiple arguments
|
|
227
|
+
const result = await agent.call("createPost", [title, content, tags]);
|
|
228
|
+
|
|
229
|
+
// Call with no arguments
|
|
230
|
+
const result = await agent.call("getStats");
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Using the Stub Proxy
|
|
234
|
+
|
|
235
|
+
The `stub` property provides a cleaner syntax for method calls:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Instead of:
|
|
239
|
+
const user = await agent.call("getUser", ["user-123"]);
|
|
240
|
+
|
|
241
|
+
// You can write:
|
|
242
|
+
const user = await agent.stub.getUser("user-123");
|
|
243
|
+
|
|
244
|
+
// Multiple arguments work naturally:
|
|
245
|
+
const post = await agent.stub.createPost(title, content, tags);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### TypeScript Integration
|
|
249
|
+
|
|
250
|
+
For full type safety, pass your Agent class as a type parameter:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import type { MyAgent } from "./agents/my-agent";
|
|
254
|
+
|
|
255
|
+
const agent = useAgent<MyAgent, MyAgentState>({
|
|
256
|
+
agent: "MyAgent",
|
|
257
|
+
name: "instance-1"
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Now stub methods are fully typed!
|
|
261
|
+
const result = await agent.stub.processData({ input: "test" });
|
|
262
|
+
// ^? Awaited<ReturnType<MyAgent["processData"]>>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Streaming Responses
|
|
266
|
+
|
|
267
|
+
For methods that return `StreamingResponse`, handle chunks as they arrive:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Agent-side:
|
|
271
|
+
@callable()
|
|
272
|
+
async generateText(prompt: string) {
|
|
273
|
+
return new StreamingResponse(async (stream) => {
|
|
274
|
+
for await (const chunk of llm.stream(prompt)) {
|
|
275
|
+
await stream.write(chunk);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Client-side:
|
|
281
|
+
await agent.call("generateText", [prompt], {
|
|
282
|
+
onChunk: (chunk) => {
|
|
283
|
+
// Called for each chunk
|
|
284
|
+
appendToOutput(chunk);
|
|
285
|
+
},
|
|
286
|
+
onDone: (finalResult) => {
|
|
287
|
+
// Called when stream completes
|
|
288
|
+
console.log("Complete:", finalResult);
|
|
289
|
+
},
|
|
290
|
+
onError: (error) => {
|
|
291
|
+
// Called if streaming fails
|
|
292
|
+
console.error("Stream error:", error);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## HTTP Requests with agentFetch
|
|
298
|
+
|
|
299
|
+
For one-off requests without maintaining a WebSocket connection:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { agentFetch } from "agents/client";
|
|
303
|
+
|
|
304
|
+
// GET request
|
|
305
|
+
const response = await agentFetch({
|
|
306
|
+
agent: "DataAgent",
|
|
307
|
+
name: "instance-1",
|
|
308
|
+
host: "my-worker.workers.dev"
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const data = await response.json();
|
|
312
|
+
|
|
313
|
+
// POST request with body
|
|
314
|
+
const response = await agentFetch(
|
|
315
|
+
{
|
|
316
|
+
agent: "DataAgent",
|
|
317
|
+
name: "instance-1",
|
|
318
|
+
host: "my-worker.workers.dev"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
method: "POST",
|
|
322
|
+
headers: { "Content-Type": "application/json" },
|
|
323
|
+
body: JSON.stringify({ action: "process" })
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**When to use `agentFetch` vs WebSocket:**
|
|
329
|
+
|
|
330
|
+
| Use `agentFetch` | Use `useAgent`/`AgentClient` |
|
|
331
|
+
| ------------------------------- | ---------------------------- |
|
|
332
|
+
| One-time requests | Real-time updates needed |
|
|
333
|
+
| Server-to-server calls | Bidirectional communication |
|
|
334
|
+
| Simple REST-style API | State synchronization |
|
|
335
|
+
| No persistent connection needed | Multiple RPC calls |
|
|
336
|
+
|
|
337
|
+
## React Hook Reference
|
|
338
|
+
|
|
339
|
+
### UseAgentOptions
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
type UseAgentOptions<State> = {
|
|
343
|
+
// Required
|
|
344
|
+
agent: string; // Agent class name
|
|
345
|
+
|
|
346
|
+
// Optional
|
|
347
|
+
name?: string; // Instance name (default: "default")
|
|
348
|
+
host?: string; // Custom host
|
|
349
|
+
path?: string; // Custom path prefix
|
|
350
|
+
|
|
351
|
+
// Query parameters
|
|
352
|
+
query?:
|
|
353
|
+
| Record<string, string | null>
|
|
354
|
+
| (() => Promise<Record<string, string | null>>);
|
|
355
|
+
queryDeps?: unknown[]; // Dependencies for async query
|
|
356
|
+
cacheTtl?: number; // Query cache TTL in ms (default: 5 min)
|
|
357
|
+
|
|
358
|
+
// Callbacks
|
|
359
|
+
onStateUpdate?: (state: State, source: "server" | "client") => void;
|
|
360
|
+
onMcpUpdate?: (mcpServers: MCPServersState) => void;
|
|
361
|
+
onOpen?: () => void;
|
|
362
|
+
onClose?: () => void;
|
|
363
|
+
onError?: (error: Event) => void;
|
|
364
|
+
onMessage?: (message: MessageEvent) => void;
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Return Value
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
const agent = useAgent(options);
|
|
372
|
+
|
|
373
|
+
agent.state; // State | undefined - Current agent state (reactive)
|
|
374
|
+
agent.agent; // string - Kebab-case agent name
|
|
375
|
+
agent.name; // string - Instance name
|
|
376
|
+
agent.setState(state); // void - Push state to agent
|
|
377
|
+
agent.call(method, args?, streamOptions?); // Promise<T> - Call agent method
|
|
378
|
+
agent.stub; // Proxy - Typed method calls
|
|
379
|
+
agent.send(data); // void - Send raw WebSocket message
|
|
380
|
+
agent.close(); // void - Close connection
|
|
381
|
+
agent.reconnect(); // void - Force reconnection
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Vanilla JS Reference
|
|
385
|
+
|
|
386
|
+
### AgentClientOptions
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
type AgentClientOptions<State> = {
|
|
390
|
+
// Required
|
|
391
|
+
agent: string; // Agent class name
|
|
392
|
+
host: string; // Worker host
|
|
393
|
+
|
|
394
|
+
// Optional
|
|
395
|
+
name?: string; // Instance name (default: "default")
|
|
396
|
+
path?: string; // Custom path prefix
|
|
397
|
+
query?: Record<string, string>;
|
|
398
|
+
|
|
399
|
+
// Callbacks
|
|
400
|
+
onStateUpdate?: (state: State, source: "server" | "client") => void;
|
|
401
|
+
};
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### AgentClient Methods
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const client = new AgentClient(options);
|
|
408
|
+
|
|
409
|
+
client.state; // State | undefined - Current agent state
|
|
410
|
+
client.agent; // string - Kebab-case agent name
|
|
411
|
+
client.name; // string - Instance name
|
|
412
|
+
client.setState(state); // void - Push state to agent
|
|
413
|
+
client.call(method, args?, streamOptions?); // Promise<T> - Call agent method
|
|
414
|
+
client.send(data); // void - Send raw WebSocket message
|
|
415
|
+
client.close(); // void - Close connection
|
|
416
|
+
client.reconnect(); // void - Force reconnection
|
|
417
|
+
|
|
418
|
+
// Event listeners (inherited from PartySocket)
|
|
419
|
+
client.addEventListener("open", () => {});
|
|
420
|
+
client.addEventListener("close", () => {});
|
|
421
|
+
client.addEventListener("error", () => {});
|
|
422
|
+
client.addEventListener("message", () => {});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## MCP Server Integration
|
|
426
|
+
|
|
427
|
+
If your agent uses MCP (Model Context Protocol) servers, you can receive updates about their state:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const agent = useAgent({
|
|
431
|
+
agent: "AssistantAgent",
|
|
432
|
+
name: "session-123",
|
|
433
|
+
onMcpUpdate: (mcpServers) => {
|
|
434
|
+
// mcpServers is a record of server states
|
|
435
|
+
for (const [serverId, server] of Object.entries(mcpServers)) {
|
|
436
|
+
console.log(`${serverId}: ${server.connectionState}`);
|
|
437
|
+
console.log(`Tools: ${server.tools?.map((t) => t.name).join(", ")}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Error Handling
|
|
444
|
+
|
|
445
|
+
### Connection Errors
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
const agent = useAgent({
|
|
449
|
+
agent: "MyAgent",
|
|
450
|
+
onError: (error) => {
|
|
451
|
+
console.error("WebSocket error:", error);
|
|
452
|
+
},
|
|
453
|
+
onClose: () => {
|
|
454
|
+
console.log("Connection closed, will auto-reconnect...");
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### RPC Errors
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
try {
|
|
463
|
+
const result = await agent.call("riskyMethod", [data]);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
// Error thrown by the agent method
|
|
466
|
+
console.error("RPC failed:", error.message);
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Streaming Errors
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
await agent.call("streamingMethod", [data], {
|
|
474
|
+
onChunk: (chunk) => handleChunk(chunk),
|
|
475
|
+
onError: (errorMessage) => {
|
|
476
|
+
// Stream-specific error handling
|
|
477
|
+
console.error("Stream error:", errorMessage);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Best Practices
|
|
483
|
+
|
|
484
|
+
### 1. Use Typed Stubs
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// Prefer this:
|
|
488
|
+
const user = await agent.stub.getUser(id);
|
|
489
|
+
|
|
490
|
+
// Over this:
|
|
491
|
+
const user = await agent.call("getUser", [id]);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### 2. Reconnection is Automatic
|
|
495
|
+
|
|
496
|
+
The client auto-reconnects and the agent automatically sends the current state on each connection. `agent.state` is updated automatically — no manual re-sync needed.
|
|
497
|
+
|
|
498
|
+
### 3. Optimize Query Caching
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// For auth tokens that expire hourly:
|
|
502
|
+
useAgent({
|
|
503
|
+
query: async () => ({ token: await getToken() }),
|
|
504
|
+
cacheTtl: 55 * 60 * 1000, // Refresh 5 min before expiry
|
|
505
|
+
queryDeps: [userId] // Refresh if user changes
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 4. Clean Up Connections
|
|
510
|
+
|
|
511
|
+
In vanilla JS, close connections when done:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
const client = new AgentClient({ agent: "MyAgent", host: "..." });
|
|
515
|
+
|
|
516
|
+
// When done:
|
|
517
|
+
client.close();
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
React's `useAgent` handles cleanup automatically on unmount.
|