bs-agent 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BuildShip
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,558 @@
1
+ # @buildship/agent
2
+
3
+ A React library for integrating BuildShip AI agents into your frontend
4
+ applications with support for streaming responses, file handling, and
5
+ client-side widgets.
6
+
7
+ ## Features
8
+
9
+ - **Real-time Streaming**: Built on Server-Sent Events (SSE) for live agent
10
+ responses
11
+ - **Session Management**: Automatic conversation persistence with localStorage
12
+ - **File Support**: Upload and send files to your agents
13
+ - **Client Tools/Widgets**: Render interactive components from agent responses
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 @buildship/agent
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { AgentContextProvider, useAgentContext } from "@buildship/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("your-agent-id", "https://your-agent-url.com");
40
+
41
+ const handleSend = () => {
42
+ agent.handleSend("Hello, agent!");
43
+ };
44
+
45
+ return (
46
+ <div>
47
+ {agent.messages.map((msg, idx) => (
48
+ <div key={idx}>
49
+ <strong>{msg.role}:</strong> {msg.content}
50
+ </div>
51
+ ))}
52
+ <button onClick={handleSend} disabled={agent.inProgress}>
53
+ Send Message
54
+ </button>
55
+ </div>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## Setup
61
+
62
+ ### AgentContextProvider
63
+
64
+ The `AgentContextProvider` must wrap your application to enable agent
65
+ functionality. It manages:
66
+
67
+ - Global session state across all agents
68
+ - Automatic localStorage persistence
69
+ - Agent runner registry
70
+
71
+ ```tsx
72
+ import { AgentContextProvider } from "@buildship/agent";
73
+
74
+ function App() {
75
+ return (
76
+ <AgentContextProvider>{/* Your app components */}</AgentContextProvider>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ## Basic Usage
82
+
83
+ ### Using the Agent Hook
84
+
85
+ The `useAgentContext` hook is the primary interface for interacting with agents:
86
+
87
+ ```tsx
88
+ import { useAgentContext } from "@buildship/agent";
89
+
90
+ function AgentChat() {
91
+ const agent = useAgentContext(
92
+ "my-agent-id", // Unique identifier for your agent
93
+ "https://agent-url.com", // Your agent's endpoint URL
94
+ );
95
+
96
+ // Send a message
97
+ const sendMessage = async () => {
98
+ await agent.handleSend("What's the weather today?");
99
+ };
100
+
101
+ // Display messages
102
+ return (
103
+ <div>
104
+ {agent.messages.map((message, idx) => (
105
+ <div key={idx}>
106
+ <strong>{message.role === "user" ? "You" : "Agent"}:</strong>
107
+ <p>{message.content}</p>
108
+ </div>
109
+ ))}
110
+
111
+ {agent.inProgress && <div>Agent is thinking...</div>}
112
+
113
+ <button onClick={sendMessage} disabled={agent.inProgress}>
114
+ Send
115
+ </button>
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ ### AgentRunner API
122
+
123
+ The `useAgentContext` hook returns an `AgentRunner` object with:
124
+
125
+ | Property | Type | Description |
126
+ | ---------------------- | ------------------------------- | ----------------------------------------- |
127
+ | `messages` | `Message[]` | Array of conversation messages |
128
+ | `inProgress` | `boolean` | Whether the agent is currently processing |
129
+ | `sessionId` | `string` | Current conversation session ID |
130
+ | `sessions` | `Session[]` | All sessions for this agent |
131
+ | `debugData` | `Record<string, DebugDataType>` | Debug information indexed by executionId |
132
+ | `handleSend` | `Function` | Send a message to the agent |
133
+ | `switchSession` | `Function` | Switch to a different session |
134
+ | `deleteSession` | `Function` | Delete a session |
135
+ | `addOptimisticMessage` | `Function` | Add message to UI without sending |
136
+ | `abort` | `Function` | Cancel current agent execution |
137
+
138
+ ### Session Management
139
+
140
+ ```tsx
141
+ function SessionList() {
142
+ const agent = useAgentContext("agent-id", "agent-url");
143
+
144
+ return (
145
+ <div>
146
+ {agent.sessions.map((session) => (
147
+ <div key={session.id}>
148
+ <button onClick={() => agent.switchSession(session.id)}>
149
+ {session.name || "Untitled Session"}
150
+ </button>
151
+ <button onClick={() => agent.deleteSession(session.id)}>
152
+ Delete
153
+ </button>
154
+ </div>
155
+ ))}
156
+
157
+ {/* Create new session */}
158
+ <button onClick={() => agent.switchSession()}>New Session</button>
159
+ </div>
160
+ );
161
+ }
162
+ ```
163
+
164
+ ## File Handling
165
+
166
+ To send files to your agent:
167
+
168
+ ### 1. Upload Files to Storage
169
+
170
+ First, upload files to a publicly accessible URL (e.g., Firebase Storage, AWS
171
+ S3):
172
+
173
+ ```tsx
174
+ async function uploadFiles(files: File[]): Promise<Record<string, string>> {
175
+ // Upload to your storage provider
176
+ const fileMap: Record<string, string> = {};
177
+
178
+ for (const file of files) {
179
+ const url = await uploadToStorage(file); // Your upload logic
180
+ const fileId = file.name.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
181
+ fileMap[fileId] = url;
182
+ }
183
+
184
+ return fileMap;
185
+ }
186
+ ```
187
+
188
+ ### 2. Send Files with Message
189
+
190
+ ```tsx
191
+ function FileUpload() {
192
+ const agent = useAgentContext("agent-id", "agent-url");
193
+ const [files, setFiles] = useState<File[]>([]);
194
+
195
+ const handleSendWithFiles = async () => {
196
+ // Upload files and get URL mapping
197
+ const fileMap = await uploadFiles(files);
198
+
199
+ // Create input with file references
200
+ const fileIds = Object.keys(fileMap).join(", ");
201
+ const input = `Analyze these files: ${fileIds}`;
202
+
203
+ // Send to agent with file context
204
+ await agent.handleSend(input, {
205
+ context: {
206
+ mapped_file_ids_with_url: fileMap,
207
+ },
208
+ });
209
+ };
210
+
211
+ return (
212
+ <div>
213
+ <input
214
+ type="file"
215
+ multiple
216
+ onChange={(e) => setFiles(Array.from(e.target.files || []))}
217
+ />
218
+ <button onClick={handleSendWithFiles}>Send with Files</button>
219
+ </div>
220
+ );
221
+ }
222
+ ```
223
+
224
+ ### File Context Structure
225
+
226
+ ```typescript
227
+ {
228
+ context: {
229
+ mapped_file_ids_with_url: {
230
+ "file_name_pdf": "https://storage.url/file1.pdf",
231
+ "image_png": "https://storage.url/image.png"
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ## Client Tools / Widgets
238
+
239
+ Client tools allow your agent to render interactive widgets directly in the chat
240
+ interface.
241
+
242
+ ### 1. Define Tool Configuration
243
+
244
+ Create tool configurations with Zod schemas:
245
+
246
+ ```tsx
247
+ import { z } from "zod";
248
+
249
+ export const chartToolConfig = {
250
+ name: "render_chart",
251
+ description: "Renders a bar chart with the provided data",
252
+ schema: z.object({
253
+ title: z.string().describe("Chart title"),
254
+ data: z
255
+ .array(
256
+ z.object({
257
+ label: z.string(),
258
+ value: z.number(),
259
+ }),
260
+ )
261
+ .describe("Data points for the chart"),
262
+ }),
263
+ };
264
+
265
+ // Create your widget component
266
+ export function ChartWidget({ title, data }) {
267
+ return (
268
+ <div>
269
+ <h3>{title}</h3>
270
+ {/* Your chart rendering logic */}
271
+ </div>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ### 2. Create Widget Registry
277
+
278
+ ```tsx
279
+ import type { ComponentType } from "react";
280
+ import type { ClientToolDefinition } from "@buildship/agent";
281
+ import { z } from "zod";
282
+
283
+ // Import all your widgets
284
+ import { ChartWidget, chartToolConfig } from "./widgets/chart";
285
+ import { MapWidget, mapToolConfig } from "./widgets/map";
286
+
287
+ const allConfigs = [chartToolConfig, mapToolConfig];
288
+
289
+ // Registry for rendering widgets
290
+ export const widgetRegistry: Record<string, ComponentType<any>> = {
291
+ [chartToolConfig.name]: ChartWidget,
292
+ [mapToolConfig.name]: MapWidget,
293
+ };
294
+
295
+ // Convert to agent tool definitions
296
+ export function getToolDefinitions(): ClientToolDefinition[] {
297
+ return allConfigs.map((config) => {
298
+ const parameters = z.toJSONSchema(config.schema);
299
+
300
+ // Remove $schema property for LLM compatibility
301
+ if (
302
+ parameters &&
303
+ typeof parameters === "object" &&
304
+ "$schema" in parameters
305
+ ) {
306
+ const { $schema, ...rest } = parameters as any;
307
+ return {
308
+ name: config.name,
309
+ description: config.description,
310
+ parameters: rest,
311
+ };
312
+ }
313
+
314
+ return {
315
+ name: config.name,
316
+ description: config.description,
317
+ parameters,
318
+ };
319
+ });
320
+ }
321
+ ```
322
+
323
+ ### 3. Pass Tools to Agent
324
+
325
+ ```tsx
326
+ import { getToolDefinitions } from "./widget-registry";
327
+
328
+ function ChatWithWidgets() {
329
+ const agent = useAgentContext("agent-id", "agent-url");
330
+ const tools = getToolDefinitions();
331
+
332
+ const handleSend = async (input: string) => {
333
+ await agent.handleSend(input, {
334
+ clientTools: tools,
335
+ });
336
+ };
337
+
338
+ return (
339
+ <div>
340
+ <button onClick={() => handleSend("Show me a chart")}>
341
+ Ask for Chart
342
+ </button>
343
+ </div>
344
+ );
345
+ }
346
+ ```
347
+
348
+ ### 4. Render Widgets in Messages
349
+
350
+ ```tsx
351
+ import { widgetRegistry } from "./widget-registry";
352
+
353
+ function MessageDisplay() {
354
+ const agent = useAgentContext("agent-id", "agent-url");
355
+
356
+ return (
357
+ <div>
358
+ {agent.messages.map((message) => (
359
+ <div key={message.executionId}>
360
+ {/* Render message parts (text + widgets) */}
361
+ {message.parts?.map((part, idx) => {
362
+ if (part.type === "text") {
363
+ return <p key={idx}>{part.text}</p>;
364
+ } else if (part.type === "widget") {
365
+ const Widget = widgetRegistry[part.toolName];
366
+ if (!Widget) return null;
367
+ return <Widget key={idx} {...part.inputs} />;
368
+ }
369
+ return null;
370
+ })}
371
+
372
+ {/* Fallback: render plain content if no parts */}
373
+ {!message.parts && <p>{message.content}</p>}
374
+ </div>
375
+ ))}
376
+ </div>
377
+ );
378
+ }
379
+ ```
380
+
381
+ ## API Reference
382
+
383
+ ### handleSend Options
384
+
385
+ ```typescript
386
+ agent.handleSend(input: string, options?: {
387
+ // Custom context data for the agent
388
+ context?: Record<string, unknown>;
389
+
390
+ // Don't add user message to UI (for optimistic updates)
391
+ skipUserMessage?: boolean;
392
+
393
+ // Additional HTTP headers
394
+ additionalHeaders?: Record<string, string>;
395
+
396
+ // Server-side tool definitions
397
+ tools?: ClientToolDefinition[];
398
+
399
+ // Client-side widget definitions
400
+ clientTools?: ClientToolDefinition[];
401
+ })
402
+ ```
403
+
404
+ ### Types
405
+
406
+ #### Message
407
+
408
+ ```typescript
409
+ type Message = {
410
+ role: "user" | "agent";
411
+ content: string; // Full text content
412
+ parts?: MessagePart[]; // Structured parts (text + widgets)
413
+ executionId?: string; // Links to debug data
414
+ };
415
+
416
+ type MessagePart =
417
+ | { type: "text"; text: string; firstSequence: number; lastSequence: number }
418
+ | {
419
+ type: "widget";
420
+ toolName: string;
421
+ callId: string;
422
+ inputs: any;
423
+ sequence: number;
424
+ };
425
+ ```
426
+
427
+ #### Session
428
+
429
+ ```typescript
430
+ type Session = {
431
+ id: string;
432
+ createdAt: number;
433
+ updatedAt: number;
434
+ messages: Message[];
435
+ name?: string;
436
+ };
437
+ ```
438
+
439
+ #### ClientToolDefinition
440
+
441
+ ```typescript
442
+ type ClientToolDefinition = {
443
+ name: string; // Unique tool identifier
444
+ description: string; // Human-readable description for LLM
445
+ parameters: unknown; // JSON Schema object
446
+ };
447
+ ```
448
+
449
+ ## Advanced Features
450
+
451
+ ### Debug Data
452
+
453
+ Access detailed execution information:
454
+
455
+ ```tsx
456
+ function DebugView() {
457
+ const agent = useAgentContext("agent-id", "agent-url");
458
+
459
+ return (
460
+ <div>
461
+ {Object.entries(agent.debugData).map(([executionId, debug]) => (
462
+ <details key={executionId}>
463
+ <summary>Execution {executionId}</summary>
464
+ <pre>{JSON.stringify(debug, null, 2)}</pre>
465
+ </details>
466
+ ))}
467
+ </div>
468
+ );
469
+ }
470
+ ```
471
+
472
+ ### Custom Headers
473
+
474
+ ```tsx
475
+ await agent.handleSend("message", {
476
+ additionalHeaders: {
477
+ "X-Custom-Header": "value",
478
+ Authorization: "Bearer token",
479
+ },
480
+ });
481
+ ```
482
+
483
+ ### Abort Requests
484
+
485
+ ```tsx
486
+ function CancellableRequest() {
487
+ const agent = useAgentContext("agent-id", "agent-url");
488
+
489
+ return (
490
+ <div>
491
+ <button onClick={() => agent.handleSend("Long task...")}>Start</button>
492
+ <button onClick={() => agent.abort()} disabled={!agent.inProgress}>
493
+ Cancel
494
+ </button>
495
+ </div>
496
+ );
497
+ }
498
+ ```
499
+
500
+ ### Optimistic Updates
501
+
502
+ ```tsx
503
+ function OptimisticMessage() {
504
+ const agent = useAgentContext("agent-id", "agent-url");
505
+
506
+ const handleSend = async (input: string) => {
507
+ // Add message to UI immediately
508
+ agent.addOptimisticMessage(input);
509
+
510
+ // Send to agent without adding duplicate
511
+ await agent.handleSend(input, { skipUserMessage: true });
512
+ };
513
+
514
+ return <button onClick={() => handleSend("Hello")}>Send</button>;
515
+ }
516
+ ```
517
+
518
+ ## Storage and Persistence
519
+
520
+ The library automatically persists conversations to localStorage:
521
+
522
+ - **Sessions**: Stored under `buildship:agent:conversations`
523
+ - **Debug Data**: Stored under `buildship:agent:debug`
524
+
525
+ Sessions are automatically synced across browser tabs and survive page
526
+ refreshes.
527
+
528
+ ## TypeScript Support
529
+
530
+ The package is written in TypeScript and exports all types:
531
+
532
+ ```tsx
533
+ import type {
534
+ Message,
535
+ Session,
536
+ ClientToolDefinition,
537
+ AgentRunner,
538
+ DebugDataType,
539
+ MessagePart,
540
+ } from "@buildship/agent";
541
+ ```
542
+
543
+ ## Best Practices
544
+
545
+ 1. **Single Provider**: Only use one `AgentContextProvider` at the root of your
546
+ app
547
+ 2. **Tool Descriptions**: Write clear, detailed descriptions for client tools to
548
+ help the LLM understand when to use them
549
+ 3. **File URLs**: Ensure file URLs are publicly accessible or use signed URLs
550
+ with sufficient expiration
551
+ 4. **Error Handling**: Wrap `handleSend` calls in try-catch blocks for error
552
+ handling
553
+ 5. **Widget Registry**: Keep your widget registry centralized for easier
554
+ maintenance
555
+
556
+ ## License
557
+
558
+ MIT