agents 0.20.0 → 0.21.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 +50 -12
- package/dist/{agent-tool-types-Btk9ETS-.d.ts → agent-tool-types-CzGGB-20.d.ts} +403 -115
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-UuScsJg3.d.ts → agent-tools-zR2d5uij.d.ts} +2 -2
- package/dist/agent-tools.d.ts +24 -8
- package/dist/agent-tools.js +14 -6
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.d.ts +5 -3
- package/dist/browser/ai.js +86 -7
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.js +13 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/chat/index.d.ts +31 -2
- package/dist/chat/index.js +57 -2
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +5 -179
- package/dist/chat/react.js +18 -555
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.d.ts +10 -0
- package/dist/chat/transport.js +2 -0
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/cloudflare-BduZwmYK.js +204 -0
- package/dist/cloudflare-BduZwmYK.js.map +1 -0
- package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
- package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
- package/dist/connector-CptFKzRh.js.map +1 -0
- package/dist/{handler-stateless-8hQN_kC3.js → handler-stateless-CIkKPETH.js} +20 -69
- package/dist/handler-stateless-CIkKPETH.js.map +1 -0
- package/dist/index.d.ts +21 -11
- package/dist/index.js +31 -21
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +32 -98
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/observability/ai/index.d.ts +10 -131
- package/dist/observability/ai/index.js +209 -444
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/{wire-types-CU9rLoeS.js → protocol-Dqc2MQxo.js} +2 -46
- package/dist/protocol-Dqc2MQxo.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +5 -8
- package/dist/react.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +18 -6
- package/dist/sub-routing.js +92 -2
- package/dist/sub-routing.js.map +1 -1
- package/dist/wire-types-CnMt6_HR.js +47 -0
- package/dist/wire-types-CnMt6_HR.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -22
- package/dist/ws-chat-transport-CIoOBbO7.js +561 -0
- package/dist/ws-chat-transport-CIoOBbO7.js.map +1 -0
- package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
- package/docs/adding-to-existing-project.md +4 -2
- package/docs/agent-class.md +1 -1
- package/docs/agent-tools.md +29 -0
- package/docs/browse-the-web.md +16 -1
- package/docs/chat-agents.md +3 -1
- package/docs/client-sdk.md +12 -8
- package/docs/configuration.md +7 -1
- package/docs/cross-domain-authentication.md +7 -35
- package/docs/email.md +2 -13
- package/docs/human-in-the-loop.md +15 -12
- package/docs/long-running-agents.md +11 -11
- package/docs/mcp-client.md +1 -1
- package/docs/mcp-servers.md +7 -1
- package/docs/mcp-transports.md +18 -12
- package/docs/migration-to-ai-sdk-v5.md +2 -2
- package/docs/migration-to-ai-sdk-v6.md +5 -1
- package/docs/observability.md +24 -67
- package/docs/routing.md +27 -0
- package/docs/sub-agents.md +47 -2
- package/docs/webhooks.md +109 -136
- package/docs/workflows.md +9 -1
- package/package.json +19 -10
- package/dist/cloudflare-BldFV0Pa.js +0 -117
- package/dist/cloudflare-BldFV0Pa.js.map +0 -1
- package/dist/connector-KEJnl6e5.js.map +0 -1
- package/dist/handler-stateless-8hQN_kC3.js.map +0 -1
- package/dist/wire-types-CU9rLoeS.js.map +0 -1
- package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
package/dist/chat/react.js
CHANGED
|
@@ -1,560 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as STREAM_RESUME_NONE_REASONS, r as transition } from "../protocol-Dqc2MQxo.js";
|
|
2
|
+
import "../wire-types-CnMt6_HR.js";
|
|
3
|
+
import { t as WebSocketChatTransport } from "../ws-chat-transport-CIoOBbO7.js";
|
|
2
4
|
import { getToolName, isToolUIPart } from "ai";
|
|
3
5
|
import { nanoid } from "nanoid";
|
|
4
6
|
import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
7
|
import { useChat } from "@ai-sdk/react";
|
|
6
|
-
//#region src/chat/ws-chat-transport.ts
|
|
7
|
-
/**
|
|
8
|
-
* Short safety-net timeout for a resume probe when the server has said nothing.
|
|
9
|
-
* Under normal operation the server answers a `STREAM_RESUME_REQUEST` with
|
|
10
|
-
* `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.
|
|
11
|
-
*/
|
|
12
|
-
const RESUME_PROBE_TIMEOUT_MS = 5e3;
|
|
13
|
-
/**
|
|
14
|
-
* Extended backstop applied once the server says a turn is pending
|
|
15
|
-
* (`STREAM_PENDING`, #1784). The pre-stream window (queueing, MCP setup,
|
|
16
|
-
* debounce, model latency) can exceed the short probe timeout, and the server
|
|
17
|
-
* guarantees a follow-up `STREAM_RESUMING` or `STREAM_RESUME_NONE` — so we wait
|
|
18
|
-
* much longer (refreshed on every keep-waiting frame) but still cap it so a
|
|
19
|
-
* dropped follow-up degrades to a null resolve instead of hanging forever.
|
|
20
|
-
*/
|
|
21
|
-
const RESUME_PENDING_TIMEOUT_MS = 6e4;
|
|
22
|
-
/**
|
|
23
|
-
* ChatTransport that sends messages over WebSocket and returns a
|
|
24
|
-
* ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.
|
|
25
|
-
* No fake fetch, no Response reconstruction, no double SSE parsing.
|
|
26
|
-
*/
|
|
27
|
-
var WebSocketChatTransport = class {
|
|
28
|
-
constructor(options) {
|
|
29
|
-
this._resumeResolver = null;
|
|
30
|
-
this._resumeNoneResolver = null;
|
|
31
|
-
this._onStreamPending = null;
|
|
32
|
-
this._retryResumeProbe = null;
|
|
33
|
-
this._expectToolContinuation = false;
|
|
34
|
-
this._abortToolContinuation = null;
|
|
35
|
-
this._activeServerTurnId = null;
|
|
36
|
-
this._cancelAttachedStream = null;
|
|
37
|
-
this._detachResumeStream = null;
|
|
38
|
-
this.agent = options.agent;
|
|
39
|
-
this.prepareBody = options.prepareBody;
|
|
40
|
-
this.activeRequestIds = options.activeRequestIds;
|
|
41
|
-
this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Point the singleton transport at a new Agent connection. A pending resolver
|
|
45
|
-
* belongs to the old Chat/socket generation and must settle before messages
|
|
46
|
-
* from the replacement connection can be consumed (#1914 review).
|
|
47
|
-
*/
|
|
48
|
-
setAgent(agent) {
|
|
49
|
-
if (this.agent === agent) return;
|
|
50
|
-
this.resetResumeState();
|
|
51
|
-
this.agent = agent;
|
|
52
|
-
}
|
|
53
|
-
setCancelOnClientAbort(cancelOnClientAbort) {
|
|
54
|
-
this.cancelOnClientAbort = cancelOnClientAbort;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Explicitly cancel the active server turn, if any.
|
|
58
|
-
* This is separate from generic client-side abort/cancel lifecycle so
|
|
59
|
-
* clients can detach locally without stopping server work.
|
|
60
|
-
*/
|
|
61
|
-
cancelActiveServerTurn() {
|
|
62
|
-
const requestId = this._activeServerTurnId;
|
|
63
|
-
let cancelledRequest = false;
|
|
64
|
-
if (requestId) {
|
|
65
|
-
this.sendCancelFrame(requestId);
|
|
66
|
-
this._cancelAttachedStream?.();
|
|
67
|
-
this.clearActiveServerTurn(requestId);
|
|
68
|
-
cancelledRequest = true;
|
|
69
|
-
}
|
|
70
|
-
const cancelledToolContinuation = this.abortActiveToolContinuation();
|
|
71
|
-
return cancelledRequest || cancelledToolContinuation;
|
|
72
|
-
}
|
|
73
|
-
sendCancelFrame(requestId) {
|
|
74
|
-
try {
|
|
75
|
-
this.agent.send(JSON.stringify({
|
|
76
|
-
id: requestId,
|
|
77
|
-
type: "cf_agent_chat_request_cancel"
|
|
78
|
-
}));
|
|
79
|
-
} catch {}
|
|
80
|
-
}
|
|
81
|
-
setActiveServerTurn(requestId, cancelAttachedStream) {
|
|
82
|
-
this._activeServerTurnId = requestId;
|
|
83
|
-
this._cancelAttachedStream = cancelAttachedStream;
|
|
84
|
-
}
|
|
85
|
-
clearActiveServerTurn(requestId) {
|
|
86
|
-
if (this._activeServerTurnId === requestId) {
|
|
87
|
-
this._activeServerTurnId = null;
|
|
88
|
-
this._cancelAttachedStream = null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Mark that the next reconnectToStream() call should attach to a
|
|
93
|
-
* server-initiated tool continuation rather than a page-load resume.
|
|
94
|
-
*/
|
|
95
|
-
expectToolContinuation() {
|
|
96
|
-
this._expectToolContinuation = true;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Abort the active client-side tool continuation stream, if one is attached
|
|
100
|
-
* to a server request id.
|
|
101
|
-
*/
|
|
102
|
-
abortActiveToolContinuation() {
|
|
103
|
-
return this._abortToolContinuation?.() ?? false;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* True when the transport is waiting for a resume handshake.
|
|
107
|
-
*/
|
|
108
|
-
isAwaitingResume() {
|
|
109
|
-
return this._resumeResolver !== null || this._resumeNoneResolver !== null;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Settle and detach the current handshake without interpreting it as a
|
|
113
|
-
* server-idle response. Used when the owning hook/agent generation changes.
|
|
114
|
-
*/
|
|
115
|
-
cancelPendingResume() {
|
|
116
|
-
const resolveNone = this._resumeNoneResolver;
|
|
117
|
-
if (!resolveNone) return false;
|
|
118
|
-
return resolveNone({});
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Invalidate all client-side resume state for an obsolete hook/agent
|
|
122
|
-
* generation without cancelling its durable server turn.
|
|
123
|
-
*/
|
|
124
|
-
resetResumeState() {
|
|
125
|
-
this._expectToolContinuation = false;
|
|
126
|
-
this.cancelPendingResume();
|
|
127
|
-
this._detachResumeStream?.();
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Re-send the active handshake request on the latest socket generation. This
|
|
131
|
-
* preserves one AI SDK resume operation while recovering a request/reply lost
|
|
132
|
-
* with the previous WebSocket.
|
|
133
|
-
*/
|
|
134
|
-
retryPendingResume() {
|
|
135
|
-
const retry = this._retryResumeProbe;
|
|
136
|
-
if (!retry) return false;
|
|
137
|
-
retry();
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.
|
|
142
|
-
* If reconnectToStream is waiting, this handles the resume handshake
|
|
143
|
-
* (ACK + stream creation) and returns true. Otherwise returns false
|
|
144
|
-
* so the caller can use its own fallback path.
|
|
145
|
-
*/
|
|
146
|
-
handleStreamResuming(data) {
|
|
147
|
-
if (!this._resumeResolver) return false;
|
|
148
|
-
this._resumeResolver(data);
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.
|
|
153
|
-
* If reconnectToStream is waiting, resolves the promise with null
|
|
154
|
-
* immediately (no 5-second timeout). Returns true if handled.
|
|
155
|
-
*/
|
|
156
|
-
handleStreamResumeNone(data = {}) {
|
|
157
|
-
if (!this._resumeNoneResolver) return false;
|
|
158
|
-
return this._resumeNoneResolver(data);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):
|
|
162
|
-
* the server accepted a turn but its stream has not started yet. If a resume
|
|
163
|
-
* path is awaiting, extend its probe timeout (so it keeps waiting for the
|
|
164
|
-
* eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null
|
|
165
|
-
* after the short window). Returns true if a waiting path consumed it.
|
|
166
|
-
*/
|
|
167
|
-
handleStreamPending() {
|
|
168
|
-
if (!this._onStreamPending) return false;
|
|
169
|
-
this._onStreamPending();
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Called by the hook's shared message handler when a server turn finishes
|
|
174
|
-
* outside the currently attached transport stream, such as after local-only
|
|
175
|
-
* client cleanup.
|
|
176
|
-
*/
|
|
177
|
-
handleServerTurnCompleted(requestId) {
|
|
178
|
-
this.clearActiveServerTurn(requestId);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Register a server turn that is being rendered outside a transport-owned
|
|
182
|
-
* stream, such as the hook's fallback cross-tab/resume observer path.
|
|
183
|
-
*/
|
|
184
|
-
observeServerTurn(requestId) {
|
|
185
|
-
this.setActiveServerTurn(requestId, null);
|
|
186
|
-
}
|
|
187
|
-
async sendMessages(options) {
|
|
188
|
-
const requestId = nanoid(8);
|
|
189
|
-
const abortController = new AbortController();
|
|
190
|
-
let completed = false;
|
|
191
|
-
let requestSent = false;
|
|
192
|
-
let extraBody = {};
|
|
193
|
-
if (this.prepareBody) extraBody = await this.prepareBody({
|
|
194
|
-
messages: options.messages,
|
|
195
|
-
trigger: options.trigger,
|
|
196
|
-
messageId: options.messageId
|
|
197
|
-
});
|
|
198
|
-
if (options.body) extraBody = {
|
|
199
|
-
...extraBody,
|
|
200
|
-
...options.body
|
|
201
|
-
};
|
|
202
|
-
const bodyPayload = JSON.stringify({
|
|
203
|
-
messages: options.messages,
|
|
204
|
-
trigger: options.trigger,
|
|
205
|
-
...extraBody
|
|
206
|
-
});
|
|
207
|
-
this.activeRequestIds?.add(requestId);
|
|
208
|
-
const agent = this.agent;
|
|
209
|
-
const activeIds = this.activeRequestIds;
|
|
210
|
-
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
211
|
-
if (completed) return;
|
|
212
|
-
completed = true;
|
|
213
|
-
if (clearServerTurn) this.clearActiveServerTurn(requestId);
|
|
214
|
-
try {
|
|
215
|
-
action();
|
|
216
|
-
} catch {}
|
|
217
|
-
if (!keepId) activeIds?.delete(requestId);
|
|
218
|
-
abortController.abort();
|
|
219
|
-
};
|
|
220
|
-
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
221
|
-
abortError.name = "AbortError";
|
|
222
|
-
const cancelActiveRequest = () => {
|
|
223
|
-
if (completed) return false;
|
|
224
|
-
finish(() => streamController.error(abortError), true);
|
|
225
|
-
return true;
|
|
226
|
-
};
|
|
227
|
-
this.setActiveServerTurn(requestId, cancelActiveRequest);
|
|
228
|
-
const onAbort = () => {
|
|
229
|
-
if (completed) return;
|
|
230
|
-
if (this.cancelOnClientAbort) {
|
|
231
|
-
if (requestSent) this.sendCancelFrame(requestId);
|
|
232
|
-
finish(() => streamController.error(abortError), requestSent);
|
|
233
|
-
} else finish(() => streamController.error(abortError), false, !requestSent);
|
|
234
|
-
};
|
|
235
|
-
let streamController;
|
|
236
|
-
const stream = new ReadableStream({
|
|
237
|
-
start(controller) {
|
|
238
|
-
streamController = controller;
|
|
239
|
-
const onMessage = (event) => {
|
|
240
|
-
try {
|
|
241
|
-
const data = JSON.parse(event.data);
|
|
242
|
-
if (data.type !== "cf_agent_use_chat_response") return;
|
|
243
|
-
if (data.id !== requestId) return;
|
|
244
|
-
if (data.error) {
|
|
245
|
-
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (data.body?.trim()) try {
|
|
249
|
-
const chunk = JSON.parse(data.body);
|
|
250
|
-
controller.enqueue(chunk);
|
|
251
|
-
} catch {}
|
|
252
|
-
if (data.done) finish(() => controller.close());
|
|
253
|
-
} catch {}
|
|
254
|
-
};
|
|
255
|
-
const onClose = () => {
|
|
256
|
-
finish(() => controller.close(), false, false);
|
|
257
|
-
};
|
|
258
|
-
agent.addEventListener("message", onMessage, { signal: abortController.signal });
|
|
259
|
-
agent.addEventListener("close", onClose, { signal: abortController.signal });
|
|
260
|
-
},
|
|
261
|
-
cancel() {
|
|
262
|
-
onAbort();
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
if (options.abortSignal) {
|
|
266
|
-
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
267
|
-
if (options.abortSignal.aborted) onAbort();
|
|
268
|
-
}
|
|
269
|
-
if (completed) return stream;
|
|
270
|
-
requestSent = true;
|
|
271
|
-
agent.send(JSON.stringify({
|
|
272
|
-
id: requestId,
|
|
273
|
-
init: {
|
|
274
|
-
method: "POST",
|
|
275
|
-
body: bodyPayload
|
|
276
|
-
},
|
|
277
|
-
type: "cf_agent_use_chat_request"
|
|
278
|
-
}));
|
|
279
|
-
return stream;
|
|
280
|
-
}
|
|
281
|
-
async reconnectToStream(_options) {
|
|
282
|
-
if (this.isAwaitingResume()) return null;
|
|
283
|
-
if (this._expectToolContinuation) {
|
|
284
|
-
this._expectToolContinuation = false;
|
|
285
|
-
return this._createToolContinuationStream();
|
|
286
|
-
}
|
|
287
|
-
const activeIds = this.activeRequestIds;
|
|
288
|
-
const probeId = nanoid(8);
|
|
289
|
-
return new Promise((resolve) => {
|
|
290
|
-
let resolved = false;
|
|
291
|
-
let timeout;
|
|
292
|
-
let resumeResolver = null;
|
|
293
|
-
let resumeNoneResolver = null;
|
|
294
|
-
let onStreamPending = null;
|
|
295
|
-
let retryResumeProbe = null;
|
|
296
|
-
const clearOwnedCallbacks = () => {
|
|
297
|
-
if (resumeResolver && this._resumeResolver === resumeResolver) this._resumeResolver = null;
|
|
298
|
-
if (resumeNoneResolver && this._resumeNoneResolver === resumeNoneResolver) this._resumeNoneResolver = null;
|
|
299
|
-
if (onStreamPending && this._onStreamPending === onStreamPending) this._onStreamPending = null;
|
|
300
|
-
if (retryResumeProbe && this._retryResumeProbe === retryResumeProbe) this._retryResumeProbe = null;
|
|
301
|
-
};
|
|
302
|
-
const done = (value) => {
|
|
303
|
-
if (resolved) return;
|
|
304
|
-
resolved = true;
|
|
305
|
-
clearOwnedCallbacks();
|
|
306
|
-
if (timeout) clearTimeout(timeout);
|
|
307
|
-
resolve(value);
|
|
308
|
-
};
|
|
309
|
-
const armTimeout = (delay) => {
|
|
310
|
-
if (timeout) clearTimeout(timeout);
|
|
311
|
-
timeout = setTimeout(() => done(null), delay);
|
|
312
|
-
};
|
|
313
|
-
onStreamPending = () => {
|
|
314
|
-
if (resolved) return;
|
|
315
|
-
armTimeout(RESUME_PENDING_TIMEOUT_MS);
|
|
316
|
-
};
|
|
317
|
-
resumeNoneResolver = (data) => {
|
|
318
|
-
if (data.probeId && data.probeId !== probeId) return false;
|
|
319
|
-
done(null);
|
|
320
|
-
return true;
|
|
321
|
-
};
|
|
322
|
-
resumeResolver = (data) => {
|
|
323
|
-
const requestId = data.id;
|
|
324
|
-
activeIds?.add(requestId);
|
|
325
|
-
const stream = this._createResumeStream(requestId);
|
|
326
|
-
this.agent.send(JSON.stringify({
|
|
327
|
-
type: "cf_agent_stream_resume_ack",
|
|
328
|
-
id: requestId
|
|
329
|
-
}));
|
|
330
|
-
done(stream);
|
|
331
|
-
};
|
|
332
|
-
retryResumeProbe = () => {
|
|
333
|
-
if (resolved) return;
|
|
334
|
-
armTimeout(RESUME_PROBE_TIMEOUT_MS);
|
|
335
|
-
try {
|
|
336
|
-
this.agent.send(JSON.stringify({
|
|
337
|
-
type: "cf_agent_stream_resume_request",
|
|
338
|
-
probeId
|
|
339
|
-
}));
|
|
340
|
-
} catch {}
|
|
341
|
-
};
|
|
342
|
-
this._onStreamPending = onStreamPending;
|
|
343
|
-
this._resumeNoneResolver = resumeNoneResolver;
|
|
344
|
-
this._resumeResolver = resumeResolver;
|
|
345
|
-
this._retryResumeProbe = retryResumeProbe;
|
|
346
|
-
retryResumeProbe();
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Creates a deferred ReadableStream for client-side tool continuations.
|
|
351
|
-
* The stream is returned immediately so AI SDK status becomes "submitted"
|
|
352
|
-
* right after addToolOutput()/addToolApprovalResponse(), then it waits for
|
|
353
|
-
* the server to announce the continuation via STREAM_RESUMING.
|
|
354
|
-
*/
|
|
355
|
-
_createToolContinuationStream() {
|
|
356
|
-
const agent = this.agent;
|
|
357
|
-
const activeIds = this.activeRequestIds;
|
|
358
|
-
const streamController = new AbortController();
|
|
359
|
-
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
360
|
-
abortError.name = "AbortError";
|
|
361
|
-
let completed = false;
|
|
362
|
-
let requestId = null;
|
|
363
|
-
let readerController = null;
|
|
364
|
-
const probeId = nanoid(8);
|
|
365
|
-
let onResumeRef = null;
|
|
366
|
-
let onResumeNoneRef = null;
|
|
367
|
-
let onStreamPendingRef = null;
|
|
368
|
-
let retryResumeProbeRef = null;
|
|
369
|
-
let abortToolContinuationRef = null;
|
|
370
|
-
let detachResumeStreamRef = null;
|
|
371
|
-
const clearOwnedHandshake = () => {
|
|
372
|
-
if (onResumeRef && this._resumeResolver === onResumeRef) this._resumeResolver = null;
|
|
373
|
-
if (onResumeNoneRef && this._resumeNoneResolver === onResumeNoneRef) this._resumeNoneResolver = null;
|
|
374
|
-
if (onStreamPendingRef && this._onStreamPending === onStreamPendingRef) this._onStreamPending = null;
|
|
375
|
-
if (retryResumeProbeRef && this._retryResumeProbe === retryResumeProbeRef) this._retryResumeProbe = null;
|
|
376
|
-
};
|
|
377
|
-
const finish = (action, keepRequestId = false) => {
|
|
378
|
-
if (completed) return;
|
|
379
|
-
completed = true;
|
|
380
|
-
if (this._abortToolContinuation === abortToolContinuationRef) this._abortToolContinuation = null;
|
|
381
|
-
if (this._detachResumeStream === detachResumeStreamRef) this._detachResumeStream = null;
|
|
382
|
-
clearOwnedHandshake();
|
|
383
|
-
try {
|
|
384
|
-
action();
|
|
385
|
-
} catch {}
|
|
386
|
-
if (requestId && !keepRequestId) activeIds?.delete(requestId);
|
|
387
|
-
streamController.abort();
|
|
388
|
-
};
|
|
389
|
-
const transport = this;
|
|
390
|
-
abortToolContinuationRef = () => {
|
|
391
|
-
if (completed) return false;
|
|
392
|
-
if (requestId === null) {
|
|
393
|
-
finish(() => readerController?.error(abortError));
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
try {
|
|
397
|
-
agent.send(JSON.stringify({
|
|
398
|
-
type: "cf_agent_chat_request_cancel",
|
|
399
|
-
id: requestId
|
|
400
|
-
}));
|
|
401
|
-
} catch {}
|
|
402
|
-
finish(() => readerController?.error(abortError), true);
|
|
403
|
-
return true;
|
|
404
|
-
};
|
|
405
|
-
this._abortToolContinuation = abortToolContinuationRef;
|
|
406
|
-
detachResumeStreamRef = () => {
|
|
407
|
-
if (completed) return false;
|
|
408
|
-
finish(() => readerController?.close());
|
|
409
|
-
return true;
|
|
410
|
-
};
|
|
411
|
-
this._detachResumeStream = detachResumeStreamRef;
|
|
412
|
-
return new ReadableStream({
|
|
413
|
-
start(controller) {
|
|
414
|
-
readerController = controller;
|
|
415
|
-
let timeout;
|
|
416
|
-
const armTimeout = (delay) => {
|
|
417
|
-
if (timeout) clearTimeout(timeout);
|
|
418
|
-
timeout = setTimeout(() => finish(() => controller.close()), delay);
|
|
419
|
-
};
|
|
420
|
-
const onResumeNone = (data) => {
|
|
421
|
-
if (data.probeId && data.probeId !== probeId) return false;
|
|
422
|
-
finish(() => controller.close());
|
|
423
|
-
return true;
|
|
424
|
-
};
|
|
425
|
-
const onResume = (data) => {
|
|
426
|
-
if (requestId) return;
|
|
427
|
-
requestId = data.id;
|
|
428
|
-
activeIds?.add(requestId);
|
|
429
|
-
clearOwnedHandshake();
|
|
430
|
-
if (timeout) clearTimeout(timeout);
|
|
431
|
-
agent.send(JSON.stringify({
|
|
432
|
-
type: "cf_agent_stream_resume_ack",
|
|
433
|
-
id: requestId
|
|
434
|
-
}));
|
|
435
|
-
};
|
|
436
|
-
const onStreamPending = () => {
|
|
437
|
-
if (completed) return;
|
|
438
|
-
armTimeout(RESUME_PENDING_TIMEOUT_MS);
|
|
439
|
-
};
|
|
440
|
-
const retryResumeProbe = () => {
|
|
441
|
-
if (completed || requestId !== null) return;
|
|
442
|
-
armTimeout(RESUME_PROBE_TIMEOUT_MS);
|
|
443
|
-
try {
|
|
444
|
-
transport.agent.send(JSON.stringify({
|
|
445
|
-
type: "cf_agent_stream_resume_request",
|
|
446
|
-
probeId
|
|
447
|
-
}));
|
|
448
|
-
} catch {}
|
|
449
|
-
};
|
|
450
|
-
onResumeRef = onResume;
|
|
451
|
-
onResumeNoneRef = onResumeNone;
|
|
452
|
-
onStreamPendingRef = onStreamPending;
|
|
453
|
-
retryResumeProbeRef = retryResumeProbe;
|
|
454
|
-
transport._resumeResolver = onResume;
|
|
455
|
-
transport._resumeNoneResolver = onResumeNone;
|
|
456
|
-
transport._onStreamPending = onStreamPending;
|
|
457
|
-
transport._retryResumeProbe = retryResumeProbe;
|
|
458
|
-
const onMessage = (event) => {
|
|
459
|
-
try {
|
|
460
|
-
const data = JSON.parse(event.data);
|
|
461
|
-
if (data.type !== "cf_agent_use_chat_response" || requestId == null || data.id !== requestId) return;
|
|
462
|
-
if (data.error) {
|
|
463
|
-
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
if (data.body?.trim()) try {
|
|
467
|
-
const chunk = JSON.parse(data.body);
|
|
468
|
-
controller.enqueue(chunk);
|
|
469
|
-
} catch {}
|
|
470
|
-
if (data.done) finish(() => controller.close());
|
|
471
|
-
} catch {}
|
|
472
|
-
};
|
|
473
|
-
const onClose = () => finish(() => controller.close());
|
|
474
|
-
agent.addEventListener("message", onMessage, { signal: streamController.signal });
|
|
475
|
-
agent.addEventListener("close", onClose, { signal: streamController.signal });
|
|
476
|
-
retryResumeProbe();
|
|
477
|
-
},
|
|
478
|
-
cancel() {
|
|
479
|
-
if (requestId && transport.cancelOnClientAbort) {
|
|
480
|
-
transport.sendCancelFrame(requestId);
|
|
481
|
-
finish(() => {}, true);
|
|
482
|
-
} else finish(() => {});
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Creates a ReadableStream that receives resumed stream chunks
|
|
488
|
-
* and forwards them to useChat as UIMessageChunk objects.
|
|
489
|
-
*/
|
|
490
|
-
_createResumeStream(requestId) {
|
|
491
|
-
const agent = this.agent;
|
|
492
|
-
const activeIds = this.activeRequestIds;
|
|
493
|
-
const chunkController = new AbortController();
|
|
494
|
-
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
495
|
-
abortError.name = "AbortError";
|
|
496
|
-
let completed = false;
|
|
497
|
-
let detachResumeStream = null;
|
|
498
|
-
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
499
|
-
if (completed) return;
|
|
500
|
-
completed = true;
|
|
501
|
-
if (clearServerTurn) this.clearActiveServerTurn(requestId);
|
|
502
|
-
if (this._detachResumeStream === detachResumeStream) this._detachResumeStream = null;
|
|
503
|
-
try {
|
|
504
|
-
action();
|
|
505
|
-
} catch {}
|
|
506
|
-
if (!keepId) activeIds?.delete(requestId);
|
|
507
|
-
chunkController.abort();
|
|
508
|
-
};
|
|
509
|
-
let streamController = null;
|
|
510
|
-
const cancelActiveRequest = () => {
|
|
511
|
-
if (completed) return false;
|
|
512
|
-
finish(() => streamController?.error(abortError), true);
|
|
513
|
-
return true;
|
|
514
|
-
};
|
|
515
|
-
this.setActiveServerTurn(requestId, cancelActiveRequest);
|
|
516
|
-
const transport = this;
|
|
517
|
-
detachResumeStream = () => {
|
|
518
|
-
if (completed) return false;
|
|
519
|
-
finish(() => streamController?.close());
|
|
520
|
-
return true;
|
|
521
|
-
};
|
|
522
|
-
this._detachResumeStream = detachResumeStream;
|
|
523
|
-
return new ReadableStream({
|
|
524
|
-
start(controller) {
|
|
525
|
-
streamController = controller;
|
|
526
|
-
const onMessage = (event) => {
|
|
527
|
-
try {
|
|
528
|
-
const data = JSON.parse(event.data);
|
|
529
|
-
if (data.type !== "cf_agent_use_chat_response") return;
|
|
530
|
-
if (data.id !== requestId) return;
|
|
531
|
-
if (data.error) {
|
|
532
|
-
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
if (data.body?.trim()) try {
|
|
536
|
-
const chunk = JSON.parse(data.body);
|
|
537
|
-
controller.enqueue(chunk);
|
|
538
|
-
} catch {}
|
|
539
|
-
if (data.done) finish(() => controller.close());
|
|
540
|
-
} catch {}
|
|
541
|
-
};
|
|
542
|
-
const onClose = () => {
|
|
543
|
-
finish(() => controller.close(), false, false);
|
|
544
|
-
};
|
|
545
|
-
agent.addEventListener("message", onMessage, { signal: chunkController.signal });
|
|
546
|
-
agent.addEventListener("close", onClose, { signal: chunkController.signal });
|
|
547
|
-
},
|
|
548
|
-
cancel() {
|
|
549
|
-
if (transport.cancelOnClientAbort) {
|
|
550
|
-
transport.sendCancelFrame(requestId);
|
|
551
|
-
finish(() => {}, true);
|
|
552
|
-
} else finish(() => {}, false, false);
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
//#endregion
|
|
558
8
|
//#region src/chat/react.tsx
|
|
559
9
|
/**
|
|
560
10
|
* One-shot deprecation warnings (warns once per key per session).
|
|
@@ -1386,8 +836,8 @@ function useAgentChat(options) {
|
|
|
1386
836
|
}
|
|
1387
837
|
}
|
|
1388
838
|
} catch {}
|
|
1389
|
-
if (data.done || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
1390
|
-
if (data.done) {
|
|
839
|
+
if (data.done || data.error || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
840
|
+
if (data.done || data.error) {
|
|
1391
841
|
if (streamStateRef.current.status === "observing" && streamStateRef.current.streamId === data.id) {
|
|
1392
842
|
streamStateRef.current = { status: "idle" };
|
|
1393
843
|
setIsServerStreaming(false);
|
|
@@ -1403,6 +853,19 @@ function useAgentChat(options) {
|
|
|
1403
853
|
}
|
|
1404
854
|
let chunkData;
|
|
1405
855
|
if (data.replay && streamStateRef.current.status !== "observing" && !pendingReplayResumeRequestIdsRef.current.has(data.id)) return;
|
|
856
|
+
if (data.error) {
|
|
857
|
+
pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
858
|
+
customTransport.handleServerTurnCompleted(data.id);
|
|
859
|
+
fallbackAckedResumeRequestIdsRef.current.delete(data.id);
|
|
860
|
+
setIsRecovering(false);
|
|
861
|
+
if (streamStateRef.current.status === "idle" || streamStateRef.current.streamId === data.id) {
|
|
862
|
+
const result = transition(streamStateRef.current, { type: "clear" });
|
|
863
|
+
streamStateRef.current = result.state;
|
|
864
|
+
setIsServerStreaming(result.isStreaming);
|
|
865
|
+
}
|
|
866
|
+
if (observedToolContinuationRequestIdRef.current === data.id) resetToolContinuation();
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
1406
869
|
if (data.body?.trim()) try {
|
|
1407
870
|
chunkData = JSON.parse(data.body);
|
|
1408
871
|
if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id && typeof chunkData.messageId === "string" && chunkData.type === "start") {
|