@peopl-health/nexus 1.5.8 → 1.5.10
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/CHANGELOG.md +0 -0
- package/LICENSE +0 -0
- package/MIGRATION_GUIDE.md +0 -0
- package/README.md +0 -0
- package/examples/.env.example +0 -0
- package/examples/assistants/BaseAssistant.js +1 -242
- package/examples/assistants/DoctorScheduleAssistant.js +83 -0
- package/examples/assistants/ExampleAssistant.js +0 -0
- package/examples/assistants/index.js +3 -1
- package/examples/basic-usage.js +8 -7
- package/examples/consumer-server.js +0 -0
- package/lib/adapters/BaileysProvider.js +0 -0
- package/lib/adapters/TwilioProvider.js +177 -5
- package/lib/adapters/index.js +0 -0
- package/lib/adapters/registry.js +0 -0
- package/lib/assistants/BaseAssistant.js +294 -0
- package/lib/assistants/index.js +5 -0
- package/lib/config/airtableConfig.js +0 -0
- package/lib/config/awsConfig.js +0 -0
- package/lib/config/configLoader.js +0 -0
- package/lib/config/llmConfig.js +0 -0
- package/lib/config/mongoAuthConfig.js +0 -0
- package/lib/config/runtimeConfig.js +0 -0
- package/lib/controllers/assistantController.js +0 -0
- package/lib/controllers/conversationController.js +0 -0
- package/lib/controllers/mediaController.js +0 -0
- package/lib/controllers/messageController.js +0 -0
- package/lib/controllers/templateController.js +0 -0
- package/lib/controllers/templateFlowController.js +0 -0
- package/lib/controllers/uploadController.js +0 -0
- package/lib/core/MessageProvider.js +0 -0
- package/lib/core/NexusMessaging.js +0 -0
- package/lib/core/index.js +0 -0
- package/lib/helpers/assistantHelper.js +0 -0
- package/lib/helpers/baileysHelper.js +0 -0
- package/lib/helpers/filesHelper.js +0 -0
- package/lib/helpers/llmsHelper.js +0 -0
- package/lib/helpers/mediaHelper.js +0 -0
- package/lib/helpers/mongoHelper.js +0 -0
- package/lib/helpers/qrHelper.js +0 -0
- package/lib/helpers/twilioHelper.js +0 -0
- package/lib/helpers/twilioMediaProcessor.js +0 -0
- package/lib/helpers/whatsappHelper.js +0 -0
- package/lib/index.d.ts +51 -0
- package/lib/index.js +6 -0
- package/lib/interactive/index.js +0 -0
- package/lib/interactive/registry.js +0 -0
- package/lib/interactive/twilioMapper.js +0 -0
- package/lib/models/agendaMessageModel.js +0 -0
- package/lib/models/index.js +0 -0
- package/lib/models/messageModel.js +2 -1
- package/lib/models/templateModel.js +0 -0
- package/lib/models/threadModel.js +0 -0
- package/lib/routes/index.js +0 -0
- package/lib/services/airtableService.js +0 -0
- package/lib/services/assistantService.js +66 -4
- package/lib/services/conversationService.js +0 -0
- package/lib/services/twilioService.js +0 -0
- package/lib/storage/MongoStorage.js +15 -4
- package/lib/storage/NoopStorage.js +0 -0
- package/lib/storage/index.js +0 -0
- package/lib/storage/registry.js +0 -0
- package/lib/templates/predefinedTemplates.js +0 -0
- package/lib/templates/templateStructure.js +0 -0
- package/lib/utils/dateUtils.js +0 -0
- package/lib/utils/defaultLLMProvider.js +0 -0
- package/lib/utils/errorHandler.js +0 -0
- package/lib/utils/index.js +0 -0
- package/lib/utils/logger.js +0 -0
- package/lib/utils/mediaValidator.js +0 -0
- package/lib/utils/messageParser.js +0 -0
- package/package.json +3 -3
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
const llmConfig = require('../config/llmConfig');
|
|
2
|
+
const { Thread } = require('../models/threadModel');
|
|
3
|
+
const { getLastNMessages } = require('../helpers/assistantHelper');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Flexible base assistant implementation that integrates with OpenAI Threads
|
|
7
|
+
* and supports dynamic tool registration.
|
|
8
|
+
*/
|
|
9
|
+
class BaseAssistant {
|
|
10
|
+
constructor(input = {}) {
|
|
11
|
+
const options = this._normalizeOptions(input);
|
|
12
|
+
|
|
13
|
+
this.assistantId = options.assistantId || null;
|
|
14
|
+
this.thread = options.thread || null;
|
|
15
|
+
this.status = options.status || 'idle';
|
|
16
|
+
this.replies = null;
|
|
17
|
+
this.lastMessages = null;
|
|
18
|
+
this.createdAt = new Date();
|
|
19
|
+
|
|
20
|
+
this.client = options.client || llmConfig.openaiClient || null;
|
|
21
|
+
this.tools = new Map();
|
|
22
|
+
|
|
23
|
+
if (Array.isArray(options.tools)) {
|
|
24
|
+
options.tools.forEach((tool) => this.registerTool(tool));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (options.setup && typeof options.setup === 'function') {
|
|
28
|
+
options.setup.call(this, options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (this.assistantId) {
|
|
32
|
+
console.log(`Assistant ${this.assistantId} initialized.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_normalizeOptions(input) {
|
|
37
|
+
if (!input || typeof input !== 'object') {
|
|
38
|
+
return { thread: input };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (input && input._id && !input.assistantId && !input.client && !input.tools) {
|
|
42
|
+
return { thread: input };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return input;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_ensureClient() {
|
|
49
|
+
if (!this.client) {
|
|
50
|
+
throw new Error('LLM client not configured. Ensure openaiClient is initialized.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getAssistantId() {
|
|
55
|
+
return this.assistantId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getStatus() {
|
|
59
|
+
return this.status;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getNextAssistant() {
|
|
63
|
+
return this.nextAssistant || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setThread(thread) {
|
|
67
|
+
this.thread = thread;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setReplies(replies) {
|
|
71
|
+
this.replies = replies;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
registerTool(nameOrConfig, schema, handler) {
|
|
75
|
+
let name;
|
|
76
|
+
let definition;
|
|
77
|
+
let executor;
|
|
78
|
+
|
|
79
|
+
if (typeof nameOrConfig === 'object' && nameOrConfig !== null) {
|
|
80
|
+
name = nameOrConfig.name;
|
|
81
|
+
definition = nameOrConfig.definition || nameOrConfig.schema || nameOrConfig;
|
|
82
|
+
executor = nameOrConfig.handler || nameOrConfig.execute || nameOrConfig.run;
|
|
83
|
+
} else {
|
|
84
|
+
name = nameOrConfig;
|
|
85
|
+
definition = schema;
|
|
86
|
+
executor = handler;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!name || typeof name !== 'string') {
|
|
90
|
+
throw new Error('Tool name is required');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!definition || typeof definition !== 'object') {
|
|
94
|
+
throw new Error(`Tool definition for '${name}' must be an object`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!executor || typeof executor !== 'function') {
|
|
98
|
+
throw new Error(`Tool handler for '${name}' must be a function`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const toolSchema = {
|
|
102
|
+
type: 'function',
|
|
103
|
+
function: {
|
|
104
|
+
name,
|
|
105
|
+
description: definition.description || 'Custom assistant tool',
|
|
106
|
+
parameters: definition.parameters || {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {},
|
|
109
|
+
additionalProperties: true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this.tools.set(name, {
|
|
115
|
+
schema: toolSchema,
|
|
116
|
+
execute: executor
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getToolSchemas() {
|
|
121
|
+
return Array.from(this.tools.values()).map((value) => value.schema);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async executeTool(name, args) {
|
|
125
|
+
const tool = this.tools.get(name);
|
|
126
|
+
if (!tool) {
|
|
127
|
+
throw new Error(`Unknown tool '${name}' requested by assistant`);
|
|
128
|
+
}
|
|
129
|
+
return await tool.execute(args);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async createThread(code, context = {}) {
|
|
133
|
+
return await this.create(code, context);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async sendMessage(userId, message, options = {}) {
|
|
137
|
+
this._ensureClient();
|
|
138
|
+
|
|
139
|
+
if (!this.thread || !this.thread.thread_id) {
|
|
140
|
+
throw new Error('Assistant thread not initialized. Call create() before sendMessage().');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await this.client.beta.threads.messages.create(
|
|
144
|
+
this.thread.thread_id,
|
|
145
|
+
{ role: 'user', content: message }
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const runConfig = {
|
|
149
|
+
assistant_id: this.assistantId,
|
|
150
|
+
...options
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const toolSchemas = this.getToolSchemas();
|
|
154
|
+
if (toolSchemas.length > 0) {
|
|
155
|
+
runConfig.tools = toolSchemas;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const run = await this.client.beta.threads.runs.create(
|
|
159
|
+
this.thread.thread_id,
|
|
160
|
+
runConfig
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return await this.waitForCompletion(this.thread.thread_id, run.id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async waitForCompletion(threadId, runId, { interval = 2000, maxAttempts = 30 } = {}) {
|
|
167
|
+
this._ensureClient();
|
|
168
|
+
let attempts = 0;
|
|
169
|
+
|
|
170
|
+
while (attempts < maxAttempts) {
|
|
171
|
+
const run = await this.client.beta.threads.runs.retrieve(threadId, runId);
|
|
172
|
+
|
|
173
|
+
if (run.status === 'completed') {
|
|
174
|
+
return run;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (run.status === 'requires_action') {
|
|
178
|
+
await this.handleRequiresAction(run);
|
|
179
|
+
} else if (['failed', 'cancelled', 'expired', 'incomplete'].includes(run.status)) {
|
|
180
|
+
throw new Error(`Assistant run ended with status '${run.status}'`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
184
|
+
attempts += 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw new Error('Assistant run timeout');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getPreviousMessages(threadDoc) {
|
|
191
|
+
this._ensureClient();
|
|
192
|
+
const threadRef = threadDoc || this.thread;
|
|
193
|
+
if (!threadRef) return [];
|
|
194
|
+
|
|
195
|
+
const response = await this.client.beta.threads.messages.list(threadRef.thread_id, { order: 'asc' });
|
|
196
|
+
return response.data.map((msg) => {
|
|
197
|
+
const textContents = msg.content
|
|
198
|
+
.filter((part) => part.type === 'text')
|
|
199
|
+
.map((part) => part.text.value);
|
|
200
|
+
const content = textContents.length <= 1 ? textContents[0] || '' : textContents;
|
|
201
|
+
return { role: msg.role, content };
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async create(code, context = {}) {
|
|
206
|
+
this._ensureClient();
|
|
207
|
+
this.status = 'active';
|
|
208
|
+
|
|
209
|
+
const whatsappId = context?.whatsapp_id || code;
|
|
210
|
+
if (whatsappId) {
|
|
211
|
+
this.lastMessages = await getLastNMessages(whatsappId, 20);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const initialMessages = await this.buildInitialMessages({ code, context });
|
|
215
|
+
const threadPayload = {};
|
|
216
|
+
|
|
217
|
+
if (initialMessages.length > 0) {
|
|
218
|
+
threadPayload.messages = initialMessages.map((msg) => ({
|
|
219
|
+
role: msg.role || 'assistant',
|
|
220
|
+
content: msg.content
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const thread = await this.client.beta.threads.create(threadPayload);
|
|
225
|
+
this.thread = thread;
|
|
226
|
+
return thread;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async buildInitialMessages({ code }) {
|
|
230
|
+
if (!this.lastMessages) return [];
|
|
231
|
+
return [{
|
|
232
|
+
role: 'assistant',
|
|
233
|
+
content: `Últimos mensajes para ${code}:
|
|
234
|
+
${this.lastMessages}`
|
|
235
|
+
}];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async handleRequiresAction(run) {
|
|
239
|
+
const toolCalls = run?.required_action?.submit_tool_outputs?.tool_calls || [];
|
|
240
|
+
if (toolCalls.length === 0) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const outputs = [];
|
|
245
|
+
|
|
246
|
+
for (const call of toolCalls) {
|
|
247
|
+
try {
|
|
248
|
+
const name = call.function?.name;
|
|
249
|
+
const args = call.function?.arguments ? JSON.parse(call.function.arguments) : {};
|
|
250
|
+
const result = await this.executeTool(name, args);
|
|
251
|
+
outputs.push({ tool_call_id: call.id, output: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('[BaseAssistant] Tool execution failed', error);
|
|
254
|
+
outputs.push({
|
|
255
|
+
tool_call_id: call.id,
|
|
256
|
+
output: JSON.stringify({ success: false, error: error?.message || 'Tool execution failed' })
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!this.client) {
|
|
262
|
+
console.warn('[BaseAssistant] Cannot submit tool outputs: client not configured');
|
|
263
|
+
return outputs;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await this.client.beta.threads.runs.submitToolOutputs(
|
|
267
|
+
run.thread_id,
|
|
268
|
+
run.id,
|
|
269
|
+
{ tool_outputs: outputs }
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return outputs;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async close() {
|
|
276
|
+
this.status = 'closed';
|
|
277
|
+
if (this.thread?.code && this.thread?.assistant_id) {
|
|
278
|
+
await Thread.updateOne(
|
|
279
|
+
{ code: this.thread.code, assistant_id: this.thread.assistant_id },
|
|
280
|
+
{ $set: { active: false } }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (this.assistantId) {
|
|
285
|
+
console.log(`Assistant ${this.assistantId} closed`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return `Assistant ${this.assistantId || ''} successfully closed.`.trim();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
BaseAssistant
|
|
294
|
+
};
|
|
File without changes
|
package/lib/config/awsConfig.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/config/llmConfig.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/core/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/helpers/qrHelper.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/index.d.ts
CHANGED
|
@@ -117,6 +117,48 @@ declare module '@peopl-health/nexus' {
|
|
|
117
117
|
onFlow?: FlowHandler;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export interface AssistantToolDefinition {
|
|
121
|
+
name: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
parameters?: any;
|
|
124
|
+
handler: (args: any) => any;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface AssistantConfigDefinition {
|
|
128
|
+
extends?: typeof BaseAssistant;
|
|
129
|
+
create?: (this: BaseAssistant, code: string, context?: any) => Promise<any>;
|
|
130
|
+
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) => any }>;
|
|
131
|
+
setup?: (this: BaseAssistant, context: { assistantId: string; thread?: any; options?: any }) => void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export class BaseAssistant {
|
|
135
|
+
constructor(options?: {
|
|
136
|
+
assistantId?: string;
|
|
137
|
+
thread?: any;
|
|
138
|
+
client?: any;
|
|
139
|
+
tools?: Array<AssistantToolDefinition | { name: string; definition?: any; handler: (args: any) => any }>;
|
|
140
|
+
setup?: (context: { assistantId: string; thread?: any; options?: any }) => void;
|
|
141
|
+
status?: string;
|
|
142
|
+
} | any);
|
|
143
|
+
assistantId: string | null;
|
|
144
|
+
thread: any;
|
|
145
|
+
status: string;
|
|
146
|
+
createdAt: Date;
|
|
147
|
+
registerTool(definition: AssistantToolDefinition | string, schema?: any, handler?: (args: any) => any): void;
|
|
148
|
+
getToolSchemas(): any[];
|
|
149
|
+
executeTool(name: string, args: any): Promise<any>;
|
|
150
|
+
getPreviousMessages(thread?: any): Promise<Array<{ role: string; content: any }>>;
|
|
151
|
+
createThread(code: string, context?: any): Promise<any>;
|
|
152
|
+
sendMessage(userId: string, message: string, options?: any): Promise<any>;
|
|
153
|
+
waitForCompletion(threadId: string, runId: string, opts?: { interval?: number; maxAttempts?: number }): Promise<any>;
|
|
154
|
+
create(code: string, context?: any): Promise<any>;
|
|
155
|
+
buildInitialMessages(context: { code: string; [key: string]: any }): Promise<Array<{ role: string; content: string }>>;
|
|
156
|
+
handleRequiresAction(run: any): Promise<any[]>;
|
|
157
|
+
close(): Promise<string>;
|
|
158
|
+
setThread(thread: any): void;
|
|
159
|
+
setReplies(replies: any): void;
|
|
160
|
+
}
|
|
161
|
+
|
|
120
162
|
// Core Classes
|
|
121
163
|
export abstract class MessageProvider {
|
|
122
164
|
constructor(config: any);
|
|
@@ -127,6 +169,15 @@ declare module '@peopl-health/nexus' {
|
|
|
127
169
|
abstract disconnect(): Promise<void>;
|
|
128
170
|
}
|
|
129
171
|
|
|
172
|
+
export function registerAssistant(
|
|
173
|
+
assistantId: string,
|
|
174
|
+
definition: typeof BaseAssistant | ((thread?: any) => any) | AssistantConfigDefinition
|
|
175
|
+
): any;
|
|
176
|
+
|
|
177
|
+
export function configureAssistantsLLM(client: any): void;
|
|
178
|
+
export function overrideGetAssistantById(resolver: (assistantId: string, thread?: any) => any): void;
|
|
179
|
+
export function configureAssistants(config: any): void;
|
|
180
|
+
|
|
130
181
|
export class TwilioProvider extends MessageProvider {
|
|
131
182
|
constructor(config: TwilioConfig);
|
|
132
183
|
initialize(): Promise<void>;
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
} = require('./services/assistantService');
|
|
17
17
|
const { TwilioProvider } = require('./adapters/TwilioProvider');
|
|
18
18
|
const { BaileysProvider } = require('./adapters/BaileysProvider');
|
|
19
|
+
const { BaseAssistant: CoreBaseAssistant } = require('./assistants/BaseAssistant');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Main Nexus class that orchestrates all components
|
|
@@ -312,6 +313,11 @@ module.exports = {
|
|
|
312
313
|
MongoStorage,
|
|
313
314
|
MessageParser,
|
|
314
315
|
DefaultLLMProvider,
|
|
316
|
+
BaseAssistant: CoreBaseAssistant,
|
|
317
|
+
registerAssistant,
|
|
318
|
+
configureAssistantsLLM,
|
|
319
|
+
overrideGetAssistantById,
|
|
320
|
+
configureAssistants: setAssistantsConfig,
|
|
315
321
|
routes,
|
|
316
322
|
setupDefaultRoutes: routes.setupDefaultRoutes,
|
|
317
323
|
createRouter: routes.createRouter,
|
package/lib/interactive/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/models/index.js
CHANGED
|
File without changes
|
|
@@ -68,7 +68,8 @@ async function insertMessage(values) {
|
|
|
68
68
|
reply_id: values.reply_id,
|
|
69
69
|
from_me: values.from_me,
|
|
70
70
|
processed: skipNumbers.includes(values.numero),
|
|
71
|
-
media: values.media ? values.media : null
|
|
71
|
+
media: values.media ? values.media : null,
|
|
72
|
+
content_sid: values.content_sid || null
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
await Message.findOneAndUpdate(
|
|
File without changes
|
|
File without changes
|
package/lib/routes/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -3,6 +3,8 @@ const AWS = require('../config/awsConfig.js');
|
|
|
3
3
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
4
4
|
const { addRecord } = require('../services/airtableService.js');
|
|
5
5
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
6
|
+
const llmConfig = require('../config/llmConfig');
|
|
7
|
+
const { BaseAssistant } = require('../assistants/BaseAssistant');
|
|
6
8
|
|
|
7
9
|
let llmProvider = null;
|
|
8
10
|
const configureLLMProvider = (provider) => {
|
|
@@ -27,8 +29,54 @@ const configureAssistants = (config) => {
|
|
|
27
29
|
assistantConfig = config;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
const registerAssistant = (assistantId,
|
|
31
|
-
|
|
32
|
+
const registerAssistant = (assistantId, definition) => {
|
|
33
|
+
if (!assistantId || typeof assistantId !== 'string') {
|
|
34
|
+
throw new Error('registerAssistant requires a string assistantId');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof definition === 'function') {
|
|
38
|
+
assistantRegistry[assistantId] = definition;
|
|
39
|
+
return definition;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (definition && typeof definition === 'object') {
|
|
43
|
+
const {
|
|
44
|
+
extends: ParentClass = BaseAssistant,
|
|
45
|
+
create,
|
|
46
|
+
tools = [],
|
|
47
|
+
setup
|
|
48
|
+
} = definition;
|
|
49
|
+
|
|
50
|
+
class ConfiguredAssistant extends ParentClass {
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
super({
|
|
53
|
+
...options,
|
|
54
|
+
assistantId,
|
|
55
|
+
client: options.client || llmProvider || llmConfig.openaiClient || null,
|
|
56
|
+
tools: [...tools, ...(options.tools || [])]
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (typeof setup === 'function') {
|
|
60
|
+
setup.call(this, {
|
|
61
|
+
assistantId,
|
|
62
|
+
thread: this.thread,
|
|
63
|
+
options
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof create === 'function') {
|
|
70
|
+
ConfiguredAssistant.prototype.create = async function(code, context) {
|
|
71
|
+
return await create.call(this, code, context);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
assistantRegistry[assistantId] = ConfiguredAssistant;
|
|
76
|
+
return ConfiguredAssistant;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error('registerAssistant expects a class/function or configuration object');
|
|
32
80
|
};
|
|
33
81
|
|
|
34
82
|
const overrideGetAssistantById = (resolverFn) => {
|
|
@@ -51,8 +99,22 @@ const getAssistantById = (assistant_id, thread) => {
|
|
|
51
99
|
if (!AssistantClass) {
|
|
52
100
|
throw new Error(`Assistant '${assistant_id}' not found. Available assistants: ${Object.keys(assistantRegistry).join(', ')}`);
|
|
53
101
|
}
|
|
54
|
-
|
|
55
|
-
|
|
102
|
+
|
|
103
|
+
const sharedClient = llmProvider || llmConfig.openaiClient || null;
|
|
104
|
+
|
|
105
|
+
if (AssistantClass.prototype instanceof BaseAssistant) {
|
|
106
|
+
return new AssistantClass({
|
|
107
|
+
assistantId: assistant_id,
|
|
108
|
+
thread,
|
|
109
|
+
client: sharedClient
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
return new AssistantClass(thread);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return new AssistantClass({ thread, assistantId: assistant_id, client: sharedClient });
|
|
117
|
+
}
|
|
56
118
|
};
|
|
57
119
|
|
|
58
120
|
|
|
File without changes
|
|
File without changes
|
|
@@ -66,10 +66,11 @@ class MongoStorage {
|
|
|
66
66
|
from: messageData?.from,
|
|
67
67
|
provider: messageData?.provider || 'unknown',
|
|
68
68
|
hasRaw: Boolean(messageData?.raw),
|
|
69
|
-
hasMedia: Boolean(messageData?.media || messageData?.fileUrl)
|
|
69
|
+
hasMedia: Boolean(messageData?.media || messageData?.fileUrl),
|
|
70
|
+
hasContentSid: Boolean(messageData?.contentSid)
|
|
70
71
|
});
|
|
71
72
|
const enrichedMessage = await this._enrichTwilioMedia(messageData);
|
|
72
|
-
const values = this.
|
|
73
|
+
const values = this.buildMessageValues(enrichedMessage);
|
|
73
74
|
const { insertMessage } = require('../models/messageModel');
|
|
74
75
|
await insertMessage(values);
|
|
75
76
|
console.log('[MongoStorage] Message stored', {
|
|
@@ -166,7 +167,8 @@ class MongoStorage {
|
|
|
166
167
|
return trimmed;
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
|
|
171
|
+
buildMessageValues(messageData = {}) {
|
|
170
172
|
const numero = messageData.to || messageData.code || messageData.numero || messageData.from;
|
|
171
173
|
const rawNumero = typeof numero === 'string' ? numero : '';
|
|
172
174
|
const normalizedNumero = this.normalizeNumero(rawNumero);
|
|
@@ -175,7 +177,16 @@ class MongoStorage {
|
|
|
175
177
|
const now = new Date();
|
|
176
178
|
const timestamp = now.toISOString();
|
|
177
179
|
const nombre = messageData.nombre_whatsapp || messageData.author || messageData.fromName || runtimeConfig.get('USER_DB_MONGO') || process.env.USER_DB_MONGO || 'Nexus';
|
|
178
|
-
|
|
180
|
+
|
|
181
|
+
// Use message body directly (template rendering is now handled by the provider)
|
|
182
|
+
let textBody = messageData.message || messageData.body;
|
|
183
|
+
|
|
184
|
+
if (!textBody && isMedia) {
|
|
185
|
+
textBody = `[Media:${messageData.fileType || 'attachment'}]`;
|
|
186
|
+
} else if (!textBody) {
|
|
187
|
+
textBody = '';
|
|
188
|
+
}
|
|
189
|
+
|
|
179
190
|
const providerId = messageData.messageId || messageData.sid || messageData.id || messageData._id || `pending-${now.getTime()}-${Math.floor(Math.random()*1000)}`;
|
|
180
191
|
|
|
181
192
|
const media = messageData.media || (messageData.fileUrl ? {
|
|
File without changes
|
package/lib/storage/index.js
CHANGED
|
File without changes
|
package/lib/storage/registry.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/utils/dateUtils.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/utils/index.js
CHANGED
|
File without changes
|
package/lib/utils/logger.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peopl-health/nexus",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.10",
|
|
4
4
|
"description": "Core messaging and assistant library for WhatsApp communication platforms",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whatsapp",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"airtable": "^0.12.2",
|
|
74
74
|
"aws-sdk": "2.1674.0",
|
|
75
75
|
"axios": "^1.5.0",
|
|
76
|
-
"dotenv": "^16.
|
|
76
|
+
"dotenv": "^16.6.1",
|
|
77
77
|
"moment-timezone": "^0.5.43",
|
|
78
78
|
"mongoose": "^7.5.0",
|
|
79
79
|
"multer": "1.4.5-lts.1",
|
|
@@ -102,4 +102,4 @@
|
|
|
102
102
|
"publishConfig": {
|
|
103
103
|
"access": "public"
|
|
104
104
|
}
|
|
105
|
-
}
|
|
105
|
+
}
|