@toothfairyai/cli 1.0.3 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -10
- package/bin/toothfairy.js +455 -197
- package/package.json +3 -2
- package/src/api.js +268 -33
- package/src/config.js +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toothfairyai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Command-line interface for ToothFairy AI API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"dotenv": "^16.0.3",
|
|
44
44
|
"js-yaml": "^4.1.0",
|
|
45
45
|
"ora": "^5.4.1",
|
|
46
|
-
"table": "^6.8.1"
|
|
46
|
+
"table": "^6.8.1",
|
|
47
|
+
"eventsource": "^2.0.2"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"eslint": "^8.32.0",
|
package/src/api.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
const axios = require(
|
|
1
|
+
const axios = require('axios');
|
|
2
2
|
|
|
3
3
|
class ToothFairyAPI {
|
|
4
|
-
constructor(baseUrl, aiUrl, apiKey, workspaceId, verbose = false) {
|
|
4
|
+
constructor(baseUrl, aiUrl, aiStreamUrl, apiKey, workspaceId, verbose = false) {
|
|
5
5
|
this.baseUrl = baseUrl;
|
|
6
6
|
this.aiUrl = aiUrl;
|
|
7
|
+
this.aiStreamUrl = aiStreamUrl;
|
|
7
8
|
this.workspaceId = workspaceId;
|
|
8
9
|
this.verbose = verbose;
|
|
9
10
|
this.headers = {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
'x-api-key': apiKey,
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -19,46 +20,46 @@ class ToothFairyAPI {
|
|
|
19
20
|
headers: this.headers,
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
if (method ===
|
|
23
|
+
if (method === 'POST' || method === 'PUT') {
|
|
23
24
|
if (data) {
|
|
24
25
|
data = { workspaceid: this.workspaceId, ...data };
|
|
25
26
|
}
|
|
26
27
|
config.data = data;
|
|
27
|
-
} else if (method ===
|
|
28
|
+
} else if (method === 'GET' && data) {
|
|
28
29
|
// For GET requests with data, add as query parameters
|
|
29
30
|
const params = new URLSearchParams(data);
|
|
30
31
|
config.url += `?${params.toString()}`;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
if (this.verbose) {
|
|
34
|
-
const chalk = require(
|
|
35
|
-
console.error(chalk.dim(
|
|
35
|
+
const chalk = require('chalk');
|
|
36
|
+
console.error(chalk.dim('\n--- API Request Debug ---'));
|
|
36
37
|
console.error(chalk.dim(`Method: ${method}`));
|
|
37
38
|
console.error(chalk.dim(`URL: ${config.url}`));
|
|
38
39
|
console.error(chalk.dim(`Headers: ${JSON.stringify(config.headers, null, 2)}`));
|
|
39
40
|
if (config.data) {
|
|
40
41
|
console.error(chalk.dim(`Data: ${JSON.stringify(config.data, null, 2)}`));
|
|
41
42
|
}
|
|
42
|
-
console.error(chalk.dim(
|
|
43
|
+
console.error(chalk.dim('----------------------\n'));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
try {
|
|
46
47
|
const response = await axios(config);
|
|
47
48
|
|
|
48
49
|
if (this.verbose) {
|
|
49
|
-
const chalk = require(
|
|
50
|
-
console.error(chalk.dim(
|
|
50
|
+
const chalk = require('chalk');
|
|
51
|
+
console.error(chalk.dim('\n--- API Response Debug ---'));
|
|
51
52
|
console.error(chalk.dim(`Status: ${response.status} ${response.statusText}`));
|
|
52
53
|
console.error(chalk.dim(`Response Headers: ${JSON.stringify(response.headers, null, 2)}`));
|
|
53
54
|
console.error(chalk.dim(`Response Data: ${JSON.stringify(response.data, null, 2)}`));
|
|
54
|
-
console.error(chalk.dim(
|
|
55
|
+
console.error(chalk.dim('------------------------\n'));
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
return response.data;
|
|
58
59
|
} catch (error) {
|
|
59
60
|
if (this.verbose) {
|
|
60
|
-
const chalk = require(
|
|
61
|
-
console.error(chalk.red(
|
|
61
|
+
const chalk = require('chalk');
|
|
62
|
+
console.error(chalk.red('\n--- API Error Debug ---'));
|
|
62
63
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
63
64
|
if (error.response) {
|
|
64
65
|
console.error(chalk.red(`Status: ${error.response.status} ${error.response.statusText}`));
|
|
@@ -68,13 +69,13 @@ class ToothFairyAPI {
|
|
|
68
69
|
if (error.request) {
|
|
69
70
|
console.error(chalk.red(`Request Config: ${JSON.stringify(error.config, null, 2)}`));
|
|
70
71
|
}
|
|
71
|
-
console.error(chalk.red(
|
|
72
|
+
console.error(chalk.red('---------------------\n'));
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
if (error.response) {
|
|
75
76
|
throw new Error(
|
|
76
77
|
`HTTP ${error.response.status}: ${
|
|
77
|
-
error.response.data.message ||
|
|
78
|
+
error.response.data.message || 'API request failed'
|
|
78
79
|
}`
|
|
79
80
|
);
|
|
80
81
|
}
|
|
@@ -83,34 +84,34 @@ class ToothFairyAPI {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async createChat(chatData) {
|
|
86
|
-
return this._makeRequest(
|
|
87
|
+
return this._makeRequest('POST', 'chat/create', chatData);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
async updateChat(chatData) {
|
|
90
|
-
return this._makeRequest(
|
|
91
|
+
return this._makeRequest('POST', 'chat/update', chatData);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
async getChat(chatId) {
|
|
94
|
-
return this._makeRequest(
|
|
95
|
+
return this._makeRequest('GET', `chat/get/${chatId}`);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
async createMessage(messageData) {
|
|
98
|
-
return this._makeRequest(
|
|
99
|
+
return this._makeRequest('POST', 'chat_message/create', messageData);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
async getMessage(messageId) {
|
|
102
|
-
return this._makeRequest(
|
|
103
|
+
return this._makeRequest('GET', `chat_message/get/${messageId}`);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
async getAllChats() {
|
|
106
|
-
return this._makeRequest(
|
|
107
|
+
return this._makeRequest('GET', 'chat/list', {
|
|
107
108
|
workspaceid: this.workspaceId,
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
async getAgentResponse(agentData) {
|
|
112
113
|
const config = {
|
|
113
|
-
method:
|
|
114
|
+
method: 'POST',
|
|
114
115
|
url: `${this.aiUrl}/chatter`,
|
|
115
116
|
headers: this.headers,
|
|
116
117
|
data: { workspaceid: this.workspaceId, ...agentData },
|
|
@@ -123,7 +124,7 @@ class ToothFairyAPI {
|
|
|
123
124
|
if (error.response) {
|
|
124
125
|
throw new Error(
|
|
125
126
|
`HTTP ${error.response.status}: ${
|
|
126
|
-
error.response.data.message ||
|
|
127
|
+
error.response.data.message || 'Agent request failed'
|
|
127
128
|
}`
|
|
128
129
|
);
|
|
129
130
|
}
|
|
@@ -145,14 +146,14 @@ class ToothFairyAPI {
|
|
|
145
146
|
customerId ||
|
|
146
147
|
`cli-user-${
|
|
147
148
|
Math.abs(
|
|
148
|
-
message.split(
|
|
149
|
+
message.split('').reduce((a, b) => {
|
|
149
150
|
a = (a << 5) - a + b.charCodeAt(0);
|
|
150
151
|
return a & a;
|
|
151
152
|
}, 0)
|
|
152
153
|
) % 10000
|
|
153
154
|
}`;
|
|
154
|
-
phoneNumber = phoneNumber ||
|
|
155
|
-
providerId = providerId ||
|
|
155
|
+
phoneNumber = phoneNumber || '+1234567890';
|
|
156
|
+
providerId = providerId || 'default-sms-provider';
|
|
156
157
|
|
|
157
158
|
const chatData = {
|
|
158
159
|
name: customerId,
|
|
@@ -176,8 +177,8 @@ class ToothFairyAPI {
|
|
|
176
177
|
const messageData = {
|
|
177
178
|
chatID: createdChat.id,
|
|
178
179
|
text: message,
|
|
179
|
-
role:
|
|
180
|
-
userID:
|
|
180
|
+
role: 'user',
|
|
181
|
+
userID: 'CLI',
|
|
181
182
|
};
|
|
182
183
|
const createdMessage = await this.createMessage(messageData);
|
|
183
184
|
// console.log(`Message created: ${createdMessage.id}`);;
|
|
@@ -188,7 +189,7 @@ class ToothFairyAPI {
|
|
|
188
189
|
{
|
|
189
190
|
text: createdMessage.text,
|
|
190
191
|
role: createdMessage.role,
|
|
191
|
-
userID: createdMessage.userID ||
|
|
192
|
+
userID: createdMessage.userID || 'System User',
|
|
192
193
|
},
|
|
193
194
|
],
|
|
194
195
|
agentid: agentId,
|
|
@@ -209,7 +210,7 @@ class ToothFairyAPI {
|
|
|
209
210
|
|
|
210
211
|
async searchDocuments(text, topK = 10, metadata = null) {
|
|
211
212
|
if (topK < 1 || topK > 50) {
|
|
212
|
-
throw new Error(
|
|
213
|
+
throw new Error('topK must be between 1 and 50');
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
const searchData = {
|
|
@@ -222,7 +223,7 @@ class ToothFairyAPI {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
const config = {
|
|
225
|
-
method:
|
|
226
|
+
method: 'POST',
|
|
226
227
|
url: `${this.aiUrl}/searcher`,
|
|
227
228
|
headers: this.headers,
|
|
228
229
|
data: { workspaceid: this.workspaceId, ...searchData },
|
|
@@ -235,13 +236,247 @@ class ToothFairyAPI {
|
|
|
235
236
|
if (error.response) {
|
|
236
237
|
throw new Error(
|
|
237
238
|
`HTTP ${error.response.status}: ${
|
|
238
|
-
error.response.data.message ||
|
|
239
|
+
error.response.data.message || 'Search request failed'
|
|
239
240
|
}`
|
|
240
241
|
);
|
|
241
242
|
}
|
|
242
243
|
throw error;
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Send a message to an agent and get a streaming response.
|
|
249
|
+
*
|
|
250
|
+
* This method handles the complete workflow with Server-Sent Events (SSE):
|
|
251
|
+
* 1. Creates a chat
|
|
252
|
+
* 2. Creates a message
|
|
253
|
+
* 3. Streams the agent response in real-time
|
|
254
|
+
*
|
|
255
|
+
* @param {string} message - The message to send to the agent
|
|
256
|
+
* @param {string} agentId - The ID of the agent to send the message to
|
|
257
|
+
* @param {string|null} phoneNumber - Phone number for SMS channel
|
|
258
|
+
* @param {string|null} customerId - Customer identifier
|
|
259
|
+
* @param {string|null} providerId - SMS provider ID
|
|
260
|
+
* @param {Object} customerInfo - Additional customer information
|
|
261
|
+
* @param {Function} onEvent - Callback function called for each event
|
|
262
|
+
* @returns {Promise<void>} - Promise resolves when streaming is complete
|
|
263
|
+
*
|
|
264
|
+
* Event Types Explained:
|
|
265
|
+
* - 'status': Connection status updates ('connected', 'complete')
|
|
266
|
+
* - 'progress': Agent processing status updates:
|
|
267
|
+
* * 'init': Agent initialization started
|
|
268
|
+
* * 'initial_setup_completed': Basic setup finished
|
|
269
|
+
* * 'tools_processing_completed': Tools processing finished
|
|
270
|
+
* * 'replying': Agent is generating response (text streaming starts)
|
|
271
|
+
* * 'updating_memory': Agent is updating conversation memory
|
|
272
|
+
* * 'memory_updated': Memory update completed
|
|
273
|
+
* - 'data': Actual response text chunks (progressive text building)
|
|
274
|
+
* - 'complete': Final response with all metadata
|
|
275
|
+
* - 'error': Error occurred during streaming
|
|
276
|
+
*
|
|
277
|
+
* The onEvent callback receives: (eventType, eventData) => {}
|
|
278
|
+
*/
|
|
279
|
+
async sendMessageToAgentStream(
|
|
280
|
+
message,
|
|
281
|
+
agentId,
|
|
282
|
+
phoneNumber = null,
|
|
283
|
+
customerId = null,
|
|
284
|
+
providerId = null,
|
|
285
|
+
customerInfo = {},
|
|
286
|
+
onEvent
|
|
287
|
+
) {
|
|
288
|
+
try {
|
|
289
|
+
// Use defaults for optional parameters
|
|
290
|
+
customerId =
|
|
291
|
+
customerId ||
|
|
292
|
+
`cli-user-${
|
|
293
|
+
Math.abs(
|
|
294
|
+
message.split('').reduce((a, b) => {
|
|
295
|
+
a = (a << 5) - a + b.charCodeAt(0);
|
|
296
|
+
return a & a;
|
|
297
|
+
}, 0)
|
|
298
|
+
) % 10000
|
|
299
|
+
}`;
|
|
300
|
+
phoneNumber = phoneNumber || '+1234567890';
|
|
301
|
+
providerId = providerId || 'default-sms-provider';
|
|
302
|
+
|
|
303
|
+
// Create chat first
|
|
304
|
+
const chatData = {
|
|
305
|
+
name: customerId,
|
|
306
|
+
primaryRole: agentId,
|
|
307
|
+
externalParticipantId: phoneNumber,
|
|
308
|
+
channelSettings: {
|
|
309
|
+
sms: {
|
|
310
|
+
isEnabled: true,
|
|
311
|
+
recipient: phoneNumber,
|
|
312
|
+
providerID: providerId,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
customerId: customerId,
|
|
316
|
+
customerInfo: customerInfo,
|
|
317
|
+
isAIReplying: true,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const createdChat = await this.createChat(chatData);
|
|
321
|
+
console.debug(`Chat created: ${createdChat.id}`);
|
|
322
|
+
|
|
323
|
+
// Create message
|
|
324
|
+
const messageData = {
|
|
325
|
+
chatID: createdChat.id,
|
|
326
|
+
text: message,
|
|
327
|
+
role: 'user',
|
|
328
|
+
userID: 'CLI',
|
|
329
|
+
};
|
|
330
|
+
const createdMessage = await this.createMessage(messageData);
|
|
331
|
+
console.debug(`Message created: ${createdMessage.id}`);
|
|
332
|
+
|
|
333
|
+
// Prepare agent data for streaming
|
|
334
|
+
const agentData = {
|
|
335
|
+
workspaceid: this.workspaceId,
|
|
336
|
+
messages: [
|
|
337
|
+
{
|
|
338
|
+
text: createdMessage.text,
|
|
339
|
+
role: createdMessage.role,
|
|
340
|
+
userID: createdMessage.userID || 'System User',
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
agentid: agentId,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Stream the agent response using the dedicated streaming URL
|
|
347
|
+
const streamUrl = `${this.aiStreamUrl}/agent`; // Using streaming URL for /agent endpoint
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
return new Promise((resolve, reject) => {
|
|
351
|
+
// For POST requests with EventSource, we need to use a different approach
|
|
352
|
+
// EventSource doesn't support POST requests directly, so we'll use axios with streaming
|
|
353
|
+
const config = {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
url: streamUrl,
|
|
356
|
+
headers: {
|
|
357
|
+
...this.headers,
|
|
358
|
+
'Accept': 'text/event-stream',
|
|
359
|
+
'Cache-Control': 'no-cache',
|
|
360
|
+
},
|
|
361
|
+
data: agentData,
|
|
362
|
+
responseType: 'stream',
|
|
363
|
+
timeout: 300000, // 5 minute timeout
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
axios(config)
|
|
367
|
+
.then(response => {
|
|
368
|
+
let buffer = '';
|
|
369
|
+
|
|
370
|
+
response.data.on('data', (chunk) => {
|
|
371
|
+
buffer += chunk.toString();
|
|
372
|
+
|
|
373
|
+
// Process complete lines
|
|
374
|
+
const lines = buffer.split('\n');
|
|
375
|
+
buffer = lines.pop() || ''; // Keep the incomplete line in buffer
|
|
376
|
+
|
|
377
|
+
for (const line of lines) {
|
|
378
|
+
if (line.trim() === '') continue;
|
|
379
|
+
|
|
380
|
+
if (line.startsWith('data: ')) {
|
|
381
|
+
const dataStr = line.slice(6); // Remove 'data: ' prefix
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const eventData = JSON.parse(dataStr);
|
|
385
|
+
|
|
386
|
+
// Determine event type based on the data structure
|
|
387
|
+
if (eventData.status) {
|
|
388
|
+
if (eventData.status === 'connected') {
|
|
389
|
+
onEvent('status', eventData);
|
|
390
|
+
} else if (eventData.status === 'complete') {
|
|
391
|
+
onEvent('status', eventData);
|
|
392
|
+
} else if (eventData.status === 'inProgress') {
|
|
393
|
+
// Parse metadata to understand what's happening
|
|
394
|
+
let metadata = {};
|
|
395
|
+
if (eventData.metadata) {
|
|
396
|
+
try {
|
|
397
|
+
metadata = JSON.parse(eventData.metadata);
|
|
398
|
+
} catch (e) {
|
|
399
|
+
metadata = { raw_metadata: eventData.metadata };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Determine progress type
|
|
404
|
+
if (metadata.agent_processing_status) {
|
|
405
|
+
const processingStatus = metadata.agent_processing_status;
|
|
406
|
+
onEvent('progress', {
|
|
407
|
+
...eventData,
|
|
408
|
+
processing_status: processingStatus,
|
|
409
|
+
metadata_parsed: metadata
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
onEvent('progress', { ...eventData, metadata_parsed: metadata });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
} else if (eventData.status === 'fulfilled') {
|
|
416
|
+
// Final response with complete data
|
|
417
|
+
onEvent('complete', eventData);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
} else if (eventData.text && eventData.type === 'message') {
|
|
421
|
+
// This is streaming text data
|
|
422
|
+
onEvent('data', eventData);
|
|
423
|
+
|
|
424
|
+
} else if (eventData.type === 'message' && eventData.images !== undefined) {
|
|
425
|
+
// Additional message metadata (images, files, etc.)
|
|
426
|
+
onEvent('metadata', eventData);
|
|
427
|
+
|
|
428
|
+
} else if (eventData.type === 'message' && eventData.callbackMetadata) {
|
|
429
|
+
// Callback metadata with function details and execution plan
|
|
430
|
+
onEvent('callback', eventData);
|
|
431
|
+
|
|
432
|
+
} else {
|
|
433
|
+
// Generic event data
|
|
434
|
+
onEvent('unknown', eventData);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.error(`Failed to parse SSE data: ${dataStr}, error: ${e.message}`);
|
|
439
|
+
onEvent('error', {
|
|
440
|
+
error: 'json_decode_error',
|
|
441
|
+
raw_data: dataStr,
|
|
442
|
+
message: e.message
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
response.data.on('end', () => {
|
|
450
|
+
resolve();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
response.data.on('error', (error) => {
|
|
454
|
+
onEvent('error', {
|
|
455
|
+
error: 'stream_error',
|
|
456
|
+
message: error.message
|
|
457
|
+
});
|
|
458
|
+
reject(error);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
})
|
|
462
|
+
.catch(error => {
|
|
463
|
+
onEvent('error', {
|
|
464
|
+
error: 'request_error',
|
|
465
|
+
message: error.message
|
|
466
|
+
});
|
|
467
|
+
reject(error);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(`Error in sendMessageToAgentStream: ${error.message}`);
|
|
473
|
+
onEvent('error', {
|
|
474
|
+
error: 'setup_error',
|
|
475
|
+
message: error.message
|
|
476
|
+
});
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
245
480
|
}
|
|
246
481
|
|
|
247
482
|
module.exports = ToothFairyAPI;
|
package/src/config.js
CHANGED
|
@@ -4,9 +4,10 @@ const os = require('os');
|
|
|
4
4
|
const yaml = require('js-yaml');
|
|
5
5
|
|
|
6
6
|
class ToothFairyConfig {
|
|
7
|
-
constructor(baseUrl, aiUrl, apiKey, workspaceId) {
|
|
7
|
+
constructor(baseUrl, aiUrl, aiStreamUrl, apiKey, workspaceId) {
|
|
8
8
|
this.baseUrl = baseUrl;
|
|
9
9
|
this.aiUrl = aiUrl;
|
|
10
|
+
this.aiStreamUrl = aiStreamUrl;
|
|
10
11
|
this.apiKey = apiKey;
|
|
11
12
|
this.workspaceId = workspaceId;
|
|
12
13
|
}
|
|
@@ -15,6 +16,7 @@ class ToothFairyConfig {
|
|
|
15
16
|
return new ToothFairyConfig(
|
|
16
17
|
process.env.TF_BASE_URL || 'https://api.toothfairyai.com',
|
|
17
18
|
process.env.TF_AI_URL || 'https://ai.toothfairyai.com',
|
|
19
|
+
process.env.TF_AI_STREAM_URL || 'https://ais.toothfairyai.com',
|
|
18
20
|
process.env.TF_API_KEY || '',
|
|
19
21
|
process.env.TF_WORKSPACE_ID || ''
|
|
20
22
|
);
|
|
@@ -37,6 +39,7 @@ class ToothFairyConfig {
|
|
|
37
39
|
return new ToothFairyConfig(
|
|
38
40
|
data.base_url || 'https://api.toothfairyai.com',
|
|
39
41
|
data.ai_url || 'https://ai.toothfairyai.com',
|
|
42
|
+
data.ai_stream_url || 'https://ais.toothfairyai.com',
|
|
40
43
|
data.api_key || '',
|
|
41
44
|
data.workspace_id || ''
|
|
42
45
|
);
|
|
@@ -50,6 +53,7 @@ class ToothFairyConfig {
|
|
|
50
53
|
return {
|
|
51
54
|
base_url: this.baseUrl,
|
|
52
55
|
ai_url: this.aiUrl,
|
|
56
|
+
ai_stream_url: this.aiStreamUrl,
|
|
53
57
|
api_key: this.apiKey,
|
|
54
58
|
workspace_id: this.workspaceId
|
|
55
59
|
};
|