@standardagents/react 0.8.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/README.md +544 -0
- package/dist/index.d.ts +457 -0
- package/dist/index.js +575 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# @standardagents/react
|
|
2
|
+
|
|
3
|
+
React hooks and components for Standard Agents - connect to AI agent threads with real-time updates, send messages, and listen for custom events.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @standardagents/react
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @standardagents/react
|
|
11
|
+
# or
|
|
12
|
+
yarn add @standardagents/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import {
|
|
19
|
+
StandardAgentsProvider,
|
|
20
|
+
ThreadProvider,
|
|
21
|
+
useThread,
|
|
22
|
+
sendMessage,
|
|
23
|
+
} from "@standardagents/react"
|
|
24
|
+
|
|
25
|
+
function App() {
|
|
26
|
+
return (
|
|
27
|
+
<StandardAgentsProvider config={{ endpoint: "https://your-api.com" }}>
|
|
28
|
+
<ThreadProvider threadId="thread-123">
|
|
29
|
+
<ChatInterface />
|
|
30
|
+
</ThreadProvider>
|
|
31
|
+
</StandardAgentsProvider>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ChatInterface() {
|
|
36
|
+
// No thread ID needed - inherited from ThreadProvider context
|
|
37
|
+
const messages = useThread()
|
|
38
|
+
|
|
39
|
+
const handleSend = async (text: string) => {
|
|
40
|
+
await sendMessage("thread-123", {
|
|
41
|
+
role: "user",
|
|
42
|
+
content: text,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
{messages.map((msg) => (
|
|
49
|
+
<div key={msg.id}>
|
|
50
|
+
<strong>{msg.role}:</strong> {msg.content}
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
<input onSubmit={(e) => handleSend(e.currentTarget.value)} />
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
The package reads the authentication token from `localStorage` using the key `standardagents_auth_token`:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Set the token before using the hooks
|
|
65
|
+
localStorage.setItem("standardagents_auth_token", "your-token-here")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
All API requests and WebSocket connections will automatically include this token.
|
|
69
|
+
|
|
70
|
+
## API Reference
|
|
71
|
+
|
|
72
|
+
### `StandardAgentsProvider`
|
|
73
|
+
|
|
74
|
+
Context provider that configures the Standard Agents client for all child components.
|
|
75
|
+
|
|
76
|
+
**Props:**
|
|
77
|
+
|
|
78
|
+
- `config.endpoint: string` - The API endpoint URL (e.g., `https://api.example.com`)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<StandardAgentsProvider config={{ endpoint: "https://api.example.com" }}>
|
|
82
|
+
{children}
|
|
83
|
+
</StandardAgentsProvider>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### `ThreadProvider`
|
|
89
|
+
|
|
90
|
+
Context provider that establishes a WebSocket connection to a specific thread. Must be nested inside `StandardAgentsProvider`. Provides thread context to child hooks like `useThread` and `onThreadEvent`.
|
|
91
|
+
|
|
92
|
+
**Props:**
|
|
93
|
+
|
|
94
|
+
- `threadId: string` - The thread ID to connect to
|
|
95
|
+
- `preload?: boolean` - Fetch existing messages on mount (default: `true`)
|
|
96
|
+
- `live?: boolean` - Enable WebSocket for real-time updates (default: `true`)
|
|
97
|
+
- `depth?: number` - Message depth level for nested conversations (default: `0`)
|
|
98
|
+
- `includeSilent?: boolean` - Include silent messages (default: `false`)
|
|
99
|
+
- `endpoint?: string` - Override the endpoint from context
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<StandardAgentsProvider config={{ endpoint: "https://api.example.com" }}>
|
|
103
|
+
<ThreadProvider threadId="thread-123" live={true} depth={0}>
|
|
104
|
+
<YourComponents />
|
|
105
|
+
</ThreadProvider>
|
|
106
|
+
</StandardAgentsProvider>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `useThread(options?)`
|
|
112
|
+
|
|
113
|
+
Hook to subscribe to thread messages. Must be used within a `ThreadProvider`.
|
|
114
|
+
|
|
115
|
+
**Parameters:**
|
|
116
|
+
|
|
117
|
+
- `options?: UseThreadOptions` - Configuration options
|
|
118
|
+
|
|
119
|
+
**Options:**
|
|
120
|
+
|
|
121
|
+
- `useWorkblocks?: boolean` - Group tool calls into workblocks (default: `true`)
|
|
122
|
+
|
|
123
|
+
**Returns:** `ThreadMessage[]` - Array of messages or workblocks
|
|
124
|
+
|
|
125
|
+
**Example:**
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
function ThreadView() {
|
|
129
|
+
// Thread ID inherited from ThreadProvider context
|
|
130
|
+
const messages = useThread()
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div>
|
|
134
|
+
{messages.map((item) => {
|
|
135
|
+
if (item.type === "workblock") {
|
|
136
|
+
return <WorkblockView key={item.id} workblock={item} />
|
|
137
|
+
}
|
|
138
|
+
return <MessageView key={item.id} message={item} />
|
|
139
|
+
})}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Disable workblocks transformation
|
|
145
|
+
function RawMessagesView() {
|
|
146
|
+
const messages = useThread({ useWorkblocks: false })
|
|
147
|
+
// Returns raw messages without workblock grouping
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### `useThreadId()`
|
|
154
|
+
|
|
155
|
+
Hook to get the current thread ID from context. Must be used within a `ThreadProvider`.
|
|
156
|
+
|
|
157
|
+
**Returns:** `string` - The thread ID
|
|
158
|
+
|
|
159
|
+
**Example:**
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
function CurrentThreadId() {
|
|
163
|
+
const threadId = useThreadId()
|
|
164
|
+
return <span>Thread: {threadId}</span>
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `sendMessage(threadId, payload, options?)`
|
|
171
|
+
|
|
172
|
+
Send a message to a thread. This is a standalone function that works outside of React components.
|
|
173
|
+
|
|
174
|
+
**Parameters:**
|
|
175
|
+
|
|
176
|
+
- `threadId: string` - The thread ID
|
|
177
|
+
- `payload: SendMessagePayload` - The message to send
|
|
178
|
+
- `role: 'user' | 'assistant'` - Message role
|
|
179
|
+
- `content: string | null` - Message content
|
|
180
|
+
- `silent?: boolean` - Silent message (not shown to user, default: `false`)
|
|
181
|
+
- `options?: { endpoint?: string }` - Override endpoint
|
|
182
|
+
|
|
183
|
+
**Returns:** `Promise<Message>` - The created message
|
|
184
|
+
|
|
185
|
+
**Example:**
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { sendMessage, useThreadId } from "@standardagents/react"
|
|
189
|
+
|
|
190
|
+
function SendButton() {
|
|
191
|
+
const threadId = useThreadId()
|
|
192
|
+
|
|
193
|
+
const handleSend = async () => {
|
|
194
|
+
await sendMessage(threadId, {
|
|
195
|
+
role: "user",
|
|
196
|
+
content: "Hello, agent!",
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return <button onClick={handleSend}>Send</button>
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Send a silent message (for context injection)
|
|
204
|
+
await sendMessage("thread-123", {
|
|
205
|
+
role: "user",
|
|
206
|
+
content: "Additional context",
|
|
207
|
+
silent: true,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Send an assistant message (for injecting responses)
|
|
211
|
+
await sendMessage("thread-123", {
|
|
212
|
+
role: "assistant",
|
|
213
|
+
content: "Custom assistant response",
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### `stopThread(threadId, options?)`
|
|
220
|
+
|
|
221
|
+
Cancel an in-flight thread execution.
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
|
|
225
|
+
- `threadId: string` - The thread ID
|
|
226
|
+
- `options?: { endpoint?: string }` - Override endpoint
|
|
227
|
+
|
|
228
|
+
**Returns:** `Promise<void>`
|
|
229
|
+
|
|
230
|
+
**Example:**
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
import { stopThread, useThreadId } from "@standardagents/react"
|
|
234
|
+
|
|
235
|
+
function StopButton() {
|
|
236
|
+
const threadId = useThreadId()
|
|
237
|
+
const [stopping, setStopping] = useState(false)
|
|
238
|
+
|
|
239
|
+
const handleStop = async () => {
|
|
240
|
+
setStopping(true)
|
|
241
|
+
try {
|
|
242
|
+
await stopThread(threadId)
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error("Failed to stop thread:", error)
|
|
245
|
+
} finally {
|
|
246
|
+
setStopping(false)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<button onClick={handleStop} disabled={stopping}>
|
|
252
|
+
{stopping ? "Stopping..." : "Stop Execution"}
|
|
253
|
+
</button>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### `onThreadEvent<T>(eventType)`
|
|
261
|
+
|
|
262
|
+
Hook to listen for custom events emitted by the agent via WebSocket. Must be used within a `ThreadProvider`.
|
|
263
|
+
|
|
264
|
+
**Parameters:**
|
|
265
|
+
|
|
266
|
+
- `eventType: string` - The custom event type to listen for
|
|
267
|
+
|
|
268
|
+
**Type Parameter:**
|
|
269
|
+
|
|
270
|
+
- `T` - The expected shape of the event data
|
|
271
|
+
|
|
272
|
+
**Returns:** `T | null` - The latest event value, or `null` if no event received yet
|
|
273
|
+
|
|
274
|
+
**Example:**
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
import { onThreadEvent } from "@standardagents/react"
|
|
278
|
+
|
|
279
|
+
function TodoProgress() {
|
|
280
|
+
// Thread ID inherited from ThreadProvider context
|
|
281
|
+
const todos = onThreadEvent<{ todos: string[]; completed: number }>(
|
|
282
|
+
"todo-updated"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if (!todos) return <div>Waiting for updates...</div>
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div>
|
|
289
|
+
<p>
|
|
290
|
+
Progress: {todos.completed} / {todos.todos.length}
|
|
291
|
+
</p>
|
|
292
|
+
<ul>
|
|
293
|
+
{todos.todos.map((todo, i) => (
|
|
294
|
+
<li key={i}>{todo}</li>
|
|
295
|
+
))}
|
|
296
|
+
</ul>
|
|
297
|
+
</div>
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Backend Event Emission:**
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// In your agent's tool or hook (using @standardagents/builder)
|
|
306
|
+
import { emitThreadEvent } from "@standardagents/builder"
|
|
307
|
+
|
|
308
|
+
// Emit an event to all connected clients
|
|
309
|
+
emitThreadEvent(flow, "todo-updated", {
|
|
310
|
+
todos: ["Task 1", "Task 2"],
|
|
311
|
+
completed: 1,
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Message Types
|
|
318
|
+
|
|
319
|
+
### Regular Message
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
interface Message {
|
|
323
|
+
id: string
|
|
324
|
+
role: "user" | "assistant" | "system" | "tool"
|
|
325
|
+
content: string | null
|
|
326
|
+
created_at: number // microseconds
|
|
327
|
+
reasoning_content?: string | null
|
|
328
|
+
silent?: boolean
|
|
329
|
+
depth?: number
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Workblock
|
|
334
|
+
|
|
335
|
+
When `useWorkblocks: true`, tool calls and results are grouped:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
interface WorkMessage {
|
|
339
|
+
type: "workblock"
|
|
340
|
+
id: string
|
|
341
|
+
content: string | null
|
|
342
|
+
reasoning_content?: string | null
|
|
343
|
+
status: "pending" | "completed"
|
|
344
|
+
workItems: WorkItem[]
|
|
345
|
+
created_at: number
|
|
346
|
+
depth?: number
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
interface WorkItem {
|
|
350
|
+
id: string
|
|
351
|
+
type: "tool_call" | "tool_result"
|
|
352
|
+
name: string
|
|
353
|
+
input?: string
|
|
354
|
+
content?: string
|
|
355
|
+
status: "success" | "error" | null
|
|
356
|
+
tool_call_id?: string
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Complete Example
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import {
|
|
366
|
+
StandardAgentsProvider,
|
|
367
|
+
ThreadProvider,
|
|
368
|
+
useThread,
|
|
369
|
+
useThreadId,
|
|
370
|
+
sendMessage,
|
|
371
|
+
stopThread,
|
|
372
|
+
onThreadEvent,
|
|
373
|
+
} from "@standardagents/react"
|
|
374
|
+
import { useState, useEffect } from "react"
|
|
375
|
+
|
|
376
|
+
function App() {
|
|
377
|
+
// Set auth token
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
localStorage.setItem("standardagents_auth_token", "your-token")
|
|
380
|
+
}, [])
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<StandardAgentsProvider config={{ endpoint: "https://api.example.com" }}>
|
|
384
|
+
<ThreadProvider threadId="thread-123" live={true}>
|
|
385
|
+
<AgentChat />
|
|
386
|
+
</ThreadProvider>
|
|
387
|
+
</StandardAgentsProvider>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function AgentChat() {
|
|
392
|
+
const [input, setInput] = useState("")
|
|
393
|
+
const [isExecuting, setIsExecuting] = useState(false)
|
|
394
|
+
|
|
395
|
+
// Get thread ID from context
|
|
396
|
+
const threadId = useThreadId()
|
|
397
|
+
|
|
398
|
+
// Subscribe to thread messages (no threadId needed)
|
|
399
|
+
const messages = useThread({ useWorkblocks: true })
|
|
400
|
+
|
|
401
|
+
// Listen for custom progress events (no threadId needed)
|
|
402
|
+
const progress = onThreadEvent<{ step: string; percent: number }>("progress")
|
|
403
|
+
|
|
404
|
+
const handleSend = async () => {
|
|
405
|
+
if (!input.trim()) return
|
|
406
|
+
|
|
407
|
+
setIsExecuting(true)
|
|
408
|
+
try {
|
|
409
|
+
await sendMessage(threadId, {
|
|
410
|
+
role: "user",
|
|
411
|
+
content: input,
|
|
412
|
+
})
|
|
413
|
+
setInput("")
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error("Failed to send message:", error)
|
|
416
|
+
} finally {
|
|
417
|
+
setIsExecuting(false)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const handleStop = async () => {
|
|
422
|
+
try {
|
|
423
|
+
await stopThread(threadId)
|
|
424
|
+
setIsExecuting(false)
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error("Failed to stop thread:", error)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<div>
|
|
432
|
+
{/* Progress indicator */}
|
|
433
|
+
{progress && (
|
|
434
|
+
<div>
|
|
435
|
+
<p>{progress.step}</p>
|
|
436
|
+
<progress value={progress.percent} max={100} />
|
|
437
|
+
</div>
|
|
438
|
+
)}
|
|
439
|
+
|
|
440
|
+
{/* Message list */}
|
|
441
|
+
<div>
|
|
442
|
+
{messages.map((item) => {
|
|
443
|
+
if (item.type === "workblock") {
|
|
444
|
+
return (
|
|
445
|
+
<div key={item.id}>
|
|
446
|
+
<p>Executing tools...</p>
|
|
447
|
+
{item.workItems.map((work) => (
|
|
448
|
+
<div key={work.id}>
|
|
449
|
+
{work.type === "tool_call" && `🔧 ${work.name}`}
|
|
450
|
+
{work.type === "tool_result" && `✅ ${work.status}`}
|
|
451
|
+
</div>
|
|
452
|
+
))}
|
|
453
|
+
</div>
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<div key={item.id}>
|
|
459
|
+
<strong>{item.role}:</strong> {item.content}
|
|
460
|
+
</div>
|
|
461
|
+
)
|
|
462
|
+
})}
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
{/* Input */}
|
|
466
|
+
<div>
|
|
467
|
+
<input
|
|
468
|
+
value={input}
|
|
469
|
+
onChange={(e) => setInput(e.target.value)}
|
|
470
|
+
onKeyDown={(e) => e.key === "Enter" && handleSend()}
|
|
471
|
+
disabled={isExecuting}
|
|
472
|
+
/>
|
|
473
|
+
<button onClick={handleSend} disabled={isExecuting}>
|
|
474
|
+
Send
|
|
475
|
+
</button>
|
|
476
|
+
{isExecuting && <button onClick={handleStop}>Stop</button>}
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## TypeScript Support
|
|
486
|
+
|
|
487
|
+
The package is written in TypeScript and includes full type definitions.
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
import type {
|
|
491
|
+
Message,
|
|
492
|
+
WorkMessage,
|
|
493
|
+
ThreadMessage,
|
|
494
|
+
SendMessagePayload,
|
|
495
|
+
UseThreadOptions,
|
|
496
|
+
ThreadProviderOptions,
|
|
497
|
+
} from "@standardagents/react"
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Local Development
|
|
503
|
+
|
|
504
|
+
### Building the Package
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Install dependencies
|
|
508
|
+
pnpm install
|
|
509
|
+
|
|
510
|
+
# Build package
|
|
511
|
+
pnpm build
|
|
512
|
+
|
|
513
|
+
# Watch mode (rebuilds on changes)
|
|
514
|
+
pnpm dev
|
|
515
|
+
|
|
516
|
+
# Type check
|
|
517
|
+
pnpm typecheck
|
|
518
|
+
|
|
519
|
+
# Run tests
|
|
520
|
+
pnpm test
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Linking for Local Development
|
|
524
|
+
|
|
525
|
+
Using `file:` protocol in your app's `package.json`:
|
|
526
|
+
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"dependencies": {
|
|
530
|
+
"@standardagents/react": "file:../path/to/packages/react"
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Or using `pnpm link`:
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
# In this package
|
|
539
|
+
cd packages/react
|
|
540
|
+
pnpm link --global
|
|
541
|
+
|
|
542
|
+
# In your app
|
|
543
|
+
pnpm link --global @standardagents/react
|
|
544
|
+
```
|