@diegoaltoworks/chatter 0.45.0 → 0.48.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 +19 -5
- 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/core/serve-static.d.ts +38 -0
- package/dist/core/serve-static.d.ts.map +1 -0
- 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 +1202 -32462
- package/dist/index.mjs +1118 -32414
- 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 +726 -538
- package/dist/server.mjs +708 -527
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +30 -9
package/dist/mcp-server.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/mcp-server.ts
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import OpenAI from "openai";
|
|
4
|
-
import { z } from "zod";
|
|
5
3
|
|
|
6
4
|
// src/core/guardrails.ts
|
|
7
5
|
function detectLeakage(text) {
|
|
@@ -47,8 +45,8 @@ async function completeOnce({
|
|
|
47
45
|
|
|
48
46
|
// src/core/answer.ts
|
|
49
47
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
50
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
|
|
51
|
-
const result = await answerFn({ system, messages, mode, sender });
|
|
48
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
49
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
52
50
|
const raw = typeof result === "string" ? result : result?.content;
|
|
53
51
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
54
52
|
return {
|
|
@@ -63,11 +61,12 @@ async function answerOnce({
|
|
|
63
61
|
messages,
|
|
64
62
|
mode,
|
|
65
63
|
sender,
|
|
64
|
+
conversationId,
|
|
66
65
|
temperature,
|
|
67
66
|
model
|
|
68
67
|
}) {
|
|
69
68
|
if (answerFn) {
|
|
70
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender });
|
|
69
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
71
70
|
}
|
|
72
71
|
return completeOnce({ client, system, messages, temperature, model });
|
|
73
72
|
}
|
|
@@ -103,6 +102,62 @@ async function resolveBuckets({
|
|
|
103
102
|
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
// src/core/logger.ts
|
|
106
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
107
|
+
function createConsoleLogger(level = "info") {
|
|
108
|
+
const threshold = LEVEL_ORDER[level];
|
|
109
|
+
const write = (at) => (...args) => {
|
|
110
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
111
|
+
console.error(`[${at}]`, ...args);
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
debug: write("debug"),
|
|
115
|
+
info: write("info"),
|
|
116
|
+
warn: write("warn"),
|
|
117
|
+
error: write("error")
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function resolveLogger(logger, level) {
|
|
121
|
+
return logger ?? createConsoleLogger(level);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/core/messages.ts
|
|
125
|
+
function lastUserMessage(messages) {
|
|
126
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/core/pipeline.ts
|
|
130
|
+
var MODE_SETTINGS = {
|
|
131
|
+
public: { topK: 6, label: "Context" },
|
|
132
|
+
private: { topK: 8, label: "Internal Context" }
|
|
133
|
+
};
|
|
134
|
+
async function prepareChat({
|
|
135
|
+
store,
|
|
136
|
+
prompts,
|
|
137
|
+
mode,
|
|
138
|
+
messages,
|
|
139
|
+
personaLayer,
|
|
140
|
+
channelHint,
|
|
141
|
+
buckets
|
|
142
|
+
}) {
|
|
143
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
144
|
+
if (!lastUserMsg) {
|
|
145
|
+
throw new Error("no user message found in conversation");
|
|
146
|
+
}
|
|
147
|
+
const { topK, label } = MODE_SETTINGS[mode];
|
|
148
|
+
const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
|
|
149
|
+
const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
|
|
150
|
+
const hint = channelHint?.trim();
|
|
151
|
+
const system = [
|
|
152
|
+
prompts.baseSystemRules,
|
|
153
|
+
persona,
|
|
154
|
+
...hint ? [hint] : [],
|
|
155
|
+
`${label}:
|
|
156
|
+
${ctx.join("\n\n")}`
|
|
157
|
+
].join("\n\n");
|
|
158
|
+
return { system, messages, context: ctx };
|
|
159
|
+
}
|
|
160
|
+
|
|
106
161
|
// src/core/prompts.ts
|
|
107
162
|
import { readFileSync } from "node:fs";
|
|
108
163
|
import { join } from "node:path";
|
|
@@ -195,6 +250,7 @@ var VectorStore = class _VectorStore {
|
|
|
195
250
|
authToken: options.databaseAuthToken
|
|
196
251
|
});
|
|
197
252
|
this.knowledgeDir = options.knowledgeDir || "./config/knowledge";
|
|
253
|
+
this.logger = options.logger ?? createConsoleLogger();
|
|
198
254
|
}
|
|
199
255
|
client;
|
|
200
256
|
/**
|
|
@@ -204,9 +260,10 @@ var VectorStore = class _VectorStore {
|
|
|
204
260
|
*/
|
|
205
261
|
db;
|
|
206
262
|
knowledgeDir;
|
|
263
|
+
logger;
|
|
207
264
|
// On boot: ingest new chunks and embed only missing ones.
|
|
208
265
|
async build() {
|
|
209
|
-
|
|
266
|
+
this.logger.info("\u{1F504} Building knowledge base...");
|
|
210
267
|
await this.db.execute(`
|
|
211
268
|
CREATE TABLE IF NOT EXISTS chunks (id TEXT PRIMARY KEY, bucket TEXT NOT NULL, source TEXT NOT NULL, text TEXT NOT NULL);
|
|
212
269
|
`);
|
|
@@ -215,7 +272,7 @@ var VectorStore = class _VectorStore {
|
|
|
215
272
|
`);
|
|
216
273
|
await this.db.execute("CREATE INDEX IF NOT EXISTS idx_chunks_bucket ON chunks(bucket);");
|
|
217
274
|
const docs = loadKnowledge(this.knowledgeDir);
|
|
218
|
-
|
|
275
|
+
this.logger.info(`\u{1F4DA} Loaded ${docs.length} knowledge documents`);
|
|
219
276
|
const rows = [];
|
|
220
277
|
for (const d of docs) {
|
|
221
278
|
for (const part of chunk(d.text)) {
|
|
@@ -223,21 +280,19 @@ var VectorStore = class _VectorStore {
|
|
|
223
280
|
rows.push({ id, bucket: d.bucket, source: d.source, text: part });
|
|
224
281
|
}
|
|
225
282
|
}
|
|
226
|
-
|
|
283
|
+
this.logger.info(`\u{1F4E6} Created ${rows.length} chunks from knowledge documents`);
|
|
227
284
|
await this.cleanupStaleChunks(rows.map((r) => r.id));
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
285
|
+
const UPSERT_BATCH = 500;
|
|
286
|
+
for (let i = 0; i < rows.length; i += UPSERT_BATCH) {
|
|
287
|
+
const batch = rows.slice(i, i + UPSERT_BATCH);
|
|
288
|
+
await this.db.batch(
|
|
289
|
+
batch.map((r) => ({
|
|
232
290
|
sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
|
|
233
291
|
ON CONFLICT(id) DO NOTHING`,
|
|
234
292
|
args: [r.id, r.bucket, r.source, r.text]
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
} catch (e) {
|
|
239
|
-
await tx.rollback();
|
|
240
|
-
throw e;
|
|
293
|
+
})),
|
|
294
|
+
"write"
|
|
295
|
+
);
|
|
241
296
|
}
|
|
242
297
|
const ids = rows.map((r) => r.id);
|
|
243
298
|
const missing = [];
|
|
@@ -252,32 +307,25 @@ var VectorStore = class _VectorStore {
|
|
|
252
307
|
for (const row of res.rows) missing.push(String(row.id));
|
|
253
308
|
}
|
|
254
309
|
if (missing.length === 0) {
|
|
255
|
-
|
|
310
|
+
this.logger.info("\u2705 No new chunks to embed - knowledge base is up to date");
|
|
256
311
|
return;
|
|
257
312
|
}
|
|
258
|
-
|
|
313
|
+
this.logger.info(`\u{1F52E} Embedding ${missing.length} new/updated chunks with ${EMB_MODEL}...`);
|
|
259
314
|
const textById = new Map(rows.map((r) => [r.id, r.text]));
|
|
260
315
|
const BATCH = 96;
|
|
261
316
|
for (let i = 0; i < missing.length; i += BATCH) {
|
|
262
317
|
const batchIds = missing.slice(i, i + BATCH);
|
|
263
318
|
const inputs = batchIds.map((id) => textById.get(id) || "");
|
|
264
319
|
const emb = await this.client.embeddings.create({ model: EMB_MODEL, input: inputs });
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
await tx2.commit();
|
|
275
|
-
} catch (e) {
|
|
276
|
-
await tx2.rollback();
|
|
277
|
-
throw e;
|
|
278
|
-
}
|
|
320
|
+
await this.db.batch(
|
|
321
|
+
emb.data.map((d, idx) => ({
|
|
322
|
+
sql: "INSERT INTO embeddings(id,model,embedding) VALUES(?,?,?)",
|
|
323
|
+
args: [batchIds[idx], EMB_MODEL, JSON.stringify(d.embedding)]
|
|
324
|
+
})),
|
|
325
|
+
"write"
|
|
326
|
+
);
|
|
279
327
|
}
|
|
280
|
-
|
|
328
|
+
this.logger.info(`\u2705 Successfully embedded ${missing.length} new chunks`);
|
|
281
329
|
}
|
|
282
330
|
// Remove chunks from database that no longer exist in markdown files
|
|
283
331
|
async cleanupStaleChunks(currentIds) {
|
|
@@ -288,7 +336,7 @@ var VectorStore = class _VectorStore {
|
|
|
288
336
|
if (staleIds.length === 0) {
|
|
289
337
|
return;
|
|
290
338
|
}
|
|
291
|
-
|
|
339
|
+
this.logger.info(`\u{1F9F9} Cleaning up ${staleIds.length} stale chunks...`);
|
|
292
340
|
const BATCH_SIZE = 500;
|
|
293
341
|
for (let i = 0; i < staleIds.length; i += BATCH_SIZE) {
|
|
294
342
|
const batch = staleIds.slice(i, i + BATCH_SIZE);
|
|
@@ -298,7 +346,7 @@ var VectorStore = class _VectorStore {
|
|
|
298
346
|
args: batch
|
|
299
347
|
});
|
|
300
348
|
}
|
|
301
|
-
|
|
349
|
+
this.logger.info(`\u2713 Cleaned up ${staleIds.length} stale chunks and their embeddings`);
|
|
302
350
|
}
|
|
303
351
|
static cosine(a, b) {
|
|
304
352
|
let dot = 0;
|
|
@@ -355,13 +403,11 @@ function getOrGenerateConversationId(providedId) {
|
|
|
355
403
|
}
|
|
356
404
|
|
|
357
405
|
// src/mcp-server/cost-tracker.ts
|
|
358
|
-
function calculateCost(usage) {
|
|
406
|
+
function calculateCost(usage, rates) {
|
|
359
407
|
const promptTokens = usage.prompt_tokens || 0;
|
|
360
408
|
const completionTokens = usage.completion_tokens || 0;
|
|
361
409
|
const totalTokens = promptTokens + completionTokens;
|
|
362
|
-
const
|
|
363
|
-
const completionCost = completionTokens / 1e6 * 10;
|
|
364
|
-
const estimatedCost = promptCost + completionCost;
|
|
410
|
+
const estimatedCost = rates ? promptTokens / 1e6 * rates.promptPer1M + completionTokens / 1e6 * rates.completionPer1M : null;
|
|
365
411
|
return {
|
|
366
412
|
promptTokens,
|
|
367
413
|
completionTokens,
|
|
@@ -374,15 +420,17 @@ function calculateCost(usage) {
|
|
|
374
420
|
var MCPLogger = class {
|
|
375
421
|
enableConsole;
|
|
376
422
|
callback;
|
|
377
|
-
|
|
423
|
+
logger;
|
|
424
|
+
constructor(enableConsole = true, callback, logger) {
|
|
378
425
|
this.enableConsole = enableConsole;
|
|
379
426
|
this.callback = callback;
|
|
427
|
+
this.logger = logger ?? createConsoleLogger();
|
|
380
428
|
}
|
|
381
429
|
/**
|
|
382
430
|
* Log a chat interaction
|
|
383
431
|
*/
|
|
384
432
|
async logChatInteraction(toolName, conversationId, conversationMessages, ragContext, response, duration, cost) {
|
|
385
|
-
const lastUserMsg =
|
|
433
|
+
const lastUserMsg = lastUserMessage(conversationMessages);
|
|
386
434
|
const logEvent = {
|
|
387
435
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
388
436
|
toolName,
|
|
@@ -395,7 +443,7 @@ var MCPLogger = class {
|
|
|
395
443
|
cost
|
|
396
444
|
};
|
|
397
445
|
if (this.enableConsole) {
|
|
398
|
-
|
|
446
|
+
this.logger.info(
|
|
399
447
|
JSON.stringify({
|
|
400
448
|
event: "mcp_chat",
|
|
401
449
|
...logEvent
|
|
@@ -406,13 +454,13 @@ var MCPLogger = class {
|
|
|
406
454
|
try {
|
|
407
455
|
await this.callback(logEvent);
|
|
408
456
|
} catch (err) {
|
|
409
|
-
|
|
457
|
+
this.logger.error("Logging callback error:", err);
|
|
410
458
|
}
|
|
411
459
|
}
|
|
412
460
|
}
|
|
413
461
|
};
|
|
414
|
-
function createLogger(enableConsole = true, callback) {
|
|
415
|
-
return new MCPLogger(enableConsole, callback);
|
|
462
|
+
function createLogger(enableConsole = true, callback, logger) {
|
|
463
|
+
return new MCPLogger(enableConsole, callback, logger);
|
|
416
464
|
}
|
|
417
465
|
|
|
418
466
|
// src/mcp-server/rate-limiter.ts
|
|
@@ -494,14 +542,57 @@ function createRateLimiter(limit, windowMs = 6e4) {
|
|
|
494
542
|
return limit ? new RateLimiter(limit, windowMs) : null;
|
|
495
543
|
}
|
|
496
544
|
|
|
545
|
+
// src/mcp-server/sdk.ts
|
|
546
|
+
function wrapMissingMcpPeerError(packageName, cause) {
|
|
547
|
+
return new Error(
|
|
548
|
+
`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.`,
|
|
549
|
+
{ cause }
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
var cachedSdk;
|
|
553
|
+
function loadMcpSdk() {
|
|
554
|
+
if (!cachedSdk) {
|
|
555
|
+
cachedSdk = import("@modelcontextprotocol/sdk/server/mcp.js").then((mod) => {
|
|
556
|
+
if (!mod.McpServer) throw new Error("module has no McpServer export");
|
|
557
|
+
return mod.McpServer;
|
|
558
|
+
}).catch((error) => {
|
|
559
|
+
cachedSdk = void 0;
|
|
560
|
+
throw wrapMissingMcpPeerError("@modelcontextprotocol/sdk", error);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return cachedSdk;
|
|
564
|
+
}
|
|
565
|
+
var cachedZod;
|
|
566
|
+
function loadZod() {
|
|
567
|
+
if (!cachedZod) {
|
|
568
|
+
cachedZod = import("zod").then((mod) => {
|
|
569
|
+
if (!mod.z) throw new Error("module has no z export");
|
|
570
|
+
return mod.z;
|
|
571
|
+
}).catch((error) => {
|
|
572
|
+
cachedZod = void 0;
|
|
573
|
+
throw wrapMissingMcpPeerError("zod", error);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return cachedZod;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/mcp-server/tool-input.ts
|
|
580
|
+
function resolveConversationMessages(message, messages) {
|
|
581
|
+
if (messages) return messages;
|
|
582
|
+
if (message) return [{ role: "user", content: message }];
|
|
583
|
+
throw new Error("Either 'message' or 'messages' is required");
|
|
584
|
+
}
|
|
585
|
+
|
|
497
586
|
// src/mcp-server.ts
|
|
498
587
|
async function createMCPServer(config) {
|
|
499
|
-
|
|
588
|
+
const log = resolveLogger(config.logger, config.logLevel);
|
|
589
|
+
log.info(`\u{1F680} Starting ${config.bot.name} MCP Server...`);
|
|
590
|
+
const [McpServer, z] = await Promise.all([loadMcpSdk(), loadZod()]);
|
|
500
591
|
const knowledgeDir = config.knowledgeDir || "./config/knowledge";
|
|
501
592
|
const promptsDir = config.promptsDir || "./config/prompts";
|
|
502
593
|
const transportMode = config.transport || "stdio";
|
|
503
594
|
const rateLimiter = createRateLimiter(config.toolRateLimit);
|
|
504
|
-
const logger = createLogger(config.logging?.console !== false, config.logging?.onChat);
|
|
595
|
+
const logger = createLogger(config.logging?.console !== false, config.logging?.onChat, log);
|
|
505
596
|
const publicToolConfig = {
|
|
506
597
|
enabled: config.tools?.public?.enabled !== false,
|
|
507
598
|
name: config.tools?.public?.name || "chat_public",
|
|
@@ -515,19 +606,21 @@ async function createMCPServer(config) {
|
|
|
515
606
|
const client = new OpenAI({
|
|
516
607
|
apiKey: config.openai.apiKey
|
|
517
608
|
});
|
|
609
|
+
const model = config.openai.model || DEFAULT_MODEL;
|
|
518
610
|
const store = new VectorStore(client, {
|
|
519
611
|
databaseUrl: config.database.url,
|
|
520
612
|
databaseAuthToken: config.database.authToken,
|
|
521
|
-
knowledgeDir
|
|
613
|
+
knowledgeDir,
|
|
614
|
+
logger: log
|
|
522
615
|
});
|
|
523
616
|
await store.build();
|
|
524
|
-
|
|
617
|
+
log.info("\u2705 Knowledge base ready");
|
|
525
618
|
const prompts = new PromptLoader(promptsDir, config.bot);
|
|
526
619
|
const server = new McpServer({
|
|
527
620
|
name: config.bot.name,
|
|
528
621
|
version: "1.0.0"
|
|
529
622
|
});
|
|
530
|
-
|
|
623
|
+
log.info("\u2705 MCP server initialized");
|
|
531
624
|
if (publicToolConfig.enabled) {
|
|
532
625
|
const publicShape = {
|
|
533
626
|
message: z.string().optional().describe("A single message to send (for simple queries)"),
|
|
@@ -556,34 +649,28 @@ async function createMCPServer(config) {
|
|
|
556
649
|
`Rate limit exceeded for ${publicToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
|
|
557
650
|
);
|
|
558
651
|
}
|
|
559
|
-
|
|
560
|
-
if (
|
|
561
|
-
conversationMessages = messages;
|
|
562
|
-
} else if (message) {
|
|
563
|
-
conversationMessages = [{ role: "user", content: String(message) }];
|
|
564
|
-
} else {
|
|
565
|
-
throw new Error("Either 'message' or 'messages' is required");
|
|
566
|
-
}
|
|
567
|
-
const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
|
|
568
|
-
if (!lastUserMsg) {
|
|
652
|
+
const conversationMessages = resolveConversationMessages(message, messages);
|
|
653
|
+
if (!lastUserMessage(conversationMessages)) {
|
|
569
654
|
throw new Error("No user message found in conversation");
|
|
570
655
|
}
|
|
571
|
-
const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor })
|
|
572
|
-
const ctx = await
|
|
573
|
-
|
|
574
|
-
prompts
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
656
|
+
const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor });
|
|
657
|
+
const { system, context: ctx } = await prepareChat({
|
|
658
|
+
store,
|
|
659
|
+
prompts,
|
|
660
|
+
mode: "public",
|
|
661
|
+
messages: conversationMessages,
|
|
662
|
+
buckets
|
|
663
|
+
});
|
|
579
664
|
const result = await answerOnce({
|
|
580
665
|
answerFn: config.answerFn,
|
|
581
666
|
client,
|
|
582
667
|
system,
|
|
583
668
|
messages: conversationMessages,
|
|
584
|
-
mode: "public"
|
|
669
|
+
mode: "public",
|
|
670
|
+
conversationId: convId,
|
|
671
|
+
model
|
|
585
672
|
});
|
|
586
|
-
const cost = calculateCost(result.usage);
|
|
673
|
+
const cost = calculateCost(result.usage, config.openai.pricing);
|
|
587
674
|
const duration = Date.now() - startTime;
|
|
588
675
|
await logger.logChatInteraction(
|
|
589
676
|
publicToolConfig.name,
|
|
@@ -593,7 +680,7 @@ ${ctx.join("\n\n")}`
|
|
|
593
680
|
result.content,
|
|
594
681
|
duration,
|
|
595
682
|
cost
|
|
596
|
-
).catch((err) =>
|
|
683
|
+
).catch((err) => log.error("Logging error:", err));
|
|
597
684
|
return {
|
|
598
685
|
content: [
|
|
599
686
|
{
|
|
@@ -613,7 +700,7 @@ ${ctx.join("\n\n")}`
|
|
|
613
700
|
};
|
|
614
701
|
}
|
|
615
702
|
);
|
|
616
|
-
|
|
703
|
+
log.info(`\u2705 Registered tool: ${publicToolConfig.name}`);
|
|
617
704
|
}
|
|
618
705
|
if (privateToolConfig.enabled) {
|
|
619
706
|
const privateShape = {
|
|
@@ -643,34 +730,28 @@ ${ctx.join("\n\n")}`
|
|
|
643
730
|
`Rate limit exceeded for ${privateToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
|
|
644
731
|
);
|
|
645
732
|
}
|
|
646
|
-
|
|
647
|
-
if (
|
|
648
|
-
conversationMessages = messages;
|
|
649
|
-
} else if (message) {
|
|
650
|
-
conversationMessages = [{ role: "user", content: String(message) }];
|
|
651
|
-
} else {
|
|
652
|
-
throw new Error("Either 'message' or 'messages' is required");
|
|
653
|
-
}
|
|
654
|
-
const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
|
|
655
|
-
if (!lastUserMsg) {
|
|
733
|
+
const conversationMessages = resolveConversationMessages(message, messages);
|
|
734
|
+
if (!lastUserMessage(conversationMessages)) {
|
|
656
735
|
throw new Error("No user message found in conversation");
|
|
657
736
|
}
|
|
658
|
-
const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor })
|
|
659
|
-
const ctx = await
|
|
660
|
-
|
|
661
|
-
prompts
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
737
|
+
const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor });
|
|
738
|
+
const { system, context: ctx } = await prepareChat({
|
|
739
|
+
store,
|
|
740
|
+
prompts,
|
|
741
|
+
mode: "private",
|
|
742
|
+
messages: conversationMessages,
|
|
743
|
+
buckets
|
|
744
|
+
});
|
|
666
745
|
const result = await answerOnce({
|
|
667
746
|
answerFn: config.answerFn,
|
|
668
747
|
client,
|
|
669
748
|
system,
|
|
670
749
|
messages: conversationMessages,
|
|
671
|
-
mode: "private"
|
|
750
|
+
mode: "private",
|
|
751
|
+
conversationId: convId,
|
|
752
|
+
model
|
|
672
753
|
});
|
|
673
|
-
const cost = calculateCost(result.usage);
|
|
754
|
+
const cost = calculateCost(result.usage, config.openai.pricing);
|
|
674
755
|
const duration = Date.now() - startTime;
|
|
675
756
|
await logger.logChatInteraction(
|
|
676
757
|
privateToolConfig.name,
|
|
@@ -680,7 +761,7 @@ ${ctx.join("\n\n")}`
|
|
|
680
761
|
result.content,
|
|
681
762
|
duration,
|
|
682
763
|
cost
|
|
683
|
-
).catch((err) =>
|
|
764
|
+
).catch((err) => log.error("Logging error:", err));
|
|
684
765
|
return {
|
|
685
766
|
content: [
|
|
686
767
|
{
|
|
@@ -700,16 +781,16 @@ ${ctx.join("\n\n")}`
|
|
|
700
781
|
};
|
|
701
782
|
}
|
|
702
783
|
);
|
|
703
|
-
|
|
784
|
+
log.info(`\u2705 Registered tool: ${privateToolConfig.name}`);
|
|
704
785
|
}
|
|
705
786
|
const enabledTools = [
|
|
706
787
|
publicToolConfig.enabled ? publicToolConfig.name : null,
|
|
707
788
|
privateToolConfig.enabled ? privateToolConfig.name : null
|
|
708
789
|
].filter(Boolean);
|
|
709
790
|
if (enabledTools.length === 0) {
|
|
710
|
-
|
|
791
|
+
log.warn("\u26A0\uFE0F Warning: No MCP tools enabled");
|
|
711
792
|
}
|
|
712
|
-
|
|
793
|
+
log.info(
|
|
713
794
|
`\u2705 ${config.bot.name} MCP Server ready (transport: ${transportMode}, tools: ${enabledTools.join(", ") || "none"})`
|
|
714
795
|
);
|
|
715
796
|
return server;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,OAGjB,OAAO,QAAQ,IAAI;;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,OAGjB,OAAO,QAAQ,IAAI;;8BA0C9D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Generous for chat text, small enough to bound worst-case parse/memory cost. */
|
|
2
|
+
export declare const DEFAULT_MAX_REQUEST_BYTES = 262144;
|
|
3
|
+
/**
|
|
4
|
+
* Rejects oversized chat request bodies before they reach JSON parsing.
|
|
5
|
+
* Wraps Hono's `bodyLimit` so the 413 body matches the caller's error shape
|
|
6
|
+
* instead of the plain-text 413 Hono sends by default. `toError` defaults to
|
|
7
|
+
* this API's usual `{ error }` shape; the OpenAI-compatible routes pass
|
|
8
|
+
* their own `{ error: { message, type } }` formatter.
|
|
9
|
+
*/
|
|
10
|
+
export declare function chatBodyLimit(maxSize?: number, toError?: (message: string) => unknown): import("hono").MiddlewareHandler;
|
|
11
|
+
//# sourceMappingURL=bodyLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bodyLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/bodyLimit.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,GAAE,MAAkC,EAC3C,OAAO,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAA2C,oCAM1E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the caller identity IP-keyed rate limiters bucket on.
|
|
4
|
+
*
|
|
5
|
+
* `X-Forwarded-For` is set by the client unless something in front of
|
|
6
|
+
* Chatter (nginx, Cloudflare, a cloud load balancer) overwrites it with the
|
|
7
|
+
* real socket address. Trusting it unconditionally lets an attacker rotate a
|
|
8
|
+
* fake value on every request and bypass per-IP limits entirely. Pass
|
|
9
|
+
* `trustProxy: true` only when such a proxy is guaranteed to be in front —
|
|
10
|
+
* otherwise every caller collapses onto one shared bucket, which is coarse
|
|
11
|
+
* but not bypassable, and pair it with a fronting proxy that does the real
|
|
12
|
+
* per-client limiting.
|
|
13
|
+
*/
|
|
14
|
+
export declare function clientRateLimitKey(c: Context, trustProxy: boolean): string;
|
|
15
|
+
//# sourceMappingURL=clientKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientKey.d.ts","sourceRoot":"","sources":["../../src/middleware/clientKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAK1E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM1C;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,oBAAqB,MAAM,EAAE,SAAe,OAAO,QAAQ,IAAI,kCA2B/E,CAAC"}
|
package/dist/middleware/jwt.d.ts
CHANGED
|
@@ -17,7 +17,4 @@ export declare function jwtSubject(c: Context): string | undefined;
|
|
|
17
17
|
export declare function createJWTMiddleware(config: ChatterConfig): (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
18
18
|
error: string;
|
|
19
19
|
}, 401, "json">) | undefined>;
|
|
20
|
-
export declare const requirePrivateJWT: (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
21
|
-
error: string;
|
|
22
|
-
}, 401, "json">) | undefined>;
|
|
23
20
|
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAO9C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,OAwBZ,OAAO,QAAQ,IAAI;;8BAa/D
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAO9C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,OAwBZ,OAAO,QAAQ,IAAI;;8BAa/D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared origin/referer matching for anything that gates on
|
|
3
|
+
* `allowedOrigins` — demo access, referrer checks. Centralized so every
|
|
4
|
+
* caller gets the same boundary-aware comparison instead of each
|
|
5
|
+
* reimplementing (and re-breaking) it.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* True when `origin` exactly equals one of `allowedOrigins`, or `referer`
|
|
9
|
+
* equals one or is prefixed by one followed by `/` or `?`. A naive
|
|
10
|
+
* `referer.startsWith(allowed)` would let `https://example.com.evil.com`
|
|
11
|
+
* match an allowed `https://example.com` — the boundary check rejects that.
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchesAllowedOrigin(origin: string | undefined, referer: string | undefined, allowedOrigins: string[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Same as {@link matchesAllowedOrigin}, plus any-port localhost/127.0.0.1 —
|
|
16
|
+
* for local-dev convenience on surfaces that would otherwise require an
|
|
17
|
+
* explicit `allowedOrigins` entry per dev port.
|
|
18
|
+
*
|
|
19
|
+
* `Origin`/`Referer` are only browser-enforced for browser-driven requests —
|
|
20
|
+
* a direct (non-browser) client can set either to anything, same as it could
|
|
21
|
+
* `Host`. This function is therefore not a substitute for real access
|
|
22
|
+
* control; callers should only reach for it where an always-on localhost
|
|
23
|
+
* allowance is an accepted tradeoff (typically gated behind an explicit
|
|
24
|
+
* opt-in), never as a blanket replacement for `matchesAllowedOrigin`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function matchesAllowedOriginOrLocalhost(origin: string | undefined, referer: string | undefined, allowedOrigins: string[]): boolean;
|
|
27
|
+
//# sourceMappingURL=originMatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"originMatch.d.ts","sourceRoot":"","sources":["../../src/middleware/originMatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAUT;AAoBD;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAGT"}
|
|
@@ -12,10 +12,4 @@ export declare function createRateLimiter(config: ChatterConfig): {
|
|
|
12
12
|
error: string;
|
|
13
13
|
}, 429, "json">) | undefined>;
|
|
14
14
|
};
|
|
15
|
-
export declare const limitPublic: () => (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
16
|
-
error: string;
|
|
17
|
-
}, 429, "json">) | undefined>;
|
|
18
|
-
export declare const limitPrivate: () => (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
19
|
-
error: string;
|
|
20
|
-
}, 429, "json">) | undefined>;
|
|
21
15
|
//# sourceMappingURL=ratelimit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../src/middleware/ratelimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../src/middleware/ratelimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAQ9C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;IAWnD,WAAW,QACQ,OAAO,QAAQ,IAAI;;;IAwBtC,YAAY,QACO,OAAO,QAAQ,IAAI;;;EAQzC"}
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* Validates that requests with demo API keys come from allowed origins
|
|
4
4
|
*/
|
|
5
5
|
import type { Context, Next } from "hono";
|
|
6
|
+
import { type Logger } from "../core/logger";
|
|
6
7
|
/**
|
|
7
8
|
* Middleware to check if the request comes from an allowed origin
|
|
8
9
|
* Only applies to demo API keys and session keys - production keys bypass this check
|
|
10
|
+
*
|
|
11
|
+
* @param demoApiKeys - API key values treated as demo keys (see
|
|
12
|
+
* `ChatterConfig.rateLimit.demoApiKeys`). Unset: no key is a demo key.
|
|
9
13
|
*/
|
|
10
|
-
export declare function requireReferrer(allowedOrigins: string[]): (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
14
|
+
export declare function requireReferrer(allowedOrigins: string[], demoApiKeys?: string[], logger?: Logger): (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
11
15
|
error: string;
|
|
12
16
|
}, 403, "json">) | undefined>;
|
|
13
17
|
export declare const createReferrerCheck: typeof requireReferrer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referrer.d.ts","sourceRoot":"","sources":["../../src/middleware/referrer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"referrer.d.ts","sourceRoot":"","sources":["../../src/middleware/referrer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGlE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,EAAE,EACxB,WAAW,GAAE,MAAM,EAAO,EAC1B,MAAM,GAAE,MAA8B,OAErB,OAAO,QAAQ,IAAI;;8BAkCrC;AAGD,eAAO,MAAM,mBAAmB,wBAAkB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Context, Next } from "hono";
|
|
2
|
+
import type { ChatterConfig } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Sets baseline security headers on every response: a Content-Security-Policy
|
|
5
|
+
* (governs pages this server itself renders — see `server.contentSecurityPolicy`),
|
|
6
|
+
* `X-Content-Type-Options` to stop MIME sniffing, and HSTS (see
|
|
7
|
+
* `server.strictTransportSecurity`). HSTS is only sent when the request
|
|
8
|
+
* actually arrived over HTTPS (directly, or via a trusted proxy's
|
|
9
|
+
* `X-Forwarded-Proto` — see `rateLimit.trustProxy`) or `NODE_ENV=production`.
|
|
10
|
+
* A browser ignores HSTS delivered over plain HTTP (RFC 6797 §8.1), so this
|
|
11
|
+
* gate is about not asserting a guarantee the deployment doesn't back, not
|
|
12
|
+
* about avoiding a localhost lockout.
|
|
13
|
+
*
|
|
14
|
+
* Headers are set after `next()` so they still land on a handler that
|
|
15
|
+
* returns a raw `Response` (e.g. from `config.customRoutes`) rather than
|
|
16
|
+
* `c.json`/`c.text` — Hono only merges headers set before `next()` into
|
|
17
|
+
* responses it builds itself.
|
|
18
|
+
*/
|
|
19
|
+
export declare function securityHeaders(config: ChatterConfig): (c: Context, next: Next) => Promise<void>;
|
|
20
|
+
//# sourceMappingURL=securityHeaders.d.ts.map
|