@sinas/sdk 0.5.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,19 +50,31 @@ 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,
57
+ cancelBatch: () => cancelBatch,
54
58
  checkPermissions: () => checkPermissions,
55
59
  createChat: () => createChat,
60
+ enqueueFunction: () => enqueueFunction,
56
61
  executeFunction: () => executeFunction,
57
62
  executeQuery: () => executeQuery,
58
63
  generateFileUrl: () => generateFileUrl,
64
+ getBatch: () => getBatch,
59
65
  getChat: () => getChat,
60
66
  getInfo: () => getInfo,
61
67
  getMe: () => getMe,
68
+ invokeAgent: () => invokeAgent,
69
+ listBatchExecutions: () => listBatchExecutions,
62
70
  listCollections: () => listCollections,
63
71
  listFiles: () => listFiles,
72
+ listPendingInputs: () => listPendingInputs,
64
73
  login: () => login,
65
74
  logout: () => logout,
75
+ parsePendingInput: () => parsePendingInput,
76
+ parsePendingInputs: () => parsePendingInputs,
77
+ pendingInputFromChunk: () => pendingInputFromChunk,
66
78
  refresh: () => refresh,
67
79
  sendMessage: () => sendMessage,
68
80
  stateDelete: () => stateDelete,
@@ -71,6 +83,8 @@ var SinasSDK = (() => {
71
83
  stateSet: () => stateSet,
72
84
  streamFromChannel: () => streamFromChannel,
73
85
  streamMessage: () => streamMessage,
86
+ submitAgentBatch: () => submitAgentBatch,
87
+ submitFunctionBatch: () => submitFunctionBatch,
74
88
  uploadFile: () => uploadFile,
75
89
  useAuth: () => useAuth,
76
90
  useChat: () => useChat,
@@ -391,6 +405,124 @@ var SinasSDK = (() => {
391
405
  error: data.error
392
406
  };
393
407
  }
408
+ async function enqueueFunction(client, ref, input = {}, options = {}) {
409
+ const [ns, name] = parseRef2(ref);
410
+ const baseUrl = client.requireBaseUrl();
411
+ const body = { input };
412
+ if (options.triggerId !== void 0) body.trigger_id = options.triggerId;
413
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
414
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
415
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/async`, {
416
+ method: "POST",
417
+ body: JSON.stringify(body)
418
+ });
419
+ const data = await res.json();
420
+ return {
421
+ executionId: data.execution_id,
422
+ status: data.status
423
+ };
424
+ }
425
+ async function submitFunctionBatch(client, ref, inputs, options = {}) {
426
+ const [ns, name] = parseRef2(ref);
427
+ const baseUrl = client.requireBaseUrl();
428
+ const body = { inputs };
429
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
430
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
431
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
432
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
433
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/batch`, {
434
+ method: "POST",
435
+ body: JSON.stringify(body)
436
+ });
437
+ const data = await res.json();
438
+ return {
439
+ batchId: data.batch_id,
440
+ executionIds: data.execution_ids,
441
+ total: data.total,
442
+ status: data.status
443
+ };
444
+ }
445
+ async function submitAgentBatch(client, ref, inputs, options = {}) {
446
+ const [ns, name] = parseRef2(ref);
447
+ const baseUrl = client.requireBaseUrl();
448
+ const body = {
449
+ inputs: inputs.map((item) => ({
450
+ input_variables: item.inputVariables || {},
451
+ message: item.message
452
+ }))
453
+ };
454
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
455
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
456
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
457
+ const res = await client.fetch(`${baseUrl}/agents/${ns}/${name}/chats/batch`, {
458
+ method: "POST",
459
+ body: JSON.stringify(body)
460
+ });
461
+ const data = await res.json();
462
+ return {
463
+ batchId: data.batch_id,
464
+ executionIds: data.execution_ids,
465
+ chatIds: data.chat_ids,
466
+ total: data.total,
467
+ status: data.status
468
+ };
469
+ }
470
+ async function getBatch(client, batchId) {
471
+ const baseUrl = client.requireBaseUrl();
472
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}`);
473
+ const data = await res.json();
474
+ return {
475
+ batchId: data.batch_id,
476
+ kind: data.kind,
477
+ target: data.target,
478
+ userId: data.user_id,
479
+ total: data.total,
480
+ completed: data.completed,
481
+ failed: data.failed,
482
+ cancelled: data.cancelled,
483
+ running: data.running,
484
+ queued: data.queued,
485
+ status: data.status,
486
+ startedAt: data.started_at,
487
+ finishedAt: data.finished_at,
488
+ triggerIdPrefix: data.trigger_id_prefix
489
+ };
490
+ }
491
+ async function listBatchExecutions(client, batchId, options = {}) {
492
+ const baseUrl = client.requireBaseUrl();
493
+ const params = new URLSearchParams();
494
+ if (options.status) params.set("status", options.status);
495
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
496
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
497
+ const q = params.toString();
498
+ const res = await client.fetch(
499
+ `${baseUrl}/batches/${batchId}/executions${q ? "?" + q : ""}`
500
+ );
501
+ const data = await res.json();
502
+ return (data.executions || []).map((row) => ({
503
+ executionId: row.execution_id,
504
+ status: row.status,
505
+ functionName: row.function_name,
506
+ chatId: row.chat_id ?? null,
507
+ triggerId: row.trigger_id,
508
+ startedAt: row.started_at ?? null,
509
+ completedAt: row.completed_at ?? null,
510
+ durationMs: row.duration_ms ?? null,
511
+ error: row.error ?? null
512
+ }));
513
+ }
514
+ async function cancelBatch(client, batchId) {
515
+ const baseUrl = client.requireBaseUrl();
516
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}/cancel`, {
517
+ method: "POST"
518
+ });
519
+ const data = await res.json();
520
+ return {
521
+ batchId: data.batch_id,
522
+ status: data.status,
523
+ cancelledChildren: data.cancelled_children
524
+ };
525
+ }
394
526
 
395
527
  // src/client/states.ts
396
528
  async function stateGet(client, ns, key) {
@@ -424,12 +556,111 @@ var SinasSDK = (() => {
424
556
  return res.json();
425
557
  }
426
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
+
427
654
  // src/client/chat.ts
428
- async function createChat(client, agentRef, input) {
655
+ function splitAgentRef(agentRef) {
429
656
  const [ns, name] = agentRef.split("/");
430
657
  if (!ns || !name) {
431
658
  throw new Error(`Invalid agent ref "${agentRef}": expected "namespace/name"`);
432
659
  }
660
+ return [ns, name];
661
+ }
662
+ async function createChat(client, agentRef, input) {
663
+ const [ns, name] = splitAgentRef(agentRef);
433
664
  const config = client.getConfig();
434
665
  const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/chats`, {
435
666
  method: "POST",
@@ -455,66 +686,117 @@ var SinasSDK = (() => {
455
686
  chatId: data.chat_id,
456
687
  role: data.role,
457
688
  content: data.content,
458
- createdAt: data.created_at
689
+ createdAt: data.created_at,
690
+ ...parseTurnState(data)
459
691
  };
460
692
  }
461
- function mapMessage(data) {
462
- 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();
463
705
  return {
464
- id: data.id,
706
+ reply: typeof data.reply === "string" ? data.reply : "",
465
707
  chatId: data.chat_id,
466
- role: data.role,
467
- content: data.content ?? null,
468
- toolCalls: toolCalls ?? null,
469
- toolCallId: data.tool_call_id ?? null,
470
- name: data.name ?? null,
471
- createdAt: data.created_at
708
+ sessionKey: data.session_key ?? null,
709
+ ...parseTurnState(data)
472
710
  };
473
711
  }
474
712
  async function getChat(client, chatId) {
475
713
  const config = client.getConfig();
476
714
  const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
477
715
  const data = await res.json();
478
- return {
716
+ const detail = {
479
717
  id: data.id,
480
718
  agentNamespace: data.agent_namespace,
481
719
  agentName: data.agent_name,
482
720
  title: data.title,
483
- messages: (data.messages || []).map(mapMessage),
484
- pendingApprovals: (data.pending_approvals || []).map((pa) => ({
485
- type: "approval_required",
486
- toolCallId: pa.tool_call_id,
487
- functionNamespace: pa.function_namespace,
488
- functionName: pa.function_name,
489
- arguments: pa.arguments || {}
490
- }))
721
+ messages: (data.messages || []).map(parseMessage),
722
+ pendingApprovals: (data.pending_approvals || []).map(parseApproval).filter((a) => a !== null)
491
723
  };
724
+ if (Array.isArray(data.pending_inputs)) {
725
+ detail.pendingInputs = parsePendingInputs(data.pending_inputs);
726
+ }
727
+ return detail;
492
728
  }
493
- async function approveToolCall(client, chatId, toolCallId, approved) {
729
+ async function approveToolCall(client, chatId, toolCallId, approved, options = {}) {
494
730
  const config = client.getConfig();
731
+ const body = { approved };
732
+ if (approved && options.alwaysAllow) body.always_allow = true;
495
733
  const res = await client.fetch(
496
- `${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
497
- { method: "POST", body: JSON.stringify({ approved }) }
734
+ `${config.apiBase}/chats/${chatId}/approve-tool/${encodeURIComponent(toolCallId)}`,
735
+ { method: "POST", body: JSON.stringify(body) }
498
736
  );
499
737
  const data = await res.json();
500
- return {
738
+ const result = {
501
739
  status: data.status,
502
740
  toolCallId: data.tool_call_id,
503
741
  channelId: data.channel_id
504
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;
505
746
  }
506
- function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
507
- 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 = {}) {
508
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();
509
794
  (async () => {
510
795
  try {
511
- const res = await fetch(
512
- `${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
513
- { headers: client.getAuthHeaders(), signal: controller.signal }
514
- );
796
+ const res = await fetch(url, init(controller.signal));
515
797
  if (!res.ok) {
516
798
  const body = await res.json().catch(() => ({ detail: res.statusText }));
517
- throw new Error(body.detail || "Stream reconnect failed");
799
+ throw new Error(body.detail || failure);
518
800
  }
519
801
  const reader = res.body.getReader();
520
802
  const decoder = new TextDecoder();
@@ -528,7 +810,7 @@ var SinasSDK = (() => {
528
810
  for (const chunk of chunks) {
529
811
  onChunk(chunk);
530
812
  if (chunk.event === "done") {
531
- onDone?.();
813
+ onDone?.(parseDone(chunk.data));
532
814
  return;
533
815
  }
534
816
  }
@@ -537,9 +819,13 @@ var SinasSDK = (() => {
537
819
  const { chunks } = parseSSE(buffer + "\n\n");
538
820
  for (const chunk of chunks) {
539
821
  onChunk(chunk);
822
+ if (chunk.event === "done") {
823
+ onDone?.(parseDone(chunk.data));
824
+ return;
825
+ }
540
826
  }
541
827
  }
542
- onDone?.();
828
+ onDone?.({ status: "closed" });
543
829
  } catch (err) {
544
830
  if (err.name === "AbortError") return;
545
831
  onError?.(err);
@@ -550,75 +836,33 @@ var SinasSDK = (() => {
550
836
  function parseSSE(buffer) {
551
837
  const chunks = [];
552
838
  const normalized = buffer.replace(/\r\n/g, "\n");
553
- const lines = normalized.split("\n");
554
- let event = "";
555
- let data = "";
556
- let remaining = "";
557
- for (let i = 0; i < lines.length; i++) {
558
- const line = lines[i];
559
- if (i === lines.length - 1 && !normalized.endsWith("\n")) {
560
- remaining = line;
561
- break;
562
- }
563
- if (line.startsWith("event:")) {
564
- event = line.slice(6).trim();
565
- } else if (line.startsWith("data:")) {
566
- data = line.slice(5).trim();
567
- } else if (line === "") {
568
- if (event || data) {
569
- chunks.push({ event: event || "message", data });
570
- event = "";
571
- data = "";
572
- }
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());
573
849
  }
850
+ if (event || data.length) chunks.push({ event: event || "message", data: data.join("\n") });
574
851
  }
575
- return { chunks, remaining };
852
+ return { chunks, remaining: normalized.slice(start) };
576
853
  }
577
- function streamMessage(client, chatId, content, onChunk, onDone, onError) {
578
- const controller = new AbortController();
854
+ function streamMessage(client, chatId, content, onChunk, onDone, onError, options = {}) {
579
855
  const config = client.getConfig();
580
- (async () => {
581
- try {
582
- const res = await fetch(`${config.apiBase}/chats/${chatId}/messages/stream`, {
583
- method: "POST",
584
- headers: client.getAuthHeaders(),
585
- body: JSON.stringify({ content }),
586
- signal: controller.signal
587
- });
588
- if (!res.ok) {
589
- const body = await res.json().catch(() => ({ detail: res.statusText }));
590
- throw new Error(body.detail || "Stream request failed");
591
- }
592
- const reader = res.body.getReader();
593
- const decoder = new TextDecoder();
594
- let buffer = "";
595
- while (true) {
596
- const { done, value } = await reader.read();
597
- if (done) break;
598
- buffer += decoder.decode(value, { stream: true });
599
- const { chunks, remaining } = parseSSE(buffer);
600
- buffer = remaining;
601
- for (const chunk of chunks) {
602
- onChunk(chunk);
603
- if (chunk.event === "done") {
604
- onDone?.();
605
- return;
606
- }
607
- }
608
- }
609
- if (buffer.trim()) {
610
- const { chunks } = parseSSE(buffer + "\n\n");
611
- for (const chunk of chunks) {
612
- onChunk(chunk);
613
- }
614
- }
615
- onDone?.();
616
- } catch (err) {
617
- if (err.name === "AbortError") return;
618
- onError?.(err);
619
- }
620
- })();
621
- 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
+ );
622
866
  }
623
867
 
624
868
  // src/client/collections.ts
@@ -833,29 +1077,172 @@ var SinasSDK = (() => {
833
1077
 
834
1078
  // src/hooks/useChat.ts
835
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
836
1198
  function useChat(agentRef, options) {
837
1199
  const client = useClient();
1200
+ const includeThinking = !!options?.includeThinking;
1201
+ const pollPendingInputs = options?.pollPendingInputs !== false;
838
1202
  const [chatId, setChatId] = import_react5.default.useState(options?.chatId ?? null);
839
1203
  const [messages, setMessages] = import_react5.default.useState([]);
840
1204
  const [loading, setLoading] = import_react5.default.useState(false);
841
1205
  const [streaming, setStreaming] = import_react5.default.useState(false);
842
1206
  const [error, setError] = import_react5.default.useState(null);
843
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]);
844
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);
845
1214
  const abortRef = import_react5.default.useRef(null);
846
1215
  const chatIdRef = import_react5.default.useRef(chatId);
847
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
+ );
848
1235
  import_react5.default.useEffect(() => {
849
1236
  if (!options?.chatId) return;
850
1237
  let cancelled = false;
1238
+ const ticket = gate.next();
851
1239
  setLoading(true);
852
1240
  getChat(client, options.chatId).then((detail) => {
853
- if (cancelled) return;
1241
+ if (cancelled || !gate.isCurrent(ticket)) return;
854
1242
  setChatId(detail.id);
855
1243
  setMessages(detail.messages);
856
- if (detail.pendingApprovals?.length) {
857
- setPendingApprovals(detail.pendingApprovals);
858
- }
1244
+ setPendingApprovals(detail.pendingApprovals ?? []);
1245
+ setPendingInputs(detail.pendingInputs ?? []);
859
1246
  }).catch((e) => {
860
1247
  if (cancelled) return;
861
1248
  setError(e.message);
@@ -865,74 +1252,52 @@ var SinasSDK = (() => {
865
1252
  return () => {
866
1253
  cancelled = true;
867
1254
  };
868
- }, [client, options?.chatId]);
1255
+ }, [client, gate, options?.chatId]);
869
1256
  import_react5.default.useEffect(() => {
870
1257
  return () => {
871
1258
  abortRef.current?.abort();
872
1259
  };
873
1260
  }, []);
874
- 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(
875
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 || "";
876
1285
  if (chunk.event === "message") {
877
- const newContent = (() => {
878
- try {
879
- const parsed = JSON.parse(chunk.data);
880
- return parsed.content || "";
881
- } catch {
882
- return chunk.data;
883
- }
884
- })();
885
- if (!newContent) return;
886
- setMessages((prev) => {
887
- const last = prev[prev.length - 1];
888
- if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
889
- const next = [...prev];
890
- next[next.length - 1] = { ...last, content: (last.content || "") + newContent };
891
- return next;
892
- }
893
- return [...prev, {
894
- id: `tmp-text-${Date.now()}`,
895
- chatId: chatIdRef.current || "",
896
- role: "assistant",
897
- content: newContent,
898
- toolCalls: null,
899
- toolCallId: null,
900
- name: null,
901
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
902
- streaming: true
903
- }];
904
- });
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
+ }
905
1292
  } else if (chunk.event === "tool_start") {
1293
+ setThinking(stopThinking);
906
1294
  try {
907
1295
  const parsed = JSON.parse(chunk.data);
908
1296
  setToolStatus((prev) => [
909
1297
  ...prev,
910
1298
  { toolCallId: parsed.tool_call_id, name: parsed.name, description: parsed.description, status: "running" }
911
1299
  ]);
912
- setMessages((prev) => {
913
- if (prev.some((m) => m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id))) {
914
- return prev;
915
- }
916
- const next = prev.map(
917
- (m, i) => i === prev.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
918
- );
919
- return [...next, {
920
- id: `tmp-tc-${parsed.tool_call_id}`,
921
- chatId: chatIdRef.current || "",
922
- role: "assistant",
923
- content: null,
924
- toolCalls: [{
925
- id: parsed.tool_call_id,
926
- type: "function",
927
- function: { name: parsed.name, arguments: parsed.arguments || "{}" },
928
- description: parsed.description
929
- }],
930
- toolCallId: null,
931
- name: null,
932
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
933
- streaming: true
934
- }];
935
- });
1300
+ setMessages((prev) => applyToolStart(prev, parsed, chatIdNow));
936
1301
  } catch {
937
1302
  }
938
1303
  } else if (chunk.event === "tool_end") {
@@ -941,39 +1306,18 @@ var SinasSDK = (() => {
941
1306
  setToolStatus(
942
1307
  (prev) => prev.map((t) => t.toolCallId === parsed.tool_call_id ? { ...t, status: "complete" } : t)
943
1308
  );
944
- if (parsed.result !== void 0) {
945
- const resultContent = typeof parsed.result === "string" ? parsed.result : JSON.stringify(parsed.result);
946
- setMessages(
947
- (prev) => prev.map((m) => {
948
- if (m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id)) {
949
- return {
950
- ...m,
951
- toolResults: {
952
- ...m.toolResults,
953
- [parsed.tool_call_id]: { content: resultContent, name: parsed.name }
954
- }
955
- };
956
- }
957
- return m;
958
- })
959
- );
960
- }
1309
+ setMessages((prev) => applyToolEnd(prev, parsed));
961
1310
  } catch {
962
1311
  }
963
1312
  } else if (chunk.event === "approval_required") {
1313
+ setThinking(stopThinking);
964
1314
  try {
965
- const raw = JSON.parse(chunk.data);
966
- const parsed = {
967
- type: raw.type,
968
- toolCallId: raw.tool_call_id,
969
- functionNamespace: raw.function_namespace,
970
- functionName: raw.function_name,
971
- arguments: raw.arguments
972
- };
973
- setPendingApprovals((prev) => [...prev, parsed]);
1315
+ const approval = parseApproval(JSON.parse(chunk.data));
1316
+ if (approval) setPendingApprovals((prev) => addApproval(prev, approval));
974
1317
  } catch {
975
1318
  }
976
1319
  } else if (chunk.event === "error") {
1320
+ setThinking(stopThinking);
977
1321
  try {
978
1322
  const parsed = JSON.parse(chunk.data);
979
1323
  setError(parsed.error || "Stream error");
@@ -984,18 +1328,37 @@ var SinasSDK = (() => {
984
1328
  },
985
1329
  []
986
1330
  );
987
- const refreshMessages = import_react5.default.useCallback(
988
- async (id) => {
989
- try {
990
- const detail = await getChat(client, id);
991
- setMessages(detail.messages);
992
- if (detail.pendingApprovals?.length) {
993
- setPendingApprovals(detail.pendingApprovals);
994
- }
995
- } 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);
996
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
+ });
997
1360
  },
998
- [client]
1361
+ [client, gate, includeThinking, handleStreamChunk, streamCallbacks]
999
1362
  );
1000
1363
  const send = import_react5.default.useCallback(
1001
1364
  (content) => {
@@ -1017,10 +1380,12 @@ var SinasSDK = (() => {
1017
1380
  setLoading(false);
1018
1381
  }
1019
1382
  abortRef.current?.abort();
1383
+ gate.next();
1020
1384
  setStreaming(true);
1021
1385
  setError(null);
1022
1386
  setPendingApprovals([]);
1023
1387
  setToolStatus([]);
1388
+ setThinking(NOT_THINKING);
1024
1389
  const displayContent = typeof content === "string" ? content : JSON.stringify(content);
1025
1390
  const userMsg = {
1026
1391
  id: `tmp-user-${Date.now()}`,
@@ -1033,80 +1398,77 @@ var SinasSDK = (() => {
1033
1398
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1034
1399
  };
1035
1400
  setMessages((prev) => [...prev, userMsg]);
1036
- const controller = streamMessage(
1037
- client,
1038
- activeChatId,
1039
- content,
1040
- handleStreamChunks,
1041
- () => {
1042
- setStreaming(false);
1043
- setToolStatus([]);
1044
- refreshMessages(activeChatId);
1045
- },
1046
- (err) => {
1047
- setError(err.message);
1048
- setStreaming(false);
1049
- setToolStatus([]);
1050
- }
1051
- );
1052
- abortRef.current = controller;
1401
+ const [onDone, onError] = streamCallbacks(activeChatId);
1402
+ abortRef.current = streamMessage(client, activeChatId, content, handleStreamChunk, onDone, onError, {
1403
+ includeThinking
1404
+ });
1053
1405
  };
1054
1406
  doSend();
1055
1407
  },
1056
- [client, agentRef, options?.input, handleStreamChunks, refreshMessages]
1408
+ [client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
1057
1409
  );
1058
- const handleApproval = import_react5.default.useCallback(
1059
- (toolCallId, approved) => {
1060
- const currentChatId = chatIdRef.current;
1061
- 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();
1062
1415
  setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
1063
- setStreaming(true);
1064
1416
  setError(null);
1065
1417
  (async () => {
1066
1418
  try {
1067
- const result = await approveToolCall(client, currentChatId, toolCallId, approved);
1068
- if (!approved) {
1069
- setStreaming(false);
1070
- refreshMessages(currentChatId);
1071
- return;
1072
- }
1073
- const controller = streamFromChannel(
1074
- client,
1075
- currentChatId,
1076
- result.channelId,
1077
- handleStreamChunks,
1078
- () => {
1079
- setStreaming(false);
1080
- setToolStatus([]);
1081
- refreshMessages(currentChatId);
1082
- },
1083
- (err) => {
1084
- setError(err.message);
1085
- setStreaming(false);
1086
- setToolStatus([]);
1087
- }
1088
- );
1089
- 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);
1090
1423
  } catch (e) {
1091
1424
  setError(e.message);
1092
- setStreaming(false);
1425
+ refresh2(id);
1093
1426
  }
1094
1427
  })();
1095
1428
  },
1096
- [client, handleStreamChunks, refreshMessages]
1429
+ [client, gate, followChannel, refresh2]
1097
1430
  );
1098
1431
  const approve = import_react5.default.useCallback(
1099
- (toolCallId) => handleApproval(toolCallId, true),
1100
- [handleApproval]
1432
+ (toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
1433
+ [decide]
1101
1434
  );
1102
1435
  const reject = import_react5.default.useCallback(
1103
- (toolCallId) => handleApproval(toolCallId, false),
1104
- [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]
1105
1466
  );
1106
1467
  const stop = import_react5.default.useCallback(() => {
1107
1468
  abortRef.current?.abort();
1108
1469
  abortRef.current = null;
1109
1470
  setStreaming(false);
1471
+ setThinking(stopThinking);
1110
1472
  }, []);
1111
1473
  const clearError = import_react5.default.useCallback(() => {
1112
1474
  setError(null);
@@ -1118,10 +1480,14 @@ var SinasSDK = (() => {
1118
1480
  streaming,
1119
1481
  error,
1120
1482
  pendingApprovals,
1483
+ pendingInputs,
1484
+ thinking: thinkingShown,
1121
1485
  toolStatus,
1122
1486
  send,
1123
1487
  approve,
1124
1488
  reject,
1489
+ answer,
1490
+ follow,
1125
1491
  stop,
1126
1492
  clearError
1127
1493
  };