@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.
@@ -50,6 +50,9 @@ var SinasSDK = (() => {
50
50
  SinasPermissionError: () => SinasPermissionError,
51
51
  SinasProvider: () => SinasProvider,
52
52
  SinasTimeoutError: () => SinasTimeoutError,
53
+ answerPendingInput: () => answerPendingInput,
54
+ answerResumeChannel: () => answerResumeChannel,
55
+ approvalResumeChannel: () => approvalResumeChannel,
53
56
  approveToolCall: () => approveToolCall,
54
57
  cancelBatch: () => cancelBatch,
55
58
  checkPermissions: () => checkPermissions,
@@ -62,11 +65,16 @@ var SinasSDK = (() => {
62
65
  getChat: () => getChat,
63
66
  getInfo: () => getInfo,
64
67
  getMe: () => getMe,
68
+ invokeAgent: () => invokeAgent,
65
69
  listBatchExecutions: () => listBatchExecutions,
66
70
  listCollections: () => listCollections,
67
71
  listFiles: () => listFiles,
72
+ listPendingInputs: () => listPendingInputs,
68
73
  login: () => login,
69
74
  logout: () => logout,
75
+ parsePendingInput: () => parsePendingInput,
76
+ parsePendingInputs: () => parsePendingInputs,
77
+ pendingInputFromChunk: () => pendingInputFromChunk,
70
78
  refresh: () => refresh,
71
79
  sendMessage: () => sendMessage,
72
80
  stateDelete: () => stateDelete,
@@ -548,12 +556,111 @@ var SinasSDK = (() => {
548
556
  return res.json();
549
557
  }
550
558
 
559
+ // src/client/chatEvents.ts
560
+ var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
561
+ var str = (v) => typeof v === "string" ? v : null;
562
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
563
+ var optionsOf = (v) => Array.isArray(v) && v.length > 0 && v.every((o) => typeof o === "string") ? v : null;
564
+ function parsePendingInput(raw) {
565
+ if (!isObject(raw)) return null;
566
+ if (typeof raw.tool_call_id !== "string" || !raw.tool_call_id) return null;
567
+ return {
568
+ toolCallId: raw.tool_call_id,
569
+ question: str(raw.question) ?? "",
570
+ options: optionsOf(raw.options),
571
+ expiresAt: str(raw.expires_at),
572
+ createdAt: str(raw.created_at)
573
+ };
574
+ }
575
+ function parsePendingInputs(raw) {
576
+ return Array.isArray(raw) ? raw.map(parsePendingInput).filter((p) => p !== null) : [];
577
+ }
578
+ function pendingInputFromChunk(chunk) {
579
+ if (chunk.event !== "input_required" && chunk.event !== "message") return null;
580
+ let parsed;
581
+ try {
582
+ parsed = JSON.parse(chunk.data);
583
+ } catch {
584
+ return null;
585
+ }
586
+ if (chunk.event === "message" && (!isObject(parsed) || parsed.type !== "input_required")) return null;
587
+ return parsePendingInput(parsed);
588
+ }
589
+ function parseApproval(raw) {
590
+ if (!isObject(raw) || typeof raw.tool_call_id !== "string") return null;
591
+ return {
592
+ type: str(raw.type) ?? "approval_required",
593
+ toolCallId: raw.tool_call_id,
594
+ functionNamespace: str(raw.function_namespace) ?? "",
595
+ functionName: str(raw.function_name) ?? "",
596
+ arguments: isObject(raw.arguments) ? raw.arguments : {}
597
+ };
598
+ }
599
+ function parseUsage(raw) {
600
+ if (!isObject(raw)) return null;
601
+ return {
602
+ calls: num(raw.calls),
603
+ promptTokens: num(raw.prompt_tokens),
604
+ completionTokens: num(raw.completion_tokens),
605
+ totalTokens: num(raw.total_tokens),
606
+ cacheReadTokens: num(raw.cache_read_tokens),
607
+ cacheWriteTokens: num(raw.cache_write_tokens),
608
+ costUsd: typeof raw.cost_usd === "number" ? raw.cost_usd : null,
609
+ unpricedCalls: num(raw.unpriced_calls),
610
+ errors: num(raw.errors)
611
+ };
612
+ }
613
+ var TURN_STATUSES = ["completed", "input_required", "in_progress", "injected"];
614
+ function parseTurnState(raw) {
615
+ const status = TURN_STATUSES.includes(raw.status) ? raw.status : "completed";
616
+ return {
617
+ status,
618
+ pendingInputs: parsePendingInputs(raw.pending_inputs),
619
+ channelId: str(raw.channel_id),
620
+ usage: parseUsage(raw.usage)
621
+ };
622
+ }
623
+ function parseMessage(data) {
624
+ const toolCalls = data.tool_calls;
625
+ return {
626
+ id: data.id,
627
+ chatId: data.chat_id,
628
+ role: data.role,
629
+ content: data.content ?? null,
630
+ toolCalls: toolCalls ?? null,
631
+ toolCallId: data.tool_call_id ?? null,
632
+ name: data.name ?? null,
633
+ createdAt: data.created_at
634
+ };
635
+ }
636
+ function parseDone(data) {
637
+ try {
638
+ const parsed = JSON.parse(data);
639
+ if (isObject(parsed) && typeof parsed.status === "string") return { status: parsed.status };
640
+ } catch {
641
+ }
642
+ return { status: "completed" };
643
+ }
644
+ function approvalResumeChannel(result, approved = true) {
645
+ if (result.resume === "waiting") return null;
646
+ if (result.resume === void 0 && !approved) return null;
647
+ return result.channelId || null;
648
+ }
649
+ function answerResumeChannel(result) {
650
+ if (!result.resumed && !result.resumePending) return null;
651
+ return result.channelId || null;
652
+ }
653
+
551
654
  // src/client/chat.ts
552
- async function createChat(client, agentRef, input) {
655
+ function splitAgentRef(agentRef) {
553
656
  const [ns, name] = agentRef.split("/");
554
657
  if (!ns || !name) {
555
658
  throw new Error(`Invalid agent ref "${agentRef}": expected "namespace/name"`);
556
659
  }
660
+ return [ns, name];
661
+ }
662
+ async function createChat(client, agentRef, input) {
663
+ const [ns, name] = splitAgentRef(agentRef);
557
664
  const config = client.getConfig();
558
665
  const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/chats`, {
559
666
  method: "POST",
@@ -579,66 +686,117 @@ var SinasSDK = (() => {
579
686
  chatId: data.chat_id,
580
687
  role: data.role,
581
688
  content: data.content,
582
- createdAt: data.created_at
689
+ createdAt: data.created_at,
690
+ ...parseTurnState(data)
583
691
  };
584
692
  }
585
- function mapMessage(data) {
586
- const toolCalls = data.tool_calls;
693
+ async function invokeAgent(client, agentRef, message, options = {}) {
694
+ const [ns, name] = splitAgentRef(agentRef);
695
+ const config = client.getConfig();
696
+ const body = { message };
697
+ if (options.sessionKey !== void 0) body.session_key = options.sessionKey;
698
+ if (options.reset !== void 0) body.reset = options.reset;
699
+ if (options.input !== void 0) body.input = options.input;
700
+ const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/invoke`, {
701
+ method: "POST",
702
+ body: JSON.stringify(body)
703
+ });
704
+ const data = await res.json();
587
705
  return {
588
- id: data.id,
706
+ reply: typeof data.reply === "string" ? data.reply : "",
589
707
  chatId: data.chat_id,
590
- role: data.role,
591
- content: data.content ?? null,
592
- toolCalls: toolCalls ?? null,
593
- toolCallId: data.tool_call_id ?? null,
594
- name: data.name ?? null,
595
- createdAt: data.created_at
708
+ sessionKey: data.session_key ?? null,
709
+ ...parseTurnState(data)
596
710
  };
597
711
  }
598
712
  async function getChat(client, chatId) {
599
713
  const config = client.getConfig();
600
714
  const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
601
715
  const data = await res.json();
602
- return {
716
+ const detail = {
603
717
  id: data.id,
604
718
  agentNamespace: data.agent_namespace,
605
719
  agentName: data.agent_name,
606
720
  title: data.title,
607
- messages: (data.messages || []).map(mapMessage),
608
- pendingApprovals: (data.pending_approvals || []).map((pa) => ({
609
- type: "approval_required",
610
- toolCallId: pa.tool_call_id,
611
- functionNamespace: pa.function_namespace,
612
- functionName: pa.function_name,
613
- arguments: pa.arguments || {}
614
- }))
721
+ messages: (data.messages || []).map(parseMessage),
722
+ pendingApprovals: (data.pending_approvals || []).map(parseApproval).filter((a) => a !== null)
615
723
  };
724
+ if (Array.isArray(data.pending_inputs)) {
725
+ detail.pendingInputs = parsePendingInputs(data.pending_inputs);
726
+ }
727
+ return detail;
616
728
  }
617
- async function approveToolCall(client, chatId, toolCallId, approved) {
729
+ async function approveToolCall(client, chatId, toolCallId, approved, options = {}) {
618
730
  const config = client.getConfig();
731
+ const body = { approved };
732
+ if (approved && options.alwaysAllow) body.always_allow = true;
619
733
  const res = await client.fetch(
620
- `${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
621
- { method: "POST", body: JSON.stringify({ approved }) }
734
+ `${config.apiBase}/chats/${chatId}/approve-tool/${encodeURIComponent(toolCallId)}`,
735
+ { method: "POST", body: JSON.stringify(body) }
622
736
  );
623
737
  const data = await res.json();
624
- return {
738
+ const result = {
625
739
  status: data.status,
626
740
  toolCallId: data.tool_call_id,
627
741
  channelId: data.channel_id
628
742
  };
743
+ if (data.resume === "waiting" || data.resume === "queued") result.resume = data.resume;
744
+ if (typeof data.message === "string") result.message = data.message;
745
+ return result;
629
746
  }
630
- function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
631
- const controller = new AbortController();
747
+ async function listPendingInputs(client, chatId) {
748
+ const config = client.getConfig();
749
+ const res = await client.fetch(`${config.apiBase}/chats/${chatId}/pending-input`);
750
+ return parsePendingInputs(await res.json());
751
+ }
752
+ async function answerPendingInput(client, chatId, toolCallId, answer, options = {}) {
632
753
  const config = client.getConfig();
754
+ const body = { answer };
755
+ if (options.wait) body.wait = true;
756
+ const res = await client.fetch(
757
+ `${config.apiBase}/chats/${chatId}/pending-input/${encodeURIComponent(toolCallId)}`,
758
+ { method: "POST", body: JSON.stringify(body) }
759
+ );
760
+ const data = await res.json();
761
+ if (options.wait) {
762
+ return {
763
+ toolCallId: data.tool_call_id,
764
+ chatId: data.chat_id,
765
+ reply: typeof data.reply === "string" ? data.reply : "",
766
+ message: data.message && typeof data.message === "object" ? parseMessage(data.message) : null,
767
+ ...parseTurnState(data)
768
+ };
769
+ }
770
+ return {
771
+ status: "answered",
772
+ toolCallId: data.tool_call_id,
773
+ resumed: !!data.resumed,
774
+ resumePending: !!data.resume_pending,
775
+ channelId: data.channel_id ?? null,
776
+ message: typeof data.message === "string" ? data.message : ""
777
+ };
778
+ }
779
+ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError, options = {}) {
780
+ const config = client.getConfig();
781
+ const params = new URLSearchParams({ last_id: options.lastId ?? "0" });
782
+ if (options.includeThinking) params.set("include_thinking", "true");
783
+ return runSSE(
784
+ `${config.apiBase}/chats/${chatId}/stream/${encodeURIComponent(channelId)}?${params}`,
785
+ (signal) => ({ headers: client.getAuthHeaders(), signal }),
786
+ "Stream reconnect failed",
787
+ onChunk,
788
+ onDone,
789
+ onError
790
+ );
791
+ }
792
+ function runSSE(url, init, failure, onChunk, onDone, onError) {
793
+ const controller = new AbortController();
633
794
  (async () => {
634
795
  try {
635
- const res = await fetch(
636
- `${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
637
- { headers: client.getAuthHeaders(), signal: controller.signal }
638
- );
796
+ const res = await fetch(url, init(controller.signal));
639
797
  if (!res.ok) {
640
798
  const body = await res.json().catch(() => ({ detail: res.statusText }));
641
- throw new Error(body.detail || "Stream reconnect failed");
799
+ throw new Error(body.detail || failure);
642
800
  }
643
801
  const reader = res.body.getReader();
644
802
  const decoder = new TextDecoder();
@@ -652,7 +810,7 @@ var SinasSDK = (() => {
652
810
  for (const chunk of chunks) {
653
811
  onChunk(chunk);
654
812
  if (chunk.event === "done") {
655
- onDone?.();
813
+ onDone?.(parseDone(chunk.data));
656
814
  return;
657
815
  }
658
816
  }
@@ -661,9 +819,13 @@ var SinasSDK = (() => {
661
819
  const { chunks } = parseSSE(buffer + "\n\n");
662
820
  for (const chunk of chunks) {
663
821
  onChunk(chunk);
822
+ if (chunk.event === "done") {
823
+ onDone?.(parseDone(chunk.data));
824
+ return;
825
+ }
664
826
  }
665
827
  }
666
- onDone?.();
828
+ onDone?.({ status: "closed" });
667
829
  } catch (err) {
668
830
  if (err.name === "AbortError") return;
669
831
  onError?.(err);
@@ -674,75 +836,33 @@ var SinasSDK = (() => {
674
836
  function parseSSE(buffer) {
675
837
  const chunks = [];
676
838
  const normalized = buffer.replace(/\r\n/g, "\n");
677
- const lines = normalized.split("\n");
678
- let event = "";
679
- let data = "";
680
- let remaining = "";
681
- for (let i = 0; i < lines.length; i++) {
682
- const line = lines[i];
683
- if (i === lines.length - 1 && !normalized.endsWith("\n")) {
684
- remaining = line;
685
- break;
686
- }
687
- if (line.startsWith("event:")) {
688
- event = line.slice(6).trim();
689
- } else if (line.startsWith("data:")) {
690
- data = line.slice(5).trim();
691
- } else if (line === "") {
692
- if (event || data) {
693
- chunks.push({ event: event || "message", data });
694
- event = "";
695
- data = "";
696
- }
839
+ let start = 0;
840
+ let end;
841
+ while ((end = normalized.indexOf("\n\n", start)) !== -1) {
842
+ const block = normalized.slice(start, end);
843
+ start = end + 2;
844
+ let event = "";
845
+ const data = [];
846
+ for (const line of block.split("\n")) {
847
+ if (line.startsWith("event:")) event = line.slice(6).trim();
848
+ else if (line.startsWith("data:")) data.push(line.slice(5).trim());
697
849
  }
850
+ if (event || data.length) chunks.push({ event: event || "message", data: data.join("\n") });
698
851
  }
699
- return { chunks, remaining };
852
+ return { chunks, remaining: normalized.slice(start) };
700
853
  }
701
- function streamMessage(client, chatId, content, onChunk, onDone, onError) {
702
- const controller = new AbortController();
854
+ function streamMessage(client, chatId, content, onChunk, onDone, onError, options = {}) {
703
855
  const config = client.getConfig();
704
- (async () => {
705
- try {
706
- const res = await fetch(`${config.apiBase}/chats/${chatId}/messages/stream`, {
707
- method: "POST",
708
- headers: client.getAuthHeaders(),
709
- body: JSON.stringify({ content }),
710
- signal: controller.signal
711
- });
712
- if (!res.ok) {
713
- const body = await res.json().catch(() => ({ detail: res.statusText }));
714
- throw new Error(body.detail || "Stream request failed");
715
- }
716
- const reader = res.body.getReader();
717
- const decoder = new TextDecoder();
718
- let buffer = "";
719
- while (true) {
720
- const { done, value } = await reader.read();
721
- if (done) break;
722
- buffer += decoder.decode(value, { stream: true });
723
- const { chunks, remaining } = parseSSE(buffer);
724
- buffer = remaining;
725
- for (const chunk of chunks) {
726
- onChunk(chunk);
727
- if (chunk.event === "done") {
728
- onDone?.();
729
- return;
730
- }
731
- }
732
- }
733
- if (buffer.trim()) {
734
- const { chunks } = parseSSE(buffer + "\n\n");
735
- for (const chunk of chunks) {
736
- onChunk(chunk);
737
- }
738
- }
739
- onDone?.();
740
- } catch (err) {
741
- if (err.name === "AbortError") return;
742
- onError?.(err);
743
- }
744
- })();
745
- return controller;
856
+ const body = { content };
857
+ if (options.includeThinking) body.include_thinking = true;
858
+ return runSSE(
859
+ `${config.apiBase}/chats/${chatId}/messages/stream`,
860
+ (signal) => ({ method: "POST", headers: client.getAuthHeaders(), body: JSON.stringify(body), signal }),
861
+ "Stream request failed",
862
+ onChunk,
863
+ onDone,
864
+ onError
865
+ );
746
866
  }
747
867
 
748
868
  // src/client/collections.ts
@@ -957,29 +1077,172 @@ var SinasSDK = (() => {
957
1077
 
958
1078
  // src/hooks/useChat.ts
959
1079
  var import_react5 = __toESM(require_react(), 1);
1080
+
1081
+ // src/hooks/chatState.ts
1082
+ function parseMessageChunk(data) {
1083
+ let parsed;
1084
+ try {
1085
+ parsed = JSON.parse(data);
1086
+ } catch {
1087
+ return { text: data };
1088
+ }
1089
+ if (!parsed || typeof parsed !== "object") return { text: "" };
1090
+ const p = parsed;
1091
+ const out = { text: typeof p.content === "string" ? p.content : "" };
1092
+ if (typeof p.thinking === "string") out.thinking = p.thinking;
1093
+ return out;
1094
+ }
1095
+ function applyTextDelta(messages, text, chatId, now = /* @__PURE__ */ new Date()) {
1096
+ if (!text) return messages;
1097
+ const last = messages[messages.length - 1];
1098
+ if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
1099
+ const next = [...messages];
1100
+ next[next.length - 1] = { ...last, content: (last.content || "") + text };
1101
+ return next;
1102
+ }
1103
+ return [
1104
+ ...messages,
1105
+ {
1106
+ id: `tmp-text-${now.getTime()}`,
1107
+ chatId,
1108
+ role: "assistant",
1109
+ content: text,
1110
+ toolCalls: null,
1111
+ toolCallId: null,
1112
+ name: null,
1113
+ createdAt: now.toISOString(),
1114
+ streaming: true
1115
+ }
1116
+ ];
1117
+ }
1118
+ function applyToolStart(messages, event, chatId, now = /* @__PURE__ */ new Date()) {
1119
+ if (messages.some((m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id))) return messages;
1120
+ const frozen = messages.map(
1121
+ (m, i) => i === messages.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
1122
+ );
1123
+ return [
1124
+ ...frozen,
1125
+ {
1126
+ id: `tmp-tc-${event.tool_call_id}`,
1127
+ chatId,
1128
+ role: "assistant",
1129
+ content: null,
1130
+ toolCalls: [
1131
+ {
1132
+ id: event.tool_call_id,
1133
+ type: "function",
1134
+ function: { name: event.name, arguments: event.arguments || "{}" },
1135
+ description: event.description
1136
+ }
1137
+ ],
1138
+ toolCallId: null,
1139
+ name: null,
1140
+ createdAt: now.toISOString(),
1141
+ streaming: true
1142
+ }
1143
+ ];
1144
+ }
1145
+ function applyToolEnd(messages, event) {
1146
+ if (event.result === void 0) return messages;
1147
+ const content = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
1148
+ return messages.map(
1149
+ (m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id) ? { ...m, toolResults: { ...m.toolResults, [event.tool_call_id]: { content, name: event.name } } } : m
1150
+ );
1151
+ }
1152
+ function addApproval(current, approval) {
1153
+ return current.some((a) => a.toolCallId === approval.toolCallId) ? current : [...current, approval];
1154
+ }
1155
+ function mergePendingInputs(current, incoming) {
1156
+ const seen = new Set(current.map((p) => p.toolCallId));
1157
+ const added = incoming.filter((p) => !seen.has(p.toolCallId) && seen.add(p.toolCallId));
1158
+ return added.length ? [...current, ...added] : current;
1159
+ }
1160
+ var NOT_THINKING = { active: false, rounds: [] };
1161
+ function applyThinking(state, thinking) {
1162
+ if (!state.active) return { active: true, rounds: [...state.rounds, thinking] };
1163
+ if (!thinking) return state;
1164
+ const rounds = [...state.rounds];
1165
+ rounds[rounds.length - 1] += thinking;
1166
+ return { active: true, rounds };
1167
+ }
1168
+ function stopThinking(state) {
1169
+ return state.active ? { ...state, active: false } : state;
1170
+ }
1171
+ function thinkingView(state) {
1172
+ return { active: state.active, text: state.rounds.filter((r) => r).join("\n\n") };
1173
+ }
1174
+ function createLatestGate() {
1175
+ let latest = 0;
1176
+ return {
1177
+ next: () => ++latest,
1178
+ isCurrent: (ticket) => ticket === latest
1179
+ };
1180
+ }
1181
+ var PENDING_POLL_MAX_MS = 3e4;
1182
+ var PENDING_POLL_MIN_MS = 1e3;
1183
+ var EXPIRY_GRACE_MS = 5e3;
1184
+ var OVERDUE_POLL_MS = 1e4;
1185
+ function nextPendingPollMs(inputs, nowMs) {
1186
+ let wait = PENDING_POLL_MAX_MS;
1187
+ for (const input of inputs) {
1188
+ const deadline = input.expiresAt ? Date.parse(input.expiresAt) : NaN;
1189
+ if (Number.isNaN(deadline)) continue;
1190
+ const untilDue = deadline + EXPIRY_GRACE_MS - nowMs;
1191
+ wait = Math.min(wait, untilDue > 0 ? untilDue : OVERDUE_POLL_MS);
1192
+ }
1193
+ return Math.max(PENDING_POLL_MIN_MS, wait);
1194
+ }
1195
+ var TRAILING_POLLS_MS = [5e3, 15e3, 3e4];
1196
+
1197
+ // src/hooks/useChat.ts
960
1198
  function useChat(agentRef, options) {
961
1199
  const client = useClient();
1200
+ const includeThinking = !!options?.includeThinking;
1201
+ const pollPendingInputs = options?.pollPendingInputs !== false;
962
1202
  const [chatId, setChatId] = import_react5.default.useState(options?.chatId ?? null);
963
1203
  const [messages, setMessages] = import_react5.default.useState([]);
964
1204
  const [loading, setLoading] = import_react5.default.useState(false);
965
1205
  const [streaming, setStreaming] = import_react5.default.useState(false);
966
1206
  const [error, setError] = import_react5.default.useState(null);
967
1207
  const [pendingApprovals, setPendingApprovals] = import_react5.default.useState([]);
1208
+ const [pendingInputs, setPendingInputs] = import_react5.default.useState([]);
1209
+ const [thinking, setThinking] = import_react5.default.useState(NOT_THINKING);
1210
+ const thinkingShown = import_react5.default.useMemo(() => thinkingView(thinking), [thinking]);
968
1211
  const [toolStatus, setToolStatus] = import_react5.default.useState([]);
1212
+ const [trailingPoll, setTrailingPoll] = import_react5.default.useState(TRAILING_POLLS_MS.length);
1213
+ const [pollTick, setPollTick] = import_react5.default.useState(0);
969
1214
  const abortRef = import_react5.default.useRef(null);
970
1215
  const chatIdRef = import_react5.default.useRef(chatId);
971
1216
  chatIdRef.current = chatId;
1217
+ const [gate] = import_react5.default.useState(createLatestGate);
1218
+ const refresh2 = import_react5.default.useCallback(
1219
+ async (id) => {
1220
+ const ticket = gate.next();
1221
+ try {
1222
+ const detail = await getChat(client, id);
1223
+ if (!gate.isCurrent(ticket)) return null;
1224
+ setMessages(detail.messages);
1225
+ setPendingApprovals(detail.pendingApprovals ?? []);
1226
+ const inputs = detail.pendingInputs ?? [];
1227
+ setPendingInputs(inputs);
1228
+ return inputs;
1229
+ } catch {
1230
+ return null;
1231
+ }
1232
+ },
1233
+ [client, gate]
1234
+ );
972
1235
  import_react5.default.useEffect(() => {
973
1236
  if (!options?.chatId) return;
974
1237
  let cancelled = false;
1238
+ const ticket = gate.next();
975
1239
  setLoading(true);
976
1240
  getChat(client, options.chatId).then((detail) => {
977
- if (cancelled) return;
1241
+ if (cancelled || !gate.isCurrent(ticket)) return;
978
1242
  setChatId(detail.id);
979
1243
  setMessages(detail.messages);
980
- if (detail.pendingApprovals?.length) {
981
- setPendingApprovals(detail.pendingApprovals);
982
- }
1244
+ setPendingApprovals(detail.pendingApprovals ?? []);
1245
+ setPendingInputs(detail.pendingInputs ?? []);
983
1246
  }).catch((e) => {
984
1247
  if (cancelled) return;
985
1248
  setError(e.message);
@@ -989,74 +1252,52 @@ var SinasSDK = (() => {
989
1252
  return () => {
990
1253
  cancelled = true;
991
1254
  };
992
- }, [client, options?.chatId]);
1255
+ }, [client, gate, options?.chatId]);
993
1256
  import_react5.default.useEffect(() => {
994
1257
  return () => {
995
1258
  abortRef.current?.abort();
996
1259
  };
997
1260
  }, []);
998
- const handleStreamChunks = import_react5.default.useCallback(
1261
+ import_react5.default.useEffect(() => {
1262
+ if (!pollPendingInputs || streaming || !chatId) return;
1263
+ let delay = null;
1264
+ if (pendingInputs.length > 0) delay = nextPendingPollMs(pendingInputs, Date.now());
1265
+ else if (trailingPoll < TRAILING_POLLS_MS.length) delay = TRAILING_POLLS_MS[trailingPoll];
1266
+ if (delay === null) return;
1267
+ const waiting = pendingInputs.map((p) => p.toolCallId);
1268
+ const timer = setTimeout(async () => {
1269
+ const now = await refresh2(chatId);
1270
+ if (waiting.length === 0) setTrailingPoll((i) => i + 1);
1271
+ else if (now && waiting.some((id) => !now.some((p) => p.toolCallId === id))) setTrailingPoll(0);
1272
+ setPollTick((t) => t + 1);
1273
+ }, delay);
1274
+ return () => clearTimeout(timer);
1275
+ }, [pollPendingInputs, chatId, streaming, pendingInputs, trailingPoll, pollTick, refresh2]);
1276
+ const handleStreamChunk = import_react5.default.useCallback(
999
1277
  (chunk) => {
1278
+ const asked = pendingInputFromChunk(chunk);
1279
+ if (asked) {
1280
+ setThinking(stopThinking);
1281
+ setPendingInputs((prev) => mergePendingInputs(prev, [asked]));
1282
+ return;
1283
+ }
1284
+ const chatIdNow = chatIdRef.current || "";
1000
1285
  if (chunk.event === "message") {
1001
- const newContent = (() => {
1002
- try {
1003
- const parsed = JSON.parse(chunk.data);
1004
- return parsed.content || "";
1005
- } catch {
1006
- return chunk.data;
1007
- }
1008
- })();
1009
- if (!newContent) return;
1010
- setMessages((prev) => {
1011
- const last = prev[prev.length - 1];
1012
- if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
1013
- const next = [...prev];
1014
- next[next.length - 1] = { ...last, content: (last.content || "") + newContent };
1015
- return next;
1016
- }
1017
- return [...prev, {
1018
- id: `tmp-text-${Date.now()}`,
1019
- chatId: chatIdRef.current || "",
1020
- role: "assistant",
1021
- content: newContent,
1022
- toolCalls: null,
1023
- toolCallId: null,
1024
- name: null,
1025
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1026
- streaming: true
1027
- }];
1028
- });
1286
+ const { text, thinking: thought } = parseMessageChunk(chunk.data);
1287
+ if (thought !== void 0) setThinking((prev) => applyThinking(prev, thought));
1288
+ if (text) {
1289
+ setThinking(stopThinking);
1290
+ setMessages((prev) => applyTextDelta(prev, text, chatIdNow));
1291
+ }
1029
1292
  } else if (chunk.event === "tool_start") {
1293
+ setThinking(stopThinking);
1030
1294
  try {
1031
1295
  const parsed = JSON.parse(chunk.data);
1032
1296
  setToolStatus((prev) => [
1033
1297
  ...prev,
1034
1298
  { toolCallId: parsed.tool_call_id, name: parsed.name, description: parsed.description, status: "running" }
1035
1299
  ]);
1036
- setMessages((prev) => {
1037
- if (prev.some((m) => m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id))) {
1038
- return prev;
1039
- }
1040
- const next = prev.map(
1041
- (m, i) => i === prev.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
1042
- );
1043
- return [...next, {
1044
- id: `tmp-tc-${parsed.tool_call_id}`,
1045
- chatId: chatIdRef.current || "",
1046
- role: "assistant",
1047
- content: null,
1048
- toolCalls: [{
1049
- id: parsed.tool_call_id,
1050
- type: "function",
1051
- function: { name: parsed.name, arguments: parsed.arguments || "{}" },
1052
- description: parsed.description
1053
- }],
1054
- toolCallId: null,
1055
- name: null,
1056
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1057
- streaming: true
1058
- }];
1059
- });
1300
+ setMessages((prev) => applyToolStart(prev, parsed, chatIdNow));
1060
1301
  } catch {
1061
1302
  }
1062
1303
  } else if (chunk.event === "tool_end") {
@@ -1065,39 +1306,18 @@ var SinasSDK = (() => {
1065
1306
  setToolStatus(
1066
1307
  (prev) => prev.map((t) => t.toolCallId === parsed.tool_call_id ? { ...t, status: "complete" } : t)
1067
1308
  );
1068
- if (parsed.result !== void 0) {
1069
- const resultContent = typeof parsed.result === "string" ? parsed.result : JSON.stringify(parsed.result);
1070
- setMessages(
1071
- (prev) => prev.map((m) => {
1072
- if (m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id)) {
1073
- return {
1074
- ...m,
1075
- toolResults: {
1076
- ...m.toolResults,
1077
- [parsed.tool_call_id]: { content: resultContent, name: parsed.name }
1078
- }
1079
- };
1080
- }
1081
- return m;
1082
- })
1083
- );
1084
- }
1309
+ setMessages((prev) => applyToolEnd(prev, parsed));
1085
1310
  } catch {
1086
1311
  }
1087
1312
  } else if (chunk.event === "approval_required") {
1313
+ setThinking(stopThinking);
1088
1314
  try {
1089
- const raw = JSON.parse(chunk.data);
1090
- const parsed = {
1091
- type: raw.type,
1092
- toolCallId: raw.tool_call_id,
1093
- functionNamespace: raw.function_namespace,
1094
- functionName: raw.function_name,
1095
- arguments: raw.arguments
1096
- };
1097
- setPendingApprovals((prev) => [...prev, parsed]);
1315
+ const approval = parseApproval(JSON.parse(chunk.data));
1316
+ if (approval) setPendingApprovals((prev) => addApproval(prev, approval));
1098
1317
  } catch {
1099
1318
  }
1100
1319
  } else if (chunk.event === "error") {
1320
+ setThinking(stopThinking);
1101
1321
  try {
1102
1322
  const parsed = JSON.parse(chunk.data);
1103
1323
  setError(parsed.error || "Stream error");
@@ -1108,18 +1328,37 @@ var SinasSDK = (() => {
1108
1328
  },
1109
1329
  []
1110
1330
  );
1111
- const refreshMessages = import_react5.default.useCallback(
1112
- async (id) => {
1113
- try {
1114
- const detail = await getChat(client, id);
1115
- setMessages(detail.messages);
1116
- if (detail.pendingApprovals?.length) {
1117
- setPendingApprovals(detail.pendingApprovals);
1118
- }
1119
- } catch {
1331
+ const streamCallbacks = import_react5.default.useCallback(
1332
+ (id) => [
1333
+ (_done) => {
1334
+ setStreaming(false);
1335
+ setToolStatus([]);
1336
+ setThinking(stopThinking);
1337
+ refresh2(id);
1338
+ },
1339
+ (err) => {
1340
+ setError(err.message);
1341
+ setStreaming(false);
1342
+ setToolStatus([]);
1343
+ setThinking(stopThinking);
1120
1344
  }
1345
+ ],
1346
+ [refresh2]
1347
+ );
1348
+ const followChannel = import_react5.default.useCallback(
1349
+ (id, channelId, lastId) => {
1350
+ abortRef.current?.abort();
1351
+ gate.next();
1352
+ setStreaming(true);
1353
+ setError(null);
1354
+ setThinking(NOT_THINKING);
1355
+ const [onDone, onError] = streamCallbacks(id);
1356
+ abortRef.current = streamFromChannel(client, id, channelId, handleStreamChunk, onDone, onError, {
1357
+ includeThinking,
1358
+ lastId
1359
+ });
1121
1360
  },
1122
- [client]
1361
+ [client, gate, includeThinking, handleStreamChunk, streamCallbacks]
1123
1362
  );
1124
1363
  const send = import_react5.default.useCallback(
1125
1364
  (content) => {
@@ -1141,10 +1380,12 @@ var SinasSDK = (() => {
1141
1380
  setLoading(false);
1142
1381
  }
1143
1382
  abortRef.current?.abort();
1383
+ gate.next();
1144
1384
  setStreaming(true);
1145
1385
  setError(null);
1146
1386
  setPendingApprovals([]);
1147
1387
  setToolStatus([]);
1388
+ setThinking(NOT_THINKING);
1148
1389
  const displayContent = typeof content === "string" ? content : JSON.stringify(content);
1149
1390
  const userMsg = {
1150
1391
  id: `tmp-user-${Date.now()}`,
@@ -1157,80 +1398,77 @@ var SinasSDK = (() => {
1157
1398
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1158
1399
  };
1159
1400
  setMessages((prev) => [...prev, userMsg]);
1160
- const controller = streamMessage(
1161
- client,
1162
- activeChatId,
1163
- content,
1164
- handleStreamChunks,
1165
- () => {
1166
- setStreaming(false);
1167
- setToolStatus([]);
1168
- refreshMessages(activeChatId);
1169
- },
1170
- (err) => {
1171
- setError(err.message);
1172
- setStreaming(false);
1173
- setToolStatus([]);
1174
- }
1175
- );
1176
- abortRef.current = controller;
1401
+ const [onDone, onError] = streamCallbacks(activeChatId);
1402
+ abortRef.current = streamMessage(client, activeChatId, content, handleStreamChunk, onDone, onError, {
1403
+ includeThinking
1404
+ });
1177
1405
  };
1178
1406
  doSend();
1179
1407
  },
1180
- [client, agentRef, options?.input, handleStreamChunks, refreshMessages]
1408
+ [client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
1181
1409
  );
1182
- const handleApproval = import_react5.default.useCallback(
1183
- (toolCallId, approved) => {
1184
- const currentChatId = chatIdRef.current;
1185
- if (!currentChatId) return;
1410
+ const decide = import_react5.default.useCallback(
1411
+ (toolCallId, approved, alwaysAllow) => {
1412
+ const id = chatIdRef.current;
1413
+ if (!id) return;
1414
+ gate.next();
1186
1415
  setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
1187
- setStreaming(true);
1188
1416
  setError(null);
1189
1417
  (async () => {
1190
1418
  try {
1191
- const result = await approveToolCall(client, currentChatId, toolCallId, approved);
1192
- if (!approved) {
1193
- setStreaming(false);
1194
- refreshMessages(currentChatId);
1195
- return;
1196
- }
1197
- const controller = streamFromChannel(
1198
- client,
1199
- currentChatId,
1200
- result.channelId,
1201
- handleStreamChunks,
1202
- () => {
1203
- setStreaming(false);
1204
- setToolStatus([]);
1205
- refreshMessages(currentChatId);
1206
- },
1207
- (err) => {
1208
- setError(err.message);
1209
- setStreaming(false);
1210
- setToolStatus([]);
1211
- }
1212
- );
1213
- abortRef.current = controller;
1419
+ const result = await approveToolCall(client, id, toolCallId, approved, { alwaysAllow });
1420
+ const channelId = approvalResumeChannel(result, approved);
1421
+ if (channelId) followChannel(id, channelId);
1422
+ else if (result.resume === void 0) refresh2(id);
1214
1423
  } catch (e) {
1215
1424
  setError(e.message);
1216
- setStreaming(false);
1425
+ refresh2(id);
1217
1426
  }
1218
1427
  })();
1219
1428
  },
1220
- [client, handleStreamChunks, refreshMessages]
1429
+ [client, gate, followChannel, refresh2]
1221
1430
  );
1222
1431
  const approve = import_react5.default.useCallback(
1223
- (toolCallId) => handleApproval(toolCallId, true),
1224
- [handleApproval]
1432
+ (toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
1433
+ [decide]
1225
1434
  );
1226
1435
  const reject = import_react5.default.useCallback(
1227
- (toolCallId) => handleApproval(toolCallId, false),
1228
- [handleApproval]
1436
+ (toolCallId) => decide(toolCallId, false, false),
1437
+ [decide]
1438
+ );
1439
+ const answer = import_react5.default.useCallback(
1440
+ async (toolCallId, text) => {
1441
+ const id = chatIdRef.current;
1442
+ if (!id) return false;
1443
+ setError(null);
1444
+ try {
1445
+ const result = await answerPendingInput(client, id, toolCallId, text);
1446
+ gate.next();
1447
+ setPendingInputs((prev) => prev.filter((p) => p.toolCallId !== toolCallId));
1448
+ const channelId = answerResumeChannel(result);
1449
+ if (channelId) followChannel(id, channelId);
1450
+ return true;
1451
+ } catch (e) {
1452
+ setError(e.message);
1453
+ refresh2(id);
1454
+ return false;
1455
+ }
1456
+ },
1457
+ [client, gate, followChannel, refresh2]
1458
+ );
1459
+ const follow = import_react5.default.useCallback(
1460
+ (channelId, opts) => {
1461
+ const id = chatIdRef.current;
1462
+ if (!id) return;
1463
+ followChannel(id, channelId, opts?.lastId);
1464
+ },
1465
+ [followChannel]
1229
1466
  );
1230
1467
  const stop = import_react5.default.useCallback(() => {
1231
1468
  abortRef.current?.abort();
1232
1469
  abortRef.current = null;
1233
1470
  setStreaming(false);
1471
+ setThinking(stopThinking);
1234
1472
  }, []);
1235
1473
  const clearError = import_react5.default.useCallback(() => {
1236
1474
  setError(null);
@@ -1242,10 +1480,14 @@ var SinasSDK = (() => {
1242
1480
  streaming,
1243
1481
  error,
1244
1482
  pendingApprovals,
1483
+ pendingInputs,
1484
+ thinking: thinkingShown,
1245
1485
  toolStatus,
1246
1486
  send,
1247
1487
  approve,
1248
1488
  reject,
1489
+ answer,
1490
+ follow,
1249
1491
  stop,
1250
1492
  clearError
1251
1493
  };