@toothfairyai/cli 1.1.0 → 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 -377
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,204 +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 {
511
- // Generic event data
512
- 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
+ });
513
718
  }
514
- } catch (e) {
515
- console.error(
516
- `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
517
- );
518
- onEvent("error", {
519
- error: "json_decode_error",
520
- raw_data: dataStr,
521
- message: e.message,
522
- });
523
719
  }
524
720
  }
525
- }
526
- });
721
+ });
527
722
 
528
- response.data.on("end", () => {
529
- resolve();
530
- });
723
+ response.data.on('end', () => {
724
+ resolve();
725
+ });
531
726
 
532
- response.data.on("error", (error) => {
533
- onEvent("error", {
534
- 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',
535
738
  message: error.message,
536
739
  });
537
740
  reject(error);
538
741
  });
539
- })
540
- .catch((error) => {
541
- onEvent("error", {
542
- error: "request_error",
543
- message: error.message,
544
- });
545
- reject(error);
546
- });
547
- });
742
+ });
743
+ }
548
744
  } catch (error) {
549
745
  console.error(`Error in sendMessageToAgentStream: ${error.message}`);
550
- onEvent("error", {
551
- error: "setup_error",
746
+ onEvent('error', {
747
+ error: 'setup_error',
552
748
  message: error.message,
553
749
  });
554
750
  throw error;
@@ -569,24 +765,24 @@ class ToothFairyAPI {
569
765
  });
570
766
 
571
767
  if (importType) {
572
- params.append("importType", importType);
768
+ params.append('importType', importType);
573
769
  }
574
770
 
575
771
  if (contentType) {
576
- params.append("contentType", contentType);
772
+ params.append('contentType', contentType);
577
773
  }
578
774
 
579
775
  const config = {
580
- method: "GET",
776
+ method: 'GET',
581
777
  url: `${this.baseUrl}/documents/requestPreSignedURL?${params.toString()}`,
582
778
  headers: this.headers,
583
779
  };
584
780
 
585
781
  if (this.verbose) {
586
- const chalk = require("chalk");
587
- console.error(chalk.dim("\n--- Upload URL Request Debug ---"));
782
+ const chalk = require('chalk');
783
+ console.error(chalk.dim('\n--- Upload URL Request Debug ---'));
588
784
  console.error(chalk.dim(`URL: ${config.url}`));
589
- console.error(chalk.dim("----------------------------\n"));
785
+ console.error(chalk.dim('----------------------------\n'));
590
786
  }
591
787
 
592
788
  try {
@@ -596,7 +792,7 @@ class ToothFairyAPI {
596
792
  if (error.response) {
597
793
  throw new Error(
598
794
  `HTTP ${error.response.status}: ${
599
- error.response.data.message || "Upload URL request failed"
795
+ error.response.data.message || 'Upload URL request failed'
600
796
  }`
601
797
  );
602
798
  }
@@ -656,14 +852,14 @@ class ToothFairyAPI {
656
852
  finalContentType
657
853
  );
658
854
  if (this.verbose) {
659
- const chalk = require("chalk");
660
- console.error(chalk.dim("\n--- File Upload URL response ---"));
855
+ const chalk = require('chalk');
856
+ console.error(chalk.dim('\n--- File Upload URL response ---'));
661
857
  console.error(chalk.dim(`Raw response: ${JSON.stringify(uploadData)}`));
662
858
  }
663
859
 
664
860
  // Parse the nested response structure
665
861
  let parsedUploadData;
666
- if (uploadData.body && typeof uploadData.body === "string") {
862
+ if (uploadData.body && typeof uploadData.body === 'string') {
667
863
  try {
668
864
  parsedUploadData = JSON.parse(uploadData.body);
669
865
  } catch (error) {
@@ -676,18 +872,18 @@ class ToothFairyAPI {
676
872
  }
677
873
 
678
874
  if (!parsedUploadData.uploadURL) {
679
- throw new Error("Failed to get upload URL from server");
875
+ throw new Error('Failed to get upload URL from server');
680
876
  }
681
877
 
682
878
  // Upload file to S3
683
879
  const fileData = fs.readFileSync(filePath);
684
880
 
685
881
  const uploadConfig = {
686
- method: "PUT",
882
+ method: 'PUT',
687
883
  url: parsedUploadData.uploadURL,
688
884
  data: fileData,
689
885
  headers: {
690
- "Content-Type": finalContentType,
886
+ 'Content-Type': finalContentType,
691
887
  },
692
888
  maxContentLength: Infinity,
693
889
  maxBodyLength: Infinity,
@@ -706,8 +902,8 @@ class ToothFairyAPI {
706
902
  };
707
903
 
708
904
  if (this.verbose) {
709
- const chalk = require("chalk");
710
- console.error(chalk.dim("\n--- File Upload Debug ---"));
905
+ const chalk = require('chalk');
906
+ console.error(chalk.dim('\n--- File Upload Debug ---'));
711
907
  console.error(chalk.dim(`File: ${filePath}`));
712
908
  console.error(chalk.dim(`Original filename: ${originalFilename}`));
713
909
  console.error(chalk.dim(`Sanitized filename: ${sanitizedFilename}`));
@@ -716,7 +912,7 @@ class ToothFairyAPI {
716
912
  console.error(chalk.dim(`Content type: ${finalContentType}`));
717
913
  console.error(chalk.dim(`Size: ${fileSizeInMB.toFixed(2)}MB`));
718
914
  console.error(chalk.dim(`Upload URL: ${parsedUploadData.uploadURL}`));
719
- console.error(chalk.dim("------------------------\n"));
915
+ console.error(chalk.dim('------------------------\n'));
720
916
  }
721
917
 
722
918
  try {
@@ -728,7 +924,7 @@ class ToothFairyAPI {
728
924
  // Remove S3 bucket prefix from filePath to get clean filename for download
729
925
  downloadFilename = parsedUploadData.filePath.replace(
730
926
  /^s3:\/\/[^/]+\//,
731
- ""
927
+ ''
732
928
  );
733
929
  }
734
930
 
@@ -746,7 +942,7 @@ class ToothFairyAPI {
746
942
  if (error.response) {
747
943
  throw new Error(
748
944
  `Upload failed: HTTP ${error.response.status}: ${
749
- error.response.data?.message || "File upload failed"
945
+ error.response.data?.message || 'File upload failed'
750
946
  }`
751
947
  );
752
948
  }
@@ -762,7 +958,7 @@ class ToothFairyAPI {
762
958
  * @param {string} context - Context for the download (default: "pdf")
763
959
  * @returns {Promise<Object>} - Download URL and metadata
764
960
  */
765
- async getDownloadUrl(filename, workspaceId, context = "pdf") {
961
+ async getDownloadUrl(filename, workspaceId, context = 'pdf') {
766
962
  const params = new URLSearchParams({
767
963
  filename: filename,
768
964
  context: context,
@@ -770,7 +966,7 @@ class ToothFairyAPI {
770
966
  });
771
967
 
772
968
  const config = {
773
- method: "GET",
969
+ method: 'GET',
774
970
  url: `${
775
971
  this.baseUrl
776
972
  }/documents/requestDownloadURLIncognito?${params.toString()}`,
@@ -778,10 +974,10 @@ class ToothFairyAPI {
778
974
  };
779
975
 
780
976
  if (this.verbose) {
781
- const chalk = require("chalk");
782
- console.error(chalk.dim("\n--- Download URL Request Debug ---"));
977
+ const chalk = require('chalk');
978
+ console.error(chalk.dim('\n--- Download URL Request Debug ---'));
783
979
  console.error(chalk.dim(`URL: ${config.url}`));
784
- console.error(chalk.dim("-----------------------------\n"));
980
+ console.error(chalk.dim('-----------------------------\n'));
785
981
  }
786
982
 
787
983
  try {
@@ -791,7 +987,7 @@ class ToothFairyAPI {
791
987
  if (error.response) {
792
988
  throw new Error(
793
989
  `HTTP ${error.response.status}: ${
794
- error.response.data.message || "Download URL request failed"
990
+ error.response.data.message || 'Download URL request failed'
795
991
  }`
796
992
  );
797
993
  }
@@ -813,7 +1009,7 @@ class ToothFairyAPI {
813
1009
  filename,
814
1010
  workspaceId,
815
1011
  outputPath,
816
- context = "pdf",
1012
+ context = 'pdf',
817
1013
  onProgress = null
818
1014
  ) {
819
1015
  // Get download URL
@@ -824,7 +1020,7 @@ class ToothFairyAPI {
824
1020
  );
825
1021
 
826
1022
  if (!downloadData.url) {
827
- throw new Error("Failed to get download URL from server");
1023
+ throw new Error('Failed to get download URL from server');
828
1024
  }
829
1025
 
830
1026
  // Ensure output directory exists
@@ -835,9 +1031,9 @@ class ToothFairyAPI {
835
1031
 
836
1032
  // Download file
837
1033
  const downloadConfig = {
838
- method: "GET",
1034
+ method: 'GET',
839
1035
  url: downloadData.url,
840
- responseType: "stream",
1036
+ responseType: 'stream',
841
1037
  onDownloadProgress: onProgress
842
1038
  ? (progressEvent) => {
843
1039
  if (progressEvent.total) {
@@ -855,11 +1051,11 @@ class ToothFairyAPI {
855
1051
  };
856
1052
 
857
1053
  if (this.verbose) {
858
- const chalk = require("chalk");
859
- console.error(chalk.dim("\n--- File Download Debug ---"));
1054
+ const chalk = require('chalk');
1055
+ console.error(chalk.dim('\n--- File Download Debug ---'));
860
1056
  console.error(chalk.dim(`Download URL: ${downloadData.url}`));
861
1057
  console.error(chalk.dim(`Output Path: ${outputPath}`));
862
- console.error(chalk.dim("---------------------------\n"));
1058
+ console.error(chalk.dim('---------------------------\n'));
863
1059
  }
864
1060
 
865
1061
  try {
@@ -870,7 +1066,7 @@ class ToothFairyAPI {
870
1066
  response.data.pipe(writer);
871
1067
 
872
1068
  return new Promise((resolve, reject) => {
873
- writer.on("finish", () => {
1069
+ writer.on('finish', () => {
874
1070
  const stats = fs.statSync(outputPath);
875
1071
  resolve({
876
1072
  success: true,
@@ -879,13 +1075,13 @@ class ToothFairyAPI {
879
1075
  sizeInMB: (stats.size / (1024 * 1024)).toFixed(2),
880
1076
  });
881
1077
  });
882
- writer.on("error", reject);
1078
+ writer.on('error', reject);
883
1079
  });
884
1080
  } catch (error) {
885
1081
  if (error.response) {
886
1082
  throw new Error(
887
1083
  `Download failed: HTTP ${error.response.status}: ${
888
- error.response.data?.message || "File download failed"
1084
+ error.response.data?.message || 'File download failed'
889
1085
  }`
890
1086
  );
891
1087
  }
@@ -901,11 +1097,11 @@ class ToothFairyAPI {
901
1097
  */
902
1098
  _sanitizeFilename(filename) {
903
1099
  // Remove non-alphanumeric characters except dots
904
- let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, "");
1100
+ let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, '');
905
1101
 
906
1102
  // Check length limit
907
1103
  if (sanitized.length > 100) {
908
- throw new Error("File name cannot be more than 100 characters.");
1104
+ throw new Error('File name cannot be more than 100 characters.');
909
1105
  }
910
1106
 
911
1107
  // Add timestamp prefix like frontend
@@ -924,20 +1120,20 @@ class ToothFairyAPI {
924
1120
  const ext = path.extname(filePath).toLowerCase().slice(1);
925
1121
 
926
1122
  // Image extensions
927
- const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "svg"];
1123
+ const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
928
1124
  // Video extensions
929
- const videoExts = ["mp4", "avi", "mov", "wmv", "flv", "webm"];
1125
+ const videoExts = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'];
930
1126
  // Audio extensions
931
- const audioExts = ["wav", "mp3", "aac", "ogg", "flac"];
1127
+ const audioExts = ['wav', 'mp3', 'aac', 'ogg', 'flac'];
932
1128
 
933
1129
  if (imageExts.includes(ext)) {
934
- return "imported-image";
1130
+ return 'imported-image';
935
1131
  } else if (videoExts.includes(ext)) {
936
- return "imported_video_files";
1132
+ return 'imported_video_files';
937
1133
  } else if (audioExts.includes(ext)) {
938
- return "imported_audio_files";
1134
+ return 'imported_audio_files';
939
1135
  } else {
940
- return "imported_doc_files";
1136
+ return 'imported_doc_files';
941
1137
  }
942
1138
  }
943
1139
 
@@ -951,38 +1147,38 @@ class ToothFairyAPI {
951
1147
  const ext = path.extname(filePath).toLowerCase().slice(1);
952
1148
 
953
1149
  const contentTypes = {
954
- txt: "text/plain",
955
- md: "text/markdown",
956
- html: "text/html",
957
- pdf: "application/pdf",
958
- json: "application/json",
959
- jsonl: "application/json",
960
- wav: "audio/wav",
961
- mp4: "video/mp4",
962
- png: "image/png",
963
- jpg: "image/jpeg",
964
- jpeg: "image/jpeg",
965
- csv: "text/csv",
966
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
967
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
968
- pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
969
- ppt: "application/vnd.ms-powerpoint",
970
- java: "text/plain",
971
- py: "text/plain",
972
- js: "text/plain",
973
- ts: "text/plain",
974
- tsx: "text/plain",
975
- jsx: "text/plain",
976
- yaml: "text/plain",
977
- yml: "text/plain",
978
- sql: "text/plain",
979
- sh: "text/plain",
980
- php: "text/plain",
981
- csharp: "text/plain",
982
- 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',
983
1179
  };
984
1180
 
985
- return contentTypes[ext] || "application/octet-stream";
1181
+ return contentTypes[ext] || 'application/octet-stream';
986
1182
  }
987
1183
 
988
1184
  /**
@@ -993,18 +1189,18 @@ class ToothFairyAPI {
993
1189
  */
994
1190
  _getDocumentType(filePath) {
995
1191
  // Check if it's a URL
996
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
997
- return "readComprehensionUrl";
1192
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1193
+ return 'readComprehensionUrl';
998
1194
  }
999
1195
 
1000
1196
  // Check if it's a PDF file
1001
1197
  const ext = path.extname(filePath).toLowerCase().slice(1);
1002
- if (ext === "pdf") {
1003
- return "readComprehensionPdf";
1198
+ if (ext === 'pdf') {
1199
+ return 'readComprehensionPdf';
1004
1200
  }
1005
1201
 
1006
1202
  // For all other file types (Excel, CSV, etc.)
1007
- return "readComprehensionFile";
1203
+ return 'readComprehensionFile';
1008
1204
  }
1009
1205
 
1010
1206
  /**
@@ -1023,9 +1219,9 @@ class ToothFairyAPI {
1023
1219
  filePath,
1024
1220
  userId,
1025
1221
  title = null,
1026
- folderId = "mrcRoot",
1222
+ folderId = 'mrcRoot',
1027
1223
  topics = [],
1028
- status = "published",
1224
+ status = 'published',
1029
1225
  scope = null
1030
1226
  ) {
1031
1227
  try {
@@ -1035,7 +1231,7 @@ class ToothFairyAPI {
1035
1231
  // Use filename as title if not provided
1036
1232
  let documentTitle = title;
1037
1233
  if (!documentTitle) {
1038
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
1234
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1039
1235
  documentTitle = filePath;
1040
1236
  } else {
1041
1237
  documentTitle = path.basename(filePath);
@@ -1044,7 +1240,7 @@ class ToothFairyAPI {
1044
1240
 
1045
1241
  // Determine external path based on file type
1046
1242
  let externalPath;
1047
- if (type === "readComprehensionUrl") {
1243
+ if (type === 'readComprehensionUrl') {
1048
1244
  externalPath = filePath; // Use URL directly
1049
1245
  } else {
1050
1246
  // For files, construct S3 path
@@ -1062,14 +1258,14 @@ class ToothFairyAPI {
1062
1258
  topics: topics,
1063
1259
  folderid: folderId,
1064
1260
  external_path: externalPath,
1065
- source: "api",
1261
+ source: 'api',
1066
1262
  status: status,
1067
1263
  ...(scope && { scope: scope }),
1068
1264
  },
1069
1265
  ],
1070
1266
  };
1071
1267
 
1072
- return await this._makeRequest("POST", "doc/create", documentData);
1268
+ return await this._makeRequest('POST', 'doc/create', documentData);
1073
1269
  } catch (error) {
1074
1270
  console.error(`Error creating document: ${error.message}`);
1075
1271
  throw error;
@@ -1100,7 +1296,7 @@ class ToothFairyAPI {
1100
1296
  },
1101
1297
  };
1102
1298
 
1103
- return await this._makeRequest("POST", "doc/update", updateData);
1299
+ return await this._makeRequest('POST', 'doc/update', updateData);
1104
1300
  } catch (error) {
1105
1301
  console.error(`Error updating document: ${error.message}`);
1106
1302
  throw error;
@@ -1115,7 +1311,7 @@ class ToothFairyAPI {
1115
1311
  */
1116
1312
  async deleteDocument(documentId) {
1117
1313
  try {
1118
- return await this._makeRequest("DELETE", `doc/delete/${documentId}`);
1314
+ return await this._makeRequest('DELETE', `doc/delete/${documentId}`);
1119
1315
  } catch (error) {
1120
1316
  console.error(`Error deleting document: ${error.message}`);
1121
1317
  throw error;
@@ -1130,7 +1326,7 @@ class ToothFairyAPI {
1130
1326
  */
1131
1327
  async getDocument(documentId) {
1132
1328
  try {
1133
- return await this._makeRequest("GET", `doc/get/${documentId}`);
1329
+ return await this._makeRequest('GET', `doc/get/${documentId}`);
1134
1330
  } catch (error) {
1135
1331
  console.error(`Error getting document: ${error.message}`);
1136
1332
  throw error;
@@ -1152,7 +1348,7 @@ class ToothFairyAPI {
1152
1348
  async createEntity(
1153
1349
  userId,
1154
1350
  label,
1155
- type = "topic",
1351
+ type = 'topic',
1156
1352
  description = null,
1157
1353
  emoji = null,
1158
1354
  parentEntity = null,
@@ -1170,7 +1366,7 @@ class ToothFairyAPI {
1170
1366
  ...(backgroundColor && { backgroundColor: backgroundColor }),
1171
1367
  };
1172
1368
 
1173
- return await this._makeRequest("POST", "entity/create", entityData);
1369
+ return await this._makeRequest('POST', 'entity/create', entityData);
1174
1370
  } catch (error) {
1175
1371
  console.error(`Error creating entity: ${error.message}`);
1176
1372
  throw error;
@@ -1197,7 +1393,7 @@ class ToothFairyAPI {
1197
1393
  ...fields,
1198
1394
  };
1199
1395
 
1200
- return await this._makeRequest("POST", "entity/update", updateData);
1396
+ return await this._makeRequest('POST', 'entity/update', updateData);
1201
1397
  } catch (error) {
1202
1398
  console.error(`Error updating entity: ${error.message}`);
1203
1399
  throw error;
@@ -1212,7 +1408,7 @@ class ToothFairyAPI {
1212
1408
  */
1213
1409
  async deleteEntity(entityId) {
1214
1410
  try {
1215
- return await this._makeRequest("DELETE", `entity/delete/${entityId}`);
1411
+ return await this._makeRequest('DELETE', `entity/delete/${entityId}`);
1216
1412
  } catch (error) {
1217
1413
  console.error(`Error deleting entity: ${error.message}`);
1218
1414
  throw error;
@@ -1227,7 +1423,7 @@ class ToothFairyAPI {
1227
1423
  */
1228
1424
  async getEntity(entityId) {
1229
1425
  try {
1230
- return await this._makeRequest("GET", `entity/get/${entityId}`);
1426
+ return await this._makeRequest('GET', `entity/get/${entityId}`);
1231
1427
  } catch (error) {
1232
1428
  console.error(`Error getting entity: ${error.message}`);
1233
1429
  throw error;
@@ -1246,7 +1442,7 @@ class ToothFairyAPI {
1246
1442
  if (limit) {
1247
1443
  params.limit = limit;
1248
1444
  }
1249
- return await this._makeRequest("GET", "entity/list", params);
1445
+ return await this._makeRequest('GET', 'entity/list', params);
1250
1446
  } catch (error) {
1251
1447
  console.error(`Error listing entities: ${error.message}`);
1252
1448
  throw error;
@@ -1284,7 +1480,7 @@ class ToothFairyAPI {
1284
1480
  ...(status && { status: status }),
1285
1481
  ...(parent && { parent: parent }),
1286
1482
  };
1287
- return await this._makeRequest("POST", "folder/create", folderData);
1483
+ return await this._makeRequest('POST', 'folder/create', folderData);
1288
1484
  } catch (error) {
1289
1485
  console.error(`Error creating folder: ${error.message}`);
1290
1486
  throw error;
@@ -1319,7 +1515,7 @@ class ToothFairyAPI {
1319
1515
  ...(status && { status: status }),
1320
1516
  ...(parent !== null && { parent: parent }),
1321
1517
  };
1322
- return await this._makeRequest("POST", "folder/update", updateData);
1518
+ return await this._makeRequest('POST', 'folder/update', updateData);
1323
1519
  } catch (error) {
1324
1520
  console.error(`Error updating folder: ${error.message}`);
1325
1521
  throw error;
@@ -1334,7 +1530,7 @@ class ToothFairyAPI {
1334
1530
  */
1335
1531
  async deleteFolder(folderId) {
1336
1532
  try {
1337
- return await this._makeRequest("DELETE", `folder/delete/${folderId}`);
1533
+ return await this._makeRequest('DELETE', `folder/delete/${folderId}`);
1338
1534
  } catch (error) {
1339
1535
  console.error(`Error deleting folder: ${error.message}`);
1340
1536
  throw error;
@@ -1349,7 +1545,7 @@ class ToothFairyAPI {
1349
1545
  */
1350
1546
  async getFolder(folderId) {
1351
1547
  try {
1352
- return await this._makeRequest("GET", `folder/get/${folderId}`);
1548
+ return await this._makeRequest('GET', `folder/get/${folderId}`);
1353
1549
  } catch (error) {
1354
1550
  console.error(`Error getting folder: ${error.message}`);
1355
1551
  throw error;
@@ -1376,7 +1572,7 @@ class ToothFairyAPI {
1376
1572
  if (offset) {
1377
1573
  params.offset = offset;
1378
1574
  }
1379
- return await this._makeRequest("GET", "folder/list", params);
1575
+ return await this._makeRequest('GET', 'folder/list', params);
1380
1576
  } catch (error) {
1381
1577
  console.error(`Error listing folders: ${error.message}`);
1382
1578
  throw error;
@@ -1426,7 +1622,7 @@ class ToothFairyAPI {
1426
1622
  ...(promptPlaceholder && { promptPlaceholder: promptPlaceholder }),
1427
1623
  ...(availableToAgents && { availableToAgents: availableToAgents }),
1428
1624
  };
1429
- return await this._makeRequest("POST", "prompt/create", promptData);
1625
+ return await this._makeRequest('POST', 'prompt/create', promptData);
1430
1626
  } catch (error) {
1431
1627
  console.error(`Error creating prompt: ${error.message}`);
1432
1628
  throw error;
@@ -1479,7 +1675,7 @@ class ToothFairyAPI {
1479
1675
  availableToAgents: availableToAgents,
1480
1676
  }),
1481
1677
  };
1482
- return await this._makeRequest("POST", "prompt/update", updateData);
1678
+ return await this._makeRequest('POST', 'prompt/update', updateData);
1483
1679
  } catch (error) {
1484
1680
  console.error(`Error updating prompt: ${error.message}`);
1485
1681
  throw error;
@@ -1494,7 +1690,7 @@ class ToothFairyAPI {
1494
1690
  */
1495
1691
  async deletePrompt(promptId) {
1496
1692
  try {
1497
- return await this._makeRequest("DELETE", `prompt/delete/${promptId}`);
1693
+ return await this._makeRequest('DELETE', `prompt/delete/${promptId}`);
1498
1694
  } catch (error) {
1499
1695
  console.error(`Error deleting prompt: ${error.message}`);
1500
1696
  throw error;
@@ -1509,7 +1705,7 @@ class ToothFairyAPI {
1509
1705
  */
1510
1706
  async getPrompt(promptId) {
1511
1707
  try {
1512
- return await this._makeRequest("GET", `prompt/get/${promptId}`);
1708
+ return await this._makeRequest('GET', `prompt/get/${promptId}`);
1513
1709
  } catch (error) {
1514
1710
  console.error(`Error getting prompt: ${error.message}`);
1515
1711
  throw error;
@@ -1536,7 +1732,7 @@ class ToothFairyAPI {
1536
1732
  if (offset) {
1537
1733
  params.offset = offset;
1538
1734
  }
1539
- return await this._makeRequest("GET", "prompt/list", params);
1735
+ return await this._makeRequest('GET', 'prompt/list', params);
1540
1736
  } catch (error) {
1541
1737
  console.error(`Error listing prompts: ${error.message}`);
1542
1738
  throw error;
@@ -1561,18 +1757,18 @@ class ToothFairyAPI {
1561
1757
  };
1562
1758
 
1563
1759
  const config = {
1564
- method: "POST",
1760
+ method: 'POST',
1565
1761
  url: `${this.baseUrl}/media/audio_generation`,
1566
1762
  headers: {
1567
- "Content-Type": "application/json",
1568
- "x-api-key": this.headers["x-api-key"],
1763
+ 'Content-Type': 'application/json',
1764
+ 'x-api-key': this.headers['x-api-key'],
1569
1765
  },
1570
1766
  data: speechData,
1571
1767
  };
1572
1768
 
1573
1769
  if (this.verbose) {
1574
- const chalk = require("chalk");
1575
- console.error(chalk.dim("\n--- Speech Generation Request Debug ---"));
1770
+ const chalk = require('chalk');
1771
+ console.error(chalk.dim('\n--- Speech Generation Request Debug ---'));
1576
1772
  console.error(chalk.dim(`Method: ${config.method}`));
1577
1773
  console.error(chalk.dim(`URL: ${config.url}`));
1578
1774
  console.error(
@@ -1581,28 +1777,28 @@ class ToothFairyAPI {
1581
1777
  console.error(
1582
1778
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1583
1779
  );
1584
- console.error(chalk.dim("--------------------------------------\n"));
1780
+ console.error(chalk.dim('--------------------------------------\n'));
1585
1781
  }
1586
1782
 
1587
1783
  const response = await axios(config);
1588
1784
 
1589
1785
  if (this.verbose) {
1590
- const chalk = require("chalk");
1591
- console.error(chalk.dim("\n--- Speech Generation Response Debug ---"));
1786
+ const chalk = require('chalk');
1787
+ console.error(chalk.dim('\n--- Speech Generation Response Debug ---'));
1592
1788
  console.error(
1593
1789
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1594
1790
  );
1595
1791
  console.error(
1596
1792
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1597
1793
  );
1598
- console.error(chalk.dim("---------------------------------------\n"));
1794
+ console.error(chalk.dim('---------------------------------------\n'));
1599
1795
  }
1600
1796
 
1601
1797
  return response.data;
1602
1798
  } catch (error) {
1603
1799
  if (this.verbose) {
1604
- const chalk = require("chalk");
1605
- console.error(chalk.red("\n--- Speech Generation Error Debug ---"));
1800
+ const chalk = require('chalk');
1801
+ console.error(chalk.red('\n--- Speech Generation Error Debug ---'));
1606
1802
  console.error(chalk.red(`Error: ${error.message}`));
1607
1803
  if (error.response) {
1608
1804
  console.error(
@@ -1616,7 +1812,7 @@ class ToothFairyAPI {
1616
1812
  )
1617
1813
  );
1618
1814
  }
1619
- console.error(chalk.red("------------------------------------\n"));
1815
+ console.error(chalk.red('------------------------------------\n'));
1620
1816
  }
1621
1817
  console.error(`Error generating speech: ${error.message}`);
1622
1818
  throw error;
@@ -1639,18 +1835,18 @@ class ToothFairyAPI {
1639
1835
  };
1640
1836
 
1641
1837
  const config = {
1642
- method: "POST",
1838
+ method: 'POST',
1643
1839
  url: `${this.baseUrl}/media/audio`,
1644
1840
  headers: {
1645
- "Content-Type": "application/json",
1646
- "x-api-key": this.headers["x-api-key"],
1841
+ 'Content-Type': 'application/json',
1842
+ 'x-api-key': this.headers['x-api-key'],
1647
1843
  },
1648
1844
  data: audioData,
1649
1845
  };
1650
1846
 
1651
1847
  if (this.verbose) {
1652
- const chalk = require("chalk");
1653
- console.error(chalk.dim("\n--- Audio Processing Request Debug ---"));
1848
+ const chalk = require('chalk');
1849
+ console.error(chalk.dim('\n--- Audio Processing Request Debug ---'));
1654
1850
  console.error(chalk.dim(`Method: ${config.method}`));
1655
1851
  console.error(chalk.dim(`URL: ${config.url}`));
1656
1852
  console.error(
@@ -1659,28 +1855,28 @@ class ToothFairyAPI {
1659
1855
  console.error(
1660
1856
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1661
1857
  );
1662
- console.error(chalk.dim("------------------------------------\n"));
1858
+ console.error(chalk.dim('------------------------------------\n'));
1663
1859
  }
1664
1860
 
1665
1861
  const response = await axios(config);
1666
1862
 
1667
1863
  if (this.verbose) {
1668
- const chalk = require("chalk");
1669
- console.error(chalk.dim("\n--- Audio Processing Response Debug ---"));
1864
+ const chalk = require('chalk');
1865
+ console.error(chalk.dim('\n--- Audio Processing Response Debug ---'));
1670
1866
  console.error(
1671
1867
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1672
1868
  );
1673
1869
  console.error(
1674
1870
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1675
1871
  );
1676
- console.error(chalk.dim("-------------------------------------\n"));
1872
+ console.error(chalk.dim('-------------------------------------\n'));
1677
1873
  }
1678
1874
 
1679
1875
  return response.data;
1680
1876
  } catch (error) {
1681
1877
  if (this.verbose) {
1682
- const chalk = require("chalk");
1683
- console.error(chalk.red("\n--- Audio Processing Error Debug ---"));
1878
+ const chalk = require('chalk');
1879
+ console.error(chalk.red('\n--- Audio Processing Error Debug ---'));
1684
1880
  console.error(chalk.red(`Error: ${error.message}`));
1685
1881
  if (error.response) {
1686
1882
  console.error(
@@ -1694,7 +1890,7 @@ class ToothFairyAPI {
1694
1890
  )
1695
1891
  );
1696
1892
  }
1697
- console.error(chalk.red("----------------------------------\n"));
1893
+ console.error(chalk.red('----------------------------------\n'));
1698
1894
  }
1699
1895
  console.error(`Error processing audio: ${error.message}`);
1700
1896
  throw error;
@@ -1721,16 +1917,16 @@ class ToothFairyAPI {
1721
1917
  try {
1722
1918
  // Validate attachment limits
1723
1919
  if (attachments.images && attachments.images.length > 1) {
1724
- throw new Error("Maximum 1 image attachment allowed");
1920
+ throw new Error('Maximum 1 image attachment allowed');
1725
1921
  }
1726
1922
  if (attachments.audios && attachments.audios.length > 1) {
1727
- throw new Error("Maximum 1 audio attachment allowed");
1923
+ throw new Error('Maximum 1 audio attachment allowed');
1728
1924
  }
1729
1925
  if (attachments.videos && attachments.videos.length > 1) {
1730
- throw new Error("Maximum 1 video attachment allowed");
1926
+ throw new Error('Maximum 1 video attachment allowed');
1731
1927
  }
1732
1928
  if (attachments.files && attachments.files.length > 5) {
1733
- throw new Error("Maximum 5 file attachments allowed");
1929
+ throw new Error('Maximum 5 file attachments allowed');
1734
1930
  }
1735
1931
 
1736
1932
  // Process images
@@ -1786,10 +1982,10 @@ class ToothFairyAPI {
1786
1982
  }
1787
1983
 
1788
1984
  if (this.verbose) {
1789
- const chalk = require("chalk");
1790
- console.error(chalk.dim("\n--- Processed Attachments ---"));
1985
+ const chalk = require('chalk');
1986
+ console.error(chalk.dim('\n--- Processed Attachments ---'));
1791
1987
  console.error(chalk.dim(`Result: ${JSON.stringify(result, null, 2)}`));
1792
- console.error(chalk.dim("-----------------------------\n"));
1988
+ console.error(chalk.dim('-----------------------------\n'));
1793
1989
  }
1794
1990
 
1795
1991
  return result;