@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.
- package/README.md +3 -1
- package/dist/channels/gates.d.ts +21 -2
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +5 -3
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +31 -13
- package/dist/channels/index.mjs +30 -13
- package/dist/channels/matrix/channel.d.ts +2 -0
- package/dist/channels/matrix/channel.d.ts.map +1 -1
- package/dist/channels/matrix/handler.d.ts +9 -0
- package/dist/channels/matrix/handler.d.ts.map +1 -1
- package/dist/channels/matrix/index.d.ts +1 -1
- package/dist/channels/matrix/index.d.ts.map +1 -1
- package/dist/channels/matrix/index.js +75 -20
- package/dist/channels/matrix/index.mjs +74 -20
- package/dist/channels/matrix/updates.d.ts +8 -2
- package/dist/channels/matrix/updates.d.ts.map +1 -1
- package/dist/channels/pipeline.d.ts +34 -4
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/polling.d.ts +11 -0
- package/dist/channels/polling.d.ts.map +1 -1
- package/dist/channels/telegram/channel.d.ts +2 -0
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/handler.d.ts +9 -0
- package/dist/channels/telegram/handler.d.ts.map +1 -1
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/index.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +73 -24
- package/dist/channels/telegram/index.mjs +72 -24
- package/dist/channels/telegram/updates.d.ts +8 -2
- package/dist/channels/telegram/updates.d.ts.map +1 -1
- package/dist/channels/telegram/webhook.d.ts +10 -0
- package/dist/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +8 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +2 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +45 -18
- package/dist/channels/whatsapp/index.mjs +45 -18
- package/dist/core/answer.d.ts +9 -2
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/chunking.d.ts +31 -0
- package/dist/core/chunking.d.ts.map +1 -0
- package/dist/core/guardrails.d.ts +11 -1
- package/dist/core/guardrails.d.ts.map +1 -1
- package/dist/core/llm.d.ts +3 -1
- package/dist/core/llm.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +39 -0
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/history/index.js +12 -9
- package/dist/history/index.mjs +12 -9
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +261 -52
- package/dist/index.mjs +254 -47
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +217 -30
- package/dist/mcp-server.mjs +216 -29
- package/dist/personas/index.d.ts +1 -1
- package/dist/personas/index.d.ts.map +1 -1
- package/dist/personas/index.js +21 -7
- package/dist/personas/index.mjs +21 -7
- package/dist/personas/resolver.d.ts +35 -8
- package/dist/personas/resolver.d.ts.map +1 -1
- package/dist/personas/timeContext.d.ts.map +1 -1
- package/dist/routes/demo.d.ts.map +1 -1
- package/dist/routes/openai.d.ts.map +1 -1
- package/dist/routes/private.d.ts.map +1 -1
- package/dist/routes/public.d.ts.map +1 -1
- package/dist/scheduler/index.js +12 -9
- package/dist/scheduler/index.mjs +12 -9
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +251 -50
- package/dist/server.mjs +246 -45
- package/dist/types.d.ts +39 -1
- package/dist/types.d.ts.map +1 -1
- 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?.({
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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?.({
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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,
|
package/dist/core/answer.d.ts
CHANGED
|
@@ -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;
|
|
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
|
|
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"}
|
package/dist/core/llm.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/llm.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/core/retrieval.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"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"}
|
package/dist/history/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|