@diegoaltoworks/chatter 1.0.0 → 2.1.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 (77) hide show
  1. package/README.md +3 -1
  2. package/dist/channels/gates.d.ts +21 -2
  3. package/dist/channels/gates.d.ts.map +1 -1
  4. package/dist/channels/index.d.ts +5 -3
  5. package/dist/channels/index.d.ts.map +1 -1
  6. package/dist/channels/index.js +31 -13
  7. package/dist/channels/index.mjs +30 -13
  8. package/dist/channels/matrix/channel.d.ts +2 -0
  9. package/dist/channels/matrix/channel.d.ts.map +1 -1
  10. package/dist/channels/matrix/handler.d.ts +9 -0
  11. package/dist/channels/matrix/handler.d.ts.map +1 -1
  12. package/dist/channels/matrix/index.d.ts +1 -1
  13. package/dist/channels/matrix/index.d.ts.map +1 -1
  14. package/dist/channels/matrix/index.js +75 -20
  15. package/dist/channels/matrix/index.mjs +74 -20
  16. package/dist/channels/matrix/updates.d.ts +8 -2
  17. package/dist/channels/matrix/updates.d.ts.map +1 -1
  18. package/dist/channels/pipeline.d.ts +34 -4
  19. package/dist/channels/pipeline.d.ts.map +1 -1
  20. package/dist/channels/polling.d.ts +11 -0
  21. package/dist/channels/polling.d.ts.map +1 -1
  22. package/dist/channels/telegram/channel.d.ts +2 -0
  23. package/dist/channels/telegram/channel.d.ts.map +1 -1
  24. package/dist/channels/telegram/handler.d.ts +9 -0
  25. package/dist/channels/telegram/handler.d.ts.map +1 -1
  26. package/dist/channels/telegram/index.d.ts +1 -1
  27. package/dist/channels/telegram/index.d.ts.map +1 -1
  28. package/dist/channels/telegram/index.js +73 -24
  29. package/dist/channels/telegram/index.mjs +72 -24
  30. package/dist/channels/telegram/updates.d.ts +8 -2
  31. package/dist/channels/telegram/updates.d.ts.map +1 -1
  32. package/dist/channels/telegram/webhook.d.ts +10 -0
  33. package/dist/channels/telegram/webhook.d.ts.map +1 -1
  34. package/dist/channels/whatsapp/channel.d.ts +8 -0
  35. package/dist/channels/whatsapp/channel.d.ts.map +1 -1
  36. package/dist/channels/whatsapp/inbound.d.ts +2 -0
  37. package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
  38. package/dist/channels/whatsapp/index.js +45 -18
  39. package/dist/channels/whatsapp/index.mjs +45 -18
  40. package/dist/core/answer.d.ts +9 -2
  41. package/dist/core/answer.d.ts.map +1 -1
  42. package/dist/core/chunking.d.ts +31 -0
  43. package/dist/core/chunking.d.ts.map +1 -0
  44. package/dist/core/guardrails.d.ts +11 -1
  45. package/dist/core/guardrails.d.ts.map +1 -1
  46. package/dist/core/llm.d.ts +3 -1
  47. package/dist/core/llm.d.ts.map +1 -1
  48. package/dist/core/retrieval.d.ts +39 -0
  49. package/dist/core/retrieval.d.ts.map +1 -1
  50. package/dist/history/index.js +12 -9
  51. package/dist/history/index.mjs +12 -9
  52. package/dist/index.d.ts +4 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +261 -52
  55. package/dist/index.mjs +254 -47
  56. package/dist/mcp-server.d.ts.map +1 -1
  57. package/dist/mcp-server.js +217 -30
  58. package/dist/mcp-server.mjs +216 -29
  59. package/dist/personas/index.d.ts +1 -1
  60. package/dist/personas/index.d.ts.map +1 -1
  61. package/dist/personas/index.js +21 -7
  62. package/dist/personas/index.mjs +21 -7
  63. package/dist/personas/resolver.d.ts +35 -8
  64. package/dist/personas/resolver.d.ts.map +1 -1
  65. package/dist/personas/timeContext.d.ts.map +1 -1
  66. package/dist/routes/demo.d.ts.map +1 -1
  67. package/dist/routes/openai.d.ts.map +1 -1
  68. package/dist/routes/private.d.ts.map +1 -1
  69. package/dist/routes/public.d.ts.map +1 -1
  70. package/dist/scheduler/index.js +12 -9
  71. package/dist/scheduler/index.mjs +12 -9
  72. package/dist/server.d.ts.map +1 -1
  73. package/dist/server.js +251 -50
  74. package/dist/server.mjs +246 -45
  75. package/dist/types.d.ts +39 -1
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +13 -13
@@ -475,6 +475,7 @@ function createWhatsAppChannel(config) {
475
475
  assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
476
476
  const channelName = config.name ?? "whatsapp";
477
477
  const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
478
+ const multiEndpoint = config.name !== void 0 || sessionIds.length > 1;
478
479
  const now = config.now ?? Date.now;
479
480
  const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
480
481
  const staleMs = config.staleMs ?? LEASE_STALE_MS;
@@ -668,7 +669,12 @@ function createWhatsAppChannel(config) {
668
669
  if (type !== "notify") return;
669
670
  for (const message of messages) {
670
671
  try {
671
- await config.onMessage?.({ sessionId, sock, message });
672
+ await config.onMessage?.({
673
+ sessionId,
674
+ sock,
675
+ message,
676
+ endpointId: multiEndpoint ? sessionId : void 0
677
+ });
672
678
  } catch (error) {
673
679
  log.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
674
680
  }
@@ -866,9 +872,10 @@ function detectLeakage(text) {
866
872
  function scrubOutput(text) {
867
873
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
868
874
  }
869
- function guardOutput(text) {
875
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
876
+ function guardOutput(text, refusal) {
870
877
  if (detectLeakage(text)) {
871
- return "Sorry, I can't share internal instructions. How else can I help?";
878
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
872
879
  }
873
880
  return scrubOutput(text);
874
881
  }
@@ -880,7 +887,8 @@ async function completeOnce({
880
887
  system,
881
888
  messages,
882
889
  temperature = 0.2,
883
- model = DEFAULT_MODEL
890
+ model = DEFAULT_MODEL,
891
+ refusal
884
892
  }) {
885
893
  const res = await client.chat.completions.create({
886
894
  model,
@@ -889,19 +897,19 @@ async function completeOnce({
889
897
  });
890
898
  const text = res.choices[0]?.message?.content ?? "";
891
899
  return {
892
- content: guardOutput(text),
900
+ content: guardOutput(text, refusal),
893
901
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
894
902
  };
895
903
  }
896
904
 
897
905
  // src/core/answer.ts
898
906
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
899
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
907
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
900
908
  const result = await answerFn({ system, messages, mode, sender, conversationId });
901
909
  const raw = typeof result === "string" ? result : result?.content;
902
910
  const usage = typeof result === "string" ? void 0 : result?.usage;
903
911
  return {
904
- content: guardOutput(typeof raw === "string" ? raw : ""),
912
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
905
913
  usage: usage ?? noUsage()
906
914
  };
907
915
  }
@@ -914,12 +922,13 @@ async function answerOnce({
914
922
  sender,
915
923
  conversationId,
916
924
  temperature,
917
- model
925
+ model,
926
+ refusal
918
927
  }) {
919
928
  if (answerFn) {
920
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
929
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
921
930
  }
922
- return completeOnce({ client, system, messages, temperature, model });
931
+ return completeOnce({ client, system, messages, temperature, model, refusal });
923
932
  }
924
933
  async function applyTransformReply(transformReply, input, logger) {
925
934
  if (!transformReply) return input.text;
@@ -1125,9 +1134,17 @@ function resolveBrainHooks(config, fallback) {
1125
1134
  rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
1126
1135
  rerankContext: config.rerankContext ?? fallback?.rerankContext,
1127
1136
  fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
1128
- transformReply: config.transformReply ?? fallback?.transformReply
1137
+ transformReply: config.transformReply ?? fallback?.transformReply,
1138
+ refusal: config.refusal ?? fallback?.refusal
1129
1139
  };
1130
1140
  }
1141
+ function escapeKeyPart(part) {
1142
+ return part.replaceAll("%", "%25").replaceAll("#", "%23");
1143
+ }
1144
+ function conversationKeyFor(chatId, endpointId) {
1145
+ if (!endpointId) return chatId;
1146
+ return `${escapeKeyPart(chatId)}#${escapeKeyPart(endpointId)}`;
1147
+ }
1131
1148
  var DEFAULT_HISTORY_LIMIT = 20;
1132
1149
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
1133
1150
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -1186,11 +1203,15 @@ function createInboundPipeline(deps, config) {
1186
1203
  return { action: "rate-limited" };
1187
1204
  }
1188
1205
  const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
1189
- const conversationId = turn.conversationId ?? msg.chatId;
1206
+ const conversationId = turn.conversationId ?? conversationKeyFor(msg.chatId, msg.endpointId);
1190
1207
  if (turn.intercept && await turn.intercept(sender)) {
1191
1208
  return { action: "intercepted" };
1192
1209
  }
1193
- const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
1210
+ const personaLayer = await resolvePersonaLayer(config, {
1211
+ sender,
1212
+ text: msg.text,
1213
+ endpointId: msg.endpointId
1214
+ });
1194
1215
  const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
1195
1216
  const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
1196
1217
  const priorTurns = config.history && historyEnabled ? await config.history.store.load(
@@ -1223,7 +1244,8 @@ function createInboundPipeline(deps, config) {
1223
1244
  mode,
1224
1245
  sender,
1225
1246
  conversationId,
1226
- model: config.model
1247
+ model: config.model,
1248
+ refusal: config.refusal
1227
1249
  });
1228
1250
  const content = await applyTransformReply(
1229
1251
  config.transformReply,
@@ -1310,7 +1332,7 @@ function stripOwnMentions(text, ownIds) {
1310
1332
  return cleaned ? cleaned : text;
1311
1333
  }
1312
1334
  function resolveWaMessage(event, registry) {
1313
- const { sessionId, sock, message } = event;
1335
+ const { sessionId, sock, message, endpointId } = event;
1314
1336
  const chatId = message.key.remoteJid ?? "";
1315
1337
  const rawText = extractText(message);
1316
1338
  const context = messageContext(message);
@@ -1334,7 +1356,13 @@ function resolveWaMessage(event, registry) {
1334
1356
  // targets. Exposed generically so a caller reaching `msg` through
1335
1357
  // `createInboundPipeline` (not just the WhatsApp-specific router) can
1336
1358
  // still react to it via `ChannelSenderRegistry.sendReaction`.
1337
- messageRef: message.key
1359
+ messageRef: message.key,
1360
+ // The session id this channel instance was configured with, not a wire
1361
+ // identity - stable across the number's own SIM or token changing.
1362
+ // Unset for a default single-session channel (see
1363
+ // WhatsAppMessageEvent.endpointId) so a host that hasn't opted into
1364
+ // multiple endpoints sees no change.
1365
+ endpointId
1338
1366
  };
1339
1367
  return { msg, ownIds };
1340
1368
  }
@@ -1351,7 +1379,7 @@ function createWhatsAppInboundHandler(config) {
1351
1379
  ...resolveBrainHooks(config, config.serverConfig),
1352
1380
  model: config.model,
1353
1381
  channelHint: config.channelHint ?? "Channel: WhatsApp.",
1354
- personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
1382
+ personaResolver: personaResolver ? ({ sender, text, endpointId }) => personaResolver({ senderPhone: sender, text, endpointId }) : void 0,
1355
1383
  history: config.history,
1356
1384
  allowedChats,
1357
1385
  muteRegex: config.muteRegex,
@@ -1388,7 +1416,6 @@ function createWhatsAppInboundHandler(config) {
1388
1416
  };
1389
1417
  await pipeline(msg, {
1390
1418
  reply,
1391
- conversationId: chatId,
1392
1419
  sender: () => senderPhoneFor(sock, message, chatId),
1393
1420
  intercept: images ? (senderPhone) => images({
1394
1421
  sock,
@@ -410,6 +410,7 @@ function createWhatsAppChannel(config) {
410
410
  assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
411
411
  const channelName = config.name ?? "whatsapp";
412
412
  const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
413
+ const multiEndpoint = config.name !== void 0 || sessionIds.length > 1;
413
414
  const now = config.now ?? Date.now;
414
415
  const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
415
416
  const staleMs = config.staleMs ?? LEASE_STALE_MS;
@@ -603,7 +604,12 @@ function createWhatsAppChannel(config) {
603
604
  if (type !== "notify") return;
604
605
  for (const message of messages) {
605
606
  try {
606
- await config.onMessage?.({ sessionId, sock, message });
607
+ await config.onMessage?.({
608
+ sessionId,
609
+ sock,
610
+ message,
611
+ endpointId: multiEndpoint ? sessionId : void 0
612
+ });
607
613
  } catch (error) {
608
614
  log.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
609
615
  }
@@ -801,9 +807,10 @@ function detectLeakage(text) {
801
807
  function scrubOutput(text) {
802
808
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
803
809
  }
804
- function guardOutput(text) {
810
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
811
+ function guardOutput(text, refusal) {
805
812
  if (detectLeakage(text)) {
806
- return "Sorry, I can't share internal instructions. How else can I help?";
813
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
807
814
  }
808
815
  return scrubOutput(text);
809
816
  }
@@ -815,7 +822,8 @@ async function completeOnce({
815
822
  system,
816
823
  messages,
817
824
  temperature = 0.2,
818
- model = DEFAULT_MODEL
825
+ model = DEFAULT_MODEL,
826
+ refusal
819
827
  }) {
820
828
  const res = await client.chat.completions.create({
821
829
  model,
@@ -824,19 +832,19 @@ async function completeOnce({
824
832
  });
825
833
  const text = res.choices[0]?.message?.content ?? "";
826
834
  return {
827
- content: guardOutput(text),
835
+ content: guardOutput(text, refusal),
828
836
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
829
837
  };
830
838
  }
831
839
 
832
840
  // src/core/answer.ts
833
841
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
834
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
842
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
835
843
  const result = await answerFn({ system, messages, mode, sender, conversationId });
836
844
  const raw = typeof result === "string" ? result : result?.content;
837
845
  const usage = typeof result === "string" ? void 0 : result?.usage;
838
846
  return {
839
- content: guardOutput(typeof raw === "string" ? raw : ""),
847
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
840
848
  usage: usage ?? noUsage()
841
849
  };
842
850
  }
@@ -849,12 +857,13 @@ async function answerOnce({
849
857
  sender,
850
858
  conversationId,
851
859
  temperature,
852
- model
860
+ model,
861
+ refusal
853
862
  }) {
854
863
  if (answerFn) {
855
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
864
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
856
865
  }
857
- return completeOnce({ client, system, messages, temperature, model });
866
+ return completeOnce({ client, system, messages, temperature, model, refusal });
858
867
  }
859
868
  async function applyTransformReply(transformReply, input, logger) {
860
869
  if (!transformReply) return input.text;
@@ -1060,9 +1069,17 @@ function resolveBrainHooks(config, fallback) {
1060
1069
  rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
1061
1070
  rerankContext: config.rerankContext ?? fallback?.rerankContext,
1062
1071
  fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
1063
- transformReply: config.transformReply ?? fallback?.transformReply
1072
+ transformReply: config.transformReply ?? fallback?.transformReply,
1073
+ refusal: config.refusal ?? fallback?.refusal
1064
1074
  };
1065
1075
  }
1076
+ function escapeKeyPart(part) {
1077
+ return part.replaceAll("%", "%25").replaceAll("#", "%23");
1078
+ }
1079
+ function conversationKeyFor(chatId, endpointId) {
1080
+ if (!endpointId) return chatId;
1081
+ return `${escapeKeyPart(chatId)}#${escapeKeyPart(endpointId)}`;
1082
+ }
1066
1083
  var DEFAULT_HISTORY_LIMIT = 20;
1067
1084
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
1068
1085
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -1121,11 +1138,15 @@ function createInboundPipeline(deps, config) {
1121
1138
  return { action: "rate-limited" };
1122
1139
  }
1123
1140
  const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
1124
- const conversationId = turn.conversationId ?? msg.chatId;
1141
+ const conversationId = turn.conversationId ?? conversationKeyFor(msg.chatId, msg.endpointId);
1125
1142
  if (turn.intercept && await turn.intercept(sender)) {
1126
1143
  return { action: "intercepted" };
1127
1144
  }
1128
- const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
1145
+ const personaLayer = await resolvePersonaLayer(config, {
1146
+ sender,
1147
+ text: msg.text,
1148
+ endpointId: msg.endpointId
1149
+ });
1129
1150
  const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
1130
1151
  const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
1131
1152
  const priorTurns = config.history && historyEnabled ? await config.history.store.load(
@@ -1158,7 +1179,8 @@ function createInboundPipeline(deps, config) {
1158
1179
  mode,
1159
1180
  sender,
1160
1181
  conversationId,
1161
- model: config.model
1182
+ model: config.model,
1183
+ refusal: config.refusal
1162
1184
  });
1163
1185
  const content = await applyTransformReply(
1164
1186
  config.transformReply,
@@ -1245,7 +1267,7 @@ function stripOwnMentions(text, ownIds) {
1245
1267
  return cleaned ? cleaned : text;
1246
1268
  }
1247
1269
  function resolveWaMessage(event, registry) {
1248
- const { sessionId, sock, message } = event;
1270
+ const { sessionId, sock, message, endpointId } = event;
1249
1271
  const chatId = message.key.remoteJid ?? "";
1250
1272
  const rawText = extractText(message);
1251
1273
  const context = messageContext(message);
@@ -1269,7 +1291,13 @@ function resolveWaMessage(event, registry) {
1269
1291
  // targets. Exposed generically so a caller reaching `msg` through
1270
1292
  // `createInboundPipeline` (not just the WhatsApp-specific router) can
1271
1293
  // still react to it via `ChannelSenderRegistry.sendReaction`.
1272
- messageRef: message.key
1294
+ messageRef: message.key,
1295
+ // The session id this channel instance was configured with, not a wire
1296
+ // identity - stable across the number's own SIM or token changing.
1297
+ // Unset for a default single-session channel (see
1298
+ // WhatsAppMessageEvent.endpointId) so a host that hasn't opted into
1299
+ // multiple endpoints sees no change.
1300
+ endpointId
1273
1301
  };
1274
1302
  return { msg, ownIds };
1275
1303
  }
@@ -1286,7 +1314,7 @@ function createWhatsAppInboundHandler(config) {
1286
1314
  ...resolveBrainHooks(config, config.serverConfig),
1287
1315
  model: config.model,
1288
1316
  channelHint: config.channelHint ?? "Channel: WhatsApp.",
1289
- personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
1317
+ personaResolver: personaResolver ? ({ sender, text, endpointId }) => personaResolver({ senderPhone: sender, text, endpointId }) : void 0,
1290
1318
  history: config.history,
1291
1319
  allowedChats,
1292
1320
  muteRegex: config.muteRegex,
@@ -1323,7 +1351,6 @@ function createWhatsAppInboundHandler(config) {
1323
1351
  };
1324
1352
  await pipeline(msg, {
1325
1353
  reply,
1326
- conversationId: chatId,
1327
1354
  sender: () => senderPhoneFor(sock, message, chatId),
1328
1355
  intercept: images ? (senderPhone) => images({
1329
1356
  sock,
@@ -59,11 +59,18 @@ export interface AnswerOptions {
59
59
  conversationId?: string;
60
60
  temperature?: number;
61
61
  model?: string;
62
+ /**
63
+ * Wording for the guardrail refusal, for a surface that has already
64
+ * resolved whose voice this answer speaks in. Falls back to the
65
+ * server/channel-level `BrainHooks.refusal` the call site passes, and then
66
+ * to `DEFAULT_REFUSAL`. Detection and scrubbing stay Chatter's either way.
67
+ */
68
+ refusal?: string;
62
69
  }
63
70
  /**
64
71
  * Produce one complete answer, from `answerFn` when configured.
65
72
  */
66
- export declare function answerOnce({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, }: AnswerOptions): Promise<{
73
+ export declare function answerOnce({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, refusal, }: AnswerOptions): Promise<{
67
74
  content: string;
68
75
  usage: AnswerUsage;
69
76
  }>;
@@ -114,5 +121,5 @@ export declare function applyTransformReply(transformReply: TransformReply | und
114
121
  * built-in stream only scrubs each delta, since leakage detection needs text
115
122
  * no streamed chunk has yet.
116
123
  */
117
- export declare function answerStream({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, }: AnswerOptions): AsyncGenerator<string>;
124
+ export declare function answerStream({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, refusal, }: AnswerOptions): AsyncGenerator<string>;
118
125
  //# sourceMappingURL=answer.d.ts.map
@@ -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,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;;;;;;;;;;;;GAYG;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,CAYxB;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;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAsBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,EACL,OAAO,GACR,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;;;;;;;;;;;;GAYG;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,CAYxB;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,EACL,OAAO,GACR,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAWxC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Section-aware Markdown chunking.
3
+ *
4
+ * A pure line scanner (no Markdown parser dependency) that splits a document at
5
+ * every ATX heading, records the heading trail each chunk sat under, and keeps
6
+ * fenced code blocks, lists and tables whole. A section longer than the cap is
7
+ * split on blank-line paragraph boundaries; consecutive small sections are
8
+ * never merged.
9
+ */
10
+ /** One chunk of a document, in reading order. */
11
+ export type SectionChunk = {
12
+ /** The chunk's text. A section's first chunk starts with its heading line. */
13
+ text: string;
14
+ /** Heading trail from the H1 down; empty for text before the first heading. */
15
+ section: string[];
16
+ /** Zero-based index of the chunk within the document. */
17
+ position: number;
18
+ };
19
+ /**
20
+ * Split Markdown `text` into structure-aware chunks of at most `max` characters
21
+ * where the structure allows it.
22
+ *
23
+ * Every ATX heading starts a new chunk, with the heading line kept at its top.
24
+ * A section longer than `max` is split on paragraph boundaries, each piece
25
+ * keeping the section's trail. A fenced code block, list or table is atomic and
26
+ * is emitted whole even if it alone exceeds `max`. Empty chunks are never
27
+ * produced, and every non-blank input line appears in exactly one chunk, in
28
+ * order.
29
+ */
30
+ export declare function chunkSections(text: string, max?: number): SectionChunk[];
31
+ //# sourceMappingURL=chunking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.d.ts","sourceRoot":"","sources":["../../src/core/chunking.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG;IACzB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AA+GF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,YAAY,EAAE,CA6BrE"}
@@ -1,11 +1,21 @@
1
1
  export declare function detectLeakage(text: string): boolean;
2
2
  export declare function scrubOutput(text: string): string;
3
+ /**
4
+ * The refusal sent when leaked instructions are detected and no host has
5
+ * supplied its own wording.
6
+ */
7
+ export declare const DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
3
8
  /**
4
9
  * Apply the full output guardrails to a complete answer: refuse leaked
5
10
  * instructions outright, then scrub credentials from whatever remains.
6
11
  *
7
12
  * Used for every non-streamed answer, whoever produced it — the built-in
8
13
  * completion or a caller-supplied brain (`answerFn`).
14
+ *
15
+ * `refusal` only replaces the wording of the refusal. Detection and scrubbing
16
+ * are not configurable, so a host can voice the guard but never weaken it; a
17
+ * blank or non-string `refusal` falls back to `DEFAULT_REFUSAL` rather than
18
+ * letting a host silence the refusal by supplying an empty line.
9
19
  */
10
- export declare function guardOutput(text: string): string;
20
+ export declare function guardOutput(text: string, refusal?: string): string;
11
21
  //# sourceMappingURL=guardrails.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/core/guardrails.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASnD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD"}
1
+ {"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/core/guardrails.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASnD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,qEAAqE,CAAC;AAElG;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKlE"}
@@ -1,6 +1,6 @@
1
1
  import type OpenAI from "openai";
2
2
  export declare const DEFAULT_MODEL = "gpt-4o";
3
- export declare function completeOnce({ client, system, messages, temperature, model, }: {
3
+ export declare function completeOnce({ client, system, messages, temperature, model, refusal, }: {
4
4
  client: OpenAI;
5
5
  system: string;
6
6
  messages: {
@@ -9,6 +9,8 @@ export declare function completeOnce({ client, system, messages, temperature, mo
9
9
  }[];
10
10
  temperature?: number;
11
11
  model?: string;
12
+ /** Wording for the guardrail refusal; falls back to `DEFAULT_REFUSAL`. */
13
+ refusal?: string;
12
14
  }): Promise<{
13
15
  content: string;
14
16
  usage: OpenAI.CompletionUsage;
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/core/llm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAiB,EACjB,KAAqB,GACtB,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;GAWA;AAGD,wBAAuB,cAAc,CAAC,EACpC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAiB,EACjB,KAAqB,GACtB,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,yCAYA"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/core/llm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAiB,EACjB,KAAqB,EACrB,OAAO,GACR,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;GAWA;AAGD,wBAAuB,cAAc,CAAC,EACpC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAiB,EACjB,KAAqB,GACtB,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,yCAYA"}
@@ -16,6 +16,13 @@ export declare const DEFAULT_KNOWLEDGE_DIR = "./config/knowledge";
16
16
  export interface Retriever {
17
17
  /** Retrieve up to `k` chunks across `allowedBuckets` for `query`, most relevant first. */
18
18
  query(query: string, k: number, allowedBuckets: string[]): Promise<string[]>;
19
+ /**
20
+ * Optional record-returning variant of `query`, for a host that needs to
21
+ * know where a passage came from (a search route, citations). `prepareChat`
22
+ * never calls it; implement it only if your own code wants provenance.
23
+ * Same scoping and ordering rules as `query`.
24
+ */
25
+ queryChunks?(query: string, k: number, allowedBuckets: string[]): Promise<RetrievedChunk[]>;
19
26
  /**
20
27
  * Optional one-time ingest/warm-up step, run once at server startup before
21
28
  * the store answers any query. Omit it for a retriever that is always
@@ -23,6 +30,18 @@ export interface Retriever {
23
30
  */
24
31
  build?(): Promise<void>;
25
32
  }
33
+ /** One retrieval result with its provenance. See {@link Retriever.queryChunks}. */
34
+ export interface RetrievedChunk {
35
+ /** The chunk text alone, without any embedding context line. */
36
+ text: string;
37
+ bucket: string;
38
+ /** Where the chunk came from; knowledgeDir-relative for rows ingested in `'sections'` mode. */
39
+ source: string;
40
+ /** Heading trail from the H1 down; empty when the row has none (every `'lines'` row). */
41
+ section: string[];
42
+ /** Cosine similarity between the query and the chunk embedding. */
43
+ score: number;
44
+ }
26
45
  /**
27
46
  * Embeds a batch of texts into vectors, in input order. Lets
28
47
  * {@link VectorStore} stay decoupled from any specific embeddings provider -
@@ -52,6 +71,8 @@ export declare function openLibsqlClient(database: {
52
71
  url: string;
53
72
  authToken: string;
54
73
  }): Promise<LibsqlClient>;
74
+ /** Chunking strategy for {@link VectorStoreOptions.chunking}. */
75
+ export type ChunkingMode = "lines" | "sections";
55
76
  /**
56
77
  * `VectorStore` always takes an already-open libsql client rather than
57
78
  * credentials to open its own - the same rule every other store in this
@@ -68,6 +89,15 @@ export interface VectorStoreOptions {
68
89
  databaseClient: LibsqlClient;
69
90
  /** Directory of markdown knowledge files. Default: `./config/knowledge` */
70
91
  knowledgeDir?: string;
92
+ /**
93
+ * How knowledge files are cut into chunks. `'lines'` (default) keeps the
94
+ * original fixed-size line chunks, so ids and embeddings are unchanged.
95
+ * `'sections'` cuts at Markdown headings (see
96
+ * [chunking.ts](./chunking.ts)), records each chunk's heading trail and
97
+ * position, and embeds it with a `<source> > <heading trail>` context line.
98
+ * Switching modes re-embeds every chunk once on the next build.
99
+ */
100
+ chunking?: ChunkingMode;
71
101
  /** Logger for build progress. Default: a console logger writing to stderr. */
72
102
  logger?: Logger;
73
103
  /**
@@ -92,6 +122,7 @@ export declare class VectorStore implements Retriever {
92
122
  */
93
123
  readonly db: LibsqlClient;
94
124
  private knowledgeDir;
125
+ private chunking;
95
126
  private logger;
96
127
  private buildLock;
97
128
  private instanceId;
@@ -113,6 +144,12 @@ export declare class VectorStore implements Retriever {
113
144
  private renewBuildLock;
114
145
  /** The ingest itself. Only ever runs with the build lock held. */
115
146
  private buildUnderLock;
147
+ /**
148
+ * Adds the nullable `section`/`position` columns to a `chunks` table that
149
+ * every released version created without them. Checked via table_info so it
150
+ * is a no-op once present; only `'sections'` mode ever calls it.
151
+ */
152
+ private ensureSectionColumns;
116
153
  private cleanupStaleChunks;
117
154
  private static cosine;
118
155
  /**
@@ -126,6 +163,8 @@ export declare class VectorStore implements Retriever {
126
163
  * interpolated. An empty list retrieves nothing, and short-circuits before
127
164
  * the embedding call.
128
165
  */
166
+ queryChunks(q: string, k?: number, allowed?: string[]): Promise<RetrievedChunk[]>;
167
+ /** Same results as {@link queryChunks}, as bare text. */
129
168
  query(q: string, k?: number, allowed?: string[]): Promise<string[]>;
130
169
  }
131
170
  //# sourceMappingURL=retrieval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI5D,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,0FAA0F;IAC1F,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E;;;;OAIG;IACH,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEhE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAK7D;AAED,2KAA2K;AAC3K,eAAO,MAAM,gCAAgC,QAG4B,CAAC;AAE1E,0MAA0M;AAC1M,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAO5D;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKxB;AAuBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,cAAc,EAAE,YAAY,CAAC;IAC7B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2HAA2H;IAC3H,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,WAAY,YAAW,SAAS;IAczC,OAAO,CAAC,KAAK;IAbf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,gBAAgB,CAAS;IAEjC,YACU,KAAK,EAAE,QAAQ,EACvB,OAAO,EAAE,kBAAkB,EAQ5B;IAED;;;;;;;;;;OAUG;IACG,KAAK,kBAyBV;IAED,0FAA0F;IAC1F,OAAO,CAAC,cAAc;IAStB,kEAAkE;YACpD,cAAc;YA8Gd,kBAAkB;IAqChC,OAAO,CAAC,MAAM,CAAC,MAAM;IAYrB;;;;;;;;;;OAUG;IACG,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,SAAI,EAAE,OAAO,GAAE,MAAM,EAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAuB7E;CACF"}
1
+ {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"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"}
@@ -41,9 +41,10 @@ function detectLeakage(text) {
41
41
  function scrubOutput(text) {
42
42
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
43
43
  }
44
- function guardOutput(text) {
44
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
45
+ function guardOutput(text, refusal) {
45
46
  if (detectLeakage(text)) {
46
- return "Sorry, I can't share internal instructions. How else can I help?";
47
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
47
48
  }
48
49
  return scrubOutput(text);
49
50
  }
@@ -55,7 +56,8 @@ async function completeOnce({
55
56
  system,
56
57
  messages,
57
58
  temperature = 0.2,
58
- model = DEFAULT_MODEL
59
+ model = DEFAULT_MODEL,
60
+ refusal
59
61
  }) {
60
62
  const res = await client.chat.completions.create({
61
63
  model,
@@ -64,19 +66,19 @@ async function completeOnce({
64
66
  });
65
67
  const text = res.choices[0]?.message?.content ?? "";
66
68
  return {
67
- content: guardOutput(text),
69
+ content: guardOutput(text, refusal),
68
70
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
69
71
  };
70
72
  }
71
73
 
72
74
  // src/core/answer.ts
73
75
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
74
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
76
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
75
77
  const result = await answerFn({ system, messages, mode, sender, conversationId });
76
78
  const raw = typeof result === "string" ? result : result?.content;
77
79
  const usage = typeof result === "string" ? void 0 : result?.usage;
78
80
  return {
79
- content: guardOutput(typeof raw === "string" ? raw : ""),
81
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
80
82
  usage: usage ?? noUsage()
81
83
  };
82
84
  }
@@ -89,12 +91,13 @@ async function answerOnce({
89
91
  sender,
90
92
  conversationId,
91
93
  temperature,
92
- model
94
+ model,
95
+ refusal
93
96
  }) {
94
97
  if (answerFn) {
95
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
98
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
96
99
  }
97
- return completeOnce({ client, system, messages, temperature, model });
100
+ return completeOnce({ client, system, messages, temperature, model, refusal });
98
101
  }
99
102
 
100
103
  // src/history/compaction.ts