@diegoaltoworks/chatter 0.54.2 → 0.56.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.
Files changed (39) hide show
  1. package/README.md +2 -1
  2. package/dist/channels/index.js +18 -1
  3. package/dist/channels/index.mjs +18 -1
  4. package/dist/channels/pipeline.d.ts +11 -1
  5. package/dist/channels/pipeline.d.ts.map +1 -1
  6. package/dist/channels/telegram/api.d.ts +122 -0
  7. package/dist/channels/telegram/api.d.ts.map +1 -0
  8. package/dist/channels/telegram/channel.d.ts +82 -0
  9. package/dist/channels/telegram/channel.d.ts.map +1 -0
  10. package/dist/channels/telegram/index.d.ts +26 -0
  11. package/dist/channels/telegram/index.d.ts.map +1 -0
  12. package/dist/channels/telegram/index.js +710 -0
  13. package/dist/channels/telegram/index.mjs +667 -0
  14. package/dist/channels/telegram/poll.d.ts +47 -0
  15. package/dist/channels/telegram/poll.d.ts.map +1 -0
  16. package/dist/channels/telegram/updates.d.ts +63 -0
  17. package/dist/channels/telegram/updates.d.ts.map +1 -0
  18. package/dist/channels/whatsapp/inbound.d.ts +4 -1
  19. package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
  20. package/dist/channels/whatsapp/index.js +22 -2
  21. package/dist/channels/whatsapp/index.mjs +22 -2
  22. package/dist/core/answer.d.ts +31 -0
  23. package/dist/core/answer.d.ts.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +54 -10
  27. package/dist/index.mjs +53 -10
  28. package/dist/mcp-server.d.ts.map +1 -1
  29. package/dist/mcp-server.js +26 -4
  30. package/dist/mcp-server.mjs +26 -4
  31. package/dist/routes/demo.d.ts.map +1 -1
  32. package/dist/routes/openai.d.ts.map +1 -1
  33. package/dist/routes/private.d.ts.map +1 -1
  34. package/dist/routes/public.d.ts.map +1 -1
  35. package/dist/server.js +38 -6
  36. package/dist/server.mjs +38 -6
  37. package/dist/types.d.ts +17 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +8 -2
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The getUpdates long-poll loop, isolated from both the HTTP client and the
3
+ * channel so its failure behaviour is testable without either.
4
+ *
5
+ * The property that matters: a Bot API that is down, rate-limiting, or
6
+ * rejecting the token must never turn into a tight retry loop. Every failure
7
+ * backs off exponentially, and Telegram's own `retry_after` wins over that
8
+ * backoff when it sends one.
9
+ */
10
+ import { type Logger } from "../../core/logger";
11
+ import { type TelegramUpdate } from "./api";
12
+ /** Exponential backoff: 2s, 4s, 8s ... capped at a minute. `failures` is the count including the one just seen. */
13
+ export declare function pollBackoffMs(failures: number): number;
14
+ /**
15
+ * How long to wait after a failed poll. A `retry_after` from Telegram (HTTP
16
+ * 429 flood control) is an instruction, not a hint — honour it verbatim even
17
+ * when it exceeds the backoff cap, because polling again sooner just extends
18
+ * the flood-wait.
19
+ */
20
+ export declare function retryDelayMs(error: unknown, failures: number): number;
21
+ export interface LongPollDeps {
22
+ /** One `getUpdates` call. Rejections are backed off; they never end the loop. */
23
+ getUpdates(offset: number | undefined): Promise<TelegramUpdate[]>;
24
+ /** Per-update work. A throw is logged and skipped — one bad update never stops the loop. */
25
+ handleUpdate(update: TelegramUpdate): Promise<void>;
26
+ /** Checked before every poll, and again after a failure, so `stop()` cuts the loop instead of sleeping out a backoff first. */
27
+ isStopped(): boolean;
28
+ /** Overridable for tests; a real channel passes a `setTimeout`-based sleep. */
29
+ sleep(ms: number): Promise<void>;
30
+ /** Where this loop's offset resumes from. Omitted = whatever Telegram has queued. */
31
+ initialOffset?: number;
32
+ /** Called with each newly acknowledged offset, for a host that persists it. */
33
+ onOffset?(offset: number): void;
34
+ logger?: Logger;
35
+ /** Prefix for log lines, e.g. `Telegram[mybot]`. */
36
+ label?: string;
37
+ }
38
+ /**
39
+ * Polls until `isStopped()`, resolving with the last offset reached (what a
40
+ * restart would resume from).
41
+ *
42
+ * The offset advances BEFORE the update is handled: a handler that throws on
43
+ * one specific update must not make Telegram redeliver it forever, which would
44
+ * wedge the loop on a single poison message and starve every later one.
45
+ */
46
+ export declare function runLongPoll(deps: LongPollDeps): Promise<number | undefined>;
47
+ //# sourceMappingURL=poll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/poll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAM9D,mHAAmH;AACnH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClE,4FAA4F;IAC5F,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,+HAA+H;IAC/H,SAAS,IAAI,OAAO,CAAC;IACrB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,qFAAqF;IACrF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmCjF"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Telegram update -> {@link ChannelMessage}: the transport's own half of the
3
+ * channel contract (see docs/build-a-channel.md), kept pure so mention and
4
+ * reply-to detection are testable without a network or a running channel.
5
+ *
6
+ * Addressing parity with WhatsApp is the point of this module. WhatsApp
7
+ * resolves "was the bot addressed?" from `contextInfo.mentionedJid` and the
8
+ * quoted participant; Telegram resolves it from message entities and
9
+ * `reply_to_message.from`. The gate policy that consumes those booleans
10
+ * (`decideChannelAction`) is identical for both.
11
+ */
12
+ import type { ChannelMessage } from "../gates";
13
+ import type { TelegramMessage, TelegramMessageEntity, TelegramUpdate } from "./api";
14
+ /** The bot's own identity, as `getMe` reports it. */
15
+ export interface TelegramBotIdentity {
16
+ id: number;
17
+ username?: string;
18
+ }
19
+ /** The message's text: plain text, or a photo/video/document caption. */
20
+ export declare function messageText(message: TelegramMessage): string;
21
+ /** Entities belong to whichever field carried the text — captions carry theirs on `caption_entities`. */
22
+ export declare function messageEntities(message: TelegramMessage): TelegramMessageEntity[];
23
+ /**
24
+ * Did this message address the bot? Three ways it can, all of which a human
25
+ * reader would call "addressed":
26
+ *
27
+ * - a `mention` entity whose text is the bot's `@username`;
28
+ * - a `text_mention` entity pointing at the bot's user id (how a mention
29
+ * arrives when the mentioned account has no username);
30
+ * - a `bot_command` entity suffixed with the bot's username (`/ask@mybot`),
31
+ * which is how Telegram disambiguates commands between several bots in one
32
+ * group.
33
+ *
34
+ * Username comparison is case-insensitive: Telegram treats `@MyBot` and
35
+ * `@mybot` as the same handle, and clients preserve whatever the sender typed.
36
+ */
37
+ export declare function mentionsBot(message: TelegramMessage, me: TelegramBotIdentity): boolean;
38
+ /**
39
+ * The {@link ChannelMessage} for an update, or `undefined` when there is
40
+ * nothing for a chat pipeline to look at: a non-message update, a channel post
41
+ * (which has no `from`, only a `sender_chat`), or a service message.
42
+ *
43
+ * A message with no text at all (a sticker, an uncaptioned photo) still maps —
44
+ * its blank text is dropped by `decideChannelAction` like any other, and
45
+ * mapping it keeps this function's rule simple: interpret the wire, let the
46
+ * gates decide policy.
47
+ */
48
+ export declare function toChannelMessage(update: TelegramUpdate, me: TelegramBotIdentity): ChannelMessage | undefined;
49
+ /**
50
+ * The `offset` to request next so `update` is never redelivered:
51
+ * `update_id + 1`, which is also how the Bot API acknowledges it (there is no
52
+ * separate ack call — see docs/telegram.md on offset persistence).
53
+ */
54
+ export declare function nextOffset(update: TelegramUpdate): number;
55
+ /**
56
+ * A stable identity key for `bucketsFor`/`personaResolver`/`answerFn`. Namespaced
57
+ * because a bare Telegram user id is a numeral with no channel in it — a host
58
+ * keying persona or spend state across several channels must not have one
59
+ * collide with a phone number or another transport's numeric id (the same
60
+ * reasoning as WhatsApp's explicit `lid:` marker).
61
+ */
62
+ export declare function telegramSenderKey(senderId: string): string;
63
+ //# sourceMappingURL=updates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updates.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/updates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAEpF,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAE5D;AAED,yGAAyG;AACzG,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,qBAAqB,EAAE,CAEjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAYtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,mBAAmB,GACtB,cAAc,GAAG,SAAS,CAiB5B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D"}
@@ -36,6 +36,7 @@
36
36
  * prompts: deps.prompts,
37
37
  * answerFn: deps.config.answerFn,
38
38
  * bucketsFor: deps.config.bucketsFor,
39
+ * transformReply: deps.config.transformReply,
39
40
  * registry: new Map(),
40
41
  * logger: deps.logger,
41
42
  * });
@@ -45,7 +46,7 @@
45
46
  */
46
47
  import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
47
48
  import type OpenAI from "openai";
48
- import type { AnswerFn } from "../../core/answer";
49
+ import type { AnswerFn, TransformReply } from "../../core/answer";
49
50
  import type { BucketsFor } from "../../core/buckets";
50
51
  import { type Logger } from "../../core/logger";
51
52
  import type { PromptLoader } from "../../core/prompts";
@@ -128,6 +129,8 @@ export interface WhatsAppInboundConfig {
128
129
  prompts: PromptLoader;
129
130
  answerFn?: AnswerFn;
130
131
  bucketsFor?: BucketsFor;
132
+ /** Modifies or vetoes the produced reply before delivery — see `ChatterConfig.transformReply`. */
133
+ transformReply?: TransformReply;
131
134
  model?: string;
132
135
  /**
133
136
  * 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) {
@@ -873,7 +885,7 @@ function createInboundPipeline(deps, config) {
873
885
  channelHint: config.channelHint,
874
886
  buckets
875
887
  });
876
- const { content } = await answerOnce({
888
+ const { content: produced } = await answerOnce({
877
889
  answerFn: config.answerFn,
878
890
  client: deps.client,
879
891
  system,
@@ -883,6 +895,11 @@ function createInboundPipeline(deps, config) {
883
895
  conversationId,
884
896
  model: config.model
885
897
  });
898
+ const content = await applyTransformReply(
899
+ config.transformReply,
900
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
901
+ config.logger
902
+ );
886
903
  if (!content) {
887
904
  return { action: "ignore" };
888
905
  }
@@ -990,8 +1007,10 @@ function createWhatsAppInboundHandler(config) {
990
1007
  const pipeline = createInboundPipeline(
991
1008
  { client: config.client, store: config.store, prompts: config.prompts },
992
1009
  {
1010
+ channel: "whatsapp",
993
1011
  answerFn: config.answerFn,
994
1012
  bucketsFor: config.bucketsFor,
1013
+ transformReply: config.transformReply,
995
1014
  model: config.model,
996
1015
  channelHint: config.channelHint,
997
1016
  personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
@@ -1003,7 +1022,8 @@ function createWhatsAppInboundHandler(config) {
1003
1022
  unmuteReply: config.unmuteReply,
1004
1023
  dmRateLimit: config.dmRateLimit,
1005
1024
  groupRateLimit: config.groupRateLimit,
1006
- now: config.now
1025
+ now: config.now,
1026
+ logger
1007
1027
  }
1008
1028
  );
1009
1029
  return async function handleWhatsAppMessage(event) {
@@ -676,6 +676,18 @@ async function answerOnce({
676
676
  }
677
677
  return completeOnce({ client, system, messages, temperature, model });
678
678
  }
679
+ async function applyTransformReply(transformReply, input, logger) {
680
+ if (!transformReply) return input.text;
681
+ try {
682
+ return await transformReply(input);
683
+ } catch (error) {
684
+ logger?.error(
685
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
686
+ error
687
+ );
688
+ return input.text;
689
+ }
690
+ }
679
691
 
680
692
  // src/core/buckets.ts
681
693
  function defaultBuckets(mode) {
@@ -811,7 +823,7 @@ function createInboundPipeline(deps, config) {
811
823
  channelHint: config.channelHint,
812
824
  buckets
813
825
  });
814
- const { content } = await answerOnce({
826
+ const { content: produced } = await answerOnce({
815
827
  answerFn: config.answerFn,
816
828
  client: deps.client,
817
829
  system,
@@ -821,6 +833,11 @@ function createInboundPipeline(deps, config) {
821
833
  conversationId,
822
834
  model: config.model
823
835
  });
836
+ const content = await applyTransformReply(
837
+ config.transformReply,
838
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
839
+ config.logger
840
+ );
824
841
  if (!content) {
825
842
  return { action: "ignore" };
826
843
  }
@@ -928,8 +945,10 @@ function createWhatsAppInboundHandler(config) {
928
945
  const pipeline = createInboundPipeline(
929
946
  { client: config.client, store: config.store, prompts: config.prompts },
930
947
  {
948
+ channel: "whatsapp",
931
949
  answerFn: config.answerFn,
932
950
  bucketsFor: config.bucketsFor,
951
+ transformReply: config.transformReply,
933
952
  model: config.model,
934
953
  channelHint: config.channelHint,
935
954
  personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
@@ -941,7 +960,8 @@ function createWhatsAppInboundHandler(config) {
941
960
  unmuteReply: config.unmuteReply,
942
961
  dmRateLimit: config.dmRateLimit,
943
962
  groupRateLimit: config.groupRateLimit,
944
- now: config.now
963
+ now: config.now,
964
+ logger
945
965
  }
946
966
  );
947
967
  return async function handleWhatsAppMessage(event) {
@@ -13,6 +13,7 @@
13
13
  * exactly what it has always been.
14
14
  */
15
15
  import type OpenAI from "openai";
16
+ import type { Logger } from "./logger";
16
17
  import type { PipelineMessage, PipelineMode } from "./pipeline";
17
18
  /** Token accounting in the OpenAI wire shape */
18
19
  export interface AnswerUsage {
@@ -66,6 +67,36 @@ export declare function answerOnce({ answerFn, client, system, messages, mode, s
66
67
  content: string;
67
68
  usage: AnswerUsage;
68
69
  }>;
70
+ /** What a `transformReply` hook receives: the produced answer plus who/where it's going. */
71
+ export interface TransformReplyInput {
72
+ /**
73
+ * Identifies the surface delivering this reply — a channel's own name
74
+ * (e.g. "whatsapp", "telegram") or a fixed identifier for an HTTP surface
75
+ * (e.g. "widget-public", "openai-compat-private").
76
+ */
77
+ channel: string;
78
+ sender?: string;
79
+ conversationId?: string;
80
+ text: string;
81
+ }
82
+ /**
83
+ * Modifies or vetoes an already-produced reply, after `answerFn`/the built-in
84
+ * completion and guardrails have run. A string result replaces the reply;
85
+ * `null` vetoes it — treated the same as an empty answer by every call site
86
+ * (nothing delivered; the channel pipeline never records an assistant turn
87
+ * for it, though the user's own turn — already appended before answering —
88
+ * stays recorded).
89
+ *
90
+ * Non-streaming surfaces only: a streaming response is delivered incrementally
91
+ * as it's produced, before a final answer exists to transform.
92
+ */
93
+ export type TransformReply = (input: TransformReplyInput) => string | null | Promise<string | null>;
94
+ /**
95
+ * Runs `transformReply` if configured. A throw/rejection is logged and the
96
+ * ORIGINAL text is returned — the delivery invariant: a bug in a plugin hook
97
+ * must never silently swallow an answer the model already produced.
98
+ */
99
+ export declare function applyTransformReply(transformReply: TransformReply | undefined, input: TransformReplyInput, logger?: Pick<Logger, "error">): Promise<string | null>;
69
100
  /**
70
101
  * Stream an answer. A brain that returns a plain string cannot stream, so its
71
102
  * answer degrades to a single chunk followed by the stream's normal end —
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAaxC"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEpG;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,KAAK,EAAE,mBAAmB,EAC1B,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxB;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAaxC"}
package/dist/index.d.ts CHANGED
@@ -14,8 +14,8 @@ export type { ApiKeyOptions, ApiKeyPayload, VerifyResult } from "./auth";
14
14
  export { ApiKeyManager } from "./auth";
15
15
  export type { Channel } from "./channels";
16
16
  export type { ChatBotConfig, ChatButtonConfig, ChatConfig, ChatMessage, ChatMode, StreamCallbacks, } from "./client";
17
- export type { AnswerFn, AnswerFnInput, AnswerFnResult, AnswerOptions, AnswerUsage, } from "./core/answer";
18
- export { answerOnce, answerStream } from "./core/answer";
17
+ export type { AnswerFn, AnswerFnInput, AnswerFnResult, AnswerOptions, AnswerUsage, TransformReply, TransformReplyInput, } from "./core/answer";
18
+ export { answerOnce, answerStream, applyTransformReply } from "./core/answer";
19
19
  export type { BucketsFor, BucketsForContext } from "./core/buckets";
20
20
  export { defaultBuckets, resolveBuckets } from "./core/buckets";
21
21
  export { detectLeakage, guardOutput, scrubOutput } from "./core/guardrails";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  VectorStore: () => VectorStore,
37
37
  answerOnce: () => answerOnce,
38
38
  answerStream: () => answerStream,
39
+ applyTransformReply: () => applyTransformReply,
39
40
  chatBodyLimit: () => chatBodyLimit,
40
41
  completeOnce: () => completeOnce,
41
42
  completeStream: () => completeStream,
@@ -282,6 +283,18 @@ async function answerOnce({
282
283
  }
283
284
  return completeOnce({ client, system, messages, temperature, model });
284
285
  }
286
+ async function applyTransformReply(transformReply, input, logger) {
287
+ if (!transformReply) return input.text;
288
+ try {
289
+ return await transformReply(input);
290
+ } catch (error) {
291
+ logger?.error(
292
+ `transformReply threw for channel "${input.channel}"; sending the original reply`,
293
+ error
294
+ );
295
+ return input.text;
296
+ }
297
+ }
285
298
  async function* answerStream({
286
299
  answerFn,
287
300
  client,
@@ -1002,6 +1015,11 @@ async function createMCPServer(config) {
1002
1015
  conversationId: convId,
1003
1016
  model
1004
1017
  });
1018
+ const replyText = await applyTransformReply(
1019
+ config.transformReply,
1020
+ { channel: "mcp-public", conversationId: convId, text: result.content },
1021
+ log
1022
+ );
1005
1023
  const cost = calculateCost(result.usage, config.openai.pricing);
1006
1024
  const duration = Date.now() - startTime;
1007
1025
  await logger.logChatInteraction(
@@ -1009,7 +1027,7 @@ async function createMCPServer(config) {
1009
1027
  convId,
1010
1028
  conversationMessages,
1011
1029
  ctx,
1012
- result.content,
1030
+ replyText ?? "",
1013
1031
  duration,
1014
1032
  cost
1015
1033
  ).catch((err) => log.error("Logging error:", err));
@@ -1017,7 +1035,7 @@ async function createMCPServer(config) {
1017
1035
  content: [
1018
1036
  {
1019
1037
  type: "text",
1020
- text: result.content
1038
+ text: replyText ?? ""
1021
1039
  }
1022
1040
  ],
1023
1041
  _meta: {
@@ -1083,6 +1101,11 @@ async function createMCPServer(config) {
1083
1101
  conversationId: convId,
1084
1102
  model
1085
1103
  });
1104
+ const replyText = await applyTransformReply(
1105
+ config.transformReply,
1106
+ { channel: "mcp-private", conversationId: convId, text: result.content },
1107
+ log
1108
+ );
1086
1109
  const cost = calculateCost(result.usage, config.openai.pricing);
1087
1110
  const duration = Date.now() - startTime;
1088
1111
  await logger.logChatInteraction(
@@ -1090,7 +1113,7 @@ async function createMCPServer(config) {
1090
1113
  convId,
1091
1114
  conversationMessages,
1092
1115
  ctx,
1093
- result.content,
1116
+ replyText ?? "",
1094
1117
  duration,
1095
1118
  cost
1096
1119
  ).catch((err) => log.error("Logging error:", err));
@@ -1098,7 +1121,7 @@ async function createMCPServer(config) {
1098
1121
  content: [
1099
1122
  {
1100
1123
  type: "text",
1101
- text: result.content
1124
+ text: replyText ?? ""
1102
1125
  }
1103
1126
  ],
1104
1127
  _meta: {
@@ -1571,7 +1594,12 @@ function demoRoutes(deps) {
1571
1594
  mode: "public",
1572
1595
  model
1573
1596
  });
1574
- return c.json({ reply: out.content });
1597
+ const reply = await applyTransformReply(
1598
+ config.transformReply,
1599
+ { channel: "widget-demo", text: out.content },
1600
+ logger
1601
+ );
1602
+ return c.json({ reply: reply ?? "" });
1575
1603
  } catch (error) {
1576
1604
  const err = error;
1577
1605
  logger.error("Demo chat error:", err);
@@ -1614,7 +1642,7 @@ function oversizedBodyError(message) {
1614
1642
  return { error: { message, type: "invalid_request_error" } };
1615
1643
  }
1616
1644
  function openaiRoutes(deps) {
1617
- const { client, store, config, prompts, apiKeyManager } = deps;
1645
+ const { client, store, config, prompts, apiKeyManager, logger } = deps;
1618
1646
  const app = new import_hono2.Hono();
1619
1647
  const { limitPublic, limitPrivate } = createRateLimiter2(config);
1620
1648
  const requirePrivateJWT = createJWTMiddleware(config);
@@ -1709,13 +1737,18 @@ function openaiRoutes(deps) {
1709
1737
  temperature,
1710
1738
  model: serverModel
1711
1739
  });
1740
+ const reply = await applyTransformReply(
1741
+ config.transformReply,
1742
+ { channel: `openai-compat-${mode}`, sender, conversationId, text: out.content },
1743
+ logger
1744
+ );
1712
1745
  return c.json({
1713
1746
  id,
1714
1747
  object: "chat.completion",
1715
1748
  created,
1716
1749
  model: serverModel,
1717
1750
  choices: [
1718
- { index: 0, message: { role: "assistant", content: out.content }, finish_reason: "stop" }
1751
+ { index: 0, message: { role: "assistant", content: reply ?? "" }, finish_reason: "stop" }
1719
1752
  ],
1720
1753
  usage: out.usage
1721
1754
  });
@@ -1745,7 +1778,7 @@ function wantsStream(c) {
1745
1778
  return acc.includes("text/event-stream");
1746
1779
  }
1747
1780
  function privateRoutes(deps) {
1748
- const { client, store, config, prompts } = deps;
1781
+ const { client, store, config, prompts, logger } = deps;
1749
1782
  const app = new import_hono3.Hono();
1750
1783
  const requirePrivateJWT = createJWTMiddleware(config);
1751
1784
  const { limitPrivate } = createRateLimiter2(config);
@@ -1800,7 +1833,12 @@ function privateRoutes(deps) {
1800
1833
  sender,
1801
1834
  model
1802
1835
  });
1803
- return c.json({ reply: out.content });
1836
+ const reply = await applyTransformReply(
1837
+ config.transformReply,
1838
+ { channel: "widget-private", sender, text: out.content },
1839
+ logger
1840
+ );
1841
+ return c.json({ reply: reply ?? "" });
1804
1842
  });
1805
1843
  return app;
1806
1844
  }
@@ -1862,7 +1900,12 @@ function publicRoutes(deps) {
1862
1900
  });
1863
1901
  }
1864
1902
  const out = await answerOnce({ answerFn, client, system, messages, mode: "public", model });
1865
- return c.json({ reply: out.content });
1903
+ const reply = await applyTransformReply(
1904
+ config.transformReply,
1905
+ { channel: "widget-public", text: out.content },
1906
+ logger
1907
+ );
1908
+ return c.json({ reply: reply ?? "" });
1866
1909
  });
1867
1910
  return app;
1868
1911
  }
@@ -2201,6 +2244,7 @@ function createShortener(config, fetchImpl = fetch) {
2201
2244
  VectorStore,
2202
2245
  answerOnce,
2203
2246
  answerStream,
2247
+ applyTransformReply,
2204
2248
  chatBodyLimit,
2205
2249
  completeOnce,
2206
2250
  completeStream,