bs-agent 0.0.9 → 0.0.12

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.
@@ -0,0 +1,1286 @@
1
+ // src/react/agent-context.tsx
2
+ import {
3
+ createContext,
4
+ useContext as useContext3,
5
+ useCallback as useCallback5,
6
+ useRef as useRef3,
7
+ useState as useState3,
8
+ useEffect as useEffect3,
9
+ useMemo as useMemo3
10
+ } from "react";
11
+
12
+ // src/react/use-agent.ts
13
+ import { useCallback as useCallback3, useRef as useRef2, useState, useEffect, useContext as useContext2, useMemo as useMemo2 } from "react";
14
+
15
+ // src/react/constants.ts
16
+ var AGENT_SESSIONS_KEY = "buildship:agent:conversations";
17
+ var AGENT_DEBUG_DATA_KEY = "buildship:agent:debug";
18
+ var DEFAULT_SESSION_NAME = "New Chat";
19
+ var TEMPORARY_SESSION_ID = "sess_temp";
20
+
21
+ // src/react/session-utils.ts
22
+ import { useCallback, useMemo, useRef } from "react";
23
+ var useSessionUtils = (agentId, allSessions, setAllSessions, currentSessionId, setCurrentSessionId, messagesRef) => {
24
+ const agentSessions = useMemo(() => allSessions[agentId] || {}, [agentId, allSessions]);
25
+ const syncSessionRef = useRef();
26
+ syncSessionRef.current = (updatedMessages) => {
27
+ if (!currentSessionId || currentSessionId === TEMPORARY_SESSION_ID) {
28
+ return;
29
+ }
30
+ setAllSessions((prev) => ({
31
+ ...prev,
32
+ [agentId]: {
33
+ ...prev[agentId],
34
+ [currentSessionId]: {
35
+ ...prev[agentId]?.[currentSessionId],
36
+ messages: updatedMessages ?? messagesRef.current,
37
+ updatedAt: Date.now()
38
+ }
39
+ }
40
+ }));
41
+ };
42
+ const getInitialSessionId = () => {
43
+ const sessions = Object.values(agentSessions);
44
+ if (sessions.length > 0) {
45
+ return sessions.sort((a, b) => b.updatedAt - a.updatedAt)[0].id;
46
+ }
47
+ return TEMPORARY_SESSION_ID;
48
+ };
49
+ const switchSession = useCallback(
50
+ (sessionId = TEMPORARY_SESSION_ID) => {
51
+ setCurrentSessionId(sessionId);
52
+ },
53
+ [setCurrentSessionId]
54
+ );
55
+ const deleteSession = useCallback(
56
+ (sessionId) => {
57
+ if (!sessionId || sessionId === TEMPORARY_SESSION_ID) {
58
+ return;
59
+ }
60
+ setAllSessions((prev) => {
61
+ const updatedAgentSessions = { ...prev[agentId] };
62
+ delete updatedAgentSessions[sessionId];
63
+ return {
64
+ ...prev,
65
+ [agentId]: updatedAgentSessions
66
+ };
67
+ });
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
+ },
78
+ [agentId, currentSessionId, agentSessions, setAllSessions, setCurrentSessionId]
79
+ );
80
+ const sessionsList = useMemo(
81
+ () => Object.values(agentSessions).sort((a, b) => b.updatedAt - a.updatedAt),
82
+ [agentSessions]
83
+ );
84
+ const createSessionFromResponse = (sessionId, sessionName, currentMessages) => {
85
+ setAllSessions((prev) => ({
86
+ ...prev,
87
+ [agentId]: {
88
+ ...prev[agentId],
89
+ [sessionId]: {
90
+ id: sessionId,
91
+ createdAt: Date.now(),
92
+ updatedAt: Date.now(),
93
+ messages: currentMessages,
94
+ name: sessionName
95
+ }
96
+ }
97
+ }));
98
+ };
99
+ return {
100
+ agentSessions,
101
+ syncSessionRef,
102
+ getInitialSessionId,
103
+ switchSession,
104
+ deleteSession,
105
+ sessionsList,
106
+ createSessionFromResponse
107
+ };
108
+ };
109
+
110
+ // src/react/debug-handlers.ts
111
+ var createDebugHandlers = (setDebugData) => {
112
+ const handleStreamEvent = (event) => {
113
+ const executionId = event.meta.executionId;
114
+ switch (event.type) {
115
+ case "tool_call_start": {
116
+ const { callId, toolName, toolType, inputs, serverName } = event.data;
117
+ setDebugData((prev) => ({
118
+ ...prev,
119
+ [executionId]: [
120
+ ...prev[executionId] || [],
121
+ {
122
+ itemType: "tool_call",
123
+ toolName,
124
+ callId,
125
+ toolType,
126
+ status: "progress",
127
+ inputs,
128
+ serverName
129
+ }
130
+ ]
131
+ }));
132
+ break;
133
+ }
134
+ case "tool_call_end": {
135
+ const { callId, result, error } = event.data;
136
+ setDebugData((prev) => {
137
+ const currentData = [...prev[executionId] || []];
138
+ for (let i = currentData.length - 1; i >= 0; i--) {
139
+ if (currentData[i].itemType === "tool_call") {
140
+ const toolItem = currentData[i];
141
+ if (toolItem.callId === callId) {
142
+ currentData[i] = {
143
+ ...toolItem,
144
+ status: error ? "error" : "complete",
145
+ output: result,
146
+ error
147
+ };
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ return {
153
+ ...prev,
154
+ [executionId]: currentData
155
+ };
156
+ });
157
+ break;
158
+ }
159
+ case "reasoning_delta": {
160
+ const { delta, index } = event.data;
161
+ setDebugData((prev) => {
162
+ const currentData = [...prev[executionId] || []];
163
+ let existingItemIndex = -1;
164
+ for (let i = currentData.length - 1; i >= 0; i--) {
165
+ const item = currentData[i];
166
+ if (item.itemType === "reasoning" && item.index === index) {
167
+ existingItemIndex = i;
168
+ break;
169
+ }
170
+ }
171
+ if (existingItemIndex === -1) {
172
+ currentData.push({ itemType: "reasoning", reasoning: delta, index });
173
+ } else {
174
+ currentData[existingItemIndex] = {
175
+ itemType: "reasoning",
176
+ reasoning: currentData[existingItemIndex].reasoning + delta,
177
+ index
178
+ };
179
+ }
180
+ return {
181
+ ...prev,
182
+ [executionId]: currentData
183
+ };
184
+ });
185
+ break;
186
+ }
187
+ case "agent_handoff": {
188
+ setDebugData((prev) => ({
189
+ ...prev,
190
+ [executionId]: [
191
+ ...prev[executionId] || [],
192
+ {
193
+ itemType: "handoff",
194
+ agentName: event.data.agentName
195
+ }
196
+ ]
197
+ }));
198
+ break;
199
+ }
200
+ case "run_error": {
201
+ setDebugData((prev) => ({
202
+ ...prev,
203
+ [executionId]: [
204
+ ...prev[executionId] || [],
205
+ {
206
+ itemType: "run_error",
207
+ message: event.data.message,
208
+ code: event.data.code
209
+ }
210
+ ]
211
+ }));
212
+ break;
213
+ }
214
+ }
215
+ };
216
+ return {
217
+ handleStreamEvent
218
+ };
219
+ };
220
+
221
+ // src/react/client-tools.ts
222
+ import { useCallback as useCallback2 } from "react";
223
+ import { toJSONSchema } from "zod";
224
+
225
+ // src/react/utils/schema.ts
226
+ function cleanSchema(obj) {
227
+ if (Array.isArray(obj)) {
228
+ return obj.map(cleanSchema);
229
+ } else if (obj !== null && typeof obj === "object") {
230
+ const newObj = {};
231
+ const currentObj = obj;
232
+ for (const key in currentObj) {
233
+ if (key === "propertyNames" || key === "$schema") {
234
+ continue;
235
+ }
236
+ newObj[key] = cleanSchema(currentObj[key]);
237
+ }
238
+ if (newObj.type === "object") {
239
+ if (newObj.additionalProperties === void 0 || newObj.additionalProperties && typeof newObj.additionalProperties === "object" && Object.keys(newObj.additionalProperties).length === 0) {
240
+ newObj.additionalProperties = false;
241
+ }
242
+ if (newObj.additionalProperties === false && newObj.properties) {
243
+ newObj.required = Object.keys(newObj.properties);
244
+ }
245
+ }
246
+ return newObj;
247
+ }
248
+ return obj;
249
+ }
250
+ function tryParseJSON(value) {
251
+ if (typeof value === "string") {
252
+ try {
253
+ return JSON.parse(value);
254
+ } catch {
255
+ return value;
256
+ }
257
+ }
258
+ return value;
259
+ }
260
+
261
+ // src/react/client-tools.ts
262
+ function resolveParameters(params) {
263
+ let schema;
264
+ if (params && typeof params === "object" && "_def" in params) {
265
+ schema = toJSONSchema(params);
266
+ delete schema.$schema;
267
+ } else {
268
+ schema = cleanSchema(params);
269
+ }
270
+ if (schema.type === "object") {
271
+ schema.additionalProperties = false;
272
+ if (schema.properties) {
273
+ schema.required = Object.keys(schema.properties);
274
+ }
275
+ }
276
+ return schema;
277
+ }
278
+ function useClientToolHelpers(agentId, toolContext) {
279
+ const getClientToolsMap = useCallback2(() => {
280
+ if (!toolContext) return /* @__PURE__ */ new Map();
281
+ const tools = toolContext.getToolsForAgent(agentId);
282
+ const map = /* @__PURE__ */ new Map();
283
+ for (const tool of tools) {
284
+ if (tool.handler) {
285
+ map.set(tool.name, {
286
+ name: tool.name,
287
+ description: tool.description,
288
+ parameters: tool.parameters,
289
+ await: tool.await,
290
+ handler: tool.handler
291
+ });
292
+ }
293
+ }
294
+ return map;
295
+ }, [agentId, toolContext]);
296
+ const getClientToolDefs = useCallback2(() => {
297
+ if (!toolContext) return [];
298
+ const tools = toolContext.getToolsForAgent(agentId);
299
+ return tools.map((tool) => ({
300
+ name: tool.name,
301
+ description: tool.description,
302
+ parameters: resolveParameters(tool.parameters),
303
+ await: tool.await
304
+ }));
305
+ }, [agentId, toolContext]);
306
+ return { getClientToolsMap, getClientToolDefs };
307
+ }
308
+
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
+ // src/react/utils/message.ts
537
+ function updateAgentMessageParts(parts, newPart) {
538
+ const last = parts[parts.length - 1];
539
+ const canMerge = last?.type === "text" && newPart.type === "text" && newPart.firstSequence === last.lastSequence + 1;
540
+ if (canMerge) {
541
+ return [
542
+ ...parts.slice(0, -1),
543
+ {
544
+ ...last,
545
+ text: last.text + newPart.text,
546
+ lastSequence: newPart.lastSequence
547
+ }
548
+ ];
549
+ }
550
+ return [...parts, newPart];
551
+ }
552
+
553
+ // src/react/stream-callbacks.ts
554
+ function buildStreamCallbacks(deps, debugKey) {
555
+ const { setMessages, setInProgress, syncSessionRef, messagesRef, toolContext, agentId } = deps;
556
+ return {
557
+ onComplete: () => {
558
+ console.log("Agent closed");
559
+ setInProgress(false);
560
+ if (syncSessionRef.current) {
561
+ syncSessionRef.current(messagesRef.current);
562
+ }
563
+ },
564
+ onError: (error) => {
565
+ console.log("Agent error", error);
566
+ setInProgress(false);
567
+ if (syncSessionRef.current) {
568
+ syncSessionRef.current(messagesRef.current);
569
+ }
570
+ },
571
+ onEvent: (event) => {
572
+ if (event.type === "text_delta") {
573
+ handleTextDelta(event, setMessages, syncSessionRef);
574
+ } else if (event.type === "tool_call_start" && event.data.toolType === "client") {
575
+ handleClientToolCall(event, setMessages, syncSessionRef, toolContext, agentId);
576
+ if (debugKey) {
577
+ const debugMeta = { ...event.meta, executionId: debugKey };
578
+ deps.debugHandlers.handleStreamEvent({
579
+ ...event,
580
+ meta: debugMeta
581
+ });
582
+ if (!event.data.paused) {
583
+ deps.debugHandlers.handleStreamEvent({
584
+ type: "tool_call_end",
585
+ data: {
586
+ callId: event.data.callId,
587
+ toolName: event.data.toolName,
588
+ toolType: event.data.toolType
589
+ },
590
+ meta: debugMeta
591
+ });
592
+ }
593
+ }
594
+ } else if (debugKey) {
595
+ deps.debugHandlers.handleStreamEvent({
596
+ ...event,
597
+ meta: { ...event.meta, executionId: debugKey }
598
+ });
599
+ }
600
+ }
601
+ };
602
+ }
603
+ var TEMPORARY_SESSION_ID2 = "sess_temp";
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) {
674
+ const sequence = event.meta.sequence;
675
+ const text = event.data;
676
+ const eventExecutionId = event.meta.executionId;
677
+ setMessages((prev) => {
678
+ const lastMessage = prev[prev.length - 1];
679
+ const newPart = {
680
+ type: "text",
681
+ text,
682
+ firstSequence: sequence,
683
+ lastSequence: sequence
684
+ };
685
+ let updatedMessages;
686
+ if (lastMessage?.role === "agent") {
687
+ const updatedMessage = {
688
+ ...lastMessage,
689
+ content: lastMessage.content + text,
690
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
691
+ };
692
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
693
+ } else {
694
+ const updatedMessage = {
695
+ role: "agent",
696
+ content: text,
697
+ parts: [newPart],
698
+ executionId: eventExecutionId
699
+ };
700
+ updatedMessages = [...prev, updatedMessage];
701
+ }
702
+ if (syncSessionRef.current) {
703
+ syncSessionRef.current(updatedMessages);
704
+ }
705
+ return updatedMessages;
706
+ });
707
+ }
708
+ function handleClientToolCall(event, setMessages, syncSessionRef, toolContext, agentId) {
709
+ const tool = toolContext?.getTool(agentId, event.data.toolName);
710
+ if (!tool?.render) return;
711
+ setMessages((prev) => {
712
+ const lastMessage = prev[prev.length - 1];
713
+ const newPart = {
714
+ type: "widget",
715
+ toolName: event.data.toolName,
716
+ callId: event.data.callId,
717
+ inputs: tryParseJSON(event.data.inputs),
718
+ sequence: event.meta.sequence,
719
+ paused: event.data.paused,
720
+ status: "pending"
721
+ };
722
+ let updatedMessages;
723
+ if (lastMessage?.role === "agent") {
724
+ const updatedMessage = {
725
+ ...lastMessage,
726
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
727
+ };
728
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
729
+ } else {
730
+ const updatedMessage = {
731
+ role: "agent",
732
+ content: "",
733
+ parts: [newPart],
734
+ executionId: event.meta.executionId
735
+ };
736
+ updatedMessages = [...prev, updatedMessage];
737
+ }
738
+ if (syncSessionRef.current) {
739
+ syncSessionRef.current(updatedMessages);
740
+ }
741
+ return updatedMessages;
742
+ });
743
+ }
744
+
745
+ // src/react/use-agent.ts
746
+ function useAgent(agentId, agentUrl, accessKey) {
747
+ const { allSessions, setAllSessions, debugData, setDebugData } = useAgentGlobalState();
748
+ const toolContext = useContext2(AgentToolContext);
749
+ const [inProgress, setInProgress] = useState(false);
750
+ const [messages, setMessages] = useState([]);
751
+ const messagesRef = useRef2([]);
752
+ const [currentSessionId, setCurrentSessionId] = useState(TEMPORARY_SESSION_ID);
753
+ const sessionUtils = useSessionUtils(
754
+ agentId,
755
+ allSessions,
756
+ setAllSessions,
757
+ currentSessionId,
758
+ setCurrentSessionId,
759
+ messagesRef
760
+ );
761
+ useEffect(() => {
762
+ const initialSessionId = sessionUtils.getInitialSessionId();
763
+ setCurrentSessionId(initialSessionId);
764
+ }, []);
765
+ const debugHandlers = useMemo2(() => createDebugHandlers(setDebugData), [setDebugData]);
766
+ useEffect(() => {
767
+ messagesRef.current = messages;
768
+ }, [messages]);
769
+ useEffect(() => {
770
+ if (inProgress) return;
771
+ const session = sessionUtils.agentSessions[currentSessionId];
772
+ if (session) {
773
+ setMessages(session.messages);
774
+ } else {
775
+ setMessages([]);
776
+ }
777
+ }, [currentSessionId, agentId]);
778
+ useEffect(() => {
779
+ return () => {
780
+ if (messagesRef.current.length > 0 && sessionUtils.syncSessionRef.current) {
781
+ sessionUtils.syncSessionRef.current();
782
+ }
783
+ };
784
+ }, []);
785
+ const controller = useRef2();
786
+ const lastRequestContextRef = useRef2({});
787
+ const { getClientToolsMap, getClientToolDefs } = useClientToolHelpers(agentId, toolContext);
788
+ const runAgentRef = useRef2();
789
+ const runAgent = useCallback3(
790
+ async (input, runOptions) => {
791
+ const realSessionId = currentSessionId && currentSessionId !== TEMPORARY_SESSION_ID ? currentSessionId : lastRequestContextRef.current.sessionId;
792
+ lastRequestContextRef.current = {
793
+ additionalHeaders: runOptions?.additionalHeaders ?? lastRequestContextRef.current.additionalHeaders,
794
+ additionalBody: runOptions?.additionalBody ?? lastRequestContextRef.current.additionalBody,
795
+ sessionId: realSessionId
796
+ };
797
+ 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
+ const debugKey = runOptions?.optimisticExecutionId || messagesRef.current.findLast((m) => m.role === "user")?.executionId;
832
+ const deps = {
833
+ agentUrl,
834
+ agentId,
835
+ accessKey,
836
+ currentSessionId,
837
+ messagesRef,
838
+ setMessages,
839
+ setInProgress,
840
+ setCurrentSessionId,
841
+ syncSessionRef: sessionUtils.syncSessionRef,
842
+ createSessionFromResponse: sessionUtils.createSessionFromResponse,
843
+ debugHandlers,
844
+ toolContext,
845
+ lastRequestContextRef,
846
+ getClientToolsMap,
847
+ signal: controller.current.signal,
848
+ runAgentRef
849
+ };
850
+ const callbacks = buildStreamCallbacks(deps, debugKey);
851
+ try {
852
+ await executeAgentStream(deps, body, headers, callbacks, debugKey);
853
+ } catch (error) {
854
+ console.log("Agent execution failed", error);
855
+ setInProgress(false);
856
+ if (sessionUtils.syncSessionRef.current) {
857
+ sessionUtils.syncSessionRef.current(messagesRef.current);
858
+ }
859
+ throw error;
860
+ }
861
+ },
862
+ [
863
+ agentUrl,
864
+ accessKey,
865
+ currentSessionId,
866
+ sessionUtils.syncSessionRef,
867
+ sessionUtils.createSessionFromResponse,
868
+ debugHandlers,
869
+ getClientToolsMap,
870
+ getClientToolDefs,
871
+ agentId,
872
+ toolContext
873
+ ]
874
+ );
875
+ useEffect(() => {
876
+ runAgentRef.current = runAgent;
877
+ }, [runAgent]);
878
+ const handleSend = useCallback3(
879
+ async (input, options) => {
880
+ const userMessage = {
881
+ role: "user",
882
+ content: input,
883
+ executionId: Date.now().toString()
884
+ };
885
+ if (!options?.skipUserMessage) {
886
+ setMessages((prev) => {
887
+ const updatedMessages = [...prev, userMessage];
888
+ if (sessionUtils.syncSessionRef.current) {
889
+ sessionUtils.syncSessionRef.current(updatedMessages);
890
+ }
891
+ return updatedMessages;
892
+ });
893
+ }
894
+ const effectiveExecutionId = options?.skipUserMessage ? messagesRef.current.findLast((m) => m.role === "user")?.executionId ?? userMessage.executionId : userMessage.executionId;
895
+ try {
896
+ await runAgent(input, {
897
+ ...options,
898
+ optimisticExecutionId: effectiveExecutionId
899
+ });
900
+ } catch (error) {
901
+ if (!options?.skipUserMessage) {
902
+ setMessages((prev) => {
903
+ const updatedMessages = prev.some((m) => m === userMessage) ? prev : [...prev, userMessage];
904
+ if (sessionUtils.syncSessionRef.current) {
905
+ sessionUtils.syncSessionRef.current(updatedMessages);
906
+ }
907
+ return updatedMessages;
908
+ });
909
+ }
910
+ throw error;
911
+ }
912
+ },
913
+ [runAgent, sessionUtils.syncSessionRef]
914
+ );
915
+ const resumeTool = useCallback3(
916
+ async (callId, result) => {
917
+ setMessages((prev) => {
918
+ const updatedMessages = prev.map((msg) => {
919
+ if (msg.parts) {
920
+ const updatedParts = msg.parts.map((part) => {
921
+ if (part.type === "widget" && part.callId === callId) {
922
+ return { ...part, status: "submitted", result };
923
+ }
924
+ return part;
925
+ });
926
+ return { ...msg, parts: updatedParts };
927
+ }
928
+ return msg;
929
+ });
930
+ if (sessionUtils.syncSessionRef.current) {
931
+ sessionUtils.syncSessionRef.current(updatedMessages);
932
+ }
933
+ return updatedMessages;
934
+ });
935
+ const debugKey = messagesRef.current.findLast((m) => m.role === "user")?.executionId;
936
+ if (debugKey) {
937
+ debugHandlers.handleStreamEvent({
938
+ type: "tool_call_end",
939
+ data: { callId, toolName: "", toolType: "client", result },
940
+ meta: { executionId: debugKey, sequence: 0 }
941
+ });
942
+ }
943
+ await runAgent(void 0, {
944
+ resumeToolCallId: callId,
945
+ resumeToolResult: result
946
+ });
947
+ },
948
+ [runAgent, sessionUtils.syncSessionRef, debugHandlers]
949
+ );
950
+ const addOptimisticMessage = useCallback3(
951
+ (input) => {
952
+ const userMessage = {
953
+ role: "user",
954
+ content: input,
955
+ executionId: Date.now().toString()
956
+ };
957
+ setMessages((prev) => {
958
+ const updatedMessages = [...prev, userMessage];
959
+ if (sessionUtils.syncSessionRef.current) {
960
+ sessionUtils.syncSessionRef.current(updatedMessages);
961
+ }
962
+ return updatedMessages;
963
+ });
964
+ },
965
+ [sessionUtils.syncSessionRef]
966
+ );
967
+ const abort = useCallback3(() => {
968
+ if (controller.current) {
969
+ controller.current.abort();
970
+ }
971
+ setInProgress(false);
972
+ if (sessionUtils.syncSessionRef.current) {
973
+ sessionUtils.syncSessionRef.current(messagesRef.current);
974
+ }
975
+ }, [sessionUtils.syncSessionRef]);
976
+ return {
977
+ inProgress,
978
+ messages,
979
+ handleSend,
980
+ resumeTool,
981
+ addOptimisticMessage,
982
+ abort,
983
+ sessionId: currentSessionId,
984
+ switchSession: sessionUtils.switchSession,
985
+ deleteSession: sessionUtils.deleteSession,
986
+ sessions: sessionUtils.sessionsList,
987
+ debugData
988
+ };
989
+ }
990
+
991
+ // src/react/utils/use-synced-local-storage.ts
992
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback4 } from "react";
993
+ function parseJSON(value, defaultValue) {
994
+ if (value === null) return defaultValue;
995
+ try {
996
+ return JSON.parse(value);
997
+ } catch {
998
+ return defaultValue;
999
+ }
1000
+ }
1001
+ function useSyncedLocalStorage(key, initialValue) {
1002
+ const [storedValue, setStoredValue] = useState2(() => {
1003
+ if (typeof window === "undefined") {
1004
+ return initialValue;
1005
+ }
1006
+ try {
1007
+ const item = window.localStorage.getItem(key);
1008
+ return parseJSON(item, initialValue);
1009
+ } catch (error) {
1010
+ console.warn(`Error reading localStorage key "${key}":`, error);
1011
+ return initialValue;
1012
+ }
1013
+ });
1014
+ const setValue = useCallback4(
1015
+ (value) => {
1016
+ try {
1017
+ setStoredValue((prev) => {
1018
+ const valueToStore = value instanceof Function ? value(prev) : value;
1019
+ if (typeof window !== "undefined") {
1020
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
1021
+ window.dispatchEvent(
1022
+ new StorageEvent("storage", { key, newValue: JSON.stringify(valueToStore) })
1023
+ );
1024
+ }
1025
+ return valueToStore;
1026
+ });
1027
+ } catch (error) {
1028
+ console.warn(`Error setting localStorage key "${key}":`, error);
1029
+ }
1030
+ },
1031
+ [key]
1032
+ );
1033
+ useEffect2(() => {
1034
+ const handleStorageChange = (event) => {
1035
+ if (event.key === key) {
1036
+ setStoredValue(parseJSON(event.newValue, initialValue));
1037
+ }
1038
+ };
1039
+ window.addEventListener("storage", handleStorageChange);
1040
+ return () => window.removeEventListener("storage", handleStorageChange);
1041
+ }, [key, initialValue]);
1042
+ return [storedValue, setValue];
1043
+ }
1044
+
1045
+ // src/react/agent-context.tsx
1046
+ import { jsx, jsxs } from "react/jsx-runtime";
1047
+ var AgentToolContext = createContext(null);
1048
+ var AgentContext = createContext(null);
1049
+ function AgentContextProvider({ children }) {
1050
+ const activeAgentsRef = useRef3(/* @__PURE__ */ new Map());
1051
+ const runnersRef = useRef3(/* @__PURE__ */ new Map());
1052
+ const listenersRef = useRef3(/* @__PURE__ */ new Map());
1053
+ const toolRegistryRef = useRef3(/* @__PURE__ */ new Map());
1054
+ const [, forceUpdate] = useState3({});
1055
+ const [allSessions, setAllSessions] = useSyncedLocalStorage(AGENT_SESSIONS_KEY, {});
1056
+ const [debugData, setDebugData] = useSyncedLocalStorage(
1057
+ AGENT_DEBUG_DATA_KEY,
1058
+ {}
1059
+ );
1060
+ const initializeAgent = useCallback5((agentId, agentUrl, accessKey) => {
1061
+ const existing = activeAgentsRef.current.get(agentId);
1062
+ if (!existing) {
1063
+ activeAgentsRef.current.set(agentId, { agentUrl, accessKey });
1064
+ forceUpdate({});
1065
+ } else if (existing.agentUrl !== agentUrl || existing.accessKey !== accessKey) {
1066
+ activeAgentsRef.current.set(agentId, { agentUrl, accessKey });
1067
+ forceUpdate({});
1068
+ }
1069
+ }, []);
1070
+ const registerRunner = useCallback5((agentId, runner) => {
1071
+ runnersRef.current.set(agentId, runner);
1072
+ const listeners = listenersRef.current.get(agentId);
1073
+ if (listeners) {
1074
+ listeners.forEach((callback) => callback());
1075
+ }
1076
+ }, []);
1077
+ const getRunner = useCallback5((agentId) => {
1078
+ return runnersRef.current.get(agentId) || null;
1079
+ }, []);
1080
+ const registerTool = useCallback5((agentId, config) => {
1081
+ if (!toolRegistryRef.current.has(agentId)) {
1082
+ toolRegistryRef.current.set(agentId, /* @__PURE__ */ new Map());
1083
+ }
1084
+ toolRegistryRef.current.get(agentId).set(config.name, config);
1085
+ }, []);
1086
+ const unregisterTool = useCallback5((agentId, toolName) => {
1087
+ toolRegistryRef.current.get(agentId)?.delete(toolName);
1088
+ }, []);
1089
+ const getTool = useCallback5((agentId, toolName) => {
1090
+ return toolRegistryRef.current.get(agentId)?.get(toolName);
1091
+ }, []);
1092
+ const getToolsForAgent = useCallback5((agentId) => {
1093
+ const toolMap = toolRegistryRef.current.get(agentId);
1094
+ return toolMap ? Array.from(toolMap.values()) : [];
1095
+ }, []);
1096
+ const resumeToolFromContext = useCallback5((agentId, callId, result) => {
1097
+ const runner = runnersRef.current.get(agentId);
1098
+ if (runner) {
1099
+ runner.resumeTool(callId, result);
1100
+ } else {
1101
+ console.warn(`Cannot resume tool: no runner found for agent "${agentId}"`);
1102
+ }
1103
+ }, []);
1104
+ const agentContextValue = useMemo3(
1105
+ () => ({
1106
+ initializeAgent,
1107
+ registerRunner,
1108
+ getRunner,
1109
+ allSessions,
1110
+ setAllSessions,
1111
+ debugData,
1112
+ setDebugData,
1113
+ runnersRef,
1114
+ listenersRef
1115
+ }),
1116
+ [
1117
+ initializeAgent,
1118
+ registerRunner,
1119
+ getRunner,
1120
+ allSessions,
1121
+ setAllSessions,
1122
+ debugData,
1123
+ setDebugData
1124
+ ]
1125
+ );
1126
+ const toolContextValue = useMemo3(
1127
+ () => ({
1128
+ registerTool,
1129
+ unregisterTool,
1130
+ getTool,
1131
+ getToolsForAgent,
1132
+ resumeTool: resumeToolFromContext
1133
+ }),
1134
+ [registerTool, unregisterTool, getTool, getToolsForAgent, resumeToolFromContext]
1135
+ );
1136
+ return /* @__PURE__ */ jsx(AgentContext.Provider, { value: agentContextValue, children: /* @__PURE__ */ jsxs(AgentToolContext.Provider, { value: toolContextValue, children: [
1137
+ children,
1138
+ Array.from(activeAgentsRef.current.entries()).map(([agentId, { agentUrl, accessKey }]) => /* @__PURE__ */ jsx(
1139
+ AgentRunnerInstance,
1140
+ {
1141
+ agentId,
1142
+ agentUrl,
1143
+ accessKey
1144
+ },
1145
+ agentId
1146
+ ))
1147
+ ] }) });
1148
+ }
1149
+ function AgentRunnerInstance({
1150
+ agentId,
1151
+ agentUrl,
1152
+ accessKey
1153
+ }) {
1154
+ const agent = useAgent(agentId, agentUrl, accessKey);
1155
+ const context = useContext3(AgentContext);
1156
+ useEffect3(() => {
1157
+ if (context) {
1158
+ context.registerRunner(agentId, agent);
1159
+ }
1160
+ }, [agentId, agent, context]);
1161
+ if (!context) return null;
1162
+ return null;
1163
+ }
1164
+ function useAgentContext(agentId, agentUrl, accessKey) {
1165
+ const context = useContext3(AgentContext);
1166
+ if (!context) {
1167
+ throw new Error("useAgentContext must be used within AgentContextProvider");
1168
+ }
1169
+ const { initializeAgent, getRunner, listenersRef } = context;
1170
+ useEffect3(() => {
1171
+ initializeAgent(agentId, agentUrl, accessKey);
1172
+ }, [agentId, agentUrl, initializeAgent]);
1173
+ const [runner, setRunner] = useState3(() => getRunner(agentId));
1174
+ useEffect3(() => {
1175
+ const currentRunner = getRunner(agentId);
1176
+ if (currentRunner !== runner) {
1177
+ setRunner(currentRunner);
1178
+ }
1179
+ const callback = () => {
1180
+ setRunner(getRunner(agentId));
1181
+ };
1182
+ if (!listenersRef.current.has(agentId)) {
1183
+ listenersRef.current.set(agentId, /* @__PURE__ */ new Set());
1184
+ }
1185
+ listenersRef.current.get(agentId)?.add(callback);
1186
+ return () => {
1187
+ listenersRef.current.get(agentId)?.delete(callback);
1188
+ };
1189
+ }, [agentId, getRunner]);
1190
+ const placeholder = useMemo3(
1191
+ () => ({
1192
+ messages: [],
1193
+ inProgress: false,
1194
+ sessionId: "",
1195
+ sessions: [],
1196
+ debugData: {},
1197
+ handleSend: async () => {
1198
+ },
1199
+ resumeTool: async () => {
1200
+ },
1201
+ switchSession: () => {
1202
+ },
1203
+ deleteSession: () => {
1204
+ },
1205
+ addOptimisticMessage: () => {
1206
+ },
1207
+ abort: () => {
1208
+ }
1209
+ }),
1210
+ []
1211
+ );
1212
+ return runner || placeholder;
1213
+ }
1214
+ function useAgentGlobalState() {
1215
+ const context = useContext3(AgentContext);
1216
+ if (!context) {
1217
+ throw new Error("useAgentGlobalState must be used within AgentContextProvider");
1218
+ }
1219
+ return {
1220
+ allSessions: context.allSessions,
1221
+ setAllSessions: context.setAllSessions,
1222
+ debugData: context.debugData,
1223
+ setDebugData: context.setDebugData
1224
+ };
1225
+ }
1226
+
1227
+ // src/react/use-client-tool.ts
1228
+ import { useContext as useContext4, useEffect as useEffect4, useState as useState4, useCallback as useCallback6 } from "react";
1229
+ function useClientTool(agentId, config) {
1230
+ const context = useContext4(AgentToolContext);
1231
+ if (!context) {
1232
+ throw new Error("useClientTool must be used within <AgentContextProvider>");
1233
+ }
1234
+ useEffect4(() => {
1235
+ context.registerTool(agentId, config);
1236
+ return () => {
1237
+ context.unregisterTool(agentId, config.name);
1238
+ };
1239
+ }, [agentId, config.name, config.handler, config.render, config.parameters]);
1240
+ }
1241
+ function ToolRenderer({ agentId, part }) {
1242
+ const context = useContext4(AgentToolContext);
1243
+ const [localStatus, setLocalStatus] = useState4(part.status || "pending");
1244
+ useEffect4(() => {
1245
+ setLocalStatus(part.status || "pending");
1246
+ }, [part.status]);
1247
+ if (!context) {
1248
+ throw new Error("ToolRenderer must be used within <AgentContextProvider>");
1249
+ }
1250
+ const tool = context.getTool(agentId, part.toolName);
1251
+ const handleSubmit = useCallback6(
1252
+ (result) => {
1253
+ if (localStatus === "submitted") return;
1254
+ setLocalStatus("submitted");
1255
+ context.resumeTool(agentId, part.callId, result);
1256
+ },
1257
+ [agentId, part.callId, localStatus, context]
1258
+ );
1259
+ if (!tool?.render) {
1260
+ return null;
1261
+ }
1262
+ return tool.render({
1263
+ inputs: part.inputs,
1264
+ submit: part.paused ? handleSubmit : () => {
1265
+ },
1266
+ status: localStatus,
1267
+ result: part.result
1268
+ });
1269
+ }
1270
+ export {
1271
+ AGENT_DEBUG_DATA_KEY,
1272
+ AGENT_SESSIONS_KEY,
1273
+ AgentContextProvider,
1274
+ AgentToolContext,
1275
+ DEFAULT_SESSION_NAME,
1276
+ TEMPORARY_SESSION_ID,
1277
+ ToolRenderer,
1278
+ cleanSchema,
1279
+ tryParseJSON,
1280
+ updateAgentMessageParts,
1281
+ useAgent,
1282
+ useAgentContext,
1283
+ useAgentGlobalState,
1284
+ useClientTool
1285
+ };
1286
+ //# sourceMappingURL=index.js.map