@burdenoff/fe-libs 2026.904.2 → 2026.904.4

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 (41) hide show
  1. package/dist/shared/assistant/attachments.d.ts +340 -0
  2. package/dist/shared/assistant/attachments.d.ts.map +1 -0
  3. package/dist/shared/assistant/attachments.js +370 -0
  4. package/dist/shared/assistant/conversation.d.ts +153 -0
  5. package/dist/shared/assistant/conversation.d.ts.map +1 -0
  6. package/dist/shared/assistant/documentText.d.ts +261 -0
  7. package/dist/shared/assistant/documentText.d.ts.map +1 -0
  8. package/dist/shared/assistant/documentText.js +235 -0
  9. package/dist/shared/assistant/gatewayLimits.d.ts +25 -0
  10. package/dist/shared/assistant/gatewayLimits.d.ts.map +1 -0
  11. package/dist/shared/assistant/gatewayLimits.js +4 -0
  12. package/dist/shared/assistant/index.d.ts +6 -0
  13. package/dist/shared/assistant/index.d.ts.map +1 -1
  14. package/dist/shared/assistant/store.d.ts +114 -0
  15. package/dist/shared/assistant/store.d.ts.map +1 -0
  16. package/dist/shared/assistant/store.js +198 -0
  17. package/dist/shared/assistant/turnState.d.ts +16 -0
  18. package/dist/shared/assistant/turnState.d.ts.map +1 -0
  19. package/dist/shared/assistant/turnState.js +6 -0
  20. package/dist/shared/config/authBridgeUrls.d.ts.map +1 -1
  21. package/dist/shared/config/authBridgeUrls.js +16 -15
  22. package/dist/shared/config/gatewayUrls.d.ts +29 -0
  23. package/dist/shared/config/gatewayUrls.d.ts.map +1 -1
  24. package/dist/shared/config/gatewayUrls.js +44 -20
  25. package/dist/shared/config/index.d.ts.map +1 -1
  26. package/dist/shared/native/capacitor.d.ts +49 -0
  27. package/dist/shared/native/capacitor.d.ts.map +1 -0
  28. package/dist/shared/native/capacitor.js +31 -0
  29. package/dist/shared/native/index.d.ts +2 -0
  30. package/dist/shared/native/index.d.ts.map +1 -1
  31. package/dist/shared/providers/shell/MultiGatewayProvider.d.ts.map +1 -1
  32. package/dist/shared/providers/shell/MultiGatewayProvider.js +101 -100
  33. package/dist/shared/utils/authCookie.d.ts.map +1 -1
  34. package/dist/shared/utils/authCookie.js +35 -34
  35. package/dist/shared/utils/gatewayFetchRewrite.d.ts.map +1 -1
  36. package/dist/shared/utils/gatewayFetchRewrite.js +95 -87
  37. package/dist/shared-assistant.js +6 -1
  38. package/dist/shared-config.js +9 -1
  39. package/dist/shared-native.js +23 -22
  40. package/dist/shared.js +124 -123
  41. package/package.json +2 -2
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Turning a PDF / DOCX / XLSX into plain text — the PURE half (BOFF-6291).
3
+ *
4
+ * Everything in this file is a string-in/string-out function with no imports,
5
+ * no DOM and no parser dependency, which is what makes it testable under the
6
+ * repo's `environment: 'node'` vitest config. The browser-only half — reading
7
+ * the File, lazily importing pdf.js / fflate, unzipping — lives in
8
+ * `documentExtract.ts` and calls into here.
9
+ *
10
+ * ## Why regex and not DOMParser
11
+ *
12
+ * DOCX and XLSX are ZIPs of XML, and the obvious tool is `DOMParser`. It is
13
+ * browser-only, so every one of these functions would then be untestable here
14
+ * and the extraction logic would ship unverified. We are pulling *text* out of
15
+ * a machine-generated, schema-fixed document part — not rendering untrusted
16
+ * markup — so a targeted scan over the tags Word/Excel actually emit is both
17
+ * sufficient and checkable. The output is inlined into a prompt as text; it is
18
+ * never interpreted as markup, so a mis-parse costs fidelity, not safety.
19
+ */
20
+ /** Upper bound on characters pulled out of one document before we stop. */
21
+ export declare const MAX_EXTRACTED_CHARS = 200000;
22
+ /** Upper bound on PDF pages we will even look at, however long the document. */
23
+ export declare const MAX_PDF_PAGES_SCANNED = 100;
24
+ /**
25
+ * How many characters of PAGE-SPECIFIC text a page must yield before we are
26
+ * willing to say we read it.
27
+ *
28
+ * A scanned page is not empty — its scanner stamps a footer on every page, and
29
+ * `page.trim() !== ''` is therefore true for a document containing no readable
30
+ * content whatsoever. 24 characters is below any real page of a soil report or
31
+ * an audit (those run to thousands) and above the watermarks that defeat an
32
+ * all-or-nothing check.
33
+ */
34
+ export declare const MIN_PAGE_TEXT_CHARS = 24;
35
+ /**
36
+ * A line repeated on at least this share of pages is a running head/footer or a
37
+ * scanner watermark, not content.
38
+ *
39
+ * Used ONLY to judge whether a page carries its own text; the line is never
40
+ * removed from the text handed to the model. So a false positive here costs
41
+ * nothing but a slightly more cautious disclosure, while a false negative is the
42
+ * failure this whole notion exists to stop.
43
+ */
44
+ export declare const BOILERPLATE_PAGE_SHARE = 0.6;
45
+ /**
46
+ * Boilerplate detection only ever applies to short lines, never to content.
47
+ *
48
+ * Named trade-off: a running footer LONGER than this — a full confidentiality
49
+ * paragraph stamped on every page of a scan — is not recognised, and the scan
50
+ * is then treated as readable. That is the safe direction to be wrong in only
51
+ * because the model still sees exactly the text the file contains; it is not a
52
+ * claim the heuristic is exact.
53
+ */
54
+ export declare const MAX_BOILERPLATE_LINE_CHARS = 80;
55
+ /**
56
+ * Below this share of pages carrying a text layer, a document is "mostly
57
+ * images" and must be presented as unread rather than as extracted.
58
+ */
59
+ export declare const MIN_TEXT_PAGE_SHARE = 0.5;
60
+ /**
61
+ * What happened when we tried to read a document.
62
+ *
63
+ * Every value other than `ok` MUST reach the model as a sentence — a document
64
+ * we could not read has to look different from a document that was empty, and
65
+ * both have to look different from one we never tried. Silence here is what
66
+ * makes an assistant answer confidently about a file it never saw.
67
+ */
68
+ export type DocumentExtractionStatus = "ok" | "no-text" | "encrypted" | "corrupt"
69
+ /** No extractor for this type — we did not even try. */
70
+ | "unsupported";
71
+ /** Document formats we can turn into text in the browser. */
72
+ export type DocumentFormat = "pdf" | "docx" | "xlsx";
73
+ export interface DocumentExtraction {
74
+ status: DocumentExtractionStatus;
75
+ /**
76
+ * Which extractor produced this. Carried so the failure wording can be
77
+ * true rather than merely plausible: "appears to be scanned images" is the
78
+ * right sentence for a PDF and a wrong one for an empty .docx.
79
+ */
80
+ format?: DocumentFormat;
81
+ /** Total pages in a PDF (not how many we read — see `pagesScanned`). */
82
+ pageCount?: number;
83
+ /** How many pages we actually looked at; capped by MAX_PDF_PAGES_SCANNED. */
84
+ pagesScanned?: number;
85
+ /** How far into the document `textContent` reaches: pages 1..N were processed. */
86
+ pagesIncluded?: number;
87
+ /**
88
+ * The 1-indexed pages whose text is ACTUALLY in `textContent`.
89
+ *
90
+ * Distinct from `pagesIncluded` on purpose: a 40-page certificate with 3 text
91
+ * pages and 37 image pages processes all 40 and reads 3, and saying "40 pages"
92
+ * there is the exact falsehood this feature must not tell.
93
+ */
94
+ pageNumbers?: number[];
95
+ /**
96
+ * The 1-indexed pages within `pagesScanned` that yielded no page-specific
97
+ * text — image or blank pages. Counted so they can be DISCLOSED rather than
98
+ * silently folded into a page total.
99
+ */
100
+ imagePages?: number[];
101
+ /** How many scanned pages carried a text layer at all. */
102
+ textPageCount?: number;
103
+ /** Characters of text the document yielded before the inline budget cut it. */
104
+ sourceChars?: number;
105
+ /** Worksheet names, in workbook order. XLSX only. */
106
+ sheetNames?: string[];
107
+ /** Parser detail for the `corrupt` case — shown to the model verbatim. */
108
+ detail?: string;
109
+ }
110
+ /** Resolve the five XML entities plus numeric character references. */
111
+ export declare function decodeXmlEntities(value: string): string;
112
+ /**
113
+ * `word/document.xml` → plain text.
114
+ *
115
+ * Word's body is a flat list of `<w:p>` paragraphs holding `<w:t>` runs, with
116
+ * `<w:tab/>` and `<w:br/>` as explicit whitespace. Table cells are paragraphs
117
+ * too, so a table comes out one cell per line — lossy for layout, faithful for
118
+ * content, and the model reads it fine.
119
+ *
120
+ * `<w:instrText>` (field codes such as a HYPERLINK target or a MERGEFIELD name)
121
+ * is deliberately skipped: it is markup the reader never sees, and inlining it
122
+ * puts strings in front of the model that are not in the document.
123
+ */
124
+ export declare function docxXmlToText(xml: string): string;
125
+ /** `xl/sharedStrings.xml` → the string table, indexed as the sheets index it. */
126
+ export declare function parseSharedStrings(xml: string): string[];
127
+ /** `A` → 0, `Z` → 25, `AA` → 26. Returns -1 for anything that is not a column. */
128
+ export declare function columnLetterToIndex(reference: string): number;
129
+ /**
130
+ * One worksheet's XML → CSV-ish rows.
131
+ *
132
+ * Sparse cells are padded from their `r="C7"` reference so column alignment
133
+ * survives, which is the whole reason a spreadsheet is worth inlining at all.
134
+ * Numbers come out exactly as stored — an Excel date is a serial number, and
135
+ * guessing at a display format would put dates in front of the model that the
136
+ * file does not contain.
137
+ */
138
+ export declare function sheetXmlToRows(xml: string, sharedStrings: string[]): string[];
139
+ /**
140
+ * `xl/workbook.xml` (+ its rels) → the sheets in workbook order.
141
+ *
142
+ * The rels file is what maps `r:id="rId3"` to `worksheets/sheet1.xml`; the
143
+ * common assumption that `sheetN.xml` matches tab order is simply false in any
144
+ * workbook whose sheets have been reordered or deleted.
145
+ */
146
+ export declare function parseWorkbookSheets(workbookXml: string, relsXml: string): {
147
+ name: string;
148
+ path: string | null;
149
+ }[];
150
+ /** Stitch the parsed sheets into one document, headed by the sheet name. */
151
+ export declare function joinSheets(sheets: {
152
+ name: string;
153
+ rows: string[];
154
+ }[]): string;
155
+ /**
156
+ * pdf.js text items → one page of text.
157
+ *
158
+ * `hasEOL` is pdf.js' own end-of-line marker, so line structure comes from the
159
+ * library rather than from us guessing at coordinates. Marked-content items
160
+ * carry no `str` and are skipped.
161
+ */
162
+ export declare function pdfTextItemsToPage(items: {
163
+ str?: string;
164
+ hasEOL?: boolean;
165
+ }[]): string;
166
+ /** Header a page gets when several of them are inlined together. */
167
+ export declare function pageMarker(pageNumber: number): string;
168
+ /**
169
+ * Which pages of a PDF actually carry text, and which are images.
170
+ *
171
+ * ## Why an all-or-nothing check is not enough
172
+ *
173
+ * The obvious test — "did ANY page yield a non-empty string" — is defeated by
174
+ * every scanner on the market, because scanners stamp a text footer onto each
175
+ * page image. A 12-page scanned soil report whose only text layer is
176
+ * `Scanned with CamScanner 1` passes that test, and the document is then handed
177
+ * to the model as `Extracted text (12 pages):` with no hint that not one word of
178
+ * the report is in it. The model answers confidently about a file nobody read.
179
+ *
180
+ * So the unit of judgement is the PAGE, and the question per page is whether it
181
+ * holds text SPECIFIC TO IT. Two signals, deliberately both cheap and pure:
182
+ *
183
+ * 1. **Repetition.** A line that appears on most pages, once digits are
184
+ * normalised away so page numbers compare equal, is a running head/footer or
185
+ * a scan watermark. Only short lines are eligible, so this can never strike
186
+ * out a paragraph of content.
187
+ * 2. **Volume.** What is left after (1) must reach `MIN_PAGE_TEXT_CHARS`.
188
+ *
189
+ * Boilerplate is subtracted only for this JUDGEMENT — the text handed to the
190
+ * model is untouched, so mis-classifying a sparse page costs a more cautious
191
+ * sentence rather than lost content.
192
+ */
193
+ export interface PageTextSummary {
194
+ /** 1-indexed pages carrying page-specific text. */
195
+ textPages: number[];
196
+ /** 1-indexed pages carrying nothing but blanks or repeated boilerplate. */
197
+ imagePages: number[];
198
+ /** The normalised lines judged to be running heads/footers or watermarks. */
199
+ boilerplate: string[];
200
+ }
201
+ export declare function summarisePageText(pages: string[]): PageTextSummary;
202
+ /**
203
+ * Is this document mostly images — i.e. must it be presented as UNREAD?
204
+ *
205
+ * Takes the counts rather than the extraction so it stays pure and is the same
206
+ * function whether it is asked at extraction time or at prompt-building time.
207
+ */
208
+ export declare function isMostlyImagePages(textPageCount: number, pagesScanned: number): boolean;
209
+ /**
210
+ * Everything `extractPdf` does once pdf.js has handed over the page strings.
211
+ *
212
+ * Pulled out of the browser half deliberately: pdf.js needs a DOM and this
213
+ * repo's vitest is `environment: 'node'`, so leaving the scanned-vs-read
214
+ * decision inside `extractPdf` left the most dangerous logic in the feature —
215
+ * "may the model treat this document as read" — with no test that could reach
216
+ * it. Here it is a string-array-in, extraction-out function.
217
+ */
218
+ export interface PdfPageFit {
219
+ extraction: DocumentExtraction;
220
+ textContent?: string;
221
+ documentPages?: string[];
222
+ textTruncated?: boolean;
223
+ }
224
+ export declare function fitPdfPages(pages: string[], pageCount: number, pagesScanned: number, limit: number): PdfPageFit;
225
+ /** `[1,2,3,7,9,10]` → `"1-3, 7, 9-10"`. Empty in, empty out. */
226
+ export declare function formatPageRanges(pages: number[]): string;
227
+ export interface FittedPages {
228
+ text: string;
229
+ truncated: boolean;
230
+ /** How far into the document `text` reaches: pages 1..N were processed. */
231
+ pagesIncluded: number;
232
+ /** The 1-indexed pages whose text is actually in `text`. */
233
+ pageNumbers: number[];
234
+ /** Rendered length of every page that HAS text, before the cut. */
235
+ sourceChars: number;
236
+ /**
237
+ * `pages` trimmed to exactly what `text` holds, indices preserved.
238
+ *
239
+ * This is what makes the per-file cap an INVARIANT rather than a hope: the
240
+ * caller stores this array, so a later re-fit against the shared budget can
241
+ * only ever shrink it. Storing the untruncated pages instead let the shared
242
+ * pass re-expand a document to 20,000 characters — 2.5x the per-file cap —
243
+ * and starve the file attached beside it.
244
+ */
245
+ pages: string[];
246
+ }
247
+ /**
248
+ * Fit whole PDF pages into a character budget, preferring a PAGE boundary.
249
+ *
250
+ * A page is the unit a reader — and a citation — thinks in, so cutting mid-page
251
+ * is a worse lie than dropping the tail entirely. Falls back to a hard character
252
+ * cut only when the first page with text will not fit on its own, because
253
+ * "0 pages included" is useless to the model.
254
+ *
255
+ * Blank entries are pages the caller has judged to carry no text (see
256
+ * `summarisePageText`). They are skipped rather than rendered as an empty
257
+ * `[page N]`, which would both waste budget and read to the model as a page it
258
+ * had seen and found empty.
259
+ */
260
+ export declare function truncatePagesForInline(pages: string[], limit: number): FittedPages;
261
+ //# sourceMappingURL=documentText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentText.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/documentText.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,235 @@
1
+ //#region src/shared/assistant/documentText.ts
2
+ var e = 2e5, t = 100, n = 24, r = .6, i = 80, a = .5, o = {
3
+ amp: "&",
4
+ lt: "<",
5
+ gt: ">",
6
+ quot: "\"",
7
+ apos: "'"
8
+ };
9
+ function s(e) {
10
+ return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (e, t) => {
11
+ if (t.startsWith("#x") || t.startsWith("#X")) {
12
+ let n = Number.parseInt(t.slice(2), 16);
13
+ return Number.isFinite(n) ? String.fromCodePoint(n) : e;
14
+ }
15
+ if (t.startsWith("#")) {
16
+ let n = Number.parseInt(t.slice(1), 10);
17
+ return Number.isFinite(n) ? String.fromCodePoint(n) : e;
18
+ }
19
+ return o[t] ?? e;
20
+ });
21
+ }
22
+ function c(e) {
23
+ return e.split("\n").map((e) => e.replace(/[ \t]+$/, "")).join("\n").replace(/\n{3,}/g, "\n\n").trim();
24
+ }
25
+ function l(e) {
26
+ let t = e.replace(/<w:instrText[\s\S]*?<\/w:instrText>/g, ""), n = [], r = "", i = /<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>|<w:tab\b[^>]*\/?>|<w:br\b[^>]*\/?>|<\/w:p>/g, a;
27
+ for (; (a = i.exec(t)) !== null;) {
28
+ let [e, t] = a;
29
+ t === void 0 ? e.startsWith("<w:tab") ? r += " " : (e.startsWith("<w:br"), n.push(r), r = "") : r += s(t);
30
+ }
31
+ return r && n.push(r), c(n.join("\n"));
32
+ }
33
+ function u(e) {
34
+ return (e.match(/<si\b[^>]*\/>|<si\b[^>]*>[\s\S]*?<\/si>/g) ?? []).map((e) => (e.match(/<t(?:\s[^>]*)?>([\s\S]*?)<\/t>/g) ?? []).map((e) => s(e.replace(/<t(?:\s[^>]*)?>|<\/t>/g, ""))).join(""));
35
+ }
36
+ function d(e) {
37
+ let t = /^([A-Z]+)/.exec(e.toUpperCase());
38
+ if (!t) return -1;
39
+ let n = 0;
40
+ for (let e of t[1]) n = n * 26 + (e.charCodeAt(0) - 64);
41
+ return n - 1;
42
+ }
43
+ function f(e) {
44
+ return /[",\n]/.test(e) ? `"${e.replace(/"/g, "\"\"")}"` : e;
45
+ }
46
+ function p(e, t) {
47
+ let n = [], r = /<row\b[^>]*\/>|<row\b[^>]*>([\s\S]*?)<\/row>/g, i;
48
+ for (; (i = r.exec(e)) !== null;) {
49
+ let e = i[1] ?? "", r = [], a = /<c\b([^>]*?)(?:\/>|>([\s\S]*?)<\/c>)/g, o;
50
+ for (; (o = a.exec(e)) !== null;) {
51
+ let e = o[1] ?? "", n = o[2] ?? "", i = /\bt="([^"]+)"/.exec(e)?.[1] ?? "n", a = /\br="([^"]+)"/.exec(e)?.[1] ?? "", c;
52
+ if (i === "s") {
53
+ let e = /<v(?:\s[^>]*)?>([\s\S]*?)<\/v>/.exec(n)?.[1] ?? "";
54
+ c = t[Number.parseInt(e, 10)] ?? "";
55
+ } else if (i === "inlineStr") c = (n.match(/<t(?:\s[^>]*)?>([\s\S]*?)<\/t>/g) ?? []).map((e) => s(e.replace(/<t(?:\s[^>]*)?>|<\/t>/g, ""))).join("");
56
+ else {
57
+ let e = /<v(?:\s[^>]*)?>([\s\S]*?)<\/v>/.exec(n)?.[1] ?? "";
58
+ c = i === "b" ? e === "1" ? "TRUE" : e === "0" ? "FALSE" : "" : s(e);
59
+ }
60
+ let l = d(a);
61
+ if (l >= 0) {
62
+ for (; r.length < l;) r.push("");
63
+ r[l] = c;
64
+ } else r.push(c);
65
+ }
66
+ for (; r.length > 0 && r[r.length - 1] === "";) r.pop();
67
+ n.push(r.map(f).join(","));
68
+ }
69
+ for (; n.length > 0 && n[n.length - 1] === "";) n.pop();
70
+ return n;
71
+ }
72
+ function m(e, t) {
73
+ let n = /* @__PURE__ */ new Map();
74
+ for (let e of t.match(/<Relationship\b[^>]*\/?>/g) ?? []) {
75
+ let t = /\bId="([^"]+)"/.exec(e)?.[1], r = /\bTarget="([^"]+)"/.exec(e)?.[1];
76
+ if (!t || !r) continue;
77
+ let i = r.replace(/^\/xl\//, "").replace(/^\.\//, "");
78
+ n.set(t, i.startsWith("xl/") ? i : `xl/${i}`);
79
+ }
80
+ return (e.match(/<sheet\b[^>]*\/?>/g) ?? []).map((e) => {
81
+ let t = s(/\bname="([^"]*)"/.exec(e)?.[1] ?? "Sheet"), r = /\br:id="([^"]+)"/.exec(e)?.[1] ?? "";
82
+ return {
83
+ name: t,
84
+ path: n.get(r) ?? null
85
+ };
86
+ });
87
+ }
88
+ function h(e) {
89
+ return c(e.filter((e) => e.rows.length > 0).map((e) => `## Sheet: ${e.name}\n${e.rows.join("\n")}`).join("\n\n"));
90
+ }
91
+ function g(e) {
92
+ let t = "";
93
+ for (let n of e) typeof n.str == "string" && (t += n.str, n.hasEOL && (t += "\n"));
94
+ return c(t);
95
+ }
96
+ function _(e) {
97
+ return `[page ${e}]`;
98
+ }
99
+ var v = /^\s*(?:page|p\.?|pg\.?)?\s*\d+(?:\s*(?:of|\/)\s*\d+)?\s*(?:[-–—|]\s*)?/i, y = /\s*(?:[-–—|]\s*)?(?:page|p\.?|pg\.?)?\s*\d+(?:\s*(?:of|\/)\s*\d+)?\s*$/i;
100
+ function b(e) {
101
+ let t = e.replace(/\s+/g, " ").trim().toLowerCase();
102
+ return (t.replace(v, "") || t).replace(y, "").trim();
103
+ }
104
+ function x(e) {
105
+ let t = e.map((e) => {
106
+ let t = /* @__PURE__ */ new Set();
107
+ for (let n of e.split("\n")) {
108
+ let e = b(n);
109
+ e !== "" && t.add(e);
110
+ }
111
+ return t;
112
+ }), n = /* @__PURE__ */ new Map();
113
+ for (let e of t) for (let t of e) n.set(t, (n.get(t) ?? 0) + 1);
114
+ let i = Math.max(2, Math.ceil(e.length * r)), a = /* @__PURE__ */ new Set();
115
+ for (let [e, t] of n) t >= i && e.length <= 80 && a.add(e);
116
+ let o = [], s = [];
117
+ return e.forEach((e, t) => {
118
+ (e.split("\n").filter((e) => {
119
+ let t = b(e);
120
+ return t !== "" && !a.has(t);
121
+ }).join("\n").trim().length >= 24 ? o : s).push(t + 1);
122
+ }), {
123
+ textPages: o,
124
+ imagePages: s,
125
+ boilerplate: [...a]
126
+ };
127
+ }
128
+ function S(e, t) {
129
+ return t <= 0 ? !1 : e < t * a;
130
+ }
131
+ function C(e, t, n, r) {
132
+ let i = x(e);
133
+ if (i.textPages.length === 0) return { extraction: {
134
+ status: "no-text",
135
+ pageCount: t,
136
+ pagesScanned: n
137
+ } };
138
+ let a = new Set(i.textPages), o = T(e.map((e, t) => a.has(t + 1) ? e : ""), r);
139
+ return o.text === "" ? { extraction: {
140
+ status: "no-text",
141
+ pageCount: t,
142
+ pagesScanned: n
143
+ } } : {
144
+ extraction: {
145
+ status: "ok",
146
+ pageCount: t,
147
+ pagesScanned: n,
148
+ pagesIncluded: o.pagesIncluded,
149
+ pageNumbers: o.pageNumbers,
150
+ imagePages: i.imagePages,
151
+ textPageCount: i.textPages.length,
152
+ sourceChars: o.sourceChars
153
+ },
154
+ textContent: o.text,
155
+ documentPages: o.pages,
156
+ textTruncated: o.truncated || n < t
157
+ };
158
+ }
159
+ function w(e) {
160
+ let t = [...new Set(e)].sort((e, t) => e - t), n = [], r = null, i = 0, a = () => {
161
+ r !== null && n.push(r === i ? `${r}` : `${r}-${i}`);
162
+ };
163
+ for (let e of t) {
164
+ if (r === null) {
165
+ r = i = e;
166
+ continue;
167
+ }
168
+ if (e === i + 1) {
169
+ i = e;
170
+ continue;
171
+ }
172
+ a(), r = i = e;
173
+ }
174
+ return a(), n.join(", ");
175
+ }
176
+ function T(e, t) {
177
+ let n = (e, t) => `${_(t + 1)}\n${e}`, r = e.map((e, t) => ({
178
+ index: t,
179
+ page: e,
180
+ rendered: n(e, t)
181
+ })).filter((e) => e.page.trim() !== ""), i = r.map((e) => e.rendered).join("\n\n").length;
182
+ if (r.length === 0) return {
183
+ text: "",
184
+ truncated: !1,
185
+ pagesIncluded: e.length,
186
+ pageNumbers: [],
187
+ sourceChars: 0,
188
+ pages: []
189
+ };
190
+ let a = [], o = 0, s = 0, c = !1;
191
+ for (let r = 0; r < e.length; r += 1) {
192
+ if (e[r].trim() === "") {
193
+ s = r + 1;
194
+ continue;
195
+ }
196
+ let i = n(e[r], r), l = a.length === 0 ? i.length : i.length + 2;
197
+ if (o + l > t) {
198
+ c = !0;
199
+ break;
200
+ }
201
+ a.push({
202
+ index: r,
203
+ page: e[r],
204
+ rendered: i
205
+ }), o += l, s = r + 1;
206
+ }
207
+ if (a.length > 0) return {
208
+ text: a.map((e) => e.rendered).join("\n\n"),
209
+ truncated: c,
210
+ pagesIncluded: s,
211
+ pageNumbers: a.map((e) => e.index + 1),
212
+ sourceChars: i,
213
+ pages: e.slice(0, s)
214
+ };
215
+ let l = r[0], u = l.rendered.slice(0, Math.max(0, t)), d = u.lastIndexOf("\n"), f = d > t * .5 ? u.slice(0, d) : u, p = f.slice(_(l.index + 1).length + 1);
216
+ if (p.trim() === "") return {
217
+ text: "",
218
+ truncated: !0,
219
+ pagesIncluded: 0,
220
+ pageNumbers: [],
221
+ sourceChars: i,
222
+ pages: []
223
+ };
224
+ let m = e.slice(0, l.index + 1);
225
+ return m[l.index] = p, {
226
+ text: f,
227
+ truncated: !0,
228
+ pagesIncluded: l.index + 1,
229
+ pageNumbers: [l.index + 1],
230
+ sourceChars: i,
231
+ pages: m
232
+ };
233
+ }
234
+ //#endregion
235
+ export { r as BOILERPLATE_PAGE_SHARE, i as MAX_BOILERPLATE_LINE_CHARS, e as MAX_EXTRACTED_CHARS, t as MAX_PDF_PAGES_SCANNED, n as MIN_PAGE_TEXT_CHARS, a as MIN_TEXT_PAGE_SHARE, d as columnLetterToIndex, s as decodeXmlEntities, l as docxXmlToText, C as fitPdfPages, w as formatPageRanges, S as isMostlyImagePages, h as joinSheets, _ as pageMarker, u as parseSharedStrings, m as parseWorkbookSheets, g as pdfTextItemsToPage, p as sheetXmlToRows, x as summarisePageText, T as truncatePagesForInline };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Hard limits imposed by the gateways this app talks to.
3
+ *
4
+ * These are not our numbers — they are deployment facts, and getting them wrong
5
+ * is expensive because the failure is opaque. `wspace-public-gateway` sets
6
+ * `MAX_REQUEST_BODY_SIZE: 256kb` in BOTH environments (burdenoff-gitops,
7
+ * `apps/config-workspace/wspace-public-gateway-config.yaml:21` and
8
+ * `apps/config-workspace-prod/…:21`). It compares Content-Length and answers
9
+ * 413 *before* authenticating and before forwarding, and fe-libs' `graphqlFetch`
10
+ * discards the status — so the user is told "The request could not be processed.
11
+ * Please refresh and try again." with nothing in it to suggest the payload was
12
+ * simply too big.
13
+ *
14
+ * Anything that POSTs base64 from the browser has to be sized against this.
15
+ */
16
+ export declare const MAX_REQUEST_BODY_BYTES = 262144;
17
+ /**
18
+ * What is left for a base64 image once the rest of a GraphQL request is paid
19
+ * for: the query document, operation name, variables, and the workspace/org
20
+ * context envelope. 32 KB is deliberately generous — the point is to fail in
21
+ * OUR code with a sentence the user can act on, rather than at the gateway with
22
+ * one they cannot.
23
+ */
24
+ export declare const MAX_INLINE_IMAGE_BASE64_BYTES: number;
25
+ //# sourceMappingURL=gatewayLimits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatewayLimits.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/gatewayLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,QAAkC,CAAC"}
@@ -0,0 +1,4 @@
1
+ //#region src/shared/assistant/gatewayLimits.ts
2
+ var e = 262144, t = e - 32768;
3
+ //#endregion
4
+ export { t as MAX_INLINE_IMAGE_BASE64_BYTES, e as MAX_REQUEST_BODY_BYTES };
@@ -63,4 +63,10 @@ export * from './i18n';
63
63
  export * from './voice/speechLabels';
64
64
  export * from './voice/replyLanguage';
65
65
  export * from './voice/speechPlayback';
66
+ export * from './conversation';
67
+ export * from './attachments';
68
+ export * from './store';
69
+ export * from './turnState';
70
+ export * from './gatewayLimits';
71
+ export * from './documentText';
66
72
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,114 @@
1
+ import { AssistantConversationProcessing, AssistantConversationStatus, AssistantMessageMetadata, AssistantSandboxRuntime, AssistantSandboxStatus, Conversation, Message } from './conversation';
2
+ import { AssistantMode } from './types';
3
+ interface AssistantState {
4
+ conversations: Conversation[];
5
+ activeConversationId: string | null;
6
+ mode: AssistantMode;
7
+ processingByConversationId: Record<string, AssistantConversationProcessing>;
8
+ conversationListOpen: boolean;
9
+ /** Active sandboxes keyed by assistant mode */
10
+ sandboxesByMode: Partial<Record<AssistantMode, AssistantSandboxRuntime>>;
11
+ /**
12
+ * A prompt queued up for auto-submit by the chat area. Used by the
13
+ * `GetHelpButton` / `useOpenAssistantHelp` hook so pages can prompt the
14
+ * assistant contextually from an empty state button without coupling to
15
+ * the chat area internals.
16
+ */
17
+ pendingPrompt: string | null;
18
+ /**
19
+ * Whether the user has acknowledged the api-calls (write) mode consent
20
+ * dialog. Persisted per-profile so the warning shows once, not every switch.
21
+ */
22
+ apiCallsAcknowledged: boolean;
23
+ /**
24
+ * Read every assistant reply aloud as it settles (BOFF-6290).
25
+ *
26
+ * Persisted per-profile and **false by default**: speech is opt-in, and the
27
+ * assistant must never start talking without an explicit prior choice. A
28
+ * one-off "read this message" needs no preference at all — it is the speak
29
+ * button on the message.
30
+ */
31
+ autoSpeakEnabled: boolean;
32
+ /**
33
+ * The language the user speaks TO the assistant, independent of the interface
34
+ * language (BOFF-7108). `null` means "follow the app locale", which is what
35
+ * every user gets until they choose otherwise — and is exactly the behaviour
36
+ * that existed before this preference did.
37
+ */
38
+ assistantLanguage: string | null;
39
+ /**
40
+ * The language the assistant ANSWERS in. `null` means "match my language" —
41
+ * i.e. follow `assistantLanguage` — which is the default and what shipped
42
+ * before the two were separated.
43
+ */
44
+ replyLanguage: string | null;
45
+ /**
46
+ * The voice the user picked, keyed by language code (BOFF-7110).
47
+ *
48
+ * Per language, because a voice only reads one: picking "Google US English"
49
+ * says nothing about which Tamil voice to use. Stored by NAME because that is
50
+ * the only identifier stable across page loads — voice objects are recreated
51
+ * each time and `voiceURI` is not reliably unique across platforms.
52
+ *
53
+ * A name saved on one device will not resolve on another; that is handled by
54
+ * falling back to the ranking rather than by trying to sync it.
55
+ */
56
+ preferredVoices: Record<string, string>;
57
+ createConversation: (title?: string, mode?: AssistantMode) => string;
58
+ deleteConversation: (id: string) => void;
59
+ renameConversation: (id: string, title: string) => void;
60
+ setActiveConversation: (id: string | null) => void;
61
+ setMode: (mode: AssistantMode) => void;
62
+ setConversationMode: (conversationId: string, mode: AssistantMode) => void;
63
+ addMessage: (conversationId: string, message: Message) => void;
64
+ updateMessage: (conversationId: string, messageId: string, content: string) => void;
65
+ patchMessage: (conversationId: string, messageId: string, patch: Partial<Message>) => void;
66
+ /**
67
+ * MERGE into a message's metadata, leaving untouched keys alone.
68
+ *
69
+ * ★ The counterpart to `patchMessage`, which REPLACES `metadata` wholesale
70
+ * (`{...m, ...patch}`). That has already shipped one defect: a write which
71
+ * rebuilt the object dropped `voice`, so a spoken reply silently became a
72
+ * typed one. Anything adding a SINGLE metadata key should use this, or it
73
+ * takes `lang` and `voice` down with it.
74
+ */
75
+ mergeMessageMetadata: (conversationId: string, messageId: string, patch: Partial<AssistantMessageMetadata>) => void;
76
+ setConversationProcessing: (conversationId: string, status: AssistantConversationStatus, messageId?: string | null, error?: string | null) => void;
77
+ clearConversationProcessing: (conversationId: string) => void;
78
+ toggleConversationList: () => void;
79
+ setConversationListOpen: (open: boolean) => void;
80
+ clearConversation: (id: string) => void;
81
+ setBackendSessionId: (conversationId: string, sessionId: string) => void;
82
+ clearBackendSessionId: (conversationId: string) => void;
83
+ setSandbox: (mode: AssistantMode, sandboxId: string | null, status: AssistantSandboxStatus) => void;
84
+ clearSandbox: (mode: AssistantMode) => void;
85
+ resetStore: () => void;
86
+ /** Queue a prompt for the chat area to auto-send on next render. */
87
+ submitPrompt: (text: string) => void;
88
+ /** Returns and clears any queued prompt. */
89
+ consumePendingPrompt: () => string | null;
90
+ /** Mark the api-calls write-mode consent as acknowledged (persisted). */
91
+ acknowledgeApiCalls: () => void;
92
+ /** Turn automatic read-aloud of new replies on or off (persisted). */
93
+ setAutoSpeak: (enabled: boolean) => void;
94
+ /** Choose the language spoken to the assistant; `null` follows the app locale (persisted). */
95
+ setAssistantLanguage: (code: string | null) => void;
96
+ setReplyLanguage: (code: string | null) => void;
97
+ /** `null` clears the pick for that language, restoring the ranked default. */
98
+ setPreferredVoice: (languageCode: string, voiceName: string | null) => void;
99
+ }
100
+ export declare const useAssistantStore: import('zustand').UseBoundStore<Omit<import('zustand').StoreApi<AssistantState>, "setState" | "persist"> & {
101
+ setState(partial: AssistantState | Partial<AssistantState> | ((state: AssistantState) => AssistantState | Partial<AssistantState>), replace?: false | undefined): unknown;
102
+ setState(state: AssistantState | ((state: AssistantState) => AssistantState), replace: true): unknown;
103
+ persist: {
104
+ setOptions: (options: Partial<import('zustand/middleware').PersistOptions<AssistantState, unknown, unknown>>) => void;
105
+ clearStorage: () => void;
106
+ rehydrate: () => Promise<void> | void;
107
+ hasHydrated: () => boolean;
108
+ onHydrate: (fn: (state: AssistantState) => void) => () => void;
109
+ onFinishHydration: (fn: (state: AssistantState) => void) => () => void;
110
+ getOptions: () => Partial<import('zustand/middleware').PersistOptions<AssistantState, unknown, unknown>>;
111
+ };
112
+ }>;
113
+ export {};
114
+ //# sourceMappingURL=store.d.ts.map