@sinas/sdk 0.1.1 → 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.
@@ -49,14 +49,19 @@ var SinasSDK = (() => {
49
49
  SinasPermissionError: () => SinasPermissionError,
50
50
  SinasProvider: () => SinasProvider,
51
51
  SinasTimeoutError: () => SinasTimeoutError,
52
+ approveToolCall: () => approveToolCall,
52
53
  createChat: () => createChat,
53
54
  executeFunction: () => executeFunction,
54
55
  executeQuery: () => executeQuery,
56
+ getChat: () => getChat,
57
+ listCollections: () => listCollections,
58
+ listFiles: () => listFiles,
55
59
  sendMessage: () => sendMessage,
56
60
  stateDelete: () => stateDelete,
57
61
  stateGet: () => stateGet,
58
62
  stateList: () => stateList,
59
63
  stateSet: () => stateSet,
64
+ streamFromChannel: () => streamFromChannel,
60
65
  streamMessage: () => streamMessage,
61
66
  useChat: () => useChat,
62
67
  useExecute: () => useExecute,
@@ -279,6 +284,88 @@ var SinasSDK = (() => {
279
284
  createdAt: data.created_at
280
285
  };
281
286
  }
287
+ function mapMessage(data) {
288
+ const toolCalls = data.tool_calls;
289
+ return {
290
+ id: data.id,
291
+ chatId: data.chat_id,
292
+ role: data.role,
293
+ content: data.content ?? null,
294
+ toolCalls: toolCalls ?? null,
295
+ toolCallId: data.tool_call_id ?? null,
296
+ name: data.name ?? null,
297
+ createdAt: data.created_at
298
+ };
299
+ }
300
+ async function getChat(client, chatId) {
301
+ const config = client.getConfig();
302
+ const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
303
+ const data = await res.json();
304
+ return {
305
+ id: data.id,
306
+ agentNamespace: data.agent_namespace,
307
+ agentName: data.agent_name,
308
+ title: data.title,
309
+ messages: (data.messages || []).map(mapMessage)
310
+ };
311
+ }
312
+ async function approveToolCall(client, chatId, toolCallId, approved) {
313
+ const config = client.getConfig();
314
+ const res = await client.fetch(
315
+ `${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
316
+ { method: "POST", body: JSON.stringify({ approved }) }
317
+ );
318
+ const data = await res.json();
319
+ return {
320
+ status: data.status,
321
+ toolCallId: data.tool_call_id,
322
+ channelId: data.channel_id
323
+ };
324
+ }
325
+ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
326
+ const controller = new AbortController();
327
+ const config = client.getConfig();
328
+ (async () => {
329
+ try {
330
+ const res = await fetch(
331
+ `${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
332
+ { headers: client.getAuthHeaders(), signal: controller.signal }
333
+ );
334
+ if (!res.ok) {
335
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
336
+ throw new Error(body.detail || "Stream reconnect failed");
337
+ }
338
+ const reader = res.body.getReader();
339
+ const decoder = new TextDecoder();
340
+ let buffer = "";
341
+ while (true) {
342
+ const { done, value } = await reader.read();
343
+ if (done) break;
344
+ buffer += decoder.decode(value, { stream: true });
345
+ const { chunks, remaining } = parseSSE(buffer);
346
+ buffer = remaining;
347
+ for (const chunk of chunks) {
348
+ onChunk(chunk);
349
+ if (chunk.event === "done") {
350
+ onDone?.();
351
+ return;
352
+ }
353
+ }
354
+ }
355
+ if (buffer.trim()) {
356
+ const { chunks } = parseSSE(buffer + "\n\n");
357
+ for (const chunk of chunks) {
358
+ onChunk(chunk);
359
+ }
360
+ }
361
+ onDone?.();
362
+ } catch (err) {
363
+ if (err.name === "AbortError") return;
364
+ onError?.(err);
365
+ }
366
+ })();
367
+ return controller;
368
+ }
282
369
  function parseSSE(buffer) {
283
370
  const chunks = [];
284
371
  const lines = buffer.split("\n");
@@ -352,6 +439,18 @@ var SinasSDK = (() => {
352
439
  return controller;
353
440
  }
354
441
 
442
+ // src/client/collections.ts
443
+ async function listCollections(client) {
444
+ const base = client.getProxyBase();
445
+ const res = await client.fetch(`${base}/collections`);
446
+ return res.json();
447
+ }
448
+ async function listFiles(client, namespace, collection) {
449
+ const base = client.getProxyBase();
450
+ const res = await client.fetch(`${base}/files/${namespace}/${collection}`);
451
+ return res.json();
452
+ }
453
+
355
454
  // src/hooks/useQuery.ts
356
455
  var import_react2 = __toESM(require_react(), 1);
357
456
 
@@ -527,125 +626,235 @@ var SinasSDK = (() => {
527
626
 
528
627
  // src/hooks/useChat.ts
529
628
  var import_react5 = __toESM(require_react(), 1);
530
- function useChat(agentRef) {
629
+ function useChat(agentRef, options) {
531
630
  const client = useClient();
532
- const [chatId, setChatId] = import_react5.default.useState(null);
631
+ const [chatId, setChatId] = import_react5.default.useState(options?.chatId ?? null);
533
632
  const [messages, setMessages] = import_react5.default.useState([]);
534
633
  const [loading, setLoading] = import_react5.default.useState(false);
535
634
  const [streaming, setStreaming] = import_react5.default.useState(false);
536
635
  const [error, setError] = import_react5.default.useState(null);
636
+ const [pendingApprovals, setPendingApprovals] = import_react5.default.useState([]);
537
637
  const abortRef = import_react5.default.useRef(null);
638
+ const chatIdRef = import_react5.default.useRef(chatId);
639
+ chatIdRef.current = chatId;
640
+ import_react5.default.useEffect(() => {
641
+ if (!options?.chatId) return;
642
+ let cancelled = false;
643
+ setLoading(true);
644
+ getChat(client, options.chatId).then((detail) => {
645
+ if (cancelled) return;
646
+ setChatId(detail.id);
647
+ setMessages(detail.messages);
648
+ }).catch((e) => {
649
+ if (cancelled) return;
650
+ setError(e.message);
651
+ }).finally(() => {
652
+ if (!cancelled) setLoading(false);
653
+ });
654
+ return () => {
655
+ cancelled = true;
656
+ };
657
+ }, [client, options?.chatId]);
538
658
  import_react5.default.useEffect(() => {
539
659
  return () => {
540
660
  abortRef.current?.abort();
541
661
  };
542
662
  }, []);
543
- const doCreateChat = import_react5.default.useCallback(
544
- async (input) => {
545
- setLoading(true);
546
- setError(null);
547
- try {
548
- const result = await createChat(client, agentRef, input);
549
- setChatId(result.id);
550
- setMessages([]);
551
- return result.id;
552
- } catch (e) {
553
- setError(e.message);
554
- } finally {
555
- setLoading(false);
663
+ const handleStreamChunks = import_react5.default.useCallback(
664
+ (assistantId) => (chunk) => {
665
+ if (chunk.event === "message") {
666
+ try {
667
+ const parsed = JSON.parse(chunk.data);
668
+ if (parsed.content) {
669
+ setMessages(
670
+ (prev) => prev.map(
671
+ (m) => m.id === assistantId ? { ...m, content: (m.content || "") + parsed.content } : m
672
+ )
673
+ );
674
+ }
675
+ } catch {
676
+ setMessages(
677
+ (prev) => prev.map(
678
+ (m) => m.id === assistantId ? { ...m, content: (m.content || "") + chunk.data } : m
679
+ )
680
+ );
681
+ }
682
+ } else if (chunk.event === "approval_required") {
683
+ try {
684
+ const parsed = JSON.parse(chunk.data);
685
+ setPendingApprovals((prev) => [...prev, parsed]);
686
+ } catch {
687
+ }
688
+ } else if (chunk.event === "error") {
689
+ try {
690
+ const parsed = JSON.parse(chunk.data);
691
+ setError(parsed.error || "Stream error");
692
+ } catch {
693
+ setError(chunk.data || "Stream error");
694
+ }
556
695
  }
557
696
  },
558
- [client, agentRef]
697
+ []
559
698
  );
560
- const doSendMessage = import_react5.default.useCallback(
561
- async (content) => {
562
- if (!chatId) {
563
- setError("No active chat. Call createChat first.");
564
- return;
565
- }
566
- setLoading(true);
567
- setError(null);
568
- const userMsg = {
569
- id: `tmp-${Date.now()}`,
570
- chatId,
571
- role: "user",
572
- content,
573
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
574
- };
575
- setMessages((prev) => [...prev, userMsg]);
699
+ const refreshMessages = import_react5.default.useCallback(
700
+ async (id) => {
576
701
  try {
577
- const msg = await sendMessage(client, chatId, content);
578
- setMessages((prev) => [...prev, msg]);
579
- return msg;
580
- } catch (e) {
581
- setError(e.message);
582
- } finally {
583
- setLoading(false);
702
+ const detail = await getChat(client, id);
703
+ setMessages(detail.messages);
704
+ } catch {
584
705
  }
585
706
  },
586
- [client, chatId]
707
+ [client]
587
708
  );
588
- const doStreamMessage = import_react5.default.useCallback(
709
+ const send = import_react5.default.useCallback(
589
710
  (content) => {
590
- if (!chatId) {
591
- setError("No active chat. Call createChat first.");
592
- return;
593
- }
594
- abortRef.current?.abort();
711
+ const doSend = async () => {
712
+ let activeChatId = chatIdRef.current;
713
+ if (!activeChatId) {
714
+ setLoading(true);
715
+ setError(null);
716
+ try {
717
+ const result = await createChat(client, agentRef, options?.input);
718
+ activeChatId = result.id;
719
+ setChatId(result.id);
720
+ chatIdRef.current = result.id;
721
+ } catch (e) {
722
+ setError(e.message);
723
+ setLoading(false);
724
+ return;
725
+ }
726
+ setLoading(false);
727
+ }
728
+ abortRef.current?.abort();
729
+ setStreaming(true);
730
+ setError(null);
731
+ setPendingApprovals([]);
732
+ const displayContent = typeof content === "string" ? content : JSON.stringify(content);
733
+ const userMsg = {
734
+ id: `tmp-user-${Date.now()}`,
735
+ chatId: activeChatId,
736
+ role: "user",
737
+ content: displayContent,
738
+ toolCalls: null,
739
+ toolCallId: null,
740
+ name: null,
741
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
742
+ };
743
+ setMessages((prev) => [...prev, userMsg]);
744
+ const assistantId = `tmp-assistant-${Date.now()}`;
745
+ const assistantMsg = {
746
+ id: assistantId,
747
+ chatId: activeChatId,
748
+ role: "assistant",
749
+ content: "",
750
+ toolCalls: null,
751
+ toolCallId: null,
752
+ name: null,
753
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
754
+ streaming: true
755
+ };
756
+ setMessages((prev) => [...prev, assistantMsg]);
757
+ const controller = streamMessage(
758
+ client,
759
+ activeChatId,
760
+ content,
761
+ handleStreamChunks(assistantId),
762
+ () => {
763
+ setStreaming(false);
764
+ refreshMessages(activeChatId);
765
+ },
766
+ (err) => {
767
+ setError(err.message);
768
+ setStreaming(false);
769
+ }
770
+ );
771
+ abortRef.current = controller;
772
+ };
773
+ doSend();
774
+ },
775
+ [client, agentRef, options?.input, handleStreamChunks, refreshMessages]
776
+ );
777
+ const handleApproval = import_react5.default.useCallback(
778
+ (toolCallId, approved) => {
779
+ const currentChatId = chatIdRef.current;
780
+ if (!currentChatId) return;
781
+ setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
595
782
  setStreaming(true);
596
783
  setError(null);
597
- const userMsg = {
598
- id: `tmp-${Date.now()}`,
599
- chatId,
600
- role: "user",
601
- content,
602
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
603
- };
604
- setMessages((prev) => [...prev, userMsg]);
605
- const assistantId = `stream-${Date.now()}`;
606
- const assistantMsg = {
607
- id: assistantId,
608
- chatId,
609
- role: "assistant",
610
- content: "",
611
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
612
- };
613
- setMessages((prev) => [...prev, assistantMsg]);
614
- const controller = streamMessage(
615
- client,
616
- chatId,
617
- content,
618
- (chunk) => {
619
- if (chunk.event === "content" || chunk.event === "message") {
620
- setMessages(
621
- (prev) => prev.map(
622
- (m) => m.id === assistantId ? { ...m, content: m.content + chunk.data } : m
623
- )
624
- );
784
+ const assistantId = `tmp-resume-${Date.now()}`;
785
+ if (approved) {
786
+ const assistantMsg = {
787
+ id: assistantId,
788
+ chatId: currentChatId,
789
+ role: "assistant",
790
+ content: "",
791
+ toolCalls: null,
792
+ toolCallId: null,
793
+ name: null,
794
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
795
+ streaming: true
796
+ };
797
+ setMessages((prev) => [...prev, assistantMsg]);
798
+ }
799
+ (async () => {
800
+ try {
801
+ const result = await approveToolCall(client, currentChatId, toolCallId, approved);
802
+ if (!approved) {
803
+ setStreaming(false);
804
+ refreshMessages(currentChatId);
805
+ return;
625
806
  }
626
- },
627
- () => {
628
- setStreaming(false);
629
- },
630
- (err) => {
631
- setError(err.message);
807
+ const controller = streamFromChannel(
808
+ client,
809
+ currentChatId,
810
+ result.channelId,
811
+ handleStreamChunks(assistantId),
812
+ () => {
813
+ setStreaming(false);
814
+ refreshMessages(currentChatId);
815
+ },
816
+ (err) => {
817
+ setError(err.message);
818
+ setStreaming(false);
819
+ }
820
+ );
821
+ abortRef.current = controller;
822
+ } catch (e) {
823
+ setError(e.message);
632
824
  setStreaming(false);
633
825
  }
634
- );
635
- abortRef.current = controller;
636
- return controller;
826
+ })();
637
827
  },
638
- [client, chatId]
828
+ [client, handleStreamChunks, refreshMessages]
829
+ );
830
+ const approve = import_react5.default.useCallback(
831
+ (toolCallId) => handleApproval(toolCallId, true),
832
+ [handleApproval]
639
833
  );
834
+ const reject = import_react5.default.useCallback(
835
+ (toolCallId) => handleApproval(toolCallId, false),
836
+ [handleApproval]
837
+ );
838
+ const stop = import_react5.default.useCallback(() => {
839
+ abortRef.current?.abort();
840
+ abortRef.current = null;
841
+ setStreaming(false);
842
+ }, []);
843
+ const clearError = import_react5.default.useCallback(() => {
844
+ setError(null);
845
+ }, []);
640
846
  return {
641
847
  chatId,
642
848
  messages,
643
849
  loading,
644
850
  streaming,
645
851
  error,
646
- createChat: doCreateChat,
647
- sendMessage: doSendMessage,
648
- streamMessage: doStreamMessage
852
+ pendingApprovals,
853
+ send,
854
+ approve,
855
+ reject,
856
+ stop,
857
+ clearError
649
858
  };
650
859
  }
651
860