@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
@@ -116,9 +116,10 @@ function detectLeakage(text) {
116
116
  function scrubOutput(text) {
117
117
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
118
118
  }
119
- function guardOutput(text) {
119
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
120
+ function guardOutput(text, refusal) {
120
121
  if (detectLeakage(text)) {
121
- return "Sorry, I can't share internal instructions. How else can I help?";
122
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
122
123
  }
123
124
  return scrubOutput(text);
124
125
  }
@@ -130,7 +131,8 @@ async function completeOnce({
130
131
  system,
131
132
  messages,
132
133
  temperature = 0.2,
133
- model = DEFAULT_MODEL
134
+ model = DEFAULT_MODEL,
135
+ refusal
134
136
  }) {
135
137
  const res = await client.chat.completions.create({
136
138
  model,
@@ -139,19 +141,19 @@ async function completeOnce({
139
141
  });
140
142
  const text = res.choices[0]?.message?.content ?? "";
141
143
  return {
142
- content: guardOutput(text),
144
+ content: guardOutput(text, refusal),
143
145
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
144
146
  };
145
147
  }
146
148
 
147
149
  // src/core/answer.ts
148
150
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
149
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
151
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
150
152
  const result = await answerFn({ system, messages, mode, sender, conversationId });
151
153
  const raw = typeof result === "string" ? result : result?.content;
152
154
  const usage = typeof result === "string" ? void 0 : result?.usage;
153
155
  return {
154
- content: guardOutput(typeof raw === "string" ? raw : ""),
156
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
155
157
  usage: usage ?? noUsage()
156
158
  };
157
159
  }
@@ -164,12 +166,13 @@ async function answerOnce({
164
166
  sender,
165
167
  conversationId,
166
168
  temperature,
167
- model
169
+ model,
170
+ refusal
168
171
  }) {
169
172
  if (answerFn) {
170
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
173
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
171
174
  }
172
- return completeOnce({ client, system, messages, temperature, model });
175
+ return completeOnce({ client, system, messages, temperature, model, refusal });
173
176
  }
174
177
 
175
178
  // src/core/logger.ts
@@ -75,9 +75,10 @@ function detectLeakage(text) {
75
75
  function scrubOutput(text) {
76
76
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
77
77
  }
78
- function guardOutput(text) {
78
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
79
+ function guardOutput(text, refusal) {
79
80
  if (detectLeakage(text)) {
80
- return "Sorry, I can't share internal instructions. How else can I help?";
81
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
81
82
  }
82
83
  return scrubOutput(text);
83
84
  }
@@ -89,7 +90,8 @@ async function completeOnce({
89
90
  system,
90
91
  messages,
91
92
  temperature = 0.2,
92
- model = DEFAULT_MODEL
93
+ model = DEFAULT_MODEL,
94
+ refusal
93
95
  }) {
94
96
  const res = await client.chat.completions.create({
95
97
  model,
@@ -98,19 +100,19 @@ async function completeOnce({
98
100
  });
99
101
  const text = res.choices[0]?.message?.content ?? "";
100
102
  return {
101
- content: guardOutput(text),
103
+ content: guardOutput(text, refusal),
102
104
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
103
105
  };
104
106
  }
105
107
 
106
108
  // src/core/answer.ts
107
109
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
108
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
110
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
109
111
  const result = await answerFn({ system, messages, mode, sender, conversationId });
110
112
  const raw = typeof result === "string" ? result : result?.content;
111
113
  const usage = typeof result === "string" ? void 0 : result?.usage;
112
114
  return {
113
- content: guardOutput(typeof raw === "string" ? raw : ""),
115
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
114
116
  usage: usage ?? noUsage()
115
117
  };
116
118
  }
@@ -123,12 +125,13 @@ async function answerOnce({
123
125
  sender,
124
126
  conversationId,
125
127
  temperature,
126
- model
128
+ model,
129
+ refusal
127
130
  }) {
128
131
  if (answerFn) {
129
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
132
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
130
133
  }
131
- return completeOnce({ client, system, messages, temperature, model });
134
+ return completeOnce({ client, system, messages, temperature, model, refusal });
132
135
  }
133
136
 
134
137
  // src/core/logger.ts
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAiB5B,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG;IAAE,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAEtE;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CA+P7E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAiB5B,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG;IAAE,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAEtE;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAsQ7E"}
package/dist/server.js CHANGED
@@ -114,6 +114,126 @@ var init_buildLock = __esm({
114
114
  }
115
115
  });
116
116
 
117
+ // src/core/chunking.ts
118
+ function headingOf(line) {
119
+ const match = HEADING.exec(line);
120
+ if (!match) return void 0;
121
+ const title = (match[2] ?? "").replace(/[ \t]+#+$/, "").replace(/^#+$/, "").trim();
122
+ return { level: match[1].length, title };
123
+ }
124
+ function startsTable(lines, i) {
125
+ return lines[i].includes("|") && i + 1 < lines.length && TABLE_DELIMITER.test(lines[i + 1]);
126
+ }
127
+ function fenceEnd(lines, start) {
128
+ const opener = FENCE.exec(lines[start]);
129
+ if (!opener) return start + 1;
130
+ const marker = opener[1];
131
+ const closer = new RegExp(`^ {0,3}${marker[0] === "`" ? "`" : "~"}{${marker.length},}[ \\t]*$`);
132
+ for (let i = start + 1; i < lines.length; i++) if (closer.test(lines[i])) return i + 1;
133
+ return lines.length;
134
+ }
135
+ function listEnd(lines, start) {
136
+ let end = start + 1;
137
+ for (let i = start + 1; i < lines.length; i++) {
138
+ const line = lines[i];
139
+ if (isBlank(line)) {
140
+ let next = i + 1;
141
+ while (next < lines.length && isBlank(lines[next])) next++;
142
+ if (next < lines.length && (LIST_ITEM.test(lines[next]) || isIndented(lines[next]))) {
143
+ i = next - 1;
144
+ continue;
145
+ }
146
+ break;
147
+ }
148
+ if (headingOf(line) && !isIndented(line)) break;
149
+ if (FENCE.test(line) && !isIndented(line)) break;
150
+ end = i + 1;
151
+ }
152
+ return end;
153
+ }
154
+ function tableEnd(lines, start) {
155
+ let end = start + 2;
156
+ while (end < lines.length && !isBlank(lines[end]) && lines[end].includes("|")) end++;
157
+ return end;
158
+ }
159
+ function paragraphEnd(lines, start) {
160
+ let end = start + 1;
161
+ while (end < lines.length && !isBlank(lines[end]) && !headingOf(lines[end]) && !FENCE.test(lines[end]) && !LIST_ITEM.test(lines[end])) {
162
+ end++;
163
+ }
164
+ return end;
165
+ }
166
+ function scan(text) {
167
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
168
+ const sections = [{ trail: [], blocks: [] }];
169
+ const stack = [];
170
+ let i = 0;
171
+ while (i < lines.length) {
172
+ const line = lines[i];
173
+ if (isBlank(line)) {
174
+ i++;
175
+ continue;
176
+ }
177
+ const heading = FENCE.test(line) ? void 0 : headingOf(line);
178
+ if (heading) {
179
+ while (stack.length && stack[stack.length - 1].level >= heading.level) stack.pop();
180
+ stack.push(heading);
181
+ sections.push({ trail: stack.map((h) => h.title), heading: line, blocks: [] });
182
+ i++;
183
+ continue;
184
+ }
185
+ let end;
186
+ if (FENCE.test(line)) end = fenceEnd(lines, i);
187
+ else if (LIST_ITEM.test(line)) end = listEnd(lines, i);
188
+ else if (startsTable(lines, i)) end = tableEnd(lines, i);
189
+ else end = paragraphEnd(lines, i);
190
+ sections[sections.length - 1].blocks.push({ lines: lines.slice(i, end) });
191
+ i = end;
192
+ }
193
+ return sections;
194
+ }
195
+ function chunkSections(text, max = 900) {
196
+ const cap = Math.max(1, max);
197
+ const out = [];
198
+ const emit = (parts, section) => {
199
+ const body = parts.join("\n\n");
200
+ if (body.trim() === "") return;
201
+ out.push({ text: body, section: [...section], position: out.length });
202
+ };
203
+ for (const section of scan(text)) {
204
+ let parts = section.heading ? [section.heading.trim()] : [];
205
+ let size = parts.join("\n\n").length;
206
+ let hasBlock = false;
207
+ for (const block of section.blocks) {
208
+ const piece = block.lines.join("\n");
209
+ const added = piece.length + (parts.length ? 2 : 0);
210
+ if (hasBlock && size + added > cap) {
211
+ emit(parts, section.trail);
212
+ parts = [];
213
+ size = 0;
214
+ hasBlock = false;
215
+ }
216
+ size += piece.length + (parts.length ? 2 : 0);
217
+ parts.push(piece);
218
+ hasBlock = true;
219
+ }
220
+ emit(parts, section.trail);
221
+ }
222
+ return out;
223
+ }
224
+ var FENCE, HEADING, LIST_ITEM, TABLE_DELIMITER, isBlank, isIndented;
225
+ var init_chunking = __esm({
226
+ "src/core/chunking.ts"() {
227
+ "use strict";
228
+ FENCE = /^ {0,3}(`{3,}|~{3,})/;
229
+ HEADING = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
230
+ LIST_ITEM = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/;
231
+ TABLE_DELIMITER = /^[ \t]*\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
232
+ isBlank = (line) => line.trim() === "";
233
+ isIndented = (line) => /^(?: {2,}|\t)/.test(line);
234
+ }
235
+ });
236
+
117
237
  // src/core/loaders.ts
118
238
  function walk(dir) {
119
239
  const items = [];
@@ -194,11 +314,13 @@ async function sha256(input) {
194
314
  const hash = await crypto.subtle.digest("SHA-256", data);
195
315
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
196
316
  }
197
- var EMB_MODEL, DEFAULT_KNOWLEDGE_DIR, DATABASE_CONFIG_REQUIRED_MESSAGE, VectorStore;
317
+ var import_node_path3, EMB_MODEL, DEFAULT_KNOWLEDGE_DIR, DATABASE_CONFIG_REQUIRED_MESSAGE, VectorStore;
198
318
  var init_retrieval = __esm({
199
319
  "src/core/retrieval.ts"() {
200
320
  "use strict";
321
+ import_node_path3 = require("node:path");
201
322
  init_buildLock();
323
+ init_chunking();
202
324
  init_loaders();
203
325
  init_logger();
204
326
  EMB_MODEL = "text-embedding-3-large";
@@ -209,6 +331,7 @@ var init_retrieval = __esm({
209
331
  this.embed = embed;
210
332
  this.db = options.databaseClient;
211
333
  this.knowledgeDir = options.knowledgeDir || DEFAULT_KNOWLEDGE_DIR;
334
+ this.chunking = options.chunking ?? "lines";
212
335
  this.logger = options.logger ?? createConsoleLogger();
213
336
  this.buildLock = options.buildLock ?? createTursoBuildLock(this.db);
214
337
  this.instanceId = options.instanceId ?? crypto.randomUUID();
@@ -222,6 +345,7 @@ var init_retrieval = __esm({
222
345
  */
223
346
  db;
224
347
  knowledgeDir;
348
+ chunking;
225
349
  logger;
226
350
  buildLock;
227
351
  instanceId;
@@ -282,11 +406,41 @@ var init_retrieval = __esm({
282
406
  this.logger.info("No knowledge documents and no existing chunks - nothing to build");
283
407
  return;
284
408
  }
409
+ this.logger.info(`Chunking mode: ${this.chunking}`);
410
+ if (this.chunking === "sections") await this.ensureSectionColumns();
285
411
  const rows = [];
286
412
  for (const d of docs) {
287
- for (const part of chunk(d.text)) {
288
- const id = await sha256(`${d.bucket}|${d.source}|${part}`);
289
- rows.push({ id, bucket: d.bucket, source: d.source, text: part });
413
+ if (this.chunking === "sections") {
414
+ const source = (0, import_node_path3.relative)(this.knowledgeDir, d.source).split(import_node_path3.sep).join("/");
415
+ for (const c of chunkSections(d.text)) {
416
+ const section = c.section.join(" > ");
417
+ const id = await sha256(`${d.bucket}|${source}|${section}|${c.text}`);
418
+ const context = section ? `${source} > ${section}` : source;
419
+ rows.push({
420
+ id,
421
+ bucket: d.bucket,
422
+ source,
423
+ text: c.text,
424
+ embedText: `${context}
425
+
426
+ ${c.text}`,
427
+ section,
428
+ position: c.position
429
+ });
430
+ }
431
+ } else {
432
+ for (const part of chunk(d.text)) {
433
+ const id = await sha256(`${d.bucket}|${d.source}|${part}`);
434
+ rows.push({
435
+ id,
436
+ bucket: d.bucket,
437
+ source: d.source,
438
+ text: part,
439
+ embedText: part,
440
+ section: null,
441
+ position: null
442
+ });
443
+ }
290
444
  }
291
445
  }
292
446
  this.logger.info(`Created ${rows.length} chunks from knowledge documents`);
@@ -295,11 +449,17 @@ var init_retrieval = __esm({
295
449
  for (let i = 0; i < rows.length; i += UPSERT_BATCH) {
296
450
  const batch = rows.slice(i, i + UPSERT_BATCH);
297
451
  await this.db.batch(
298
- batch.map((r) => ({
299
- sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
300
- ON CONFLICT(id) DO NOTHING`,
301
- args: [r.id, r.bucket, r.source, r.text]
302
- })),
452
+ batch.map(
453
+ (r) => this.chunking === "sections" ? {
454
+ sql: `INSERT INTO chunks(id,bucket,source,text,section,position) VALUES(?,?,?,?,?,?)
455
+ ON CONFLICT(id) DO NOTHING`,
456
+ args: [r.id, r.bucket, r.source, r.text, r.section, r.position]
457
+ } : {
458
+ sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
459
+ ON CONFLICT(id) DO NOTHING`,
460
+ args: [r.id, r.bucket, r.source, r.text]
461
+ }
462
+ ),
303
463
  "write"
304
464
  );
305
465
  }
@@ -320,7 +480,7 @@ var init_retrieval = __esm({
320
480
  return;
321
481
  }
322
482
  this.logger.info(`Embedding ${missing.length} new/updated chunks with ${EMB_MODEL}...`);
323
- const textById = new Map(rows.map((r) => [r.id, r.text]));
483
+ const textById = new Map(rows.map((r) => [r.id, r.embedText]));
324
484
  const BATCH = 96;
325
485
  for (let i = 0; i < missing.length; i += BATCH) {
326
486
  const batchIds = missing.slice(i, i + BATCH);
@@ -341,6 +501,18 @@ var init_retrieval = __esm({
341
501
  }
342
502
  this.logger.info(`Successfully embedded ${missing.length} new chunks`);
343
503
  }
504
+ /**
505
+ * Adds the nullable `section`/`position` columns to a `chunks` table that
506
+ * every released version created without them. Checked via table_info so it
507
+ * is a no-op once present; only `'sections'` mode ever calls it.
508
+ */
509
+ async ensureSectionColumns() {
510
+ const info = await this.db.execute("PRAGMA table_info(chunks)");
511
+ const have = new Set(info.rows.map((r) => String(r.name)));
512
+ if (!have.has("section")) await this.db.execute("ALTER TABLE chunks ADD COLUMN section TEXT");
513
+ if (!have.has("position"))
514
+ await this.db.execute("ALTER TABLE chunks ADD COLUMN position INTEGER");
515
+ }
344
516
  // Remove chunks from database that no longer exist in markdown files
345
517
  async cleanupStaleChunks(currentIds) {
346
518
  const result = await this.db.execute("SELECT id FROM chunks");
@@ -387,25 +559,34 @@ var init_retrieval = __esm({
387
559
  * interpolated. An empty list retrieves nothing, and short-circuits before
388
560
  * the embedding call.
389
561
  */
390
- async query(q, k = 6, allowed = ["base"]) {
562
+ async queryChunks(q, k = 6, allowed = ["base"]) {
391
563
  if (allowed.length === 0) return [];
392
564
  const [qv] = await this.embed([q]);
393
565
  const placeholders = allowed.map(() => "?").join(",");
394
566
  const res = await this.db.execute({
395
- sql: `SELECT c.id, c.text, e.embedding
567
+ sql: `SELECT c.*, e.embedding
396
568
  FROM chunks c
397
569
  JOIN embeddings e ON e.id = c.id
398
570
  WHERE c.bucket IN (${placeholders})`,
399
571
  args: allowed
400
572
  });
401
- const scored = [];
402
- for (const row of res.rows) {
573
+ const scored = res.rows.map((row) => {
403
574
  const emb = JSON.parse(String(row.embedding));
404
- const s = _VectorStore.cosine(qv, emb);
405
- scored.push({ s, text: String(row.text) });
406
- }
407
- scored.sort((a, b) => b.s - a.s);
408
- return scored.slice(0, k).map((r) => r.text);
575
+ const trail = row.section == null ? "" : String(row.section);
576
+ return {
577
+ text: String(row.text),
578
+ bucket: String(row.bucket ?? ""),
579
+ source: String(row.source ?? ""),
580
+ section: trail ? trail.split(" > ") : [],
581
+ score: _VectorStore.cosine(qv, emb)
582
+ };
583
+ });
584
+ scored.sort((a, b) => b.score - a.score);
585
+ return scored.slice(0, k);
586
+ }
587
+ /** Same results as {@link queryChunks}, as bare text. */
588
+ async query(q, k = 6, allowed = ["base"]) {
589
+ return (await this.queryChunks(q, k, allowed)).map((c) => c.text);
409
590
  }
410
591
  };
411
592
  }
@@ -530,7 +711,7 @@ __export(server_exports, {
530
711
  createServer: () => createServer
531
712
  });
532
713
  module.exports = __toCommonJS(server_exports);
533
- var import_node_path4 = require("node:path");
714
+ var import_node_path5 = require("node:path");
534
715
  var import_hono5 = require("hono");
535
716
  var import_openai = __toESM(require("openai"));
536
717
 
@@ -672,9 +853,10 @@ function detectLeakage(text) {
672
853
  function scrubOutput(text) {
673
854
  return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
674
855
  }
675
- function guardOutput(text) {
856
+ var DEFAULT_REFUSAL = "Sorry, I can't share internal instructions. How else can I help?";
857
+ function guardOutput(text, refusal) {
676
858
  if (detectLeakage(text)) {
677
- return "Sorry, I can't share internal instructions. How else can I help?";
859
+ return typeof refusal === "string" && refusal.trim() ? refusal : DEFAULT_REFUSAL;
678
860
  }
679
861
  return scrubOutput(text);
680
862
  }
@@ -686,7 +868,8 @@ async function completeOnce({
686
868
  system,
687
869
  messages,
688
870
  temperature = 0.2,
689
- model = DEFAULT_MODEL
871
+ model = DEFAULT_MODEL,
872
+ refusal
690
873
  }) {
691
874
  const res = await client.chat.completions.create({
692
875
  model,
@@ -695,7 +878,7 @@ async function completeOnce({
695
878
  });
696
879
  const text = res.choices[0]?.message?.content ?? "";
697
880
  return {
698
- content: guardOutput(text),
881
+ content: guardOutput(text, refusal),
699
882
  usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
700
883
  };
701
884
  }
@@ -720,12 +903,12 @@ async function* completeStream({
720
903
 
721
904
  // src/core/answer.ts
722
905
  var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
723
- async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
906
+ async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal) {
724
907
  const result = await answerFn({ system, messages, mode, sender, conversationId });
725
908
  const raw = typeof result === "string" ? result : result?.content;
726
909
  const usage = typeof result === "string" ? void 0 : result?.usage;
727
910
  return {
728
- content: guardOutput(typeof raw === "string" ? raw : ""),
911
+ content: guardOutput(typeof raw === "string" ? raw : "", refusal),
729
912
  usage: usage ?? noUsage()
730
913
  };
731
914
  }
@@ -738,12 +921,13 @@ async function answerOnce({
738
921
  sender,
739
922
  conversationId,
740
923
  temperature,
741
- model
924
+ model,
925
+ refusal
742
926
  }) {
743
927
  if (answerFn) {
744
- return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
928
+ return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }, refusal);
745
929
  }
746
- return completeOnce({ client, system, messages, temperature, model });
930
+ return completeOnce({ client, system, messages, temperature, model, refusal });
747
931
  }
748
932
  async function applyTransformReply(transformReply, input, logger) {
749
933
  if (!transformReply) return input.text;
@@ -767,16 +951,15 @@ async function* answerStream({
767
951
  sender,
768
952
  conversationId,
769
953
  temperature,
770
- model
954
+ model,
955
+ refusal
771
956
  }) {
772
957
  if (answerFn) {
773
- const { content } = await runAnswerFn(answerFn, {
774
- system,
775
- messages,
776
- mode,
777
- sender,
778
- conversationId
779
- });
958
+ const { content } = await runAnswerFn(
959
+ answerFn,
960
+ { system, messages, mode, sender, conversationId },
961
+ refusal
962
+ );
780
963
  if (content) yield content;
781
964
  return;
782
965
  }
@@ -1076,7 +1259,7 @@ function loadServeStatic(runtime = detectRuntime()) {
1076
1259
 
1077
1260
  // src/core/widgets.ts
1078
1261
  var import_node_fs3 = require("node:fs");
1079
- var import_node_path3 = require("node:path");
1262
+ var import_node_path4 = require("node:path");
1080
1263
  var import_node_url = require("node:url");
1081
1264
  var import_meta = {};
1082
1265
  function resolveStatic(configStaticDir) {
@@ -1085,15 +1268,15 @@ function resolveStatic(configStaticDir) {
1085
1268
  }
1086
1269
  try {
1087
1270
  if (import_meta.url) {
1088
- const moduleDir = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
1089
- const staticDir = (0, import_node_path3.join)(moduleDir, "static");
1271
+ const moduleDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
1272
+ const staticDir = (0, import_node_path4.join)(moduleDir, "static");
1090
1273
  if ((0, import_node_fs3.existsSync)(staticDir)) return { staticDir };
1091
1274
  }
1092
1275
  } catch {
1093
1276
  }
1094
1277
  try {
1095
1278
  if (typeof __dirname !== "undefined") {
1096
- const staticDir = (0, import_node_path3.join)(__dirname, "static");
1279
+ const staticDir = (0, import_node_path4.join)(__dirname, "static");
1097
1280
  if ((0, import_node_fs3.existsSync)(staticDir)) return { staticDir };
1098
1281
  }
1099
1282
  } catch {
@@ -1425,7 +1608,8 @@ function demoRoutes(deps) {
1425
1608
  system,
1426
1609
  messages,
1427
1610
  mode: "public",
1428
- model
1611
+ model,
1612
+ refusal: config.refusal
1429
1613
  })) {
1430
1614
  await s.write(`data: ${JSON.stringify({ delta })}
1431
1615
 
@@ -1440,7 +1624,8 @@ function demoRoutes(deps) {
1440
1624
  system,
1441
1625
  messages,
1442
1626
  mode: "public",
1443
- model
1627
+ model,
1628
+ refusal: config.refusal
1444
1629
  });
1445
1630
  const reply = await applyTransformReply(
1446
1631
  config.transformReply,
@@ -1662,7 +1847,8 @@ function openaiRoutes(deps) {
1662
1847
  sender,
1663
1848
  conversationId,
1664
1849
  temperature,
1665
- model: serverModel
1850
+ model: serverModel,
1851
+ refusal: config.refusal
1666
1852
  })) {
1667
1853
  await s.write(chunk2({ content: delta }, null));
1668
1854
  }
@@ -1679,7 +1865,8 @@ function openaiRoutes(deps) {
1679
1865
  sender,
1680
1866
  conversationId,
1681
1867
  temperature,
1682
- model: serverModel
1868
+ model: serverModel,
1869
+ refusal: config.refusal
1683
1870
  });
1684
1871
  const reply = await applyTransformReply(
1685
1872
  config.transformReply,
@@ -1770,7 +1957,8 @@ function privateRoutes(deps) {
1770
1957
  messages,
1771
1958
  mode: "private",
1772
1959
  sender,
1773
- model
1960
+ model,
1961
+ refusal: config.refusal
1774
1962
  })) {
1775
1963
  await s.write(`data: ${JSON.stringify({ delta })}
1776
1964
 
@@ -1786,7 +1974,8 @@ function privateRoutes(deps) {
1786
1974
  messages,
1787
1975
  mode: "private",
1788
1976
  sender,
1789
- model
1977
+ model,
1978
+ refusal: config.refusal
1790
1979
  });
1791
1980
  const reply = await applyTransformReply(
1792
1981
  config.transformReply,
@@ -1953,7 +2142,8 @@ function publicRoutes(deps) {
1953
2142
  system,
1954
2143
  messages,
1955
2144
  mode: "public",
1956
- model
2145
+ model,
2146
+ refusal: config.refusal
1957
2147
  })) {
1958
2148
  await s.write(`data: ${JSON.stringify({ delta })}
1959
2149
 
@@ -1962,7 +2152,15 @@ function publicRoutes(deps) {
1962
2152
  await s.write("event: end\ndata: {}\n\n");
1963
2153
  });
1964
2154
  }
1965
- const out = await answerOnce({ answerFn, client, system, messages, mode: "public", model });
2155
+ const out = await answerOnce({
2156
+ answerFn,
2157
+ client,
2158
+ system,
2159
+ messages,
2160
+ mode: "public",
2161
+ model,
2162
+ refusal: config.refusal
2163
+ });
1966
2164
  const reply = await applyTransformReply(
1967
2165
  config.transformReply,
1968
2166
  { channel: "widget-public", text: out.content },
@@ -2004,6 +2202,7 @@ async function createServer(config) {
2004
2202
  store = new VectorStore2(createOpenAIEmbedder2(client), {
2005
2203
  databaseClient: db,
2006
2204
  knowledgeDir: config.knowledgeDir || DEFAULT_KNOWLEDGE_DIR2,
2205
+ chunking: config.chunking,
2007
2206
  logger
2008
2207
  });
2009
2208
  }
@@ -2054,7 +2253,7 @@ async function createServer(config) {
2054
2253
  }
2055
2254
  }
2056
2255
  if (serveStatic && staticDir) {
2057
- const relativePath = (0, import_node_path4.relative)(process.cwd(), staticDir);
2256
+ const relativePath = (0, import_node_path5.relative)(process.cwd(), staticDir);
2058
2257
  app.get("/chatter.js", serveStatic({ path: `${relativePath}/chatter.js` }));
2059
2258
  app.get("/chatter.css", serveStatic({ path: `${relativePath}/chatter.css` }));
2060
2259
  }
@@ -2101,6 +2300,7 @@ async function createServer(config) {
2101
2300
  app.get("/demo/react", serveStatic({ path: `${publicDir}/react-demo.html` }));
2102
2301
  }
2103
2302
  const senders = createSenderRegistry(logger);
2303
+ const identities = /* @__PURE__ */ new Map();
2104
2304
  const deps = {
2105
2305
  client,
2106
2306
  store,
@@ -2112,6 +2312,7 @@ async function createServer(config) {
2112
2312
  prompts,
2113
2313
  apiKeyManager,
2114
2314
  senders,
2315
+ identities,
2115
2316
  logger
2116
2317
  };
2117
2318
  if (enablePublic) {