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