agents-vue 0.2.1 β 0.4.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/CHANGELOG.md +33 -0
- package/README.md +116 -12
- package/dist/index.d.mts +56 -95
- package/dist/index.d.ts +56 -95
- package/dist/index.mjs +260 -516
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.4.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/ymansurozer/agents-vue/compare/v0.3.0...v0.4.0)
|
|
7
|
+
|
|
8
|
+
### π Enhancements
|
|
9
|
+
|
|
10
|
+
- `sendOnlyNewMessages` option: a send carries only the messages after the last one the server is known to hold, naming it as `afterMessageId` in the request body, rather than the whole transcript. An edit, or a bare `sendMessage()` resubmitting after an approval or a tool output, carries the message it rewrote; a regenerate carries the full list. The server must rebuild the full list before it persists or broadcasts the request: when the first message sent is one it stores, its stored messages before that one, then the ones sent; otherwise its whole stored transcript, then the sent messages it does not already store, so a client that missed another's messages does not erase them. The option is off by default, and with it off a request body is unchanged.
|
|
11
|
+
|
|
12
|
+
## v0.3.0
|
|
13
|
+
|
|
14
|
+
Rebased onto AI SDK v7 and `agents` 0.27, with upstream's own transport and the four client-side bugs the Vue port carried fixed against the React client.
|
|
15
|
+
|
|
16
|
+
### β οΈ Breaking changes
|
|
17
|
+
|
|
18
|
+
- Peer dependencies: `ai ^7`, `@ai-sdk/vue ^4`, `agents >=0.27.0 <1`, `vue ^3.5`
|
|
19
|
+
- `WebSocketChatTransport` and `WebSocketLike` are no longer exported: the hand-rolled transport is deleted and the composable wires upstream's `WebSocketChatTransport` from `agents/chat/transport`. Import it from `agents` if you were using it directly.
|
|
20
|
+
|
|
21
|
+
### π©Ή Fixes
|
|
22
|
+
|
|
23
|
+
- Client-side tool calls are dispatched off the rendered transcript, deduped by tool-call id, rather than off the AI SDK's own callback alone. A call in a turn this client only observed β a resumed turn, a turn another tab started β used to render as a chip and never run.
|
|
24
|
+
- An early `cf_agent_stream_resuming` is either fed to the continuation this client armed or observed as the server's own turn, never both. Doing both delivered the same turn twice.
|
|
25
|
+
- The continuation is armed before the `cf_agent_tool_result` / `cf_agent_tool_approval` frame is sent, so the common case does not race at all.
|
|
26
|
+
- A streaming assistant tail that matches nothing in an incoming sync is dropped rather than appended, which is what put the same turn on screen twice.
|
|
27
|
+
- Message ids are minted with `crypto.randomUUID` and otherwise from `crypto.getRandomValues`, never `Math.random`.
|
|
28
|
+
- A turn another tab started can be stopped from the watching tab, and an errored observed turn completes: the chunk handler registers a turn this client is only watching, and the completion branch reads an error frame as an ending rather than waiting for a done that never comes.
|
|
29
|
+
- A continuation handed to the server is released when the socket carrying that turn is replaced: the reconnect probe runs for exactly that case, and a `cf_agent_stream_resume_none` settles the continuation it answers. Without it a dropped socket left the page unable to auto-continue after any later tool result.
|
|
30
|
+
|
|
31
|
+
### π Enhancements
|
|
32
|
+
|
|
33
|
+
- `onFrame` option: every socket frame, parsed, before the chat protocol reads it; return `true` to consume it. This is where an application's own frames belong.
|
|
34
|
+
- `mergeMessages` option: how a reconnect's read of the stored transcript joins the one on screen. Defaults to merge-by-id with the stored copy winning.
|
|
35
|
+
- `onStateUpdate` forwarded to the `AgentClient`, `setAgent()` exposed, `connectionState` derived from the socket's own open/close, `retryPendingResume()` on open, and an async `get-messages` seed with a re-read on a replacement socket.
|
|
36
|
+
|
|
4
37
|
## v0.2.1
|
|
5
38
|
|
|
6
39
|
[compare changes](https://github.com/ymansurozer/agents-vue/compare/v0.2.0...v0.2.1)
|
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ Vue 3 composable for [Cloudflare Agents](https://github.com/cloudflare/agents)
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
Covers the surface needed for a working chat UI β streaming responses, client-side tools, tool approvals, stream resumption, broadcast handling β at parity with upstream `@cloudflare/ai-chat` 0.
|
|
9
|
+
Covers the surface needed for a working chat UI β streaming responses, client-side tools, tool approvals, stream resumption, broadcast handling β at parity with upstream `@cloudflare/ai-chat` 0.12.x on resume/recovery: resume dedupe, `isRecovering`, `cancelOnClientAbort`, pre-stream `cf_agent_stream_pending`, and `connectionError`. Deeper upstream features (the `useAgent` connection hook, the cross-tab observer, `isServerStreaming` / `isStreaming` flags) are on the roadmap.
|
|
10
|
+
|
|
11
|
+
0.3.0 moves the transport to upstream's own `WebSocketChatTransport` from `agents/chat/transport` and fixes four client-side bugs β see [CHANGELOG](./CHANGELOG.md).
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
@@ -18,10 +20,10 @@ Peer dependencies (must also be installed):
|
|
|
18
20
|
|
|
19
21
|
| Peer | Version |
|
|
20
22
|
|---|---|
|
|
21
|
-
| `vue` | `^3.
|
|
22
|
-
| `@ai-sdk/vue` | `^
|
|
23
|
-
| `ai` | `^
|
|
24
|
-
| `agents` | `>=0.
|
|
23
|
+
| `vue` | `^3.5.0` |
|
|
24
|
+
| `@ai-sdk/vue` | `^4.0.0` |
|
|
25
|
+
| `ai` | `^7.0.0` |
|
|
26
|
+
| `agents` | `>=0.27.0 <1.0.0` |
|
|
25
27
|
|
|
26
28
|
## Usage
|
|
27
29
|
|
|
@@ -98,6 +100,98 @@ Returns:
|
|
|
98
100
|
| `onClose` | `(event: CloseEvent) => void` | β | WebSocket close. |
|
|
99
101
|
| `onSocketError` | `(event: Event) => void` | β | Connection-level error. |
|
|
100
102
|
| `onError` | `(error: Error) => void` | β | Chat-turn error from the AI SDK. |
|
|
103
|
+
| `onStateUpdate` | `(state: State, source: "server" \| "client") => void` | β | The agent's state, on connect and on every change the server broadcasts. |
|
|
104
|
+
| `onFrame` | `(frame: unknown) => boolean \| void` | β | Every socket frame, parsed, **before** the chat protocol reads it. Return `true` to consume it. |
|
|
105
|
+
| `mergeMessages` | `(onScreen, stored) => ChatMessage[]` | merge by id | How a reconnect's read of the stored transcript joins the one on screen. |
|
|
106
|
+
| `sendOnlyNewMessages` | `boolean` | `false` | A send carries only the messages after the last one the server is known to hold, named as `afterMessageId`. The server must rebuild the rest. |
|
|
107
|
+
|
|
108
|
+
### `onFrame` β your own frames
|
|
109
|
+
|
|
110
|
+
The chat protocol parser narrows a frame to the fields `cf_agent_*` messages have and drops every
|
|
111
|
+
other one, so a frame of your own would arrive with its contents removed. `onFrame` runs first and
|
|
112
|
+
sees the frame whole; returning `true` consumes it, and the protocol handling never sees it.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
useAgentChat({
|
|
116
|
+
client: { /* ... */ },
|
|
117
|
+
onFrame: (frame) => {
|
|
118
|
+
if (!isRecord(frame) || frame.type !== "my_app_event") return;
|
|
119
|
+
showBanner(frame);
|
|
120
|
+
return true; // consumed
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `mergeMessages` β what a reconnect does to the transcript
|
|
126
|
+
|
|
127
|
+
Nothing pushes a snapshot on connect, so a replacement socket re-reads the stored transcript β and
|
|
128
|
+
that read is a round trip of its own: it can be issued before a turn is persisted and land after the
|
|
129
|
+
socket has already delivered the finished answer. The default merges by id with the stored copy
|
|
130
|
+
winning and anything only on screen kept after it. Pass your own policy to decide differently.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import type { UIMessage } from "ai";
|
|
134
|
+
|
|
135
|
+
const textLength = (message: UIMessage) =>
|
|
136
|
+
message.parts.reduce((total, part) => total + (part.type === "text" ? part.text.length : 0), 0);
|
|
137
|
+
|
|
138
|
+
/** The stored copy can be behind the stream, so the longer of the two wins. */
|
|
139
|
+
const fresher = (stored: UIMessage, onScreen: UIMessage | undefined) =>
|
|
140
|
+
onScreen && textLength(onScreen) > textLength(stored) ? onScreen : stored;
|
|
141
|
+
|
|
142
|
+
useAgentChat({
|
|
143
|
+
client: { /* ... */ },
|
|
144
|
+
mergeMessages: (onScreen, stored) => {
|
|
145
|
+
const live = new Map(onScreen.map((message) => [message.id, message]));
|
|
146
|
+
const storedIds = new Set(stored.map((message) => message.id));
|
|
147
|
+
|
|
148
|
+
return [
|
|
149
|
+
...stored.map((message) => fresher(message, live.get(message.id))),
|
|
150
|
+
// Anything only on screen β a question typed while the socket was down β is kept after.
|
|
151
|
+
...onScreen.filter((message) => !storedIds.has(message.id)),
|
|
152
|
+
];
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `sendOnlyNewMessages` β sending what the server does not have
|
|
158
|
+
|
|
159
|
+
By default every send carries the whole transcript, so a request grows with the session. With
|
|
160
|
+
`sendOnlyNewMessages: true` it carries only the messages after the last one the server is known to
|
|
161
|
+
hold β usually just the new question β and names that message as `afterMessageId`, beside
|
|
162
|
+
`messages` in the request body. Known to hold means seen arriving from the server: `initialMessages`,
|
|
163
|
+
the `get-messages` read, a broadcast, a `cf_agent_message_updated` frame, or a stream that finished
|
|
164
|
+
without an error with a part in its reply. A message only this client has never counts. A request
|
|
165
|
+
that rewrites a message in place carries it too: an edited question, or the assistant message a
|
|
166
|
+
bare `sendMessage()` resubmits after an approval or a tool output. A regenerate, and a send before
|
|
167
|
+
the server is known to hold anything, carry the full list and no `afterMessageId`.
|
|
168
|
+
|
|
169
|
+
A client tool's output is not part of any request: it reaches the server in a `cf_agent_tool_result`
|
|
170
|
+
frame, with the option on or off. What changes is the next request. A question typed after a tool
|
|
171
|
+
output carries only the question, where the full list re-sent the output along with everything
|
|
172
|
+
else; only a request a bare `sendMessage()` makes carries the rewritten assistant message.
|
|
173
|
+
|
|
174
|
+
**The server's half.** A request with `afterMessageId` holds only part of the transcript, so the
|
|
175
|
+
server must rebuild the full list before it persists the request or broadcasts it β `AIChatAgent`
|
|
176
|
+
broadcasts a request's list to the other clients as it arrived. When the first message sent is one
|
|
177
|
+
it already stores, at index `k`, the request rewrites in place, and the full list is its stored
|
|
178
|
+
messages before `k`, then the ones sent. Otherwise the messages sent are new and go after the whole
|
|
179
|
+
stored transcript, less any it already stores, so a client that missed messages another client
|
|
180
|
+
added does not erase them. The rebuild reads `afterMessageId` only to know the list is partial: its
|
|
181
|
+
value, the last message the client saw the server hold, is a hint, and can name a reply the server
|
|
182
|
+
never stored. A request without the field carries the full list, as before. `AIChatAgent` does none
|
|
183
|
+
of this on its own: it takes a request's list for the whole transcript and deletes the stored
|
|
184
|
+
messages missing from it, which is why the option is off by default.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
function rebuild(stored: UIMessage[], sent: UIMessage[], afterMessageId?: string): UIMessage[] {
|
|
188
|
+
if (afterMessageId === undefined) return sent;
|
|
189
|
+
const rewriteAt = stored.findIndex((message) => message.id === sent[0]?.id);
|
|
190
|
+
if (rewriteAt >= 0) return [...stored.slice(0, rewriteAt), ...sent];
|
|
191
|
+
const storedIds = new Set(stored.map((message) => message.id));
|
|
192
|
+
return [...stored, ...sent.filter((message) => !storedIds.has(message.id))];
|
|
193
|
+
}
|
|
194
|
+
```
|
|
101
195
|
|
|
102
196
|
### Client-side tools
|
|
103
197
|
|
|
@@ -133,25 +227,27 @@ The composable matches the approval id against a `tool-approve` part in `chat.me
|
|
|
133
227
|
|
|
134
228
|
### `WebSocketChatTransport`
|
|
135
229
|
|
|
136
|
-
|
|
230
|
+
No longer exported from here. Since 0.3.0 the composable wires upstream's own transport, so import
|
|
231
|
+
it from `agents` directly if you are building a non-`@ai-sdk/vue` flow over the same `cf_agent_*`
|
|
232
|
+
wire protocol:
|
|
137
233
|
|
|
138
234
|
```ts
|
|
139
|
-
import { WebSocketChatTransport
|
|
235
|
+
import { WebSocketChatTransport } from "agents/chat/transport";
|
|
140
236
|
|
|
141
237
|
const transport = new WebSocketChatTransport({
|
|
142
|
-
|
|
238
|
+
agent, // an AgentClient
|
|
143
239
|
prepareBody, // optional
|
|
144
240
|
});
|
|
145
241
|
```
|
|
146
242
|
|
|
147
243
|
## Coverage vs upstream
|
|
148
244
|
|
|
149
|
-
`agents-vue
|
|
245
|
+
`agents-vue` covers the surface real apps use. Wire protocol is identical to upstream β same `cf_agent_*` message shapes, same resumable streaming, same broadcast transitions, same tool continuation flow.
|
|
150
246
|
|
|
151
247
|
| Feature | Status |
|
|
152
248
|
|---|---|
|
|
153
249
|
| `useAgentChat` composable | β
|
|
|
154
|
-
| `WebSocketChatTransport` | β
|
|
|
250
|
+
| `WebSocketChatTransport` (upstream's, from `agents/chat/transport`) | β
|
|
|
155
251
|
| Streaming responses | β
|
|
|
156
252
|
| Client-side tools with `execute` | β
|
|
|
157
253
|
| Tool approvals (HITL) | β
|
|
|
@@ -166,8 +262,16 @@ const transport = new WebSocketChatTransport({
|
|
|
166
262
|
| Pre-stream `cf_agent_stream_pending` (extended resume probe) | β
|
|
|
167
263
|
| `connectionError` (terminal WebSocket close) | β
|
|
|
168
264
|
| Reconnect resume serialization (in-flight gate, `agents` 0.17.2 / #1837) | β
|
|
|
265
|
+
| `setAgent()`, which invalidates the transport's resume generation | β
|
|
|
266
|
+
| `connectionState`, from the socket's own open/close | β
|
|
|
267
|
+
| `onStateUpdate`, forwarded to the `AgentClient` | β
|
|
|
268
|
+
| Initial `get-messages` seed, and a re-read on a replacement socket | β
|
|
|
269
|
+
| Client-side tool calls dispatched off the rendered transcript | β
|
|
|
270
|
+
| Early-continuation branch (observe the announced turn, or launch β never both) | β
|
|
|
271
|
+
| Continuation armed before the tool frame is sent | β
|
|
|
272
|
+
| An unmatchable streaming tail dropped rather than appended | β
|
|
|
169
273
|
| `useAgent` connection hook | π§ roadmap |
|
|
170
|
-
|
|
|
274
|
+
| Cross-tab observer | π§ roadmap |
|
|
171
275
|
| `isServerStreaming` / `isStreaming` flags | π§ roadmap |
|
|
172
276
|
|
|
173
277
|
## Contributing
|
|
@@ -175,7 +279,7 @@ const transport = new WebSocketChatTransport({
|
|
|
175
279
|
```bash
|
|
176
280
|
pnpm install
|
|
177
281
|
pnpm exec playwright install chromium
|
|
178
|
-
pnpm test #
|
|
282
|
+
pnpm test # chromium (vitest-browser-vue)
|
|
179
283
|
pnpm typecheck
|
|
180
284
|
pnpm lint
|
|
181
285
|
pnpm build
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import * as ai from 'ai';
|
|
3
|
-
import { UIMessage, Tool, JSONSchema7
|
|
3
|
+
import { UIMessage, Tool, JSONSchema7 } from 'ai';
|
|
4
4
|
import { Chat } from '@ai-sdk/vue';
|
|
5
5
|
import { AgentClientOptions, AgentClient, AgentConnectionError } from 'agents/client';
|
|
6
6
|
|
|
@@ -53,6 +53,54 @@ interface UseAgentChatOptions<AgentT = unknown, ChatMessage extends UIMessage =
|
|
|
53
53
|
onSocketError?: (event: Event) => void;
|
|
54
54
|
/** Called for chat-turn errors (AI SDK). */
|
|
55
55
|
onError?: (error: Error) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Called with every socket frame, parsed, **before** the chat protocol reads it. Return `true`
|
|
58
|
+
* to consume the frame, which stops the protocol handling below from seeing it at all.
|
|
59
|
+
*
|
|
60
|
+
* This is where an application's own frames belong. The protocol parser narrows a frame to the
|
|
61
|
+
* fields the chat protocol has and drops every other one, so a frame of your own would otherwise
|
|
62
|
+
* arrive with its contents removed.
|
|
63
|
+
*/
|
|
64
|
+
onFrame?: (frame: unknown) => boolean | void;
|
|
65
|
+
/**
|
|
66
|
+
* How a transcript read back from the server is merged into the one on screen, used when a
|
|
67
|
+
* replacement socket re-reads `get-messages`.
|
|
68
|
+
*
|
|
69
|
+
* The read is a round trip of its own: it can be issued before a turn is persisted and land
|
|
70
|
+
* after the socket has already delivered the finished answer, so replacing the transcript with
|
|
71
|
+
* it wholesale would erase a reply that is on screen. The default merges by id with the stored
|
|
72
|
+
* copy winning and anything only on screen appended after; pass your own policy to decide
|
|
73
|
+
* differently β which copy of a message is fresher, say.
|
|
74
|
+
*/
|
|
75
|
+
mergeMessages?: (onScreen: ChatMessage[], stored: ChatMessage[]) => ChatMessage[];
|
|
76
|
+
/**
|
|
77
|
+
* When true, a send carries only the messages after the last one the server is known to hold, and
|
|
78
|
+
* names that message in the request body as `afterMessageId`. Known to hold means seen arriving
|
|
79
|
+
* from the server β in `initialMessages`, the `get-messages` read, a `cf_agent_chat_messages`
|
|
80
|
+
* broadcast, a `cf_agent_message_updated` frame, or a stream that finished without an error with
|
|
81
|
+
* a part in its reply β never a message only this client has.
|
|
82
|
+
*
|
|
83
|
+
* A request that rewrites a message in place carries it too: an edited question, or the assistant
|
|
84
|
+
* message a bare `sendMessage()` resubmits after an approval or a tool output. A tool output itself
|
|
85
|
+
* travels in its own frame, so a question typed after one no longer carries it. A regenerate, and a
|
|
86
|
+
* send before the server is known to hold anything, carry the full list and no `afterMessageId`.
|
|
87
|
+
*
|
|
88
|
+
* Turn it on only against a server that rebuilds the transcript before it persists or broadcasts
|
|
89
|
+
* the request: when the first message sent is one it stores, its stored messages before that one,
|
|
90
|
+
* then the ones sent; otherwise its whole stored transcript, then the sent messages it does not
|
|
91
|
+
* already store, so a client that missed another's messages does not erase them. `AIChatAgent` on
|
|
92
|
+
* its own takes a request's list for the whole transcript, and would keep only the messages sent.
|
|
93
|
+
*/
|
|
94
|
+
sendOnlyNewMessages?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Called with the agent's state, on connect and on every change the server broadcasts.
|
|
97
|
+
*
|
|
98
|
+
* The same option the React hook passes to `useAgent`. It is forwarded to `AgentClient`, which
|
|
99
|
+
* owns the `cf_agent_state` frame; the dispatcher below never sees one. `source` is `"server"` for
|
|
100
|
+
* a broadcast and `"client"` for this client's own `setState` β which a server-owned state refuses,
|
|
101
|
+
* so nothing here sends one.
|
|
102
|
+
*/
|
|
103
|
+
onStateUpdate?: (state: State, source: "server" | "client") => void;
|
|
56
104
|
}
|
|
57
105
|
declare function useAgentChat<AgentT = unknown, ChatMessage extends UIMessage = UIMessage, State = StateOf<AgentT>>(options: UseAgentChatOptions<AgentT, ChatMessage, State>): {
|
|
58
106
|
addToolApprovalResponse: (args_0: {
|
|
@@ -77,104 +125,17 @@ declare function useAgentChat<AgentT = unknown, ChatMessage extends UIMessage =
|
|
|
77
125
|
clearHistory: () => void;
|
|
78
126
|
chat: Chat<ChatMessage>;
|
|
79
127
|
socket: AgentClient<AgentT, State>;
|
|
128
|
+
setAgent: (next: AgentClient<AgentT, State>) => void;
|
|
80
129
|
stop: () => Promise<void>;
|
|
130
|
+
/** Reactive: true once the `get-messages` seed has settled (or was skipped). */
|
|
131
|
+
initialMessagesLoaded: vue.Ref<boolean, boolean>;
|
|
81
132
|
/** Reactive: true while the server is recovering a durable chat turn. */
|
|
82
133
|
isRecovering: vue.Ref<boolean, boolean>;
|
|
83
134
|
/** Reactive: terminal WebSocket connection failure, or null. */
|
|
84
135
|
connectionError: vue.Ref<AgentConnectionError | null, AgentConnectionError | null>;
|
|
136
|
+
/** Reactive: whether the socket is up, for the header's "Reconnectingβ¦". */
|
|
137
|
+
connectionState: vue.Ref<"connected" | "reconnecting", "connected" | "reconnecting">;
|
|
85
138
|
};
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
send(data: string): void;
|
|
90
|
-
addEventListener(type: string, listener: (event: MessageEvent) => void, options?: AddEventListenerOptions): void;
|
|
91
|
-
}
|
|
92
|
-
interface StreamResumingData {
|
|
93
|
-
type: string;
|
|
94
|
-
id: string;
|
|
95
|
-
}
|
|
96
|
-
interface WebSocketChatTransportOptions {
|
|
97
|
-
socket: WebSocketLike;
|
|
98
|
-
activeRequestIds?: Set<string>;
|
|
99
|
-
prepareBody?: (options: {
|
|
100
|
-
messages: UIMessage[];
|
|
101
|
-
trigger: "submit-message" | "regenerate-message";
|
|
102
|
-
messageId?: string;
|
|
103
|
-
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
104
|
-
/**
|
|
105
|
-
* Whether a generic client-side abort (e.g. the AI SDK aborting the request
|
|
106
|
-
* signal, or the stream being cancelled on teardown) should cancel the
|
|
107
|
-
* *server* turn. Local-only by default β the server turn keeps running and can
|
|
108
|
-
* be resumed/observed. Explicit `stop()` always cancels via
|
|
109
|
-
* `cancelActiveServerTurn()`, regardless of this flag. Matches upstream 0.7.0.
|
|
110
|
-
* @default false
|
|
111
|
-
*/
|
|
112
|
-
cancelOnClientAbort?: boolean;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* ChatTransport that communicates with a Cloudflare Agent over WebSocket.
|
|
116
|
-
* Mirrors the protocol implemented by `@cloudflare/ai-chat`'s
|
|
117
|
-
* `WebSocketChatTransport`: same `cf_agent_*` message shapes, same resumable
|
|
118
|
-
* streaming handshake, same tool continuation flow. Framework-agnostic β the
|
|
119
|
-
* Vue composable consumes this; another framework binding could too.
|
|
120
|
-
*/
|
|
121
|
-
declare class WebSocketChatTransport implements ChatTransport<UIMessage> {
|
|
122
|
-
private socket;
|
|
123
|
-
private prepareBody?;
|
|
124
|
-
private activeRequestIds;
|
|
125
|
-
_resumeResolver: ((data: StreamResumingData) => void) | null;
|
|
126
|
-
_resumeNoneResolver: (() => void) | null;
|
|
127
|
-
private _expectToolContinuation;
|
|
128
|
-
private _abortToolContinuation;
|
|
129
|
-
private cancelOnClientAbort;
|
|
130
|
-
private _activeServerTurnId;
|
|
131
|
-
private _cancelAttachedStream;
|
|
132
|
-
private _onStreamPending;
|
|
133
|
-
constructor(options: WebSocketChatTransportOptions);
|
|
134
|
-
/** Mark that the next reconnectToStream() should attach to a server-initiated tool continuation. */
|
|
135
|
-
expectToolContinuation(): void;
|
|
136
|
-
/** Abort the active client-side tool continuation stream. */
|
|
137
|
-
abortActiveToolContinuation(): boolean;
|
|
138
|
-
/** Send a server-turn cancel frame for a request id (best-effort). */
|
|
139
|
-
private sendCancelFrame;
|
|
140
|
-
private setActiveServerTurn;
|
|
141
|
-
private clearActiveServerTurn;
|
|
142
|
-
/** Register a server-observed (broadcast / resume-fallback) turn as the cancellable one. */
|
|
143
|
-
observeServerTurn(requestId: string): void;
|
|
144
|
-
/** Mark a server turn complete so it is no longer the cancellable turn. */
|
|
145
|
-
handleServerTurnCompleted(requestId: string): void;
|
|
146
|
-
/**
|
|
147
|
-
* Explicitly cancel the active server turn (and any tool continuation). Unlike a
|
|
148
|
-
* generic client abort, this always signals the server. Drives `useAgentChat.stop()`.
|
|
149
|
-
*/
|
|
150
|
-
cancelActiveServerTurn(): boolean;
|
|
151
|
-
/** Called by the composable on cf_agent_stream_pending β extend the in-flight resume probe. */
|
|
152
|
-
handleStreamPending(): boolean;
|
|
153
|
-
/** True when the transport is waiting for a resume handshake. */
|
|
154
|
-
isAwaitingResume(): boolean;
|
|
155
|
-
/** Called by the composable when it receives cf_agent_stream_resuming. */
|
|
156
|
-
handleStreamResuming(data: StreamResumingData): boolean;
|
|
157
|
-
/** Called by the composable when it receives cf_agent_stream_resume_none. */
|
|
158
|
-
handleStreamResumeNone(): boolean;
|
|
159
|
-
sendMessages(options: {
|
|
160
|
-
trigger: "submit-message" | "regenerate-message";
|
|
161
|
-
chatId: string;
|
|
162
|
-
messageId: string | undefined;
|
|
163
|
-
messages: UIMessage[];
|
|
164
|
-
abortSignal: AbortSignal | undefined;
|
|
165
|
-
} & ChatRequestOptions): Promise<ReadableStream<UIMessageChunk>>;
|
|
166
|
-
reconnectToStream(_options: {
|
|
167
|
-
chatId: string;
|
|
168
|
-
} & ChatRequestOptions): Promise<ReadableStream<UIMessageChunk> | null>;
|
|
169
|
-
/**
|
|
170
|
-
* Creates a deferred ReadableStream for client-side tool continuations.
|
|
171
|
-
* Returned immediately so AI SDK status becomes "submitted" right after tool output,
|
|
172
|
-
* then waits for the server to announce continuation via STREAM_RESUMING.
|
|
173
|
-
*/
|
|
174
|
-
private _createToolContinuationStream;
|
|
175
|
-
/** Creates a ReadableStream that receives resumed stream chunks. */
|
|
176
|
-
private _createResumeStream;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export { WebSocketChatTransport, useAgentChat };
|
|
180
|
-
export type { UseAgentChatOptions, WebSocketChatTransportOptions, WebSocketLike };
|
|
140
|
+
export { useAgentChat };
|
|
141
|
+
export type { UseAgentChatOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import * as ai from 'ai';
|
|
3
|
-
import { UIMessage, Tool, JSONSchema7
|
|
3
|
+
import { UIMessage, Tool, JSONSchema7 } from 'ai';
|
|
4
4
|
import { Chat } from '@ai-sdk/vue';
|
|
5
5
|
import { AgentClientOptions, AgentClient, AgentConnectionError } from 'agents/client';
|
|
6
6
|
|
|
@@ -53,6 +53,54 @@ interface UseAgentChatOptions<AgentT = unknown, ChatMessage extends UIMessage =
|
|
|
53
53
|
onSocketError?: (event: Event) => void;
|
|
54
54
|
/** Called for chat-turn errors (AI SDK). */
|
|
55
55
|
onError?: (error: Error) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Called with every socket frame, parsed, **before** the chat protocol reads it. Return `true`
|
|
58
|
+
* to consume the frame, which stops the protocol handling below from seeing it at all.
|
|
59
|
+
*
|
|
60
|
+
* This is where an application's own frames belong. The protocol parser narrows a frame to the
|
|
61
|
+
* fields the chat protocol has and drops every other one, so a frame of your own would otherwise
|
|
62
|
+
* arrive with its contents removed.
|
|
63
|
+
*/
|
|
64
|
+
onFrame?: (frame: unknown) => boolean | void;
|
|
65
|
+
/**
|
|
66
|
+
* How a transcript read back from the server is merged into the one on screen, used when a
|
|
67
|
+
* replacement socket re-reads `get-messages`.
|
|
68
|
+
*
|
|
69
|
+
* The read is a round trip of its own: it can be issued before a turn is persisted and land
|
|
70
|
+
* after the socket has already delivered the finished answer, so replacing the transcript with
|
|
71
|
+
* it wholesale would erase a reply that is on screen. The default merges by id with the stored
|
|
72
|
+
* copy winning and anything only on screen appended after; pass your own policy to decide
|
|
73
|
+
* differently β which copy of a message is fresher, say.
|
|
74
|
+
*/
|
|
75
|
+
mergeMessages?: (onScreen: ChatMessage[], stored: ChatMessage[]) => ChatMessage[];
|
|
76
|
+
/**
|
|
77
|
+
* When true, a send carries only the messages after the last one the server is known to hold, and
|
|
78
|
+
* names that message in the request body as `afterMessageId`. Known to hold means seen arriving
|
|
79
|
+
* from the server β in `initialMessages`, the `get-messages` read, a `cf_agent_chat_messages`
|
|
80
|
+
* broadcast, a `cf_agent_message_updated` frame, or a stream that finished without an error with
|
|
81
|
+
* a part in its reply β never a message only this client has.
|
|
82
|
+
*
|
|
83
|
+
* A request that rewrites a message in place carries it too: an edited question, or the assistant
|
|
84
|
+
* message a bare `sendMessage()` resubmits after an approval or a tool output. A tool output itself
|
|
85
|
+
* travels in its own frame, so a question typed after one no longer carries it. A regenerate, and a
|
|
86
|
+
* send before the server is known to hold anything, carry the full list and no `afterMessageId`.
|
|
87
|
+
*
|
|
88
|
+
* Turn it on only against a server that rebuilds the transcript before it persists or broadcasts
|
|
89
|
+
* the request: when the first message sent is one it stores, its stored messages before that one,
|
|
90
|
+
* then the ones sent; otherwise its whole stored transcript, then the sent messages it does not
|
|
91
|
+
* already store, so a client that missed another's messages does not erase them. `AIChatAgent` on
|
|
92
|
+
* its own takes a request's list for the whole transcript, and would keep only the messages sent.
|
|
93
|
+
*/
|
|
94
|
+
sendOnlyNewMessages?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Called with the agent's state, on connect and on every change the server broadcasts.
|
|
97
|
+
*
|
|
98
|
+
* The same option the React hook passes to `useAgent`. It is forwarded to `AgentClient`, which
|
|
99
|
+
* owns the `cf_agent_state` frame; the dispatcher below never sees one. `source` is `"server"` for
|
|
100
|
+
* a broadcast and `"client"` for this client's own `setState` β which a server-owned state refuses,
|
|
101
|
+
* so nothing here sends one.
|
|
102
|
+
*/
|
|
103
|
+
onStateUpdate?: (state: State, source: "server" | "client") => void;
|
|
56
104
|
}
|
|
57
105
|
declare function useAgentChat<AgentT = unknown, ChatMessage extends UIMessage = UIMessage, State = StateOf<AgentT>>(options: UseAgentChatOptions<AgentT, ChatMessage, State>): {
|
|
58
106
|
addToolApprovalResponse: (args_0: {
|
|
@@ -77,104 +125,17 @@ declare function useAgentChat<AgentT = unknown, ChatMessage extends UIMessage =
|
|
|
77
125
|
clearHistory: () => void;
|
|
78
126
|
chat: Chat<ChatMessage>;
|
|
79
127
|
socket: AgentClient<AgentT, State>;
|
|
128
|
+
setAgent: (next: AgentClient<AgentT, State>) => void;
|
|
80
129
|
stop: () => Promise<void>;
|
|
130
|
+
/** Reactive: true once the `get-messages` seed has settled (or was skipped). */
|
|
131
|
+
initialMessagesLoaded: vue.Ref<boolean, boolean>;
|
|
81
132
|
/** Reactive: true while the server is recovering a durable chat turn. */
|
|
82
133
|
isRecovering: vue.Ref<boolean, boolean>;
|
|
83
134
|
/** Reactive: terminal WebSocket connection failure, or null. */
|
|
84
135
|
connectionError: vue.Ref<AgentConnectionError | null, AgentConnectionError | null>;
|
|
136
|
+
/** Reactive: whether the socket is up, for the header's "Reconnectingβ¦". */
|
|
137
|
+
connectionState: vue.Ref<"connected" | "reconnecting", "connected" | "reconnecting">;
|
|
85
138
|
};
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
send(data: string): void;
|
|
90
|
-
addEventListener(type: string, listener: (event: MessageEvent) => void, options?: AddEventListenerOptions): void;
|
|
91
|
-
}
|
|
92
|
-
interface StreamResumingData {
|
|
93
|
-
type: string;
|
|
94
|
-
id: string;
|
|
95
|
-
}
|
|
96
|
-
interface WebSocketChatTransportOptions {
|
|
97
|
-
socket: WebSocketLike;
|
|
98
|
-
activeRequestIds?: Set<string>;
|
|
99
|
-
prepareBody?: (options: {
|
|
100
|
-
messages: UIMessage[];
|
|
101
|
-
trigger: "submit-message" | "regenerate-message";
|
|
102
|
-
messageId?: string;
|
|
103
|
-
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
104
|
-
/**
|
|
105
|
-
* Whether a generic client-side abort (e.g. the AI SDK aborting the request
|
|
106
|
-
* signal, or the stream being cancelled on teardown) should cancel the
|
|
107
|
-
* *server* turn. Local-only by default β the server turn keeps running and can
|
|
108
|
-
* be resumed/observed. Explicit `stop()` always cancels via
|
|
109
|
-
* `cancelActiveServerTurn()`, regardless of this flag. Matches upstream 0.7.0.
|
|
110
|
-
* @default false
|
|
111
|
-
*/
|
|
112
|
-
cancelOnClientAbort?: boolean;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* ChatTransport that communicates with a Cloudflare Agent over WebSocket.
|
|
116
|
-
* Mirrors the protocol implemented by `@cloudflare/ai-chat`'s
|
|
117
|
-
* `WebSocketChatTransport`: same `cf_agent_*` message shapes, same resumable
|
|
118
|
-
* streaming handshake, same tool continuation flow. Framework-agnostic β the
|
|
119
|
-
* Vue composable consumes this; another framework binding could too.
|
|
120
|
-
*/
|
|
121
|
-
declare class WebSocketChatTransport implements ChatTransport<UIMessage> {
|
|
122
|
-
private socket;
|
|
123
|
-
private prepareBody?;
|
|
124
|
-
private activeRequestIds;
|
|
125
|
-
_resumeResolver: ((data: StreamResumingData) => void) | null;
|
|
126
|
-
_resumeNoneResolver: (() => void) | null;
|
|
127
|
-
private _expectToolContinuation;
|
|
128
|
-
private _abortToolContinuation;
|
|
129
|
-
private cancelOnClientAbort;
|
|
130
|
-
private _activeServerTurnId;
|
|
131
|
-
private _cancelAttachedStream;
|
|
132
|
-
private _onStreamPending;
|
|
133
|
-
constructor(options: WebSocketChatTransportOptions);
|
|
134
|
-
/** Mark that the next reconnectToStream() should attach to a server-initiated tool continuation. */
|
|
135
|
-
expectToolContinuation(): void;
|
|
136
|
-
/** Abort the active client-side tool continuation stream. */
|
|
137
|
-
abortActiveToolContinuation(): boolean;
|
|
138
|
-
/** Send a server-turn cancel frame for a request id (best-effort). */
|
|
139
|
-
private sendCancelFrame;
|
|
140
|
-
private setActiveServerTurn;
|
|
141
|
-
private clearActiveServerTurn;
|
|
142
|
-
/** Register a server-observed (broadcast / resume-fallback) turn as the cancellable one. */
|
|
143
|
-
observeServerTurn(requestId: string): void;
|
|
144
|
-
/** Mark a server turn complete so it is no longer the cancellable turn. */
|
|
145
|
-
handleServerTurnCompleted(requestId: string): void;
|
|
146
|
-
/**
|
|
147
|
-
* Explicitly cancel the active server turn (and any tool continuation). Unlike a
|
|
148
|
-
* generic client abort, this always signals the server. Drives `useAgentChat.stop()`.
|
|
149
|
-
*/
|
|
150
|
-
cancelActiveServerTurn(): boolean;
|
|
151
|
-
/** Called by the composable on cf_agent_stream_pending β extend the in-flight resume probe. */
|
|
152
|
-
handleStreamPending(): boolean;
|
|
153
|
-
/** True when the transport is waiting for a resume handshake. */
|
|
154
|
-
isAwaitingResume(): boolean;
|
|
155
|
-
/** Called by the composable when it receives cf_agent_stream_resuming. */
|
|
156
|
-
handleStreamResuming(data: StreamResumingData): boolean;
|
|
157
|
-
/** Called by the composable when it receives cf_agent_stream_resume_none. */
|
|
158
|
-
handleStreamResumeNone(): boolean;
|
|
159
|
-
sendMessages(options: {
|
|
160
|
-
trigger: "submit-message" | "regenerate-message";
|
|
161
|
-
chatId: string;
|
|
162
|
-
messageId: string | undefined;
|
|
163
|
-
messages: UIMessage[];
|
|
164
|
-
abortSignal: AbortSignal | undefined;
|
|
165
|
-
} & ChatRequestOptions): Promise<ReadableStream<UIMessageChunk>>;
|
|
166
|
-
reconnectToStream(_options: {
|
|
167
|
-
chatId: string;
|
|
168
|
-
} & ChatRequestOptions): Promise<ReadableStream<UIMessageChunk> | null>;
|
|
169
|
-
/**
|
|
170
|
-
* Creates a deferred ReadableStream for client-side tool continuations.
|
|
171
|
-
* Returned immediately so AI SDK status becomes "submitted" right after tool output,
|
|
172
|
-
* then waits for the server to announce continuation via STREAM_RESUMING.
|
|
173
|
-
*/
|
|
174
|
-
private _createToolContinuationStream;
|
|
175
|
-
/** Creates a ReadableStream that receives resumed stream chunks. */
|
|
176
|
-
private _createResumeStream;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export { WebSocketChatTransport, useAgentChat };
|
|
180
|
-
export type { UseAgentChatOptions, WebSocketChatTransportOptions, WebSocketLike };
|
|
140
|
+
export { useAgentChat };
|
|
141
|
+
export type { UseAgentChatOptions };
|