@standardagents/client 0.1.0-dev.ffffff
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/LICENSE.txt +48 -0
- package/README.md +320 -0
- package/dist/index.d.ts +476 -0
- package/dist/index.js +746 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
// src/client/AgentBuilderClient.ts
|
|
2
|
+
var AgentBuilderClient = class {
|
|
3
|
+
endpoint;
|
|
4
|
+
token;
|
|
5
|
+
constructor(endpoint) {
|
|
6
|
+
this.endpoint = endpoint.replace(/\/$/, "");
|
|
7
|
+
this.token = typeof localStorage !== "undefined" ? localStorage.getItem("agentbuilder_auth_token") : null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get the current endpoint
|
|
11
|
+
*/
|
|
12
|
+
getEndpoint() {
|
|
13
|
+
return this.endpoint;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a new thread
|
|
17
|
+
*/
|
|
18
|
+
async createThread(payload) {
|
|
19
|
+
const response = await fetch(`${this.endpoint}/threads`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
...this.getHeaders(),
|
|
23
|
+
"Content-Type": "application/json"
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(payload)
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`Failed to create thread: ${response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get thread metadata
|
|
34
|
+
*/
|
|
35
|
+
async getThread(id) {
|
|
36
|
+
const response = await fetch(`${this.endpoint}/threads/${id}`, {
|
|
37
|
+
method: "GET",
|
|
38
|
+
headers: this.getHeaders()
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to get thread: ${response.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
return response.json();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get messages from a thread with optional pagination and filtering
|
|
47
|
+
*/
|
|
48
|
+
async getMessages(id, options = {}) {
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
51
|
+
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
52
|
+
if (options.depth !== void 0) params.set("depth", String(options.depth));
|
|
53
|
+
if (options.includeSilent !== void 0) params.set("includeSilent", String(options.includeSilent));
|
|
54
|
+
const queryString = params.toString();
|
|
55
|
+
const url = `${this.endpoint}/threads/${id}/messages${queryString ? `?${queryString}` : ""}`;
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
method: "GET",
|
|
58
|
+
headers: this.getHeaders()
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Failed to get messages: ${response.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return data.messages || [];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Send a message to a thread
|
|
68
|
+
*/
|
|
69
|
+
async sendMessage(id, payload) {
|
|
70
|
+
const response = await fetch(`${this.endpoint}/threads/${id}/messages`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
...this.getHeaders(),
|
|
74
|
+
"Content-Type": "application/json"
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify(payload)
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(`Failed to send message: ${response.statusText}`);
|
|
80
|
+
}
|
|
81
|
+
return response.json();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stop execution of a thread
|
|
85
|
+
*/
|
|
86
|
+
async stopExecution(id) {
|
|
87
|
+
const response = await fetch(`${this.endpoint}/threads/${id}/stop`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: this.getHeaders()
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error(`Failed to stop execution: ${response.statusText}`);
|
|
93
|
+
}
|
|
94
|
+
await response.json();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Options for file upload
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* Upload a file to a thread's filesystem
|
|
101
|
+
* @param threadId - The thread ID
|
|
102
|
+
* @param file - The file to upload
|
|
103
|
+
* @param options - Optional upload options
|
|
104
|
+
* @param options.thumbnail - Base64-encoded thumbnail data (for images)
|
|
105
|
+
* @param options.width - Image width in pixels
|
|
106
|
+
* @param options.height - Image height in pixels
|
|
107
|
+
* @returns AttachmentRef with file metadata
|
|
108
|
+
*/
|
|
109
|
+
async uploadFile(threadId, file, options) {
|
|
110
|
+
const encodedFilename = encodeURIComponent(file.name);
|
|
111
|
+
const url = `${this.endpoint}/threads/${threadId}/fs/${encodedFilename}`;
|
|
112
|
+
if (options?.thumbnail) {
|
|
113
|
+
const base64Data = await this.fileToBase64(file);
|
|
114
|
+
const response2 = await fetch(url, {
|
|
115
|
+
method: "PUT",
|
|
116
|
+
headers: {
|
|
117
|
+
...this.getHeaders(),
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
data: base64Data,
|
|
122
|
+
mimeType: file.type,
|
|
123
|
+
thumbnail: options.thumbnail,
|
|
124
|
+
metadata: {
|
|
125
|
+
width: options.width,
|
|
126
|
+
height: options.height
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
if (!response2.ok) {
|
|
131
|
+
throw new Error(`Failed to upload file: ${response2.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
return response2.json();
|
|
134
|
+
}
|
|
135
|
+
const response = await fetch(url, {
|
|
136
|
+
method: "PUT",
|
|
137
|
+
headers: {
|
|
138
|
+
...this.getHeaders(),
|
|
139
|
+
"Content-Type": file.type
|
|
140
|
+
},
|
|
141
|
+
body: file
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
throw new Error(`Failed to upload file: ${response.statusText}`);
|
|
145
|
+
}
|
|
146
|
+
return response.json();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Convert a File to base64 string
|
|
150
|
+
*/
|
|
151
|
+
fileToBase64(file) {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
const reader = new FileReader();
|
|
154
|
+
reader.onload = () => {
|
|
155
|
+
const result = reader.result;
|
|
156
|
+
const base64 = result.split(",")[1];
|
|
157
|
+
resolve(base64);
|
|
158
|
+
};
|
|
159
|
+
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
160
|
+
reader.readAsDataURL(file);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the full URL for a file in a thread's filesystem
|
|
165
|
+
*/
|
|
166
|
+
getFileUrl(threadId, path) {
|
|
167
|
+
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
168
|
+
const encodedPath = normalizedPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
169
|
+
return `${this.endpoint}/threads/${threadId}/fs/${encodedPath}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get the thumbnail URL for an image in a thread's filesystem
|
|
173
|
+
*/
|
|
174
|
+
getThumbnailUrl(threadId, path) {
|
|
175
|
+
return `${this.getFileUrl(threadId, path)}?thumbnail=true`;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Connect to message WebSocket for real-time message updates
|
|
179
|
+
*/
|
|
180
|
+
connectMessageWebSocket(id, callbacks = {}, options = {}) {
|
|
181
|
+
const params = new URLSearchParams();
|
|
182
|
+
if (this.token) params.set("token", this.token);
|
|
183
|
+
if (options.includeSilent !== void 0) params.set("includeSilent", String(options.includeSilent));
|
|
184
|
+
if (options.depth !== void 0) params.set("depth", String(options.depth));
|
|
185
|
+
const wsProtocol = this.endpoint.startsWith("https") ? "wss" : "ws";
|
|
186
|
+
const wsEndpoint = this.endpoint.replace(/^https?/, wsProtocol);
|
|
187
|
+
const url = `${wsEndpoint}/threads/${id}/stream?${params.toString()}`;
|
|
188
|
+
const ws = new WebSocket(url);
|
|
189
|
+
ws.onopen = () => {
|
|
190
|
+
callbacks.onOpen?.();
|
|
191
|
+
};
|
|
192
|
+
ws.onmessage = (event) => {
|
|
193
|
+
try {
|
|
194
|
+
if (typeof event.data === "string" && event.data === "pong") {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const data = JSON.parse(event.data);
|
|
198
|
+
switch (data.type) {
|
|
199
|
+
case "message_data":
|
|
200
|
+
callbacks.onMessage?.(data);
|
|
201
|
+
break;
|
|
202
|
+
case "message_chunk":
|
|
203
|
+
callbacks.onChunk?.(data);
|
|
204
|
+
break;
|
|
205
|
+
case "event":
|
|
206
|
+
callbacks.onEvent?.(data);
|
|
207
|
+
break;
|
|
208
|
+
case "error":
|
|
209
|
+
callbacks.onError?.(data);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("Failed to parse WebSocket message:", error);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
ws.onerror = (event) => {
|
|
217
|
+
console.error("WebSocket error:", event);
|
|
218
|
+
callbacks.onError?.({ type: "error", error: "WebSocket connection error" });
|
|
219
|
+
};
|
|
220
|
+
ws.onclose = (event) => {
|
|
221
|
+
console.log(`[AgentBuilderClient] Message WebSocket closed - code: ${event.code}, reason: ${event.reason || "none"}, wasClean: ${event.wasClean}`);
|
|
222
|
+
callbacks.onClose?.();
|
|
223
|
+
};
|
|
224
|
+
return ws;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Connect to log WebSocket for custom events
|
|
228
|
+
*/
|
|
229
|
+
connectLogWebSocket(id, callbacks = {}) {
|
|
230
|
+
const params = new URLSearchParams();
|
|
231
|
+
if (this.token) params.set("token", this.token);
|
|
232
|
+
const wsProtocol = this.endpoint.startsWith("https") ? "wss" : "ws";
|
|
233
|
+
const wsEndpoint = this.endpoint.replace(/^https?/, wsProtocol);
|
|
234
|
+
const url = `${wsEndpoint}/threads/${id}?${params.toString()}`;
|
|
235
|
+
const ws = new WebSocket(url);
|
|
236
|
+
ws.onopen = () => {
|
|
237
|
+
callbacks.onOpen?.();
|
|
238
|
+
};
|
|
239
|
+
ws.onmessage = (event) => {
|
|
240
|
+
try {
|
|
241
|
+
if (typeof event.data === "string" && event.data === "pong") {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const data = JSON.parse(event.data);
|
|
245
|
+
switch (data.type) {
|
|
246
|
+
case "log_data":
|
|
247
|
+
callbacks.onLog?.(data);
|
|
248
|
+
break;
|
|
249
|
+
case "custom":
|
|
250
|
+
callbacks.onCustom?.(data);
|
|
251
|
+
break;
|
|
252
|
+
case "stopped_by_user":
|
|
253
|
+
callbacks.onStopped?.(data);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error("Failed to parse WebSocket message:", error);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
ws.onerror = (event) => {
|
|
261
|
+
console.error("WebSocket error:", event);
|
|
262
|
+
};
|
|
263
|
+
ws.onclose = () => {
|
|
264
|
+
callbacks.onClose?.();
|
|
265
|
+
};
|
|
266
|
+
return ws;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get headers for HTTP requests
|
|
270
|
+
*/
|
|
271
|
+
getHeaders() {
|
|
272
|
+
const headers = {};
|
|
273
|
+
if (this.token) {
|
|
274
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
275
|
+
}
|
|
276
|
+
return headers;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/connection/ThreadConnectionManager.ts
|
|
281
|
+
var ThreadConnectionManager = class {
|
|
282
|
+
client;
|
|
283
|
+
threadId;
|
|
284
|
+
callbacks;
|
|
285
|
+
options;
|
|
286
|
+
ws = null;
|
|
287
|
+
status = "disconnected";
|
|
288
|
+
reconnectAttempts = 0;
|
|
289
|
+
reconnectTimeout = null;
|
|
290
|
+
heartbeatInterval = null;
|
|
291
|
+
isReconnecting = false;
|
|
292
|
+
shouldReconnect = true;
|
|
293
|
+
constructor(client, threadId, callbacks = {}, options = {}) {
|
|
294
|
+
this.client = client;
|
|
295
|
+
this.threadId = threadId;
|
|
296
|
+
this.callbacks = callbacks;
|
|
297
|
+
this.options = {
|
|
298
|
+
depth: options.depth ?? 0,
|
|
299
|
+
includeSilent: options.includeSilent ?? false,
|
|
300
|
+
heartbeatInterval: options.heartbeatInterval ?? 3e4,
|
|
301
|
+
maxReconnectDelay: options.maxReconnectDelay ?? 3e4
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get current connection status
|
|
306
|
+
*/
|
|
307
|
+
getStatus() {
|
|
308
|
+
return this.status;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get the thread ID this manager is connected to
|
|
312
|
+
*/
|
|
313
|
+
getThreadId() {
|
|
314
|
+
return this.threadId;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Connect to the thread WebSocket
|
|
318
|
+
*/
|
|
319
|
+
connect() {
|
|
320
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.shouldReconnect = true;
|
|
324
|
+
this.isReconnecting = false;
|
|
325
|
+
this.setStatus("connecting");
|
|
326
|
+
this.ws = this.client.connectMessageWebSocket(
|
|
327
|
+
this.threadId,
|
|
328
|
+
{
|
|
329
|
+
onOpen: () => {
|
|
330
|
+
this.setStatus("connected");
|
|
331
|
+
this.reconnectAttempts = 0;
|
|
332
|
+
this.isReconnecting = false;
|
|
333
|
+
this.startHeartbeat();
|
|
334
|
+
},
|
|
335
|
+
onMessage: (event) => {
|
|
336
|
+
this.callbacks.onMessage?.(event);
|
|
337
|
+
},
|
|
338
|
+
onChunk: (event) => {
|
|
339
|
+
this.callbacks.onChunk?.(event);
|
|
340
|
+
},
|
|
341
|
+
onEvent: (event) => {
|
|
342
|
+
this.callbacks.onEvent?.(event);
|
|
343
|
+
},
|
|
344
|
+
onError: (event) => {
|
|
345
|
+
this.callbacks.onError?.(event);
|
|
346
|
+
},
|
|
347
|
+
onClose: () => {
|
|
348
|
+
this.clearTimers();
|
|
349
|
+
this.scheduleReconnect();
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
depth: this.options.depth,
|
|
354
|
+
includeSilent: this.options.includeSilent
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Disconnect from the thread WebSocket
|
|
360
|
+
*/
|
|
361
|
+
disconnect() {
|
|
362
|
+
this.shouldReconnect = false;
|
|
363
|
+
this.clearTimers();
|
|
364
|
+
if (this.ws) {
|
|
365
|
+
this.ws.close();
|
|
366
|
+
this.ws = null;
|
|
367
|
+
}
|
|
368
|
+
this.reconnectAttempts = 0;
|
|
369
|
+
this.isReconnecting = false;
|
|
370
|
+
this.setStatus("disconnected");
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Update callbacks without reconnecting
|
|
374
|
+
*/
|
|
375
|
+
updateCallbacks(callbacks) {
|
|
376
|
+
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Update options (requires reconnect to take effect for depth/includeSilent)
|
|
380
|
+
*/
|
|
381
|
+
updateOptions(options) {
|
|
382
|
+
this.options = { ...this.options, ...options };
|
|
383
|
+
}
|
|
384
|
+
setStatus(status) {
|
|
385
|
+
if (this.status !== status) {
|
|
386
|
+
this.status = status;
|
|
387
|
+
this.callbacks.onStatusChange?.(status);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
startHeartbeat() {
|
|
391
|
+
this.clearHeartbeat();
|
|
392
|
+
this.heartbeatInterval = setInterval(() => {
|
|
393
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
394
|
+
this.ws.send("ping");
|
|
395
|
+
}
|
|
396
|
+
}, this.options.heartbeatInterval);
|
|
397
|
+
}
|
|
398
|
+
clearHeartbeat() {
|
|
399
|
+
if (this.heartbeatInterval) {
|
|
400
|
+
clearInterval(this.heartbeatInterval);
|
|
401
|
+
this.heartbeatInterval = null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
clearReconnectTimeout() {
|
|
405
|
+
if (this.reconnectTimeout) {
|
|
406
|
+
clearTimeout(this.reconnectTimeout);
|
|
407
|
+
this.reconnectTimeout = null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
clearTimers() {
|
|
411
|
+
this.clearHeartbeat();
|
|
412
|
+
this.clearReconnectTimeout();
|
|
413
|
+
}
|
|
414
|
+
scheduleReconnect() {
|
|
415
|
+
if (!this.shouldReconnect || this.isReconnecting) {
|
|
416
|
+
this.setStatus("disconnected");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.isReconnecting = true;
|
|
420
|
+
this.setStatus("reconnecting");
|
|
421
|
+
const delay = Math.min(
|
|
422
|
+
1e3 * Math.pow(2, this.reconnectAttempts),
|
|
423
|
+
this.options.maxReconnectDelay
|
|
424
|
+
);
|
|
425
|
+
this.reconnectAttempts++;
|
|
426
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
427
|
+
if (this.shouldReconnect) {
|
|
428
|
+
this.connect();
|
|
429
|
+
}
|
|
430
|
+
}, delay);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/utils/attachments.ts
|
|
435
|
+
function parseAttachments(message) {
|
|
436
|
+
if (!message.attachments) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const parsed = JSON.parse(message.attachments);
|
|
441
|
+
if (!Array.isArray(parsed)) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
return parsed;
|
|
445
|
+
} catch {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function isImageMimeType(mimeType) {
|
|
450
|
+
return mimeType.toLowerCase().startsWith("image/");
|
|
451
|
+
}
|
|
452
|
+
function messagesToFiles(messages) {
|
|
453
|
+
const files = [];
|
|
454
|
+
for (const message of messages) {
|
|
455
|
+
const attachments = parseAttachments(message);
|
|
456
|
+
for (const attachment of attachments) {
|
|
457
|
+
files.push({
|
|
458
|
+
id: attachment.id,
|
|
459
|
+
name: attachment.name,
|
|
460
|
+
mimeType: attachment.mimeType,
|
|
461
|
+
size: attachment.size,
|
|
462
|
+
isImage: isImageMimeType(attachment.mimeType),
|
|
463
|
+
localPreviewUrl: null,
|
|
464
|
+
// Committed files don't have local preview
|
|
465
|
+
status: "committed",
|
|
466
|
+
path: attachment.path,
|
|
467
|
+
width: attachment.width,
|
|
468
|
+
height: attachment.height,
|
|
469
|
+
messageId: message.id
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return files;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/utils/imageProcessing.ts
|
|
477
|
+
var THUMBNAIL_SIZE = 256;
|
|
478
|
+
function loadImage(file) {
|
|
479
|
+
return new Promise((resolve, reject) => {
|
|
480
|
+
const img = new Image();
|
|
481
|
+
const objectUrl = URL.createObjectURL(file);
|
|
482
|
+
img.onload = () => {
|
|
483
|
+
URL.revokeObjectURL(objectUrl);
|
|
484
|
+
resolve(img);
|
|
485
|
+
};
|
|
486
|
+
img.onerror = () => {
|
|
487
|
+
URL.revokeObjectURL(objectUrl);
|
|
488
|
+
reject(new Error("Failed to load image"));
|
|
489
|
+
};
|
|
490
|
+
img.src = objectUrl;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function createThumbnailFromImage(img) {
|
|
494
|
+
const { width, height } = img;
|
|
495
|
+
const aspectRatio = width / height;
|
|
496
|
+
let thumbWidth;
|
|
497
|
+
let thumbHeight;
|
|
498
|
+
if (aspectRatio > 1) {
|
|
499
|
+
thumbWidth = Math.min(THUMBNAIL_SIZE, width);
|
|
500
|
+
thumbHeight = Math.floor(thumbWidth / aspectRatio);
|
|
501
|
+
} else {
|
|
502
|
+
thumbHeight = Math.min(THUMBNAIL_SIZE, height);
|
|
503
|
+
thumbWidth = Math.floor(thumbHeight * aspectRatio);
|
|
504
|
+
}
|
|
505
|
+
const canvas = document.createElement("canvas");
|
|
506
|
+
canvas.width = thumbWidth;
|
|
507
|
+
canvas.height = thumbHeight;
|
|
508
|
+
const ctx = canvas.getContext("2d");
|
|
509
|
+
if (!ctx) {
|
|
510
|
+
throw new Error("Failed to get canvas context");
|
|
511
|
+
}
|
|
512
|
+
ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight);
|
|
513
|
+
const dataUrl = canvas.toDataURL("image/webp", 0.8);
|
|
514
|
+
return dataUrl.split(",")[1];
|
|
515
|
+
}
|
|
516
|
+
async function generateImageThumbnail(file) {
|
|
517
|
+
const img = await loadImage(file);
|
|
518
|
+
return {
|
|
519
|
+
thumbnail: createThumbnailFromImage(img),
|
|
520
|
+
width: img.width,
|
|
521
|
+
height: img.height
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function canGenerateThumbnails() {
|
|
525
|
+
return typeof document !== "undefined" && typeof document.createElement === "function";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/utils/fileHelpers.ts
|
|
529
|
+
function generatePendingFileId() {
|
|
530
|
+
return `pending-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
531
|
+
}
|
|
532
|
+
function readFileAsDataUrl(file) {
|
|
533
|
+
return new Promise((resolve, reject) => {
|
|
534
|
+
const reader = new FileReader();
|
|
535
|
+
reader.onload = () => resolve(reader.result);
|
|
536
|
+
reader.onerror = reject;
|
|
537
|
+
reader.readAsDataURL(file);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/uploads/FileUploadManager.ts
|
|
542
|
+
var FileUploadManager = class {
|
|
543
|
+
/**
|
|
544
|
+
* Create a pending file object from a File.
|
|
545
|
+
* The returned object can be immediately added to state for UI feedback.
|
|
546
|
+
*/
|
|
547
|
+
createPendingFile(file) {
|
|
548
|
+
return {
|
|
549
|
+
id: generatePendingFileId(),
|
|
550
|
+
name: file.name,
|
|
551
|
+
mimeType: file.type,
|
|
552
|
+
size: file.size,
|
|
553
|
+
isImage: isImageMimeType(file.type),
|
|
554
|
+
localPreviewUrl: null,
|
|
555
|
+
status: "uploading"
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Queue multiple files for upload.
|
|
560
|
+
* Returns an array of pending file objects paired with their source File objects.
|
|
561
|
+
*
|
|
562
|
+
* @param files - Files to queue (File array or FileList)
|
|
563
|
+
* @returns Array of [ThreadFile, File] tuples
|
|
564
|
+
*/
|
|
565
|
+
queueFiles(files) {
|
|
566
|
+
return Array.from(files).map((file) => ({
|
|
567
|
+
pending: this.createPendingFile(file),
|
|
568
|
+
file
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Execute the upload for a file.
|
|
573
|
+
*
|
|
574
|
+
* This method:
|
|
575
|
+
* 1. Generates a local preview for images (async, non-blocking)
|
|
576
|
+
* 2. Generates a thumbnail for images (if supported)
|
|
577
|
+
* 3. Uploads the file to the server
|
|
578
|
+
* 4. Calls onUpdate with state changes at each step
|
|
579
|
+
*
|
|
580
|
+
* @param threadId - The thread to upload to
|
|
581
|
+
* @param file - The file to upload
|
|
582
|
+
* @param pendingId - The ID of the pending file (from queueFiles)
|
|
583
|
+
* @param client - The AgentBuilderClient instance
|
|
584
|
+
* @param onUpdate - Callback for state updates
|
|
585
|
+
* @param options - Upload options
|
|
586
|
+
*/
|
|
587
|
+
async executeUpload(threadId, file, pendingId, client, onUpdate, options = {}) {
|
|
588
|
+
const {
|
|
589
|
+
generateThumbnail = true,
|
|
590
|
+
generatePreview = true
|
|
591
|
+
} = options;
|
|
592
|
+
const isImage = isImageMimeType(file.type);
|
|
593
|
+
if (isImage && generatePreview) {
|
|
594
|
+
readFileAsDataUrl(file).then((dataUrl) => onUpdate({ localPreviewUrl: dataUrl })).catch((err) => console.error("Failed to generate preview:", err));
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
let uploadOptions;
|
|
598
|
+
if (isImage && generateThumbnail) {
|
|
599
|
+
try {
|
|
600
|
+
const result = await generateImageThumbnail(file);
|
|
601
|
+
uploadOptions = {
|
|
602
|
+
thumbnail: result.thumbnail,
|
|
603
|
+
width: result.width,
|
|
604
|
+
height: result.height
|
|
605
|
+
};
|
|
606
|
+
} catch (err) {
|
|
607
|
+
console.warn("Failed to generate thumbnail:", err);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const attachment = await client.uploadFile(threadId, file, uploadOptions);
|
|
611
|
+
onUpdate({
|
|
612
|
+
id: attachment.id,
|
|
613
|
+
status: "ready",
|
|
614
|
+
path: attachment.path,
|
|
615
|
+
width: attachment.width,
|
|
616
|
+
height: attachment.height
|
|
617
|
+
});
|
|
618
|
+
return attachment;
|
|
619
|
+
} catch (err) {
|
|
620
|
+
onUpdate({
|
|
621
|
+
status: "error",
|
|
622
|
+
error: err instanceof Error ? err.message : "Failed to upload file"
|
|
623
|
+
});
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Execute uploads for multiple files in parallel.
|
|
629
|
+
*
|
|
630
|
+
* @param threadId - The thread to upload to
|
|
631
|
+
* @param items - Array of pending/file pairs from queueFiles()
|
|
632
|
+
* @param client - The AgentBuilderClient instance
|
|
633
|
+
* @param onUpdate - Callback receiving (pendingId, updates) for each file
|
|
634
|
+
* @param options - Upload options
|
|
635
|
+
* @returns Array of upload results (AttachmentRef or Error for each file)
|
|
636
|
+
*/
|
|
637
|
+
async executeUploads(threadId, items, client, onUpdate, options = {}) {
|
|
638
|
+
const promises = items.map(
|
|
639
|
+
({ pending, file }) => this.executeUpload(
|
|
640
|
+
threadId,
|
|
641
|
+
file,
|
|
642
|
+
pending.id,
|
|
643
|
+
client,
|
|
644
|
+
(updates) => onUpdate(pending.id, updates),
|
|
645
|
+
options
|
|
646
|
+
).catch((err) => err instanceof Error ? err : new Error(String(err)))
|
|
647
|
+
);
|
|
648
|
+
return Promise.all(promises);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/utils/workblocks.ts
|
|
653
|
+
function transformToWorkblocks(messages) {
|
|
654
|
+
if (messages.length === 0) {
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
const result = [];
|
|
658
|
+
let i = 0;
|
|
659
|
+
while (i < messages.length) {
|
|
660
|
+
const message = messages[i];
|
|
661
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
662
|
+
let toolCalls;
|
|
663
|
+
try {
|
|
664
|
+
toolCalls = JSON.parse(message.tool_calls);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
result.push(message);
|
|
667
|
+
i++;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const workItems = [];
|
|
671
|
+
for (const toolCall of toolCalls) {
|
|
672
|
+
workItems.push({
|
|
673
|
+
id: toolCall.id || message.id,
|
|
674
|
+
type: "tool_call",
|
|
675
|
+
name: toolCall.function?.name,
|
|
676
|
+
content: toolCall.function?.arguments || null,
|
|
677
|
+
status: null,
|
|
678
|
+
// Will be updated below based on matching results
|
|
679
|
+
tool_call_id: toolCall.id
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
let j = i + 1;
|
|
683
|
+
while (j < messages.length && messages[j].role === "tool") {
|
|
684
|
+
const toolMessage = messages[j];
|
|
685
|
+
const resultStatus = toolMessage.tool_status || "pending";
|
|
686
|
+
workItems.push({
|
|
687
|
+
id: toolMessage.id,
|
|
688
|
+
type: "tool_result",
|
|
689
|
+
name: toolMessage.name || void 0,
|
|
690
|
+
content: toolMessage.content,
|
|
691
|
+
status: resultStatus,
|
|
692
|
+
tool_call_id: toolMessage.tool_call_id || void 0
|
|
693
|
+
});
|
|
694
|
+
j++;
|
|
695
|
+
}
|
|
696
|
+
for (const item of workItems) {
|
|
697
|
+
if (item.type === "tool_call" && item.tool_call_id) {
|
|
698
|
+
const matchingResult = workItems.find(
|
|
699
|
+
(wi) => wi.type === "tool_result" && wi.tool_call_id === item.tool_call_id
|
|
700
|
+
);
|
|
701
|
+
if (matchingResult) {
|
|
702
|
+
item.status = matchingResult.status;
|
|
703
|
+
} else {
|
|
704
|
+
item.status = "pending";
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
let status = "completed";
|
|
709
|
+
if (message.status === "pending") {
|
|
710
|
+
status = "pending";
|
|
711
|
+
} else if (message.status === "failed") {
|
|
712
|
+
status = "failed";
|
|
713
|
+
}
|
|
714
|
+
const workblock = {
|
|
715
|
+
id: message.id,
|
|
716
|
+
type: "workblock",
|
|
717
|
+
content: message.content,
|
|
718
|
+
reasoning_content: message.reasoning_content,
|
|
719
|
+
workItems,
|
|
720
|
+
status,
|
|
721
|
+
created_at: message.created_at,
|
|
722
|
+
depth: message.depth
|
|
723
|
+
};
|
|
724
|
+
result.push(workblock);
|
|
725
|
+
i = j;
|
|
726
|
+
} else {
|
|
727
|
+
result.push(message);
|
|
728
|
+
i++;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return result;
|
|
732
|
+
}
|
|
733
|
+
export {
|
|
734
|
+
AgentBuilderClient,
|
|
735
|
+
FileUploadManager,
|
|
736
|
+
ThreadConnectionManager,
|
|
737
|
+
canGenerateThumbnails,
|
|
738
|
+
generateImageThumbnail,
|
|
739
|
+
generatePendingFileId,
|
|
740
|
+
isImageMimeType,
|
|
741
|
+
messagesToFiles,
|
|
742
|
+
parseAttachments,
|
|
743
|
+
readFileAsDataUrl,
|
|
744
|
+
transformToWorkblocks
|
|
745
|
+
};
|
|
746
|
+
//# sourceMappingURL=index.js.map
|