dooers-agents-client 0.1.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 +261 -0
- package/dist/main.cjs +939 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.d.cts +212 -0
- package/dist/main.d.ts +212 -0
- package/dist/main.js +927 -0
- package/dist/main.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# dooers-agents-client
|
|
2
|
+
|
|
3
|
+
React agents client SDK for agents server SDK.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install dooers-agents-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependency: `react >= 18`
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
Wrap your component tree in `WorkerProvider`, then use hooks anywhere inside.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { WorkerProvider, useConnection, useThreadDetails, useMessage } from "dooers-agents-client"
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<WorkerProvider
|
|
23
|
+
url="ws://localhost:8000/ws"
|
|
24
|
+
workerId="worker-1"
|
|
25
|
+
userId="user-1"
|
|
26
|
+
userName="Alice"
|
|
27
|
+
>
|
|
28
|
+
<Chat threadId="thread-1" />
|
|
29
|
+
</WorkerProvider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Chat({ threadId }: { threadId: string }) {
|
|
34
|
+
const { status } = useConnection()
|
|
35
|
+
const { events, isWaiting } = useThreadDetails(threadId)
|
|
36
|
+
const { send } = useMessage()
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<p>{status}</p>
|
|
41
|
+
|
|
42
|
+
{events.map((event) => (
|
|
43
|
+
<div key={event.id}>
|
|
44
|
+
<strong>{event.actor}</strong>
|
|
45
|
+
{event.content?.map((part, i) =>
|
|
46
|
+
part.type === "text" ? <p key={i}>{part.text}</p> : null
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
|
|
51
|
+
{isWaiting && <p>Thinking...</p>}
|
|
52
|
+
|
|
53
|
+
<button onClick={() => send({ text: "Hello!", threadId })}>
|
|
54
|
+
Send
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Provider
|
|
62
|
+
|
|
63
|
+
`WorkerProvider` manages the WebSocket lifecycle. On mount it connects, on unmount it disconnects. When any prop changes, the connection resets.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<WorkerProvider
|
|
67
|
+
url="ws://localhost:8000/ws" // WebSocket endpoint
|
|
68
|
+
workerId="worker-1" // Worker to connect to
|
|
69
|
+
organizationId="org-1" // Context passed to handler
|
|
70
|
+
workspaceId="ws-1"
|
|
71
|
+
userId="user-1"
|
|
72
|
+
userName="Alice"
|
|
73
|
+
userEmail="alice@example.com"
|
|
74
|
+
userRole="member"
|
|
75
|
+
authToken="sk-..." // Optional authentication
|
|
76
|
+
onError={(err) => console.error(err.code, err.message)}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</WorkerProvider>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
All props except `children` and `onError` are primitive strings, so React can diff them cheaply.
|
|
83
|
+
|
|
84
|
+
## Hooks
|
|
85
|
+
|
|
86
|
+
All hooks must be called inside a `WorkerProvider`.
|
|
87
|
+
|
|
88
|
+
### useConnection
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
const { status, error, reconnectFailed, reconnect } = useConnection()
|
|
92
|
+
|
|
93
|
+
// status: "idle" | "connecting" | "connected" | "disconnected" | "error"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Auto-reconnects up to 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s). When all attempts fail, `reconnectFailed` becomes `true` and you can offer a manual retry button calling `reconnect()`.
|
|
97
|
+
|
|
98
|
+
### useThreadDetails
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const { thread, events, runs, isLoading, isWaiting } = useThreadDetails(threadId)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Subscribes on mount, unsubscribes on unmount. Refcounted internally, so multiple components subscribing to the same thread share a single WebSocket subscription.
|
|
105
|
+
|
|
106
|
+
`events` includes optimistic messages that appear instantly when sending, then get replaced by the server-confirmed version. `isWaiting` is `true` while any run has status `"running"`.
|
|
107
|
+
|
|
108
|
+
### useThreadEvents
|
|
109
|
+
|
|
110
|
+
Load older messages beyond the initial snapshot.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
const { hasOlderEvents, loadOlderEvents } = useThreadEvents(threadId)
|
|
114
|
+
|
|
115
|
+
if (hasOlderEvents) {
|
|
116
|
+
loadOlderEvents(50) // load 50 more
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### useThreadsList
|
|
121
|
+
|
|
122
|
+
Read-only thread list for the connected worker.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
const { threads, isLoading, hasMore, totalCount } = useThreadsList()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Threads load automatically on connect. `totalCount` reflects the server-reported total.
|
|
129
|
+
|
|
130
|
+
### useThreadsActions
|
|
131
|
+
|
|
132
|
+
Thread mutations (delete, load more).
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const { deleteThread, loadMore } = useThreadsActions()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`loadMore()` for cursor-based pagination. `deleteThread(id)` broadcasts deletion to all connected clients.
|
|
139
|
+
|
|
140
|
+
### useMessage
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
const { send } = useMessage()
|
|
144
|
+
|
|
145
|
+
// Existing thread
|
|
146
|
+
send({ text: "Hello!", threadId: "t-1" })
|
|
147
|
+
|
|
148
|
+
// New thread (omit threadId — returns the created thread ID)
|
|
149
|
+
const { threadId } = await send({ text: "Start a conversation" })
|
|
150
|
+
|
|
151
|
+
// Rich content
|
|
152
|
+
send({
|
|
153
|
+
threadId: "t-1",
|
|
154
|
+
content: [
|
|
155
|
+
{ type: "text", text: "Check this out:" },
|
|
156
|
+
{ type: "image", url: "https://...", mimeType: "image/png" },
|
|
157
|
+
],
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### useFeedback
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const { feedback, like, dislike } = useFeedback(eventId, "event")
|
|
165
|
+
|
|
166
|
+
// feedback: "like" | "dislike" | null
|
|
167
|
+
like()
|
|
168
|
+
dislike("The response was incorrect")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Second argument is the target type: `"event"`, `"run"`, or `"thread"`.
|
|
172
|
+
|
|
173
|
+
### useAnalytics
|
|
174
|
+
|
|
175
|
+
Live event stream. Subscribe and unsubscribe explicitly.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const { events, counters, subscribe, unsubscribe } = useAnalytics()
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
subscribe()
|
|
182
|
+
return () => unsubscribe()
|
|
183
|
+
}, [subscribe, unsubscribe])
|
|
184
|
+
|
|
185
|
+
// events: AnalyticsEvent[] — latest events
|
|
186
|
+
// counters: Record<string, number> — aggregated counters
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### useSettings
|
|
190
|
+
|
|
191
|
+
Worker settings with real-time sync across clients.
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
const { fields, updatedAt, isLoading, subscribe, unsubscribe, patchField } = useSettings()
|
|
195
|
+
|
|
196
|
+
// fields: (SettingsField | SettingsFieldGroup)[]
|
|
197
|
+
patchField("model", "gpt-4o")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Subscribe explicitly — designed so you only receive updates when the settings UI is visible:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
subscribe()
|
|
205
|
+
return () => unsubscribe()
|
|
206
|
+
}, [subscribe, unsubscribe])
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Types
|
|
210
|
+
|
|
211
|
+
All public types are camelCase. The SDK transforms the wire format (snake_case) internally.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import type {
|
|
215
|
+
Thread, // { id, workerId, title, createdAt, updatedAt, lastEventAt, metadata }
|
|
216
|
+
ThreadEvent, // { id, threadId, type, actor, author, content, data, createdAt, ... }
|
|
217
|
+
Run, // { id, threadId, agentId, status, startedAt, endedAt, error }
|
|
218
|
+
ContentPart, // TextPart | ImagePart | DocumentPart
|
|
219
|
+
Metadata, // { userId, userName, userEmail, userRole, organizationId, workspaceId }
|
|
220
|
+
ConnectionStatus, // "idle" | "connecting" | "connected" | "disconnected" | "error"
|
|
221
|
+
Actor, // "user" | "assistant" | "system" | "tool"
|
|
222
|
+
EventType, // "message" | "tool.call" | "tool.result" | "tool.transaction" | ...
|
|
223
|
+
RunStatus, // "running" | "succeeded" | "failed" | "canceled"
|
|
224
|
+
ThreadState, // { thread, events, runs, isLoading, isWaiting }
|
|
225
|
+
SettingsField,
|
|
226
|
+
SettingsFieldGroup,
|
|
227
|
+
SettingsItem, // SettingsField | SettingsFieldGroup
|
|
228
|
+
AnalyticsEvent,
|
|
229
|
+
FeedbackType, // "like" | "dislike"
|
|
230
|
+
FeedbackTarget, // "event" | "run" | "thread"
|
|
231
|
+
} from "dooers-agents-client"
|
|
232
|
+
|
|
233
|
+
import { isSettingsFieldGroup } from "dooers-agents-client" // type guard
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Architecture
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
WorkerProvider
|
|
240
|
+
├── WorkerClient WebSocket lifecycle, reconnection, optimistic events
|
|
241
|
+
└── WorkerStore Zustand vanilla store
|
|
242
|
+
├── connection { status, error, reconnectFailed }
|
|
243
|
+
├── threads Record<id, Thread>
|
|
244
|
+
├── events Record<threadId, ThreadEvent[]>
|
|
245
|
+
├── optimistic Record<threadId, ThreadEvent[]> (merged into events by useThreadDetails)
|
|
246
|
+
├── runs Record<threadId, Run[]>
|
|
247
|
+
├── settings { fields, updatedAt, isLoading }
|
|
248
|
+
├── analytics { events, counters }
|
|
249
|
+
└── feedback Record<targetId, FeedbackType>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The client handles:
|
|
253
|
+
|
|
254
|
+
- **Optimistic messages** — shown immediately on send, reconciled when the server confirms via `clientEventId`
|
|
255
|
+
- **Refcounted subscriptions** — multiple `useThreadDetails(id)` calls share one WebSocket subscription
|
|
256
|
+
- **Gap recovery** — on reconnect, sends `afterEventId` to resume from where the client left off
|
|
257
|
+
- **Exponential backoff** — reconnects at 1s, 2s, 4s, 8s, 16s intervals before giving up
|
|
258
|
+
|
|
259
|
+
## See Also
|
|
260
|
+
|
|
261
|
+
- [dooers-agents-server](https://github.com/Dooers-ai/dooers-agents-server) — Python SDK for building the agents this client connects to
|