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.
- package/README.md +496 -410
- package/dist/core/index.cjs +520 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +133 -0
- package/dist/core/index.d.ts +133 -0
- package/dist/core/index.js +489 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.cjs +1318 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +287 -0
- package/dist/react/index.d.ts +287 -0
- package/dist/react/index.js +1286 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-DryLPWU9.d.cts +160 -0
- package/dist/types-DryLPWU9.d.ts +160 -0
- package/package.json +26 -11
- package/dist/index.cjs +0 -915
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -196
- package/dist/index.d.ts +0 -196
- package/dist/index.js +0 -886
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,420 +1,443 @@
|
|
|
1
1
|
# @buildship/agent
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
3
|
+
A type-safe TypeScript SDK for [BuildShip](https://buildship.com) agents with
|
|
4
|
+
streaming support.
|
|
5
|
+
|
|
6
|
+
- 🔄 **Streaming-first** — real-time text, reasoning, tool calls & handoffs via
|
|
7
|
+
SSE
|
|
8
|
+
- 🧩 **Client tools** — headless handlers, interactive widgets, pause/resume
|
|
9
|
+
- ⚛️ **React bindings** — hooks & context for chat UIs with session management
|
|
10
|
+
- 💬 **Multi-turn** — session-based conversations with persistent history
|
|
11
|
+
- 🛑 **Abort** — cancel any streaming request mid-flight
|
|
12
|
+
- 🐛 **Debug data** — structured debug entries for every tool call, reasoning
|
|
13
|
+
step & handoff
|
|
14
|
+
- 📦 **Zero extra deps** — native `fetch` + `ReadableStream`, only `zod` as a
|
|
15
|
+
dependency
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install @buildship/agent
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
The package exposes two entry points:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { ... } from "@buildship/agent/core"; // Vanilla JS/TS — works anywhere
|
|
27
|
+
import { ... } from "@buildship/agent/react"; // React hooks, context & components
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Core (`@buildship/agent/core`)
|
|
33
|
+
|
|
34
|
+
The core module provides a class-based API that works in any JavaScript
|
|
35
|
+
environment — Node.js, browser, Edge Runtime, etc.
|
|
36
|
+
|
|
23
37
|
## Quick Start
|
|
24
38
|
|
|
25
|
-
```
|
|
26
|
-
import {
|
|
39
|
+
```ts
|
|
40
|
+
import { BuildShipAgent, z } from "@buildship/agent/core";
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</AgentContextProvider>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
42
|
+
const agent = new BuildShipAgent({
|
|
43
|
+
agentId: "YOUR_AGENT_ID",
|
|
44
|
+
accessKey: "YOUR_ACCESS_KEY", // optional
|
|
45
|
+
baseUrl: "https://your-project.buildship.run",
|
|
46
|
+
});
|
|
36
47
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
// Simple one-shot
|
|
49
|
+
const session = await agent.execute("Hello!", {
|
|
50
|
+
onText: (text) => process.stdout.write(text),
|
|
51
|
+
onComplete: (fullText) => console.log("\nDone:", fullText),
|
|
52
|
+
onError: (err) => console.error(err),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
agent.handleSend("Hello, agent!");
|
|
47
|
-
};
|
|
56
|
+
## Multi-Turn Conversations
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
}
|
|
58
|
+
```ts
|
|
59
|
+
// First message returns a session
|
|
60
|
+
const session = await agent.execute("What is 2 + 2?", {
|
|
61
|
+
onText: (t) => process.stdout.write(t),
|
|
62
|
+
onComplete: () => console.log(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Continue with the same session ID
|
|
66
|
+
const continued = agent.session(session.getSessionId());
|
|
67
|
+
await continued.execute("Now multiply that by 3", {
|
|
68
|
+
onText: (t) => process.stdout.write(t),
|
|
69
|
+
onComplete: () => console.log(),
|
|
70
|
+
});
|
|
62
71
|
```
|
|
63
72
|
|
|
64
|
-
##
|
|
73
|
+
## Client Tools
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
Define tools the agent can invoke on the client side. Use **Zod schemas** for
|
|
76
|
+
type-safe parameter definitions:
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
```ts
|
|
79
|
+
import { BuildShipAgent, z } from "@buildship/agent/core";
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
- Automatic localStorage persistence
|
|
73
|
-
- Agent runner registry
|
|
81
|
+
const agent = new BuildShipAgent({ agentId: "..." });
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
// Fire-and-forget tool
|
|
84
|
+
agent.registerClientTool({
|
|
85
|
+
name: "show_notification",
|
|
86
|
+
description: "Display a notification to the user",
|
|
87
|
+
parameters: z.object({
|
|
88
|
+
title: z.string().describe("Notification title"),
|
|
89
|
+
message: z.string().describe("Notification body"),
|
|
90
|
+
}),
|
|
91
|
+
handler: (args) => {
|
|
92
|
+
showNotification(args.title, args.message);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
96
|
+
// Blocking tool — agent pauses until result is returned
|
|
97
|
+
agent.registerClientTool({
|
|
98
|
+
name: "get_location",
|
|
99
|
+
description: "Get the user's current location",
|
|
100
|
+
parameters: z.object({}),
|
|
101
|
+
await: true,
|
|
102
|
+
handler: async () => {
|
|
103
|
+
const pos = await navigator.geolocation.getCurrentPosition();
|
|
104
|
+
return { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
|
105
|
+
},
|
|
106
|
+
});
|
|
83
107
|
```
|
|
84
108
|
|
|
85
|
-
|
|
109
|
+
### Pause & Resume (Manual)
|
|
86
110
|
|
|
87
|
-
|
|
111
|
+
For blocking tools without a handler, the agent pauses and you resume manually:
|
|
88
112
|
|
|
89
|
-
|
|
113
|
+
```ts
|
|
114
|
+
agent.registerClientTool({
|
|
115
|
+
name: "confirm_action",
|
|
116
|
+
description: "Ask the user to confirm an action",
|
|
117
|
+
parameters: z.object({
|
|
118
|
+
action: z.string().describe("The action to confirm"),
|
|
119
|
+
}),
|
|
120
|
+
await: true,
|
|
121
|
+
// No handler — you handle it manually
|
|
122
|
+
});
|
|
90
123
|
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
const session = await agent.execute("Delete my account", {
|
|
125
|
+
onText: (t) => process.stdout.write(t),
|
|
126
|
+
onPaused: (toolName, args) => {
|
|
127
|
+
console.log(`Agent paused for: ${toolName}`, args);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
93
130
|
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
131
|
+
if (session.isPaused()) {
|
|
132
|
+
const tool = session.getPausedTool();
|
|
133
|
+
// ... show confirmation UI, then resume:
|
|
134
|
+
await session.resume(
|
|
135
|
+
{ confirmed: true },
|
|
136
|
+
{
|
|
137
|
+
onText: (t) => process.stdout.write(t),
|
|
138
|
+
},
|
|
99
139
|
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
100
142
|
|
|
101
|
-
|
|
102
|
-
const sendMessage = async () => {
|
|
103
|
-
await agent.handleSend("What's the weather today?");
|
|
104
|
-
};
|
|
143
|
+
## Abort
|
|
105
144
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
))}
|
|
145
|
+
```ts
|
|
146
|
+
const session = agent.session(sessionId);
|
|
115
147
|
|
|
116
|
-
|
|
148
|
+
session.execute("Write a long essay...", {
|
|
149
|
+
onText: (text) => {
|
|
150
|
+
process.stdout.write(text);
|
|
151
|
+
if (userCancelled) session.abort();
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
```
|
|
117
155
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
## Stream Callbacks
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
interface StreamCallbacks {
|
|
160
|
+
/** Called for each text chunk from the agent. */
|
|
161
|
+
onText?: (text: string) => void;
|
|
162
|
+
/** Called for each reasoning chunk (models with chain-of-thought). */
|
|
163
|
+
onReasoning?: (delta: string, index: number) => void;
|
|
164
|
+
/** Called when control is handed off to a sub-agent. */
|
|
165
|
+
onAgentHandoff?: (agentName: string) => void;
|
|
166
|
+
/** Called when a tool execution starts. */
|
|
167
|
+
onToolStart?: (toolName: string, toolType: ToolType) => void;
|
|
168
|
+
/** Called when a tool execution completes. */
|
|
169
|
+
onToolEnd?: (toolName: string, result?: any, error?: string) => void;
|
|
170
|
+
/** Called when agent pauses for a blocking client tool. */
|
|
171
|
+
onPaused?: (toolName: string, args: any) => void;
|
|
172
|
+
/** Called when the stream completes successfully. */
|
|
173
|
+
onComplete?: (fullText: string) => void;
|
|
174
|
+
/** Called if an error occurs during streaming. */
|
|
175
|
+
onError?: (error: Error) => void;
|
|
176
|
+
/** Called for every raw SSE event. Useful for debug panels. */
|
|
177
|
+
onEvent?: (event: StreamEvent) => void;
|
|
123
178
|
}
|
|
124
179
|
```
|
|
125
180
|
|
|
126
|
-
|
|
181
|
+
## Core API Reference
|
|
127
182
|
|
|
128
|
-
|
|
183
|
+
### `BuildShipAgent`
|
|
129
184
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `debugData` | `Record<string, DebugDataType>` | Debug information indexed by executionId |
|
|
137
|
-
| `handleSend` | `Function` | Send a message to the agent |
|
|
138
|
-
| `switchSession` | `Function` | Switch to a different session |
|
|
139
|
-
| `deleteSession` | `Function` | Delete a session |
|
|
140
|
-
| `addOptimisticMessage` | `Function` | Add message to UI without sending |
|
|
141
|
-
| `abort` | `Function` | Cancel current agent execution |
|
|
185
|
+
| Method | Description |
|
|
186
|
+
| --------------------------------------- | ---------------------------------------------------------- |
|
|
187
|
+
| `execute(message, callbacks, context?)` | Start a new conversation. Returns `AgentSession`. |
|
|
188
|
+
| `session(sessionId)` | Continue an existing conversation. Returns `AgentSession`. |
|
|
189
|
+
| `registerClientTool(tool)` | Register a client-side tool. |
|
|
190
|
+
| `unregisterClientTool(name)` | Remove a registered tool. |
|
|
142
191
|
|
|
143
|
-
###
|
|
192
|
+
### `AgentSession`
|
|
144
193
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
194
|
+
| Method | Description |
|
|
195
|
+
| --------------------------------------- | ---------------------------------------------------- |
|
|
196
|
+
| `execute(message, callbacks, context?)` | Send a message in this session. |
|
|
197
|
+
| `resume(result, callbacks)` | Resume after a blocking tool pause. |
|
|
198
|
+
| `isPaused()` | Check if waiting for a tool result. |
|
|
199
|
+
| `getPausedTool()` | Get paused tool info (`{ callId, toolName, args }`). |
|
|
200
|
+
| `getSessionId()` | Get the session ID. |
|
|
201
|
+
| `abort()` | Cancel the current stream. |
|
|
148
202
|
|
|
149
|
-
|
|
150
|
-
<div>
|
|
151
|
-
{agent.sessions.map((session) => (
|
|
152
|
-
<div key={session.id}>
|
|
153
|
-
<button onClick={() => agent.switchSession(session.id)}>
|
|
154
|
-
{session.name || "Untitled Session"}
|
|
155
|
-
</button>
|
|
156
|
-
<button onClick={() => agent.deleteSession(session.id)}>
|
|
157
|
-
Delete
|
|
158
|
-
</button>
|
|
159
|
-
</div>
|
|
160
|
-
))}
|
|
203
|
+
### Stream Events
|
|
161
204
|
|
|
162
|
-
|
|
163
|
-
<button onClick={() => agent.switchSession()}>New Session</button>
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
```
|
|
205
|
+
All events share a `meta` object with `executionId` and `sequence`.
|
|
168
206
|
|
|
169
|
-
|
|
207
|
+
| Event Type | Description | Data |
|
|
208
|
+
| ----------------- | ---------------------------------- | ----------------------------------------------------------------- |
|
|
209
|
+
| `text_delta` | Text chunk from the agent | `string` |
|
|
210
|
+
| `reasoning_delta` | Chain-of-thought reasoning chunk | `{ delta: string, index: number }` |
|
|
211
|
+
| `tool_call_start` | A tool execution started | `{ callId, toolName, toolType, inputs?, serverName?, paused? }` |
|
|
212
|
+
| `tool_call_end` | A tool execution completed | `{ callId, toolName, toolType, result?, error?, executionTime? }` |
|
|
213
|
+
| `agent_handoff` | Control transferred to a sub-agent | `{ agentName: string }` |
|
|
170
214
|
|
|
171
|
-
|
|
215
|
+
### Tool Types
|
|
172
216
|
|
|
173
|
-
|
|
217
|
+
```ts
|
|
218
|
+
type ToolType = "flow" | "node" | "mcp" | "client" | "builtin" | "agent";
|
|
219
|
+
```
|
|
174
220
|
|
|
175
|
-
|
|
176
|
-
S3):
|
|
221
|
+
---
|
|
177
222
|
|
|
178
|
-
|
|
179
|
-
async function uploadFiles(files: File[]): Promise<Record<string, string>> {
|
|
180
|
-
// Upload to your storage provider
|
|
181
|
-
const fileMap: Record<string, string> = {};
|
|
223
|
+
# React (`@buildship/agent/react`)
|
|
182
224
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const fileId = file.name.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
|
|
186
|
-
fileMap[fileId] = url;
|
|
187
|
-
}
|
|
225
|
+
The React module provides hooks, context providers, and components for building
|
|
226
|
+
chat UIs with full session management, client tool support, and debug panels.
|
|
188
227
|
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
```
|
|
228
|
+
## Setup
|
|
192
229
|
|
|
193
|
-
|
|
230
|
+
Wrap your app (or the chat area) with `AgentContextProvider`:
|
|
194
231
|
|
|
195
232
|
```tsx
|
|
196
|
-
|
|
197
|
-
const agent = useAgentContext("agent-id", "agent-url", "access-key");
|
|
198
|
-
const [files, setFiles] = useState<File[]>([]);
|
|
199
|
-
|
|
200
|
-
const handleSendWithFiles = async () => {
|
|
201
|
-
// Upload files and get URL mapping
|
|
202
|
-
const fileMap = await uploadFiles(files);
|
|
203
|
-
|
|
204
|
-
// Create input with file references
|
|
205
|
-
const fileIds = Object.keys(fileMap).join(", ");
|
|
206
|
-
const input = `Analyze these files: ${fileIds}`;
|
|
207
|
-
|
|
208
|
-
// Send to agent with file context
|
|
209
|
-
await agent.handleSend(input, {
|
|
210
|
-
context: {
|
|
211
|
-
mapped_file_ids_with_url: fileMap,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
};
|
|
233
|
+
import { AgentContextProvider } from "@buildship/agent/react";
|
|
215
234
|
|
|
235
|
+
function App() {
|
|
216
236
|
return (
|
|
217
|
-
<
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
multiple
|
|
221
|
-
onChange={(e) => setFiles(Array.from(e.target.files || []))}
|
|
222
|
-
/>
|
|
223
|
-
<button onClick={handleSendWithFiles}>Send with Files</button>
|
|
224
|
-
</div>
|
|
237
|
+
<AgentContextProvider>
|
|
238
|
+
<ChatPage />
|
|
239
|
+
</AgentContextProvider>
|
|
225
240
|
);
|
|
226
241
|
}
|
|
227
242
|
```
|
|
228
243
|
|
|
229
|
-
|
|
244
|
+
## `useAgent` Hook
|
|
230
245
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
context: {
|
|
234
|
-
mapped_file_ids_with_url: {
|
|
235
|
-
"file_name_pdf": "https://storage.url/file1.pdf",
|
|
236
|
-
"image_png": "https://storage.url/image.png"
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Client Tools / Widgets
|
|
243
|
-
|
|
244
|
-
Client tools allow your agent to render interactive widgets directly in the chat
|
|
245
|
-
interface.
|
|
246
|
-
|
|
247
|
-
### 1. Define Tool Configuration
|
|
248
|
-
|
|
249
|
-
Create tool configurations with Zod schemas:
|
|
246
|
+
The main hook for interacting with an agent. Manages messages, streaming,
|
|
247
|
+
sessions, and debug data.
|
|
250
248
|
|
|
251
249
|
```tsx
|
|
252
|
-
import {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
250
|
+
import { useAgent } from "@buildship/agent/react";
|
|
251
|
+
|
|
252
|
+
function ChatPage() {
|
|
253
|
+
const {
|
|
254
|
+
messages, // Message[] — full conversation history
|
|
255
|
+
inProgress, // boolean — true while streaming
|
|
256
|
+
handleSend, // (input, options?) => Promise — send a message
|
|
257
|
+
resumeTool, // (callId, result) => Promise — resume a paused tool
|
|
258
|
+
abort, // () => void — cancel the current stream
|
|
259
|
+
sessionId, // string — current session ID
|
|
260
|
+
sessions, // Session[] — all sessions for this agent
|
|
261
|
+
switchSession, // (sessionId?) => void — switch to a session (or create new)
|
|
262
|
+
deleteSession, // (sessionId) => void — delete a session
|
|
263
|
+
addOptimisticMessage, // (input) => void — add a user message immediately
|
|
264
|
+
debugData, // Record<string, DebugDataType> — debug entries by execution ID
|
|
265
|
+
} = useAgent(
|
|
266
|
+
"agent-id",
|
|
267
|
+
"https://your-project.buildship.run/executeAgent/AGENT_ID",
|
|
268
|
+
"access-key",
|
|
269
|
+
);
|
|
269
270
|
|
|
270
|
-
// Create your widget component
|
|
271
|
-
export function ChartWidget({ title, data }) {
|
|
272
271
|
return (
|
|
273
272
|
<div>
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
{messages.map((msg, i) => (
|
|
274
|
+
<div key={i} className={msg.role}>
|
|
275
|
+
{msg.content}
|
|
276
|
+
</div>
|
|
277
|
+
))}
|
|
278
|
+
|
|
279
|
+
<button onClick={() => handleSend("Hello!")} disabled={inProgress}>
|
|
280
|
+
Send
|
|
281
|
+
</button>
|
|
276
282
|
</div>
|
|
277
283
|
);
|
|
278
284
|
}
|
|
279
285
|
```
|
|
280
286
|
|
|
281
|
-
###
|
|
287
|
+
### `handleSend` Options
|
|
282
288
|
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
```ts
|
|
290
|
+
handleSend(input: string, options?: {
|
|
291
|
+
context?: Record<string, unknown>; // Additional context passed to the agent
|
|
292
|
+
skipUserMessage?: boolean; // Don't add a user message to the conversation
|
|
293
|
+
});
|
|
294
|
+
```
|
|
287
295
|
|
|
288
|
-
|
|
289
|
-
import { ChartWidget, chartToolConfig } from "./widgets/chart";
|
|
290
|
-
import { MapWidget, mapToolConfig } from "./widgets/map";
|
|
296
|
+
## `useAgentContext` Hook
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
An alternative to `useAgent` for multi-agent setups. Initializes agents
|
|
299
|
+
declaratively and shares state through context.
|
|
293
300
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
[chartToolConfig.name]: ChartWidget,
|
|
297
|
-
[mapToolConfig.name]: MapWidget,
|
|
298
|
-
};
|
|
301
|
+
```tsx
|
|
302
|
+
import { useAgentContext } from "@buildship/agent/react";
|
|
299
303
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
"$schema" in parameters
|
|
310
|
-
) {
|
|
311
|
-
const { $schema, ...rest } = parameters as any;
|
|
312
|
-
return {
|
|
313
|
-
name: config.name,
|
|
314
|
-
description: config.description,
|
|
315
|
-
parameters: rest,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return {
|
|
320
|
-
name: config.name,
|
|
321
|
-
description: config.description,
|
|
322
|
-
parameters,
|
|
323
|
-
};
|
|
324
|
-
});
|
|
304
|
+
function ChatPage() {
|
|
305
|
+
const agent = useAgentContext(
|
|
306
|
+
"agent-id",
|
|
307
|
+
"https://your-project.buildship.run/executeAgent/AGENT_ID",
|
|
308
|
+
"access-key",
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Same return shape as useAgent
|
|
312
|
+
const { messages, handleSend, inProgress, sessions, ... } = agent;
|
|
325
313
|
}
|
|
326
314
|
```
|
|
327
315
|
|
|
328
|
-
|
|
316
|
+
## Client Tools (React)
|
|
329
317
|
|
|
330
|
-
|
|
331
|
-
import { getToolDefinitions } from "./widget-registry";
|
|
318
|
+
### `useClientTool` — Headless Tools
|
|
332
319
|
|
|
333
|
-
|
|
334
|
-
const agent = useAgentContext("agent-id", "agent-url", "access-key");
|
|
335
|
-
const tools = getToolDefinitions();
|
|
320
|
+
Register a tool that runs code without rendering any UI:
|
|
336
321
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
322
|
+
```tsx
|
|
323
|
+
import { useClientTool } from "@buildship/agent/react";
|
|
324
|
+
import { z } from "@buildship/agent/core";
|
|
325
|
+
|
|
326
|
+
function ChatPage() {
|
|
327
|
+
// Fire-and-forget — runs handler, result is discarded
|
|
328
|
+
useClientTool("agent-id", {
|
|
329
|
+
name: "show_notification",
|
|
330
|
+
description: "Display a notification to the user",
|
|
331
|
+
parameters: z.object({
|
|
332
|
+
title: z.string(),
|
|
333
|
+
message: z.string(),
|
|
334
|
+
}),
|
|
335
|
+
handler: (inputs) => {
|
|
336
|
+
toast(inputs.title, inputs.message);
|
|
337
|
+
},
|
|
338
|
+
});
|
|
342
339
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
340
|
+
// Blocking tool — agent pauses, handler result is sent back
|
|
341
|
+
useClientTool("agent-id", {
|
|
342
|
+
name: "get_location",
|
|
343
|
+
description: "Get the user's current GPS location",
|
|
344
|
+
parameters: z.object({}),
|
|
345
|
+
await: true,
|
|
346
|
+
handler: async () => {
|
|
347
|
+
const pos = await getCurrentPosition();
|
|
348
|
+
return { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ...
|
|
350
353
|
}
|
|
351
354
|
```
|
|
352
355
|
|
|
353
|
-
###
|
|
356
|
+
### `useClientTool` — Widget Tools
|
|
354
357
|
|
|
355
|
-
|
|
356
|
-
import { widgetRegistry } from "./widget-registry";
|
|
358
|
+
Register a tool that renders interactive UI inline in the conversation:
|
|
357
359
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
+
```tsx
|
|
361
|
+
import { useClientTool, ToolRenderer } from "@buildship/agent/react";
|
|
362
|
+
import { z } from "@buildship/agent/core";
|
|
363
|
+
|
|
364
|
+
function ChatPage() {
|
|
365
|
+
const { messages } = useAgent("agent-id", agentUrl);
|
|
366
|
+
|
|
367
|
+
// Register a widget tool with a render function
|
|
368
|
+
useClientTool("agent-id", {
|
|
369
|
+
name: "feedback_form",
|
|
370
|
+
description: "Collects user feedback",
|
|
371
|
+
parameters: z.object({
|
|
372
|
+
question: z.string().describe("The feedback question"),
|
|
373
|
+
}),
|
|
374
|
+
await: true, // Agent pauses until user submits
|
|
375
|
+
render: ({ inputs, submit, status, result }) => (
|
|
376
|
+
<div>
|
|
377
|
+
<p>{inputs.question}</p>
|
|
378
|
+
{status === "pending" ? (
|
|
379
|
+
<button onClick={() => submit({ answer: "Great!" })}>Submit</button>
|
|
380
|
+
) : (
|
|
381
|
+
<p>✅ Submitted: {JSON.stringify(result)}</p>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
),
|
|
385
|
+
});
|
|
360
386
|
|
|
387
|
+
// Render messages with embedded widgets
|
|
361
388
|
return (
|
|
362
389
|
<div>
|
|
363
|
-
{
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
{/* Fallback: render plain content if no parts */}
|
|
378
|
-
{!message.parts && <p>{message.content}</p>}
|
|
379
|
-
</div>
|
|
380
|
-
))}
|
|
390
|
+
{messages.map((msg) =>
|
|
391
|
+
msg.parts?.map((part) => {
|
|
392
|
+
if (part.type === "text") {
|
|
393
|
+
return <p key={part.firstSequence}>{part.text}</p>;
|
|
394
|
+
}
|
|
395
|
+
if (part.type === "widget") {
|
|
396
|
+
return (
|
|
397
|
+
<ToolRenderer key={part.callId} agentId="agent-id" part={part} />
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}),
|
|
402
|
+
)}
|
|
381
403
|
</div>
|
|
382
404
|
);
|
|
383
405
|
}
|
|
384
406
|
```
|
|
385
407
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
### handleSend Options
|
|
408
|
+
### `ClientToolConfig`
|
|
389
409
|
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
410
|
+
```ts
|
|
411
|
+
interface ClientToolConfig {
|
|
412
|
+
name: string; // Must match the tool name the agent knows
|
|
413
|
+
description: string; // Description of what the tool does
|
|
414
|
+
parameters: ZodSchema | Record<string, any>; // Zod schema or raw JSON Schema
|
|
415
|
+
await?: boolean; // If true, agent pauses until result
|
|
416
|
+
handler?: (inputs: any) => any | Promise<any>; // For headless tools
|
|
417
|
+
render?: (props: ClientToolRenderProps) => any; // For widget tools
|
|
418
|
+
}
|
|
419
|
+
```
|
|
400
420
|
|
|
401
|
-
|
|
402
|
-
tools?: ClientToolDefinition[];
|
|
421
|
+
### `ClientToolRenderProps`
|
|
403
422
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
423
|
+
```ts
|
|
424
|
+
interface ClientToolRenderProps<T = any> {
|
|
425
|
+
inputs: T; // Parsed inputs from the agent
|
|
426
|
+
submit: (result: any) => void; // Submit a result (only for await: true tools)
|
|
427
|
+
status: "pending" | "submitted"; // Widget status
|
|
428
|
+
result?: any; // Persisted result after submission
|
|
429
|
+
}
|
|
407
430
|
```
|
|
408
431
|
|
|
409
|
-
|
|
432
|
+
## Messages & Parts
|
|
410
433
|
|
|
411
|
-
|
|
434
|
+
Messages can contain rich, interleaved content via `parts`:
|
|
412
435
|
|
|
413
|
-
```
|
|
436
|
+
```ts
|
|
414
437
|
type Message = {
|
|
415
438
|
role: "user" | "agent";
|
|
416
439
|
content: string; // Full text content
|
|
417
|
-
parts?: MessagePart[]; //
|
|
440
|
+
parts?: MessagePart[]; // Rich content (text + widgets)
|
|
418
441
|
executionId?: string; // Links to debug data
|
|
419
442
|
};
|
|
420
443
|
|
|
@@ -425,13 +448,39 @@ type MessagePart =
|
|
|
425
448
|
toolName: string;
|
|
426
449
|
callId: string;
|
|
427
450
|
inputs: any;
|
|
428
|
-
|
|
451
|
+
paused?: boolean;
|
|
452
|
+
status?: "pending" | "submitted";
|
|
453
|
+
result?: any;
|
|
429
454
|
};
|
|
430
455
|
```
|
|
431
456
|
|
|
432
|
-
|
|
457
|
+
> **Tip:** When rendering messages, iterate over `msg.parts` instead of
|
|
458
|
+
> `msg.content` to get both text and widgets interleaved in the correct order.
|
|
459
|
+
|
|
460
|
+
## Sessions
|
|
461
|
+
|
|
462
|
+
Sessions are automatically persisted to local storage and synced across tabs.
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
const { sessions, switchSession, deleteSession, sessionId } = useAgent(...);
|
|
466
|
+
|
|
467
|
+
// List all sessions
|
|
468
|
+
sessions.map((s) => (
|
|
469
|
+
<button key={s.id} onClick={() => switchSession(s.id)}>
|
|
470
|
+
{s.name} ({s.messages.length} messages)
|
|
471
|
+
</button>
|
|
472
|
+
));
|
|
473
|
+
|
|
474
|
+
// Create a new session
|
|
475
|
+
switchSession(); // No argument = new session
|
|
476
|
+
|
|
477
|
+
// Delete a session
|
|
478
|
+
deleteSession(sessionId);
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Session Type
|
|
433
482
|
|
|
434
|
-
```
|
|
483
|
+
```ts
|
|
435
484
|
type Session = {
|
|
436
485
|
id: string;
|
|
437
486
|
createdAt: number;
|
|
@@ -441,123 +490,160 @@ type Session = {
|
|
|
441
490
|
};
|
|
442
491
|
```
|
|
443
492
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
type ClientToolDefinition = {
|
|
448
|
-
name: string; // Unique tool identifier
|
|
449
|
-
description: string; // Human-readable description for LLM
|
|
450
|
-
parameters: unknown; // JSON Schema object
|
|
451
|
-
};
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
## Advanced Features
|
|
493
|
+
## Debug Data
|
|
455
494
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
495
|
+
Debug data captures structured information about every tool call, reasoning
|
|
496
|
+
step, and agent handoff during execution. It's stored by `executionId` (matching
|
|
497
|
+
the user message it belongs to).
|
|
459
498
|
|
|
460
499
|
```tsx
|
|
461
|
-
|
|
462
|
-
const agent = useAgentContext("agent-id", "agent-url", "access-key");
|
|
500
|
+
const { debugData, messages } = useAgent(...);
|
|
463
501
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
<pre>{JSON.stringify(debug, null, 2)}</pre>
|
|
470
|
-
</details>
|
|
471
|
-
))}
|
|
472
|
-
</div>
|
|
473
|
-
);
|
|
474
|
-
}
|
|
502
|
+
// Get debug data for a specific message
|
|
503
|
+
const messageDebug = debugData[message.executionId];
|
|
504
|
+
|
|
505
|
+
// Each entry is one of:
|
|
506
|
+
type DebugDataType = Array<ToolExecutionItem | ReasoningItem | HandoffItem>;
|
|
475
507
|
```
|
|
476
508
|
|
|
477
|
-
###
|
|
509
|
+
### Debug Entry Types
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
type ToolExecutionItem = {
|
|
513
|
+
itemType: "tool_call";
|
|
514
|
+
toolName: string;
|
|
515
|
+
callId: string;
|
|
516
|
+
toolType: ToolType; // "flow" | "node" | "mcp" | "client" | "builtin" | "agent"
|
|
517
|
+
status: "progress" | "complete" | "error";
|
|
518
|
+
inputs?: unknown;
|
|
519
|
+
output?: unknown;
|
|
520
|
+
error?: string;
|
|
521
|
+
serverName?: string; // For MCP tools
|
|
522
|
+
};
|
|
478
523
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
524
|
+
type ReasoningItem = {
|
|
525
|
+
itemType: "reasoning";
|
|
526
|
+
reasoning: string;
|
|
527
|
+
index?: number;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
type HandoffItem = {
|
|
531
|
+
itemType: "handoff";
|
|
532
|
+
agentName: string;
|
|
533
|
+
};
|
|
486
534
|
```
|
|
487
535
|
|
|
488
|
-
|
|
536
|
+
## React API Reference
|
|
489
537
|
|
|
490
|
-
|
|
491
|
-
function CancellableRequest() {
|
|
492
|
-
const agent = useAgentContext("agent-id", "agent-url", "access-key");
|
|
538
|
+
### Hooks
|
|
493
539
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
</button>
|
|
500
|
-
</div>
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
```
|
|
540
|
+
| Hook | Description |
|
|
541
|
+
| ------------------------------------------ | ------------------------------------------------ |
|
|
542
|
+
| `useAgent(agentId, agentUrl, accessKey?)` | Main hook — messages, streaming, sessions, debug |
|
|
543
|
+
| `useAgentContext(agentId, agentUrl, key?)` | Context-based alternative for multi-agent setups |
|
|
544
|
+
| `useClientTool(agentId, config)` | Register a client tool (headless or widget) |
|
|
504
545
|
|
|
505
|
-
###
|
|
546
|
+
### Components
|
|
506
547
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
548
|
+
| Component | Description |
|
|
549
|
+
| ------------------------------------------- | -------------------------------------------------- |
|
|
550
|
+
| `<AgentContextProvider>` | Provides shared agent state (sessions, debug data) |
|
|
551
|
+
| `<ToolRenderer agentId={id} part={part} />` | Renders a widget tool from a message part |
|
|
510
552
|
|
|
511
|
-
|
|
512
|
-
// Add message to UI immediately
|
|
513
|
-
agent.addOptimisticMessage(input);
|
|
553
|
+
### Utilities
|
|
514
554
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
555
|
+
| Export | Description |
|
|
556
|
+
| ------------------------------------- | -------------------------------------------------------------------- |
|
|
557
|
+
| `tryParseJSON(value)` | Safely parse a JSON string, returns parsed object or original string |
|
|
558
|
+
| `updateAgentMessageParts(msg, event)` | Append/merge parts into an agent message |
|
|
518
559
|
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
```
|
|
560
|
+
## Full Example
|
|
522
561
|
|
|
523
|
-
|
|
562
|
+
```tsx
|
|
563
|
+
import {
|
|
564
|
+
AgentContextProvider,
|
|
565
|
+
useAgent,
|
|
566
|
+
useClientTool,
|
|
567
|
+
ToolRenderer,
|
|
568
|
+
} from "@buildship/agent/react";
|
|
569
|
+
import { z } from "@buildship/agent/core";
|
|
524
570
|
|
|
525
|
-
|
|
571
|
+
const AGENT_ID = "my-agent";
|
|
572
|
+
const AGENT_URL = "https://my-project.buildship.run/executeAgent/my-agent";
|
|
526
573
|
|
|
527
|
-
|
|
528
|
-
|
|
574
|
+
function App() {
|
|
575
|
+
return (
|
|
576
|
+
<AgentContextProvider>
|
|
577
|
+
<Chat />
|
|
578
|
+
</AgentContextProvider>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
529
581
|
|
|
530
|
-
|
|
531
|
-
|
|
582
|
+
function Chat() {
|
|
583
|
+
const { messages, handleSend, inProgress, resumeTool, abort, debugData } =
|
|
584
|
+
useAgent(AGENT_ID, AGENT_URL);
|
|
585
|
+
const [input, setInput] = useState("");
|
|
586
|
+
|
|
587
|
+
// Register a widget tool
|
|
588
|
+
useClientTool(AGENT_ID, {
|
|
589
|
+
name: "poll",
|
|
590
|
+
description: "Ask the user to vote on options",
|
|
591
|
+
parameters: z.object({
|
|
592
|
+
question: z.string(),
|
|
593
|
+
options: z.array(z.string()),
|
|
594
|
+
}),
|
|
595
|
+
await: true,
|
|
596
|
+
render: ({ inputs, submit, status }) => (
|
|
597
|
+
<div>
|
|
598
|
+
<p>{inputs.question}</p>
|
|
599
|
+
{status === "pending" ? (
|
|
600
|
+
inputs.options.map((opt) => (
|
|
601
|
+
<button key={opt} onClick={() => submit({ vote: opt })}>
|
|
602
|
+
{opt}
|
|
603
|
+
</button>
|
|
604
|
+
))
|
|
605
|
+
) : (
|
|
606
|
+
<p>✅ Vote recorded</p>
|
|
607
|
+
)}
|
|
608
|
+
</div>
|
|
609
|
+
),
|
|
610
|
+
});
|
|
532
611
|
|
|
533
|
-
|
|
612
|
+
const send = () => {
|
|
613
|
+
if (!input.trim()) return;
|
|
614
|
+
handleSend(input);
|
|
615
|
+
setInput("");
|
|
616
|
+
};
|
|
534
617
|
|
|
535
|
-
|
|
618
|
+
return (
|
|
619
|
+
<div>
|
|
620
|
+
{messages.map((msg, i) => (
|
|
621
|
+
<div key={i}>
|
|
622
|
+
<strong>{msg.role}:</strong>
|
|
623
|
+
{msg.parts?.map((part) =>
|
|
624
|
+
part.type === "text" ? (
|
|
625
|
+
<span key={part.firstSequence}>{part.text}</span>
|
|
626
|
+
) : (
|
|
627
|
+
<ToolRenderer key={part.callId} agentId={AGENT_ID} part={part} />
|
|
628
|
+
),
|
|
629
|
+
) ?? msg.content}
|
|
630
|
+
</div>
|
|
631
|
+
))}
|
|
536
632
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
633
|
+
<input
|
|
634
|
+
value={input}
|
|
635
|
+
onChange={(e) => setInput(e.target.value)}
|
|
636
|
+
onKeyDown={(e) => e.key === "Enter" && send()}
|
|
637
|
+
/>
|
|
638
|
+
<button onClick={send} disabled={inProgress}>
|
|
639
|
+
Send
|
|
640
|
+
</button>
|
|
641
|
+
{inProgress && <button onClick={abort}>Stop</button>}
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
}
|
|
546
645
|
```
|
|
547
646
|
|
|
548
|
-
## Best Practices
|
|
549
|
-
|
|
550
|
-
1. **Single Provider**: Only use one `AgentContextProvider` at the root of your
|
|
551
|
-
app
|
|
552
|
-
2. **Tool Descriptions**: Write clear, detailed descriptions for client tools to
|
|
553
|
-
help the LLM understand when to use them
|
|
554
|
-
3. **File URLs**: Ensure file URLs are publicly accessible or use signed URLs
|
|
555
|
-
with sufficient expiration
|
|
556
|
-
4. **Error Handling**: Wrap `handleSend` calls in try-catch blocks for error
|
|
557
|
-
handling
|
|
558
|
-
5. **Widget Registry**: Keep your widget registry centralized for easier
|
|
559
|
-
maintenance
|
|
560
|
-
|
|
561
647
|
## License
|
|
562
648
|
|
|
563
649
|
MIT
|