react-websocket 3.6.0 → 4.18.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 +34 -5
- package/dist/ag-ui.cjs +421 -0
- package/dist/ag-ui.cjs.map +1 -0
- package/dist/ag-ui.d.cts +493 -0
- package/dist/ag-ui.d.ts +171 -0
- package/dist/ag-ui.js +418 -0
- package/dist/ag-ui.js.map +1 -0
- package/dist/core.cjs +727 -162
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +501 -62
- package/dist/core.d.ts +501 -62
- package/dist/core.js +723 -163
- package/dist/core.js.map +1 -1
- package/dist/devtools.cjs +186 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +32 -0
- package/dist/devtools.d.ts +32 -0
- package/dist/devtools.js +185 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +214 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +705 -56
- package/dist/index.d.ts +421 -30
- package/dist/index.js +211 -21
- package/dist/index.js.map +1 -1
- package/dist/outbox.cjs +222 -0
- package/dist/outbox.cjs.map +1 -0
- package/dist/outbox.d.cts +127 -0
- package/dist/outbox.d.ts +127 -0
- package/dist/outbox.js +219 -0
- package/dist/outbox.js.map +1 -0
- package/dist/replay.cjs +324 -0
- package/dist/replay.cjs.map +1 -0
- package/dist/replay.d.cts +124 -0
- package/dist/replay.d.ts +124 -0
- package/dist/replay.js +322 -0
- package/dist/replay.js.map +1 -0
- package/dist/tabs.cjs +336 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +81 -0
- package/dist/tabs.d.ts +81 -0
- package/dist/tabs.js +335 -0
- package/dist/tabs.js.map +1 -0
- package/dist/timeline.d.cts +1 -213
- package/dist/types-BEecFqIB.d.cts +268 -0
- package/dist/webtransport.cjs +243 -0
- package/dist/webtransport.cjs.map +1 -0
- package/dist/webtransport.d.cts +67 -0
- package/dist/webtransport.d.ts +67 -0
- package/dist/webtransport.js +242 -0
- package/dist/webtransport.js.map +1 -0
- package/package.json +139 -3
package/README.md
CHANGED
|
@@ -12,15 +12,24 @@
|
|
|
12
12
|
**[Documentation](https://mehmetkose.github.io/react-websocket/)** · [Getting started](https://mehmetkose.github.io/react-websocket/guide/getting-started) · [API](https://mehmetkose.github.io/react-websocket/api/) · [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Changelog](CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
- **Shared connections.** Components using the same URL share one socket. StrictMode, `<Activity>` and route transitions never open duplicates.
|
|
15
|
-
- **Auto-reconnect.** Exponential backoff with jitter, plus an immediate retry when the browser comes back online or the tab becomes visible. Optional heartbeats detect dead links.
|
|
15
|
+
- **Auto-reconnect.** Exponential backoff with full jitter, plus an immediate retry when the browser comes back online or the tab becomes visible. It backs off from a server that closes right after accepting, and stops on close codes that retrying can't fix. Optional heartbeats detect dead links.
|
|
16
16
|
- **Send queue.** `send()` before the socket is open just works. The message goes out on connect. Mark input `volatile` so it's never replayed late, or `key` it so only the newest is sent. Put control messages first with `priority`, and drop stream data on a slow link with `maxBuffered`.
|
|
17
17
|
- **Typed messages.** `json: true`, your own codec, or validation with any [Standard Schema](https://standardschema.dev) library (zod, valibot, arktype), with inferred types.
|
|
18
18
|
- **Suspense.** `use(client.ready)` waits for the first connection, and reconnects never flash a fallback.
|
|
19
|
-
- **Topic subscriptions.** Reference-counted `subscribe`/`unsubscribe` messages that are sent again after every reconnect, so feeds don't silently stop.
|
|
19
|
+
- **Topic subscriptions.** Reference-counted `subscribe`/`unsubscribe` messages that are sent again after every reconnect, so feeds don't silently stop. With numbered messages, each arrives once and in order: duplicates are dropped, gaps are filled, and a reconnect resumes where it left off.
|
|
20
20
|
- **Message state.** Reduce messages into a store, and let each component re-render only when its slice changes, at most once per frame if you like.
|
|
21
21
|
- **Requests, streams and latency.** Await a reply, or iterate over streamed ones such as an LLM's tokens, with timeouts and a cancel that tells the server to stop. Measure round-trip time and the server's clock for ping displays and synchronized countdowns.
|
|
22
22
|
- **Games.** Snapshot interpolation and client-side prediction with server reconciliation, from `react-websocket/timeline`, so other players move smoothly and your own controls respond at once. A send rate that adapts to the connection keeps players on a poor link from building up lag.
|
|
23
|
+
- **Presence and live cursors.** Share per-peer state such as cursors: throttled, refreshed while idle, never replayed stale, and expired when a peer vanishes without saying goodbye.
|
|
24
|
+
- **Offline outbox.** Messages kept in IndexedDB until the server acknowledges them, from `react-websocket/outbox`, so an order taken on flaky Wi-Fi survives reconnects and page reloads, and retries are safe with idempotency keys.
|
|
25
|
+
- **Resumable runs.** An AI agent's streamed answer that survives a refresh, a network switch or a sleeping laptop: it resumes from the last event received, or is cancelled so the server stops spending.
|
|
26
|
+
- **Voice.** Microphone frames that never replay stale or build up lag, control messages that jump the queue, and a [guide](https://mehmetkose.github.io/react-websocket/guide/voice) to interruptions that truncate the transcript to what the user heard.
|
|
27
|
+
- **AG-UI.** Run [AG-UI](https://docs.ag-ui.com) agents over a WebSocket with `react-websocket/ag-ui`: messages, tool calls and JSON Patch shared state, with a snapshot when a patch fails, and runs that resume after a reconnect or a reload.
|
|
23
28
|
- **Server requests.** Answer requests from the server, such as an AI agent's, in code for browser-only tools, or from a dialog for human approval. Every request gets exactly one reply, and none is sent to the wrong connection.
|
|
29
|
+
- **WebTransport.** `react-websocket/webtransport` connects over WebTransport behind the same API, with `volatile` sends as datagrams and a WebSocket fallback where it can't connect.
|
|
30
|
+
- **One connection across tabs.** `react-websocket/tabs` lets every tab of an app share one connection, with a leader tab elected over the Web Locks API, and topics that one tab can't unsubscribe from under another.
|
|
31
|
+
- **Recording and replay.** Record a session to a file and replay it without a server, for bug reports, offline demos, and UI tests that don't call an AI model on every run. Add faults on top with `react-websocket/chaos`.
|
|
32
|
+
- **Devtools.** A development panel, `react-websocket/devtools`, with each client's status, queue, send buffer, messages, round-trip time and topics.
|
|
24
33
|
- **SSR-safe.** Never connects during a server render, hydrates cleanly, and ships a `'use client'` entry for Next.js and other Server Components frameworks.
|
|
25
34
|
- **Small and typed.** Zero dependencies, ESM + CJS, TypeScript declarations with inline docs, plus a React-free `react-websocket/core` entry.
|
|
26
35
|
|
|
@@ -118,15 +127,31 @@ function Room() {
|
|
|
118
127
|
| `useWebSocketLatency(latency)` | Round-trip time and server clock offset |
|
|
119
128
|
| `useAgentApproval(responder, method)` | Requests from the server waiting for a person to answer |
|
|
120
129
|
| `useWebSocketStream(rpc, options?)` | Stream the replies to a request into a value, e.g. tokens into an answer; returns `{ value, status, error, start, abort }` |
|
|
130
|
+
| `useAgentRun(runs, runId)` | A resumable run's value and status, e.g. an AI answer that keeps streaming after a reconnect or a reload |
|
|
131
|
+
| `useAgUiAgent(agent)` | An AG-UI agent's thread: messages, tool calls, shared state and status |
|
|
132
|
+
| `useWebSocketPresence(presence)` | The other peers' shared state, such as live cursors |
|
|
133
|
+
| `useInView(ref, options?)` | Whether an element is on screen, to pick a topic with: full detail only for what's visible |
|
|
134
|
+
| `useWebSocketOutbox(outbox)` | The messages waiting in an outbox, for a "3 orders waiting to sync" banner |
|
|
121
135
|
| `createWebSocket(url, options?)` | A shareable client: `send`, `on`, `connect`, `close`, `reconnect`, `ready`, `getSnapshot`, `subscribe` |
|
|
122
136
|
| `createWebSocketTopics(client, { subscribe, unsubscribe })` | Topic subscription manager for `useWebSocketTopic` |
|
|
137
|
+
| `createWebSocketFeeds(client, { subscribe, sequence, snapshot })` | Topics with numbered messages, each delivered once and in order across reconnects; pass it to `createWebSocketState` |
|
|
123
138
|
| `createWebSocketState(client, { initialState, reduce })` | Message-state store for `useWebSocketState` |
|
|
124
139
|
| `createWebSocketRequests(client, { send, reply })` | `request(message)` returns a promise of the reply, and `stream(message)` an async iterator of replies, with timeouts and `cancel` |
|
|
125
140
|
| `createWebSocketLatency(client, { ping, pong })` | Ping tracker for `useWebSocketLatency`, plus `serverNow()` |
|
|
126
141
|
| `createWebSocketSendRate(client, options?)` | `send(message, key)` coalesced per key at a rate that drops on a congested link |
|
|
142
|
+
| `createWebSocketPresence(client, { send, receive })` | `set(state)` shares this client's state, throttled and refreshed; peers expire after `timeout` |
|
|
127
143
|
| `createWebSocketResponder(client, { request, respond, error })` | Answer the server's requests: `onRequest(method, handler)`, or a person through `useAgentApproval` |
|
|
144
|
+
| `createWebSocketRuns(client, { start, resume, event, reduce })` | Runs that resume after a reconnect or a reload: `start(request)`, `abort(runId)`, a `keep` or `cancel` policy |
|
|
128
145
|
| `MessageError` | Reported to `onError` when a message fails `parse` or `schema` |
|
|
146
|
+
| `retryableClose(close)` | The default `reconnect.shouldReconnect`: retries every close except `1002`, `1003` and `1008` |
|
|
147
|
+
| `retryAfter(close)` | The default `reconnect.retryAfter`: the delay a `1012` (Service Restart) or `1013` (Try Again Later) close asks for, in seconds in its reason |
|
|
129
148
|
| `createWebSocketTimeline(client, { snapshot, interpolate, input })` | From `react-websocket/timeline`: interpolated server snapshots (`at()`) and predicted inputs (`predicted()`, `input()`) for multiplayer games |
|
|
149
|
+
| `createWebSocketOutbox(client, { name, send, ack })` | From `react-websocket/outbox`: `add(message)` keeps a message in IndexedDB and sends it until the server acknowledges it, across reconnects and reloads |
|
|
150
|
+
| `createAgUiAgent(client, options?)` | From `react-websocket/ag-ui`: `run({ messages })` an AG-UI agent over a WebSocket; also `applyAgUiEvent` and `applyPatch` |
|
|
151
|
+
| `createWebTransportSocket(options?)` | From `react-websocket/webtransport`: a `WebSocket` class over WebTransport, with datagrams for `volatile` sends and a WebSocket fallback |
|
|
152
|
+
| `createTabsWebSocket(options?)` | From `react-websocket/tabs`: a `WebSocket` class that shares one connection between all the tabs of an app |
|
|
153
|
+
| `createRecordingWebSocket()` / `createReplayWebSocket(recording)` | From `react-websocket/replay`: record sessions to JSON, and replay them without a server, at any speed |
|
|
154
|
+
| `<WebSocketDevtools sources={…} />` | From `react-websocket/devtools`: a development panel showing every client's status, queue, buffer, messages, RTT and topics |
|
|
130
155
|
| `createChaosWebSocket(options)` | From `react-websocket/chaos`: a `WebSocket` class that adds latency, lost messages and dropped connections in development |
|
|
131
156
|
|
|
132
157
|
| Option | Default | |
|
|
@@ -136,9 +161,9 @@ function Room() {
|
|
|
136
161
|
| `json` | `false` | `JSON.parse` incoming and `JSON.stringify` outgoing messages |
|
|
137
162
|
| `parse(data)` / `serialize(message)` | | Custom codec; takes precedence over `json` |
|
|
138
163
|
| `schema` | | Standard Schema validation; types are inferred |
|
|
139
|
-
| `reconnect` | `true` | `false`, or `{ maxAttempts, minDelay, maxDelay, factor, jitter, shouldReconnect }` |
|
|
164
|
+
| `reconnect` | `true` | `false`, or `{ maxAttempts, minDelay, maxDelay, factor, jitter, minUptime, shouldReconnect, retryAfter }` |
|
|
140
165
|
| `heartbeat` | | `{ interval, message, pong, timeout }` |
|
|
141
|
-
| `queue` | `true` | `false`, or `{ limit }` |
|
|
166
|
+
| `queue` | `true` (100 messages) | `false`, or `{ limit, maxAge }` |
|
|
142
167
|
| `logger` | | e.g. `console.debug` |
|
|
143
168
|
| `WebSocket` | global | Custom implementation for tests, Node, or chaos testing |
|
|
144
169
|
|
|
@@ -149,7 +174,7 @@ function Room() {
|
|
|
149
174
|
- [Shared clients](https://mehmetkose.github.io/react-websocket/guide/shared-clients) · [Messages and types](https://mehmetkose.github.io/react-websocket/guide/messages) · [Reconnecting and heartbeats](https://mehmetkose.github.io/react-websocket/guide/reconnecting)
|
|
150
175
|
- [Suspense](https://mehmetkose.github.io/react-websocket/guide/suspense) · [Server rendering and Next.js](https://mehmetkose.github.io/react-websocket/guide/ssr) · [StrictMode, Activity and lifecycle](https://mehmetkose.github.io/react-websocket/guide/lifecycle)
|
|
151
176
|
- [Authentication](https://mehmetkose.github.io/react-websocket/guide/authentication) · [Performance](https://mehmetkose.github.io/react-websocket/guide/performance) · [Real-time games](https://mehmetkose.github.io/react-websocket/guide/games) · [AI agents](https://mehmetkose.github.io/react-websocket/guide/agents) · [Testing](https://mehmetkose.github.io/react-websocket/guide/testing) · [Chaos testing](https://mehmetkose.github.io/react-websocket/guide/chaos) · [Without React](https://mehmetkose.github.io/react-websocket/guide/without-react)
|
|
152
|
-
- [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Troubleshooting](https://mehmetkose.github.io/react-websocket/troubleshooting) · [Migrating from v2](https://mehmetkose.github.io/react-websocket/migration)
|
|
177
|
+
- [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Troubleshooting](https://mehmetkose.github.io/react-websocket/troubleshooting) · [Migrating from v3](https://mehmetkose.github.io/react-websocket/migration-v3) · [Migrating from v2](https://mehmetkose.github.io/react-websocket/migration)
|
|
153
178
|
|
|
154
179
|
The docs source lives in [`docs/`](docs). Run `npm run docs:dev` to browse it locally.
|
|
155
180
|
|
|
@@ -161,6 +186,10 @@ The docs source lives in [`docs/`](docs). Run `npm run docs:dev` to browse it lo
|
|
|
161
186
|
| Runtimes | Modern browsers; Node via the `WebSocket` option |
|
|
162
187
|
| Frameworks | Vite, Next.js (App and Pages Router), Remix/React Router, Astro islands, and any React setup |
|
|
163
188
|
|
|
189
|
+
## Migrating from v3
|
|
190
|
+
|
|
191
|
+
v4 changes no APIs, only four defaults: the backoff resets once a connection has stayed open for 5 seconds, close codes `1002`, `1003` and `1008` aren't retried, jitter is full, and the send queue holds 100 messages. Each has a one-line opt-out. See [Migrating from v3](https://mehmetkose.github.io/react-websocket/migration-v3).
|
|
192
|
+
|
|
164
193
|
## Migrating from v2
|
|
165
194
|
|
|
166
195
|
v2 was a `<Websocket>` class component. v3 replaces it with hooks: `<Websocket url onMessage />` becomes `useWebSocket(url, { onMessage })`, and `ref.sendMessage(msg)` becomes `send(msg)`. See the [migration guide](https://mehmetkose.github.io/react-websocket/migration) for the full mapping.
|
package/dist/ag-ui.cjs
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let __core_cjs = require("./core.cjs");
|
|
3
|
+
//#region src/ag-ui.ts
|
|
4
|
+
function unescape(token) {
|
|
5
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6
|
+
}
|
|
7
|
+
function equal(a, b) {
|
|
8
|
+
if (a === b) return true;
|
|
9
|
+
if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
|
|
10
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
11
|
+
const keys = Object.keys(a);
|
|
12
|
+
return keys.length === Object.keys(b).length && keys.every((key) => equal(a[key], b[key]));
|
|
13
|
+
}
|
|
14
|
+
function fail(operation) {
|
|
15
|
+
throw new Error(`JSON Patch failed: ${operation.op} ${operation.path}`);
|
|
16
|
+
}
|
|
17
|
+
const INDEX = /^(0|[1-9]\d*)$/;
|
|
18
|
+
/** A JSON Pointer's tokens. It's empty, for the whole document, or starts with `/`. */
|
|
19
|
+
function tokens(pointer, operation) {
|
|
20
|
+
if (pointer !== "" && pointer[0] !== "/") fail(operation);
|
|
21
|
+
return pointer.split("/").slice(1).map(unescape);
|
|
22
|
+
}
|
|
23
|
+
/** Whether `node` has `token`: an index in range for an array, not `length`; an own key otherwise. */
|
|
24
|
+
function has(node, token) {
|
|
25
|
+
return Array.isArray(node) ? INDEX.test(token) && +token < node.length : Object.hasOwn(node, token);
|
|
26
|
+
}
|
|
27
|
+
/** Set an own property, even `__proto__`, as JSON.parse would. */
|
|
28
|
+
function put(object, key, value) {
|
|
29
|
+
Object.defineProperty(object, key, {
|
|
30
|
+
value,
|
|
31
|
+
writable: true,
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Apply a JSON Patch (RFC 6902) without changing `document`: what changes is copied, the rest is
|
|
38
|
+
* shared. Throws, leaving `document` as it was, if an operation can't be applied: a missing path,
|
|
39
|
+
* an index out of range, a pointer that doesn't start with `/`, or a failed `test`.
|
|
40
|
+
*/
|
|
41
|
+
function applyPatch(document, operations) {
|
|
42
|
+
let root = document;
|
|
43
|
+
function get(path, operation) {
|
|
44
|
+
let node = root;
|
|
45
|
+
for (const token of tokens(path, operation)) {
|
|
46
|
+
if (typeof node !== "object" || node === null || !has(node, token)) fail(operation);
|
|
47
|
+
node = node[token];
|
|
48
|
+
}
|
|
49
|
+
return node;
|
|
50
|
+
}
|
|
51
|
+
function edit(path, operation, change) {
|
|
52
|
+
const keys = tokens(path, operation);
|
|
53
|
+
if (!keys.length) fail(operation);
|
|
54
|
+
const walk = (node, depth) => {
|
|
55
|
+
if (typeof node !== "object" || node === null) fail(operation);
|
|
56
|
+
const copy = Array.isArray(node) ? [...node] : { ...node };
|
|
57
|
+
const key = keys[depth];
|
|
58
|
+
if (depth === keys.length - 1) change(copy, key);
|
|
59
|
+
else if (!has(node, key)) fail(operation);
|
|
60
|
+
else copy[key] = walk(node[key], depth + 1);
|
|
61
|
+
return copy;
|
|
62
|
+
};
|
|
63
|
+
root = walk(root, 0);
|
|
64
|
+
}
|
|
65
|
+
function index(parent, key, operation, adding) {
|
|
66
|
+
const i = key === "-" ? adding ? parent.length : -1 : INDEX.test(key) ? +key : -1;
|
|
67
|
+
if (i < 0 || i > parent.length - (adding ? 0 : 1)) fail(operation);
|
|
68
|
+
return i;
|
|
69
|
+
}
|
|
70
|
+
function add(path, value, operation) {
|
|
71
|
+
if (path === "") return void (root = value);
|
|
72
|
+
edit(path, operation, (parent, key) => {
|
|
73
|
+
if (Array.isArray(parent)) parent.splice(index(parent, key, operation, true), 0, value);
|
|
74
|
+
else put(parent, key, value);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function remove(path, operation) {
|
|
78
|
+
edit(path, operation, (parent, key) => {
|
|
79
|
+
if (Array.isArray(parent)) parent.splice(index(parent, key, operation, false), 1);
|
|
80
|
+
else if (Object.hasOwn(parent, key)) delete parent[key];
|
|
81
|
+
else fail(operation);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const operation of operations) {
|
|
85
|
+
const { op, path, from, value } = operation;
|
|
86
|
+
if (op === "add") add(path, value, operation);
|
|
87
|
+
else if (op === "remove") remove(path, operation);
|
|
88
|
+
else if (op === "replace") {
|
|
89
|
+
get(path, operation);
|
|
90
|
+
if (path === "") root = value;
|
|
91
|
+
else edit(path, operation, (parent, key) => {
|
|
92
|
+
if (Array.isArray(parent)) parent[key] = value;
|
|
93
|
+
else put(parent, key, value);
|
|
94
|
+
});
|
|
95
|
+
} else if (op === "move" || op === "copy") {
|
|
96
|
+
const moved = get(from ?? fail(operation), operation);
|
|
97
|
+
if (op === "move") remove(from, operation);
|
|
98
|
+
add(path, op === "copy" ? structuredClone(moved) : moved, operation);
|
|
99
|
+
} else if (op === "test") {
|
|
100
|
+
if (!equal(get(path, operation), value)) fail(operation);
|
|
101
|
+
} else fail(operation);
|
|
102
|
+
}
|
|
103
|
+
return root;
|
|
104
|
+
}
|
|
105
|
+
function updateMessage(thread, id, change) {
|
|
106
|
+
return thread.messages.map((message) => message.id === id ? change(message) : message);
|
|
107
|
+
}
|
|
108
|
+
function appendText(message, delta) {
|
|
109
|
+
return {
|
|
110
|
+
...message,
|
|
111
|
+
content: `${message.content ?? ""}${delta ?? ""}`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function updateToolCall(thread, id, change) {
|
|
115
|
+
return thread.messages.map((message) => message.toolCalls?.some((call) => call.id === id) ? {
|
|
116
|
+
...message,
|
|
117
|
+
toolCalls: message.toolCalls.map((call) => call.id === id ? change(call) : call)
|
|
118
|
+
} : message);
|
|
119
|
+
}
|
|
120
|
+
/** The last message with a tool call, for chunks that leave out `toolCallId`. */
|
|
121
|
+
function lastToolCallId(thread) {
|
|
122
|
+
for (let i = thread.messages.length - 1; i >= 0; i--) {
|
|
123
|
+
const calls = thread.messages[i].toolCalls;
|
|
124
|
+
if (calls?.length) return calls.at(-1).id;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Fold one AG-UI event into a thread, without changing it. Handles the run lifecycle, steps,
|
|
129
|
+
* text, tool calls and their results, reasoning, activity, and message and state snapshots and
|
|
130
|
+
* deltas. A `STATE_DELTA` that can't be applied marks the thread `stale` instead of throwing.
|
|
131
|
+
* Other events, such as `CUSTOM` and `RAW`, return the thread unchanged.
|
|
132
|
+
*
|
|
133
|
+
* Use it as a `reduce` function, or wrap it to handle events of your own.
|
|
134
|
+
*/
|
|
135
|
+
function applyAgUiEvent(thread, event) {
|
|
136
|
+
const e = event;
|
|
137
|
+
const message = (role, extra) => ({
|
|
138
|
+
...thread,
|
|
139
|
+
messages: [...thread.messages, {
|
|
140
|
+
id: e.messageId,
|
|
141
|
+
role,
|
|
142
|
+
content: "",
|
|
143
|
+
...extra
|
|
144
|
+
}]
|
|
145
|
+
});
|
|
146
|
+
const known = (id) => thread.messages.some((m) => m.id === id);
|
|
147
|
+
switch (e.type) {
|
|
148
|
+
case "RUN_STARTED": return {
|
|
149
|
+
...thread,
|
|
150
|
+
status: "running",
|
|
151
|
+
error: void 0,
|
|
152
|
+
result: void 0
|
|
153
|
+
};
|
|
154
|
+
case "RUN_FINISHED": return {
|
|
155
|
+
...thread,
|
|
156
|
+
status: "finished",
|
|
157
|
+
step: void 0,
|
|
158
|
+
result: e.result
|
|
159
|
+
};
|
|
160
|
+
case "RUN_ERROR": return {
|
|
161
|
+
...thread,
|
|
162
|
+
status: "error",
|
|
163
|
+
step: void 0,
|
|
164
|
+
error: {
|
|
165
|
+
message: e.message,
|
|
166
|
+
code: e.code
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
case "STEP_STARTED": return {
|
|
170
|
+
...thread,
|
|
171
|
+
step: e.stepName
|
|
172
|
+
};
|
|
173
|
+
case "STEP_FINISHED": return {
|
|
174
|
+
...thread,
|
|
175
|
+
step: void 0
|
|
176
|
+
};
|
|
177
|
+
case "TEXT_MESSAGE_START": return message(e.role ?? "assistant");
|
|
178
|
+
case "REASONING_MESSAGE_START": return message("reasoning");
|
|
179
|
+
case "TEXT_MESSAGE_CONTENT":
|
|
180
|
+
case "REASONING_MESSAGE_CONTENT": return {
|
|
181
|
+
...thread,
|
|
182
|
+
messages: updateMessage(thread, e.messageId, (m) => appendText(m, e.delta))
|
|
183
|
+
};
|
|
184
|
+
case "TEXT_MESSAGE_CHUNK":
|
|
185
|
+
case "REASONING_MESSAGE_CHUNK": {
|
|
186
|
+
const id = e.messageId ?? thread.messages.at(-1)?.id;
|
|
187
|
+
const role = e.type === "TEXT_MESSAGE_CHUNK" ? e.role ?? "assistant" : "reasoning";
|
|
188
|
+
const next = known(id) ? thread : message(role);
|
|
189
|
+
return {
|
|
190
|
+
...next,
|
|
191
|
+
messages: updateMessage(next, id, (m) => appendText(m, e.delta))
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
case "TOOL_CALL_START":
|
|
195
|
+
case "TOOL_CALL_CHUNK": {
|
|
196
|
+
const id = e.toolCallId ?? lastToolCallId(thread);
|
|
197
|
+
let next = thread;
|
|
198
|
+
if (e.type === "TOOL_CALL_START" || !thread.messages.some((m) => m.toolCalls?.some((c) => c.id === id))) {
|
|
199
|
+
const call = {
|
|
200
|
+
id,
|
|
201
|
+
type: "function",
|
|
202
|
+
function: {
|
|
203
|
+
name: e.toolCallName,
|
|
204
|
+
arguments: ""
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const parent = e.parentMessageId ?? id;
|
|
208
|
+
next = known(parent) ? thread : {
|
|
209
|
+
...thread,
|
|
210
|
+
messages: [...thread.messages, {
|
|
211
|
+
id: parent,
|
|
212
|
+
role: "assistant"
|
|
213
|
+
}]
|
|
214
|
+
};
|
|
215
|
+
next = {
|
|
216
|
+
...next,
|
|
217
|
+
messages: updateMessage(next, parent, (m) => ({
|
|
218
|
+
...m,
|
|
219
|
+
toolCalls: [...m.toolCalls ?? [], call]
|
|
220
|
+
}))
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (e.type === "TOOL_CALL_START") return next;
|
|
224
|
+
return toolArgs(next, id, e.delta);
|
|
225
|
+
}
|
|
226
|
+
case "TOOL_CALL_ARGS": return toolArgs(thread, e.toolCallId, e.delta);
|
|
227
|
+
case "TOOL_CALL_RESULT": return message(e.role ?? "tool", {
|
|
228
|
+
toolCallId: e.toolCallId,
|
|
229
|
+
content: e.content
|
|
230
|
+
});
|
|
231
|
+
case "STATE_SNAPSHOT": return {
|
|
232
|
+
...thread,
|
|
233
|
+
state: e.snapshot,
|
|
234
|
+
stale: false
|
|
235
|
+
};
|
|
236
|
+
case "STATE_DELTA":
|
|
237
|
+
if (thread.stale) return thread;
|
|
238
|
+
try {
|
|
239
|
+
return {
|
|
240
|
+
...thread,
|
|
241
|
+
state: applyPatch(thread.state, e.delta)
|
|
242
|
+
};
|
|
243
|
+
} catch {
|
|
244
|
+
return {
|
|
245
|
+
...thread,
|
|
246
|
+
stale: true
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
case "MESSAGES_SNAPSHOT": return {
|
|
250
|
+
...thread,
|
|
251
|
+
messages: e.messages
|
|
252
|
+
};
|
|
253
|
+
case "ACTIVITY_SNAPSHOT": {
|
|
254
|
+
if (known(e.messageId) && e.replace === false) return thread;
|
|
255
|
+
const next = known(e.messageId) ? thread : message("activity");
|
|
256
|
+
return {
|
|
257
|
+
...next,
|
|
258
|
+
messages: updateMessage(next, e.messageId, (m) => ({
|
|
259
|
+
...m,
|
|
260
|
+
activityType: e.activityType,
|
|
261
|
+
content: e.content
|
|
262
|
+
}))
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
case "ACTIVITY_DELTA": try {
|
|
266
|
+
return {
|
|
267
|
+
...thread,
|
|
268
|
+
messages: updateMessage(thread, e.messageId, (m) => ({
|
|
269
|
+
...m,
|
|
270
|
+
content: applyPatch(m.content, e.patch)
|
|
271
|
+
}))
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
return thread;
|
|
275
|
+
}
|
|
276
|
+
default: return thread;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function toolArgs(thread, id, delta) {
|
|
280
|
+
return {
|
|
281
|
+
...thread,
|
|
282
|
+
messages: updateToolCall(thread, id, (call) => ({
|
|
283
|
+
...call,
|
|
284
|
+
function: {
|
|
285
|
+
...call.function,
|
|
286
|
+
arguments: call.function.arguments + (delta ?? "")
|
|
287
|
+
}
|
|
288
|
+
}))
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function randomId() {
|
|
292
|
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
293
|
+
}
|
|
294
|
+
const isEnvelope = (message) => typeof message?.runId === "string" && typeof message.offset === "number" && typeof message.event?.type === "string" ? message : void 0;
|
|
295
|
+
const ENDS = /* @__PURE__ */ new Set(["RUN_FINISHED", "RUN_ERROR"]);
|
|
296
|
+
/**
|
|
297
|
+
* Run an [AG-UI](https://docs.ag-ui.com) agent over a WebSocket. Each run streams AG-UI events,
|
|
298
|
+
* which are folded into the thread's messages and shared state with {@link applyAgUiEvent}.
|
|
299
|
+
* Runs come from `createWebSocketRuns`, so they resume after a reconnect or a reload, and a
|
|
300
|
+
* state delta that can't be applied asks for a snapshot (`resync`).
|
|
301
|
+
*
|
|
302
|
+
* AG-UI doesn't define messages for WebSockets, so the defaults are small: the client sends
|
|
303
|
+
* `{ type: 'run', runId, input }`, `{ type: 'resume', runId, after }` and
|
|
304
|
+
* `{ type: 'cancel', runId }`, and the server sends `{ runId, offset, event }`, with `offset`
|
|
305
|
+
* counting each run's events from 1. Each option replaces one of them.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { createAgUiAgent } from 'react-websocket/ag-ui';
|
|
310
|
+
*
|
|
311
|
+
* export const agent = createAgUiAgent(createWebSocket(url, { json: true }));
|
|
312
|
+
*
|
|
313
|
+
* agent.run({ messages: [{ id: crypto.randomUUID(), role: 'user', content: 'Plan my week' }] });
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
function createAgUiAgent(client, options = {}) {
|
|
317
|
+
const threadId = options.threadId ?? randomId();
|
|
318
|
+
const empty = {
|
|
319
|
+
messages: [],
|
|
320
|
+
state: options.state,
|
|
321
|
+
status: "idle",
|
|
322
|
+
stale: false
|
|
323
|
+
};
|
|
324
|
+
let base = empty;
|
|
325
|
+
let snapshot = empty;
|
|
326
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
327
|
+
const resynced = /* @__PURE__ */ new Set();
|
|
328
|
+
const event = options.event ?? isEnvelope;
|
|
329
|
+
const send = (message) => client.socket?.readyState === 1 && client.send(message);
|
|
330
|
+
const runs = (0, __core_cjs.createWebSocketRuns)(client, {
|
|
331
|
+
name: options.name ?? "ag-ui",
|
|
332
|
+
start: options.run ?? ((input, runId) => ({
|
|
333
|
+
type: "run",
|
|
334
|
+
runId,
|
|
335
|
+
input
|
|
336
|
+
})),
|
|
337
|
+
resume: options.resume ?? ((runId, after) => ({
|
|
338
|
+
type: "resume",
|
|
339
|
+
runId,
|
|
340
|
+
after
|
|
341
|
+
})),
|
|
342
|
+
cancel: options.cancel ?? ((runId) => ({
|
|
343
|
+
type: "cancel",
|
|
344
|
+
runId
|
|
345
|
+
})),
|
|
346
|
+
event: (message) => event(message),
|
|
347
|
+
done: (message) => ENDS.has(event(message).event.type),
|
|
348
|
+
initial: empty,
|
|
349
|
+
reduce: (thread, message) => applyAgUiEvent(thread, event(message).event),
|
|
350
|
+
policy: options.policy,
|
|
351
|
+
storage: options.storage
|
|
352
|
+
});
|
|
353
|
+
function update() {
|
|
354
|
+
const latest = runs.list().at(-1);
|
|
355
|
+
let next = latest ? latest.value : base;
|
|
356
|
+
if (latest?.status === "aborted" && next.status === "running") next = {
|
|
357
|
+
...next,
|
|
358
|
+
status: "aborted"
|
|
359
|
+
};
|
|
360
|
+
if (latest && !latest.value.stale) resynced.delete(latest.runId);
|
|
361
|
+
else if (latest && options.resync && !resynced.has(latest.runId)) {
|
|
362
|
+
if (send(options.resync(latest.runId))) resynced.add(latest.runId);
|
|
363
|
+
}
|
|
364
|
+
if (next === snapshot) return;
|
|
365
|
+
snapshot = next;
|
|
366
|
+
for (const listener of [...listeners]) listener();
|
|
367
|
+
}
|
|
368
|
+
const off = runs.subscribe(update);
|
|
369
|
+
update();
|
|
370
|
+
function abort() {
|
|
371
|
+
for (const run of runs.list()) runs.abort(run.runId);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
client,
|
|
375
|
+
runs,
|
|
376
|
+
run(input = {}, runOptions) {
|
|
377
|
+
abort();
|
|
378
|
+
const runId = randomId();
|
|
379
|
+
base = {
|
|
380
|
+
...snapshot,
|
|
381
|
+
stale: false,
|
|
382
|
+
messages: [...snapshot.messages, ...input.messages ?? []]
|
|
383
|
+
};
|
|
384
|
+
runs.start({
|
|
385
|
+
threadId,
|
|
386
|
+
runId,
|
|
387
|
+
state: base.state,
|
|
388
|
+
messages: base.messages,
|
|
389
|
+
tools: input.tools ?? [],
|
|
390
|
+
context: input.context ?? [],
|
|
391
|
+
forwardedProps: input.forwardedProps ?? {}
|
|
392
|
+
}, {
|
|
393
|
+
runId,
|
|
394
|
+
initial: {
|
|
395
|
+
...base,
|
|
396
|
+
status: "running"
|
|
397
|
+
},
|
|
398
|
+
policy: runOptions?.policy
|
|
399
|
+
});
|
|
400
|
+
return runId;
|
|
401
|
+
},
|
|
402
|
+
abort,
|
|
403
|
+
getSnapshot: () => snapshot,
|
|
404
|
+
subscribe(listener) {
|
|
405
|
+
listeners.add(listener);
|
|
406
|
+
return () => {
|
|
407
|
+
listeners.delete(listener);
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
dispose() {
|
|
411
|
+
off();
|
|
412
|
+
runs.dispose();
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
exports.applyAgUiEvent = applyAgUiEvent;
|
|
418
|
+
exports.applyPatch = applyPatch;
|
|
419
|
+
exports.createAgUiAgent = createAgUiAgent;
|
|
420
|
+
|
|
421
|
+
//# sourceMappingURL=ag-ui.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ag-ui.cjs","names":["createWebSocketRuns"],"sources":["../src/ag-ui.ts"],"sourcesContent":["// The build keeps `./core` external, so this entry shares the client with the others.\nimport {\n createWebSocketRuns,\n type RunPolicy,\n type RunStorage,\n type WebSocketClient,\n type WebSocketRuns\n} from './core';\n\n/** A JSON Patch (RFC 6902) operation, as in AG-UI's `STATE_DELTA` and `ACTIVITY_DELTA`. */\nexport interface JsonPatchOperation {\n readonly op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';\n /** A JSON Pointer (RFC 6901), such as `/items/0/name`. */\n readonly path: string;\n /** The source of `move` and `copy`. */\n readonly from?: string;\n /** The value to `add`, `replace` with or `test` for. */\n readonly value?: unknown;\n}\n\n/** An AG-UI event: `type` is `'RUN_STARTED'`, `'TEXT_MESSAGE_CONTENT'`, `'STATE_DELTA'`, … */\nexport interface AgUiEvent {\n readonly type: string;\n readonly [field: string]: any;\n}\n\n/** A tool call on an assistant message, as in AG-UI. */\nexport interface AgUiToolCall {\n readonly id: string;\n readonly type: 'function';\n readonly function: { readonly name: string; readonly arguments: string };\n}\n\n/** A message in an AG-UI thread. */\nexport interface AgUiMessage {\n readonly id: string;\n readonly role: 'developer' | 'system' | 'assistant' | 'user' | 'tool' | 'reasoning' | 'activity';\n readonly content?: unknown;\n readonly toolCalls?: readonly AgUiToolCall[];\n /** For a `tool` message, the call it answers. */\n readonly toolCallId?: string;\n /** For an `activity` message. */\n readonly activityType?: string;\n readonly [field: string]: unknown;\n}\n\n/** AG-UI's `RunAgentInput`: what a run starts from. */\nexport interface RunAgentInput {\n readonly threadId: string;\n readonly runId: string;\n readonly parentRunId?: string;\n readonly state?: unknown;\n readonly messages: readonly AgUiMessage[];\n readonly tools: readonly unknown[];\n readonly context: readonly unknown[];\n readonly forwardedProps: unknown;\n}\n\n/** A thread's messages and shared state, reduced from AG-UI events. */\nexport interface AgUiThread<S = unknown> {\n readonly messages: readonly AgUiMessage[];\n /** The shared state, from `STATE_SNAPSHOT` and `STATE_DELTA`. */\n readonly state: S;\n /**\n * `'running'` from `RUN_STARTED`, `'finished'` after `RUN_FINISHED`, `'error'` after\n * `RUN_ERROR`, `'aborted'` when the run was stopped from this side.\n */\n readonly status: 'idle' | 'running' | 'finished' | 'error' | 'aborted';\n /** The step in progress, between `STEP_STARTED` and `STEP_FINISHED`. */\n readonly step?: string;\n /** From `RUN_ERROR`. */\n readonly error?: { readonly message: string; readonly code?: string };\n /** From `RUN_FINISHED`. */\n readonly result?: unknown;\n /**\n * A `STATE_DELTA` couldn't be applied. `state` is the last good one, and later deltas are\n * skipped until a `STATE_SNAPSHOT` arrives.\n */\n readonly stale: boolean;\n}\n\ntype Json = any;\n\nfunction unescape(token: string) {\n return token.replace(/~1/g, '/').replace(/~0/g, '~');\n}\n\nfunction equal(a: Json, b: Json): boolean {\n if (a === b) return true;\n if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const keys = Object.keys(a);\n return keys.length === Object.keys(b).length && keys.every(key => equal(a[key], b[key]));\n}\n\nfunction fail(operation: JsonPatchOperation): never {\n throw new Error(`JSON Patch failed: ${operation.op} ${operation.path}`);\n}\n\nconst INDEX = /^(0|[1-9]\\d*)$/;\n\n/** A JSON Pointer's tokens. It's empty, for the whole document, or starts with `/`. */\nfunction tokens(pointer: string, operation: JsonPatchOperation) {\n if (pointer !== '' && pointer[0] !== '/') fail(operation);\n return pointer.split('/').slice(1).map(unescape);\n}\n\n/** Whether `node` has `token`: an index in range for an array, not `length`; an own key otherwise. */\nfunction has(node: Json, token: string) {\n return Array.isArray(node)\n ? INDEX.test(token) && +token < node.length\n : Object.hasOwn(node, token);\n}\n\n/** Set an own property, even `__proto__`, as JSON.parse would. */\nfunction put(object: Json, key: string, value: Json) {\n Object.defineProperty(object, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\n });\n}\n\n/**\n * Apply a JSON Patch (RFC 6902) without changing `document`: what changes is copied, the rest is\n * shared. Throws, leaving `document` as it was, if an operation can't be applied: a missing path,\n * an index out of range, a pointer that doesn't start with `/`, or a failed `test`.\n */\nexport function applyPatch<T>(document: T, operations: readonly JsonPatchOperation[]): T {\n let root: Json = document;\n\n function get(path: string, operation: JsonPatchOperation): Json {\n let node = root;\n for (const token of tokens(path, operation)) {\n if (typeof node !== 'object' || node === null || !has(node, token)) fail(operation);\n node = node[token];\n }\n return node;\n }\n\n // Replace the parent of `path` with a copy, change it with `change`, and copy its ancestors.\n function edit(\n path: string,\n operation: JsonPatchOperation,\n change: (parent: Json, key: string) => void\n ) {\n const keys = tokens(path, operation);\n if (!keys.length) fail(operation);\n const walk = (node: Json, depth: number): Json => {\n if (typeof node !== 'object' || node === null) fail(operation);\n const copy = Array.isArray(node) ? [...node] : { ...node };\n const key = keys[depth]!;\n if (depth === keys.length - 1) change(copy, key);\n else if (!has(node, key)) fail(operation);\n else copy[key] = walk(node[key], depth + 1);\n return copy;\n };\n root = walk(root, 0);\n }\n\n function index(parent: Json[], key: string, operation: JsonPatchOperation, adding: boolean) {\n // `-` is the end of the array, which only `add` can use.\n const i = key === '-' ? (adding ? parent.length : -1) : INDEX.test(key) ? +key : -1;\n if (i < 0 || i > parent.length - (adding ? 0 : 1)) fail(operation);\n return i;\n }\n\n function add(path: string, value: Json, operation: JsonPatchOperation) {\n if (path === '') return void (root = value);\n edit(path, operation, (parent, key) => {\n if (Array.isArray(parent)) parent.splice(index(parent, key, operation, true), 0, value);\n else put(parent, key, value);\n });\n }\n\n function remove(path: string, operation: JsonPatchOperation) {\n edit(path, operation, (parent, key) => {\n if (Array.isArray(parent)) parent.splice(index(parent, key, operation, false), 1);\n else if (Object.hasOwn(parent, key)) delete parent[key];\n else fail(operation);\n });\n }\n\n for (const operation of operations) {\n const { op, path, from, value } = operation;\n if (op === 'add') add(path, value, operation);\n else if (op === 'remove') remove(path, operation);\n else if (op === 'replace') {\n get(path, operation);\n if (path === '') root = value;\n else\n edit(path, operation, (parent, key) => {\n if (Array.isArray(parent)) parent[key as unknown as number] = value;\n else put(parent, key, value);\n });\n } else if (op === 'move' || op === 'copy') {\n const moved = get(from ?? fail(operation), operation);\n if (op === 'move') remove(from!, operation);\n add(path, op === 'copy' ? structuredClone(moved) : moved, operation);\n } else if (op === 'test') {\n if (!equal(get(path, operation), value)) fail(operation);\n } else fail(operation);\n }\n return root;\n}\n\nfunction updateMessage(\n thread: AgUiThread<any>,\n id: string | undefined,\n change: (message: AgUiMessage) => AgUiMessage\n) {\n return thread.messages.map(message => (message.id === id ? change(message) : message));\n}\n\nfunction appendText(message: AgUiMessage, delta: string | undefined) {\n return { ...message, content: `${(message.content as string | undefined) ?? ''}${delta ?? ''}` };\n}\n\nfunction updateToolCall(\n thread: AgUiThread<any>,\n id: string | undefined,\n change: (call: AgUiToolCall) => AgUiToolCall\n) {\n return thread.messages.map(message =>\n message.toolCalls?.some(call => call.id === id)\n ? {\n ...message,\n toolCalls: message.toolCalls.map(call => (call.id === id ? change(call) : call))\n }\n : message\n );\n}\n\n/** The last message with a tool call, for chunks that leave out `toolCallId`. */\nfunction lastToolCallId(thread: AgUiThread<any>) {\n for (let i = thread.messages.length - 1; i >= 0; i--) {\n const calls = thread.messages[i]!.toolCalls;\n if (calls?.length) return calls.at(-1)!.id;\n }\n}\n\n/**\n * Fold one AG-UI event into a thread, without changing it. Handles the run lifecycle, steps,\n * text, tool calls and their results, reasoning, activity, and message and state snapshots and\n * deltas. A `STATE_DELTA` that can't be applied marks the thread `stale` instead of throwing.\n * Other events, such as `CUSTOM` and `RAW`, return the thread unchanged.\n *\n * Use it as a `reduce` function, or wrap it to handle events of your own.\n */\nexport function applyAgUiEvent<S>(thread: AgUiThread<S>, event: AgUiEvent): AgUiThread<S> {\n const e = event;\n const message = (role: AgUiMessage['role'], extra?: object): AgUiThread<S> => ({\n ...thread,\n messages: [...thread.messages, { id: e.messageId, role, content: '', ...extra }]\n });\n const known = (id: string | undefined) => thread.messages.some(m => m.id === id);\n switch (e.type) {\n case 'RUN_STARTED':\n return { ...thread, status: 'running', error: undefined, result: undefined };\n case 'RUN_FINISHED':\n return { ...thread, status: 'finished', step: undefined, result: e.result };\n case 'RUN_ERROR':\n return {\n ...thread,\n status: 'error',\n step: undefined,\n error: { message: e.message, code: e.code }\n };\n case 'STEP_STARTED':\n return { ...thread, step: e.stepName };\n case 'STEP_FINISHED':\n return { ...thread, step: undefined };\n case 'TEXT_MESSAGE_START':\n return message(e.role ?? 'assistant');\n case 'REASONING_MESSAGE_START':\n return message('reasoning');\n case 'TEXT_MESSAGE_CONTENT':\n case 'REASONING_MESSAGE_CONTENT':\n return {\n ...thread,\n messages: updateMessage(thread, e.messageId, m => appendText(m, e.delta))\n };\n case 'TEXT_MESSAGE_CHUNK':\n case 'REASONING_MESSAGE_CHUNK': {\n // Only the first chunk of a message must carry its id.\n const id = e.messageId ?? thread.messages.at(-1)?.id;\n const role = e.type === 'TEXT_MESSAGE_CHUNK' ? (e.role ?? 'assistant') : 'reasoning';\n const next = known(id) ? thread : message(role);\n return { ...next, messages: updateMessage(next, id, m => appendText(m, e.delta)) };\n }\n case 'TOOL_CALL_START':\n case 'TOOL_CALL_CHUNK': {\n const id = e.toolCallId ?? lastToolCallId(thread);\n let next = thread;\n if (\n e.type === 'TOOL_CALL_START' ||\n !thread.messages.some(m => m.toolCalls?.some(c => c.id === id))\n ) {\n const call: AgUiToolCall = {\n id,\n type: 'function',\n function: { name: e.toolCallName, arguments: '' }\n };\n const parent = e.parentMessageId ?? id;\n next = known(parent)\n ? thread\n : { ...thread, messages: [...thread.messages, { id: parent, role: 'assistant' }] };\n next = {\n ...next,\n messages: updateMessage(next, parent, m => ({\n ...m,\n toolCalls: [...(m.toolCalls ?? []), call]\n }))\n };\n }\n if (e.type === 'TOOL_CALL_START') return next;\n return toolArgs(next, id, e.delta);\n }\n case 'TOOL_CALL_ARGS':\n return toolArgs(thread, e.toolCallId, e.delta);\n case 'TOOL_CALL_RESULT':\n return message(e.role ?? 'tool', { toolCallId: e.toolCallId, content: e.content });\n case 'STATE_SNAPSHOT':\n return { ...thread, state: e.snapshot, stale: false };\n case 'STATE_DELTA':\n if (thread.stale) return thread;\n try {\n return { ...thread, state: applyPatch(thread.state, e.delta) };\n } catch {\n return { ...thread, stale: true };\n }\n case 'MESSAGES_SNAPSHOT':\n return { ...thread, messages: e.messages };\n case 'ACTIVITY_SNAPSHOT': {\n if (known(e.messageId) && e.replace === false) return thread;\n const next = known(e.messageId) ? thread : message('activity');\n return {\n ...next,\n messages: updateMessage(next, e.messageId, m => ({\n ...m,\n activityType: e.activityType,\n content: e.content\n }))\n };\n }\n case 'ACTIVITY_DELTA':\n try {\n return {\n ...thread,\n messages: updateMessage(thread, e.messageId, m => ({\n ...m,\n content: applyPatch(m.content, e.patch)\n }))\n };\n } catch {\n return thread;\n }\n default:\n return thread;\n }\n}\n\nfunction toolArgs<S>(thread: AgUiThread<S>, id: string | undefined, delta: string | undefined) {\n return {\n ...thread,\n messages: updateToolCall(thread, id, call => ({\n ...call,\n function: { ...call.function, arguments: call.function.arguments + (delta ?? '') }\n }))\n };\n}\n\n/** What the server sends for each event of a run: its run, its offset (from 1), and the event. */\nexport interface AgUiEnvelope {\n readonly runId: string;\n readonly offset: number;\n readonly event: AgUiEvent;\n}\n\n/** Options for {@link createAgUiAgent}. The message formats have defaults; see the guide. */\nexport interface AgUiAgentOptions<S, In, Out> {\n /** The thread's id, sent in every `RunAgentInput`. A new random one by default. */\n threadId?: string;\n /** The shared state before the first `STATE_SNAPSHOT`, sent with the first run. */\n state?: S;\n /** Names where streaming runs are remembered across reloads. @default 'ag-ui' */\n name?: string;\n /** @default `{ type: 'run', runId, input }` */\n run?: (input: RunAgentInput, runId: string) => Out;\n /** @default `{ type: 'resume', runId, after }` */\n resume?: (runId: string, after: number | undefined) => Out;\n /** @default `{ type: 'cancel', runId }` */\n cancel?: (runId: string) => Out;\n /**\n * The message that asks the server for a `STATE_SNAPSHOT`, sent when a `STATE_DELTA` can't\n * be applied. Without it, the state stays `stale` until the server sends one by itself.\n */\n resync?: (runId: string) => Out;\n /** Recognize a run's event. @default messages shaped like {@link AgUiEnvelope} */\n event?: (message: In) => AgUiEnvelope | null | undefined;\n /** What happens to a run when its connection drops. @default 'keep' */\n policy?: RunPolicy;\n /** Where streaming runs are remembered. @default sessionStorage */\n storage?: RunStorage | null;\n}\n\n/** An AG-UI agent over a WebSocket. Create with {@link createAgUiAgent}. */\nexport interface AgUiAgent<S> {\n /** The client the runs stream on. */\n readonly client: WebSocketClient<any, any>;\n /** The runs underneath, from `createWebSocketRuns`. */\n readonly runs: WebSocketRuns<RunAgentInput, AgUiThread<S>>;\n /**\n * Start a run with the thread so far plus `input.messages`, which show up in the thread right\n * away. A run that's still streaming is aborted first. Returns the run's id.\n */\n run(\n input?: {\n messages?: readonly AgUiMessage[];\n tools?: readonly unknown[];\n context?: readonly unknown[];\n forwardedProps?: unknown;\n },\n options?: { policy?: RunPolicy }\n ): string;\n /** Stop the run that's streaming, if any. */\n abort(): void;\n /** The thread as of the latest run. The same object until it changes. */\n getSnapshot(): AgUiThread<S>;\n /** Get notified when the thread changes. Compatible with `useSyncExternalStore`. */\n subscribe(listener: () => void): () => void;\n /** Stop following runs, and release the connection. */\n dispose(): void;\n}\n\nfunction randomId() {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n );\n}\n\nconst isEnvelope = (message: any): AgUiEnvelope | undefined =>\n typeof message?.runId === 'string' &&\n typeof message.offset === 'number' &&\n typeof message.event?.type === 'string'\n ? message\n : undefined;\n\nconst ENDS = new Set(['RUN_FINISHED', 'RUN_ERROR']);\n\n/**\n * Run an [AG-UI](https://docs.ag-ui.com) agent over a WebSocket. Each run streams AG-UI events,\n * which are folded into the thread's messages and shared state with {@link applyAgUiEvent}.\n * Runs come from `createWebSocketRuns`, so they resume after a reconnect or a reload, and a\n * state delta that can't be applied asks for a snapshot (`resync`).\n *\n * AG-UI doesn't define messages for WebSockets, so the defaults are small: the client sends\n * `{ type: 'run', runId, input }`, `{ type: 'resume', runId, after }` and\n * `{ type: 'cancel', runId }`, and the server sends `{ runId, offset, event }`, with `offset`\n * counting each run's events from 1. Each option replaces one of them.\n *\n * @example\n * ```ts\n * import { createAgUiAgent } from 'react-websocket/ag-ui';\n *\n * export const agent = createAgUiAgent(createWebSocket(url, { json: true }));\n *\n * agent.run({ messages: [{ id: crypto.randomUUID(), role: 'user', content: 'Plan my week' }] });\n * ```\n */\nexport function createAgUiAgent<S = unknown, In = any, Out = any>(\n client: WebSocketClient<In, Out>,\n options: AgUiAgentOptions<S, NoInfer<In>, NoInfer<Out>> = {}\n): AgUiAgent<S> {\n const threadId = options.threadId ?? randomId();\n const empty: AgUiThread<S> = {\n messages: [],\n state: options.state as S,\n status: 'idle',\n stale: false\n };\n let base = empty;\n let snapshot = empty;\n const listeners = new Set<() => void>();\n // Runs that asked for a snapshot after a failed delta, until one arrives.\n const resynced = new Set<string>();\n const event = options.event ?? isEnvelope;\n // Now or never: not queued, and not `volatile`, which can be a datagram that never arrives.\n const send = (message: unknown) => client.socket?.readyState === 1 && client.send(message as Out);\n\n const runs = createWebSocketRuns<RunAgentInput, AgUiThread<S>, In, Out>(client, {\n name: options.name ?? 'ag-ui',\n start: options.run ?? ((input, runId) => ({ type: 'run', runId, input }) as Out),\n resume: options.resume ?? ((runId, after) => ({ type: 'resume', runId, after }) as Out),\n cancel: options.cancel ?? (runId => ({ type: 'cancel', runId }) as Out),\n event: message => event(message),\n done: message => ENDS.has(event(message)!.event.type),\n initial: empty,\n reduce: (thread, message) => applyAgUiEvent(thread, event(message)!.event),\n policy: options.policy,\n storage: options.storage\n });\n\n function update() {\n const latest = runs.list().at(-1);\n let next = latest ? latest.value : base;\n if (latest?.status === 'aborted' && next.status === 'running')\n next = { ...next, status: 'aborted' };\n // Once each time the state goes stale, or on a later event if the socket wasn't open.\n if (latest && !latest.value.stale) resynced.delete(latest.runId);\n else if (latest && options.resync && !resynced.has(latest.runId)) {\n if (send(options.resync(latest.runId))) resynced.add(latest.runId);\n }\n if (next === snapshot) return;\n snapshot = next;\n for (const listener of [...listeners]) listener();\n }\n const off = runs.subscribe(update);\n update();\n\n function abort() {\n for (const run of runs.list()) runs.abort(run.runId);\n }\n\n return {\n client,\n runs,\n run(input = {}, runOptions) {\n abort();\n const runId = randomId();\n // The run starts from the state sent with it, the last good one: its deltas apply to it.\n base = {\n ...snapshot,\n stale: false,\n messages: [...snapshot.messages, ...(input.messages ?? [])]\n };\n runs.start(\n {\n threadId,\n runId,\n state: base.state,\n messages: base.messages,\n tools: input.tools ?? [],\n context: input.context ?? [],\n forwardedProps: input.forwardedProps ?? {}\n },\n { runId, initial: { ...base, status: 'running' }, policy: runOptions?.policy }\n );\n return runId;\n },\n abort,\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n dispose() {\n off();\n runs.dispose();\n }\n };\n}\n"],"mappings":"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"}
|