@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/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
|
-
|
|
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
|
|
451
|
-
const
|
|
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
|
-
|
|
695
|
+
reply: typeof data.reply === "string" ? data.reply : "",
|
|
454
696
|
chatId: data.chat_id,
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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(
|
|
473
|
-
pendingApprovals: (data.pending_approvals || []).map((
|
|
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(
|
|
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
|
-
|
|
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
|
|
496
|
-
const
|
|
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 ||
|
|
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
|
-
|
|
543
|
-
let
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
846
|
-
|
|
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
|
-
|
|
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
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
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
|
|
955
|
-
|
|
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
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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,
|
|
1397
|
+
[client, agentRef, options?.input, gate, includeThinking, handleStreamChunk, streamCallbacks]
|
|
1046
1398
|
);
|
|
1047
|
-
const
|
|
1048
|
-
(toolCallId, approved) => {
|
|
1049
|
-
const
|
|
1050
|
-
if (!
|
|
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,
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1414
|
+
refresh2(id);
|
|
1082
1415
|
}
|
|
1083
1416
|
})();
|
|
1084
1417
|
},
|
|
1085
|
-
[client,
|
|
1418
|
+
[client, gate, followChannel, refresh2]
|
|
1086
1419
|
);
|
|
1087
1420
|
const approve = import_react5.default.useCallback(
|
|
1088
|
-
(toolCallId) =>
|
|
1089
|
-
[
|
|
1421
|
+
(toolCallId, opts) => decide(toolCallId, true, !!opts?.alwaysAllow),
|
|
1422
|
+
[decide]
|
|
1090
1423
|
);
|
|
1091
1424
|
const reject = import_react5.default.useCallback(
|
|
1092
|
-
(toolCallId) =>
|
|
1093
|
-
[
|
|
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,
|