bs-agent 0.0.12 → 0.0.16
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/agent-BuA0m5H-.d.cts +305 -0
- package/dist/agent-BuA0m5H-.d.ts +305 -0
- package/dist/core/index.cjs +71 -20
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -120
- package/dist/core/index.d.ts +3 -120
- package/dist/core/index.js +71 -20
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +695 -427
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +28 -28
- package/dist/react/index.d.ts +28 -28
- package/dist/react/index.js +695 -427
- package/dist/react/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/types-DryLPWU9.d.cts +0 -160
- package/dist/types-DryLPWU9.d.ts +0 -160
package/dist/react/index.cjs
CHANGED
|
@@ -91,22 +91,22 @@ var useSessionUtils = (agentId, allSessions, setAllSessions, currentSessionId, s
|
|
|
91
91
|
setAllSessions((prev) => {
|
|
92
92
|
const updatedAgentSessions = { ...prev[agentId] };
|
|
93
93
|
delete updatedAgentSessions[sessionId];
|
|
94
|
+
if (sessionId === currentSessionId) {
|
|
95
|
+
const remainingSessions = Object.values(updatedAgentSessions);
|
|
96
|
+
if (remainingSessions.length > 0) {
|
|
97
|
+
const mostRecent = remainingSessions.sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
98
|
+
setCurrentSessionId(mostRecent.id);
|
|
99
|
+
} else {
|
|
100
|
+
setCurrentSessionId(TEMPORARY_SESSION_ID);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
94
103
|
return {
|
|
95
104
|
...prev,
|
|
96
105
|
[agentId]: updatedAgentSessions
|
|
97
106
|
};
|
|
98
107
|
});
|
|
99
|
-
if (sessionId === currentSessionId) {
|
|
100
|
-
const remainingSessions = Object.values(agentSessions).filter((s) => s.id !== sessionId);
|
|
101
|
-
if (remainingSessions.length > 0) {
|
|
102
|
-
const mostRecent = remainingSessions.sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
103
|
-
setCurrentSessionId(mostRecent.id);
|
|
104
|
-
} else {
|
|
105
|
-
setCurrentSessionId(TEMPORARY_SESSION_ID);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
108
|
},
|
|
109
|
-
[agentId, currentSessionId,
|
|
109
|
+
[agentId, currentSessionId, setAllSessions, setCurrentSessionId]
|
|
110
110
|
);
|
|
111
111
|
const sessionsList = (0, import_react.useMemo)(
|
|
112
112
|
() => Object.values(agentSessions).sort((a, b) => b.updatedAt - a.updatedAt),
|
|
@@ -312,15 +312,13 @@ function useClientToolHelpers(agentId, toolContext) {
|
|
|
312
312
|
const tools = toolContext.getToolsForAgent(agentId);
|
|
313
313
|
const map = /* @__PURE__ */ new Map();
|
|
314
314
|
for (const tool of tools) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
});
|
|
323
|
-
}
|
|
315
|
+
map.set(tool.name, {
|
|
316
|
+
name: tool.name,
|
|
317
|
+
description: tool.description,
|
|
318
|
+
parameters: tool.parameters,
|
|
319
|
+
await: tool.await,
|
|
320
|
+
handler: tool.handler
|
|
321
|
+
});
|
|
324
322
|
}
|
|
325
323
|
return map;
|
|
326
324
|
}, [agentId, toolContext]);
|
|
@@ -337,233 +335,6 @@ function useClientToolHelpers(agentId, toolContext) {
|
|
|
337
335
|
return { getClientToolsMap, getClientToolDefs };
|
|
338
336
|
}
|
|
339
337
|
|
|
340
|
-
// src/core/stream.ts
|
|
341
|
-
function tryParseJSON2(value) {
|
|
342
|
-
if (typeof value === "string") {
|
|
343
|
-
try {
|
|
344
|
-
return JSON.parse(value);
|
|
345
|
-
} catch {
|
|
346
|
-
return value;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return value;
|
|
350
|
-
}
|
|
351
|
-
var FatalStreamError = class extends Error {
|
|
352
|
-
constructor(message, statusCode) {
|
|
353
|
-
super(message);
|
|
354
|
-
this.statusCode = statusCode;
|
|
355
|
-
this.name = "FatalStreamError";
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
async function executeStream(options) {
|
|
359
|
-
const {
|
|
360
|
-
url,
|
|
361
|
-
body,
|
|
362
|
-
headers,
|
|
363
|
-
callbacks,
|
|
364
|
-
clientTools,
|
|
365
|
-
signal,
|
|
366
|
-
onSessionId,
|
|
367
|
-
onPaused,
|
|
368
|
-
onAutoResume,
|
|
369
|
-
onResponse
|
|
370
|
-
} = options;
|
|
371
|
-
let fullText = "";
|
|
372
|
-
const pendingAutoResumes = [];
|
|
373
|
-
const response = await fetch(url, {
|
|
374
|
-
method: "POST",
|
|
375
|
-
headers: {
|
|
376
|
-
"Content-Type": "application/json",
|
|
377
|
-
Accept: "text/event-stream",
|
|
378
|
-
...headers
|
|
379
|
-
},
|
|
380
|
-
body: JSON.stringify(body),
|
|
381
|
-
signal
|
|
382
|
-
});
|
|
383
|
-
const sessionId = response.headers.get("X-BuildShip-Agent-Session-ID");
|
|
384
|
-
if (sessionId && onSessionId) {
|
|
385
|
-
const sessionName = response.headers.get("X-BuildShip-Agent-Session-Name") || void 0;
|
|
386
|
-
onSessionId(sessionId, sessionName);
|
|
387
|
-
}
|
|
388
|
-
onResponse?.(response);
|
|
389
|
-
if (!response.ok) {
|
|
390
|
-
let errorBody = "";
|
|
391
|
-
try {
|
|
392
|
-
errorBody = await response.text();
|
|
393
|
-
} catch {
|
|
394
|
-
}
|
|
395
|
-
const error = new FatalStreamError(
|
|
396
|
-
`HTTP ${response.status}: ${errorBody || response.statusText}`,
|
|
397
|
-
response.status
|
|
398
|
-
);
|
|
399
|
-
callbacks.onError?.(error);
|
|
400
|
-
throw error;
|
|
401
|
-
}
|
|
402
|
-
if (!response.body) {
|
|
403
|
-
callbacks.onComplete?.(fullText);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const reader = response.body.getReader();
|
|
407
|
-
const decoder = new TextDecoder();
|
|
408
|
-
let buffer = "";
|
|
409
|
-
try {
|
|
410
|
-
while (true) {
|
|
411
|
-
const { done, value } = await reader.read();
|
|
412
|
-
if (done) break;
|
|
413
|
-
buffer += decoder.decode(value, { stream: true });
|
|
414
|
-
const parts = buffer.split("\n\n");
|
|
415
|
-
buffer = parts.pop() || "";
|
|
416
|
-
for (const part of parts) {
|
|
417
|
-
const trimmedPart = part.trim();
|
|
418
|
-
if (!trimmedPart) continue;
|
|
419
|
-
let jsonStr = "";
|
|
420
|
-
for (const line of trimmedPart.split("\n")) {
|
|
421
|
-
if (line.startsWith("data: ")) {
|
|
422
|
-
jsonStr += line.slice(6);
|
|
423
|
-
} else if (line.startsWith("data:")) {
|
|
424
|
-
jsonStr += line.slice(5);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
jsonStr = jsonStr.trim();
|
|
428
|
-
if (!jsonStr) continue;
|
|
429
|
-
let raw;
|
|
430
|
-
try {
|
|
431
|
-
raw = JSON.parse(jsonStr);
|
|
432
|
-
} catch {
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
const streamEvent = normalizeEvent(raw);
|
|
436
|
-
handleEvent(
|
|
437
|
-
streamEvent,
|
|
438
|
-
fullText,
|
|
439
|
-
callbacks,
|
|
440
|
-
clientTools,
|
|
441
|
-
onPaused,
|
|
442
|
-
onAutoResume,
|
|
443
|
-
pendingAutoResumes
|
|
444
|
-
);
|
|
445
|
-
if (streamEvent.type === "text_delta") {
|
|
446
|
-
fullText += streamEvent.data;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (buffer.trim()) {
|
|
451
|
-
let jsonStr = "";
|
|
452
|
-
for (const line of buffer.trim().split("\n")) {
|
|
453
|
-
if (line.startsWith("data: ")) {
|
|
454
|
-
jsonStr += line.slice(6);
|
|
455
|
-
} else if (line.startsWith("data:")) {
|
|
456
|
-
jsonStr += line.slice(5);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
jsonStr = jsonStr.trim();
|
|
460
|
-
if (jsonStr) {
|
|
461
|
-
try {
|
|
462
|
-
const raw = JSON.parse(jsonStr);
|
|
463
|
-
const streamEvent = normalizeEvent(raw);
|
|
464
|
-
handleEvent(
|
|
465
|
-
streamEvent,
|
|
466
|
-
fullText,
|
|
467
|
-
callbacks,
|
|
468
|
-
clientTools,
|
|
469
|
-
onPaused,
|
|
470
|
-
onAutoResume,
|
|
471
|
-
pendingAutoResumes
|
|
472
|
-
);
|
|
473
|
-
if (streamEvent.type === "text_delta") {
|
|
474
|
-
fullText += streamEvent.data;
|
|
475
|
-
}
|
|
476
|
-
} catch {
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} catch (err) {
|
|
481
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
482
|
-
throw err;
|
|
483
|
-
}
|
|
484
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
485
|
-
callbacks.onError?.(error);
|
|
486
|
-
throw error;
|
|
487
|
-
}
|
|
488
|
-
if (pendingAutoResumes.length > 0) {
|
|
489
|
-
await Promise.allSettled(pendingAutoResumes);
|
|
490
|
-
}
|
|
491
|
-
callbacks.onComplete?.(fullText);
|
|
492
|
-
}
|
|
493
|
-
function normalizeEvent(raw) {
|
|
494
|
-
const typeMap = {
|
|
495
|
-
llm_text_delta: "text_delta",
|
|
496
|
-
llm_reasoning_delta: "reasoning_delta"
|
|
497
|
-
};
|
|
498
|
-
if (raw && typeof raw.type === "string" && typeMap[raw.type]) {
|
|
499
|
-
raw.type = typeMap[raw.type];
|
|
500
|
-
}
|
|
501
|
-
return raw;
|
|
502
|
-
}
|
|
503
|
-
function handleEvent(event, _fullText, callbacks, clientTools, onPaused, onAutoResume, pendingAutoResumes) {
|
|
504
|
-
callbacks.onEvent?.(event);
|
|
505
|
-
switch (event.type) {
|
|
506
|
-
case "text_delta": {
|
|
507
|
-
callbacks.onText?.(event.data);
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
case "reasoning_delta": {
|
|
511
|
-
callbacks.onReasoning?.(event.data.delta, event.data.index);
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
case "agent_handoff": {
|
|
515
|
-
callbacks.onAgentHandoff?.(event.data.agentName);
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
case "tool_call_start": {
|
|
519
|
-
const { callId, toolName, toolType, inputs: rawInputs, paused } = event.data;
|
|
520
|
-
const inputs = tryParseJSON2(rawInputs);
|
|
521
|
-
callbacks.onToolStart?.(toolName, toolType);
|
|
522
|
-
if (toolType === "client") {
|
|
523
|
-
const tool = clientTools.get(toolName);
|
|
524
|
-
if (tool) {
|
|
525
|
-
if (paused && tool.handler) {
|
|
526
|
-
const handlerPromise = (async () => {
|
|
527
|
-
try {
|
|
528
|
-
const result = await tool.handler(inputs);
|
|
529
|
-
await onAutoResume?.(callId, result);
|
|
530
|
-
} catch {
|
|
531
|
-
callbacks.onPaused?.(toolName, inputs);
|
|
532
|
-
onPaused?.({ callId, toolName, args: inputs });
|
|
533
|
-
}
|
|
534
|
-
})();
|
|
535
|
-
pendingAutoResumes.push(handlerPromise);
|
|
536
|
-
} else if (paused) {
|
|
537
|
-
callbacks.onPaused?.(toolName, inputs);
|
|
538
|
-
onPaused?.({ callId, toolName, args: inputs });
|
|
539
|
-
} else if (tool.handler) {
|
|
540
|
-
try {
|
|
541
|
-
tool.handler(inputs);
|
|
542
|
-
} catch {
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
} else if (paused) {
|
|
546
|
-
callbacks.onPaused?.(toolName, inputs);
|
|
547
|
-
onPaused?.({ callId, toolName, args: inputs });
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
case "tool_call_end": {
|
|
553
|
-
const { toolName, result, error } = event.data;
|
|
554
|
-
callbacks.onToolEnd?.(toolName, result, error);
|
|
555
|
-
break;
|
|
556
|
-
}
|
|
557
|
-
case "run_error": {
|
|
558
|
-
const { message, code } = event.data;
|
|
559
|
-
const error = new Error(message);
|
|
560
|
-
if (code != null) error.code = code;
|
|
561
|
-
callbacks.onError?.(error);
|
|
562
|
-
break;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
338
|
// src/react/utils/message.ts
|
|
568
339
|
function updateAgentMessageParts(parts, newPart) {
|
|
569
340
|
const last = parts[parts.length - 1];
|
|
@@ -583,7 +354,15 @@ function updateAgentMessageParts(parts, newPart) {
|
|
|
583
354
|
|
|
584
355
|
// src/react/stream-callbacks.ts
|
|
585
356
|
function buildStreamCallbacks(deps, debugKey) {
|
|
586
|
-
const {
|
|
357
|
+
const {
|
|
358
|
+
setMessages,
|
|
359
|
+
setInProgress,
|
|
360
|
+
syncSessionRef,
|
|
361
|
+
messagesRef,
|
|
362
|
+
toolContext,
|
|
363
|
+
agentId,
|
|
364
|
+
textDeltaModifier
|
|
365
|
+
} = deps;
|
|
587
366
|
return {
|
|
588
367
|
onComplete: () => {
|
|
589
368
|
console.log("Agent closed");
|
|
@@ -601,7 +380,7 @@ function buildStreamCallbacks(deps, debugKey) {
|
|
|
601
380
|
},
|
|
602
381
|
onEvent: (event) => {
|
|
603
382
|
if (event.type === "text_delta") {
|
|
604
|
-
handleTextDelta(event, setMessages, syncSessionRef);
|
|
383
|
+
handleTextDelta(event, setMessages, syncSessionRef, textDeltaModifier, agentId);
|
|
605
384
|
} else if (event.type === "tool_call_start" && event.data.toolType === "client") {
|
|
606
385
|
handleClientToolCall(event, setMessages, syncSessionRef, toolContext, agentId);
|
|
607
386
|
if (debugKey) {
|
|
@@ -631,82 +410,21 @@ function buildStreamCallbacks(deps, debugKey) {
|
|
|
631
410
|
}
|
|
632
411
|
};
|
|
633
412
|
}
|
|
634
|
-
|
|
635
|
-
var DEFAULT_SESSION_NAME2 = "New Chat";
|
|
636
|
-
async function executeAgentStream(deps, body, headers, callbacks, debugKey) {
|
|
637
|
-
const {
|
|
638
|
-
agentUrl,
|
|
639
|
-
signal,
|
|
640
|
-
currentSessionId,
|
|
641
|
-
lastRequestContextRef,
|
|
642
|
-
messagesRef,
|
|
643
|
-
createSessionFromResponse,
|
|
644
|
-
setMessages,
|
|
645
|
-
syncSessionRef,
|
|
646
|
-
runAgentRef
|
|
647
|
-
} = deps;
|
|
648
|
-
await executeStream({
|
|
649
|
-
url: agentUrl,
|
|
650
|
-
body,
|
|
651
|
-
headers,
|
|
652
|
-
callbacks,
|
|
653
|
-
clientTools: deps.getClientToolsMap(),
|
|
654
|
-
signal,
|
|
655
|
-
onSessionId: (sessionId, sessionName) => {
|
|
656
|
-
lastRequestContextRef.current.sessionId = sessionId;
|
|
657
|
-
if (!currentSessionId || currentSessionId === TEMPORARY_SESSION_ID2) {
|
|
658
|
-
createSessionFromResponse(
|
|
659
|
-
sessionId,
|
|
660
|
-
sessionName || DEFAULT_SESSION_NAME2,
|
|
661
|
-
messagesRef.current
|
|
662
|
-
);
|
|
663
|
-
deps.setCurrentSessionId(sessionId);
|
|
664
|
-
}
|
|
665
|
-
},
|
|
666
|
-
// Session creation is handled by onSessionId above.
|
|
667
|
-
onAutoResume: async (callId, result) => {
|
|
668
|
-
setMessages((prev) => {
|
|
669
|
-
const updatedMessages = prev.map((msg) => {
|
|
670
|
-
if (msg.parts) {
|
|
671
|
-
const updatedParts = msg.parts.map((part) => {
|
|
672
|
-
if (part.type === "widget" && part.callId === callId) {
|
|
673
|
-
return { ...part, status: "submitted", result };
|
|
674
|
-
}
|
|
675
|
-
return part;
|
|
676
|
-
});
|
|
677
|
-
return { ...msg, parts: updatedParts };
|
|
678
|
-
}
|
|
679
|
-
return msg;
|
|
680
|
-
});
|
|
681
|
-
if (syncSessionRef.current) {
|
|
682
|
-
syncSessionRef.current(updatedMessages);
|
|
683
|
-
}
|
|
684
|
-
return updatedMessages;
|
|
685
|
-
});
|
|
686
|
-
if (debugKey) {
|
|
687
|
-
deps.debugHandlers.handleStreamEvent({
|
|
688
|
-
type: "tool_call_end",
|
|
689
|
-
data: { callId, toolName: "", toolType: "client", result },
|
|
690
|
-
meta: { executionId: debugKey, sequence: 0 }
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
try {
|
|
694
|
-
await runAgentRef.current?.(void 0, {
|
|
695
|
-
resumeToolCallId: callId,
|
|
696
|
-
resumeToolResult: result
|
|
697
|
-
});
|
|
698
|
-
} catch (error) {
|
|
699
|
-
console.error("Auto-resume failed", error);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
function handleTextDelta(event, setMessages, syncSessionRef) {
|
|
413
|
+
function handleTextDelta(event, setMessages, syncSessionRef, modifier, agentId) {
|
|
705
414
|
const sequence = event.meta.sequence;
|
|
706
|
-
const
|
|
415
|
+
const originalText = event.data;
|
|
707
416
|
const eventExecutionId = event.meta.executionId;
|
|
708
417
|
setMessages((prev) => {
|
|
709
418
|
const lastMessage = prev[prev.length - 1];
|
|
419
|
+
let text = originalText;
|
|
420
|
+
if (modifier) {
|
|
421
|
+
const currentFullText = lastMessage?.role === "agent" ? lastMessage.content : "";
|
|
422
|
+
text = modifier(originalText, currentFullText, {
|
|
423
|
+
executionId: eventExecutionId,
|
|
424
|
+
sequence,
|
|
425
|
+
agentId: event.meta.agentId || agentId
|
|
426
|
+
});
|
|
427
|
+
}
|
|
710
428
|
const newPart = {
|
|
711
429
|
type: "text",
|
|
712
430
|
text,
|
|
@@ -774,7 +492,8 @@ function handleClientToolCall(event, setMessages, syncSessionRef, toolContext, a
|
|
|
774
492
|
}
|
|
775
493
|
|
|
776
494
|
// src/react/use-agent.ts
|
|
777
|
-
function useAgent(
|
|
495
|
+
function useAgent(agent, options) {
|
|
496
|
+
const agentId = agent._agentId;
|
|
778
497
|
const { allSessions, setAllSessions, debugData, setDebugData } = useAgentGlobalState();
|
|
779
498
|
const toolContext = (0, import_react3.useContext)(AgentToolContext);
|
|
780
499
|
const [inProgress, setInProgress] = (0, import_react3.useState)(false);
|
|
@@ -792,7 +511,7 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
792
511
|
(0, import_react3.useEffect)(() => {
|
|
793
512
|
const initialSessionId = sessionUtils.getInitialSessionId();
|
|
794
513
|
setCurrentSessionId(initialSessionId);
|
|
795
|
-
}, []);
|
|
514
|
+
}, [agentId]);
|
|
796
515
|
const debugHandlers = (0, import_react3.useMemo)(() => createDebugHandlers(setDebugData), [setDebugData]);
|
|
797
516
|
(0, import_react3.useEffect)(() => {
|
|
798
517
|
messagesRef.current = messages;
|
|
@@ -805,82 +524,90 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
805
524
|
} else {
|
|
806
525
|
setMessages([]);
|
|
807
526
|
}
|
|
808
|
-
}, [currentSessionId, agentId]);
|
|
527
|
+
}, [currentSessionId, agentId, sessionUtils.agentSessions, inProgress]);
|
|
809
528
|
(0, import_react3.useEffect)(() => {
|
|
529
|
+
const syncRef = sessionUtils.syncSessionRef;
|
|
530
|
+
const msgRef = messagesRef;
|
|
810
531
|
return () => {
|
|
811
|
-
if (
|
|
812
|
-
|
|
532
|
+
if (msgRef.current.length > 0 && syncRef.current) {
|
|
533
|
+
syncRef.current();
|
|
813
534
|
}
|
|
814
535
|
};
|
|
815
|
-
}, []);
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
536
|
+
}, [sessionUtils.syncSessionRef]);
|
|
537
|
+
const optionsRef = (0, import_react3.useRef)(options);
|
|
538
|
+
(0, import_react3.useEffect)(() => {
|
|
539
|
+
optionsRef.current = options;
|
|
540
|
+
}, [options]);
|
|
541
|
+
const lastRunOptionsRef = (0, import_react3.useRef)({});
|
|
542
|
+
const { getClientToolsMap } = useClientToolHelpers(agentId, toolContext);
|
|
543
|
+
const activeSessionRef = (0, import_react3.useRef)(null);
|
|
820
544
|
const runAgent = (0, import_react3.useCallback)(
|
|
821
545
|
async (input, runOptions) => {
|
|
822
|
-
const
|
|
823
|
-
lastRequestContextRef.current = {
|
|
824
|
-
additionalHeaders: runOptions?.additionalHeaders ?? lastRequestContextRef.current.additionalHeaders,
|
|
825
|
-
additionalBody: runOptions?.additionalBody ?? lastRequestContextRef.current.additionalBody,
|
|
826
|
-
sessionId: realSessionId
|
|
827
|
-
};
|
|
546
|
+
const isNewSession = !currentSessionId || currentSessionId === TEMPORARY_SESSION_ID;
|
|
828
547
|
setInProgress(true);
|
|
829
|
-
if (!runOptions?.resumeToolCallId && controller.current) {
|
|
830
|
-
controller.current.abort();
|
|
831
|
-
}
|
|
832
|
-
controller.current = new AbortController();
|
|
833
|
-
const headers = {
|
|
834
|
-
...lastRequestContextRef.current.additionalHeaders || {},
|
|
835
|
-
...accessKey ? { Authorization: `Bearer ${accessKey}` } : {}
|
|
836
|
-
};
|
|
837
|
-
if (realSessionId) {
|
|
838
|
-
headers["x-buildship-agent-session-id"] = realSessionId;
|
|
839
|
-
}
|
|
840
|
-
const body = {
|
|
841
|
-
stream: true,
|
|
842
|
-
...lastRequestContextRef.current.additionalBody || {}
|
|
843
|
-
};
|
|
844
|
-
if (input !== void 0) {
|
|
845
|
-
body.input = input;
|
|
846
|
-
}
|
|
847
|
-
if (runOptions?.context) {
|
|
848
|
-
Object.assign(body, { context: runOptions.context });
|
|
849
|
-
}
|
|
850
|
-
if (runOptions?.resumeToolCallId) {
|
|
851
|
-
body.toolCallResult = {
|
|
852
|
-
callId: runOptions.resumeToolCallId,
|
|
853
|
-
result: runOptions.resumeToolResult
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
const registeredToolDefs = getClientToolDefs() || [];
|
|
857
|
-
const legacyToolDefs = runOptions?.clientTools || [];
|
|
858
|
-
const allToolDefs = [...registeredToolDefs, ...legacyToolDefs];
|
|
859
|
-
if (allToolDefs.length > 0) {
|
|
860
|
-
body.clientTools = allToolDefs;
|
|
861
|
-
}
|
|
862
548
|
const debugKey = runOptions?.optimisticExecutionId || messagesRef.current.findLast((m) => m.role === "user")?.executionId;
|
|
863
549
|
const deps = {
|
|
864
|
-
agentUrl,
|
|
865
550
|
agentId,
|
|
866
|
-
accessKey,
|
|
867
551
|
currentSessionId,
|
|
868
552
|
messagesRef,
|
|
869
553
|
setMessages,
|
|
870
554
|
setInProgress,
|
|
871
|
-
setCurrentSessionId,
|
|
872
555
|
syncSessionRef: sessionUtils.syncSessionRef,
|
|
873
|
-
createSessionFromResponse: sessionUtils.createSessionFromResponse,
|
|
874
556
|
debugHandlers,
|
|
875
557
|
toolContext,
|
|
876
|
-
|
|
877
|
-
getClientToolsMap,
|
|
878
|
-
signal: controller.current.signal,
|
|
879
|
-
runAgentRef
|
|
558
|
+
textDeltaModifier: optionsRef.current?.textDeltaModifier
|
|
880
559
|
};
|
|
881
560
|
const callbacks = buildStreamCallbacks(deps, debugKey);
|
|
561
|
+
const executeOptions = {
|
|
562
|
+
context: runOptions?.context,
|
|
563
|
+
headers: runOptions?.additionalHeaders,
|
|
564
|
+
body: runOptions?.additionalBody
|
|
565
|
+
};
|
|
566
|
+
const toolsMap = getClientToolsMap();
|
|
567
|
+
for (const tool of toolsMap.values()) {
|
|
568
|
+
agent.registerClientTool(tool);
|
|
569
|
+
}
|
|
570
|
+
if (runOptions?.clientTools && runOptions.clientTools.length > 0) {
|
|
571
|
+
executeOptions.body = {
|
|
572
|
+
...executeOptions.body || {},
|
|
573
|
+
clientTools: [
|
|
574
|
+
...executeOptions.body?.clientTools || [],
|
|
575
|
+
...runOptions.clientTools
|
|
576
|
+
]
|
|
577
|
+
};
|
|
578
|
+
}
|
|
882
579
|
try {
|
|
883
|
-
|
|
580
|
+
if (runOptions?.resumeToolCallId) {
|
|
581
|
+
if (isNewSession) throw new Error("Cannot resume a tool call on a temporary session.");
|
|
582
|
+
const session = agent.session(currentSessionId);
|
|
583
|
+
activeSessionRef.current = session;
|
|
584
|
+
await session.resumeWithCallId(
|
|
585
|
+
runOptions.resumeToolCallId,
|
|
586
|
+
runOptions.resumeToolResult,
|
|
587
|
+
callbacks,
|
|
588
|
+
executeOptions
|
|
589
|
+
);
|
|
590
|
+
} else {
|
|
591
|
+
if (isNewSession) {
|
|
592
|
+
const extendedOptions = {
|
|
593
|
+
...executeOptions,
|
|
594
|
+
onSessionId: (newSessionId, sessionName) => {
|
|
595
|
+
sessionUtils.createSessionFromResponse(
|
|
596
|
+
newSessionId,
|
|
597
|
+
sessionName || DEFAULT_SESSION_NAME,
|
|
598
|
+
messagesRef.current
|
|
599
|
+
);
|
|
600
|
+
setCurrentSessionId(newSessionId);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const session = await agent.execute(input || "", callbacks, extendedOptions);
|
|
604
|
+
activeSessionRef.current = session;
|
|
605
|
+
} else {
|
|
606
|
+
const session = agent.session(currentSessionId);
|
|
607
|
+
activeSessionRef.current = session;
|
|
608
|
+
await session.execute(input || "", callbacks, executeOptions);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
884
611
|
} catch (error) {
|
|
885
612
|
console.log("Agent execution failed", error);
|
|
886
613
|
setInProgress(false);
|
|
@@ -888,32 +615,20 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
888
615
|
sessionUtils.syncSessionRef.current(messagesRef.current);
|
|
889
616
|
}
|
|
890
617
|
throw error;
|
|
618
|
+
} finally {
|
|
619
|
+
activeSessionRef.current = null;
|
|
891
620
|
}
|
|
892
621
|
},
|
|
893
|
-
[
|
|
894
|
-
agentUrl,
|
|
895
|
-
accessKey,
|
|
896
|
-
currentSessionId,
|
|
897
|
-
sessionUtils.syncSessionRef,
|
|
898
|
-
sessionUtils.createSessionFromResponse,
|
|
899
|
-
debugHandlers,
|
|
900
|
-
getClientToolsMap,
|
|
901
|
-
getClientToolDefs,
|
|
902
|
-
agentId,
|
|
903
|
-
toolContext
|
|
904
|
-
]
|
|
622
|
+
[currentSessionId, sessionUtils, debugHandlers, agentId, toolContext, agent, getClientToolsMap]
|
|
905
623
|
);
|
|
906
|
-
(0, import_react3.useEffect)(() => {
|
|
907
|
-
runAgentRef.current = runAgent;
|
|
908
|
-
}, [runAgent]);
|
|
909
624
|
const handleSend = (0, import_react3.useCallback)(
|
|
910
|
-
async (input,
|
|
625
|
+
async (input, options2) => {
|
|
911
626
|
const userMessage = {
|
|
912
627
|
role: "user",
|
|
913
628
|
content: input,
|
|
914
629
|
executionId: Date.now().toString()
|
|
915
630
|
};
|
|
916
|
-
if (!
|
|
631
|
+
if (!options2?.skipUserMessage) {
|
|
917
632
|
setMessages((prev) => {
|
|
918
633
|
const updatedMessages = [...prev, userMessage];
|
|
919
634
|
if (sessionUtils.syncSessionRef.current) {
|
|
@@ -922,14 +637,19 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
922
637
|
return updatedMessages;
|
|
923
638
|
});
|
|
924
639
|
}
|
|
925
|
-
const effectiveExecutionId =
|
|
640
|
+
const effectiveExecutionId = options2?.skipUserMessage ? messagesRef.current.findLast((m) => m.role === "user")?.executionId ?? userMessage.executionId : userMessage.executionId;
|
|
641
|
+
lastRunOptionsRef.current = {
|
|
642
|
+
context: options2?.context,
|
|
643
|
+
additionalHeaders: options2?.additionalHeaders,
|
|
644
|
+
additionalBody: options2?.additionalBody
|
|
645
|
+
};
|
|
926
646
|
try {
|
|
927
647
|
await runAgent(input, {
|
|
928
|
-
...
|
|
648
|
+
...options2,
|
|
929
649
|
optimisticExecutionId: effectiveExecutionId
|
|
930
650
|
});
|
|
931
651
|
} catch (error) {
|
|
932
|
-
if (!
|
|
652
|
+
if (!options2?.skipUserMessage) {
|
|
933
653
|
setMessages((prev) => {
|
|
934
654
|
const updatedMessages = prev.some((m) => m === userMessage) ? prev : [...prev, userMessage];
|
|
935
655
|
if (sessionUtils.syncSessionRef.current) {
|
|
@@ -973,7 +693,8 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
973
693
|
}
|
|
974
694
|
await runAgent(void 0, {
|
|
975
695
|
resumeToolCallId: callId,
|
|
976
|
-
resumeToolResult: result
|
|
696
|
+
resumeToolResult: result,
|
|
697
|
+
...lastRunOptionsRef.current
|
|
977
698
|
});
|
|
978
699
|
},
|
|
979
700
|
[runAgent, sessionUtils.syncSessionRef, debugHandlers]
|
|
@@ -996,8 +717,8 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
996
717
|
[sessionUtils.syncSessionRef]
|
|
997
718
|
);
|
|
998
719
|
const abort = (0, import_react3.useCallback)(() => {
|
|
999
|
-
if (
|
|
1000
|
-
|
|
720
|
+
if (activeSessionRef.current) {
|
|
721
|
+
activeSessionRef.current.abort();
|
|
1001
722
|
}
|
|
1002
723
|
setInProgress(false);
|
|
1003
724
|
if (sessionUtils.syncSessionRef.current) {
|
|
@@ -1019,6 +740,536 @@ function useAgent(agentId, agentUrl, accessKey) {
|
|
|
1019
740
|
};
|
|
1020
741
|
}
|
|
1021
742
|
|
|
743
|
+
// src/core/stream.ts
|
|
744
|
+
function tryParseJSON2(value) {
|
|
745
|
+
if (typeof value === "string") {
|
|
746
|
+
try {
|
|
747
|
+
return JSON.parse(value);
|
|
748
|
+
} catch {
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return value;
|
|
753
|
+
}
|
|
754
|
+
var FatalStreamError = class extends Error {
|
|
755
|
+
constructor(message, statusCode) {
|
|
756
|
+
super(message);
|
|
757
|
+
this.statusCode = statusCode;
|
|
758
|
+
this.name = "FatalStreamError";
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
async function executeStream(options) {
|
|
762
|
+
const {
|
|
763
|
+
url,
|
|
764
|
+
body,
|
|
765
|
+
headers,
|
|
766
|
+
callbacks,
|
|
767
|
+
clientTools,
|
|
768
|
+
signal,
|
|
769
|
+
onSessionId,
|
|
770
|
+
onPaused,
|
|
771
|
+
onAutoResume,
|
|
772
|
+
onResponse
|
|
773
|
+
} = options;
|
|
774
|
+
let fullText = "";
|
|
775
|
+
const pendingAutoResumes = [];
|
|
776
|
+
const response = await fetch(url, {
|
|
777
|
+
method: "POST",
|
|
778
|
+
headers: {
|
|
779
|
+
"Content-Type": "application/json",
|
|
780
|
+
Accept: "text/event-stream",
|
|
781
|
+
...headers
|
|
782
|
+
},
|
|
783
|
+
body: JSON.stringify(body),
|
|
784
|
+
signal
|
|
785
|
+
});
|
|
786
|
+
const sessionId = response.headers.get("X-BuildShip-Agent-Session-ID");
|
|
787
|
+
if (sessionId && onSessionId) {
|
|
788
|
+
const sessionName = response.headers.get("X-BuildShip-Agent-Session-Name") || void 0;
|
|
789
|
+
onSessionId(sessionId, sessionName);
|
|
790
|
+
}
|
|
791
|
+
onResponse?.(response);
|
|
792
|
+
if (!response.ok) {
|
|
793
|
+
let errorBody = "";
|
|
794
|
+
try {
|
|
795
|
+
errorBody = await response.text();
|
|
796
|
+
} catch {
|
|
797
|
+
}
|
|
798
|
+
const error = new FatalStreamError(
|
|
799
|
+
`HTTP ${response.status}: ${errorBody || response.statusText}`,
|
|
800
|
+
response.status
|
|
801
|
+
);
|
|
802
|
+
callbacks.onError?.(error);
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
if (!response.body) {
|
|
806
|
+
callbacks.onComplete?.(fullText);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const reader = response.body.getReader();
|
|
810
|
+
const decoder = new TextDecoder();
|
|
811
|
+
let buffer = "";
|
|
812
|
+
try {
|
|
813
|
+
while (true) {
|
|
814
|
+
const { done, value } = await reader.read();
|
|
815
|
+
if (done) break;
|
|
816
|
+
buffer += decoder.decode(value, { stream: true });
|
|
817
|
+
const parts = buffer.split("\n\n");
|
|
818
|
+
buffer = parts.pop() || "";
|
|
819
|
+
for (const part of parts) {
|
|
820
|
+
const trimmedPart = part.trim();
|
|
821
|
+
if (!trimmedPart) continue;
|
|
822
|
+
let jsonStr = "";
|
|
823
|
+
for (const line of trimmedPart.split("\n")) {
|
|
824
|
+
if (line.startsWith("data: ")) {
|
|
825
|
+
jsonStr += line.slice(6);
|
|
826
|
+
} else if (line.startsWith("data:")) {
|
|
827
|
+
jsonStr += line.slice(5);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
jsonStr = jsonStr.trim();
|
|
831
|
+
if (!jsonStr) continue;
|
|
832
|
+
let raw;
|
|
833
|
+
try {
|
|
834
|
+
raw = JSON.parse(jsonStr);
|
|
835
|
+
} catch {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const streamEvent = normalizeEvent(raw);
|
|
839
|
+
handleEvent(
|
|
840
|
+
streamEvent,
|
|
841
|
+
fullText,
|
|
842
|
+
callbacks,
|
|
843
|
+
clientTools,
|
|
844
|
+
onPaused,
|
|
845
|
+
onAutoResume,
|
|
846
|
+
pendingAutoResumes
|
|
847
|
+
);
|
|
848
|
+
if (streamEvent.type === "text_delta") {
|
|
849
|
+
fullText += streamEvent.data;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (buffer.trim()) {
|
|
854
|
+
let jsonStr = "";
|
|
855
|
+
for (const line of buffer.trim().split("\n")) {
|
|
856
|
+
if (line.startsWith("data: ")) {
|
|
857
|
+
jsonStr += line.slice(6);
|
|
858
|
+
} else if (line.startsWith("data:")) {
|
|
859
|
+
jsonStr += line.slice(5);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
jsonStr = jsonStr.trim();
|
|
863
|
+
if (jsonStr) {
|
|
864
|
+
try {
|
|
865
|
+
const raw = JSON.parse(jsonStr);
|
|
866
|
+
const streamEvent = normalizeEvent(raw);
|
|
867
|
+
handleEvent(
|
|
868
|
+
streamEvent,
|
|
869
|
+
fullText,
|
|
870
|
+
callbacks,
|
|
871
|
+
clientTools,
|
|
872
|
+
onPaused,
|
|
873
|
+
onAutoResume,
|
|
874
|
+
pendingAutoResumes
|
|
875
|
+
);
|
|
876
|
+
if (streamEvent.type === "text_delta") {
|
|
877
|
+
fullText += streamEvent.data;
|
|
878
|
+
}
|
|
879
|
+
} catch {
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
} catch (err) {
|
|
884
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
885
|
+
throw err;
|
|
886
|
+
}
|
|
887
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
888
|
+
callbacks.onError?.(error);
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
if (pendingAutoResumes.length > 0) {
|
|
892
|
+
const handlerResults = await Promise.allSettled(pendingAutoResumes);
|
|
893
|
+
for (const settled of handlerResults) {
|
|
894
|
+
if (settled.status === "fulfilled" && settled.value) {
|
|
895
|
+
const { callId, result } = settled.value;
|
|
896
|
+
await onAutoResume?.(callId, result);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
callbacks.onComplete?.(fullText);
|
|
901
|
+
}
|
|
902
|
+
function normalizeEvent(raw) {
|
|
903
|
+
const typeMap = {
|
|
904
|
+
llm_text_delta: "text_delta",
|
|
905
|
+
llm_reasoning_delta: "reasoning_delta"
|
|
906
|
+
};
|
|
907
|
+
if (raw && typeof raw.type === "string" && typeMap[raw.type]) {
|
|
908
|
+
raw.type = typeMap[raw.type];
|
|
909
|
+
}
|
|
910
|
+
return raw;
|
|
911
|
+
}
|
|
912
|
+
function handleEvent(event, _fullText, callbacks, clientTools, onPaused, onAutoResume, pendingAutoResumes) {
|
|
913
|
+
callbacks.onEvent?.(event);
|
|
914
|
+
switch (event.type) {
|
|
915
|
+
case "text_delta": {
|
|
916
|
+
callbacks.onText?.(event.data);
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
case "reasoning_delta": {
|
|
920
|
+
callbacks.onReasoning?.(event.data.delta, event.data.index);
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
case "agent_handoff": {
|
|
924
|
+
callbacks.onAgentHandoff?.(event.data.agentName);
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
case "tool_call_start": {
|
|
928
|
+
const { callId, toolName, toolType, inputs: rawInputs, paused } = event.data;
|
|
929
|
+
const inputs = tryParseJSON2(rawInputs);
|
|
930
|
+
callbacks.onToolStart?.(toolName, toolType);
|
|
931
|
+
if (toolType === "client") {
|
|
932
|
+
const tool = clientTools.get(toolName);
|
|
933
|
+
if (tool) {
|
|
934
|
+
if (paused && tool.handler) {
|
|
935
|
+
const handlerPromise = (async () => {
|
|
936
|
+
try {
|
|
937
|
+
const result = await tool.handler(inputs);
|
|
938
|
+
callbacks.onEvent?.({
|
|
939
|
+
type: "tool_call_end",
|
|
940
|
+
data: {
|
|
941
|
+
callId,
|
|
942
|
+
toolName,
|
|
943
|
+
toolType: "client",
|
|
944
|
+
result
|
|
945
|
+
},
|
|
946
|
+
meta: event.meta
|
|
947
|
+
});
|
|
948
|
+
return { callId, result };
|
|
949
|
+
} catch (error) {
|
|
950
|
+
callbacks.onPaused?.(toolName, inputs);
|
|
951
|
+
onPaused?.({ callId, toolName, args: inputs });
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
})();
|
|
955
|
+
pendingAutoResumes.push(handlerPromise);
|
|
956
|
+
} else if (paused) {
|
|
957
|
+
callbacks.onPaused?.(toolName, inputs);
|
|
958
|
+
onPaused?.({ callId, toolName, args: inputs });
|
|
959
|
+
} else if (tool.handler) {
|
|
960
|
+
try {
|
|
961
|
+
tool.handler(inputs);
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
} else if (paused) {
|
|
966
|
+
callbacks.onPaused?.(toolName, inputs);
|
|
967
|
+
onPaused?.({ callId, toolName, args: inputs });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
case "tool_call_end": {
|
|
973
|
+
const { toolName, result, error } = event.data;
|
|
974
|
+
callbacks.onToolEnd?.(toolName, result, error);
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case "run_error": {
|
|
978
|
+
const { message, code } = event.data;
|
|
979
|
+
const error = new Error(message);
|
|
980
|
+
if (code != null) error.code = code;
|
|
981
|
+
callbacks.onError?.(error);
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/core/session.ts
|
|
988
|
+
var import_zod2 = require("zod");
|
|
989
|
+
var AgentSession = class {
|
|
990
|
+
/** @internal */
|
|
991
|
+
_agent;
|
|
992
|
+
/** @internal */
|
|
993
|
+
_sessionId;
|
|
994
|
+
/** @internal */
|
|
995
|
+
_paused = false;
|
|
996
|
+
/** @internal */
|
|
997
|
+
_pausedToolInfo = null;
|
|
998
|
+
/** @internal */
|
|
999
|
+
_abortController = null;
|
|
1000
|
+
/** @internal */
|
|
1001
|
+
constructor(agent, sessionId) {
|
|
1002
|
+
this._agent = agent;
|
|
1003
|
+
this._sessionId = sessionId;
|
|
1004
|
+
}
|
|
1005
|
+
// ─── Public API ────────────────────────────────────────────────────
|
|
1006
|
+
/**
|
|
1007
|
+
* Send a message in this session.
|
|
1008
|
+
*
|
|
1009
|
+
* @param message - The message to send
|
|
1010
|
+
* @param callbacks - Event handlers for the stream
|
|
1011
|
+
* @param callbacks - Event handlers for the stream
|
|
1012
|
+
* @param options - Optional settings like context, headers, or body
|
|
1013
|
+
* @returns This session (for chaining)
|
|
1014
|
+
*/
|
|
1015
|
+
async execute(message, callbacks, options) {
|
|
1016
|
+
this._paused = false;
|
|
1017
|
+
this._pausedToolInfo = null;
|
|
1018
|
+
const body = {
|
|
1019
|
+
...options?.body || {},
|
|
1020
|
+
input: message,
|
|
1021
|
+
stream: true
|
|
1022
|
+
};
|
|
1023
|
+
if (options?.context) {
|
|
1024
|
+
Object.assign(body, { context: options.context });
|
|
1025
|
+
}
|
|
1026
|
+
const clientToolDefs = this._getClientToolDefs();
|
|
1027
|
+
if (clientToolDefs.length > 0) {
|
|
1028
|
+
body.clientTools = clientToolDefs;
|
|
1029
|
+
}
|
|
1030
|
+
await this._run(body, callbacks, options);
|
|
1031
|
+
return this;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Resume a paused session with a tool result.
|
|
1035
|
+
*
|
|
1036
|
+
* @param result - The result to send back to the agent
|
|
1037
|
+
* @param callbacks - Event handlers for the resumed stream
|
|
1038
|
+
* @param options - Optional settings like headers or body
|
|
1039
|
+
* @returns This session (for chaining)
|
|
1040
|
+
*/
|
|
1041
|
+
async resume(result, callbacks, options) {
|
|
1042
|
+
if (!this._paused || !this._pausedToolInfo) {
|
|
1043
|
+
throw new Error("AgentSession.resume(): session is not paused. Check isPaused() first.");
|
|
1044
|
+
}
|
|
1045
|
+
const body = {
|
|
1046
|
+
...options?.body || {},
|
|
1047
|
+
stream: true,
|
|
1048
|
+
toolCallResult: {
|
|
1049
|
+
callId: this._pausedToolInfo.callId,
|
|
1050
|
+
result
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
const clientToolDefs = this._getClientToolDefs();
|
|
1054
|
+
if (clientToolDefs.length > 0) {
|
|
1055
|
+
body.clientTools = clientToolDefs;
|
|
1056
|
+
}
|
|
1057
|
+
this._paused = false;
|
|
1058
|
+
this._pausedToolInfo = null;
|
|
1059
|
+
await this._run(body, callbacks, options);
|
|
1060
|
+
return this;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Resume a session with an explicit tool call ID and result.
|
|
1064
|
+
*
|
|
1065
|
+
* Unlike `resume()`, this does NOT require the session to be in a paused state,
|
|
1066
|
+
* making it suitable for external resume flows (e.g. React widget submissions)
|
|
1067
|
+
* where the session object may have been re-created.
|
|
1068
|
+
*
|
|
1069
|
+
* @param callId - The tool call ID to resume
|
|
1070
|
+
* @param result - The result to send back to the agent
|
|
1071
|
+
* @param callbacks - Event handlers for the resumed stream
|
|
1072
|
+
* @param options - Optional settings like headers or body
|
|
1073
|
+
* @returns This session (for chaining)
|
|
1074
|
+
*/
|
|
1075
|
+
async resumeWithCallId(callId, result, callbacks, options) {
|
|
1076
|
+
const body = {
|
|
1077
|
+
...options?.body || {},
|
|
1078
|
+
stream: true,
|
|
1079
|
+
toolCallResult: { callId, result }
|
|
1080
|
+
};
|
|
1081
|
+
const clientToolDefs = this._getClientToolDefs();
|
|
1082
|
+
if (clientToolDefs.length > 0) {
|
|
1083
|
+
body.clientTools = clientToolDefs;
|
|
1084
|
+
}
|
|
1085
|
+
this._paused = false;
|
|
1086
|
+
this._pausedToolInfo = null;
|
|
1087
|
+
await this._run(body, callbacks, options);
|
|
1088
|
+
return this;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Check if this session is waiting for a tool result.
|
|
1092
|
+
*/
|
|
1093
|
+
isPaused() {
|
|
1094
|
+
return this._paused;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Get information about the paused tool call.
|
|
1098
|
+
* Returns `null` if the session is not paused.
|
|
1099
|
+
*/
|
|
1100
|
+
getPausedTool() {
|
|
1101
|
+
return this._pausedToolInfo;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Get the session ID.
|
|
1105
|
+
* May be `undefined` if the session hasn't executed yet.
|
|
1106
|
+
*/
|
|
1107
|
+
getSessionId() {
|
|
1108
|
+
if (!this._sessionId) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
"AgentSession.getSessionId(): session ID not yet available. Call execute() first."
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
return this._sessionId;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Cancel the current streaming operation.
|
|
1117
|
+
*/
|
|
1118
|
+
abort() {
|
|
1119
|
+
this._abortController?.abort();
|
|
1120
|
+
this._abortController = null;
|
|
1121
|
+
}
|
|
1122
|
+
// ─── Private helpers ───────────────────────────────────────────────
|
|
1123
|
+
/** @internal */
|
|
1124
|
+
async _run(body, callbacks, options) {
|
|
1125
|
+
this._abortController = new AbortController();
|
|
1126
|
+
const baseHeaders = this._agent._buildHeaders(this._sessionId);
|
|
1127
|
+
const mergedHeaders = { ...baseHeaders, ...options?.headers || {} };
|
|
1128
|
+
await executeStream({
|
|
1129
|
+
url: this._agent._url,
|
|
1130
|
+
body,
|
|
1131
|
+
headers: mergedHeaders,
|
|
1132
|
+
callbacks,
|
|
1133
|
+
clientTools: this._agent._clientTools,
|
|
1134
|
+
signal: this._abortController.signal,
|
|
1135
|
+
onSessionId: (id, name) => {
|
|
1136
|
+
this._sessionId = id;
|
|
1137
|
+
options?.onSessionId?.(id, name);
|
|
1138
|
+
},
|
|
1139
|
+
onPaused: (info) => {
|
|
1140
|
+
this._paused = true;
|
|
1141
|
+
this._pausedToolInfo = info;
|
|
1142
|
+
},
|
|
1143
|
+
onAutoResume: async (callId, result) => {
|
|
1144
|
+
const resumeBody = {
|
|
1145
|
+
...options?.body || {},
|
|
1146
|
+
stream: true,
|
|
1147
|
+
toolCallResult: { callId, result }
|
|
1148
|
+
};
|
|
1149
|
+
const clientToolDefs = this._getClientToolDefs();
|
|
1150
|
+
if (clientToolDefs.length > 0) {
|
|
1151
|
+
resumeBody.clientTools = clientToolDefs;
|
|
1152
|
+
}
|
|
1153
|
+
await this._run(resumeBody, callbacks, options);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
/** @internal */
|
|
1158
|
+
_getClientToolDefs() {
|
|
1159
|
+
const defs = [];
|
|
1160
|
+
for (const tool of this._agent._clientTools.values()) {
|
|
1161
|
+
defs.push({
|
|
1162
|
+
name: tool.name,
|
|
1163
|
+
description: tool.description,
|
|
1164
|
+
parameters: resolveParameters2(tool.parameters),
|
|
1165
|
+
await: tool.await
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
return defs;
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
function resolveParameters2(params) {
|
|
1172
|
+
let schema;
|
|
1173
|
+
if (params && typeof params === "object" && "_def" in params) {
|
|
1174
|
+
schema = (0, import_zod2.toJSONSchema)(params);
|
|
1175
|
+
delete schema.$schema;
|
|
1176
|
+
} else {
|
|
1177
|
+
schema = { ...params };
|
|
1178
|
+
}
|
|
1179
|
+
if (schema.type === "object") {
|
|
1180
|
+
schema.additionalProperties = false;
|
|
1181
|
+
if (schema.properties) {
|
|
1182
|
+
schema.required = Object.keys(schema.properties);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return schema;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/core/agent.ts
|
|
1189
|
+
var DEFAULT_BASE_URL = "https://api.buildship.run";
|
|
1190
|
+
var BuildShipAgent = class {
|
|
1191
|
+
/** @internal */
|
|
1192
|
+
_agentId;
|
|
1193
|
+
/** @internal */
|
|
1194
|
+
_accessKey;
|
|
1195
|
+
/** @internal */
|
|
1196
|
+
_baseUrl;
|
|
1197
|
+
/** @internal */
|
|
1198
|
+
_clientTools = /* @__PURE__ */ new Map();
|
|
1199
|
+
constructor(config) {
|
|
1200
|
+
if (!config.agentId) {
|
|
1201
|
+
throw new Error("BuildShipAgent: agentId is required");
|
|
1202
|
+
}
|
|
1203
|
+
this._agentId = config.agentId;
|
|
1204
|
+
this._accessKey = config.accessKey;
|
|
1205
|
+
this._baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* The URL for the agent's execute endpoint.
|
|
1209
|
+
* @internal
|
|
1210
|
+
*/
|
|
1211
|
+
get _url() {
|
|
1212
|
+
return `${this._baseUrl}/executeAgent/${this._agentId}`;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Build the authorization / common headers.
|
|
1216
|
+
* @internal
|
|
1217
|
+
*/
|
|
1218
|
+
_buildHeaders(sessionId) {
|
|
1219
|
+
const headers = {};
|
|
1220
|
+
if (this._accessKey) {
|
|
1221
|
+
headers["Authorization"] = `Bearer ${this._accessKey}`;
|
|
1222
|
+
}
|
|
1223
|
+
if (sessionId) {
|
|
1224
|
+
headers["X-BuildShip-Agent-Session-ID"] = sessionId;
|
|
1225
|
+
}
|
|
1226
|
+
return headers;
|
|
1227
|
+
}
|
|
1228
|
+
// ─── Public API ────────────────────────────────────────────────────
|
|
1229
|
+
/**
|
|
1230
|
+
* Start a new conversation.
|
|
1231
|
+
*
|
|
1232
|
+
* Creates a fresh session and sends the first message.
|
|
1233
|
+
*
|
|
1234
|
+
* @param message - The message to send
|
|
1235
|
+
* @param callbacks - Event handlers for the stream
|
|
1236
|
+
* @param options - Optional execution settings (context, headers, body, onSessionId)
|
|
1237
|
+
* @returns The new session
|
|
1238
|
+
*/
|
|
1239
|
+
async execute(message, callbacks, options) {
|
|
1240
|
+
const session = new AgentSession(this);
|
|
1241
|
+
await session.execute(message, callbacks, options);
|
|
1242
|
+
return session;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Get an existing session by ID to continue a conversation.
|
|
1246
|
+
*
|
|
1247
|
+
* @param sessionId - The session ID from a previous conversation
|
|
1248
|
+
* @returns The session object
|
|
1249
|
+
*/
|
|
1250
|
+
session(sessionId) {
|
|
1251
|
+
if (!sessionId) {
|
|
1252
|
+
throw new Error("BuildShipAgent.session(): sessionId is required");
|
|
1253
|
+
}
|
|
1254
|
+
return new AgentSession(this, sessionId);
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Register a client-side tool that the agent can call.
|
|
1258
|
+
*/
|
|
1259
|
+
registerClientTool(tool) {
|
|
1260
|
+
if (!tool.name) {
|
|
1261
|
+
throw new Error("registerClientTool: tool.name is required");
|
|
1262
|
+
}
|
|
1263
|
+
this._clientTools.set(tool.name, tool);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Remove a registered client tool.
|
|
1267
|
+
*/
|
|
1268
|
+
unregisterClientTool(name) {
|
|
1269
|
+
this._clientTools.delete(name);
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1022
1273
|
// src/react/utils/use-synced-local-storage.ts
|
|
1023
1274
|
var import_react4 = require("react");
|
|
1024
1275
|
function parseJSON(value, defaultValue) {
|
|
@@ -1088,16 +1339,21 @@ function AgentContextProvider({ children }) {
|
|
|
1088
1339
|
AGENT_DEBUG_DATA_KEY,
|
|
1089
1340
|
{}
|
|
1090
1341
|
);
|
|
1091
|
-
const initializeAgent = (0, import_react5.useCallback)(
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1342
|
+
const initializeAgent = (0, import_react5.useCallback)(
|
|
1343
|
+
(agentId, agentUrl, accessKey, options) => {
|
|
1344
|
+
const existing = activeAgentsRef.current.get(agentId);
|
|
1345
|
+
if (!existing) {
|
|
1346
|
+
activeAgentsRef.current.set(agentId, { agentUrl, accessKey, options });
|
|
1347
|
+
forceUpdate({});
|
|
1348
|
+
} else if (existing.agentUrl !== agentUrl || existing.accessKey !== accessKey) {
|
|
1349
|
+
activeAgentsRef.current.set(agentId, { agentUrl, accessKey, options });
|
|
1350
|
+
forceUpdate({});
|
|
1351
|
+
} else if (options) {
|
|
1352
|
+
existing.options = options;
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
[]
|
|
1356
|
+
);
|
|
1101
1357
|
const registerRunner = (0, import_react5.useCallback)((agentId, runner) => {
|
|
1102
1358
|
runnersRef.current.set(agentId, runner);
|
|
1103
1359
|
const listeners = listenersRef.current.get(agentId);
|
|
@@ -1166,41 +1422,53 @@ function AgentContextProvider({ children }) {
|
|
|
1166
1422
|
);
|
|
1167
1423
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AgentContext.Provider, { value: agentContextValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AgentToolContext.Provider, { value: toolContextValue, children: [
|
|
1168
1424
|
children,
|
|
1169
|
-
Array.from(activeAgentsRef.current.entries()).map(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1425
|
+
Array.from(activeAgentsRef.current.entries()).map(
|
|
1426
|
+
([agentId, { agentUrl, accessKey, options }]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1427
|
+
AgentRunnerInstance,
|
|
1428
|
+
{
|
|
1429
|
+
agentId,
|
|
1430
|
+
agentUrl,
|
|
1431
|
+
accessKey,
|
|
1432
|
+
options
|
|
1433
|
+
},
|
|
1434
|
+
agentId
|
|
1435
|
+
)
|
|
1436
|
+
)
|
|
1178
1437
|
] }) });
|
|
1179
1438
|
}
|
|
1180
1439
|
function AgentRunnerInstance({
|
|
1181
1440
|
agentId,
|
|
1182
1441
|
agentUrl,
|
|
1183
|
-
accessKey
|
|
1442
|
+
accessKey,
|
|
1443
|
+
options
|
|
1184
1444
|
}) {
|
|
1185
|
-
const agent =
|
|
1445
|
+
const agent = (0, import_react5.useMemo)(
|
|
1446
|
+
() => new BuildShipAgent({ agentId, baseUrl: agentUrl, accessKey }),
|
|
1447
|
+
[agentId, agentUrl, accessKey]
|
|
1448
|
+
);
|
|
1449
|
+
const optionsRef = (0, import_react5.useRef)(options);
|
|
1450
|
+
optionsRef.current = options;
|
|
1451
|
+
const agentRunner = useAgent(agent, optionsRef.current);
|
|
1186
1452
|
const context = (0, import_react5.useContext)(AgentContext);
|
|
1187
1453
|
(0, import_react5.useEffect)(() => {
|
|
1188
1454
|
if (context) {
|
|
1189
|
-
context.registerRunner(agentId,
|
|
1455
|
+
context.registerRunner(agentId, agentRunner);
|
|
1190
1456
|
}
|
|
1191
|
-
}, [agentId,
|
|
1457
|
+
}, [agentId, agentRunner, context]);
|
|
1192
1458
|
if (!context) return null;
|
|
1193
1459
|
return null;
|
|
1194
1460
|
}
|
|
1195
|
-
function useAgentContext(agentId, agentUrl, accessKey) {
|
|
1461
|
+
function useAgentContext(agentId, agentUrl, accessKey, options) {
|
|
1196
1462
|
const context = (0, import_react5.useContext)(AgentContext);
|
|
1197
1463
|
if (!context) {
|
|
1198
1464
|
throw new Error("useAgentContext must be used within AgentContextProvider");
|
|
1199
1465
|
}
|
|
1200
1466
|
const { initializeAgent, getRunner, listenersRef } = context;
|
|
1467
|
+
const optionsRef = (0, import_react5.useRef)(options);
|
|
1468
|
+
optionsRef.current = options;
|
|
1201
1469
|
(0, import_react5.useEffect)(() => {
|
|
1202
|
-
initializeAgent(agentId, agentUrl, accessKey);
|
|
1203
|
-
}, [agentId, agentUrl, initializeAgent]);
|
|
1470
|
+
initializeAgent(agentId, agentUrl, accessKey, optionsRef.current);
|
|
1471
|
+
}, [agentId, agentUrl, accessKey, initializeAgent]);
|
|
1204
1472
|
const [runner, setRunner] = (0, import_react5.useState)(() => getRunner(agentId));
|
|
1205
1473
|
(0, import_react5.useEffect)(() => {
|
|
1206
1474
|
const currentRunner = getRunner(agentId);
|