@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.js CHANGED
@@ -6,19 +6,32 @@ import {
6
6
  SinasNotFoundError,
7
7
  SinasPermissionError,
8
8
  SinasTimeoutError,
9
+ answerPendingInput,
10
+ answerResumeChannel,
11
+ approvalResumeChannel,
9
12
  approveToolCall,
13
+ cancelBatch,
10
14
  checkPermissions,
11
15
  createChat,
16
+ enqueueFunction,
12
17
  executeFunction,
13
18
  executeQuery,
14
19
  generateFileUrl,
20
+ getBatch,
15
21
  getChat,
16
22
  getInfo,
17
23
  getMe,
24
+ invokeAgent,
25
+ listBatchExecutions,
18
26
  listCollections,
19
27
  listFiles,
28
+ listPendingInputs,
20
29
  login,
21
30
  logout,
31
+ parseApproval,
32
+ parsePendingInput,
33
+ parsePendingInputs,
34
+ pendingInputFromChunk,
22
35
  refresh,
23
36
  sendMessage,
24
37
  stateDelete,
@@ -27,9 +40,11 @@ import {
27
40
  stateSet,
28
41
  streamFromChannel,
29
42
  streamMessage,
43
+ submitAgentBatch,
44
+ submitFunctionBatch,
30
45
  uploadFile,
31
46
  verifyOTP
32
- } from "./chunk-IABVF56L.js";
47
+ } from "./chunk-RHQVV5O6.js";
33
48
 
34
49
  // src/hooks/useQuery.ts
35
50
  import React2 from "react";
@@ -212,29 +227,172 @@ function useStateStore(ns, key) {
212
227
 
213
228
  // src/hooks/useChat.ts
214
229
  import React5 from "react";
230
+
231
+ // src/hooks/chatState.ts
232
+ function parseMessageChunk(data) {
233
+ let parsed;
234
+ try {
235
+ parsed = JSON.parse(data);
236
+ } catch {
237
+ return { text: data };
238
+ }
239
+ if (!parsed || typeof parsed !== "object") return { text: "" };
240
+ const p = parsed;
241
+ const out = { text: typeof p.content === "string" ? p.content : "" };
242
+ if (typeof p.thinking === "string") out.thinking = p.thinking;
243
+ return out;
244
+ }
245
+ function applyTextDelta(messages, text, chatId, now = /* @__PURE__ */ new Date()) {
246
+ if (!text) return messages;
247
+ const last = messages[messages.length - 1];
248
+ if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
249
+ const next = [...messages];
250
+ next[next.length - 1] = { ...last, content: (last.content || "") + text };
251
+ return next;
252
+ }
253
+ return [
254
+ ...messages,
255
+ {
256
+ id: `tmp-text-${now.getTime()}`,
257
+ chatId,
258
+ role: "assistant",
259
+ content: text,
260
+ toolCalls: null,
261
+ toolCallId: null,
262
+ name: null,
263
+ createdAt: now.toISOString(),
264
+ streaming: true
265
+ }
266
+ ];
267
+ }
268
+ function applyToolStart(messages, event, chatId, now = /* @__PURE__ */ new Date()) {
269
+ if (messages.some((m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id))) return messages;
270
+ const frozen = messages.map(
271
+ (m, i) => i === messages.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
272
+ );
273
+ return [
274
+ ...frozen,
275
+ {
276
+ id: `tmp-tc-${event.tool_call_id}`,
277
+ chatId,
278
+ role: "assistant",
279
+ content: null,
280
+ toolCalls: [
281
+ {
282
+ id: event.tool_call_id,
283
+ type: "function",
284
+ function: { name: event.name, arguments: event.arguments || "{}" },
285
+ description: event.description
286
+ }
287
+ ],
288
+ toolCallId: null,
289
+ name: null,
290
+ createdAt: now.toISOString(),
291
+ streaming: true
292
+ }
293
+ ];
294
+ }
295
+ function applyToolEnd(messages, event) {
296
+ if (event.result === void 0) return messages;
297
+ const content = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
298
+ return messages.map(
299
+ (m) => m.toolCalls?.some((tc) => tc.id === event.tool_call_id) ? { ...m, toolResults: { ...m.toolResults, [event.tool_call_id]: { content, name: event.name } } } : m
300
+ );
301
+ }
302
+ function addApproval(current, approval) {
303
+ return current.some((a) => a.toolCallId === approval.toolCallId) ? current : [...current, approval];
304
+ }
305
+ function mergePendingInputs(current, incoming) {
306
+ const seen = new Set(current.map((p) => p.toolCallId));
307
+ const added = incoming.filter((p) => !seen.has(p.toolCallId) && seen.add(p.toolCallId));
308
+ return added.length ? [...current, ...added] : current;
309
+ }
310
+ var NOT_THINKING = { active: false, rounds: [] };
311
+ function applyThinking(state, thinking) {
312
+ if (!state.active) return { active: true, rounds: [...state.rounds, thinking] };
313
+ if (!thinking) return state;
314
+ const rounds = [...state.rounds];
315
+ rounds[rounds.length - 1] += thinking;
316
+ return { active: true, rounds };
317
+ }
318
+ function stopThinking(state) {
319
+ return state.active ? { ...state, active: false } : state;
320
+ }
321
+ function thinkingView(state) {
322
+ return { active: state.active, text: state.rounds.filter((r) => r).join("\n\n") };
323
+ }
324
+ function createLatestGate() {
325
+ let latest = 0;
326
+ return {
327
+ next: () => ++latest,
328
+ isCurrent: (ticket) => ticket === latest
329
+ };
330
+ }
331
+ var PENDING_POLL_MAX_MS = 3e4;
332
+ var PENDING_POLL_MIN_MS = 1e3;
333
+ var EXPIRY_GRACE_MS = 5e3;
334
+ var OVERDUE_POLL_MS = 1e4;
335
+ function nextPendingPollMs(inputs, nowMs) {
336
+ let wait = PENDING_POLL_MAX_MS;
337
+ for (const input of inputs) {
338
+ const deadline = input.expiresAt ? Date.parse(input.expiresAt) : NaN;
339
+ if (Number.isNaN(deadline)) continue;
340
+ const untilDue = deadline + EXPIRY_GRACE_MS - nowMs;
341
+ wait = Math.min(wait, untilDue > 0 ? untilDue : OVERDUE_POLL_MS);
342
+ }
343
+ return Math.max(PENDING_POLL_MIN_MS, wait);
344
+ }
345
+ var TRAILING_POLLS_MS = [5e3, 15e3, 3e4];
346
+
347
+ // src/hooks/useChat.ts
215
348
  function useChat(agentRef, options) {
216
349
  const client = useClient();
350
+ const includeThinking = !!options?.includeThinking;
351
+ const pollPendingInputs = options?.pollPendingInputs !== false;
217
352
  const [chatId, setChatId] = React5.useState(options?.chatId ?? null);
218
353
  const [messages, setMessages] = React5.useState([]);
219
354
  const [loading, setLoading] = React5.useState(false);
220
355
  const [streaming, setStreaming] = React5.useState(false);
221
356
  const [error, setError] = React5.useState(null);
222
357
  const [pendingApprovals, setPendingApprovals] = React5.useState([]);
358
+ const [pendingInputs, setPendingInputs] = React5.useState([]);
359
+ const [thinking, setThinking] = React5.useState(NOT_THINKING);
360
+ const thinkingShown = React5.useMemo(() => thinkingView(thinking), [thinking]);
223
361
  const [toolStatus, setToolStatus] = React5.useState([]);
362
+ const [trailingPoll, setTrailingPoll] = React5.useState(TRAILING_POLLS_MS.length);
363
+ const [pollTick, setPollTick] = React5.useState(0);
224
364
  const abortRef = React5.useRef(null);
225
365
  const chatIdRef = React5.useRef(chatId);
226
366
  chatIdRef.current = chatId;
367
+ const [gate] = React5.useState(createLatestGate);
368
+ const refresh2 = React5.useCallback(
369
+ async (id) => {
370
+ const ticket = gate.next();
371
+ try {
372
+ const detail = await getChat(client, id);
373
+ if (!gate.isCurrent(ticket)) return null;
374
+ setMessages(detail.messages);
375
+ setPendingApprovals(detail.pendingApprovals ?? []);
376
+ const inputs = detail.pendingInputs ?? [];
377
+ setPendingInputs(inputs);
378
+ return inputs;
379
+ } catch {
380
+ return null;
381
+ }
382
+ },
383
+ [client, gate]
384
+ );
227
385
  React5.useEffect(() => {
228
386
  if (!options?.chatId) return;
229
387
  let cancelled = false;
388
+ const ticket = gate.next();
230
389
  setLoading(true);
231
390
  getChat(client, options.chatId).then((detail) => {
232
- if (cancelled) return;
391
+ if (cancelled || !gate.isCurrent(ticket)) return;
233
392
  setChatId(detail.id);
234
393
  setMessages(detail.messages);
235
- if (detail.pendingApprovals?.length) {
236
- setPendingApprovals(detail.pendingApprovals);
237
- }
394
+ setPendingApprovals(detail.pendingApprovals ?? []);
395
+ setPendingInputs(detail.pendingInputs ?? []);
238
396
  }).catch((e) => {
239
397
  if (cancelled) return;
240
398
  setError(e.message);
@@ -244,74 +402,52 @@ function useChat(agentRef, options) {
244
402
  return () => {
245
403
  cancelled = true;
246
404
  };
247
- }, [client, options?.chatId]);
405
+ }, [client, gate, options?.chatId]);
248
406
  React5.useEffect(() => {
249
407
  return () => {
250
408
  abortRef.current?.abort();
251
409
  };
252
410
  }, []);
253
- const handleStreamChunks = React5.useCallback(
411
+ React5.useEffect(() => {
412
+ if (!pollPendingInputs || streaming || !chatId) return;
413
+ let delay = null;
414
+ if (pendingInputs.length > 0) delay = nextPendingPollMs(pendingInputs, Date.now());
415
+ else if (trailingPoll < TRAILING_POLLS_MS.length) delay = TRAILING_POLLS_MS[trailingPoll];
416
+ if (delay === null) return;
417
+ const waiting = pendingInputs.map((p) => p.toolCallId);
418
+ const timer = setTimeout(async () => {
419
+ const now = await refresh2(chatId);
420
+ if (waiting.length === 0) setTrailingPoll((i) => i + 1);
421
+ else if (now && waiting.some((id) => !now.some((p) => p.toolCallId === id))) setTrailingPoll(0);
422
+ setPollTick((t) => t + 1);
423
+ }, delay);
424
+ return () => clearTimeout(timer);
425
+ }, [pollPendingInputs, chatId, streaming, pendingInputs, trailingPoll, pollTick, refresh2]);
426
+ const handleStreamChunk = React5.useCallback(
254
427
  (chunk) => {
428
+ const asked = pendingInputFromChunk(chunk);
429
+ if (asked) {
430
+ setThinking(stopThinking);
431
+ setPendingInputs((prev) => mergePendingInputs(prev, [asked]));
432
+ return;
433
+ }
434
+ const chatIdNow = chatIdRef.current || "";
255
435
  if (chunk.event === "message") {
256
- const newContent = (() => {
257
- try {
258
- const parsed = JSON.parse(chunk.data);
259
- return parsed.content || "";
260
- } catch {
261
- return chunk.data;
262
- }
263
- })();
264
- if (!newContent) return;
265
- setMessages((prev) => {
266
- const last = prev[prev.length - 1];
267
- if (last && last.role === "assistant" && !last.toolCalls && last.streaming) {
268
- const next = [...prev];
269
- next[next.length - 1] = { ...last, content: (last.content || "") + newContent };
270
- return next;
271
- }
272
- return [...prev, {
273
- id: `tmp-text-${Date.now()}`,
274
- chatId: chatIdRef.current || "",
275
- role: "assistant",
276
- content: newContent,
277
- toolCalls: null,
278
- toolCallId: null,
279
- name: null,
280
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
281
- streaming: true
282
- }];
283
- });
436
+ const { text, thinking: thought } = parseMessageChunk(chunk.data);
437
+ if (thought !== void 0) setThinking((prev) => applyThinking(prev, thought));
438
+ if (text) {
439
+ setThinking(stopThinking);
440
+ setMessages((prev) => applyTextDelta(prev, text, chatIdNow));
441
+ }
284
442
  } else if (chunk.event === "tool_start") {
443
+ setThinking(stopThinking);
285
444
  try {
286
445
  const parsed = JSON.parse(chunk.data);
287
446
  setToolStatus((prev) => [
288
447
  ...prev,
289
448
  { toolCallId: parsed.tool_call_id, name: parsed.name, description: parsed.description, status: "running" }
290
449
  ]);
291
- setMessages((prev) => {
292
- if (prev.some((m) => m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id))) {
293
- return prev;
294
- }
295
- const next = prev.map(
296
- (m, i) => i === prev.length - 1 && m.streaming && m.role === "assistant" && !m.toolCalls ? { ...m, streaming: false } : m
297
- );
298
- return [...next, {
299
- id: `tmp-tc-${parsed.tool_call_id}`,
300
- chatId: chatIdRef.current || "",
301
- role: "assistant",
302
- content: null,
303
- toolCalls: [{
304
- id: parsed.tool_call_id,
305
- type: "function",
306
- function: { name: parsed.name, arguments: parsed.arguments || "{}" },
307
- description: parsed.description
308
- }],
309
- toolCallId: null,
310
- name: null,
311
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
312
- streaming: true
313
- }];
314
- });
450
+ setMessages((prev) => applyToolStart(prev, parsed, chatIdNow));
315
451
  } catch {
316
452
  }
317
453
  } else if (chunk.event === "tool_end") {
@@ -320,39 +456,18 @@ function useChat(agentRef, options) {
320
456
  setToolStatus(
321
457
  (prev) => prev.map((t) => t.toolCallId === parsed.tool_call_id ? { ...t, status: "complete" } : t)
322
458
  );
323
- if (parsed.result !== void 0) {
324
- const resultContent = typeof parsed.result === "string" ? parsed.result : JSON.stringify(parsed.result);
325
- setMessages(
326
- (prev) => prev.map((m) => {
327
- if (m.toolCalls?.some((tc) => tc.id === parsed.tool_call_id)) {
328
- return {
329
- ...m,
330
- toolResults: {
331
- ...m.toolResults,
332
- [parsed.tool_call_id]: { content: resultContent, name: parsed.name }
333
- }
334
- };
335
- }
336
- return m;
337
- })
338
- );
339
- }
459
+ setMessages((prev) => applyToolEnd(prev, parsed));
340
460
  } catch {
341
461
  }
342
462
  } else if (chunk.event === "approval_required") {
463
+ setThinking(stopThinking);
343
464
  try {
344
- const raw = JSON.parse(chunk.data);
345
- const parsed = {
346
- type: raw.type,
347
- toolCallId: raw.tool_call_id,
348
- functionNamespace: raw.function_namespace,
349
- functionName: raw.function_name,
350
- arguments: raw.arguments
351
- };
352
- setPendingApprovals((prev) => [...prev, parsed]);
465
+ const approval = parseApproval(JSON.parse(chunk.data));
466
+ if (approval) setPendingApprovals((prev) => addApproval(prev, approval));
353
467
  } catch {
354
468
  }
355
469
  } else if (chunk.event === "error") {
470
+ setThinking(stopThinking);
356
471
  try {
357
472
  const parsed = JSON.parse(chunk.data);
358
473
  setError(parsed.error || "Stream error");
@@ -363,18 +478,37 @@ function useChat(agentRef, options) {
363
478
  },
364
479
  []
365
480
  );
366
- const refreshMessages = React5.useCallback(
367
- async (id) => {
368
- try {
369
- const detail = await getChat(client, id);
370
- setMessages(detail.messages);
371
- if (detail.pendingApprovals?.length) {
372
- setPendingApprovals(detail.pendingApprovals);
373
- }
374
- } catch {
481
+ const streamCallbacks = React5.useCallback(
482
+ (id) => [
483
+ (_done) => {
484
+ setStreaming(false);
485
+ setToolStatus([]);
486
+ setThinking(stopThinking);
487
+ refresh2(id);
488
+ },
489
+ (err) => {
490
+ setError(err.message);
491
+ setStreaming(false);
492
+ setToolStatus([]);
493
+ setThinking(stopThinking);
375
494
  }
495
+ ],
496
+ [refresh2]
497
+ );
498
+ const followChannel = React5.useCallback(
499
+ (id, channelId, lastId) => {
500
+ abortRef.current?.abort();
501
+ gate.next();
502
+ setStreaming(true);
503
+ setError(null);
504
+ setThinking(NOT_THINKING);
505
+ const [onDone, onError] = streamCallbacks(id);
506
+ abortRef.current = streamFromChannel(client, id, channelId, handleStreamChunk, onDone, onError, {
507
+ includeThinking,
508
+ lastId
509
+ });
376
510
  },
377
- [client]
511
+ [client, gate, includeThinking, handleStreamChunk, streamCallbacks]
378
512
  );
379
513
  const send = React5.useCallback(
380
514
  (content) => {
@@ -396,10 +530,12 @@ function useChat(agentRef, options) {
396
530
  setLoading(false);
397
531
  }
398
532
  abortRef.current?.abort();
533
+ gate.next();
399
534
  setStreaming(true);
400
535
  setError(null);
401
536
  setPendingApprovals([]);
402
537
  setToolStatus([]);
538
+ setThinking(NOT_THINKING);
403
539
  const displayContent = typeof content === "string" ? content : JSON.stringify(content);
404
540
  const userMsg = {
405
541
  id: `tmp-user-${Date.now()}`,
@@ -412,80 +548,77 @@ function useChat(agentRef, options) {
412
548
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
413
549
  };
414
550
  setMessages((prev) => [...prev, userMsg]);
415
- const controller = streamMessage(
416
- client,
417
- activeChatId,
418
- content,
419
- handleStreamChunks,
420
- () => {
421
- setStreaming(false);
422
- setToolStatus([]);
423
- refreshMessages(activeChatId);
424
- },
425
- (err) => {
426
- setError(err.message);
427
- setStreaming(false);
428
- setToolStatus([]);
429
- }
430
- );
431
- abortRef.current = controller;
551
+ const [onDone, onError] = streamCallbacks(activeChatId);
552
+ abortRef.current = streamMessage(client, activeChatId, content, handleStreamChunk, onDone, onError, {
553
+ includeThinking
554
+ });
432
555
  };
433
556
  doSend();
434
557
  },
435
- [client, agentRef, options?.input, handleStreamChunks, refreshMessages]
558
+ [client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
436
559
  );
437
- const handleApproval = React5.useCallback(
438
- (toolCallId, approved) => {
439
- const currentChatId = chatIdRef.current;
440
- if (!currentChatId) return;
560
+ const decide = React5.useCallback(
561
+ (toolCallId, approved, alwaysAllow) => {
562
+ const id = chatIdRef.current;
563
+ if (!id) return;
564
+ gate.next();
441
565
  setPendingApprovals((prev) => prev.filter((a) => a.toolCallId !== toolCallId));
442
- setStreaming(true);
443
566
  setError(null);
444
567
  (async () => {
445
568
  try {
446
- const result = await approveToolCall(client, currentChatId, toolCallId, approved);
447
- if (!approved) {
448
- setStreaming(false);
449
- refreshMessages(currentChatId);
450
- return;
451
- }
452
- const controller = streamFromChannel(
453
- client,
454
- currentChatId,
455
- result.channelId,
456
- handleStreamChunks,
457
- () => {
458
- setStreaming(false);
459
- setToolStatus([]);
460
- refreshMessages(currentChatId);
461
- },
462
- (err) => {
463
- setError(err.message);
464
- setStreaming(false);
465
- setToolStatus([]);
466
- }
467
- );
468
- abortRef.current = controller;
569
+ const result = await approveToolCall(client, id, toolCallId, approved, { alwaysAllow });
570
+ const channelId = approvalResumeChannel(result, approved);
571
+ if (channelId) followChannel(id, channelId);
572
+ else if (result.resume === void 0) refresh2(id);
469
573
  } catch (e) {
470
574
  setError(e.message);
471
- setStreaming(false);
575
+ refresh2(id);
472
576
  }
473
577
  })();
474
578
  },
475
- [client, handleStreamChunks, refreshMessages]
579
+ [client, gate, followChannel, refresh2]
476
580
  );
477
581
  const approve = React5.useCallback(
478
- (toolCallId) => handleApproval(toolCallId, true),
479
- [handleApproval]
582
+ (toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
583
+ [decide]
480
584
  );
481
585
  const reject = React5.useCallback(
482
- (toolCallId) => handleApproval(toolCallId, false),
483
- [handleApproval]
586
+ (toolCallId) => decide(toolCallId, false, false),
587
+ [decide]
588
+ );
589
+ const answer = React5.useCallback(
590
+ async (toolCallId, text) => {
591
+ const id = chatIdRef.current;
592
+ if (!id) return false;
593
+ setError(null);
594
+ try {
595
+ const result = await answerPendingInput(client, id, toolCallId, text);
596
+ gate.next();
597
+ setPendingInputs((prev) => prev.filter((p) => p.toolCallId !== toolCallId));
598
+ const channelId = answerResumeChannel(result);
599
+ if (channelId) followChannel(id, channelId);
600
+ return true;
601
+ } catch (e) {
602
+ setError(e.message);
603
+ refresh2(id);
604
+ return false;
605
+ }
606
+ },
607
+ [client, gate, followChannel, refresh2]
608
+ );
609
+ const follow = React5.useCallback(
610
+ (channelId, opts) => {
611
+ const id = chatIdRef.current;
612
+ if (!id) return;
613
+ followChannel(id, channelId, opts?.lastId);
614
+ },
615
+ [followChannel]
484
616
  );
485
617
  const stop = React5.useCallback(() => {
486
618
  abortRef.current?.abort();
487
619
  abortRef.current = null;
488
620
  setStreaming(false);
621
+ setThinking(stopThinking);
489
622
  }, []);
490
623
  const clearError = React5.useCallback(() => {
491
624
  setError(null);
@@ -497,10 +630,14 @@ function useChat(agentRef, options) {
497
630
  streaming,
498
631
  error,
499
632
  pendingApprovals,
633
+ pendingInputs,
634
+ thinking: thinkingShown,
500
635
  toolStatus,
501
636
  send,
502
637
  approve,
503
638
  reject,
639
+ answer,
640
+ follow,
504
641
  stop,
505
642
  clearError
506
643
  };
@@ -603,19 +740,31 @@ export {
603
740
  SinasPermissionError,
604
741
  SinasProvider,
605
742
  SinasTimeoutError,
743
+ answerPendingInput,
744
+ answerResumeChannel,
745
+ approvalResumeChannel,
606
746
  approveToolCall,
747
+ cancelBatch,
607
748
  checkPermissions,
608
749
  createChat,
750
+ enqueueFunction,
609
751
  executeFunction,
610
752
  executeQuery,
611
753
  generateFileUrl,
754
+ getBatch,
612
755
  getChat,
613
756
  getInfo,
614
757
  getMe,
758
+ invokeAgent,
759
+ listBatchExecutions,
615
760
  listCollections,
616
761
  listFiles,
762
+ listPendingInputs,
617
763
  login,
618
764
  logout,
765
+ parsePendingInput,
766
+ parsePendingInputs,
767
+ pendingInputFromChunk,
619
768
  refresh,
620
769
  sendMessage,
621
770
  stateDelete,
@@ -624,6 +773,8 @@ export {
624
773
  stateSet,
625
774
  streamFromChannel,
626
775
  streamMessage,
776
+ submitAgentBatch,
777
+ submitFunctionBatch,
627
778
  uploadFile,
628
779
  useAuth,
629
780
  useChat,