@diegoaltoworks/chatter 0.42.0 → 0.44.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.
@@ -82,6 +82,29 @@ export interface WaMessageContext {
82
82
  export declare function messageContext(message: WAMessage): WaMessageContext;
83
83
  /** Text/caption across every shape that can carry one — plain text, a reply, or a photo/video/document caption. */
84
84
  export declare function extractText(message: WAMessage): string;
85
+ /**
86
+ * Removes the bot's OWN @mention tokens from a message's text.
87
+ *
88
+ * WhatsApp puts a mention in the raw text as the literal token `@<digits>` —
89
+ * the mentioned jid's user part — and only resolves it to a jid separately,
90
+ * on `contextInfo.mentionedJid`. Handed to a model untouched, the bot's own
91
+ * mention reads as a meaningless number, and the answer derails into asking
92
+ * the sender what `@<botDigits>` means instead of doing what the rest of the
93
+ * message asked.
94
+ *
95
+ * Only the bot's own tokens go: another participant's mention is real
96
+ * context ("tell @<someoneElse> I'm late"), and the numeral is all the model
97
+ * gets of them. Matching is on the digits alone, so both the phone-number and
98
+ * the LID form of the bot's own identity are covered.
99
+ *
100
+ * Everything else in the message is left byte-for-byte alone — indentation
101
+ * and blank lines in a pasted snippet included.
102
+ *
103
+ * A mention-only message ("@bot" and nothing else) keeps its original text:
104
+ * emptying it would make the reply gates read it as blank and drop it, so the
105
+ * bot would go silent on being addressed rather than answer.
106
+ */
107
+ export declare function stripOwnMentions(text: string, ownIds: string[]): string;
85
108
  export interface WhatsAppInboundConfig {
86
109
  client: OpenAI;
87
110
  store: VectorStore;
@@ -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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"}
@@ -30,6 +30,6 @@ export { loadBaileys } from "./baileys";
30
30
  export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMessageEvent, } from "./channel";
31
31
  export { decrypt, encrypt } from "./crypto";
32
32
  export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
33
- export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
33
+ export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, stripOwnMentions, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
34
34
  export { canAcquireLease, createTursoWaLeaseStore, LEASE_HEARTBEAT_MS, LEASE_STALE_MS, LEASE_WAIT_MS, type WaLeaseRow, type WaLeaseStore, } from "./lease";
35
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
@@ -53,6 +53,7 @@ __export(index_exports, {
53
53
  senderNameFor: () => senderNameFor,
54
54
  senderPhoneFor: () => senderPhoneFor,
55
55
  shutdownWaSessions: () => shutdownWaSessions,
56
+ stripOwnMentions: () => stripOwnMentions,
56
57
  useTursoAuthState: () => useTursoAuthState
57
58
  });
58
59
  module.exports = __toCommonJS(index_exports);
@@ -717,19 +718,21 @@ function isEffectivelyFromSelf(msg, registry) {
717
718
  function isGroupJid(jid) {
718
719
  return jid.endsWith("@g.us");
719
720
  }
721
+ function bareUser(jid) {
722
+ return jid.split("@")[0]?.split(":")[0] ?? "";
723
+ }
720
724
  function jidsMatch(a, b) {
721
725
  if (!a || !b) return false;
722
- const bare = (jid) => jid.split("@")[0]?.split(":")[0] ?? jid;
723
- return bare(a) === bare(b);
726
+ return bareUser(a) === bareUser(b);
724
727
  }
725
728
  function jidToPhoneNumber(jid) {
726
- const bare = jid.split("@")[0]?.split(":")[0] ?? "";
729
+ const bare = bareUser(jid);
727
730
  return bare.startsWith("+") ? bare : `+${bare}`;
728
731
  }
729
732
  function normalizeJid(jid) {
730
733
  const [user = "", domain] = jid.split("@");
731
- const bareUser = user.split(":")[0] ?? user;
732
- return domain ? `${bareUser}@${domain}` : bareUser;
734
+ const withoutDevice = user.split(":")[0] ?? user;
735
+ return domain ? `${withoutDevice}@${domain}` : withoutDevice;
733
736
  }
734
737
  async function senderPhoneFor(sock, message, chatJid) {
735
738
  const key = message.key;
@@ -758,6 +761,19 @@ function extractText(message) {
758
761
  if (!content) return "";
759
762
  return content.conversation ?? content.extendedTextMessage?.text ?? content.imageMessage?.caption ?? content.videoMessage?.caption ?? content.documentMessage?.caption ?? "";
760
763
  }
764
+ function stripOwnMentions(text, ownIds) {
765
+ const ownUsers = new Set(ownIds.map(bareUser).filter(Boolean));
766
+ if (!text || ownUsers.size === 0) return text;
767
+ let struck = false;
768
+ const stripped = text.replace(/(?<!\S)@(\d+)[^\S\n]*/g, (token, digits) => {
769
+ if (!ownUsers.has(digits)) return token;
770
+ struck = true;
771
+ return "";
772
+ });
773
+ if (!struck) return text;
774
+ const cleaned = stripped.trim();
775
+ return cleaned ? cleaned : text;
776
+ }
761
777
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
762
778
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
763
779
  async function resolvePersonaLayer(config, ctx) {
@@ -780,11 +796,12 @@ function createWhatsAppInboundHandler(config) {
780
796
  try {
781
797
  const chatId = message.key.remoteJid ?? "";
782
798
  if (!chatId || chatId === "status@broadcast") return;
783
- const text = extractText(message);
799
+ const rawText = extractText(message);
784
800
  const context = messageContext(message);
785
801
  const senderId = message.key.participant ?? chatId;
786
802
  const ownIds = [sock.user?.id, sock.user?.lid].filter((id) => Boolean(id)).map(normalizeJid);
787
803
  config.registry.set(sessionId, ownIds);
804
+ const text = stripOwnMentions(rawText, ownIds);
788
805
  const fromBot = isEffectivelyFromSelf(
789
806
  { fromBot: message.key.fromMe ?? false, senderId: normalizeJid(senderId) },
790
807
  config.registry
@@ -888,5 +905,6 @@ function createWhatsAppInboundHandler(config) {
888
905
  senderNameFor,
889
906
  senderPhoneFor,
890
907
  shutdownWaSessions,
908
+ stripOwnMentions,
891
909
  useTursoAuthState
892
910
  });
@@ -658,19 +658,21 @@ function isEffectivelyFromSelf(msg, registry) {
658
658
  function isGroupJid(jid) {
659
659
  return jid.endsWith("@g.us");
660
660
  }
661
+ function bareUser(jid) {
662
+ return jid.split("@")[0]?.split(":")[0] ?? "";
663
+ }
661
664
  function jidsMatch(a, b) {
662
665
  if (!a || !b) return false;
663
- const bare = (jid) => jid.split("@")[0]?.split(":")[0] ?? jid;
664
- return bare(a) === bare(b);
666
+ return bareUser(a) === bareUser(b);
665
667
  }
666
668
  function jidToPhoneNumber(jid) {
667
- const bare = jid.split("@")[0]?.split(":")[0] ?? "";
669
+ const bare = bareUser(jid);
668
670
  return bare.startsWith("+") ? bare : `+${bare}`;
669
671
  }
670
672
  function normalizeJid(jid) {
671
673
  const [user = "", domain] = jid.split("@");
672
- const bareUser = user.split(":")[0] ?? user;
673
- return domain ? `${bareUser}@${domain}` : bareUser;
674
+ const withoutDevice = user.split(":")[0] ?? user;
675
+ return domain ? `${withoutDevice}@${domain}` : withoutDevice;
674
676
  }
675
677
  async function senderPhoneFor(sock, message, chatJid) {
676
678
  const key = message.key;
@@ -699,6 +701,19 @@ function extractText(message) {
699
701
  if (!content) return "";
700
702
  return content.conversation ?? content.extendedTextMessage?.text ?? content.imageMessage?.caption ?? content.videoMessage?.caption ?? content.documentMessage?.caption ?? "";
701
703
  }
704
+ function stripOwnMentions(text, ownIds) {
705
+ const ownUsers = new Set(ownIds.map(bareUser).filter(Boolean));
706
+ if (!text || ownUsers.size === 0) return text;
707
+ let struck = false;
708
+ const stripped = text.replace(/(?<!\S)@(\d+)[^\S\n]*/g, (token, digits) => {
709
+ if (!ownUsers.has(digits)) return token;
710
+ struck = true;
711
+ return "";
712
+ });
713
+ if (!struck) return text;
714
+ const cleaned = stripped.trim();
715
+ return cleaned ? cleaned : text;
716
+ }
702
717
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
703
718
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
704
719
  async function resolvePersonaLayer(config, ctx) {
@@ -721,11 +736,12 @@ function createWhatsAppInboundHandler(config) {
721
736
  try {
722
737
  const chatId = message.key.remoteJid ?? "";
723
738
  if (!chatId || chatId === "status@broadcast") return;
724
- const text = extractText(message);
739
+ const rawText = extractText(message);
725
740
  const context = messageContext(message);
726
741
  const senderId = message.key.participant ?? chatId;
727
742
  const ownIds = [sock.user?.id, sock.user?.lid].filter((id) => Boolean(id)).map(normalizeJid);
728
743
  config.registry.set(sessionId, ownIds);
744
+ const text = stripOwnMentions(rawText, ownIds);
729
745
  const fromBot = isEffectivelyFromSelf(
730
746
  { fromBot: message.key.fromMe ?? false, senderId: normalizeJid(senderId) },
731
747
  config.registry
@@ -828,5 +844,6 @@ export {
828
844
  senderNameFor,
829
845
  senderPhoneFor,
830
846
  shutdownWaSessions,
847
+ stripOwnMentions,
831
848
  useTursoAuthState
832
849
  };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Inbound chat message normalization — the trust boundary for conversations.
3
+ *
4
+ * Every HTTP chat surface accepts a caller-supplied conversation, and the
5
+ * server owns the system prompt (guardrails + persona + retrieved context).
6
+ * Nothing a client sends may add to, or masquerade as, that prompt, so the
7
+ * only roles that survive normalization are `user` and `assistant`:
8
+ * `system`, `developer` and `tool` messages are dropped, not forwarded.
9
+ *
10
+ * Content is reduced to a plain string: strings pass through and OpenAI-style
11
+ * content-part arrays are flattened to their text parts. Anything else (image
12
+ * parts, tool calls, objects, numbers) carries no usable text and is skipped.
13
+ */
14
+ import type { PipelineMessage } from "./pipeline";
15
+ /**
16
+ * Normalize a raw `messages` array to the pipeline format.
17
+ *
18
+ * Returns `null` when the input is not a non-empty array, when any entry is
19
+ * not an object, or when nothing usable remains after filtering.
20
+ */
21
+ export declare function normalizeMessages(raw: unknown): PipelineMessage[] | null;
22
+ /** Outcome of normalizing a widget chat request body. */
23
+ export type NormalizedChatBody = {
24
+ ok: true;
25
+ messages: PipelineMessage[];
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ /**
31
+ * Normalize the widget chat body shape, which accepts either a single
32
+ * `message` string or a `messages` conversation array.
33
+ *
34
+ * The resulting messages have been through `normalizeMessages`, so a client
35
+ * cannot inject a system turn on these surfaces either.
36
+ */
37
+ export declare function normalizeChatBody(body: unknown): NormalizedChatBody;
38
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/core/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAWlD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AAED,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CA2BnE"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Inbound message normalization tests.
3
+ *
4
+ * The invariant under test is that a client cannot contribute a system turn
5
+ * and cannot smuggle non-text content into the conversation.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=messages.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.test.d.ts","sourceRoot":"","sources":["../../src/core/messages.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/dist/index.d.ts CHANGED
@@ -22,6 +22,8 @@ export { defaultBuckets, resolveBuckets } from "./core/buckets";
22
22
  export { detectLeakage, guardOutput, scrubOutput } from "./core/guardrails";
23
23
  export { completeOnce, completeStream, DEFAULT_MODEL } from "./core/llm";
24
24
  export { loadKnowledge } from "./core/loaders";
25
+ export type { NormalizedChatBody } from "./core/messages";
26
+ export { normalizeChatBody, normalizeMessages } from "./core/messages";
25
27
  export type { PipelineMessage, PipelineMode, PreparedChat } from "./core/pipeline";
26
28
  export { prepareChat } from "./core/pipeline";
27
29
  export { PromptLoader } from "./core/prompts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -7559,6 +7559,8 @@ __export(index_exports, {
7559
7559
  guardOutput: () => guardOutput,
7560
7560
  jwtSubject: () => jwtSubject,
7561
7561
  loadKnowledge: () => loadKnowledge,
7562
+ normalizeChatBody: () => normalizeChatBody,
7563
+ normalizeMessages: () => normalizeMessages,
7562
7564
  openaiRoutes: () => openaiRoutes,
7563
7565
  prepareChat: () => prepareChat,
7564
7566
  privateRoutes: () => privateRoutes,
@@ -7845,6 +7847,52 @@ function loadKnowledge(root = "config/knowledge") {
7845
7847
  return docs;
7846
7848
  }
7847
7849
 
7850
+ // src/core/messages.ts
7851
+ function isClientRole(role) {
7852
+ return role === "user" || role === "assistant";
7853
+ }
7854
+ function normalizeMessages(raw) {
7855
+ if (!Array.isArray(raw) || raw.length === 0) return null;
7856
+ const messages = [];
7857
+ for (const entry of raw) {
7858
+ if (!entry || typeof entry !== "object") return null;
7859
+ const { role, content } = entry;
7860
+ if (!isClientRole(role)) continue;
7861
+ let text = null;
7862
+ if (typeof content === "string") {
7863
+ text = content;
7864
+ } else if (Array.isArray(content)) {
7865
+ text = content.filter(
7866
+ (part) => !!part && typeof part === "object" && part.type === "text" && typeof part.text === "string"
7867
+ ).map((part) => part.text).join("");
7868
+ }
7869
+ if (text === null) continue;
7870
+ messages.push({ role, content: text });
7871
+ }
7872
+ return messages.length > 0 ? messages : null;
7873
+ }
7874
+ function normalizeChatBody(body) {
7875
+ const source = body && typeof body === "object" ? body : {};
7876
+ if (Array.isArray(source.messages)) {
7877
+ if (source.messages.length === 0) {
7878
+ return { ok: false, error: "messages array cannot be empty" };
7879
+ }
7880
+ const messages = normalizeMessages(source.messages);
7881
+ if (!messages) {
7882
+ return {
7883
+ ok: false,
7884
+ error: "messages must contain user/assistant messages with text content"
7885
+ };
7886
+ }
7887
+ return { ok: true, messages };
7888
+ }
7889
+ const { message } = source;
7890
+ if (message && typeof message !== "object") {
7891
+ return { ok: true, messages: [{ role: "user", content: String(message) }] };
7892
+ }
7893
+ return { ok: false, error: "either 'message' or 'messages' required" };
7894
+ }
7895
+
7848
7896
  // src/core/pipeline.ts
7849
7897
  var MODE_SETTINGS = {
7850
7898
  public: { topK: 6, label: "Context" },
@@ -32840,17 +32888,11 @@ function demoRoutes(deps) {
32840
32888
  app.post("/api/demo/chat", limitDemo(), async (c) => {
32841
32889
  try {
32842
32890
  const body = await c.req.json().catch(() => ({}));
32843
- let messages;
32844
- if (body?.messages && Array.isArray(body.messages)) {
32845
- messages = body.messages;
32846
- if (messages.length === 0) {
32847
- return c.json({ error: "messages array cannot be empty" }, 400);
32848
- }
32849
- } else if (body?.message) {
32850
- messages = [{ role: "user", content: String(body.message) }];
32851
- } else {
32852
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
32891
+ const normalized = normalizeChatBody(body);
32892
+ if (!normalized.ok) {
32893
+ return c.json({ error: normalized.error }, 400);
32853
32894
  }
32895
+ const { messages } = normalized;
32854
32896
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
32855
32897
  if (!lastUserMsg) {
32856
32898
  return c.json({ error: "no user message found in conversation" }, 400);
@@ -32901,26 +32943,6 @@ ${ctx.join("\n\n")}`
32901
32943
  // src/routes/openai.ts
32902
32944
  var import_hono2 = require("hono");
32903
32945
  var import_streaming2 = require("hono/streaming");
32904
- function normalizeMessages(raw) {
32905
- if (!Array.isArray(raw) || raw.length === 0) return null;
32906
- const messages = [];
32907
- for (const entry of raw) {
32908
- if (!entry || typeof entry !== "object") return null;
32909
- const { role, content } = entry;
32910
- if (role !== "user" && role !== "assistant") continue;
32911
- let text = null;
32912
- if (typeof content === "string") {
32913
- text = content;
32914
- } else if (Array.isArray(content)) {
32915
- text = content.filter(
32916
- (part) => !!part && typeof part === "object" && part.type === "text" && typeof part.text === "string"
32917
- ).map((part) => part.text).join("");
32918
- }
32919
- if (text === null) continue;
32920
- messages.push({ role, content: text });
32921
- }
32922
- return messages.length > 0 ? messages : null;
32923
- }
32924
32946
  function errorJson(c, status, message) {
32925
32947
  return c.json(
32926
32948
  { error: { message, type: status === 401 ? "authentication_error" : "invalid_request_error" } },
@@ -33055,17 +33077,11 @@ function privateRoutes(deps) {
33055
33077
  app.use("/api/private/*", limitPrivate2());
33056
33078
  app.post("/api/private/chat", async (c) => {
33057
33079
  const body = await c.req.json().catch(() => ({}));
33058
- let messages;
33059
- if (body?.messages && Array.isArray(body.messages)) {
33060
- messages = body.messages;
33061
- if (messages.length === 0) {
33062
- return c.json({ error: "messages array cannot be empty" }, 400);
33063
- }
33064
- } else if (body?.message) {
33065
- messages = [{ role: "user", content: String(body.message) }];
33066
- } else {
33067
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
33080
+ const normalized = normalizeChatBody(body);
33081
+ if (!normalized.ok) {
33082
+ return c.json({ error: normalized.error }, 400);
33068
33083
  }
33084
+ const { messages } = normalized;
33069
33085
  const buckets = await resolveBuckets({
33070
33086
  mode: "private",
33071
33087
  sender: jwtSubject(c),
@@ -33123,17 +33139,11 @@ function publicRoutes(deps) {
33123
33139
  app.use("/api/public/*", limitPublic2());
33124
33140
  app.post("/api/public/chat", async (c) => {
33125
33141
  const body = await c.req.json().catch(() => ({}));
33126
- let messages;
33127
- if (body?.messages && Array.isArray(body.messages)) {
33128
- messages = body.messages;
33129
- if (messages.length === 0) {
33130
- return c.json({ error: "messages array cannot be empty" }, 400);
33131
- }
33132
- } else if (body?.message) {
33133
- messages = [{ role: "user", content: String(body.message) }];
33134
- } else {
33135
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
33142
+ const normalized = normalizeChatBody(body);
33143
+ if (!normalized.ok) {
33144
+ return c.json({ error: normalized.error }, 400);
33136
33145
  }
33146
+ const { messages } = normalized;
33137
33147
  const buckets = await resolveBuckets({ mode: "public", bucketsFor: config2.bucketsFor });
33138
33148
  let system;
33139
33149
  try {
@@ -33462,6 +33472,8 @@ function createShortener(config2, fetchImpl = fetch) {
33462
33472
  guardOutput,
33463
33473
  jwtSubject,
33464
33474
  loadKnowledge,
33475
+ normalizeChatBody,
33476
+ normalizeMessages,
33465
33477
  openaiRoutes,
33466
33478
  prepareChat,
33467
33479
  privateRoutes,
package/dist/index.mjs CHANGED
@@ -7807,6 +7807,52 @@ function loadKnowledge(root = "config/knowledge") {
7807
7807
  return docs;
7808
7808
  }
7809
7809
 
7810
+ // src/core/messages.ts
7811
+ function isClientRole(role) {
7812
+ return role === "user" || role === "assistant";
7813
+ }
7814
+ function normalizeMessages(raw) {
7815
+ if (!Array.isArray(raw) || raw.length === 0) return null;
7816
+ const messages = [];
7817
+ for (const entry of raw) {
7818
+ if (!entry || typeof entry !== "object") return null;
7819
+ const { role, content } = entry;
7820
+ if (!isClientRole(role)) continue;
7821
+ let text = null;
7822
+ if (typeof content === "string") {
7823
+ text = content;
7824
+ } else if (Array.isArray(content)) {
7825
+ text = content.filter(
7826
+ (part) => !!part && typeof part === "object" && part.type === "text" && typeof part.text === "string"
7827
+ ).map((part) => part.text).join("");
7828
+ }
7829
+ if (text === null) continue;
7830
+ messages.push({ role, content: text });
7831
+ }
7832
+ return messages.length > 0 ? messages : null;
7833
+ }
7834
+ function normalizeChatBody(body) {
7835
+ const source = body && typeof body === "object" ? body : {};
7836
+ if (Array.isArray(source.messages)) {
7837
+ if (source.messages.length === 0) {
7838
+ return { ok: false, error: "messages array cannot be empty" };
7839
+ }
7840
+ const messages = normalizeMessages(source.messages);
7841
+ if (!messages) {
7842
+ return {
7843
+ ok: false,
7844
+ error: "messages must contain user/assistant messages with text content"
7845
+ };
7846
+ }
7847
+ return { ok: true, messages };
7848
+ }
7849
+ const { message } = source;
7850
+ if (message && typeof message !== "object") {
7851
+ return { ok: true, messages: [{ role: "user", content: String(message) }] };
7852
+ }
7853
+ return { ok: false, error: "either 'message' or 'messages' required" };
7854
+ }
7855
+
7810
7856
  // src/core/pipeline.ts
7811
7857
  var MODE_SETTINGS = {
7812
7858
  public: { topK: 6, label: "Context" },
@@ -32802,17 +32848,11 @@ function demoRoutes(deps) {
32802
32848
  app.post("/api/demo/chat", limitDemo(), async (c) => {
32803
32849
  try {
32804
32850
  const body = await c.req.json().catch(() => ({}));
32805
- let messages;
32806
- if (body?.messages && Array.isArray(body.messages)) {
32807
- messages = body.messages;
32808
- if (messages.length === 0) {
32809
- return c.json({ error: "messages array cannot be empty" }, 400);
32810
- }
32811
- } else if (body?.message) {
32812
- messages = [{ role: "user", content: String(body.message) }];
32813
- } else {
32814
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
32851
+ const normalized = normalizeChatBody(body);
32852
+ if (!normalized.ok) {
32853
+ return c.json({ error: normalized.error }, 400);
32815
32854
  }
32855
+ const { messages } = normalized;
32816
32856
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
32817
32857
  if (!lastUserMsg) {
32818
32858
  return c.json({ error: "no user message found in conversation" }, 400);
@@ -32863,26 +32903,6 @@ ${ctx.join("\n\n")}`
32863
32903
  // src/routes/openai.ts
32864
32904
  import { Hono as Hono2 } from "hono";
32865
32905
  import { stream as stream2 } from "hono/streaming";
32866
- function normalizeMessages(raw) {
32867
- if (!Array.isArray(raw) || raw.length === 0) return null;
32868
- const messages = [];
32869
- for (const entry of raw) {
32870
- if (!entry || typeof entry !== "object") return null;
32871
- const { role, content } = entry;
32872
- if (role !== "user" && role !== "assistant") continue;
32873
- let text = null;
32874
- if (typeof content === "string") {
32875
- text = content;
32876
- } else if (Array.isArray(content)) {
32877
- text = content.filter(
32878
- (part) => !!part && typeof part === "object" && part.type === "text" && typeof part.text === "string"
32879
- ).map((part) => part.text).join("");
32880
- }
32881
- if (text === null) continue;
32882
- messages.push({ role, content: text });
32883
- }
32884
- return messages.length > 0 ? messages : null;
32885
- }
32886
32906
  function errorJson(c, status, message) {
32887
32907
  return c.json(
32888
32908
  { error: { message, type: status === 401 ? "authentication_error" : "invalid_request_error" } },
@@ -33017,17 +33037,11 @@ function privateRoutes(deps) {
33017
33037
  app.use("/api/private/*", limitPrivate2());
33018
33038
  app.post("/api/private/chat", async (c) => {
33019
33039
  const body = await c.req.json().catch(() => ({}));
33020
- let messages;
33021
- if (body?.messages && Array.isArray(body.messages)) {
33022
- messages = body.messages;
33023
- if (messages.length === 0) {
33024
- return c.json({ error: "messages array cannot be empty" }, 400);
33025
- }
33026
- } else if (body?.message) {
33027
- messages = [{ role: "user", content: String(body.message) }];
33028
- } else {
33029
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
33040
+ const normalized = normalizeChatBody(body);
33041
+ if (!normalized.ok) {
33042
+ return c.json({ error: normalized.error }, 400);
33030
33043
  }
33044
+ const { messages } = normalized;
33031
33045
  const buckets = await resolveBuckets({
33032
33046
  mode: "private",
33033
33047
  sender: jwtSubject(c),
@@ -33085,17 +33099,11 @@ function publicRoutes(deps) {
33085
33099
  app.use("/api/public/*", limitPublic2());
33086
33100
  app.post("/api/public/chat", async (c) => {
33087
33101
  const body = await c.req.json().catch(() => ({}));
33088
- let messages;
33089
- if (body?.messages && Array.isArray(body.messages)) {
33090
- messages = body.messages;
33091
- if (messages.length === 0) {
33092
- return c.json({ error: "messages array cannot be empty" }, 400);
33093
- }
33094
- } else if (body?.message) {
33095
- messages = [{ role: "user", content: String(body.message) }];
33096
- } else {
33097
- return c.json({ error: "either 'message' or 'messages' required" }, 400);
33102
+ const normalized = normalizeChatBody(body);
33103
+ if (!normalized.ok) {
33104
+ return c.json({ error: normalized.error }, 400);
33098
33105
  }
33106
+ const { messages } = normalized;
33099
33107
  const buckets = await resolveBuckets({ mode: "public", bucketsFor: config2.bucketsFor });
33100
33108
  let system;
33101
33109
  try {
@@ -33425,6 +33433,8 @@ export {
33425
33433
  guardOutput,
33426
33434
  jwtSubject,
33427
33435
  loadKnowledge,
33436
+ normalizeChatBody,
33437
+ normalizeMessages,
33428
33438
  openaiRoutes,
33429
33439
  prepareChat,
33430
33440
  privateRoutes,
@@ -1 +1 @@
1
- {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/routes/demo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsHnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,8EAqHlD"}
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/routes/demo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsHnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,8EA+GlD"}
@@ -22,13 +22,6 @@
22
22
  * Bearer` or `x-api-key`; widget-only session/demo keys are not valid.
23
23
  */
24
24
  import { Hono } from "hono";
25
- import { type PipelineMessage } from "../core/pipeline";
26
25
  import type { ServerDependencies } from "../types";
27
- /**
28
- * Normalize OpenAI-format messages to the pipeline format.
29
- * Accepts string content and text content-part arrays; drops system/tool
30
- * messages. Returns null when nothing usable remains.
31
- */
32
- export declare function normalizeMessages(raw: unknown): PipelineMessage[] | null;
33
26
  export declare function openaiRoutes(deps: ServerDependencies): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
34
27
  //# sourceMappingURL=openai.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/routes/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,EAAE,KAAK,eAAe,EAAkC,MAAM,kBAAkB,CAAC;AAGxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AASD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAwIpD"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/routes/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAwIpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/routes/private.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,8EAoErD"}
1
+ {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/routes/private.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,8EA4DrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/routes/public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EA0EpD"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/routes/public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAkEpD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",