@meshagent/meshagent-tailwind 0.39.5 → 0.39.6
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 +6 -0
- package/dist/cjs/Chat.js +12 -2
- package/dist/cjs/chat-hooks.d.ts +5 -2
- package/dist/cjs/chat-hooks.js +245 -26
- package/dist/esm/Chat.js +12 -2
- package/dist/esm/chat-hooks.d.ts +5 -2
- package/dist/esm/chat-hooks.js +245 -26
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.39.6]
|
|
2
|
+
- Chat thread message sending now supports an “agent messages” mode: it selects participants that advertise agent-message support and sends `agent-message` payloads using `meshagent.agent.turn.start` / `meshagent.agent.turn.steer` types (including turn/thread scoping), with Promise-based sending and cancellation when recipients never materialize.
|
|
3
|
+
- Chat UI/logic now determines the correct outbound message type (chat vs steer) and turn context from thread status, and passes that into message sending.
|
|
4
|
+
- DatasetsClient now adds strongly typed `importFromStorage` and `exportToStorage` APIs with dataset storage format + import mode options, optional `namespace`/`branch` scoping, and `batch_size` support that is omitted when unset.
|
|
5
|
+
- Unit tests were updated to verify the new import/export request payload shapes and defaults.
|
|
6
|
+
|
|
1
7
|
## [0.39.5]
|
|
2
8
|
- Stability
|
|
3
9
|
|
package/dist/cjs/Chat.js
CHANGED
|
@@ -152,6 +152,9 @@ function ResolvedChatView({
|
|
|
152
152
|
showNewThreadButton = false,
|
|
153
153
|
onStartNewThread
|
|
154
154
|
}) {
|
|
155
|
+
const threadStatus = (0, import_chat_hooks.useThreadStatus)({ room, path, agentName });
|
|
156
|
+
const useAgentMessages = threadStatus.supportsAgentMessages;
|
|
157
|
+
const messageType = useAgentMessages && threadStatus.mode === "steerable" && threadStatus.turnId ? "steer" : "chat";
|
|
155
158
|
const {
|
|
156
159
|
document,
|
|
157
160
|
messages,
|
|
@@ -162,9 +165,16 @@ function ResolvedChatView({
|
|
|
162
165
|
onlineParticipants,
|
|
163
166
|
localParticipantName,
|
|
164
167
|
cancelRequest
|
|
165
|
-
} = (0, import_chat_hooks.useChatThread)({
|
|
168
|
+
} = (0, import_chat_hooks.useChatThread)({
|
|
169
|
+
room,
|
|
170
|
+
path,
|
|
171
|
+
participants,
|
|
172
|
+
agentName,
|
|
173
|
+
useAgentMessages,
|
|
174
|
+
messageType,
|
|
175
|
+
turnId: threadStatus.turnId
|
|
176
|
+
});
|
|
166
177
|
const { typing, thinking } = (0, import_meshagent_react.useRoomIndicators)({ room, path });
|
|
167
|
-
const threadStatus = (0, import_chat_hooks.useThreadStatus)({ room, path, agentName });
|
|
168
178
|
const [showCompletedToolCalls, setShowCompletedToolCalls] = (0, import_react.useState)(false);
|
|
169
179
|
const onTextChange = (0, import_react.useCallback)(() => {
|
|
170
180
|
for (const participant of onlineParticipants) {
|
package/dist/cjs/chat-hooks.d.ts
CHANGED
|
@@ -9,11 +9,14 @@ export interface UseChatThreadProps {
|
|
|
9
9
|
includeLocalParticipant?: boolean;
|
|
10
10
|
initialMessage?: ChatMessage;
|
|
11
11
|
agentName?: string;
|
|
12
|
+
useAgentMessages?: boolean;
|
|
13
|
+
messageType?: string;
|
|
14
|
+
turnId?: string;
|
|
12
15
|
}
|
|
13
16
|
export interface UseChatThreadResult {
|
|
14
17
|
document: MeshDocument | null;
|
|
15
18
|
messages: Element[];
|
|
16
|
-
sendMessage: (message: ChatMessage) => void
|
|
19
|
+
sendMessage: (message: ChatMessage) => Promise<void>;
|
|
17
20
|
selectAttachments: (files: File[]) => void;
|
|
18
21
|
attachments: FileUpload[];
|
|
19
22
|
setAttachments: (attachments: FileUpload[]) => void;
|
|
@@ -70,5 +73,5 @@ export interface UseThreadStatusProps {
|
|
|
70
73
|
previous?: ChatThreadStatusState;
|
|
71
74
|
}
|
|
72
75
|
export declare function formatThreadStatusText(text: string, startedAt?: Date | null): string;
|
|
73
|
-
export declare function useChatThread({ room, path, participants, participantNames, initialMessage, includeLocalParticipant, agentName, }: UseChatThreadProps): UseChatThreadResult;
|
|
76
|
+
export declare function useChatThread({ room, path, participants, participantNames, initialMessage, includeLocalParticipant, agentName, useAgentMessages, messageType, turnId, }: UseChatThreadProps): UseChatThreadResult;
|
|
74
77
|
export declare function useThreadStatus({ room, path, agentName }: UseThreadStatusProps): ThreadStatus;
|
package/dist/cjs/chat-hooks.js
CHANGED
|
@@ -29,7 +29,15 @@ var import_react = require("react");
|
|
|
29
29
|
var import_meshagent = require("@meshagent/meshagent");
|
|
30
30
|
var import_meshagent_react = require("@meshagent/meshagent-react");
|
|
31
31
|
var import_file_attachment = require("./file-attachment");
|
|
32
|
+
const agentRoomMessageType = "agent-message";
|
|
33
|
+
const agentTurnStartType = "meshagent.agent.turn.start";
|
|
32
34
|
const agentTurnSteerType = "meshagent.agent.turn.steer";
|
|
35
|
+
class ChatSendCancelledError extends Error {
|
|
36
|
+
constructor() {
|
|
37
|
+
super("chat send cancelled");
|
|
38
|
+
this.name = "ChatSendCancelledError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
33
41
|
class PendingAgentMessage {
|
|
34
42
|
messageId;
|
|
35
43
|
messageType;
|
|
@@ -202,6 +210,175 @@ function getOnlineParticipants(roomParticipants, participantNames) {
|
|
|
202
210
|
function supportsAgentMessages(participant) {
|
|
203
211
|
return participant.getAttribute("supports_agent_messages") === true;
|
|
204
212
|
}
|
|
213
|
+
function uniqueRemoteParticipantsById(participants) {
|
|
214
|
+
const seenParticipantIds = /* @__PURE__ */ new Set();
|
|
215
|
+
const uniqueParticipants = [];
|
|
216
|
+
for (const participant of participants) {
|
|
217
|
+
if (seenParticipantIds.has(participant.id)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
seenParticipantIds.add(participant.id);
|
|
221
|
+
uniqueParticipants.push(participant);
|
|
222
|
+
}
|
|
223
|
+
return uniqueParticipants;
|
|
224
|
+
}
|
|
225
|
+
function getAgentParticipants({
|
|
226
|
+
room,
|
|
227
|
+
document,
|
|
228
|
+
participantName
|
|
229
|
+
}) {
|
|
230
|
+
const normalizedParticipantName = participantName?.trim();
|
|
231
|
+
return uniqueRemoteParticipantsById(
|
|
232
|
+
getOnlineParticipants(room.messaging.remoteParticipants, getDocumentParticipantNames(document)).filter((participant) => {
|
|
233
|
+
if (normalizedParticipantName && getParticipantName(participant) !== normalizedParticipantName) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return supportsAgentMessages(participant);
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
function matchingRecipients({
|
|
241
|
+
room,
|
|
242
|
+
document,
|
|
243
|
+
useAgentMessages,
|
|
244
|
+
participantName
|
|
245
|
+
}) {
|
|
246
|
+
const normalizedParticipantName = participantName?.trim();
|
|
247
|
+
if (useAgentMessages) {
|
|
248
|
+
return getAgentParticipants({ room, document, participantName: normalizedParticipantName });
|
|
249
|
+
}
|
|
250
|
+
return uniqueRemoteParticipantsById(
|
|
251
|
+
getOnlineParticipants(room.messaging.remoteParticipants, getDocumentParticipantNames(document)).filter((participant) => {
|
|
252
|
+
if (!normalizedParticipantName) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return getParticipantName(participant) === normalizedParticipantName;
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
function normalizeAgentAttachmentUrl(path) {
|
|
260
|
+
const trimmedPath = path.trim();
|
|
261
|
+
if (trimmedPath === "") {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const url = new URL(trimmedPath);
|
|
266
|
+
if (url.protocol !== "") {
|
|
267
|
+
return trimmedPath;
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
const roomPath = trimmedPath.startsWith("/") ? trimmedPath.slice(1) : trimmedPath;
|
|
272
|
+
return roomPath === "" ? null : `room:///${roomPath}`;
|
|
273
|
+
}
|
|
274
|
+
function agentInputContentFromMessage(message) {
|
|
275
|
+
const content = [];
|
|
276
|
+
if (message.text.trim() !== "") {
|
|
277
|
+
content.push({ type: "text", text: message.text });
|
|
278
|
+
}
|
|
279
|
+
for (const attachmentPath of message.attachments) {
|
|
280
|
+
const normalizedUrl = normalizeAgentAttachmentUrl(attachmentPath);
|
|
281
|
+
if (normalizedUrl !== null) {
|
|
282
|
+
content.push({ type: "file", url: normalizedUrl });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return content;
|
|
286
|
+
}
|
|
287
|
+
async function sendMessageToParticipant({
|
|
288
|
+
room,
|
|
289
|
+
participant,
|
|
290
|
+
path,
|
|
291
|
+
message,
|
|
292
|
+
messageType = "chat",
|
|
293
|
+
useAgentMessages = false,
|
|
294
|
+
turnId,
|
|
295
|
+
store = false
|
|
296
|
+
}) {
|
|
297
|
+
if (message.text.trim() === "" && message.attachments.length === 0) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (useAgentMessages) {
|
|
301
|
+
const isSteer = messageType === "steer";
|
|
302
|
+
const payload = {
|
|
303
|
+
type: isSteer ? agentTurnSteerType : agentTurnStartType,
|
|
304
|
+
thread_id: path,
|
|
305
|
+
message_id: message.id,
|
|
306
|
+
content: agentInputContentFromMessage(message)
|
|
307
|
+
};
|
|
308
|
+
if (isSteer && turnId?.trim()) {
|
|
309
|
+
payload.turn_id = turnId.trim();
|
|
310
|
+
}
|
|
311
|
+
await room.messaging.sendMessage({
|
|
312
|
+
to: participant,
|
|
313
|
+
type: agentRoomMessageType,
|
|
314
|
+
message: { payload }
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await room.messaging.sendMessage({
|
|
319
|
+
to: participant,
|
|
320
|
+
type: messageType,
|
|
321
|
+
message: {
|
|
322
|
+
path,
|
|
323
|
+
text: message.text,
|
|
324
|
+
attachments: message.attachments.map((attachmentPath) => ({ path: attachmentPath })),
|
|
325
|
+
store
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function waitForRecipients({
|
|
330
|
+
room,
|
|
331
|
+
document,
|
|
332
|
+
messageId,
|
|
333
|
+
useAgentMessages,
|
|
334
|
+
participantName,
|
|
335
|
+
pendingRecipientWaits
|
|
336
|
+
}) {
|
|
337
|
+
pendingRecipientWaits.get(messageId)?.();
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
let finished = false;
|
|
340
|
+
const eventNames = [
|
|
341
|
+
"participant_added",
|
|
342
|
+
"participant_removed",
|
|
343
|
+
"participant_attributes_updated",
|
|
344
|
+
"messaging_enabled"
|
|
345
|
+
];
|
|
346
|
+
const finish = () => {
|
|
347
|
+
if (finished) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
finished = true;
|
|
351
|
+
for (const eventName of eventNames) {
|
|
352
|
+
room.messaging.off(eventName, listener);
|
|
353
|
+
}
|
|
354
|
+
if (pendingRecipientWaits.get(messageId) === cancel) {
|
|
355
|
+
pendingRecipientWaits.delete(messageId);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const cancel = () => {
|
|
359
|
+
finish();
|
|
360
|
+
reject(new ChatSendCancelledError());
|
|
361
|
+
};
|
|
362
|
+
const listener = () => {
|
|
363
|
+
const recipients = matchingRecipients({
|
|
364
|
+
room,
|
|
365
|
+
document,
|
|
366
|
+
useAgentMessages,
|
|
367
|
+
participantName
|
|
368
|
+
});
|
|
369
|
+
if (recipients.length === 0) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
finish();
|
|
373
|
+
resolve(recipients);
|
|
374
|
+
};
|
|
375
|
+
pendingRecipientWaits.set(messageId, cancel);
|
|
376
|
+
for (const eventName of eventNames) {
|
|
377
|
+
room.messaging.on(eventName, listener);
|
|
378
|
+
}
|
|
379
|
+
listener();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
205
382
|
function threadStatusAttributeCandidates(path, prefix) {
|
|
206
383
|
if (path.startsWith("/")) {
|
|
207
384
|
return [`${prefix}.${path}`, `${prefix}.${path.slice(1)}`];
|
|
@@ -387,13 +564,17 @@ function useChatThread({
|
|
|
387
564
|
participantNames,
|
|
388
565
|
initialMessage,
|
|
389
566
|
includeLocalParticipant,
|
|
390
|
-
agentName
|
|
567
|
+
agentName,
|
|
568
|
+
useAgentMessages = false,
|
|
569
|
+
messageType = "chat",
|
|
570
|
+
turnId
|
|
391
571
|
}) {
|
|
392
572
|
const [document, setDocument] = (0, import_react.useState)(null);
|
|
393
573
|
const [messages, setMessages] = (0, import_react.useState)(() => document ? mapThreadElements(document) : []);
|
|
394
574
|
const [attachments, setAttachments] = (0, import_react.useState)([]);
|
|
395
575
|
const [documentParticipantNames, setDocumentParticipantNames] = (0, import_react.useState)(() => document ? getDocumentParticipantNames(document) : []);
|
|
396
576
|
const initialMessageSentRef = (0, import_react.useRef)(false);
|
|
577
|
+
const pendingRecipientWaitsRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
397
578
|
const syncDocumentState = (0, import_react.useCallback)((nextDocument) => {
|
|
398
579
|
const nextMessages = mapThreadElements(nextDocument);
|
|
399
580
|
const nextParticipantNames = getDocumentParticipantNames(nextDocument);
|
|
@@ -442,6 +623,14 @@ function useChatThread({
|
|
|
442
623
|
}
|
|
443
624
|
};
|
|
444
625
|
}, [path, room, syncDocumentState]);
|
|
626
|
+
(0, import_react.useEffect)(() => {
|
|
627
|
+
return () => {
|
|
628
|
+
for (const cancel of pendingRecipientWaitsRef.current.values()) {
|
|
629
|
+
cancel();
|
|
630
|
+
}
|
|
631
|
+
pendingRecipientWaitsRef.current.clear();
|
|
632
|
+
};
|
|
633
|
+
}, []);
|
|
445
634
|
(0, import_react.useEffect)(() => {
|
|
446
635
|
if (!document || !room.localParticipant) {
|
|
447
636
|
return;
|
|
@@ -476,38 +665,68 @@ function useChatThread({
|
|
|
476
665
|
() => getOnlineParticipants(roomParticipants, documentParticipantNames),
|
|
477
666
|
[roomParticipants, documentParticipantNames]
|
|
478
667
|
);
|
|
479
|
-
const sendMessage = (0, import_react.useCallback)((message) => {
|
|
668
|
+
const sendMessage = (0, import_react.useCallback)(async (message) => {
|
|
669
|
+
if (message.text.trim() === "" && message.attachments.length === 0) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const normalizedParticipantName = agentName?.trim();
|
|
673
|
+
const storeLocally = !normalizedParticipantName && !useAgentMessages;
|
|
480
674
|
const children = document?.root.getChildren() ?? [];
|
|
481
|
-
const
|
|
482
|
-
if (!
|
|
675
|
+
const messagesElement = children.find((child) => child.tagName === "messages");
|
|
676
|
+
if (!document || !messagesElement) {
|
|
483
677
|
return;
|
|
484
678
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
679
|
+
if (storeLocally) {
|
|
680
|
+
const authorName = getParticipantName(room.localParticipant);
|
|
681
|
+
const messageElement = messagesElement.createChildElement("message", {
|
|
682
|
+
id: message.id,
|
|
683
|
+
text: message.text,
|
|
684
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
685
|
+
author_name: authorName,
|
|
686
|
+
author_ref: null
|
|
687
|
+
});
|
|
688
|
+
for (const attachmentPath of message.attachments) {
|
|
689
|
+
messageElement.createChildElement("file", { path: attachmentPath });
|
|
690
|
+
}
|
|
495
691
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
692
|
+
let recipients = matchingRecipients({
|
|
693
|
+
room,
|
|
694
|
+
document,
|
|
695
|
+
useAgentMessages,
|
|
696
|
+
participantName: normalizedParticipantName
|
|
697
|
+
});
|
|
698
|
+
if (recipients.length === 0) {
|
|
699
|
+
const shouldWaitForRecipient = useAgentMessages || Boolean(normalizedParticipantName);
|
|
700
|
+
if (!shouldWaitForRecipient) {
|
|
701
|
+
throw new Error(`no matching recipients are available for '${path}'`);
|
|
499
702
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
703
|
+
try {
|
|
704
|
+
recipients = await waitForRecipients({
|
|
705
|
+
room,
|
|
706
|
+
document,
|
|
707
|
+
messageId: message.id,
|
|
708
|
+
useAgentMessages,
|
|
709
|
+
participantName: normalizedParticipantName,
|
|
710
|
+
pendingRecipientWaits: pendingRecipientWaitsRef.current
|
|
711
|
+
});
|
|
712
|
+
} catch (error) {
|
|
713
|
+
if (error instanceof ChatSendCancelledError) {
|
|
714
|
+
return;
|
|
507
715
|
}
|
|
508
|
-
|
|
716
|
+
throw error;
|
|
717
|
+
}
|
|
509
718
|
}
|
|
510
|
-
|
|
719
|
+
await Promise.all(recipients.map((participant) => sendMessageToParticipant({
|
|
720
|
+
room,
|
|
721
|
+
participant,
|
|
722
|
+
path,
|
|
723
|
+
message,
|
|
724
|
+
messageType,
|
|
725
|
+
useAgentMessages,
|
|
726
|
+
turnId,
|
|
727
|
+
store: Boolean(normalizedParticipantName) && getParticipantName(participant) === normalizedParticipantName
|
|
728
|
+
})));
|
|
729
|
+
}, [agentName, document, messageType, path, room, turnId, useAgentMessages]);
|
|
511
730
|
(0, import_react.useEffect)(() => {
|
|
512
731
|
if (!document || !initialMessage || initialMessageSentRef.current) {
|
|
513
732
|
return;
|
package/dist/esm/Chat.js
CHANGED
|
@@ -129,6 +129,9 @@ function ResolvedChatView({
|
|
|
129
129
|
showNewThreadButton = false,
|
|
130
130
|
onStartNewThread
|
|
131
131
|
}) {
|
|
132
|
+
const threadStatus = useThreadStatus({ room, path, agentName });
|
|
133
|
+
const useAgentMessages = threadStatus.supportsAgentMessages;
|
|
134
|
+
const messageType = useAgentMessages && threadStatus.mode === "steerable" && threadStatus.turnId ? "steer" : "chat";
|
|
132
135
|
const {
|
|
133
136
|
document,
|
|
134
137
|
messages,
|
|
@@ -139,9 +142,16 @@ function ResolvedChatView({
|
|
|
139
142
|
onlineParticipants,
|
|
140
143
|
localParticipantName,
|
|
141
144
|
cancelRequest
|
|
142
|
-
} = useChatThread({
|
|
145
|
+
} = useChatThread({
|
|
146
|
+
room,
|
|
147
|
+
path,
|
|
148
|
+
participants,
|
|
149
|
+
agentName,
|
|
150
|
+
useAgentMessages,
|
|
151
|
+
messageType,
|
|
152
|
+
turnId: threadStatus.turnId
|
|
153
|
+
});
|
|
143
154
|
const { typing, thinking } = useRoomIndicators({ room, path });
|
|
144
|
-
const threadStatus = useThreadStatus({ room, path, agentName });
|
|
145
155
|
const [showCompletedToolCalls, setShowCompletedToolCalls] = useState(false);
|
|
146
156
|
const onTextChange = useCallback(() => {
|
|
147
157
|
for (const participant of onlineParticipants) {
|
package/dist/esm/chat-hooks.d.ts
CHANGED
|
@@ -9,11 +9,14 @@ export interface UseChatThreadProps {
|
|
|
9
9
|
includeLocalParticipant?: boolean;
|
|
10
10
|
initialMessage?: ChatMessage;
|
|
11
11
|
agentName?: string;
|
|
12
|
+
useAgentMessages?: boolean;
|
|
13
|
+
messageType?: string;
|
|
14
|
+
turnId?: string;
|
|
12
15
|
}
|
|
13
16
|
export interface UseChatThreadResult {
|
|
14
17
|
document: MeshDocument | null;
|
|
15
18
|
messages: Element[];
|
|
16
|
-
sendMessage: (message: ChatMessage) => void
|
|
19
|
+
sendMessage: (message: ChatMessage) => Promise<void>;
|
|
17
20
|
selectAttachments: (files: File[]) => void;
|
|
18
21
|
attachments: FileUpload[];
|
|
19
22
|
setAttachments: (attachments: FileUpload[]) => void;
|
|
@@ -70,5 +73,5 @@ export interface UseThreadStatusProps {
|
|
|
70
73
|
previous?: ChatThreadStatusState;
|
|
71
74
|
}
|
|
72
75
|
export declare function formatThreadStatusText(text: string, startedAt?: Date | null): string;
|
|
73
|
-
export declare function useChatThread({ room, path, participants, participantNames, initialMessage, includeLocalParticipant, agentName, }: UseChatThreadProps): UseChatThreadResult;
|
|
76
|
+
export declare function useChatThread({ room, path, participants, participantNames, initialMessage, includeLocalParticipant, agentName, useAgentMessages, messageType, turnId, }: UseChatThreadProps): UseChatThreadResult;
|
|
74
77
|
export declare function useThreadStatus({ room, path, agentName }: UseThreadStatusProps): ThreadStatus;
|
package/dist/esm/chat-hooks.js
CHANGED
|
@@ -4,7 +4,15 @@ import {
|
|
|
4
4
|
} from "@meshagent/meshagent";
|
|
5
5
|
import { subscribe, useDocumentChanged, useRoomParticipants } from "@meshagent/meshagent-react";
|
|
6
6
|
import { MeshagentFileUpload, fileToAsyncIterable } from "./file-attachment";
|
|
7
|
+
const agentRoomMessageType = "agent-message";
|
|
8
|
+
const agentTurnStartType = "meshagent.agent.turn.start";
|
|
7
9
|
const agentTurnSteerType = "meshagent.agent.turn.steer";
|
|
10
|
+
class ChatSendCancelledError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("chat send cancelled");
|
|
13
|
+
this.name = "ChatSendCancelledError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
8
16
|
class PendingAgentMessage {
|
|
9
17
|
messageId;
|
|
10
18
|
messageType;
|
|
@@ -177,6 +185,175 @@ function getOnlineParticipants(roomParticipants, participantNames) {
|
|
|
177
185
|
function supportsAgentMessages(participant) {
|
|
178
186
|
return participant.getAttribute("supports_agent_messages") === true;
|
|
179
187
|
}
|
|
188
|
+
function uniqueRemoteParticipantsById(participants) {
|
|
189
|
+
const seenParticipantIds = /* @__PURE__ */ new Set();
|
|
190
|
+
const uniqueParticipants = [];
|
|
191
|
+
for (const participant of participants) {
|
|
192
|
+
if (seenParticipantIds.has(participant.id)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
seenParticipantIds.add(participant.id);
|
|
196
|
+
uniqueParticipants.push(participant);
|
|
197
|
+
}
|
|
198
|
+
return uniqueParticipants;
|
|
199
|
+
}
|
|
200
|
+
function getAgentParticipants({
|
|
201
|
+
room,
|
|
202
|
+
document,
|
|
203
|
+
participantName
|
|
204
|
+
}) {
|
|
205
|
+
const normalizedParticipantName = participantName?.trim();
|
|
206
|
+
return uniqueRemoteParticipantsById(
|
|
207
|
+
getOnlineParticipants(room.messaging.remoteParticipants, getDocumentParticipantNames(document)).filter((participant) => {
|
|
208
|
+
if (normalizedParticipantName && getParticipantName(participant) !== normalizedParticipantName) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return supportsAgentMessages(participant);
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function matchingRecipients({
|
|
216
|
+
room,
|
|
217
|
+
document,
|
|
218
|
+
useAgentMessages,
|
|
219
|
+
participantName
|
|
220
|
+
}) {
|
|
221
|
+
const normalizedParticipantName = participantName?.trim();
|
|
222
|
+
if (useAgentMessages) {
|
|
223
|
+
return getAgentParticipants({ room, document, participantName: normalizedParticipantName });
|
|
224
|
+
}
|
|
225
|
+
return uniqueRemoteParticipantsById(
|
|
226
|
+
getOnlineParticipants(room.messaging.remoteParticipants, getDocumentParticipantNames(document)).filter((participant) => {
|
|
227
|
+
if (!normalizedParticipantName) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return getParticipantName(participant) === normalizedParticipantName;
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
function normalizeAgentAttachmentUrl(path) {
|
|
235
|
+
const trimmedPath = path.trim();
|
|
236
|
+
if (trimmedPath === "") {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const url = new URL(trimmedPath);
|
|
241
|
+
if (url.protocol !== "") {
|
|
242
|
+
return trimmedPath;
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
const roomPath = trimmedPath.startsWith("/") ? trimmedPath.slice(1) : trimmedPath;
|
|
247
|
+
return roomPath === "" ? null : `room:///${roomPath}`;
|
|
248
|
+
}
|
|
249
|
+
function agentInputContentFromMessage(message) {
|
|
250
|
+
const content = [];
|
|
251
|
+
if (message.text.trim() !== "") {
|
|
252
|
+
content.push({ type: "text", text: message.text });
|
|
253
|
+
}
|
|
254
|
+
for (const attachmentPath of message.attachments) {
|
|
255
|
+
const normalizedUrl = normalizeAgentAttachmentUrl(attachmentPath);
|
|
256
|
+
if (normalizedUrl !== null) {
|
|
257
|
+
content.push({ type: "file", url: normalizedUrl });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return content;
|
|
261
|
+
}
|
|
262
|
+
async function sendMessageToParticipant({
|
|
263
|
+
room,
|
|
264
|
+
participant,
|
|
265
|
+
path,
|
|
266
|
+
message,
|
|
267
|
+
messageType = "chat",
|
|
268
|
+
useAgentMessages = false,
|
|
269
|
+
turnId,
|
|
270
|
+
store = false
|
|
271
|
+
}) {
|
|
272
|
+
if (message.text.trim() === "" && message.attachments.length === 0) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (useAgentMessages) {
|
|
276
|
+
const isSteer = messageType === "steer";
|
|
277
|
+
const payload = {
|
|
278
|
+
type: isSteer ? agentTurnSteerType : agentTurnStartType,
|
|
279
|
+
thread_id: path,
|
|
280
|
+
message_id: message.id,
|
|
281
|
+
content: agentInputContentFromMessage(message)
|
|
282
|
+
};
|
|
283
|
+
if (isSteer && turnId?.trim()) {
|
|
284
|
+
payload.turn_id = turnId.trim();
|
|
285
|
+
}
|
|
286
|
+
await room.messaging.sendMessage({
|
|
287
|
+
to: participant,
|
|
288
|
+
type: agentRoomMessageType,
|
|
289
|
+
message: { payload }
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
await room.messaging.sendMessage({
|
|
294
|
+
to: participant,
|
|
295
|
+
type: messageType,
|
|
296
|
+
message: {
|
|
297
|
+
path,
|
|
298
|
+
text: message.text,
|
|
299
|
+
attachments: message.attachments.map((attachmentPath) => ({ path: attachmentPath })),
|
|
300
|
+
store
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function waitForRecipients({
|
|
305
|
+
room,
|
|
306
|
+
document,
|
|
307
|
+
messageId,
|
|
308
|
+
useAgentMessages,
|
|
309
|
+
participantName,
|
|
310
|
+
pendingRecipientWaits
|
|
311
|
+
}) {
|
|
312
|
+
pendingRecipientWaits.get(messageId)?.();
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
let finished = false;
|
|
315
|
+
const eventNames = [
|
|
316
|
+
"participant_added",
|
|
317
|
+
"participant_removed",
|
|
318
|
+
"participant_attributes_updated",
|
|
319
|
+
"messaging_enabled"
|
|
320
|
+
];
|
|
321
|
+
const finish = () => {
|
|
322
|
+
if (finished) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
finished = true;
|
|
326
|
+
for (const eventName of eventNames) {
|
|
327
|
+
room.messaging.off(eventName, listener);
|
|
328
|
+
}
|
|
329
|
+
if (pendingRecipientWaits.get(messageId) === cancel) {
|
|
330
|
+
pendingRecipientWaits.delete(messageId);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const cancel = () => {
|
|
334
|
+
finish();
|
|
335
|
+
reject(new ChatSendCancelledError());
|
|
336
|
+
};
|
|
337
|
+
const listener = () => {
|
|
338
|
+
const recipients = matchingRecipients({
|
|
339
|
+
room,
|
|
340
|
+
document,
|
|
341
|
+
useAgentMessages,
|
|
342
|
+
participantName
|
|
343
|
+
});
|
|
344
|
+
if (recipients.length === 0) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
finish();
|
|
348
|
+
resolve(recipients);
|
|
349
|
+
};
|
|
350
|
+
pendingRecipientWaits.set(messageId, cancel);
|
|
351
|
+
for (const eventName of eventNames) {
|
|
352
|
+
room.messaging.on(eventName, listener);
|
|
353
|
+
}
|
|
354
|
+
listener();
|
|
355
|
+
});
|
|
356
|
+
}
|
|
180
357
|
function threadStatusAttributeCandidates(path, prefix) {
|
|
181
358
|
if (path.startsWith("/")) {
|
|
182
359
|
return [`${prefix}.${path}`, `${prefix}.${path.slice(1)}`];
|
|
@@ -362,13 +539,17 @@ function useChatThread({
|
|
|
362
539
|
participantNames,
|
|
363
540
|
initialMessage,
|
|
364
541
|
includeLocalParticipant,
|
|
365
|
-
agentName
|
|
542
|
+
agentName,
|
|
543
|
+
useAgentMessages = false,
|
|
544
|
+
messageType = "chat",
|
|
545
|
+
turnId
|
|
366
546
|
}) {
|
|
367
547
|
const [document, setDocument] = useState(null);
|
|
368
548
|
const [messages, setMessages] = useState(() => document ? mapThreadElements(document) : []);
|
|
369
549
|
const [attachments, setAttachments] = useState([]);
|
|
370
550
|
const [documentParticipantNames, setDocumentParticipantNames] = useState(() => document ? getDocumentParticipantNames(document) : []);
|
|
371
551
|
const initialMessageSentRef = useRef(false);
|
|
552
|
+
const pendingRecipientWaitsRef = useRef(/* @__PURE__ */ new Map());
|
|
372
553
|
const syncDocumentState = useCallback((nextDocument) => {
|
|
373
554
|
const nextMessages = mapThreadElements(nextDocument);
|
|
374
555
|
const nextParticipantNames = getDocumentParticipantNames(nextDocument);
|
|
@@ -417,6 +598,14 @@ function useChatThread({
|
|
|
417
598
|
}
|
|
418
599
|
};
|
|
419
600
|
}, [path, room, syncDocumentState]);
|
|
601
|
+
useEffect(() => {
|
|
602
|
+
return () => {
|
|
603
|
+
for (const cancel of pendingRecipientWaitsRef.current.values()) {
|
|
604
|
+
cancel();
|
|
605
|
+
}
|
|
606
|
+
pendingRecipientWaitsRef.current.clear();
|
|
607
|
+
};
|
|
608
|
+
}, []);
|
|
420
609
|
useEffect(() => {
|
|
421
610
|
if (!document || !room.localParticipant) {
|
|
422
611
|
return;
|
|
@@ -451,38 +640,68 @@ function useChatThread({
|
|
|
451
640
|
() => getOnlineParticipants(roomParticipants, documentParticipantNames),
|
|
452
641
|
[roomParticipants, documentParticipantNames]
|
|
453
642
|
);
|
|
454
|
-
const sendMessage = useCallback((message) => {
|
|
643
|
+
const sendMessage = useCallback(async (message) => {
|
|
644
|
+
if (message.text.trim() === "" && message.attachments.length === 0) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const normalizedParticipantName = agentName?.trim();
|
|
648
|
+
const storeLocally = !normalizedParticipantName && !useAgentMessages;
|
|
455
649
|
const children = document?.root.getChildren() ?? [];
|
|
456
|
-
const
|
|
457
|
-
if (!
|
|
650
|
+
const messagesElement = children.find((child) => child.tagName === "messages");
|
|
651
|
+
if (!document || !messagesElement) {
|
|
458
652
|
return;
|
|
459
653
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
654
|
+
if (storeLocally) {
|
|
655
|
+
const authorName = getParticipantName(room.localParticipant);
|
|
656
|
+
const messageElement = messagesElement.createChildElement("message", {
|
|
657
|
+
id: message.id,
|
|
658
|
+
text: message.text,
|
|
659
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
660
|
+
author_name: authorName,
|
|
661
|
+
author_ref: null
|
|
662
|
+
});
|
|
663
|
+
for (const attachmentPath of message.attachments) {
|
|
664
|
+
messageElement.createChildElement("file", { path: attachmentPath });
|
|
665
|
+
}
|
|
470
666
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
667
|
+
let recipients = matchingRecipients({
|
|
668
|
+
room,
|
|
669
|
+
document,
|
|
670
|
+
useAgentMessages,
|
|
671
|
+
participantName: normalizedParticipantName
|
|
672
|
+
});
|
|
673
|
+
if (recipients.length === 0) {
|
|
674
|
+
const shouldWaitForRecipient = useAgentMessages || Boolean(normalizedParticipantName);
|
|
675
|
+
if (!shouldWaitForRecipient) {
|
|
676
|
+
throw new Error(`no matching recipients are available for '${path}'`);
|
|
474
677
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
678
|
+
try {
|
|
679
|
+
recipients = await waitForRecipients({
|
|
680
|
+
room,
|
|
681
|
+
document,
|
|
682
|
+
messageId: message.id,
|
|
683
|
+
useAgentMessages,
|
|
684
|
+
participantName: normalizedParticipantName,
|
|
685
|
+
pendingRecipientWaits: pendingRecipientWaitsRef.current
|
|
686
|
+
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (error instanceof ChatSendCancelledError) {
|
|
689
|
+
return;
|
|
482
690
|
}
|
|
483
|
-
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
484
693
|
}
|
|
485
|
-
|
|
694
|
+
await Promise.all(recipients.map((participant) => sendMessageToParticipant({
|
|
695
|
+
room,
|
|
696
|
+
participant,
|
|
697
|
+
path,
|
|
698
|
+
message,
|
|
699
|
+
messageType,
|
|
700
|
+
useAgentMessages,
|
|
701
|
+
turnId,
|
|
702
|
+
store: Boolean(normalizedParticipantName) && getParticipantName(participant) === normalizedParticipantName
|
|
703
|
+
})));
|
|
704
|
+
}, [agentName, document, messageType, path, room, turnId, useAgentMessages]);
|
|
486
705
|
useEffect(() => {
|
|
487
706
|
if (!document || !initialMessage || initialMessageSentRef.current) {
|
|
488
707
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meshagent/meshagent-tailwind",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.6",
|
|
4
4
|
"description": "Meshagent Tailwind Components",
|
|
5
5
|
"homepage": "https://github.com/meshagent/meshagent-tailwind",
|
|
6
6
|
"scripts": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"CHANGELOG.md"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@meshagent/meshagent": "^0.39.
|
|
36
|
-
"@meshagent/meshagent-react": "^0.39.
|
|
35
|
+
"@meshagent/meshagent": "^0.39.6",
|
|
36
|
+
"@meshagent/meshagent-react": "^0.39.6",
|
|
37
37
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
38
38
|
"@radix-ui/react-checkbox": "^1.3.2",
|
|
39
39
|
"@radix-ui/react-dialog": "^1.1.14",
|