buildship-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1116 @@
1
+ // src/agent-context.tsx
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useCallback as useCallback4,
6
+ useRef as useRef3,
7
+ useState as useState3,
8
+ useEffect as useEffect3,
9
+ useMemo as useMemo2
10
+ } from "react";
11
+
12
+ // src/use-agent.ts
13
+ import { useCallback as useCallback2, useRef as useRef2, useState, useEffect } from "react";
14
+ import { fetchEventSource } from "@microsoft/fetch-event-source";
15
+
16
+ // src/constants.ts
17
+ var AGENT_SESSIONS_KEY = "buildship:agent:conversations";
18
+ var AGENT_DEBUG_DATA_KEY = "buildship:agent:debug";
19
+ var DEFAULT_SESSION_NAME = "New Chat";
20
+ var TEMPORARY_SESSION_ID = "sess_temp";
21
+
22
+ // src/session-utils.ts
23
+ import { useCallback, useMemo, useRef } from "react";
24
+ var useSessionUtils = (agentId, allSessions, setAllSessions, currentSessionId, setCurrentSessionId, messagesRef) => {
25
+ const agentSessions = useMemo(() => allSessions[agentId] || {}, [agentId, allSessions]);
26
+ const syncSessionRef = useRef();
27
+ syncSessionRef.current = (updatedMessages) => {
28
+ if (!currentSessionId || currentSessionId === TEMPORARY_SESSION_ID) {
29
+ return;
30
+ }
31
+ setAllSessions((prev) => ({
32
+ ...prev,
33
+ [agentId]: {
34
+ ...prev[agentId],
35
+ [currentSessionId]: {
36
+ ...prev[agentId]?.[currentSessionId],
37
+ messages: updatedMessages ?? messagesRef.current,
38
+ updatedAt: Date.now()
39
+ }
40
+ }
41
+ }));
42
+ };
43
+ const getInitialSessionId = () => {
44
+ const sessions = Object.values(agentSessions);
45
+ if (sessions.length > 0) {
46
+ return sessions.sort((a, b) => b.updatedAt - a.updatedAt)[0].id;
47
+ }
48
+ return TEMPORARY_SESSION_ID;
49
+ };
50
+ const switchSession = useCallback(
51
+ (sessionId = TEMPORARY_SESSION_ID) => {
52
+ setCurrentSessionId(sessionId);
53
+ },
54
+ [setCurrentSessionId]
55
+ );
56
+ const deleteSession = useCallback(
57
+ (sessionId) => {
58
+ if (!sessionId || sessionId === TEMPORARY_SESSION_ID) {
59
+ return;
60
+ }
61
+ setAllSessions((prev) => {
62
+ const updatedAgentSessions = { ...prev[agentId] };
63
+ delete updatedAgentSessions[sessionId];
64
+ return {
65
+ ...prev,
66
+ [agentId]: updatedAgentSessions
67
+ };
68
+ });
69
+ if (sessionId === currentSessionId) {
70
+ const remainingSessions = Object.values(agentSessions).filter((s) => s.id !== sessionId);
71
+ if (remainingSessions.length > 0) {
72
+ const mostRecent = remainingSessions.sort((a, b) => b.updatedAt - a.updatedAt)[0];
73
+ setCurrentSessionId(mostRecent.id);
74
+ } else {
75
+ setCurrentSessionId(TEMPORARY_SESSION_ID);
76
+ }
77
+ }
78
+ },
79
+ [agentId, currentSessionId, agentSessions, setAllSessions, setCurrentSessionId]
80
+ );
81
+ const sessionsList = useMemo(
82
+ () => Object.values(agentSessions).sort((a, b) => b.updatedAt - a.updatedAt),
83
+ [agentSessions]
84
+ );
85
+ const createSessionFromResponse = (sessionId, sessionName, currentMessages) => {
86
+ setAllSessions((prev) => ({
87
+ ...prev,
88
+ [agentId]: {
89
+ ...prev[agentId],
90
+ [sessionId]: {
91
+ id: sessionId,
92
+ createdAt: Date.now(),
93
+ updatedAt: Date.now(),
94
+ messages: currentMessages,
95
+ name: sessionName
96
+ }
97
+ }
98
+ }));
99
+ };
100
+ return {
101
+ agentSessions,
102
+ syncSessionRef,
103
+ getInitialSessionId,
104
+ switchSession,
105
+ deleteSession,
106
+ sessionsList,
107
+ createSessionFromResponse
108
+ };
109
+ };
110
+
111
+ // src/debug-handlers.ts
112
+ var createDebugHandlers = (setDebugData) => {
113
+ const handleDebugToolExecutionStarted = (parsed) => {
114
+ const executionId = parsed.meta.executionId;
115
+ setDebugData((prev) => ({
116
+ ...prev,
117
+ [executionId]: [
118
+ ...prev[executionId] || [],
119
+ {
120
+ itemType: "tool_call",
121
+ toolId: parsed.data.toolId || "unknown",
122
+ toolCallId: parsed.data.toolCallId,
123
+ status: "progress"
124
+ }
125
+ ]
126
+ }));
127
+ };
128
+ const handleDebugToolExecutionInputs = (parsed) => {
129
+ const executionId = parsed.meta.executionId;
130
+ const toolCallId = parsed.data.toolCallId;
131
+ setDebugData((prev) => {
132
+ const currentData = [...prev[executionId] || []];
133
+ for (let i = currentData.length - 1; i >= 0; i--) {
134
+ if (currentData[i].itemType === "tool_call") {
135
+ const toolItem = currentData[i];
136
+ if (toolItem.toolCallId === toolCallId) {
137
+ currentData[i] = {
138
+ ...toolItem,
139
+ inputs: parsed.data.value
140
+ };
141
+ break;
142
+ }
143
+ }
144
+ }
145
+ return {
146
+ ...prev,
147
+ [executionId]: currentData
148
+ };
149
+ });
150
+ };
151
+ const handleDebugToolExecutionFinished = (parsed) => {
152
+ const executionId = parsed.meta.executionId;
153
+ const toolCallId = parsed.data.toolCallId;
154
+ setDebugData((prev) => {
155
+ const currentData = [...prev[executionId] || []];
156
+ for (let i = currentData.length - 1; i >= 0; i--) {
157
+ if (currentData[i].itemType === "tool_call") {
158
+ const toolItem = currentData[i];
159
+ if (toolItem.toolCallId === toolCallId) {
160
+ currentData[i] = {
161
+ ...toolItem,
162
+ status: "complete",
163
+ output: parsed.data.value
164
+ };
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ return {
170
+ ...prev,
171
+ [executionId]: currentData
172
+ };
173
+ });
174
+ };
175
+ const handleDebugToolExecutionError = (parsed) => {
176
+ const executionId = parsed.meta.executionId;
177
+ const toolCallId = parsed.data.toolCallId;
178
+ setDebugData((prev) => {
179
+ const currentData = [...prev[executionId] || []];
180
+ for (let i = currentData.length - 1; i >= 0; i--) {
181
+ if (currentData[i].itemType === "tool_call") {
182
+ const toolItem = currentData[i];
183
+ if (toolItem.toolCallId === toolCallId) {
184
+ currentData[i] = {
185
+ ...toolItem,
186
+ status: "error",
187
+ output: parsed.data.value
188
+ };
189
+ break;
190
+ }
191
+ }
192
+ }
193
+ return {
194
+ ...prev,
195
+ [executionId]: currentData
196
+ };
197
+ });
198
+ };
199
+ const handleDebugToolLog = (parsed) => {
200
+ const executionId = parsed.meta.executionId;
201
+ const toolCallId = parsed.data.toolCallId;
202
+ setDebugData((prev) => {
203
+ const currentData = [...prev[executionId] || []];
204
+ for (let i = currentData.length - 1; i >= 0; i--) {
205
+ if (currentData[i].itemType === "tool_call") {
206
+ const toolItem = currentData[i];
207
+ if (toolItem.toolCallId === toolCallId) {
208
+ const currentLogs = toolItem.logs || [];
209
+ currentData[i] = {
210
+ ...toolItem,
211
+ logs: [...currentLogs, parsed.data.value[0]]
212
+ };
213
+ break;
214
+ }
215
+ }
216
+ }
217
+ return {
218
+ ...prev,
219
+ [executionId]: currentData
220
+ };
221
+ });
222
+ };
223
+ let lastReasoningIndex = -1;
224
+ const handleDebugReasoningStep = (parsed) => {
225
+ const executionId = parsed.meta.executionId;
226
+ const { delta, index } = parsed.data;
227
+ setDebugData((prev) => {
228
+ const currentData = [...prev[executionId] || []];
229
+ if (lastReasoningIndex < index) {
230
+ currentData.push({ itemType: "reasoning", reasoning: "" });
231
+ lastReasoningIndex = index;
232
+ }
233
+ for (let i = currentData.length - 1; i >= 0; i--) {
234
+ if (currentData[i].itemType === "reasoning") {
235
+ currentData[i] = {
236
+ itemType: "reasoning",
237
+ reasoning: currentData[i].reasoning + delta
238
+ };
239
+ break;
240
+ }
241
+ }
242
+ return {
243
+ ...prev,
244
+ [executionId]: currentData
245
+ };
246
+ });
247
+ };
248
+ const handleAgentHandoff = (parsed) => {
249
+ const executionId = parsed.meta.executionId;
250
+ setDebugData((prev) => ({
251
+ ...prev,
252
+ [executionId]: [
253
+ ...prev[executionId] || [],
254
+ {
255
+ itemType: "handoff",
256
+ agentName: parsed.data.agentName
257
+ }
258
+ ]
259
+ }));
260
+ };
261
+ const handleMCPToolCallStart = (parsed) => {
262
+ const executionId = parsed.meta.executionId;
263
+ setDebugData((prev) => ({
264
+ ...prev,
265
+ [executionId]: [
266
+ ...prev[executionId] || [],
267
+ {
268
+ itemType: "mcp_tool_call",
269
+ toolName: parsed.data.toolName,
270
+ serverName: parsed.data.serverName,
271
+ callId: parsed.data.callId,
272
+ status: "progress",
273
+ inputs: parsed.data.inputs
274
+ }
275
+ ]
276
+ }));
277
+ };
278
+ const handleMCPToolCallEnd = (parsed) => {
279
+ const executionId = parsed.meta.executionId;
280
+ const callId = parsed.data.callId;
281
+ setDebugData((prev) => {
282
+ const currentData = [...prev[executionId] || []];
283
+ for (let i = currentData.length - 1; i >= 0; i--) {
284
+ if (currentData[i].itemType === "mcp_tool_call") {
285
+ const mcpToolItem = currentData[i];
286
+ if (mcpToolItem.callId === callId) {
287
+ currentData[i] = {
288
+ ...mcpToolItem,
289
+ status: parsed.data.error ? "error" : "complete",
290
+ output: parsed.data.result,
291
+ error: parsed.data.error
292
+ };
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ return {
298
+ ...prev,
299
+ [executionId]: currentData
300
+ };
301
+ });
302
+ };
303
+ const handleDebugEvent = (parsed) => {
304
+ switch (parsed.type) {
305
+ case "debug_tool_execution_started":
306
+ handleDebugToolExecutionStarted(parsed);
307
+ break;
308
+ case "debug_tool_execution_inputs":
309
+ handleDebugToolExecutionInputs(parsed);
310
+ break;
311
+ case "debug_tool_execution_finished":
312
+ handleDebugToolExecutionFinished(parsed);
313
+ break;
314
+ case "debug_tool_execution_error":
315
+ handleDebugToolExecutionError(parsed);
316
+ break;
317
+ case "debug_tool_log":
318
+ handleDebugToolLog(parsed);
319
+ break;
320
+ case "llm_reasoning_delta":
321
+ handleDebugReasoningStep(parsed);
322
+ break;
323
+ case "agent_handoff":
324
+ handleAgentHandoff(parsed);
325
+ break;
326
+ case "mcp_tool_call_start":
327
+ handleMCPToolCallStart(parsed);
328
+ break;
329
+ case "mcp_tool_call_end":
330
+ handleMCPToolCallEnd(parsed);
331
+ break;
332
+ }
333
+ };
334
+ return {
335
+ handleDebugEvent
336
+ };
337
+ };
338
+
339
+ // src/utils/schema.ts
340
+ function cleanSchema(obj) {
341
+ if (Array.isArray(obj)) {
342
+ return obj.map(cleanSchema);
343
+ } else if (obj !== null && typeof obj === "object") {
344
+ const newObj = {};
345
+ const currentObj = obj;
346
+ for (const key in currentObj) {
347
+ if (key === "propertyNames" || key === "$schema") {
348
+ continue;
349
+ }
350
+ newObj[key] = cleanSchema(currentObj[key]);
351
+ }
352
+ if (newObj.type === "object") {
353
+ if (newObj.additionalProperties === void 0 || newObj.additionalProperties && typeof newObj.additionalProperties === "object" && Object.keys(newObj.additionalProperties).length === 0) {
354
+ newObj.additionalProperties = false;
355
+ }
356
+ if (newObj.additionalProperties === false && newObj.properties) {
357
+ newObj.required = Object.keys(newObj.properties);
358
+ }
359
+ }
360
+ return newObj;
361
+ }
362
+ return obj;
363
+ }
364
+ function tryParseJSON(value) {
365
+ if (typeof value === "string") {
366
+ try {
367
+ return JSON.parse(value);
368
+ } catch {
369
+ return value;
370
+ }
371
+ }
372
+ return value;
373
+ }
374
+
375
+ // src/utils/message.ts
376
+ function updateAgentMessageParts(parts, newPart) {
377
+ const sorted = [...parts, newPart].sort((a, b) => {
378
+ const aStart = a.type === "text" ? a.firstSequence : a.sequence;
379
+ const bStart = b.type === "text" ? b.firstSequence : b.sequence;
380
+ return aStart - bStart;
381
+ });
382
+ return sorted.reduce((acc, current) => {
383
+ const last = acc[acc.length - 1];
384
+ const canMerge = last?.type === "text" && current.type === "text" && current.firstSequence === last.lastSequence + 1;
385
+ if (canMerge) {
386
+ acc[acc.length - 1] = {
387
+ ...last,
388
+ text: last.text + current.text,
389
+ lastSequence: current.lastSequence
390
+ };
391
+ return acc;
392
+ }
393
+ acc.push(current);
394
+ return acc;
395
+ }, []);
396
+ }
397
+
398
+ // src/use-agent.ts
399
+ function useAgent(agentId, agentUrl, accessKey) {
400
+ const { allSessions, setAllSessions, debugData, setDebugData } = useAgentGlobalState();
401
+ const [inProgress, setInProgress] = useState(false);
402
+ const [messages, setMessages] = useState([]);
403
+ const [pendingToolCalls, setPendingToolCalls] = useState(/* @__PURE__ */ new Map());
404
+ const [isPaused, setIsPaused] = useState(false);
405
+ const messagesRef = useRef2([]);
406
+ const clientToolsRef = useRef2([]);
407
+ const [currentSessionId, setCurrentSessionId] = useState(TEMPORARY_SESSION_ID);
408
+ const sessionUtils = useSessionUtils(
409
+ agentId,
410
+ allSessions,
411
+ setAllSessions,
412
+ currentSessionId,
413
+ setCurrentSessionId,
414
+ messagesRef
415
+ );
416
+ useEffect(() => {
417
+ const initialSessionId = sessionUtils.getInitialSessionId();
418
+ setCurrentSessionId(initialSessionId);
419
+ }, []);
420
+ const debugHandlers = createDebugHandlers(setDebugData);
421
+ useEffect(() => {
422
+ messagesRef.current = messages;
423
+ }, [messages]);
424
+ useEffect(() => {
425
+ if (inProgress) {
426
+ return;
427
+ }
428
+ const session = sessionUtils.agentSessions[currentSessionId];
429
+ if (session) {
430
+ setMessages(session.messages);
431
+ } else {
432
+ setMessages([]);
433
+ }
434
+ }, [currentSessionId, sessionUtils.agentSessions, agentId, inProgress]);
435
+ useEffect(() => {
436
+ return () => {
437
+ if (messagesRef.current.length > 0 && sessionUtils.syncSessionRef.current) {
438
+ sessionUtils.syncSessionRef.current();
439
+ }
440
+ };
441
+ }, []);
442
+ const controller = useRef2();
443
+ const runAgent = useCallback2(
444
+ async (input, options) => {
445
+ setInProgress(true);
446
+ if (controller.current) {
447
+ controller.current.abort();
448
+ }
449
+ controller.current = new AbortController();
450
+ const headers = {
451
+ "Content-Type": "application/json",
452
+ ...options?.additionalHeaders || {},
453
+ ...accessKey ? { Authorization: `Bearer ${accessKey}` } : {}
454
+ };
455
+ if (currentSessionId) {
456
+ headers["x-buildship-agent-session-id"] = currentSessionId;
457
+ }
458
+ const body = {
459
+ stream: true,
460
+ input,
461
+ context: options?.context || {},
462
+ testBuildId: options?.testBuildId,
463
+ tools: options?.tools?.map((t) => ({
464
+ ...t,
465
+ parameters: cleanSchema(t.parameters)
466
+ })),
467
+ clientTools: options?.clientTools?.map((t) => ({
468
+ ...t,
469
+ parameters: cleanSchema(t.parameters)
470
+ }))
471
+ };
472
+ try {
473
+ await fetchEventSource(agentUrl, {
474
+ method: "POST",
475
+ headers,
476
+ signal: controller.current.signal,
477
+ openWhenHidden: true,
478
+ body: JSON.stringify(body),
479
+ onopen: async (resp) => {
480
+ if (resp.status >= 400) {
481
+ console.log(`AI onopen error with status: ${resp.status}`);
482
+ if (resp.status === 404) {
483
+ throw new Error(`Not Found (${resp.statusText})`);
484
+ }
485
+ throw new Error(`Error status ${resp.status}`);
486
+ }
487
+ if (!currentSessionId || currentSessionId === TEMPORARY_SESSION_ID) {
488
+ const sessionId = resp.headers.get("x-buildship-agent-session-id");
489
+ if (sessionId) {
490
+ const currentMessages = messagesRef.current;
491
+ const sessionName = resp.headers.get("x-buildship-agent-session-name") || DEFAULT_SESSION_NAME;
492
+ sessionUtils.createSessionFromResponse(sessionId, sessionName, currentMessages);
493
+ setCurrentSessionId(sessionId);
494
+ }
495
+ }
496
+ const executionId = resp.headers.get("x-buildship-agent-execution-id");
497
+ if (executionId) {
498
+ setMessages((prev) => {
499
+ const lastMessage = prev[prev.length - 1];
500
+ if (lastMessage?.role === "user") {
501
+ const updatedMessage = {
502
+ ...lastMessage,
503
+ executionId
504
+ };
505
+ const updatedMessages = [...prev.slice(0, -1), updatedMessage];
506
+ if (sessionUtils.syncSessionRef.current) {
507
+ sessionUtils.syncSessionRef.current(updatedMessages);
508
+ }
509
+ return updatedMessages;
510
+ }
511
+ return prev;
512
+ });
513
+ }
514
+ },
515
+ onmessage: (event) => {
516
+ try {
517
+ const parsed = JSON.parse(event.data);
518
+ if (parsed.type === "llm_text_delta") {
519
+ setMessages((prev) => {
520
+ const lastMessage = prev[prev.length - 1];
521
+ const sequence = parsed.meta.sequence;
522
+ const newPart = {
523
+ type: "text",
524
+ text: parsed.data,
525
+ firstSequence: sequence,
526
+ lastSequence: sequence
527
+ };
528
+ let updatedMessages;
529
+ if (lastMessage?.role === "agent") {
530
+ const updatedMessage = {
531
+ ...lastMessage,
532
+ content: lastMessage.content + parsed.data,
533
+ // content is still useful for simple displays
534
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
535
+ };
536
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
537
+ } else {
538
+ const updatedMessage = {
539
+ role: "agent",
540
+ content: parsed.data,
541
+ parts: [newPart],
542
+ executionId: parsed.meta.executionId
543
+ };
544
+ updatedMessages = [...prev, updatedMessage];
545
+ }
546
+ if (sessionUtils.syncSessionRef.current) {
547
+ sessionUtils.syncSessionRef.current(updatedMessages);
548
+ }
549
+ return updatedMessages;
550
+ });
551
+ } else if (parsed.type === "client_tool_call") {
552
+ const { toolName, callId, inputs } = parsed.data;
553
+ const toolDefinition = clientToolsRef.current.find((t) => t.name === toolName);
554
+ const requiresResult = toolDefinition?.requiresResult ?? false;
555
+ if (requiresResult) {
556
+ setPendingToolCalls((prev) => {
557
+ const updated = new Map(prev);
558
+ updated.set(callId, {
559
+ toolName,
560
+ callId,
561
+ inputs: tryParseJSON(inputs),
562
+ requiresResult
563
+ });
564
+ return updated;
565
+ });
566
+ }
567
+ setMessages((prev) => {
568
+ const lastMessage = prev[prev.length - 1];
569
+ const newPart = {
570
+ type: "widget",
571
+ toolName,
572
+ callId,
573
+ inputs: tryParseJSON(inputs),
574
+ sequence: parsed.meta.sequence
575
+ };
576
+ let updatedMessages;
577
+ if (lastMessage?.role === "agent") {
578
+ const updatedMessage = {
579
+ ...lastMessage,
580
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
581
+ };
582
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
583
+ } else {
584
+ const updatedMessage = {
585
+ role: "agent",
586
+ content: "",
587
+ parts: [newPart],
588
+ executionId: parsed.meta.executionId
589
+ };
590
+ updatedMessages = [...prev, updatedMessage];
591
+ }
592
+ if (sessionUtils.syncSessionRef.current) {
593
+ sessionUtils.syncSessionRef.current(updatedMessages);
594
+ }
595
+ return updatedMessages;
596
+ });
597
+ } else if (parsed.type.startsWith("debug_") || parsed.type === "llm_reasoning_delta" || parsed.type === "agent_handoff" || parsed.type === "mcp_tool_call_start" || parsed.type === "mcp_tool_call_end") {
598
+ debugHandlers.handleDebugEvent(parsed);
599
+ }
600
+ } catch (e) {
601
+ console.log("Failed to parse agent message", e);
602
+ }
603
+ },
604
+ onclose: () => {
605
+ console.log("Agent closed");
606
+ setInProgress(false);
607
+ setPendingToolCalls((currentPending) => {
608
+ if (currentPending.size > 0) {
609
+ setIsPaused(true);
610
+ }
611
+ return currentPending;
612
+ });
613
+ if (sessionUtils.syncSessionRef.current) {
614
+ sessionUtils.syncSessionRef.current();
615
+ }
616
+ },
617
+ onerror: (e) => {
618
+ console.log("Agent error", e);
619
+ setInProgress(false);
620
+ if (sessionUtils.syncSessionRef.current) {
621
+ sessionUtils.syncSessionRef.current(messagesRef.current);
622
+ }
623
+ throw new Error("Failed to execute agent");
624
+ }
625
+ });
626
+ } catch (error) {
627
+ console.log("Agent execution failed", error);
628
+ setInProgress(false);
629
+ if (sessionUtils.syncSessionRef.current) {
630
+ sessionUtils.syncSessionRef.current(messagesRef.current);
631
+ }
632
+ throw error;
633
+ }
634
+ },
635
+ [agentUrl, accessKey, currentSessionId, sessionUtils, debugHandlers]
636
+ );
637
+ const resumeAgent = useCallback2(
638
+ async (toolResults, options) => {
639
+ if (!currentSessionId || currentSessionId === TEMPORARY_SESSION_ID) {
640
+ throw new Error("Cannot resume: no active session");
641
+ }
642
+ setInProgress(true);
643
+ setIsPaused(false);
644
+ if (controller.current) {
645
+ controller.current.abort();
646
+ }
647
+ controller.current = new AbortController();
648
+ const headers = {
649
+ "Content-Type": "application/json",
650
+ ...options?.additionalHeaders || {},
651
+ ...accessKey ? { Authorization: `Bearer ${accessKey}` } : {}
652
+ };
653
+ const baseUrl = agentUrl.replace(/\/executeAgent\/.*$/, "");
654
+ const resumeUrl = `${baseUrl}/resumeAgent/${currentSessionId}`;
655
+ const body = {
656
+ stream: true,
657
+ input: options?.input || "",
658
+ context: options?.context || {},
659
+ clientToolResults: toolResults
660
+ };
661
+ try {
662
+ await fetchEventSource(resumeUrl, {
663
+ method: "POST",
664
+ headers,
665
+ signal: controller.current.signal,
666
+ openWhenHidden: true,
667
+ body: JSON.stringify(body),
668
+ onopen: async (resp) => {
669
+ if (resp.status >= 400) {
670
+ console.log(`AI onopen error with status: ${resp.status}`);
671
+ if (resp.status === 404) {
672
+ throw new Error(`Session not found (${resp.statusText})`);
673
+ }
674
+ throw new Error(`Error status ${resp.status}`);
675
+ }
676
+ const executionId = resp.headers.get("x-buildship-agent-execution-id");
677
+ if (executionId && options?.input) {
678
+ setMessages((prev) => {
679
+ const userMessage = {
680
+ role: "user",
681
+ content: options.input || "",
682
+ executionId
683
+ };
684
+ const updatedMessages = [...prev, userMessage];
685
+ if (sessionUtils.syncSessionRef.current) {
686
+ sessionUtils.syncSessionRef.current(updatedMessages);
687
+ }
688
+ return updatedMessages;
689
+ });
690
+ }
691
+ },
692
+ onmessage: (event) => {
693
+ try {
694
+ const parsed = JSON.parse(event.data);
695
+ if (parsed.type === "llm_text_delta") {
696
+ setMessages((prev) => {
697
+ const lastMessage = prev[prev.length - 1];
698
+ const sequence = parsed.meta.sequence;
699
+ const newPart = {
700
+ type: "text",
701
+ text: parsed.data,
702
+ firstSequence: sequence,
703
+ lastSequence: sequence
704
+ };
705
+ let updatedMessages;
706
+ if (lastMessage?.role === "agent") {
707
+ const updatedMessage = {
708
+ ...lastMessage,
709
+ content: lastMessage.content + parsed.data,
710
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
711
+ };
712
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
713
+ } else {
714
+ const updatedMessage = {
715
+ role: "agent",
716
+ content: parsed.data,
717
+ parts: [newPart],
718
+ executionId: parsed.meta.executionId
719
+ };
720
+ updatedMessages = [...prev, updatedMessage];
721
+ }
722
+ if (sessionUtils.syncSessionRef.current) {
723
+ sessionUtils.syncSessionRef.current(updatedMessages);
724
+ }
725
+ return updatedMessages;
726
+ });
727
+ } else if (parsed.type === "client_tool_call") {
728
+ const { toolName, callId, inputs } = parsed.data;
729
+ const toolDefinition = clientToolsRef.current.find((t) => t.name === toolName);
730
+ const requiresResult = toolDefinition?.requiresResult ?? false;
731
+ if (requiresResult) {
732
+ setPendingToolCalls((prev) => {
733
+ const updated = new Map(prev);
734
+ updated.set(callId, {
735
+ toolName,
736
+ callId,
737
+ inputs: tryParseJSON(inputs),
738
+ requiresResult
739
+ });
740
+ return updated;
741
+ });
742
+ }
743
+ setMessages((prev) => {
744
+ const lastMessage = prev[prev.length - 1];
745
+ const newPart = {
746
+ type: "widget",
747
+ toolName,
748
+ callId,
749
+ inputs: tryParseJSON(inputs),
750
+ sequence: parsed.meta.sequence
751
+ };
752
+ let updatedMessages;
753
+ if (lastMessage?.role === "agent") {
754
+ const updatedMessage = {
755
+ ...lastMessage,
756
+ parts: updateAgentMessageParts(lastMessage.parts || [], newPart)
757
+ };
758
+ updatedMessages = [...prev.slice(0, -1), updatedMessage];
759
+ } else {
760
+ const updatedMessage = {
761
+ role: "agent",
762
+ content: "",
763
+ parts: [newPart],
764
+ executionId: parsed.meta.executionId
765
+ };
766
+ updatedMessages = [...prev, updatedMessage];
767
+ }
768
+ if (sessionUtils.syncSessionRef.current) {
769
+ sessionUtils.syncSessionRef.current(updatedMessages);
770
+ }
771
+ return updatedMessages;
772
+ });
773
+ } else if (parsed.type.startsWith("debug_") || parsed.type === "llm_reasoning_delta" || parsed.type === "agent_handoff" || parsed.type === "mcp_tool_call_start" || parsed.type === "mcp_tool_call_end") {
774
+ debugHandlers.handleDebugEvent(parsed);
775
+ }
776
+ } catch (e) {
777
+ console.log("Failed to parse agent message", e);
778
+ }
779
+ },
780
+ onclose: () => {
781
+ console.log("Agent resume closed");
782
+ setInProgress(false);
783
+ setPendingToolCalls((currentPending) => {
784
+ if (currentPending.size > 0) {
785
+ setIsPaused(true);
786
+ }
787
+ return currentPending;
788
+ });
789
+ if (sessionUtils.syncSessionRef.current) {
790
+ sessionUtils.syncSessionRef.current();
791
+ }
792
+ },
793
+ onerror: (e) => {
794
+ console.log("Agent resume error", e);
795
+ setInProgress(false);
796
+ if (sessionUtils.syncSessionRef.current) {
797
+ sessionUtils.syncSessionRef.current(messagesRef.current);
798
+ }
799
+ throw new Error("Failed to resume agent");
800
+ }
801
+ });
802
+ } catch (error) {
803
+ console.log("Agent resume execution failed", error);
804
+ setInProgress(false);
805
+ if (sessionUtils.syncSessionRef.current) {
806
+ sessionUtils.syncSessionRef.current(messagesRef.current);
807
+ }
808
+ throw error;
809
+ }
810
+ },
811
+ [agentUrl, accessKey, currentSessionId, sessionUtils, debugHandlers]
812
+ );
813
+ const handleSend = useCallback2(
814
+ async (input, options) => {
815
+ if (options?.clientTools) {
816
+ clientToolsRef.current = options.clientTools;
817
+ }
818
+ setPendingToolCalls(/* @__PURE__ */ new Map());
819
+ setIsPaused(false);
820
+ const userMessage = {
821
+ role: "user",
822
+ content: input,
823
+ executionId: Date.now().toString()
824
+ };
825
+ if (!options?.skipUserMessage) {
826
+ setMessages((prev) => {
827
+ const updatedMessages = [...prev, userMessage];
828
+ if (sessionUtils.syncSessionRef.current) {
829
+ sessionUtils.syncSessionRef.current(updatedMessages);
830
+ }
831
+ return updatedMessages;
832
+ });
833
+ }
834
+ try {
835
+ await runAgent(input, options);
836
+ } catch (error) {
837
+ if (!options?.skipUserMessage) {
838
+ setMessages((prev) => {
839
+ const updatedMessages = prev.some((m) => m === userMessage) ? prev : [...prev, userMessage];
840
+ if (sessionUtils.syncSessionRef.current) {
841
+ sessionUtils.syncSessionRef.current(updatedMessages);
842
+ }
843
+ return updatedMessages;
844
+ });
845
+ }
846
+ throw error;
847
+ }
848
+ },
849
+ [runAgent, sessionUtils.syncSessionRef]
850
+ );
851
+ const addOptimisticMessage = useCallback2(
852
+ (input) => {
853
+ const userMessage = {
854
+ role: "user",
855
+ content: input,
856
+ executionId: Date.now().toString()
857
+ };
858
+ setMessages((prev) => {
859
+ const updatedMessages = [...prev, userMessage];
860
+ if (sessionUtils.syncSessionRef.current) {
861
+ sessionUtils.syncSessionRef.current(updatedMessages);
862
+ }
863
+ return updatedMessages;
864
+ });
865
+ },
866
+ [sessionUtils.syncSessionRef]
867
+ );
868
+ const submitToolResults = useCallback2(
869
+ async (toolResults, options) => {
870
+ setPendingToolCalls((prev) => {
871
+ const updated = new Map(prev);
872
+ toolResults.forEach((result) => {
873
+ updated.delete(result.callId);
874
+ });
875
+ return updated;
876
+ });
877
+ try {
878
+ await resumeAgent(toolResults, options);
879
+ } catch (error) {
880
+ console.error("Failed to submit tool results:", error);
881
+ throw error;
882
+ }
883
+ },
884
+ [resumeAgent]
885
+ );
886
+ const abort = useCallback2(() => {
887
+ if (controller.current) {
888
+ controller.current.abort();
889
+ }
890
+ setInProgress(false);
891
+ if (sessionUtils.syncSessionRef.current) {
892
+ sessionUtils.syncSessionRef.current(messagesRef.current);
893
+ }
894
+ }, [sessionUtils.syncSessionRef]);
895
+ return {
896
+ inProgress,
897
+ messages,
898
+ handleSend,
899
+ addOptimisticMessage,
900
+ submitToolResults,
901
+ abort,
902
+ sessionId: currentSessionId,
903
+ switchSession: sessionUtils.switchSession,
904
+ deleteSession: sessionUtils.deleteSession,
905
+ sessions: sessionUtils.sessionsList,
906
+ debugData,
907
+ pendingToolCalls: Array.from(pendingToolCalls.values()),
908
+ isPaused
909
+ };
910
+ }
911
+
912
+ // src/utils/use-synced-local-storage.ts
913
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback3 } from "react";
914
+ function parseJSON(value, defaultValue) {
915
+ if (value === null) return defaultValue;
916
+ try {
917
+ return JSON.parse(value);
918
+ } catch {
919
+ return defaultValue;
920
+ }
921
+ }
922
+ function useSyncedLocalStorage(key, initialValue) {
923
+ const [storedValue, setStoredValue] = useState2(() => {
924
+ if (typeof window === "undefined") {
925
+ return initialValue;
926
+ }
927
+ try {
928
+ const item = window.localStorage.getItem(key);
929
+ return parseJSON(item, initialValue);
930
+ } catch (error) {
931
+ console.warn(`Error reading localStorage key "${key}":`, error);
932
+ return initialValue;
933
+ }
934
+ });
935
+ const setValue = useCallback3(
936
+ (value) => {
937
+ try {
938
+ setStoredValue((prev) => {
939
+ const valueToStore = value instanceof Function ? value(prev) : value;
940
+ if (typeof window !== "undefined") {
941
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
942
+ window.dispatchEvent(
943
+ new StorageEvent("storage", { key, newValue: JSON.stringify(valueToStore) })
944
+ );
945
+ }
946
+ return valueToStore;
947
+ });
948
+ } catch (error) {
949
+ console.warn(`Error setting localStorage key "${key}":`, error);
950
+ }
951
+ },
952
+ [key]
953
+ );
954
+ useEffect2(() => {
955
+ const handleStorageChange = (event) => {
956
+ if (event.key === key) {
957
+ setStoredValue(parseJSON(event.newValue, initialValue));
958
+ }
959
+ };
960
+ window.addEventListener("storage", handleStorageChange);
961
+ return () => window.removeEventListener("storage", handleStorageChange);
962
+ }, [key, initialValue]);
963
+ return [storedValue, setValue];
964
+ }
965
+
966
+ // src/agent-context.tsx
967
+ import { jsx, jsxs } from "react/jsx-runtime";
968
+ var AgentContext = createContext(null);
969
+ function AgentContextProvider({ children }) {
970
+ const activeAgentsRef = useRef3(/* @__PURE__ */ new Map());
971
+ const runnersRef = useRef3(/* @__PURE__ */ new Map());
972
+ const listenersRef = useRef3(/* @__PURE__ */ new Map());
973
+ const [, forceUpdate] = useState3({});
974
+ const [allSessions, setAllSessions] = useSyncedLocalStorage(AGENT_SESSIONS_KEY, {});
975
+ const [debugData, setDebugData] = useSyncedLocalStorage(
976
+ AGENT_DEBUG_DATA_KEY,
977
+ {}
978
+ );
979
+ const initializeAgent = useCallback4((agentId, agentUrl, accessKey) => {
980
+ const existing = activeAgentsRef.current.get(agentId);
981
+ if (!existing) {
982
+ activeAgentsRef.current.set(agentId, { agentUrl, accessKey });
983
+ forceUpdate({});
984
+ } else if (existing.agentUrl !== agentUrl || existing.accessKey !== accessKey) {
985
+ activeAgentsRef.current.set(agentId, { agentUrl, accessKey });
986
+ forceUpdate({});
987
+ }
988
+ }, []);
989
+ const registerRunner = useCallback4((agentId, runner) => {
990
+ runnersRef.current.set(agentId, runner);
991
+ const listeners = listenersRef.current.get(agentId);
992
+ if (listeners) {
993
+ listeners.forEach((callback) => callback());
994
+ }
995
+ }, []);
996
+ const getRunner = useCallback4((agentId) => {
997
+ return runnersRef.current.get(agentId) || null;
998
+ }, []);
999
+ const contextValue = useMemo2(
1000
+ () => ({
1001
+ initializeAgent,
1002
+ registerRunner,
1003
+ getRunner,
1004
+ allSessions,
1005
+ setAllSessions,
1006
+ debugData,
1007
+ setDebugData,
1008
+ runnersRef,
1009
+ listenersRef
1010
+ }),
1011
+ [
1012
+ initializeAgent,
1013
+ registerRunner,
1014
+ getRunner,
1015
+ allSessions,
1016
+ setAllSessions,
1017
+ debugData,
1018
+ setDebugData
1019
+ ]
1020
+ );
1021
+ return /* @__PURE__ */ jsxs(AgentContext.Provider, { value: contextValue, children: [
1022
+ children,
1023
+ Array.from(activeAgentsRef.current.entries()).map(([agentId, { agentUrl, accessKey }]) => /* @__PURE__ */ jsx(AgentRunnerInstance, { agentId, agentUrl, accessKey }, agentId))
1024
+ ] });
1025
+ }
1026
+ function AgentRunnerInstance({
1027
+ agentId,
1028
+ agentUrl,
1029
+ accessKey
1030
+ }) {
1031
+ const agent = useAgent(agentId, agentUrl, accessKey);
1032
+ const context = useContext(AgentContext);
1033
+ if (!context) return null;
1034
+ useEffect3(() => {
1035
+ context.registerRunner(agentId, agent);
1036
+ }, [agentId, agent, context]);
1037
+ return null;
1038
+ }
1039
+ function useAgentContext(agentId, agentUrl, accessKey) {
1040
+ const context = useContext(AgentContext);
1041
+ if (!context) {
1042
+ throw new Error("useAgentContext must be used within AgentContextProvider");
1043
+ }
1044
+ const { initializeAgent, getRunner, listenersRef } = context;
1045
+ useEffect3(() => {
1046
+ initializeAgent(agentId, agentUrl, accessKey);
1047
+ }, [agentId, agentUrl, initializeAgent]);
1048
+ const [runner, setRunner] = useState3(() => getRunner(agentId));
1049
+ useEffect3(() => {
1050
+ const currentRunner = getRunner(agentId);
1051
+ if (currentRunner !== runner) {
1052
+ setRunner(currentRunner);
1053
+ }
1054
+ const callback = () => {
1055
+ setRunner(getRunner(agentId));
1056
+ };
1057
+ if (!listenersRef.current.has(agentId)) {
1058
+ listenersRef.current.set(agentId, /* @__PURE__ */ new Set());
1059
+ }
1060
+ listenersRef.current.get(agentId)?.add(callback);
1061
+ return () => {
1062
+ listenersRef.current.get(agentId)?.delete(callback);
1063
+ };
1064
+ }, [agentId, getRunner]);
1065
+ const placeholder = useMemo2(
1066
+ () => ({
1067
+ messages: [],
1068
+ inProgress: false,
1069
+ sessionId: "",
1070
+ sessions: [],
1071
+ debugData: {},
1072
+ pendingToolCalls: [],
1073
+ isPaused: false,
1074
+ handleSend: async () => {
1075
+ },
1076
+ submitToolResults: async () => {
1077
+ },
1078
+ switchSession: () => {
1079
+ },
1080
+ deleteSession: () => {
1081
+ },
1082
+ addOptimisticMessage: () => {
1083
+ },
1084
+ abort: () => {
1085
+ }
1086
+ }),
1087
+ []
1088
+ );
1089
+ return runner || placeholder;
1090
+ }
1091
+ function useAgentGlobalState() {
1092
+ const context = useContext(AgentContext);
1093
+ if (!context) {
1094
+ throw new Error("useAgentGlobalState must be used within AgentContextProvider");
1095
+ }
1096
+ return {
1097
+ allSessions: context.allSessions,
1098
+ setAllSessions: context.setAllSessions,
1099
+ debugData: context.debugData,
1100
+ setDebugData: context.setDebugData
1101
+ };
1102
+ }
1103
+ export {
1104
+ AGENT_DEBUG_DATA_KEY,
1105
+ AGENT_SESSIONS_KEY,
1106
+ AgentContextProvider,
1107
+ DEFAULT_SESSION_NAME,
1108
+ TEMPORARY_SESSION_ID,
1109
+ cleanSchema,
1110
+ tryParseJSON,
1111
+ updateAgentMessageParts,
1112
+ useAgent,
1113
+ useAgentContext,
1114
+ useAgentGlobalState
1115
+ };
1116
+ //# sourceMappingURL=index.js.map