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/README.md ADDED
@@ -0,0 +1,901 @@
1
+ # super-agent
2
+
3
+ A React library for building AI agent applications with streaming responses, interactive tools, and session management. Works with any AI agent backend that supports Server-Sent Events (SSE).
4
+
5
+ ## Features
6
+
7
+ - **Real-time Streaming**: Built on Server-Sent Events (SSE) for live agent
8
+ responses
9
+ - **Session Management**: Automatic conversation persistence with localStorage
10
+ - **File Support**: Upload and send files to your agents
11
+ - **Client Tools/Widgets**: Render interactive components from agent responses
12
+ - **Interactive Tools**: Pause agent execution and collect user input with
13
+ `requiresResult` tools
14
+ - **TypeScript**: Full type safety with comprehensive TypeScript definitions
15
+ - **Multi-Agent**: Support for multiple agents in a single application
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install super-agent
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { AgentContextProvider, useAgentContext } from "super-agent";
27
+
28
+ // 1. Wrap your app with the provider
29
+ function App() {
30
+ return (
31
+ <AgentContextProvider>
32
+ <YourApp />
33
+ </AgentContextProvider>
34
+ );
35
+ }
36
+
37
+ // 2. Use the agent in your components
38
+ function ChatComponent() {
39
+ const agent = useAgentContext(
40
+ "your-agent-id",
41
+ "https://your-agent-url.com",
42
+ "your-access-key",
43
+ );
44
+
45
+ const handleSend = () => {
46
+ agent.handleSend("Hello, agent!");
47
+ };
48
+
49
+ return (
50
+ <div>
51
+ {agent.messages.map((msg, idx) => (
52
+ <div key={idx}>
53
+ <strong>{msg.role}:</strong> {msg.content}
54
+ </div>
55
+ ))}
56
+ <button onClick={handleSend} disabled={agent.inProgress}>
57
+ Send Message
58
+ </button>
59
+ </div>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## Setup
65
+
66
+ ### AgentContextProvider
67
+
68
+ The `AgentContextProvider` must wrap your application to enable agent
69
+ functionality. It manages:
70
+
71
+ - Global session state across all agents
72
+ - Automatic localStorage persistence
73
+ - Agent runner registry
74
+
75
+ ```tsx
76
+ import { AgentContextProvider } from "super-agent";
77
+
78
+ function App() {
79
+ return (
80
+ <AgentContextProvider>{/* Your app components */}</AgentContextProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Basic Usage
86
+
87
+ ### Using the Agent Hook
88
+
89
+ The `useAgentContext` hook is the primary interface for interacting with agents:
90
+
91
+ ```tsx
92
+ import { useAgentContext } from "super-agent";
93
+
94
+ function AgentChat() {
95
+ const agent = useAgentContext(
96
+ "my-agent-id", // Unique identifier for your agent
97
+ "https://agent-url.com", // Your agent's endpoint URL
98
+ "your-access-key", // Access key for authenticated requests (sent as Bearer token)
99
+ );
100
+
101
+ // Send a message
102
+ const sendMessage = async () => {
103
+ await agent.handleSend("What's the weather today?");
104
+ };
105
+
106
+ // Display messages
107
+ return (
108
+ <div>
109
+ {agent.messages.map((message, idx) => (
110
+ <div key={idx}>
111
+ <strong>{message.role === "user" ? "You" : "Agent"}:</strong>
112
+ <p>{message.content}</p>
113
+ </div>
114
+ ))}
115
+
116
+ {agent.inProgress && <div>Agent is thinking...</div>}
117
+
118
+ <button onClick={sendMessage} disabled={agent.inProgress}>
119
+ Send
120
+ </button>
121
+ </div>
122
+ );
123
+ }
124
+ ```
125
+
126
+ ### AgentRunner API
127
+
128
+ The `useAgentContext` hook returns an `AgentRunner` object with:
129
+
130
+ | Property | Type | Description |
131
+ | ---------------------- | ------------------------------- | ------------------------------------------- |
132
+ | `messages` | `Message[]` | Array of conversation messages |
133
+ | `inProgress` | `boolean` | Whether the agent is currently processing |
134
+ | `isPaused` | `boolean` | Whether agent is paused waiting for results |
135
+ | `pendingToolCalls` | `PendingToolCall[]` | Tools waiting for user input |
136
+ | `sessionId` | `string` | Current conversation session ID |
137
+ | `sessions` | `Session[]` | All sessions for this agent |
138
+ | `debugData` | `Record<string, DebugDataType>` | Debug information indexed by executionId |
139
+ | `handleSend` | `Function` | Send a message to the agent |
140
+ | `submitToolResults` | `Function` | Submit results from interactive tools |
141
+ | `switchSession` | `Function` | Switch to a different session |
142
+ | `deleteSession` | `Function` | Delete a session |
143
+ | `addOptimisticMessage` | `Function` | Add message to UI without sending |
144
+ | `abort` | `Function` | Cancel current agent execution |
145
+
146
+ ### Session Management
147
+
148
+ ```tsx
149
+ function SessionList() {
150
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
151
+
152
+ return (
153
+ <div>
154
+ {agent.sessions.map((session) => (
155
+ <div key={session.id}>
156
+ <button onClick={() => agent.switchSession(session.id)}>
157
+ {session.name || "Untitled Session"}
158
+ </button>
159
+ <button onClick={() => agent.deleteSession(session.id)}>
160
+ Delete
161
+ </button>
162
+ </div>
163
+ ))}
164
+
165
+ {/* Create new session */}
166
+ <button onClick={() => agent.switchSession()}>New Session</button>
167
+ </div>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ## File Handling
173
+
174
+ To send files to your agent:
175
+
176
+ ### 1. Upload Files to Storage
177
+
178
+ First, upload files to a publicly accessible URL (e.g., Firebase Storage, AWS
179
+ S3):
180
+
181
+ ```tsx
182
+ async function uploadFiles(files: File[]): Promise<Record<string, string>> {
183
+ // Upload to your storage provider
184
+ const fileMap: Record<string, string> = {};
185
+
186
+ for (const file of files) {
187
+ const url = await uploadToStorage(file); // Your upload logic
188
+ const fileId = file.name.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
189
+ fileMap[fileId] = url;
190
+ }
191
+
192
+ return fileMap;
193
+ }
194
+ ```
195
+
196
+ ### 2. Send Files with Message
197
+
198
+ ```tsx
199
+ function FileUpload() {
200
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
201
+ const [files, setFiles] = useState<File[]>([]);
202
+
203
+ const handleSendWithFiles = async () => {
204
+ // Upload files and get URL mapping
205
+ const fileMap = await uploadFiles(files);
206
+
207
+ // Create input with file references
208
+ const fileIds = Object.keys(fileMap).join(", ");
209
+ const input = `Analyze these files: ${fileIds}`;
210
+
211
+ // Send to agent with file context
212
+ await agent.handleSend(input, {
213
+ context: {
214
+ mapped_file_ids_with_url: fileMap,
215
+ },
216
+ });
217
+ };
218
+
219
+ return (
220
+ <div>
221
+ <input
222
+ type="file"
223
+ multiple
224
+ onChange={(e) => setFiles(Array.from(e.target.files || []))}
225
+ />
226
+ <button onClick={handleSendWithFiles}>Send with Files</button>
227
+ </div>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ### File Context Structure
233
+
234
+ ```typescript
235
+ {
236
+ context: {
237
+ mapped_file_ids_with_url: {
238
+ "file_name_pdf": "https://storage.url/file1.pdf",
239
+ "image_png": "https://storage.url/image.png"
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ ## Client Tools / Widgets
246
+
247
+ Client tools allow your agent to render interactive widgets directly in the chat
248
+ interface. There are two types of client tools:
249
+
250
+ - **Fire-and-forget tools** (default): Agent calls the tool and continues
251
+ immediately without waiting for a result
252
+ - **Interactive tools** (`requiresResult: true`): Agent pauses execution and
253
+ waits for user input before continuing
254
+
255
+ ### 1. Define Tool Configuration
256
+
257
+ Create tool configurations with Zod schemas:
258
+
259
+ ```tsx
260
+ import { z } from "zod";
261
+
262
+ export const chartToolConfig = {
263
+ name: "render_chart",
264
+ description: "Renders a bar chart with the provided data",
265
+ schema: z.object({
266
+ title: z.string().describe("Chart title"),
267
+ data: z
268
+ .array(
269
+ z.object({
270
+ label: z.string(),
271
+ value: z.number(),
272
+ }),
273
+ )
274
+ .describe("Data points for the chart"),
275
+ }),
276
+ };
277
+
278
+ // Create your widget component
279
+ export function ChartWidget({ title, data }) {
280
+ return (
281
+ <div>
282
+ <h3>{title}</h3>
283
+ {/* Your chart rendering logic */}
284
+ </div>
285
+ );
286
+ }
287
+ ```
288
+
289
+ ### 2. Create Widget Registry
290
+
291
+ ```tsx
292
+ import type { ComponentType } from "react";
293
+ import type { ClientToolDefinition } from "super-agent";
294
+ import { z } from "zod";
295
+
296
+ // Import all your widgets
297
+ import { ChartWidget, chartToolConfig } from "./widgets/chart";
298
+ import { MapWidget, mapToolConfig } from "./widgets/map";
299
+
300
+ const allConfigs = [chartToolConfig, mapToolConfig];
301
+
302
+ // Registry for rendering widgets
303
+ export const widgetRegistry: Record<string, ComponentType<any>> = {
304
+ [chartToolConfig.name]: ChartWidget,
305
+ [mapToolConfig.name]: MapWidget,
306
+ };
307
+
308
+ // Convert to agent tool definitions
309
+ export function getToolDefinitions(): ClientToolDefinition[] {
310
+ return allConfigs.map((config) => {
311
+ const parameters = z.toJSONSchema(config.schema);
312
+
313
+ // Remove $schema property for LLM compatibility
314
+ if (
315
+ parameters &&
316
+ typeof parameters === "object" &&
317
+ "$schema" in parameters
318
+ ) {
319
+ const { $schema, ...rest } = parameters as any;
320
+ return {
321
+ name: config.name,
322
+ description: config.description,
323
+ parameters: rest,
324
+ };
325
+ }
326
+
327
+ return {
328
+ name: config.name,
329
+ description: config.description,
330
+ parameters,
331
+ };
332
+ });
333
+ }
334
+ ```
335
+
336
+ ### 3. Pass Tools to Agent
337
+
338
+ ```tsx
339
+ import { getToolDefinitions } from "./widget-registry";
340
+
341
+ function ChatWithWidgets() {
342
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
343
+ const tools = getToolDefinitions();
344
+
345
+ const handleSend = async (input: string) => {
346
+ await agent.handleSend(input, {
347
+ clientTools: tools,
348
+ });
349
+ };
350
+
351
+ return (
352
+ <div>
353
+ <button onClick={() => handleSend("Show me a chart")}>
354
+ Ask for Chart
355
+ </button>
356
+ </div>
357
+ );
358
+ }
359
+ ```
360
+
361
+ ### 4. Render Widgets in Messages
362
+
363
+ ```tsx
364
+ import { widgetRegistry } from "./widget-registry";
365
+
366
+ function MessageDisplay() {
367
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
368
+
369
+ return (
370
+ <div>
371
+ {agent.messages.map((message) => (
372
+ <div key={message.executionId}>
373
+ {/* Render message parts (text + widgets) */}
374
+ {message.parts?.map((part, idx) => {
375
+ if (part.type === "text") {
376
+ return <p key={idx}>{part.text}</p>;
377
+ } else if (part.type === "widget") {
378
+ const Widget = widgetRegistry[part.toolName];
379
+ if (!Widget) return null;
380
+ return <Widget key={idx} {...part.inputs} />;
381
+ }
382
+ return null;
383
+ })}
384
+
385
+ {/* Fallback: render plain content if no parts */}
386
+ {!message.parts && <p>{message.content}</p>}
387
+ </div>
388
+ ))}
389
+ </div>
390
+ );
391
+ }
392
+ ```
393
+
394
+ ## Interactive Client Tools (Pause & Resume)
395
+
396
+ Interactive client tools allow agents to pause execution and wait for user input
397
+ before continuing. This is useful for scenarios like:
398
+
399
+ - Flight/hotel selection from search results
400
+ - Form inputs and confirmations
401
+ - Date/time pickers
402
+ - File selection
403
+ - Custom decision points
404
+
405
+ ### How It Works
406
+
407
+ 1. Agent calls a tool with `requiresResult: true`
408
+ 2. Agent execution **pauses** (`isPaused` becomes `true`)
409
+ 3. Frontend renders UI and collects user input
410
+ 4. Frontend calls `submitToolResults()` with user's selection
411
+ 5. Agent **resumes** execution with the result and continues
412
+
413
+ ### Complete Example: Flight Booking
414
+
415
+ ```tsx
416
+ import { useState, useEffect } from "react";
417
+ import { useAgentContext } from "super-agent";
418
+ import type { ClientToolDefinition } from "super-agent";
419
+
420
+ // Define interactive tool
421
+ const FLIGHT_PICKER_TOOL: ClientToolDefinition = {
422
+ name: "show_flight_picker",
423
+ description: "Show flight picker UI and get user's flight selection",
424
+ parameters: {
425
+ type: "object",
426
+ properties: {
427
+ origin: { type: "string", description: "Origin airport code" },
428
+ destination: { type: "string", description: "Destination airport code" },
429
+ date: { type: "string", description: "Travel date" },
430
+ flights: {
431
+ type: "array",
432
+ description: "Available flights",
433
+ items: {
434
+ type: "object",
435
+ properties: {
436
+ flightNumber: { type: "string" },
437
+ airline: { type: "string" },
438
+ price: { type: "number" },
439
+ departureTime: { type: "string" },
440
+ },
441
+ },
442
+ },
443
+ },
444
+ required: ["origin", "destination", "flights"],
445
+ },
446
+ requiresResult: true, // ✅ Agent will pause and wait
447
+ };
448
+
449
+ // Flight picker component
450
+ function FlightPickerModal({ flights, onSelect, onCancel }) {
451
+ return (
452
+ <div className="modal">
453
+ <h3>Select a Flight</h3>
454
+ {flights.map((flight) => (
455
+ <div key={flight.flightNumber} className="flight-option">
456
+ <div>
457
+ {flight.airline} {flight.flightNumber}
458
+ </div>
459
+ <div>Departure: {flight.departureTime}</div>
460
+ <div>${flight.price}</div>
461
+ <button onClick={() => onSelect(flight)}>Select</button>
462
+ </div>
463
+ ))}
464
+ <button onClick={onCancel}>Cancel</button>
465
+ </div>
466
+ );
467
+ }
468
+
469
+ // Main chat component
470
+ function FlightBookingChat() {
471
+ const agent = useAgentContext(
472
+ "flight-agent",
473
+ "https://api.example.com/executeAgent/flight-agent",
474
+ "your-access-key",
475
+ );
476
+
477
+ const [showFlightPicker, setShowFlightPicker] = useState(false);
478
+ const [currentToolCall, setCurrentToolCall] = useState(null);
479
+
480
+ // Monitor for pending tool calls
481
+ useEffect(() => {
482
+ if (agent.isPaused && agent.pendingToolCalls.length > 0) {
483
+ const toolCall = agent.pendingToolCalls[0];
484
+
485
+ if (toolCall.toolName === "show_flight_picker") {
486
+ setCurrentToolCall(toolCall);
487
+ setShowFlightPicker(true);
488
+ }
489
+ }
490
+ }, [agent.isPaused, agent.pendingToolCalls]);
491
+
492
+ const handleFlightSelect = async (selectedFlight) => {
493
+ if (!currentToolCall) return;
494
+
495
+ // Submit the result back to the agent
496
+ await agent.submitToolResults([
497
+ {
498
+ callId: currentToolCall.callId,
499
+ result: selectedFlight,
500
+ },
501
+ ]);
502
+
503
+ // Close the modal
504
+ setShowFlightPicker(false);
505
+ setCurrentToolCall(null);
506
+ };
507
+
508
+ const handleCancel = async () => {
509
+ if (!currentToolCall) return;
510
+
511
+ // Submit null/cancel result
512
+ await agent.submitToolResults([
513
+ {
514
+ callId: currentToolCall.callId,
515
+ result: { cancelled: true },
516
+ },
517
+ ]);
518
+
519
+ setShowFlightPicker(false);
520
+ setCurrentToolCall(null);
521
+ };
522
+
523
+ const handleSendMessage = async (input: string) => {
524
+ await agent.handleSend(input, {
525
+ clientTools: [FLIGHT_PICKER_TOOL],
526
+ });
527
+ };
528
+
529
+ return (
530
+ <div>
531
+ {/* Messages */}
532
+ <div className="messages">
533
+ {agent.messages.map((msg, idx) => (
534
+ <div key={idx} className={msg.role}>
535
+ {msg.content}
536
+ </div>
537
+ ))}
538
+
539
+ {agent.isPaused && <div className="status">Waiting for input...</div>}
540
+ </div>
541
+
542
+ {/* Flight Picker Modal */}
543
+ {showFlightPicker && currentToolCall && (
544
+ <FlightPickerModal
545
+ flights={currentToolCall.inputs.flights}
546
+ onSelect={handleFlightSelect}
547
+ onCancel={handleCancel}
548
+ />
549
+ )}
550
+
551
+ {/* Input */}
552
+ <input
553
+ type="text"
554
+ placeholder="Ask to book a flight..."
555
+ onKeyPress={(e) => {
556
+ if (e.key === "Enter") {
557
+ handleSendMessage(e.currentTarget.value);
558
+ e.currentTarget.value = "";
559
+ }
560
+ }}
561
+ disabled={agent.inProgress}
562
+ />
563
+ </div>
564
+ );
565
+ }
566
+ ```
567
+
568
+ ### submitToolResults Options
569
+
570
+ ```typescript
571
+ agent.submitToolResults(
572
+ toolResults: ClientToolResult[],
573
+ options?: {
574
+ input?: string; // Optional follow-up message
575
+ context?: object; // Additional context
576
+ additionalHeaders?: Record<string, string>;
577
+ }
578
+ )
579
+ ```
580
+
581
+ ### Example: Multiple Tool Results
582
+
583
+ If the agent calls multiple tools that require results:
584
+
585
+ ```tsx
586
+ function MultiToolHandler() {
587
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
588
+
589
+ const handleSubmitAll = async () => {
590
+ // Submit results for all pending tools at once
591
+ const results = agent.pendingToolCalls.map((toolCall) => ({
592
+ callId: toolCall.callId,
593
+ result: getResultForTool(toolCall.toolName, toolCall.inputs),
594
+ }));
595
+
596
+ await agent.submitToolResults(results);
597
+ };
598
+
599
+ return (
600
+ <div>
601
+ {agent.isPaused && (
602
+ <div>
603
+ <h3>Pending Actions ({agent.pendingToolCalls.length})</h3>
604
+ {agent.pendingToolCalls.map((toolCall) => (
605
+ <div key={toolCall.callId}>
606
+ <strong>{toolCall.toolName}</strong>
607
+ <pre>{JSON.stringify(toolCall.inputs, null, 2)}</pre>
608
+ </div>
609
+ ))}
610
+ <button onClick={handleSubmitAll}>Submit All</button>
611
+ </div>
612
+ )}
613
+ </div>
614
+ );
615
+ }
616
+ ```
617
+
618
+ ### Example: Resume with Follow-up Message
619
+
620
+ ```tsx
621
+ const handleSubmitWithMessage = async (result: any) => {
622
+ await agent.submitToolResults(
623
+ [{ callId: currentToolCall.callId, result }],
624
+ {
625
+ input: "Great! Now book the hotel too.", // Optional follow-up
626
+ },
627
+ );
628
+ };
629
+ ```
630
+
631
+ ### Fire-and-Forget Tools (No Pause)
632
+
633
+ For tools that don't need user input, simply omit `requiresResult`:
634
+
635
+ ```tsx
636
+ const NOTIFICATION_TOOL: ClientToolDefinition = {
637
+ name: "show_notification",
638
+ description: "Display a notification to the user",
639
+ parameters: {
640
+ type: "object",
641
+ properties: {
642
+ message: { type: "string" },
643
+ type: { type: "string", enum: ["info", "success", "warning", "error"] },
644
+ },
645
+ },
646
+ // No requiresResult - agent continues immediately
647
+ };
648
+ ```
649
+
650
+ ### Best Practices for Interactive Tools
651
+
652
+ 1. **Clear Tool Descriptions**: Help the LLM understand when to use the tool
653
+
654
+ ```tsx
655
+ {
656
+ name: "show_flight_picker",
657
+ description: "Show a flight selection UI when the user asks to book or search for flights. Use this AFTER searching for available flights.",
658
+ // ...
659
+ }
660
+ ```
661
+
662
+ 2. **Handle Cancellation**: Always provide a way for users to cancel
663
+
664
+ ```tsx
665
+ await agent.submitToolResults([
666
+ {
667
+ callId: toolCall.callId,
668
+ result: { cancelled: true, reason: "User cancelled" },
669
+ },
670
+ ]);
671
+ ```
672
+
673
+ 3. **Loading States**: Show appropriate UI while paused
674
+
675
+ ```tsx
676
+ {
677
+ agent.isPaused && (
678
+ <div className="loading">Waiting for your selection...</div>
679
+ );
680
+ }
681
+ ```
682
+
683
+ 4. **Error Handling**: Wrap submitToolResults in try-catch
684
+
685
+ ```tsx
686
+ try {
687
+ await agent.submitToolResults([{ callId, result }]);
688
+ } catch (error) {
689
+ console.error("Failed to submit tool result:", error);
690
+ // Show error to user
691
+ }
692
+ ```
693
+
694
+ 5. **Type Safety**: Type your tool inputs and results
695
+ ```tsx
696
+ type FlightPickerInputs = {
697
+ origin: string;
698
+ destination: string;
699
+ flights: Flight[];
700
+ };
701
+ type FlightPickerResult = Flight | { cancelled: true };
702
+ ```
703
+
704
+ ## API Reference
705
+
706
+ ### handleSend Options
707
+
708
+ ```typescript
709
+ agent.handleSend(input: string, options?: {
710
+ // Custom context data for the agent
711
+ context?: Record<string, unknown>;
712
+
713
+ // Don't add user message to UI (for optimistic updates)
714
+ skipUserMessage?: boolean;
715
+
716
+ // Additional HTTP headers
717
+ additionalHeaders?: Record<string, string>;
718
+
719
+ // Server-side tool definitions
720
+ tools?: ClientToolDefinition[];
721
+
722
+ // Client-side widget definitions
723
+ clientTools?: ClientToolDefinition[];
724
+ })
725
+ ```
726
+
727
+ ### Types
728
+
729
+ #### Message
730
+
731
+ ```typescript
732
+ type Message = {
733
+ role: "user" | "agent";
734
+ content: string; // Full text content
735
+ parts?: MessagePart[]; // Structured parts (text + widgets)
736
+ executionId?: string; // Links to debug data
737
+ };
738
+
739
+ type MessagePart =
740
+ | { type: "text"; text: string; firstSequence: number; lastSequence: number }
741
+ | {
742
+ type: "widget";
743
+ toolName: string;
744
+ callId: string;
745
+ inputs: any;
746
+ sequence: number;
747
+ };
748
+ ```
749
+
750
+ #### Session
751
+
752
+ ```typescript
753
+ type Session = {
754
+ id: string;
755
+ createdAt: number;
756
+ updatedAt: number;
757
+ messages: Message[];
758
+ name?: string;
759
+ };
760
+ ```
761
+
762
+ #### ClientToolDefinition
763
+
764
+ ```typescript
765
+ type ClientToolDefinition = {
766
+ name: string; // Unique tool identifier
767
+ description: string; // Human-readable description for LLM
768
+ parameters: unknown; // JSON Schema object
769
+ requiresResult?: boolean; // If true, agent pauses and waits for user input
770
+ };
771
+
772
+ type ClientToolResult = {
773
+ callId: string; // ID from the client_tool_call event
774
+ result: any; // User's input or selection
775
+ };
776
+
777
+ type PendingToolCall = {
778
+ toolName: string;
779
+ callId: string;
780
+ inputs: any;
781
+ requiresResult: boolean;
782
+ };
783
+ ```
784
+
785
+ ## Advanced Features
786
+
787
+ ### Debug Data
788
+
789
+ Access detailed execution information:
790
+
791
+ ```tsx
792
+ function DebugView() {
793
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
794
+
795
+ return (
796
+ <div>
797
+ {Object.entries(agent.debugData).map(([executionId, debug]) => (
798
+ <details key={executionId}>
799
+ <summary>Execution {executionId}</summary>
800
+ <pre>{JSON.stringify(debug, null, 2)}</pre>
801
+ </details>
802
+ ))}
803
+ </div>
804
+ );
805
+ }
806
+ ```
807
+
808
+ ### Custom Headers
809
+
810
+ ```tsx
811
+ await agent.handleSend("message", {
812
+ additionalHeaders: {
813
+ "X-Custom-Header": "value",
814
+ Authorization: "Bearer token",
815
+ },
816
+ });
817
+ ```
818
+
819
+ ### Abort Requests
820
+
821
+ ```tsx
822
+ function CancellableRequest() {
823
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
824
+
825
+ return (
826
+ <div>
827
+ <button onClick={() => agent.handleSend("Long task...")}>Start</button>
828
+ <button onClick={() => agent.abort()} disabled={!agent.inProgress}>
829
+ Cancel
830
+ </button>
831
+ </div>
832
+ );
833
+ }
834
+ ```
835
+
836
+ ### Optimistic Updates
837
+
838
+ ```tsx
839
+ function OptimisticMessage() {
840
+ const agent = useAgentContext("agent-id", "agent-url", "access-key");
841
+
842
+ const handleSend = async (input: string) => {
843
+ // Add message to UI immediately
844
+ agent.addOptimisticMessage(input);
845
+
846
+ // Send to agent without adding duplicate
847
+ await agent.handleSend(input, { skipUserMessage: true });
848
+ };
849
+
850
+ return <button onClick={() => handleSend("Hello")}>Send</button>;
851
+ }
852
+ ```
853
+
854
+ ## Storage and Persistence
855
+
856
+ The library automatically persists conversations to localStorage:
857
+
858
+ - **Sessions**: Stored under `buildship:agent:conversations`
859
+ - **Debug Data**: Stored under `buildship:agent:debug`
860
+
861
+ Sessions are automatically synced across browser tabs and survive page
862
+ refreshes.
863
+
864
+ ## TypeScript Support
865
+
866
+ The package is written in TypeScript and exports all types:
867
+
868
+ ```tsx
869
+ import type {
870
+ Message,
871
+ Session,
872
+ ClientToolDefinition,
873
+ ClientToolResult,
874
+ PendingToolCall,
875
+ AgentRunner,
876
+ DebugDataType,
877
+ MessagePart,
878
+ } from "super-agent";
879
+ ```
880
+
881
+ ## Best Practices
882
+
883
+ 1. **Single Provider**: Only use one `AgentContextProvider` at the root of your
884
+ app
885
+ 2. **Tool Descriptions**: Write clear, detailed descriptions for client tools to
886
+ help the LLM understand when to use them
887
+ 3. **Interactive Tools**: Use `requiresResult: true` only when you truly need to
888
+ pause execution for user input. For simple notifications or displays, use
889
+ fire-and-forget tools
890
+ 4. **File URLs**: Ensure file URLs are publicly accessible or use signed URLs
891
+ with sufficient expiration
892
+ 5. **Error Handling**: Wrap `handleSend` and `submitToolResults` calls in
893
+ try-catch blocks for error handling
894
+ 6. **Widget Registry**: Keep your widget registry centralized for easier
895
+ maintenance
896
+ 7. **Pending Tool Management**: Always handle `isPaused` state and provide clear
897
+ UI for users when the agent is waiting for input
898
+
899
+ ## License
900
+
901
+ MIT