@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.
@@ -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) {
@@ -725,14 +737,35 @@ async function prepareChat({
725
737
  messages,
726
738
  personaLayer,
727
739
  channelHint,
728
- buckets
740
+ buckets,
741
+ sender,
742
+ rewriteQuery,
743
+ rerankContext,
744
+ logger
729
745
  }) {
730
746
  const lastUserMsg = lastUserMessage(messages);
731
747
  if (!lastUserMsg) {
732
748
  throw new Error("no user message found in conversation");
733
749
  }
734
750
  const { topK, label } = MODE_SETTINGS[mode];
735
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
751
+ let query = lastUserMsg.content;
752
+ if (rewriteQuery) {
753
+ try {
754
+ const rewritten = await rewriteQuery({ query, mode, sender });
755
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
756
+ } catch (error) {
757
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
758
+ }
759
+ }
760
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
761
+ if (rerankContext) {
762
+ try {
763
+ const reranked = await rerankContext({ query, chunks: ctx });
764
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
765
+ } catch (error) {
766
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
767
+ }
768
+ }
736
769
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
737
770
  const hint = channelHint?.trim();
738
771
  const system = [
@@ -809,9 +842,13 @@ function createInboundPipeline(deps, config) {
809
842
  messages,
810
843
  personaLayer,
811
844
  channelHint: config.channelHint,
812
- buckets
845
+ buckets,
846
+ sender,
847
+ rewriteQuery: config.rewriteQuery,
848
+ rerankContext: config.rerankContext,
849
+ logger: config.logger
813
850
  });
814
- const { content } = await answerOnce({
851
+ const { content: produced } = await answerOnce({
815
852
  answerFn: config.answerFn,
816
853
  client: deps.client,
817
854
  system,
@@ -821,6 +858,11 @@ function createInboundPipeline(deps, config) {
821
858
  conversationId,
822
859
  model: config.model
823
860
  });
861
+ const content = await applyTransformReply(
862
+ config.transformReply,
863
+ { channel: config.channel ?? "channel", sender, conversationId, text: produced },
864
+ config.logger
865
+ );
824
866
  if (!content) {
825
867
  return { action: "ignore" };
826
868
  }
@@ -928,8 +970,12 @@ function createWhatsAppInboundHandler(config) {
928
970
  const pipeline = createInboundPipeline(
929
971
  { client: config.client, store: config.store, prompts: config.prompts },
930
972
  {
973
+ channel: "whatsapp",
931
974
  answerFn: config.answerFn,
932
975
  bucketsFor: config.bucketsFor,
976
+ rewriteQuery: config.rewriteQuery,
977
+ rerankContext: config.rerankContext,
978
+ transformReply: config.transformReply,
933
979
  model: config.model,
934
980
  channelHint: config.channelHint,
935
981
  personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
@@ -941,7 +987,8 @@ function createWhatsAppInboundHandler(config) {
941
987
  unmuteReply: config.unmuteReply,
942
988
  dmRateLimit: config.dmRateLimit,
943
989
  groupRateLimit: config.groupRateLimit,
944
- now: config.now
990
+ now: config.now,
991
+ logger
945
992
  }
946
993
  );
947
994
  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"}
@@ -8,6 +8,7 @@
8
8
  * then ask for the answer via `answerOnce`/`answerStream` (see ./answer.ts)
9
9
  * so a caller-supplied brain is honoured.
10
10
  */
11
+ import type { Logger } from "./logger";
11
12
  import type { PromptLoader } from "./prompts";
12
13
  import type { VectorStore } from "./retrieval";
13
14
  export type PipelineMode = "public" | "private";
@@ -23,6 +24,42 @@ export interface PreparedChat {
23
24
  /** Raw retrieved chunks, before being folded into `system` — for callers that log or display RAG context alongside the answer */
24
25
  context: string[];
25
26
  }
27
+ /** What `rewriteQuery` sees for a turn. */
28
+ export interface RewriteQueryArgs {
29
+ /** The latest user message's content — what retrieval would search on unmodified. */
30
+ query: string;
31
+ mode: PipelineMode;
32
+ /** Caller identity, where the surface has one. Absent on anonymous surfaces. */
33
+ sender?: string;
34
+ }
35
+ /**
36
+ * Rewrites the retrieval query before it reaches the vector store — e.g.
37
+ * expanding an ambiguous follow-up ("what about the second one?") into
38
+ * something embeddable on its own. Return the query unchanged to opt out for
39
+ * a given turn. A throw/rejection is caught by `prepareChat`, which falls
40
+ * back to the original query — a broken rewrite must never break retrieval.
41
+ */
42
+ export type RewriteQuery = (ctx: RewriteQueryArgs) => string | Promise<string>;
43
+ /** What `rerankContext` sees for a turn. */
44
+ export interface RerankContextArgs {
45
+ /** The query retrieval actually ran with — post-`rewriteQuery`, if configured. */
46
+ query: string;
47
+ /** Chunks `store.query` returned, in its own order. */
48
+ chunks: string[];
49
+ }
50
+ /**
51
+ * Reorders, filters, or otherwise post-processes the chunks retrieval
52
+ * returned before they're folded into the system prompt. A throw/rejection
53
+ * is caught by `prepareChat`, which falls back to the original chunks in
54
+ * their original order — a broken reranker must never break retrieval.
55
+ *
56
+ * This is NOT the seam for access control. Because a failure falls back to
57
+ * the unfiltered chunks, a hook that drops chunks a sender shouldn't see
58
+ * would silently un-drop them on its own error — the opposite of safe
59
+ * failure. Scope decisions belong in `bucketsFor` (see ./buckets), which
60
+ * fails closed by design; use this hook for relevance/ordering only.
61
+ */
62
+ export type RerankContext = (ctx: RerankContextArgs) => string[] | Promise<string[]>;
26
63
  /**
27
64
  * Run retrieval for the latest user message and assemble the system prompt.
28
65
  *
@@ -40,9 +77,15 @@ export interface PreparedChat {
40
77
  * `resolveBuckets` (see ./buckets) first, which is where the anonymous
41
78
  * ceiling is enforced.
42
79
  *
80
+ * `rewriteQuery` and `rerankContext` shape retrieval itself rather than the
81
+ * prompt around it: the former runs before `store.query`, the latter after.
82
+ * Both are optional and fail open — a throw, rejection, or malformed return
83
+ * value falls back to what retrieval would have done unmodified, so a buggy
84
+ * hook degrades relevance rather than breaking the chat path.
85
+ *
43
86
  * @throws if the conversation contains no user message
44
87
  */
45
- export declare function prepareChat({ store, prompts, mode, messages, personaLayer, channelHint, buckets, }: {
88
+ export declare function prepareChat({ store, prompts, mode, messages, personaLayer, channelHint, buckets, sender, rewriteQuery, rerankContext, logger, }: {
46
89
  store: VectorStore;
47
90
  prompts: PromptLoader;
48
91
  mode: PipelineMode;
@@ -56,5 +99,13 @@ export declare function prepareChat({ store, prompts, mode, messages, personaLay
56
99
  * `["base", mode]`. An empty array retrieves nothing.
57
100
  */
58
101
  buckets?: string[];
102
+ /** Caller identity, passed through to `rewriteQuery`. Absent on anonymous surfaces. */
103
+ sender?: string;
104
+ /** Rewrites the retrieval query before `store.query` runs — see {@link RewriteQuery}. */
105
+ rewriteQuery?: RewriteQuery;
106
+ /** Post-processes retrieved chunks before they're folded into the prompt — see {@link RerankContext}. */
107
+ rerankContext?: RerankContext;
108
+ /** Logs a `rewriteQuery`/`rerankContext` throw before falling back. Unset: the failure is silent. */
109
+ logger?: Pick<Logger, "error">;
59
110
  }): Promise<PreparedChat>;
60
111
  //# sourceMappingURL=pipeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,iIAAiI;IACjI,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAAC,EAChC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBxB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,iIAAiI;IACjI,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,gBAAgB,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE/E,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,iBAAiB,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAOrF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,WAAW,CAAC,EAChC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,OAAO,EACP,MAAM,EACN,YAAY,EACZ,aAAa,EACb,MAAM,GACP,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yFAAyF;IACzF,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,yGAAyG;IACzG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qGAAqG;IACrG,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,GAAG,OAAO,CAAC,YAAY,CAAC,CAuCxB"}
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";
@@ -25,7 +25,7 @@ export type { Logger, LogLevel } from "./core/logger";
25
25
  export { createConsoleLogger } from "./core/logger";
26
26
  export type { NormalizedChatBody } from "./core/messages";
27
27
  export { normalizeChatBody, normalizeMessages } from "./core/messages";
28
- export type { PipelineMessage, PipelineMode, PreparedChat } from "./core/pipeline";
28
+ export type { PipelineMessage, PipelineMode, PreparedChat, RerankContext, RerankContextArgs, RewriteQuery, RewriteQueryArgs, } from "./core/pipeline";
29
29
  export { prepareChat } from "./core/pipeline";
30
30
  export { PromptLoader } from "./core/prompts";
31
31
  export type { VectorStoreOptions } from "./core/retrieval";
@@ -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,
@@ -445,14 +458,35 @@ async function prepareChat({
445
458
  messages,
446
459
  personaLayer,
447
460
  channelHint,
448
- buckets
461
+ buckets,
462
+ sender,
463
+ rewriteQuery,
464
+ rerankContext,
465
+ logger
449
466
  }) {
450
467
  const lastUserMsg = lastUserMessage(messages);
451
468
  if (!lastUserMsg) {
452
469
  throw new Error("no user message found in conversation");
453
470
  }
454
471
  const { topK, label } = MODE_SETTINGS[mode];
455
- const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
472
+ let query = lastUserMsg.content;
473
+ if (rewriteQuery) {
474
+ try {
475
+ const rewritten = await rewriteQuery({ query, mode, sender });
476
+ if (typeof rewritten === "string" && rewritten.trim()) query = rewritten;
477
+ } catch (error) {
478
+ logger?.error("rewriteQuery threw; falling back to the original query", error);
479
+ }
480
+ }
481
+ let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
482
+ if (rerankContext) {
483
+ try {
484
+ const reranked = await rerankContext({ query, chunks: ctx });
485
+ if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
486
+ } catch (error) {
487
+ logger?.error("rerankContext threw; falling back to the original chunks", error);
488
+ }
489
+ }
456
490
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
457
491
  const hint = channelHint?.trim();
458
492
  const system = [
@@ -991,7 +1025,10 @@ async function createMCPServer(config) {
991
1025
  prompts,
992
1026
  mode: "public",
993
1027
  messages: conversationMessages,
994
- buckets
1028
+ buckets,
1029
+ rewriteQuery: config.rewriteQuery,
1030
+ rerankContext: config.rerankContext,
1031
+ logger: log
995
1032
  });
996
1033
  const result = await answerOnce({
997
1034
  answerFn: config.answerFn,
@@ -1002,6 +1039,11 @@ async function createMCPServer(config) {
1002
1039
  conversationId: convId,
1003
1040
  model
1004
1041
  });
1042
+ const replyText = await applyTransformReply(
1043
+ config.transformReply,
1044
+ { channel: "mcp-public", conversationId: convId, text: result.content },
1045
+ log
1046
+ );
1005
1047
  const cost = calculateCost(result.usage, config.openai.pricing);
1006
1048
  const duration = Date.now() - startTime;
1007
1049
  await logger.logChatInteraction(
@@ -1009,7 +1051,7 @@ async function createMCPServer(config) {
1009
1051
  convId,
1010
1052
  conversationMessages,
1011
1053
  ctx,
1012
- result.content,
1054
+ replyText ?? "",
1013
1055
  duration,
1014
1056
  cost
1015
1057
  ).catch((err) => log.error("Logging error:", err));
@@ -1017,7 +1059,7 @@ async function createMCPServer(config) {
1017
1059
  content: [
1018
1060
  {
1019
1061
  type: "text",
1020
- text: result.content
1062
+ text: replyText ?? ""
1021
1063
  }
1022
1064
  ],
1023
1065
  _meta: {
@@ -1072,7 +1114,10 @@ async function createMCPServer(config) {
1072
1114
  prompts,
1073
1115
  mode: "private",
1074
1116
  messages: conversationMessages,
1075
- buckets
1117
+ buckets,
1118
+ rewriteQuery: config.rewriteQuery,
1119
+ rerankContext: config.rerankContext,
1120
+ logger: log
1076
1121
  });
1077
1122
  const result = await answerOnce({
1078
1123
  answerFn: config.answerFn,
@@ -1083,6 +1128,11 @@ async function createMCPServer(config) {
1083
1128
  conversationId: convId,
1084
1129
  model
1085
1130
  });
1131
+ const replyText = await applyTransformReply(
1132
+ config.transformReply,
1133
+ { channel: "mcp-private", conversationId: convId, text: result.content },
1134
+ log
1135
+ );
1086
1136
  const cost = calculateCost(result.usage, config.openai.pricing);
1087
1137
  const duration = Date.now() - startTime;
1088
1138
  await logger.logChatInteraction(
@@ -1090,7 +1140,7 @@ async function createMCPServer(config) {
1090
1140
  convId,
1091
1141
  conversationMessages,
1092
1142
  ctx,
1093
- result.content,
1143
+ replyText ?? "",
1094
1144
  duration,
1095
1145
  cost
1096
1146
  ).catch((err) => log.error("Logging error:", err));
@@ -1098,7 +1148,7 @@ async function createMCPServer(config) {
1098
1148
  content: [
1099
1149
  {
1100
1150
  type: "text",
1101
- text: result.content
1151
+ text: replyText ?? ""
1102
1152
  }
1103
1153
  ],
1104
1154
  _meta: {
@@ -1542,7 +1592,16 @@ function demoRoutes(deps) {
1542
1592
  return c.json({ error: "no user message found in conversation" }, 400);
1543
1593
  }
1544
1594
  const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor });
1545
- const { system } = await prepareChat({ store, prompts, mode: "public", messages, buckets });
1595
+ const { system } = await prepareChat({
1596
+ store,
1597
+ prompts,
1598
+ mode: "public",
1599
+ messages,
1600
+ buckets,
1601
+ rewriteQuery: config.rewriteQuery,
1602
+ rerankContext: config.rerankContext,
1603
+ logger
1604
+ });
1546
1605
  const model = config.openai.model || DEFAULT_MODEL;
1547
1606
  const url = new URL(c.req.url);
1548
1607
  const wantsStream3 = url.searchParams.get("stream") === "1";
@@ -1571,7 +1630,12 @@ function demoRoutes(deps) {
1571
1630
  mode: "public",
1572
1631
  model
1573
1632
  });
1574
- return c.json({ reply: out.content });
1633
+ const reply = await applyTransformReply(
1634
+ config.transformReply,
1635
+ { channel: "widget-demo", text: out.content },
1636
+ logger
1637
+ );
1638
+ return c.json({ reply: reply ?? "" });
1575
1639
  } catch (error) {
1576
1640
  const err = error;
1577
1641
  logger.error("Demo chat error:", err);
@@ -1614,7 +1678,7 @@ function oversizedBodyError(message) {
1614
1678
  return { error: { message, type: "invalid_request_error" } };
1615
1679
  }
1616
1680
  function openaiRoutes(deps) {
1617
- const { client, store, config, prompts, apiKeyManager } = deps;
1681
+ const { client, store, config, prompts, apiKeyManager, logger } = deps;
1618
1682
  const app = new import_hono2.Hono();
1619
1683
  const { limitPublic, limitPrivate } = createRateLimiter2(config);
1620
1684
  const requirePrivateJWT = createJWTMiddleware(config);
@@ -1656,13 +1720,23 @@ function openaiRoutes(deps) {
1656
1720
  sender: jwtSubject(c),
1657
1721
  bucketsFor: config.bucketsFor
1658
1722
  });
1723
+ const sender = jwtSubject(c) ?? apiKeySubject(c);
1659
1724
  let system;
1660
1725
  try {
1661
- ({ system } = await prepareChat({ store, prompts, mode, messages, buckets }));
1726
+ ({ system } = await prepareChat({
1727
+ store,
1728
+ prompts,
1729
+ mode,
1730
+ messages,
1731
+ buckets,
1732
+ sender,
1733
+ rewriteQuery: config.rewriteQuery,
1734
+ rerankContext: config.rerankContext,
1735
+ logger
1736
+ }));
1662
1737
  } catch {
1663
1738
  return errorJson(c, 400, "Conversation must contain at least one user message");
1664
1739
  }
1665
- const sender = jwtSubject(c) ?? apiKeySubject(c);
1666
1740
  const conversationId = clientConversationId(c, body) ?? crypto.randomUUID();
1667
1741
  c.header("x-conversation-id", conversationId);
1668
1742
  const id = `chatcmpl-${crypto.randomUUID()}`;
@@ -1709,13 +1783,18 @@ function openaiRoutes(deps) {
1709
1783
  temperature,
1710
1784
  model: serverModel
1711
1785
  });
1786
+ const reply = await applyTransformReply(
1787
+ config.transformReply,
1788
+ { channel: `openai-compat-${mode}`, sender, conversationId, text: out.content },
1789
+ logger
1790
+ );
1712
1791
  return c.json({
1713
1792
  id,
1714
1793
  object: "chat.completion",
1715
1794
  created,
1716
1795
  model: serverModel,
1717
1796
  choices: [
1718
- { index: 0, message: { role: "assistant", content: out.content }, finish_reason: "stop" }
1797
+ { index: 0, message: { role: "assistant", content: reply ?? "" }, finish_reason: "stop" }
1719
1798
  ],
1720
1799
  usage: out.usage
1721
1800
  });
@@ -1745,7 +1824,7 @@ function wantsStream(c) {
1745
1824
  return acc.includes("text/event-stream");
1746
1825
  }
1747
1826
  function privateRoutes(deps) {
1748
- const { client, store, config, prompts } = deps;
1827
+ const { client, store, config, prompts, logger } = deps;
1749
1828
  const app = new import_hono3.Hono();
1750
1829
  const requirePrivateJWT = createJWTMiddleware(config);
1751
1830
  const { limitPrivate } = createRateLimiter2(config);
@@ -1767,7 +1846,17 @@ function privateRoutes(deps) {
1767
1846
  });
1768
1847
  let system;
1769
1848
  try {
1770
- ({ system } = await prepareChat({ store, prompts, mode: "private", messages, buckets }));
1849
+ ({ system } = await prepareChat({
1850
+ store,
1851
+ prompts,
1852
+ mode: "private",
1853
+ messages,
1854
+ buckets,
1855
+ sender,
1856
+ rewriteQuery: config.rewriteQuery,
1857
+ rerankContext: config.rerankContext,
1858
+ logger
1859
+ }));
1771
1860
  } catch {
1772
1861
  return c.json({ error: "no user message found in conversation" }, 400);
1773
1862
  }
@@ -1800,7 +1889,12 @@ function privateRoutes(deps) {
1800
1889
  sender,
1801
1890
  model
1802
1891
  });
1803
- return c.json({ reply: out.content });
1892
+ const reply = await applyTransformReply(
1893
+ config.transformReply,
1894
+ { channel: "widget-private", sender, text: out.content },
1895
+ logger
1896
+ );
1897
+ return c.json({ reply: reply ?? "" });
1804
1898
  });
1805
1899
  return app;
1806
1900
  }
@@ -1838,7 +1932,16 @@ function publicRoutes(deps) {
1838
1932
  const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor });
1839
1933
  let system;
1840
1934
  try {
1841
- ({ system } = await prepareChat({ store, prompts, mode: "public", messages, buckets }));
1935
+ ({ system } = await prepareChat({
1936
+ store,
1937
+ prompts,
1938
+ mode: "public",
1939
+ messages,
1940
+ buckets,
1941
+ rewriteQuery: config.rewriteQuery,
1942
+ rerankContext: config.rerankContext,
1943
+ logger
1944
+ }));
1842
1945
  } catch {
1843
1946
  return c.json({ error: "no user message found in conversation" }, 400);
1844
1947
  }
@@ -1862,7 +1965,12 @@ function publicRoutes(deps) {
1862
1965
  });
1863
1966
  }
1864
1967
  const out = await answerOnce({ answerFn, client, system, messages, mode: "public", model });
1865
- return c.json({ reply: out.content });
1968
+ const reply = await applyTransformReply(
1969
+ config.transformReply,
1970
+ { channel: "widget-public", text: out.content },
1971
+ logger
1972
+ );
1973
+ return c.json({ reply: reply ?? "" });
1866
1974
  });
1867
1975
  return app;
1868
1976
  }
@@ -2201,6 +2309,7 @@ function createShortener(config, fetchImpl = fetch) {
2201
2309
  VectorStore,
2202
2310
  answerOnce,
2203
2311
  answerStream,
2312
+ applyTransformReply,
2204
2313
  chatBodyLimit,
2205
2314
  completeOnce,
2206
2315
  completeStream,