@toothfairyai/cli 1.1.1 → 1.1.3

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 +576 -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,96 @@ 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
- };
209
+ if (chatId) {
210
+ // Use existing chat - let REST endpoint handle message creation
211
+ const messageData = {
212
+ text: message,
213
+ role: 'user',
214
+ userID: 'CLI',
215
+ };
223
216
 
224
- const createdChat = await this.createChat(chatData);
225
- // console.log(`Chat created: ${createdChat.id}`);
217
+ // Only include attachment fields if they have content
218
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
219
+ messageData.images = processedAttachments.images;
220
+ }
221
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
222
+ messageData.audios = processedAttachments.audios;
223
+ }
224
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
225
+ messageData.videos = processedAttachments.videos;
226
+ }
227
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
228
+ messageData.files = processedAttachments.files;
229
+ }
226
230
 
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}`);;
231
+ const agentData = {
232
+ chatid: chatId,
233
+ messages: [messageData],
234
+ agentid: agentId,
235
+ };
236
+
237
+ const agentResponse = await this.getAgentResponse(agentData);
238
+
239
+ return {
240
+ chatId: agentResponse.chatId || chatId,
241
+ messageId: agentResponse.messageId || 'auto-generated',
242
+ agentResponse: agentResponse,
243
+ };
244
+ } else {
245
+ // No chatId provided - let API create chat automatically
246
+ // Prepare message data for automatic chat creation
247
+ const messageData = {
248
+ text: message,
249
+ role: 'user',
250
+ userID: customerId,
251
+ };
236
252
 
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');
253
+ // Only include attachment fields if they have content
254
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
255
+ messageData.images = processedAttachments.images;
256
+ }
257
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
258
+ messageData.audios = processedAttachments.audios;
259
+ }
260
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
261
+ messageData.videos = processedAttachments.videos;
262
+ }
263
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
264
+ messageData.files = processedAttachments.files;
265
+ }
250
266
 
251
- return {
252
- chatId: createdChat.id,
253
- messageId: createdMessage.id,
254
- agentResponse: agentResponse,
255
- };
267
+ // Send agent request without chatid - API will create chat and message automatically
268
+ const agentData = {
269
+ // No chatid - let API create the chat
270
+ messages: [messageData],
271
+ agentid: agentId,
272
+ // Include chat creation data since we're not pre-creating
273
+ phoneNumber: phoneNumber,
274
+ customerId: customerId,
275
+ providerId: providerId,
276
+ customerInfo: customerInfo,
277
+ };
278
+
279
+ const agentResponse = await this.getAgentResponse(agentData);
280
+
281
+ return {
282
+ chatId: agentResponse.chatId || 'auto-generated',
283
+ messageId: agentResponse.messageId || 'auto-generated',
284
+ agentResponse: agentResponse,
285
+ };
286
+ }
256
287
  } catch (error) {
257
288
  console.error(`Error in sendMessageToAgent: ${error.message}`);
258
289
  throw error;
@@ -261,7 +292,7 @@ class ToothFairyAPI {
261
292
 
262
293
  async searchDocuments(text, topK = 10, metadata = null) {
263
294
  if (topK < 1 || topK > 50) {
264
- throw new Error("topK must be between 1 and 50");
295
+ throw new Error('topK must be between 1 and 50');
265
296
  }
266
297
 
267
298
  const searchData = {
@@ -274,7 +305,7 @@ class ToothFairyAPI {
274
305
  }
275
306
 
276
307
  const config = {
277
- method: "POST",
308
+ method: 'POST',
278
309
  url: `${this.aiUrl}/searcher`,
279
310
  headers: this.headers,
280
311
  data: { workspaceid: this.workspaceId, ...searchData },
@@ -287,7 +318,7 @@ class ToothFairyAPI {
287
318
  if (error.response) {
288
319
  throw new Error(
289
320
  `HTTP ${error.response.status}: ${
290
- error.response.data.message || "Search request failed"
321
+ error.response.data.message || 'Search request failed'
291
322
  }`
292
323
  );
293
324
  }
@@ -329,7 +360,7 @@ class ToothFairyAPI {
329
360
  * - 'sse_event': All raw SSE events (only when showProgress=true)
330
361
  *
331
362
  * The onEvent callback receives: (eventType, eventData) => {}
332
- *
363
+ *
333
364
  * When showProgress is true, all SSE events will be emitted to the onEvent callback
334
365
  * with the 'sse_event' type, providing access to all raw events from the streaming
335
366
  * endpoint, similar to the CLI's --show-progress flag behavior.
@@ -343,7 +374,8 @@ class ToothFairyAPI {
343
374
  customerInfo = {},
344
375
  onEvent,
345
376
  attachments = {},
346
- showProgress = false
377
+ showProgress = false,
378
+ chatId = null
347
379
  ) {
348
380
  try {
349
381
  // Use defaults for optional parameters
@@ -351,207 +383,374 @@ class ToothFairyAPI {
351
383
  customerId ||
352
384
  `cli-user-${
353
385
  Math.abs(
354
- message.split("").reduce((a, b) => {
386
+ message.split('').reduce((a, b) => {
355
387
  a = (a << 5) - a + b.charCodeAt(0);
356
388
  return a & a;
357
389
  }, 0)
358
390
  ) % 10000
359
391
  }`;
360
- phoneNumber = phoneNumber || "+1234567890";
361
- providerId = providerId || "default-sms-provider";
392
+ phoneNumber = phoneNumber || '+1234567890';
393
+ providerId = providerId || 'default-sms-provider';
362
394
 
363
395
  // Process file attachments if provided
364
396
  const processedAttachments = await this._processAttachments(attachments);
365
397
 
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
- };
398
+ if (chatId) {
399
+ // Use existing chat - let SSE endpoint handle message creation
400
+ if (this.verbose) {
401
+ console.debug(`Using existing chat: ${chatId}`);
402
+ }
382
403
 
383
- const createdChat = await this.createChat(chatData);
384
- if (this.verbose) {
385
- console.debug(`Chat created: ${createdChat.id}`);
386
- }
404
+ const messageData = {
405
+ text: message,
406
+ role: 'user',
407
+ userID: 'CLI',
408
+ };
387
409
 
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
- }
410
+ // Only include attachment fields if they have content
411
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
412
+ messageData.images = processedAttachments.images;
413
+ }
414
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
415
+ messageData.audios = processedAttachments.audios;
416
+ }
417
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
418
+ messageData.videos = processedAttachments.videos;
419
+ }
420
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
421
+ messageData.files = processedAttachments.files;
422
+ }
400
423
 
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
- };
424
+ // Prepare agent data for streaming with existing chat
425
+ const agentData = {
426
+ workspaceid: this.workspaceId,
427
+ chatid: chatId,
428
+ messages: [messageData],
429
+ agentid: agentId,
430
+ };
413
431
 
414
- // Stream the agent response using the dedicated streaming URL
415
- const streamUrl = `${this.aiStreamUrl}/agent`; // Using streaming URL for /agent endpoint
432
+ // Stream the agent response using the dedicated streaming URL
433
+ const streamUrl = `${this.aiStreamUrl}/agent`;
434
+
435
+ return new Promise((resolve, reject) => {
436
+ // For POST requests with EventSource, we need to use a different approach
437
+ // EventSource doesn't support POST requests directly, so we'll use axios with streaming
438
+ const config = {
439
+ method: 'POST',
440
+ url: streamUrl,
441
+ headers: {
442
+ ...this.headers,
443
+ Accept: 'text/event-stream',
444
+ 'Cache-Control': 'no-cache',
445
+ },
446
+ data: agentData,
447
+ responseType: 'stream',
448
+ timeout: 300000, // 5 minute timeout
449
+ };
450
+
451
+ axios(config)
452
+ .then((response) => {
453
+ let buffer = '';
454
+
455
+ response.data.on('data', (chunk) => {
456
+ buffer += chunk.toString();
457
+
458
+ // Process complete lines
459
+ const lines = buffer.split('\n');
460
+ buffer = lines.pop() || ''; // Keep the incomplete line in buffer
461
+
462
+ for (const line of lines) {
463
+ if (line.trim() === '') continue;
464
+
465
+ if (line.startsWith('data: ')) {
466
+ const dataStr = line.slice(6); // Remove 'data: ' prefix
467
+
468
+ try {
469
+ const eventData = JSON.parse(dataStr);
470
+
471
+ // If showProgress is enabled, emit all events
472
+ if (showProgress) {
473
+ // Emit all events with their raw structure
474
+ onEvent('sse_event', eventData);
475
+ }
416
476
 
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
- };
477
+ // Standard event processing (always executed for backward compatibility)
478
+ if (eventData.status) {
479
+ if (eventData.status === 'connected') {
480
+ onEvent('status', eventData);
481
+ } else if (eventData.status === 'complete') {
482
+ onEvent('status', eventData);
483
+ } else if (eventData.status === 'inProgress') {
484
+ // Parse metadata to understand what's happening
485
+ let metadata = {};
486
+ if (eventData.metadata) {
487
+ try {
488
+ metadata = JSON.parse(eventData.metadata);
489
+ } catch (e) {
490
+ metadata = { raw_metadata: eventData.metadata };
491
+ }
492
+ }
432
493
 
433
- axios(config)
434
- .then((response) => {
435
- let buffer = "";
494
+ // Determine progress type
495
+ if (metadata.agent_processing_status) {
496
+ const processingStatus =
497
+ metadata.agent_processing_status;
498
+ onEvent('progress', {
499
+ ...eventData,
500
+ processing_status: processingStatus,
501
+ metadata_parsed: metadata,
502
+ });
503
+ } else {
504
+ onEvent('progress', {
505
+ ...eventData,
506
+ metadata_parsed: metadata,
507
+ });
508
+ }
509
+ } else if (eventData.status === 'fulfilled') {
510
+ // Final response with complete data
511
+ onEvent('complete', eventData);
512
+ }
513
+ } else if (eventData.text && eventData.type === 'message') {
514
+ // This is streaming text data
515
+ onEvent('data', eventData);
516
+ } else if (
517
+ eventData.type === 'message' &&
518
+ eventData.images !== undefined
519
+ ) {
520
+ // Additional message metadata (images, files, etc.)
521
+ onEvent('metadata', eventData);
522
+ } else if (
523
+ eventData.type === 'message' &&
524
+ eventData.callbackMetadata
525
+ ) {
526
+ // Callback metadata with function details and execution plan
527
+ onEvent('callback', eventData);
528
+ } else if (eventData.type === 'chat_created' || eventData.event === 'chat_created') {
529
+ // Chat creation event
530
+ onEvent('chat_created', eventData);
531
+ } else {
532
+ // Generic event data
533
+ onEvent('unknown', eventData);
534
+ }
535
+ } catch (e) {
536
+ console.error(
537
+ `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
538
+ );
539
+ onEvent('error', {
540
+ error: 'json_decode_error',
541
+ raw_data: dataStr,
542
+ message: e.message,
543
+ });
544
+ }
545
+ }
546
+ }
547
+ });
436
548
 
437
- response.data.on("data", (chunk) => {
438
- buffer += chunk.toString();
549
+ response.data.on('end', () => {
550
+ resolve();
551
+ });
439
552
 
440
- // Process complete lines
441
- const lines = buffer.split("\n");
442
- buffer = lines.pop() || ""; // Keep the incomplete line in buffer
553
+ response.data.on('error', (error) => {
554
+ onEvent('error', {
555
+ error: 'stream_error',
556
+ message: error.message,
557
+ });
558
+ reject(error);
559
+ });
560
+ })
561
+ .catch((error) => {
562
+ onEvent('error', {
563
+ error: 'request_error',
564
+ message: error.message,
565
+ });
566
+ reject(error);
567
+ });
568
+ });
569
+ } else {
570
+ // No chatId provided - let streaming API create chat automatically
571
+ if (this.verbose) {
572
+ console.debug('No chatId provided - letting API create chat automatically');
573
+ }
443
574
 
444
- for (const line of lines) {
445
- if (line.trim() === "") continue;
575
+ // Prepare message data for automatic chat creation
576
+ const messageData = {
577
+ text: message,
578
+ role: 'user',
579
+ userID: customerId,
580
+ };
446
581
 
447
- if (line.startsWith("data: ")) {
448
- const dataStr = line.slice(6); // Remove 'data: ' prefix
582
+ // Only include attachment fields if they have content
583
+ if (processedAttachments.images && processedAttachments.images.length > 0) {
584
+ messageData.images = processedAttachments.images;
585
+ }
586
+ if (processedAttachments.audios && processedAttachments.audios.length > 0) {
587
+ messageData.audios = processedAttachments.audios;
588
+ }
589
+ if (processedAttachments.videos && processedAttachments.videos.length > 0) {
590
+ messageData.videos = processedAttachments.videos;
591
+ }
592
+ if (processedAttachments.files && processedAttachments.files.length > 0) {
593
+ messageData.files = processedAttachments.files;
594
+ }
449
595
 
450
- try {
451
- const eventData = JSON.parse(dataStr);
596
+ // Send agent request without chatid - API will create chat and message automatically
597
+ const agentData = {
598
+ workspaceid: this.workspaceId,
599
+ // No chatid - let API create the chat
600
+ messages: [messageData],
601
+ agentid: agentId,
602
+ // Include chat creation data since we're not pre-creating
603
+ phoneNumber: phoneNumber,
604
+ customerId: customerId,
605
+ providerId: providerId,
606
+ customerInfo: customerInfo,
607
+ };
452
608
 
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
- }
609
+ // Stream the agent response using the dedicated streaming URL
610
+ const streamUrl = `${this.aiStreamUrl}/agent`;
611
+
612
+ return new Promise((resolve, reject) => {
613
+ // For POST requests with EventSource, we need to use a different approach
614
+ // EventSource doesn't support POST requests directly, so we'll use axios with streaming
615
+ const config = {
616
+ method: 'POST',
617
+ url: streamUrl,
618
+ headers: {
619
+ ...this.headers,
620
+ Accept: 'text/event-stream',
621
+ 'Cache-Control': 'no-cache',
622
+ },
623
+ data: agentData,
624
+ responseType: 'stream',
625
+ timeout: 300000, // 5 minute timeout
626
+ };
627
+
628
+ axios(config)
629
+ .then((response) => {
630
+ let buffer = '';
631
+
632
+ response.data.on('data', (chunk) => {
633
+ buffer += chunk.toString();
634
+
635
+ // Process complete lines
636
+ const lines = buffer.split('\n');
637
+ buffer = lines.pop() || ''; // Keep the incomplete line in buffer
638
+
639
+ for (const line of lines) {
640
+ if (line.trim() === '') continue;
641
+
642
+ if (line.startsWith('data: ')) {
643
+ const dataStr = line.slice(6); // Remove 'data: ' prefix
644
+
645
+ try {
646
+ const eventData = JSON.parse(dataStr);
647
+
648
+ // If showProgress is enabled, emit all events
649
+ if (showProgress) {
650
+ // Emit all events with their raw structure
651
+ onEvent('sse_event', eventData);
652
+ }
458
653
 
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 };
654
+ // Standard event processing (always executed for backward compatibility)
655
+ if (eventData.status) {
656
+ if (eventData.status === 'connected') {
657
+ onEvent('status', eventData);
658
+ } else if (eventData.status === 'complete') {
659
+ onEvent('status', eventData);
660
+ } else if (eventData.status === 'inProgress') {
661
+ // Parse metadata to understand what's happening
662
+ let metadata = {};
663
+ if (eventData.metadata) {
664
+ try {
665
+ metadata = JSON.parse(eventData.metadata);
666
+ } catch (e) {
667
+ metadata = { raw_metadata: eventData.metadata };
668
+ }
473
669
  }
474
- }
475
670
 
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
- });
671
+ // Determine progress type
672
+ if (metadata.agent_processing_status) {
673
+ const processingStatus =
674
+ metadata.agent_processing_status;
675
+ onEvent('progress', {
676
+ ...eventData,
677
+ processing_status: processingStatus,
678
+ metadata_parsed: metadata,
679
+ });
680
+ } else {
681
+ onEvent('progress', {
682
+ ...eventData,
683
+ metadata_parsed: metadata,
684
+ });
685
+ }
686
+ } else if (eventData.status === 'fulfilled') {
687
+ // Final response with complete data
688
+ onEvent('complete', eventData);
490
689
  }
491
- } else if (eventData.status === 'fulfilled') {
492
- // Final response with complete data
493
- onEvent('complete', eventData);
690
+ } else if (eventData.text && eventData.type === 'message') {
691
+ // This is streaming text data
692
+ onEvent('data', eventData);
693
+ } else if (
694
+ eventData.type === 'message' &&
695
+ eventData.images !== undefined
696
+ ) {
697
+ // Additional message metadata (images, files, etc.)
698
+ onEvent('metadata', eventData);
699
+ } else if (
700
+ eventData.type === 'message' &&
701
+ eventData.callbackMetadata
702
+ ) {
703
+ // Callback metadata with function details and execution plan
704
+ onEvent('callback', eventData);
705
+ } else if (
706
+ eventData.type === 'chat_created' ||
707
+ eventData.event === 'chat_created'
708
+ ) {
709
+ // Chat creation event
710
+ onEvent('chat_created', eventData);
711
+ } else {
712
+ // Generic event data
713
+ onEvent('unknown', eventData);
494
714
  }
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);
715
+ } catch (e) {
716
+ console.error(
717
+ `Failed to parse SSE data: ${dataStr}, error: ${e.message}`
718
+ );
719
+ onEvent('error', {
720
+ error: 'json_decode_error',
721
+ raw_data: dataStr,
722
+ message: e.message,
723
+ });
516
724
  }
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
725
  }
527
726
  }
528
- }
529
- });
727
+ });
530
728
 
531
- response.data.on("end", () => {
532
- resolve();
533
- });
729
+ response.data.on('end', () => {
730
+ resolve();
731
+ });
534
732
 
535
- response.data.on("error", (error) => {
536
- onEvent("error", {
537
- error: "stream_error",
733
+ response.data.on('error', (error) => {
734
+ onEvent('error', {
735
+ error: 'stream_error',
736
+ message: error.message,
737
+ });
738
+ reject(error);
739
+ });
740
+ })
741
+ .catch((error) => {
742
+ onEvent('error', {
743
+ error: 'request_error',
538
744
  message: error.message,
539
745
  });
540
746
  reject(error);
541
747
  });
542
- })
543
- .catch((error) => {
544
- onEvent("error", {
545
- error: "request_error",
546
- message: error.message,
547
- });
548
- reject(error);
549
- });
550
- });
748
+ });
749
+ }
551
750
  } catch (error) {
552
751
  console.error(`Error in sendMessageToAgentStream: ${error.message}`);
553
- onEvent("error", {
554
- error: "setup_error",
752
+ onEvent('error', {
753
+ error: 'setup_error',
555
754
  message: error.message,
556
755
  });
557
756
  throw error;
@@ -572,24 +771,24 @@ class ToothFairyAPI {
572
771
  });
573
772
 
574
773
  if (importType) {
575
- params.append("importType", importType);
774
+ params.append('importType', importType);
576
775
  }
577
776
 
578
777
  if (contentType) {
579
- params.append("contentType", contentType);
778
+ params.append('contentType', contentType);
580
779
  }
581
780
 
582
781
  const config = {
583
- method: "GET",
782
+ method: 'GET',
584
783
  url: `${this.baseUrl}/documents/requestPreSignedURL?${params.toString()}`,
585
784
  headers: this.headers,
586
785
  };
587
786
 
588
787
  if (this.verbose) {
589
- const chalk = require("chalk");
590
- console.error(chalk.dim("\n--- Upload URL Request Debug ---"));
788
+ const chalk = require('chalk');
789
+ console.error(chalk.dim('\n--- Upload URL Request Debug ---'));
591
790
  console.error(chalk.dim(`URL: ${config.url}`));
592
- console.error(chalk.dim("----------------------------\n"));
791
+ console.error(chalk.dim('----------------------------\n'));
593
792
  }
594
793
 
595
794
  try {
@@ -599,7 +798,7 @@ class ToothFairyAPI {
599
798
  if (error.response) {
600
799
  throw new Error(
601
800
  `HTTP ${error.response.status}: ${
602
- error.response.data.message || "Upload URL request failed"
801
+ error.response.data.message || 'Upload URL request failed'
603
802
  }`
604
803
  );
605
804
  }
@@ -659,14 +858,14 @@ class ToothFairyAPI {
659
858
  finalContentType
660
859
  );
661
860
  if (this.verbose) {
662
- const chalk = require("chalk");
663
- console.error(chalk.dim("\n--- File Upload URL response ---"));
861
+ const chalk = require('chalk');
862
+ console.error(chalk.dim('\n--- File Upload URL response ---'));
664
863
  console.error(chalk.dim(`Raw response: ${JSON.stringify(uploadData)}`));
665
864
  }
666
865
 
667
866
  // Parse the nested response structure
668
867
  let parsedUploadData;
669
- if (uploadData.body && typeof uploadData.body === "string") {
868
+ if (uploadData.body && typeof uploadData.body === 'string') {
670
869
  try {
671
870
  parsedUploadData = JSON.parse(uploadData.body);
672
871
  } catch (error) {
@@ -679,18 +878,18 @@ class ToothFairyAPI {
679
878
  }
680
879
 
681
880
  if (!parsedUploadData.uploadURL) {
682
- throw new Error("Failed to get upload URL from server");
881
+ throw new Error('Failed to get upload URL from server');
683
882
  }
684
883
 
685
884
  // Upload file to S3
686
885
  const fileData = fs.readFileSync(filePath);
687
886
 
688
887
  const uploadConfig = {
689
- method: "PUT",
888
+ method: 'PUT',
690
889
  url: parsedUploadData.uploadURL,
691
890
  data: fileData,
692
891
  headers: {
693
- "Content-Type": finalContentType,
892
+ 'Content-Type': finalContentType,
694
893
  },
695
894
  maxContentLength: Infinity,
696
895
  maxBodyLength: Infinity,
@@ -709,8 +908,8 @@ class ToothFairyAPI {
709
908
  };
710
909
 
711
910
  if (this.verbose) {
712
- const chalk = require("chalk");
713
- console.error(chalk.dim("\n--- File Upload Debug ---"));
911
+ const chalk = require('chalk');
912
+ console.error(chalk.dim('\n--- File Upload Debug ---'));
714
913
  console.error(chalk.dim(`File: ${filePath}`));
715
914
  console.error(chalk.dim(`Original filename: ${originalFilename}`));
716
915
  console.error(chalk.dim(`Sanitized filename: ${sanitizedFilename}`));
@@ -719,7 +918,7 @@ class ToothFairyAPI {
719
918
  console.error(chalk.dim(`Content type: ${finalContentType}`));
720
919
  console.error(chalk.dim(`Size: ${fileSizeInMB.toFixed(2)}MB`));
721
920
  console.error(chalk.dim(`Upload URL: ${parsedUploadData.uploadURL}`));
722
- console.error(chalk.dim("------------------------\n"));
921
+ console.error(chalk.dim('------------------------\n'));
723
922
  }
724
923
 
725
924
  try {
@@ -731,7 +930,7 @@ class ToothFairyAPI {
731
930
  // Remove S3 bucket prefix from filePath to get clean filename for download
732
931
  downloadFilename = parsedUploadData.filePath.replace(
733
932
  /^s3:\/\/[^/]+\//,
734
- ""
933
+ ''
735
934
  );
736
935
  }
737
936
 
@@ -749,7 +948,7 @@ class ToothFairyAPI {
749
948
  if (error.response) {
750
949
  throw new Error(
751
950
  `Upload failed: HTTP ${error.response.status}: ${
752
- error.response.data?.message || "File upload failed"
951
+ error.response.data?.message || 'File upload failed'
753
952
  }`
754
953
  );
755
954
  }
@@ -765,7 +964,7 @@ class ToothFairyAPI {
765
964
  * @param {string} context - Context for the download (default: "pdf")
766
965
  * @returns {Promise<Object>} - Download URL and metadata
767
966
  */
768
- async getDownloadUrl(filename, workspaceId, context = "pdf") {
967
+ async getDownloadUrl(filename, workspaceId, context = 'pdf') {
769
968
  const params = new URLSearchParams({
770
969
  filename: filename,
771
970
  context: context,
@@ -773,7 +972,7 @@ class ToothFairyAPI {
773
972
  });
774
973
 
775
974
  const config = {
776
- method: "GET",
975
+ method: 'GET',
777
976
  url: `${
778
977
  this.baseUrl
779
978
  }/documents/requestDownloadURLIncognito?${params.toString()}`,
@@ -781,10 +980,10 @@ class ToothFairyAPI {
781
980
  };
782
981
 
783
982
  if (this.verbose) {
784
- const chalk = require("chalk");
785
- console.error(chalk.dim("\n--- Download URL Request Debug ---"));
983
+ const chalk = require('chalk');
984
+ console.error(chalk.dim('\n--- Download URL Request Debug ---'));
786
985
  console.error(chalk.dim(`URL: ${config.url}`));
787
- console.error(chalk.dim("-----------------------------\n"));
986
+ console.error(chalk.dim('-----------------------------\n'));
788
987
  }
789
988
 
790
989
  try {
@@ -794,7 +993,7 @@ class ToothFairyAPI {
794
993
  if (error.response) {
795
994
  throw new Error(
796
995
  `HTTP ${error.response.status}: ${
797
- error.response.data.message || "Download URL request failed"
996
+ error.response.data.message || 'Download URL request failed'
798
997
  }`
799
998
  );
800
999
  }
@@ -816,7 +1015,7 @@ class ToothFairyAPI {
816
1015
  filename,
817
1016
  workspaceId,
818
1017
  outputPath,
819
- context = "pdf",
1018
+ context = 'pdf',
820
1019
  onProgress = null
821
1020
  ) {
822
1021
  // Get download URL
@@ -827,7 +1026,7 @@ class ToothFairyAPI {
827
1026
  );
828
1027
 
829
1028
  if (!downloadData.url) {
830
- throw new Error("Failed to get download URL from server");
1029
+ throw new Error('Failed to get download URL from server');
831
1030
  }
832
1031
 
833
1032
  // Ensure output directory exists
@@ -838,9 +1037,9 @@ class ToothFairyAPI {
838
1037
 
839
1038
  // Download file
840
1039
  const downloadConfig = {
841
- method: "GET",
1040
+ method: 'GET',
842
1041
  url: downloadData.url,
843
- responseType: "stream",
1042
+ responseType: 'stream',
844
1043
  onDownloadProgress: onProgress
845
1044
  ? (progressEvent) => {
846
1045
  if (progressEvent.total) {
@@ -858,11 +1057,11 @@ class ToothFairyAPI {
858
1057
  };
859
1058
 
860
1059
  if (this.verbose) {
861
- const chalk = require("chalk");
862
- console.error(chalk.dim("\n--- File Download Debug ---"));
1060
+ const chalk = require('chalk');
1061
+ console.error(chalk.dim('\n--- File Download Debug ---'));
863
1062
  console.error(chalk.dim(`Download URL: ${downloadData.url}`));
864
1063
  console.error(chalk.dim(`Output Path: ${outputPath}`));
865
- console.error(chalk.dim("---------------------------\n"));
1064
+ console.error(chalk.dim('---------------------------\n'));
866
1065
  }
867
1066
 
868
1067
  try {
@@ -873,7 +1072,7 @@ class ToothFairyAPI {
873
1072
  response.data.pipe(writer);
874
1073
 
875
1074
  return new Promise((resolve, reject) => {
876
- writer.on("finish", () => {
1075
+ writer.on('finish', () => {
877
1076
  const stats = fs.statSync(outputPath);
878
1077
  resolve({
879
1078
  success: true,
@@ -882,13 +1081,13 @@ class ToothFairyAPI {
882
1081
  sizeInMB: (stats.size / (1024 * 1024)).toFixed(2),
883
1082
  });
884
1083
  });
885
- writer.on("error", reject);
1084
+ writer.on('error', reject);
886
1085
  });
887
1086
  } catch (error) {
888
1087
  if (error.response) {
889
1088
  throw new Error(
890
1089
  `Download failed: HTTP ${error.response.status}: ${
891
- error.response.data?.message || "File download failed"
1090
+ error.response.data?.message || 'File download failed'
892
1091
  }`
893
1092
  );
894
1093
  }
@@ -904,11 +1103,11 @@ class ToothFairyAPI {
904
1103
  */
905
1104
  _sanitizeFilename(filename) {
906
1105
  // Remove non-alphanumeric characters except dots
907
- let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, "");
1106
+ let sanitized = filename.replace(/[^a-zA-Z0-9.]/g, '');
908
1107
 
909
1108
  // Check length limit
910
1109
  if (sanitized.length > 100) {
911
- throw new Error("File name cannot be more than 100 characters.");
1110
+ throw new Error('File name cannot be more than 100 characters.');
912
1111
  }
913
1112
 
914
1113
  // Add timestamp prefix like frontend
@@ -927,20 +1126,20 @@ class ToothFairyAPI {
927
1126
  const ext = path.extname(filePath).toLowerCase().slice(1);
928
1127
 
929
1128
  // Image extensions
930
- const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "svg"];
1129
+ const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
931
1130
  // Video extensions
932
- const videoExts = ["mp4", "avi", "mov", "wmv", "flv", "webm"];
1131
+ const videoExts = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'];
933
1132
  // Audio extensions
934
- const audioExts = ["wav", "mp3", "aac", "ogg", "flac"];
1133
+ const audioExts = ['wav', 'mp3', 'aac', 'ogg', 'flac'];
935
1134
 
936
1135
  if (imageExts.includes(ext)) {
937
- return "imported-image";
1136
+ return 'imported-image';
938
1137
  } else if (videoExts.includes(ext)) {
939
- return "imported_video_files";
1138
+ return 'imported_video_files';
940
1139
  } else if (audioExts.includes(ext)) {
941
- return "imported_audio_files";
1140
+ return 'imported_audio_files';
942
1141
  } else {
943
- return "imported_doc_files";
1142
+ return 'imported_doc_files';
944
1143
  }
945
1144
  }
946
1145
 
@@ -954,38 +1153,38 @@ class ToothFairyAPI {
954
1153
  const ext = path.extname(filePath).toLowerCase().slice(1);
955
1154
 
956
1155
  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",
1156
+ txt: 'text/plain',
1157
+ md: 'text/markdown',
1158
+ html: 'text/html',
1159
+ pdf: 'application/pdf',
1160
+ json: 'application/json',
1161
+ jsonl: 'application/json',
1162
+ wav: 'audio/wav',
1163
+ mp4: 'video/mp4',
1164
+ png: 'image/png',
1165
+ jpg: 'image/jpeg',
1166
+ jpeg: 'image/jpeg',
1167
+ csv: 'text/csv',
1168
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1169
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1170
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
1171
+ ppt: 'application/vnd.ms-powerpoint',
1172
+ java: 'text/plain',
1173
+ py: 'text/plain',
1174
+ js: 'text/plain',
1175
+ ts: 'text/plain',
1176
+ tsx: 'text/plain',
1177
+ jsx: 'text/plain',
1178
+ yaml: 'text/plain',
1179
+ yml: 'text/plain',
1180
+ sql: 'text/plain',
1181
+ sh: 'text/plain',
1182
+ php: 'text/plain',
1183
+ csharp: 'text/plain',
1184
+ rb: 'text/plain',
986
1185
  };
987
1186
 
988
- return contentTypes[ext] || "application/octet-stream";
1187
+ return contentTypes[ext] || 'application/octet-stream';
989
1188
  }
990
1189
 
991
1190
  /**
@@ -996,18 +1195,18 @@ class ToothFairyAPI {
996
1195
  */
997
1196
  _getDocumentType(filePath) {
998
1197
  // Check if it's a URL
999
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
1000
- return "readComprehensionUrl";
1198
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1199
+ return 'readComprehensionUrl';
1001
1200
  }
1002
1201
 
1003
1202
  // Check if it's a PDF file
1004
1203
  const ext = path.extname(filePath).toLowerCase().slice(1);
1005
- if (ext === "pdf") {
1006
- return "readComprehensionPdf";
1204
+ if (ext === 'pdf') {
1205
+ return 'readComprehensionPdf';
1007
1206
  }
1008
1207
 
1009
1208
  // For all other file types (Excel, CSV, etc.)
1010
- return "readComprehensionFile";
1209
+ return 'readComprehensionFile';
1011
1210
  }
1012
1211
 
1013
1212
  /**
@@ -1026,9 +1225,9 @@ class ToothFairyAPI {
1026
1225
  filePath,
1027
1226
  userId,
1028
1227
  title = null,
1029
- folderId = "mrcRoot",
1228
+ folderId = 'mrcRoot',
1030
1229
  topics = [],
1031
- status = "published",
1230
+ status = 'published',
1032
1231
  scope = null
1033
1232
  ) {
1034
1233
  try {
@@ -1038,7 +1237,7 @@ class ToothFairyAPI {
1038
1237
  // Use filename as title if not provided
1039
1238
  let documentTitle = title;
1040
1239
  if (!documentTitle) {
1041
- if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
1240
+ if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
1042
1241
  documentTitle = filePath;
1043
1242
  } else {
1044
1243
  documentTitle = path.basename(filePath);
@@ -1047,7 +1246,7 @@ class ToothFairyAPI {
1047
1246
 
1048
1247
  // Determine external path based on file type
1049
1248
  let externalPath;
1050
- if (type === "readComprehensionUrl") {
1249
+ if (type === 'readComprehensionUrl') {
1051
1250
  externalPath = filePath; // Use URL directly
1052
1251
  } else {
1053
1252
  // For files, construct S3 path
@@ -1065,14 +1264,14 @@ class ToothFairyAPI {
1065
1264
  topics: topics,
1066
1265
  folderid: folderId,
1067
1266
  external_path: externalPath,
1068
- source: "api",
1267
+ source: 'api',
1069
1268
  status: status,
1070
1269
  ...(scope && { scope: scope }),
1071
1270
  },
1072
1271
  ],
1073
1272
  };
1074
1273
 
1075
- return await this._makeRequest("POST", "doc/create", documentData);
1274
+ return await this._makeRequest('POST', 'doc/create', documentData);
1076
1275
  } catch (error) {
1077
1276
  console.error(`Error creating document: ${error.message}`);
1078
1277
  throw error;
@@ -1103,7 +1302,7 @@ class ToothFairyAPI {
1103
1302
  },
1104
1303
  };
1105
1304
 
1106
- return await this._makeRequest("POST", "doc/update", updateData);
1305
+ return await this._makeRequest('POST', 'doc/update', updateData);
1107
1306
  } catch (error) {
1108
1307
  console.error(`Error updating document: ${error.message}`);
1109
1308
  throw error;
@@ -1118,7 +1317,7 @@ class ToothFairyAPI {
1118
1317
  */
1119
1318
  async deleteDocument(documentId) {
1120
1319
  try {
1121
- return await this._makeRequest("DELETE", `doc/delete/${documentId}`);
1320
+ return await this._makeRequest('DELETE', `doc/delete/${documentId}`);
1122
1321
  } catch (error) {
1123
1322
  console.error(`Error deleting document: ${error.message}`);
1124
1323
  throw error;
@@ -1133,7 +1332,7 @@ class ToothFairyAPI {
1133
1332
  */
1134
1333
  async getDocument(documentId) {
1135
1334
  try {
1136
- return await this._makeRequest("GET", `doc/get/${documentId}`);
1335
+ return await this._makeRequest('GET', `doc/get/${documentId}`);
1137
1336
  } catch (error) {
1138
1337
  console.error(`Error getting document: ${error.message}`);
1139
1338
  throw error;
@@ -1155,7 +1354,7 @@ class ToothFairyAPI {
1155
1354
  async createEntity(
1156
1355
  userId,
1157
1356
  label,
1158
- type = "topic",
1357
+ type = 'topic',
1159
1358
  description = null,
1160
1359
  emoji = null,
1161
1360
  parentEntity = null,
@@ -1173,7 +1372,7 @@ class ToothFairyAPI {
1173
1372
  ...(backgroundColor && { backgroundColor: backgroundColor }),
1174
1373
  };
1175
1374
 
1176
- return await this._makeRequest("POST", "entity/create", entityData);
1375
+ return await this._makeRequest('POST', 'entity/create', entityData);
1177
1376
  } catch (error) {
1178
1377
  console.error(`Error creating entity: ${error.message}`);
1179
1378
  throw error;
@@ -1200,7 +1399,7 @@ class ToothFairyAPI {
1200
1399
  ...fields,
1201
1400
  };
1202
1401
 
1203
- return await this._makeRequest("POST", "entity/update", updateData);
1402
+ return await this._makeRequest('POST', 'entity/update', updateData);
1204
1403
  } catch (error) {
1205
1404
  console.error(`Error updating entity: ${error.message}`);
1206
1405
  throw error;
@@ -1215,7 +1414,7 @@ class ToothFairyAPI {
1215
1414
  */
1216
1415
  async deleteEntity(entityId) {
1217
1416
  try {
1218
- return await this._makeRequest("DELETE", `entity/delete/${entityId}`);
1417
+ return await this._makeRequest('DELETE', `entity/delete/${entityId}`);
1219
1418
  } catch (error) {
1220
1419
  console.error(`Error deleting entity: ${error.message}`);
1221
1420
  throw error;
@@ -1230,7 +1429,7 @@ class ToothFairyAPI {
1230
1429
  */
1231
1430
  async getEntity(entityId) {
1232
1431
  try {
1233
- return await this._makeRequest("GET", `entity/get/${entityId}`);
1432
+ return await this._makeRequest('GET', `entity/get/${entityId}`);
1234
1433
  } catch (error) {
1235
1434
  console.error(`Error getting entity: ${error.message}`);
1236
1435
  throw error;
@@ -1249,7 +1448,7 @@ class ToothFairyAPI {
1249
1448
  if (limit) {
1250
1449
  params.limit = limit;
1251
1450
  }
1252
- return await this._makeRequest("GET", "entity/list", params);
1451
+ return await this._makeRequest('GET', 'entity/list', params);
1253
1452
  } catch (error) {
1254
1453
  console.error(`Error listing entities: ${error.message}`);
1255
1454
  throw error;
@@ -1287,7 +1486,7 @@ class ToothFairyAPI {
1287
1486
  ...(status && { status: status }),
1288
1487
  ...(parent && { parent: parent }),
1289
1488
  };
1290
- return await this._makeRequest("POST", "folder/create", folderData);
1489
+ return await this._makeRequest('POST', 'folder/create', folderData);
1291
1490
  } catch (error) {
1292
1491
  console.error(`Error creating folder: ${error.message}`);
1293
1492
  throw error;
@@ -1322,7 +1521,7 @@ class ToothFairyAPI {
1322
1521
  ...(status && { status: status }),
1323
1522
  ...(parent !== null && { parent: parent }),
1324
1523
  };
1325
- return await this._makeRequest("POST", "folder/update", updateData);
1524
+ return await this._makeRequest('POST', 'folder/update', updateData);
1326
1525
  } catch (error) {
1327
1526
  console.error(`Error updating folder: ${error.message}`);
1328
1527
  throw error;
@@ -1337,7 +1536,7 @@ class ToothFairyAPI {
1337
1536
  */
1338
1537
  async deleteFolder(folderId) {
1339
1538
  try {
1340
- return await this._makeRequest("DELETE", `folder/delete/${folderId}`);
1539
+ return await this._makeRequest('DELETE', `folder/delete/${folderId}`);
1341
1540
  } catch (error) {
1342
1541
  console.error(`Error deleting folder: ${error.message}`);
1343
1542
  throw error;
@@ -1352,7 +1551,7 @@ class ToothFairyAPI {
1352
1551
  */
1353
1552
  async getFolder(folderId) {
1354
1553
  try {
1355
- return await this._makeRequest("GET", `folder/get/${folderId}`);
1554
+ return await this._makeRequest('GET', `folder/get/${folderId}`);
1356
1555
  } catch (error) {
1357
1556
  console.error(`Error getting folder: ${error.message}`);
1358
1557
  throw error;
@@ -1379,7 +1578,7 @@ class ToothFairyAPI {
1379
1578
  if (offset) {
1380
1579
  params.offset = offset;
1381
1580
  }
1382
- return await this._makeRequest("GET", "folder/list", params);
1581
+ return await this._makeRequest('GET', 'folder/list', params);
1383
1582
  } catch (error) {
1384
1583
  console.error(`Error listing folders: ${error.message}`);
1385
1584
  throw error;
@@ -1429,7 +1628,7 @@ class ToothFairyAPI {
1429
1628
  ...(promptPlaceholder && { promptPlaceholder: promptPlaceholder }),
1430
1629
  ...(availableToAgents && { availableToAgents: availableToAgents }),
1431
1630
  };
1432
- return await this._makeRequest("POST", "prompt/create", promptData);
1631
+ return await this._makeRequest('POST', 'prompt/create', promptData);
1433
1632
  } catch (error) {
1434
1633
  console.error(`Error creating prompt: ${error.message}`);
1435
1634
  throw error;
@@ -1482,7 +1681,7 @@ class ToothFairyAPI {
1482
1681
  availableToAgents: availableToAgents,
1483
1682
  }),
1484
1683
  };
1485
- return await this._makeRequest("POST", "prompt/update", updateData);
1684
+ return await this._makeRequest('POST', 'prompt/update', updateData);
1486
1685
  } catch (error) {
1487
1686
  console.error(`Error updating prompt: ${error.message}`);
1488
1687
  throw error;
@@ -1497,7 +1696,7 @@ class ToothFairyAPI {
1497
1696
  */
1498
1697
  async deletePrompt(promptId) {
1499
1698
  try {
1500
- return await this._makeRequest("DELETE", `prompt/delete/${promptId}`);
1699
+ return await this._makeRequest('DELETE', `prompt/delete/${promptId}`);
1501
1700
  } catch (error) {
1502
1701
  console.error(`Error deleting prompt: ${error.message}`);
1503
1702
  throw error;
@@ -1512,7 +1711,7 @@ class ToothFairyAPI {
1512
1711
  */
1513
1712
  async getPrompt(promptId) {
1514
1713
  try {
1515
- return await this._makeRequest("GET", `prompt/get/${promptId}`);
1714
+ return await this._makeRequest('GET', `prompt/get/${promptId}`);
1516
1715
  } catch (error) {
1517
1716
  console.error(`Error getting prompt: ${error.message}`);
1518
1717
  throw error;
@@ -1539,7 +1738,7 @@ class ToothFairyAPI {
1539
1738
  if (offset) {
1540
1739
  params.offset = offset;
1541
1740
  }
1542
- return await this._makeRequest("GET", "prompt/list", params);
1741
+ return await this._makeRequest('GET', 'prompt/list', params);
1543
1742
  } catch (error) {
1544
1743
  console.error(`Error listing prompts: ${error.message}`);
1545
1744
  throw error;
@@ -1564,18 +1763,18 @@ class ToothFairyAPI {
1564
1763
  };
1565
1764
 
1566
1765
  const config = {
1567
- method: "POST",
1766
+ method: 'POST',
1568
1767
  url: `${this.baseUrl}/media/audio_generation`,
1569
1768
  headers: {
1570
- "Content-Type": "application/json",
1571
- "x-api-key": this.headers["x-api-key"],
1769
+ 'Content-Type': 'application/json',
1770
+ 'x-api-key': this.headers['x-api-key'],
1572
1771
  },
1573
1772
  data: speechData,
1574
1773
  };
1575
1774
 
1576
1775
  if (this.verbose) {
1577
- const chalk = require("chalk");
1578
- console.error(chalk.dim("\n--- Speech Generation Request Debug ---"));
1776
+ const chalk = require('chalk');
1777
+ console.error(chalk.dim('\n--- Speech Generation Request Debug ---'));
1579
1778
  console.error(chalk.dim(`Method: ${config.method}`));
1580
1779
  console.error(chalk.dim(`URL: ${config.url}`));
1581
1780
  console.error(
@@ -1584,28 +1783,28 @@ class ToothFairyAPI {
1584
1783
  console.error(
1585
1784
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1586
1785
  );
1587
- console.error(chalk.dim("--------------------------------------\n"));
1786
+ console.error(chalk.dim('--------------------------------------\n'));
1588
1787
  }
1589
1788
 
1590
1789
  const response = await axios(config);
1591
1790
 
1592
1791
  if (this.verbose) {
1593
- const chalk = require("chalk");
1594
- console.error(chalk.dim("\n--- Speech Generation Response Debug ---"));
1792
+ const chalk = require('chalk');
1793
+ console.error(chalk.dim('\n--- Speech Generation Response Debug ---'));
1595
1794
  console.error(
1596
1795
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1597
1796
  );
1598
1797
  console.error(
1599
1798
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1600
1799
  );
1601
- console.error(chalk.dim("---------------------------------------\n"));
1800
+ console.error(chalk.dim('---------------------------------------\n'));
1602
1801
  }
1603
1802
 
1604
1803
  return response.data;
1605
1804
  } catch (error) {
1606
1805
  if (this.verbose) {
1607
- const chalk = require("chalk");
1608
- console.error(chalk.red("\n--- Speech Generation Error Debug ---"));
1806
+ const chalk = require('chalk');
1807
+ console.error(chalk.red('\n--- Speech Generation Error Debug ---'));
1609
1808
  console.error(chalk.red(`Error: ${error.message}`));
1610
1809
  if (error.response) {
1611
1810
  console.error(
@@ -1619,7 +1818,7 @@ class ToothFairyAPI {
1619
1818
  )
1620
1819
  );
1621
1820
  }
1622
- console.error(chalk.red("------------------------------------\n"));
1821
+ console.error(chalk.red('------------------------------------\n'));
1623
1822
  }
1624
1823
  console.error(`Error generating speech: ${error.message}`);
1625
1824
  throw error;
@@ -1642,18 +1841,18 @@ class ToothFairyAPI {
1642
1841
  };
1643
1842
 
1644
1843
  const config = {
1645
- method: "POST",
1844
+ method: 'POST',
1646
1845
  url: `${this.baseUrl}/media/audio`,
1647
1846
  headers: {
1648
- "Content-Type": "application/json",
1649
- "x-api-key": this.headers["x-api-key"],
1847
+ 'Content-Type': 'application/json',
1848
+ 'x-api-key': this.headers['x-api-key'],
1650
1849
  },
1651
1850
  data: audioData,
1652
1851
  };
1653
1852
 
1654
1853
  if (this.verbose) {
1655
- const chalk = require("chalk");
1656
- console.error(chalk.dim("\n--- Audio Processing Request Debug ---"));
1854
+ const chalk = require('chalk');
1855
+ console.error(chalk.dim('\n--- Audio Processing Request Debug ---'));
1657
1856
  console.error(chalk.dim(`Method: ${config.method}`));
1658
1857
  console.error(chalk.dim(`URL: ${config.url}`));
1659
1858
  console.error(
@@ -1662,28 +1861,28 @@ class ToothFairyAPI {
1662
1861
  console.error(
1663
1862
  chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`)
1664
1863
  );
1665
- console.error(chalk.dim("------------------------------------\n"));
1864
+ console.error(chalk.dim('------------------------------------\n'));
1666
1865
  }
1667
1866
 
1668
1867
  const response = await axios(config);
1669
1868
 
1670
1869
  if (this.verbose) {
1671
- const chalk = require("chalk");
1672
- console.error(chalk.dim("\n--- Audio Processing Response Debug ---"));
1870
+ const chalk = require('chalk');
1871
+ console.error(chalk.dim('\n--- Audio Processing Response Debug ---'));
1673
1872
  console.error(
1674
1873
  chalk.dim(`Status: ${response.status} ${response.statusText}`)
1675
1874
  );
1676
1875
  console.error(
1677
1876
  chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`)
1678
1877
  );
1679
- console.error(chalk.dim("-------------------------------------\n"));
1878
+ console.error(chalk.dim('-------------------------------------\n'));
1680
1879
  }
1681
1880
 
1682
1881
  return response.data;
1683
1882
  } catch (error) {
1684
1883
  if (this.verbose) {
1685
- const chalk = require("chalk");
1686
- console.error(chalk.red("\n--- Audio Processing Error Debug ---"));
1884
+ const chalk = require('chalk');
1885
+ console.error(chalk.red('\n--- Audio Processing Error Debug ---'));
1687
1886
  console.error(chalk.red(`Error: ${error.message}`));
1688
1887
  if (error.response) {
1689
1888
  console.error(
@@ -1697,7 +1896,7 @@ class ToothFairyAPI {
1697
1896
  )
1698
1897
  );
1699
1898
  }
1700
- console.error(chalk.red("----------------------------------\n"));
1899
+ console.error(chalk.red('----------------------------------\n'));
1701
1900
  }
1702
1901
  console.error(`Error processing audio: ${error.message}`);
1703
1902
  throw error;
@@ -1724,16 +1923,16 @@ class ToothFairyAPI {
1724
1923
  try {
1725
1924
  // Validate attachment limits
1726
1925
  if (attachments.images && attachments.images.length > 1) {
1727
- throw new Error("Maximum 1 image attachment allowed");
1926
+ throw new Error('Maximum 1 image attachment allowed');
1728
1927
  }
1729
1928
  if (attachments.audios && attachments.audios.length > 1) {
1730
- throw new Error("Maximum 1 audio attachment allowed");
1929
+ throw new Error('Maximum 1 audio attachment allowed');
1731
1930
  }
1732
1931
  if (attachments.videos && attachments.videos.length > 1) {
1733
- throw new Error("Maximum 1 video attachment allowed");
1932
+ throw new Error('Maximum 1 video attachment allowed');
1734
1933
  }
1735
1934
  if (attachments.files && attachments.files.length > 5) {
1736
- throw new Error("Maximum 5 file attachments allowed");
1935
+ throw new Error('Maximum 5 file attachments allowed');
1737
1936
  }
1738
1937
 
1739
1938
  // Process images
@@ -1789,10 +1988,10 @@ class ToothFairyAPI {
1789
1988
  }
1790
1989
 
1791
1990
  if (this.verbose) {
1792
- const chalk = require("chalk");
1793
- console.error(chalk.dim("\n--- Processed Attachments ---"));
1991
+ const chalk = require('chalk');
1992
+ console.error(chalk.dim('\n--- Processed Attachments ---'));
1794
1993
  console.error(chalk.dim(`Result: ${JSON.stringify(result, null, 2)}`));
1795
- console.error(chalk.dim("-----------------------------\n"));
1994
+ console.error(chalk.dim('-----------------------------\n'));
1796
1995
  }
1797
1996
 
1798
1997
  return result;