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
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import "./wire-types-CnMt6_HR.js";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
//#region src/chat/replay-batch.ts
|
|
4
|
+
/**
|
|
5
|
+
* Batches chunks replayed during stream resume.
|
|
6
|
+
*
|
|
7
|
+
* Replaying every chunk separately can exceed React's update limit. Adjacent
|
|
8
|
+
* deltas for the same message part are merged until the replay pauses or ends.
|
|
9
|
+
*/
|
|
10
|
+
var ReplayChunkBatch = class {
|
|
11
|
+
constructor(controller, closeWindow = (flush) => {
|
|
12
|
+
const timer = setTimeout(flush, 0);
|
|
13
|
+
return () => clearTimeout(timer);
|
|
14
|
+
}) {
|
|
15
|
+
this.controller = controller;
|
|
16
|
+
this.closeWindow = closeWindow;
|
|
17
|
+
this.chunks = [];
|
|
18
|
+
this.tailMergeKey = null;
|
|
19
|
+
}
|
|
20
|
+
get bufferedStartMessageId() {
|
|
21
|
+
return this.startMessageId;
|
|
22
|
+
}
|
|
23
|
+
get isEmpty() {
|
|
24
|
+
return this.chunks.length === 0;
|
|
25
|
+
}
|
|
26
|
+
push(chunk) {
|
|
27
|
+
const key = mergeKeyOf(chunk);
|
|
28
|
+
const tail = this.chunks[this.chunks.length - 1];
|
|
29
|
+
if (tail !== void 0 && key !== null && key === this.tailMergeKey) {
|
|
30
|
+
appendDeltaText(tail, chunk);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (this.chunks.length === 0) this.cancelWindow = this.closeWindow(() => {
|
|
34
|
+
this.cancelWindow = void 0;
|
|
35
|
+
this.flush();
|
|
36
|
+
});
|
|
37
|
+
if (chunk.type === "start") this.startMessageId = chunk.messageId;
|
|
38
|
+
this.chunks.push(chunk);
|
|
39
|
+
this.tailMergeKey = key;
|
|
40
|
+
}
|
|
41
|
+
discard() {
|
|
42
|
+
this.cancelWindow?.();
|
|
43
|
+
this.cancelWindow = void 0;
|
|
44
|
+
this.chunks = [];
|
|
45
|
+
this.tailMergeKey = null;
|
|
46
|
+
this.startMessageId = void 0;
|
|
47
|
+
}
|
|
48
|
+
flush() {
|
|
49
|
+
this.cancelWindow?.();
|
|
50
|
+
this.cancelWindow = void 0;
|
|
51
|
+
if (this.chunks.length === 0) return;
|
|
52
|
+
const batch = this.chunks;
|
|
53
|
+
this.chunks = [];
|
|
54
|
+
this.tailMergeKey = null;
|
|
55
|
+
this.startMessageId = void 0;
|
|
56
|
+
try {
|
|
57
|
+
for (const chunk of batch) this.controller.enqueue(chunk);
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
enqueueNow(chunk) {
|
|
61
|
+
this.controller.enqueue(chunk);
|
|
62
|
+
}
|
|
63
|
+
closeNow() {
|
|
64
|
+
this.controller.close();
|
|
65
|
+
}
|
|
66
|
+
errorNow(error) {
|
|
67
|
+
this.controller.error(error);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/** Buffers replay chunks and flushes them before live or boundary frames. */
|
|
71
|
+
function applyChatResponseFrame(batch, frame) {
|
|
72
|
+
const endsReplayBurst = frame.replay !== true || frame.done === true || frame.replayComplete === true;
|
|
73
|
+
const body = frame.body?.trim();
|
|
74
|
+
if (!body) {
|
|
75
|
+
if (endsReplayBurst) batch.flush();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
let chunk;
|
|
79
|
+
try {
|
|
80
|
+
chunk = JSON.parse(body);
|
|
81
|
+
} catch {
|
|
82
|
+
if (endsReplayBurst) batch.flush();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (endsReplayBurst) {
|
|
86
|
+
batch.flush();
|
|
87
|
+
batch.enqueueNow(chunk);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (supersedesBufferedPass(batch, chunk, frame)) batch.discard();
|
|
91
|
+
batch.push(chunk);
|
|
92
|
+
}
|
|
93
|
+
function supersedesBufferedPass(batch, chunk, frame) {
|
|
94
|
+
return chunk.type === "start" && frame.continuation !== true && chunk.messageId !== void 0 && chunk.messageId === batch.bufferedStartMessageId;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reports an error after buffered content. `controller.error()` would discard
|
|
98
|
+
* chunks that have not reached the consumer.
|
|
99
|
+
*/
|
|
100
|
+
function failChatStream(batch, message) {
|
|
101
|
+
if (batch.isEmpty) {
|
|
102
|
+
batch.errorNow(new Error(message));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
batch.flush();
|
|
106
|
+
batch.enqueueNow({
|
|
107
|
+
errorText: message,
|
|
108
|
+
type: "error"
|
|
109
|
+
});
|
|
110
|
+
batch.closeNow();
|
|
111
|
+
}
|
|
112
|
+
function mergeKeyOf(chunk) {
|
|
113
|
+
switch (chunk.type) {
|
|
114
|
+
case "text-delta":
|
|
115
|
+
case "reasoning-delta": return chunk.providerMetadata === void 0 ? `${chunk.type}\u0000${chunk.id}` : null;
|
|
116
|
+
case "tool-input-delta": return `tool-input-delta\u0000${chunk.toolCallId}`;
|
|
117
|
+
default: return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function appendDeltaText(target, source) {
|
|
121
|
+
if (target.type === "tool-input-delta" && source.type === "tool-input-delta") {
|
|
122
|
+
target.inputTextDelta += source.inputTextDelta;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if ((target.type === "text-delta" || target.type === "reasoning-delta") && (source.type === "text-delta" || source.type === "reasoning-delta")) target.delta += source.delta;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/chat/ws-chat-transport.ts
|
|
129
|
+
/**
|
|
130
|
+
* Short safety-net timeout for a resume probe when the server has said nothing.
|
|
131
|
+
* Under normal operation the server answers a `STREAM_RESUME_REQUEST` with
|
|
132
|
+
* `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.
|
|
133
|
+
*/
|
|
134
|
+
const RESUME_PROBE_TIMEOUT_MS = 5e3;
|
|
135
|
+
/**
|
|
136
|
+
* Extended backstop applied once the server says a turn is pending
|
|
137
|
+
* (`STREAM_PENDING`, #1784). The pre-stream window (queueing, MCP setup,
|
|
138
|
+
* debounce, model latency) can exceed the short probe timeout, and the server
|
|
139
|
+
* guarantees a follow-up `STREAM_RESUMING` or `STREAM_RESUME_NONE` — so we wait
|
|
140
|
+
* much longer (refreshed on every keep-waiting frame) but still cap it so a
|
|
141
|
+
* dropped follow-up degrades to a null resolve instead of hanging forever.
|
|
142
|
+
*/
|
|
143
|
+
const RESUME_PENDING_TIMEOUT_MS = 6e4;
|
|
144
|
+
/**
|
|
145
|
+
* ChatTransport that sends messages over WebSocket and returns a
|
|
146
|
+
* ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.
|
|
147
|
+
*
|
|
148
|
+
* This low-level transport handles new and regenerated request streams plus
|
|
149
|
+
* cancellation. Higher-level protocol coordination such as automatic reconnect
|
|
150
|
+
* resume, cross-tab transcript synchronization, and client-tool continuations
|
|
151
|
+
* is provided by integrations such as `useAgentChat`.
|
|
152
|
+
*/
|
|
153
|
+
var WebSocketChatTransport = class {
|
|
154
|
+
constructor(options) {
|
|
155
|
+
this._resumeResolver = null;
|
|
156
|
+
this._resumeNoneResolver = null;
|
|
157
|
+
this._onStreamPending = null;
|
|
158
|
+
this._retryResumeProbe = null;
|
|
159
|
+
this._expectToolContinuation = false;
|
|
160
|
+
this._abortToolContinuation = null;
|
|
161
|
+
this._activeServerTurnId = null;
|
|
162
|
+
this._cancelAttachedStream = null;
|
|
163
|
+
this._detachResumeStream = null;
|
|
164
|
+
this.agent = options.agent;
|
|
165
|
+
this.prepareBody = options.prepareBody;
|
|
166
|
+
this.activeRequestIds = options.activeRequestIds;
|
|
167
|
+
this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Point the singleton transport at a new Agent connection. A pending resolver
|
|
171
|
+
* belongs to the old Chat/socket generation and must settle before messages
|
|
172
|
+
* from the replacement connection can be consumed (#1914 review).
|
|
173
|
+
*/
|
|
174
|
+
setAgent(agent) {
|
|
175
|
+
if (this.agent === agent) return;
|
|
176
|
+
this.resetResumeState();
|
|
177
|
+
this.agent = agent;
|
|
178
|
+
}
|
|
179
|
+
setCancelOnClientAbort(cancelOnClientAbort) {
|
|
180
|
+
this.cancelOnClientAbort = cancelOnClientAbort;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Explicitly cancel the active server turn, if any.
|
|
184
|
+
* This is separate from generic client-side abort/cancel lifecycle so
|
|
185
|
+
* clients can detach locally without stopping server work.
|
|
186
|
+
*/
|
|
187
|
+
cancelActiveServerTurn() {
|
|
188
|
+
const requestId = this._activeServerTurnId;
|
|
189
|
+
let cancelledRequest = false;
|
|
190
|
+
if (requestId) {
|
|
191
|
+
this.sendCancelFrame(requestId);
|
|
192
|
+
this._cancelAttachedStream?.();
|
|
193
|
+
this.clearActiveServerTurn(requestId);
|
|
194
|
+
cancelledRequest = true;
|
|
195
|
+
}
|
|
196
|
+
const cancelledToolContinuation = this.abortActiveToolContinuation();
|
|
197
|
+
return cancelledRequest || cancelledToolContinuation;
|
|
198
|
+
}
|
|
199
|
+
sendCancelFrame(requestId) {
|
|
200
|
+
try {
|
|
201
|
+
this.agent.send(JSON.stringify({
|
|
202
|
+
id: requestId,
|
|
203
|
+
type: "cf_agent_chat_request_cancel"
|
|
204
|
+
}));
|
|
205
|
+
} catch {}
|
|
206
|
+
}
|
|
207
|
+
setActiveServerTurn(requestId, cancelAttachedStream) {
|
|
208
|
+
this._activeServerTurnId = requestId;
|
|
209
|
+
this._cancelAttachedStream = cancelAttachedStream;
|
|
210
|
+
}
|
|
211
|
+
clearActiveServerTurn(requestId) {
|
|
212
|
+
if (this._activeServerTurnId === requestId) {
|
|
213
|
+
this._activeServerTurnId = null;
|
|
214
|
+
this._cancelAttachedStream = null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Mark that the next reconnectToStream() call should attach to a
|
|
219
|
+
* server-initiated tool continuation rather than a page-load resume.
|
|
220
|
+
*/
|
|
221
|
+
expectToolContinuation() {
|
|
222
|
+
this._expectToolContinuation = true;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Abort the active client-side tool continuation stream, if one is attached
|
|
226
|
+
* to a server request id.
|
|
227
|
+
*/
|
|
228
|
+
abortActiveToolContinuation() {
|
|
229
|
+
return this._abortToolContinuation?.() ?? false;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* True when the transport is waiting for a resume handshake.
|
|
233
|
+
*/
|
|
234
|
+
isAwaitingResume() {
|
|
235
|
+
return this._resumeResolver !== null || this._resumeNoneResolver !== null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Settle and detach the current handshake without interpreting it as a
|
|
239
|
+
* server-idle response. Used when the owning hook/agent generation changes.
|
|
240
|
+
*/
|
|
241
|
+
cancelPendingResume() {
|
|
242
|
+
const resolveNone = this._resumeNoneResolver;
|
|
243
|
+
if (!resolveNone) return false;
|
|
244
|
+
return resolveNone({});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Invalidate all client-side resume state for an obsolete hook/agent
|
|
248
|
+
* generation without cancelling its durable server turn.
|
|
249
|
+
*/
|
|
250
|
+
resetResumeState() {
|
|
251
|
+
this._expectToolContinuation = false;
|
|
252
|
+
this.cancelPendingResume();
|
|
253
|
+
this._detachResumeStream?.();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Re-send the active handshake request on the latest socket generation. This
|
|
257
|
+
* preserves one AI SDK resume operation while recovering a request/reply lost
|
|
258
|
+
* with the previous WebSocket.
|
|
259
|
+
*/
|
|
260
|
+
retryPendingResume() {
|
|
261
|
+
const retry = this._retryResumeProbe;
|
|
262
|
+
if (!retry) return false;
|
|
263
|
+
retry();
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.
|
|
268
|
+
* If reconnectToStream is waiting, this handles the resume handshake
|
|
269
|
+
* (ACK + stream creation) and returns true. Otherwise returns false
|
|
270
|
+
* so the caller can use its own fallback path.
|
|
271
|
+
*/
|
|
272
|
+
handleStreamResuming(data) {
|
|
273
|
+
if (!this._resumeResolver) return false;
|
|
274
|
+
this._resumeResolver(data);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.
|
|
279
|
+
* If reconnectToStream is waiting, resolves the promise with null
|
|
280
|
+
* immediately (no 5-second timeout). Returns true if handled.
|
|
281
|
+
*/
|
|
282
|
+
handleStreamResumeNone(data = {}) {
|
|
283
|
+
if (!this._resumeNoneResolver) return false;
|
|
284
|
+
return this._resumeNoneResolver(data);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):
|
|
288
|
+
* the server accepted a turn but its stream has not started yet. If a resume
|
|
289
|
+
* path is awaiting, extend its probe timeout (so it keeps waiting for the
|
|
290
|
+
* eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null
|
|
291
|
+
* after the short window). Returns true if a waiting path consumed it.
|
|
292
|
+
*/
|
|
293
|
+
handleStreamPending() {
|
|
294
|
+
if (!this._onStreamPending) return false;
|
|
295
|
+
this._onStreamPending();
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Called by the hook's shared message handler when a server turn finishes
|
|
300
|
+
* outside the currently attached transport stream, such as after local-only
|
|
301
|
+
* client cleanup.
|
|
302
|
+
*/
|
|
303
|
+
handleServerTurnCompleted(requestId) {
|
|
304
|
+
this.clearActiveServerTurn(requestId);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Register a server turn that is being rendered outside a transport-owned
|
|
308
|
+
* stream, such as the hook's fallback cross-tab/resume observer path.
|
|
309
|
+
*/
|
|
310
|
+
observeServerTurn(requestId) {
|
|
311
|
+
this.setActiveServerTurn(requestId, null);
|
|
312
|
+
}
|
|
313
|
+
async sendMessages(options) {
|
|
314
|
+
const requestId = nanoid(8);
|
|
315
|
+
const abortController = new AbortController();
|
|
316
|
+
let completed = false;
|
|
317
|
+
let requestSent = false;
|
|
318
|
+
let extraBody = {};
|
|
319
|
+
if (this.prepareBody) extraBody = await this.prepareBody({
|
|
320
|
+
messages: options.messages,
|
|
321
|
+
trigger: options.trigger,
|
|
322
|
+
messageId: options.messageId
|
|
323
|
+
});
|
|
324
|
+
if (options.body) extraBody = {
|
|
325
|
+
...extraBody,
|
|
326
|
+
...options.body
|
|
327
|
+
};
|
|
328
|
+
const bodyPayload = JSON.stringify({
|
|
329
|
+
messages: options.messages,
|
|
330
|
+
trigger: options.trigger,
|
|
331
|
+
...extraBody
|
|
332
|
+
});
|
|
333
|
+
this.activeRequestIds?.add(requestId);
|
|
334
|
+
const agent = this.agent;
|
|
335
|
+
const activeIds = this.activeRequestIds;
|
|
336
|
+
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
337
|
+
if (completed) return;
|
|
338
|
+
completed = true;
|
|
339
|
+
if (clearServerTurn) this.clearActiveServerTurn(requestId);
|
|
340
|
+
try {
|
|
341
|
+
action();
|
|
342
|
+
} catch {}
|
|
343
|
+
if (!keepId) activeIds?.delete(requestId);
|
|
344
|
+
abortController.abort();
|
|
345
|
+
};
|
|
346
|
+
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
347
|
+
abortError.name = "AbortError";
|
|
348
|
+
const cancelActiveRequest = () => {
|
|
349
|
+
if (completed) return false;
|
|
350
|
+
finish(() => streamController.error(abortError), true);
|
|
351
|
+
return true;
|
|
352
|
+
};
|
|
353
|
+
this.setActiveServerTurn(requestId, cancelActiveRequest);
|
|
354
|
+
const onAbort = () => {
|
|
355
|
+
if (completed) return;
|
|
356
|
+
if (this.cancelOnClientAbort) {
|
|
357
|
+
if (requestSent) this.sendCancelFrame(requestId);
|
|
358
|
+
finish(() => streamController.error(abortError), requestSent);
|
|
359
|
+
} else finish(() => streamController.error(abortError), false, !requestSent);
|
|
360
|
+
};
|
|
361
|
+
let streamController;
|
|
362
|
+
const stream = new ReadableStream({
|
|
363
|
+
start(controller) {
|
|
364
|
+
streamController = controller;
|
|
365
|
+
const onMessage = (event) => {
|
|
366
|
+
try {
|
|
367
|
+
const data = JSON.parse(event.data);
|
|
368
|
+
if (data.type !== "cf_agent_use_chat_response") return;
|
|
369
|
+
if (data.id !== requestId) return;
|
|
370
|
+
if (data.error) {
|
|
371
|
+
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (data.body?.trim()) try {
|
|
375
|
+
const chunk = JSON.parse(data.body);
|
|
376
|
+
controller.enqueue(chunk);
|
|
377
|
+
} catch {}
|
|
378
|
+
if (data.done) finish(() => controller.close());
|
|
379
|
+
} catch {}
|
|
380
|
+
};
|
|
381
|
+
const onClose = () => {
|
|
382
|
+
finish(() => controller.close(), false, false);
|
|
383
|
+
};
|
|
384
|
+
agent.addEventListener("message", onMessage, { signal: abortController.signal });
|
|
385
|
+
agent.addEventListener("close", onClose, { signal: abortController.signal });
|
|
386
|
+
},
|
|
387
|
+
cancel() {
|
|
388
|
+
onAbort();
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
if (options.abortSignal) {
|
|
392
|
+
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
393
|
+
if (options.abortSignal.aborted) onAbort();
|
|
394
|
+
}
|
|
395
|
+
if (completed) return stream;
|
|
396
|
+
requestSent = true;
|
|
397
|
+
agent.send(JSON.stringify({
|
|
398
|
+
id: requestId,
|
|
399
|
+
init: {
|
|
400
|
+
method: "POST",
|
|
401
|
+
body: bodyPayload
|
|
402
|
+
},
|
|
403
|
+
type: "cf_agent_use_chat_request"
|
|
404
|
+
}));
|
|
405
|
+
return stream;
|
|
406
|
+
}
|
|
407
|
+
async reconnectToStream(_options) {
|
|
408
|
+
if (this.isAwaitingResume()) return null;
|
|
409
|
+
if (this._expectToolContinuation) {
|
|
410
|
+
this._expectToolContinuation = false;
|
|
411
|
+
return this._createToolContinuationStream();
|
|
412
|
+
}
|
|
413
|
+
const activeIds = this.activeRequestIds;
|
|
414
|
+
const probeId = nanoid(8);
|
|
415
|
+
return new Promise((resolve) => {
|
|
416
|
+
let resolved = false;
|
|
417
|
+
let timeout;
|
|
418
|
+
let resumeResolver = null;
|
|
419
|
+
let resumeNoneResolver = null;
|
|
420
|
+
let onStreamPending = null;
|
|
421
|
+
let retryResumeProbe = null;
|
|
422
|
+
const clearOwnedCallbacks = () => {
|
|
423
|
+
if (resumeResolver && this._resumeResolver === resumeResolver) this._resumeResolver = null;
|
|
424
|
+
if (resumeNoneResolver && this._resumeNoneResolver === resumeNoneResolver) this._resumeNoneResolver = null;
|
|
425
|
+
if (onStreamPending && this._onStreamPending === onStreamPending) this._onStreamPending = null;
|
|
426
|
+
if (retryResumeProbe && this._retryResumeProbe === retryResumeProbe) this._retryResumeProbe = null;
|
|
427
|
+
};
|
|
428
|
+
const done = (value) => {
|
|
429
|
+
if (resolved) return;
|
|
430
|
+
resolved = true;
|
|
431
|
+
clearOwnedCallbacks();
|
|
432
|
+
if (timeout) clearTimeout(timeout);
|
|
433
|
+
resolve(value);
|
|
434
|
+
};
|
|
435
|
+
const armTimeout = (delay) => {
|
|
436
|
+
if (timeout) clearTimeout(timeout);
|
|
437
|
+
timeout = setTimeout(() => done(null), delay);
|
|
438
|
+
};
|
|
439
|
+
onStreamPending = () => {
|
|
440
|
+
if (resolved) return;
|
|
441
|
+
armTimeout(RESUME_PENDING_TIMEOUT_MS);
|
|
442
|
+
};
|
|
443
|
+
resumeNoneResolver = (data) => {
|
|
444
|
+
if (data.probeId && data.probeId !== probeId) return false;
|
|
445
|
+
done(null);
|
|
446
|
+
return true;
|
|
447
|
+
};
|
|
448
|
+
resumeResolver = (data) => {
|
|
449
|
+
const requestId = data.id;
|
|
450
|
+
activeIds?.add(requestId);
|
|
451
|
+
const stream = this._createResumeStream(requestId);
|
|
452
|
+
this.agent.send(JSON.stringify({
|
|
453
|
+
type: "cf_agent_stream_resume_ack",
|
|
454
|
+
id: requestId
|
|
455
|
+
}));
|
|
456
|
+
done(stream);
|
|
457
|
+
};
|
|
458
|
+
retryResumeProbe = () => {
|
|
459
|
+
if (resolved) return;
|
|
460
|
+
armTimeout(RESUME_PROBE_TIMEOUT_MS);
|
|
461
|
+
try {
|
|
462
|
+
this.agent.send(JSON.stringify({
|
|
463
|
+
type: "cf_agent_stream_resume_request",
|
|
464
|
+
probeId
|
|
465
|
+
}));
|
|
466
|
+
} catch {}
|
|
467
|
+
};
|
|
468
|
+
this._onStreamPending = onStreamPending;
|
|
469
|
+
this._resumeNoneResolver = resumeNoneResolver;
|
|
470
|
+
this._resumeResolver = resumeResolver;
|
|
471
|
+
this._retryResumeProbe = retryResumeProbe;
|
|
472
|
+
retryResumeProbe();
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Creates a deferred ReadableStream for client-side tool continuations.
|
|
477
|
+
* The stream is returned immediately so AI SDK status becomes "submitted"
|
|
478
|
+
* right after addToolOutput()/addToolApprovalResponse(), then it waits for
|
|
479
|
+
* the server to announce the continuation via STREAM_RESUMING.
|
|
480
|
+
*/
|
|
481
|
+
_createToolContinuationStream() {
|
|
482
|
+
const agent = this.agent;
|
|
483
|
+
const activeIds = this.activeRequestIds;
|
|
484
|
+
const streamController = new AbortController();
|
|
485
|
+
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
486
|
+
abortError.name = "AbortError";
|
|
487
|
+
let completed = false;
|
|
488
|
+
let requestId = null;
|
|
489
|
+
let readerController = null;
|
|
490
|
+
let replayBatch = null;
|
|
491
|
+
const probeId = nanoid(8);
|
|
492
|
+
let onResumeRef = null;
|
|
493
|
+
let onResumeNoneRef = null;
|
|
494
|
+
let onStreamPendingRef = null;
|
|
495
|
+
let retryResumeProbeRef = null;
|
|
496
|
+
let abortToolContinuationRef = null;
|
|
497
|
+
let detachResumeStreamRef = null;
|
|
498
|
+
const clearOwnedHandshake = () => {
|
|
499
|
+
if (onResumeRef && this._resumeResolver === onResumeRef) this._resumeResolver = null;
|
|
500
|
+
if (onResumeNoneRef && this._resumeNoneResolver === onResumeNoneRef) this._resumeNoneResolver = null;
|
|
501
|
+
if (onStreamPendingRef && this._onStreamPending === onStreamPendingRef) this._onStreamPending = null;
|
|
502
|
+
if (retryResumeProbeRef && this._retryResumeProbe === retryResumeProbeRef) this._retryResumeProbe = null;
|
|
503
|
+
};
|
|
504
|
+
const finish = (action, keepRequestId = false) => {
|
|
505
|
+
if (completed) return;
|
|
506
|
+
completed = true;
|
|
507
|
+
if (this._abortToolContinuation === abortToolContinuationRef) this._abortToolContinuation = null;
|
|
508
|
+
if (this._detachResumeStream === detachResumeStreamRef) this._detachResumeStream = null;
|
|
509
|
+
clearOwnedHandshake();
|
|
510
|
+
try {
|
|
511
|
+
action();
|
|
512
|
+
} catch {}
|
|
513
|
+
if (requestId && !keepRequestId) activeIds?.delete(requestId);
|
|
514
|
+
streamController.abort();
|
|
515
|
+
};
|
|
516
|
+
const transport = this;
|
|
517
|
+
abortToolContinuationRef = () => {
|
|
518
|
+
if (completed) return false;
|
|
519
|
+
if (requestId === null) {
|
|
520
|
+
finish(() => readerController?.error(abortError));
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
agent.send(JSON.stringify({
|
|
525
|
+
type: "cf_agent_chat_request_cancel",
|
|
526
|
+
id: requestId
|
|
527
|
+
}));
|
|
528
|
+
} catch {}
|
|
529
|
+
finish(() => readerController?.error(abortError), true);
|
|
530
|
+
return true;
|
|
531
|
+
};
|
|
532
|
+
this._abortToolContinuation = abortToolContinuationRef;
|
|
533
|
+
detachResumeStreamRef = () => {
|
|
534
|
+
if (completed) return false;
|
|
535
|
+
finish(() => {
|
|
536
|
+
replayBatch?.flush();
|
|
537
|
+
readerController?.close();
|
|
538
|
+
});
|
|
539
|
+
return true;
|
|
540
|
+
};
|
|
541
|
+
this._detachResumeStream = detachResumeStreamRef;
|
|
542
|
+
return new ReadableStream({
|
|
543
|
+
start(controller) {
|
|
544
|
+
readerController = controller;
|
|
545
|
+
const batch = new ReplayChunkBatch(controller);
|
|
546
|
+
replayBatch = batch;
|
|
547
|
+
let timeout;
|
|
548
|
+
const armTimeout = (delay) => {
|
|
549
|
+
if (timeout) clearTimeout(timeout);
|
|
550
|
+
timeout = setTimeout(() => finish(() => controller.close()), delay);
|
|
551
|
+
};
|
|
552
|
+
const onResumeNone = (data) => {
|
|
553
|
+
if (data.probeId && data.probeId !== probeId) return false;
|
|
554
|
+
finish(() => controller.close());
|
|
555
|
+
return true;
|
|
556
|
+
};
|
|
557
|
+
const onResume = (data) => {
|
|
558
|
+
if (requestId) return;
|
|
559
|
+
requestId = data.id;
|
|
560
|
+
activeIds?.add(requestId);
|
|
561
|
+
clearOwnedHandshake();
|
|
562
|
+
if (timeout) clearTimeout(timeout);
|
|
563
|
+
agent.send(JSON.stringify({
|
|
564
|
+
type: "cf_agent_stream_resume_ack",
|
|
565
|
+
id: requestId
|
|
566
|
+
}));
|
|
567
|
+
};
|
|
568
|
+
const onStreamPending = () => {
|
|
569
|
+
if (completed) return;
|
|
570
|
+
armTimeout(RESUME_PENDING_TIMEOUT_MS);
|
|
571
|
+
};
|
|
572
|
+
const retryResumeProbe = () => {
|
|
573
|
+
if (completed || requestId !== null) return;
|
|
574
|
+
armTimeout(RESUME_PROBE_TIMEOUT_MS);
|
|
575
|
+
try {
|
|
576
|
+
transport.agent.send(JSON.stringify({
|
|
577
|
+
type: "cf_agent_stream_resume_request",
|
|
578
|
+
probeId
|
|
579
|
+
}));
|
|
580
|
+
} catch {}
|
|
581
|
+
};
|
|
582
|
+
onResumeRef = onResume;
|
|
583
|
+
onResumeNoneRef = onResumeNone;
|
|
584
|
+
onStreamPendingRef = onStreamPending;
|
|
585
|
+
retryResumeProbeRef = retryResumeProbe;
|
|
586
|
+
transport._resumeResolver = onResume;
|
|
587
|
+
transport._resumeNoneResolver = onResumeNone;
|
|
588
|
+
transport._onStreamPending = onStreamPending;
|
|
589
|
+
transport._retryResumeProbe = retryResumeProbe;
|
|
590
|
+
const onMessage = (event) => {
|
|
591
|
+
try {
|
|
592
|
+
const data = JSON.parse(event.data);
|
|
593
|
+
if (data.type !== "cf_agent_use_chat_response" || requestId == null || data.id !== requestId) return;
|
|
594
|
+
if (data.error) {
|
|
595
|
+
finish(() => failChatStream(batch, data.body || "Stream error"));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
applyChatResponseFrame(batch, data);
|
|
599
|
+
if (data.done) finish(() => controller.close());
|
|
600
|
+
} catch {}
|
|
601
|
+
};
|
|
602
|
+
const onClose = () => finish(() => {
|
|
603
|
+
batch.flush();
|
|
604
|
+
controller.close();
|
|
605
|
+
});
|
|
606
|
+
agent.addEventListener("message", onMessage, { signal: streamController.signal });
|
|
607
|
+
agent.addEventListener("close", onClose, { signal: streamController.signal });
|
|
608
|
+
retryResumeProbe();
|
|
609
|
+
},
|
|
610
|
+
cancel() {
|
|
611
|
+
if (requestId && transport.cancelOnClientAbort) {
|
|
612
|
+
transport.sendCancelFrame(requestId);
|
|
613
|
+
finish(() => {}, true);
|
|
614
|
+
} else finish(() => {});
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Creates a ReadableStream that receives resumed stream chunks
|
|
620
|
+
* and forwards them to useChat as UIMessageChunk objects.
|
|
621
|
+
*/
|
|
622
|
+
_createResumeStream(requestId) {
|
|
623
|
+
const agent = this.agent;
|
|
624
|
+
const activeIds = this.activeRequestIds;
|
|
625
|
+
const chunkController = new AbortController();
|
|
626
|
+
const abortError = /* @__PURE__ */ new Error("Aborted");
|
|
627
|
+
abortError.name = "AbortError";
|
|
628
|
+
let completed = false;
|
|
629
|
+
let detachResumeStream = null;
|
|
630
|
+
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
631
|
+
if (completed) return;
|
|
632
|
+
completed = true;
|
|
633
|
+
if (clearServerTurn) this.clearActiveServerTurn(requestId);
|
|
634
|
+
if (this._detachResumeStream === detachResumeStream) this._detachResumeStream = null;
|
|
635
|
+
try {
|
|
636
|
+
action();
|
|
637
|
+
} catch {}
|
|
638
|
+
if (!keepId) activeIds?.delete(requestId);
|
|
639
|
+
chunkController.abort();
|
|
640
|
+
};
|
|
641
|
+
let streamController = null;
|
|
642
|
+
let replayBatch = null;
|
|
643
|
+
const cancelActiveRequest = () => {
|
|
644
|
+
if (completed) return false;
|
|
645
|
+
finish(() => streamController?.error(abortError), true);
|
|
646
|
+
return true;
|
|
647
|
+
};
|
|
648
|
+
this.setActiveServerTurn(requestId, cancelActiveRequest);
|
|
649
|
+
const transport = this;
|
|
650
|
+
detachResumeStream = () => {
|
|
651
|
+
if (completed) return false;
|
|
652
|
+
finish(() => {
|
|
653
|
+
replayBatch?.flush();
|
|
654
|
+
streamController?.close();
|
|
655
|
+
});
|
|
656
|
+
return true;
|
|
657
|
+
};
|
|
658
|
+
this._detachResumeStream = detachResumeStream;
|
|
659
|
+
return new ReadableStream({
|
|
660
|
+
start(controller) {
|
|
661
|
+
streamController = controller;
|
|
662
|
+
const batch = new ReplayChunkBatch(controller);
|
|
663
|
+
replayBatch = batch;
|
|
664
|
+
const onMessage = (event) => {
|
|
665
|
+
try {
|
|
666
|
+
const data = JSON.parse(event.data);
|
|
667
|
+
if (data.type !== "cf_agent_use_chat_response") return;
|
|
668
|
+
if (data.id !== requestId) return;
|
|
669
|
+
if (data.error) {
|
|
670
|
+
finish(() => failChatStream(batch, data.body || "Stream error"));
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
applyChatResponseFrame(batch, data);
|
|
674
|
+
if (data.done) finish(() => controller.close());
|
|
675
|
+
} catch {}
|
|
676
|
+
};
|
|
677
|
+
const onClose = () => {
|
|
678
|
+
finish(() => {
|
|
679
|
+
batch.flush();
|
|
680
|
+
controller.close();
|
|
681
|
+
}, false, false);
|
|
682
|
+
};
|
|
683
|
+
agent.addEventListener("message", onMessage, { signal: chunkController.signal });
|
|
684
|
+
agent.addEventListener("close", onClose, { signal: chunkController.signal });
|
|
685
|
+
},
|
|
686
|
+
cancel() {
|
|
687
|
+
if (transport.cancelOnClientAbort) {
|
|
688
|
+
transport.sendCancelFrame(requestId);
|
|
689
|
+
finish(() => {}, true);
|
|
690
|
+
} else finish(() => {}, false, false);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
//#endregion
|
|
696
|
+
export { WebSocketChatTransport as t };
|
|
697
|
+
|
|
698
|
+
//# sourceMappingURL=ws-chat-transport-rWwta645.js.map
|