@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.
- package/dist/channels/index.js +46 -4
- package/dist/channels/index.mjs +46 -4
- package/dist/channels/pipeline.d.ts +16 -1
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/telegram/channel.d.ts +8 -1
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +52 -5
- package/dist/channels/telegram/index.mjs +52 -5
- package/dist/channels/whatsapp/inbound.d.ts +11 -1
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +52 -5
- package/dist/channels/whatsapp/index.mjs +52 -5
- package/dist/core/answer.d.ts +31 -0
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts +52 -1
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -19
- package/dist/index.mjs +127 -19
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +57 -8
- package/dist/mcp-server.mjs +57 -8
- 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/server.js +104 -13
- package/dist/server.mjs +104 -13
- package/dist/types.d.ts +42 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/channels/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/channels/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,SAAS,CAAC;AAEjB,iNAAiN;AACjN,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,kFAAkF;IAClF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iHAAiH;IACjH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kGAAkG;IAClG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sGAAsG;IACtG,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,gFAAgF;IAChF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,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,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;CACpF;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CAAA;CAAE,GACzE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,2FAA2F;AAC3F,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAsBrC;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE,qBAAqB,GAC5B,eAAe,CA6GjB"}
|
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA
|
|
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
|
-
|
|
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) {
|