agents-vue 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +116 -12
- package/dist/index.d.mts +56 -95
- package/dist/index.d.ts +56 -95
- package/dist/index.mjs +260 -516
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -1,498 +1,14 @@
|
|
|
1
1
|
import { Chat } from '@ai-sdk/vue';
|
|
2
2
|
import { broadcastTransition } from 'agents/chat';
|
|
3
|
+
import { WebSocketChatTransport } from 'agents/chat/transport';
|
|
3
4
|
import { AgentClient } from 'agents/client';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
function randomRequestId() {
|
|
7
|
-
return Math.random().toString(36).slice(2, 10);
|
|
8
|
-
}
|
|
9
|
-
const RESUME_PROBE_TIMEOUT_MS = 5e3;
|
|
10
|
-
const RESUME_PENDING_TIMEOUT_MS = 6e4;
|
|
11
|
-
class WebSocketChatTransport {
|
|
12
|
-
socket;
|
|
13
|
-
prepareBody;
|
|
14
|
-
activeRequestIds;
|
|
15
|
-
// Resume handshake state (public so the composable message handler can resolve them)
|
|
16
|
-
_resumeResolver = null;
|
|
17
|
-
_resumeNoneResolver = null;
|
|
18
|
-
// Tool continuation state
|
|
19
|
-
_expectToolContinuation = false;
|
|
20
|
-
_abortToolContinuation = null;
|
|
21
|
-
// Whether a generic client abort should cancel the server turn (default: local-only).
|
|
22
|
-
cancelOnClientAbort;
|
|
23
|
-
// The currently cancellable server turn — either locally initiated (sendMessages)
|
|
24
|
-
// or observed via the broadcast/resume-fallback path. `_cancelAttachedStream` tears
|
|
25
|
-
// down the local stream attached to a locally-initiated turn (null for observed turns).
|
|
26
|
-
_activeServerTurnId = null;
|
|
27
|
-
_cancelAttachedStream = null;
|
|
28
|
-
// Pre-stream "pending" extender for the in-flight resume probe (cleared on resolve).
|
|
29
|
-
_onStreamPending = null;
|
|
30
|
-
constructor(options) {
|
|
31
|
-
this.socket = options.socket;
|
|
32
|
-
this.prepareBody = options.prepareBody;
|
|
33
|
-
this.activeRequestIds = options.activeRequestIds ?? /* @__PURE__ */ new Set();
|
|
34
|
-
this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;
|
|
35
|
-
}
|
|
36
|
-
/** Mark that the next reconnectToStream() should attach to a server-initiated tool continuation. */
|
|
37
|
-
expectToolContinuation() {
|
|
38
|
-
this._expectToolContinuation = true;
|
|
39
|
-
}
|
|
40
|
-
/** Abort the active client-side tool continuation stream. */
|
|
41
|
-
abortActiveToolContinuation() {
|
|
42
|
-
return this._abortToolContinuation?.() ?? false;
|
|
43
|
-
}
|
|
44
|
-
/** Send a server-turn cancel frame for a request id (best-effort). */
|
|
45
|
-
sendCancelFrame(id) {
|
|
46
|
-
try {
|
|
47
|
-
this.socket.send(JSON.stringify({ id, type: "cf_agent_chat_request_cancel" }));
|
|
48
|
-
} catch {
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
setActiveServerTurn(id, cancelStream) {
|
|
52
|
-
this._activeServerTurnId = id;
|
|
53
|
-
this._cancelAttachedStream = cancelStream;
|
|
54
|
-
}
|
|
55
|
-
clearActiveServerTurn(id) {
|
|
56
|
-
if (this._activeServerTurnId === id) {
|
|
57
|
-
this._activeServerTurnId = null;
|
|
58
|
-
this._cancelAttachedStream = null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/** Register a server-observed (broadcast / resume-fallback) turn as the cancellable one. */
|
|
62
|
-
observeServerTurn(requestId) {
|
|
63
|
-
this.setActiveServerTurn(requestId, null);
|
|
64
|
-
}
|
|
65
|
-
/** Mark a server turn complete so it is no longer the cancellable turn. */
|
|
66
|
-
handleServerTurnCompleted(requestId) {
|
|
67
|
-
this.clearActiveServerTurn(requestId);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Explicitly cancel the active server turn (and any tool continuation). Unlike a
|
|
71
|
-
* generic client abort, this always signals the server. Drives `useAgentChat.stop()`.
|
|
72
|
-
*/
|
|
73
|
-
cancelActiveServerTurn() {
|
|
74
|
-
const id = this._activeServerTurnId;
|
|
75
|
-
let cancelledRequest = false;
|
|
76
|
-
if (id) {
|
|
77
|
-
this.sendCancelFrame(id);
|
|
78
|
-
this._cancelAttachedStream?.();
|
|
79
|
-
this.clearActiveServerTurn(id);
|
|
80
|
-
cancelledRequest = true;
|
|
81
|
-
}
|
|
82
|
-
const cancelledToolContinuation = this.abortActiveToolContinuation();
|
|
83
|
-
return cancelledRequest || cancelledToolContinuation;
|
|
84
|
-
}
|
|
85
|
-
/** Called by the composable on cf_agent_stream_pending — extend the in-flight resume probe. */
|
|
86
|
-
handleStreamPending() {
|
|
87
|
-
if (!this._onStreamPending) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
this._onStreamPending();
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
/** True when the transport is waiting for a resume handshake. */
|
|
94
|
-
isAwaitingResume() {
|
|
95
|
-
return this._resumeResolver !== null || this._resumeNoneResolver !== null;
|
|
96
|
-
}
|
|
97
|
-
/** Called by the composable when it receives cf_agent_stream_resuming. */
|
|
98
|
-
handleStreamResuming(data) {
|
|
99
|
-
if (!this._resumeResolver) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
this._resumeResolver(data);
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
/** Called by the composable when it receives cf_agent_stream_resume_none. */
|
|
106
|
-
handleStreamResumeNone() {
|
|
107
|
-
if (!this._resumeNoneResolver) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
this._resumeNoneResolver();
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
async sendMessages(options) {
|
|
114
|
-
const requestId = randomRequestId();
|
|
115
|
-
const abortController = new AbortController();
|
|
116
|
-
let completed = false;
|
|
117
|
-
let extraBody = {};
|
|
118
|
-
if (this.prepareBody) {
|
|
119
|
-
extraBody = await this.prepareBody({
|
|
120
|
-
messages: options.messages,
|
|
121
|
-
trigger: options.trigger,
|
|
122
|
-
messageId: options.messageId
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
if (options.body) {
|
|
126
|
-
extraBody = { ...extraBody, ...options.body };
|
|
127
|
-
}
|
|
128
|
-
const tail = options.messages.at(-1);
|
|
129
|
-
const bodyPayload = JSON.stringify({
|
|
130
|
-
messages: tail ? [tail] : [],
|
|
131
|
-
trigger: options.trigger,
|
|
132
|
-
...extraBody
|
|
133
|
-
});
|
|
134
|
-
this.activeRequestIds.add(requestId);
|
|
135
|
-
const socket = this.socket;
|
|
136
|
-
const activeIds = this.activeRequestIds;
|
|
137
|
-
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
138
|
-
if (completed) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
completed = true;
|
|
142
|
-
if (clearServerTurn) {
|
|
143
|
-
this.clearActiveServerTurn(requestId);
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
action();
|
|
147
|
-
} catch {
|
|
148
|
-
}
|
|
149
|
-
if (!keepId) {
|
|
150
|
-
activeIds.delete(requestId);
|
|
151
|
-
}
|
|
152
|
-
abortController.abort();
|
|
153
|
-
};
|
|
154
|
-
const abortError = new Error("Aborted");
|
|
155
|
-
abortError.name = "AbortError";
|
|
156
|
-
let requestSent = false;
|
|
157
|
-
let streamController;
|
|
158
|
-
const cancelActiveRequest = () => {
|
|
159
|
-
if (completed) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
finish(() => streamController.error(abortError), true);
|
|
163
|
-
return true;
|
|
164
|
-
};
|
|
165
|
-
const onAbort = () => {
|
|
166
|
-
if (completed) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (this.cancelOnClientAbort) {
|
|
170
|
-
if (requestSent) {
|
|
171
|
-
this.sendCancelFrame(requestId);
|
|
172
|
-
}
|
|
173
|
-
finish(() => streamController.error(abortError), requestSent);
|
|
174
|
-
} else {
|
|
175
|
-
finish(() => streamController.error(abortError), false, !requestSent);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
const stream = new ReadableStream({
|
|
179
|
-
start(controller) {
|
|
180
|
-
streamController = controller;
|
|
181
|
-
const onMessage = (event) => {
|
|
182
|
-
try {
|
|
183
|
-
const data = JSON.parse(String(event.data));
|
|
184
|
-
if (data.type !== "cf_agent_use_chat_response") {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (data.id !== requestId) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (data.error) {
|
|
191
|
-
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (data.body?.trim()) {
|
|
195
|
-
try {
|
|
196
|
-
const chunk = JSON.parse(data.body);
|
|
197
|
-
controller.enqueue(chunk);
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (data.done) {
|
|
202
|
-
finish(() => controller.close());
|
|
203
|
-
}
|
|
204
|
-
} catch {
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
socket.addEventListener("message", onMessage, { signal: abortController.signal });
|
|
208
|
-
},
|
|
209
|
-
cancel() {
|
|
210
|
-
onAbort();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
this.setActiveServerTurn(requestId, cancelActiveRequest);
|
|
214
|
-
if (options.abortSignal) {
|
|
215
|
-
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
216
|
-
if (options.abortSignal.aborted) {
|
|
217
|
-
onAbort();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!completed) {
|
|
221
|
-
socket.send(
|
|
222
|
-
JSON.stringify({
|
|
223
|
-
id: requestId,
|
|
224
|
-
init: { method: "POST", body: bodyPayload },
|
|
225
|
-
type: "cf_agent_use_chat_request"
|
|
226
|
-
})
|
|
227
|
-
);
|
|
228
|
-
requestSent = true;
|
|
229
|
-
}
|
|
230
|
-
return stream;
|
|
231
|
-
}
|
|
232
|
-
async reconnectToStream(_options) {
|
|
233
|
-
if (this._expectToolContinuation) {
|
|
234
|
-
this._expectToolContinuation = false;
|
|
235
|
-
return this._createToolContinuationStream();
|
|
236
|
-
}
|
|
237
|
-
const activeIds = this.activeRequestIds;
|
|
238
|
-
return new Promise((resolve) => {
|
|
239
|
-
let resolved = false;
|
|
240
|
-
let timeout;
|
|
241
|
-
const done = (value) => {
|
|
242
|
-
if (resolved) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
resolved = true;
|
|
246
|
-
this._resumeResolver = null;
|
|
247
|
-
this._resumeNoneResolver = null;
|
|
248
|
-
this._onStreamPending = null;
|
|
249
|
-
clearTimeout(timeout);
|
|
250
|
-
resolve(value);
|
|
251
|
-
};
|
|
252
|
-
this._resumeNoneResolver = () => done(null);
|
|
253
|
-
this._resumeResolver = (data) => {
|
|
254
|
-
const id = data.id;
|
|
255
|
-
activeIds.add(id);
|
|
256
|
-
this.socket.send(JSON.stringify({ type: "cf_agent_stream_resume_ack", id }));
|
|
257
|
-
done(this._createResumeStream(id));
|
|
258
|
-
};
|
|
259
|
-
this._onStreamPending = () => {
|
|
260
|
-
if (resolved) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
clearTimeout(timeout);
|
|
264
|
-
timeout = setTimeout(() => done(null), RESUME_PENDING_TIMEOUT_MS);
|
|
265
|
-
};
|
|
266
|
-
try {
|
|
267
|
-
this.socket.send(JSON.stringify({ type: "cf_agent_stream_resume_request" }));
|
|
268
|
-
} catch {
|
|
269
|
-
}
|
|
270
|
-
timeout = setTimeout(() => done(null), RESUME_PROBE_TIMEOUT_MS);
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Creates a deferred ReadableStream for client-side tool continuations.
|
|
275
|
-
* Returned immediately so AI SDK status becomes "submitted" right after tool output,
|
|
276
|
-
* then waits for the server to announce continuation via STREAM_RESUMING.
|
|
277
|
-
*/
|
|
278
|
-
_createToolContinuationStream() {
|
|
279
|
-
const socket = this.socket;
|
|
280
|
-
const activeIds = this.activeRequestIds;
|
|
281
|
-
const streamAbort = new AbortController();
|
|
282
|
-
const abortError = new Error("Aborted");
|
|
283
|
-
abortError.name = "AbortError";
|
|
284
|
-
let completed = false;
|
|
285
|
-
let requestId = null;
|
|
286
|
-
let readerController;
|
|
287
|
-
let onResumeRef = null;
|
|
288
|
-
let onResumeNoneRef = null;
|
|
289
|
-
const clearHandshakeResolvers = (resumeResolver, resumeNoneResolver) => {
|
|
290
|
-
if (resumeResolver === void 0 && resumeNoneResolver === void 0) {
|
|
291
|
-
this._resumeResolver = null;
|
|
292
|
-
this._resumeNoneResolver = null;
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (resumeResolver && this._resumeResolver === resumeResolver) {
|
|
296
|
-
this._resumeResolver = null;
|
|
297
|
-
}
|
|
298
|
-
if (resumeNoneResolver && this._resumeNoneResolver === resumeNoneResolver) {
|
|
299
|
-
this._resumeNoneResolver = null;
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
const finish = (action, resumeResolver, resumeNoneResolver, keepRequestId = false) => {
|
|
303
|
-
if (completed) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
completed = true;
|
|
307
|
-
this._abortToolContinuation = null;
|
|
308
|
-
this._onStreamPending = null;
|
|
309
|
-
clearHandshakeResolvers(resumeResolver, resumeNoneResolver);
|
|
310
|
-
try {
|
|
311
|
-
action();
|
|
312
|
-
} catch {
|
|
313
|
-
}
|
|
314
|
-
if (requestId && !keepRequestId) {
|
|
315
|
-
activeIds.delete(requestId);
|
|
316
|
-
}
|
|
317
|
-
streamAbort.abort();
|
|
318
|
-
};
|
|
319
|
-
this._abortToolContinuation = () => {
|
|
320
|
-
if (completed) {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
if (requestId === null) {
|
|
324
|
-
finish(() => readerController.error(abortError), onResumeRef, onResumeNoneRef);
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
socket.send(JSON.stringify({ type: "cf_agent_chat_request_cancel", id: requestId }));
|
|
329
|
-
} catch {
|
|
330
|
-
}
|
|
331
|
-
finish(() => readerController.error(abortError), onResumeRef, onResumeNoneRef, true);
|
|
332
|
-
return true;
|
|
333
|
-
};
|
|
334
|
-
return new ReadableStream({
|
|
335
|
-
start: (controller) => {
|
|
336
|
-
readerController = controller;
|
|
337
|
-
const onResumeNone = () => {
|
|
338
|
-
clearTimeout(timeout);
|
|
339
|
-
finish(() => controller.close(), onResume, onResumeNone);
|
|
340
|
-
};
|
|
341
|
-
const onResume = (data) => {
|
|
342
|
-
if (requestId) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
requestId = data.id;
|
|
346
|
-
activeIds.add(requestId);
|
|
347
|
-
clearHandshakeResolvers(onResume, onResumeNone);
|
|
348
|
-
this._onStreamPending = null;
|
|
349
|
-
clearTimeout(timeout);
|
|
350
|
-
socket.send(JSON.stringify({ type: "cf_agent_stream_resume_ack", id: requestId }));
|
|
351
|
-
};
|
|
352
|
-
onResumeRef = onResume;
|
|
353
|
-
onResumeNoneRef = onResumeNone;
|
|
354
|
-
let timeout = setTimeout(
|
|
355
|
-
() => finish(() => controller.close(), onResume, onResumeNone),
|
|
356
|
-
RESUME_PROBE_TIMEOUT_MS
|
|
357
|
-
);
|
|
358
|
-
this._onStreamPending = () => {
|
|
359
|
-
if (completed) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
clearTimeout(timeout);
|
|
363
|
-
timeout = setTimeout(
|
|
364
|
-
() => finish(() => controller.close(), onResume, onResumeNone),
|
|
365
|
-
RESUME_PENDING_TIMEOUT_MS
|
|
366
|
-
);
|
|
367
|
-
};
|
|
368
|
-
this._resumeResolver = onResume;
|
|
369
|
-
this._resumeNoneResolver = onResumeNone;
|
|
370
|
-
const onMessage = (event) => {
|
|
371
|
-
try {
|
|
372
|
-
const data = JSON.parse(String(event.data));
|
|
373
|
-
if (data.type !== "cf_agent_use_chat_response" || requestId == null || data.id !== requestId) {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
if (data.error) {
|
|
377
|
-
finish(() => controller.error(new Error(data.body || "Stream error")), onResume, onResumeNone);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (data.body?.trim()) {
|
|
381
|
-
try {
|
|
382
|
-
const chunk = JSON.parse(data.body);
|
|
383
|
-
controller.enqueue(chunk);
|
|
384
|
-
} catch {
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (data.done) {
|
|
388
|
-
finish(() => controller.close(), onResume, onResumeNone);
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
socket.addEventListener("message", onMessage, { signal: streamAbort.signal });
|
|
394
|
-
try {
|
|
395
|
-
socket.send(JSON.stringify({ type: "cf_agent_stream_resume_request" }));
|
|
396
|
-
} catch {
|
|
397
|
-
finish(() => controller.close());
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
cancel: () => {
|
|
401
|
-
if (requestId && this.cancelOnClientAbort) {
|
|
402
|
-
this.sendCancelFrame(requestId);
|
|
403
|
-
finish(() => {
|
|
404
|
-
}, onResumeRef, onResumeNoneRef, true);
|
|
405
|
-
} else {
|
|
406
|
-
finish(() => {
|
|
407
|
-
}, onResumeRef, onResumeNoneRef);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
/** Creates a ReadableStream that receives resumed stream chunks. */
|
|
413
|
-
_createResumeStream(requestId) {
|
|
414
|
-
const socket = this.socket;
|
|
415
|
-
const activeIds = this.activeRequestIds;
|
|
416
|
-
const chunkAbort = new AbortController();
|
|
417
|
-
const abortError = new Error("Aborted");
|
|
418
|
-
abortError.name = "AbortError";
|
|
419
|
-
let completed = false;
|
|
420
|
-
let streamController;
|
|
421
|
-
const finish = (action, keepId = false, clearServerTurn = true) => {
|
|
422
|
-
if (completed) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
completed = true;
|
|
426
|
-
if (clearServerTurn) {
|
|
427
|
-
this.clearActiveServerTurn(requestId);
|
|
428
|
-
}
|
|
429
|
-
try {
|
|
430
|
-
action();
|
|
431
|
-
} catch {
|
|
432
|
-
}
|
|
433
|
-
if (!keepId) {
|
|
434
|
-
activeIds.delete(requestId);
|
|
435
|
-
}
|
|
436
|
-
chunkAbort.abort();
|
|
437
|
-
};
|
|
438
|
-
const stream = new ReadableStream({
|
|
439
|
-
start(controller) {
|
|
440
|
-
streamController = controller;
|
|
441
|
-
const onMessage = (event) => {
|
|
442
|
-
try {
|
|
443
|
-
const data = JSON.parse(String(event.data));
|
|
444
|
-
if (data.type !== "cf_agent_use_chat_response") {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (data.id !== requestId) {
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (data.error) {
|
|
451
|
-
finish(() => controller.error(new Error(data.body || "Stream error")));
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (data.body?.trim()) {
|
|
455
|
-
try {
|
|
456
|
-
const chunk = JSON.parse(data.body);
|
|
457
|
-
controller.enqueue(chunk);
|
|
458
|
-
} catch {
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (data.done) {
|
|
462
|
-
finish(() => controller.close());
|
|
463
|
-
}
|
|
464
|
-
} catch {
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
socket.addEventListener("message", onMessage, { signal: chunkAbort.signal });
|
|
468
|
-
},
|
|
469
|
-
cancel: () => {
|
|
470
|
-
if (this.cancelOnClientAbort) {
|
|
471
|
-
this.sendCancelFrame(requestId);
|
|
472
|
-
finish(() => {
|
|
473
|
-
}, true);
|
|
474
|
-
} else {
|
|
475
|
-
finish(() => {
|
|
476
|
-
}, false, false);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
this.setActiveServerTurn(requestId, () => {
|
|
481
|
-
if (completed) {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
finish(() => streamController.error(abortError), true);
|
|
485
|
-
return true;
|
|
486
|
-
});
|
|
487
|
-
return stream;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
5
|
+
import { isToolUIPart, getToolName } from 'ai';
|
|
6
|
+
import { ref, watch, onScopeDispose } from 'vue';
|
|
490
7
|
|
|
491
8
|
function isRecord(value) {
|
|
492
9
|
return typeof value === "object" && value !== null;
|
|
493
10
|
}
|
|
494
|
-
function parseProtocolMessage(
|
|
495
|
-
const parsed = JSON.parse(data);
|
|
11
|
+
function parseProtocolMessage(parsed) {
|
|
496
12
|
if (!isRecord(parsed) || typeof parsed.type !== "string") {
|
|
497
13
|
return null;
|
|
498
14
|
}
|
|
@@ -508,6 +24,7 @@ function parseProtocolMessage(data) {
|
|
|
508
24
|
replayComplete: typeof parsed.replayComplete === "boolean" ? parsed.replayComplete : void 0,
|
|
509
25
|
continuation: typeof parsed.continuation === "boolean" ? parsed.continuation : void 0,
|
|
510
26
|
recovering: typeof parsed.recovering === "boolean" ? parsed.recovering : void 0,
|
|
27
|
+
probeId: typeof parsed.probeId === "string" ? parsed.probeId : void 0,
|
|
511
28
|
message,
|
|
512
29
|
messages
|
|
513
30
|
};
|
|
@@ -604,10 +121,31 @@ function preserveActiveStreamMessages(currentMessages, incomingMessages) {
|
|
|
604
121
|
return nextMessages;
|
|
605
122
|
}
|
|
606
123
|
}
|
|
607
|
-
return
|
|
124
|
+
return incomingMessages;
|
|
125
|
+
}
|
|
126
|
+
const CONTINUATION_BLOCKING_STATUSES = /* @__PURE__ */ new Set(["submitted", "streaming", "error"]);
|
|
127
|
+
function mergeById(onScreen, stored) {
|
|
128
|
+
const storedIds = new Set(stored.map((message) => message.id));
|
|
129
|
+
return [...stored, ...onScreen.filter((message) => !storedIds.has(message.id))];
|
|
130
|
+
}
|
|
131
|
+
function idsOf(messages) {
|
|
132
|
+
return messages.map((message) => message.id);
|
|
133
|
+
}
|
|
134
|
+
function startMessageIdOf(body) {
|
|
135
|
+
try {
|
|
136
|
+
const chunk = JSON.parse(body);
|
|
137
|
+
return isRecord(chunk) && chunk.type === "start" && typeof chunk.messageId === "string" ? chunk.messageId : null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
608
141
|
}
|
|
609
142
|
function createMessageId() {
|
|
610
|
-
|
|
143
|
+
if (globalThis.crypto?.randomUUID) {
|
|
144
|
+
return globalThis.crypto.randomUUID();
|
|
145
|
+
}
|
|
146
|
+
const bytes = new Uint8Array(16);
|
|
147
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
148
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
611
149
|
}
|
|
612
150
|
function useAgentChat(options) {
|
|
613
151
|
const autoContinueAfterToolResult = options.autoContinueAfterToolResult ?? true;
|
|
@@ -615,19 +153,32 @@ function useAgentChat(options) {
|
|
|
615
153
|
const fallbackAckedResumeRequestIds = /* @__PURE__ */ new Set();
|
|
616
154
|
let streamState = { status: "idle" };
|
|
617
155
|
let isResumingToolContinuation = false;
|
|
156
|
+
let pendingToolContinuation = false;
|
|
157
|
+
let continuationLaunchTimer = null;
|
|
158
|
+
let observedToolContinuationRequestId = null;
|
|
159
|
+
const dispatchedToolCallIds = /* @__PURE__ */ new Set();
|
|
160
|
+
let heldMessageIds = idsOf(options.initialMessages ?? []);
|
|
161
|
+
const streamMessageIds = /* @__PURE__ */ new Map();
|
|
618
162
|
let resumeInFlight = false;
|
|
619
163
|
let resumeGeneration = 0;
|
|
620
164
|
const isRecovering = ref(false);
|
|
621
165
|
const connectionError = ref(null);
|
|
166
|
+
const connectionState = ref("connected");
|
|
167
|
+
let resyncPending = false;
|
|
168
|
+
const isServerStreaming = ref(false);
|
|
622
169
|
const socket = new AgentClient({
|
|
623
170
|
...options.client,
|
|
624
171
|
onConnectionError: (error) => {
|
|
625
172
|
connectionError.value = error;
|
|
626
173
|
options.client.onConnectionError?.(error);
|
|
174
|
+
},
|
|
175
|
+
onStateUpdate: (state, source) => {
|
|
176
|
+
options.onStateUpdate?.(state, source);
|
|
177
|
+
options.client.onStateUpdate?.(state, source);
|
|
627
178
|
}
|
|
628
179
|
});
|
|
629
180
|
const transport = new WebSocketChatTransport({
|
|
630
|
-
socket,
|
|
181
|
+
agent: socket,
|
|
631
182
|
activeRequestIds,
|
|
632
183
|
cancelOnClientAbort: options.cancelOnClientAbort ?? false,
|
|
633
184
|
prepareBody: async () => {
|
|
@@ -637,7 +188,7 @@ function useAgentChat(options) {
|
|
|
637
188
|
return clientTools ? { ...extraBody, clientTools } : extraBody;
|
|
638
189
|
}
|
|
639
190
|
});
|
|
640
|
-
|
|
191
|
+
async function dispatchToolCall(toolCall) {
|
|
641
192
|
const clientTool = options.tools?.[toolCall.toolName];
|
|
642
193
|
const addClientToolOutput = (toolOutput) => addToolOutputForTool(toolCall.toolName, toolOutput);
|
|
643
194
|
if (options.onToolCall) {
|
|
@@ -657,23 +208,110 @@ function useAgentChat(options) {
|
|
|
657
208
|
errorText: getErrorText(error)
|
|
658
209
|
});
|
|
659
210
|
}
|
|
660
|
-
}
|
|
211
|
+
}
|
|
661
212
|
const chat = new Chat({
|
|
662
213
|
messages: options.initialMessages,
|
|
663
|
-
transport
|
|
664
|
-
|
|
214
|
+
transport: options.sendOnlyNewMessages ? {
|
|
215
|
+
sendMessages: (request) => transport.sendMessages(onlyNewMessages(request)),
|
|
216
|
+
reconnectToStream: (request) => transport.reconnectToStream(request)
|
|
217
|
+
} : transport,
|
|
665
218
|
onError: options.onError
|
|
666
219
|
});
|
|
667
|
-
function
|
|
668
|
-
if (
|
|
220
|
+
function onlyNewMessages(request) {
|
|
221
|
+
if (request.trigger === "regenerate-message") {
|
|
222
|
+
return request;
|
|
223
|
+
}
|
|
224
|
+
const { messages } = request;
|
|
225
|
+
let held = 0;
|
|
226
|
+
while (held < messages.length && messages[held]?.id === heldMessageIds[held]) {
|
|
227
|
+
held++;
|
|
228
|
+
}
|
|
229
|
+
const rewrittenIndex = messages.findIndex((message) => message.id === request.messageId);
|
|
230
|
+
const start = rewrittenIndex >= 0 ? Math.min(held, rewrittenIndex) : held;
|
|
231
|
+
if (rewrittenIndex >= 0) {
|
|
232
|
+
heldMessageIds = heldMessageIds.slice(0, start);
|
|
233
|
+
}
|
|
234
|
+
const afterMessageId = messages[start - 1]?.id;
|
|
235
|
+
if (afterMessageId === void 0 || start === messages.length) {
|
|
236
|
+
return request;
|
|
237
|
+
}
|
|
238
|
+
return { ...request, messages: messages.slice(start), body: { ...request.body, afterMessageId } };
|
|
239
|
+
}
|
|
240
|
+
function holdThrough(messageId) {
|
|
241
|
+
const index = chat.messages.findIndex((message) => message.id === messageId);
|
|
242
|
+
if (index >= 0) {
|
|
243
|
+
heldMessageIds = idsOf(chat.messages.slice(0, index + 1));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function trackStreamMessage(requestId, frame) {
|
|
247
|
+
if (frame.done || frame.error) {
|
|
248
|
+
const messageId = streamMessageIds.get(requestId);
|
|
249
|
+
streamMessageIds.delete(requestId);
|
|
250
|
+
const reply = chat.messages.find((message) => message.id === messageId);
|
|
251
|
+
if (reply && reply.parts.length > 0 && !frame.error) {
|
|
252
|
+
holdThrough(reply.id);
|
|
253
|
+
}
|
|
254
|
+
} else if (!streamMessageIds.has(requestId) && frame.body?.trim()) {
|
|
255
|
+
streamMessageIds.set(requestId, startMessageIdOf(frame.body));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function dispatchRenderedToolCalls() {
|
|
259
|
+
if (chat.status === "streaming" || chat.status === "submitted" || isServerStreaming.value || isRecovering.value) {
|
|
669
260
|
return;
|
|
670
261
|
}
|
|
262
|
+
const lastMessage = chat.messages.at(-1);
|
|
263
|
+
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const part of lastMessage.parts) {
|
|
267
|
+
if (!isToolUIPart(part) || part.state !== "input-available" || dispatchedToolCallIds.has(part.toolCallId)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
dispatchedToolCallIds.add(part.toolCallId);
|
|
271
|
+
void dispatchToolCall({ toolCallId: part.toolCallId, toolName: getToolName(part), input: part.input });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function armToolContinuation() {
|
|
275
|
+
if (!autoContinueAfterToolResult || isResumingToolContinuation) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
671
278
|
isResumingToolContinuation = true;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
isResumingToolContinuation = false;
|
|
675
|
-
});
|
|
279
|
+
pendingToolContinuation = true;
|
|
280
|
+
return true;
|
|
676
281
|
}
|
|
282
|
+
function scheduleToolContinuationLaunch() {
|
|
283
|
+
if (!pendingToolContinuation || continuationLaunchTimer !== null || CONTINUATION_BLOCKING_STATUSES.has(chat.status)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
continuationLaunchTimer = setTimeout(() => {
|
|
287
|
+
continuationLaunchTimer = null;
|
|
288
|
+
if (!pendingToolContinuation || CONTINUATION_BLOCKING_STATUSES.has(chat.status)) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
pendingToolContinuation = false;
|
|
292
|
+
transport.expectToolContinuation();
|
|
293
|
+
void chat.resumeStream().finally(() => {
|
|
294
|
+
isResumingToolContinuation = false;
|
|
295
|
+
});
|
|
296
|
+
}, 0);
|
|
297
|
+
}
|
|
298
|
+
function resetToolContinuation() {
|
|
299
|
+
pendingToolContinuation = false;
|
|
300
|
+
isResumingToolContinuation = false;
|
|
301
|
+
observedToolContinuationRequestId = null;
|
|
302
|
+
if (continuationLaunchTimer !== null) {
|
|
303
|
+
clearTimeout(continuationLaunchTimer);
|
|
304
|
+
continuationLaunchTimer = null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
watch([() => chat.messages, () => chat.status, isServerStreaming, isRecovering], () => {
|
|
308
|
+
if (chat.status === "error" && pendingToolContinuation) {
|
|
309
|
+
resetToolContinuation();
|
|
310
|
+
} else {
|
|
311
|
+
scheduleToolContinuationLaunch();
|
|
312
|
+
}
|
|
313
|
+
dispatchRenderedToolCalls();
|
|
314
|
+
});
|
|
677
315
|
async function stop() {
|
|
678
316
|
try {
|
|
679
317
|
transport.cancelActiveServerTurn();
|
|
@@ -684,15 +322,18 @@ function useAgentChat(options) {
|
|
|
684
322
|
}
|
|
685
323
|
function clearHistory() {
|
|
686
324
|
void stop();
|
|
687
|
-
|
|
325
|
+
resetToolContinuation();
|
|
688
326
|
isRecovering.value = false;
|
|
327
|
+
isServerStreaming.value = false;
|
|
689
328
|
streamState = broadcastTransition(streamState, { type: "clear" }).state;
|
|
690
329
|
chat.messages = [];
|
|
330
|
+
heldMessageIds = [];
|
|
691
331
|
socket.send(JSON.stringify({ type: "cf_agent_chat_clear" }));
|
|
692
332
|
}
|
|
693
333
|
async function addToolOutputForTool(toolName, toolOutput) {
|
|
694
334
|
const shouldAutoContinue = toolOutput.state === "output-error" ? false : autoContinueAfterToolResult;
|
|
695
335
|
const clientTools = getClientToolSchemas(options.tools);
|
|
336
|
+
const continuing = shouldAutoContinue && armToolContinuation();
|
|
696
337
|
socket.send(
|
|
697
338
|
JSON.stringify({
|
|
698
339
|
type: "cf_agent_tool_result",
|
|
@@ -719,8 +360,8 @@ function useAgentChat(options) {
|
|
|
719
360
|
output: toolOutput.output
|
|
720
361
|
});
|
|
721
362
|
}
|
|
722
|
-
if (
|
|
723
|
-
|
|
363
|
+
if (continuing) {
|
|
364
|
+
scheduleToolContinuationLaunch();
|
|
724
365
|
}
|
|
725
366
|
}
|
|
726
367
|
async function addToolOutput(...args) {
|
|
@@ -734,6 +375,7 @@ function useAgentChat(options) {
|
|
|
734
375
|
console.warn(`[agents-vue] Could not find tool call for approval "${approvalResponse.id}".`);
|
|
735
376
|
return;
|
|
736
377
|
}
|
|
378
|
+
const continuing = armToolContinuation();
|
|
737
379
|
socket.send(
|
|
738
380
|
JSON.stringify({
|
|
739
381
|
type: "cf_agent_tool_approval",
|
|
@@ -743,11 +385,17 @@ function useAgentChat(options) {
|
|
|
743
385
|
})
|
|
744
386
|
);
|
|
745
387
|
await chat.addToolApprovalResponse(...args);
|
|
746
|
-
|
|
388
|
+
if (continuing) {
|
|
389
|
+
scheduleToolContinuationLaunch();
|
|
390
|
+
}
|
|
747
391
|
}
|
|
748
392
|
socket.addEventListener("message", (event) => {
|
|
749
393
|
try {
|
|
750
|
-
const
|
|
394
|
+
const raw = JSON.parse(String(event.data));
|
|
395
|
+
if (options.onFrame?.(raw) === true) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const data = parseProtocolMessage(raw);
|
|
751
399
|
if (!data) {
|
|
752
400
|
return;
|
|
753
401
|
}
|
|
@@ -755,36 +403,57 @@ function useAgentChat(options) {
|
|
|
755
403
|
// Full message list sync (sent on connect, and after every appendMessage / turn).
|
|
756
404
|
case "cf_agent_chat_messages":
|
|
757
405
|
chat.messages = chat.status === "streaming" || chat.status === "submitted" || streamState.status !== "idle" ? preserveActiveStreamMessages(chat.messages, data.messages ?? []) : data.messages ?? [];
|
|
406
|
+
heldMessageIds = idsOf(data.messages ?? []);
|
|
758
407
|
break;
|
|
759
408
|
// Single message update (e.g. tool result applied)
|
|
760
409
|
case "cf_agent_message_updated":
|
|
761
410
|
if (data.message) {
|
|
762
411
|
chat.messages = replaceUpdatedMessage(chat.messages, data.message);
|
|
412
|
+
holdThrough(data.message.id);
|
|
763
413
|
}
|
|
764
414
|
break;
|
|
765
415
|
// Clear all messages
|
|
766
416
|
case "cf_agent_chat_clear":
|
|
767
417
|
streamState = broadcastTransition(streamState, { type: "clear" }).state;
|
|
768
418
|
chat.messages = [];
|
|
419
|
+
heldMessageIds = [];
|
|
769
420
|
isRecovering.value = false;
|
|
421
|
+
isServerStreaming.value = false;
|
|
770
422
|
break;
|
|
771
423
|
// Forward resume handshake to transport
|
|
772
|
-
case "cf_agent_stream_resuming":
|
|
773
|
-
|
|
774
|
-
|
|
424
|
+
case "cf_agent_stream_resuming": {
|
|
425
|
+
const isEarlyToolContinuation = isResumingToolContinuation && !transport.isAwaitingResume();
|
|
426
|
+
if (data.id && !transport.handleStreamResuming({ id: data.id }) && !activeRequestIds.has(data.id) && !fallbackAckedResumeRequestIds.has(data.id)) {
|
|
427
|
+
if (isEarlyToolContinuation) {
|
|
428
|
+
pendingToolContinuation = false;
|
|
429
|
+
observedToolContinuationRequestId = data.id;
|
|
430
|
+
if (continuationLaunchTimer !== null) {
|
|
431
|
+
clearTimeout(continuationLaunchTimer);
|
|
432
|
+
continuationLaunchTimer = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const resumed = broadcastTransition(streamState, {
|
|
775
436
|
type: "resume-fallback",
|
|
776
437
|
streamId: data.id,
|
|
777
438
|
messageId: createMessageId()
|
|
778
|
-
})
|
|
439
|
+
});
|
|
440
|
+
streamState = resumed.state;
|
|
441
|
+
isServerStreaming.value = resumed.isStreaming;
|
|
779
442
|
fallbackAckedResumeRequestIds.add(data.id);
|
|
780
443
|
transport.observeServerTurn(data.id);
|
|
781
444
|
isRecovering.value = false;
|
|
782
445
|
socket.send(JSON.stringify({ type: "cf_agent_stream_resume_ack", id: data.id }));
|
|
783
446
|
}
|
|
784
447
|
break;
|
|
785
|
-
|
|
786
|
-
|
|
448
|
+
}
|
|
449
|
+
// 0.23 carries the probe id, so a NONE answering an older probe cannot settle this one.
|
|
450
|
+
case "cf_agent_stream_resume_none": {
|
|
451
|
+
const settled = transport.handleStreamResumeNone({ probeId: data.probeId });
|
|
452
|
+
if (settled && observedToolContinuationRequestId !== null) {
|
|
453
|
+
resetToolContinuation();
|
|
454
|
+
}
|
|
787
455
|
break;
|
|
456
|
+
}
|
|
788
457
|
// Pre-stream window (0.9.0): keep the in-flight resume probe waiting.
|
|
789
458
|
case "cf_agent_stream_pending":
|
|
790
459
|
transport.handleStreamPending();
|
|
@@ -794,10 +463,16 @@ function useAgentChat(options) {
|
|
|
794
463
|
isRecovering.value = Boolean(data.recovering);
|
|
795
464
|
break;
|
|
796
465
|
case "cf_agent_use_chat_response":
|
|
466
|
+
if (data.id) {
|
|
467
|
+
trackStreamMessage(data.id, data);
|
|
468
|
+
}
|
|
797
469
|
if (!data.id || activeRequestIds.has(data.id)) {
|
|
798
470
|
return;
|
|
799
471
|
}
|
|
800
472
|
{
|
|
473
|
+
if (!data.done && !data.error && transport.activeServerTurnId !== data.id) {
|
|
474
|
+
transport.observeServerTurn(data.id);
|
|
475
|
+
}
|
|
801
476
|
const chunkData = data.body?.trim() ? JSON.parse(data.body) : void 0;
|
|
802
477
|
const result = broadcastTransition(streamState, {
|
|
803
478
|
type: "response",
|
|
@@ -812,12 +487,16 @@ function useAgentChat(options) {
|
|
|
812
487
|
currentMessages: data.continuation ? chat.messages : void 0
|
|
813
488
|
});
|
|
814
489
|
streamState = result.state;
|
|
490
|
+
isServerStreaming.value = result.isStreaming;
|
|
815
491
|
if (result.messagesUpdate) {
|
|
816
492
|
chat.messages = result.messagesUpdate(chat.messages);
|
|
817
493
|
}
|
|
818
|
-
if (data.done) {
|
|
494
|
+
if (data.done || data.error) {
|
|
819
495
|
isRecovering.value = false;
|
|
820
496
|
transport.handleServerTurnCompleted(data.id);
|
|
497
|
+
if (observedToolContinuationRequestId === data.id) {
|
|
498
|
+
resetToolContinuation();
|
|
499
|
+
}
|
|
821
500
|
}
|
|
822
501
|
}
|
|
823
502
|
break;
|
|
@@ -827,8 +506,18 @@ function useAgentChat(options) {
|
|
|
827
506
|
});
|
|
828
507
|
socket.addEventListener("open", (event) => {
|
|
829
508
|
connectionError.value = null;
|
|
509
|
+
connectionState.value = "connected";
|
|
830
510
|
options.onOpen?.(event);
|
|
831
|
-
|
|
511
|
+
void seedInitialMessages();
|
|
512
|
+
if (resyncPending) {
|
|
513
|
+
resyncPending = false;
|
|
514
|
+
void resyncMessages();
|
|
515
|
+
}
|
|
516
|
+
if (transport.retryPendingResume()) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const canReconcileObservedContinuation = observedToolContinuationRequestId !== null;
|
|
520
|
+
if (options.resume !== false && !resumeInFlight && (!isResumingToolContinuation || canReconcileObservedContinuation) && !transport.isAwaitingResume()) {
|
|
832
521
|
resumeInFlight = true;
|
|
833
522
|
const myGeneration = resumeGeneration;
|
|
834
523
|
void chat.resumeStream().catch(() => {
|
|
@@ -842,7 +531,10 @@ function useAgentChat(options) {
|
|
|
842
531
|
});
|
|
843
532
|
socket.addEventListener("close", (event) => {
|
|
844
533
|
fallbackAckedResumeRequestIds.clear();
|
|
534
|
+
streamMessageIds.clear();
|
|
845
535
|
isRecovering.value = false;
|
|
536
|
+
connectionState.value = "reconnecting";
|
|
537
|
+
resyncPending = true;
|
|
846
538
|
if (options.onClose && event instanceof CloseEvent) {
|
|
847
539
|
options.onClose(event);
|
|
848
540
|
}
|
|
@@ -852,23 +544,75 @@ function useAgentChat(options) {
|
|
|
852
544
|
});
|
|
853
545
|
onScopeDispose(() => {
|
|
854
546
|
fallbackAckedResumeRequestIds.clear();
|
|
547
|
+
resetToolContinuation();
|
|
855
548
|
isRecovering.value = false;
|
|
856
549
|
resumeGeneration++;
|
|
857
550
|
resumeInFlight = false;
|
|
858
551
|
socket.close();
|
|
859
552
|
});
|
|
553
|
+
function setAgent(next) {
|
|
554
|
+
transport.setAgent(next);
|
|
555
|
+
}
|
|
556
|
+
const initialMessagesLoaded = ref(false);
|
|
557
|
+
let initialMessagesSeedStarted = options.initialMessages !== void 0;
|
|
558
|
+
initialMessagesLoaded.value = initialMessagesSeedStarted;
|
|
559
|
+
async function fetchStoredMessages() {
|
|
560
|
+
try {
|
|
561
|
+
const target = new URL(socket.url);
|
|
562
|
+
target.protocol = target.protocol === "wss:" ? "https:" : "http:";
|
|
563
|
+
target.searchParams.delete("_pk");
|
|
564
|
+
target.pathname += "/get-messages";
|
|
565
|
+
const response = await fetch(target.toString(), { credentials: "include" });
|
|
566
|
+
if (!response.ok) {
|
|
567
|
+
console.warn(`[agents-vue] get-messages failed: ${response.status} ${response.statusText}`);
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
const text = await response.text();
|
|
571
|
+
return text.trim() ? JSON.parse(text) : null;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.warn("[agents-vue] get-messages errored", error);
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function seedInitialMessages() {
|
|
578
|
+
if (initialMessagesSeedStarted) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
initialMessagesSeedStarted = true;
|
|
582
|
+
try {
|
|
583
|
+
const stored = await fetchStoredMessages();
|
|
584
|
+
if (stored && chat.messages.length === 0) {
|
|
585
|
+
chat.messages = stored;
|
|
586
|
+
heldMessageIds = idsOf(stored);
|
|
587
|
+
}
|
|
588
|
+
} finally {
|
|
589
|
+
initialMessagesLoaded.value = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function resyncMessages() {
|
|
593
|
+
const stored = await fetchStoredMessages();
|
|
594
|
+
if (stored) {
|
|
595
|
+
chat.messages = (options.mergeMessages ?? mergeById)(chat.messages, stored);
|
|
596
|
+
heldMessageIds = idsOf(stored);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
860
599
|
return {
|
|
861
600
|
addToolApprovalResponse,
|
|
862
601
|
addToolOutput,
|
|
863
602
|
clearHistory,
|
|
864
603
|
chat,
|
|
865
604
|
socket,
|
|
605
|
+
setAgent,
|
|
866
606
|
stop,
|
|
607
|
+
/** Reactive: true once the `get-messages` seed has settled (or was skipped). */
|
|
608
|
+
initialMessagesLoaded,
|
|
867
609
|
/** Reactive: true while the server is recovering a durable chat turn. */
|
|
868
610
|
isRecovering,
|
|
869
611
|
/** Reactive: terminal WebSocket connection failure, or null. */
|
|
870
|
-
connectionError
|
|
612
|
+
connectionError,
|
|
613
|
+
/** Reactive: whether the socket is up, for the header's "Reconnecting…". */
|
|
614
|
+
connectionState
|
|
871
615
|
};
|
|
872
616
|
}
|
|
873
617
|
|
|
874
|
-
export {
|
|
618
|
+
export { useAgentChat };
|