@standardagents/react 0.10.1-next.bbd142a → 0.11.0-next.ab7e1ea

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, messagesToFiles, 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,34 @@ function ThreadProvider({
296
130
  const [connectionStatus, setConnectionStatus] = useState(
297
131
  live ? "connecting" : "disconnected"
298
132
  );
133
+ const [pendingFiles, setPendingFiles] = useState([]);
134
+ const [attachments, setAttachments] = useState([]);
135
+ const committedFiles = useMemo(() => {
136
+ return messagesToFiles(messages);
137
+ }, [messages]);
138
+ const files = useMemo(() => {
139
+ return [...pendingFiles, ...committedFiles];
140
+ }, [pendingFiles, committedFiles]);
141
+ const workblocks = useMemo(() => {
142
+ if (!useWorkblocks) {
143
+ return messages;
144
+ }
145
+ return transformToWorkblocks(messages);
146
+ }, [messages, useWorkblocks]);
299
147
  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);
148
+ const connectionManagerRef = useRef(null);
149
+ const fileToBase64 = useCallback((file) => {
150
+ return new Promise((resolve, reject) => {
151
+ const reader = new FileReader();
152
+ reader.onload = () => {
153
+ const result = reader.result;
154
+ const base64 = result.split(",")[1];
155
+ resolve(base64);
156
+ };
157
+ reader.onerror = () => reject(new Error("Failed to read file"));
158
+ reader.readAsDataURL(file);
159
+ });
160
+ }, []);
306
161
  const subscribeToEvent = useCallback(
307
162
  (eventType, listener) => {
308
163
  if (!eventListenersRef.current.has(eventType)) {
@@ -333,60 +188,161 @@ function ThreadProvider({
333
188
  });
334
189
  }
335
190
  }, []);
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
- }
191
+ const addFiles = useCallback(
192
+ (filesToAdd) => {
193
+ const items = uploadManager.queueFiles(filesToAdd);
194
+ setPendingFiles((prev) => [...prev, ...items.map((i) => i.pending)]);
195
+ for (const { pending, file } of items) {
196
+ uploadManager.executeUpload(
197
+ threadId,
198
+ file,
199
+ pending.id,
200
+ clientRef.current,
201
+ (updates) => {
202
+ setPendingFiles(
203
+ (prev) => prev.map(
204
+ (f) => f.id === pending.id ? { ...f, ...updates } : f
205
+ )
206
+ );
207
+ }
208
+ ).catch(() => {
209
+ });
210
+ }
211
+ },
212
+ [threadId]
213
+ );
214
+ const removeFile = useCallback((id) => {
215
+ setPendingFiles((prev) => prev.filter((f) => f.id !== id));
345
216
  }, []);
346
- const startHeartbeat = useCallback((ws) => {
347
- if (heartbeatIntervalRef.current) {
348
- clearInterval(heartbeatIntervalRef.current);
349
- }
350
- heartbeatIntervalRef.current = setInterval(() => {
351
- if (ws.readyState === WebSocket.OPEN) {
352
- ws.send("ping");
217
+ const getFileUrl = useCallback(
218
+ (file) => {
219
+ if (!file.path) return "";
220
+ return clientRef.current.getFileUrl(threadId, file.path);
221
+ },
222
+ [threadId]
223
+ );
224
+ const getThumbnailUrl = useCallback(
225
+ (file) => {
226
+ if (!file.path) return "";
227
+ return clientRef.current.getThumbnailUrl(threadId, file.path);
228
+ },
229
+ [threadId]
230
+ );
231
+ const getPreviewUrl = useCallback(
232
+ (file) => {
233
+ if (!file.isImage) return null;
234
+ if (file.localPreviewUrl) return file.localPreviewUrl;
235
+ if (file.path) return clientRef.current.getThumbnailUrl(threadId, file.path);
236
+ return null;
237
+ },
238
+ [threadId]
239
+ );
240
+ const addAttachment = useCallback((input) => {
241
+ const files2 = input instanceof FileList ? Array.from(input) : Array.isArray(input) ? input : [input];
242
+ const newAttachments = files2.map((file) => {
243
+ const isImage = file.type.startsWith("image/");
244
+ return {
245
+ id: crypto.randomUUID(),
246
+ file,
247
+ name: file.name,
248
+ mimeType: file.type,
249
+ size: file.size,
250
+ isImage,
251
+ previewUrl: isImage ? URL.createObjectURL(file) : null
252
+ };
253
+ });
254
+ setAttachments((prev) => [...prev, ...newAttachments]);
255
+ }, []);
256
+ const removeAttachment = useCallback((id) => {
257
+ setAttachments((prev) => {
258
+ const attachment = prev.find((a) => a.id === id);
259
+ if (attachment?.previewUrl) {
260
+ URL.revokeObjectURL(attachment.previewUrl);
353
261
  }
354
- }, 3e4);
262
+ return prev.filter((a) => a.id !== id);
263
+ });
355
264
  }, []);
265
+ const clearAttachments = useCallback(() => {
266
+ setAttachments((prev) => {
267
+ prev.forEach((a) => {
268
+ if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
269
+ });
270
+ return [];
271
+ });
272
+ }, []);
273
+ const sendMessage2 = useCallback(
274
+ async (payload) => {
275
+ const optimisticId = `optimistic-${crypto.randomUUID()}`;
276
+ const optimisticAttachments = attachments.map((a) => ({
277
+ id: a.id,
278
+ type: "file",
279
+ path: "",
280
+ // No path yet - will be assigned by server
281
+ name: a.name,
282
+ mimeType: a.mimeType,
283
+ size: a.size,
284
+ width: a.width,
285
+ height: a.height,
286
+ localPreviewUrl: a.previewUrl || void 0
287
+ }));
288
+ const optimisticMessage = {
289
+ id: optimisticId,
290
+ role: payload.role,
291
+ content: payload.content,
292
+ attachments: optimisticAttachments.length > 0 ? JSON.stringify(optimisticAttachments) : null,
293
+ created_at: Date.now() * 1e3,
294
+ // microseconds
295
+ status: "pending"
296
+ };
297
+ setMessages((prev) => [...prev, optimisticMessage]);
298
+ const currentAttachments = [...attachments];
299
+ setAttachments([]);
300
+ try {
301
+ const attachmentPayloads = await Promise.all(
302
+ currentAttachments.map(async (a) => ({
303
+ name: a.name,
304
+ mimeType: a.mimeType,
305
+ data: await fileToBase64(a.file),
306
+ width: a.width,
307
+ height: a.height
308
+ }))
309
+ );
310
+ const result = await clientRef.current.sendMessage(threadId, {
311
+ ...payload,
312
+ attachments: attachmentPayloads.length > 0 ? attachmentPayloads : void 0
313
+ });
314
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
315
+ currentAttachments.forEach((a) => {
316
+ if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
317
+ });
318
+ return result;
319
+ } catch (err) {
320
+ setAttachments(currentAttachments);
321
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
322
+ setError(err instanceof Error ? err : new Error(String(err)));
323
+ throw err;
324
+ }
325
+ },
326
+ [threadId, attachments, fileToBase64]
327
+ );
328
+ const stopExecution = useCallback(async () => {
329
+ try {
330
+ await clientRef.current.stopExecution(threadId);
331
+ } catch (err) {
332
+ setError(err instanceof Error ? err : new Error(String(err)));
333
+ throw err;
334
+ }
335
+ }, [threadId]);
356
336
  useEffect(() => {
357
337
  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
338
  console.log(`[ThreadProvider] Connecting WebSocket for thread ${threadId}`);
378
- setConnectionStatus("connecting");
379
- const ws = clientRef.current.connectMessageWebSocket(
339
+ const manager = new ThreadConnectionManager(
340
+ clientRef.current,
380
341
  threadId,
381
342
  {
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);
343
+ onStatusChange: (status) => {
344
+ console.log(`[ThreadProvider] Connection status: ${status}`);
345
+ setConnectionStatus(status);
390
346
  },
391
347
  onMessage: (event) => {
392
348
  setMessages((prev) => {
@@ -417,30 +373,6 @@ function ThreadProvider({
417
373
  onError: (event) => {
418
374
  console.error("[ThreadProvider] WebSocket error:", event.error);
419
375
  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
376
  }
445
377
  },
446
378
  {
@@ -448,16 +380,13 @@ function ThreadProvider({
448
380
  includeSilent
449
381
  }
450
382
  );
451
- wsRef.current = ws;
452
- }, [
453
- threadId,
454
- live,
455
- depth,
456
- includeSilent,
457
- dispatchEvent,
458
- startHeartbeat,
459
- clearTimers
460
- ]);
383
+ connectionManagerRef.current = manager;
384
+ manager.connect();
385
+ return () => {
386
+ manager.disconnect();
387
+ connectionManagerRef.current = null;
388
+ };
389
+ }, [threadId, live, depth, includeSilent, dispatchEvent]);
461
390
  useEffect(() => {
462
391
  if (!preload || !threadId) return;
463
392
  const fetchMessages = async () => {
@@ -481,172 +410,185 @@ function ThreadProvider({
481
410
  };
482
411
  fetchMessages();
483
412
  }, [threadId, preload, depth, includeSilent]);
484
- useEffect(() => {
485
- isMountedRef.current = true;
486
- return () => {
487
- isMountedRef.current = false;
488
- };
489
- }, []);
490
- const contextValue = useMemo(
413
+ const staticValue = useMemo(
491
414
  () => ({
492
415
  threadId,
493
- messages,
494
- loading,
495
- error,
496
- connectionStatus,
416
+ options: { preload, live, useWorkblocks, depth, includeSilent },
417
+ sendMessage: sendMessage2,
418
+ stopExecution,
497
419
  subscribeToEvent,
498
- options: {
499
- depth,
500
- includeSilent,
501
- ...options
502
- }
420
+ addFiles,
421
+ removeFile,
422
+ getFileUrl,
423
+ getThumbnailUrl,
424
+ getPreviewUrl,
425
+ addAttachment,
426
+ removeAttachment,
427
+ clearAttachments
503
428
  }),
504
429
  [
505
430
  threadId,
506
- messages,
507
- loading,
508
- error,
509
- connectionStatus,
510
- subscribeToEvent,
431
+ preload,
432
+ live,
433
+ useWorkblocks,
511
434
  depth,
512
435
  includeSilent,
513
- options
436
+ sendMessage2,
437
+ stopExecution,
438
+ subscribeToEvent,
439
+ addFiles,
440
+ removeFile,
441
+ getFileUrl,
442
+ getThumbnailUrl,
443
+ getPreviewUrl,
444
+ addAttachment,
445
+ removeAttachment,
446
+ clearAttachments
514
447
  ]
515
448
  );
516
- return /* @__PURE__ */ jsx(ThreadContext.Provider, { value: contextValue, children });
449
+ const messagesValue = useMemo(
450
+ () => ({
451
+ messages,
452
+ workblocks,
453
+ loading
454
+ }),
455
+ [messages, workblocks, loading]
456
+ );
457
+ const filesValue = useMemo(
458
+ () => ({
459
+ files
460
+ }),
461
+ [files]
462
+ );
463
+ const connectionValue = useMemo(
464
+ () => ({
465
+ connectionStatus,
466
+ error
467
+ }),
468
+ [connectionStatus, error]
469
+ );
470
+ const attachmentsValue = useMemo(
471
+ () => ({
472
+ attachments
473
+ }),
474
+ [attachments]
475
+ );
476
+ 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
477
  }
518
- function useThreadContext() {
519
- const context = useContext(ThreadContext);
478
+ function useStaticContext() {
479
+ const context = useContext(StaticContext);
520
480
  if (!context) {
521
- throw new Error("useThreadContext must be used within a ThreadProvider");
481
+ throw new Error("useThread must be used within a ThreadProvider");
522
482
  }
523
483
  return context;
524
484
  }
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 [];
485
+ function useMessagesContext() {
486
+ const context = useContext(MessagesContext);
487
+ if (!context) {
488
+ throw new Error("useThread must be used within a ThreadProvider");
533
489
  }
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
- }
490
+ return context;
491
+ }
492
+ function useFilesContext() {
493
+ const context = useContext(FilesContext);
494
+ if (!context) {
495
+ throw new Error("useThread must be used within a ThreadProvider");
607
496
  }
608
- return result;
497
+ return context;
609
498
  }
610
-
611
- // src/hooks/useThread.ts
612
- function useThread(options = {}) {
613
- const {
614
- useWorkblocks = true
615
- } = options;
616
- const context = useContext(ThreadContext);
499
+ function useConnectionContext() {
500
+ const context = useContext(ConnectionContext);
617
501
  if (!context) {
618
- throw new Error(
619
- 'useThread must be used within a ThreadProvider. Wrap your component with <ThreadProvider threadId="...">.'
620
- );
502
+ throw new Error("useThread must be used within a ThreadProvider");
621
503
  }
622
- const { messages } = context;
623
- const transformedMessages = useMemo(() => {
624
- return useWorkblocks ? transformToWorkblocks(messages) : messages;
625
- }, [messages, useWorkblocks]);
626
- return transformedMessages;
504
+ return context;
627
505
  }
628
- function onThreadEvent(type, callback) {
629
- const context = useContext(ThreadContext);
506
+ function useAttachmentsContext() {
507
+ const context = useContext(AttachmentsContext);
630
508
  if (!context) {
631
- throw new Error(
632
- 'onThreadEvent must be used within a ThreadProvider. Wrap your component with <ThreadProvider threadId="...">.'
509
+ throw new Error("useThread must be used within a ThreadProvider");
510
+ }
511
+ return context;
512
+ }
513
+ function useThreadContextInternal() {
514
+ const static_ = useStaticContext();
515
+ const messages_ = useMessagesContext();
516
+ const files_ = useFilesContext();
517
+ const attachments_ = useAttachmentsContext();
518
+ const connection_ = useConnectionContext();
519
+ return {
520
+ threadId: static_.threadId,
521
+ options: static_.options,
522
+ sendMessage: static_.sendMessage,
523
+ stopExecution: static_.stopExecution,
524
+ subscribeToEvent: static_.subscribeToEvent,
525
+ onEvent: static_.subscribeToEvent,
526
+ // File management (uploads to filesystem)
527
+ addFiles: static_.addFiles,
528
+ removeFile: static_.removeFile,
529
+ getFileUrl: static_.getFileUrl,
530
+ getThumbnailUrl: static_.getThumbnailUrl,
531
+ getPreviewUrl: static_.getPreviewUrl,
532
+ // Attachment management (sent inline with messages)
533
+ addAttachment: static_.addAttachment,
534
+ removeAttachment: static_.removeAttachment,
535
+ clearAttachments: static_.clearAttachments,
536
+ attachments: attachments_.attachments,
537
+ // Messages
538
+ messages: messages_.messages,
539
+ workblocks: messages_.workblocks,
540
+ loading: messages_.loading,
541
+ isLoading: messages_.loading,
542
+ files: files_.files,
543
+ connectionStatus: connection_.connectionStatus,
544
+ status: connection_.connectionStatus,
545
+ error: connection_.error
546
+ };
547
+ }
548
+ var hasWarnedAboutUseThreadContext = false;
549
+ function useThreadContext() {
550
+ if (!hasWarnedAboutUseThreadContext && process.env.NODE_ENV !== "production") {
551
+ hasWarnedAboutUseThreadContext = true;
552
+ console.warn(
553
+ "[DEPRECATED] useThreadContext() is deprecated.\nUse: const { messages, sendMessage, ... } = useThread()"
633
554
  );
634
555
  }
635
- const { subscribeToEvent } = context;
556
+ return useThreadContextInternal();
557
+ }
558
+ function useThreadId() {
559
+ return useStaticContext().threadId;
560
+ }
561
+
562
+ // src/hooks/useThread.ts
563
+ var hasWarnedAboutArrayUsage = false;
564
+ function useThread() {
565
+ const context = useThreadContextInternal();
566
+ if (process.env.NODE_ENV !== "production") {
567
+ const arrayMethods = ["map", "filter", "forEach", "find", "some", "every", "reduce", "length", "push", "pop", "slice", "splice"];
568
+ return new Proxy(context, {
569
+ get(target, prop) {
570
+ if (arrayMethods.includes(prop) && !hasWarnedAboutArrayUsage) {
571
+ hasWarnedAboutArrayUsage = true;
572
+ console.warn(
573
+ "[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."
574
+ );
575
+ }
576
+ return target[prop];
577
+ }
578
+ });
579
+ }
580
+ return context;
581
+ }
582
+ function onThreadEvent(type, callback) {
583
+ const { subscribeToEvent } = useThreadContextInternal();
636
584
  useEffect(() => {
637
585
  const unsubscribe = subscribeToEvent(type, callback);
638
586
  return unsubscribe;
639
587
  }, [subscribeToEvent, type, callback]);
640
588
  }
641
589
  function useThreadEvent(type) {
642
- const context = useContext(ThreadContext);
590
+ const { subscribeToEvent } = useThreadContextInternal();
643
591
  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
592
  useEffect(() => {
651
593
  const unsubscribe = subscribeToEvent(type, (data) => {
652
594
  setEventData(data);
@@ -655,7 +597,14 @@ function useThreadEvent(type) {
655
597
  }, [subscribeToEvent, type]);
656
598
  return eventData;
657
599
  }
600
+ var hasWarned = false;
658
601
  function useSendMessage() {
602
+ if (!hasWarned && process.env.NODE_ENV !== "production") {
603
+ hasWarned = true;
604
+ console.warn(
605
+ '[DEPRECATED] useSendMessage() is deprecated.\nUse: const { sendMessage } = useThread()\nThen: await sendMessage({ role: "user", content: "Hello!" })'
606
+ );
607
+ }
659
608
  let context;
660
609
  try {
661
610
  context = useThreadContext();
@@ -670,7 +619,14 @@ function useSendMessage() {
670
619
  [threadId]
671
620
  );
672
621
  }
622
+ var hasWarned2 = false;
673
623
  function useStopThread() {
624
+ if (!hasWarned2 && process.env.NODE_ENV !== "production") {
625
+ hasWarned2 = true;
626
+ console.warn(
627
+ "[DEPRECATED] useStopThread() is deprecated.\nUse: const { stopExecution } = useThread()"
628
+ );
629
+ }
674
630
  let context;
675
631
  try {
676
632
  context = useThreadContext();