@gmickel/gno 1.45.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/skill/cli-reference.md +14 -6
  5. package/assets/skill/mcp-reference.md +4 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  10. package/browser-extension/dist/manifest.json +1 -1
  11. package/browser-extension/dist/preview.html +1 -1
  12. package/browser-extension/dist/service-worker.js +32 -33
  13. package/bunfig.toml +2 -0
  14. package/package.json +40 -26
  15. package/spec/cli.md +30 -11
  16. package/spec/db/schema.sql +146 -1
  17. package/spec/mcp.md +26 -0
  18. package/src/app/context-runtime-types.ts +3 -0
  19. package/src/app/context-runtime.ts +2 -0
  20. package/src/cli/commands/ask.ts +6 -1
  21. package/src/cli/commands/daemon.ts +21 -8
  22. package/src/cli/commands/embed.ts +77 -41
  23. package/src/cli/commands/mcp/install.ts +20 -0
  24. package/src/cli/commands/mcp/paths.ts +25 -0
  25. package/src/cli/commands/mcp/status.ts +6 -0
  26. package/src/cli/detach.ts +3 -2
  27. package/src/cli/program.ts +6 -0
  28. package/src/config/types.ts +3 -3
  29. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  30. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  31. package/src/converters/versions.ts +6 -8
  32. package/src/core/context-evidence.ts +8 -4
  33. package/src/core/job-manager.ts +95 -13
  34. package/src/core/network-boundary-inventory.ts +10 -0
  35. package/src/core/shutdown-budget.ts +45 -0
  36. package/src/embed/backlog.ts +107 -4
  37. package/src/embed/batch.ts +42 -2
  38. package/src/embed/fingerprint.ts +16 -0
  39. package/src/embed/retry.ts +113 -5
  40. package/src/embed/variant-backlog.ts +105 -0
  41. package/src/embed/variant-plan.ts +62 -0
  42. package/src/embed/variant-retry.ts +113 -0
  43. package/src/ingestion/graph-reconciliation.ts +327 -0
  44. package/src/ingestion/sync.ts +9 -272
  45. package/src/llm/http-inference.ts +6 -0
  46. package/src/llm/httpEmbedding.ts +37 -6
  47. package/src/llm/httpGeneration.ts +18 -3
  48. package/src/llm/httpRerank.ts +23 -5
  49. package/src/llm/inference-cancellation.ts +168 -0
  50. package/src/llm/inference-scope.ts +202 -0
  51. package/src/llm/lazy-ports.ts +115 -0
  52. package/src/llm/native-worker/client.ts +541 -0
  53. package/src/llm/native-worker/dispatcher.ts +228 -0
  54. package/src/llm/native-worker/embedding-identity.ts +33 -0
  55. package/src/llm/native-worker/entry.ts +173 -0
  56. package/src/llm/native-worker/errors.ts +32 -0
  57. package/src/llm/native-worker/evaluation.ts +16 -0
  58. package/src/llm/native-worker/owned-exit.ts +108 -0
  59. package/src/llm/native-worker/owner.ts +141 -0
  60. package/src/llm/native-worker/ports.ts +317 -0
  61. package/src/llm/native-worker/protocol.ts +442 -0
  62. package/src/llm/native-worker/runtime-config.ts +92 -0
  63. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  64. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  65. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  66. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  67. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  68. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  69. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  70. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  71. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  72. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  73. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  74. package/src/llm/types.ts +35 -5
  75. package/src/mcp/context.ts +27 -0
  76. package/src/mcp/http-transport.ts +12 -10
  77. package/src/mcp/server.ts +3 -0
  78. package/src/mcp/tool-profile.ts +30 -8
  79. package/src/mcp/tools/context.ts +8 -11
  80. package/src/mcp/tools/embed.ts +1 -1
  81. package/src/mcp/tools/index-cmd.ts +1 -1
  82. package/src/mcp/tools/index.ts +10 -8
  83. package/src/mcp/tools/query.ts +14 -30
  84. package/src/mcp/tools/vsearch.ts +1 -1
  85. package/src/pipeline/answer.ts +23 -3
  86. package/src/pipeline/claim-verifier.ts +6 -0
  87. package/src/pipeline/expansion.ts +43 -40
  88. package/src/pipeline/explain.ts +6 -2
  89. package/src/pipeline/filters.ts +63 -0
  90. package/src/pipeline/fusion.ts +29 -9
  91. package/src/pipeline/graph-retrieval.ts +29 -9
  92. package/src/pipeline/hybrid.ts +198 -55
  93. package/src/pipeline/hydration.ts +161 -0
  94. package/src/pipeline/owner-fusion.ts +87 -0
  95. package/src/pipeline/rerank.ts +35 -11
  96. package/src/pipeline/search.ts +13 -2
  97. package/src/pipeline/types.ts +5 -3
  98. package/src/pipeline/vsearch.ts +87 -7
  99. package/src/sdk/client.ts +47 -3
  100. package/src/sdk/embed.ts +63 -39
  101. package/src/serve/background-runtime.ts +1 -1
  102. package/src/serve/context.ts +41 -56
  103. package/src/serve/embed-scheduler.ts +58 -35
  104. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  105. package/src/serve/public/globals.built.css +1 -1
  106. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  107. package/src/serve/resident-admission.ts +36 -36
  108. package/src/serve/resident-background-work.ts +20 -2
  109. package/src/serve/resident-request.ts +11 -5
  110. package/src/serve/resident-runtime.ts +97 -61
  111. package/src/serve/resident-shutdown.ts +153 -0
  112. package/src/serve/routes/api.ts +3 -1
  113. package/src/serve/server.ts +47 -26
  114. package/src/store/migrations/028-vector-variants.ts +54 -0
  115. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  116. package/src/store/migrations/index.ts +4 -0
  117. package/src/store/sqlite/adapter.ts +251 -183
  118. package/src/store/sqlite/eligibility.ts +174 -0
  119. package/src/store/sqlite/graph-edge-application.ts +66 -0
  120. package/src/store/sqlite/graph-reference-state.ts +194 -0
  121. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  122. package/src/store/types.ts +80 -12
  123. package/src/store/vector/eligibility.ts +36 -0
  124. package/src/store/vector/freshness.ts +33 -6
  125. package/src/store/vector/lazy.ts +81 -0
  126. package/src/store/vector/sqlite-vec.ts +106 -54
  127. package/src/store/vector/stats.ts +14 -3
  128. package/src/store/vector/types.ts +35 -2
  129. package/src/store/vector/variant-search.ts +192 -0
  130. package/src/store/vector/variants.ts +451 -0
  131. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  132. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  136. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  137. package/vendor/converters/markitdown-ts/package.json +77 -0
  138. package/vendor/converters/officeparser/LICENSE +21 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  140. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  142. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  144. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  145. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  146. package/vendor/converters/officeparser/dist/cli.js +381 -0
  147. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  148. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  150. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  152. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  154. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  156. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  158. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  160. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  162. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  164. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  166. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  167. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  168. package/vendor/converters/officeparser/dist/index.js +72 -0
  169. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  175. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  177. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  179. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  181. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  183. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  185. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  187. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  189. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  191. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  193. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  195. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  196. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  197. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  198. package/vendor/converters/officeparser/dist/types.js +107 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  200. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  202. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  204. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  206. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  208. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  210. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  212. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  214. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  216. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  218. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  220. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  222. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  224. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  226. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  228. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  229. package/vendor/converters/officeparser/package.json +147 -0
  230. package/vendor/converters/upstream-manifest.json +124 -0
  231. package/vendor/dependency-fixes/README.md +77 -0
  232. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
  234. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
  235. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  236. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -0,0 +1,797 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChunkingGenerator = void 0;
4
+ const defaults_js_1 = require("../defaults.js");
5
+ const types_js_1 = require("../types.js");
6
+ const errorUtils_js_1 = require("../utils/errorUtils.js");
7
+ const BaseGenerator_js_1 = require("./BaseGenerator.js");
8
+ /** Node types whose text is block-level, so a boundary between two of them is a real break. */
9
+ const BLOCK_NODE_TYPES = new Set([
10
+ 'paragraph', 'heading', 'list', 'table', 'row', 'cell', 'code', 'note', 'admonition',
11
+ 'definitionList', 'definitionTerm', 'definitionDescription', 'sheet', 'slide', 'page', 'embed',
12
+ ]);
13
+ /**
14
+ * The visible text of a content node: its own `.text` when set, otherwise its descendants' text,
15
+ * plus any footnote/endnote bodies hanging off `node.notes`.
16
+ * HTML- and Markdown-origin parsers build paragraphs as `{ children: [...] }` with no `.text`, so
17
+ * reading `node.text` alone dropped their content from every chunk. Block-level children are joined
18
+ * with a newline so words don't merge across paragraphs/list items (matching the text generator);
19
+ * inline runs join with no separator. Notes live on `node.notes`, off the children tree, and a
20
+ * content node is emitted as a chunk without recursing into them - so their text was silently
21
+ * absent from the RAG index. Fold them in here (joined as block content) so a footnote's body is
22
+ * searchable alongside the paragraph that references it.
23
+ */
24
+ function collectNodeText(node) {
25
+ let out = '';
26
+ if (typeof node.text === 'string' && node.text.length > 0) {
27
+ out = node.text;
28
+ // The `.text` fast-path above skips the children walk, but DOCX/ODT/RTF set `.text` on the
29
+ // paragraph while the footnote hangs off a nested text child - so its body would be missed.
30
+ // Fold in descendant note bodies (visible text already covered by `.text`, not re-added).
31
+ const descendantNotes = collectDescendantNoteText(node);
32
+ if (descendantNotes)
33
+ out += '\n' + descendantNotes;
34
+ }
35
+ else if (node.children && node.children.length > 0) {
36
+ for (const child of node.children) {
37
+ if (out && BLOCK_NODE_TYPES.has(child.type))
38
+ out += '\n';
39
+ out += collectNodeText(child);
40
+ }
41
+ }
42
+ if (node.notes && node.notes.length > 0) {
43
+ for (const note of node.notes) {
44
+ const noteText = collectNodeText(note);
45
+ if (noteText)
46
+ out += (out ? '\n' : '') + noteText;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * Footnote/endnote bodies hanging off a node's descendants, without the descendants' own visible
53
+ * text (the caller already has that via `.text`). Only reached from the `.text` fast-path above, to
54
+ * recover notes that office-origin parsers attach to a nested text child of a `.text`-bearing node.
55
+ */
56
+ function collectDescendantNoteText(node) {
57
+ if (!node.children || node.children.length === 0)
58
+ return '';
59
+ let out = '';
60
+ for (const child of node.children) {
61
+ if (child.notes) {
62
+ for (const note of child.notes) {
63
+ const t = collectNodeText(note);
64
+ if (t)
65
+ out += (out ? '\n' : '') + t;
66
+ }
67
+ }
68
+ const deeper = collectDescendantNoteText(child);
69
+ if (deeper)
70
+ out += (out ? '\n' : '') + deeper;
71
+ }
72
+ return out;
73
+ }
74
+ /**
75
+ * Generates a list of OfficeChunk objects from an AST for use in RAG pipelines.
76
+ * Supports three strategies: 'fixed-size', 'document-structure', and 'semantic'.
77
+ */
78
+ class ChunkingGenerator extends BaseGenerator_js_1.BaseGenerator {
79
+ /** The resolved chunking config (with defaults applied). */
80
+ chunkConfig;
81
+ /** Whether the user provided an explicit sentence boundary regex. */
82
+ isCustomRegex;
83
+ constructor(ast, config) {
84
+ super('chunks', ast, config);
85
+ this.chunkConfig = this.resolveChunkingConfig(this.config.chunksConfig);
86
+ // Track if the user explicitly provided a regex (vs using the library default)
87
+ this.isCustomRegex = !!config?.chunksConfig?.sentenceBoundaryRegex;
88
+ }
89
+ /**
90
+ * Merges the user's chunking config with the appropriate defaults for the chosen strategy.
91
+ */
92
+ resolveChunkingConfig(userChunkConfig) {
93
+ const strategy = userChunkConfig?.strategy ?? 'document-structure';
94
+ switch (strategy) {
95
+ case 'fixed-size':
96
+ return { ...defaults_js_1.DEFAULT_FIXED_SIZE_CHUNKING_CONFIG, ...userChunkConfig };
97
+ case 'semantic':
98
+ return { ...defaults_js_1.DEFAULT_SEMANTIC_CHUNKING_CONFIG, ...userChunkConfig };
99
+ case 'document-structure':
100
+ return { ...defaults_js_1.DEFAULT_DOCUMENT_STRUCTURE_CHUNKING_CONFIG, ...userChunkConfig };
101
+ }
102
+ }
103
+ /**
104
+ * Main entry point. Routes to the correct strategy implementation.
105
+ * Note: ConversionResult.value is a real OfficeChunk[] array for the 'chunks' destination, not
106
+ * a JSON string. Consumers serialize it to JSON/JSONL themselves.
107
+ */
108
+ async generate() {
109
+ (0, errorUtils_js_1.checkAbortSignal)(this.config.abortSignal);
110
+ let chunks;
111
+ switch (this.chunkConfig.strategy) {
112
+ case 'fixed-size':
113
+ chunks = await this.generateFixedSize(this.chunkConfig);
114
+ break;
115
+ case 'semantic':
116
+ chunks = await this.generateSemantic(this.chunkConfig);
117
+ break;
118
+ case 'document-structure':
119
+ chunks = await this.generateDocumentStructure(this.chunkConfig);
120
+ break;
121
+ }
122
+ return {
123
+ value: chunks,
124
+ messages: this.messages,
125
+ };
126
+ }
127
+ // ─── Strategy 1: Fixed-Size ────────────────────────────────────────────────
128
+ /**
129
+ * Splits the full document text into fixed-size chunks with optional overlap.
130
+ * Attempts to split on natural separators before hard-cutting.
131
+ */
132
+ async generateFixedSize(config) {
133
+ const { chunkSize, chunkOverlap, separators, lengthFunction: measure } = config;
134
+ // Build a flat text with positional map from top-level nodes
135
+ const { text: fullText, nodeMap } = await this.buildFlatTextWithPositions();
136
+ const rawChunks = this.splitTextRecursively(fullText, chunkSize, chunkOverlap, separators, measure);
137
+ return rawChunks.map(({ text, start, end }) => {
138
+ const chunk = { text, metadata: { sourceType: this.ast.type } };
139
+ if (config.includeMetadata ?? true) {
140
+ this.enrichMetadataFromPosition(chunk, nodeMap, start);
141
+ }
142
+ if (config.addStartIndex) {
143
+ chunk.startIndex = start;
144
+ chunk.endIndex = end;
145
+ }
146
+ return chunk;
147
+ });
148
+ }
149
+ /**
150
+ * Recursively tries separators to split text into chunks of at most `chunkSize`,
151
+ * with `chunkOverlap` characters of overlap between consecutive chunks.
152
+ */
153
+ splitTextRecursively(text, chunkSize, chunkOverlap, separators, measure) {
154
+ if (measure(text) <= chunkSize) {
155
+ return text.trim() ? [{ text, start: 0, end: text.length }] : [];
156
+ }
157
+ let chosenSep;
158
+ let nextSeparators = [];
159
+ let parts = [];
160
+ let actualSep = '';
161
+ // 1. Try splitting into sentences first
162
+ const sentences = this.splitIntoSentences(text);
163
+ if (sentences.length > 1) {
164
+ parts = sentences;
165
+ actualSep = ' ';
166
+ chosenSep = 'SENTENCE_SPLIT'; // Internal marker to avoid hard-cut
167
+ nextSeparators = separators;
168
+ }
169
+ else {
170
+ // 2. Fallback to the character separators provided in config
171
+ for (let i = 0; i < separators.length; i++) {
172
+ const sep = separators[i];
173
+ parts = sep ? text.split(sep) : [...text];
174
+ if (parts.length > 1) {
175
+ chosenSep = sep;
176
+ actualSep = sep;
177
+ nextSeparators = separators.slice(i + 1);
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ if (chosenSep === undefined) {
183
+ // Last resort: hard cut
184
+ const results = [];
185
+ let offset = 0;
186
+ while (offset < text.length) {
187
+ const slice = text.slice(offset, offset + chunkSize);
188
+ results.push({ text: slice, start: offset, end: offset + slice.length });
189
+ offset += Math.max(1, chunkSize - chunkOverlap);
190
+ }
191
+ return results;
192
+ }
193
+ const results = [];
194
+ let currentChunk = '';
195
+ let currentStart = 0;
196
+ let absoluteOffset = 0;
197
+ for (let i = 0; i < parts.length; i++) {
198
+ const part = parts[i];
199
+ const candidate = currentChunk ? currentChunk + actualSep + part : part;
200
+ if (measure(candidate) <= chunkSize) {
201
+ currentChunk = candidate;
202
+ }
203
+ else {
204
+ if (currentChunk.trim()) {
205
+ // If currentChunk is still too big (should only happen if it's a single part), recurse!
206
+ if (measure(currentChunk) > chunkSize) {
207
+ const subResults = this.splitTextRecursively(currentChunk, chunkSize, chunkOverlap, nextSeparators, measure);
208
+ for (const r of subResults) {
209
+ results.push({ text: r.text, start: currentStart + r.start, end: currentStart + r.end });
210
+ }
211
+ }
212
+ else {
213
+ results.push({ text: currentChunk, start: currentStart, end: currentStart + currentChunk.length });
214
+ }
215
+ }
216
+ // Start next chunk
217
+ if (chunkOverlap > 0 && currentChunk.length > chunkOverlap && measure(currentChunk) <= chunkSize) {
218
+ const overlapText = currentChunk.slice(-chunkOverlap);
219
+ currentStart = absoluteOffset - chunkOverlap;
220
+ currentChunk = overlapText + actualSep + part;
221
+ }
222
+ else {
223
+ currentStart = absoluteOffset;
224
+ currentChunk = part;
225
+ }
226
+ }
227
+ absoluteOffset += part.length + actualSep.length;
228
+ }
229
+ if (currentChunk.trim()) {
230
+ if (measure(currentChunk) > chunkSize) {
231
+ const subResults = this.splitTextRecursively(currentChunk, chunkSize, chunkOverlap, nextSeparators, measure);
232
+ for (const r of subResults) {
233
+ results.push({ text: r.text, start: currentStart + r.start, end: currentStart + r.end });
234
+ }
235
+ }
236
+ else {
237
+ results.push({ text: currentChunk, start: currentStart, end: currentStart + currentChunk.length });
238
+ }
239
+ }
240
+ return results;
241
+ }
242
+ // ─── Strategy 2: Document Structure ───────────────────────────────────────
243
+ /**
244
+ * Walks the AST and splits at the designated structural boundaries (slide, page, heading, paragraph).
245
+ */
246
+ async generateDocumentStructure(config) {
247
+ const { splitBy, maxChunkSize, lengthFunction: measure } = config;
248
+ const chunks = [];
249
+ const contextStack = {};
250
+ // Walk top-level nodes and decide where to cut
251
+ for (const node of this.ast.content) {
252
+ await this.processNodeForStructure(node, config, splitBy, maxChunkSize, measure, chunks, contextStack);
253
+ }
254
+ return this.finalizeChunks(chunks, config);
255
+ }
256
+ async processNodeForStructure(node, config, splitBy, maxChunkSize, measure, chunks, contextStack) {
257
+ (0, errorUtils_js_1.checkAbortSignal)(this.config.abortSignal);
258
+ // Check for node override or skip
259
+ const override = await this.handleOnNode(node);
260
+ if (override === false)
261
+ return;
262
+ // Update context from structural container nodes
263
+ if (node.type === 'slide') {
264
+ const meta = node.metadata;
265
+ contextStack.slideNumber = meta?.slideNumber;
266
+ }
267
+ else if (node.type === 'page') {
268
+ const meta = node.metadata;
269
+ contextStack.pageNumber = meta?.pageNumber;
270
+ }
271
+ else if (node.type === 'sheet') {
272
+ const meta = node.metadata;
273
+ contextStack.sheetName = meta?.sheetName;
274
+ }
275
+ else if (node.type === 'heading') {
276
+ contextStack.heading = node.text;
277
+ }
278
+ const isStructuralBoundary = this.isStructuralBoundary(node, splitBy);
279
+ const isForcedSplit = splitBy === 'slide' && node.type === 'slide'
280
+ || splitBy === 'page' && node.type === 'page'
281
+ || splitBy === 'sheet' && node.type === 'sheet';
282
+ if (isForcedSplit) {
283
+ // Process children of the container as individual chunks within the boundary
284
+ if (node.children || node.notes) {
285
+ const innerChunks = [];
286
+ if (node.children) {
287
+ for (const child of node.children) {
288
+ await this.processNodeForStructure(child, config, 'paragraph', maxChunkSize, measure, innerChunks, contextStack);
289
+ }
290
+ }
291
+ if (node.notes) {
292
+ for (const note of node.notes) {
293
+ await this.processNodeForStructure(note, config, 'paragraph', maxChunkSize, measure, innerChunks, contextStack);
294
+ }
295
+ }
296
+ for (const ic of innerChunks) {
297
+ ic.metadata.slideNumber = contextStack.slideNumber;
298
+ ic.metadata.pageNumber = contextStack.pageNumber;
299
+ ic.metadata.sheetName = contextStack.sheetName;
300
+ chunks.push(ic);
301
+ }
302
+ }
303
+ return;
304
+ }
305
+ if (node.type === 'table') {
306
+ await this.processTableNode(node, config, maxChunkSize, measure, chunks, contextStack);
307
+ return;
308
+ }
309
+ const isContentNode = node.type === 'paragraph' || node.type === 'heading' || node.type === 'list' || node.type === 'code' || node.type === 'cell' || (node.text && (!node.children || node.children.length === 0));
310
+ if (isStructuralBoundary || isContentNode) {
311
+ const text = typeof override === 'string' ? override : collectNodeText(node);
312
+ const isWhitespaceOnly = !text.trim() && !text.includes('\u00A0');
313
+ if (isWhitespaceOnly && text.length > 0) {
314
+ // Log skipped empty nodes if debugging
315
+ // Only warn for non-cell nodes to reduce spreadsheet noise
316
+ if (node.type !== 'cell') {
317
+ this.warn(types_js_1.OfficeWarningType.WHITESPACE_NODE_SKIPPED, node.type, node);
318
+ }
319
+ return;
320
+ }
321
+ if (node.type === 'heading')
322
+ contextStack.heading = text;
323
+ const chunk = {
324
+ text,
325
+ metadata: {
326
+ sourceType: this.ast.type,
327
+ closestHeading: contextStack.heading,
328
+ slideNumber: contextStack.slideNumber,
329
+ pageNumber: contextStack.pageNumber,
330
+ sheetName: contextStack.sheetName,
331
+ },
332
+ };
333
+ // If this chunk is too big, further split it
334
+ if (measure(text) > maxChunkSize) {
335
+ const subChunks = this.splitTextRecursively(text, maxChunkSize, 0, ['\n\n', '\n', ' ', ''], measure);
336
+ for (const sub of subChunks) {
337
+ chunks.push({ ...chunk, text: sub.text });
338
+ }
339
+ }
340
+ else {
341
+ chunks.push(chunk);
342
+ }
343
+ return;
344
+ }
345
+ // Recurse into children and notes for container nodes
346
+ if (node.children) {
347
+ for (const child of node.children) {
348
+ await this.processNodeForStructure(child, config, splitBy, maxChunkSize, measure, chunks, contextStack);
349
+ }
350
+ }
351
+ if (node.notes) {
352
+ for (const note of node.notes) {
353
+ await this.processNodeForStructure(note, config, splitBy, maxChunkSize, measure, chunks, contextStack);
354
+ }
355
+ }
356
+ }
357
+ isStructuralBoundary(node, splitBy) {
358
+ if (splitBy === 'heading')
359
+ return node.type === 'heading';
360
+ if (splitBy === 'paragraph')
361
+ return node.type === 'paragraph' || node.type === 'heading' || node.type === 'cell';
362
+ return false;
363
+ }
364
+ /**
365
+ * Handles table chunking with the configured tableSplitStrategy.
366
+ * 'row': keeps header row attached to every chunk.
367
+ * 'flatten': converts table to text and splits normally.
368
+ */
369
+ async processTableNode(node, config, maxChunkSize, measure, chunks, contextStack) {
370
+ const strategy = config.tableSplitStrategy;
371
+ if (strategy === 'flatten' || !node.children || node.children.length === 0) {
372
+ // Flatten: treat as plain text (collect from children for HTML/MD-origin tables).
373
+ const text = collectNodeText(node);
374
+ if (!text.trim())
375
+ return;
376
+ chunks.push({
377
+ text,
378
+ metadata: {
379
+ sourceType: this.ast.type,
380
+ closestHeading: contextStack.heading,
381
+ slideNumber: contextStack.slideNumber,
382
+ pageNumber: contextStack.pageNumber,
383
+ sheetName: contextStack.sheetName,
384
+ },
385
+ });
386
+ return;
387
+ }
388
+ // 'row' strategy: extract header row(s) and chunk remaining rows
389
+ const rows = node.children; // Each child is a 'row' node
390
+ const headerRows = [];
391
+ const dataRows = [];
392
+ // Heuristic: first row is the header
393
+ if (rows.length > 0) {
394
+ const firstRow = rows[0];
395
+ const override = await this.handleOnNode(firstRow);
396
+ if (override !== false) {
397
+ // If overridden, we use the string as header, but we still treat it as a header
398
+ headerRows.push(firstRow); // We still add the node to get metadata later if needed, but renderRowsAsText will handle it?
399
+ // Actually renderRowsAsText needs to be updated too.
400
+ }
401
+ for (let i = 1; i < rows.length; i++) {
402
+ dataRows.push(rows[i]);
403
+ }
404
+ }
405
+ const headerText = await this.renderRowsAsText(headerRows);
406
+ const baseMetadata = {
407
+ sourceType: this.ast.type,
408
+ closestHeading: contextStack.heading,
409
+ slideNumber: contextStack.slideNumber,
410
+ pageNumber: contextStack.pageNumber,
411
+ sheetName: contextStack.sheetName,
412
+ isTableChunk: true,
413
+ };
414
+ // Group data rows into chunks
415
+ let currentRows = [];
416
+ let currentSize = headerText ? measure(headerText) : 0;
417
+ const flushCurrentRows = async () => {
418
+ if (currentRows.length === 0)
419
+ return;
420
+ const rowText = await this.renderRowsAsText(currentRows);
421
+ const chunkText = headerText ? `${headerText}\n${rowText}` : rowText;
422
+ if (chunkText.trim()) {
423
+ if (measure(chunkText) > maxChunkSize) {
424
+ // Fallback to recursive splitting for oversized table chunks
425
+ const subChunks = this.splitTextRecursively(chunkText, maxChunkSize, 0, ['\n', ' ', ''], measure);
426
+ for (const sub of subChunks) {
427
+ chunks.push({ text: sub.text, metadata: { ...baseMetadata } });
428
+ }
429
+ }
430
+ else {
431
+ chunks.push({ text: chunkText, metadata: { ...baseMetadata } });
432
+ }
433
+ }
434
+ currentRows = [];
435
+ currentSize = headerText ? measure(headerText) : 0;
436
+ };
437
+ for (const row of dataRows) {
438
+ const override = await this.handleOnNode(row);
439
+ if (override === false)
440
+ continue;
441
+ const rowText = typeof override === 'string' ? override : await this.renderRowsAsText([row]);
442
+ const rowSize = measure(rowText);
443
+ if (currentSize + rowSize > maxChunkSize && currentRows.length > 0) {
444
+ await flushCurrentRows();
445
+ }
446
+ if (typeof override === 'string') {
447
+ // If row was overridden, we flush current, then add the override as a chunk.
448
+ await flushCurrentRows();
449
+ const chunkText = headerText ? `${headerText}\n${override}` : override;
450
+ chunks.push({ text: chunkText, metadata: { ...baseMetadata } });
451
+ continue;
452
+ }
453
+ currentRows.push(row);
454
+ currentSize += rowSize;
455
+ }
456
+ await flushCurrentRows();
457
+ }
458
+ /**
459
+ * Renders a list of row nodes as a pipe-separated text string.
460
+ */
461
+ async renderRowsAsText(rows) {
462
+ const renderedRows = [];
463
+ for (const row of rows) {
464
+ const override = await this.handleOnNode(row);
465
+ if (override === false)
466
+ continue;
467
+ if (typeof override === 'string') {
468
+ renderedRows.push(override);
469
+ continue;
470
+ }
471
+ if (!row.children) {
472
+ renderedRows.push(row.text ?? '');
473
+ continue;
474
+ }
475
+ const getCellText = (cell) => {
476
+ if (cell.text)
477
+ return cell.text;
478
+ if (!cell.children || cell.children.length === 0)
479
+ return '';
480
+ return cell.children.map(c => getCellText(c)).join(' ');
481
+ };
482
+ const cells = row.children.map(cell => getCellText(cell).replace(/\n/g, ' ').trim());
483
+ renderedRows.push(`| ${cells.join(' | ')} |`);
484
+ }
485
+ return renderedRows.join('\n');
486
+ }
487
+ // ─── Strategy 3: Semantic ─────────────────────────────────────────────────
488
+ /**
489
+ * Splits document into semantically coherent chunks using cosine similarity
490
+ * between sentence embeddings. A new chunk begins when similarity drops
491
+ * below `similarityThreshold`.
492
+ */
493
+ async generateSemantic(config) {
494
+ const { similarityThreshold: threshold, maxChunkSize, bufferSize, lengthFunction: measure, embeddingBatchSize: batchSize } = config;
495
+ if (typeof config.embeddingFunction !== 'function') {
496
+ throw (0, errorUtils_js_1.getOfficeError)(types_js_1.OfficeErrorType.MISSING_EMBEDDING_FUNCTION, this.config);
497
+ }
498
+ // Extract all leaf-level text sentences from the AST
499
+ const sentences = await this.extractSentences();
500
+ if (sentences.length === 0)
501
+ return [];
502
+ // Embed all sentences in batches to avoid rate limiting
503
+ const embeddings = await this.batchEmbeddings(sentences, config.embeddingFunction, batchSize, config.timeout);
504
+ // Calculate cosine similarity between adjacent sentence windows
505
+ const chunks = [];
506
+ let currentSentences = [];
507
+ let currentSize = 0;
508
+ for (let i = 0; i < sentences.length; i++) {
509
+ const sentenceText = sentences[i].text;
510
+ currentSentences.push(sentences[i]);
511
+ currentSize += measure(sentenceText);
512
+ // Check if we should split here
513
+ const isLast = i === sentences.length - 1;
514
+ const exceedsMax = currentSize > maxChunkSize;
515
+ let shouldSplit = isLast || exceedsMax;
516
+ if (!shouldSplit && i < sentences.length - 1) {
517
+ // Compare the current window with the next window
518
+ const currentWindowEnd = Math.min(i + bufferSize, sentences.length - 1);
519
+ const nextWindowStart = i + 1;
520
+ const nextWindowEnd = Math.min(i + 1 + bufferSize, sentences.length - 1);
521
+ const currentEmbedding = this.averageEmbeddings(embeddings.slice(Math.max(0, i - bufferSize + 1), currentWindowEnd + 1));
522
+ const nextEmbedding = this.averageEmbeddings(embeddings.slice(nextWindowStart, nextWindowEnd + 1));
523
+ const similarity = this.cosineSimilarity(currentEmbedding, nextEmbedding);
524
+ if (similarity < threshold) {
525
+ shouldSplit = true;
526
+ }
527
+ }
528
+ if (shouldSplit && currentSentences.length > 0) {
529
+ const chunkText = currentSentences.map(s => s.text).join(' ');
530
+ const firstSentence = currentSentences[0];
531
+ const baseMetadata = {
532
+ sourceType: this.ast.type,
533
+ closestHeading: firstSentence.closestHeading,
534
+ slideNumber: firstSentence.slideNumber,
535
+ pageNumber: firstSentence.pageNumber,
536
+ sheetName: firstSentence.sheetName,
537
+ };
538
+ if (measure(chunkText) > maxChunkSize) {
539
+ // Fallback to recursive splitting for oversized semantic chunks
540
+ const subChunks = this.splitTextRecursively(chunkText, maxChunkSize, 0, ['\n', ' ', ''], measure);
541
+ for (const sub of subChunks) {
542
+ chunks.push({
543
+ text: sub.text,
544
+ metadata: { ...baseMetadata }
545
+ });
546
+ }
547
+ }
548
+ else {
549
+ chunks.push({
550
+ text: chunkText,
551
+ metadata: baseMetadata
552
+ });
553
+ }
554
+ currentSentences = [];
555
+ currentSize = 0;
556
+ }
557
+ }
558
+ return this.finalizeChunks(chunks, config);
559
+ }
560
+ /**
561
+ * Extracts all text sentences from the AST with their contextual metadata.
562
+ */
563
+ async extractSentences() {
564
+ const results = [];
565
+ let currentHeading;
566
+ let currentSlide;
567
+ let currentPage;
568
+ let currentSheet;
569
+ const walk = async (node) => {
570
+ (0, errorUtils_js_1.checkAbortSignal)(this.config.abortSignal);
571
+ const override = await this.handleOnNode(node);
572
+ if (override === false)
573
+ return;
574
+ if (node.type === 'heading')
575
+ currentHeading = node.text;
576
+ if (node.type === 'slide')
577
+ currentSlide = node.metadata?.slideNumber;
578
+ if (node.type === 'page')
579
+ currentPage = node.metadata?.pageNumber;
580
+ if (node.type === 'sheet')
581
+ currentSheet = node.metadata?.sheetName;
582
+ const isContentNode = node.type === 'paragraph' || node.type === 'heading' || node.type === 'list' || node.type === 'cell' || (node.text && (!node.children || node.children.length === 0));
583
+ if (isContentNode) {
584
+ const text = (typeof override === 'string' ? override : collectNodeText(node)).trim();
585
+ if (!text)
586
+ return;
587
+ // Split paragraph text into individual sentences for finer-grained similarity
588
+ const sentences = this.splitIntoSentences(text);
589
+ for (const sentence of sentences) {
590
+ if (sentence.trim()) {
591
+ results.push({
592
+ text: sentence.trim(),
593
+ closestHeading: currentHeading,
594
+ slideNumber: currentSlide,
595
+ pageNumber: currentPage,
596
+ sheetName: currentSheet,
597
+ });
598
+ }
599
+ }
600
+ return; // don't recurse into children; we already have the text
601
+ }
602
+ if (node.children) {
603
+ for (const child of node.children)
604
+ await walk(child);
605
+ }
606
+ if (node.notes) {
607
+ for (const note of node.notes)
608
+ await walk(note);
609
+ }
610
+ };
611
+ for (const node of this.ast.content)
612
+ await walk(node);
613
+ return results;
614
+ }
615
+ // ─── Shared Utilities ─────────────────────────────────────────────────────
616
+ /**
617
+ * Builds a flat text string from the entire document and a map of
618
+ * character offsets to AST node metadata for position-based metadata lookups.
619
+ */
620
+ async buildFlatTextWithPositions() {
621
+ const parts = [];
622
+ const nodeMap = [];
623
+ let offset = 0;
624
+ let currentHeading;
625
+ let currentSlide;
626
+ let currentPage;
627
+ let currentSheet;
628
+ const walk = async (node) => {
629
+ (0, errorUtils_js_1.checkAbortSignal)(this.config.abortSignal);
630
+ const override = await this.handleOnNode(node);
631
+ if (override === false)
632
+ return;
633
+ if (node.type === 'heading')
634
+ currentHeading = node.text;
635
+ if (node.type === 'slide')
636
+ currentSlide = node.metadata?.slideNumber;
637
+ if (node.type === 'page')
638
+ currentPage = node.metadata?.pageNumber;
639
+ if (node.type === 'sheet')
640
+ currentSheet = node.metadata?.sheetName;
641
+ const isContentNode = node.type === 'paragraph' || node.type === 'heading' || node.type === 'list' || node.type === 'code' || node.type === 'cell' || (node.text && (!node.children || node.children.length === 0));
642
+ if (isContentNode) {
643
+ const nodeText = typeof override === 'string' ? override : collectNodeText(node);
644
+ const txt = nodeText + '\n';
645
+ nodeMap.push({ offset, heading: currentHeading, slideNumber: currentSlide, pageNumber: currentPage, sheetName: currentSheet });
646
+ parts.push(txt);
647
+ offset += txt.length;
648
+ return;
649
+ }
650
+ if (node.children) {
651
+ for (const child of node.children)
652
+ await walk(child);
653
+ }
654
+ if (node.notes) {
655
+ for (const note of node.notes)
656
+ await walk(note);
657
+ }
658
+ };
659
+ for (const node of this.ast.content)
660
+ await walk(node);
661
+ return { text: parts.join(''), nodeMap };
662
+ }
663
+ /**
664
+ * Finds the closest AST metadata for a given character position.
665
+ */
666
+ enrichMetadataFromPosition(chunk, nodeMap, charOffset) {
667
+ let best = nodeMap[0];
668
+ for (const entry of nodeMap) {
669
+ if (entry.offset <= charOffset)
670
+ best = entry;
671
+ else
672
+ break;
673
+ }
674
+ if (best) {
675
+ chunk.metadata.closestHeading = best.heading;
676
+ chunk.metadata.slideNumber = best.slideNumber;
677
+ chunk.metadata.pageNumber = best.pageNumber;
678
+ chunk.metadata.sheetName = best.sheetName;
679
+ }
680
+ }
681
+ /**
682
+ * Applies final post-processing: strips whitespace, sets sourceType.
683
+ */
684
+ finalizeChunks(chunks, config) {
685
+ if (chunks.length === 0) {
686
+ this.warn(types_js_1.OfficeWarningType.EMPTY_CHUNK_GENERATED, this.chunkConfig.strategy);
687
+ }
688
+ return chunks
689
+ .map(chunk => {
690
+ const text = config.stripWhitespace !== false ? chunk.text.trim() : chunk.text;
691
+ const result = { text, metadata: { sourceType: this.ast.type } };
692
+ if (config.includeMetadata !== false) {
693
+ result.metadata = chunk.metadata;
694
+ }
695
+ return result;
696
+ })
697
+ .filter(chunk => chunk.text.length > 0);
698
+ }
699
+ // ─── Embedding Math Utilities ──────────────────────────────────────────────
700
+ /**
701
+ * Helper to process embeddings in sequential batches to avoid API rate limits and memory issues.
702
+ */
703
+ async batchEmbeddings(sentences, embedFn, batchSize = 50, timeoutMs) {
704
+ const results = [];
705
+ for (let i = 0; i < sentences.length; i += batchSize) {
706
+ (0, errorUtils_js_1.checkAbortSignal)(this.config.abortSignal);
707
+ const batch = sentences.slice(i, i + batchSize);
708
+ const batchPromises = batch.map(s => {
709
+ const call = embedFn(s.text);
710
+ if (timeoutMs !== undefined && timeoutMs > 0) {
711
+ let timerId;
712
+ const timeoutPromise = new Promise((_, reject) => {
713
+ timerId = setTimeout(() => {
714
+ reject((0, errorUtils_js_1.getOfficeError)(types_js_1.OfficeErrorType.EMBEDDING_TIMEOUT, this.config, timeoutMs));
715
+ }, timeoutMs);
716
+ });
717
+ return Promise.race([call, timeoutPromise]).finally(() => {
718
+ clearTimeout(timerId);
719
+ });
720
+ }
721
+ return call;
722
+ });
723
+ const batchResults = await Promise.all(batchPromises);
724
+ results.push(...batchResults);
725
+ }
726
+ return results;
727
+ }
728
+ cosineSimilarity(a, b) {
729
+ if (a.length !== b.length || a.length === 0)
730
+ return 0;
731
+ let dot = 0, normA = 0, normB = 0;
732
+ for (let i = 0; i < a.length; i++) {
733
+ dot += a[i] * b[i];
734
+ normA += a[i] * a[i];
735
+ normB += b[i] * b[i];
736
+ }
737
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
738
+ return denom === 0 ? 0 : dot / denom;
739
+ }
740
+ averageEmbeddings(embeddings) {
741
+ if (embeddings.length === 0)
742
+ return [];
743
+ const len = embeddings[0].length;
744
+ const avg = new Array(len).fill(0);
745
+ for (const emb of embeddings) {
746
+ for (let i = 0; i < len; i++)
747
+ avg[i] += emb[i];
748
+ }
749
+ return avg.map(v => v / embeddings.length);
750
+ }
751
+ /**
752
+ * Robustly splits text into sentences, respecting abbreviations and non-Western punctuation.
753
+ */
754
+ splitIntoSentences(text) {
755
+ if (this.isCustomRegex) {
756
+ const userRegex = this.chunkConfig.sentenceBoundaryRegex;
757
+ const regex = typeof userRegex === 'string' ? new RegExp(userRegex, 'g') : userRegex;
758
+ // Split while keeping the separator if possible, or just split
759
+ return text.split(regex).map(s => s.trim()).filter(Boolean);
760
+ }
761
+ const abbreviations = this.chunkConfig.abbreviations;
762
+ const sentences = [];
763
+ let start = 0;
764
+ // Japanese full stop: 。 Exclamation: ! Question: ?
765
+ // Western: . ! ?
766
+ const markRegex = /[.!?。!?]/g;
767
+ let match;
768
+ while ((match = markRegex.exec(text)) !== null) {
769
+ const mark = match[0];
770
+ const pos = match.index;
771
+ const nextChar = text[pos + 1];
772
+ const isAtEnd = pos === text.length - 1;
773
+ const isFollowedByWhitespace = !nextChar || /\s/.test(nextChar);
774
+ const isJapaneseMark = /[。!?]/.test(mark);
775
+ if (isFollowedByWhitespace || isJapaneseMark) {
776
+ // Check for abbreviations (only for period)
777
+ if (mark === '.') {
778
+ const prevSpace = text.lastIndexOf(' ', pos - 1);
779
+ let lastWord = text.substring(prevSpace + 1, pos);
780
+ // Strip punctuation like quotes, parentheses, brackets
781
+ lastWord = lastWord.replace(/^[^\w]+|[^\w]+$/g, '');
782
+ if (abbreviations.includes(lastWord))
783
+ continue;
784
+ }
785
+ sentences.push(text.substring(start, pos + 1).trim());
786
+ start = pos + 1;
787
+ }
788
+ }
789
+ if (start < text.length) {
790
+ const remaining = text.substring(start).trim();
791
+ if (remaining)
792
+ sentences.push(remaining);
793
+ }
794
+ return sentences.length > 0 ? sentences : [text];
795
+ }
796
+ }
797
+ exports.ChunkingGenerator = ChunkingGenerator;