@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.js
CHANGED
|
@@ -110,12 +110,48 @@ var AgentClient = class {
|
|
|
110
110
|
signal: request.signal
|
|
111
111
|
} : {}
|
|
112
112
|
});
|
|
113
|
+
if (response.status === 202) {
|
|
114
|
+
return {
|
|
115
|
+
body: emptyStream(),
|
|
116
|
+
queued: await response.json()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
113
119
|
if (!response.ok || !response.body) {
|
|
114
120
|
throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
|
|
115
121
|
}
|
|
116
122
|
return streamResponse(response);
|
|
117
123
|
}
|
|
118
124
|
/**
|
|
125
|
+
* `POST <path>/chat` for a message that should wait in the thread's queue — the body carries
|
|
126
|
+
* `mode: 'queue'` (or `'interrupt'`), which the server always answers with `202` JSON.
|
|
127
|
+
*/
|
|
128
|
+
enqueueMessage(request) {
|
|
129
|
+
return this.request("POST", "/chat", {
|
|
130
|
+
mode: "queue",
|
|
131
|
+
...request.body
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** `GET <path>/threads/:id/queue` — the thread's waiting messages, and whether it drains. */
|
|
135
|
+
getQueue(threadId) {
|
|
136
|
+
return this.request("GET", `/threads/${encodeURIComponent(threadId)}/queue`);
|
|
137
|
+
}
|
|
138
|
+
/** `PATCH <path>/queue/:messageId` — change a waiting message's text/attachments, or move it. */
|
|
139
|
+
updateQueuedMessage(messageId, update) {
|
|
140
|
+
return this.request("PATCH", `/queue/${encodeURIComponent(messageId)}`, update);
|
|
141
|
+
}
|
|
142
|
+
/** `DELETE <path>/queue/:messageId`. */
|
|
143
|
+
removeQueuedMessage(messageId) {
|
|
144
|
+
return this.request("DELETE", `/queue/${encodeURIComponent(messageId)}`);
|
|
145
|
+
}
|
|
146
|
+
/** `DELETE <path>/threads/:id/queue` — drop every waiting message. */
|
|
147
|
+
clearQueue(threadId) {
|
|
148
|
+
return this.request("DELETE", `/threads/${encodeURIComponent(threadId)}/queue`);
|
|
149
|
+
}
|
|
150
|
+
/** `POST <path>/threads/:id/queue/resume` — lift a pause; `runId` when the head started. */
|
|
151
|
+
resumeQueue(threadId) {
|
|
152
|
+
return this.request("POST", `/threads/${encodeURIComponent(threadId)}/queue/resume`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
119
155
|
* `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
|
|
120
156
|
* streaming under that id (404).
|
|
121
157
|
*/
|
|
@@ -381,6 +417,14 @@ var AgentClient = class {
|
|
|
381
417
|
return JSON.parse(text);
|
|
382
418
|
}
|
|
383
419
|
};
|
|
420
|
+
function emptyStream() {
|
|
421
|
+
return new ReadableStream({
|
|
422
|
+
start(controller) {
|
|
423
|
+
controller.close();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
__name(emptyStream, "emptyStream");
|
|
384
428
|
function streamResponse(response) {
|
|
385
429
|
const body = response.body;
|
|
386
430
|
const runId = response.headers?.get(HEADER_RUN_ID) ?? void 0;
|
|
@@ -543,6 +587,10 @@ var AgentChatTransport = class {
|
|
|
543
587
|
signal: options.abortSignal
|
|
544
588
|
} : {}
|
|
545
589
|
});
|
|
590
|
+
if (response.queued !== void 0) {
|
|
591
|
+
this.attemptLive = false;
|
|
592
|
+
return queuedChunkStream(response.queued);
|
|
593
|
+
}
|
|
546
594
|
this.captureHeaderMeta(response);
|
|
547
595
|
return this.toChunkStream(response, {
|
|
548
596
|
...headers !== void 0 ? {
|
|
@@ -573,6 +621,7 @@ var AgentChatTransport = class {
|
|
|
573
621
|
});
|
|
574
622
|
if (response === null) {
|
|
575
623
|
this.attemptLive = false;
|
|
624
|
+
this.options.onResumeGone?.(runId);
|
|
576
625
|
return null;
|
|
577
626
|
}
|
|
578
627
|
this.captureHeaderMeta(response);
|
|
@@ -950,6 +999,9 @@ var AgentChatTransport = class {
|
|
|
950
999
|
break;
|
|
951
1000
|
case "title":
|
|
952
1001
|
case "cancelled":
|
|
1002
|
+
// The thread's queue changed, or this run handed the thread to the next queued message
|
|
1003
|
+
// (`started`). Thread-level, like the title.
|
|
1004
|
+
case "queue":
|
|
953
1005
|
forwardAsData(event, true);
|
|
954
1006
|
break;
|
|
955
1007
|
default:
|
|
@@ -1052,6 +1104,22 @@ var AgentChatTransport = class {
|
|
|
1052
1104
|
});
|
|
1053
1105
|
}
|
|
1054
1106
|
};
|
|
1107
|
+
function queuedChunkStream(queued) {
|
|
1108
|
+
return new ReadableStream({
|
|
1109
|
+
start(controller) {
|
|
1110
|
+
controller.enqueue({
|
|
1111
|
+
type: "data-queue",
|
|
1112
|
+
data: {
|
|
1113
|
+
queue: queued.queue,
|
|
1114
|
+
queuedSend: queued
|
|
1115
|
+
},
|
|
1116
|
+
transient: true
|
|
1117
|
+
});
|
|
1118
|
+
controller.close();
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
__name(queuedChunkStream, "queuedChunkStream");
|
|
1055
1123
|
function parseSseFrame(raw) {
|
|
1056
1124
|
let event;
|
|
1057
1125
|
let id;
|
|
@@ -3766,6 +3834,7 @@ function useChatTranscript(options) {
|
|
|
3766
3834
|
} : {}
|
|
3767
3835
|
});
|
|
3768
3836
|
const showTypingIndicator = isBusy && lastMessage?.role !== "assistant";
|
|
3837
|
+
const queued = useQueuedItems(options.queue ?? null, options.readOnly === true);
|
|
3769
3838
|
return {
|
|
3770
3839
|
items,
|
|
3771
3840
|
status,
|
|
@@ -3788,10 +3857,53 @@ function useChatTranscript(options) {
|
|
|
3788
3857
|
available: isBusy && options.readOnly !== true && options.onStop !== void 0,
|
|
3789
3858
|
isStopping: stopRequested,
|
|
3790
3859
|
stop
|
|
3791
|
-
}
|
|
3860
|
+
},
|
|
3861
|
+
queued,
|
|
3862
|
+
queuePaused: options.queue?.paused ?? null
|
|
3792
3863
|
};
|
|
3793
3864
|
}
|
|
3794
3865
|
__name(useChatTranscript, "useChatTranscript");
|
|
3866
|
+
function useQueuedItems(queue, readOnly) {
|
|
3867
|
+
const latestQueue = useRef9(queue);
|
|
3868
|
+
latestQueue.current = queue;
|
|
3869
|
+
const removers = useRef9(/* @__PURE__ */ new Map());
|
|
3870
|
+
const removerFor = useCallback8((id) => {
|
|
3871
|
+
let remover = removers.current.get(id);
|
|
3872
|
+
if (remover === void 0) {
|
|
3873
|
+
remover = /* @__PURE__ */ __name(() => {
|
|
3874
|
+
void latestQueue.current?.remove?.(id);
|
|
3875
|
+
}, "remover");
|
|
3876
|
+
removers.current.set(id, remover);
|
|
3877
|
+
}
|
|
3878
|
+
return remover;
|
|
3879
|
+
}, []);
|
|
3880
|
+
if (queue === null || queue === void 0) {
|
|
3881
|
+
return [];
|
|
3882
|
+
}
|
|
3883
|
+
return queue.items.map((message, position) => {
|
|
3884
|
+
const state = message.state === "sending" ? "sending" : queue.paused !== null ? "paused" : "queued";
|
|
3885
|
+
return {
|
|
3886
|
+
id: message.id,
|
|
3887
|
+
role: "user",
|
|
3888
|
+
text: message.text,
|
|
3889
|
+
files: message.attachments.map((attachment) => ({
|
|
3890
|
+
url: attachment.url,
|
|
3891
|
+
mediaType: attachment.contentType,
|
|
3892
|
+
filename: attachment.name,
|
|
3893
|
+
isImage: attachment.contentType.startsWith("image/")
|
|
3894
|
+
})),
|
|
3895
|
+
state,
|
|
3896
|
+
position,
|
|
3897
|
+
isNext: position === 0,
|
|
3898
|
+
interrupt: message.interrupt,
|
|
3899
|
+
remove: {
|
|
3900
|
+
available: !readOnly && state !== "sending" && queue.remove !== void 0,
|
|
3901
|
+
run: removerFor(message.id)
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
__name(useQueuedItems, "useQueuedItems");
|
|
3795
3907
|
function useTranscriptItem(options) {
|
|
3796
3908
|
const { message, isStreaming = false } = options;
|
|
3797
3909
|
const messagesRef = useRef9([
|
|
@@ -4410,7 +4522,7 @@ function joinClasses(...entries) {
|
|
|
4410
4522
|
__name(joinClasses, "joinClasses");
|
|
4411
4523
|
|
|
4412
4524
|
// src/components/message-list.tsx
|
|
4413
|
-
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 }) {
|
|
4525
|
+
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 }) {
|
|
4414
4526
|
const transcript = useChatTranscript({
|
|
4415
4527
|
messages,
|
|
4416
4528
|
status,
|
|
@@ -4440,9 +4552,16 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
|
|
|
4440
4552
|
} : {},
|
|
4441
4553
|
...followUps !== void 0 ? {
|
|
4442
4554
|
followUps
|
|
4555
|
+
} : {},
|
|
4556
|
+
...queue !== void 0 ? {
|
|
4557
|
+
queue: {
|
|
4558
|
+
items: queue.items,
|
|
4559
|
+
paused: queue.paused,
|
|
4560
|
+
remove: queue.remove
|
|
4561
|
+
}
|
|
4443
4562
|
} : {}
|
|
4444
4563
|
});
|
|
4445
|
-
if (transcript.showEmptyState) {
|
|
4564
|
+
if (transcript.showEmptyState && transcript.queued.length === 0) {
|
|
4446
4565
|
return /* @__PURE__ */ React.createElement("div", {
|
|
4447
4566
|
className: classNames?.empty
|
|
4448
4567
|
}, emptyState);
|
|
@@ -4486,7 +4605,23 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
|
|
|
4486
4605
|
...getMeta ? {
|
|
4487
4606
|
meta: getMeta(item.message)
|
|
4488
4607
|
} : {}
|
|
4489
|
-
})), transcript.
|
|
4608
|
+
})), transcript.queued.length > 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
4609
|
+
className: classNames?.queue
|
|
4610
|
+
}, transcript.queued.map((item) => /* @__PURE__ */ React.createElement("div", {
|
|
4611
|
+
key: item.id,
|
|
4612
|
+
className: classNames?.queuedItem,
|
|
4613
|
+
"data-state": item.state
|
|
4614
|
+
}, /* @__PURE__ */ React.createElement("span", null, item.text), item.remove.available ? /* @__PURE__ */ React.createElement("button", {
|
|
4615
|
+
type: "button",
|
|
4616
|
+
className: classNames?.queuedRemove,
|
|
4617
|
+
onClick: item.remove.run
|
|
4618
|
+
}, "Remove") : null)), transcript.queuePaused !== null && queue !== void 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
4619
|
+
className: classNames?.queuePaused
|
|
4620
|
+
}, /* @__PURE__ */ React.createElement("span", null, "Queue paused"), /* @__PURE__ */ React.createElement("button", {
|
|
4621
|
+
type: "button",
|
|
4622
|
+
className: classNames?.queueResume,
|
|
4623
|
+
onClick: /* @__PURE__ */ __name(() => void queue.resume(), "onClick")
|
|
4624
|
+
}, "Resume")) : null) : null, transcript.showTypingIndicator ? /* @__PURE__ */ React.createElement("div", {
|
|
4490
4625
|
className: classNames?.typing
|
|
4491
4626
|
}, typingIndicator ?? "Thinking\u2026") : null, error && onRetry ? /* @__PURE__ */ React.createElement("div", {
|
|
4492
4627
|
className: classNames?.error
|
|
@@ -5255,6 +5390,77 @@ __name(sumUsage, "sumUsage");
|
|
|
5255
5390
|
// src/use-agent-chat.ts
|
|
5256
5391
|
import { useChat } from "@ai-sdk/react";
|
|
5257
5392
|
import { useCallback as useCallback11, useEffect as useEffect15, useMemo as useMemo11, useRef as useRef12, useState as useState15 } from "react";
|
|
5393
|
+
var EMPTY_QUEUE = {
|
|
5394
|
+
items: [],
|
|
5395
|
+
paused: null
|
|
5396
|
+
};
|
|
5397
|
+
var localQueueSequence = 0;
|
|
5398
|
+
function queuedUserMessage(turn) {
|
|
5399
|
+
return {
|
|
5400
|
+
id: turn.messageId,
|
|
5401
|
+
role: "user",
|
|
5402
|
+
metadata: {
|
|
5403
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5404
|
+
},
|
|
5405
|
+
parts: [
|
|
5406
|
+
...turn.attachments.map((attachment) => ({
|
|
5407
|
+
type: "file",
|
|
5408
|
+
mediaType: attachment.contentType,
|
|
5409
|
+
filename: attachment.name,
|
|
5410
|
+
url: attachment.url,
|
|
5411
|
+
providerMetadata: {
|
|
5412
|
+
agent: {
|
|
5413
|
+
mediaId: attachment.mediaId
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
})),
|
|
5417
|
+
...turn.text.length > 0 ? [
|
|
5418
|
+
{
|
|
5419
|
+
type: "text",
|
|
5420
|
+
text: turn.text
|
|
5421
|
+
}
|
|
5422
|
+
] : []
|
|
5423
|
+
]
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
__name(queuedUserMessage, "queuedUserMessage");
|
|
5427
|
+
function draftOf(message) {
|
|
5428
|
+
if (typeof message !== "object" || message === null) {
|
|
5429
|
+
return {
|
|
5430
|
+
text: "",
|
|
5431
|
+
attachments: []
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
const draft = message;
|
|
5435
|
+
let text = typeof draft.text === "string" ? draft.text : "";
|
|
5436
|
+
if (text === "" && Array.isArray(draft.parts)) {
|
|
5437
|
+
text = draft.parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
|
|
5438
|
+
}
|
|
5439
|
+
const files = Array.isArray(draft.files) ? draft.files : Array.isArray(draft.parts) ? draft.parts.filter((part) => part.type === "file") : [];
|
|
5440
|
+
const attachments = files.flatMap((file) => {
|
|
5441
|
+
const mediaId = file.providerMetadata?.agent?.mediaId;
|
|
5442
|
+
return typeof mediaId === "string" ? [
|
|
5443
|
+
{
|
|
5444
|
+
mediaId,
|
|
5445
|
+
url: String(file.url ?? ""),
|
|
5446
|
+
contentType: String(file.mediaType ?? "application/octet-stream"),
|
|
5447
|
+
name: String(file.filename ?? mediaId)
|
|
5448
|
+
}
|
|
5449
|
+
] : [];
|
|
5450
|
+
});
|
|
5451
|
+
return {
|
|
5452
|
+
text,
|
|
5453
|
+
attachments
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
__name(draftOf, "draftOf");
|
|
5457
|
+
function lastUserIndex(messages) {
|
|
5458
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
5459
|
+
if (messages[index]?.role === "user") return index;
|
|
5460
|
+
}
|
|
5461
|
+
return -1;
|
|
5462
|
+
}
|
|
5463
|
+
__name(lastUserIndex, "lastUserIndex");
|
|
5258
5464
|
var localChatSequence = 0;
|
|
5259
5465
|
function localChatId() {
|
|
5260
5466
|
localChatSequence += 1;
|
|
@@ -5294,6 +5500,41 @@ function useAgentChat(options = {}) {
|
|
|
5294
5500
|
const pendingPin = useRef12(void 0);
|
|
5295
5501
|
const regenerateNext = useRef12(false);
|
|
5296
5502
|
const currentThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
|
|
5503
|
+
const [queueState, setQueueState] = useState15(EMPTY_QUEUE);
|
|
5504
|
+
const queueStateRef = useRef12(queueState);
|
|
5505
|
+
const applyQueue = useCallback11((next) => {
|
|
5506
|
+
queueStateRef.current = next;
|
|
5507
|
+
setQueueState(next);
|
|
5508
|
+
}, []);
|
|
5509
|
+
const [pendingSends, setPendingSends] = useState15([]);
|
|
5510
|
+
const [queueError, setQueueError] = useState15(null);
|
|
5511
|
+
const handoff = useRef12(void 0);
|
|
5512
|
+
const queuedSend = useRef12(void 0);
|
|
5513
|
+
const threadWaiters = useRef12([]);
|
|
5514
|
+
const turnBody = useCallback11(() => {
|
|
5515
|
+
const current = latest.current;
|
|
5516
|
+
const pageContext = current.getPageContext?.() ?? null;
|
|
5517
|
+
const threadId = currentThreadId();
|
|
5518
|
+
const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
|
|
5519
|
+
return {
|
|
5520
|
+
// Read per send: a host that switches agents before the first message (a picker on a new
|
|
5521
|
+
// chat) sends the one picked now, not the one this chat mounted with.
|
|
5522
|
+
...current.agent !== void 0 ? {
|
|
5523
|
+
agent: current.agent
|
|
5524
|
+
} : {},
|
|
5525
|
+
...threadId !== void 0 ? {
|
|
5526
|
+
threadId
|
|
5527
|
+
} : {},
|
|
5528
|
+
...model !== void 0 ? {
|
|
5529
|
+
model
|
|
5530
|
+
} : {},
|
|
5531
|
+
...pageContext ? {
|
|
5532
|
+
pageContext
|
|
5533
|
+
} : {}
|
|
5534
|
+
};
|
|
5535
|
+
}, [
|
|
5536
|
+
currentThreadId
|
|
5537
|
+
]);
|
|
5297
5538
|
const [connection, setConnection] = useState15({
|
|
5298
5539
|
status: "live"
|
|
5299
5540
|
});
|
|
@@ -5302,6 +5543,11 @@ function useAgentChat(options = {}) {
|
|
|
5302
5543
|
function onMeta(meta) {
|
|
5303
5544
|
settlingRunIdRef.current = meta.runId;
|
|
5304
5545
|
setRunId(meta.runId);
|
|
5546
|
+
if (meta.threadId) {
|
|
5547
|
+
const waiting = threadWaiters.current;
|
|
5548
|
+
threadWaiters.current = [];
|
|
5549
|
+
for (const resolve of waiting) resolve(meta.threadId);
|
|
5550
|
+
}
|
|
5305
5551
|
if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
|
|
5306
5552
|
createdThreadId.current = meta.threadId;
|
|
5307
5553
|
const pin = pendingPin.current;
|
|
@@ -5333,27 +5579,10 @@ function useAgentChat(options = {}) {
|
|
|
5333
5579
|
agent: options.agent
|
|
5334
5580
|
} : {},
|
|
5335
5581
|
getBody: /* @__PURE__ */ __name(() => {
|
|
5336
|
-
const current = latest.current;
|
|
5337
|
-
const pageContext = current.getPageContext?.() ?? null;
|
|
5338
5582
|
const regenerate2 = regenerateNext.current;
|
|
5339
5583
|
regenerateNext.current = false;
|
|
5340
|
-
const threadId = currentThreadId();
|
|
5341
|
-
const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
|
|
5342
5584
|
return {
|
|
5343
|
-
|
|
5344
|
-
// new chat) sends the one picked now, not the one this chat mounted with.
|
|
5345
|
-
...current.agent !== void 0 ? {
|
|
5346
|
-
agent: current.agent
|
|
5347
|
-
} : {},
|
|
5348
|
-
...threadId !== void 0 ? {
|
|
5349
|
-
threadId
|
|
5350
|
-
} : {},
|
|
5351
|
-
...model !== void 0 ? {
|
|
5352
|
-
model
|
|
5353
|
-
} : {},
|
|
5354
|
-
...pageContext ? {
|
|
5355
|
-
pageContext
|
|
5356
|
-
} : {},
|
|
5585
|
+
...turnBody(),
|
|
5357
5586
|
...regenerate2 ? {
|
|
5358
5587
|
regenerate: true
|
|
5359
5588
|
} : {}
|
|
@@ -5363,7 +5592,11 @@ function useAgentChat(options = {}) {
|
|
|
5363
5592
|
onMeta,
|
|
5364
5593
|
onAttemptStart: /* @__PURE__ */ __name(() => {
|
|
5365
5594
|
settlingRunIdRef.current = void 0;
|
|
5366
|
-
}, "onAttemptStart")
|
|
5595
|
+
}, "onAttemptStart"),
|
|
5596
|
+
// A queued turn this chat went to attach to had already finished: read what it wrote.
|
|
5597
|
+
onResumeGone: /* @__PURE__ */ __name(() => {
|
|
5598
|
+
void resyncFromThread();
|
|
5599
|
+
}, "onResumeGone")
|
|
5367
5600
|
});
|
|
5368
5601
|
}, []);
|
|
5369
5602
|
const backgroundRef = useRef12(() => Promise.resolve());
|
|
@@ -5376,6 +5609,9 @@ function useAgentChat(options = {}) {
|
|
|
5376
5609
|
} : {},
|
|
5377
5610
|
onData: /* @__PURE__ */ __name((part) => {
|
|
5378
5611
|
latest.current.onData?.(part);
|
|
5612
|
+
if (part.type === "data-queue") {
|
|
5613
|
+
onQueueData(part.data);
|
|
5614
|
+
}
|
|
5379
5615
|
if (part.type === "data-title") {
|
|
5380
5616
|
const title = part.data?.title;
|
|
5381
5617
|
if (typeof title === "string") {
|
|
@@ -5407,6 +5643,20 @@ function useAgentChat(options = {}) {
|
|
|
5407
5643
|
setConnection({
|
|
5408
5644
|
status: "live"
|
|
5409
5645
|
});
|
|
5646
|
+
const queuedInstead = queuedSend.current;
|
|
5647
|
+
if (queuedInstead !== void 0) {
|
|
5648
|
+
queuedSend.current = void 0;
|
|
5649
|
+
chatRef.current.setMessages((current) => {
|
|
5650
|
+
const last = lastUserIndex(current);
|
|
5651
|
+
return last === -1 ? current : current.slice(0, last);
|
|
5652
|
+
});
|
|
5653
|
+
}
|
|
5654
|
+
const next = handoff.current;
|
|
5655
|
+
handoff.current = void 0;
|
|
5656
|
+
if (next !== void 0) {
|
|
5657
|
+
attachTo(next);
|
|
5658
|
+
return;
|
|
5659
|
+
}
|
|
5410
5660
|
if (resyncAfterSettle.current) {
|
|
5411
5661
|
resyncAfterSettle.current = false;
|
|
5412
5662
|
void resyncFromThread();
|
|
@@ -5415,6 +5665,46 @@ function useAgentChat(options = {}) {
|
|
|
5415
5665
|
});
|
|
5416
5666
|
const chatRef = useRef12(chat);
|
|
5417
5667
|
chatRef.current = chat;
|
|
5668
|
+
function onQueueData(data) {
|
|
5669
|
+
const frame = data ?? {};
|
|
5670
|
+
const before = queueStateRef.current;
|
|
5671
|
+
if (frame.queue !== void 0) applyQueue(frame.queue);
|
|
5672
|
+
const started = frame.started;
|
|
5673
|
+
if (started !== void 0 && started.runId !== settlingRunIdRef.current) {
|
|
5674
|
+
const waiting = before.items.find((item) => item.id === started.messageId);
|
|
5675
|
+
handoff.current = {
|
|
5676
|
+
...started,
|
|
5677
|
+
text: waiting?.content ?? "",
|
|
5678
|
+
attachments: waiting?.attachments ?? []
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
if (frame.queuedSend !== void 0) {
|
|
5682
|
+
queuedSend.current = frame.queuedSend;
|
|
5683
|
+
if (frame.queuedSend.runId !== void 0) {
|
|
5684
|
+
handoff.current = {
|
|
5685
|
+
messageId: frame.queuedSend.messageId,
|
|
5686
|
+
runId: frame.queuedSend.runId,
|
|
5687
|
+
...draftOf(chatRef.current.messages[lastUserIndex(chatRef.current.messages)])
|
|
5688
|
+
};
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
__name(onQueueData, "onQueueData");
|
|
5693
|
+
function attachTo(turn) {
|
|
5694
|
+
setTimeout(() => {
|
|
5695
|
+
if (turn.text.length > 0 || turn.attachments.length > 0) {
|
|
5696
|
+
chatRef.current.setMessages((current) => current.some((message) => message.id === turn.messageId) ? current : [
|
|
5697
|
+
...current,
|
|
5698
|
+
queuedUserMessage(turn)
|
|
5699
|
+
]);
|
|
5700
|
+
} else {
|
|
5701
|
+
resyncAfterSettle.current = true;
|
|
5702
|
+
}
|
|
5703
|
+
autoResumeRunIdRef.current = turn.runId;
|
|
5704
|
+
void chatRef.current.resumeStream();
|
|
5705
|
+
}, 0);
|
|
5706
|
+
}
|
|
5707
|
+
__name(attachTo, "attachTo");
|
|
5418
5708
|
async function resyncFromThread() {
|
|
5419
5709
|
const threadId = currentThreadId();
|
|
5420
5710
|
if (threadId === void 0) return;
|
|
@@ -5437,6 +5727,11 @@ function useAgentChat(options = {}) {
|
|
|
5437
5727
|
setHistoryError(null);
|
|
5438
5728
|
autoResumeRunIdRef.current = void 0;
|
|
5439
5729
|
pendingPin.current = void 0;
|
|
5730
|
+
applyQueue(EMPTY_QUEUE);
|
|
5731
|
+
setPendingSends([]);
|
|
5732
|
+
setQueueError(null);
|
|
5733
|
+
handoff.current = void 0;
|
|
5734
|
+
queuedSend.current = void 0;
|
|
5440
5735
|
}, [
|
|
5441
5736
|
chatId
|
|
5442
5737
|
]);
|
|
@@ -5456,6 +5751,11 @@ function useAgentChat(options = {}) {
|
|
|
5456
5751
|
setActiveRunId(active);
|
|
5457
5752
|
setThreadModel(thread.model ?? null);
|
|
5458
5753
|
setHistoryError(null);
|
|
5754
|
+
const queue2 = thread.queue ?? EMPTY_QUEUE;
|
|
5755
|
+
applyQueue(queue2);
|
|
5756
|
+
if (queue2.items.length > 0 && queue2.paused === null && active === null && typeof client.resumeQueue === "function") {
|
|
5757
|
+
void resumeQueueRef.current().catch(() => void 0);
|
|
5758
|
+
}
|
|
5459
5759
|
const resuming = wantResume && active !== null;
|
|
5460
5760
|
if (wantHistory && chatRef.current.messages.length === 0) {
|
|
5461
5761
|
const rows = resuming ? stored.filter((message) => !(message.role === "assistant" && message.runId === active)) : stored;
|
|
@@ -5491,10 +5791,98 @@ function useAgentChat(options = {}) {
|
|
|
5491
5791
|
const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
|
|
5492
5792
|
const blockedRef = useRef12(blocked);
|
|
5493
5793
|
blockedRef.current = blocked;
|
|
5794
|
+
const whileRunning = typeof client.enqueueMessage === "function" ? options.whileRunning ?? "queue" : "block";
|
|
5795
|
+
const whileRunningRef = useRef12(whileRunning);
|
|
5796
|
+
whileRunningRef.current = whileRunning;
|
|
5797
|
+
const threadForQueue = useCallback11(async () => {
|
|
5798
|
+
const known = currentThreadId();
|
|
5799
|
+
if (known !== void 0) return known;
|
|
5800
|
+
if (!isTurnInFlight()) throw new Error("queue: this chat has no thread yet");
|
|
5801
|
+
return new Promise((resolve) => threadWaiters.current.push(resolve));
|
|
5802
|
+
}, [
|
|
5803
|
+
currentThreadId,
|
|
5804
|
+
isTurnInFlight
|
|
5805
|
+
]);
|
|
5806
|
+
const queueCall = useCallback11(async (run) => {
|
|
5807
|
+
try {
|
|
5808
|
+
const result = await run();
|
|
5809
|
+
setQueueError(null);
|
|
5810
|
+
return result;
|
|
5811
|
+
} catch (error) {
|
|
5812
|
+
setQueueError(error instanceof Error ? error : new Error(String(error)));
|
|
5813
|
+
throw error;
|
|
5814
|
+
}
|
|
5815
|
+
}, []);
|
|
5816
|
+
const enqueue = useCallback11(async (text2, { attachments = [], mode = "queue" } = {}) => {
|
|
5817
|
+
const block = blockedRef.current;
|
|
5818
|
+
if (block != null) throw new QuotaBlockedError(block);
|
|
5819
|
+
const send = client.enqueueMessage;
|
|
5820
|
+
if (typeof send !== "function") throw new AgentBackendUnsupportedError("enqueueMessage");
|
|
5821
|
+
localQueueSequence += 1;
|
|
5822
|
+
const localId = `local-queued-${localQueueSequence}`;
|
|
5823
|
+
setPendingSends((current) => [
|
|
5824
|
+
...current,
|
|
5825
|
+
{
|
|
5826
|
+
id: localId,
|
|
5827
|
+
text: text2,
|
|
5828
|
+
attachments,
|
|
5829
|
+
state: "sending",
|
|
5830
|
+
interrupt: mode === "interrupt",
|
|
5831
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5832
|
+
}
|
|
5833
|
+
]);
|
|
5834
|
+
try {
|
|
5835
|
+
await queueCall(async () => {
|
|
5836
|
+
const threadId = await threadForQueue();
|
|
5837
|
+
const result = await send.call(client, {
|
|
5838
|
+
body: {
|
|
5839
|
+
...turnBody(),
|
|
5840
|
+
threadId,
|
|
5841
|
+
message: text2,
|
|
5842
|
+
mode,
|
|
5843
|
+
...attachments.length > 0 ? {
|
|
5844
|
+
attachments: attachments.map(({ mediaId }) => ({
|
|
5845
|
+
mediaId
|
|
5846
|
+
}))
|
|
5847
|
+
} : {}
|
|
5848
|
+
}
|
|
5849
|
+
});
|
|
5850
|
+
applyQueue(result.queue);
|
|
5851
|
+
if (result.runId !== void 0) {
|
|
5852
|
+
const turn = {
|
|
5853
|
+
messageId: result.messageId,
|
|
5854
|
+
runId: result.runId,
|
|
5855
|
+
text: text2,
|
|
5856
|
+
attachments
|
|
5857
|
+
};
|
|
5858
|
+
if (isTurnInFlight()) handoff.current = turn;
|
|
5859
|
+
else attachTo(turn);
|
|
5860
|
+
}
|
|
5861
|
+
});
|
|
5862
|
+
} finally {
|
|
5863
|
+
setPendingSends((current) => current.filter((item) => item.id !== localId));
|
|
5864
|
+
}
|
|
5865
|
+
}, [
|
|
5866
|
+
client,
|
|
5867
|
+
queueCall,
|
|
5868
|
+
threadForQueue,
|
|
5869
|
+
turnBody,
|
|
5870
|
+
applyQueue,
|
|
5871
|
+
isTurnInFlight
|
|
5872
|
+
]);
|
|
5494
5873
|
const sendMessage = useCallback11(async (...args) => {
|
|
5495
5874
|
const block = blockedRef.current;
|
|
5496
5875
|
if (block != null) throw new QuotaBlockedError(block);
|
|
5497
|
-
if (isTurnInFlight())
|
|
5876
|
+
if (isTurnInFlight()) {
|
|
5877
|
+
const mode = whileRunningRef.current;
|
|
5878
|
+
if (mode === "block") return;
|
|
5879
|
+
const draft = draftOf(args[0]);
|
|
5880
|
+
await enqueue(draft.text, {
|
|
5881
|
+
attachments: draft.attachments,
|
|
5882
|
+
mode
|
|
5883
|
+
});
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5498
5886
|
turnInFlight.current = true;
|
|
5499
5887
|
try {
|
|
5500
5888
|
await chatRef.current.sendMessage(...args);
|
|
@@ -5502,8 +5890,122 @@ function useAgentChat(options = {}) {
|
|
|
5502
5890
|
turnInFlight.current = false;
|
|
5503
5891
|
}
|
|
5504
5892
|
}, [
|
|
5893
|
+
isTurnInFlight,
|
|
5894
|
+
enqueue
|
|
5895
|
+
]);
|
|
5896
|
+
const removeQueued = useCallback11(async (id) => {
|
|
5897
|
+
const previous = queueStateRef.current;
|
|
5898
|
+
applyQueue({
|
|
5899
|
+
...previous,
|
|
5900
|
+
items: previous.items.filter((item) => item.id !== id)
|
|
5901
|
+
});
|
|
5902
|
+
try {
|
|
5903
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "removeQueuedMessage")(id)));
|
|
5904
|
+
} catch (error) {
|
|
5905
|
+
applyQueue(previous);
|
|
5906
|
+
throw error;
|
|
5907
|
+
}
|
|
5908
|
+
}, [
|
|
5909
|
+
client,
|
|
5910
|
+
queueCall,
|
|
5911
|
+
applyQueue
|
|
5912
|
+
]);
|
|
5913
|
+
const editQueued = useCallback11(async (id, text2) => {
|
|
5914
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "updateQueuedMessage")(id, {
|
|
5915
|
+
message: text2
|
|
5916
|
+
})));
|
|
5917
|
+
}, [
|
|
5918
|
+
client,
|
|
5919
|
+
queueCall,
|
|
5920
|
+
applyQueue
|
|
5921
|
+
]);
|
|
5922
|
+
const moveQueued = useCallback11(async (id, index) => {
|
|
5923
|
+
const previous = queueStateRef.current;
|
|
5924
|
+
const moving = previous.items.find((item) => item.id === id);
|
|
5925
|
+
if (moving !== void 0) {
|
|
5926
|
+
const rest = previous.items.filter((item) => item.id !== id);
|
|
5927
|
+
rest.splice(Math.max(0, Math.min(rest.length, index)), 0, moving);
|
|
5928
|
+
applyQueue({
|
|
5929
|
+
...previous,
|
|
5930
|
+
items: rest
|
|
5931
|
+
});
|
|
5932
|
+
}
|
|
5933
|
+
try {
|
|
5934
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "updateQueuedMessage")(id, {
|
|
5935
|
+
position: index
|
|
5936
|
+
})));
|
|
5937
|
+
} catch (error) {
|
|
5938
|
+
applyQueue(previous);
|
|
5939
|
+
throw error;
|
|
5940
|
+
}
|
|
5941
|
+
}, [
|
|
5942
|
+
client,
|
|
5943
|
+
queueCall,
|
|
5944
|
+
applyQueue
|
|
5945
|
+
]);
|
|
5946
|
+
const clearQueue = useCallback11(async () => {
|
|
5947
|
+
const threadId = currentThreadId();
|
|
5948
|
+
if (threadId === void 0) return;
|
|
5949
|
+
applyQueue(await queueCall(() => requireBackendMethod(client, "clearQueue")(threadId)));
|
|
5950
|
+
}, [
|
|
5951
|
+
client,
|
|
5952
|
+
queueCall,
|
|
5953
|
+
currentThreadId,
|
|
5954
|
+
applyQueue
|
|
5955
|
+
]);
|
|
5956
|
+
const resumeQueue = useCallback11(async () => {
|
|
5957
|
+
const threadId = currentThreadId();
|
|
5958
|
+
if (threadId === void 0) return;
|
|
5959
|
+
const before = queueStateRef.current;
|
|
5960
|
+
const result = await queueCall(() => requireBackendMethod(client, "resumeQueue")(threadId));
|
|
5961
|
+
applyQueue({
|
|
5962
|
+
items: result.items,
|
|
5963
|
+
paused: result.paused
|
|
5964
|
+
});
|
|
5965
|
+
if (result.runId !== void 0) {
|
|
5966
|
+
const head = before.items.find((item) => item.id === result.runId) ?? before.items[0];
|
|
5967
|
+
const turn = {
|
|
5968
|
+
messageId: result.runId,
|
|
5969
|
+
runId: result.runId,
|
|
5970
|
+
text: head?.content ?? "",
|
|
5971
|
+
attachments: head?.attachments ?? []
|
|
5972
|
+
};
|
|
5973
|
+
if (isTurnInFlight()) handoff.current = turn;
|
|
5974
|
+
else attachTo(turn);
|
|
5975
|
+
}
|
|
5976
|
+
}, [
|
|
5977
|
+
client,
|
|
5978
|
+
queueCall,
|
|
5979
|
+
currentThreadId,
|
|
5980
|
+
applyQueue,
|
|
5505
5981
|
isTurnInFlight
|
|
5506
5982
|
]);
|
|
5983
|
+
const resumeQueueRef = useRef12(resumeQueue);
|
|
5984
|
+
resumeQueueRef.current = resumeQueue;
|
|
5985
|
+
const queuedItems = [
|
|
5986
|
+
...pendingSends.filter((item) => item.interrupt),
|
|
5987
|
+
...queueState.items.map((item) => ({
|
|
5988
|
+
id: item.id,
|
|
5989
|
+
text: item.content,
|
|
5990
|
+
attachments: item.attachments ?? [],
|
|
5991
|
+
state: "queued",
|
|
5992
|
+
interrupt: item.interrupt === true,
|
|
5993
|
+
createdAt: item.createdAt
|
|
5994
|
+
})),
|
|
5995
|
+
...pendingSends.filter((item) => !item.interrupt)
|
|
5996
|
+
];
|
|
5997
|
+
const queue = {
|
|
5998
|
+
items: queuedItems,
|
|
5999
|
+
paused: queueState.paused,
|
|
6000
|
+
isSupported: typeof client.enqueueMessage === "function",
|
|
6001
|
+
add: enqueue,
|
|
6002
|
+
remove: removeQueued,
|
|
6003
|
+
edit: editQueued,
|
|
6004
|
+
move: moveQueued,
|
|
6005
|
+
clear: clearQueue,
|
|
6006
|
+
resume: resumeQueue,
|
|
6007
|
+
error: queueError
|
|
6008
|
+
};
|
|
5507
6009
|
const addToolResult = useCallback11(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
|
|
5508
6010
|
tool,
|
|
5509
6011
|
toolCallId,
|
|
@@ -5744,12 +6246,14 @@ function useAgentChat(options = {}) {
|
|
|
5744
6246
|
};
|
|
5745
6247
|
const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
|
|
5746
6248
|
const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
|
|
6249
|
+
const isBusyRef = useRef12(isBusy);
|
|
6250
|
+
isBusyRef.current = isBusy;
|
|
5747
6251
|
const files = useAttachments({
|
|
5748
6252
|
...options.composer,
|
|
5749
6253
|
backend: client
|
|
5750
6254
|
});
|
|
5751
6255
|
const [text, setText] = useState15("");
|
|
5752
|
-
const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
|
|
6256
|
+
const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy && whileRunning === "block" ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
|
|
5753
6257
|
const composerRef = useRef12({
|
|
5754
6258
|
text,
|
|
5755
6259
|
files,
|
|
@@ -5780,6 +6284,17 @@ function useAgentChat(options = {}) {
|
|
|
5780
6284
|
const draft = current.text.trim();
|
|
5781
6285
|
setText("");
|
|
5782
6286
|
current.files.clear();
|
|
6287
|
+
const mode = whileRunningRef.current;
|
|
6288
|
+
if (mode !== "block" && (isTurnInFlight() || isBusyRef.current)) {
|
|
6289
|
+
const attachments = current.files.items.flatMap((item) => item.status === "ready" && item.attachment !== void 0 ? [
|
|
6290
|
+
item.attachment
|
|
6291
|
+
] : []);
|
|
6292
|
+
await enqueue(draft, {
|
|
6293
|
+
attachments,
|
|
6294
|
+
mode
|
|
6295
|
+
});
|
|
6296
|
+
return;
|
|
6297
|
+
}
|
|
5783
6298
|
await sendMessage(files2.length > 0 ? {
|
|
5784
6299
|
text: draft,
|
|
5785
6300
|
files: files2
|
|
@@ -5791,7 +6306,9 @@ function useAgentChat(options = {}) {
|
|
|
5791
6306
|
}
|
|
5792
6307
|
} : void 0);
|
|
5793
6308
|
}, [
|
|
5794
|
-
sendMessage
|
|
6309
|
+
sendMessage,
|
|
6310
|
+
enqueue,
|
|
6311
|
+
isTurnInFlight
|
|
5795
6312
|
]);
|
|
5796
6313
|
const composer = {
|
|
5797
6314
|
text,
|
|
@@ -5826,6 +6343,11 @@ function useAgentChat(options = {}) {
|
|
|
5826
6343
|
...ownCatalog === void 0 ? {
|
|
5827
6344
|
toolCatalog: tools.catalog
|
|
5828
6345
|
} : {},
|
|
6346
|
+
queue: {
|
|
6347
|
+
items: queue.items,
|
|
6348
|
+
paused: queue.paused,
|
|
6349
|
+
remove: removeQueued
|
|
6350
|
+
},
|
|
5829
6351
|
...options.transcript
|
|
5830
6352
|
});
|
|
5831
6353
|
const background = {
|
|
@@ -5860,6 +6382,11 @@ function useAgentChat(options = {}) {
|
|
|
5860
6382
|
transcript,
|
|
5861
6383
|
/** A headless composer: draft, files, send gate, `submit()`. */
|
|
5862
6384
|
composer,
|
|
6385
|
+
/**
|
|
6386
|
+
* Messages sent while a turn was running, waiting their turn (server-side): list, edit, move,
|
|
6387
|
+
* remove, clear, and resume a paused queue.
|
|
6388
|
+
*/
|
|
6389
|
+
queue,
|
|
5863
6390
|
/** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
|
|
5864
6391
|
models,
|
|
5865
6392
|
/** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
|