@diegoaltoworks/chatter 1.0.0 → 2.0.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/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/history/index.js +12 -9
- package/dist/history/index.mjs +12 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -27
- package/dist/index.mjs +49 -27
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +16 -11
- package/dist/mcp-server.mjs +16 -11
- package/dist/personas/index.d.ts +1 -1
- package/dist/personas/index.d.ts.map +1 -1
- package/dist/personas/index.js +17 -5
- package/dist/personas/index.mjs +17 -5
- package/dist/personas/resolver.d.ts +35 -8
- package/dist/personas/resolver.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 +44 -25
- package/dist/server.mjs +44 -25
- package/dist/types.d.ts +32 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -116,11 +116,13 @@ Complete guides for setup, deployment, and integration - see
|
|
|
116
116
|
[0001, brain and sockets split](./docs/decisions/0001-brain-and-sockets-split.md),
|
|
117
117
|
[0002, no LangChain in core](./docs/decisions/0002-no-langchain-in-core.md),
|
|
118
118
|
[0003, slot-filling is brain territory](./docs/decisions/0003-slot-filling-is-brain-territory.md),
|
|
119
|
-
[0004, main-protection keeps only non-fast-forward and deletion protections](./docs/decisions/0004-main-protection-stays-non-fast-forward-only.md)
|
|
119
|
+
[0004, main-protection keeps only non-fast-forward and deletion protections](./docs/decisions/0004-main-protection-stays-non-fast-forward-only.md),
|
|
120
|
+
[0005, chatter ships no content](./docs/decisions/0005-ships-no-content.md)
|
|
120
121
|
- **Patterns** - Worked "how do I add X" guides:
|
|
121
122
|
[adding a capability](./docs/patterns/adding-a-capability.md),
|
|
122
123
|
[adding a store](./docs/patterns/adding-a-store.md),
|
|
123
124
|
[adding a retriever](./docs/patterns/adding-a-retriever.md),
|
|
125
|
+
[extending chatter](./docs/patterns/extending-chatter.md),
|
|
124
126
|
[exemplars](./docs/patterns/exemplars.md)
|
|
125
127
|
|
|
126
128
|
### Archive
|
package/dist/channels/gates.d.ts
CHANGED
|
@@ -29,6 +29,17 @@ export interface ChannelMessage {
|
|
|
29
29
|
* messages a transport can't target individually) simply have none.
|
|
30
30
|
*/
|
|
31
31
|
messageRef?: unknown;
|
|
32
|
+
/**
|
|
33
|
+
* Which of the bot's own endpoints received this message - the
|
|
34
|
+
* host-chosen key it was configured with (a WhatsApp `sessionId`, a
|
|
35
|
+
* Telegram/Matrix channel `name`), NOT a wire identity. It is stable
|
|
36
|
+
* across a token or SIM change and is already the key of
|
|
37
|
+
* {@link SessionIdentityRegistry}, so a caller binding behaviour to "which
|
|
38
|
+
* endpoint was reached" and the loop guard share one map rather than
|
|
39
|
+
* running two parallel mechanisms. A transport hosting only one endpoint,
|
|
40
|
+
* or one that hasn't chosen to populate it, leaves it unset.
|
|
41
|
+
*/
|
|
42
|
+
endpointId?: string;
|
|
32
43
|
}
|
|
33
44
|
export type ChannelAction = "reply" | "ignore" | "mute" | "unmute";
|
|
34
45
|
export interface ChannelGateConfig {
|
|
@@ -38,8 +49,9 @@ export interface ChannelGateConfig {
|
|
|
38
49
|
mutedChats: Set<string>;
|
|
39
50
|
/**
|
|
40
51
|
* Caller-supplied trigger for "go quiet in this group chat". No default:
|
|
41
|
-
* this module ships no
|
|
42
|
-
* are inert unless a host configures a pattern. Never
|
|
52
|
+
* this module ships no content (see docs/decisions/0005-ships-no-content.md),
|
|
53
|
+
* so mute/unmute are inert unless a host configures a pattern. Never
|
|
54
|
+
* applied to DMs.
|
|
43
55
|
*/
|
|
44
56
|
muteRegex?: RegExp;
|
|
45
57
|
unmuteRegex?: RegExp;
|
|
@@ -115,6 +127,13 @@ export declare function underReplyRateLimit(msg: Pick<ChannelMessage, "chatId" |
|
|
|
115
127
|
* session's own identity is still "us" while it reconnects, and losing that
|
|
116
128
|
* during exactly the disconnect/reconnect window this guard exists for
|
|
117
129
|
* would defeat it.
|
|
130
|
+
*
|
|
131
|
+
* One key space, shared by every endpoint writing into the same registry -
|
|
132
|
+
* `createServer` gives all its channels one (`ServerDependencies.identities`),
|
|
133
|
+
* where the built-ins key by WhatsApp session id and by channel name
|
|
134
|
+
* respectively. Two endpoints choosing the same key overwrite each other's
|
|
135
|
+
* identities silently, so a key must be unique across everything the server
|
|
136
|
+
* runs, not just within one transport.
|
|
118
137
|
*/
|
|
119
138
|
export type SessionIdentityRegistry = Map<string, string[]>;
|
|
120
139
|
/** True when `id` matches an identity of ANY session ever registered, not just the one that received the message. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/channels/gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,eAAe,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,wFAAwF;IACxF,YAAY,EAAE,OAAO,CAAC;IACtB,sHAAsH;IACtH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/channels/gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,eAAe,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,wFAAwF;IACxF,YAAY,EAAE,OAAO,CAAC;IACtB,sHAAsH;IACtH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,iBAAiB;IAChC,gGAAgG;IAChG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kIAAkI;IAClI,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,GAC9C,OAAO,CAQT;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAUjG;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,8BAA8B,CAC5C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,MAAiB;UAcR,MAAM,GAAG,OAAO;;EAiBrC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,iBAAiB,CAAC,EACvD,QAAQ,EAAE;IAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,GAChF,OAAO,CAET;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,uBAAuB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAE5D,qHAAqH;AACrH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAMT;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC,EACjD,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAET"}
|
package/dist/channels/index.d.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* configured channel with the same {@link ServerDependencies} route
|
|
5
5
|
* factories and custom routes receive, so a channel can call
|
|
6
6
|
* `prepareChat`/`answerOnce`, share `deps.db`, etc. without knowing how the
|
|
7
|
-
* server was assembled
|
|
8
|
-
*
|
|
7
|
+
* server was assembled. `ServerDependencies` is `createServer`'s to build -
|
|
8
|
+
* a host embedding chatter into its own server does so through
|
|
9
|
+
* `customRoutes` or by mounting the returned `ChatterApp`, not by calling
|
|
10
|
+
* `channel.start` directly with a hand-assembled `deps`.
|
|
9
11
|
*
|
|
10
12
|
* Published as the `@diegoaltoworks/chatter/channels` subpath alongside the
|
|
11
13
|
* pure decision gates ({@link decideChannelAction} and friends) and the
|
|
@@ -42,7 +44,7 @@ export interface Channel {
|
|
|
42
44
|
stop?(): void | Promise<void>;
|
|
43
45
|
}
|
|
44
46
|
export * from "./gates";
|
|
45
|
-
export { createInboundPipeline, type InboundPipeline, type InboundPipelineConfig, type InboundPipelineDeps, type InboundPipelineOutcome, type InboundReplySender, type InboundTurn, resolveBrainHooks, } from "./pipeline";
|
|
47
|
+
export { conversationKeyFor, createInboundPipeline, type InboundPipeline, type InboundPipelineConfig, type InboundPipelineDeps, type InboundPipelineOutcome, type InboundReplySender, type InboundTurn, resolveBrainHooks, } from "./pipeline";
|
|
46
48
|
export type { ChannelSender, ChannelSenderRegistry } from "./senders";
|
|
47
49
|
export { createSenderRegistry } from "./senders";
|
|
48
50
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,OAAO;IACtB,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD;;;;;;OAMG;IACH,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/channels/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/channels/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
conversationKeyFor: () => conversationKeyFor,
|
|
23
24
|
createInboundPipeline: () => createInboundPipeline,
|
|
24
25
|
createSenderRegistry: () => createSenderRegistry,
|
|
25
26
|
createSlidingWindowRateLimiter: () => createSlidingWindowRateLimiter,
|
|
@@ -106,9 +107,10 @@ function detectLeakage(text) {
|
|
|
106
107
|
function scrubOutput(text) {
|
|
107
108
|
return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
|
|
111
|
+
function guardOutput(text, refusal) {
|
|
110
112
|
if (detectLeakage(text)) {
|
|
111
|
-
return
|
|
113
|
+
return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
|
|
112
114
|
}
|
|
113
115
|
return scrubOutput(text);
|
|
114
116
|
}
|
|
@@ -120,7 +122,8 @@ async function completeOnce({
|
|
|
120
122
|
system,
|
|
121
123
|
messages,
|
|
122
124
|
temperature = 0.2,
|
|
123
|
-
model = DEFAULT_MODEL
|
|
125
|
+
model = DEFAULT_MODEL,
|
|
126
|
+
refusal
|
|
124
127
|
}) {
|
|
125
128
|
const res = await client.chat.completions.create({
|
|
126
129
|
model,
|
|
@@ -129,19 +132,19 @@ async function completeOnce({
|
|
|
129
132
|
});
|
|
130
133
|
const text = res.choices[0]?.message?.content ?? "";
|
|
131
134
|
return {
|
|
132
|
-
content: guardOutput(text),
|
|
135
|
+
content: guardOutput(text, refusal),
|
|
133
136
|
usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
// src/core/answer.ts
|
|
138
141
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
139
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
142
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
|
|
140
143
|
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
141
144
|
const raw = typeof result === "string" ? result : result?.content;
|
|
142
145
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
143
146
|
return {
|
|
144
|
-
content: guardOutput(typeof raw === "string" ? raw : ""),
|
|
147
|
+
content: guardOutput(typeof raw === "string" ? raw : "", refusal),
|
|
145
148
|
usage: usage ?? noUsage()
|
|
146
149
|
};
|
|
147
150
|
}
|
|
@@ -154,12 +157,13 @@ async function answerOnce({
|
|
|
154
157
|
sender,
|
|
155
158
|
conversationId,
|
|
156
159
|
temperature,
|
|
157
|
-
model
|
|
160
|
+
model,
|
|
161
|
+
refusal
|
|
158
162
|
}) {
|
|
159
163
|
if (answerFn) {
|
|
160
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
164
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
|
|
161
165
|
}
|
|
162
|
-
return completeOnce({ client, system, messages, temperature, model });
|
|
166
|
+
return completeOnce({ client, system, messages, temperature, model, refusal });
|
|
163
167
|
}
|
|
164
168
|
async function applyTransformReply(transformReply, input, logger) {
|
|
165
169
|
if (!transformReply) return input.text;
|
|
@@ -365,9 +369,17 @@ function resolveBrainHooks(config, fallback) {
|
|
|
365
369
|
rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
|
|
366
370
|
rerankContext: config.rerankContext ?? fallback?.rerankContext,
|
|
367
371
|
fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
|
|
368
|
-
transformReply: config.transformReply ?? fallback?.transformReply
|
|
372
|
+
transformReply: config.transformReply ?? fallback?.transformReply,
|
|
373
|
+
refusal: config.refusal ?? fallback?.refusal
|
|
369
374
|
};
|
|
370
375
|
}
|
|
376
|
+
function escapeKeyPart(part) {
|
|
377
|
+
return part.replaceAll("%", "%25").replaceAll("#", "%23");
|
|
378
|
+
}
|
|
379
|
+
function conversationKeyFor(chatId, endpointId) {
|
|
380
|
+
if (!endpointId) return chatId;
|
|
381
|
+
return `${escapeKeyPart(chatId)}#${escapeKeyPart(endpointId)}`;
|
|
382
|
+
}
|
|
371
383
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
372
384
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
373
385
|
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
@@ -426,11 +438,15 @@ function createInboundPipeline(deps, config) {
|
|
|
426
438
|
return { action: "rate-limited" };
|
|
427
439
|
}
|
|
428
440
|
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
429
|
-
const conversationId = turn.conversationId ?? msg.chatId;
|
|
441
|
+
const conversationId = turn.conversationId ?? conversationKeyFor(msg.chatId, msg.endpointId);
|
|
430
442
|
if (turn.intercept && await turn.intercept(sender)) {
|
|
431
443
|
return { action: "intercepted" };
|
|
432
444
|
}
|
|
433
|
-
const personaLayer = await resolvePersonaLayer(config, {
|
|
445
|
+
const personaLayer = await resolvePersonaLayer(config, {
|
|
446
|
+
sender,
|
|
447
|
+
text: msg.text,
|
|
448
|
+
endpointId: msg.endpointId
|
|
449
|
+
});
|
|
434
450
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
435
451
|
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
436
452
|
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
@@ -463,7 +479,8 @@ function createInboundPipeline(deps, config) {
|
|
|
463
479
|
mode,
|
|
464
480
|
sender,
|
|
465
481
|
conversationId,
|
|
466
|
-
model: config.model
|
|
482
|
+
model: config.model,
|
|
483
|
+
refusal: config.refusal
|
|
467
484
|
});
|
|
468
485
|
const content = await applyTransformReply(
|
|
469
486
|
config.transformReply,
|
|
@@ -569,6 +586,7 @@ function createSenderRegistry(logger = createConsoleLogger()) {
|
|
|
569
586
|
}
|
|
570
587
|
// Annotate the CommonJS export names for ESM import in node:
|
|
571
588
|
0 && (module.exports = {
|
|
589
|
+
conversationKeyFor,
|
|
572
590
|
createInboundPipeline,
|
|
573
591
|
createSenderRegistry,
|
|
574
592
|
createSlidingWindowRateLimiter,
|
package/dist/channels/index.mjs
CHANGED
|
@@ -72,9 +72,10 @@ function detectLeakage(text) {
|
|
|
72
72
|
function scrubOutput(text) {
|
|
73
73
|
return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
|
|
76
|
+
function guardOutput(text, refusal) {
|
|
76
77
|
if (detectLeakage(text)) {
|
|
77
|
-
return
|
|
78
|
+
return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
|
|
78
79
|
}
|
|
79
80
|
return scrubOutput(text);
|
|
80
81
|
}
|
|
@@ -86,7 +87,8 @@ async function completeOnce({
|
|
|
86
87
|
system,
|
|
87
88
|
messages,
|
|
88
89
|
temperature = 0.2,
|
|
89
|
-
model = DEFAULT_MODEL
|
|
90
|
+
model = DEFAULT_MODEL,
|
|
91
|
+
refusal
|
|
90
92
|
}) {
|
|
91
93
|
const res = await client.chat.completions.create({
|
|
92
94
|
model,
|
|
@@ -95,19 +97,19 @@ async function completeOnce({
|
|
|
95
97
|
});
|
|
96
98
|
const text = res.choices[0]?.message?.content ?? "";
|
|
97
99
|
return {
|
|
98
|
-
content: guardOutput(text),
|
|
100
|
+
content: guardOutput(text, refusal),
|
|
99
101
|
usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// src/core/answer.ts
|
|
104
106
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
105
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
107
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
|
|
106
108
|
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
107
109
|
const raw = typeof result === "string" ? result : result?.content;
|
|
108
110
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
109
111
|
return {
|
|
110
|
-
content: guardOutput(typeof raw === "string" ? raw : ""),
|
|
112
|
+
content: guardOutput(typeof raw === "string" ? raw : "", refusal),
|
|
111
113
|
usage: usage ?? noUsage()
|
|
112
114
|
};
|
|
113
115
|
}
|
|
@@ -120,12 +122,13 @@ async function answerOnce({
|
|
|
120
122
|
sender,
|
|
121
123
|
conversationId,
|
|
122
124
|
temperature,
|
|
123
|
-
model
|
|
125
|
+
model,
|
|
126
|
+
refusal
|
|
124
127
|
}) {
|
|
125
128
|
if (answerFn) {
|
|
126
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
129
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
|
|
127
130
|
}
|
|
128
|
-
return completeOnce({ client, system, messages, temperature, model });
|
|
131
|
+
return completeOnce({ client, system, messages, temperature, model, refusal });
|
|
129
132
|
}
|
|
130
133
|
async function applyTransformReply(transformReply, input, logger) {
|
|
131
134
|
if (!transformReply) return input.text;
|
|
@@ -331,9 +334,17 @@ function resolveBrainHooks(config, fallback) {
|
|
|
331
334
|
rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
|
|
332
335
|
rerankContext: config.rerankContext ?? fallback?.rerankContext,
|
|
333
336
|
fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
|
|
334
|
-
transformReply: config.transformReply ?? fallback?.transformReply
|
|
337
|
+
transformReply: config.transformReply ?? fallback?.transformReply,
|
|
338
|
+
refusal: config.refusal ?? fallback?.refusal
|
|
335
339
|
};
|
|
336
340
|
}
|
|
341
|
+
function escapeKeyPart(part) {
|
|
342
|
+
return part.replaceAll("%", "%25").replaceAll("#", "%23");
|
|
343
|
+
}
|
|
344
|
+
function conversationKeyFor(chatId, endpointId) {
|
|
345
|
+
if (!endpointId) return chatId;
|
|
346
|
+
return `${escapeKeyPart(chatId)}#${escapeKeyPart(endpointId)}`;
|
|
347
|
+
}
|
|
337
348
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
338
349
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
339
350
|
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
@@ -392,11 +403,15 @@ function createInboundPipeline(deps, config) {
|
|
|
392
403
|
return { action: "rate-limited" };
|
|
393
404
|
}
|
|
394
405
|
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
395
|
-
const conversationId = turn.conversationId ?? msg.chatId;
|
|
406
|
+
const conversationId = turn.conversationId ?? conversationKeyFor(msg.chatId, msg.endpointId);
|
|
396
407
|
if (turn.intercept && await turn.intercept(sender)) {
|
|
397
408
|
return { action: "intercepted" };
|
|
398
409
|
}
|
|
399
|
-
const personaLayer = await resolvePersonaLayer(config, {
|
|
410
|
+
const personaLayer = await resolvePersonaLayer(config, {
|
|
411
|
+
sender,
|
|
412
|
+
text: msg.text,
|
|
413
|
+
endpointId: msg.endpointId
|
|
414
|
+
});
|
|
400
415
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
401
416
|
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
402
417
|
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
@@ -429,7 +444,8 @@ function createInboundPipeline(deps, config) {
|
|
|
429
444
|
mode,
|
|
430
445
|
sender,
|
|
431
446
|
conversationId,
|
|
432
|
-
model: config.model
|
|
447
|
+
model: config.model,
|
|
448
|
+
refusal: config.refusal
|
|
433
449
|
});
|
|
434
450
|
const content = await applyTransformReply(
|
|
435
451
|
config.transformReply,
|
|
@@ -534,6 +550,7 @@ function createSenderRegistry(logger = createConsoleLogger()) {
|
|
|
534
550
|
};
|
|
535
551
|
}
|
|
536
552
|
export {
|
|
553
|
+
conversationKeyFor,
|
|
537
554
|
createInboundPipeline,
|
|
538
555
|
createSenderRegistry,
|
|
539
556
|
createSlidingWindowRateLimiter,
|
|
@@ -28,6 +28,8 @@ export interface MatrixChannelConfig extends PollingChannelConfig {
|
|
|
28
28
|
personaResolver?: (ctx: {
|
|
29
29
|
sender: string;
|
|
30
30
|
text: string;
|
|
31
|
+
/** Which of this process's endpoints received the message - see `ChannelMessage.endpointId`. Unset unless the channel runs more than one. */
|
|
32
|
+
endpointId?: string;
|
|
31
33
|
}) => string | undefined | Promise<string | undefined>;
|
|
32
34
|
/** Auto-accept room invites, so the bot can actually receive messages in a room a user just invited it to. @default true */
|
|
33
35
|
autoJoin?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQxD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gIAAgI;IAChI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iHAAiH;IACjH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQxD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gIAAgI;IAChI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iHAAiH;IACjH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,6IAA6I;QAC7I,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kHAAkH;IAClH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wGAAwG;IACxG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0MAA0M;IAC1M,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAyIxE"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* rooms and events at once instead of one Telegram update.
|
|
7
7
|
*/
|
|
8
8
|
import type { Logger } from "../../core/logger";
|
|
9
|
+
import { type SessionIdentityRegistry } from "../gates";
|
|
9
10
|
import type { InboundPipeline } from "../pipeline";
|
|
10
11
|
import type { ChannelSender } from "../senders";
|
|
11
12
|
import type { MatrixApi, MatrixSyncResponse } from "./api";
|
|
@@ -14,6 +15,12 @@ export interface MatrixSyncHandlerDeps {
|
|
|
14
15
|
api: MatrixApi;
|
|
15
16
|
me: MatrixIdentity;
|
|
16
17
|
pipeline: InboundPipeline;
|
|
18
|
+
/**
|
|
19
|
+
* Every identity this process answers to, across all its channels - what
|
|
20
|
+
* `fromBot` is resolved against, so another endpoint of the same process is
|
|
21
|
+
* recognised as "us". Omitted: `me` alone, which cannot see a second bot.
|
|
22
|
+
*/
|
|
23
|
+
identities?: SessionIdentityRegistry;
|
|
17
24
|
/** Group rooms eligible for a reply. Empty = every room. */
|
|
18
25
|
allowedChats: string[];
|
|
19
26
|
/**
|
|
@@ -44,6 +51,8 @@ export interface MatrixSyncHandlerDeps {
|
|
|
44
51
|
logger: Logger;
|
|
45
52
|
/** Log-line prefix, e.g. `Matrix[@bot:example.org]`. */
|
|
46
53
|
label: string;
|
|
54
|
+
/** This channel's configured name - populates `ChannelMessage.endpointId`. Omitted: `endpointId` is left unset. */
|
|
55
|
+
channelName?: string;
|
|
47
56
|
}
|
|
48
57
|
/**
|
|
49
58
|
* One `/sync` response -> zero or more pipeline turns, one per
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAKpB,MAAM,WAAW,CAAC;AAmBnB,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,SAAS,CAAC;IACf,EAAE,EAAE,cAAc,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC,4DAA4D;IAC5D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;OAKG;IACH,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,qBAAqB,GAC1B,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CA8GjD;AAED,kHAAkH;AAClH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,aAAa,CAkB3F"}
|
|
@@ -33,5 +33,5 @@ export { createMatrixApi, DEFAULT_MAX_MEDIA_BYTES, type MatrixAccountDataEvent,
|
|
|
33
33
|
export { createMatrixChannel, type MatrixChannelConfig } from "./channel";
|
|
34
34
|
export { createMatrixSender, createMatrixSyncHandler, type MatrixSyncHandlerDeps, } from "./handler";
|
|
35
35
|
export { retryDelayMs, runMatrixSyncLoop, syncBackoffMs } from "./sync";
|
|
36
|
-
export { directInviteFrom, directMappingFromEvents, directMappingRooms, directRoomIds, isReplyToBot, MAX_TRACKED_SENT_EVENTS, type MatrixDirectMapping, type MatrixIdentity, matrixSenderKey, mentionsBot, messageText, recordSentEventId, toChannelMessage, toDirectMapping, withDirectRoom, } from "./updates";
|
|
36
|
+
export { directInviteFrom, directMappingFromEvents, directMappingRooms, directRoomIds, isReplyToBot, MAX_TRACKED_SENT_EVENTS, type MatrixDirectMapping, type MatrixIdentity, matrixOwnIdentities, matrixSenderKey, mentionsBot, messageText, recordSentEventId, toChannelMessage, toDirectMapping, withDirectRoom, } from "./updates";
|
|
37
37
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,WAAW,GACZ,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,WAAW,GACZ,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,WAAW,CAAC"}
|
|
@@ -32,6 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
directMappingRooms: () => directMappingRooms,
|
|
33
33
|
directRoomIds: () => directRoomIds,
|
|
34
34
|
isReplyToBot: () => isReplyToBot,
|
|
35
|
+
matrixOwnIdentities: () => matrixOwnIdentities,
|
|
35
36
|
matrixSenderKey: () => matrixSenderKey,
|
|
36
37
|
mentionsBot: () => mentionsBot,
|
|
37
38
|
messageText: () => messageText,
|
|
@@ -343,9 +344,10 @@ function detectLeakage(text) {
|
|
|
343
344
|
function scrubOutput(text) {
|
|
344
345
|
return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
|
|
345
346
|
}
|
|
346
|
-
|
|
347
|
+
var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
|
|
348
|
+
function guardOutput(text, refusal) {
|
|
347
349
|
if (detectLeakage(text)) {
|
|
348
|
-
return
|
|
350
|
+
return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
|
|
349
351
|
}
|
|
350
352
|
return scrubOutput(text);
|
|
351
353
|
}
|
|
@@ -357,7 +359,8 @@ async function completeOnce({
|
|
|
357
359
|
system,
|
|
358
360
|
messages,
|
|
359
361
|
temperature = 0.2,
|
|
360
|
-
model = DEFAULT_MODEL
|
|
362
|
+
model = DEFAULT_MODEL,
|
|
363
|
+
refusal
|
|
361
364
|
}) {
|
|
362
365
|
const res = await client.chat.completions.create({
|
|
363
366
|
model,
|
|
@@ -366,19 +369,19 @@ async function completeOnce({
|
|
|
366
369
|
});
|
|
367
370
|
const text = res.choices[0]?.message?.content ?? "";
|
|
368
371
|
return {
|
|
369
|
-
content: guardOutput(text),
|
|
372
|
+
content: guardOutput(text, refusal),
|
|
370
373
|
usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
371
374
|
};
|
|
372
375
|
}
|
|
373
376
|
|
|
374
377
|
// src/core/answer.ts
|
|
375
378
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
376
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
379
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
|
|
377
380
|
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
378
381
|
const raw = typeof result === "string" ? result : result?.content;
|
|
379
382
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
380
383
|
return {
|
|
381
|
-
content: guardOutput(typeof raw === "string" ? raw : ""),
|
|
384
|
+
content: guardOutput(typeof raw === "string" ? raw : "", refusal),
|
|
382
385
|
usage: usage ?? noUsage()
|
|
383
386
|
};
|
|
384
387
|
}
|
|
@@ -391,12 +394,13 @@ async function answerOnce({
|
|
|
391
394
|
sender,
|
|
392
395
|
conversationId,
|
|
393
396
|
temperature,
|
|
394
|
-
model
|
|
397
|
+
model,
|
|
398
|
+
refusal
|
|
395
399
|
}) {
|
|
396
400
|
if (answerFn) {
|
|
397
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
401
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
|
|
398
402
|
}
|
|
399
|
-
return completeOnce({ client, system, messages, temperature, model });
|
|
403
|
+
return completeOnce({ client, system, messages, temperature, model, refusal });
|
|
400
404
|
}
|
|
401
405
|
async function applyTransformReply(transformReply, input, logger) {
|
|
402
406
|
if (!transformReply) return input.text;
|
|
@@ -643,6 +647,16 @@ function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now)
|
|
|
643
647
|
function underReplyRateLimit(msg, limiters) {
|
|
644
648
|
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
645
649
|
}
|
|
650
|
+
function isFromAnySession(id, registry) {
|
|
651
|
+
if (!id) return false;
|
|
652
|
+
for (const ids of registry.values()) {
|
|
653
|
+
if (ids.includes(id)) return true;
|
|
654
|
+
}
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
function isEffectivelyFromSelf(msg, registry) {
|
|
658
|
+
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
659
|
+
}
|
|
646
660
|
|
|
647
661
|
// src/channels/pipeline.ts
|
|
648
662
|
function resolveBrainHooks(config, fallback) {
|
|
@@ -652,9 +666,17 @@ function resolveBrainHooks(config, fallback) {
|
|
|
652
666
|
rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
|
|
653
667
|
rerankContext: config.rerankContext ?? fallback?.rerankContext,
|
|
654
668
|
fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
|
|
655
|
-
transformReply: config.transformReply ?? fallback?.transformReply
|
|
669
|
+
transformReply: config.transformReply ?? fallback?.transformReply,
|
|
670
|
+
refusal: config.refusal ?? fallback?.refusal
|
|
656
671
|
};
|
|
657
672
|
}
|
|
673
|
+
function escapeKeyPart(part) {
|
|
674
|
+
return part.replaceAll("%", "%25").replaceAll("#", "%23");
|
|
675
|
+
}
|
|
676
|
+
function conversationKeyFor(chatId, endpointId) {
|
|
677
|
+
if (!endpointId) return chatId;
|
|
678
|
+
return `${escapeKeyPart(chatId)}#${escapeKeyPart(endpointId)}`;
|
|
679
|
+
}
|
|
658
680
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
659
681
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
660
682
|
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
@@ -713,11 +735,15 @@ function createInboundPipeline(deps, config) {
|
|
|
713
735
|
return { action: "rate-limited" };
|
|
714
736
|
}
|
|
715
737
|
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
716
|
-
const conversationId = turn.conversationId ?? msg.chatId;
|
|
738
|
+
const conversationId = turn.conversationId ?? conversationKeyFor(msg.chatId, msg.endpointId);
|
|
717
739
|
if (turn.intercept && await turn.intercept(sender)) {
|
|
718
740
|
return { action: "intercepted" };
|
|
719
741
|
}
|
|
720
|
-
const personaLayer = await resolvePersonaLayer(config, {
|
|
742
|
+
const personaLayer = await resolvePersonaLayer(config, {
|
|
743
|
+
sender,
|
|
744
|
+
text: msg.text,
|
|
745
|
+
endpointId: msg.endpointId
|
|
746
|
+
});
|
|
721
747
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
722
748
|
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
723
749
|
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
@@ -750,7 +776,8 @@ function createInboundPipeline(deps, config) {
|
|
|
750
776
|
mode,
|
|
751
777
|
sender,
|
|
752
778
|
conversationId,
|
|
753
|
-
model: config.model
|
|
779
|
+
model: config.model,
|
|
780
|
+
refusal: config.refusal
|
|
754
781
|
});
|
|
755
782
|
const content = await applyTransformReply(
|
|
756
783
|
config.transformReply,
|
|
@@ -785,6 +812,7 @@ function defaultSleep(ms) {
|
|
|
785
812
|
}
|
|
786
813
|
|
|
787
814
|
// src/channels/matrix/updates.ts
|
|
815
|
+
var NO_IDENTITIES = /* @__PURE__ */ new Map();
|
|
788
816
|
function messageText(content) {
|
|
789
817
|
return content.body ?? "";
|
|
790
818
|
}
|
|
@@ -852,7 +880,7 @@ function directInviteFrom(room, me) {
|
|
|
852
880
|
}
|
|
853
881
|
return void 0;
|
|
854
882
|
}
|
|
855
|
-
function toChannelMessage(roomId, event, me, directRooms, sentEventIds) {
|
|
883
|
+
function toChannelMessage(roomId, event, me, directRooms, sentEventIds, identities = NO_IDENTITIES, channelName) {
|
|
856
884
|
if (event.type !== "m.room.message") return void 0;
|
|
857
885
|
const content = event.content;
|
|
858
886
|
if (!content.msgtype) return void 0;
|
|
@@ -864,10 +892,22 @@ function toChannelMessage(roomId, event, me, directRooms, sentEventIds) {
|
|
|
864
892
|
isDirectMessage: directRooms.has(roomId),
|
|
865
893
|
mentionsBot: mentionsBot(content, me),
|
|
866
894
|
isReplyToBot: isReplyToBot(content, sentEventIds),
|
|
867
|
-
|
|
868
|
-
|
|
895
|
+
// One access token is one user, but one process is not: a second bot
|
|
896
|
+
// account mounted alongside this one is a stranger to `me` and would be
|
|
897
|
+
// answered, and would answer back, forever. `identities` is what makes
|
|
898
|
+
// the other endpoint's user id "us" too - an empty one degenerates to
|
|
899
|
+
// the equality check.
|
|
900
|
+
fromBot: isEffectivelyFromSelf(
|
|
901
|
+
{ fromBot: event.sender === me.userId, senderId: event.sender },
|
|
902
|
+
identities
|
|
903
|
+
),
|
|
904
|
+
messageRef: event.event_id,
|
|
905
|
+
endpointId: channelName
|
|
869
906
|
};
|
|
870
907
|
}
|
|
908
|
+
function matrixOwnIdentities(me) {
|
|
909
|
+
return [me.userId];
|
|
910
|
+
}
|
|
871
911
|
function matrixSenderKey(userId) {
|
|
872
912
|
return `mx:${userId}`;
|
|
873
913
|
}
|
|
@@ -882,7 +922,7 @@ async function sendText(api, roomId, text, sentEventIds, replyToEventId) {
|
|
|
882
922
|
recordSentEventId(sentEventIds, eventId);
|
|
883
923
|
}
|
|
884
924
|
function createMatrixSyncHandler(deps) {
|
|
885
|
-
const { api, me, pipeline, allowedChats, sentEventIds, logger, label } = deps;
|
|
925
|
+
const { api, me, pipeline, identities, allowedChats, sentEventIds, logger, label, channelName } = deps;
|
|
886
926
|
const autoJoin = deps.autoJoin ?? true;
|
|
887
927
|
const loggedUnallowedChats = /* @__PURE__ */ new Set();
|
|
888
928
|
const loggedDeclinedInvites = /* @__PURE__ */ new Set();
|
|
@@ -927,7 +967,15 @@ function createMatrixSyncHandler(deps) {
|
|
|
927
967
|
}
|
|
928
968
|
for (const [roomId, room] of Object.entries(response.rooms?.join ?? {})) {
|
|
929
969
|
for (const event of room.timeline.events) {
|
|
930
|
-
const msg = toChannelMessage(
|
|
970
|
+
const msg = toChannelMessage(
|
|
971
|
+
roomId,
|
|
972
|
+
event,
|
|
973
|
+
me,
|
|
974
|
+
directRooms,
|
|
975
|
+
sentEventIds,
|
|
976
|
+
identities,
|
|
977
|
+
channelName
|
|
978
|
+
);
|
|
931
979
|
if (!msg) continue;
|
|
932
980
|
if (isBlockedByAllowlist(msg, { allowedChats })) {
|
|
933
981
|
if (!loggedUnallowedChats.has(msg.chatId)) {
|
|
@@ -944,7 +992,6 @@ function createMatrixSyncHandler(deps) {
|
|
|
944
992
|
try {
|
|
945
993
|
await pipeline(msg, {
|
|
946
994
|
reply,
|
|
947
|
-
conversationId: msg.chatId,
|
|
948
995
|
sender: matrixSenderKey(msg.senderId)
|
|
949
996
|
});
|
|
950
997
|
} catch (error) {
|
|
@@ -1040,6 +1087,8 @@ function createMatrixChannel(config) {
|
|
|
1040
1087
|
});
|
|
1041
1088
|
const me = await api.whoami();
|
|
1042
1089
|
const label = `Matrix[${me.userId}]`;
|
|
1090
|
+
const identities = config.identities ?? deps.identities;
|
|
1091
|
+
identities.set(channelName, matrixOwnIdentities(me));
|
|
1043
1092
|
let initialDirect;
|
|
1044
1093
|
try {
|
|
1045
1094
|
const direct = await api.getAccountData(me.userId, "m.direct");
|
|
@@ -1073,13 +1122,18 @@ function createMatrixChannel(config) {
|
|
|
1073
1122
|
const handleSync = createMatrixSyncHandler({
|
|
1074
1123
|
api,
|
|
1075
1124
|
me,
|
|
1125
|
+
identities,
|
|
1076
1126
|
pipeline,
|
|
1077
1127
|
allowedChats,
|
|
1078
1128
|
sentEventIds,
|
|
1079
1129
|
autoJoin: config.autoJoin,
|
|
1080
1130
|
initialDirect,
|
|
1081
1131
|
logger: log,
|
|
1082
|
-
label
|
|
1132
|
+
label,
|
|
1133
|
+
// config.name, not the resolved channelName: endpointId opts in only
|
|
1134
|
+
// when the host explicitly named this channel (running more than
|
|
1135
|
+
// one bot in the process), not for a default single-token channel.
|
|
1136
|
+
channelName: config.name
|
|
1083
1137
|
});
|
|
1084
1138
|
abort = new AbortController();
|
|
1085
1139
|
const signal = abort.signal;
|
|
@@ -1119,6 +1173,7 @@ function createMatrixChannel(config) {
|
|
|
1119
1173
|
directMappingRooms,
|
|
1120
1174
|
directRoomIds,
|
|
1121
1175
|
isReplyToBot,
|
|
1176
|
+
matrixOwnIdentities,
|
|
1122
1177
|
matrixSenderKey,
|
|
1123
1178
|
mentionsBot,
|
|
1124
1179
|
messageText,
|