@diegoaltoworks/chatter 0.47.0 → 0.49.0
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 +7 -6
- package/dist/auth/apikeys.d.ts.map +1 -1
- package/dist/auth/secretStrength.d.ts +8 -0
- package/dist/auth/secretStrength.d.ts.map +1 -0
- package/dist/bin/create-apikey.js +22 -3
- package/dist/bin/wa-pair.js +59 -11
- package/dist/channels/gates.d.ts +9 -0
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +252 -8
- package/dist/channels/index.mjs +250 -8
- package/dist/channels/pipeline.d.ts +106 -0
- package/dist/channels/pipeline.d.ts.map +1 -0
- package/dist/channels/senders.d.ts +2 -1
- package/dist/channels/senders.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +4 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/crypto.d.ts +17 -3
- package/dist/channels/whatsapp/crypto.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +34 -4
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +2 -1
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +395 -159
- package/dist/channels/whatsapp/index.mjs +394 -160
- package/dist/channels/whatsapp/router.d.ts +107 -0
- package/dist/channels/whatsapp/router.d.ts.map +1 -0
- package/dist/core/answer.d.ts +5 -2
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/logger.d.ts +23 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/messages.d.ts +8 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +4 -0
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/flows/engine.d.ts +3 -0
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/index.d.ts +1 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +91 -25
- package/dist/flows/index.mjs +91 -26
- package/dist/flows/intent.d.ts +2 -1
- package/dist/flows/intent.d.ts.map +1 -1
- package/dist/flows/loader.d.ts +5 -3
- package/dist/flows/loader.d.ts.map +1 -1
- package/dist/flows/types.d.ts +9 -0
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/history/index.d.ts +17 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +100 -0
- package/dist/history/index.mjs +73 -0
- package/dist/history/tursoStore.d.ts +26 -0
- package/dist/history/tursoStore.d.ts.map +1 -0
- package/dist/history/types.d.ts +18 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1146 -32444
- package/dist/index.mjs +1082 -32410
- package/dist/mcp-server/cost-tracker.d.ts +11 -6
- package/dist/mcp-server/cost-tracker.d.ts.map +1 -1
- package/dist/mcp-server/logger.d.ts +5 -2
- package/dist/mcp-server/logger.d.ts.map +1 -1
- package/dist/mcp-server/sdk.d.ts +23 -0
- package/dist/mcp-server/sdk.d.ts.map +1 -0
- package/dist/mcp-server/tool-input.d.ts +12 -0
- package/dist/mcp-server/tool-input.d.ts.map +1 -0
- package/dist/mcp-server/types.d.ts +10 -3
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server.d.ts +1 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +192 -111
- package/dist/mcp-server.mjs +179 -98
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/bodyLimit.d.ts +11 -0
- package/dist/middleware/bodyLimit.d.ts.map +1 -0
- package/dist/middleware/clientKey.d.ts +15 -0
- package/dist/middleware/clientKey.d.ts.map +1 -0
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/jwt.d.ts +0 -3
- package/dist/middleware/jwt.d.ts.map +1 -1
- package/dist/middleware/originMatch.d.ts +27 -0
- package/dist/middleware/originMatch.d.ts.map +1 -0
- package/dist/middleware/ratelimit.d.ts +0 -6
- package/dist/middleware/ratelimit.d.ts.map +1 -1
- package/dist/middleware/referrer.d.ts +5 -1
- package/dist/middleware/referrer.d.ts.map +1 -1
- package/dist/middleware/securityHeaders.d.ts +20 -0
- package/dist/middleware/securityHeaders.d.ts.map +1 -0
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/middleware/windowBucket.d.ts +19 -0
- package/dist/middleware/windowBucket.d.ts.map +1 -0
- package/dist/personas/index.js +20 -1
- package/dist/personas/index.mjs +20 -1
- package/dist/personas/resolver.d.ts +3 -0
- package/dist/personas/resolver.d.ts.map +1 -1
- package/dist/routes/demo.d.ts.map +1 -1
- package/dist/routes/openai.d.ts.map +1 -1
- package/dist/routes/private.d.ts.map +1 -1
- package/dist/routes/public.d.ts.map +1 -1
- package/dist/scheduler/compose.d.ts +2 -1
- package/dist/scheduler/compose.d.ts.map +1 -1
- package/dist/scheduler/index.js +29 -9
- package/dist/scheduler/index.mjs +29 -9
- package/dist/scheduler/scheduler.d.ts +3 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/tick.d.ts +2 -0
- package/dist/scheduler/tick.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +678 -528
- package/dist/server.mjs +678 -528
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +23 -8
package/dist/mcp-server.js
CHANGED
|
@@ -33,9 +33,7 @@ __export(mcp_server_exports, {
|
|
|
33
33
|
createMCPServer: () => createMCPServer
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(mcp_server_exports);
|
|
36
|
-
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
37
36
|
var import_openai = __toESM(require("openai"));
|
|
38
|
-
var import_zod = require("zod");
|
|
39
37
|
|
|
40
38
|
// src/core/guardrails.ts
|
|
41
39
|
function detectLeakage(text) {
|
|
@@ -81,8 +79,8 @@ async function completeOnce({
|
|
|
81
79
|
|
|
82
80
|
// src/core/answer.ts
|
|
83
81
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
84
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
|
|
85
|
-
const result = await answerFn({ system, messages, mode, sender });
|
|
82
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
83
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
86
84
|
const raw = typeof result === "string" ? result : result?.content;
|
|
87
85
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
88
86
|
return {
|
|
@@ -97,11 +95,12 @@ async function answerOnce({
|
|
|
97
95
|
messages,
|
|
98
96
|
mode,
|
|
99
97
|
sender,
|
|
98
|
+
conversationId,
|
|
100
99
|
temperature,
|
|
101
100
|
model
|
|
102
101
|
}) {
|
|
103
102
|
if (answerFn) {
|
|
104
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender });
|
|
103
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
105
104
|
}
|
|
106
105
|
return completeOnce({ client, system, messages, temperature, model });
|
|
107
106
|
}
|
|
@@ -137,6 +136,62 @@ async function resolveBuckets({
|
|
|
137
136
|
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
138
137
|
}
|
|
139
138
|
|
|
139
|
+
// src/core/logger.ts
|
|
140
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
141
|
+
function createConsoleLogger(level = "info") {
|
|
142
|
+
const threshold = LEVEL_ORDER[level];
|
|
143
|
+
const write = (at) => (...args) => {
|
|
144
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
145
|
+
console.error(`[${at}]`, ...args);
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
debug: write("debug"),
|
|
149
|
+
info: write("info"),
|
|
150
|
+
warn: write("warn"),
|
|
151
|
+
error: write("error")
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function resolveLogger(logger, level) {
|
|
155
|
+
return logger ?? createConsoleLogger(level);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/core/messages.ts
|
|
159
|
+
function lastUserMessage(messages) {
|
|
160
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/core/pipeline.ts
|
|
164
|
+
var MODE_SETTINGS = {
|
|
165
|
+
public: { topK: 6, label: "Context" },
|
|
166
|
+
private: { topK: 8, label: "Internal Context" }
|
|
167
|
+
};
|
|
168
|
+
async function prepareChat({
|
|
169
|
+
store,
|
|
170
|
+
prompts,
|
|
171
|
+
mode,
|
|
172
|
+
messages,
|
|
173
|
+
personaLayer,
|
|
174
|
+
channelHint,
|
|
175
|
+
buckets
|
|
176
|
+
}) {
|
|
177
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
178
|
+
if (!lastUserMsg) {
|
|
179
|
+
throw new Error("no user message found in conversation");
|
|
180
|
+
}
|
|
181
|
+
const { topK, label } = MODE_SETTINGS[mode];
|
|
182
|
+
const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
|
|
183
|
+
const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
|
|
184
|
+
const hint = channelHint?.trim();
|
|
185
|
+
const system = [
|
|
186
|
+
prompts.baseSystemRules,
|
|
187
|
+
persona,
|
|
188
|
+
...hint ? [hint] : [],
|
|
189
|
+
`${label}:
|
|
190
|
+
${ctx.join("\n\n")}`
|
|
191
|
+
].join("\n\n");
|
|
192
|
+
return { system, messages, context: ctx };
|
|
193
|
+
}
|
|
194
|
+
|
|
140
195
|
// src/core/prompts.ts
|
|
141
196
|
var import_node_fs = require("node:fs");
|
|
142
197
|
var import_node_path = require("node:path");
|
|
@@ -229,6 +284,7 @@ var VectorStore = class _VectorStore {
|
|
|
229
284
|
authToken: options.databaseAuthToken
|
|
230
285
|
});
|
|
231
286
|
this.knowledgeDir = options.knowledgeDir || "./config/knowledge";
|
|
287
|
+
this.logger = options.logger ?? createConsoleLogger();
|
|
232
288
|
}
|
|
233
289
|
client;
|
|
234
290
|
/**
|
|
@@ -238,9 +294,10 @@ var VectorStore = class _VectorStore {
|
|
|
238
294
|
*/
|
|
239
295
|
db;
|
|
240
296
|
knowledgeDir;
|
|
297
|
+
logger;
|
|
241
298
|
// On boot: ingest new chunks and embed only missing ones.
|
|
242
299
|
async build() {
|
|
243
|
-
|
|
300
|
+
this.logger.info("\u{1F504} Building knowledge base...");
|
|
244
301
|
await this.db.execute(`
|
|
245
302
|
CREATE TABLE IF NOT EXISTS chunks (id TEXT PRIMARY KEY, bucket TEXT NOT NULL, source TEXT NOT NULL, text TEXT NOT NULL);
|
|
246
303
|
`);
|
|
@@ -249,7 +306,7 @@ var VectorStore = class _VectorStore {
|
|
|
249
306
|
`);
|
|
250
307
|
await this.db.execute("CREATE INDEX IF NOT EXISTS idx_chunks_bucket ON chunks(bucket);");
|
|
251
308
|
const docs = loadKnowledge(this.knowledgeDir);
|
|
252
|
-
|
|
309
|
+
this.logger.info(`\u{1F4DA} Loaded ${docs.length} knowledge documents`);
|
|
253
310
|
const rows = [];
|
|
254
311
|
for (const d of docs) {
|
|
255
312
|
for (const part of chunk(d.text)) {
|
|
@@ -257,21 +314,19 @@ var VectorStore = class _VectorStore {
|
|
|
257
314
|
rows.push({ id, bucket: d.bucket, source: d.source, text: part });
|
|
258
315
|
}
|
|
259
316
|
}
|
|
260
|
-
|
|
317
|
+
this.logger.info(`\u{1F4E6} Created ${rows.length} chunks from knowledge documents`);
|
|
261
318
|
await this.cleanupStaleChunks(rows.map((r) => r.id));
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
319
|
+
const UPSERT_BATCH = 500;
|
|
320
|
+
for (let i = 0; i < rows.length; i += UPSERT_BATCH) {
|
|
321
|
+
const batch = rows.slice(i, i + UPSERT_BATCH);
|
|
322
|
+
await this.db.batch(
|
|
323
|
+
batch.map((r) => ({
|
|
266
324
|
sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
|
|
267
325
|
ON CONFLICT(id) DO NOTHING`,
|
|
268
326
|
args: [r.id, r.bucket, r.source, r.text]
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
} catch (e) {
|
|
273
|
-
await tx.rollback();
|
|
274
|
-
throw e;
|
|
327
|
+
})),
|
|
328
|
+
"write"
|
|
329
|
+
);
|
|
275
330
|
}
|
|
276
331
|
const ids = rows.map((r) => r.id);
|
|
277
332
|
const missing = [];
|
|
@@ -286,32 +341,25 @@ var VectorStore = class _VectorStore {
|
|
|
286
341
|
for (const row of res.rows) missing.push(String(row.id));
|
|
287
342
|
}
|
|
288
343
|
if (missing.length === 0) {
|
|
289
|
-
|
|
344
|
+
this.logger.info("\u2705 No new chunks to embed - knowledge base is up to date");
|
|
290
345
|
return;
|
|
291
346
|
}
|
|
292
|
-
|
|
347
|
+
this.logger.info(`\u{1F52E} Embedding ${missing.length} new/updated chunks with ${EMB_MODEL}...`);
|
|
293
348
|
const textById = new Map(rows.map((r) => [r.id, r.text]));
|
|
294
349
|
const BATCH = 96;
|
|
295
350
|
for (let i = 0; i < missing.length; i += BATCH) {
|
|
296
351
|
const batchIds = missing.slice(i, i + BATCH);
|
|
297
352
|
const inputs = batchIds.map((id) => textById.get(id) || "");
|
|
298
353
|
const emb = await this.client.embeddings.create({ model: EMB_MODEL, input: inputs });
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
await tx2.commit();
|
|
309
|
-
} catch (e) {
|
|
310
|
-
await tx2.rollback();
|
|
311
|
-
throw e;
|
|
312
|
-
}
|
|
354
|
+
await this.db.batch(
|
|
355
|
+
emb.data.map((d, idx) => ({
|
|
356
|
+
sql: "INSERT INTO embeddings(id,model,embedding) VALUES(?,?,?)",
|
|
357
|
+
args: [batchIds[idx], EMB_MODEL, JSON.stringify(d.embedding)]
|
|
358
|
+
})),
|
|
359
|
+
"write"
|
|
360
|
+
);
|
|
313
361
|
}
|
|
314
|
-
|
|
362
|
+
this.logger.info(`\u2705 Successfully embedded ${missing.length} new chunks`);
|
|
315
363
|
}
|
|
316
364
|
// Remove chunks from database that no longer exist in markdown files
|
|
317
365
|
async cleanupStaleChunks(currentIds) {
|
|
@@ -322,7 +370,7 @@ var VectorStore = class _VectorStore {
|
|
|
322
370
|
if (staleIds.length === 0) {
|
|
323
371
|
return;
|
|
324
372
|
}
|
|
325
|
-
|
|
373
|
+
this.logger.info(`\u{1F9F9} Cleaning up ${staleIds.length} stale chunks...`);
|
|
326
374
|
const BATCH_SIZE = 500;
|
|
327
375
|
for (let i = 0; i < staleIds.length; i += BATCH_SIZE) {
|
|
328
376
|
const batch = staleIds.slice(i, i + BATCH_SIZE);
|
|
@@ -332,7 +380,7 @@ var VectorStore = class _VectorStore {
|
|
|
332
380
|
args: batch
|
|
333
381
|
});
|
|
334
382
|
}
|
|
335
|
-
|
|
383
|
+
this.logger.info(`\u2713 Cleaned up ${staleIds.length} stale chunks and their embeddings`);
|
|
336
384
|
}
|
|
337
385
|
static cosine(a, b) {
|
|
338
386
|
let dot = 0;
|
|
@@ -389,13 +437,11 @@ function getOrGenerateConversationId(providedId) {
|
|
|
389
437
|
}
|
|
390
438
|
|
|
391
439
|
// src/mcp-server/cost-tracker.ts
|
|
392
|
-
function calculateCost(usage) {
|
|
440
|
+
function calculateCost(usage, rates) {
|
|
393
441
|
const promptTokens = usage.prompt_tokens || 0;
|
|
394
442
|
const completionTokens = usage.completion_tokens || 0;
|
|
395
443
|
const totalTokens = promptTokens + completionTokens;
|
|
396
|
-
const
|
|
397
|
-
const completionCost = completionTokens / 1e6 * 10;
|
|
398
|
-
const estimatedCost = promptCost + completionCost;
|
|
444
|
+
const estimatedCost = rates ? promptTokens / 1e6 * rates.promptPer1M + completionTokens / 1e6 * rates.completionPer1M : null;
|
|
399
445
|
return {
|
|
400
446
|
promptTokens,
|
|
401
447
|
completionTokens,
|
|
@@ -408,15 +454,17 @@ function calculateCost(usage) {
|
|
|
408
454
|
var MCPLogger = class {
|
|
409
455
|
enableConsole;
|
|
410
456
|
callback;
|
|
411
|
-
|
|
457
|
+
logger;
|
|
458
|
+
constructor(enableConsole = true, callback, logger) {
|
|
412
459
|
this.enableConsole = enableConsole;
|
|
413
460
|
this.callback = callback;
|
|
461
|
+
this.logger = logger ?? createConsoleLogger();
|
|
414
462
|
}
|
|
415
463
|
/**
|
|
416
464
|
* Log a chat interaction
|
|
417
465
|
*/
|
|
418
466
|
async logChatInteraction(toolName, conversationId, conversationMessages, ragContext, response, duration, cost) {
|
|
419
|
-
const lastUserMsg =
|
|
467
|
+
const lastUserMsg = lastUserMessage(conversationMessages);
|
|
420
468
|
const logEvent = {
|
|
421
469
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
422
470
|
toolName,
|
|
@@ -429,7 +477,7 @@ var MCPLogger = class {
|
|
|
429
477
|
cost
|
|
430
478
|
};
|
|
431
479
|
if (this.enableConsole) {
|
|
432
|
-
|
|
480
|
+
this.logger.info(
|
|
433
481
|
JSON.stringify({
|
|
434
482
|
event: "mcp_chat",
|
|
435
483
|
...logEvent
|
|
@@ -440,13 +488,13 @@ var MCPLogger = class {
|
|
|
440
488
|
try {
|
|
441
489
|
await this.callback(logEvent);
|
|
442
490
|
} catch (err) {
|
|
443
|
-
|
|
491
|
+
this.logger.error("Logging callback error:", err);
|
|
444
492
|
}
|
|
445
493
|
}
|
|
446
494
|
}
|
|
447
495
|
};
|
|
448
|
-
function createLogger(enableConsole = true, callback) {
|
|
449
|
-
return new MCPLogger(enableConsole, callback);
|
|
496
|
+
function createLogger(enableConsole = true, callback, logger) {
|
|
497
|
+
return new MCPLogger(enableConsole, callback, logger);
|
|
450
498
|
}
|
|
451
499
|
|
|
452
500
|
// src/mcp-server/rate-limiter.ts
|
|
@@ -528,14 +576,57 @@ function createRateLimiter(limit, windowMs = 6e4) {
|
|
|
528
576
|
return limit ? new RateLimiter(limit, windowMs) : null;
|
|
529
577
|
}
|
|
530
578
|
|
|
579
|
+
// src/mcp-server/sdk.ts
|
|
580
|
+
function wrapMissingMcpPeerError(packageName, cause) {
|
|
581
|
+
return new Error(
|
|
582
|
+
`The MCP server ('@diegoaltoworks/chatter/mcp') requires the optional peer dependency '${packageName}', which is not installed. Install it with \`bun add ${packageName}\` (or npm/pnpm/yarn) to use this feature.`,
|
|
583
|
+
{ cause }
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
var cachedSdk;
|
|
587
|
+
function loadMcpSdk() {
|
|
588
|
+
if (!cachedSdk) {
|
|
589
|
+
cachedSdk = import("@modelcontextprotocol/sdk/server/mcp.js").then((mod) => {
|
|
590
|
+
if (!mod.McpServer) throw new Error("module has no McpServer export");
|
|
591
|
+
return mod.McpServer;
|
|
592
|
+
}).catch((error) => {
|
|
593
|
+
cachedSdk = void 0;
|
|
594
|
+
throw wrapMissingMcpPeerError("@modelcontextprotocol/sdk", error);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return cachedSdk;
|
|
598
|
+
}
|
|
599
|
+
var cachedZod;
|
|
600
|
+
function loadZod() {
|
|
601
|
+
if (!cachedZod) {
|
|
602
|
+
cachedZod = import("zod").then((mod) => {
|
|
603
|
+
if (!mod.z) throw new Error("module has no z export");
|
|
604
|
+
return mod.z;
|
|
605
|
+
}).catch((error) => {
|
|
606
|
+
cachedZod = void 0;
|
|
607
|
+
throw wrapMissingMcpPeerError("zod", error);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return cachedZod;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/mcp-server/tool-input.ts
|
|
614
|
+
function resolveConversationMessages(message, messages) {
|
|
615
|
+
if (messages) return messages;
|
|
616
|
+
if (message) return [{ role: "user", content: message }];
|
|
617
|
+
throw new Error("Either 'message' or 'messages' is required");
|
|
618
|
+
}
|
|
619
|
+
|
|
531
620
|
// src/mcp-server.ts
|
|
532
621
|
async function createMCPServer(config) {
|
|
533
|
-
|
|
622
|
+
const log = resolveLogger(config.logger, config.logLevel);
|
|
623
|
+
log.info(`\u{1F680} Starting ${config.bot.name} MCP Server...`);
|
|
624
|
+
const [McpServer, z] = await Promise.all([loadMcpSdk(), loadZod()]);
|
|
534
625
|
const knowledgeDir = config.knowledgeDir || "./config/knowledge";
|
|
535
626
|
const promptsDir = config.promptsDir || "./config/prompts";
|
|
536
627
|
const transportMode = config.transport || "stdio";
|
|
537
628
|
const rateLimiter = createRateLimiter(config.toolRateLimit);
|
|
538
|
-
const logger = createLogger(config.logging?.console !== false, config.logging?.onChat);
|
|
629
|
+
const logger = createLogger(config.logging?.console !== false, config.logging?.onChat, log);
|
|
539
630
|
const publicToolConfig = {
|
|
540
631
|
enabled: config.tools?.public?.enabled !== false,
|
|
541
632
|
name: config.tools?.public?.name || "chat_public",
|
|
@@ -549,29 +640,31 @@ async function createMCPServer(config) {
|
|
|
549
640
|
const client = new import_openai.default({
|
|
550
641
|
apiKey: config.openai.apiKey
|
|
551
642
|
});
|
|
643
|
+
const model = config.openai.model || DEFAULT_MODEL;
|
|
552
644
|
const store = new VectorStore(client, {
|
|
553
645
|
databaseUrl: config.database.url,
|
|
554
646
|
databaseAuthToken: config.database.authToken,
|
|
555
|
-
knowledgeDir
|
|
647
|
+
knowledgeDir,
|
|
648
|
+
logger: log
|
|
556
649
|
});
|
|
557
650
|
await store.build();
|
|
558
|
-
|
|
651
|
+
log.info("\u2705 Knowledge base ready");
|
|
559
652
|
const prompts = new PromptLoader(promptsDir, config.bot);
|
|
560
|
-
const server = new
|
|
653
|
+
const server = new McpServer({
|
|
561
654
|
name: config.bot.name,
|
|
562
655
|
version: "1.0.0"
|
|
563
656
|
});
|
|
564
|
-
|
|
657
|
+
log.info("\u2705 MCP server initialized");
|
|
565
658
|
if (publicToolConfig.enabled) {
|
|
566
659
|
const publicShape = {
|
|
567
|
-
message:
|
|
568
|
-
messages:
|
|
569
|
-
|
|
570
|
-
role:
|
|
571
|
-
content:
|
|
660
|
+
message: z.string().optional().describe("A single message to send (for simple queries)"),
|
|
661
|
+
messages: z.array(
|
|
662
|
+
z.object({
|
|
663
|
+
role: z.enum(["user", "assistant"]),
|
|
664
|
+
content: z.string()
|
|
572
665
|
})
|
|
573
666
|
).optional().describe("Conversation history with alternating user/assistant messages"),
|
|
574
|
-
conversationId:
|
|
667
|
+
conversationId: z.string().optional().describe(
|
|
575
668
|
"Optional conversation ID to track sessions. If not provided, a new ID will be generated."
|
|
576
669
|
)
|
|
577
670
|
};
|
|
@@ -590,34 +683,28 @@ async function createMCPServer(config) {
|
|
|
590
683
|
`Rate limit exceeded for ${publicToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
|
|
591
684
|
);
|
|
592
685
|
}
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
conversationMessages = messages;
|
|
596
|
-
} else if (message) {
|
|
597
|
-
conversationMessages = [{ role: "user", content: String(message) }];
|
|
598
|
-
} else {
|
|
599
|
-
throw new Error("Either 'message' or 'messages' is required");
|
|
600
|
-
}
|
|
601
|
-
const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
|
|
602
|
-
if (!lastUserMsg) {
|
|
686
|
+
const conversationMessages = resolveConversationMessages(message, messages);
|
|
687
|
+
if (!lastUserMessage(conversationMessages)) {
|
|
603
688
|
throw new Error("No user message found in conversation");
|
|
604
689
|
}
|
|
605
|
-
const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor })
|
|
606
|
-
const ctx = await
|
|
607
|
-
|
|
608
|
-
prompts
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
690
|
+
const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor });
|
|
691
|
+
const { system, context: ctx } = await prepareChat({
|
|
692
|
+
store,
|
|
693
|
+
prompts,
|
|
694
|
+
mode: "public",
|
|
695
|
+
messages: conversationMessages,
|
|
696
|
+
buckets
|
|
697
|
+
});
|
|
613
698
|
const result = await answerOnce({
|
|
614
699
|
answerFn: config.answerFn,
|
|
615
700
|
client,
|
|
616
701
|
system,
|
|
617
702
|
messages: conversationMessages,
|
|
618
|
-
mode: "public"
|
|
703
|
+
mode: "public",
|
|
704
|
+
conversationId: convId,
|
|
705
|
+
model
|
|
619
706
|
});
|
|
620
|
-
const cost = calculateCost(result.usage);
|
|
707
|
+
const cost = calculateCost(result.usage, config.openai.pricing);
|
|
621
708
|
const duration = Date.now() - startTime;
|
|
622
709
|
await logger.logChatInteraction(
|
|
623
710
|
publicToolConfig.name,
|
|
@@ -627,7 +714,7 @@ ${ctx.join("\n\n")}`
|
|
|
627
714
|
result.content,
|
|
628
715
|
duration,
|
|
629
716
|
cost
|
|
630
|
-
).catch((err) =>
|
|
717
|
+
).catch((err) => log.error("Logging error:", err));
|
|
631
718
|
return {
|
|
632
719
|
content: [
|
|
633
720
|
{
|
|
@@ -647,18 +734,18 @@ ${ctx.join("\n\n")}`
|
|
|
647
734
|
};
|
|
648
735
|
}
|
|
649
736
|
);
|
|
650
|
-
|
|
737
|
+
log.info(`\u2705 Registered tool: ${publicToolConfig.name}`);
|
|
651
738
|
}
|
|
652
739
|
if (privateToolConfig.enabled) {
|
|
653
740
|
const privateShape = {
|
|
654
|
-
message:
|
|
655
|
-
messages:
|
|
656
|
-
|
|
657
|
-
role:
|
|
658
|
-
content:
|
|
741
|
+
message: z.string().optional().describe("A single message to send (for simple queries)"),
|
|
742
|
+
messages: z.array(
|
|
743
|
+
z.object({
|
|
744
|
+
role: z.enum(["user", "assistant"]),
|
|
745
|
+
content: z.string()
|
|
659
746
|
})
|
|
660
747
|
).optional().describe("Conversation history with alternating user/assistant messages"),
|
|
661
|
-
conversationId:
|
|
748
|
+
conversationId: z.string().optional().describe(
|
|
662
749
|
"Optional conversation ID to track sessions. If not provided, a new ID will be generated."
|
|
663
750
|
)
|
|
664
751
|
};
|
|
@@ -677,34 +764,28 @@ ${ctx.join("\n\n")}`
|
|
|
677
764
|
`Rate limit exceeded for ${privateToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
|
|
678
765
|
);
|
|
679
766
|
}
|
|
680
|
-
|
|
681
|
-
if (
|
|
682
|
-
conversationMessages = messages;
|
|
683
|
-
} else if (message) {
|
|
684
|
-
conversationMessages = [{ role: "user", content: String(message) }];
|
|
685
|
-
} else {
|
|
686
|
-
throw new Error("Either 'message' or 'messages' is required");
|
|
687
|
-
}
|
|
688
|
-
const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
|
|
689
|
-
if (!lastUserMsg) {
|
|
767
|
+
const conversationMessages = resolveConversationMessages(message, messages);
|
|
768
|
+
if (!lastUserMessage(conversationMessages)) {
|
|
690
769
|
throw new Error("No user message found in conversation");
|
|
691
770
|
}
|
|
692
|
-
const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor })
|
|
693
|
-
const ctx = await
|
|
694
|
-
|
|
695
|
-
prompts
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
771
|
+
const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor });
|
|
772
|
+
const { system, context: ctx } = await prepareChat({
|
|
773
|
+
store,
|
|
774
|
+
prompts,
|
|
775
|
+
mode: "private",
|
|
776
|
+
messages: conversationMessages,
|
|
777
|
+
buckets
|
|
778
|
+
});
|
|
700
779
|
const result = await answerOnce({
|
|
701
780
|
answerFn: config.answerFn,
|
|
702
781
|
client,
|
|
703
782
|
system,
|
|
704
783
|
messages: conversationMessages,
|
|
705
|
-
mode: "private"
|
|
784
|
+
mode: "private",
|
|
785
|
+
conversationId: convId,
|
|
786
|
+
model
|
|
706
787
|
});
|
|
707
|
-
const cost = calculateCost(result.usage);
|
|
788
|
+
const cost = calculateCost(result.usage, config.openai.pricing);
|
|
708
789
|
const duration = Date.now() - startTime;
|
|
709
790
|
await logger.logChatInteraction(
|
|
710
791
|
privateToolConfig.name,
|
|
@@ -714,7 +795,7 @@ ${ctx.join("\n\n")}`
|
|
|
714
795
|
result.content,
|
|
715
796
|
duration,
|
|
716
797
|
cost
|
|
717
|
-
).catch((err) =>
|
|
798
|
+
).catch((err) => log.error("Logging error:", err));
|
|
718
799
|
return {
|
|
719
800
|
content: [
|
|
720
801
|
{
|
|
@@ -734,16 +815,16 @@ ${ctx.join("\n\n")}`
|
|
|
734
815
|
};
|
|
735
816
|
}
|
|
736
817
|
);
|
|
737
|
-
|
|
818
|
+
log.info(`\u2705 Registered tool: ${privateToolConfig.name}`);
|
|
738
819
|
}
|
|
739
820
|
const enabledTools = [
|
|
740
821
|
publicToolConfig.enabled ? publicToolConfig.name : null,
|
|
741
822
|
privateToolConfig.enabled ? privateToolConfig.name : null
|
|
742
823
|
].filter(Boolean);
|
|
743
824
|
if (enabledTools.length === 0) {
|
|
744
|
-
|
|
825
|
+
log.warn("\u26A0\uFE0F Warning: No MCP tools enabled");
|
|
745
826
|
}
|
|
746
|
-
|
|
827
|
+
log.info(
|
|
747
828
|
`\u2705 ${config.bot.name} MCP Server ready (transport: ${transportMode}, tools: ${enabledTools.join(", ") || "none"})`
|
|
748
829
|
);
|
|
749
830
|
return server;
|