@sinas/sdk 0.7.0 → 0.8.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/client.cjs CHANGED
@@ -27,6 +27,9 @@ __export(client_exports, {
27
27
  SinasNotFoundError: () => SinasNotFoundError,
28
28
  SinasPermissionError: () => SinasPermissionError,
29
29
  SinasTimeoutError: () => SinasTimeoutError,
30
+ answerPendingInput: () => answerPendingInput,
31
+ answerResumeChannel: () => answerResumeChannel,
32
+ approvalResumeChannel: () => approvalResumeChannel,
30
33
  approveToolCall: () => approveToolCall,
31
34
  cancelBatch: () => cancelBatch,
32
35
  checkPermissions: () => checkPermissions,
@@ -39,11 +42,16 @@ __export(client_exports, {
39
42
  getChat: () => getChat,
40
43
  getInfo: () => getInfo,
41
44
  getMe: () => getMe,
45
+ invokeAgent: () => invokeAgent,
42
46
  listBatchExecutions: () => listBatchExecutions,
43
47
  listCollections: () => listCollections,
44
48
  listFiles: () => listFiles,
49
+ listPendingInputs: () => listPendingInputs,
45
50
  login: () => login,
46
51
  logout: () => logout,
52
+ parsePendingInput: () => parsePendingInput,
53
+ parsePendingInputs: () => parsePendingInputs,
54
+ pendingInputFromChunk: () => pendingInputFromChunk,
47
55
  refresh: () => refresh,
48
56
  sendMessage: () => sendMessage,
49
57
  stateDelete: () => stateDelete,
@@ -519,12 +527,111 @@ async function stateList(client, ns) {
519
527
  return res.json();
520
528
  }
521
529
 
530
+ // src/client/chatEvents.ts
531
+ var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
532
+ var str = (v) => typeof v === "string" ? v : null;
533
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
534
+ var optionsOf = (v) => Array.isArray(v) && v.length > 0 && v.every((o) => typeof o === "string") ? v : null;
535
+ function parsePendingInput(raw) {
536
+ if (!isObject(raw)) return null;
537
+ if (typeof raw.tool_call_id !== "string" || !raw.tool_call_id) return null;
538
+ return {
539
+ toolCallId: raw.tool_call_id,
540
+ question: str(raw.question) ?? "",
541
+ options: optionsOf(raw.options),
542
+ expiresAt: str(raw.expires_at),
543
+ createdAt: str(raw.created_at)
544
+ };
545
+ }
546
+ function parsePendingInputs(raw) {
547
+ return Array.isArray(raw) ? raw.map(parsePendingInput).filter((p) => p !== null) : [];
548
+ }
549
+ function pendingInputFromChunk(chunk) {
550
+ if (chunk.event !== "input_required" && chunk.event !== "message") return null;
551
+ let parsed;
552
+ try {
553
+ parsed = JSON.parse(chunk.data);
554
+ } catch {
555
+ return null;
556
+ }
557
+ if (chunk.event === "message" && (!isObject(parsed) || parsed.type !== "input_required")) return null;
558
+ return parsePendingInput(parsed);
559
+ }
560
+ function parseApproval(raw) {
561
+ if (!isObject(raw) || typeof raw.tool_call_id !== "string") return null;
562
+ return {
563
+ type: str(raw.type) ?? "approval_required",
564
+ toolCallId: raw.tool_call_id,
565
+ functionNamespace: str(raw.function_namespace) ?? "",
566
+ functionName: str(raw.function_name) ?? "",
567
+ arguments: isObject(raw.arguments) ? raw.arguments : {}
568
+ };
569
+ }
570
+ function parseUsage(raw) {
571
+ if (!isObject(raw)) return null;
572
+ return {
573
+ calls: num(raw.calls),
574
+ promptTokens: num(raw.prompt_tokens),
575
+ completionTokens: num(raw.completion_tokens),
576
+ totalTokens: num(raw.total_tokens),
577
+ cacheReadTokens: num(raw.cache_read_tokens),
578
+ cacheWriteTokens: num(raw.cache_write_tokens),
579
+ costUsd: typeof raw.cost_usd === "number" ? raw.cost_usd : null,
580
+ unpricedCalls: num(raw.unpriced_calls),
581
+ errors: num(raw.errors)
582
+ };
583
+ }
584
+ var TURN_STATUSES = ["completed", "input_required", "in_progress", "injected"];
585
+ function parseTurnState(raw) {
586
+ const status = TURN_STATUSES.includes(raw.status) ? raw.status : "completed";
587
+ return {
588
+ status,
589
+ pendingInputs: parsePendingInputs(raw.pending_inputs),
590
+ channelId: str(raw.channel_id),
591
+ usage: parseUsage(raw.usage)
592
+ };
593
+ }
594
+ function parseMessage(data) {
595
+ const toolCalls = data.tool_calls;
596
+ return {
597
+ id: data.id,
598
+ chatId: data.chat_id,
599
+ role: data.role,
600
+ content: data.content ?? null,
601
+ toolCalls: toolCalls ?? null,
602
+ toolCallId: data.tool_call_id ?? null,
603
+ name: data.name ?? null,
604
+ createdAt: data.created_at
605
+ };
606
+ }
607
+ function parseDone(data) {
608
+ try {
609
+ const parsed = JSON.parse(data);
610
+ if (isObject(parsed) && typeof parsed.status === "string") return { status: parsed.status };
611
+ } catch {
612
+ }
613
+ return { status: "completed" };
614
+ }
615
+ function approvalResumeChannel(result, approved = true) {
616
+ if (result.resume === "waiting") return null;
617
+ if (result.resume === void 0 && !approved) return null;
618
+ return result.channelId || null;
619
+ }
620
+ function answerResumeChannel(result) {
621
+ if (!result.resumed && !result.resumePending) return null;
622
+ return result.channelId || null;
623
+ }
624
+
522
625
  // src/client/chat.ts
523
- async function createChat(client, agentRef, input) {
626
+ function splitAgentRef(agentRef) {
524
627
  const [ns, name] = agentRef.split("/");
525
628
  if (!ns || !name) {
526
629
  throw new Error(`Invalid agent ref "${agentRef}": expected "namespace/name"`);
527
630
  }
631
+ return [ns, name];
632
+ }
633
+ async function createChat(client, agentRef, input) {
634
+ const [ns, name] = splitAgentRef(agentRef);
528
635
  const config = client.getConfig();
529
636
  const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/chats`, {
530
637
  method: "POST",
@@ -550,66 +657,117 @@ async function sendMessage(client, chatId, content) {
550
657
  chatId: data.chat_id,
551
658
  role: data.role,
552
659
  content: data.content,
553
- createdAt: data.created_at
660
+ createdAt: data.created_at,
661
+ ...parseTurnState(data)
554
662
  };
555
663
  }
556
- function mapMessage(data) {
557
- const toolCalls = data.tool_calls;
664
+ async function invokeAgent(client, agentRef, message, options = {}) {
665
+ const [ns, name] = splitAgentRef(agentRef);
666
+ const config = client.getConfig();
667
+ const body = { message };
668
+ if (options.sessionKey !== void 0) body.session_key = options.sessionKey;
669
+ if (options.reset !== void 0) body.reset = options.reset;
670
+ if (options.input !== void 0) body.input = options.input;
671
+ const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/invoke`, {
672
+ method: "POST",
673
+ body: JSON.stringify(body)
674
+ });
675
+ const data = await res.json();
558
676
  return {
559
- id: data.id,
677
+ reply: typeof data.reply === "string" ? data.reply : "",
560
678
  chatId: data.chat_id,
561
- role: data.role,
562
- content: data.content ?? null,
563
- toolCalls: toolCalls ?? null,
564
- toolCallId: data.tool_call_id ?? null,
565
- name: data.name ?? null,
566
- createdAt: data.created_at
679
+ sessionKey: data.session_key ?? null,
680
+ ...parseTurnState(data)
567
681
  };
568
682
  }
569
683
  async function getChat(client, chatId) {
570
684
  const config = client.getConfig();
571
685
  const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
572
686
  const data = await res.json();
573
- return {
687
+ const detail = {
574
688
  id: data.id,
575
689
  agentNamespace: data.agent_namespace,
576
690
  agentName: data.agent_name,
577
691
  title: data.title,
578
- messages: (data.messages || []).map(mapMessage),
579
- pendingApprovals: (data.pending_approvals || []).map((pa) => ({
580
- type: "approval_required",
581
- toolCallId: pa.tool_call_id,
582
- functionNamespace: pa.function_namespace,
583
- functionName: pa.function_name,
584
- arguments: pa.arguments || {}
585
- }))
692
+ messages: (data.messages || []).map(parseMessage),
693
+ pendingApprovals: (data.pending_approvals || []).map(parseApproval).filter((a) => a !== null)
586
694
  };
695
+ if (Array.isArray(data.pending_inputs)) {
696
+ detail.pendingInputs = parsePendingInputs(data.pending_inputs);
697
+ }
698
+ return detail;
587
699
  }
588
- async function approveToolCall(client, chatId, toolCallId, approved) {
700
+ async function approveToolCall(client, chatId, toolCallId, approved, options = {}) {
589
701
  const config = client.getConfig();
702
+ const body = { approved };
703
+ if (approved && options.alwaysAllow) body.always_allow = true;
590
704
  const res = await client.fetch(
591
- `${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
592
- { method: "POST", body: JSON.stringify({ approved }) }
705
+ `${config.apiBase}/chats/${chatId}/approve-tool/${encodeURIComponent(toolCallId)}`,
706
+ { method: "POST", body: JSON.stringify(body) }
593
707
  );
594
708
  const data = await res.json();
595
- return {
709
+ const result = {
596
710
  status: data.status,
597
711
  toolCallId: data.tool_call_id,
598
712
  channelId: data.channel_id
599
713
  };
714
+ if (data.resume === "waiting" || data.resume === "queued") result.resume = data.resume;
715
+ if (typeof data.message === "string") result.message = data.message;
716
+ return result;
600
717
  }
601
- function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
602
- const controller = new AbortController();
718
+ async function listPendingInputs(client, chatId) {
719
+ const config = client.getConfig();
720
+ const res = await client.fetch(`${config.apiBase}/chats/${chatId}/pending-input`);
721
+ return parsePendingInputs(await res.json());
722
+ }
723
+ async function answerPendingInput(client, chatId, toolCallId, answer, options = {}) {
603
724
  const config = client.getConfig();
725
+ const body = { answer };
726
+ if (options.wait) body.wait = true;
727
+ const res = await client.fetch(
728
+ `${config.apiBase}/chats/${chatId}/pending-input/${encodeURIComponent(toolCallId)}`,
729
+ { method: "POST", body: JSON.stringify(body) }
730
+ );
731
+ const data = await res.json();
732
+ if (options.wait) {
733
+ return {
734
+ toolCallId: data.tool_call_id,
735
+ chatId: data.chat_id,
736
+ reply: typeof data.reply === "string" ? data.reply : "",
737
+ message: data.message && typeof data.message === "object" ? parseMessage(data.message) : null,
738
+ ...parseTurnState(data)
739
+ };
740
+ }
741
+ return {
742
+ status: "answered",
743
+ toolCallId: data.tool_call_id,
744
+ resumed: !!data.resumed,
745
+ resumePending: !!data.resume_pending,
746
+ channelId: data.channel_id ?? null,
747
+ message: typeof data.message === "string" ? data.message : ""
748
+ };
749
+ }
750
+ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError, options = {}) {
751
+ const config = client.getConfig();
752
+ const params = new URLSearchParams({ last_id: options.lastId ?? "0" });
753
+ if (options.includeThinking) params.set("include_thinking", "true");
754
+ return runSSE(
755
+ `${config.apiBase}/chats/${chatId}/stream/${encodeURIComponent(channelId)}?${params}`,
756
+ (signal) => ({ headers: client.getAuthHeaders(), signal }),
757
+ "Stream reconnect failed",
758
+ onChunk,
759
+ onDone,
760
+ onError
761
+ );
762
+ }
763
+ function runSSE(url, init, failure, onChunk, onDone, onError) {
764
+ const controller = new AbortController();
604
765
  (async () => {
605
766
  try {
606
- const res = await fetch(
607
- `${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
608
- { headers: client.getAuthHeaders(), signal: controller.signal }
609
- );
767
+ const res = await fetch(url, init(controller.signal));
610
768
  if (!res.ok) {
611
769
  const body = await res.json().catch(() => ({ detail: res.statusText }));
612
- throw new Error(body.detail || "Stream reconnect failed");
770
+ throw new Error(body.detail || failure);
613
771
  }
614
772
  const reader = res.body.getReader();
615
773
  const decoder = new TextDecoder();
@@ -623,7 +781,7 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
623
781
  for (const chunk of chunks) {
624
782
  onChunk(chunk);
625
783
  if (chunk.event === "done") {
626
- onDone?.();
784
+ onDone?.(parseDone(chunk.data));
627
785
  return;
628
786
  }
629
787
  }
@@ -632,9 +790,13 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
632
790
  const { chunks } = parseSSE(buffer + "\n\n");
633
791
  for (const chunk of chunks) {
634
792
  onChunk(chunk);
793
+ if (chunk.event === "done") {
794
+ onDone?.(parseDone(chunk.data));
795
+ return;
796
+ }
635
797
  }
636
798
  }
637
- onDone?.();
799
+ onDone?.({ status: "closed" });
638
800
  } catch (err) {
639
801
  if (err.name === "AbortError") return;
640
802
  onError?.(err);
@@ -645,75 +807,33 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
645
807
  function parseSSE(buffer) {
646
808
  const chunks = [];
647
809
  const normalized = buffer.replace(/\r\n/g, "\n");
648
- const lines = normalized.split("\n");
649
- let event = "";
650
- let data = "";
651
- let remaining = "";
652
- for (let i = 0; i < lines.length; i++) {
653
- const line = lines[i];
654
- if (i === lines.length - 1 && !normalized.endsWith("\n")) {
655
- remaining = line;
656
- break;
657
- }
658
- if (line.startsWith("event:")) {
659
- event = line.slice(6).trim();
660
- } else if (line.startsWith("data:")) {
661
- data = line.slice(5).trim();
662
- } else if (line === "") {
663
- if (event || data) {
664
- chunks.push({ event: event || "message", data });
665
- event = "";
666
- data = "";
667
- }
810
+ let start = 0;
811
+ let end;
812
+ while ((end = normalized.indexOf("\n\n", start)) !== -1) {
813
+ const block = normalized.slice(start, end);
814
+ start = end + 2;
815
+ let event = "";
816
+ const data = [];
817
+ for (const line of block.split("\n")) {
818
+ if (line.startsWith("event:")) event = line.slice(6).trim();
819
+ else if (line.startsWith("data:")) data.push(line.slice(5).trim());
668
820
  }
821
+ if (event || data.length) chunks.push({ event: event || "message", data: data.join("\n") });
669
822
  }
670
- return { chunks, remaining };
823
+ return { chunks, remaining: normalized.slice(start) };
671
824
  }
672
- function streamMessage(client, chatId, content, onChunk, onDone, onError) {
673
- const controller = new AbortController();
825
+ function streamMessage(client, chatId, content, onChunk, onDone, onError, options = {}) {
674
826
  const config = client.getConfig();
675
- (async () => {
676
- try {
677
- const res = await fetch(`${config.apiBase}/chats/${chatId}/messages/stream`, {
678
- method: "POST",
679
- headers: client.getAuthHeaders(),
680
- body: JSON.stringify({ content }),
681
- signal: controller.signal
682
- });
683
- if (!res.ok) {
684
- const body = await res.json().catch(() => ({ detail: res.statusText }));
685
- throw new Error(body.detail || "Stream request failed");
686
- }
687
- const reader = res.body.getReader();
688
- const decoder = new TextDecoder();
689
- let buffer = "";
690
- while (true) {
691
- const { done, value } = await reader.read();
692
- if (done) break;
693
- buffer += decoder.decode(value, { stream: true });
694
- const { chunks, remaining } = parseSSE(buffer);
695
- buffer = remaining;
696
- for (const chunk of chunks) {
697
- onChunk(chunk);
698
- if (chunk.event === "done") {
699
- onDone?.();
700
- return;
701
- }
702
- }
703
- }
704
- if (buffer.trim()) {
705
- const { chunks } = parseSSE(buffer + "\n\n");
706
- for (const chunk of chunks) {
707
- onChunk(chunk);
708
- }
709
- }
710
- onDone?.();
711
- } catch (err) {
712
- if (err.name === "AbortError") return;
713
- onError?.(err);
714
- }
715
- })();
716
- return controller;
827
+ const body = { content };
828
+ if (options.includeThinking) body.include_thinking = true;
829
+ return runSSE(
830
+ `${config.apiBase}/chats/${chatId}/messages/stream`,
831
+ (signal) => ({ method: "POST", headers: client.getAuthHeaders(), body: JSON.stringify(body), signal }),
832
+ "Stream request failed",
833
+ onChunk,
834
+ onDone,
835
+ onError
836
+ );
717
837
  }
718
838
 
719
839
  // src/client/collections.ts
@@ -755,6 +875,9 @@ async function generateFileUrl(client, namespace, collection, filename, expiresI
755
875
  SinasNotFoundError,
756
876
  SinasPermissionError,
757
877
  SinasTimeoutError,
878
+ answerPendingInput,
879
+ answerResumeChannel,
880
+ approvalResumeChannel,
758
881
  approveToolCall,
759
882
  cancelBatch,
760
883
  checkPermissions,
@@ -767,11 +890,16 @@ async function generateFileUrl(client, namespace, collection, filename, expiresI
767
890
  getChat,
768
891
  getInfo,
769
892
  getMe,
893
+ invokeAgent,
770
894
  listBatchExecutions,
771
895
  listCollections,
772
896
  listFiles,
897
+ listPendingInputs,
773
898
  login,
774
899
  logout,
900
+ parsePendingInput,
901
+ parsePendingInputs,
902
+ pendingInputFromChunk,
775
903
  refresh,
776
904
  sendMessage,
777
905
  stateDelete,