@steventsao/agent-session 0.1.22 → 0.1.24

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/dist/react.js CHANGED
@@ -1,26 +1,482 @@
1
- /**
2
- * React bindings for agent-session
3
- *
4
- * @example
5
- * ```tsx
6
- * import { useAgentSession } from 'agent-session/react';
7
- *
8
- * function Chat() {
9
- * const { messages, sendMessage, isConnected } = useAgentSession({
10
- * url: 'wss://your-agent-session.workers.dev',
11
- * sessionId: 'doc-123',
12
- * });
13
- *
14
- * return (
15
- * <div>
16
- * {messages.map(m => <p key={m.id}>{m.content}</p>)}
17
- * <button onClick={() => sendMessage('Hello!')}>Send</button>
18
- * </div>
19
- * );
20
- * }
21
- * ```
22
- */
23
- export { useAgentSession } from './use-agent-session';
24
- // Re-export core types for convenience
25
- export * from '@steventsao/agent-session-core';
26
- //# sourceMappingURL=react.js.map
1
+ // src/use-agent-session.ts
2
+ import { useEffect, useState, useCallback, useRef } from "react";
3
+
4
+ // src/client.ts
5
+ var AgentSessionClient = class {
6
+ ws = null;
7
+ baseUrl;
8
+ sessionId;
9
+ userId;
10
+ pingIntervalId = null;
11
+ reconnectTimeout = null;
12
+ reconnectAttempts = 0;
13
+ socketId = 0;
14
+ maxReconnectAttempts;
15
+ autoReconnect;
16
+ pingInterval;
17
+ // Event resumption (Manus pattern)
18
+ lastEventId = null;
19
+ onEventIdChange = null;
20
+ // Event handlers
21
+ handlers = /* @__PURE__ */ new Map();
22
+ globalHandlers = /* @__PURE__ */ new Set();
23
+ constructor(options) {
24
+ this.baseUrl = options.url.replace(/\/$/, "");
25
+ this.sessionId = options.sessionId;
26
+ this.userId = options.userId || null;
27
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
28
+ this.autoReconnect = options.autoReconnect ?? true;
29
+ this.pingInterval = options.pingInterval ?? 3e4;
30
+ this.lastEventId = options.lastEventId || null;
31
+ this.onEventIdChange = options.onEventIdChange || null;
32
+ if (options.autoConnect) {
33
+ this.connect();
34
+ }
35
+ }
36
+ // ==========================================================================
37
+ // Connection Management
38
+ // ==========================================================================
39
+ connect() {
40
+ if (this.ws?.readyState === WebSocket.OPEN) {
41
+ return;
42
+ }
43
+ this.reconnectAttempts = 0;
44
+ const thisSocketId = ++this.socketId;
45
+ const params = new URLSearchParams();
46
+ if (this.userId) params.set("userId", this.userId);
47
+ const url = `${this.baseUrl}/session/${this.sessionId}/ws?${params}`;
48
+ console.log("[AgentSession] Connecting to:", url);
49
+ this.ws = new WebSocket(url);
50
+ this.ws.onopen = () => {
51
+ if (thisSocketId !== this.socketId) return;
52
+ console.log("[AgentSession] Connected");
53
+ this.reconnectAttempts = 0;
54
+ this.startPing();
55
+ this.send({
56
+ type: "JOIN_SESSION",
57
+ lastEventId: this.lastEventId || void 0
58
+ });
59
+ };
60
+ this.ws.onmessage = (event) => {
61
+ if (thisSocketId !== this.socketId) return;
62
+ try {
63
+ const data = JSON.parse(event.data);
64
+ this.handleMessage(data);
65
+ } catch (err) {
66
+ console.error("[AgentSession] Parse error:", err);
67
+ }
68
+ };
69
+ this.ws.onclose = (event) => {
70
+ if (thisSocketId !== this.socketId) return;
71
+ console.log("[AgentSession] Disconnected:", event.code);
72
+ this.stopPing();
73
+ this.emit({ type: "SYSTEM", msg: "Disconnected" });
74
+ if (this.autoReconnect) {
75
+ this.maybeReconnect();
76
+ }
77
+ };
78
+ this.ws.onerror = (event) => {
79
+ if (thisSocketId !== this.socketId) return;
80
+ console.error("[AgentSession] Error:", event);
81
+ };
82
+ }
83
+ disconnect() {
84
+ this.stopPing();
85
+ this.clearReconnect();
86
+ this.socketId++;
87
+ this.ws?.close();
88
+ this.ws = null;
89
+ }
90
+ get isConnected() {
91
+ return this.ws?.readyState === WebSocket.OPEN;
92
+ }
93
+ /** Get current event cursor for persistence */
94
+ getLastEventId() {
95
+ return this.lastEventId;
96
+ }
97
+ /** Set event cursor (load from storage before connect) */
98
+ setLastEventId(eventId) {
99
+ this.lastEventId = eventId;
100
+ }
101
+ /** Clear event cursor (for new chat/reset) */
102
+ clearEventId() {
103
+ this.lastEventId = null;
104
+ }
105
+ // ==========================================================================
106
+ // Event Handling
107
+ // ==========================================================================
108
+ on(eventType, handler) {
109
+ if (!this.handlers.has(eventType)) {
110
+ this.handlers.set(eventType, /* @__PURE__ */ new Set());
111
+ }
112
+ this.handlers.get(eventType).add(handler);
113
+ return () => this.handlers.get(eventType)?.delete(handler);
114
+ }
115
+ onAny(handler) {
116
+ this.globalHandlers.add(handler);
117
+ return () => this.globalHandlers.delete(handler);
118
+ }
119
+ off(eventType) {
120
+ this.handlers.delete(eventType);
121
+ }
122
+ offAll() {
123
+ this.handlers.clear();
124
+ this.globalHandlers.clear();
125
+ }
126
+ handleMessage(event) {
127
+ if (event.type === "EVENTS_BATCH") {
128
+ const batchEvent = event;
129
+ console.log(`[AgentSession] Replaying ${batchEvent.count} events from batch`);
130
+ for (const batchedEvent of batchEvent.events) {
131
+ this.handleMessage(batchedEvent);
132
+ }
133
+ if (batchEvent.lastEventId) {
134
+ this.updateEventId(batchEvent.lastEventId);
135
+ }
136
+ return;
137
+ }
138
+ const eventWithMeta = event;
139
+ if (eventWithMeta.eventId) {
140
+ this.updateEventId(eventWithMeta.eventId);
141
+ }
142
+ const handlers = this.handlers.get(event.type);
143
+ if (handlers) {
144
+ for (const handler of handlers) {
145
+ try {
146
+ handler(event);
147
+ } catch (err) {
148
+ console.error("[AgentSession] Handler error:", err);
149
+ }
150
+ }
151
+ }
152
+ for (const handler of this.globalHandlers) {
153
+ try {
154
+ handler(event);
155
+ } catch (err) {
156
+ console.error("[AgentSession] Global handler error:", err);
157
+ }
158
+ }
159
+ }
160
+ updateEventId(eventId) {
161
+ this.lastEventId = eventId;
162
+ this.onEventIdChange?.(eventId);
163
+ }
164
+ emit(event) {
165
+ this.handleMessage(event);
166
+ }
167
+ // ==========================================================================
168
+ // Client Commands
169
+ // ==========================================================================
170
+ send(message) {
171
+ if (this.ws?.readyState !== WebSocket.OPEN) {
172
+ console.warn("[AgentSession] Not connected");
173
+ return;
174
+ }
175
+ this.ws.send(JSON.stringify(message));
176
+ }
177
+ init(metadata) {
178
+ this.send({ type: "INIT", metadata });
179
+ }
180
+ startSandbox(template) {
181
+ this.send({ type: "START_SANDBOX", template });
182
+ }
183
+ stopSandbox() {
184
+ this.send({ type: "STOP_SANDBOX" });
185
+ }
186
+ exec(cmd, cwd) {
187
+ this.send({ type: "EXEC", cmd, cwd });
188
+ }
189
+ sendAgentMessage(content, model, options) {
190
+ this.send({
191
+ type: "AGENT_MESSAGE",
192
+ clientMessageId: options?.clientMessageId,
193
+ content,
194
+ model,
195
+ config: options?.config,
196
+ systemPrompt: options?.systemPrompt,
197
+ agentType: options?.agentType,
198
+ author: options?.author
199
+ });
200
+ }
201
+ stopAgent() {
202
+ this.send({ type: "AGENT_STOP" });
203
+ }
204
+ resetAgent() {
205
+ this.send({ type: "AGENT_RESET" });
206
+ }
207
+ // ==========================================================================
208
+ // Private Helpers
209
+ // ==========================================================================
210
+ startPing() {
211
+ this.stopPing();
212
+ this.pingIntervalId = setInterval(() => {
213
+ this.send({ type: "PING" });
214
+ }, this.pingInterval);
215
+ }
216
+ stopPing() {
217
+ if (this.pingIntervalId) {
218
+ clearInterval(this.pingIntervalId);
219
+ this.pingIntervalId = null;
220
+ }
221
+ }
222
+ clearReconnect() {
223
+ if (this.reconnectTimeout) {
224
+ clearTimeout(this.reconnectTimeout);
225
+ this.reconnectTimeout = null;
226
+ }
227
+ }
228
+ maybeReconnect() {
229
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
230
+ console.error("[AgentSession] Max reconnect attempts");
231
+ this.emit({ type: "ERROR", msg: "Max reconnect attempts reached" });
232
+ return;
233
+ }
234
+ this.reconnectAttempts++;
235
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
236
+ console.log(`[AgentSession] Reconnecting in ${delay}ms`);
237
+ this.reconnectTimeout = setTimeout(() => {
238
+ this.connect();
239
+ }, delay);
240
+ }
241
+ };
242
+
243
+ // src/use-agent-session.ts
244
+ import {
245
+ DEFAULT_SESSION_REFRESH_CONFIG,
246
+ shouldResetOnSessionChange
247
+ } from "@steventsao/agent-session-core";
248
+ function useAgentSession(options) {
249
+ const {
250
+ url,
251
+ sessionId,
252
+ userId,
253
+ autoConnect = true,
254
+ refreshConfig = DEFAULT_SESSION_REFRESH_CONFIG,
255
+ onLifecycle,
256
+ onAgentMessage,
257
+ onError
258
+ } = options;
259
+ const clientRef = useRef(null);
260
+ const unsubscribersRef = useRef([]);
261
+ const prevSessionIdRef = useRef(null);
262
+ const onLifecycleRef = useRef(onLifecycle);
263
+ const onAgentMessageRef = useRef(onAgentMessage);
264
+ const onErrorRef = useRef(onError);
265
+ onLifecycleRef.current = onLifecycle;
266
+ onAgentMessageRef.current = onAgentMessage;
267
+ onErrorRef.current = onError;
268
+ const [isConnected, setIsConnected] = useState(false);
269
+ const [clientId, setClientId] = useState(null);
270
+ const [connectedSessionId, setConnectedSessionId] = useState(null);
271
+ const [sandboxStatus, setSandboxStatus] = useState("idle");
272
+ const [sandboxId, setSandboxId] = useState(null);
273
+ const [sandboxUrl, setSandboxUrl] = useState(null);
274
+ const [lifecycle, setLifecycle] = useState("idle");
275
+ const [lifecycleMessage, setLifecycleMessage] = useState(null);
276
+ const [agentStatus, setAgentStatus] = useState("idle");
277
+ const [agentSessionId, setAgentSessionId] = useState(null);
278
+ const [messages, setMessages] = useState([]);
279
+ const [error, setError] = useState(null);
280
+ useEffect(() => {
281
+ if (shouldResetOnSessionChange(refreshConfig, prevSessionIdRef.current, sessionId || null)) {
282
+ console.log(`[useAgentSession] Session changed: ${prevSessionIdRef.current} -> ${sessionId}, resetting state`);
283
+ setSandboxId(null);
284
+ setSandboxUrl(null);
285
+ setSandboxStatus("idle");
286
+ setLifecycle("idle");
287
+ setLifecycleMessage(null);
288
+ if (refreshConfig.clearMessagesOnReset !== false) {
289
+ setMessages([]);
290
+ }
291
+ if (refreshConfig.clearAgentSessionOnReset !== false) {
292
+ setAgentSessionId(null);
293
+ }
294
+ setAgentStatus("idle");
295
+ setError(null);
296
+ }
297
+ prevSessionIdRef.current = sessionId || null;
298
+ }, [sessionId, refreshConfig]);
299
+ useEffect(() => {
300
+ if (!sessionId) return;
301
+ if (clientRef.current && clientRef.current.isConnected && connectedSessionId === sessionId) {
302
+ return;
303
+ }
304
+ unsubscribersRef.current.forEach((unsub) => unsub());
305
+ unsubscribersRef.current = [];
306
+ clientRef.current?.disconnect();
307
+ const client = new AgentSessionClient({
308
+ url,
309
+ sessionId,
310
+ userId,
311
+ autoConnect: false
312
+ });
313
+ clientRef.current = client;
314
+ unsubscribersRef.current.push(
315
+ client.on("CONNECTED", (event) => {
316
+ setIsConnected(true);
317
+ setClientId(event.clientId);
318
+ setConnectedSessionId(event.sessionId);
319
+ setError(null);
320
+ })
321
+ );
322
+ unsubscribersRef.current.push(
323
+ client.on("READY", (event) => {
324
+ setSandboxId(event.sandboxId);
325
+ setSandboxUrl(event.sandboxUrl);
326
+ })
327
+ );
328
+ unsubscribersRef.current.push(
329
+ client.on("SANDBOX_STATUS", (event) => {
330
+ setSandboxStatus(event.status);
331
+ if (event.sandboxId) setSandboxId(event.sandboxId);
332
+ if (event.sandboxUrl) setSandboxUrl(event.sandboxUrl);
333
+ if (event.error) setError(event.error);
334
+ })
335
+ );
336
+ unsubscribersRef.current.push(
337
+ client.on("LIFECYCLE", (event) => {
338
+ setLifecycle(event.phase);
339
+ setLifecycleMessage(event.message || null);
340
+ onLifecycleRef.current?.(event.phase, event.message);
341
+ })
342
+ );
343
+ unsubscribersRef.current.push(
344
+ client.on("AGENT_STARTED", () => {
345
+ setAgentStatus("streaming");
346
+ const id = `msg-${Date.now()}`;
347
+ setMessages((prev) => [
348
+ ...prev,
349
+ { id, role: "assistant", content: "", timestamp: Date.now() }
350
+ ]);
351
+ })
352
+ );
353
+ unsubscribersRef.current.push(
354
+ client.on("AGENT_MESSAGE", (event) => {
355
+ onAgentMessageRef.current?.(event);
356
+ if (event.message?.content) {
357
+ setMessages((prev) => {
358
+ const lastIdx = prev.length - 1;
359
+ if (lastIdx >= 0 && prev[lastIdx].role === "assistant") {
360
+ const updated = [...prev];
361
+ updated[lastIdx] = {
362
+ ...updated[lastIdx],
363
+ content: event.message.content
364
+ };
365
+ return updated;
366
+ }
367
+ return prev;
368
+ });
369
+ }
370
+ })
371
+ );
372
+ unsubscribersRef.current.push(
373
+ client.on("AGENT_DONE", (event) => {
374
+ setAgentStatus("idle");
375
+ if (event.sessionId) setAgentSessionId(event.sessionId);
376
+ })
377
+ );
378
+ unsubscribersRef.current.push(
379
+ client.on("AGENT_ERROR", (event) => {
380
+ setAgentStatus("error");
381
+ setError(event.error);
382
+ onErrorRef.current?.(event.error);
383
+ })
384
+ );
385
+ unsubscribersRef.current.push(
386
+ client.on("ERROR", (event) => {
387
+ setError(event.msg);
388
+ onErrorRef.current?.(event.msg);
389
+ })
390
+ );
391
+ unsubscribersRef.current.push(
392
+ client.on("SYSTEM", (event) => {
393
+ if (event.msg === "Disconnected") {
394
+ setIsConnected(false);
395
+ }
396
+ })
397
+ );
398
+ if (autoConnect) {
399
+ client.connect();
400
+ }
401
+ return () => {
402
+ unsubscribersRef.current.forEach((unsub) => unsub());
403
+ unsubscribersRef.current = [];
404
+ client.disconnect();
405
+ };
406
+ }, [url, sessionId, userId, autoConnect]);
407
+ const connect = useCallback((sid, uid) => {
408
+ if (clientRef.current) {
409
+ clientRef.current.disconnect();
410
+ }
411
+ const client = new AgentSessionClient({
412
+ url,
413
+ sessionId: sid,
414
+ userId: uid,
415
+ autoConnect: true
416
+ });
417
+ clientRef.current = client;
418
+ }, [url]);
419
+ const disconnect = useCallback(() => {
420
+ clientRef.current?.disconnect();
421
+ setIsConnected(false);
422
+ setClientId(null);
423
+ setConnectedSessionId(null);
424
+ }, []);
425
+ const startSandbox = useCallback((template) => {
426
+ clientRef.current?.startSandbox(template);
427
+ }, []);
428
+ const stopSandbox = useCallback(() => {
429
+ clientRef.current?.stopSandbox();
430
+ }, []);
431
+ const exec = useCallback((cmd, cwd) => {
432
+ clientRef.current?.exec(cmd, cwd);
433
+ }, []);
434
+ const sendMessage = useCallback(
435
+ (content, model, opts) => {
436
+ const id = `msg-${Date.now()}`;
437
+ setMessages((prev) => [...prev, { id, role: "user", content, timestamp: Date.now() }]);
438
+ setError(null);
439
+ clientRef.current?.sendAgentMessage(content, model, opts);
440
+ },
441
+ []
442
+ );
443
+ const stopAgent = useCallback(() => {
444
+ clientRef.current?.stopAgent();
445
+ }, []);
446
+ const resetAgent = useCallback(() => {
447
+ clientRef.current?.resetAgent();
448
+ setAgentSessionId(null);
449
+ }, []);
450
+ const clearMessages = useCallback(() => {
451
+ setMessages([]);
452
+ }, []);
453
+ return {
454
+ isConnected,
455
+ clientId,
456
+ sessionId: connectedSessionId,
457
+ sandboxStatus,
458
+ sandboxId,
459
+ sandboxUrl,
460
+ lifecycle,
461
+ lifecycleMessage,
462
+ agentStatus,
463
+ agentSessionId,
464
+ messages,
465
+ error,
466
+ connect,
467
+ disconnect,
468
+ startSandbox,
469
+ stopSandbox,
470
+ exec,
471
+ sendMessage,
472
+ stopAgent,
473
+ resetAgent,
474
+ clearMessages
475
+ };
476
+ }
477
+
478
+ // src/react.ts
479
+ export * from "@steventsao/agent-session-core";
480
+ export {
481
+ useAgentSession
482
+ };