@peopl-health/nexus 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/MIGRATION_GUIDE.md +388 -0
- package/README.md +532 -0
- package/examples/.env.example +24 -0
- package/examples/assistants/BaseAssistant.js +242 -0
- package/examples/assistants/ExampleAssistant.js +111 -0
- package/examples/assistants/index.js +7 -0
- package/examples/basic-usage.js +109 -0
- package/examples/consumer-server.js +206 -0
- package/lib/adapters/BaileysProvider.js +180 -0
- package/lib/adapters/TwilioProvider.js +118 -0
- package/lib/adapters/index.js +7 -0
- package/lib/core/MessageProvider.js +71 -0
- package/lib/core/NexusMessaging.js +231 -0
- package/lib/core/index.js +7 -0
- package/lib/index.d.ts +276 -0
- package/lib/index.js +204 -0
- package/lib/models/index.js +9 -0
- package/lib/models/messageModel.js +91 -0
- package/lib/models/threadModel.js +20 -0
- package/lib/storage/MongoStorage.js +183 -0
- package/lib/storage/index.js +5 -0
- package/lib/utils/AssistantManager.js +218 -0
- package/lib/utils/DefaultLLMProvider.js +22 -0
- package/lib/utils/MessageParser.js +249 -0
- package/lib/utils/index.js +22 -0
- package/lib/utils/logger.js +22 -0
- package/lib/utils/mongoAuthConfig.js +139 -0
- package/lib/utils/twilioHelper.js +77 -0
- package/lib/utils/whatsappHelper.js +68 -0
- package/package.json +82 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Assistant class for consumer server extensions
|
|
3
|
+
* Provides common functionality for AI assistant implementations
|
|
4
|
+
*/
|
|
5
|
+
class BaseAssistant {
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.assistantId = config.assistantId;
|
|
8
|
+
this.llmClient = config.llmClient;
|
|
9
|
+
this.storage = config.storage;
|
|
10
|
+
this.nexus = config.nexus;
|
|
11
|
+
this.functions = new Map();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Register a function that can be called by the assistant
|
|
16
|
+
* @param {string} name - Function name
|
|
17
|
+
* @param {Function} handler - Function handler
|
|
18
|
+
* @param {Object} schema - Function schema for LLM
|
|
19
|
+
*/
|
|
20
|
+
registerFunction(name, handler, schema) {
|
|
21
|
+
this.functions.set(name, { handler, schema });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all registered function schemas for LLM
|
|
26
|
+
* @returns {Array} Function schemas
|
|
27
|
+
*/
|
|
28
|
+
getFunctionSchemas() {
|
|
29
|
+
return Array.from(this.functions.values()).map(f => f.schema);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execute a function call from the assistant
|
|
34
|
+
* @param {string} name - Function name
|
|
35
|
+
* @param {Object} args - Function arguments
|
|
36
|
+
* @returns {Promise<any>} Function result
|
|
37
|
+
*/
|
|
38
|
+
async executeFunction(name, args) {
|
|
39
|
+
const func = this.functions.get(name);
|
|
40
|
+
if (!func) {
|
|
41
|
+
throw new Error(`Unknown function: ${name}`);
|
|
42
|
+
}
|
|
43
|
+
return await func.handler(args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a new thread for this assistant
|
|
48
|
+
* @param {string} userId - User identifier
|
|
49
|
+
* @param {Array} initialMessages - Initial messages
|
|
50
|
+
* @returns {Promise<Object>} Thread data
|
|
51
|
+
*/
|
|
52
|
+
async createThread(userId, initialMessages = []) {
|
|
53
|
+
if (!this.llmClient) {
|
|
54
|
+
throw new Error('LLM client not configured');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const thread = await this.llmClient.beta.threads.create();
|
|
58
|
+
|
|
59
|
+
// Add initial messages if provided
|
|
60
|
+
for (const message of initialMessages) {
|
|
61
|
+
await this.llmClient.beta.threads.messages.create(
|
|
62
|
+
thread.id,
|
|
63
|
+
{ role: "assistant", content: message }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const threadData = {
|
|
68
|
+
code: userId,
|
|
69
|
+
assistantId: this.assistantId,
|
|
70
|
+
threadId: thread.id,
|
|
71
|
+
active: true,
|
|
72
|
+
createdAt: new Date()
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Save to storage if available
|
|
76
|
+
if (this.storage) {
|
|
77
|
+
await this.storage.createThread(threadData);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return threadData;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Send message to assistant and handle response
|
|
85
|
+
* @param {string} userId - User identifier
|
|
86
|
+
* @param {string} message - User message
|
|
87
|
+
* @param {Object} options - Additional options
|
|
88
|
+
* @returns {Promise<string>} Assistant response
|
|
89
|
+
*/
|
|
90
|
+
async sendMessage(userId, message, options = {}) {
|
|
91
|
+
if (!this.llmClient) {
|
|
92
|
+
throw new Error('LLM client not configured');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get or create thread
|
|
96
|
+
let threadData = null;
|
|
97
|
+
if (this.storage) {
|
|
98
|
+
threadData = await this.storage.getThread(userId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!threadData) {
|
|
102
|
+
threadData = await this.createThread(userId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add user message to thread
|
|
106
|
+
await this.llmClient.beta.threads.messages.create(
|
|
107
|
+
threadData.threadId,
|
|
108
|
+
{ role: "user", content: message }
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Create run with function schemas if available
|
|
112
|
+
const runConfig = {
|
|
113
|
+
assistant_id: this.assistantId,
|
|
114
|
+
...options
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const functionSchemas = this.getFunctionSchemas();
|
|
118
|
+
if (functionSchemas.length > 0) {
|
|
119
|
+
runConfig.tools = functionSchemas;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const run = await this.llmClient.beta.threads.runs.create(
|
|
123
|
+
threadData.threadId,
|
|
124
|
+
runConfig
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Wait for completion and handle function calls
|
|
128
|
+
const result = await this.waitForCompletion(threadData.threadId, run.id);
|
|
129
|
+
|
|
130
|
+
if (result.status === 'completed') {
|
|
131
|
+
const messages = await this.llmClient.beta.threads.messages.list(
|
|
132
|
+
threadData.threadId,
|
|
133
|
+
{ run_id: run.id }
|
|
134
|
+
);
|
|
135
|
+
return messages.data[0]?.content[0]?.text?.value || "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Wait for run completion and handle function calls
|
|
143
|
+
* @param {string} threadId - Thread ID
|
|
144
|
+
* @param {string} runId - Run ID
|
|
145
|
+
* @returns {Promise<Object>} Run result
|
|
146
|
+
*/
|
|
147
|
+
async waitForCompletion(threadId, runId) {
|
|
148
|
+
const maxAttempts = 30;
|
|
149
|
+
let attempts = 0;
|
|
150
|
+
|
|
151
|
+
while (attempts < maxAttempts) {
|
|
152
|
+
const run = await this.llmClient.beta.threads.runs.retrieve(threadId, runId);
|
|
153
|
+
|
|
154
|
+
if (run.status === 'completed') {
|
|
155
|
+
return { status: 'completed', run };
|
|
156
|
+
} else if (run.status === 'requires_action') {
|
|
157
|
+
// Handle function calls
|
|
158
|
+
const toolOutputs = await this.handleRequiredActions(run);
|
|
159
|
+
|
|
160
|
+
if (toolOutputs.length > 0) {
|
|
161
|
+
await this.llmClient.beta.threads.runs.submitToolOutputs(
|
|
162
|
+
threadId,
|
|
163
|
+
runId,
|
|
164
|
+
{ tool_outputs: toolOutputs }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Continue waiting for completion
|
|
168
|
+
attempts = 0; // Reset attempts after submitting outputs
|
|
169
|
+
await this.delay(2000);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
} else if (run.status === 'failed' || run.status === 'cancelled' || run.status === 'expired') {
|
|
173
|
+
return { status: 'failed', run };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
await this.delay(2000);
|
|
177
|
+
attempts++;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
throw new Error('Assistant run timeout');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handle required actions (function calls)
|
|
185
|
+
* @param {Object} run - Run object with required actions
|
|
186
|
+
* @returns {Promise<Array>} Tool outputs
|
|
187
|
+
*/
|
|
188
|
+
async handleRequiredActions(run) {
|
|
189
|
+
const toolCalls = run.required_action?.submit_tool_outputs?.tool_calls || [];
|
|
190
|
+
const toolOutputs = [];
|
|
191
|
+
|
|
192
|
+
for (const toolCall of toolCalls) {
|
|
193
|
+
try {
|
|
194
|
+
const functionName = toolCall.function.name;
|
|
195
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
196
|
+
|
|
197
|
+
const result = await this.executeFunction(functionName, args);
|
|
198
|
+
|
|
199
|
+
toolOutputs.push({
|
|
200
|
+
tool_call_id: toolCall.id,
|
|
201
|
+
output: JSON.stringify(result)
|
|
202
|
+
});
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`Error executing function ${toolCall.function.name}:`, error);
|
|
205
|
+
toolOutputs.push({
|
|
206
|
+
tool_call_id: toolCall.id,
|
|
207
|
+
output: JSON.stringify({ error: error.message })
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return toolOutputs;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Send a message through Nexus
|
|
217
|
+
* @param {string} to - Recipient
|
|
218
|
+
* @param {string} message - Message text
|
|
219
|
+
* @param {Object} options - Additional options
|
|
220
|
+
*/
|
|
221
|
+
async sendNexusMessage(to, message, options = {}) {
|
|
222
|
+
if (!this.nexus) {
|
|
223
|
+
throw new Error('Nexus instance not provided');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return await this.nexus.sendMessage({
|
|
227
|
+
to,
|
|
228
|
+
message,
|
|
229
|
+
...options
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Utility delay function
|
|
235
|
+
* @param {number} ms - Milliseconds to delay
|
|
236
|
+
*/
|
|
237
|
+
delay(ms) {
|
|
238
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = { BaseAssistant };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const { BaseAssistant } = require('./BaseAssistant');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Assistant implementation showing how to extend BaseAssistant
|
|
5
|
+
*/
|
|
6
|
+
class ExampleAssistant extends BaseAssistant {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
super(config);
|
|
9
|
+
|
|
10
|
+
// Register custom functions
|
|
11
|
+
this.registerFunction('get_weather', this.getWeather.bind(this), {
|
|
12
|
+
type: "function",
|
|
13
|
+
function: {
|
|
14
|
+
name: "get_weather",
|
|
15
|
+
description: "Get current weather for a location",
|
|
16
|
+
parameters: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
location: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The city and state, e.g. San Francisco, CA"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ["location"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.registerFunction('schedule_reminder', this.scheduleReminder.bind(this), {
|
|
30
|
+
type: "function",
|
|
31
|
+
function: {
|
|
32
|
+
name: "schedule_reminder",
|
|
33
|
+
description: "Schedule a reminder for the user",
|
|
34
|
+
parameters: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
message: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "The reminder message"
|
|
40
|
+
},
|
|
41
|
+
datetime: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "When to send the reminder (ISO format)"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
required: ["message", "datetime"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get weather information (mock implementation)
|
|
54
|
+
* @param {Object} args - Function arguments
|
|
55
|
+
* @returns {Promise<Object>} Weather data
|
|
56
|
+
*/
|
|
57
|
+
async getWeather(args) {
|
|
58
|
+
const { location } = args;
|
|
59
|
+
|
|
60
|
+
// Mock weather data - replace with actual weather API
|
|
61
|
+
return {
|
|
62
|
+
location,
|
|
63
|
+
temperature: "22°C",
|
|
64
|
+
condition: "Sunny",
|
|
65
|
+
humidity: "65%",
|
|
66
|
+
timestamp: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Schedule a reminder (mock implementation)
|
|
72
|
+
* @param {Object} args - Function arguments
|
|
73
|
+
* @returns {Promise<Object>} Scheduling result
|
|
74
|
+
*/
|
|
75
|
+
async scheduleReminder(args) {
|
|
76
|
+
const { message, datetime } = args;
|
|
77
|
+
|
|
78
|
+
// Mock scheduling - replace with actual scheduling logic
|
|
79
|
+
console.log(`Scheduling reminder: "${message}" for ${datetime}`);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
reminderId: `reminder_${Date.now()}`,
|
|
84
|
+
scheduledFor: datetime,
|
|
85
|
+
message
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handle incoming messages with custom logic
|
|
91
|
+
* @param {string} userId - User identifier
|
|
92
|
+
* @param {string} message - User message
|
|
93
|
+
* @returns {Promise<string>} Response
|
|
94
|
+
*/
|
|
95
|
+
async handleMessage(userId, message) {
|
|
96
|
+
// Add custom pre-processing logic here
|
|
97
|
+
console.log(`ExampleAssistant handling message from ${userId}: ${message}`);
|
|
98
|
+
|
|
99
|
+
// Call parent method to handle LLM interaction
|
|
100
|
+
const response = await this.sendMessage(userId, message);
|
|
101
|
+
|
|
102
|
+
// Add custom post-processing logic here
|
|
103
|
+
if (response) {
|
|
104
|
+
console.log(`ExampleAssistant responding to ${userId}: ${response}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return response;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { ExampleAssistant };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { Nexus } = require('@peopl/nexus');
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const nexus = new Nexus();
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
// Initialize with defaults (twilio provider, mongo storage, openai llm)
|
|
9
|
+
await nexus.initialize({
|
|
10
|
+
providerConfig: {
|
|
11
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID,
|
|
12
|
+
authToken: process.env.TWILIO_AUTH_TOKEN,
|
|
13
|
+
phoneNumber: process.env.TWILIO_PHONE_NUMBER
|
|
14
|
+
},
|
|
15
|
+
// storage: 'mongo' is default
|
|
16
|
+
storageConfig: {
|
|
17
|
+
uri: process.env.MONGODB_URI,
|
|
18
|
+
dbName: 'nexus_basic'
|
|
19
|
+
},
|
|
20
|
+
// parser: 'MessageParser' is default
|
|
21
|
+
parserConfig: {
|
|
22
|
+
commandPrefixes: ['/', '!'],
|
|
23
|
+
keywords: ['help', 'support', 'info'],
|
|
24
|
+
flowTriggers: ['start survey', 'begin onboarding']
|
|
25
|
+
},
|
|
26
|
+
// llm: 'openai' is default (optional for basic usage)
|
|
27
|
+
llmConfig: {
|
|
28
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Set up message handlers
|
|
33
|
+
nexus.onMessage(async (messageData) => {
|
|
34
|
+
console.log(`Received message from ${messageData.from}: ${messageData.message}`);
|
|
35
|
+
|
|
36
|
+
// Simple echo response
|
|
37
|
+
await nexus.sendMessage({
|
|
38
|
+
to: messageData.from,
|
|
39
|
+
message: `You said: ${messageData.message}`
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
nexus.onCommand(async (messageData) => {
|
|
44
|
+
const { command, args } = messageData.command;
|
|
45
|
+
|
|
46
|
+
switch (command) {
|
|
47
|
+
case 'help':
|
|
48
|
+
await nexus.sendMessage({
|
|
49
|
+
to: messageData.from,
|
|
50
|
+
message: 'Available commands:\n/help - Show this help\n/status - Check system status\n/echo [text] - Echo your text'
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
case 'status':
|
|
54
|
+
await nexus.sendMessage({
|
|
55
|
+
to: messageData.from,
|
|
56
|
+
message: `System Status: ${nexus.isConnected() ? 'Connected' : 'Disconnected'}`
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
case 'echo':
|
|
60
|
+
const text = args.join(' ') || 'Nothing to echo!';
|
|
61
|
+
await nexus.sendMessage({
|
|
62
|
+
to: messageData.from,
|
|
63
|
+
message: `Echo: ${text}`
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
await nexus.sendMessage({
|
|
68
|
+
to: messageData.from,
|
|
69
|
+
message: `Unknown command: ${command}. Type /help for available commands.`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
nexus.onKeyword(async (messageData) => {
|
|
75
|
+
const keyword = messageData.keyword;
|
|
76
|
+
|
|
77
|
+
switch (keyword) {
|
|
78
|
+
case 'help':
|
|
79
|
+
case 'support':
|
|
80
|
+
await nexus.sendMessage({
|
|
81
|
+
to: messageData.from,
|
|
82
|
+
message: 'Hello! I can help you with:\n- Basic messaging\n- Commands (try /help)\n- Keyword responses\n\nWhat would you like to know?'
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
case 'info':
|
|
86
|
+
await nexus.sendMessage({
|
|
87
|
+
to: messageData.from,
|
|
88
|
+
message: 'This is a basic Nexus messaging bot example. It demonstrates simple message handling without AI assistants.'
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log('Nexus basic example started successfully');
|
|
95
|
+
console.log('Send messages to your configured phone number to test');
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error starting Nexus:', error);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Handle graceful shutdown
|
|
104
|
+
process.on('SIGINT', async () => {
|
|
105
|
+
console.log('\nShutting down gracefully...');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const { Nexus } = require('@peopl/nexus');
|
|
2
|
+
const { ExampleAssistant } = require('./assistants/ExampleAssistant');
|
|
3
|
+
const express = require('express');
|
|
4
|
+
require('dotenv').config();
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
app.use(express.urlencoded({ extended: false }));
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const nexus = new Nexus();
|
|
12
|
+
let assistants = {};
|
|
13
|
+
|
|
14
|
+
async function initializeNexus() {
|
|
15
|
+
await nexus.initialize({
|
|
16
|
+
provider: 'twilio',
|
|
17
|
+
providerConfig: {
|
|
18
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID,
|
|
19
|
+
authToken: process.env.TWILIO_AUTH_TOKEN,
|
|
20
|
+
phoneNumber: process.env.TWILIO_PHONE_NUMBER
|
|
21
|
+
},
|
|
22
|
+
storageConfig: {
|
|
23
|
+
uri: process.env.MONGODB_URI,
|
|
24
|
+
dbName: 'nexus_messaging'
|
|
25
|
+
},
|
|
26
|
+
llmConfig: {
|
|
27
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
assistants = {
|
|
32
|
+
sales: new ExampleAssistant({
|
|
33
|
+
assistantId: process.env.SALES_ASSISTANT_ID,
|
|
34
|
+
llmClient: nexus.getLLMProvider().getClient(),
|
|
35
|
+
storage: nexus.getStorage(),
|
|
36
|
+
nexus: nexus
|
|
37
|
+
}),
|
|
38
|
+
support: new ExampleAssistant({
|
|
39
|
+
assistantId: process.env.SUPPORT_ASSISTANT_ID,
|
|
40
|
+
llmClient: nexus.getLLMProvider().getClient(),
|
|
41
|
+
storage: nexus.getStorage(),
|
|
42
|
+
nexus: nexus
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
nexus.onMessage(async (messageData) => {
|
|
47
|
+
console.log('Received message:', messageData);
|
|
48
|
+
|
|
49
|
+
if (messageData.type === 'text') {
|
|
50
|
+
const response = await processTextMessage(messageData);
|
|
51
|
+
if (response) {
|
|
52
|
+
await nexus.sendMessage({
|
|
53
|
+
to: messageData.from,
|
|
54
|
+
message: response
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
nexus.onCommand(async (messageData) => {
|
|
61
|
+
const { command, args } = messageData.command;
|
|
62
|
+
|
|
63
|
+
switch (command) {
|
|
64
|
+
case 'help':
|
|
65
|
+
await nexus.sendMessage({
|
|
66
|
+
to: messageData.from,
|
|
67
|
+
message: `🤖 Available Commands:
|
|
68
|
+
/help - Show this help
|
|
69
|
+
/support - Connect to support assistant
|
|
70
|
+
/sales - Connect to sales assistant
|
|
71
|
+
/status - Check system status
|
|
72
|
+
/reset - Reset conversation`
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 'support':
|
|
77
|
+
const supportResponse = await assistants.support.handleMessage(messageData.from, 'Hello, I need support.');
|
|
78
|
+
if (supportResponse) {
|
|
79
|
+
await nexus.sendMessage({
|
|
80
|
+
to: messageData.from,
|
|
81
|
+
message: supportResponse
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'sales':
|
|
87
|
+
const salesResponse = await assistants.sales.handleMessage(messageData.from, 'Hello, I\'m interested in your products.');
|
|
88
|
+
if (salesResponse) {
|
|
89
|
+
await nexus.sendMessage({
|
|
90
|
+
to: messageData.from,
|
|
91
|
+
message: salesResponse
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 'status':
|
|
97
|
+
await nexus.sendMessage({
|
|
98
|
+
to: messageData.from,
|
|
99
|
+
message: `System Status: ${nexus.isConnected() ? '✅ Connected' : '❌ Disconnected'}`
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case 'reset':
|
|
104
|
+
if (nexus.getStorage()) {
|
|
105
|
+
await nexus.getStorage().clearThread(messageData.from);
|
|
106
|
+
}
|
|
107
|
+
await nexus.sendMessage({
|
|
108
|
+
to: messageData.from,
|
|
109
|
+
message: 'Conversation reset! You can start fresh now.'
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
await nexus.sendMessage({
|
|
115
|
+
to: messageData.from,
|
|
116
|
+
message: `Unknown command: /${command}. Type /help for available commands.`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log('🚀 Nexus consumer server initialized successfully');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Webhook endpoints - provider agnostic
|
|
125
|
+
app.post('/webhook', async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
await nexus.processMessage(req.body);
|
|
128
|
+
res.sendStatus(200);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Webhook error:', error);
|
|
131
|
+
res.sendStatus(500);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// API endpoints
|
|
136
|
+
app.post('/api/send-message', async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const { to, message, fileUrl, fileType } = req.body;
|
|
139
|
+
|
|
140
|
+
const result = await nexus.sendMessage({
|
|
141
|
+
to,
|
|
142
|
+
message,
|
|
143
|
+
fileUrl,
|
|
144
|
+
fileType
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
res.json({ success: true, result });
|
|
148
|
+
} catch (error) {
|
|
149
|
+
res.status(500).json({ success: false, error: error.message });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
app.post('/api/send-template', async (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
const { to, contentSid, variables } = req.body;
|
|
156
|
+
|
|
157
|
+
const result = await nexus.sendMessage({
|
|
158
|
+
to,
|
|
159
|
+
contentSid,
|
|
160
|
+
variables
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.json({ success: true, result });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
res.status(500).json({ success: false, error: error.message });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
app.get('/api/status', (req, res) => {
|
|
170
|
+
res.json({
|
|
171
|
+
connected: nexus.isConnected(),
|
|
172
|
+
provider: process.env.MESSAGING_PROVIDER || 'twilio'
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Health check
|
|
177
|
+
app.get('/health', (req, res) => {
|
|
178
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Initialize and start server
|
|
182
|
+
async function start() {
|
|
183
|
+
try {
|
|
184
|
+
await initializeNexus();
|
|
185
|
+
|
|
186
|
+
const PORT = process.env.PORT || 3000;
|
|
187
|
+
app.listen(PORT, () => {
|
|
188
|
+
const provider = process.env.MESSAGING_PROVIDER || 'twilio';
|
|
189
|
+
console.log(`🚀 Consumer server running on port ${PORT}`);
|
|
190
|
+
console.log(`📱 Messaging provider: ${provider}`);
|
|
191
|
+
console.log(`🔗 Webhook URL: http://localhost:${PORT}/webhook`);
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Failed to start server:', error);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Graceful shutdown
|
|
200
|
+
process.on('SIGINT', async () => {
|
|
201
|
+
console.log('Shutting down gracefully...');
|
|
202
|
+
await nexus.disconnect();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
start();
|