@sinas/sdk 0.1.0 → 0.2.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/dist/{chunk-M7YHE5Y6.js → chunk-EV5ARSUJ.js} +101 -2
- package/dist/chunk-EV5ARSUJ.js.map +1 -0
- package/dist/client.cjs +104 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +89 -3
- package/dist/client.d.ts +89 -3
- package/dist/client.js +11 -1
- package/dist/index.cjs +301 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -8
- package/dist/index.d.ts +19 -8
- package/dist/index.js +208 -88
- package/dist/index.js.map +1 -1
- package/dist/sinas-sdk.umd.js +299 -2265
- package/dist/sinas-sdk.umd.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-M7YHE5Y6.js.map +0 -1
package/dist/client.js
CHANGED
|
@@ -5,16 +5,21 @@ import {
|
|
|
5
5
|
SinasNotFoundError,
|
|
6
6
|
SinasPermissionError,
|
|
7
7
|
SinasTimeoutError,
|
|
8
|
+
approveToolCall,
|
|
8
9
|
createChat,
|
|
9
10
|
executeFunction,
|
|
10
11
|
executeQuery,
|
|
12
|
+
getChat,
|
|
13
|
+
listCollections,
|
|
14
|
+
listFiles,
|
|
11
15
|
sendMessage,
|
|
12
16
|
stateDelete,
|
|
13
17
|
stateGet,
|
|
14
18
|
stateList,
|
|
15
19
|
stateSet,
|
|
20
|
+
streamFromChannel,
|
|
16
21
|
streamMessage
|
|
17
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-EV5ARSUJ.js";
|
|
18
23
|
export {
|
|
19
24
|
SinasAuthError,
|
|
20
25
|
SinasClient,
|
|
@@ -22,14 +27,19 @@ export {
|
|
|
22
27
|
SinasNotFoundError,
|
|
23
28
|
SinasPermissionError,
|
|
24
29
|
SinasTimeoutError,
|
|
30
|
+
approveToolCall,
|
|
25
31
|
createChat,
|
|
26
32
|
executeFunction,
|
|
27
33
|
executeQuery,
|
|
34
|
+
getChat,
|
|
35
|
+
listCollections,
|
|
36
|
+
listFiles,
|
|
28
37
|
sendMessage,
|
|
29
38
|
stateDelete,
|
|
30
39
|
stateGet,
|
|
31
40
|
stateList,
|
|
32
41
|
stateSet,
|
|
42
|
+
streamFromChannel,
|
|
33
43
|
streamMessage
|
|
34
44
|
};
|
|
35
45
|
//# sourceMappingURL=client.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -37,14 +37,19 @@ __export(src_exports, {
|
|
|
37
37
|
SinasPermissionError: () => SinasPermissionError,
|
|
38
38
|
SinasProvider: () => SinasProvider,
|
|
39
39
|
SinasTimeoutError: () => SinasTimeoutError,
|
|
40
|
+
approveToolCall: () => approveToolCall,
|
|
40
41
|
createChat: () => createChat,
|
|
41
42
|
executeFunction: () => executeFunction,
|
|
42
43
|
executeQuery: () => executeQuery,
|
|
44
|
+
getChat: () => getChat,
|
|
45
|
+
listCollections: () => listCollections,
|
|
46
|
+
listFiles: () => listFiles,
|
|
43
47
|
sendMessage: () => sendMessage,
|
|
44
48
|
stateDelete: () => stateDelete,
|
|
45
49
|
stateGet: () => stateGet,
|
|
46
50
|
stateList: () => stateList,
|
|
47
51
|
stateSet: () => stateSet,
|
|
52
|
+
streamFromChannel: () => streamFromChannel,
|
|
48
53
|
streamMessage: () => streamMessage,
|
|
49
54
|
useChat: () => useChat,
|
|
50
55
|
useExecute: () => useExecute,
|
|
@@ -268,6 +273,88 @@ async function sendMessage(client, chatId, content) {
|
|
|
268
273
|
createdAt: data.created_at
|
|
269
274
|
};
|
|
270
275
|
}
|
|
276
|
+
function mapMessage(data) {
|
|
277
|
+
const toolCalls = data.tool_calls;
|
|
278
|
+
return {
|
|
279
|
+
id: data.id,
|
|
280
|
+
chatId: data.chat_id,
|
|
281
|
+
role: data.role,
|
|
282
|
+
content: data.content ?? null,
|
|
283
|
+
toolCalls: toolCalls ?? null,
|
|
284
|
+
toolCallId: data.tool_call_id ?? null,
|
|
285
|
+
name: data.name ?? null,
|
|
286
|
+
createdAt: data.created_at
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function getChat(client, chatId) {
|
|
290
|
+
const config = client.getConfig();
|
|
291
|
+
const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
|
|
292
|
+
const data = await res.json();
|
|
293
|
+
return {
|
|
294
|
+
id: data.id,
|
|
295
|
+
agentNamespace: data.agent_namespace,
|
|
296
|
+
agentName: data.agent_name,
|
|
297
|
+
title: data.title,
|
|
298
|
+
messages: (data.messages || []).map(mapMessage)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
async function approveToolCall(client, chatId, toolCallId, approved) {
|
|
302
|
+
const config = client.getConfig();
|
|
303
|
+
const res = await client.fetch(
|
|
304
|
+
`${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
|
|
305
|
+
{ method: "POST", body: JSON.stringify({ approved }) }
|
|
306
|
+
);
|
|
307
|
+
const data = await res.json();
|
|
308
|
+
return {
|
|
309
|
+
status: data.status,
|
|
310
|
+
toolCallId: data.tool_call_id,
|
|
311
|
+
channelId: data.channel_id
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
const config = client.getConfig();
|
|
317
|
+
(async () => {
|
|
318
|
+
try {
|
|
319
|
+
const res = await fetch(
|
|
320
|
+
`${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
|
|
321
|
+
{ headers: client.getAuthHeaders(), signal: controller.signal }
|
|
322
|
+
);
|
|
323
|
+
if (!res.ok) {
|
|
324
|
+
const body = await res.json().catch(() => ({ detail: res.statusText }));
|
|
325
|
+
throw new Error(body.detail || "Stream reconnect failed");
|
|
326
|
+
}
|
|
327
|
+
const reader = res.body.getReader();
|
|
328
|
+
const decoder = new TextDecoder();
|
|
329
|
+
let buffer = "";
|
|
330
|
+
while (true) {
|
|
331
|
+
const { done, value } = await reader.read();
|
|
332
|
+
if (done) break;
|
|
333
|
+
buffer += decoder.decode(value, { stream: true });
|
|
334
|
+
const { chunks, remaining } = parseSSE(buffer);
|
|
335
|
+
buffer = remaining;
|
|
336
|
+
for (const chunk of chunks) {
|
|
337
|
+
onChunk(chunk);
|
|
338
|
+
if (chunk.event === "done") {
|
|
339
|
+
onDone?.();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (buffer.trim()) {
|
|
345
|
+
const { chunks } = parseSSE(buffer + "\n\n");
|
|
346
|
+
for (const chunk of chunks) {
|
|
347
|
+
onChunk(chunk);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
onDone?.();
|
|
351
|
+
} catch (err) {
|
|
352
|
+
if (err.name === "AbortError") return;
|
|
353
|
+
onError?.(err);
|
|
354
|
+
}
|
|
355
|
+
})();
|
|
356
|
+
return controller;
|
|
357
|
+
}
|
|
271
358
|
function parseSSE(buffer) {
|
|
272
359
|
const chunks = [];
|
|
273
360
|
const lines = buffer.split("\n");
|
|
@@ -341,6 +428,18 @@ function streamMessage(client, chatId, content, onChunk, onDone, onError) {
|
|
|
341
428
|
return controller;
|
|
342
429
|
}
|
|
343
430
|
|
|
431
|
+
// src/client/collections.ts
|
|
432
|
+
async function listCollections(client) {
|
|
433
|
+
const base = client.getProxyBase();
|
|
434
|
+
const res = await client.fetch(`${base}/collections`);
|
|
435
|
+
return res.json();
|
|
436
|
+
}
|
|
437
|
+
async function listFiles(client, namespace, collection) {
|
|
438
|
+
const base = client.getProxyBase();
|
|
439
|
+
const res = await client.fetch(`${base}/files/${namespace}/${collection}`);
|
|
440
|
+
return res.json();
|
|
441
|
+
}
|
|
442
|
+
|
|
344
443
|
// src/hooks/useQuery.ts
|
|
345
444
|
var import_react2 = __toESM(require("react"), 1);
|
|
346
445
|
|
|
@@ -516,125 +615,235 @@ function useStateStore(ns, key) {
|
|
|
516
615
|
|
|
517
616
|
// src/hooks/useChat.ts
|
|
518
617
|
var import_react5 = __toESM(require("react"), 1);
|
|
519
|
-
function useChat(agentRef) {
|
|
618
|
+
function useChat(agentRef, options) {
|
|
520
619
|
const client = useClient();
|
|
521
|
-
const [chatId, setChatId] = import_react5.default.useState(null);
|
|
620
|
+
const [chatId, setChatId] = import_react5.default.useState(options?.chatId ?? null);
|
|
522
621
|
const [messages, setMessages] = import_react5.default.useState([]);
|
|
523
622
|
const [loading, setLoading] = import_react5.default.useState(false);
|
|
524
623
|
const [streaming, setStreaming] = import_react5.default.useState(false);
|
|
525
624
|
const [error, setError] = import_react5.default.useState(null);
|
|
625
|
+
const [pendingApprovals, setPendingApprovals] = import_react5.default.useState([]);
|
|
526
626
|
const abortRef = import_react5.default.useRef(null);
|
|
627
|
+
const chatIdRef = import_react5.default.useRef(chatId);
|
|
628
|
+
chatIdRef.current = chatId;
|
|
629
|
+
import_react5.default.useEffect(() => {
|
|
630
|
+
if (!options?.chatId) return;
|
|
631
|
+
let cancelled = false;
|
|
632
|
+
setLoading(true);
|
|
633
|
+
getChat(client, options.chatId).then((detail) => {
|
|
634
|
+
if (cancelled) return;
|
|
635
|
+
setChatId(detail.id);
|
|
636
|
+
setMessages(detail.messages);
|
|
637
|
+
}).catch((e) => {
|
|
638
|
+
if (cancelled) return;
|
|
639
|
+
setError(e.message);
|
|
640
|
+
}).finally(() => {
|
|
641
|
+
if (!cancelled) setLoading(false);
|
|
642
|
+
});
|
|
643
|
+
return () => {
|
|
644
|
+
cancelled = true;
|
|
645
|
+
};
|
|
646
|
+
}, [client, options?.chatId]);
|
|
527
647
|
import_react5.default.useEffect(() => {
|
|
528
648
|
return () => {
|
|
529
649
|
abortRef.current?.abort();
|
|
530
650
|
};
|
|
531
651
|
}, []);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
652
|
+
const handleStreamChunks = import_react5.default.useCallback(
|
|
653
|
+
(assistantId) => (chunk) => {
|
|
654
|
+
if (chunk.event === "message") {
|
|
655
|
+
try {
|
|
656
|
+
const parsed = JSON.parse(chunk.data);
|
|
657
|
+
if (parsed.content) {
|
|
658
|
+
setMessages(
|
|
659
|
+
(prev) => prev.map(
|
|
660
|
+
(m) => m.id === assistantId ? { ...m, content: (m.content || "") + parsed.content } : m
|
|
661
|
+
)
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
} catch {
|
|
665
|
+
setMessages(
|
|
666
|
+
(prev) => prev.map(
|
|
667
|
+
(m) => m.id === assistantId ? { ...m, content: (m.content || "") + chunk.data } : m
|
|
668
|
+
)
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
} else if (chunk.event === "approval_required") {
|
|
672
|
+
try {
|
|
673
|
+
const parsed = JSON.parse(chunk.data);
|
|
674
|
+
setPendingApprovals((prev) => [...prev, parsed]);
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
} else if (chunk.event === "error") {
|
|
678
|
+
try {
|
|
679
|
+
const parsed = JSON.parse(chunk.data);
|
|
680
|
+
setError(parsed.error || "Stream error");
|
|
681
|
+
} catch {
|
|
682
|
+
setError(chunk.data || "Stream error");
|
|
683
|
+
}
|
|
545
684
|
}
|
|
546
685
|
},
|
|
547
|
-
[
|
|
686
|
+
[]
|
|
548
687
|
);
|
|
549
|
-
const
|
|
550
|
-
async (
|
|
551
|
-
if (!chatId) {
|
|
552
|
-
setError("No active chat. Call createChat first.");
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
setLoading(true);
|
|
556
|
-
setError(null);
|
|
557
|
-
const userMsg = {
|
|
558
|
-
id: `tmp-${Date.now()}`,
|
|
559
|
-
chatId,
|
|
560
|
-
role: "user",
|
|
561
|
-
content,
|
|
562
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
563
|
-
};
|
|
564
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
688
|
+
const refreshMessages = import_react5.default.useCallback(
|
|
689
|
+
async (id) => {
|
|
565
690
|
try {
|
|
566
|
-
const
|
|
567
|
-
setMessages(
|
|
568
|
-
|
|
569
|
-
} catch (e) {
|
|
570
|
-
setError(e.message);
|
|
571
|
-
} finally {
|
|
572
|
-
setLoading(false);
|
|
691
|
+
const detail = await getChat(client, id);
|
|
692
|
+
setMessages(detail.messages);
|
|
693
|
+
} catch {
|
|
573
694
|
}
|
|
574
695
|
},
|
|
575
|
-
[client
|
|
696
|
+
[client]
|
|
576
697
|
);
|
|
577
|
-
const
|
|
698
|
+
const send = import_react5.default.useCallback(
|
|
578
699
|
(content) => {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
700
|
+
const doSend = async () => {
|
|
701
|
+
let activeChatId = chatIdRef.current;
|
|
702
|
+
if (!activeChatId) {
|
|
703
|
+
setLoading(true);
|
|
704
|
+
setError(null);
|
|
705
|
+
try {
|
|
706
|
+
const result = await createChat(client, agentRef, options?.input);
|
|
707
|
+
activeChatId = result.id;
|
|
708
|
+
setChatId(result.id);
|
|
709
|
+
chatIdRef.current = result.id;
|
|
710
|
+
} catch (e) {
|
|
711
|
+
setError(e.message);
|
|
712
|
+
setLoading(false);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
setLoading(false);
|
|
716
|
+
}
|
|
717
|
+
abortRef.current?.abort();
|
|
718
|
+
setStreaming(true);
|
|
719
|
+
setError(null);
|
|
720
|
+
setPendingApprovals([]);
|
|
721
|
+
const displayContent = typeof content === "string" ? content : JSON.stringify(content);
|
|
722
|
+
const userMsg = {
|
|
723
|
+
id: `tmp-user-${Date.now()}`,
|
|
724
|
+
chatId: activeChatId,
|
|
725
|
+
role: "user",
|
|
726
|
+
content: displayContent,
|
|
727
|
+
toolCalls: null,
|
|
728
|
+
toolCallId: null,
|
|
729
|
+
name: null,
|
|
730
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
731
|
+
};
|
|
732
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
733
|
+
const assistantId = `tmp-assistant-${Date.now()}`;
|
|
734
|
+
const assistantMsg = {
|
|
735
|
+
id: assistantId,
|
|
736
|
+
chatId: activeChatId,
|
|
737
|
+
role: "assistant",
|
|
738
|
+
content: "",
|
|
739
|
+
toolCalls: null,
|
|
740
|
+
toolCallId: null,
|
|
741
|
+
name: null,
|
|
742
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
743
|
+
streaming: true
|
|
744
|
+
};
|
|
745
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
746
|
+
const controller = streamMessage(
|
|
747
|
+
client,
|
|
748
|
+
activeChatId,
|
|
749
|
+
content,
|
|
750
|
+
handleStreamChunks(assistantId),
|
|
751
|
+
() => {
|
|
752
|
+
setStreaming(false);
|
|
753
|
+
refreshMessages(activeChatId);
|
|
754
|
+
},
|
|
755
|
+
(err) => {
|
|
756
|
+
setError(err.message);
|
|
757
|
+
setStreaming(false);
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
abortRef.current = controller;
|
|
761
|
+
};
|
|
762
|
+
doSend();
|
|
763
|
+
},
|
|
764
|
+
[client, agentRef, options?.input, handleStreamChunks, refreshMessages]
|
|
765
|
+
);
|
|
766
|
+
const handleApproval = import_react5.default.useCallback(
|
|
767
|
+
(toolCallId, approved) => {
|
|
768
|
+
const currentChatId = chatIdRef.current;
|
|
769
|
+
if (!currentChatId) return;
|
|
770
|
+
setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
|
|
584
771
|
setStreaming(true);
|
|
585
772
|
setError(null);
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (chunk.event === "content" || chunk.event === "message") {
|
|
609
|
-
setMessages(
|
|
610
|
-
(prev) => prev.map(
|
|
611
|
-
(m) => m.id === assistantId ? { ...m, content: m.content + chunk.data } : m
|
|
612
|
-
)
|
|
613
|
-
);
|
|
773
|
+
const assistantId = `tmp-resume-${Date.now()}`;
|
|
774
|
+
if (approved) {
|
|
775
|
+
const assistantMsg = {
|
|
776
|
+
id: assistantId,
|
|
777
|
+
chatId: currentChatId,
|
|
778
|
+
role: "assistant",
|
|
779
|
+
content: "",
|
|
780
|
+
toolCalls: null,
|
|
781
|
+
toolCallId: null,
|
|
782
|
+
name: null,
|
|
783
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
784
|
+
streaming: true
|
|
785
|
+
};
|
|
786
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
787
|
+
}
|
|
788
|
+
(async () => {
|
|
789
|
+
try {
|
|
790
|
+
const result = await approveToolCall(client, currentChatId, toolCallId, approved);
|
|
791
|
+
if (!approved) {
|
|
792
|
+
setStreaming(false);
|
|
793
|
+
refreshMessages(currentChatId);
|
|
794
|
+
return;
|
|
614
795
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
796
|
+
const controller = streamFromChannel(
|
|
797
|
+
client,
|
|
798
|
+
currentChatId,
|
|
799
|
+
result.channelId,
|
|
800
|
+
handleStreamChunks(assistantId),
|
|
801
|
+
() => {
|
|
802
|
+
setStreaming(false);
|
|
803
|
+
refreshMessages(currentChatId);
|
|
804
|
+
},
|
|
805
|
+
(err) => {
|
|
806
|
+
setError(err.message);
|
|
807
|
+
setStreaming(false);
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
abortRef.current = controller;
|
|
811
|
+
} catch (e) {
|
|
812
|
+
setError(e.message);
|
|
621
813
|
setStreaming(false);
|
|
622
814
|
}
|
|
623
|
-
);
|
|
624
|
-
abortRef.current = controller;
|
|
625
|
-
return controller;
|
|
815
|
+
})();
|
|
626
816
|
},
|
|
627
|
-
[client,
|
|
817
|
+
[client, handleStreamChunks, refreshMessages]
|
|
818
|
+
);
|
|
819
|
+
const approve = import_react5.default.useCallback(
|
|
820
|
+
(toolCallId) => handleApproval(toolCallId, true),
|
|
821
|
+
[handleApproval]
|
|
628
822
|
);
|
|
823
|
+
const reject = import_react5.default.useCallback(
|
|
824
|
+
(toolCallId) => handleApproval(toolCallId, false),
|
|
825
|
+
[handleApproval]
|
|
826
|
+
);
|
|
827
|
+
const stop = import_react5.default.useCallback(() => {
|
|
828
|
+
abortRef.current?.abort();
|
|
829
|
+
abortRef.current = null;
|
|
830
|
+
setStreaming(false);
|
|
831
|
+
}, []);
|
|
832
|
+
const clearError = import_react5.default.useCallback(() => {
|
|
833
|
+
setError(null);
|
|
834
|
+
}, []);
|
|
629
835
|
return {
|
|
630
836
|
chatId,
|
|
631
837
|
messages,
|
|
632
838
|
loading,
|
|
633
839
|
streaming,
|
|
634
840
|
error,
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
841
|
+
pendingApprovals,
|
|
842
|
+
send,
|
|
843
|
+
approve,
|
|
844
|
+
reject,
|
|
845
|
+
stop,
|
|
846
|
+
clearError
|
|
638
847
|
};
|
|
639
848
|
}
|
|
640
849
|
|
|
@@ -660,14 +869,19 @@ function useSinas() {
|
|
|
660
869
|
SinasPermissionError,
|
|
661
870
|
SinasProvider,
|
|
662
871
|
SinasTimeoutError,
|
|
872
|
+
approveToolCall,
|
|
663
873
|
createChat,
|
|
664
874
|
executeFunction,
|
|
665
875
|
executeQuery,
|
|
876
|
+
getChat,
|
|
877
|
+
listCollections,
|
|
878
|
+
listFiles,
|
|
666
879
|
sendMessage,
|
|
667
880
|
stateDelete,
|
|
668
881
|
stateGet,
|
|
669
882
|
stateList,
|
|
670
883
|
stateSet,
|
|
884
|
+
streamFromChannel,
|
|
671
885
|
streamMessage,
|
|
672
886
|
useChat,
|
|
673
887
|
useExecute,
|