@dudousxd/nestjs-agent-react 0.26.0 → 0.27.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 +38 -1
- package/dist/{client-D4ZSbZJ8.d.cts → client-DNM5Qm5N.d.cts} +70 -2
- package/dist/{client-D4ZSbZJ8.d.ts → client-DNM5Qm5N.d.ts} +70 -2
- package/dist/{generative-ui-BejHxxbR.d.cts → generative-ui-jT9TYfCi.d.cts} +1 -1
- package/dist/{generative-ui-BejHxxbR.d.ts → generative-ui-jT9TYfCi.d.ts} +1 -1
- package/dist/genui-json-render.d.cts +1 -1
- package/dist/genui-json-render.d.ts +1 -1
- package/dist/genui.d.cts +2 -2
- package/dist/genui.d.ts +2 -2
- package/dist/index.cjs +553 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -9
- package/dist/index.d.ts +127 -9
- package/dist/index.js +553 -26
- package/dist/index.js.map +1 -1
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -214,12 +214,48 @@ var AgentClient = class {
|
|
|
214
214
|
signal: request.signal
|
|
215
215
|
} : {}
|
|
216
216
|
});
|
|
217
|
+
if (response.status === 202) {
|
|
218
|
+
return {
|
|
219
|
+
body: emptyStream(),
|
|
220
|
+
queued: await response.json()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
217
223
|
if (!response.ok || !response.body) {
|
|
218
224
|
throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
|
|
219
225
|
}
|
|
220
226
|
return streamResponse(response);
|
|
221
227
|
}
|
|
222
228
|
/**
|
|
229
|
+
* `POST <path>/chat` for a message that should wait in the thread's queue — the body carries
|
|
230
|
+
* `mode: 'queue'` (or `'interrupt'`), which the server always answers with `202` JSON.
|
|
231
|
+
*/
|
|
232
|
+
enqueueMessage(request) {
|
|
233
|
+
return this.request("POST", "/chat", {
|
|
234
|
+
mode: "queue",
|
|
235
|
+
...request.body
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** `GET <path>/threads/:id/queue` — the thread's waiting messages, and whether it drains. */
|
|
239
|
+
getQueue(threadId) {
|
|
240
|
+
return this.request("GET", `/threads/${encodeURIComponent(threadId)}/queue`);
|
|
241
|
+
}
|
|
242
|
+
/** `PATCH <path>/queue/:messageId` — change a waiting message's text/attachments, or move it. */
|
|
243
|
+
updateQueuedMessage(messageId, update) {
|
|
244
|
+
return this.request("PATCH", `/queue/${encodeURIComponent(messageId)}`, update);
|
|
245
|
+
}
|
|
246
|
+
/** `DELETE <path>/queue/:messageId`. */
|
|
247
|
+
removeQueuedMessage(messageId) {
|
|
248
|
+
return this.request("DELETE", `/queue/${encodeURIComponent(messageId)}`);
|
|
249
|
+
}
|
|
250
|
+
/** `DELETE <path>/threads/:id/queue` — drop every waiting message. */
|
|
251
|
+
clearQueue(threadId) {
|
|
252
|
+
return this.request("DELETE", `/threads/${encodeURIComponent(threadId)}/queue`);
|
|
253
|
+
}
|
|
254
|
+
/** `POST <path>/threads/:id/queue/resume` — lift a pause; `runId` when the head started. */
|
|
255
|
+
resumeQueue(threadId) {
|
|
256
|
+
return this.request("POST", `/threads/${encodeURIComponent(threadId)}/queue/resume`);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
223
259
|
* `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
|
|
224
260
|
* streaming under that id (404).
|
|
225
261
|
*/
|
|
@@ -485,6 +521,14 @@ var AgentClient = class {
|
|
|
485
521
|
return JSON.parse(text);
|
|
486
522
|
}
|
|
487
523
|
};
|
|
524
|
+
function emptyStream() {
|
|
525
|
+
return new ReadableStream({
|
|
526
|
+
start(controller) {
|
|
527
|
+
controller.close();
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
__name(emptyStream, "emptyStream");
|
|
488
532
|
function streamResponse(response) {
|
|
489
533
|
const body = response.body;
|
|
490
534
|
const runId = response.headers?.get(HEADER_RUN_ID) ?? void 0;
|
|
@@ -647,6 +691,10 @@ var AgentChatTransport = class {
|
|
|
647
691
|
signal: options.abortSignal
|
|
648
692
|
} : {}
|
|
649
693
|
});
|
|
694
|
+
if (response.queued !== void 0) {
|
|
695
|
+
this.attemptLive = false;
|
|
696
|
+
return queuedChunkStream(response.queued);
|
|
697
|
+
}
|
|
650
698
|
this.captureHeaderMeta(response);
|
|
651
699
|
return this.toChunkStream(response, {
|
|
652
700
|
...headers !== void 0 ? {
|
|
@@ -677,6 +725,7 @@ var AgentChatTransport = class {
|
|
|
677
725
|
});
|
|
678
726
|
if (response === null) {
|
|
679
727
|
this.attemptLive = false;
|
|
728
|
+
this.options.onResumeGone?.(runId);
|
|
680
729
|
return null;
|
|
681
730
|
}
|
|
682
731
|
this.captureHeaderMeta(response);
|
|
@@ -1054,6 +1103,9 @@ var AgentChatTransport = class {
|
|
|
1054
1103
|
break;
|
|
1055
1104
|
case "title":
|
|
1056
1105
|
case "cancelled":
|
|
1106
|
+
// The thread's queue changed, or this run handed the thread to the next queued message
|
|
1107
|
+
// (`started`). Thread-level, like the title.
|
|
1108
|
+
case "queue":
|
|
1057
1109
|
forwardAsData(event, true);
|
|
1058
1110
|
break;
|
|
1059
1111
|
default:
|
|
@@ -1156,6 +1208,22 @@ var AgentChatTransport = class {
|
|
|
1156
1208
|
});
|
|
1157
1209
|
}
|
|
1158
1210
|
};
|
|
1211
|
+
function queuedChunkStream(queued) {
|
|
1212
|
+
return new ReadableStream({
|
|
1213
|
+
start(controller) {
|
|
1214
|
+
controller.enqueue({
|
|
1215
|
+
type: "data-queue",
|
|
1216
|
+
data: {
|
|
1217
|
+
queue: queued.queue,
|
|
1218
|
+
queuedSend: queued
|
|
1219
|
+
},
|
|
1220
|
+
transient: true
|
|
1221
|
+
});
|
|
1222
|
+
controller.close();
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
__name(queuedChunkStream, "queuedChunkStream");
|
|
1159
1227
|
function parseSseFrame(raw) {
|
|
1160
1228
|
let event;
|
|
1161
1229
|
let id;
|
|
@@ -3870,6 +3938,7 @@ function useChatTranscript(options) {
|
|
|
3870
3938
|
} : {}
|
|
3871
3939
|
});
|
|
3872
3940
|
const showTypingIndicator = isBusy && lastMessage?.role !== "assistant";
|
|
3941
|
+
const queued = useQueuedItems(options.queue ?? null, options.readOnly === true);
|
|
3873
3942
|
return {
|
|
3874
3943
|
items,
|
|
3875
3944
|
status,
|
|
@@ -3892,10 +3961,53 @@ function useChatTranscript(options) {
|
|
|
3892
3961
|
available: isBusy && options.readOnly !== true && options.onStop !== void 0,
|
|
3893
3962
|
isStopping: stopRequested,
|
|
3894
3963
|
stop
|
|
3895
|
-
}
|
|
3964
|
+
},
|
|
3965
|
+
queued,
|
|
3966
|
+
queuePaused: options.queue?.paused ?? null
|
|
3896
3967
|
};
|
|
3897
3968
|
}
|
|
3898
3969
|
__name(useChatTranscript, "useChatTranscript");
|
|
3970
|
+
function useQueuedItems(queue, readOnly) {
|
|
3971
|
+
const latestQueue = (0, import_react17.useRef)(queue);
|
|
3972
|
+
latestQueue.current = queue;
|
|
3973
|
+
const removers = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
|
|
3974
|
+
const removerFor = (0, import_react17.useCallback)((id) => {
|
|
3975
|
+
let remover = removers.current.get(id);
|
|
3976
|
+
if (remover === void 0) {
|
|
3977
|
+
remover = /* @__PURE__ */ __name(() => {
|
|
3978
|
+
void latestQueue.current?.remove?.(id);
|
|
3979
|
+
}, "remover");
|
|
3980
|
+
removers.current.set(id, remover);
|
|
3981
|
+
}
|
|
3982
|
+
return remover;
|
|
3983
|
+
}, []);
|
|
3984
|
+
if (queue === null || queue === void 0) {
|
|
3985
|
+
return [];
|
|
3986
|
+
}
|
|
3987
|
+
return queue.items.map((message, position) => {
|
|
3988
|
+
const state = message.state === "sending" ? "sending" : queue.paused !== null ? "paused" : "queued";
|
|
3989
|
+
return {
|
|
3990
|
+
id: message.id,
|
|
3991
|
+
role: "user",
|
|
3992
|
+
text: message.text,
|
|
3993
|
+
files: message.attachments.map((attachment) => ({
|
|
3994
|
+
url: attachment.url,
|
|
3995
|
+
mediaType: attachment.contentType,
|
|
3996
|
+
filename: attachment.name,
|
|
3997
|
+
isImage: attachment.contentType.startsWith("image/")
|
|
3998
|
+
})),
|
|
3999
|
+
state,
|
|
4000
|
+
position,
|
|
4001
|
+
isNext: position === 0,
|
|
4002
|
+
interrupt: message.interrupt,
|
|
4003
|
+
remove: {
|
|
4004
|
+
available: !readOnly && state !== "sending" && queue.remove !== void 0,
|
|
4005
|
+
run: removerFor(message.id)
|
|
4006
|
+
}
|
|
4007
|
+
};
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
__name(useQueuedItems, "useQueuedItems");
|
|
3899
4011
|
function useTranscriptItem(options) {
|
|
3900
4012
|
const { message, isStreaming = false } = options;
|
|
3901
4013
|
const messagesRef = (0, import_react17.useRef)([
|
|
@@ -4514,7 +4626,7 @@ function joinClasses(...entries) {
|
|
|
4514
4626
|
__name(joinClasses, "joinClasses");
|
|
4515
4627
|
|
|
4516
4628
|
// src/components/message-list.tsx
|
|
4517
|
-
function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, readOnly, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
|
|
4629
|
+
function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, readOnly, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, queue, classNames }) {
|
|
4518
4630
|
const transcript = useChatTranscript({
|
|
4519
4631
|
messages,
|
|
4520
4632
|
status,
|
|
@@ -4544,9 +4656,16 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
|
|
|
4544
4656
|
} : {},
|
|
4545
4657
|
...followUps !== void 0 ? {
|
|
4546
4658
|
followUps
|
|
4659
|
+
} : {},
|
|
4660
|
+
...queue !== void 0 ? {
|
|
4661
|
+
queue: {
|
|
4662
|
+
items: queue.items,
|
|
4663
|
+
paused: queue.paused,
|
|
4664
|
+
remove: queue.remove
|
|
4665
|
+
}
|
|
4547
4666
|
} : {}
|
|
4548
4667
|
});
|
|
4549
|
-
if (transcript.showEmptyState) {
|
|
4668
|
+
if (transcript.showEmptyState && transcript.queued.length === 0) {
|
|
4550
4669
|
return /* @__PURE__ */ React.createElement("div", {
|
|
4551
4670
|
className: classNames?.empty
|
|
4552
4671
|
}, emptyState);
|
|
@@ -4590,7 +4709,23 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
|
|
|
4590
4709
|
...getMeta ? {
|
|
4591
4710
|
meta: getMeta(item.message)
|
|
4592
4711
|
} : {}
|
|
4593
|
-
})), transcript.
|
|
4712
|
+
})), transcript.queued.length > 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
4713
|
+
className: classNames?.queue
|
|
4714
|
+
}, transcript.queued.map((item) => /* @__PURE__ */ React.createElement("div", {
|
|
4715
|
+
key: item.id,
|
|
4716
|
+
className: classNames?.queuedItem,
|
|
4717
|
+
"data-state": item.state
|
|
4718
|
+
}, /* @__PURE__ */ React.createElement("span", null, item.text), item.remove.available ? /* @__PURE__ */ React.createElement("button", {
|
|
4719
|
+
type: "button",
|
|
4720
|
+
className: classNames?.queuedRemove,
|
|
4721
|
+
onClick: item.remove.run
|
|
4722
|
+
}, "Remove") : null)), transcript.queuePaused !== null && queue !== void 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
4723
|
+
className: classNames?.queuePaused
|
|
4724
|
+
}, /* @__PURE__ */ React.createElement("span", null, "Queue paused"), /* @__PURE__ */ React.createElement("button", {
|
|
4725
|
+
type: "button",
|
|
4726
|
+
className: classNames?.queueResume,
|
|
4727
|
+
onClick: /* @__PURE__ */ __name(() => void queue.resume(), "onClick")
|
|
4728
|
+
}, "Resume")) : null) : null, transcript.showTypingIndicator ? /* @__PURE__ */ React.createElement("div", {
|
|
4594
4729
|
className: classNames?.typing
|
|
4595
4730
|
}, typingIndicator ?? "Thinking\u2026") : null, error && onRetry ? /* @__PURE__ */ React.createElement("div", {
|
|
4596
4731
|
className: classNames?.error
|
|
@@ -5359,6 +5494,77 @@ __name(sumUsage, "sumUsage");
|
|
|
5359
5494
|
// src/use-agent-chat.ts
|
|
5360
5495
|
var import_react22 = require("@ai-sdk/react");
|
|
5361
5496
|
var import_react23 = require("react");
|
|
5497
|
+
var EMPTY_QUEUE = {
|
|
5498
|
+
items: [],
|
|
5499
|
+
paused: null
|
|
5500
|
+
};
|
|
5501
|
+
var localQueueSequence = 0;
|
|
5502
|
+
function queuedUserMessage(turn) {
|
|
5503
|
+
return {
|
|
5504
|
+
id: turn.messageId,
|
|
5505
|
+
role: "user",
|
|
5506
|
+
metadata: {
|
|
5507
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5508
|
+
},
|
|
5509
|
+
parts: [
|
|
5510
|
+
...turn.attachments.map((attachment) => ({
|
|
5511
|
+
type: "file",
|
|
5512
|
+
mediaType: attachment.contentType,
|
|
5513
|
+
filename: attachment.name,
|
|
5514
|
+
url: attachment.url,
|
|
5515
|
+
providerMetadata: {
|
|
5516
|
+
agent: {
|
|
5517
|
+
mediaId: attachment.mediaId
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
})),
|
|
5521
|
+
...turn.text.length > 0 ? [
|
|
5522
|
+
{
|
|
5523
|
+
type: "text",
|
|
5524
|
+
text: turn.text
|
|
5525
|
+
}
|
|
5526
|
+
] : []
|
|
5527
|
+
]
|
|
5528
|
+
};
|
|
5529
|
+
}
|
|
5530
|
+
__name(queuedUserMessage, "queuedUserMessage");
|
|
5531
|
+
function draftOf(message) {
|
|
5532
|
+
if (typeof message !== "object" || message === null) {
|
|
5533
|
+
return {
|
|
5534
|
+
text: "",
|
|
5535
|
+
attachments: []
|
|
5536
|
+
};
|
|
5537
|
+
}
|
|
5538
|
+
const draft = message;
|
|
5539
|
+
let text = typeof draft.text === "string" ? draft.text : "";
|
|
5540
|
+
if (text === "" && Array.isArray(draft.parts)) {
|
|
5541
|
+
text = draft.parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
|
|
5542
|
+
}
|
|
5543
|
+
const files = Array.isArray(draft.files) ? draft.files : Array.isArray(draft.parts) ? draft.parts.filter((part) => part.type === "file") : [];
|
|
5544
|
+
const attachments = files.flatMap((file) => {
|
|
5545
|
+
const mediaId = file.providerMetadata?.agent?.mediaId;
|
|
5546
|
+
return typeof mediaId === "string" ? [
|
|
5547
|
+
{
|
|
5548
|
+
mediaId,
|
|
5549
|
+
url: String(file.url ?? ""),
|
|
5550
|
+
contentType: String(file.mediaType ?? "application/octet-stream"),
|
|
5551
|
+
name: String(file.filename ?? mediaId)
|
|
5552
|
+
}
|
|
5553
|
+
] : [];
|
|
5554
|
+
});
|
|
5555
|
+
return {
|
|
5556
|
+
text,
|
|
5557
|
+
attachments
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
__name(draftOf, "draftOf");
|
|
5561
|
+
function lastUserIndex(messages) {
|
|
5562
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
5563
|
+
if (messages[index]?.role === "user") return index;
|
|
5564
|
+
}
|
|
5565
|
+
return -1;
|
|
5566
|
+
}
|
|
5567
|
+
__name(lastUserIndex, "lastUserIndex");
|
|
5362
5568
|
var localChatSequence = 0;
|
|
5363
5569
|
function localChatId() {
|
|
5364
5570
|
localChatSequence += 1;
|
|
@@ -5398,6 +5604,41 @@ function useAgentChat(options = {}) {
|
|
|
5398
5604
|
const pendingPin = (0, import_react23.useRef)(void 0);
|
|
5399
5605
|
const regenerateNext = (0, import_react23.useRef)(false);
|
|
5400
5606
|
const currentThreadId = (0, import_react23.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
|
|
5607
|
+
const [queueState, setQueueState] = (0, import_react23.useState)(EMPTY_QUEUE);
|
|
5608
|
+
const queueStateRef = (0, import_react23.useRef)(queueState);
|
|
5609
|
+
const applyQueue = (0, import_react23.useCallback)((next) => {
|
|
5610
|
+
queueStateRef.current = next;
|
|
5611
|
+
setQueueState(next);
|
|
5612
|
+
}, []);
|
|
5613
|
+
const [pendingSends, setPendingSends] = (0, import_react23.useState)([]);
|
|
5614
|
+
const [queueError, setQueueError] = (0, import_react23.useState)(null);
|
|
5615
|
+
const handoff = (0, import_react23.useRef)(void 0);
|
|
5616
|
+
const queuedSend = (0, import_react23.useRef)(void 0);
|
|
5617
|
+
const threadWaiters = (0, import_react23.useRef)([]);
|
|
5618
|
+
const turnBody = (0, import_react23.useCallback)(() => {
|
|
5619
|
+
const current = latest.current;
|
|
5620
|
+
const pageContext = current.getPageContext?.() ?? null;
|
|
5621
|
+
const threadId = currentThreadId();
|
|
5622
|
+
const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
|
|
5623
|
+
return {
|
|
5624
|
+
// Read per send: a host that switches agents before the first message (a picker on a new
|
|
5625
|
+
// chat) sends the one picked now, not the one this chat mounted with.
|
|
5626
|
+
...current.agent !== void 0 ? {
|
|
5627
|
+
agent: current.agent
|
|
5628
|
+
} : {},
|
|
5629
|
+
...threadId !== void 0 ? {
|
|
5630
|
+
threadId
|
|
5631
|
+
} : {},
|
|
5632
|
+
...model !== void 0 ? {
|
|
5633
|
+
model
|
|
5634
|
+
} : {},
|
|
5635
|
+
...pageContext ? {
|
|
5636
|
+
pageContext
|
|
5637
|
+
} : {}
|
|
5638
|
+
};
|
|
5639
|
+
}, [
|
|
5640
|
+
currentThreadId
|
|
5641
|
+
]);
|
|
5401
5642
|
const [connection, setConnection] = (0, import_react23.useState)({
|
|
5402
5643
|
status: "live"
|
|
5403
5644
|
});
|
|
@@ -5406,6 +5647,11 @@ function useAgentChat(options = {}) {
|
|
|
5406
5647
|
function onMeta(meta) {
|
|
5407
5648
|
settlingRunIdRef.current = meta.runId;
|
|
5408
5649
|
setRunId(meta.runId);
|
|
5650
|
+
if (meta.threadId) {
|
|
5651
|
+
const waiting = threadWaiters.current;
|
|
5652
|
+
threadWaiters.current = [];
|
|
5653
|
+
for (const resolve of waiting) resolve(meta.threadId);
|
|
5654
|
+
}
|
|
5409
5655
|
if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
|
|
5410
5656
|
createdThreadId.current = meta.threadId;
|
|
5411
5657
|
const pin = pendingPin.current;
|
|
@@ -5437,27 +5683,10 @@ function useAgentChat(options = {}) {
|
|
|
5437
5683
|
agent: options.agent
|
|
5438
5684
|
} : {},
|
|
5439
5685
|
getBody: /* @__PURE__ */ __name(() => {
|
|
5440
|
-
const current = latest.current;
|
|
5441
|
-
const pageContext = current.getPageContext?.() ?? null;
|
|
5442
5686
|
const regenerate2 = regenerateNext.current;
|
|
5443
5687
|
regenerateNext.current = false;
|
|
5444
|
-
const threadId = currentThreadId();
|
|
5445
|
-
const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
|
|
5446
5688
|
return {
|
|
5447
|
-
|
|
5448
|
-
// new chat) sends the one picked now, not the one this chat mounted with.
|
|
5449
|
-
...current.agent !== void 0 ? {
|
|
5450
|
-
agent: current.agent
|
|
5451
|
-
} : {},
|
|
5452
|
-
...threadId !== void 0 ? {
|
|
5453
|
-
threadId
|
|
5454
|
-
} : {},
|
|
5455
|
-
...model !== void 0 ? {
|
|
5456
|
-
model
|
|
5457
|
-
} : {},
|
|
5458
|
-
...pageContext ? {
|
|
5459
|
-
pageContext
|
|
5460
|
-
} : {},
|
|
5689
|
+
...turnBody(),
|
|
5461
5690
|
...regenerate2 ? {
|
|
5462
5691
|
regenerate: true
|
|
5463
5692
|
} : {}
|
|
@@ -5467,7 +5696,11 @@ function useAgentChat(options = {}) {
|
|
|
5467
5696
|
onMeta,
|
|
5468
5697
|
onAttemptStart: /* @__PURE__ */ __name(() => {
|
|
5469
5698
|
settlingRunIdRef.current = void 0;
|
|
5470
|
-
}, "onAttemptStart")
|
|
5699
|
+
}, "onAttemptStart"),
|
|
5700
|
+
// A queued turn this chat went to attach to had already finished: read what it wrote.
|
|
5701
|
+
onResumeGone: /* @__PURE__ */ __name(() => {
|
|
5702
|
+
void resyncFromThread();
|
|
5703
|
+
}, "onResumeGone")
|
|
5471
5704
|
});
|
|
5472
5705
|
}, []);
|
|
5473
5706
|
const backgroundRef = (0, import_react23.useRef)(() => Promise.resolve());
|
|
@@ -5480,6 +5713,9 @@ function useAgentChat(options = {}) {
|
|
|
5480
5713
|
} : {},
|
|
5481
5714
|
onData: /* @__PURE__ */ __name((part) => {
|
|
5482
5715
|
latest.current.onData?.(part);
|
|
5716
|
+
if (part.type === "data-queue") {
|
|
5717
|
+
onQueueData(part.data);
|
|
5718
|
+
}
|
|
5483
5719
|
if (part.type === "data-title") {
|
|
5484
5720
|
const title = part.data?.title;
|
|
5485
5721
|
if (typeof title === "string") {
|
|
@@ -5511,6 +5747,20 @@ function useAgentChat(options = {}) {
|
|
|
5511
5747
|
setConnection({
|
|
5512
5748
|
status: "live"
|
|
5513
5749
|
});
|
|
5750
|
+
const queuedInstead = queuedSend.current;
|
|
5751
|
+
if (queuedInstead !== void 0) {
|
|
5752
|
+
queuedSend.current = void 0;
|
|
5753
|
+
chatRef.current.setMessages((current) => {
|
|
5754
|
+
const last = lastUserIndex(current);
|
|
5755
|
+
return last === -1 ? current : current.slice(0, last);
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
const next = handoff.current;
|
|
5759
|
+
handoff.current = void 0;
|
|
5760
|
+
if (next !== void 0) {
|
|
5761
|
+
attachTo(next);
|
|
5762
|
+
return;
|
|
5763
|
+
}
|
|
5514
5764
|
if (resyncAfterSettle.current) {
|
|
5515
5765
|
resyncAfterSettle.current = false;
|
|
5516
5766
|
void resyncFromThread();
|
|
@@ -5519,6 +5769,46 @@ function useAgentChat(options = {}) {
|
|
|
5519
5769
|
});
|
|
5520
5770
|
const chatRef = (0, import_react23.useRef)(chat);
|
|
5521
5771
|
chatRef.current = chat;
|
|
5772
|
+
function onQueueData(data) {
|
|
5773
|
+
const frame = data ?? {};
|
|
5774
|
+
const before = queueStateRef.current;
|
|
5775
|
+
if (frame.queue !== void 0) applyQueue(frame.queue);
|
|
5776
|
+
const started = frame.started;
|
|
5777
|
+
if (started !== void 0 && started.runId !== settlingRunIdRef.current) {
|
|
5778
|
+
const waiting = before.items.find((item) => item.id === started.messageId);
|
|
5779
|
+
handoff.current = {
|
|
5780
|
+
...started,
|
|
5781
|
+
text: waiting?.content ?? "",
|
|
5782
|
+
attachments: waiting?.attachments ?? []
|
|
5783
|
+
};
|
|
5784
|
+
}
|
|
5785
|
+
if (frame.queuedSend !== void 0) {
|
|
5786
|
+
queuedSend.current = frame.queuedSend;
|
|
5787
|
+
if (frame.queuedSend.runId !== void 0) {
|
|
5788
|
+
handoff.current = {
|
|
5789
|
+
messageId: frame.queuedSend.messageId,
|
|
5790
|
+
runId: frame.queuedSend.runId,
|
|
5791
|
+
...draftOf(chatRef.current.messages[lastUserIndex(chatRef.current.messages)])
|
|
5792
|
+
};
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
__name(onQueueData, "onQueueData");
|
|
5797
|
+
function attachTo(turn) {
|
|
5798
|
+
setTimeout(() => {
|
|
5799
|
+
if (turn.text.length > 0 || turn.attachments.length > 0) {
|
|
5800
|
+
chatRef.current.setMessages((current) => current.some((message) => message.id === turn.messageId) ? current : [
|
|
5801
|
+
...current,
|
|
5802
|
+
queuedUserMessage(turn)
|
|
5803
|
+
]);
|
|
5804
|
+
} else {
|
|
5805
|
+
resyncAfterSettle.current = true;
|
|
5806
|
+
}
|
|
5807
|
+
autoResumeRunIdRef.current = turn.runId;
|
|
5808
|
+
void chatRef.current.resumeStream();
|
|
5809
|
+
}, 0);
|
|
5810
|
+
}
|
|
5811
|
+
__name(attachTo, "attachTo");
|
|
5522
5812
|
async function resyncFromThread() {
|
|
5523
5813
|
const threadId = currentThreadId();
|
|
5524
5814
|
if (threadId === void 0) return;
|
|
@@ -5541,6 +5831,11 @@ function useAgentChat(options = {}) {
|
|
|
5541
5831
|
setHistoryError(null);
|
|
5542
5832
|
autoResumeRunIdRef.current = void 0;
|
|
5543
5833
|
pendingPin.current = void 0;
|
|
5834
|
+
applyQueue(EMPTY_QUEUE);
|
|
5835
|
+
setPendingSends([]);
|
|
5836
|
+
setQueueError(null);
|
|
5837
|
+
handoff.current = void 0;
|
|
5838
|
+
queuedSend.current = void 0;
|
|
5544
5839
|
}, [
|
|
5545
5840
|
chatId
|
|
5546
5841
|
]);
|
|
@@ -5560,6 +5855,11 @@ function useAgentChat(options = {}) {
|
|
|
5560
5855
|
setActiveRunId(active);
|
|
5561
5856
|
setThreadModel(thread.model ?? null);
|
|
5562
5857
|
setHistoryError(null);
|
|
5858
|
+
const queue2 = thread.queue ?? EMPTY_QUEUE;
|
|
5859
|
+
applyQueue(queue2);
|
|
5860
|
+
if (queue2.items.length > 0 && queue2.paused === null && active === null && typeof client.resumeQueue === "function") {
|
|
5861
|
+
void resumeQueueRef.current().catch(() => void 0);
|
|
5862
|
+
}
|
|
5563
5863
|
const resuming = wantResume && active !== null;
|
|
5564
5864
|
if (wantHistory && chatRef.current.messages.length === 0) {
|
|
5565
5865
|
const rows = resuming ? stored.filter((message) => !(message.role === "assistant" && message.runId === active)) : stored;
|
|
@@ -5595,10 +5895,98 @@ function useAgentChat(options = {}) {
|
|
|
5595
5895
|
const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
|
|
5596
5896
|
const blockedRef = (0, import_react23.useRef)(blocked);
|
|
5597
5897
|
blockedRef.current = blocked;
|
|
5898
|
+
const whileRunning = typeof client.enqueueMessage === "function" ? options.whileRunning ?? "queue" : "block";
|
|
5899
|
+
const whileRunningRef = (0, import_react23.useRef)(whileRunning);
|
|
5900
|
+
whileRunningRef.current = whileRunning;
|
|
5901
|
+
const threadForQueue = (0, import_react23.useCallback)(async () => {
|
|
5902
|
+
const known = currentThreadId();
|
|
5903
|
+
if (known !== void 0) return known;
|
|
5904
|
+
if (!isTurnInFlight()) throw new Error("queue: this chat has no thread yet");
|
|
5905
|
+
return new Promise((resolve) => threadWaiters.current.push(resolve));
|
|
5906
|
+
}, [
|
|
5907
|
+
currentThreadId,
|
|
5908
|
+
isTurnInFlight
|
|
5909
|
+
]);
|
|
5910
|
+
const queueCall = (0, import_react23.useCallback)(async (run) => {
|
|
5911
|
+
try {
|
|
5912
|
+
const result = await run();
|
|
5913
|
+
setQueueError(null);
|
|
5914
|
+
return result;
|
|
5915
|
+
} catch (error) {
|
|
5916
|
+
setQueueError(error instanceof Error ? error : new Error(String(error)));
|
|
5917
|
+
throw error;
|
|
5918
|
+
}
|
|
5919
|
+
}, []);
|
|
5920
|
+
const enqueue = (0, import_react23.useCallback)(async (text2, { attachments = [], mode = "queue" } = {}) => {
|
|
5921
|
+
const block = blockedRef.current;
|
|
5922
|
+
if (block != null) throw new QuotaBlockedError(block);
|
|
5923
|
+
const send = client.enqueueMessage;
|
|
5924
|
+
if (typeof send !== "function") throw new AgentBackendUnsupportedError("enqueueMessage");
|
|
5925
|
+
localQueueSequence += 1;
|
|
5926
|
+
const localId = `local-queued-${localQueueSequence}`;
|
|
5927
|
+
setPendingSends((current) => [
|
|
5928
|
+
...current,
|
|
5929
|
+
{
|
|
5930
|
+
id: localId,
|
|
5931
|
+
text: text2,
|
|
5932
|
+
attachments,
|
|
5933
|
+
state: "sending",
|
|
5934
|
+
interrupt: mode === "interrupt",
|
|
5935
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5936
|
+
}
|
|
5937
|
+
]);
|
|
5938
|
+
try {
|
|
5939
|
+
await queueCall(async () => {
|
|
5940
|
+
const threadId = await threadForQueue();
|
|
5941
|
+
const result = await send.call(client, {
|
|
5942
|
+
body: {
|
|
5943
|
+
...turnBody(),
|
|
5944
|
+
threadId,
|
|
5945
|
+
message: text2,
|
|
5946
|
+
mode,
|
|
5947
|
+
...attachments.length > 0 ? {
|
|
5948
|
+
attachments: attachments.map(({ mediaId }) => ({
|
|
5949
|
+
mediaId
|
|
5950
|
+
}))
|
|
5951
|
+
} : {}
|
|
5952
|
+
}
|
|
5953
|
+
});
|
|
5954
|
+
applyQueue(result.queue);
|
|
5955
|
+
if (result.runId !== void 0) {
|
|
5956
|
+
const turn = {
|
|
5957
|
+
messageId: result.messageId,
|
|
5958
|
+
runId: result.runId,
|
|
5959
|
+
text: text2,
|
|
5960
|
+
attachments
|
|
5961
|
+
};
|
|
5962
|
+
if (isTurnInFlight()) handoff.current = turn;
|
|
5963
|
+
else attachTo(turn);
|
|
5964
|
+
}
|
|
5965
|
+
});
|
|
5966
|
+
} finally {
|
|
5967
|
+
setPendingSends((current) => current.filter((item) => item.id !== localId));
|
|
5968
|
+
}
|
|
5969
|
+
}, [
|
|
5970
|
+
client,
|
|
5971
|
+
queueCall,
|
|
5972
|
+
threadForQueue,
|
|
5973
|
+
turnBody,
|
|
5974
|
+
applyQueue,
|
|
5975
|
+
isTurnInFlight
|
|
5976
|
+
]);
|
|
5598
5977
|
const sendMessage = (0, import_react23.useCallback)(async (...args) => {
|
|
5599
5978
|
const block = blockedRef.current;
|
|
5600
5979
|
if (block != null) throw new QuotaBlockedError(block);
|
|
5601
|
-
if (isTurnInFlight())
|
|
5980
|
+
if (isTurnInFlight()) {
|
|
5981
|
+
const mode = whileRunningRef.current;
|
|
5982
|
+
if (mode === "block") return;
|
|
5983
|
+
const draft = draftOf(args[0]);
|
|
5984
|
+
await enqueue(draft.text, {
|
|
5985
|
+
attachments: draft.attachments,
|
|
5986
|
+
mode
|
|
5987
|
+
});
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5602
5990
|
turnInFlight.current = true;
|
|
5603
5991
|
try {
|
|
5604
5992
|
await chatRef.current.sendMessage(...args);
|
|
@@ -5606,8 +5994,122 @@ function useAgentChat(options = {}) {
|
|
|
5606
5994
|
turnInFlight.current = false;
|
|
5607
5995
|
}
|
|
5608
5996
|
}, [
|
|
5997
|
+
isTurnInFlight,
|
|
5998
|
+
enqueue
|
|
5999
|
+
]);
|
|
6000
|
+
const removeQueued = (0, import_react23.useCallback)(async (id) => {
|
|
6001
|
+
const previous = queueStateRef.current;
|
|
6002
|
+
applyQueue({
|
|
6003
|
+
...previous,
|
|
6004
|
+
items: previous.items.filter((item) => item.id !== id)
|
|
6005
|
+
});
|
|
6006
|
+
try {
|
|
6007
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "removeQueuedMessage")(id)));
|
|
6008
|
+
} catch (error) {
|
|
6009
|
+
applyQueue(previous);
|
|
6010
|
+
throw error;
|
|
6011
|
+
}
|
|
6012
|
+
}, [
|
|
6013
|
+
client,
|
|
6014
|
+
queueCall,
|
|
6015
|
+
applyQueue
|
|
6016
|
+
]);
|
|
6017
|
+
const editQueued = (0, import_react23.useCallback)(async (id, text2) => {
|
|
6018
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "updateQueuedMessage")(id, {
|
|
6019
|
+
message: text2
|
|
6020
|
+
})));
|
|
6021
|
+
}, [
|
|
6022
|
+
client,
|
|
6023
|
+
queueCall,
|
|
6024
|
+
applyQueue
|
|
6025
|
+
]);
|
|
6026
|
+
const moveQueued = (0, import_react23.useCallback)(async (id, index) => {
|
|
6027
|
+
const previous = queueStateRef.current;
|
|
6028
|
+
const moving = previous.items.find((item) => item.id === id);
|
|
6029
|
+
if (moving !== void 0) {
|
|
6030
|
+
const rest = previous.items.filter((item) => item.id !== id);
|
|
6031
|
+
rest.splice(Math.max(0, Math.min(rest.length, index)), 0, moving);
|
|
6032
|
+
applyQueue({
|
|
6033
|
+
...previous,
|
|
6034
|
+
items: rest
|
|
6035
|
+
});
|
|
6036
|
+
}
|
|
6037
|
+
try {
|
|
6038
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "updateQueuedMessage")(id, {
|
|
6039
|
+
position: index
|
|
6040
|
+
})));
|
|
6041
|
+
} catch (error) {
|
|
6042
|
+
applyQueue(previous);
|
|
6043
|
+
throw error;
|
|
6044
|
+
}
|
|
6045
|
+
}, [
|
|
6046
|
+
client,
|
|
6047
|
+
queueCall,
|
|
6048
|
+
applyQueue
|
|
6049
|
+
]);
|
|
6050
|
+
const clearQueue = (0, import_react23.useCallback)(async () => {
|
|
6051
|
+
const threadId = currentThreadId();
|
|
6052
|
+
if (threadId === void 0) return;
|
|
6053
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "clearQueue")(threadId)));
|
|
6054
|
+
}, [
|
|
6055
|
+
client,
|
|
6056
|
+
queueCall,
|
|
6057
|
+
currentThreadId,
|
|
6058
|
+
applyQueue
|
|
6059
|
+
]);
|
|
6060
|
+
const resumeQueue = (0, import_react23.useCallback)(async () => {
|
|
6061
|
+
const threadId = currentThreadId();
|
|
6062
|
+
if (threadId === void 0) return;
|
|
6063
|
+
const before = queueStateRef.current;
|
|
6064
|
+
const result = await queueCall(() => requireBackendMethod(client, "resumeQueue")(threadId));
|
|
6065
|
+
applyQueue({
|
|
6066
|
+
items: result.items,
|
|
6067
|
+
paused: result.paused
|
|
6068
|
+
});
|
|
6069
|
+
if (result.runId !== void 0) {
|
|
6070
|
+
const head = before.items.find((item) => item.id === result.runId) ?? before.items[0];
|
|
6071
|
+
const turn = {
|
|
6072
|
+
messageId: result.runId,
|
|
6073
|
+
runId: result.runId,
|
|
6074
|
+
text: head?.content ?? "",
|
|
6075
|
+
attachments: head?.attachments ?? []
|
|
6076
|
+
};
|
|
6077
|
+
if (isTurnInFlight()) handoff.current = turn;
|
|
6078
|
+
else attachTo(turn);
|
|
6079
|
+
}
|
|
6080
|
+
}, [
|
|
6081
|
+
client,
|
|
6082
|
+
queueCall,
|
|
6083
|
+
currentThreadId,
|
|
6084
|
+
applyQueue,
|
|
5609
6085
|
isTurnInFlight
|
|
5610
6086
|
]);
|
|
6087
|
+
const resumeQueueRef = (0, import_react23.useRef)(resumeQueue);
|
|
6088
|
+
resumeQueueRef.current = resumeQueue;
|
|
6089
|
+
const queuedItems = [
|
|
6090
|
+
...pendingSends.filter((item) => item.interrupt),
|
|
6091
|
+
...queueState.items.map((item) => ({
|
|
6092
|
+
id: item.id,
|
|
6093
|
+
text: item.content,
|
|
6094
|
+
attachments: item.attachments ?? [],
|
|
6095
|
+
state: "queued",
|
|
6096
|
+
interrupt: item.interrupt === true,
|
|
6097
|
+
createdAt: item.createdAt
|
|
6098
|
+
})),
|
|
6099
|
+
...pendingSends.filter((item) => !item.interrupt)
|
|
6100
|
+
];
|
|
6101
|
+
const queue = {
|
|
6102
|
+
items: queuedItems,
|
|
6103
|
+
paused: queueState.paused,
|
|
6104
|
+
isSupported: typeof client.enqueueMessage === "function",
|
|
6105
|
+
add: enqueue,
|
|
6106
|
+
remove: removeQueued,
|
|
6107
|
+
edit: editQueued,
|
|
6108
|
+
move: moveQueued,
|
|
6109
|
+
clear: clearQueue,
|
|
6110
|
+
resume: resumeQueue,
|
|
6111
|
+
error: queueError
|
|
6112
|
+
};
|
|
5611
6113
|
const addToolResult = (0, import_react23.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
|
|
5612
6114
|
tool,
|
|
5613
6115
|
toolCallId,
|
|
@@ -5848,12 +6350,14 @@ function useAgentChat(options = {}) {
|
|
|
5848
6350
|
};
|
|
5849
6351
|
const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
|
|
5850
6352
|
const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
|
|
6353
|
+
const isBusyRef = (0, import_react23.useRef)(isBusy);
|
|
6354
|
+
isBusyRef.current = isBusy;
|
|
5851
6355
|
const files = useAttachments({
|
|
5852
6356
|
...options.composer,
|
|
5853
6357
|
backend: client
|
|
5854
6358
|
});
|
|
5855
6359
|
const [text, setText] = (0, import_react23.useState)("");
|
|
5856
|
-
const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
|
|
6360
|
+
const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy && whileRunning === "block" ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
|
|
5857
6361
|
const composerRef = (0, import_react23.useRef)({
|
|
5858
6362
|
text,
|
|
5859
6363
|
files,
|
|
@@ -5884,6 +6388,17 @@ function useAgentChat(options = {}) {
|
|
|
5884
6388
|
const draft = current.text.trim();
|
|
5885
6389
|
setText("");
|
|
5886
6390
|
current.files.clear();
|
|
6391
|
+
const mode = whileRunningRef.current;
|
|
6392
|
+
if (mode !== "block" && (isTurnInFlight() || isBusyRef.current)) {
|
|
6393
|
+
const attachments = current.files.items.flatMap((item) => item.status === "ready" && item.attachment !== void 0 ? [
|
|
6394
|
+
item.attachment
|
|
6395
|
+
] : []);
|
|
6396
|
+
await enqueue(draft, {
|
|
6397
|
+
attachments,
|
|
6398
|
+
mode
|
|
6399
|
+
});
|
|
6400
|
+
return;
|
|
6401
|
+
}
|
|
5887
6402
|
await sendMessage(files2.length > 0 ? {
|
|
5888
6403
|
text: draft,
|
|
5889
6404
|
files: files2
|
|
@@ -5895,7 +6410,9 @@ function useAgentChat(options = {}) {
|
|
|
5895
6410
|
}
|
|
5896
6411
|
} : void 0);
|
|
5897
6412
|
}, [
|
|
5898
|
-
sendMessage
|
|
6413
|
+
sendMessage,
|
|
6414
|
+
enqueue,
|
|
6415
|
+
isTurnInFlight
|
|
5899
6416
|
]);
|
|
5900
6417
|
const composer = {
|
|
5901
6418
|
text,
|
|
@@ -5930,6 +6447,11 @@ function useAgentChat(options = {}) {
|
|
|
5930
6447
|
...ownCatalog === void 0 ? {
|
|
5931
6448
|
toolCatalog: tools.catalog
|
|
5932
6449
|
} : {},
|
|
6450
|
+
queue: {
|
|
6451
|
+
items: queue.items,
|
|
6452
|
+
paused: queue.paused,
|
|
6453
|
+
remove: removeQueued
|
|
6454
|
+
},
|
|
5933
6455
|
...options.transcript
|
|
5934
6456
|
});
|
|
5935
6457
|
const background = {
|
|
@@ -5964,6 +6486,11 @@ function useAgentChat(options = {}) {
|
|
|
5964
6486
|
transcript,
|
|
5965
6487
|
/** A headless composer: draft, files, send gate, `submit()`. */
|
|
5966
6488
|
composer,
|
|
6489
|
+
/**
|
|
6490
|
+
* Messages sent while a turn was running, waiting their turn (server-side): list, edit, move,
|
|
6491
|
+
* remove, clear, and resume a paused queue.
|
|
6492
|
+
*/
|
|
6493
|
+
queue,
|
|
5967
6494
|
/** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
|
|
5968
6495
|
models,
|
|
5969
6496
|
/** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
|