@toothfairyai/cli 1.1.1 → 1.1.2

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.
Files changed (3) hide show
  1. package/bin/toothfairy.js +868 -856
  2. package/package.json +1 -1
  3. package/src/api.js +573 -380
package/src/api.js CHANGED
@@ -1,6 +1,6 @@
1
- const axios = require("axios");
2
- const fs = require("fs");
3
- const path = require("path");
1
+ const axios = require('axios');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
4
 
5
5
  class ToothFairyAPI {
6
6
  constructor(
@@ -17,8 +17,8 @@ class ToothFairyAPI {
17
17
  this.workspaceId = workspaceId;
18
18
  this.verbose = verbose;
19
19
  this.headers = {
20
- "Content-Type": "application/json",
21
- "x-api-key": apiKey,
20
+ 'Content-Type': 'application/json',
21
+ 'x-api-key': apiKey,
22
22
  };
23
23
  }
24
24
 
@@ -29,20 +29,20 @@ class ToothFairyAPI {
29
29
  headers: this.headers,
30
30
  };
31
31
 
32
- if (method === "POST" || method === "PUT") {
32
+ if (method === 'POST' || method === 'PUT') {
33
33
  if (data) {
34
34
  data = { workspaceid: this.workspaceId, ...data };
35
35
  }
36
36
  config.data = data;
37
- } else if (method === "GET" && data) {
37
+ } else if (method === 'GET' && data) {
38
38
  // For GET requests with data, add as query parameters
39
39
  const params = new URLSearchParams(data);
40
40
  config.url += `?${params.toString()}`;
41
41
  }
42
42
 
43
43
  if (this.verbose) {
44
- const chalk = require("chalk");
45
- console.error(chalk.dim("\n--- API Request Debug ---"));
44
+ const chalk = require('chalk');
45
+ console.error(chalk.dim('\n--- API Request Debug ---'));
46
46
  console.error(chalk.dim(`Method: ${method}`));
47
47
  console.error(chalk.dim(`URL: ${config.url}`));
48
48
  console.error(
@@ -53,15 +53,15 @@ class ToothFairyAPI {
53
53
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
54
54
  );
55
55
  }
56
- console.error(chalk.dim("----------------------\n"));
56
+ console.error(chalk.dim('----------------------\n'));
57
57
  }
58
58
 
59
59
  try {
60
60
  const response = await axios(config);
61
61
 
62
62
  if (this.verbose) {
63
- const chalk = require("chalk");
64
- console.error(chalk.dim("\n--- API Response Debug ---"));
63
+ const chalk = require('chalk');
64
+ console.error(chalk.dim('\n--- API Response Debug ---'));
65
65
  console.error(
66
66
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
67
67
  );
@@ -73,14 +73,14 @@ class ToothFairyAPI {
73
73
  console.error(
74
74
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
75
75
  );
76
- console.error(chalk.dim("------------------------\n"));
76
+ console.error(chalk.dim('------------------------\n'));
77
77
  }
78
78
 
79
79
  return response.data;
80
80
  } catch (error) {
81
81
  if (this.verbose) {
82
- const chalk = require("chalk");
83
- console.error(chalk.red("\n--- API Error Debug ---"));
82
+ const chalk = require('chalk');
83
+ console.error(chalk.red('\n--- API Error Debug ---'));
84
84
  console.error(chalk.red(`Error: ${error.message}`));
85
85
  if (error.response) {
86
86
  console.error(
@@ -110,13 +110,13 @@ class ToothFairyAPI {
110
110
  )
111
111
  );
112
112
  }
113
- console.error(chalk.red("---------------------\n"));
113
+ console.error(chalk.red('---------------------\n'));
114
114
  }
115
115
 
116
116
  if (error.response) {
117
117
  throw new Error(
118
118
  `HTTP ${error.response.status}: ${
119
- error.response.data.message || "API request failed"
119
+ error.response.data.message || 'API request failed'
120
120
  }`
121
121
  );
122
122
  }
@@ -125,39 +125,39 @@ class ToothFairyAPI {
125
125
  }
126
126
 
127
127
  async createChat(chatData) {
128
- return this._makeRequest("POST", "chat/create", chatData);
128
+ return this._makeRequest('POST', 'chat/create', chatData);
129
129
  }
130
130
 
131
131
  async updateChat(chatData) {
132
- return this._makeRequest("POST", "chat/update", chatData);
132
+ return this._makeRequest('POST', 'chat/update', chatData);
133
133
  }
134
134
 
135
135
  async getChat(chatId) {
136
- return this._makeRequest("GET", `chat/get/${chatId}`);
136
+ return this._makeRequest('GET', `chat/get/${chatId}`);
137
137
  }
138
138
 
139
139
  async createMessage(messageData) {
140
- return this._makeRequest("POST", "chat_message/create", messageData);
140
+ return this._makeRequest('POST', 'chat_message/create', messageData);
141
141
  }
142
142
 
143
143
  async getMessage(messageId) {
144
- return this._makeRequest("GET", `chat_message/get/${messageId}`);
144
+ return this._makeRequest('GET', `chat_message/get/${messageId}`);
145
145
  }
146
146
 
147
147
  async getAllChats() {
148
- return this._makeRequest("GET", "chat/list", {
148
+ return this._makeRequest('GET', 'chat/list', {
149
149
  workspaceid: this.workspaceId,
150
150
  });
151
151
  }
152
152
  async getAllMessagesByChat(chatid) {
153
- return this._makeRequest("GET", "chat_message/by_chat", {
153
+ return this._makeRequest('GET', 'chat_message/by_chat', {
154
154
  chatid: chatid,
155
155
  });
156
156
  }
157
157
 
158
158
  async getAgentResponse(agentData) {
159
159
  const config = {
160
- method: "POST",
160
+ method: 'POST',
161
161
  url: `${this.aiUrl}/chatter`,
162
162
  headers: this.headers,
163
163
  data: { workspaceid: this.workspaceId, ...agentData },
@@ -170,7 +170,7 @@ class ToothFairyAPI {
170
170
  if (error.response) {
171
171
  throw new Error(
172
172
  `HTTP ${error.response.status}: ${
173
- error.response.data.message || "Agent request failed"
173
+ error.response.data.message || 'Agent request failed'
174
174
  }`
175
175
  );
176
176
  }
@@ -185,7 +185,8 @@ class ToothFairyAPI {
185
185
  customerId = null,
186
186
  providerId = null,
187
187
  customerInfo = {},
188
- attachments = {}
188
+ attachments = {},
189
+ chatId = null
189
190
  ) {
190
191
  try {
191
192
  // Use defaults for optional parameters
@@ -193,66 +194,92 @@ class ToothFairyAPI {
193
194
  customerId ||
194
195
  `cli-user-${
195
196
  Math.abs(
196
- message.split("").reduce((a, b) => {
197
+ message.split('').reduce((a, b) => {
197
198
  a = (a << 5) - a + b.charCodeAt(0);
198
199
  return a & a;
199
200
  }, 0)
200
201
  ) % 10000
201
202
  }`;
202
- phoneNumber = phoneNumber || "+1234567890";
203
- providerId = providerId || "default-sms-provider";
203
+ phoneNumber = phoneNumber || '+1234567890';
204
+ providerId = providerId || 'default-sms-provider';
204
205
 
205
206
  // Process file attachments if provided
206
207
  const processedAttachments = await this._processAttachments(attachments);
207
208
 
208
- const chatData = {
209
- name: customerId,
210
- primaryRole: agentId,
211
- externalParticipantId: phoneNumber,
212
- channelSettings: {
213
- sms: {
214
- isEnabled: true,
215
- recipient: phoneNumber,
216
- providerID: providerId,
217
- },
218
- },
219
- customerId: customerId,
220
- customerInfo: customerInfo,
221
- isAIReplying: true,
222
- };
223
-
224
- const createdChat = await this.createChat(chatData);
225
- // console.log(`Chat created: ${createdChat.id}`);
226
-
227
- const messageData = {
228
- chatID: createdChat.id,
229
- text: message,
230
- role: "user",
231
- userID: "CLI",
232
- ...processedAttachments,
233
- };
234
- const createdMessage = await this.createMessage(messageData);
235
- // console.log(`Message created: ${createdMessage.id}`);;
209
+ if (chatId) {
210
+ // Use existing chat - create message in existing chat
211
+ const messageData = {
212
+ chatID: chatId,
213
+ text: message,
214
+ role: 'user',
215
+ userID: 'CLI',
216
+ ...processedAttachments,
217
+ };
218
+ const createdMessage = await this.createMessage(messageData);
219
+ // console.log(`Message created: ${createdMessage.id}`);;
220
+
221
+ const agentData = {
222
+ chatid: chatId,
223
+ messages: [
224
+ {
225
+ text: createdMessage.text,
226
+ role: createdMessage.role,
227
+ userID: createdMessage.userID || 'System User',
228
+ },
229
+ ],
230
+ agentid: agentId,
231
+ };
232
+
233
+ const agentResponse = await this.getAgentResponse(agentData);
234
+
235
+ return {
236
+ chatId: chatId,
237
+ messageId: createdMessage.id,
238
+ agentResponse: agentResponse,
239
+ };
240
+ } else {
241
+ // No chatId provided - let API create chat automatically
242
+ // Prepare message data for automatic chat creation
243
+ const messageData = {
244
+ text: message,
245
+ role: 'user',
246
+ userID: customerId,
247
+ };
236
248
 
237
- const agentData = {
238
- chatid: createdChat.id,
239
- messages: [
240
- {
241
- text: createdMessage.text,
242
- role: createdMessage.role,
243
- userID: createdMessage.userID || "System User",
244
- },
245
- ],
246
- agentid: agentId,
247
- };
248
- const agentResponse = await this.getAgentResponse(agentData);
249
- // console.log('Agent response received');
249
+ // Only include attachment fields if they have content
250
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
251
+ messageData.images = processedAttachments.images;
252
+ }
253
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
254
+ messageData.audios = processedAttachments.audios;
255
+ }
256
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
257
+ messageData.videos = processedAttachments.videos;
258
+ }
259
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
260
+ messageData.files = processedAttachments.files;
261
+ }
250
262
 
251
- return {
252
- chatId: createdChat.id,
253
- messageId: createdMessage.id,
254
- agentResponse: agentResponse,
255
- };
263
+ // Send agent request without chatid - API will create chat and message automatically
264
+ const agentData = {
265
+ // No chatid - let API create the chat
266
+ messages: [messageData],
267
+ agentid: agentId,
268
+ // Include chat creation data since we're not pre-creating
269
+ phoneNumber: phoneNumber,
270
+ customerId: customerId,
271
+ providerId: providerId,
272
+ customerInfo: customerInfo,
273
+ };
274
+
275
+ const agentResponse = await this.getAgentResponse(agentData);
276
+
277
+ return {
278
+ chatId: agentResponse.chatId || 'auto-generated',
279
+ messageId: agentResponse.messageId || 'auto-generated',
280
+ agentResponse: agentResponse,
281
+ };
282
+ }
256
283
  } catch (error) {
257
284
  console.error(`Error in sendMessageToAgent: ${error.message}`);
258
285
  throw error;
@@ -261,7 +288,7 @@ class ToothFairyAPI {
261
288
 
262
289
  async searchDocuments(text, topK = 10, metadata = null) {
263
290
  if (topK < 1 || topK > 50) {
264
- throw new Error("topK must be between 1 and 50");
291
+ throw new Error('topK must be between 1 and 50');
265
292
  }
266
293
 
267
294
  const searchData = {
@@ -274,7 +301,7 @@ class ToothFairyAPI {
274
301
  }
275
302
 
276
303
  const config = {
277
- method: "POST",
304
+ method: 'POST',
278
305
  url: `${this.aiUrl}/searcher`,
279
306
  headers: this.headers,
280
307
  data: { workspaceid: this.workspaceId, ...searchData },
@@ -287,7 +314,7 @@ class ToothFairyAPI {
287
314
  if (error.response) {
288
315
  throw new Error(
289
316
  `HTTP ${error.response.status}: ${
290
- error.response.data.message || "Search request failed"
317
+ error.response.data.message || 'Search request failed'
291
318
  }`
292
319
  );
293
320
  }
@@ -329,7 +356,7 @@ class ToothFairyAPI {
329
356
  * - 'sse_event': All raw SSE events (only when showProgress=true)
330
357
  *
331
358
  * The onEvent callback receives: (eventType, eventData) => {}
332
- *
359
+ *
333
360
  * When showProgress is true, all SSE events will be emitted to the onEvent callback
334
361
  * with the 'sse_event' type, providing access to all raw events from the streaming
335
362
  * endpoint, similar to the CLI's --show-progress flag behavior.
@@ -343,7 +370,8 @@ class ToothFairyAPI {
343
370
  customerInfo = {},
344
371
  onEvent,
345
372
  attachments = {},
346
- showProgress = false
373
+ showProgress = false,
374
+ chatId = null
347
375
  ) {
348
376
  try {
349
377
  // Use defaults for optional parameters
@@ -351,207 +379,372 @@ class ToothFairyAPI {
351
379
  customerId ||
352
380
  `cli-user-${
353
381
  Math.abs(
354
- message.split("").reduce((a, b) => {
382
+ message.split('').reduce((a, b) => {
355
383
  a = (a << 5) - a + b.charCodeAt(0);
356
384
  return a & a;
357
385
  }, 0)
358
386
  ) % 10000
359
387
  }`;
360
- phoneNumber = phoneNumber || "+1234567890";
361
- providerId = providerId || "default-sms-provider";
388
+ phoneNumber = phoneNumber || '+1234567890';
389
+ providerId = providerId || 'default-sms-provider';
362
390
 
363
391
  // Process file attachments if provided
364
392
  const processedAttachments = await this._processAttachments(attachments);
365
393
 
366
- // Create chat first
367
- const chatData = {
368
- name: customerId,
369
- primaryRole: agentId,
370
- externalParticipantId: phoneNumber,
371
- channelSettings: {
372
- sms: {
373
- isEnabled: true,
374
- recipient: phoneNumber,
375
- providerID: providerId,
376
- },
377
- },
378
- customerId: customerId,
379
- customerInfo: customerInfo,
380
- isAIReplying: true,
381
- };
382
-
383
- const createdChat = await this.createChat(chatData);
384
- if (this.verbose) {
385
- console.debug(`Chat created: ${createdChat.id}`);
386
- }
394
+ if (chatId) {
395
+ // Use existing chat - create message in existing chat
396
+ if (this.verbose) {
397
+ console.debug(`Using existing chat: ${chatId}`);
398
+ }
387
399
 
388
- // Create message
389
- const messageData = {
390
- chatID: createdChat.id,
391
- text: message,
392
- role: "user",
393
- userID: "CLI",
394
- ...processedAttachments,
395
- };
396
- const createdMessage = await this.createMessage(messageData);
397
- if (this.verbose) {
398
- console.debug(`Message created: ${createdMessage.id}`);
399
- }
400
+ const messageData = {
401
+ chatID: chatId,
402
+ text: message,
403
+ role: 'user',
404
+ userID: 'CLI',
405
+ ...processedAttachments,
406
+ };
407
+ const createdMessage = await this.createMessage(messageData);
408
+ if (this.verbose) {
409
+ console.debug(`Message created: ${createdMessage.id}`);
410
+ }
400
411
 
401
- // Prepare agent data for streaming
402
- const agentData = {
403
- workspaceid: this.workspaceId,
404
- messages: [
405
- {
406
- text: createdMessage.text,
407
- role: createdMessage.role,
408
- userID: createdMessage.userID || "System User",
409
- },
410
- ],
411
- agentid: agentId,
412
- };
412
+ // Prepare agent data for streaming with existing chat
413
+ const agentData = {
414
+ workspaceid: this.workspaceId,
415
+ chatid: chatId,
416
+ messages: [
417
+ {
418
+ text: createdMessage.text,
419
+ role: createdMessage.role,
420
+ userID: createdMessage.userID || 'System User',
421
+ },
422
+ ],
423
+ agentid: agentId,
424
+ };
413
425
 
414
- // Stream the agent response using the dedicated streaming URL
415
- const streamUrl = `${this.aiStreamUrl}/agent`; // Using streaming URL for /agent endpoint
426
+ // Stream the agent response using the dedicated streaming URL
427
+ const streamUrl = `${this.aiStreamUrl}/agent`;
428
+
429
+ return new Promise((resolve, reject) => {
430
+ // For POST requests with EventSource, we need to use a different approach
431
+ // EventSource doesn't support POST requests directly, so we'll use axios with streaming
432
+ const config = {
433
+ method: 'POST',
434
+ url: streamUrl,
435
+ headers: {
436
+ ...this.headers,
437
+ Accept: 'text/event-stream',
438
+ 'Cache-Control': 'no-cache',
439
+ },
440
+ data: agentData,
441
+ responseType: 'stream',
442
+ timeout: 300000, // 5 minute timeout
443
+ };
444
+
445
+ axios(config)
446
+ .then((response) => {
447
+ let buffer = '';
448
+
449
+ response.data.on('data', (chunk) => {
450
+ buffer += chunk.toString();
451
+
452
+ // Process complete lines
453
+ const lines = buffer.split('\n');
454
+ buffer = lines.pop() || ''; // Keep the incomplete line in buffer
455
+
456
+ for (const line of lines) {
457
+ if (line.trim() === '') continue;
458
+
459
+ if (line.startsWith('data: ')) {
460
+ const dataStr = line.slice(6); // Remove 'data: ' prefix
461
+
462
+ try {
463
+ const eventData = JSON.parse(dataStr);
464
+
465
+ // If showProgress is enabled, emit all events
466
+ if (showProgress) {
467
+ // Emit all events with their raw structure
468
+ onEvent('sse_event', eventData);
469
+ }
416
470
 
417
- return new Promise((resolve, reject) => {
418
- // For POST requests with EventSource, we need to use a different approach
419
- // EventSource doesn't support POST requests directly, so we'll use axios with streaming
420
- const config = {
421
- method: "POST",
422
- url: streamUrl,
423
- headers: {
424
- ...this.headers,
425
- Accept: "text/event-stream",
426
- "Cache-Control": "no-cache",
427
- },
428
- data: agentData,
429
- responseType: "stream",
430
- timeout: 300000, // 5 minute timeout
431
- };
471
+ // Standard event processing (always executed for backward compatibility)
472
+ if (eventData.status) {
473
+ if (eventData.status === 'connected') {
474
+ onEvent('status', eventData);
475
+ } else if (eventData.status === 'complete') {
476
+ onEvent('status', eventData);
477
+ } else if (eventData.status === 'inProgress') {
478
+ // Parse metadata to understand what's happening
479
+ let metadata = {};
480
+ if (eventData.metadata) {
481
+ try {
482
+ metadata = JSON.parse(eventData.metadata);
483
+ } catch (e) {
484
+ metadata = { raw_metadata: eventData.metadata };
485
+ }
486
+ }
432
487
 
433
- axios(config)
434
- .then((response) => {
435
- let buffer = "";
488
+ // Determine progress type
489
+ if (metadata.agent_processing_status) {
490
+ const processingStatus =
491
+ metadata.agent_processing_status;
492
+ onEvent('progress', {
493
+ ...eventData,
494
+ processing_status: processingStatus,
495
+ metadata_parsed: metadata,
496
+ });
497
+ } else {
498
+ onEvent('progress', {
499
+ ...eventData,
500
+ metadata_parsed: metadata,
501
+ });
502
+ }
503
+ } else if (eventData.status === 'fulfilled') {
504
+ // Final response with complete data
505
+ onEvent('complete', eventData);
506
+ }
507
+ } else if (eventData.text && eventData.type === 'message') {
508
+ // This is streaming text data
509
+ onEvent('data', eventData);
510
+ } else if (
511
+ eventData.type === 'message' &&
512
+ eventData.images !== undefined
513
+ ) {
514
+ // Additional message metadata (images, files, etc.)
515
+ onEvent('metadata', eventData);
516
+ } else if (
517
+ eventData.type === 'message' &&
518
+ eventData.callbackMetadata
519
+ ) {
520
+ // Callback metadata with function details and execution plan
521
+ onEvent('callback', eventData);
522
+ } else if (eventData.type === 'chat_created' || eventData.event === 'chat_created') {
523
+ // Chat creation event
524
+ onEvent('chat_created', eventData);
525
+ } else {
526
+ // Generic event data
527
+ onEvent('unknown', eventData);
528
+ }
529
+ } catch (e) {
530
+ console.error(
531
+ `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
532
+ );
533
+ onEvent('error', {
534
+ error: 'json_decode_error',
535
+ raw_data: dataStr,
536
+ message: e.message,
537
+ });
538
+ }
539
+ }
540
+ }
541
+ });
436
542
 
437
- response.data.on("data", (chunk) => {
438
- buffer += chunk.toString();
543
+ response.data.on('end', () => {
544
+ resolve();
545
+ });
439
546
 
440
- // Process complete lines
441
- const lines = buffer.split("\n");
442
- buffer = lines.pop() || ""; // Keep the incomplete line in buffer
547
+ response.data.on('error', (error) => {
548
+ onEvent('error', {
549
+ error: 'stream_error',
550
+ message: error.message,
551
+ });
552
+ reject(error);
553
+ });
554
+ })
555
+ .catch((error) => {
556
+ onEvent('error', {
557
+ error: 'request_error',
558
+ message: error.message,
559
+ });
560
+ reject(error);
561
+ });
562
+ });
563
+ } else {
564
+ // No chatId provided - let streaming API create chat automatically
565
+ if (this.verbose) {
566
+ console.debug('No chatId provided - letting API create chat automatically');
567
+ }
443
568
 
444
- for (const line of lines) {
445
- if (line.trim() === "") continue;
569
+ // Prepare message data for automatic chat creation
570
+ const messageData = {
571
+ text: message,
572
+ role: 'user',
573
+ userID: customerId,
574
+ };
446
575
 
447
- if (line.startsWith("data: ")) {
448
- const dataStr = line.slice(6); // Remove 'data: ' prefix
576
+ // Only include attachment fields if they have content
577
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
578
+ messageData.images = processedAttachments.images;
579
+ }
580
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
581
+ messageData.audios = processedAttachments.audios;
582
+ }
583
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
584
+ messageData.videos = processedAttachments.videos;
585
+ }
586
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
587
+ messageData.files = processedAttachments.files;
588
+ }
449
589
 
450
- try {
451
- const eventData = JSON.parse(dataStr);
590
+ // Send agent request without chatid - API will create chat and message automatically
591
+ const agentData = {
592
+ workspaceid: this.workspaceId,
593
+ // No chatid - let API create the chat
594
+ messages: [messageData],
595
+ agentid: agentId,
596
+ // Include chat creation data since we're not pre-creating
597
+ phoneNumber: phoneNumber,
598
+ customerId: customerId,
599
+ providerId: providerId,
600
+ customerInfo: customerInfo,
601
+ };
452
602
 
453
- // If showProgress is enabled, emit all events
454
- if (showProgress) {
455
- // Emit all events with their raw structure
456
- onEvent('sse_event', eventData);
457
- }
603
+ // Stream the agent response using the dedicated streaming URL
604
+ const streamUrl = `${this.aiStreamUrl}/agent`;
605
+
606
+ return new Promise((resolve, reject) => {
607
+ // For POST requests with EventSource, we need to use a different approach
608
+ // EventSource doesn't support POST requests directly, so we'll use axios with streaming
609
+ const config = {
610
+ method: 'POST',
611
+ url: streamUrl,
612
+ headers: {
613
+ ...this.headers,
614
+ Accept: 'text/event-stream',
615
+ 'Cache-Control': 'no-cache',
616
+ },
617
+ data: agentData,
618
+ responseType: 'stream',
619
+ timeout: 300000, // 5 minute timeout
620
+ };
621
+
622
+ axios(config)
623
+ .then((response) => {
624
+ let buffer = '';
625
+
626
+ response.data.on('data', (chunk) => {
627
+ buffer += chunk.toString();
628
+
629
+ // Process complete lines
630
+ const lines = buffer.split('\n');
631
+ buffer = lines.pop() || ''; // Keep the incomplete line in buffer
632
+
633
+ for (const line of lines) {
634
+ if (line.trim() === '') continue;
635
+
636
+ if (line.startsWith('data: ')) {
637
+ const dataStr = line.slice(6); // Remove 'data: ' prefix
638
+
639
+ try {
640
+ const eventData = JSON.parse(dataStr);
641
+
642
+ // If showProgress is enabled, emit all events
643
+ if (showProgress) {
644
+ // Emit all events with their raw structure
645
+ onEvent('sse_event', eventData);
646
+ }
458
647
 
459
- // Standard event processing (always executed for backward compatibility)
460
- if (eventData.status) {
461
- if (eventData.status === 'connected') {
462
- onEvent('status', eventData);
463
- } else if (eventData.status === 'complete') {
464
- onEvent('status', eventData);
465
- } else if (eventData.status === 'inProgress') {
466
- // Parse metadata to understand what's happening
467
- let metadata = {};
468
- if (eventData.metadata) {
469
- try {
470
- metadata = JSON.parse(eventData.metadata);
471
- } catch (e) {
472
- metadata = { raw_metadata: eventData.metadata };
648
+ // Standard event processing (always executed for backward compatibility)
649
+ if (eventData.status) {
650
+ if (eventData.status === 'connected') {
651
+ onEvent('status', eventData);
652
+ } else if (eventData.status === 'complete') {
653
+ onEvent('status', eventData);
654
+ } else if (eventData.status === 'inProgress') {
655
+ // Parse metadata to understand what's happening
656
+ let metadata = {};
657
+ if (eventData.metadata) {
658
+ try {
659
+ metadata = JSON.parse(eventData.metadata);
660
+ } catch (e) {
661
+ metadata = { raw_metadata: eventData.metadata };
662
+ }
473
663
  }
474
- }
475
664
 
476
- // Determine progress type
477
- if (metadata.agent_processing_status) {
478
- const processingStatus =
479
- metadata.agent_processing_status;
480
- onEvent('progress', {
481
- ...eventData,
482
- processing_status: processingStatus,
483
- metadata_parsed: metadata,
484
- });
485
- } else {
486
- onEvent('progress', {
487
- ...eventData,
488
- metadata_parsed: metadata,
489
- });
665
+ // Determine progress type
666
+ if (metadata.agent_processing_status) {
667
+ const processingStatus =
668
+ metadata.agent_processing_status;
669
+ onEvent('progress', {
670
+ ...eventData,
671
+ processing_status: processingStatus,
672
+ metadata_parsed: metadata,
673
+ });
674
+ } else {
675
+ onEvent('progress', {
676
+ ...eventData,
677
+ metadata_parsed: metadata,
678
+ });
679
+ }
680
+ } else if (eventData.status === 'fulfilled') {
681
+ // Final response with complete data
682
+ onEvent('complete', eventData);
490
683
  }
491
- } else if (eventData.status === 'fulfilled') {
492
- // Final response with complete data
493
- onEvent('complete', eventData);
684
+ } else if (eventData.text && eventData.type === 'message') {
685
+ // This is streaming text data
686
+ onEvent('data', eventData);
687
+ } else if (
688
+ eventData.type === 'message' &&
689
+ eventData.images !== undefined
690
+ ) {
691
+ // Additional message metadata (images, files, etc.)
692
+ onEvent('metadata', eventData);
693
+ } else if (
694
+ eventData.type === 'message' &&
695
+ eventData.callbackMetadata
696
+ ) {
697
+ // Callback metadata with function details and execution plan
698
+ onEvent('callback', eventData);
699
+ } else if (
700
+ eventData.type === 'chat_created' ||
701
+ eventData.event === 'chat_created'
702
+ ) {
703
+ // Chat creation event
704
+ onEvent('chat_created', eventData);
705
+ } else {
706
+ // Generic event data
707
+ onEvent('unknown', eventData);
494
708
  }
495
- } else if (eventData.text && eventData.type === 'message') {
496
- // This is streaming text data
497
- onEvent('data', eventData);
498
- } else if (
499
- eventData.type === 'message' &&
500
- eventData.images !== undefined
501
- ) {
502
- // Additional message metadata (images, files, etc.)
503
- onEvent('metadata', eventData);
504
- } else if (
505
- eventData.type === 'message' &&
506
- eventData.callbackMetadata
507
- ) {
508
- // Callback metadata with function details and execution plan
509
- onEvent('callback', eventData);
510
- } else if (eventData.type === 'chat_created' || eventData.event === 'chat_created') {
511
- // Chat creation event
512
- onEvent('chat_created', eventData);
513
- } else {
514
- // Generic event data
515
- onEvent('unknown', eventData);
709
+ } catch (e) {
710
+ console.error(
711
+ `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
712
+ );
713
+ onEvent('error', {
714
+ error: 'json_decode_error',
715
+ raw_data: dataStr,
716
+ message: e.message,
717
+ });
516
718
  }
517
- } catch (e) {
518
- console.error(
519
- `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
520
- );
521
- onEvent("error", {
522
- error: "json_decode_error",
523
- raw_data: dataStr,
524
- message: e.message,
525
- });
526
719
  }
527
720
  }
528
- }
529
- });
721
+ });
530
722
 
531
- response.data.on("end", () => {
532
- resolve();
533
- });
723
+ response.data.on('end', () => {
724
+ resolve();
725
+ });
534
726
 
535
- response.data.on("error", (error) => {
536
- onEvent("error", {
537
- error: "stream_error",
727
+ response.data.on('error', (error) => {
728
+ onEvent('error', {
729
+ error: 'stream_error',
730
+ message: error.message,
731
+ });
732
+ reject(error);
733
+ });
734
+ })
735
+ .catch((error) => {
736
+ onEvent('error', {
737
+ error: 'request_error',
538
738
  message: error.message,
539
739
  });
540
740
  reject(error);
541
741
  });
542
- })
543
- .catch((error) => {
544
- onEvent("error", {
545
- error: "request_error",
546
- message: error.message,
547
- });
548
- reject(error);
549
- });
550
- });
742
+ });
743
+ }
551
744
  } catch (error) {
552
745
  console.error(`Error in sendMessageToAgentStream: ${error.message}`);
553
- onEvent("error", {
554
- error: "setup_error",
746
+ onEvent('error', {
747
+ error: 'setup_error',
555
748
  message: error.message,
556
749
  });
557
750
  throw error;
@@ -572,24 +765,24 @@ class ToothFairyAPI {
572
765
  });
573
766
 
574
767
  if (importType) {
575
- params.append("importType", importType);
768
+ params.append('importType', importType);
576
769
  }
577
770
 
578
771
  if (contentType) {
579
- params.append("contentType", contentType);
772
+ params.append('contentType', contentType);
580
773
  }
581
774
 
582
775
  const config = {
583
- method: "GET",
776
+ method: 'GET',
584
777
  url: `${this.baseUrl}/documents/requestPreSignedURL?${params.toString()}`,
585
778
  headers: this.headers,
586
779
  };
587
780
 
588
781
  if (this.verbose) {
589
- const chalk = require("chalk");
590
- console.error(chalk.dim("\n--- Upload URL Request Debug ---"));
782
+ const chalk = require('chalk');
783
+ console.error(chalk.dim('\n--- Upload URL Request Debug ---'));
591
784
  console.error(chalk.dim(`URL: ${config.url}`));
592
- console.error(chalk.dim("----------------------------\n"));
785
+ console.error(chalk.dim('----------------------------\n'));
593
786
  }
594
787
 
595
788
  try {
@@ -599,7 +792,7 @@ class ToothFairyAPI {
599
792
  if (error.response) {
600
793
  throw new Error(
601
794
  `HTTP ${error.response.status}: ${
602
- error.response.data.message || "Upload URL request failed"
795
+ error.response.data.message || 'Upload URL request failed'
603
796
  }`
604
797
  );
605
798
  }
@@ -659,14 +852,14 @@ class ToothFairyAPI {
659
852
  finalContentType
660
853
  );
661
854
  if (this.verbose) {
662
- const chalk = require("chalk");
663
- console.error(chalk.dim("\n--- File Upload URL response ---"));
855
+ const chalk = require('chalk');
856
+ console.error(chalk.dim('\n--- File Upload URL response ---'));
664
857
  console.error(chalk.dim(`Raw response: ${JSON.stringify(uploadData)}`));
665
858
  }
666
859
 
667
860
  // Parse the nested response structure
668
861
  let parsedUploadData;
669
- if (uploadData.body && typeof uploadData.body === "string") {
862
+ if (uploadData.body && typeof uploadData.body === 'string') {
670
863
  try {
671
864
  parsedUploadData = JSON.parse(uploadData.body);
672
865
  } catch (error) {
@@ -679,18 +872,18 @@ class ToothFairyAPI {
679
872
  }
680
873
 
681
874
  if (!parsedUploadData.uploadURL) {
682
- throw new Error("Failed to get upload URL from server");
875
+ throw new Error('Failed to get upload URL from server');
683
876
  }
684
877
 
685
878
  // Upload file to S3
686
879
  const fileData = fs.readFileSync(filePath);
687
880
 
688
881
  const uploadConfig = {
689
- method: "PUT",
882
+ method: 'PUT',
690
883
  url: parsedUploadData.uploadURL,
691
884
  data: fileData,
692
885
  headers: {
693
- "Content-Type": finalContentType,
886
+ 'Content-Type': finalContentType,
694
887
  },
695
888
  maxContentLength: Infinity,
696
889
  maxBodyLength: Infinity,
@@ -709,8 +902,8 @@ class ToothFairyAPI {
709
902
  };
710
903
 
711
904
  if (this.verbose) {
712
- const chalk = require("chalk");
713
- console.error(chalk.dim("\n--- File Upload Debug ---"));
905
+ const chalk = require('chalk');
906
+ console.error(chalk.dim('\n--- File Upload Debug ---'));
714
907
  console.error(chalk.dim(`File: ${filePath}`));
715
908
  console.error(chalk.dim(`Original filename: ${originalFilename}`));
716
909
  console.error(chalk.dim(`Sanitized filename: ${sanitizedFilename}`));
@@ -719,7 +912,7 @@ class ToothFairyAPI {
719
912
  console.error(chalk.dim(`Content type: ${finalContentType}`));
720
913
  console.error(chalk.dim(`Size: ${fileSizeInMB.toFixed(2)}MB`));
721
914
  console.error(chalk.dim(`Upload URL: ${parsedUploadData.uploadURL}`));
722
- console.error(chalk.dim("------------------------\n"));
915
+ console.error(chalk.dim('------------------------\n'));
723
916
  }
724
917
 
725
918
  try {
@@ -731,7 +924,7 @@ class ToothFairyAPI {
731
924
  // Remove S3 bucket prefix from filePath to get clean filename for download
732
925
  downloadFilename = parsedUploadData.filePath.replace(
733
926
  /^s3:\/\/[^/]+\//,
734
- ""
927
+ ''
735
928
  );
736
929
  }
737
930
 
@@ -749,7 +942,7 @@ class ToothFairyAPI {
749
942
  if (error.response) {
750
943
  throw new Error(
751
944
  `Upload failed: HTTP ${error.response.status}: ${
752
- error.response.data?.message || "File upload failed"
945
+ error.response.data?.message || 'File upload failed'
753
946
  }`
754
947
  );
755
948
  }
@@ -765,7 +958,7 @@ class ToothFairyAPI {
765
958
  * @param {string} context - Context for the download (default: "pdf")
766
959
  * @returns {Promise<Object>} - Download URL and metadata
767
960
  */
768
- async getDownloadUrl(filename, workspaceId, context = "pdf") {
961
+ async getDownloadUrl(filename, workspaceId, context = 'pdf') {
769
962
  const params = new URLSearchParams({
770
963
  filename: filename,
771
964
  context: context,
@@ -773,7 +966,7 @@ class ToothFairyAPI {
773
966
  });
774
967
 
775
968
  const config = {
776
- method: "GET",
969
+ method: 'GET',
777
970
  url: `${
778
971
  this.baseUrl
779
972
  }/documents/requestDownloadURLIncognito?${params.toString()}`,
@@ -781,10 +974,10 @@ class ToothFairyAPI {
781
974
  };
782
975
 
783
976
  if (this.verbose) {
784
- const chalk = require("chalk");
785
- console.error(chalk.dim("\n--- Download URL Request Debug ---"));
977
+ const chalk = require('chalk');
978
+ console.error(chalk.dim('\n--- Download URL Request Debug ---'));
786
979
  console.error(chalk.dim(`URL: ${config.url}`));
787
- console.error(chalk.dim("-----------------------------\n"));
980
+ console.error(chalk.dim('-----------------------------\n'));
788
981
  }
789
982
 
790
983
  try {
@@ -794,7 +987,7 @@ class ToothFairyAPI {
794
987
  if (error.response) {
795
988
  throw new Error(
796
989
  `HTTP ${error.response.status}: ${
797
- error.response.data.message || "Download URL request failed"
990
+ error.response.data.message || 'Download URL request failed'
798
991
  }`
799
992
  );
800
993
  }
@@ -816,7 +1009,7 @@ class ToothFairyAPI {
816
1009
  filename,
817
1010
  workspaceId,
818
1011
  outputPath,
819
- context = "pdf",
1012
+ context = 'pdf',
820
1013
  onProgress = null
821
1014
  ) {
822
1015
  // Get download URL
@@ -827,7 +1020,7 @@ class ToothFairyAPI {
827
1020
  );
828
1021
 
829
1022
  if (!downloadData.url) {
830
- throw new Error("Failed to get download URL from server");
1023
+ throw new Error('Failed to get download URL from server');
831
1024
  }
832
1025
 
833
1026
  // Ensure output directory exists
@@ -838,9 +1031,9 @@ class ToothFairyAPI {
838
1031
 
839
1032
  // Download file
840
1033
  const downloadConfig = {
841
- method: "GET",
1034
+ method: 'GET',
842
1035
  url: downloadData.url,
843
- responseType: "stream",
1036
+ responseType: 'stream',
844
1037
  onDownloadProgress: onProgress
845
1038
  ? (progressEvent) => {
846
1039
  if (progressEvent.total) {
@@ -858,11 +1051,11 @@ class ToothFairyAPI {
858
1051
  };
859
1052
 
860
1053
  if (this.verbose) {
861
- const chalk = require("chalk");
862
- console.error(chalk.dim("\n--- File Download Debug ---"));
1054
+ const chalk = require('chalk');
1055
+ console.error(chalk.dim('\n--- File Download Debug ---'));
863
1056
  console.error(chalk.dim(`Download URL: ${downloadData.url}`));
864
1057
  console.error(chalk.dim(`Output Path: ${outputPath}`));
865
- console.error(chalk.dim("---------------------------\n"));
1058
+ console.error(chalk.dim('---------------------------\n'));
866
1059
  }
867
1060
 
868
1061
  try {
@@ -873,7 +1066,7 @@ class ToothFairyAPI {
873
1066
  response.data.pipe(writer);
874
1067
 
875
1068
  return new Promise((resolve, reject) => {
876
- writer.on("finish", () => {
1069
+ writer.on('finish', () => {
877
1070
  const stats = fs.statSync(outputPath);
878
1071
  resolve({
879
1072
  success: true,
@@ -882,13 +1075,13 @@ class ToothFairyAPI {
882
1075
  sizeInMB: (stats.size / (1024 * 1024)).toFixed(2),
883
1076
  });
884
1077
  });
885
- writer.on("error", reject);
1078
+ writer.on('error', reject);
886
1079
  });
887
1080
  } catch (error) {
888
1081
  if (error.response) {
889
1082
  throw new Error(
890
1083
  `Download failed: HTTP ${error.response.status}: ${
891
- error.response.data?.message || "File download failed"
1084
+ error.response.data?.message || 'File download failed'
892
1085
  }`
893
1086
  );
894
1087
  }
@@ -904,11 +1097,11 @@ class ToothFairyAPI {
904
1097
  */
905
1098
  _sanitizeFilename(filename) {
906
1099
  // Remove non-alphanumeric characters except dots
907
- let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, "");
1100
+ let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, '');
908
1101
 
909
1102
  // Check length limit
910
1103
  if (sanitized.length > 100) {
911
- throw new Error("File name cannot be more than 100 characters.");
1104
+ throw new Error('File name cannot be more than 100 characters.');
912
1105
  }
913
1106
 
914
1107
  // Add timestamp prefix like frontend
@@ -927,20 +1120,20 @@ class ToothFairyAPI {
927
1120
  const ext = path.extname(filePath).toLowerCase().slice(1);
928
1121
 
929
1122
  // Image extensions
930
- const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "svg"];
1123
+ const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
931
1124
  // Video extensions
932
- const videoExts = ["mp4", "avi", "mov", "wmv", "flv", "webm"];
1125
+ const videoExts = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'];
933
1126
  // Audio extensions
934
- const audioExts = ["wav", "mp3", "aac", "ogg", "flac"];
1127
+ const audioExts = ['wav', 'mp3', 'aac', 'ogg', 'flac'];
935
1128
 
936
1129
  if (imageExts.includes(ext)) {
937
- return "imported-image";
1130
+ return 'imported-image';
938
1131
  } else if (videoExts.includes(ext)) {
939
- return "imported_video_files";
1132
+ return 'imported_video_files';
940
1133
  } else if (audioExts.includes(ext)) {
941
- return "imported_audio_files";
1134
+ return 'imported_audio_files';
942
1135
  } else {
943
- return "imported_doc_files";
1136
+ return 'imported_doc_files';
944
1137
  }
945
1138
  }
946
1139
 
@@ -954,38 +1147,38 @@ class ToothFairyAPI {
954
1147
  const ext = path.extname(filePath).toLowerCase().slice(1);
955
1148
 
956
1149
  const contentTypes = {
957
- txt: "text/plain",
958
- md: "text/markdown",
959
- html: "text/html",
960
- pdf: "application/pdf",
961
- json: "application/json",
962
- jsonl: "application/json",
963
- wav: "audio/wav",
964
- mp4: "video/mp4",
965
- png: "image/png",
966
- jpg: "image/jpeg",
967
- jpeg: "image/jpeg",
968
- csv: "text/csv",
969
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
970
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
971
- pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
972
- ppt: "application/vnd.ms-powerpoint",
973
- java: "text/plain",
974
- py: "text/plain",
975
- js: "text/plain",
976
- ts: "text/plain",
977
- tsx: "text/plain",
978
- jsx: "text/plain",
979
- yaml: "text/plain",
980
- yml: "text/plain",
981
- sql: "text/plain",
982
- sh: "text/plain",
983
- php: "text/plain",
984
- csharp: "text/plain",
985
- rb: "text/plain",
1150
+ txt: 'text/plain',
1151
+ md: 'text/markdown',
1152
+ html: 'text/html',
1153
+ pdf: 'application/pdf',
1154
+ json: 'application/json',
1155
+ jsonl: 'application/json',
1156
+ wav: 'audio/wav',
1157
+ mp4: 'video/mp4',
1158
+ png: 'image/png',
1159
+ jpg: 'image/jpeg',
1160
+ jpeg: 'image/jpeg',
1161
+ csv: 'text/csv',
1162
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1163
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1164
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
1165
+ ppt: 'application/vnd.ms-powerpoint',
1166
+ java: 'text/plain',
1167
+ py: 'text/plain',
1168
+ js: 'text/plain',
1169
+ ts: 'text/plain',
1170
+ tsx: 'text/plain',
1171
+ jsx: 'text/plain',
1172
+ yaml: 'text/plain',
1173
+ yml: 'text/plain',
1174
+ sql: 'text/plain',
1175
+ sh: 'text/plain',
1176
+ php: 'text/plain',
1177
+ csharp: 'text/plain',
1178
+ rb: 'text/plain',
986
1179
  };
987
1180
 
988
- return contentTypes[ext] || "application/octet-stream";
1181
+ return contentTypes[ext] || 'application/octet-stream';
989
1182
  }
990
1183
 
991
1184
  /**
@@ -996,18 +1189,18 @@ class ToothFairyAPI {
996
1189
  */
997
1190
  _getDocumentType(filePath) {
998
1191
  // Check if it's a URL
999
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
1000
- return "readComprehensionUrl";
1192
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1193
+ return 'readComprehensionUrl';
1001
1194
  }
1002
1195
 
1003
1196
  // Check if it's a PDF file
1004
1197
  const ext = path.extname(filePath).toLowerCase().slice(1);
1005
- if (ext === "pdf") {
1006
- return "readComprehensionPdf";
1198
+ if (ext === 'pdf') {
1199
+ return 'readComprehensionPdf';
1007
1200
  }
1008
1201
 
1009
1202
  // For all other file types (Excel, CSV, etc.)
1010
- return "readComprehensionFile";
1203
+ return 'readComprehensionFile';
1011
1204
  }
1012
1205
 
1013
1206
  /**
@@ -1026,9 +1219,9 @@ class ToothFairyAPI {
1026
1219
  filePath,
1027
1220
  userId,
1028
1221
  title = null,
1029
- folderId = "mrcRoot",
1222
+ folderId = 'mrcRoot',
1030
1223
  topics = [],
1031
- status = "published",
1224
+ status = 'published',
1032
1225
  scope = null
1033
1226
  ) {
1034
1227
  try {
@@ -1038,7 +1231,7 @@ class ToothFairyAPI {
1038
1231
  // Use filename as title if not provided
1039
1232
  let documentTitle = title;
1040
1233
  if (!documentTitle) {
1041
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
1234
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1042
1235
  documentTitle = filePath;
1043
1236
  } else {
1044
1237
  documentTitle = path.basename(filePath);
@@ -1047,7 +1240,7 @@ class ToothFairyAPI {
1047
1240
 
1048
1241
  // Determine external path based on file type
1049
1242
  let externalPath;
1050
- if (type === "readComprehensionUrl") {
1243
+ if (type === 'readComprehensionUrl') {
1051
1244
  externalPath = filePath; // Use URL directly
1052
1245
  } else {
1053
1246
  // For files, construct S3 path
@@ -1065,14 +1258,14 @@ class ToothFairyAPI {
1065
1258
  topics: topics,
1066
1259
  folderid: folderId,
1067
1260
  external_path: externalPath,
1068
- source: "api",
1261
+ source: 'api',
1069
1262
  status: status,
1070
1263
  ...(scope && { scope: scope }),
1071
1264
  },
1072
1265
  ],
1073
1266
  };
1074
1267
 
1075
- return await this._makeRequest("POST", "doc/create", documentData);
1268
+ return await this._makeRequest('POST', 'doc/create', documentData);
1076
1269
  } catch (error) {
1077
1270
  console.error(`Error creating document: ${error.message}`);
1078
1271
  throw error;
@@ -1103,7 +1296,7 @@ class ToothFairyAPI {
1103
1296
  },
1104
1297
  };
1105
1298
 
1106
- return await this._makeRequest("POST", "doc/update", updateData);
1299
+ return await this._makeRequest('POST', 'doc/update', updateData);
1107
1300
  } catch (error) {
1108
1301
  console.error(`Error updating document: ${error.message}`);
1109
1302
  throw error;
@@ -1118,7 +1311,7 @@ class ToothFairyAPI {
1118
1311
  */
1119
1312
  async deleteDocument(documentId) {
1120
1313
  try {
1121
- return await this._makeRequest("DELETE", `doc/delete/${documentId}`);
1314
+ return await this._makeRequest('DELETE', `doc/delete/${documentId}`);
1122
1315
  } catch (error) {
1123
1316
  console.error(`Error deleting document: ${error.message}`);
1124
1317
  throw error;
@@ -1133,7 +1326,7 @@ class ToothFairyAPI {
1133
1326
  */
1134
1327
  async getDocument(documentId) {
1135
1328
  try {
1136
- return await this._makeRequest("GET", `doc/get/${documentId}`);
1329
+ return await this._makeRequest('GET', `doc/get/${documentId}`);
1137
1330
  } catch (error) {
1138
1331
  console.error(`Error getting document: ${error.message}`);
1139
1332
  throw error;
@@ -1155,7 +1348,7 @@ class ToothFairyAPI {
1155
1348
  async createEntity(
1156
1349
  userId,
1157
1350
  label,
1158
- type = "topic",
1351
+ type = 'topic',
1159
1352
  description = null,
1160
1353
  emoji = null,
1161
1354
  parentEntity = null,
@@ -1173,7 +1366,7 @@ class ToothFairyAPI {
1173
1366
  ...(backgroundColor && { backgroundColor: backgroundColor }),
1174
1367
  };
1175
1368
 
1176
- return await this._makeRequest("POST", "entity/create", entityData);
1369
+ return await this._makeRequest('POST', 'entity/create', entityData);
1177
1370
  } catch (error) {
1178
1371
  console.error(`Error creating entity: ${error.message}`);
1179
1372
  throw error;
@@ -1200,7 +1393,7 @@ class ToothFairyAPI {
1200
1393
  ...fields,
1201
1394
  };
1202
1395
 
1203
- return await this._makeRequest("POST", "entity/update", updateData);
1396
+ return await this._makeRequest('POST', 'entity/update', updateData);
1204
1397
  } catch (error) {
1205
1398
  console.error(`Error updating entity: ${error.message}`);
1206
1399
  throw error;
@@ -1215,7 +1408,7 @@ class ToothFairyAPI {
1215
1408
  */
1216
1409
  async deleteEntity(entityId) {
1217
1410
  try {
1218
- return await this._makeRequest("DELETE", `entity/delete/${entityId}`);
1411
+ return await this._makeRequest('DELETE', `entity/delete/${entityId}`);
1219
1412
  } catch (error) {
1220
1413
  console.error(`Error deleting entity: ${error.message}`);
1221
1414
  throw error;
@@ -1230,7 +1423,7 @@ class ToothFairyAPI {
1230
1423
  */
1231
1424
  async getEntity(entityId) {
1232
1425
  try {
1233
- return await this._makeRequest("GET", `entity/get/${entityId}`);
1426
+ return await this._makeRequest('GET', `entity/get/${entityId}`);
1234
1427
  } catch (error) {
1235
1428
  console.error(`Error getting entity: ${error.message}`);
1236
1429
  throw error;
@@ -1249,7 +1442,7 @@ class ToothFairyAPI {
1249
1442
  if (limit) {
1250
1443
  params.limit = limit;
1251
1444
  }
1252
- return await this._makeRequest("GET", "entity/list", params);
1445
+ return await this._makeRequest('GET', 'entity/list', params);
1253
1446
  } catch (error) {
1254
1447
  console.error(`Error listing entities: ${error.message}`);
1255
1448
  throw error;
@@ -1287,7 +1480,7 @@ class ToothFairyAPI {
1287
1480
  ...(status && { status: status }),
1288
1481
  ...(parent && { parent: parent }),
1289
1482
  };
1290
- return await this._makeRequest("POST", "folder/create", folderData);
1483
+ return await this._makeRequest('POST', 'folder/create', folderData);
1291
1484
  } catch (error) {
1292
1485
  console.error(`Error creating folder: ${error.message}`);
1293
1486
  throw error;
@@ -1322,7 +1515,7 @@ class ToothFairyAPI {
1322
1515
  ...(status && { status: status }),
1323
1516
  ...(parent !== null && { parent: parent }),
1324
1517
  };
1325
- return await this._makeRequest("POST", "folder/update", updateData);
1518
+ return await this._makeRequest('POST', 'folder/update', updateData);
1326
1519
  } catch (error) {
1327
1520
  console.error(`Error updating folder: ${error.message}`);
1328
1521
  throw error;
@@ -1337,7 +1530,7 @@ class ToothFairyAPI {
1337
1530
  */
1338
1531
  async deleteFolder(folderId) {
1339
1532
  try {
1340
- return await this._makeRequest("DELETE", `folder/delete/${folderId}`);
1533
+ return await this._makeRequest('DELETE', `folder/delete/${folderId}`);
1341
1534
  } catch (error) {
1342
1535
  console.error(`Error deleting folder: ${error.message}`);
1343
1536
  throw error;
@@ -1352,7 +1545,7 @@ class ToothFairyAPI {
1352
1545
  */
1353
1546
  async getFolder(folderId) {
1354
1547
  try {
1355
- return await this._makeRequest("GET", `folder/get/${folderId}`);
1548
+ return await this._makeRequest('GET', `folder/get/${folderId}`);
1356
1549
  } catch (error) {
1357
1550
  console.error(`Error getting folder: ${error.message}`);
1358
1551
  throw error;
@@ -1379,7 +1572,7 @@ class ToothFairyAPI {
1379
1572
  if (offset) {
1380
1573
  params.offset = offset;
1381
1574
  }
1382
- return await this._makeRequest("GET", "folder/list", params);
1575
+ return await this._makeRequest('GET', 'folder/list', params);
1383
1576
  } catch (error) {
1384
1577
  console.error(`Error listing folders: ${error.message}`);
1385
1578
  throw error;
@@ -1429,7 +1622,7 @@ class ToothFairyAPI {
1429
1622
  ...(promptPlaceholder && { promptPlaceholder: promptPlaceholder }),
1430
1623
  ...(availableToAgents && { availableToAgents: availableToAgents }),
1431
1624
  };
1432
- return await this._makeRequest("POST", "prompt/create", promptData);
1625
+ return await this._makeRequest('POST', 'prompt/create', promptData);
1433
1626
  } catch (error) {
1434
1627
  console.error(`Error creating prompt: ${error.message}`);
1435
1628
  throw error;
@@ -1482,7 +1675,7 @@ class ToothFairyAPI {
1482
1675
  availableToAgents: availableToAgents,
1483
1676
  }),
1484
1677
  };
1485
- return await this._makeRequest("POST", "prompt/update", updateData);
1678
+ return await this._makeRequest('POST', 'prompt/update', updateData);
1486
1679
  } catch (error) {
1487
1680
  console.error(`Error updating prompt: ${error.message}`);
1488
1681
  throw error;
@@ -1497,7 +1690,7 @@ class ToothFairyAPI {
1497
1690
  */
1498
1691
  async deletePrompt(promptId) {
1499
1692
  try {
1500
- return await this._makeRequest("DELETE", `prompt/delete/${promptId}`);
1693
+ return await this._makeRequest('DELETE', `prompt/delete/${promptId}`);
1501
1694
  } catch (error) {
1502
1695
  console.error(`Error deleting prompt: ${error.message}`);
1503
1696
  throw error;
@@ -1512,7 +1705,7 @@ class ToothFairyAPI {
1512
1705
  */
1513
1706
  async getPrompt(promptId) {
1514
1707
  try {
1515
- return await this._makeRequest("GET", `prompt/get/${promptId}`);
1708
+ return await this._makeRequest('GET', `prompt/get/${promptId}`);
1516
1709
  } catch (error) {
1517
1710
  console.error(`Error getting prompt: ${error.message}`);
1518
1711
  throw error;
@@ -1539,7 +1732,7 @@ class ToothFairyAPI {
1539
1732
  if (offset) {
1540
1733
  params.offset = offset;
1541
1734
  }
1542
- return await this._makeRequest("GET", "prompt/list", params);
1735
+ return await this._makeRequest('GET', 'prompt/list', params);
1543
1736
  } catch (error) {
1544
1737
  console.error(`Error listing prompts: ${error.message}`);
1545
1738
  throw error;
@@ -1564,18 +1757,18 @@ class ToothFairyAPI {
1564
1757
  };
1565
1758
 
1566
1759
  const config = {
1567
- method: "POST",
1760
+ method: 'POST',
1568
1761
  url: `${this.baseUrl}/media/audio_generation`,
1569
1762
  headers: {
1570
- "Content-Type": "application/json",
1571
- "x-api-key": this.headers["x-api-key"],
1763
+ 'Content-Type': 'application/json',
1764
+ 'x-api-key': this.headers['x-api-key'],
1572
1765
  },
1573
1766
  data: speechData,
1574
1767
  };
1575
1768
 
1576
1769
  if (this.verbose) {
1577
- const chalk = require("chalk");
1578
- console.error(chalk.dim("\n--- Speech Generation Request Debug ---"));
1770
+ const chalk = require('chalk');
1771
+ console.error(chalk.dim('\n--- Speech Generation Request Debug ---'));
1579
1772
  console.error(chalk.dim(`Method: ${config.method}`));
1580
1773
  console.error(chalk.dim(`URL: ${config.url}`));
1581
1774
  console.error(
@@ -1584,28 +1777,28 @@ class ToothFairyAPI {
1584
1777
  console.error(
1585
1778
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1586
1779
  );
1587
- console.error(chalk.dim("--------------------------------------\n"));
1780
+ console.error(chalk.dim('--------------------------------------\n'));
1588
1781
  }
1589
1782
 
1590
1783
  const response = await axios(config);
1591
1784
 
1592
1785
  if (this.verbose) {
1593
- const chalk = require("chalk");
1594
- console.error(chalk.dim("\n--- Speech Generation Response Debug ---"));
1786
+ const chalk = require('chalk');
1787
+ console.error(chalk.dim('\n--- Speech Generation Response Debug ---'));
1595
1788
  console.error(
1596
1789
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1597
1790
  );
1598
1791
  console.error(
1599
1792
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1600
1793
  );
1601
- console.error(chalk.dim("---------------------------------------\n"));
1794
+ console.error(chalk.dim('---------------------------------------\n'));
1602
1795
  }
1603
1796
 
1604
1797
  return response.data;
1605
1798
  } catch (error) {
1606
1799
  if (this.verbose) {
1607
- const chalk = require("chalk");
1608
- console.error(chalk.red("\n--- Speech Generation Error Debug ---"));
1800
+ const chalk = require('chalk');
1801
+ console.error(chalk.red('\n--- Speech Generation Error Debug ---'));
1609
1802
  console.error(chalk.red(`Error: ${error.message}`));
1610
1803
  if (error.response) {
1611
1804
  console.error(
@@ -1619,7 +1812,7 @@ class ToothFairyAPI {
1619
1812
  )
1620
1813
  );
1621
1814
  }
1622
- console.error(chalk.red("------------------------------------\n"));
1815
+ console.error(chalk.red('------------------------------------\n'));
1623
1816
  }
1624
1817
  console.error(`Error generating speech: ${error.message}`);
1625
1818
  throw error;
@@ -1642,18 +1835,18 @@ class ToothFairyAPI {
1642
1835
  };
1643
1836
 
1644
1837
  const config = {
1645
- method: "POST",
1838
+ method: 'POST',
1646
1839
  url: `${this.baseUrl}/media/audio`,
1647
1840
  headers: {
1648
- "Content-Type": "application/json",
1649
- "x-api-key": this.headers["x-api-key"],
1841
+ 'Content-Type': 'application/json',
1842
+ 'x-api-key': this.headers['x-api-key'],
1650
1843
  },
1651
1844
  data: audioData,
1652
1845
  };
1653
1846
 
1654
1847
  if (this.verbose) {
1655
- const chalk = require("chalk");
1656
- console.error(chalk.dim("\n--- Audio Processing Request Debug ---"));
1848
+ const chalk = require('chalk');
1849
+ console.error(chalk.dim('\n--- Audio Processing Request Debug ---'));
1657
1850
  console.error(chalk.dim(`Method: ${config.method}`));
1658
1851
  console.error(chalk.dim(`URL: ${config.url}`));
1659
1852
  console.error(
@@ -1662,28 +1855,28 @@ class ToothFairyAPI {
1662
1855
  console.error(
1663
1856
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1664
1857
  );
1665
- console.error(chalk.dim("------------------------------------\n"));
1858
+ console.error(chalk.dim('------------------------------------\n'));
1666
1859
  }
1667
1860
 
1668
1861
  const response = await axios(config);
1669
1862
 
1670
1863
  if (this.verbose) {
1671
- const chalk = require("chalk");
1672
- console.error(chalk.dim("\n--- Audio Processing Response Debug ---"));
1864
+ const chalk = require('chalk');
1865
+ console.error(chalk.dim('\n--- Audio Processing Response Debug ---'));
1673
1866
  console.error(
1674
1867
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1675
1868
  );
1676
1869
  console.error(
1677
1870
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1678
1871
  );
1679
- console.error(chalk.dim("-------------------------------------\n"));
1872
+ console.error(chalk.dim('-------------------------------------\n'));
1680
1873
  }
1681
1874
 
1682
1875
  return response.data;
1683
1876
  } catch (error) {
1684
1877
  if (this.verbose) {
1685
- const chalk = require("chalk");
1686
- console.error(chalk.red("\n--- Audio Processing Error Debug ---"));
1878
+ const chalk = require('chalk');
1879
+ console.error(chalk.red('\n--- Audio Processing Error Debug ---'));
1687
1880
  console.error(chalk.red(`Error: ${error.message}`));
1688
1881
  if (error.response) {
1689
1882
  console.error(
@@ -1697,7 +1890,7 @@ class ToothFairyAPI {
1697
1890
  )
1698
1891
  );
1699
1892
  }
1700
- console.error(chalk.red("----------------------------------\n"));
1893
+ console.error(chalk.red('----------------------------------\n'));
1701
1894
  }
1702
1895
  console.error(`Error processing audio: ${error.message}`);
1703
1896
  throw error;
@@ -1724,16 +1917,16 @@ class ToothFairyAPI {
1724
1917
  try {
1725
1918
  // Validate attachment limits
1726
1919
  if (attachments.images && attachments.images.length > 1) {
1727
- throw new Error("Maximum 1 image attachment allowed");
1920
+ throw new Error('Maximum 1 image attachment allowed');
1728
1921
  }
1729
1922
  if (attachments.audios && attachments.audios.length > 1) {
1730
- throw new Error("Maximum 1 audio attachment allowed");
1923
+ throw new Error('Maximum 1 audio attachment allowed');
1731
1924
  }
1732
1925
  if (attachments.videos && attachments.videos.length > 1) {
1733
- throw new Error("Maximum 1 video attachment allowed");
1926
+ throw new Error('Maximum 1 video attachment allowed');
1734
1927
  }
1735
1928
  if (attachments.files && attachments.files.length > 5) {
1736
- throw new Error("Maximum 5 file attachments allowed");
1929
+ throw new Error('Maximum 5 file attachments allowed');
1737
1930
  }
1738
1931
 
1739
1932
  // Process images
@@ -1789,10 +1982,10 @@ class ToothFairyAPI {
1789
1982
  }
1790
1983
 
1791
1984
  if (this.verbose) {
1792
- const chalk = require("chalk");
1793
- console.error(chalk.dim("\n--- Processed Attachments ---"));
1985
+ const chalk = require('chalk');
1986
+ console.error(chalk.dim('\n--- Processed Attachments ---'));
1794
1987
  console.error(chalk.dim(`Result: ${JSON.stringify(result, null, 2)}`));
1795
- console.error(chalk.dim("-----------------------------\n"));
1988
+ console.error(chalk.dim('-----------------------------\n'));
1796
1989
  }
1797
1990
 
1798
1991
  return result;