@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.
package/dist/index.cjs CHANGED
@@ -38,19 +38,31 @@ __export(src_exports, {
38
38
  SinasPermissionError: () => SinasPermissionError,
39
39
  SinasProvider: () => SinasProvider,
40
40
  SinasTimeoutError: () => SinasTimeoutError,
41
+ answerPendingInput: () => answerPendingInput,
42
+ answerResumeChannel: () => answerResumeChannel,
43
+ approvalResumeChannel: () => approvalResumeChannel,
41
44
  approveToolCall: () => approveToolCall,
45
+ cancelBatch: () => cancelBatch,
42
46
  checkPermissions: () => checkPermissions,
43
47
  createChat: () => createChat,
48
+ enqueueFunction: () => enqueueFunction,
44
49
  executeFunction: () => executeFunction,
45
50
  executeQuery: () => executeQuery,
46
51
  generateFileUrl: () => generateFileUrl,
52
+ getBatch: () => getBatch,
47
53
  getChat: () => getChat,
48
54
  getInfo: () => getInfo,
49
55
  getMe: () => getMe,
56
+ invokeAgent: () => invokeAgent,
57
+ listBatchExecutions: () => listBatchExecutions,
50
58
  listCollections: () => listCollections,
51
59
  listFiles: () => listFiles,
60
+ listPendingInputs: () => listPendingInputs,
52
61
  login: () => login,
53
62
  logout: () => logout,
63
+ parsePendingInput: () => parsePendingInput,
64
+ parsePendingInputs: () => parsePendingInputs,
65
+ pendingInputFromChunk: () => pendingInputFromChunk,
54
66
  refresh: () => refresh,
55
67
  sendMessage: () => sendMessage,
56
68
  stateDelete: () => stateDelete,
@@ -59,6 +71,8 @@ __export(src_exports, {
59
71
  stateSet: () => stateSet,
60
72
  streamFromChannel: () => streamFromChannel,
61
73
  streamMessage: () => streamMessage,
74
+ submitAgentBatch: () => submitAgentBatch,
75
+ submitFunctionBatch: () => submitFunctionBatch,
62
76
  uploadFile: () => uploadFile,
63
77
  useAuth: () => useAuth,
64
78
  useChat: () => useChat,
@@ -380,6 +394,124 @@ async function executeFunction(client, ref, input = {}, timeout) {
380
394
  error: data.error
381
395
  };
382
396
  }
397
+ async function enqueueFunction(client, ref, input = {}, options = {}) {
398
+ const [ns, name] = parseRef2(ref);
399
+ const baseUrl = client.requireBaseUrl();
400
+ const body = { input };
401
+ if (options.triggerId !== void 0) body.trigger_id = options.triggerId;
402
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
403
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
404
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/async`, {
405
+ method: "POST",
406
+ body: JSON.stringify(body)
407
+ });
408
+ const data = await res.json();
409
+ return {
410
+ executionId: data.execution_id,
411
+ status: data.status
412
+ };
413
+ }
414
+ async function submitFunctionBatch(client, ref, inputs, options = {}) {
415
+ const [ns, name] = parseRef2(ref);
416
+ const baseUrl = client.requireBaseUrl();
417
+ const body = { inputs };
418
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
419
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
420
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
421
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
422
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/batch`, {
423
+ method: "POST",
424
+ body: JSON.stringify(body)
425
+ });
426
+ const data = await res.json();
427
+ return {
428
+ batchId: data.batch_id,
429
+ executionIds: data.execution_ids,
430
+ total: data.total,
431
+ status: data.status
432
+ };
433
+ }
434
+ async function submitAgentBatch(client, ref, inputs, options = {}) {
435
+ const [ns, name] = parseRef2(ref);
436
+ const baseUrl = client.requireBaseUrl();
437
+ const body = {
438
+ inputs: inputs.map((item) => ({
439
+ input_variables: item.inputVariables || {},
440
+ message: item.message
441
+ }))
442
+ };
443
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
444
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
445
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
446
+ const res = await client.fetch(`${baseUrl}/agents/${ns}/${name}/chats/batch`, {
447
+ method: "POST",
448
+ body: JSON.stringify(body)
449
+ });
450
+ const data = await res.json();
451
+ return {
452
+ batchId: data.batch_id,
453
+ executionIds: data.execution_ids,
454
+ chatIds: data.chat_ids,
455
+ total: data.total,
456
+ status: data.status
457
+ };
458
+ }
459
+ async function getBatch(client, batchId) {
460
+ const baseUrl = client.requireBaseUrl();
461
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}`);
462
+ const data = await res.json();
463
+ return {
464
+ batchId: data.batch_id,
465
+ kind: data.kind,
466
+ target: data.target,
467
+ userId: data.user_id,
468
+ total: data.total,
469
+ completed: data.completed,
470
+ failed: data.failed,
471
+ cancelled: data.cancelled,
472
+ running: data.running,
473
+ queued: data.queued,
474
+ status: data.status,
475
+ startedAt: data.started_at,
476
+ finishedAt: data.finished_at,
477
+ triggerIdPrefix: data.trigger_id_prefix
478
+ };
479
+ }
480
+ async function listBatchExecutions(client, batchId, options = {}) {
481
+ const baseUrl = client.requireBaseUrl();
482
+ const params = new URLSearchParams();
483
+ if (options.status) params.set("status", options.status);
484
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
485
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
486
+ const q = params.toString();
487
+ const res = await client.fetch(
488
+ `${baseUrl}/batches/${batchId}/executions${q ? "?" + q : ""}`
489
+ );
490
+ const data = await res.json();
491
+ return (data.executions || []).map((row) => ({
492
+ executionId: row.execution_id,
493
+ status: row.status,
494
+ functionName: row.function_name,
495
+ chatId: row.chat_id ?? null,
496
+ triggerId: row.trigger_id,
497
+ startedAt: row.started_at ?? null,
498
+ completedAt: row.completed_at ?? null,
499
+ durationMs: row.duration_ms ?? null,
500
+ error: row.error ?? null
501
+ }));
502
+ }
503
+ async function cancelBatch(client, batchId) {
504
+ const baseUrl = client.requireBaseUrl();
505
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}/cancel`, {
506
+ method: "POST"
507
+ });
508
+ const data = await res.json();
509
+ return {
510
+ batchId: data.batch_id,
511
+ status: data.status,
512
+ cancelledChildren: data.cancelled_children
513
+ };
514
+ }
383
515
 
384
516
  // src/client/states.ts
385
517
  async function stateGet(client, ns, key) {
@@ -413,12 +545,111 @@ async function stateList(client, ns) {
413
545
  return res.json();
414
546
  }
415
547
 
548
+ // src/client/chatEvents.ts
549
+ var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
550
+ var str = (v) => typeof v === "string" ? v : null;
551
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
552
+ var optionsOf = (v) => Array.isArray(v) && v.length > 0 && v.every((o) => typeof o === "string") ? v : null;
553
+ function parsePendingInput(raw) {
554
+ if (!isObject(raw)) return null;
555
+ if (typeof raw.tool_call_id !== "string" || !raw.tool_call_id) return null;
556
+ return {
557
+ toolCallId: raw.tool_call_id,
558
+ question: str(raw.question) ?? "",
559
+ options: optionsOf(raw.options),
560
+ expiresAt: str(raw.expires_at),
561
+ createdAt: str(raw.created_at)
562
+ };
563
+ }
564
+ function parsePendingInputs(raw) {
565
+ return Array.isArray(raw) ? raw.map(parsePendingInput).filter((p) => p !== null) : [];
566
+ }
567
+ function pendingInputFromChunk(chunk) {
568
+ if (chunk.event !== "input_required" && chunk.event !== "message") return null;
569
+ let parsed;
570
+ try {
571
+ parsed = JSON.parse(chunk.data);
572
+ } catch {
573
+ return null;
574
+ }
575
+ if (chunk.event === "message" && (!isObject(parsed) || parsed.type !== "input_required")) return null;
576
+ return parsePendingInput(parsed);
577
+ }
578
+ function parseApproval(raw) {
579
+ if (!isObject(raw) || typeof raw.tool_call_id !== "string") return null;
580
+ return {
581
+ type: str(raw.type) ?? "approval_required",
582
+ toolCallId: raw.tool_call_id,
583
+ functionNamespace: str(raw.function_namespace) ?? "",
584
+ functionName: str(raw.function_name) ?? "",
585
+ arguments: isObject(raw.arguments) ? raw.arguments : {}
586
+ };
587
+ }
588
+ function parseUsage(raw) {
589
+ if (!isObject(raw)) return null;
590
+ return {
591
+ calls: num(raw.calls),
592
+ promptTokens: num(raw.prompt_tokens),
593
+ completionTokens: num(raw.completion_tokens),
594
+ totalTokens: num(raw.total_tokens),
595
+ cacheReadTokens: num(raw.cache_read_tokens),
596
+ cacheWriteTokens: num(raw.cache_write_tokens),
597
+ costUsd: typeof raw.cost_usd === "number" ? raw.cost_usd : null,
598
+ unpricedCalls: num(raw.unpriced_calls),
599
+ errors: num(raw.errors)
600
+ };
601
+ }
602
+ var TURN_STATUSES = ["completed", "input_required", "in_progress", "injected"];
603
+ function parseTurnState(raw) {
604
+ const status = TURN_STATUSES.includes(raw.status) ? raw.status : "completed";
605
+ return {
606
+ status,
607
+ pendingInputs: parsePendingInputs(raw.pending_inputs),
608
+ channelId: str(raw.channel_id),
609
+ usage: parseUsage(raw.usage)
610
+ };
611
+ }
612
+ function parseMessage(data) {
613
+ const toolCalls = data.tool_calls;
614
+ return {
615
+ id: data.id,
616
+ chatId: data.chat_id,
617
+ role: data.role,
618
+ content: data.content ?? null,
619
+ toolCalls: toolCalls ?? null,
620
+ toolCallId: data.tool_call_id ?? null,
621
+ name: data.name ?? null,
622
+ createdAt: data.created_at
623
+ };
624
+ }
625
+ function parseDone(data) {
626
+ try {
627
+ const parsed = JSON.parse(data);
628
+ if (isObject(parsed) && typeof parsed.status === "string") return { status: parsed.status };
629
+ } catch {
630
+ }
631
+ return { status: "completed" };
632
+ }
633
+ function approvalResumeChannel(result, approved = true) {
634
+ if (result.resume === "waiting") return null;
635
+ if (result.resume === void 0 && !approved) return null;
636
+ return result.channelId || null;
637
+ }
638
+ function answerResumeChannel(result) {
639
+ if (!result.resumed && !result.resumePending) return null;
640
+ return result.channelId || null;
641
+ }
642
+
416
643
  // src/client/chat.ts
417
- async function createChat(client, agentRef, input) {
644
+ function splitAgentRef(agentRef) {
418
645
  const [ns, name] = agentRef.split("/");
419
646
  if (!ns || !name) {
420
647
  throw new Error(`Invalid agent ref "${agentRef}": expected "namespace/name"`);
421
648
  }
649
+ return [ns, name];
650
+ }
651
+ async function createChat(client, agentRef, input) {
652
+ const [ns, name] = splitAgentRef(agentRef);
422
653
  const config = client.getConfig();
423
654
  const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/chats`, {
424
655
  method: "POST",
@@ -444,66 +675,117 @@ async function sendMessage(client, chatId, content) {
444
675
  chatId: data.chat_id,
445
676
  role: data.role,
446
677
  content: data.content,
447
- createdAt: data.created_at
678
+ createdAt: data.created_at,
679
+ ...parseTurnState(data)
448
680
  };
449
681
  }
450
- function mapMessage(data) {
451
- const toolCalls = data.tool_calls;
682
+ async function invokeAgent(client, agentRef, message, options = {}) {
683
+ const [ns, name] = splitAgentRef(agentRef);
684
+ const config = client.getConfig();
685
+ const body = { message };
686
+ if (options.sessionKey !== void 0) body.session_key = options.sessionKey;
687
+ if (options.reset !== void 0) body.reset = options.reset;
688
+ if (options.input !== void 0) body.input = options.input;
689
+ const res = await client.fetch(`${config.apiBase}/agents/${ns}/${name}/invoke`, {
690
+ method: "POST",
691
+ body: JSON.stringify(body)
692
+ });
693
+ const data = await res.json();
452
694
  return {
453
- id: data.id,
695
+ reply: typeof data.reply === "string" ? data.reply : "",
454
696
  chatId: data.chat_id,
455
- role: data.role,
456
- content: data.content ?? null,
457
- toolCalls: toolCalls ?? null,
458
- toolCallId: data.tool_call_id ?? null,
459
- name: data.name ?? null,
460
- createdAt: data.created_at
697
+ sessionKey: data.session_key ?? null,
698
+ ...parseTurnState(data)
461
699
  };
462
700
  }
463
701
  async function getChat(client, chatId) {
464
702
  const config = client.getConfig();
465
703
  const res = await client.fetch(`${config.apiBase}/chats/${chatId}`);
466
704
  const data = await res.json();
467
- return {
705
+ const detail = {
468
706
  id: data.id,
469
707
  agentNamespace: data.agent_namespace,
470
708
  agentName: data.agent_name,
471
709
  title: data.title,
472
- messages: (data.messages || []).map(mapMessage),
473
- pendingApprovals: (data.pending_approvals || []).map((pa) => ({
474
- type: "approval_required",
475
- toolCallId: pa.tool_call_id,
476
- functionNamespace: pa.function_namespace,
477
- functionName: pa.function_name,
478
- arguments: pa.arguments || {}
479
- }))
710
+ messages: (data.messages || []).map(parseMessage),
711
+ pendingApprovals: (data.pending_approvals || []).map(parseApproval).filter((a) => a !== null)
480
712
  };
713
+ if (Array.isArray(data.pending_inputs)) {
714
+ detail.pendingInputs = parsePendingInputs(data.pending_inputs);
715
+ }
716
+ return detail;
481
717
  }
482
- async function approveToolCall(client, chatId, toolCallId, approved) {
718
+ async function approveToolCall(client, chatId, toolCallId, approved, options = {}) {
483
719
  const config = client.getConfig();
720
+ const body = { approved };
721
+ if (approved && options.alwaysAllow) body.always_allow = true;
484
722
  const res = await client.fetch(
485
- `${config.apiBase}/chats/${chatId}/approve-tool/${toolCallId}`,
486
- { method: "POST", body: JSON.stringify({ approved }) }
723
+ `${config.apiBase}/chats/${chatId}/approve-tool/${encodeURIComponent(toolCallId)}`,
724
+ { method: "POST", body: JSON.stringify(body) }
487
725
  );
488
726
  const data = await res.json();
489
- return {
727
+ const result = {
490
728
  status: data.status,
491
729
  toolCallId: data.tool_call_id,
492
730
  channelId: data.channel_id
493
731
  };
732
+ if (data.resume === "waiting" || data.resume === "queued") result.resume = data.resume;
733
+ if (typeof data.message === "string") result.message = data.message;
734
+ return result;
494
735
  }
495
- function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError) {
496
- const controller = new AbortController();
736
+ async function listPendingInputs(client, chatId) {
737
+ const config = client.getConfig();
738
+ const res = await client.fetch(`${config.apiBase}/chats/${chatId}/pending-input`);
739
+ return parsePendingInputs(await res.json());
740
+ }
741
+ async function answerPendingInput(client, chatId, toolCallId, answer, options = {}) {
497
742
  const config = client.getConfig();
743
+ const body = { answer };
744
+ if (options.wait) body.wait = true;
745
+ const res = await client.fetch(
746
+ `${config.apiBase}/chats/${chatId}/pending-input/${encodeURIComponent(toolCallId)}`,
747
+ { method: "POST", body: JSON.stringify(body) }
748
+ );
749
+ const data = await res.json();
750
+ if (options.wait) {
751
+ return {
752
+ toolCallId: data.tool_call_id,
753
+ chatId: data.chat_id,
754
+ reply: typeof data.reply === "string" ? data.reply : "",
755
+ message: data.message && typeof data.message === "object" ? parseMessage(data.message) : null,
756
+ ...parseTurnState(data)
757
+ };
758
+ }
759
+ return {
760
+ status: "answered",
761
+ toolCallId: data.tool_call_id,
762
+ resumed: !!data.resumed,
763
+ resumePending: !!data.resume_pending,
764
+ channelId: data.channel_id ?? null,
765
+ message: typeof data.message === "string" ? data.message : ""
766
+ };
767
+ }
768
+ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError, options = {}) {
769
+ const config = client.getConfig();
770
+ const params = new URLSearchParams({ last_id: options.lastId ?? "0" });
771
+ if (options.includeThinking) params.set("include_thinking", "true");
772
+ return runSSE(
773
+ `${config.apiBase}/chats/${chatId}/stream/${encodeURIComponent(channelId)}?${params}`,
774
+ (signal) => ({ headers: client.getAuthHeaders(), signal }),
775
+ "Stream reconnect failed",
776
+ onChunk,
777
+ onDone,
778
+ onError
779
+ );
780
+ }
781
+ function runSSE(url, init, failure, onChunk, onDone, onError) {
782
+ const controller = new AbortController();
498
783
  (async () => {
499
784
  try {
500
- const res = await fetch(
501
- `${config.apiBase}/chats/${chatId}/stream/${channelId}?last_id=0`,
502
- { headers: client.getAuthHeaders(), signal: controller.signal }
503
- );
785
+ const res = await fetch(url, init(controller.signal));
504
786
  if (!res.ok) {
505
787
  const body = await res.json().catch(() => ({ detail: res.statusText }));
506
- throw new Error(body.detail || "Stream reconnect failed");
788
+ throw new Error(body.detail || failure);
507
789
  }
508
790
  const reader = res.body.getReader();
509
791
  const decoder = new TextDecoder();
@@ -517,7 +799,7 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
517
799
  for (const chunk of chunks) {
518
800
  onChunk(chunk);
519
801
  if (chunk.event === "done") {
520
- onDone?.();
802
+ onDone?.(parseDone(chunk.data));
521
803
  return;
522
804
  }
523
805
  }
@@ -526,9 +808,13 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
526
808
  const { chunks } = parseSSE(buffer + "\n\n");
527
809
  for (const chunk of chunks) {
528
810
  onChunk(chunk);
811
+ if (chunk.event === "done") {
812
+ onDone?.(parseDone(chunk.data));
813
+ return;
814
+ }
529
815
  }
530
816
  }
531
- onDone?.();
817
+ onDone?.({ status: "closed" });
532
818
  } catch (err) {
533
819
  if (err.name === "AbortError") return;
534
820
  onError?.(err);
@@ -539,75 +825,33 @@ function streamFromChannel(client, chatId, channelId, onChunk, onDone, onError)
539
825
  function parseSSE(buffer) {
540
826
  const chunks = [];
541
827
  const normalized = buffer.replace(/\r\n/g, "\n");
542
- const lines = normalized.split("\n");
543
- let event = "";
544
- let data = "";
545
- let remaining = "";
546
- for (let i = 0; i < lines.length; i++) {
547
- const line = lines[i];
548
- if (i === lines.length - 1 && !normalized.endsWith("\n")) {
549
- remaining = line;
550
- break;
551
- }
552
- if (line.startsWith("event:")) {
553
- event = line.slice(6).trim();
554
- } else if (line.startsWith("data:")) {
555
- data = line.slice(5).trim();
556
- } else if (line === "") {
557
- if (event || data) {
558
- chunks.push({ event: event || "message", data });
559
- event = "";
560
- data = "";
561
- }
828
+ let start = 0;
829
+ let end;
830
+ while ((end = normalized.indexOf("\n\n", start)) !== -1) {
831
+ const block = normalized.slice(start, end);
832
+ start = end + 2;
833
+ let event = "";
834
+ const data = [];
835
+ for (const line of block.split("\n")) {
836
+ if (line.startsWith("event:")) event = line.slice(6).trim();
837
+ else if (line.startsWith("data:")) data.push(line.slice(5).trim());
562
838
  }
839
+ if (event || data.length) chunks.push({ event: event || "message", data: data.join("\n") });
563
840
  }
564
- return { chunks, remaining };
841
+ return { chunks, remaining: normalized.slice(start) };
565
842
  }
566
- function streamMessage(client, chatId, content, onChunk, onDone, onError) {
567
- const controller = new AbortController();
843
+ function streamMessage(client, chatId, content, onChunk, onDone, onError, options = {}) {
568
844
  const config = client.getConfig();
569
- (async () => {
570
- try {
571
- const res = await fetch(`${config.apiBase}/chats/${chatId}/messages/stream`, {
572
- method: "POST",
573
- headers: client.getAuthHeaders(),
574
- body: JSON.stringify({ content }),
575
- signal: controller.signal
576
- });
577
- if (!res.ok) {
578
- const body = await res.json().catch(() => ({ detail: res.statusText }));
579
- throw new Error(body.detail || "Stream request failed");
580
- }
581
- const reader = res.body.getReader();
582
- const decoder = new TextDecoder();
583
- let buffer = "";
584
- while (true) {
585
- const { done, value } = await reader.read();
586
- if (done) break;
587
- buffer += decoder.decode(value, { stream: true });
588
- const { chunks, remaining } = parseSSE(buffer);
589
- buffer = remaining;
590
- for (const chunk of chunks) {
591
- onChunk(chunk);
592
- if (chunk.event === "done") {
593
- onDone?.();
594
- return;
595
- }
596
- }
597
- }
598
- if (buffer.trim()) {
599
- const { chunks } = parseSSE(buffer + "\n\n");
600
- for (const chunk of chunks) {
601
- onChunk(chunk);
602
- }
603
- }
604
- onDone?.();
605
- } catch (err) {
606
- if (err.name === "AbortError") return;
607
- onError?.(err);
608
- }
609
- })();
610
- return controller;
845
+ const body = { content };
846
+ if (options.includeThinking) body.include_thinking = true;
847
+ return runSSE(
848
+ `${config.apiBase}/chats/${chatId}/messages/stream`,
849
+ (signal) => ({ method: "POST", headers: client.getAuthHeaders(), body: JSON.stringify(body), signal }),
850
+ "Stream request failed",
851
+ onChunk,
852
+ onDone,
853
+ onError
854
+ );
611
855
  }
612
856
 
613
857
  // src/client/collections.ts
@@ -822,29 +1066,172 @@ function useStateStore(ns, key) {
822
1066
 
823
1067
  // src/hooks/useChat.ts
824
1068
  var import_react5 = __toESM(require("react"), 1);
1069
+
1070
+ // src/hooks/chatState.ts
1071
+ function parseMessageChunk(data) {
1072
+ let parsed;
1073
+ try {
1074
+ parsed = JSON.parse(data);
1075
+ } catch {
1076
+ return { text: data };
1077
+ }
1078
+ if (!parsed || typeof parsed !== "object") return { text: "" };
1079
+ const p = parsed;
1080
+ const out = { text: typeof p.content === "string" ? p.content : "" };
1081
+ if (typeof p.thinking === "string") out.thinking = p.thinking;
1082
+ return out;
1083
+ }
1084
+ function applyTextDelta(messages, text, chatId, now = /* @__PURE__ */ new Date()) {
1085
+ if (!text) return messages;
1086
+ const last = messages[messages.length - 1];
1087
+ if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
1088
+ const next = [...messages];
1089
+ next[next.length - 1] = { ...last, content: (last.content || "") + text };
1090
+ return next;
1091
+ }
1092
+ return [
1093
+ ...messages,
1094
+ {
1095
+ id: `tmp-text-${now.getTime()}`,
1096
+ chatId,
1097
+ role: "assistant",
1098
+ content: text,
1099
+ toolCalls: null,
1100
+ toolCallId: null,
1101
+ name: null,
1102
+ createdAt: now.toISOString(),
1103
+ streaming: true
1104
+ }
1105
+ ];
1106
+ }
1107
+ function applyToolStart(messages, event, chatId, now = /* @__PURE__ */ new Date()) {
1108
+ if (messages.some((m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id))) return messages;
1109
+ const frozen = messages.map(
1110
+ (m, i) => i === messages.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
1111
+ );
1112
+ return [
1113
+ ...frozen,
1114
+ {
1115
+ id: `tmp-tc-${event.tool_call_id}`,
1116
+ chatId,
1117
+ role: "assistant",
1118
+ content: null,
1119
+ toolCalls: [
1120
+ {
1121
+ id: event.tool_call_id,
1122
+ type: "function",
1123
+ function: { name: event.name, arguments: event.arguments || "{}" },
1124
+ description: event.description
1125
+ }
1126
+ ],
1127
+ toolCallId: null,
1128
+ name: null,
1129
+ createdAt: now.toISOString(),
1130
+ streaming: true
1131
+ }
1132
+ ];
1133
+ }
1134
+ function applyToolEnd(messages, event) {
1135
+ if (event.result === void 0) return messages;
1136
+ const content = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
1137
+ return messages.map(
1138
+ (m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id) ? { ...m, toolResults: { ...m.toolResults, [event.tool_call_id]: { content, name: event.name } } } : m
1139
+ );
1140
+ }
1141
+ function addApproval(current, approval) {
1142
+ return current.some((a) => a.toolCallId === approval.toolCallId) ? current : [...current, approval];
1143
+ }
1144
+ function mergePendingInputs(current, incoming) {
1145
+ const seen = new Set(current.map((p) => p.toolCallId));
1146
+ const added = incoming.filter((p) => !seen.has(p.toolCallId) && seen.add(p.toolCallId));
1147
+ return added.length ? [...current, ...added] : current;
1148
+ }
1149
+ var NOT_THINKING = { active: false, rounds: [] };
1150
+ function applyThinking(state, thinking) {
1151
+ if (!state.active) return { active: true, rounds: [...state.rounds, thinking] };
1152
+ if (!thinking) return state;
1153
+ const rounds = [...state.rounds];
1154
+ rounds[rounds.length - 1] += thinking;
1155
+ return { active: true, rounds };
1156
+ }
1157
+ function stopThinking(state) {
1158
+ return state.active ? { ...state, active: false } : state;
1159
+ }
1160
+ function thinkingView(state) {
1161
+ return { active: state.active, text: state.rounds.filter((r) => r).join("\n\n") };
1162
+ }
1163
+ function createLatestGate() {
1164
+ let latest = 0;
1165
+ return {
1166
+ next: () => ++latest,
1167
+ isCurrent: (ticket) => ticket === latest
1168
+ };
1169
+ }
1170
+ var PENDING_POLL_MAX_MS = 3e4;
1171
+ var PENDING_POLL_MIN_MS = 1e3;
1172
+ var EXPIRY_GRACE_MS = 5e3;
1173
+ var OVERDUE_POLL_MS = 1e4;
1174
+ function nextPendingPollMs(inputs, nowMs) {
1175
+ let wait = PENDING_POLL_MAX_MS;
1176
+ for (const input of inputs) {
1177
+ const deadline = input.expiresAt ? Date.parse(input.expiresAt) : NaN;
1178
+ if (Number.isNaN(deadline)) continue;
1179
+ const untilDue = deadline + EXPIRY_GRACE_MS - nowMs;
1180
+ wait = Math.min(wait, untilDue > 0 ? untilDue : OVERDUE_POLL_MS);
1181
+ }
1182
+ return Math.max(PENDING_POLL_MIN_MS, wait);
1183
+ }
1184
+ var TRAILING_POLLS_MS = [5e3, 15e3, 3e4];
1185
+
1186
+ // src/hooks/useChat.ts
825
1187
  function useChat(agentRef, options) {
826
1188
  const client = useClient();
1189
+ const includeThinking = !!options?.includeThinking;
1190
+ const pollPendingInputs = options?.pollPendingInputs !== false;
827
1191
  const [chatId, setChatId] = import_react5.default.useState(options?.chatId ?? null);
828
1192
  const [messages, setMessages] = import_react5.default.useState([]);
829
1193
  const [loading, setLoading] = import_react5.default.useState(false);
830
1194
  const [streaming, setStreaming] = import_react5.default.useState(false);
831
1195
  const [error, setError] = import_react5.default.useState(null);
832
1196
  const [pendingApprovals, setPendingApprovals] = import_react5.default.useState([]);
1197
+ const [pendingInputs, setPendingInputs] = import_react5.default.useState([]);
1198
+ const [thinking, setThinking] = import_react5.default.useState(NOT_THINKING);
1199
+ const thinkingShown = import_react5.default.useMemo(() => thinkingView(thinking), [thinking]);
833
1200
  const [toolStatus, setToolStatus] = import_react5.default.useState([]);
1201
+ const [trailingPoll, setTrailingPoll] = import_react5.default.useState(TRAILING_POLLS_MS.length);
1202
+ const [pollTick, setPollTick] = import_react5.default.useState(0);
834
1203
  const abortRef = import_react5.default.useRef(null);
835
1204
  const chatIdRef = import_react5.default.useRef(chatId);
836
1205
  chatIdRef.current = chatId;
1206
+ const [gate] = import_react5.default.useState(createLatestGate);
1207
+ const refresh2 = import_react5.default.useCallback(
1208
+ async (id) => {
1209
+ const ticket = gate.next();
1210
+ try {
1211
+ const detail = await getChat(client, id);
1212
+ if (!gate.isCurrent(ticket)) return null;
1213
+ setMessages(detail.messages);
1214
+ setPendingApprovals(detail.pendingApprovals ?? []);
1215
+ const inputs = detail.pendingInputs ?? [];
1216
+ setPendingInputs(inputs);
1217
+ return inputs;
1218
+ } catch {
1219
+ return null;
1220
+ }
1221
+ },
1222
+ [client, gate]
1223
+ );
837
1224
  import_react5.default.useEffect(() => {
838
1225
  if (!options?.chatId) return;
839
1226
  let cancelled = false;
1227
+ const ticket = gate.next();
840
1228
  setLoading(true);
841
1229
  getChat(client, options.chatId).then((detail) => {
842
- if (cancelled) return;
1230
+ if (cancelled || !gate.isCurrent(ticket)) return;
843
1231
  setChatId(detail.id);
844
1232
  setMessages(detail.messages);
845
- if (detail.pendingApprovals?.length) {
846
- setPendingApprovals(detail.pendingApprovals);
847
- }
1233
+ setPendingApprovals(detail.pendingApprovals ?? []);
1234
+ setPendingInputs(detail.pendingInputs ?? []);
848
1235
  }).catch((e) => {
849
1236
  if (cancelled) return;
850
1237
  setError(e.message);
@@ -854,74 +1241,52 @@ function useChat(agentRef, options) {
854
1241
  return () => {
855
1242
  cancelled = true;
856
1243
  };
857
- }, [client, options?.chatId]);
1244
+ }, [client, gate, options?.chatId]);
858
1245
  import_react5.default.useEffect(() => {
859
1246
  return () => {
860
1247
  abortRef.current?.abort();
861
1248
  };
862
1249
  }, []);
863
- const handleStreamChunks = import_react5.default.useCallback(
1250
+ import_react5.default.useEffect(() => {
1251
+ if (!pollPendingInputs || streaming || !chatId) return;
1252
+ let delay = null;
1253
+ if (pendingInputs.length > 0) delay = nextPendingPollMs(pendingInputs, Date.now());
1254
+ else if (trailingPoll < TRAILING_POLLS_MS.length) delay = TRAILING_POLLS_MS[trailingPoll];
1255
+ if (delay === null) return;
1256
+ const waiting = pendingInputs.map((p) => p.toolCallId);
1257
+ const timer = setTimeout(async () => {
1258
+ const now = await refresh2(chatId);
1259
+ if (waiting.length === 0) setTrailingPoll((i) => i + 1);
1260
+ else if (now && waiting.some((id) => !now.some((p) => p.toolCallId === id))) setTrailingPoll(0);
1261
+ setPollTick((t) => t + 1);
1262
+ }, delay);
1263
+ return () => clearTimeout(timer);
1264
+ }, [pollPendingInputs, chatId, streaming, pendingInputs, trailingPoll, pollTick, refresh2]);
1265
+ const handleStreamChunk = import_react5.default.useCallback(
864
1266
  (chunk) => {
1267
+ const asked = pendingInputFromChunk(chunk);
1268
+ if (asked) {
1269
+ setThinking(stopThinking);
1270
+ setPendingInputs((prev) => mergePendingInputs(prev, [asked]));
1271
+ return;
1272
+ }
1273
+ const chatIdNow = chatIdRef.current || "";
865
1274
  if (chunk.event === "message") {
866
- const newContent = (() => {
867
- try {
868
- const parsed = JSON.parse(chunk.data);
869
- return parsed.content || "";
870
- } catch {
871
- return chunk.data;
872
- }
873
- })();
874
- if (!newContent) return;
875
- setMessages((prev) => {
876
- const last = prev[prev.length - 1];
877
- if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
878
- const next = [...prev];
879
- next[next.length - 1] = { ...last, content: (last.content || "") + newContent };
880
- return next;
881
- }
882
- return [...prev, {
883
- id: `tmp-text-${Date.now()}`,
884
- chatId: chatIdRef.current || "",
885
- role: "assistant",
886
- content: newContent,
887
- toolCalls: null,
888
- toolCallId: null,
889
- name: null,
890
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
891
- streaming: true
892
- }];
893
- });
1275
+ const { text, thinking: thought } = parseMessageChunk(chunk.data);
1276
+ if (thought !== void 0) setThinking((prev) => applyThinking(prev, thought));
1277
+ if (text) {
1278
+ setThinking(stopThinking);
1279
+ setMessages((prev) => applyTextDelta(prev, text, chatIdNow));
1280
+ }
894
1281
  } else if (chunk.event === "tool_start") {
1282
+ setThinking(stopThinking);
895
1283
  try {
896
1284
  const parsed = JSON.parse(chunk.data);
897
1285
  setToolStatus((prev) => [
898
1286
  ...prev,
899
1287
  { toolCallId: parsed.tool_call_id, name: parsed.name, description: parsed.description, status: "running" }
900
1288
  ]);
901
- setMessages((prev) => {
902
- if (prev.some((m) => m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id))) {
903
- return prev;
904
- }
905
- const next = prev.map(
906
- (m, i) => i === prev.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
907
- );
908
- return [...next, {
909
- id: `tmp-tc-${parsed.tool_call_id}`,
910
- chatId: chatIdRef.current || "",
911
- role: "assistant",
912
- content: null,
913
- toolCalls: [{
914
- id: parsed.tool_call_id,
915
- type: "function",
916
- function: { name: parsed.name, arguments: parsed.arguments || "{}" },
917
- description: parsed.description
918
- }],
919
- toolCallId: null,
920
- name: null,
921
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
922
- streaming: true
923
- }];
924
- });
1289
+ setMessages((prev) => applyToolStart(prev, parsed, chatIdNow));
925
1290
  } catch {
926
1291
  }
927
1292
  } else if (chunk.event === "tool_end") {
@@ -930,39 +1295,18 @@ function useChat(agentRef, options) {
930
1295
  setToolStatus(
931
1296
  (prev) => prev.map((t) => t.toolCallId === parsed.tool_call_id ? { ...t, status: "complete" } : t)
932
1297
  );
933
- if (parsed.result !== void 0) {
934
- const resultContent = typeof parsed.result === "string" ? parsed.result : JSON.stringify(parsed.result);
935
- setMessages(
936
- (prev) => prev.map((m) => {
937
- if (m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id)) {
938
- return {
939
- ...m,
940
- toolResults: {
941
- ...m.toolResults,
942
- [parsed.tool_call_id]: { content: resultContent, name: parsed.name }
943
- }
944
- };
945
- }
946
- return m;
947
- })
948
- );
949
- }
1298
+ setMessages((prev) => applyToolEnd(prev, parsed));
950
1299
  } catch {
951
1300
  }
952
1301
  } else if (chunk.event === "approval_required") {
1302
+ setThinking(stopThinking);
953
1303
  try {
954
- const raw = JSON.parse(chunk.data);
955
- const parsed = {
956
- type: raw.type,
957
- toolCallId: raw.tool_call_id,
958
- functionNamespace: raw.function_namespace,
959
- functionName: raw.function_name,
960
- arguments: raw.arguments
961
- };
962
- setPendingApprovals((prev) => [...prev, parsed]);
1304
+ const approval = parseApproval(JSON.parse(chunk.data));
1305
+ if (approval) setPendingApprovals((prev) => addApproval(prev, approval));
963
1306
  } catch {
964
1307
  }
965
1308
  } else if (chunk.event === "error") {
1309
+ setThinking(stopThinking);
966
1310
  try {
967
1311
  const parsed = JSON.parse(chunk.data);
968
1312
  setError(parsed.error || "Stream error");
@@ -973,18 +1317,37 @@ function useChat(agentRef, options) {
973
1317
  },
974
1318
  []
975
1319
  );
976
- const refreshMessages = import_react5.default.useCallback(
977
- async (id) => {
978
- try {
979
- const detail = await getChat(client, id);
980
- setMessages(detail.messages);
981
- if (detail.pendingApprovals?.length) {
982
- setPendingApprovals(detail.pendingApprovals);
983
- }
984
- } catch {
1320
+ const streamCallbacks = import_react5.default.useCallback(
1321
+ (id) => [
1322
+ (_done) => {
1323
+ setStreaming(false);
1324
+ setToolStatus([]);
1325
+ setThinking(stopThinking);
1326
+ refresh2(id);
1327
+ },
1328
+ (err) => {
1329
+ setError(err.message);
1330
+ setStreaming(false);
1331
+ setToolStatus([]);
1332
+ setThinking(stopThinking);
985
1333
  }
1334
+ ],
1335
+ [refresh2]
1336
+ );
1337
+ const followChannel = import_react5.default.useCallback(
1338
+ (id, channelId, lastId) => {
1339
+ abortRef.current?.abort();
1340
+ gate.next();
1341
+ setStreaming(true);
1342
+ setError(null);
1343
+ setThinking(NOT_THINKING);
1344
+ const [onDone, onError] = streamCallbacks(id);
1345
+ abortRef.current = streamFromChannel(client, id, channelId, handleStreamChunk, onDone, onError, {
1346
+ includeThinking,
1347
+ lastId
1348
+ });
986
1349
  },
987
- [client]
1350
+ [client, gate, includeThinking, handleStreamChunk, streamCallbacks]
988
1351
  );
989
1352
  const send = import_react5.default.useCallback(
990
1353
  (content) => {
@@ -1006,10 +1369,12 @@ function useChat(agentRef, options) {
1006
1369
  setLoading(false);
1007
1370
  }
1008
1371
  abortRef.current?.abort();
1372
+ gate.next();
1009
1373
  setStreaming(true);
1010
1374
  setError(null);
1011
1375
  setPendingApprovals([]);
1012
1376
  setToolStatus([]);
1377
+ setThinking(NOT_THINKING);
1013
1378
  const displayContent = typeof content === "string" ? content : JSON.stringify(content);
1014
1379
  const userMsg = {
1015
1380
  id: `tmp-user-${Date.now()}`,
@@ -1022,80 +1387,77 @@ function useChat(agentRef, options) {
1022
1387
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1023
1388
  };
1024
1389
  setMessages((prev) => [...prev, userMsg]);
1025
- const controller = streamMessage(
1026
- client,
1027
- activeChatId,
1028
- content,
1029
- handleStreamChunks,
1030
- () => {
1031
- setStreaming(false);
1032
- setToolStatus([]);
1033
- refreshMessages(activeChatId);
1034
- },
1035
- (err) => {
1036
- setError(err.message);
1037
- setStreaming(false);
1038
- setToolStatus([]);
1039
- }
1040
- );
1041
- abortRef.current = controller;
1390
+ const [onDone, onError] = streamCallbacks(activeChatId);
1391
+ abortRef.current = streamMessage(client, activeChatId, content, handleStreamChunk, onDone, onError, {
1392
+ includeThinking
1393
+ });
1042
1394
  };
1043
1395
  doSend();
1044
1396
  },
1045
- [client, agentRef, options?.input, handleStreamChunks, refreshMessages]
1397
+ [client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
1046
1398
  );
1047
- const handleApproval = import_react5.default.useCallback(
1048
- (toolCallId, approved) => {
1049
- const currentChatId = chatIdRef.current;
1050
- if (!currentChatId) return;
1399
+ const decide = import_react5.default.useCallback(
1400
+ (toolCallId, approved, alwaysAllow) => {
1401
+ const id = chatIdRef.current;
1402
+ if (!id) return;
1403
+ gate.next();
1051
1404
  setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
1052
- setStreaming(true);
1053
1405
  setError(null);
1054
1406
  (async () => {
1055
1407
  try {
1056
- const result = await approveToolCall(client, currentChatId, toolCallId, approved);
1057
- if (!approved) {
1058
- setStreaming(false);
1059
- refreshMessages(currentChatId);
1060
- return;
1061
- }
1062
- const controller = streamFromChannel(
1063
- client,
1064
- currentChatId,
1065
- result.channelId,
1066
- handleStreamChunks,
1067
- () => {
1068
- setStreaming(false);
1069
- setToolStatus([]);
1070
- refreshMessages(currentChatId);
1071
- },
1072
- (err) => {
1073
- setError(err.message);
1074
- setStreaming(false);
1075
- setToolStatus([]);
1076
- }
1077
- );
1078
- abortRef.current = controller;
1408
+ const result = await approveToolCall(client, id, toolCallId, approved, { alwaysAllow });
1409
+ const channelId = approvalResumeChannel(result, approved);
1410
+ if (channelId) followChannel(id, channelId);
1411
+ else if (result.resume === void 0) refresh2(id);
1079
1412
  } catch (e) {
1080
1413
  setError(e.message);
1081
- setStreaming(false);
1414
+ refresh2(id);
1082
1415
  }
1083
1416
  })();
1084
1417
  },
1085
- [client, handleStreamChunks, refreshMessages]
1418
+ [client, gate, followChannel, refresh2]
1086
1419
  );
1087
1420
  const approve = import_react5.default.useCallback(
1088
- (toolCallId) => handleApproval(toolCallId, true),
1089
- [handleApproval]
1421
+ (toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
1422
+ [decide]
1090
1423
  );
1091
1424
  const reject = import_react5.default.useCallback(
1092
- (toolCallId) => handleApproval(toolCallId, false),
1093
- [handleApproval]
1425
+ (toolCallId) => decide(toolCallId, false, false),
1426
+ [decide]
1427
+ );
1428
+ const answer = import_react5.default.useCallback(
1429
+ async (toolCallId, text) => {
1430
+ const id = chatIdRef.current;
1431
+ if (!id) return false;
1432
+ setError(null);
1433
+ try {
1434
+ const result = await answerPendingInput(client, id, toolCallId, text);
1435
+ gate.next();
1436
+ setPendingInputs((prev) => prev.filter((p) => p.toolCallId !== toolCallId));
1437
+ const channelId = answerResumeChannel(result);
1438
+ if (channelId) followChannel(id, channelId);
1439
+ return true;
1440
+ } catch (e) {
1441
+ setError(e.message);
1442
+ refresh2(id);
1443
+ return false;
1444
+ }
1445
+ },
1446
+ [client, gate, followChannel, refresh2]
1447
+ );
1448
+ const follow = import_react5.default.useCallback(
1449
+ (channelId, opts) => {
1450
+ const id = chatIdRef.current;
1451
+ if (!id) return;
1452
+ followChannel(id, channelId, opts?.lastId);
1453
+ },
1454
+ [followChannel]
1094
1455
  );
1095
1456
  const stop = import_react5.default.useCallback(() => {
1096
1457
  abortRef.current?.abort();
1097
1458
  abortRef.current = null;
1098
1459
  setStreaming(false);
1460
+ setThinking(stopThinking);
1099
1461
  }, []);
1100
1462
  const clearError = import_react5.default.useCallback(() => {
1101
1463
  setError(null);
@@ -1107,10 +1469,14 @@ function useChat(agentRef, options) {
1107
1469
  streaming,
1108
1470
  error,
1109
1471
  pendingApprovals,
1472
+ pendingInputs,
1473
+ thinking: thinkingShown,
1110
1474
  toolStatus,
1111
1475
  send,
1112
1476
  approve,
1113
1477
  reject,
1478
+ answer,
1479
+ follow,
1114
1480
  stop,
1115
1481
  clearError
1116
1482
  };
@@ -1214,19 +1580,31 @@ function useAuth(options = {}) {
1214
1580
  SinasPermissionError,
1215
1581
  SinasProvider,
1216
1582
  SinasTimeoutError,
1583
+ answerPendingInput,
1584
+ answerResumeChannel,
1585
+ approvalResumeChannel,
1217
1586
  approveToolCall,
1587
+ cancelBatch,
1218
1588
  checkPermissions,
1219
1589
  createChat,
1590
+ enqueueFunction,
1220
1591
  executeFunction,
1221
1592
  executeQuery,
1222
1593
  generateFileUrl,
1594
+ getBatch,
1223
1595
  getChat,
1224
1596
  getInfo,
1225
1597
  getMe,
1598
+ invokeAgent,
1599
+ listBatchExecutions,
1226
1600
  listCollections,
1227
1601
  listFiles,
1602
+ listPendingInputs,
1228
1603
  login,
1229
1604
  logout,
1605
+ parsePendingInput,
1606
+ parsePendingInputs,
1607
+ pendingInputFromChunk,
1230
1608
  refresh,
1231
1609
  sendMessage,
1232
1610
  stateDelete,
@@ -1235,6 +1613,8 @@ function useAuth(options = {}) {
1235
1613
  stateSet,
1236
1614
  streamFromChannel,
1237
1615
  streamMessage,
1616
+ submitAgentBatch,
1617
+ submitFunctionBatch,
1238
1618
  uploadFile,
1239
1619
  useAuth,
1240
1620
  useChat,