@lov3kaizen/agentsea-react 0.4.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +153 -6
- package/dist/index.d.ts +153 -6
- package/dist/index.js +806 -0
- package/dist/index.mjs +798 -0
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -220,9 +220,807 @@ var useStreamingContent = () => {
|
|
|
220
220
|
consumeStream
|
|
221
221
|
};
|
|
222
222
|
};
|
|
223
|
+
|
|
224
|
+
// src/useChat.ts
|
|
225
|
+
import { useState, useCallback, useRef, useEffect } from "react";
|
|
226
|
+
|
|
227
|
+
// src/types.ts
|
|
228
|
+
function generateId() {
|
|
229
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
230
|
+
}
|
|
231
|
+
function createMessage(role, content, options) {
|
|
232
|
+
return {
|
|
233
|
+
id: generateId(),
|
|
234
|
+
role,
|
|
235
|
+
content,
|
|
236
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
237
|
+
...options
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/adapters.ts
|
|
242
|
+
function createSSEAdapter() {
|
|
243
|
+
let eventSource = null;
|
|
244
|
+
let abortController = null;
|
|
245
|
+
let messageCallback = null;
|
|
246
|
+
let errorCallback = null;
|
|
247
|
+
let closeCallback = null;
|
|
248
|
+
let currentUrl = "";
|
|
249
|
+
let currentOptions = {};
|
|
250
|
+
return {
|
|
251
|
+
connect(url, options) {
|
|
252
|
+
currentUrl = url;
|
|
253
|
+
currentOptions = options || {};
|
|
254
|
+
abortController = new AbortController();
|
|
255
|
+
if (options?.signal) {
|
|
256
|
+
options.signal.addEventListener("abort", () => {
|
|
257
|
+
abortController?.abort();
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return Promise.resolve();
|
|
261
|
+
},
|
|
262
|
+
async send(data) {
|
|
263
|
+
const response = await fetch(currentUrl, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json",
|
|
267
|
+
Accept: "text/event-stream",
|
|
268
|
+
...currentOptions.headers
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify(data),
|
|
271
|
+
signal: abortController?.signal
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const error = new Error(`HTTP error: ${response.status}`);
|
|
275
|
+
errorCallback?.(error);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const reader = response.body?.getReader();
|
|
279
|
+
if (!reader) {
|
|
280
|
+
errorCallback?.(new Error("No response body"));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const decoder = new TextDecoder();
|
|
284
|
+
let buffer = "";
|
|
285
|
+
try {
|
|
286
|
+
let reading = true;
|
|
287
|
+
while (reading) {
|
|
288
|
+
const { done, value } = await reader.read();
|
|
289
|
+
if (done) {
|
|
290
|
+
reading = false;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
buffer += decoder.decode(value, { stream: true });
|
|
294
|
+
const lines = buffer.split("\n");
|
|
295
|
+
buffer = lines.pop() || "";
|
|
296
|
+
for (const line of lines) {
|
|
297
|
+
if (line.startsWith("data: ")) {
|
|
298
|
+
const eventData = line.slice(6);
|
|
299
|
+
if (eventData === "[DONE]") {
|
|
300
|
+
closeCallback?.();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const chunk = JSON.parse(eventData);
|
|
305
|
+
messageCallback?.(chunk);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (error.name !== "AbortError") {
|
|
313
|
+
errorCallback?.(error);
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
closeCallback?.();
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
onMessage(callback) {
|
|
320
|
+
messageCallback = callback;
|
|
321
|
+
},
|
|
322
|
+
onError(callback) {
|
|
323
|
+
errorCallback = callback;
|
|
324
|
+
},
|
|
325
|
+
onClose(callback) {
|
|
326
|
+
closeCallback = callback;
|
|
327
|
+
},
|
|
328
|
+
close() {
|
|
329
|
+
abortController?.abort();
|
|
330
|
+
eventSource?.close();
|
|
331
|
+
eventSource = null;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function createHTTPStreamAdapter() {
|
|
336
|
+
let abortController = null;
|
|
337
|
+
let messageCallback = null;
|
|
338
|
+
let errorCallback = null;
|
|
339
|
+
let closeCallback = null;
|
|
340
|
+
let currentUrl = "";
|
|
341
|
+
let currentOptions = {};
|
|
342
|
+
return {
|
|
343
|
+
connect(url, options) {
|
|
344
|
+
currentUrl = url;
|
|
345
|
+
currentOptions = options || {};
|
|
346
|
+
abortController = new AbortController();
|
|
347
|
+
if (options?.signal) {
|
|
348
|
+
options.signal.addEventListener("abort", () => {
|
|
349
|
+
abortController?.abort();
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return Promise.resolve();
|
|
353
|
+
},
|
|
354
|
+
async send(data) {
|
|
355
|
+
try {
|
|
356
|
+
const response = await fetch(currentUrl, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: {
|
|
359
|
+
"Content-Type": "application/json",
|
|
360
|
+
Accept: "application/x-ndjson",
|
|
361
|
+
...currentOptions.headers
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify(data),
|
|
364
|
+
signal: abortController?.signal
|
|
365
|
+
});
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
const error = new Error(`HTTP error: ${response.status}`);
|
|
368
|
+
errorCallback?.(error);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const reader = response.body?.getReader();
|
|
372
|
+
if (!reader) {
|
|
373
|
+
errorCallback?.(new Error("No response body"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const decoder = new TextDecoder();
|
|
377
|
+
let buffer = "";
|
|
378
|
+
let reading = true;
|
|
379
|
+
while (reading) {
|
|
380
|
+
const { done, value } = await reader.read();
|
|
381
|
+
if (done) {
|
|
382
|
+
reading = false;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
buffer += decoder.decode(value, { stream: true });
|
|
386
|
+
const lines = buffer.split("\n");
|
|
387
|
+
buffer = lines.pop() || "";
|
|
388
|
+
for (const line of lines) {
|
|
389
|
+
if (line.trim()) {
|
|
390
|
+
try {
|
|
391
|
+
const chunk = JSON.parse(line);
|
|
392
|
+
messageCallback?.(chunk);
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (buffer.trim()) {
|
|
399
|
+
try {
|
|
400
|
+
const chunk = JSON.parse(buffer);
|
|
401
|
+
messageCallback?.(chunk);
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error.name !== "AbortError") {
|
|
407
|
+
errorCallback?.(error);
|
|
408
|
+
}
|
|
409
|
+
} finally {
|
|
410
|
+
closeCallback?.();
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
onMessage(callback) {
|
|
414
|
+
messageCallback = callback;
|
|
415
|
+
},
|
|
416
|
+
onError(callback) {
|
|
417
|
+
errorCallback = callback;
|
|
418
|
+
},
|
|
419
|
+
onClose(callback) {
|
|
420
|
+
closeCallback = callback;
|
|
421
|
+
},
|
|
422
|
+
close() {
|
|
423
|
+
abortController?.abort();
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
async function fetchChat(url, data, options) {
|
|
428
|
+
const response = await fetch(url, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: {
|
|
431
|
+
"Content-Type": "application/json",
|
|
432
|
+
...options?.headers
|
|
433
|
+
},
|
|
434
|
+
body: JSON.stringify({ ...data, stream: false }),
|
|
435
|
+
signal: options?.signal
|
|
436
|
+
});
|
|
437
|
+
if (!response.ok) {
|
|
438
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
439
|
+
}
|
|
440
|
+
const result = await response.json();
|
|
441
|
+
return result.chunks || [result];
|
|
442
|
+
}
|
|
443
|
+
function getAdapter(type) {
|
|
444
|
+
if (typeof type === "object") {
|
|
445
|
+
return type;
|
|
446
|
+
}
|
|
447
|
+
return type === "sse" ? createSSEAdapter() : createHTTPStreamAdapter();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/useChat.ts
|
|
451
|
+
function useChat(config) {
|
|
452
|
+
const {
|
|
453
|
+
endpoint,
|
|
454
|
+
conversationId: initialConversationId,
|
|
455
|
+
agentId,
|
|
456
|
+
initialMessages = [],
|
|
457
|
+
adapter: adapterType = "sse",
|
|
458
|
+
headers = {},
|
|
459
|
+
onMessage,
|
|
460
|
+
onContentUpdate,
|
|
461
|
+
onToolApproval,
|
|
462
|
+
onError,
|
|
463
|
+
onComplete,
|
|
464
|
+
onThinking,
|
|
465
|
+
autoApprove = false,
|
|
466
|
+
maxRetries = 3
|
|
467
|
+
} = config;
|
|
468
|
+
const [messages, setMessages] = useState(initialMessages);
|
|
469
|
+
const [streamingContent, setStreamingContent] = useState("");
|
|
470
|
+
const [thinkingContent, setThinkingContent] = useState("");
|
|
471
|
+
const [activeToolCalls, setActiveToolCalls] = useState([]);
|
|
472
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
473
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
474
|
+
const [error, setError] = useState(null);
|
|
475
|
+
const adapterRef = useRef(getAdapter(adapterType));
|
|
476
|
+
const abortControllerRef = useRef(null);
|
|
477
|
+
const conversationIdRef = useRef(initialConversationId || generateId());
|
|
478
|
+
const pendingApprovalsRef = useRef(
|
|
479
|
+
/* @__PURE__ */ new Map()
|
|
480
|
+
);
|
|
481
|
+
const retryCountRef = useRef(0);
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
return () => {
|
|
484
|
+
abortControllerRef.current?.abort();
|
|
485
|
+
adapterRef.current.close();
|
|
486
|
+
};
|
|
487
|
+
}, []);
|
|
488
|
+
const processChunk = useCallback(
|
|
489
|
+
(chunk) => {
|
|
490
|
+
switch (chunk.type) {
|
|
491
|
+
case "content":
|
|
492
|
+
if (chunk.delta) {
|
|
493
|
+
setStreamingContent((prev) => prev + chunk.content);
|
|
494
|
+
} else {
|
|
495
|
+
setStreamingContent(chunk.content);
|
|
496
|
+
}
|
|
497
|
+
onContentUpdate?.(chunk.content);
|
|
498
|
+
break;
|
|
499
|
+
case "thinking": {
|
|
500
|
+
if (chunk.delta) {
|
|
501
|
+
setThinkingContent((prev) => prev + chunk.content);
|
|
502
|
+
} else {
|
|
503
|
+
setThinkingContent(chunk.content);
|
|
504
|
+
}
|
|
505
|
+
const thinking = {
|
|
506
|
+
type: "thinking",
|
|
507
|
+
content: chunk.content,
|
|
508
|
+
isComplete: false
|
|
509
|
+
};
|
|
510
|
+
onThinking?.(thinking);
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
case "tool_call":
|
|
514
|
+
setActiveToolCalls((prev) => {
|
|
515
|
+
const existing = prev.find((tc) => tc.id === chunk.toolCall.id);
|
|
516
|
+
if (existing) {
|
|
517
|
+
return prev.map(
|
|
518
|
+
(tc) => tc.id === chunk.toolCall.id ? chunk.toolCall : tc
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return [...prev, chunk.toolCall];
|
|
522
|
+
});
|
|
523
|
+
if (chunk.toolCall.needsApproval && !autoApprove) {
|
|
524
|
+
onToolApproval?.(chunk.toolCall);
|
|
525
|
+
} else if (autoApprove && chunk.toolCall.needsApproval) {
|
|
526
|
+
pendingApprovalsRef.current.set(chunk.toolCall.id, {
|
|
527
|
+
toolCallId: chunk.toolCall.id,
|
|
528
|
+
approved: true
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
case "tool_result":
|
|
533
|
+
setActiveToolCalls(
|
|
534
|
+
(prev) => prev.map(
|
|
535
|
+
(tc) => tc.id === chunk.toolCall.id ? { ...chunk.toolCall, state: "complete" } : tc
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
break;
|
|
539
|
+
case "tool_state":
|
|
540
|
+
setActiveToolCalls(
|
|
541
|
+
(prev) => prev.map(
|
|
542
|
+
(tc) => tc.id === chunk.toolCallId ? { ...tc, state: chunk.state } : tc
|
|
543
|
+
)
|
|
544
|
+
);
|
|
545
|
+
break;
|
|
546
|
+
case "done": {
|
|
547
|
+
setIsStreaming(false);
|
|
548
|
+
setIsLoading(false);
|
|
549
|
+
const finalContent = streamingContent;
|
|
550
|
+
const finalThinking = thinkingContent;
|
|
551
|
+
const finalToolCalls = [...activeToolCalls];
|
|
552
|
+
if (finalContent || finalToolCalls.length > 0) {
|
|
553
|
+
const assistantMessage = createMessage("assistant", finalContent, {
|
|
554
|
+
toolCalls: finalToolCalls.length > 0 ? finalToolCalls : void 0,
|
|
555
|
+
thinking: finalThinking ? { type: "thinking", content: finalThinking, isComplete: true } : void 0,
|
|
556
|
+
metadata: chunk.metadata
|
|
557
|
+
});
|
|
558
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
559
|
+
onMessage?.(assistantMessage);
|
|
560
|
+
onComplete?.(assistantMessage);
|
|
561
|
+
}
|
|
562
|
+
setStreamingContent("");
|
|
563
|
+
setThinkingContent("");
|
|
564
|
+
setActiveToolCalls([]);
|
|
565
|
+
retryCountRef.current = 0;
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "error": {
|
|
569
|
+
const err = new Error(chunk.error);
|
|
570
|
+
setError(err);
|
|
571
|
+
setIsStreaming(false);
|
|
572
|
+
setIsLoading(false);
|
|
573
|
+
onError?.(err);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
[
|
|
579
|
+
streamingContent,
|
|
580
|
+
thinkingContent,
|
|
581
|
+
activeToolCalls,
|
|
582
|
+
autoApprove,
|
|
583
|
+
onContentUpdate,
|
|
584
|
+
onThinking,
|
|
585
|
+
onToolApproval,
|
|
586
|
+
onMessage,
|
|
587
|
+
onComplete,
|
|
588
|
+
onError
|
|
589
|
+
]
|
|
590
|
+
);
|
|
591
|
+
const sendMessage = useCallback(
|
|
592
|
+
async (content) => {
|
|
593
|
+
if (!content.trim()) return;
|
|
594
|
+
const userMessage = createMessage("user", content);
|
|
595
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
596
|
+
setError(null);
|
|
597
|
+
setIsLoading(true);
|
|
598
|
+
setIsStreaming(true);
|
|
599
|
+
setStreamingContent("");
|
|
600
|
+
setThinkingContent("");
|
|
601
|
+
setActiveToolCalls([]);
|
|
602
|
+
abortControllerRef.current = new AbortController();
|
|
603
|
+
try {
|
|
604
|
+
const adapter = adapterRef.current;
|
|
605
|
+
await adapter.connect(endpoint, {
|
|
606
|
+
headers,
|
|
607
|
+
signal: abortControllerRef.current.signal
|
|
608
|
+
});
|
|
609
|
+
adapter.onMessage(processChunk);
|
|
610
|
+
adapter.onError((err) => {
|
|
611
|
+
setError(err);
|
|
612
|
+
setIsStreaming(false);
|
|
613
|
+
setIsLoading(false);
|
|
614
|
+
onError?.(err);
|
|
615
|
+
});
|
|
616
|
+
adapter.onClose(() => {
|
|
617
|
+
setIsStreaming(false);
|
|
618
|
+
});
|
|
619
|
+
const allMessages = [...messages, userMessage];
|
|
620
|
+
await adapter.send({
|
|
621
|
+
messages: allMessages,
|
|
622
|
+
conversationId: conversationIdRef.current,
|
|
623
|
+
agentId,
|
|
624
|
+
stream: true,
|
|
625
|
+
toolApprovals: Array.from(pendingApprovalsRef.current.values())
|
|
626
|
+
});
|
|
627
|
+
pendingApprovalsRef.current.clear();
|
|
628
|
+
} catch (err) {
|
|
629
|
+
const error2 = err;
|
|
630
|
+
if (error2.name !== "AbortError") {
|
|
631
|
+
setError(error2);
|
|
632
|
+
onError?.(error2);
|
|
633
|
+
if (retryCountRef.current < maxRetries) {
|
|
634
|
+
retryCountRef.current++;
|
|
635
|
+
setMessages((prev) => prev.slice(0, -1));
|
|
636
|
+
await sendMessage(content);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
setIsStreaming(false);
|
|
641
|
+
setIsLoading(false);
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
[endpoint, headers, agentId, messages, processChunk, onError, maxRetries]
|
|
645
|
+
);
|
|
646
|
+
const approveToolCall = useCallback((toolCallId) => {
|
|
647
|
+
pendingApprovalsRef.current.set(toolCallId, {
|
|
648
|
+
toolCallId,
|
|
649
|
+
approved: true
|
|
650
|
+
});
|
|
651
|
+
setActiveToolCalls(
|
|
652
|
+
(prev) => prev.map(
|
|
653
|
+
(tc) => tc.id === toolCallId ? { ...tc, state: "executing" } : tc
|
|
654
|
+
)
|
|
655
|
+
);
|
|
656
|
+
}, []);
|
|
657
|
+
const denyToolCall = useCallback(
|
|
658
|
+
(toolCallId, reason) => {
|
|
659
|
+
pendingApprovalsRef.current.set(toolCallId, {
|
|
660
|
+
toolCallId,
|
|
661
|
+
approved: false,
|
|
662
|
+
reason
|
|
663
|
+
});
|
|
664
|
+
setActiveToolCalls(
|
|
665
|
+
(prev) => prev.map(
|
|
666
|
+
(tc) => tc.id === toolCallId ? { ...tc, state: "approval-denied" } : tc
|
|
667
|
+
)
|
|
668
|
+
);
|
|
669
|
+
},
|
|
670
|
+
[]
|
|
671
|
+
);
|
|
672
|
+
const stop = useCallback(() => {
|
|
673
|
+
abortControllerRef.current?.abort();
|
|
674
|
+
adapterRef.current.close();
|
|
675
|
+
setIsStreaming(false);
|
|
676
|
+
setIsLoading(false);
|
|
677
|
+
if (streamingContent) {
|
|
678
|
+
const partialMessage = createMessage("assistant", streamingContent, {
|
|
679
|
+
toolCalls: activeToolCalls.length > 0 ? activeToolCalls : void 0,
|
|
680
|
+
thinking: thinkingContent ? { type: "thinking", content: thinkingContent, isComplete: false } : void 0,
|
|
681
|
+
metadata: { finishReason: "stopped" }
|
|
682
|
+
});
|
|
683
|
+
setMessages((prev) => [...prev, partialMessage]);
|
|
684
|
+
}
|
|
685
|
+
setStreamingContent("");
|
|
686
|
+
setThinkingContent("");
|
|
687
|
+
setActiveToolCalls([]);
|
|
688
|
+
}, [streamingContent, thinkingContent, activeToolCalls]);
|
|
689
|
+
const clear = useCallback(() => {
|
|
690
|
+
stop();
|
|
691
|
+
setMessages([]);
|
|
692
|
+
setError(null);
|
|
693
|
+
conversationIdRef.current = generateId();
|
|
694
|
+
}, [stop]);
|
|
695
|
+
const reload = useCallback(async () => {
|
|
696
|
+
const lastUserMessageIndex = messages.map((m) => m.role).lastIndexOf("user");
|
|
697
|
+
if (lastUserMessageIndex === -1) return;
|
|
698
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
699
|
+
setMessages((prev) => prev.slice(0, lastUserMessageIndex));
|
|
700
|
+
await sendMessage(lastUserMessage.content);
|
|
701
|
+
}, [messages, sendMessage]);
|
|
702
|
+
return {
|
|
703
|
+
messages,
|
|
704
|
+
streamingContent,
|
|
705
|
+
thinkingContent,
|
|
706
|
+
activeToolCalls,
|
|
707
|
+
isLoading,
|
|
708
|
+
isStreaming,
|
|
709
|
+
error,
|
|
710
|
+
sendMessage,
|
|
711
|
+
approveToolCall,
|
|
712
|
+
denyToolCall,
|
|
713
|
+
stop,
|
|
714
|
+
clear,
|
|
715
|
+
reload,
|
|
716
|
+
setMessages
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/useAgent.ts
|
|
721
|
+
import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
722
|
+
function useAgent(config) {
|
|
723
|
+
const {
|
|
724
|
+
endpoint,
|
|
725
|
+
agentId,
|
|
726
|
+
headers = {},
|
|
727
|
+
onStart,
|
|
728
|
+
onContentUpdate,
|
|
729
|
+
onToolApproval,
|
|
730
|
+
onError,
|
|
731
|
+
onComplete,
|
|
732
|
+
onThinking
|
|
733
|
+
} = config;
|
|
734
|
+
const [content, setContent] = useState2("");
|
|
735
|
+
const [thinkingContent, setThinkingContent] = useState2("");
|
|
736
|
+
const [activeToolCalls, setActiveToolCalls] = useState2([]);
|
|
737
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
738
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
739
|
+
const [error, setError] = useState2(null);
|
|
740
|
+
const [metadata, setMetadata] = useState2(
|
|
741
|
+
null
|
|
742
|
+
);
|
|
743
|
+
const abortControllerRef = useRef2(null);
|
|
744
|
+
const pendingApprovalsRef = useRef2(
|
|
745
|
+
/* @__PURE__ */ new Map()
|
|
746
|
+
);
|
|
747
|
+
useEffect2(() => {
|
|
748
|
+
return () => {
|
|
749
|
+
abortControllerRef.current?.abort();
|
|
750
|
+
};
|
|
751
|
+
}, []);
|
|
752
|
+
async function* executeStream(input, context) {
|
|
753
|
+
onStart?.();
|
|
754
|
+
setError(null);
|
|
755
|
+
setIsLoading(true);
|
|
756
|
+
setIsStreaming(true);
|
|
757
|
+
setContent("");
|
|
758
|
+
setThinkingContent("");
|
|
759
|
+
setActiveToolCalls([]);
|
|
760
|
+
setMetadata(null);
|
|
761
|
+
abortControllerRef.current = new AbortController();
|
|
762
|
+
try {
|
|
763
|
+
const response = await fetch(endpoint, {
|
|
764
|
+
method: "POST",
|
|
765
|
+
headers: {
|
|
766
|
+
"Content-Type": "application/json",
|
|
767
|
+
Accept: "text/event-stream",
|
|
768
|
+
...headers
|
|
769
|
+
},
|
|
770
|
+
body: JSON.stringify({
|
|
771
|
+
input,
|
|
772
|
+
agentId,
|
|
773
|
+
context,
|
|
774
|
+
stream: true,
|
|
775
|
+
toolApprovals: Array.from(pendingApprovalsRef.current.values())
|
|
776
|
+
}),
|
|
777
|
+
signal: abortControllerRef.current.signal
|
|
778
|
+
});
|
|
779
|
+
if (!response.ok) {
|
|
780
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
781
|
+
}
|
|
782
|
+
const reader = response.body?.getReader();
|
|
783
|
+
if (!reader) {
|
|
784
|
+
throw new Error("No response body");
|
|
785
|
+
}
|
|
786
|
+
const decoder = new TextDecoder();
|
|
787
|
+
let buffer = "";
|
|
788
|
+
let accumulatedContent = "";
|
|
789
|
+
let accumulatedThinking = "";
|
|
790
|
+
let reading = true;
|
|
791
|
+
while (reading) {
|
|
792
|
+
const { done, value } = await reader.read();
|
|
793
|
+
if (done) {
|
|
794
|
+
reading = false;
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
buffer += decoder.decode(value, { stream: true });
|
|
798
|
+
const lines = buffer.split("\n");
|
|
799
|
+
buffer = lines.pop() || "";
|
|
800
|
+
for (const line of lines) {
|
|
801
|
+
if (line.startsWith("data: ")) {
|
|
802
|
+
const data = line.slice(6);
|
|
803
|
+
if (data === "[DONE]") {
|
|
804
|
+
setIsStreaming(false);
|
|
805
|
+
setIsLoading(false);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
const chunk = JSON.parse(data);
|
|
810
|
+
switch (chunk.type) {
|
|
811
|
+
case "content":
|
|
812
|
+
if (chunk.delta) {
|
|
813
|
+
accumulatedContent += chunk.content;
|
|
814
|
+
} else {
|
|
815
|
+
accumulatedContent = chunk.content;
|
|
816
|
+
}
|
|
817
|
+
setContent(accumulatedContent);
|
|
818
|
+
onContentUpdate?.(chunk.content);
|
|
819
|
+
break;
|
|
820
|
+
case "thinking": {
|
|
821
|
+
if (chunk.delta) {
|
|
822
|
+
accumulatedThinking += chunk.content;
|
|
823
|
+
} else {
|
|
824
|
+
accumulatedThinking = chunk.content;
|
|
825
|
+
}
|
|
826
|
+
setThinkingContent(accumulatedThinking);
|
|
827
|
+
const thinking = {
|
|
828
|
+
type: "thinking",
|
|
829
|
+
content: chunk.content,
|
|
830
|
+
isComplete: false
|
|
831
|
+
};
|
|
832
|
+
onThinking?.(thinking);
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case "tool_call":
|
|
836
|
+
setActiveToolCalls((prev) => {
|
|
837
|
+
const existing = prev.find(
|
|
838
|
+
(tc) => tc.id === chunk.toolCall.id
|
|
839
|
+
);
|
|
840
|
+
if (existing) {
|
|
841
|
+
return prev.map(
|
|
842
|
+
(tc) => tc.id === chunk.toolCall.id ? chunk.toolCall : tc
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
return [...prev, chunk.toolCall];
|
|
846
|
+
});
|
|
847
|
+
if (chunk.toolCall.needsApproval) {
|
|
848
|
+
onToolApproval?.(chunk.toolCall);
|
|
849
|
+
}
|
|
850
|
+
break;
|
|
851
|
+
case "tool_result":
|
|
852
|
+
setActiveToolCalls(
|
|
853
|
+
(prev) => prev.map(
|
|
854
|
+
(tc) => tc.id === chunk.toolCall.id ? { ...chunk.toolCall, state: "complete" } : tc
|
|
855
|
+
)
|
|
856
|
+
);
|
|
857
|
+
break;
|
|
858
|
+
case "tool_state":
|
|
859
|
+
setActiveToolCalls(
|
|
860
|
+
(prev) => prev.map(
|
|
861
|
+
(tc) => tc.id === chunk.toolCallId ? { ...tc, state: chunk.state } : tc
|
|
862
|
+
)
|
|
863
|
+
);
|
|
864
|
+
break;
|
|
865
|
+
case "done":
|
|
866
|
+
setMetadata(chunk.metadata || null);
|
|
867
|
+
setIsStreaming(false);
|
|
868
|
+
setIsLoading(false);
|
|
869
|
+
break;
|
|
870
|
+
case "error": {
|
|
871
|
+
const err = new Error(chunk.error);
|
|
872
|
+
setError(err);
|
|
873
|
+
setIsStreaming(false);
|
|
874
|
+
setIsLoading(false);
|
|
875
|
+
onError?.(err);
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
yield chunk;
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
pendingApprovalsRef.current.clear();
|
|
886
|
+
} catch (err) {
|
|
887
|
+
const error2 = err;
|
|
888
|
+
if (error2.name !== "AbortError") {
|
|
889
|
+
setError(error2);
|
|
890
|
+
onError?.(error2);
|
|
891
|
+
}
|
|
892
|
+
setIsStreaming(false);
|
|
893
|
+
setIsLoading(false);
|
|
894
|
+
throw error2;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const execute = useCallback2(
|
|
898
|
+
async (input, context) => {
|
|
899
|
+
onStart?.();
|
|
900
|
+
setError(null);
|
|
901
|
+
setIsLoading(true);
|
|
902
|
+
setContent("");
|
|
903
|
+
setThinkingContent("");
|
|
904
|
+
setActiveToolCalls([]);
|
|
905
|
+
setMetadata(null);
|
|
906
|
+
abortControllerRef.current = new AbortController();
|
|
907
|
+
try {
|
|
908
|
+
const response = await fetch(endpoint, {
|
|
909
|
+
method: "POST",
|
|
910
|
+
headers: {
|
|
911
|
+
"Content-Type": "application/json",
|
|
912
|
+
...headers
|
|
913
|
+
},
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
input,
|
|
916
|
+
agentId,
|
|
917
|
+
context,
|
|
918
|
+
stream: false,
|
|
919
|
+
toolApprovals: Array.from(pendingApprovalsRef.current.values())
|
|
920
|
+
}),
|
|
921
|
+
signal: abortControllerRef.current.signal
|
|
922
|
+
});
|
|
923
|
+
if (!response.ok) {
|
|
924
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
925
|
+
}
|
|
926
|
+
const result = await response.json();
|
|
927
|
+
setContent(result.content);
|
|
928
|
+
setMetadata({
|
|
929
|
+
tokensUsed: result.metadata?.tokensUsed,
|
|
930
|
+
latencyMs: result.metadata?.latencyMs,
|
|
931
|
+
finishReason: result.finishReason
|
|
932
|
+
});
|
|
933
|
+
if (result.toolCalls) {
|
|
934
|
+
setActiveToolCalls(
|
|
935
|
+
result.toolCalls.map((tc) => ({
|
|
936
|
+
...tc,
|
|
937
|
+
state: "complete"
|
|
938
|
+
}))
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
setIsLoading(false);
|
|
942
|
+
onComplete?.(result);
|
|
943
|
+
pendingApprovalsRef.current.clear();
|
|
944
|
+
return result;
|
|
945
|
+
} catch (err) {
|
|
946
|
+
const error2 = err;
|
|
947
|
+
if (error2.name !== "AbortError") {
|
|
948
|
+
setError(error2);
|
|
949
|
+
onError?.(error2);
|
|
950
|
+
}
|
|
951
|
+
setIsLoading(false);
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
[endpoint, headers, agentId, onStart, onComplete, onError]
|
|
956
|
+
);
|
|
957
|
+
const approveToolCall = useCallback2((toolCallId) => {
|
|
958
|
+
pendingApprovalsRef.current.set(toolCallId, {
|
|
959
|
+
toolCallId,
|
|
960
|
+
approved: true
|
|
961
|
+
});
|
|
962
|
+
setActiveToolCalls(
|
|
963
|
+
(prev) => prev.map(
|
|
964
|
+
(tc) => tc.id === toolCallId ? { ...tc, state: "executing" } : tc
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
}, []);
|
|
968
|
+
const denyToolCall = useCallback2(
|
|
969
|
+
(toolCallId, reason) => {
|
|
970
|
+
pendingApprovalsRef.current.set(toolCallId, {
|
|
971
|
+
toolCallId,
|
|
972
|
+
approved: false,
|
|
973
|
+
reason
|
|
974
|
+
});
|
|
975
|
+
setActiveToolCalls(
|
|
976
|
+
(prev) => prev.map(
|
|
977
|
+
(tc) => tc.id === toolCallId ? { ...tc, state: "approval-denied" } : tc
|
|
978
|
+
)
|
|
979
|
+
);
|
|
980
|
+
},
|
|
981
|
+
[]
|
|
982
|
+
);
|
|
983
|
+
const stop = useCallback2(() => {
|
|
984
|
+
abortControllerRef.current?.abort();
|
|
985
|
+
setIsStreaming(false);
|
|
986
|
+
setIsLoading(false);
|
|
987
|
+
}, []);
|
|
988
|
+
const reset = useCallback2(() => {
|
|
989
|
+
stop();
|
|
990
|
+
setContent("");
|
|
991
|
+
setThinkingContent("");
|
|
992
|
+
setActiveToolCalls([]);
|
|
993
|
+
setError(null);
|
|
994
|
+
setMetadata(null);
|
|
995
|
+
pendingApprovalsRef.current.clear();
|
|
996
|
+
}, [stop]);
|
|
997
|
+
return {
|
|
998
|
+
execute,
|
|
999
|
+
executeStream,
|
|
1000
|
+
content,
|
|
1001
|
+
thinkingContent,
|
|
1002
|
+
activeToolCalls,
|
|
1003
|
+
isLoading,
|
|
1004
|
+
isStreaming,
|
|
1005
|
+
error,
|
|
1006
|
+
metadata,
|
|
1007
|
+
approveToolCall,
|
|
1008
|
+
denyToolCall,
|
|
1009
|
+
stop,
|
|
1010
|
+
reset
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
223
1013
|
export {
|
|
224
1014
|
AgentResponse,
|
|
225
1015
|
StreamingResponse,
|
|
1016
|
+
createHTTPStreamAdapter,
|
|
1017
|
+
createMessage,
|
|
1018
|
+
createSSEAdapter,
|
|
1019
|
+
fetchChat,
|
|
1020
|
+
generateId,
|
|
1021
|
+
getAdapter,
|
|
1022
|
+
useAgent,
|
|
1023
|
+
useChat,
|
|
226
1024
|
useFormattedContent,
|
|
227
1025
|
useStreamingContent
|
|
228
1026
|
};
|