@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 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
+ ```