agents 0.20.1 → 0.22.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 +51 -12
- package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
- package/dist/agent-tools.d.ts +44 -28
- package/dist/agent-tools.js +16 -7
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.d.ts +5 -3
- package/dist/browser/ai.js +88 -8
- 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/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
- package/dist/chat/index.d.ts +51 -15
- package/dist/chat/index.js +59 -4
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +19 -180
- package/dist/chat/react.js +93 -600
- 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 +4 -4
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- 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/current-agent-CuMErtly.d.ts +283 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-CaBjfz4J.js +41 -0
- package/dist/diagnostics-CaBjfz4J.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +3 -2
- package/dist/experimental/memory/session/index.js +15 -10
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
- package/dist/index.d.ts +102 -91
- package/dist/index.js +12 -7235
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +48 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.d.ts +1 -130
- package/dist/observability/ai/index.js +88 -433
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +6 -9
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-CR9RHGos.js +857 -0
- package/dist/scheduler-CR9RHGos.js.map +1 -0
- package/dist/scheduler-DQoTGoAW.d.ts +224 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-5W6JNKVb.js +6613 -0
- package/dist/src-5W6JNKVb.js.map +1 -0
- 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/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
- package/dist/ws-chat-transport-rWwta645.js +698 -0
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/adding-to-existing-project.md +4 -2
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +29 -0
- package/docs/browse-the-web.md +16 -1
- package/docs/chat-agents.md +16 -13
- package/docs/client-sdk.md +12 -8
- package/docs/configuration.md +7 -1
- package/docs/cross-domain-authentication.md +7 -35
- package/docs/durable-execution.md +1 -1
- package/docs/email.md +2 -13
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +16 -13
- package/docs/index.md +1 -0
- package/docs/lifecycle.md +323 -0
- package/docs/long-running-agents.md +13 -15
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +6 -0
- 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 +33 -77
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +27 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sub-agents.md +47 -2
- package/docs/webhooks.md +109 -136
- package/docs/workflows.md +9 -1
- package/package.json +41 -23
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/connector-KEJnl6e5.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.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,559 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as STREAM_RESUME_NONE_REASONS, r as transition } from "../protocol-B0nh6KNf.js";
|
|
2
|
+
import "../wire-types-CnMt6_HR.js";
|
|
3
|
+
import { t as WebSocketChatTransport } from "../ws-chat-transport-rWwta645.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
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.
|
|
9
|
+
* Picks the throttle from an explicit caller value, the deprecated alias, or
|
|
10
|
+
* the default — in that order. `false` turns throttling off.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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;
|
|
12
|
+
function resolveChatThrottleMs(options) {
|
|
13
|
+
if (options.throttle === false) return void 0;
|
|
14
|
+
return options.throttle ?? options.experimental_throttle ?? 50;
|
|
15
|
+
}
|
|
22
16
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
17
|
+
* The throttle spelled under both option names, or neither name when it is off.
|
|
18
|
+
*
|
|
19
|
+
* The two names are not interchangeable across the peer range. `@ai-sdk/react`
|
|
20
|
+
* v3 only reads `experimental_throttle`; v4 renamed it to `throttle` and reads
|
|
21
|
+
* `throttle ?? experimental_throttle`. Our peer range allows both majors, so
|
|
22
|
+
* sending one name would silently do nothing on the other. Unknown option keys
|
|
23
|
+
* are ignored by both, so sending both names is safe.
|
|
24
|
+
*
|
|
25
|
+
* Turning throttling off omits both names rather than inventing a numeric
|
|
26
|
+
* value. Both majors also treat an explicit `0` as unthrottled, so callers that
|
|
27
|
+
* pass `0` keep that value under both spellings.
|
|
26
28
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
};
|
|
29
|
+
function chatThrottleOptions(options) {
|
|
30
|
+
const ms = resolveChatThrottleMs(options);
|
|
31
|
+
if (ms === void 0) return {};
|
|
32
|
+
return {
|
|
33
|
+
experimental_throttle: ms,
|
|
34
|
+
throttle: ms
|
|
35
|
+
};
|
|
36
|
+
}
|
|
557
37
|
//#endregion
|
|
558
38
|
//#region src/chat/react.tsx
|
|
559
39
|
/**
|
|
@@ -736,7 +216,7 @@ function detectToolsRequiringConfirmation(tools) {
|
|
|
736
216
|
return Object.entries(tools).filter(([_name, tool]) => !tool.execute).map(([name]) => name);
|
|
737
217
|
}
|
|
738
218
|
function useAgentChat(options) {
|
|
739
|
-
const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, onData, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = true, autoSendAfterAllConfirmationsResolved = true, resume = true, cancelOnClientAbort = false, syncMessagesToServer = true, body: bodyOption, prepareSendMessagesRequest, ...rest } = options;
|
|
219
|
+
const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, onData, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = true, autoSendAfterAllConfirmationsResolved = true, resume = true, cancelOnClientAbort = false, syncMessagesToServer = true, body: bodyOption, prepareSendMessagesRequest, throttle, experimental_throttle, ...rest } = options;
|
|
740
220
|
if (manualToolsRequiringConfirmation) warnDeprecated("useAgentChat.toolsRequiringConfirmation", "The 'toolsRequiringConfirmation' option is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.");
|
|
741
221
|
if (experimental_automaticToolResolution) warnDeprecated("useAgentChat.experimental_automaticToolResolution", "The 'experimental_automaticToolResolution' option is deprecated. Use the onToolCall callback instead. Will be removed in the next major version.");
|
|
742
222
|
if (options.autoSendAfterAllConfirmationsResolved !== void 0) warnDeprecated("useAgentChat.autoSendAfterAllConfirmationsResolved", "The 'autoSendAfterAllConfirmationsResolved' option is deprecated. Use sendAutomaticallyWhen from AI SDK instead. Will be removed in the next major version.");
|
|
@@ -862,6 +342,10 @@ function useAgentChat(options) {
|
|
|
862
342
|
const customTransport = customTransportRef.current;
|
|
863
343
|
const useChatHelpers = useChat({
|
|
864
344
|
...rest,
|
|
345
|
+
...chatThrottleOptions({
|
|
346
|
+
experimental_throttle,
|
|
347
|
+
throttle
|
|
348
|
+
}),
|
|
865
349
|
onData,
|
|
866
350
|
messages: initialMessages,
|
|
867
351
|
transport: customTransport,
|
|
@@ -965,8 +449,6 @@ function useAgentChat(options) {
|
|
|
965
449
|
const isResolvingToolsRef = useRef(false);
|
|
966
450
|
const [toolResolutionTrigger, setToolResolutionTrigger] = useState(0);
|
|
967
451
|
const [clientToolResults, setClientToolResults] = useState(/* @__PURE__ */ new Map());
|
|
968
|
-
const messagesRef = useRef(chatMessages);
|
|
969
|
-
messagesRef.current = chatMessages;
|
|
970
452
|
const initialMessagesRef = useRef(initialMessages);
|
|
971
453
|
initialMessagesRef.current = initialMessages;
|
|
972
454
|
const seededInitialMessagesKeyRef = useRef(null);
|
|
@@ -986,7 +468,7 @@ function useAgentChat(options) {
|
|
|
986
468
|
]);
|
|
987
469
|
const localResponseMessageIdsRef = useRef(/* @__PURE__ */ new Map());
|
|
988
470
|
const protectedStreamingAssistantRef = useRef(null);
|
|
989
|
-
const preserveProtectedStreamingAssistant = useCallback((messages) => {
|
|
471
|
+
const preserveProtectedStreamingAssistant = useCallback((messages, currentMessages) => {
|
|
990
472
|
const protection = protectedStreamingAssistantRef.current;
|
|
991
473
|
if (!protection) return [...messages];
|
|
992
474
|
const protectedIndex = messages.findIndex((message) => message.id === protection.assistantId);
|
|
@@ -994,22 +476,20 @@ function useAgentChat(options) {
|
|
|
994
476
|
protectedStreamingAssistantRef.current = null;
|
|
995
477
|
return [...messages];
|
|
996
478
|
}
|
|
997
|
-
const protectedAssistant =
|
|
479
|
+
const protectedAssistant = currentMessages.find((message) => message.id === protection.assistantId) ?? messages.find((message) => message.id === protection.assistantId);
|
|
998
480
|
if (!protectedAssistant) return [...messages];
|
|
999
481
|
return [...messages.filter((message) => message.id !== protection.assistantId), protectedAssistant];
|
|
1000
482
|
}, []);
|
|
1001
483
|
const protectStreamingAssistantTail = useCallback(() => {
|
|
1002
484
|
if (statusRef.current !== "streaming") return;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
assistantId
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (!protection) return prevMessages;
|
|
1012
|
-
return moveMessageToEnd(prevMessages, protection.assistantId);
|
|
485
|
+
setMessages((currentMessages) => {
|
|
486
|
+
const assistantInfo = findLastAssistantMessage(currentMessages);
|
|
487
|
+
if (!assistantInfo) return currentMessages;
|
|
488
|
+
if (protectedStreamingAssistantRef.current?.assistantId !== assistantInfo.message.id) protectedStreamingAssistantRef.current = {
|
|
489
|
+
assistantId: assistantInfo.message.id,
|
|
490
|
+
anchorMessageId: currentMessages[assistantInfo.index - 1]?.id ?? null
|
|
491
|
+
};
|
|
492
|
+
return moveMessageToEnd(currentMessages, assistantInfo.message.id);
|
|
1013
493
|
});
|
|
1014
494
|
}, [setMessages]);
|
|
1015
495
|
const restoreProtectedStreamingAssistant = useCallback((assistantId) => {
|
|
@@ -1290,17 +770,18 @@ function useAgentChat(options) {
|
|
|
1290
770
|
case "cf_agent_chat_recovering":
|
|
1291
771
|
setIsRecovering(Boolean(data.recovering));
|
|
1292
772
|
break;
|
|
1293
|
-
case "cf_agent_chat_messages":
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
773
|
+
case "cf_agent_chat_messages":
|
|
774
|
+
setMessages((currentMessages) => {
|
|
775
|
+
let next = preserveProtectedStreamingAssistant(data.messages, currentMessages);
|
|
776
|
+
const observed = streamStateRef.current;
|
|
777
|
+
if (observed.status === "observing" && observed.accumulator.parts.length > 0) {
|
|
778
|
+
const snapshotIdx = next.findIndex((m) => m.id === observed.accumulator.messageId);
|
|
779
|
+
const snapshotParts = snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;
|
|
780
|
+
if (observed.accumulator.parts.length >= snapshotParts) next = observed.accumulator.mergeInto(next);
|
|
781
|
+
}
|
|
782
|
+
return next;
|
|
783
|
+
});
|
|
1302
784
|
break;
|
|
1303
|
-
}
|
|
1304
785
|
case "cf_agent_message_updated":
|
|
1305
786
|
setMessages((prevMessages) => {
|
|
1306
787
|
const updatedMessage = data.message;
|
|
@@ -1368,26 +849,26 @@ function useAgentChat(options) {
|
|
|
1368
849
|
if (data.body?.trim()) try {
|
|
1369
850
|
const chunkData = JSON.parse(data.body);
|
|
1370
851
|
if (chunkData.type === "start" && typeof chunkData.messageId === "string") {
|
|
1371
|
-
|
|
852
|
+
const messageId = chunkData.messageId;
|
|
853
|
+
localResponseIds.set(data.id, messageId);
|
|
1372
854
|
if (!data.continuation) {
|
|
1373
|
-
if (protectedStreamingAssistantRef.current?.assistantId !==
|
|
1374
|
-
const
|
|
1375
|
-
const idx = msgs.findIndex((m) => m.id === chunkData.messageId);
|
|
1376
|
-
const anchorMessageId = idx >= 0 ? msgs[idx - 1]?.id ?? null : msgs[msgs.length - 1]?.id ?? null;
|
|
855
|
+
if (protectedStreamingAssistantRef.current?.assistantId !== messageId) setMessages((currentMessages) => {
|
|
856
|
+
const idx = currentMessages.findIndex((message) => message.id === messageId);
|
|
1377
857
|
protectedStreamingAssistantRef.current = {
|
|
1378
|
-
assistantId:
|
|
1379
|
-
anchorMessageId
|
|
858
|
+
assistantId: messageId,
|
|
859
|
+
anchorMessageId: idx >= 0 ? currentMessages[idx - 1]?.id ?? null : currentMessages[currentMessages.length - 1]?.id ?? null
|
|
1380
860
|
};
|
|
1381
|
-
|
|
861
|
+
return currentMessages;
|
|
862
|
+
});
|
|
1382
863
|
}
|
|
1383
864
|
if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id) {
|
|
1384
865
|
pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
1385
|
-
resetMatchingHydratedAssistantForReplay(
|
|
866
|
+
resetMatchingHydratedAssistantForReplay(messageId);
|
|
1386
867
|
}
|
|
1387
868
|
}
|
|
1388
869
|
} catch {}
|
|
1389
|
-
if (data.done || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
1390
|
-
if (data.done) {
|
|
870
|
+
if (data.done || data.error || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
871
|
+
if (data.done || data.error) {
|
|
1391
872
|
if (streamStateRef.current.status === "observing" && streamStateRef.current.streamId === data.id) {
|
|
1392
873
|
streamStateRef.current = { status: "idle" };
|
|
1393
874
|
setIsServerStreaming(false);
|
|
@@ -1403,6 +884,19 @@ function useAgentChat(options) {
|
|
|
1403
884
|
}
|
|
1404
885
|
let chunkData;
|
|
1405
886
|
if (data.replay && streamStateRef.current.status !== "observing" && !pendingReplayResumeRequestIdsRef.current.has(data.id)) return;
|
|
887
|
+
if (data.error) {
|
|
888
|
+
pendingReplayResumeRequestIdsRef.current.delete(data.id);
|
|
889
|
+
customTransport.handleServerTurnCompleted(data.id);
|
|
890
|
+
fallbackAckedResumeRequestIdsRef.current.delete(data.id);
|
|
891
|
+
setIsRecovering(false);
|
|
892
|
+
if (streamStateRef.current.status === "idle" || streamStateRef.current.streamId === data.id) {
|
|
893
|
+
const result = transition(streamStateRef.current, { type: "clear" });
|
|
894
|
+
streamStateRef.current = result.state;
|
|
895
|
+
setIsServerStreaming(result.isStreaming);
|
|
896
|
+
}
|
|
897
|
+
if (observedToolContinuationRequestIdRef.current === data.id) resetToolContinuation();
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
1406
900
|
if (data.body?.trim()) try {
|
|
1407
901
|
chunkData = JSON.parse(data.body);
|
|
1408
902
|
if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id && typeof chunkData.messageId === "string" && chunkData.type === "start") {
|
|
@@ -1429,8 +923,7 @@ function useAgentChat(options) {
|
|
|
1429
923
|
error: data.error,
|
|
1430
924
|
replay: data.replay,
|
|
1431
925
|
replayComplete: data.replayComplete,
|
|
1432
|
-
continuation: data.continuation
|
|
1433
|
-
currentMessages: data.continuation ? messagesRef.current : void 0
|
|
926
|
+
continuation: data.continuation
|
|
1434
927
|
});
|
|
1435
928
|
streamStateRef.current = result.state;
|
|
1436
929
|
if (result.messagesUpdate) setMessages(result.messagesUpdate);
|
|
@@ -1550,13 +1043,13 @@ function useAgentChat(options) {
|
|
|
1550
1043
|
const addToolApprovalResponseAndNotifyServer = (args) => {
|
|
1551
1044
|
const { id: approvalId, approved } = args;
|
|
1552
1045
|
let toolCallId;
|
|
1553
|
-
|
|
1554
|
-
for (const part of msg.parts) if ("toolCallId" in part && "approval" in part && part.approval?.id === approvalId) {
|
|
1046
|
+
setMessages((currentMessages) => {
|
|
1047
|
+
for (const msg of currentMessages) for (const part of msg.parts) if ("toolCallId" in part && "approval" in part && part.approval?.id === approvalId) {
|
|
1555
1048
|
toolCallId = part.toolCallId;
|
|
1556
|
-
|
|
1049
|
+
return currentMessages;
|
|
1557
1050
|
}
|
|
1558
|
-
|
|
1559
|
-
}
|
|
1051
|
+
return currentMessages;
|
|
1052
|
+
});
|
|
1560
1053
|
if (toolCallId) sendToolApprovalToServer(toolCallId, approved);
|
|
1561
1054
|
else console.warn(`[useAgentChat] addToolApprovalResponse: Could not find toolCallId for approval ID "${approvalId}". Server will not be notified, which may cause duplicate messages.`);
|
|
1562
1055
|
addToolApprovalResponse(args);
|
|
@@ -1655,15 +1148,15 @@ function useAgentChat(options) {
|
|
|
1655
1148
|
agent.send(JSON.stringify({ type: "cf_agent_chat_clear" }));
|
|
1656
1149
|
},
|
|
1657
1150
|
setMessages: (messagesOrUpdater) => {
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
})
|
|
1151
|
+
setMessages((currentMessages) => {
|
|
1152
|
+
const resolvedMessages = typeof messagesOrUpdater === "function" ? messagesOrUpdater(currentMessages) : messagesOrUpdater;
|
|
1153
|
+
if (resolvedMessages.length === 0) markInitialMessagesSeeded();
|
|
1154
|
+
if (syncMessagesToServer) agent.send(JSON.stringify({
|
|
1155
|
+
messages: resolvedMessages,
|
|
1156
|
+
type: "cf_agent_chat_messages"
|
|
1157
|
+
}));
|
|
1158
|
+
return resolvedMessages;
|
|
1159
|
+
});
|
|
1667
1160
|
}
|
|
1668
1161
|
};
|
|
1669
1162
|
}
|