@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/{chunk-IABVF56L.js → chunk-RHQVV5O6.js} +344 -99
- package/dist/chunk-RHQVV5O6.js.map +1 -0
- package/dist/client.cjs +356 -98
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +330 -5
- package/dist/client.d.ts +330 -5
- package/dist/client.js +29 -1
- package/dist/index.cjs +624 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -4
- package/dist/index.d.ts +62 -4
- package/dist/index.js +298 -147
- package/dist/index.js.map +1 -1
- package/dist/sinas-sdk.umd.js +610 -244
- package/dist/sinas-sdk.umd.js.map +1 -1
- package/package.json +6 -3
- package/dist/chunk-IABVF56L.js.map +0 -1
package/dist/sinas-sdk.umd.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
462
|
-
const
|
|
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
|
-
|
|
706
|
+
reply: typeof data.reply === "string" ? data.reply : "",
|
|
465
707
|
chatId: data.chat_id,
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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(
|
|
484
|
-
pendingApprovals: (data.pending_approvals || []).map((
|
|
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(
|
|
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
|
-
|
|
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
|
|
507
|
-
const
|
|
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 ||
|
|
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
|
-
|
|
554
|
-
let
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
857
|
-
|
|
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
|
-
|
|
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
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
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
|
|
966
|
-
|
|
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
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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,
|
|
1408
|
+
[client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
|
|
1057
1409
|
);
|
|
1058
|
-
const
|
|
1059
|
-
(toolCallId, approved) => {
|
|
1060
|
-
const
|
|
1061
|
-
if (!
|
|
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,
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
-
|
|
1425
|
+
refresh2(id);
|
|
1093
1426
|
}
|
|
1094
1427
|
})();
|
|
1095
1428
|
},
|
|
1096
|
-
[client,
|
|
1429
|
+
[client, gate, followChannel, refresh2]
|
|
1097
1430
|
);
|
|
1098
1431
|
const approve = import_react5.default.useCallback(
|
|
1099
|
-
(toolCallId) =>
|
|
1100
|
-
[
|
|
1432
|
+
(toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
|
|
1433
|
+
[decide]
|
|
1101
1434
|
);
|
|
1102
1435
|
const reject = import_react5.default.useCallback(
|
|
1103
|
-
(toolCallId) =>
|
|
1104
|
-
[
|
|
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
|
};
|