@standardagents/react 0.10.1-next.bbd142a → 0.11.0-next.99fb790

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/index.js CHANGED
@@ -1,183 +1,10 @@
1
1
  import { createContext, useMemo, useEffect, useRef, useState, useCallback, useContext } from 'react';
2
+ import { FileUploadManager, AgentBuilderClient, transformToWorkblocks, ThreadConnectionManager } from '@standardagents/client';
3
+ export { AgentBuilderClient, FileUploadManager, ThreadConnectionManager, generatePendingFileId, isImageMimeType, messagesToFiles, parseAttachments, readFileAsDataUrl, transformToWorkblocks } from '@standardagents/client';
2
4
  import { jsx } from 'react/jsx-runtime';
3
5
 
4
6
  // src/context/AgentBuilderProvider.tsx
5
7
 
6
- // src/services/client.ts
7
- var AgentBuilderClient = class {
8
- endpoint;
9
- token;
10
- constructor(endpoint) {
11
- this.endpoint = endpoint.replace(/\/$/, "");
12
- this.token = typeof localStorage !== "undefined" ? localStorage.getItem("agentbuilder_auth_token") : null;
13
- }
14
- /**
15
- * Get thread metadata
16
- */
17
- async getThread(id) {
18
- const response = await fetch(`${this.endpoint}/threads/${id}`, {
19
- method: "GET",
20
- headers: this.getHeaders()
21
- });
22
- if (!response.ok) {
23
- throw new Error(`Failed to get thread: ${response.statusText}`);
24
- }
25
- return response.json();
26
- }
27
- /**
28
- * Get messages from a thread with optional pagination and filtering
29
- */
30
- async getMessages(id, options = {}) {
31
- const params = new URLSearchParams();
32
- if (options.limit !== void 0) params.set("limit", String(options.limit));
33
- if (options.offset !== void 0) params.set("offset", String(options.offset));
34
- if (options.depth !== void 0) params.set("depth", String(options.depth));
35
- if (options.includeSilent !== void 0) params.set("includeSilent", String(options.includeSilent));
36
- const queryString = params.toString();
37
- const url = `${this.endpoint}/threads/${id}/messages${queryString ? `?${queryString}` : ""}`;
38
- const response = await fetch(url, {
39
- method: "GET",
40
- headers: this.getHeaders()
41
- });
42
- if (!response.ok) {
43
- throw new Error(`Failed to get messages: ${response.statusText}`);
44
- }
45
- const data = await response.json();
46
- return data.messages || [];
47
- }
48
- /**
49
- * Send a message to a thread
50
- */
51
- async sendMessage(id, payload) {
52
- const response = await fetch(`${this.endpoint}/threads/${id}/message`, {
53
- method: "POST",
54
- headers: {
55
- ...this.getHeaders(),
56
- "Content-Type": "application/json"
57
- },
58
- body: JSON.stringify(payload)
59
- });
60
- if (!response.ok) {
61
- throw new Error(`Failed to send message: ${response.statusText}`);
62
- }
63
- return response.json();
64
- }
65
- /**
66
- * Stop execution of a thread
67
- */
68
- async stopExecution(id) {
69
- const response = await fetch(`${this.endpoint}/threads/${id}/stop`, {
70
- method: "POST",
71
- headers: this.getHeaders()
72
- });
73
- if (!response.ok) {
74
- throw new Error(`Failed to stop execution: ${response.statusText}`);
75
- }
76
- await response.json();
77
- }
78
- /**
79
- * Connect to message WebSocket for real-time message updates
80
- */
81
- connectMessageWebSocket(id, callbacks = {}, options = {}) {
82
- const params = new URLSearchParams();
83
- if (this.token) params.set("token", this.token);
84
- if (options.includeSilent !== void 0) params.set("includeSilent", String(options.includeSilent));
85
- if (options.depth !== void 0) params.set("depth", String(options.depth));
86
- const wsProtocol = this.endpoint.startsWith("https") ? "wss" : "ws";
87
- const wsEndpoint = this.endpoint.replace(/^https?/, wsProtocol);
88
- const url = `${wsEndpoint}/threads/${id}/stream?${params.toString()}`;
89
- const ws = new WebSocket(url);
90
- ws.onopen = () => {
91
- callbacks.onOpen?.();
92
- };
93
- ws.onmessage = (event) => {
94
- try {
95
- if (typeof event.data === "string" && event.data === "pong") {
96
- return;
97
- }
98
- const data = JSON.parse(event.data);
99
- switch (data.type) {
100
- case "message_data":
101
- callbacks.onMessage?.(data);
102
- break;
103
- case "message_chunk":
104
- callbacks.onChunk?.(data);
105
- break;
106
- case "event":
107
- callbacks.onEvent?.(data);
108
- break;
109
- case "error":
110
- callbacks.onError?.(data);
111
- break;
112
- }
113
- } catch (error) {
114
- console.error("Failed to parse WebSocket message:", error);
115
- }
116
- };
117
- ws.onerror = (event) => {
118
- console.error("WebSocket error:", event);
119
- callbacks.onError?.({ type: "error", error: "WebSocket connection error" });
120
- };
121
- ws.onclose = (event) => {
122
- console.log(`[AgentBuilderClient] Message WebSocket closed - code: ${event.code}, reason: ${event.reason || "none"}, wasClean: ${event.wasClean}`);
123
- callbacks.onClose?.();
124
- };
125
- return ws;
126
- }
127
- /**
128
- * Connect to log WebSocket for custom events
129
- */
130
- connectLogWebSocket(id, callbacks = {}) {
131
- const params = new URLSearchParams();
132
- if (this.token) params.set("token", this.token);
133
- const wsProtocol = this.endpoint.startsWith("https") ? "wss" : "ws";
134
- const wsEndpoint = this.endpoint.replace(/^https?/, wsProtocol);
135
- const url = `${wsEndpoint}/threads/${id}?${params.toString()}`;
136
- const ws = new WebSocket(url);
137
- ws.onopen = () => {
138
- callbacks.onOpen?.();
139
- };
140
- ws.onmessage = (event) => {
141
- try {
142
- if (typeof event.data === "string" && event.data === "pong") {
143
- return;
144
- }
145
- const data = JSON.parse(event.data);
146
- switch (data.type) {
147
- case "log_data":
148
- callbacks.onLog?.(data);
149
- break;
150
- case "custom":
151
- callbacks.onCustom?.(data);
152
- break;
153
- case "stopped_by_user":
154
- callbacks.onStopped?.(data);
155
- break;
156
- }
157
- } catch (error) {
158
- console.error("Failed to parse WebSocket message:", error);
159
- }
160
- };
161
- ws.onerror = (event) => {
162
- console.error("WebSocket error:", event);
163
- };
164
- ws.onclose = () => {
165
- callbacks.onClose?.();
166
- };
167
- return ws;
168
- }
169
- /**
170
- * Get headers for HTTP requests
171
- */
172
- getHeaders() {
173
- const headers = {};
174
- if (this.token) {
175
- headers["Authorization"] = `Bearer ${this.token}`;
176
- }
177
- return headers;
178
- }
179
- };
180
-
181
8
  // src/services/sendMessage.ts
182
9
  var globalEndpoint = null;
183
10
  function __setGlobalEndpoint(endpoint) {
@@ -196,7 +23,7 @@ async function sendMessage(id, payload) {
196
23
  if (token) {
197
24
  headers["Authorization"] = `Bearer ${token}`;
198
25
  }
199
- const response = await fetch(`${globalEndpoint}/threads/${id}/message`, {
26
+ const response = await fetch(`${globalEndpoint}/threads/${id}/messages`, {
200
27
  method: "POST",
201
28
  headers,
202
29
  body: JSON.stringify(payload)
@@ -266,12 +93,19 @@ function useAgentBuilderConfig() {
266
93
  }
267
94
  return context.config;
268
95
  }
269
- var ThreadContext = createContext(null);
96
+ var uploadManager = new FileUploadManager();
97
+ var StaticContext = createContext(null);
98
+ var MessagesContext = createContext(null);
99
+ var FilesContext = createContext(null);
100
+ var AttachmentsContext = createContext(null);
101
+ var ConnectionContext = createContext(null);
102
+ createContext(null);
270
103
  function ThreadProvider({
271
104
  threadId,
272
105
  options = {},
273
106
  preload = true,
274
107
  live = true,
108
+ useWorkblocks = false,
275
109
  depth = 0,
276
110
  includeSilent = false,
277
111
  endpoint: endpointOverride,
@@ -296,13 +130,38 @@ function ThreadProvider({
296
130
  const [connectionStatus, setConnectionStatus] = useState(
297
131
  live ? "connecting" : "disconnected"
298
132
  );
133
+ const [pendingFiles, setPendingFiles] = useState([]);
134
+ const [serverFiles, setServerFiles] = useState([]);
135
+ const [attachments, setAttachments] = useState([]);
136
+ const messagesRef = useRef(messages);
137
+ messagesRef.current = messages;
138
+ const files = useMemo(() => {
139
+ const serverPaths = new Set(serverFiles.map((f) => f.path));
140
+ const uniquePendingFiles = pendingFiles.filter(
141
+ (f) => !f.path || !serverPaths.has(f.path)
142
+ );
143
+ return [...uniquePendingFiles, ...serverFiles];
144
+ }, [pendingFiles, serverFiles]);
145
+ const workblocks = useMemo(() => {
146
+ if (!useWorkblocks) {
147
+ return messages;
148
+ }
149
+ return transformToWorkblocks(messages);
150
+ }, [messages, useWorkblocks]);
299
151
  const eventListenersRef = useRef(/* @__PURE__ */ new Map());
300
- const wsRef = useRef(null);
301
- const reconnectAttempts = useRef(0);
302
- const reconnectTimeoutRef = useRef(null);
303
- const heartbeatIntervalRef = useRef(null);
304
- const isReconnectingRef = useRef(false);
305
- const isMountedRef = useRef(true);
152
+ const connectionManagerRef = useRef(null);
153
+ const fileToBase64 = useCallback((file) => {
154
+ return new Promise((resolve, reject) => {
155
+ const reader = new FileReader();
156
+ reader.onload = () => {
157
+ const result = reader.result;
158
+ const base64 = result.split(",")[1];
159
+ resolve(base64);
160
+ };
161
+ reader.onerror = () => reject(new Error("Failed to read file"));
162
+ reader.readAsDataURL(file);
163
+ });
164
+ }, []);
306
165
  const subscribeToEvent = useCallback(
307
166
  (eventType, listener) => {
308
167
  if (!eventListenersRef.current.has(eventType)) {
@@ -333,60 +192,239 @@ function ThreadProvider({
333
192
  });
334
193
  }
335
194
  }, []);
336
- const clearTimers = useCallback(() => {
337
- if (reconnectTimeoutRef.current) {
338
- clearTimeout(reconnectTimeoutRef.current);
339
- reconnectTimeoutRef.current = null;
340
- }
341
- if (heartbeatIntervalRef.current) {
342
- clearInterval(heartbeatIntervalRef.current);
343
- heartbeatIntervalRef.current = null;
344
- }
195
+ const addFiles = useCallback(
196
+ (filesToAdd) => {
197
+ const items = uploadManager.queueFiles(filesToAdd);
198
+ setPendingFiles((prev) => [...prev, ...items.map((i) => i.pending)]);
199
+ for (const { pending, file } of items) {
200
+ uploadManager.executeUpload(
201
+ threadId,
202
+ file,
203
+ pending.id,
204
+ clientRef.current,
205
+ (updates) => {
206
+ setPendingFiles(
207
+ (prev) => prev.map(
208
+ (f) => f.id === pending.id ? { ...f, ...updates } : f
209
+ )
210
+ );
211
+ }
212
+ ).catch(() => {
213
+ });
214
+ }
215
+ },
216
+ [threadId]
217
+ );
218
+ const removeFile = useCallback((id) => {
219
+ setPendingFiles((prev) => prev.filter((f) => f.id !== id));
345
220
  }, []);
346
- const startHeartbeat = useCallback((ws) => {
347
- if (heartbeatIntervalRef.current) {
348
- clearInterval(heartbeatIntervalRef.current);
221
+ const getFileUrl = useCallback(
222
+ (file) => {
223
+ if (!file.path) return "";
224
+ return clientRef.current.getFileUrl(threadId, file.path);
225
+ },
226
+ [threadId]
227
+ );
228
+ const getThumbnailUrl = useCallback(
229
+ (file) => {
230
+ if (!file.path) return "";
231
+ return clientRef.current.getThumbnailUrl(threadId, file.path);
232
+ },
233
+ [threadId]
234
+ );
235
+ const getPreviewUrl = useCallback(
236
+ (file) => {
237
+ if (!file.isImage) return null;
238
+ if (file.localPreviewUrl) return file.localPreviewUrl;
239
+ if (file.path) return clientRef.current.getThumbnailUrl(threadId, file.path);
240
+ return null;
241
+ },
242
+ [threadId]
243
+ );
244
+ const loadFiles = useCallback(async () => {
245
+ try {
246
+ const fileList = await clientRef.current.listFiles(threadId);
247
+ setServerFiles(
248
+ fileList.filter((f) => !f.isDirectory).map((f) => ({
249
+ id: f.path,
250
+ name: f.name,
251
+ path: f.path,
252
+ mimeType: f.mimeType,
253
+ size: f.size,
254
+ isImage: f.mimeType.startsWith("image/"),
255
+ status: "committed",
256
+ localPreviewUrl: null
257
+ }))
258
+ );
259
+ } catch (err) {
260
+ console.error("Failed to load files:", err);
349
261
  }
350
- heartbeatIntervalRef.current = setInterval(() => {
351
- if (ws.readyState === WebSocket.OPEN) {
352
- ws.send("ping");
262
+ }, [threadId]);
263
+ const handleFileCreated = useCallback(
264
+ (data) => {
265
+ if (data.file.isDirectory) return;
266
+ const newFile = {
267
+ id: data.path,
268
+ name: data.file.name,
269
+ path: data.path,
270
+ mimeType: data.file.mimeType,
271
+ size: data.file.size,
272
+ isImage: data.file.mimeType?.startsWith("image/") || false,
273
+ status: "committed",
274
+ localPreviewUrl: null
275
+ };
276
+ setServerFiles((prev) => {
277
+ const existingIndex = prev.findIndex((f) => f.path === data.path);
278
+ if (existingIndex >= 0) {
279
+ const updated = [...prev];
280
+ updated[existingIndex] = newFile;
281
+ return updated;
282
+ }
283
+ return [...prev, newFile];
284
+ });
285
+ },
286
+ []
287
+ );
288
+ const handleFileUpdated = useCallback(
289
+ (data) => {
290
+ if (data.file.isDirectory) return;
291
+ setServerFiles(
292
+ (prev) => prev.map(
293
+ (f) => f.path === data.path ? {
294
+ ...f,
295
+ name: data.file.name,
296
+ mimeType: data.file.mimeType,
297
+ size: data.file.size,
298
+ isImage: data.file.mimeType?.startsWith("image/") || false
299
+ } : f
300
+ )
301
+ );
302
+ },
303
+ []
304
+ );
305
+ const handleFileDeleted = useCallback((data) => {
306
+ setServerFiles((prev) => prev.filter((f) => f.path !== data.path));
307
+ }, []);
308
+ const addAttachment = useCallback((input) => {
309
+ const files2 = input instanceof FileList ? Array.from(input) : Array.isArray(input) ? input : [input];
310
+ const newAttachments = files2.map((file) => {
311
+ const isImage = file.type.startsWith("image/");
312
+ return {
313
+ id: crypto.randomUUID(),
314
+ file,
315
+ name: file.name,
316
+ mimeType: file.type,
317
+ size: file.size,
318
+ isImage,
319
+ previewUrl: isImage ? URL.createObjectURL(file) : null
320
+ };
321
+ });
322
+ setAttachments((prev) => [...prev, ...newAttachments]);
323
+ }, []);
324
+ const removeAttachment = useCallback((id) => {
325
+ setAttachments((prev) => {
326
+ const attachment = prev.find((a) => a.id === id);
327
+ if (attachment?.previewUrl) {
328
+ URL.revokeObjectURL(attachment.previewUrl);
353
329
  }
354
- }, 3e4);
330
+ return prev.filter((a) => a.id !== id);
331
+ });
332
+ }, []);
333
+ const clearAttachments = useCallback(() => {
334
+ setAttachments((prev) => {
335
+ prev.forEach((a) => {
336
+ if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
337
+ });
338
+ return [];
339
+ });
355
340
  }, []);
341
+ const sendMessage2 = useCallback(
342
+ async (payload) => {
343
+ const optimisticId = `optimistic-${crypto.randomUUID()}`;
344
+ const optimisticAttachments = attachments.map((a) => ({
345
+ id: a.id,
346
+ type: "file",
347
+ path: "",
348
+ // No path yet - will be assigned by server
349
+ name: a.name,
350
+ mimeType: a.mimeType,
351
+ size: a.size,
352
+ width: a.width,
353
+ height: a.height,
354
+ localPreviewUrl: a.previewUrl || void 0
355
+ }));
356
+ const optimisticMessage = {
357
+ id: optimisticId,
358
+ role: payload.role,
359
+ content: payload.content,
360
+ attachments: optimisticAttachments.length > 0 ? JSON.stringify(optimisticAttachments) : null,
361
+ created_at: Date.now() * 1e3,
362
+ // microseconds
363
+ status: "pending"
364
+ };
365
+ setMessages((prev) => [...prev, optimisticMessage]);
366
+ const currentAttachments = [...attachments];
367
+ setAttachments([]);
368
+ try {
369
+ const attachmentPayloads = await Promise.all(
370
+ currentAttachments.map(async (a) => ({
371
+ name: a.name,
372
+ mimeType: a.mimeType,
373
+ data: await fileToBase64(a.file),
374
+ width: a.width,
375
+ height: a.height
376
+ }))
377
+ );
378
+ const result = await clientRef.current.sendMessage(threadId, {
379
+ ...payload,
380
+ attachments: attachmentPayloads.length > 0 ? attachmentPayloads : void 0
381
+ });
382
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
383
+ currentAttachments.forEach((a) => {
384
+ if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
385
+ });
386
+ return result;
387
+ } catch (err) {
388
+ setAttachments(currentAttachments);
389
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
390
+ setError(err instanceof Error ? err : new Error(String(err)));
391
+ throw err;
392
+ }
393
+ },
394
+ [threadId, attachments, fileToBase64]
395
+ );
396
+ const stopExecution = useCallback(async () => {
397
+ try {
398
+ await clientRef.current.stopExecution(threadId);
399
+ } catch (err) {
400
+ setError(err instanceof Error ? err : new Error(String(err)));
401
+ throw err;
402
+ }
403
+ }, [threadId]);
404
+ const deleteMessage = useCallback(
405
+ async (messageId) => {
406
+ const previousMessages = messagesRef.current;
407
+ setMessages((prev) => prev.filter((m) => m.id !== messageId));
408
+ try {
409
+ await clientRef.current.deleteMessage(threadId, messageId);
410
+ } catch (err) {
411
+ setMessages(previousMessages);
412
+ setError(err instanceof Error ? err : new Error(String(err)));
413
+ throw err;
414
+ }
415
+ },
416
+ [threadId]
417
+ );
356
418
  useEffect(() => {
357
419
  if (!live || !threadId) return;
358
- console.log(
359
- `[ThreadProvider] useEffect running - threadId: ${threadId}, live: ${live}`
360
- );
361
- connectWebSocket();
362
- return () => {
363
- console.log(
364
- `[ThreadProvider] useEffect cleanup - threadId: ${threadId}, live: ${live}`
365
- );
366
- clearTimers();
367
- if (wsRef.current) {
368
- wsRef.current.close();
369
- wsRef.current = null;
370
- }
371
- reconnectAttempts.current = 0;
372
- isReconnectingRef.current = false;
373
- };
374
- }, [threadId, live]);
375
- const connectWebSocket = useCallback(() => {
376
- if (!live || !threadId || !isMountedRef.current) return;
377
420
  console.log(`[ThreadProvider] Connecting WebSocket for thread ${threadId}`);
378
- setConnectionStatus("connecting");
379
- const ws = clientRef.current.connectMessageWebSocket(
421
+ const manager = new ThreadConnectionManager(
422
+ clientRef.current,
380
423
  threadId,
381
424
  {
382
- onOpen: () => {
383
- console.log(
384
- `[ThreadProvider] WebSocket connected for thread ${threadId}`
385
- );
386
- setConnectionStatus("connected");
387
- reconnectAttempts.current = 0;
388
- isReconnectingRef.current = false;
389
- startHeartbeat(ws);
425
+ onStatusChange: (status) => {
426
+ console.log(`[ThreadProvider] Connection status: ${status}`);
427
+ setConnectionStatus(status);
390
428
  },
391
429
  onMessage: (event) => {
392
430
  setMessages((prev) => {
@@ -412,35 +450,18 @@ function ThreadProvider({
412
450
  });
413
451
  },
414
452
  onEvent: (event) => {
453
+ if (event.eventType === "file_created") {
454
+ handleFileCreated(event.data);
455
+ } else if (event.eventType === "file_updated") {
456
+ handleFileUpdated(event.data);
457
+ } else if (event.eventType === "file_deleted") {
458
+ handleFileDeleted(event.data);
459
+ }
415
460
  dispatchEvent(event.eventType, event.data);
416
461
  },
417
462
  onError: (event) => {
418
463
  console.error("[ThreadProvider] WebSocket error:", event.error);
419
464
  setError(new Error(event.error));
420
- setConnectionStatus("disconnected");
421
- },
422
- onClose: () => {
423
- console.log("[ThreadProvider] WebSocket closed");
424
- clearTimers();
425
- if (isMountedRef.current && !isReconnectingRef.current) {
426
- isReconnectingRef.current = true;
427
- setConnectionStatus("connecting");
428
- const delay = Math.min(
429
- 1e3 * Math.pow(2, reconnectAttempts.current),
430
- 3e4
431
- );
432
- reconnectAttempts.current++;
433
- console.log(
434
- `[ThreadProvider] Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current})`
435
- );
436
- reconnectTimeoutRef.current = setTimeout(() => {
437
- if (isMountedRef.current) {
438
- connectWebSocket();
439
- }
440
- }, delay);
441
- } else {
442
- setConnectionStatus("disconnected");
443
- }
444
465
  }
445
466
  },
446
467
  {
@@ -448,16 +469,13 @@ function ThreadProvider({
448
469
  includeSilent
449
470
  }
450
471
  );
451
- wsRef.current = ws;
452
- }, [
453
- threadId,
454
- live,
455
- depth,
456
- includeSilent,
457
- dispatchEvent,
458
- startHeartbeat,
459
- clearTimers
460
- ]);
472
+ connectionManagerRef.current = manager;
473
+ manager.connect();
474
+ return () => {
475
+ manager.disconnect();
476
+ connectionManagerRef.current = null;
477
+ };
478
+ }, [threadId, live, depth, includeSilent, dispatchEvent, handleFileCreated, handleFileUpdated, handleFileDeleted]);
461
479
  useEffect(() => {
462
480
  if (!preload || !threadId) return;
463
481
  const fetchMessages = async () => {
@@ -482,171 +500,192 @@ function ThreadProvider({
482
500
  fetchMessages();
483
501
  }, [threadId, preload, depth, includeSilent]);
484
502
  useEffect(() => {
485
- isMountedRef.current = true;
486
- return () => {
487
- isMountedRef.current = false;
488
- };
489
- }, []);
490
- const contextValue = useMemo(
503
+ if (!threadId) return;
504
+ setServerFiles([]);
505
+ loadFiles();
506
+ }, [threadId, loadFiles]);
507
+ const staticValue = useMemo(
491
508
  () => ({
492
509
  threadId,
493
- messages,
494
- loading,
495
- error,
496
- connectionStatus,
510
+ options: { preload, live, useWorkblocks, depth, includeSilent },
511
+ sendMessage: sendMessage2,
512
+ stopExecution,
513
+ deleteMessage,
497
514
  subscribeToEvent,
498
- options: {
499
- depth,
500
- includeSilent,
501
- ...options
502
- }
515
+ addFiles,
516
+ removeFile,
517
+ getFileUrl,
518
+ getThumbnailUrl,
519
+ getPreviewUrl,
520
+ addAttachment,
521
+ removeAttachment,
522
+ clearAttachments
503
523
  }),
504
524
  [
505
525
  threadId,
506
- messages,
507
- loading,
508
- error,
509
- connectionStatus,
510
- subscribeToEvent,
526
+ preload,
527
+ live,
528
+ useWorkblocks,
511
529
  depth,
512
530
  includeSilent,
513
- options
531
+ sendMessage2,
532
+ stopExecution,
533
+ deleteMessage,
534
+ subscribeToEvent,
535
+ addFiles,
536
+ removeFile,
537
+ getFileUrl,
538
+ getThumbnailUrl,
539
+ getPreviewUrl,
540
+ addAttachment,
541
+ removeAttachment,
542
+ clearAttachments
514
543
  ]
515
544
  );
516
- return /* @__PURE__ */ jsx(ThreadContext.Provider, { value: contextValue, children });
545
+ const messagesValue = useMemo(
546
+ () => ({
547
+ messages,
548
+ workblocks,
549
+ loading
550
+ }),
551
+ [messages, workblocks, loading]
552
+ );
553
+ const filesValue = useMemo(
554
+ () => ({
555
+ files
556
+ }),
557
+ [files]
558
+ );
559
+ const connectionValue = useMemo(
560
+ () => ({
561
+ connectionStatus,
562
+ error
563
+ }),
564
+ [connectionStatus, error]
565
+ );
566
+ const attachmentsValue = useMemo(
567
+ () => ({
568
+ attachments
569
+ }),
570
+ [attachments]
571
+ );
572
+ return /* @__PURE__ */ jsx(StaticContext.Provider, { value: staticValue, children: /* @__PURE__ */ jsx(MessagesContext.Provider, { value: messagesValue, children: /* @__PURE__ */ jsx(FilesContext.Provider, { value: filesValue, children: /* @__PURE__ */ jsx(AttachmentsContext.Provider, { value: attachmentsValue, children: /* @__PURE__ */ jsx(ConnectionContext.Provider, { value: connectionValue, children }) }) }) }) });
517
573
  }
518
- function useThreadContext() {
519
- const context = useContext(ThreadContext);
574
+ function useStaticContext() {
575
+ const context = useContext(StaticContext);
520
576
  if (!context) {
521
- throw new Error("useThreadContext must be used within a ThreadProvider");
577
+ throw new Error("useThread must be used within a ThreadProvider");
522
578
  }
523
579
  return context;
524
580
  }
525
- function useThreadId() {
526
- return useThreadContext().threadId;
527
- }
528
-
529
- // src/utils/workblocks.ts
530
- function transformToWorkblocks(messages) {
531
- if (messages.length === 0) {
532
- return [];
581
+ function useMessagesContext() {
582
+ const context = useContext(MessagesContext);
583
+ if (!context) {
584
+ throw new Error("useThread must be used within a ThreadProvider");
533
585
  }
534
- const result = [];
535
- let i = 0;
536
- while (i < messages.length) {
537
- const message = messages[i];
538
- if (message.role === "assistant" && message.tool_calls) {
539
- let toolCalls;
540
- try {
541
- toolCalls = JSON.parse(message.tool_calls);
542
- } catch (error) {
543
- result.push(message);
544
- i++;
545
- continue;
546
- }
547
- const workItems = [];
548
- for (const toolCall of toolCalls) {
549
- workItems.push({
550
- id: toolCall.id || message.id,
551
- type: "tool_call",
552
- name: toolCall.function?.name,
553
- content: toolCall.function?.arguments || null,
554
- status: null,
555
- // Will be updated below based on matching results
556
- tool_call_id: toolCall.id
557
- });
558
- }
559
- let j = i + 1;
560
- while (j < messages.length && messages[j].role === "tool") {
561
- const toolMessage = messages[j];
562
- const resultStatus = toolMessage.tool_status || "pending";
563
- workItems.push({
564
- id: toolMessage.id,
565
- type: "tool_result",
566
- name: toolMessage.name || void 0,
567
- content: toolMessage.content,
568
- status: resultStatus,
569
- tool_call_id: toolMessage.tool_call_id || void 0
570
- });
571
- j++;
572
- }
573
- for (const item of workItems) {
574
- if (item.type === "tool_call" && item.tool_call_id) {
575
- const matchingResult = workItems.find(
576
- (wi) => wi.type === "tool_result" && wi.tool_call_id === item.tool_call_id
577
- );
578
- if (matchingResult) {
579
- item.status = matchingResult.status;
580
- } else {
581
- item.status = "pending";
582
- }
583
- }
584
- }
585
- let status = "completed";
586
- if (message.status === "pending") {
587
- status = "pending";
588
- } else if (message.status === "failed") {
589
- status = "failed";
590
- }
591
- const workblock = {
592
- id: message.id,
593
- type: "workblock",
594
- content: message.content,
595
- reasoning_content: message.reasoning_content,
596
- workItems,
597
- status,
598
- created_at: message.created_at,
599
- depth: message.depth
600
- };
601
- result.push(workblock);
602
- i = j;
603
- } else {
604
- result.push(message);
605
- i++;
606
- }
586
+ return context;
587
+ }
588
+ function useFilesContext() {
589
+ const context = useContext(FilesContext);
590
+ if (!context) {
591
+ throw new Error("useThread must be used within a ThreadProvider");
607
592
  }
608
- return result;
593
+ return context;
609
594
  }
610
-
611
- // src/hooks/useThread.ts
612
- function useThread(options = {}) {
613
- const {
614
- useWorkblocks = true
615
- } = options;
616
- const context = useContext(ThreadContext);
595
+ function useConnectionContext() {
596
+ const context = useContext(ConnectionContext);
617
597
  if (!context) {
618
- throw new Error(
619
- 'useThread must be used within a ThreadProvider. Wrap your component with <ThreadProvider threadId="...">.'
620
- );
598
+ throw new Error("useThread must be used within a ThreadProvider");
621
599
  }
622
- const { messages } = context;
623
- const transformedMessages = useMemo(() => {
624
- return useWorkblocks ? transformToWorkblocks(messages) : messages;
625
- }, [messages, useWorkblocks]);
626
- return transformedMessages;
600
+ return context;
627
601
  }
628
- function onThreadEvent(type, callback) {
629
- const context = useContext(ThreadContext);
602
+ function useAttachmentsContext() {
603
+ const context = useContext(AttachmentsContext);
630
604
  if (!context) {
631
- throw new Error(
632
- 'onThreadEvent must be used within a ThreadProvider. Wrap your component with <ThreadProvider threadId="...">.'
605
+ throw new Error("useThread must be used within a ThreadProvider");
606
+ }
607
+ return context;
608
+ }
609
+ function useThreadContextInternal() {
610
+ const static_ = useStaticContext();
611
+ const messages_ = useMessagesContext();
612
+ const files_ = useFilesContext();
613
+ const attachments_ = useAttachmentsContext();
614
+ const connection_ = useConnectionContext();
615
+ return {
616
+ threadId: static_.threadId,
617
+ options: static_.options,
618
+ sendMessage: static_.sendMessage,
619
+ stopExecution: static_.stopExecution,
620
+ deleteMessage: static_.deleteMessage,
621
+ subscribeToEvent: static_.subscribeToEvent,
622
+ onEvent: static_.subscribeToEvent,
623
+ // File management (uploads to filesystem)
624
+ addFiles: static_.addFiles,
625
+ removeFile: static_.removeFile,
626
+ getFileUrl: static_.getFileUrl,
627
+ getThumbnailUrl: static_.getThumbnailUrl,
628
+ getPreviewUrl: static_.getPreviewUrl,
629
+ // Attachment management (sent inline with messages)
630
+ addAttachment: static_.addAttachment,
631
+ removeAttachment: static_.removeAttachment,
632
+ clearAttachments: static_.clearAttachments,
633
+ attachments: attachments_.attachments,
634
+ // Messages
635
+ messages: messages_.messages,
636
+ workblocks: messages_.workblocks,
637
+ loading: messages_.loading,
638
+ isLoading: messages_.loading,
639
+ files: files_.files,
640
+ connectionStatus: connection_.connectionStatus,
641
+ status: connection_.connectionStatus,
642
+ error: connection_.error
643
+ };
644
+ }
645
+ var hasWarnedAboutUseThreadContext = false;
646
+ function useThreadContext() {
647
+ if (!hasWarnedAboutUseThreadContext && process.env.NODE_ENV !== "production") {
648
+ hasWarnedAboutUseThreadContext = true;
649
+ console.warn(
650
+ "[DEPRECATED] useThreadContext() is deprecated.\nUse: const { messages, sendMessage, ... } = useThread()"
633
651
  );
634
652
  }
635
- const { subscribeToEvent } = context;
653
+ return useThreadContextInternal();
654
+ }
655
+ function useThreadId() {
656
+ return useStaticContext().threadId;
657
+ }
658
+
659
+ // src/hooks/useThread.ts
660
+ var hasWarnedAboutArrayUsage = false;
661
+ function useThread() {
662
+ const context = useThreadContextInternal();
663
+ if (process.env.NODE_ENV !== "production") {
664
+ const arrayMethods = ["map", "filter", "forEach", "find", "some", "every", "reduce", "length", "push", "pop", "slice", "splice"];
665
+ return new Proxy(context, {
666
+ get(target, prop) {
667
+ if (arrayMethods.includes(prop) && !hasWarnedAboutArrayUsage) {
668
+ hasWarnedAboutArrayUsage = true;
669
+ console.warn(
670
+ "[BREAKING CHANGE] useThread() now returns an object, not an array.\nChange: const messages = useThread()\nTo: const { messages } = useThread()\n\nThe returned object includes: messages, sendMessage, stopExecution, files, and more."
671
+ );
672
+ }
673
+ return target[prop];
674
+ }
675
+ });
676
+ }
677
+ return context;
678
+ }
679
+ function onThreadEvent(type, callback) {
680
+ const { subscribeToEvent } = useThreadContextInternal();
636
681
  useEffect(() => {
637
682
  const unsubscribe = subscribeToEvent(type, callback);
638
683
  return unsubscribe;
639
684
  }, [subscribeToEvent, type, callback]);
640
685
  }
641
686
  function useThreadEvent(type) {
642
- const context = useContext(ThreadContext);
687
+ const { subscribeToEvent } = useThreadContextInternal();
643
688
  const [eventData, setEventData] = useState(null);
644
- if (!context) {
645
- throw new Error(
646
- 'useThreadEvent must be used within a ThreadProvider. Wrap your component with <ThreadProvider threadId="...">.'
647
- );
648
- }
649
- const { subscribeToEvent } = context;
650
689
  useEffect(() => {
651
690
  const unsubscribe = subscribeToEvent(type, (data) => {
652
691
  setEventData(data);
@@ -655,7 +694,14 @@ function useThreadEvent(type) {
655
694
  }, [subscribeToEvent, type]);
656
695
  return eventData;
657
696
  }
697
+ var hasWarned = false;
658
698
  function useSendMessage() {
699
+ if (!hasWarned && process.env.NODE_ENV !== "production") {
700
+ hasWarned = true;
701
+ console.warn(
702
+ '[DEPRECATED] useSendMessage() is deprecated.\nUse: const { sendMessage } = useThread()\nThen: await sendMessage({ role: "user", content: "Hello!" })'
703
+ );
704
+ }
659
705
  let context;
660
706
  try {
661
707
  context = useThreadContext();
@@ -670,7 +716,14 @@ function useSendMessage() {
670
716
  [threadId]
671
717
  );
672
718
  }
719
+ var hasWarned2 = false;
673
720
  function useStopThread() {
721
+ if (!hasWarned2 && process.env.NODE_ENV !== "production") {
722
+ hasWarned2 = true;
723
+ console.warn(
724
+ "[DEPRECATED] useStopThread() is deprecated.\nUse: const { stopExecution } = useThread()"
725
+ );
726
+ }
674
727
  let context;
675
728
  try {
676
729
  context = useThreadContext();