@diegoaltoworks/chatter 0.55.0 → 0.57.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.
@@ -160,6 +160,18 @@ async function answerOnce({
160
160
  }
161
161
  return completeOnce({ client, system, messages, temperature, model });
162
162
  }
163
+ async function applyTransformReply(transformReply, input, logger) {
164
+ if (!transformReply) return input.text;
165
+ try {
166
+ return await transformReply(input);
167
+ } catch (error) {
168
+ logger?.error(
169
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
170
+ error
171
+ );
172
+ return input.text;
173
+ }
174
+ }
163
175
 
164
176
  // src/core/buckets.ts
165
177
  function defaultBuckets(mode) {
@@ -209,14 +221,35 @@ async function prepareChat({
209
221
  messages,
210
222
  personaLayer,
211
223
  channelHint,
212
- buckets
224
+ buckets,
225
+ sender,
226
+ rewriteQuery,
227
+ rerankContext,
228
+ logger
213
229
  }) {
214
230
  const lastUserMsg = lastUserMessage(messages);
215
231
  if (!lastUserMsg) {
216
232
  throw new Error("no user message found in conversation");
217
233
  }
218
234
  const { topK, label } = MODE_SETTINGS[mode];
219
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
235
+ let query = lastUserMsg.content;
236
+ if (rewriteQuery) {
237
+ try {
238
+ const rewritten = await rewriteQuery({ query, mode, sender });
239
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
240
+ } catch (error) {
241
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
242
+ }
243
+ }
244
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
245
+ if (rerankContext) {
246
+ try {
247
+ const reranked = await rerankContext({ query, chunks: ctx });
248
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
249
+ } catch (error) {
250
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
251
+ }
252
+ }
220
253
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
221
254
  const hint = channelHint?.trim();
222
255
  const system = [
@@ -293,9 +326,13 @@ function createInboundPipeline(deps, config) {
293
326
  messages,
294
327
  personaLayer,
295
328
  channelHint: config.channelHint,
296
- buckets
329
+ buckets,
330
+ sender,
331
+ rewriteQuery: config.rewriteQuery,
332
+ rerankContext: config.rerankContext,
333
+ logger: config.logger
297
334
  });
298
- const { content } = await answerOnce({
335
+ const { content: produced } = await answerOnce({
299
336
  answerFn: config.answerFn,
300
337
  client: deps.client,
301
338
  system,
@@ -305,6 +342,11 @@ function createInboundPipeline(deps, config) {
305
342
  conversationId,
306
343
  model: config.model
307
344
  });
345
+ const content = await applyTransformReply(
346
+ config.transformReply,
347
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
348
+ config.logger
349
+ );
308
350
  if (!content) {
309
351
  return { action: "ignore" };
310
352
  }
@@ -127,6 +127,18 @@ async function answerOnce({
127
127
  }
128
128
  return completeOnce({ client, system, messages, temperature, model });
129
129
  }
130
+ async function applyTransformReply(transformReply, input, logger) {
131
+ if (!transformReply) return input.text;
132
+ try {
133
+ return await transformReply(input);
134
+ } catch (error) {
135
+ logger?.error(
136
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
137
+ error
138
+ );
139
+ return input.text;
140
+ }
141
+ }
130
142
 
131
143
  // src/core/buckets.ts
132
144
  function defaultBuckets(mode) {
@@ -176,14 +188,35 @@ async function prepareChat({
176
188
  messages,
177
189
  personaLayer,
178
190
  channelHint,
179
- buckets
191
+ buckets,
192
+ sender,
193
+ rewriteQuery,
194
+ rerankContext,
195
+ logger
180
196
  }) {
181
197
  const lastUserMsg = lastUserMessage(messages);
182
198
  if (!lastUserMsg) {
183
199
  throw new Error("no user message found in conversation");
184
200
  }
185
201
  const { topK, label } = MODE_SETTINGS[mode];
186
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
202
+ let query = lastUserMsg.content;
203
+ if (rewriteQuery) {
204
+ try {
205
+ const rewritten = await rewriteQuery({ query, mode, sender });
206
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
207
+ } catch (error) {
208
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
209
+ }
210
+ }
211
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
212
+ if (rerankContext) {
213
+ try {
214
+ const reranked = await rerankContext({ query, chunks: ctx });
215
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
216
+ } catch (error) {
217
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
218
+ }
219
+ }
187
220
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
188
221
  const hint = channelHint?.trim();
189
222
  const system = [
@@ -260,9 +293,13 @@ function createInboundPipeline(deps, config) {
260
293
  messages,
261
294
  personaLayer,
262
295
  channelHint: config.channelHint,
263
- buckets
296
+ buckets,
297
+ sender,
298
+ rewriteQuery: config.rewriteQuery,
299
+ rerankContext: config.rerankContext,
300
+ logger: config.logger
264
301
  });
265
- const { content } = await answerOnce({
302
+ const { content: produced } = await answerOnce({
266
303
  answerFn: config.answerFn,
267
304
  client: deps.client,
268
305
  system,
@@ -272,6 +309,11 @@ function createInboundPipeline(deps, config) {
272
309
  conversationId,
273
310
  model: config.model
274
311
  });
312
+ const content = await applyTransformReply(
313
+ config.transformReply,
314
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
315
+ config.logger
316
+ );
275
317
  if (!content) {
276
318
  return { action: "ignore" };
277
319
  }
@@ -14,8 +14,10 @@
14
14
  * re-derive it. See docs/build-a-channel.md for a worked example.
15
15
  */
16
16
  import type OpenAI from "openai";
17
- import type { AnswerFn } from "../core/answer";
17
+ import type { AnswerFn, TransformReply } from "../core/answer";
18
18
  import type { BucketsFor } from "../core/buckets";
19
+ import type { Logger } from "../core/logger";
20
+ import { type RerankContext, type RewriteQuery } from "../core/pipeline";
19
21
  import type { PromptLoader } from "../core/prompts";
20
22
  import type { VectorStore } from "../core/retrieval";
21
23
  import type { HistoryStore } from "../history/types";
@@ -33,8 +35,19 @@ export interface InboundPipelineDeps {
33
35
  prompts: PromptLoader;
34
36
  }
35
37
  export interface InboundPipelineConfig {
38
+ /**
39
+ * This channel's identity for `transformReply` — e.g. "whatsapp",
40
+ * "telegram". @default "channel"
41
+ */
42
+ channel?: string;
36
43
  answerFn?: AnswerFn;
37
44
  bucketsFor?: BucketsFor;
45
+ /** Rewrites the retrieval query before it reaches the vector store — see `ChatterConfig.rewriteQuery`. */
46
+ rewriteQuery?: RewriteQuery;
47
+ /** Post-processes retrieved chunks before they're folded into the prompt — see `ChatterConfig.rerankContext`. */
48
+ rerankContext?: RerankContext;
49
+ /** Modifies or vetoes the produced reply before delivery — see `ChatterConfig.transformReply`. */
50
+ transformReply?: TransformReply;
38
51
  model?: string;
39
52
  /** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. */
40
53
  channelHint?: string;
@@ -65,6 +78,8 @@ export interface InboundPipelineConfig {
65
78
  windowMs: number;
66
79
  };
67
80
  now?: () => number;
81
+ /** Logs a `transformReply` throw; the original reply still sends. */
82
+ logger?: Logger;
68
83
  }
69
84
  export interface InboundTurn {
70
85
  /** Delivers this turn's gate ack / chat answer — see {@link InboundReplySender}. */
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"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"}
@@ -17,9 +17,10 @@
17
17
  * Telegram's privacy mode means it only receives messages that address it —
18
18
  * which is the same policy `decideChannelAction` applies anyway.
19
19
  */
20
- import type { AnswerFn } from "../../core/answer";
20
+ import type { AnswerFn, TransformReply } from "../../core/answer";
21
21
  import type { BucketsFor } from "../../core/buckets";
22
22
  import { type Logger } from "../../core/logger";
23
+ import type { RerankContext, RewriteQuery } from "../../core/pipeline";
23
24
  import type { HistoryStore } from "../../history/types";
24
25
  import type { Channel } from "../index";
25
26
  import { type TelegramApi } from "./api";
@@ -32,6 +33,12 @@ export interface TelegramChannelConfig {
32
33
  allowedChats?: string[];
33
34
  answerFn?: AnswerFn;
34
35
  bucketsFor?: BucketsFor;
36
+ /** Rewrites the retrieval query before it reaches the vector store — see `ChatterConfig.rewriteQuery`. Falls back to the server's own. */
37
+ rewriteQuery?: RewriteQuery;
38
+ /** Post-processes retrieved chunks before they're folded into the prompt — see `ChatterConfig.rerankContext`. Falls back to the server's own. */
39
+ rerankContext?: RerankContext;
40
+ /** Modifies or vetoes the produced reply before delivery — see `ChatterConfig.transformReply`. Falls back to the server's own. */
41
+ transformReply?: TransformReply;
35
42
  model?: string;
36
43
  /** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. @default "Channel: Telegram." */
37
44
  channelHint?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAOjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,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,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;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,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,kBAAkB;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,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,qEAAqE;IACrE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CA+H5E"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,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,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAOjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,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,0IAA0I;IAC1I,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iJAAiJ;IACjJ,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kIAAkI;IAClI,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;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,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,kBAAkB;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,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,qEAAqE;IACrE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAoI5E"}
@@ -327,6 +327,18 @@ async function answerOnce({
327
327
  }
328
328
  return completeOnce({ client, system, messages, temperature, model });
329
329
  }
330
+ async function applyTransformReply(transformReply, input, logger) {
331
+ if (!transformReply) return input.text;
332
+ try {
333
+ return await transformReply(input);
334
+ } catch (error) {
335
+ logger?.error(
336
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
337
+ error
338
+ );
339
+ return input.text;
340
+ }
341
+ }
330
342
 
331
343
  // src/core/buckets.ts
332
344
  function defaultBuckets(mode) {
@@ -376,14 +388,35 @@ async function prepareChat({
376
388
  messages,
377
389
  personaLayer,
378
390
  channelHint,
379
- buckets
391
+ buckets,
392
+ sender,
393
+ rewriteQuery,
394
+ rerankContext,
395
+ logger
380
396
  }) {
381
397
  const lastUserMsg = lastUserMessage(messages);
382
398
  if (!lastUserMsg) {
383
399
  throw new Error("no user message found in conversation");
384
400
  }
385
401
  const { topK, label } = MODE_SETTINGS[mode];
386
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
402
+ let query = lastUserMsg.content;
403
+ if (rewriteQuery) {
404
+ try {
405
+ const rewritten = await rewriteQuery({ query, mode, sender });
406
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
407
+ } catch (error) {
408
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
409
+ }
410
+ }
411
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
412
+ if (rerankContext) {
413
+ try {
414
+ const reranked = await rerankContext({ query, chunks: ctx });
415
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
416
+ } catch (error) {
417
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
418
+ }
419
+ }
387
420
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
388
421
  const hint = channelHint?.trim();
389
422
  const system = [
@@ -460,9 +493,13 @@ function createInboundPipeline(deps, config) {
460
493
  messages,
461
494
  personaLayer,
462
495
  channelHint: config.channelHint,
463
- buckets
496
+ buckets,
497
+ sender,
498
+ rewriteQuery: config.rewriteQuery,
499
+ rerankContext: config.rerankContext,
500
+ logger: config.logger
464
501
  });
465
- const { content } = await answerOnce({
502
+ const { content: produced } = await answerOnce({
466
503
  answerFn: config.answerFn,
467
504
  client: deps.client,
468
505
  system,
@@ -472,6 +509,11 @@ function createInboundPipeline(deps, config) {
472
509
  conversationId,
473
510
  model: config.model
474
511
  });
512
+ const content = await applyTransformReply(
513
+ config.transformReply,
514
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
515
+ config.logger
516
+ );
475
517
  if (!content) {
476
518
  return { action: "ignore" };
477
519
  }
@@ -605,8 +647,12 @@ function createTelegramChannel(config) {
605
647
  const pipeline = createInboundPipeline(
606
648
  { client: deps.client, store: deps.store, prompts: deps.prompts },
607
649
  {
650
+ channel: channelName,
608
651
  answerFn: config.answerFn ?? deps.config.answerFn,
609
652
  bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
653
+ rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
654
+ rerankContext: config.rerankContext ?? deps.config.rerankContext,
655
+ transformReply: config.transformReply ?? deps.config.transformReply,
610
656
  model: config.model,
611
657
  channelHint: config.channelHint ?? "Channel: Telegram.",
612
658
  personaResolver: config.personaResolver,
@@ -618,7 +664,8 @@ function createTelegramChannel(config) {
618
664
  unmuteReply: config.unmuteReply,
619
665
  dmRateLimit: config.dmRateLimit,
620
666
  groupRateLimit: config.groupRateLimit,
621
- now: config.now
667
+ now: config.now,
668
+ logger: log
622
669
  }
623
670
  );
624
671
  const loggedUnallowedChats = /* @__PURE__ */ new Set();
@@ -285,6 +285,18 @@ async function answerOnce({
285
285
  }
286
286
  return completeOnce({ client, system, messages, temperature, model });
287
287
  }
288
+ async function applyTransformReply(transformReply, input, logger) {
289
+ if (!transformReply) return input.text;
290
+ try {
291
+ return await transformReply(input);
292
+ } catch (error) {
293
+ logger?.error(
294
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
295
+ error
296
+ );
297
+ return input.text;
298
+ }
299
+ }
288
300
 
289
301
  // src/core/buckets.ts
290
302
  function defaultBuckets(mode) {
@@ -334,14 +346,35 @@ async function prepareChat({
334
346
  messages,
335
347
  personaLayer,
336
348
  channelHint,
337
- buckets
349
+ buckets,
350
+ sender,
351
+ rewriteQuery,
352
+ rerankContext,
353
+ logger
338
354
  }) {
339
355
  const lastUserMsg = lastUserMessage(messages);
340
356
  if (!lastUserMsg) {
341
357
  throw new Error("no user message found in conversation");
342
358
  }
343
359
  const { topK, label } = MODE_SETTINGS[mode];
344
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
360
+ let query = lastUserMsg.content;
361
+ if (rewriteQuery) {
362
+ try {
363
+ const rewritten = await rewriteQuery({ query, mode, sender });
364
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
365
+ } catch (error) {
366
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
367
+ }
368
+ }
369
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
370
+ if (rerankContext) {
371
+ try {
372
+ const reranked = await rerankContext({ query, chunks: ctx });
373
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
374
+ } catch (error) {
375
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
376
+ }
377
+ }
345
378
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
346
379
  const hint = channelHint?.trim();
347
380
  const system = [
@@ -418,9 +451,13 @@ function createInboundPipeline(deps, config) {
418
451
  messages,
419
452
  personaLayer,
420
453
  channelHint: config.channelHint,
421
- buckets
454
+ buckets,
455
+ sender,
456
+ rewriteQuery: config.rewriteQuery,
457
+ rerankContext: config.rerankContext,
458
+ logger: config.logger
422
459
  });
423
- const { content } = await answerOnce({
460
+ const { content: produced } = await answerOnce({
424
461
  answerFn: config.answerFn,
425
462
  client: deps.client,
426
463
  system,
@@ -430,6 +467,11 @@ function createInboundPipeline(deps, config) {
430
467
  conversationId,
431
468
  model: config.model
432
469
  });
470
+ const content = await applyTransformReply(
471
+ config.transformReply,
472
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
473
+ config.logger
474
+ );
433
475
  if (!content) {
434
476
  return { action: "ignore" };
435
477
  }
@@ -563,8 +605,12 @@ function createTelegramChannel(config) {
563
605
  const pipeline = createInboundPipeline(
564
606
  { client: deps.client, store: deps.store, prompts: deps.prompts },
565
607
  {
608
+ channel: channelName,
566
609
  answerFn: config.answerFn ?? deps.config.answerFn,
567
610
  bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
611
+ rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
612
+ rerankContext: config.rerankContext ?? deps.config.rerankContext,
613
+ transformReply: config.transformReply ?? deps.config.transformReply,
568
614
  model: config.model,
569
615
  channelHint: config.channelHint ?? "Channel: Telegram.",
570
616
  personaResolver: config.personaResolver,
@@ -576,7 +622,8 @@ function createTelegramChannel(config) {
576
622
  unmuteReply: config.unmuteReply,
577
623
  dmRateLimit: config.dmRateLimit,
578
624
  groupRateLimit: config.groupRateLimit,
579
- now: config.now
625
+ now: config.now,
626
+ logger: log
580
627
  }
581
628
  );
582
629
  const loggedUnallowedChats = /* @__PURE__ */ new Set();
@@ -36,6 +36,9 @@
36
36
  * prompts: deps.prompts,
37
37
  * answerFn: deps.config.answerFn,
38
38
  * bucketsFor: deps.config.bucketsFor,
39
+ * rewriteQuery: deps.config.rewriteQuery,
40
+ * rerankContext: deps.config.rerankContext,
41
+ * transformReply: deps.config.transformReply,
39
42
  * registry: new Map(),
40
43
  * logger: deps.logger,
41
44
  * });
@@ -45,9 +48,10 @@
45
48
  */
46
49
  import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
47
50
  import type OpenAI from "openai";
48
- import type { AnswerFn } from "../../core/answer";
51
+ import type { AnswerFn, TransformReply } from "../../core/answer";
49
52
  import type { BucketsFor } from "../../core/buckets";
50
53
  import { type Logger } from "../../core/logger";
54
+ import type { RerankContext, RewriteQuery } from "../../core/pipeline";
51
55
  import type { PromptLoader } from "../../core/prompts";
52
56
  import type { VectorStore } from "../../core/retrieval";
53
57
  import type { HistoryStore } from "../../history/types";
@@ -128,6 +132,12 @@ export interface WhatsAppInboundConfig {
128
132
  prompts: PromptLoader;
129
133
  answerFn?: AnswerFn;
130
134
  bucketsFor?: BucketsFor;
135
+ /** Rewrites the retrieval query before it reaches the vector store — see `ChatterConfig.rewriteQuery`. */
136
+ rewriteQuery?: RewriteQuery;
137
+ /** Post-processes retrieved chunks before they're folded into the prompt — see `ChatterConfig.rerankContext`. */
138
+ rerankContext?: RerankContext;
139
+ /** Modifies or vetoes the produced reply before delivery — see `ChatterConfig.transformReply`. */
140
+ transformReply?: TransformReply;
131
141
  model?: string;
132
142
  /**
133
143
  * Own identities per session — share ONE registry across every session
@@ -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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"}
@@ -738,6 +738,18 @@ async function answerOnce({
738
738
  }
739
739
  return completeOnce({ client, system, messages, temperature, model });
740
740
  }
741
+ async function applyTransformReply(transformReply, input, logger) {
742
+ if (!transformReply) return input.text;
743
+ try {
744
+ return await transformReply(input);
745
+ } catch (error) {
746
+ logger?.error(
747
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
748
+ error
749
+ );
750
+ return input.text;
751
+ }
752
+ }
741
753
 
742
754
  // src/core/buckets.ts
743
755
  function defaultBuckets(mode) {
@@ -787,14 +799,35 @@ async function prepareChat({
787
799
  messages,
788
800
  personaLayer,
789
801
  channelHint,
790
- buckets
802
+ buckets,
803
+ sender,
804
+ rewriteQuery,
805
+ rerankContext,
806
+ logger
791
807
  }) {
792
808
  const lastUserMsg = lastUserMessage(messages);
793
809
  if (!lastUserMsg) {
794
810
  throw new Error("no user message found in conversation");
795
811
  }
796
812
  const { topK, label } = MODE_SETTINGS[mode];
797
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
813
+ let query = lastUserMsg.content;
814
+ if (rewriteQuery) {
815
+ try {
816
+ const rewritten = await rewriteQuery({ query, mode, sender });
817
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
818
+ } catch (error) {
819
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
820
+ }
821
+ }
822
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
823
+ if (rerankContext) {
824
+ try {
825
+ const reranked = await rerankContext({ query, chunks: ctx });
826
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
827
+ } catch (error) {
828
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
829
+ }
830
+ }
798
831
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
799
832
  const hint = channelHint?.trim();
800
833
  const system = [
@@ -871,9 +904,13 @@ function createInboundPipeline(deps, config) {
871
904
  messages,
872
905
  personaLayer,
873
906
  channelHint: config.channelHint,
874
- buckets
907
+ buckets,
908
+ sender,
909
+ rewriteQuery: config.rewriteQuery,
910
+ rerankContext: config.rerankContext,
911
+ logger: config.logger
875
912
  });
876
- const { content } = await answerOnce({
913
+ const { content: produced } = await answerOnce({
877
914
  answerFn: config.answerFn,
878
915
  client: deps.client,
879
916
  system,
@@ -883,6 +920,11 @@ function createInboundPipeline(deps, config) {
883
920
  conversationId,
884
921
  model: config.model
885
922
  });
923
+ const content = await applyTransformReply(
924
+ config.transformReply,
925
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
926
+ config.logger
927
+ );
886
928
  if (!content) {
887
929
  return { action: "ignore" };
888
930
  }
@@ -990,8 +1032,12 @@ function createWhatsAppInboundHandler(config) {
990
1032
  const pipeline = createInboundPipeline(
991
1033
  { client: config.client, store: config.store, prompts: config.prompts },
992
1034
  {
1035
+ channel: "whatsapp",
993
1036
  answerFn: config.answerFn,
994
1037
  bucketsFor: config.bucketsFor,
1038
+ rewriteQuery: config.rewriteQuery,
1039
+ rerankContext: config.rerankContext,
1040
+ transformReply: config.transformReply,
995
1041
  model: config.model,
996
1042
  channelHint: config.channelHint,
997
1043
  personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
@@ -1003,7 +1049,8 @@ function createWhatsAppInboundHandler(config) {
1003
1049
  unmuteReply: config.unmuteReply,
1004
1050
  dmRateLimit: config.dmRateLimit,
1005
1051
  groupRateLimit: config.groupRateLimit,
1006
- now: config.now
1052
+ now: config.now,
1053
+ logger
1007
1054
  }
1008
1055
  );
1009
1056
  return async function handleWhatsAppMessage(event) {