@ganju/utils 0.0.14 → 0.0.15

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 (70) hide show
  1. package/dist/channelCustomer.d.ts +99 -0
  2. package/dist/channelCustomer.d.ts.map +1 -0
  3. package/dist/channelCustomer.js +533 -0
  4. package/dist/channelDebounce.d.ts +7 -0
  5. package/dist/channelDebounce.d.ts.map +1 -1
  6. package/dist/chunkOverlap.d.ts +2 -0
  7. package/dist/chunkOverlap.d.ts.map +1 -0
  8. package/dist/chunkOverlap.js +9 -0
  9. package/dist/chunking.d.ts.map +1 -1
  10. package/dist/chunking.js +24 -4
  11. package/dist/citations.d.ts +28 -0
  12. package/dist/citations.d.ts.map +1 -0
  13. package/dist/citations.js +111 -0
  14. package/dist/constants.d.ts +26 -0
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +107 -9
  17. package/dist/index.d.ts +263 -31
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +70 -0
  20. package/dist/jsonSchemaToZodShape.d.ts +5 -1
  21. package/dist/jsonSchemaToZodShape.d.ts.map +1 -1
  22. package/dist/jsonSchemaToZodShape.js +21 -9
  23. package/dist/pagination.d.ts +47 -0
  24. package/dist/pagination.d.ts.map +1 -0
  25. package/dist/pagination.js +160 -0
  26. package/dist/processQueueBatch.d.ts +6 -1
  27. package/dist/processQueueBatch.d.ts.map +1 -1
  28. package/dist/processQueueBatch.js +23 -10
  29. package/dist/queueFailure.d.ts +14 -0
  30. package/dist/queueFailure.d.ts.map +1 -0
  31. package/dist/queueFailure.js +43 -0
  32. package/dist/resourceRerank.d.ts.map +1 -1
  33. package/dist/resourceRerank.js +3 -1
  34. package/dist/schema.d.ts +90 -26
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +87 -6
  37. package/dist/ssrf.d.ts +1 -0
  38. package/dist/ssrf.d.ts.map +1 -1
  39. package/dist/ssrf.js +48 -12
  40. package/dist/toolConfirmation.d.ts +48 -0
  41. package/dist/toolConfirmation.d.ts.map +1 -1
  42. package/dist/toolConfirmation.js +75 -1
  43. package/dist/toolCount.d.ts +13 -0
  44. package/dist/toolCount.d.ts.map +1 -0
  45. package/dist/toolCount.js +45 -0
  46. package/dist/toolLint.d.ts +1 -1
  47. package/dist/toolLint.d.ts.map +1 -1
  48. package/dist/toolLint.js +20 -0
  49. package/dist/untrustedContent.d.ts +135 -0
  50. package/dist/untrustedContent.d.ts.map +1 -0
  51. package/dist/untrustedContent.js +402 -0
  52. package/package.json +2 -1
  53. package/src/channelCustomer.ts +604 -0
  54. package/src/channelDebounce.ts +17 -0
  55. package/src/chunkOverlap.ts +6 -0
  56. package/src/chunking.ts +25 -4
  57. package/src/citations.ts +127 -0
  58. package/src/constants.ts +111 -9
  59. package/src/index.ts +152 -2
  60. package/src/jsonSchemaToZodShape.ts +31 -13
  61. package/src/pagination.ts +193 -0
  62. package/src/processQueueBatch.ts +42 -12
  63. package/src/queueFailure.ts +63 -0
  64. package/src/resourceRerank.ts +5 -1
  65. package/src/schema.ts +100 -4
  66. package/src/ssrf.ts +40 -11
  67. package/src/toolConfirmation.ts +123 -0
  68. package/src/toolCount.ts +53 -0
  69. package/src/toolLint.ts +27 -0
  70. package/src/untrustedContent.ts +476 -0
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.overlapTail = void 0;
4
+ // The tail of the previous chunk a chunk opens with, so a sentence cut at the
5
+ // boundary still reads whole on one side of it. `overlap` of zero (or less)
6
+ // borrows nothing — `slice(-0)` is `slice(0)`, the whole previous chunk, which
7
+ // would double every chunk's text.
8
+ const overlapTail = (previous, overlap) => overlap > 0 ? previous.slice(-overlap) : '';
9
+ exports.overlapTail = overlapTail;
@@ -1 +1 @@
1
- {"version":3,"file":"chunking.d.ts","sourceRoot":"","sources":["../src/chunking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,KAAK,EACV,iBAAiB,EACjB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AASpE,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,aAAY,SAAS,SAAS,EAA+B,KAC5D,MAAM,EA8BR,CAAC;AA4BF,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,UAAU;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KACxD,MAAM,EAAoD,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC;AAehD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO;IACtC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,KAAG,aAAa,GAAG,IAUnB,CAAC;AAuEF,eAAO,MAAM,WAAW,GAAI,OAAO;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,KAAG,MASW,CAAC;AAIhB,MAAM,WAAW,aAAc,SAAQ,OAAO,CAAC,yBAAyB,CAAC;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;CAChC;AAID,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,WAAW,iBAAiB,EAAE,GAAG,IAAI,EACrC,iBAAiB,MAAM,GAAG,IAAI,EAC9B,UAAU;IACR,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,KACA,aAAa,EA4Cf,CAAC"}
1
+ {"version":3,"file":"chunking.d.ts","sourceRoot":"","sources":["../src/chunking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,OAAO,KAAK,EACV,iBAAiB,EACjB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AASpE,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,aAAY,SAAS,SAAS,EAA+B,KAC5D,MAAM,EA8BR,CAAC;AA4BF,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,UAAU;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KACxD,MAAM,EAAoD,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC;AAqBhD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO;IACtC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,KAAG,aAAa,GAAG,IAUnB,CAAC;AAqFF,eAAO,MAAM,WAAW,GAAI,OAAO;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,KAAG,MASW,CAAC;AAIhB,MAAM,WAAW,aAAc,SAAQ,OAAO,CAAC,yBAAyB,CAAC;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;CAChC;AAID,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,WAAW,iBAAiB,EAAE,GAAG,IAAI,EACrC,iBAAiB,MAAM,GAAG,IAAI,EAC9B,UAAU;IACR,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,KACA,aAAa,EA4Cf,CAAC"}
package/dist/chunking.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareChunks = exports.buildHeader = exports.headingSyntaxFor = exports.chunkText = exports.splitRecursive = void 0;
4
4
  const constants_1 = require("./constants");
5
+ const chunkOverlap_1 = require("./chunkOverlap");
5
6
  const sanitize_1 = require("./sanitize");
6
7
  const trySplit = (text, sep) => {
7
8
  if (typeof sep === 'string') {
@@ -55,7 +56,7 @@ const chunkPieces = (text, options) => {
55
56
  const rawChunks = (0, exports.splitRecursive)(trimmed, target);
56
57
  const pieces = [];
57
58
  for (let i = 0; i < rawChunks.length; i++) {
58
- const prev = i > 0 ? rawChunks[i - 1].slice(-overlap) : '';
59
+ const prev = i > 0 ? (0, chunkOverlap_1.overlapTail)(rawChunks[i - 1], overlap) : '';
59
60
  pieces.push({
60
61
  raw: rawChunks[i],
61
62
  content: (prev ? prev + '\n' : '') + rawChunks[i]
@@ -68,6 +69,12 @@ exports.chunkText = chunkText;
68
69
  const MARKDOWN_HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)(?:[ \t]+#+)?[ \t]*$/;
69
70
  const MARKDOWN_FENCE = /^ {0,3}(```|~~~)/;
70
71
  const HTML_HEADING = /<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1\s*>/gi;
72
+ // A heading tag still open at the end of a piece, from its opening tag on: the
73
+ // chunker split inside it, and the rest of it opens the next piece.
74
+ const OPEN_HTML_HEADING = /<h([1-6])\b[^>]*>(?![\s\S]*<\/h\1\s*>)[\s\S]*$/i;
75
+ // Longer than any heading worth carrying: an opening tag this far back with no
76
+ // close is broken markup, not a heading that happened to be split.
77
+ const MAX_OPEN_HEADING_CHARS = 1000;
71
78
  const MAX_HEADING_CHARS = 120;
72
79
  const MARKDOWN_FILE_NAME = /\.(md|markdown|mdx)$/i;
73
80
  /**
@@ -128,14 +135,27 @@ const scanHtmlHeadings = (text) => [...text.matchAll(HTML_HEADING)]
128
135
  const createHeadingTracker = (syntax) => {
129
136
  let stack = [];
130
137
  const fence = { inFence: false };
138
+ // The unfinished HTML heading the previous piece ended in, if any.
139
+ let carry = '';
131
140
  const apply = (event) => {
132
141
  stack = stack.filter(h => h.level < event.level);
133
142
  stack.push({ level: event.level, text: event.text });
134
143
  };
144
+ // An HTML heading split between two pieces is read whole from the second,
145
+ // where it ends, as if that piece opened with it: it does, mid-heading. The
146
+ // separator the chunker dropped at the split is restored as a space.
147
+ const scanHtml = (raw) => {
148
+ const text = carry ? `${carry} ${raw}` : raw;
149
+ const shift = text.length - raw.length;
150
+ const open = OPEN_HTML_HEADING.exec(text)?.[0] ?? '';
151
+ carry = open.length <= MAX_OPEN_HEADING_CHARS ? open : '';
152
+ return scanHtmlHeadings(text).map(event => ({
153
+ ...event,
154
+ offset: Math.max(0, event.offset - shift)
155
+ }));
156
+ };
135
157
  return (raw) => {
136
- const events = syntax === 'markdown'
137
- ? scanMarkdownHeadings(raw, fence)
138
- : scanHtmlHeadings(raw);
158
+ const events = syntax === 'markdown' ? scanMarkdownHeadings(raw, fence) : scanHtml(raw);
139
159
  const [first, ...rest] = events;
140
160
  // Before a document's first heading there is no section to continue — only
141
161
  // the metadata header, or the markup around an HTML body — so a chunk that
@@ -0,0 +1,28 @@
1
+ import type { Source } from './sources';
2
+ /**
3
+ * Whether the answer cites this exact URL, not merely one that starts with it:
4
+ * on a crawled docs site the index page is a prefix of every page beneath it,
5
+ * so a plain substring test kept "/docs" and the site root in the footer of an
6
+ * answer that cited "/docs/api". A trailing slash and a #fragment still count
7
+ * as the same page.
8
+ */
9
+ export declare const citesUrl: (answer: string, url: string) => boolean;
10
+ /** Every page number the answer cites, ranges expanded. */
11
+ export declare const citedPages: (answer: string) => Set<number>;
12
+ /**
13
+ * Narrow a turn's sources to the ones its answer actually cites.
14
+ *
15
+ * Every search hit used to become a footer line, so an answer drawn from one
16
+ * handbook section was followed by eight unrelated pages the search had also
17
+ * returned. A search hit is kept when the answer names its resource — by title,
18
+ * file name or URL — and, for a paged file, only the pages the answer cites
19
+ * (or the best-ranked one, when it cites none). Resources the model read whole
20
+ * are always kept: reading one is itself the citation.
21
+ *
22
+ * An answer that names nothing gets no search sources at all. The model is told
23
+ * to cite what it uses, so a reply that names nothing is a greeting or "I
24
+ * couldn't find that" — and a footer under it would claim sources it never
25
+ * used, just because search runs on every message.
26
+ */
27
+ export declare const selectCitedSources: (sources: Source[], answerText: string) => Source[];
28
+ //# sourceMappingURL=citations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citations.d.ts","sourceRoot":"","sources":["../src/citations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAyCxC;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OAetD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,GAAG,CAAC,MAAM,CAYrD,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,EAAE,EACjB,YAAY,MAAM,KACjB,MAAM,EA4BR,CAAC"}
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectCitedSources = exports.citedPages = exports.citesUrl = void 0;
4
+ // "p. 125", "pp. 123–126", "page 12", "págs. 4-5", "páginas 3 a 5". An
5
+ // abbreviation counts only with its dot and a number has to stand alone, so
6
+ // "p2p", "mp3" and "pp 2026" are not page references. The lookarounds, not \b,
7
+ // mark the edges: \b sees "á" as a word break.
8
+ const PAGE_REFERENCE = /(?<![\p{L}\p{N}])(?:(?:pp?|p[aá]gs?)\.|pages?|p[aá]ginas?)\s*(\d{1,5})(?![\p{L}\p{N}])(?:(?:\s*[-–—]\s*|\s+(?:to|a)\s+)(\d{1,5})(?![\p{L}\p{N}]))?/giu;
9
+ const MAX_PAGE_RANGE = 50;
10
+ // Lowercased words only, so "ETag header — HTTP | MDN" in an answer matches the
11
+ // title "ETag header - HTTP | MDN", and "mml-book.pdf" matches "mml-book".
12
+ const normalize = (text) => ` ${text
13
+ .toLowerCase()
14
+ .normalize('NFKD')
15
+ .replace(/[̀-ͯ]/g, '')
16
+ .replace(/[^a-z0-9]+/g, ' ')
17
+ .trim()} `;
18
+ // The ways an answer refers to a source: its whole title, the title without a
19
+ // parenthetical or a site suffix ("Northwind Handbook (draft)", "ETag header
20
+ // - HTTP | MDN"), its file name with and without the extension, and its URL.
21
+ const namesFor = (source) => {
22
+ const names = [source.title, source.title.replace(/\s*\([^)]*\)\s*/g, ' ')];
23
+ const lead = source.title.split(/\s+[|\-–—:]\s+/)[0];
24
+ // A one-word lead ("Introduction", "Pricing") names half the corpus.
25
+ if (lead.trim().split(/\s+/).length >= 2)
26
+ names.push(lead);
27
+ if (source.fileName) {
28
+ names.push(source.fileName, source.fileName.replace(/\.[a-z0-9]+$/i, ''));
29
+ }
30
+ return names.map(normalize).filter(name => name.trim().length >= 4);
31
+ };
32
+ // The characters that can carry a URL on past the point we matched. Brackets,
33
+ // quotes and whitespace end it — that is how a URL sits inside Markdown links,
34
+ // parentheses and prose.
35
+ const URL_CONTINUATION = /^[^\s<>()[\]{}"'`|]*/;
36
+ // Punctuation that closes a sentence around a URL rather than belonging to it.
37
+ const TRAILING_PUNCTUATION = /[.,;:!?*_]+$/;
38
+ /**
39
+ * Whether the answer cites this exact URL, not merely one that starts with it:
40
+ * on a crawled docs site the index page is a prefix of every page beneath it,
41
+ * so a plain substring test kept "/docs" and the site root in the footer of an
42
+ * answer that cited "/docs/api". A trailing slash and a #fragment still count
43
+ * as the same page.
44
+ */
45
+ const citesUrl = (answer, url) => {
46
+ const target = url.replace(/\/+$/, '');
47
+ if (!target)
48
+ return false;
49
+ for (let at = answer.indexOf(target); at !== -1; at = answer.indexOf(target, at + 1)) {
50
+ const rest = answer
51
+ .slice(at + target.length)
52
+ .match(URL_CONTINUATION)[0]
53
+ .replace(TRAILING_PUNCTUATION, '');
54
+ if (/^\/*(#.*)?$/.test(rest))
55
+ return true;
56
+ }
57
+ return false;
58
+ };
59
+ exports.citesUrl = citesUrl;
60
+ /** Every page number the answer cites, ranges expanded. */
61
+ const citedPages = (answer) => {
62
+ const pages = new Set();
63
+ for (const match of answer.matchAll(PAGE_REFERENCE)) {
64
+ const from = Number(match[1]);
65
+ const to = match[2] ? Number(match[2]) : from;
66
+ if (to < from || to - from > MAX_PAGE_RANGE) {
67
+ pages.add(from);
68
+ continue;
69
+ }
70
+ for (let page = from; page <= to; page++)
71
+ pages.add(page);
72
+ }
73
+ return pages;
74
+ };
75
+ exports.citedPages = citedPages;
76
+ /**
77
+ * Narrow a turn's sources to the ones its answer actually cites.
78
+ *
79
+ * Every search hit used to become a footer line, so an answer drawn from one
80
+ * handbook section was followed by eight unrelated pages the search had also
81
+ * returned. A search hit is kept when the answer names its resource — by title,
82
+ * file name or URL — and, for a paged file, only the pages the answer cites
83
+ * (or the best-ranked one, when it cites none). Resources the model read whole
84
+ * are always kept: reading one is itself the citation.
85
+ *
86
+ * An answer that names nothing gets no search sources at all. The model is told
87
+ * to cite what it uses, so a reply that names nothing is a greeting or "I
88
+ * couldn't find that" — and a footer under it would claim sources it never
89
+ * used, just because search runs on every message.
90
+ */
91
+ const selectCitedSources = (sources, answerText) => {
92
+ const searchEntries = sources.filter(s => s.score !== undefined);
93
+ const readEntries = sources.filter(s => s.score === undefined);
94
+ if (searchEntries.length === 0)
95
+ return sources;
96
+ const answer = normalize(answerText);
97
+ const pages = (0, exports.citedPages)(answerText);
98
+ const citedResources = new Set(searchEntries
99
+ .filter(s => (0, exports.citesUrl)(answerText, s.uri) ||
100
+ namesFor(s).some(name => answer.includes(name)))
101
+ .map(s => s.resourceId));
102
+ const kept = [];
103
+ for (const resourceId of citedResources) {
104
+ const entries = searchEntries.filter(s => s.resourceId === resourceId);
105
+ const onCitedPage = entries.filter(s => s.pageNumber !== undefined && pages.has(s.pageNumber));
106
+ kept.push(...(onCitedPage.length > 0 ? onCitedPage : entries.slice(0, 1)));
107
+ }
108
+ const cited = searchEntries.filter(s => kept.includes(s));
109
+ return [...cited, ...readEntries];
110
+ };
111
+ exports.selectCitedSources = selectCitedSources;
@@ -333,6 +333,7 @@ export declare const constants: {
333
333
  CRAWL_DEFAULT_MAX_DEPTH: number;
334
334
  CRAWL_MAX_DEPTH_LIMIT: number;
335
335
  CRAWL_PAGE_FETCH_TIMEOUT_MS: number;
336
+ CRAWL_MAX_REDIRECTS: number;
336
337
  CRAWL_PAGE_QUEUE_BATCH_SIZE: number;
337
338
  CRAWL_USER_AGENT: string;
338
339
  RESOURCE_ICON_THEME_DARK: string;
@@ -350,6 +351,7 @@ export declare const constants: {
350
351
  TELEGRAM_MAX_FILE_BYTES: number;
351
352
  RATE_LIMIT_BACKOFF_SECONDS: number;
352
353
  TRANSIENT_BACKOFF_SECONDS: number;
354
+ QUEUE_MAX_RETRIES: number;
353
355
  ENCODINGS: string[];
354
356
  ENCODING_UTF8: "utf-8";
355
357
  CHANNEL_PLATFORM_TELEGRAM: "telegram";
@@ -395,6 +397,16 @@ export declare const constants: {
395
397
  MCP_TOOL_CONFIRMATION_TTL_MS: number;
396
398
  MCP_TOOL_CONFIRMATION_ARG: string;
397
399
  MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION: string;
400
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION_CONDITIONAL: string;
401
+ TOOL_APPROVAL_PATH: string;
402
+ TOOL_APPROVAL_DECISION_APPROVE: string;
403
+ TOOL_APPROVAL_DECISION_DENY: string;
404
+ TOOL_APPROVAL_DECISIONS: readonly ["approve", "deny"];
405
+ TOOL_APPROVAL_STATUS_PENDING: string;
406
+ TOOL_APPROVAL_STATUS_APPROVED: string;
407
+ TOOL_APPROVAL_STATUS_DENIED: string;
408
+ TOOL_APPROVAL_STATUS_USED: string;
409
+ TOOL_APPROVAL_STATUS_EXPIRED: string;
398
410
  CHANNEL_DEBOUNCE_DEFAULT_MS: number;
399
411
  CHANNEL_DEBOUNCE_MIN_MS: number;
400
412
  CHANNEL_DEBOUNCE_MAX_MS: number;
@@ -467,6 +479,9 @@ export declare const constants: {
467
479
  RESOURCE_SEARCH_COMMON_TERM_MIN: number;
468
480
  RESOURCE_SEARCH_RERANK_CANDIDATES: number;
469
481
  RESOURCE_SEARCH_RERANK_MODEL: string;
482
+ RESOURCE_SEARCH_EXACT_MAX_RESOURCES: number;
483
+ RESOURCE_SEARCH_EXACT_MAX_CHUNKS: number;
484
+ RESOURCE_SEARCH_SUBSTRING_MAX_CHUNKS: number;
470
485
  RESOURCE_HANDLER_SLEEP_AFTER: string;
471
486
  DOCS_URL: string;
472
487
  BASE64_DATA_URI_RE: RegExp;
@@ -477,6 +492,8 @@ export declare const constants: {
477
492
  }[];
478
493
  DEFAULT_MAX_TOKENS: number;
479
494
  RESOURCE_TOOL_KEY_LIST_RESOURCES: string;
495
+ LIST_RESOURCES_DEFAULT_LIMIT: number;
496
+ LIST_RESOURCES_MAX_LIMIT: number;
480
497
  RESOURCE_TOOL_KEY_SEARCH_RESOURCES: string;
481
498
  RESOURCE_TOOL_KEY_READ_RESOURCE: string;
482
499
  RESOURCE_TOOL_KEY_SEND_RESOURCE: string;
@@ -650,6 +667,7 @@ export declare const constants: {
650
667
  MCP_INTERNAL_HEADER: string;
651
668
  MCP_CHANNEL_ID_HEADER: string;
652
669
  MCP_CHANNEL_PLATFORM_HEADER: string;
670
+ MCP_CHANNEL_CONVERSATION_HEADER: string;
653
671
  MCP_CHANNEL_CLIENT_USER_AGENT: string;
654
672
  MCP_SESSION_HEADER: string;
655
673
  MCP_REQUEST_METHOD_INITIALIZE: "initialize";
@@ -687,6 +705,14 @@ export declare const constants: {
687
705
  ACCESS_TOKEN_SCOPE_MESSAGE: string;
688
706
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
689
707
  CUSTOM_CODE_LOGS_MAX_LIMIT: number;
708
+ OBSERVABILITY_RANGE_DAYS: number[];
709
+ OBSERVABILITY_UNUSED_DAYS: number;
710
+ OBSERVABILITY_FOLLOW_UP_SECONDS: number;
711
+ OBSERVABILITY_SCHEMA_REJECTION_PREFIX: string;
712
+ OBSERVABILITY_TOP_ERRORS: number;
713
+ OBSERVABILITY_CALLS_DEFAULT_LIMIT: number;
714
+ OBSERVABILITY_CALLS_MAX_LIMIT: number;
715
+ OBSERVABILITY_SESSION_MAX_CALLS: number;
690
716
  BOT_GRANT_TYPE: string;
691
717
  EXTERNAL_LINK_VERIFICATION_PREFIX: string;
692
718
  EXTERNAL_LINK_TTL_SECONDS: number;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
package/dist/constants.js CHANGED
@@ -252,6 +252,9 @@ const CRAWL_MAX_PAGES_LIMIT = 1000;
252
252
  const CRAWL_DEFAULT_MAX_DEPTH = 3;
253
253
  const CRAWL_MAX_DEPTH_LIMIT = 10;
254
254
  const CRAWL_PAGE_FETCH_TIMEOUT_MS = 30000;
255
+ // Redirects one crawl fetch may follow. Each hop is screened on its own, so a
256
+ // public URL can't bounce the crawler onto a private address.
257
+ const CRAWL_MAX_REDIRECTS = 5;
255
258
  const CRAWL_USER_AGENT = 'Mozilla/5.0 (compatible; GanjuCrawler/1.0; +https://ganju.ai)';
256
259
  const CRAWL_PAGE_QUEUE_BATCH_SIZE = 100;
257
260
  const RESOURCE_ICON_THEME_DARK = 'dark';
@@ -484,6 +487,10 @@ const RATE_LIMIT_BACKOFF_SECONDS = 60;
484
487
  // Short backoff for transient Cloudflare platform errors (e.g. a Durable Object
485
488
  // / Container reset) so the retried job lands after the object has recovered.
486
489
  const TRANSIENT_BACKOFF_SECONDS = 5;
490
+ // Retries every resource queue consumer is configured with (`max_retries` in
491
+ // the API worker's wrangler config). A job's last delivery is attempt
492
+ // QUEUE_MAX_RETRIES + 1; only then is its work marked failed.
493
+ const QUEUE_MAX_RETRIES = 3;
487
494
  const CHANNEL_PLATFORM_TELEGRAM = 'telegram';
488
495
  const CHANNEL_PLATFORM_SLACK = 'slack';
489
496
  const CHANNEL_PLATFORM_WHATSAPP = 'whatsapp';
@@ -667,14 +674,37 @@ const TOOL_CONFIRMATION_ASK_INSTRUCTION = 'IMPORTANT: these actions have NOT bee
667
674
  'confirm, and must not say or suggest that they were done, nor describe ' +
668
675
  'or make up any result from them.';
669
676
  // For MCP clients, whose chat the server can't see: a sensitive call without a
670
- // valid confirmation isn't run, and the model is told to ask the user and call
671
- // again with a token that binds the second call to these exact arguments.
672
- // How long that token stays good, and the argument that carries it.
677
+ // valid confirmation isn't run. Ordinarily the model is told to ask the user
678
+ // and call again with a token that binds the second call to these exact
679
+ // arguments; once the session has read outside content, the user approves it
680
+ // on a dashboard page instead, and the second call carries that approval's
681
+ // id. How long either stays good, and the argument that carries it.
673
682
  const MCP_TOOL_CONFIRMATION_TTL_MS = 10 * 60 * 1000;
674
683
  const MCP_TOOL_CONFIRMATION_ARG = 'confirmation';
675
684
  const MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION = "Leave out on the first call. This action needs the user's yes: the " +
676
- 'first call returns a token; after the user says yes, call again with the ' +
677
- 'same arguments and that token here.';
685
+ 'first call returns a code and says how to get that yes; once the user ' +
686
+ 'has given it, call again with the same arguments and that code here.';
687
+ // The same argument where only some calls ask: those made after the session
688
+ // read outside content.
689
+ const MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION_CONDITIONAL = "Leave out on the first call. If this action needs the user's yes, the " +
690
+ 'first call returns a code instead of running and says how to get that ' +
691
+ 'yes; once the user has given it, call again with the same arguments and ' +
692
+ 'that code here.';
693
+ // The dashboard page where a signed-in user approves or denies one such call,
694
+ // as `${TOOL_APPROVAL_PATH}/<approval id>`, and the two answers it sends.
695
+ const TOOL_APPROVAL_PATH = '/tool-approval';
696
+ const TOOL_APPROVAL_DECISION_APPROVE = 'approve';
697
+ const TOOL_APPROVAL_DECISION_DENY = 'deny';
698
+ const TOOL_APPROVAL_DECISIONS = [
699
+ TOOL_APPROVAL_DECISION_APPROVE,
700
+ TOOL_APPROVAL_DECISION_DENY
701
+ ];
702
+ // Where an approval stands, as the page shows it.
703
+ const TOOL_APPROVAL_STATUS_PENDING = 'pending';
704
+ const TOOL_APPROVAL_STATUS_APPROVED = 'approved';
705
+ const TOOL_APPROVAL_STATUS_DENIED = 'denied';
706
+ const TOOL_APPROVAL_STATUS_USED = 'used';
707
+ const TOOL_APPROVAL_STATUS_EXPIRED = 'expired';
678
708
  // The question when the model wrote none, above the runner's own summary.
679
709
  const TOOL_CONFIRMATION_FALLBACK_QUESTION = 'Before I do this, please confirm. Reply yes to go ahead, or no to cancel.';
680
710
  const TELEGRAM_SECRET_HEADER = 'x-telegram-bot-api-secret-token';
@@ -695,13 +725,15 @@ const SLACK_MESSAGE_LIMIT = 2900;
695
725
  const SLACK_MAX_SOURCE_BUTTONS = 10;
696
726
  // The Slack app configuration Ganju needs, surfaced in the UI when connecting a
697
727
  // Slack channel. Required scopes back the runner's calls (mentions, DMs,
698
- // posting, file upload); recommended scopes only enrich names/titles and
699
- // degrade gracefully; bot events are what the webhook subscribes to.
728
+ // posting, file upload, downloading a voice clip to transcribe it);
729
+ // recommended scopes only enrich names/titles and degrade gracefully; bot
730
+ // events are what the webhook subscribes to.
700
731
  const SLACK_REQUIRED_SCOPES = [
701
732
  'app_mentions:read',
702
733
  'im:history',
703
734
  'chat:write',
704
- 'files:write'
735
+ 'files:write',
736
+ 'files:read'
705
737
  ];
706
738
  const SLACK_RECOMMENDED_SCOPES = ['users:read', 'channels:read', 'groups:read'];
707
739
  const SLACK_BOT_EVENTS = ['app_mention', 'message.im'];
@@ -814,6 +846,19 @@ const RESOURCE_SEARCH_COMMON_TERM_MIN = 5;
814
846
  // call per search; a failure falls back to the fused order.
815
847
  const RESOURCE_SEARCH_RERANK_CANDIDATES = 30;
816
848
  const RESOURCE_SEARCH_RERANK_MODEL = '@cf/baai/bge-reranker-base';
849
+ // An identifier in the query (a code mixing letters and digits, a snake_case
850
+ // name, or a number, dotted or hyphenated name typed on its own) found in at
851
+ // most this many documents is what the user is after: its chunks go first,
852
+ // whatever the reranker thinks. A cross-encoder reads "86400" or "PGRST116" as noise and
853
+ // would bury the one chunk that contains it under pages that merely look
854
+ // related. Past EXACT_MAX_CHUNKS matching chunks the identifier is common
855
+ // enough that the usual ranking is left to decide, and the scan stops there.
856
+ const RESOURCE_SEARCH_EXACT_MAX_RESOURCES = 5;
857
+ const RESOURCE_SEARCH_EXACT_MAX_CHUNKS = 30;
858
+ // An identifier the index never saw is looked for as plain text, which reads
859
+ // every chunk of the artifact when nothing matches. Past this many chunks that
860
+ // costs more than the rare hit is worth, so a large artifact skips it.
861
+ const RESOURCE_SEARCH_SUBSTRING_MAX_CHUNKS = 20000;
817
862
  const RESOURCE_HANDLER_SLEEP_AFTER = '10m';
818
863
  const DOCS_URL = 'https://ganju.ai/docs/';
819
864
  const BASE64_DATA_URI_RE = /data:image\/[a-zA-Z0-9+\-.]+;base64,[A-Za-z0-9+/=\s]+/g;
@@ -826,6 +871,11 @@ const CHUNK_SEPARATORS = [
826
871
  { split: ' ', join: ' ' }
827
872
  ];
828
873
  const DEFAULT_MAX_TOKENS = 4096;
874
+ // One page of `list-resources`. A knowledge base with a crawled site runs to
875
+ // thousands of resources; listed whole, that is one result of several hundred
876
+ // KB in the model's context.
877
+ const LIST_RESOURCES_DEFAULT_LIMIT = 100;
878
+ const LIST_RESOURCES_MAX_LIMIT = 500;
829
879
  const RESOURCE_TOOL_KEY_LIST_RESOURCES = 'list-resources';
830
880
  const RESOURCE_TOOL_KEY_SEARCH_RESOURCES = 'search-resources';
831
881
  const RESOURCE_TOOL_KEY_READ_RESOURCE = 'read-resource';
@@ -1288,7 +1338,9 @@ const RESERVED_TOOL_NAMES = [
1288
1338
  TOOL_DEFINITION_KEY_HTTP_ENDPOINT,
1289
1339
  TOOL_DEFINITION_KEY_MCP_PROXY,
1290
1340
  TOOL_DEFINITION_KEY_CUSTOM_CODE,
1291
- sdkConstants_1.CUSTOM_CODE_HEALTH_TOOL
1341
+ sdkConstants_1.CUSTOM_CODE_HEALTH_TOOL,
1342
+ // The channel runner's own memory tool (PARTICIPANT_MEMORY_TOOL_NAME).
1343
+ 'remember-customer'
1292
1344
  ];
1293
1345
  // Shared by both write paths so there is one string to translate. It has to
1294
1346
  // carry a word `matchStatus` recognises (errorHandler.ts), because the
@@ -1518,6 +1570,10 @@ const MCP_SESSION_HEADER = 'mcp-session-id';
1518
1570
  const MCP_INTERNAL_HEADER = 'x-ganju-internal-secret';
1519
1571
  const MCP_CHANNEL_ID_HEADER = 'x-ganju-channel-id';
1520
1572
  const MCP_CHANNEL_PLATFORM_HEADER = 'x-ganju-channel-platform';
1573
+ // The conversation a channel turn belongs to. The MCP worker files that turn's
1574
+ // calls under one session per conversation, so a call can be traced back to
1575
+ // the chat it ran in.
1576
+ const MCP_CHANNEL_CONVERSATION_HEADER = 'x-ganju-channel-conversation-id';
1521
1577
  const MCP_CHANNEL_CLIENT_USER_AGENT = 'ganju-channel/0.0.1';
1522
1578
  const JWKS_KV_KEY = 'jwks:v1';
1523
1579
  const JWKS_TTL_SECONDS = 600;
@@ -1604,6 +1660,22 @@ const ACCESS_TOKEN_SCOPE_MESSAGE = 'This token is scoped to a different project'
1604
1660
  // Recent custom-tool invocations, as `ganju logs` reads them.
1605
1661
  const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
1606
1662
  const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
1663
+ // Tool observability on the project home. The windows match the activity
1664
+ // chart's, and none reaches past `RETENTION_DAYS.mcpRequest`.
1665
+ const OBSERVABILITY_RANGE_DAYS = [7, 30, 90];
1666
+ // An enabled tool with no call in this many days is offered for disabling.
1667
+ const OBSERVABILITY_UNUSED_DAYS = 30;
1668
+ // Two calls in one session this close together are one attempt followed by
1669
+ // the next: a retry when it's the same tool, a switch when it's another tool
1670
+ // after an error.
1671
+ const OBSERVABILITY_FOLLOW_UP_SECONDS = 120;
1672
+ // How a call rejected by its tool's input schema reads in `error_message`.
1673
+ const OBSERVABILITY_SCHEMA_REJECTION_PREFIX = 'Input validation error';
1674
+ const OBSERVABILITY_TOP_ERRORS = 8;
1675
+ const OBSERVABILITY_CALLS_DEFAULT_LIMIT = 25;
1676
+ const OBSERVABILITY_CALLS_MAX_LIMIT = 100;
1677
+ // A session timeline is read whole up to this many calls, newest kept.
1678
+ const OBSERVABILITY_SESSION_MAX_CALLS = 500;
1607
1679
  const BOT_GRANT_TYPE = 'urn:ganju:bot-on-behalf-of';
1608
1680
  const EXTERNAL_LINK_VERIFICATION_PREFIX = 'external_link:';
1609
1681
  const EXTERNAL_LINK_TTL_SECONDS = 600;
@@ -2251,6 +2323,7 @@ exports.constants = {
2251
2323
  CRAWL_DEFAULT_MAX_DEPTH,
2252
2324
  CRAWL_MAX_DEPTH_LIMIT,
2253
2325
  CRAWL_PAGE_FETCH_TIMEOUT_MS,
2326
+ CRAWL_MAX_REDIRECTS,
2254
2327
  CRAWL_PAGE_QUEUE_BATCH_SIZE,
2255
2328
  CRAWL_USER_AGENT,
2256
2329
  RESOURCE_ICON_THEME_DARK,
@@ -2268,6 +2341,7 @@ exports.constants = {
2268
2341
  TELEGRAM_MAX_FILE_BYTES,
2269
2342
  RATE_LIMIT_BACKOFF_SECONDS,
2270
2343
  TRANSIENT_BACKOFF_SECONDS,
2344
+ QUEUE_MAX_RETRIES,
2271
2345
  ENCODINGS,
2272
2346
  ENCODING_UTF8,
2273
2347
  CHANNEL_PLATFORM_TELEGRAM,
@@ -2309,6 +2383,16 @@ exports.constants = {
2309
2383
  MCP_TOOL_CONFIRMATION_TTL_MS,
2310
2384
  MCP_TOOL_CONFIRMATION_ARG,
2311
2385
  MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION,
2386
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION_CONDITIONAL,
2387
+ TOOL_APPROVAL_PATH,
2388
+ TOOL_APPROVAL_DECISION_APPROVE,
2389
+ TOOL_APPROVAL_DECISION_DENY,
2390
+ TOOL_APPROVAL_DECISIONS,
2391
+ TOOL_APPROVAL_STATUS_PENDING,
2392
+ TOOL_APPROVAL_STATUS_APPROVED,
2393
+ TOOL_APPROVAL_STATUS_DENIED,
2394
+ TOOL_APPROVAL_STATUS_USED,
2395
+ TOOL_APPROVAL_STATUS_EXPIRED,
2312
2396
  CHANNEL_DEBOUNCE_DEFAULT_MS,
2313
2397
  CHANNEL_DEBOUNCE_MIN_MS,
2314
2398
  CHANNEL_DEBOUNCE_MAX_MS,
@@ -2381,6 +2465,9 @@ exports.constants = {
2381
2465
  RESOURCE_SEARCH_COMMON_TERM_MIN,
2382
2466
  RESOURCE_SEARCH_RERANK_CANDIDATES,
2383
2467
  RESOURCE_SEARCH_RERANK_MODEL,
2468
+ RESOURCE_SEARCH_EXACT_MAX_RESOURCES,
2469
+ RESOURCE_SEARCH_EXACT_MAX_CHUNKS,
2470
+ RESOURCE_SEARCH_SUBSTRING_MAX_CHUNKS,
2384
2471
  RESOURCE_HANDLER_SLEEP_AFTER,
2385
2472
  DOCS_URL,
2386
2473
  BASE64_DATA_URI_RE,
@@ -2388,6 +2475,8 @@ exports.constants = {
2388
2475
  CHUNK_SEPARATORS,
2389
2476
  DEFAULT_MAX_TOKENS,
2390
2477
  RESOURCE_TOOL_KEY_LIST_RESOURCES,
2478
+ LIST_RESOURCES_DEFAULT_LIMIT,
2479
+ LIST_RESOURCES_MAX_LIMIT,
2391
2480
  RESOURCE_TOOL_KEY_SEARCH_RESOURCES,
2392
2481
  RESOURCE_TOOL_KEY_READ_RESOURCE,
2393
2482
  RESOURCE_TOOL_KEY_SEND_RESOURCE,
@@ -2561,6 +2650,7 @@ exports.constants = {
2561
2650
  MCP_INTERNAL_HEADER,
2562
2651
  MCP_CHANNEL_ID_HEADER,
2563
2652
  MCP_CHANNEL_PLATFORM_HEADER,
2653
+ MCP_CHANNEL_CONVERSATION_HEADER,
2564
2654
  MCP_CHANNEL_CLIENT_USER_AGENT,
2565
2655
  MCP_SESSION_HEADER,
2566
2656
  MCP_REQUEST_METHOD_INITIALIZE,
@@ -2598,6 +2688,14 @@ exports.constants = {
2598
2688
  ACCESS_TOKEN_SCOPE_MESSAGE,
2599
2689
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
2600
2690
  CUSTOM_CODE_LOGS_MAX_LIMIT,
2691
+ OBSERVABILITY_RANGE_DAYS,
2692
+ OBSERVABILITY_UNUSED_DAYS,
2693
+ OBSERVABILITY_FOLLOW_UP_SECONDS,
2694
+ OBSERVABILITY_SCHEMA_REJECTION_PREFIX,
2695
+ OBSERVABILITY_TOP_ERRORS,
2696
+ OBSERVABILITY_CALLS_DEFAULT_LIMIT,
2697
+ OBSERVABILITY_CALLS_MAX_LIMIT,
2698
+ OBSERVABILITY_SESSION_MAX_CALLS,
2601
2699
  BOT_GRANT_TYPE,
2602
2700
  EXTERNAL_LINK_VERIFICATION_PREFIX,
2603
2701
  EXTERNAL_LINK_TTL_SECONDS,