@diegoaltoworks/chatter 0.57.0 → 0.59.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/dist/channels/index.js +18 -3
- package/dist/channels/index.mjs +18 -3
- package/dist/channels/pipeline.d.ts +8 -0
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/telegram/api.d.ts +17 -0
- package/dist/channels/telegram/api.d.ts.map +1 -1
- package/dist/channels/telegram/channel.d.ts +11 -1
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/handler.d.ts +31 -0
- package/dist/channels/telegram/handler.d.ts.map +1 -0
- package/dist/channels/telegram/index.d.ts +7 -0
- package/dist/channels/telegram/index.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +319 -82
- package/dist/channels/telegram/index.mjs +316 -82
- package/dist/channels/telegram/webhook.d.ts +95 -0
- package/dist/channels/telegram/webhook.d.ts.map +1 -0
- package/dist/channels/whatsapp/inbound.d.ts +6 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +18 -3
- package/dist/channels/whatsapp/index.mjs +18 -3
- package/dist/history/index.d.ts +1 -0
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +23 -2
- package/dist/history/index.mjs +23 -2
- package/dist/history/tursoStore.d.ts +13 -1
- package/dist/history/tursoStore.d.ts.map +1 -1
- package/dist/history/types.d.ts +2 -0
- package/dist/history/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -146,6 +146,17 @@ function createTelegramApi(config) {
|
|
|
146
146
|
message_id: messageId,
|
|
147
147
|
reaction: [{ type: "emoji", emoji }]
|
|
148
148
|
});
|
|
149
|
+
},
|
|
150
|
+
async setWebhook(url, options) {
|
|
151
|
+
await call("setWebhook", {
|
|
152
|
+
url,
|
|
153
|
+
// Only what this channel interprets — see the matching comment on getUpdates.
|
|
154
|
+
allowed_updates: ["message"],
|
|
155
|
+
...options?.secretToken ? { secret_token: options.secretToken } : {}
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
async deleteWebhook() {
|
|
159
|
+
await call("deleteWebhook");
|
|
149
160
|
}
|
|
150
161
|
};
|
|
151
162
|
}
|
|
@@ -166,56 +177,6 @@ function createConsoleLogger(level = "info") {
|
|
|
166
177
|
};
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
// src/channels/gates.ts
|
|
170
|
-
function matches(re, text) {
|
|
171
|
-
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
172
|
-
}
|
|
173
|
-
function isBlockedByAllowlist(msg, config) {
|
|
174
|
-
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
175
|
-
}
|
|
176
|
-
function decideChannelAction(msg, config) {
|
|
177
|
-
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
178
|
-
if (!msg.isDirectMessage) {
|
|
179
|
-
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
180
|
-
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
181
|
-
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
182
|
-
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
183
|
-
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
184
|
-
}
|
|
185
|
-
return "reply";
|
|
186
|
-
}
|
|
187
|
-
function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
|
|
188
|
-
const log = /* @__PURE__ */ new Map();
|
|
189
|
-
let lastSweep = now();
|
|
190
|
-
function sweep(current) {
|
|
191
|
-
if (current - lastSweep < windowMs) return;
|
|
192
|
-
lastSweep = current;
|
|
193
|
-
const cutoff = current - windowMs;
|
|
194
|
-
for (const [key, entries] of log) {
|
|
195
|
-
if (!entries.some((t) => t > cutoff)) log.delete(key);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function allow(key) {
|
|
199
|
-
const current = now();
|
|
200
|
-
sweep(current);
|
|
201
|
-
const cutoff = current - windowMs;
|
|
202
|
-
const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
|
|
203
|
-
if (entries.length >= maxPerWindow) {
|
|
204
|
-
if (entries.length > 0) log.set(key, entries);
|
|
205
|
-
else log.delete(key);
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
entries.push(current);
|
|
209
|
-
log.set(key, entries);
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
allow.size = () => log.size;
|
|
213
|
-
return allow;
|
|
214
|
-
}
|
|
215
|
-
function underReplyRateLimit(msg, limiters) {
|
|
216
|
-
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
180
|
// src/core/guardrails.ts
|
|
220
181
|
function detectLeakage(text) {
|
|
221
182
|
const flags = [
|
|
@@ -387,6 +348,56 @@ ${ctx.join("\n\n")}`
|
|
|
387
348
|
return { system, messages, context: ctx };
|
|
388
349
|
}
|
|
389
350
|
|
|
351
|
+
// src/channels/gates.ts
|
|
352
|
+
function matches(re, text) {
|
|
353
|
+
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
354
|
+
}
|
|
355
|
+
function isBlockedByAllowlist(msg, config) {
|
|
356
|
+
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
357
|
+
}
|
|
358
|
+
function decideChannelAction(msg, config) {
|
|
359
|
+
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
360
|
+
if (!msg.isDirectMessage) {
|
|
361
|
+
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
362
|
+
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
363
|
+
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
364
|
+
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
365
|
+
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
366
|
+
}
|
|
367
|
+
return "reply";
|
|
368
|
+
}
|
|
369
|
+
function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
|
|
370
|
+
const log = /* @__PURE__ */ new Map();
|
|
371
|
+
let lastSweep = now();
|
|
372
|
+
function sweep(current) {
|
|
373
|
+
if (current - lastSweep < windowMs) return;
|
|
374
|
+
lastSweep = current;
|
|
375
|
+
const cutoff = current - windowMs;
|
|
376
|
+
for (const [key, entries] of log) {
|
|
377
|
+
if (!entries.some((t) => t > cutoff)) log.delete(key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function allow(key) {
|
|
381
|
+
const current = now();
|
|
382
|
+
sweep(current);
|
|
383
|
+
const cutoff = current - windowMs;
|
|
384
|
+
const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
|
|
385
|
+
if (entries.length >= maxPerWindow) {
|
|
386
|
+
if (entries.length > 0) log.set(key, entries);
|
|
387
|
+
else log.delete(key);
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
entries.push(current);
|
|
391
|
+
log.set(key, entries);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
allow.size = () => log.size;
|
|
395
|
+
return allow;
|
|
396
|
+
}
|
|
397
|
+
function underReplyRateLimit(msg, limiters) {
|
|
398
|
+
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
399
|
+
}
|
|
400
|
+
|
|
390
401
|
// src/channels/pipeline.ts
|
|
391
402
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
392
403
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
@@ -399,6 +410,16 @@ async function resolvePersonaLayer(config, ctx) {
|
|
|
399
410
|
return void 0;
|
|
400
411
|
}
|
|
401
412
|
}
|
|
413
|
+
async function isHistoryEnabled(config, sender) {
|
|
414
|
+
const predicate = config.history?.historyEnabledFor;
|
|
415
|
+
if (!predicate) return true;
|
|
416
|
+
try {
|
|
417
|
+
return await predicate(sender);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
config.logger?.error("historyEnabledFor threw; disabling history for this turn", error);
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
402
423
|
function createInboundPipeline(deps, config) {
|
|
403
424
|
const mutedChats = /* @__PURE__ */ new Set();
|
|
404
425
|
const now = config.now ?? Date.now;
|
|
@@ -438,12 +459,15 @@ function createInboundPipeline(deps, config) {
|
|
|
438
459
|
}
|
|
439
460
|
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
440
461
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
441
|
-
const
|
|
462
|
+
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
463
|
+
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
442
464
|
conversationId,
|
|
443
465
|
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
444
466
|
) : [];
|
|
445
467
|
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
446
|
-
|
|
468
|
+
if (historyEnabled) {
|
|
469
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
470
|
+
}
|
|
447
471
|
const { system } = await prepareChat({
|
|
448
472
|
store: deps.store,
|
|
449
473
|
prompts: deps.prompts,
|
|
@@ -475,7 +499,9 @@ function createInboundPipeline(deps, config) {
|
|
|
475
499
|
if (!content) {
|
|
476
500
|
return { action: "ignore" };
|
|
477
501
|
}
|
|
478
|
-
|
|
502
|
+
if (historyEnabled) {
|
|
503
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
504
|
+
}
|
|
479
505
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
480
506
|
return { action: "reply", content };
|
|
481
507
|
};
|
|
@@ -524,6 +550,41 @@ function telegramSenderKey(senderId) {
|
|
|
524
550
|
return `tg:${senderId}`;
|
|
525
551
|
}
|
|
526
552
|
|
|
553
|
+
// src/channels/telegram/handler.ts
|
|
554
|
+
function createTelegramUpdateHandler(deps) {
|
|
555
|
+
const { api, me, pipeline, allowedChats, logger, label } = deps;
|
|
556
|
+
const loggedUnallowedChats = /* @__PURE__ */ new Set();
|
|
557
|
+
return async function handleUpdate(update) {
|
|
558
|
+
const message = update.message;
|
|
559
|
+
const msg = toChannelMessage(update, me);
|
|
560
|
+
if (!msg || !message) return;
|
|
561
|
+
if (isBlockedByAllowlist(msg, { allowedChats })) {
|
|
562
|
+
if (!loggedUnallowedChats.has(msg.chatId)) {
|
|
563
|
+
loggedUnallowedChats.add(msg.chatId);
|
|
564
|
+
logger.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const reply = {
|
|
568
|
+
// Threaded onto the incoming message: in a busy group an untethered
|
|
569
|
+
// answer reads as a non-sequitur.
|
|
570
|
+
sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
|
|
571
|
+
sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
|
|
572
|
+
};
|
|
573
|
+
await pipeline(msg, {
|
|
574
|
+
reply,
|
|
575
|
+
conversationId: msg.chatId,
|
|
576
|
+
sender: telegramSenderKey(msg.senderId)
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function createTelegramSender(api) {
|
|
581
|
+
return {
|
|
582
|
+
sendText: (chatId, text) => api.sendMessage(chatId, text),
|
|
583
|
+
sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
|
|
584
|
+
sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
527
588
|
// src/channels/telegram/poll.ts
|
|
528
589
|
var BASE_DELAY_MS = 1e3;
|
|
529
590
|
var MAX_DELAY_MS = 6e4;
|
|
@@ -594,12 +655,7 @@ function createTelegramChannel(config) {
|
|
|
594
655
|
});
|
|
595
656
|
const me = await api.getMe();
|
|
596
657
|
const label = `Telegram[${me.username ?? me.id}]`;
|
|
597
|
-
|
|
598
|
-
sendText: (chatId, text) => api.sendMessage(chatId, text),
|
|
599
|
-
sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
|
|
600
|
-
sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
|
|
601
|
-
};
|
|
602
|
-
deps.senders.register(channelName, sender);
|
|
658
|
+
deps.senders.register(channelName, createTelegramSender(api));
|
|
603
659
|
senders = deps.senders;
|
|
604
660
|
registered = true;
|
|
605
661
|
const pipeline = createInboundPipeline(
|
|
@@ -626,29 +682,14 @@ function createTelegramChannel(config) {
|
|
|
626
682
|
logger: log
|
|
627
683
|
}
|
|
628
684
|
);
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
log.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
const reply = {
|
|
641
|
-
// Threaded onto the incoming message: in a busy group an untethered
|
|
642
|
-
// answer reads as a non-sequitur.
|
|
643
|
-
sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
|
|
644
|
-
sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
|
|
645
|
-
};
|
|
646
|
-
await pipeline(msg, {
|
|
647
|
-
reply,
|
|
648
|
-
conversationId: msg.chatId,
|
|
649
|
-
sender: telegramSenderKey(msg.senderId)
|
|
650
|
-
});
|
|
651
|
-
}
|
|
685
|
+
const handleUpdate = createTelegramUpdateHandler({
|
|
686
|
+
api,
|
|
687
|
+
me,
|
|
688
|
+
pipeline,
|
|
689
|
+
allowedChats,
|
|
690
|
+
logger: log,
|
|
691
|
+
label
|
|
692
|
+
});
|
|
652
693
|
abort = new AbortController();
|
|
653
694
|
const signal = abort.signal;
|
|
654
695
|
void runLongPoll({
|
|
@@ -673,12 +714,205 @@ function createTelegramChannel(config) {
|
|
|
673
714
|
}
|
|
674
715
|
};
|
|
675
716
|
}
|
|
717
|
+
|
|
718
|
+
// src/channels/telegram/webhook.ts
|
|
719
|
+
import { timingSafeEqual } from "node:crypto";
|
|
720
|
+
|
|
721
|
+
// node_modules/hono/dist/http-exception.js
|
|
722
|
+
var HTTPException = class extends Error {
|
|
723
|
+
res;
|
|
724
|
+
status;
|
|
725
|
+
/**
|
|
726
|
+
* Creates an instance of `HTTPException`.
|
|
727
|
+
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
728
|
+
* @param options - Additional options for the exception.
|
|
729
|
+
*/
|
|
730
|
+
constructor(status = 500, options) {
|
|
731
|
+
super(options?.message, { cause: options?.cause });
|
|
732
|
+
this.res = options?.res;
|
|
733
|
+
this.status = status;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Returns the response object associated with the exception.
|
|
737
|
+
* If a response object is not provided, a new response is created with the error message and status code.
|
|
738
|
+
* @returns The response object.
|
|
739
|
+
*/
|
|
740
|
+
getResponse() {
|
|
741
|
+
if (this.res) {
|
|
742
|
+
const newResponse = new Response(this.res.body, {
|
|
743
|
+
status: this.status,
|
|
744
|
+
headers: this.res.headers
|
|
745
|
+
});
|
|
746
|
+
return newResponse;
|
|
747
|
+
}
|
|
748
|
+
return new Response(this.message, {
|
|
749
|
+
status: this.status
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
// node_modules/hono/dist/middleware/body-limit/index.js
|
|
755
|
+
var ERROR_MESSAGE = "Payload Too Large";
|
|
756
|
+
var bodyLimit = (options) => {
|
|
757
|
+
const onError = options.onError || (() => {
|
|
758
|
+
const res = new Response(ERROR_MESSAGE, {
|
|
759
|
+
status: 413
|
|
760
|
+
});
|
|
761
|
+
throw new HTTPException(413, { res });
|
|
762
|
+
});
|
|
763
|
+
const maxSize = options.maxSize;
|
|
764
|
+
return async function bodyLimit2(c, next) {
|
|
765
|
+
if (!c.req.raw.body) {
|
|
766
|
+
return next();
|
|
767
|
+
}
|
|
768
|
+
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
|
|
769
|
+
const hasContentLength = c.req.raw.headers.has("content-length");
|
|
770
|
+
if (hasContentLength && !hasTransferEncoding) {
|
|
771
|
+
const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
|
|
772
|
+
return contentLength > maxSize ? onError(c) : next();
|
|
773
|
+
}
|
|
774
|
+
let size = 0;
|
|
775
|
+
const chunks = [];
|
|
776
|
+
const rawReader = c.req.raw.body.getReader();
|
|
777
|
+
for (; ; ) {
|
|
778
|
+
const { done, value } = await rawReader.read();
|
|
779
|
+
if (done) {
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
size += value.length;
|
|
783
|
+
if (size > maxSize) {
|
|
784
|
+
return onError(c);
|
|
785
|
+
}
|
|
786
|
+
chunks.push(value);
|
|
787
|
+
}
|
|
788
|
+
const requestInit = {
|
|
789
|
+
body: new ReadableStream({
|
|
790
|
+
start(controller) {
|
|
791
|
+
for (const chunk of chunks) {
|
|
792
|
+
controller.enqueue(chunk);
|
|
793
|
+
}
|
|
794
|
+
controller.close();
|
|
795
|
+
}
|
|
796
|
+
}),
|
|
797
|
+
duplex: "half"
|
|
798
|
+
};
|
|
799
|
+
c.req.raw = new Request(c.req.raw, requestInit);
|
|
800
|
+
return next();
|
|
801
|
+
};
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// src/middleware/bodyLimit.ts
|
|
805
|
+
var DEFAULT_MAX_REQUEST_BYTES = 262144;
|
|
806
|
+
function chatBodyLimit(maxSize = DEFAULT_MAX_REQUEST_BYTES, toError = (message) => ({ error: message })) {
|
|
807
|
+
return bodyLimit({
|
|
808
|
+
maxSize,
|
|
809
|
+
onError: (c) => c.json(toError("Request body too large"), 413)
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/channels/telegram/webhook.ts
|
|
814
|
+
var SECRET_TOKEN_HEADER = "x-telegram-bot-api-secret-token";
|
|
815
|
+
function secureCompare(a, b) {
|
|
816
|
+
const bufA = Buffer.from(a);
|
|
817
|
+
const bufB = Buffer.from(b);
|
|
818
|
+
return bufA.length === bufB.length && timingSafeEqual(bufA, bufB);
|
|
819
|
+
}
|
|
820
|
+
var SECRET_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,256}$/;
|
|
821
|
+
function createTelegramWebhookRoute(config) {
|
|
822
|
+
const webhookSecret = config.webhookSecret;
|
|
823
|
+
if (!webhookSecret?.trim()) {
|
|
824
|
+
throw new Error("Telegram webhook requires webhookSecret (it fails closed without one)");
|
|
825
|
+
}
|
|
826
|
+
if (!SECRET_TOKEN_PATTERN.test(webhookSecret)) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
"Telegram webhookSecret must match /^[A-Za-z0-9_-]{1,256}$/ (Telegram's own secret_token charset)"
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
const channelName = config.name ?? "telegram";
|
|
832
|
+
const allowedChats = config.allowedChats ?? [];
|
|
833
|
+
const path = config.path ?? "/webhooks/telegram";
|
|
834
|
+
return async (app, deps) => {
|
|
835
|
+
const log = config.logger ?? deps.logger ?? createConsoleLogger();
|
|
836
|
+
const api = config.api ?? createTelegramApi({
|
|
837
|
+
botToken: config.botToken,
|
|
838
|
+
fetch: config.fetch,
|
|
839
|
+
baseUrl: config.apiBaseUrl
|
|
840
|
+
});
|
|
841
|
+
const me = await api.getMe();
|
|
842
|
+
const label = `Telegram[${me.username ?? me.id}] (webhook)`;
|
|
843
|
+
deps.senders.register(channelName, createTelegramSender(api));
|
|
844
|
+
const pipeline = createInboundPipeline(
|
|
845
|
+
{ client: deps.client, store: deps.store, prompts: deps.prompts },
|
|
846
|
+
{
|
|
847
|
+
channel: channelName,
|
|
848
|
+
answerFn: config.answerFn ?? deps.config.answerFn,
|
|
849
|
+
bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
|
|
850
|
+
rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
|
|
851
|
+
rerankContext: config.rerankContext ?? deps.config.rerankContext,
|
|
852
|
+
transformReply: config.transformReply ?? deps.config.transformReply,
|
|
853
|
+
model: config.model,
|
|
854
|
+
channelHint: config.channelHint ?? "Channel: Telegram.",
|
|
855
|
+
personaResolver: config.personaResolver,
|
|
856
|
+
history: config.history,
|
|
857
|
+
allowedChats,
|
|
858
|
+
muteRegex: config.muteRegex,
|
|
859
|
+
unmuteRegex: config.unmuteRegex,
|
|
860
|
+
muteReply: config.muteReply,
|
|
861
|
+
unmuteReply: config.unmuteReply,
|
|
862
|
+
dmRateLimit: config.dmRateLimit,
|
|
863
|
+
groupRateLimit: config.groupRateLimit,
|
|
864
|
+
now: config.now,
|
|
865
|
+
logger: log
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
const handleUpdate = createTelegramUpdateHandler({
|
|
869
|
+
api,
|
|
870
|
+
me,
|
|
871
|
+
pipeline,
|
|
872
|
+
allowedChats,
|
|
873
|
+
logger: log,
|
|
874
|
+
label
|
|
875
|
+
});
|
|
876
|
+
app.post(
|
|
877
|
+
path,
|
|
878
|
+
chatBodyLimit(deps.config.server?.maxRequestBytes),
|
|
879
|
+
// Reject oversized bodies first
|
|
880
|
+
async (c) => {
|
|
881
|
+
const provided = c.req.header(SECRET_TOKEN_HEADER);
|
|
882
|
+
if (!provided || !secureCompare(provided, webhookSecret)) {
|
|
883
|
+
log.warn(`${label}: rejected webhook POST - missing or invalid secret token`);
|
|
884
|
+
return c.text("", 403);
|
|
885
|
+
}
|
|
886
|
+
let body;
|
|
887
|
+
try {
|
|
888
|
+
body = await c.req.json();
|
|
889
|
+
} catch {
|
|
890
|
+
return c.text("", 400);
|
|
891
|
+
}
|
|
892
|
+
if (typeof body !== "object" || body === null) {
|
|
893
|
+
return c.text("", 400);
|
|
894
|
+
}
|
|
895
|
+
const update = body;
|
|
896
|
+
try {
|
|
897
|
+
await handleUpdate(update);
|
|
898
|
+
} catch (error) {
|
|
899
|
+
log.warn(`${label}: update handling failed:`, error);
|
|
900
|
+
}
|
|
901
|
+
return c.text("", 200);
|
|
902
|
+
}
|
|
903
|
+
);
|
|
904
|
+
log.info(`${label}: webhook mounted at ${path} as @${me.username ?? me.id}`);
|
|
905
|
+
};
|
|
906
|
+
}
|
|
676
907
|
export {
|
|
677
908
|
TELEGRAM_API_BASE,
|
|
678
909
|
TELEGRAM_TEXT_LIMIT,
|
|
679
910
|
TelegramApiError,
|
|
680
911
|
createTelegramApi,
|
|
681
912
|
createTelegramChannel,
|
|
913
|
+
createTelegramSender,
|
|
914
|
+
createTelegramUpdateHandler,
|
|
915
|
+
createTelegramWebhookRoute,
|
|
682
916
|
mentionsBot,
|
|
683
917
|
messageEntities,
|
|
684
918
|
messageText,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Bot API transport: a `CustomRoutes` mount that receives updates
|
|
3
|
+
* over HTTPS instead of long-polling `getUpdates` (see `./channel` for that
|
|
4
|
+
* transport). Update handling is identical between the two — both build on
|
|
5
|
+
* `./handler`'s shared `createTelegramUpdateHandler`/`createTelegramSender` —
|
|
6
|
+
* so choosing one over the other is purely an infrastructure decision: a
|
|
7
|
+
* webhook needs a publicly reachable HTTPS endpoint and no open connection,
|
|
8
|
+
* long-polling needs neither but holds one request open at a time. See
|
|
9
|
+
* docs/telegram.md for the full comparison and `TelegramApi.setWebhook` for
|
|
10
|
+
* registering the URL with Telegram once this route is mounted.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* await createServer({
|
|
14
|
+
* ...,
|
|
15
|
+
* customRoutes: createTelegramWebhookRoute({
|
|
16
|
+
* botToken: process.env.TELEGRAM_BOT_TOKEN as string,
|
|
17
|
+
* webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET as string,
|
|
18
|
+
* }),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { AnswerFn, TransformReply } from "../../core/answer";
|
|
23
|
+
import type { BucketsFor } from "../../core/buckets";
|
|
24
|
+
import { type Logger } from "../../core/logger";
|
|
25
|
+
import type { RerankContext, RewriteQuery } from "../../core/pipeline";
|
|
26
|
+
import type { HistoryStore } from "../../history/types";
|
|
27
|
+
import type { CustomRoutes } from "../../types";
|
|
28
|
+
import { type TelegramApi } from "./api";
|
|
29
|
+
export interface TelegramWebhookConfig {
|
|
30
|
+
/** From @BotFather. A credential — pass it from the environment, never commit it. */
|
|
31
|
+
botToken: string;
|
|
32
|
+
/**
|
|
33
|
+
* Shared secret Telegram is told about via `setWebhook({ secretToken })`
|
|
34
|
+
* and then echoes back on every POST as `X-Telegram-Bot-Api-Secret-Token`.
|
|
35
|
+
* Required: without one there is no way to tell a genuine Telegram POST
|
|
36
|
+
* from a forged one, so this fails closed by construction — a missing or
|
|
37
|
+
* blank value throws rather than falling back to accepting every request.
|
|
38
|
+
*/
|
|
39
|
+
webhookSecret: string;
|
|
40
|
+
/** Where the route is mounted. @default "/webhooks/telegram" */
|
|
41
|
+
path?: string;
|
|
42
|
+
/** Channel and sender-registry name. Override to run more than one bot in one process. @default "telegram" */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Group chats eligible for a reply. Empty (default) = every group. Has no effect on DMs, which always reply. */
|
|
45
|
+
allowedChats?: string[];
|
|
46
|
+
answerFn?: AnswerFn;
|
|
47
|
+
bucketsFor?: BucketsFor;
|
|
48
|
+
rewriteQuery?: RewriteQuery;
|
|
49
|
+
rerankContext?: RerankContext;
|
|
50
|
+
transformReply?: TransformReply;
|
|
51
|
+
model?: string;
|
|
52
|
+
/** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. @default "Channel: Telegram." */
|
|
53
|
+
channelHint?: string;
|
|
54
|
+
/** A throw/rejection is treated as "no persona" for that turn. `sender` is the namespaced `tg:<id>` key. */
|
|
55
|
+
personaResolver?: (ctx: {
|
|
56
|
+
sender: string;
|
|
57
|
+
text: string;
|
|
58
|
+
}) => string | undefined | Promise<string | undefined>;
|
|
59
|
+
/** Off by default — the channel stays single-turn until a store is configured. */
|
|
60
|
+
history?: {
|
|
61
|
+
store: HistoryStore;
|
|
62
|
+
/** Most recent turns to load per reply. @default 20 */
|
|
63
|
+
limit?: number;
|
|
64
|
+
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
65
|
+
};
|
|
66
|
+
muteRegex?: RegExp;
|
|
67
|
+
unmuteRegex?: RegExp;
|
|
68
|
+
muteReply?: string;
|
|
69
|
+
unmuteReply?: string;
|
|
70
|
+
dmRateLimit?: {
|
|
71
|
+
max: number;
|
|
72
|
+
windowMs: number;
|
|
73
|
+
};
|
|
74
|
+
groupRateLimit?: {
|
|
75
|
+
max: number;
|
|
76
|
+
windowMs: number;
|
|
77
|
+
};
|
|
78
|
+
now?: () => number;
|
|
79
|
+
/** Overridable for tests and for hosts routing through a proxy; defaults to `globalThis.fetch`. */
|
|
80
|
+
fetch?: typeof fetch;
|
|
81
|
+
/** Self-hosted Bot API server origin. */
|
|
82
|
+
apiBaseUrl?: string;
|
|
83
|
+
/** Overridable for tests, which fake the Bot API instead of calling it; defaults to a `fetch` client over {@link TelegramWebhookConfig.botToken}. */
|
|
84
|
+
api?: TelegramApi;
|
|
85
|
+
logger?: Logger;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Builds a `CustomRoutes` mount that receives Telegram updates over HTTPS.
|
|
89
|
+
* Throws synchronously if `webhookSecret` is missing, blank, or outside the
|
|
90
|
+
* charset Telegram accepts for it — before the route ever exists, rather
|
|
91
|
+
* than mounting an endpoint that would silently accept everything or reject
|
|
92
|
+
* every request.
|
|
93
|
+
*/
|
|
94
|
+
export declare function createTelegramWebhookRoute(config: TelegramWebhookConfig): CustomRoutes;
|
|
95
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAMjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACpE,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qJAAqJ;IACrJ,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,qBAAqB,GAAG,YAAY,CAkHtF"}
|
|
@@ -193,6 +193,12 @@ export interface WhatsAppInboundConfig {
|
|
|
193
193
|
store: HistoryStore;
|
|
194
194
|
/** Most recent turns to load per reply. @default 20 */
|
|
195
195
|
limit?: number;
|
|
196
|
+
/**
|
|
197
|
+
* Excludes a sender from history entirely — see
|
|
198
|
+
* `InboundPipelineConfig.history.historyEnabledFor` in `./channels`.
|
|
199
|
+
* @default every sender is enabled
|
|
200
|
+
*/
|
|
201
|
+
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
196
202
|
};
|
|
197
203
|
now?: () => number;
|
|
198
204
|
/** Logger for gate/dispatch diagnostics. Default: a console logger writing to stderr. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -852,6 +852,16 @@ async function resolvePersonaLayer(config, ctx) {
|
|
|
852
852
|
return void 0;
|
|
853
853
|
}
|
|
854
854
|
}
|
|
855
|
+
async function isHistoryEnabled(config, sender) {
|
|
856
|
+
const predicate = config.history?.historyEnabledFor;
|
|
857
|
+
if (!predicate) return true;
|
|
858
|
+
try {
|
|
859
|
+
return await predicate(sender);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
config.logger?.error("historyEnabledFor threw; disabling history for this turn", error);
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
855
865
|
function createInboundPipeline(deps, config) {
|
|
856
866
|
const mutedChats = /* @__PURE__ */ new Set();
|
|
857
867
|
const now = config.now ?? Date.now;
|
|
@@ -891,12 +901,15 @@ function createInboundPipeline(deps, config) {
|
|
|
891
901
|
}
|
|
892
902
|
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
893
903
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
894
|
-
const
|
|
904
|
+
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
905
|
+
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
895
906
|
conversationId,
|
|
896
907
|
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
897
908
|
) : [];
|
|
898
909
|
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
899
|
-
|
|
910
|
+
if (historyEnabled) {
|
|
911
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
912
|
+
}
|
|
900
913
|
const { system } = await prepareChat({
|
|
901
914
|
store: deps.store,
|
|
902
915
|
prompts: deps.prompts,
|
|
@@ -928,7 +941,9 @@ function createInboundPipeline(deps, config) {
|
|
|
928
941
|
if (!content) {
|
|
929
942
|
return { action: "ignore" };
|
|
930
943
|
}
|
|
931
|
-
|
|
944
|
+
if (historyEnabled) {
|
|
945
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
946
|
+
}
|
|
932
947
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
933
948
|
return { action: "reply", content };
|
|
934
949
|
};
|