@gmickel/gno 1.46.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) 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/spa-production.json.gz +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  6. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  7. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  8. package/browser-extension/dist/manifest.json +1 -1
  9. package/browser-extension/dist/preview.html +1 -1
  10. package/browser-extension/dist/service-worker.js +32 -33
  11. package/bunfig.toml +2 -0
  12. package/package.json +40 -26
  13. package/spec/cli.md +21 -4
  14. package/spec/db/schema.sql +146 -1
  15. package/spec/mcp.md +26 -0
  16. package/src/app/context-runtime-types.ts +3 -0
  17. package/src/app/context-runtime.ts +2 -0
  18. package/src/cli/commands/ask.ts +6 -1
  19. package/src/cli/commands/daemon.ts +21 -8
  20. package/src/cli/commands/embed.ts +77 -41
  21. package/src/cli/detach.ts +3 -2
  22. package/src/config/types.ts +3 -3
  23. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  24. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  25. package/src/converters/versions.ts +6 -8
  26. package/src/core/context-evidence.ts +8 -4
  27. package/src/core/job-manager.ts +95 -13
  28. package/src/core/network-boundary-inventory.ts +10 -0
  29. package/src/core/shutdown-budget.ts +45 -0
  30. package/src/embed/backlog.ts +107 -4
  31. package/src/embed/batch.ts +42 -2
  32. package/src/embed/fingerprint.ts +16 -0
  33. package/src/embed/retry.ts +113 -5
  34. package/src/embed/variant-backlog.ts +105 -0
  35. package/src/embed/variant-plan.ts +62 -0
  36. package/src/embed/variant-retry.ts +113 -0
  37. package/src/ingestion/graph-reconciliation.ts +327 -0
  38. package/src/ingestion/sync.ts +9 -272
  39. package/src/llm/http-inference.ts +6 -0
  40. package/src/llm/httpEmbedding.ts +37 -6
  41. package/src/llm/httpGeneration.ts +18 -3
  42. package/src/llm/httpRerank.ts +23 -5
  43. package/src/llm/inference-cancellation.ts +168 -0
  44. package/src/llm/inference-scope.ts +202 -0
  45. package/src/llm/lazy-ports.ts +115 -0
  46. package/src/llm/native-worker/client.ts +541 -0
  47. package/src/llm/native-worker/dispatcher.ts +228 -0
  48. package/src/llm/native-worker/embedding-identity.ts +33 -0
  49. package/src/llm/native-worker/entry.ts +173 -0
  50. package/src/llm/native-worker/errors.ts +32 -0
  51. package/src/llm/native-worker/evaluation.ts +16 -0
  52. package/src/llm/native-worker/owned-exit.ts +108 -0
  53. package/src/llm/native-worker/owner.ts +141 -0
  54. package/src/llm/native-worker/ports.ts +317 -0
  55. package/src/llm/native-worker/protocol.ts +442 -0
  56. package/src/llm/native-worker/runtime-config.ts +92 -0
  57. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  58. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  59. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  60. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  61. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  62. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  63. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  64. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  65. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  66. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  67. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  68. package/src/llm/types.ts +35 -5
  69. package/src/mcp/context.ts +27 -0
  70. package/src/mcp/http-transport.ts +12 -10
  71. package/src/mcp/server.ts +3 -0
  72. package/src/mcp/tool-profile.ts +30 -8
  73. package/src/mcp/tools/context.ts +8 -11
  74. package/src/mcp/tools/embed.ts +1 -1
  75. package/src/mcp/tools/index-cmd.ts +1 -1
  76. package/src/mcp/tools/index.ts +10 -8
  77. package/src/mcp/tools/query.ts +14 -30
  78. package/src/mcp/tools/vsearch.ts +1 -1
  79. package/src/pipeline/answer.ts +23 -3
  80. package/src/pipeline/claim-verifier.ts +6 -0
  81. package/src/pipeline/expansion.ts +43 -40
  82. package/src/pipeline/explain.ts +6 -2
  83. package/src/pipeline/filters.ts +63 -0
  84. package/src/pipeline/fusion.ts +29 -9
  85. package/src/pipeline/graph-retrieval.ts +29 -9
  86. package/src/pipeline/hybrid.ts +198 -55
  87. package/src/pipeline/hydration.ts +161 -0
  88. package/src/pipeline/owner-fusion.ts +87 -0
  89. package/src/pipeline/rerank.ts +35 -11
  90. package/src/pipeline/search.ts +13 -2
  91. package/src/pipeline/types.ts +5 -3
  92. package/src/pipeline/vsearch.ts +87 -7
  93. package/src/sdk/client.ts +47 -3
  94. package/src/sdk/embed.ts +63 -39
  95. package/src/serve/background-runtime.ts +1 -1
  96. package/src/serve/context.ts +41 -56
  97. package/src/serve/embed-scheduler.ts +58 -35
  98. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  99. package/src/serve/public/globals.built.css +1 -1
  100. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  101. package/src/serve/resident-admission.ts +36 -36
  102. package/src/serve/resident-background-work.ts +20 -2
  103. package/src/serve/resident-request.ts +11 -5
  104. package/src/serve/resident-runtime.ts +97 -61
  105. package/src/serve/resident-shutdown.ts +153 -0
  106. package/src/serve/routes/api.ts +3 -1
  107. package/src/serve/server.ts +47 -26
  108. package/src/store/migrations/028-vector-variants.ts +54 -0
  109. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  110. package/src/store/migrations/index.ts +4 -0
  111. package/src/store/sqlite/adapter.ts +251 -183
  112. package/src/store/sqlite/eligibility.ts +174 -0
  113. package/src/store/sqlite/graph-edge-application.ts +66 -0
  114. package/src/store/sqlite/graph-reference-state.ts +194 -0
  115. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  116. package/src/store/types.ts +80 -12
  117. package/src/store/vector/eligibility.ts +36 -0
  118. package/src/store/vector/freshness.ts +33 -6
  119. package/src/store/vector/lazy.ts +81 -0
  120. package/src/store/vector/sqlite-vec.ts +106 -54
  121. package/src/store/vector/stats.ts +14 -3
  122. package/src/store/vector/types.ts +35 -2
  123. package/src/store/vector/variant-search.ts +192 -0
  124. package/src/store/vector/variants.ts +451 -0
  125. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  126. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  127. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  128. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  129. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  130. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  131. package/vendor/converters/markitdown-ts/package.json +77 -0
  132. package/vendor/converters/officeparser/LICENSE +21 -0
  133. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  134. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  135. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  136. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  137. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  138. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  139. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  140. package/vendor/converters/officeparser/dist/cli.js +381 -0
  141. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  142. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  143. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  144. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  145. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  146. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  147. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  148. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  149. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  150. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  151. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  152. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  153. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  154. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  155. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  156. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  157. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  158. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  159. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  160. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  161. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  162. package/vendor/converters/officeparser/dist/index.js +72 -0
  163. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  164. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  165. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  166. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  167. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  168. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  169. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  170. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  171. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  172. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  173. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  174. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  175. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  176. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  177. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  178. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  179. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  180. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  181. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  182. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  183. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  184. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  185. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  186. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  187. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  188. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  189. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  190. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  191. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  192. package/vendor/converters/officeparser/dist/types.js +107 -0
  193. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  194. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  195. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  196. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  197. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  198. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  199. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  200. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  201. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  202. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  203. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  204. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  205. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  206. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  207. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  208. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  209. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  210. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  211. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  212. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  213. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  214. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  215. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  216. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  217. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  218. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  219. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  220. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  221. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  222. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  223. package/vendor/converters/officeparser/package.json +147 -0
  224. package/vendor/converters/upstream-manifest.json +124 -0
  225. package/vendor/dependency-fixes/README.md +77 -0
  226. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  227. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
  228. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
  229. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  230. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+ /**
3
+ * OCR (Optical Character Recognition) Utilities
4
+ *
5
+ * This module provides functions for extracting text from images using Tesseract.js.
6
+ * Used when `config.ocr` is enabled to extract text from embedded images in documents.
7
+ *
8
+ * Includes a worker pool via OcrSchedulerManager to improve performance when
9
+ * processing multiple images.
10
+ *
11
+ * @module ocrUtils
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.terminateOcr = exports.performOcr = void 0;
15
+ const envUtils_js_1 = require("./envUtils.js");
16
+ const errorUtils_js_1 = require("./errorUtils.js");
17
+ /**
18
+ * Wraps a promise in a timeout.
19
+ *
20
+ * @param promise - The promise to wrap
21
+ * @param ms - Timeout duration in milliseconds
22
+ * @param errMsg - Error message to throw if timeout occurs
23
+ * @returns The wrapped promise
24
+ */
25
+ function withTimeout(promise, ms, errMsg) {
26
+ let id;
27
+ const timeout = new Promise((_, reject) => {
28
+ id = setTimeout(() => {
29
+ reject(new Error(errMsg));
30
+ }, ms);
31
+ });
32
+ return Promise.race([promise, timeout]).then((res) => { clearTimeout(id); return res; }, (err) => { clearTimeout(id); throw err; });
33
+ }
34
+ /**
35
+ * Manages a pool of Tesseract workers with "Smart Affinity".
36
+ *
37
+ * Instead of a simple scheduler, this manager allows workers to persist with
38
+ * a specific language affinity. If a new language is requested and the pool
39
+ * is at capacity, it re-initializes the Least Recently Used (LRU) idle worker
40
+ * rather than resetting the entire pool.
41
+ *
42
+ * Implements lazy loading of tesseract.js to ensure no background processes
43
+ * are spawned unless OCR is explicitly used.
44
+ */
45
+ class OcrSchedulerManager {
46
+ static instance;
47
+ pool = [];
48
+ queue = [];
49
+ MAX_WORKERS = 4;
50
+ idleTimeout = 10000; // 10s default
51
+ timeoutId = null;
52
+ isProcessing = false;
53
+ constructor() { }
54
+ /**
55
+ * Returns the singleton instance of the manager.
56
+ */
57
+ static getInstance() {
58
+ if (!OcrSchedulerManager.instance) {
59
+ OcrSchedulerManager.instance = new OcrSchedulerManager();
60
+ }
61
+ return OcrSchedulerManager.instance;
62
+ }
63
+ /**
64
+ * Checks if the singleton instance has been initialized.
65
+ */
66
+ static hasInstance() {
67
+ return !!OcrSchedulerManager.instance;
68
+ }
69
+ /**
70
+ * Resets the inactivity timer. If the timer reaches its duration,
71
+ * all workers are terminated automatically.
72
+ */
73
+ resetIdleTimer() {
74
+ if (this.timeoutId) {
75
+ clearTimeout(this.timeoutId);
76
+ }
77
+ if (this.idleTimeout > 0) {
78
+ this.timeoutId = setTimeout(async () => {
79
+ await this.terminate();
80
+ }, this.idleTimeout);
81
+ }
82
+ }
83
+ /**
84
+ * Performs OCR on an image using the smart worker pool.
85
+ *
86
+ * @param image - Image data (Buffer, string path, or Blob)
87
+ * @param config - OCR configuration (language, custom paths, timeouts, signal)
88
+ * @returns Recognized text
89
+ */
90
+ async recognize(image, config) {
91
+ const signal = config?.abortSignal;
92
+ if (signal?.aborted) {
93
+ return Promise.reject((0, errorUtils_js_1.getAbortError)());
94
+ }
95
+ return new Promise((resolve, reject) => {
96
+ // Update idle timeout if provided.
97
+ // Priority: timeout.autoTerminate (new) > autoTerminateTimeout (deprecated) > built-in default.
98
+ const effectiveAutoTerminate = config?.timeout?.autoTerminate ?? config?.autoTerminateTimeout;
99
+ if (effectiveAutoTerminate !== undefined) {
100
+ this.idleTimeout = effectiveAutoTerminate;
101
+ }
102
+ // Reset the inactivity timer every time a new job is requested
103
+ this.resetIdleTimer();
104
+ let abortListener = null;
105
+ let finished = false;
106
+ let job;
107
+ const cleanResolve = (val) => {
108
+ if (finished)
109
+ return;
110
+ finished = true;
111
+ if (job)
112
+ job.isFinished = true;
113
+ if (abortListener && signal) {
114
+ signal.removeEventListener('abort', abortListener);
115
+ }
116
+ resolve(val);
117
+ };
118
+ const cleanReject = (err) => {
119
+ if (finished)
120
+ return;
121
+ finished = true;
122
+ if (job)
123
+ job.isFinished = true;
124
+ if (abortListener && signal) {
125
+ signal.removeEventListener('abort', abortListener);
126
+ }
127
+ reject(err);
128
+ };
129
+ // Priority: timeout.recognition (new) > 30 s default.
130
+ const recogTimeout = config?.timeout?.recognition ?? 30000;
131
+ // Create job
132
+ job = {
133
+ image,
134
+ config: config || {},
135
+ resolve: cleanResolve,
136
+ reject: cleanReject,
137
+ startTime: Date.now(),
138
+ timeoutMs: recogTimeout
139
+ };
140
+ if (signal) {
141
+ abortListener = () => {
142
+ if (finished)
143
+ return;
144
+ const err = (0, errorUtils_js_1.getAbortError)();
145
+ cleanReject(err);
146
+ // 1. Remove job from queue if it hasn't run yet
147
+ const idx = this.queue.indexOf(job);
148
+ if (idx !== -1) {
149
+ this.queue.splice(idx, 1);
150
+ }
151
+ // 2. Find if any worker is currently running this job and terminate/remove it
152
+ const workerIndex = this.pool.findIndex(mw => mw.activeJob === job);
153
+ if (workerIndex !== -1) {
154
+ const managedWorker = this.pool[workerIndex];
155
+ // Remove from pool immediately to prevent reuse
156
+ this.pool.splice(workerIndex, 1);
157
+ // Terminate the worker process
158
+ try {
159
+ managedWorker.worker.terminate();
160
+ }
161
+ catch (e) { }
162
+ // Trigger queue processing for subsequent tasks
163
+ this.processQueue();
164
+ }
165
+ };
166
+ signal.addEventListener('abort', abortListener);
167
+ }
168
+ // Add job to queue and trigger processing
169
+ this.queue.push(job);
170
+ this.processQueue();
171
+ });
172
+ }
173
+ /**
174
+ * Attempts to process the next job in the queue using an available worker.
175
+ * Designed to be race-free and support concurrent/parallel job execution.
176
+ */
177
+ async processQueue() {
178
+ if (this.isProcessing)
179
+ return;
180
+ this.isProcessing = true;
181
+ try {
182
+ while (this.queue.length > 0) {
183
+ const nextJob = this.queue[0];
184
+ if (nextJob.isFinished) {
185
+ this.queue.shift();
186
+ continue;
187
+ }
188
+ const requestedLanguage = nextJob.config.language || 'eng';
189
+ // 1. Find an idle worker with the EXACT language affinity
190
+ let managed = this.pool.find(mw => !mw.isBusy && mw.language === requestedLanguage);
191
+ // 2. If not found and we have room, create a new worker
192
+ if (!managed && this.pool.length < this.MAX_WORKERS) {
193
+ const job = this.queue.shift();
194
+ if (!job)
195
+ continue;
196
+ this.createAndRunWorker(job, requestedLanguage);
197
+ continue;
198
+ }
199
+ // 3. If still not found and we are at capacity, find the LRU idle worker and re-initialize it
200
+ if (!managed) {
201
+ const idleWorkers = this.pool.filter(mw => !mw.isBusy);
202
+ if (idleWorkers.length > 0) {
203
+ const job = this.queue.shift();
204
+ if (!job)
205
+ continue;
206
+ managed = idleWorkers.reduce((prev, curr) => (prev.lastUsed < curr.lastUsed ? prev : curr));
207
+ this.reinitializeAndRunWorker(managed, job, requestedLanguage);
208
+ continue;
209
+ }
210
+ }
211
+ // 4. If we have a worker ready, execute the job
212
+ if (managed) {
213
+ const job = this.queue.shift();
214
+ if (!job)
215
+ continue;
216
+ this.runWorker(managed, job);
217
+ continue;
218
+ }
219
+ // No workers can be allocated right now (all busy and pool at capacity). Break work loop.
220
+ break;
221
+ }
222
+ }
223
+ finally {
224
+ this.isProcessing = false;
225
+ }
226
+ }
227
+ /**
228
+ * Helper to dynamically instantiate a Tesseract worker, register it to the pool, and run the job.
229
+ */
230
+ async createAndRunWorker(job, requestedLanguage) {
231
+ // Priority: timeout.workerLoad (new) > 60 s default.
232
+ const loadTimeout = job.config.timeout?.workerLoad ?? 60000;
233
+ let managed = null;
234
+ try {
235
+ const { createWorker } = await import('tesseract.js');
236
+ const options = { logger: () => { } };
237
+ if (job.config.workerPath)
238
+ options.workerPath = job.config.workerPath;
239
+ if (job.config.corePath)
240
+ options.corePath = job.config.corePath;
241
+ if (job.config.langPath)
242
+ options.langPath = job.config.langPath;
243
+ const workerPromise = createWorker(requestedLanguage, 1, options);
244
+ // To prevent dangling worker threads on timeout or abort, we register a post-resolution hook
245
+ // that terminates the worker if the promise finishes after the timeout has fired or the job is finished.
246
+ let hasTimedOutOrAborted = false;
247
+ workerPromise.then(async (worker) => {
248
+ if (hasTimedOutOrAborted || job.isFinished) {
249
+ try {
250
+ await worker.terminate();
251
+ }
252
+ catch (e) { }
253
+ }
254
+ }, () => { });
255
+ const worker = loadTimeout > 0
256
+ ? await withTimeout(workerPromise, loadTimeout, `OCR worker initialization timed out after ${loadTimeout}ms`).catch(err => {
257
+ hasTimedOutOrAborted = true;
258
+ throw err;
259
+ })
260
+ : await workerPromise;
261
+ // If the job finished/aborted while loading, clean up the worker and skip execution.
262
+ if (job.isFinished) {
263
+ hasTimedOutOrAborted = true;
264
+ try {
265
+ await worker.terminate();
266
+ }
267
+ catch (e) { }
268
+ this.processQueue();
269
+ return;
270
+ }
271
+ managed = {
272
+ worker,
273
+ language: requestedLanguage,
274
+ lastUsed: Date.now(),
275
+ isBusy: false
276
+ };
277
+ this.pool.push(managed);
278
+ await this.runWorker(managed, job);
279
+ }
280
+ catch (err) {
281
+ job.reject(err);
282
+ this.processQueue();
283
+ }
284
+ }
285
+ /**
286
+ * Helper to reinitialize an existing idle worker with a different language affinity and run the job.
287
+ */
288
+ async reinitializeAndRunWorker(managed, job, requestedLanguage) {
289
+ // Priority: timeout.workerLoad (new) > 60 s default.
290
+ const loadTimeout = job.config.timeout?.workerLoad ?? 60000;
291
+ managed.isBusy = true;
292
+ managed.lastUsed = Date.now();
293
+ managed.activeJob = job;
294
+ try {
295
+ const reinitPromise = managed.worker.reinitialize(requestedLanguage);
296
+ if (loadTimeout > 0) {
297
+ await withTimeout(reinitPromise, loadTimeout, `OCR worker re-initialization timed out after ${loadTimeout}ms`);
298
+ }
299
+ else {
300
+ await reinitPromise;
301
+ }
302
+ managed.language = requestedLanguage;
303
+ // If the job finished/aborted while reinitializing, clean up and skip execution.
304
+ if (job.isFinished) {
305
+ const index = this.pool.indexOf(managed);
306
+ if (index !== -1) {
307
+ this.pool.splice(index, 1);
308
+ }
309
+ try {
310
+ await managed.worker.terminate();
311
+ }
312
+ catch (e) { }
313
+ this.processQueue();
314
+ return;
315
+ }
316
+ await this.runWorker(managed, job);
317
+ }
318
+ catch (err) {
319
+ // Re-initialization failed/timed out, remove worker from pool and terminate
320
+ const index = this.pool.indexOf(managed);
321
+ if (index !== -1) {
322
+ this.pool.splice(index, 1);
323
+ }
324
+ try {
325
+ await managed.worker.terminate();
326
+ }
327
+ catch (e) { }
328
+ job.reject(err);
329
+ this.processQueue();
330
+ }
331
+ }
332
+ /**
333
+ * Helper to execute OCR text recognition on the worker and return the results.
334
+ */
335
+ async runWorker(managed, job) {
336
+ // Priority: timeout.recognition (new) > 30 s default.
337
+ const recogTimeout = job.config.timeout?.recognition ?? 30000;
338
+ managed.isBusy = true;
339
+ managed.lastUsed = Date.now();
340
+ managed.activeJob = job;
341
+ try {
342
+ const recognizePromise = managed.worker.recognize(job.image);
343
+ const { data: { text } } = recogTimeout > 0
344
+ ? await withTimeout(recognizePromise, recogTimeout, `OCR recognition timed out after ${recogTimeout}ms`)
345
+ : await recognizePromise;
346
+ job.resolve(text);
347
+ }
348
+ catch (err) {
349
+ // If it timed out, terminate and remove worker to avoid reusing a stuck process
350
+ if (err.message?.includes('timed out')) {
351
+ const index = this.pool.indexOf(managed);
352
+ if (index !== -1) {
353
+ this.pool.splice(index, 1);
354
+ }
355
+ try {
356
+ await managed.worker.terminate();
357
+ }
358
+ catch (e) { }
359
+ }
360
+ job.reject(err);
361
+ }
362
+ finally {
363
+ if (this.pool.includes(managed)) {
364
+ managed.isBusy = false;
365
+ managed.activeJob = undefined;
366
+ managed.lastUsed = Date.now();
367
+ }
368
+ this.processQueue();
369
+ }
370
+ }
371
+ /**
372
+ * Terminates all workers in the pool and resets the state.
373
+ */
374
+ async terminate() {
375
+ if (this.timeoutId) {
376
+ clearTimeout(this.timeoutId);
377
+ this.timeoutId = null;
378
+ }
379
+ const workersToTerminate = this.pool.map(mw => mw.worker.terminate());
380
+ await Promise.all(workersToTerminate);
381
+ this.pool = [];
382
+ }
383
+ }
384
+ /**
385
+ * Performs Optical Character Recognition (OCR) on an image to extract text.
386
+ *
387
+ * Uses Tesseract.js to recognize text in the provided image buffer.
388
+ * This is useful for extracting text from screenshots, scanned documents,
389
+ * charts with labels, or any image containing text.
390
+ *
391
+ * This function uses a shared worker pool to minimize initialization overhead.
392
+ *
393
+ * @param image - The image data as a Buffer, file path, or Blob
394
+ * @param config - Optional configuration for language and custom worker paths
395
+ * @returns A promise that resolves to the recognized text as a string
396
+ * @throws {Error} If the image cannot be processed or Tesseract initialization fails
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * // Extract text from an English image
401
+ * const text = await performOcr(imageBuffer, { language: 'eng' });
402
+ * ```
403
+ *
404
+ * @see https://github.com/naptha/tesseract.js for supported languages and options
405
+ */
406
+ const performOcr = async (image, config) => {
407
+ // Prepare image data
408
+ let inputImage = image;
409
+ // In browser environment, convert Buffer to Blob for better compatibility
410
+ // @ts-ignore
411
+ if (envUtils_js_1.isBrowser && typeof Blob !== 'undefined' && Buffer.isBuffer(image)) {
412
+ inputImage = new Blob([image], { type: 'image/bmp' });
413
+ }
414
+ return await OcrSchedulerManager.getInstance().recognize(inputImage, config);
415
+ };
416
+ exports.performOcr = performOcr;
417
+ /**
418
+ * Terminates all OCR workers and cleans up resources.
419
+ *
420
+ * Should be called when the application is shutting down or OCR is no longer needed
421
+ * to prevent memory leaks and dangling worker processes.
422
+ */
423
+ const terminateOcr = async () => {
424
+ if (OcrSchedulerManager.hasInstance()) {
425
+ await OcrSchedulerManager.getInstance().terminate();
426
+ }
427
+ };
428
+ exports.terminateOcr = terminateOcr;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Shared output-sanitization helpers.
3
+ *
4
+ * Every string in the parsed AST originates from an untrusted document, so any
5
+ * value interpolated into generated output (HTML, XHTML, CSS, URLs, inline
6
+ * scripts, CSV, RTF, Markdown) must be escaped for its destination context.
7
+ * These are the single source of truth — each generator delegates to them so
8
+ * escaping stays consistent and a gap fixed here is fixed everywhere.
9
+ */
10
+ /**
11
+ * Whether a string is a plain HTML attribute *name*, safe to interpolate before `="..."`.
12
+ *
13
+ * Escaping the value is not enough on its own: a key containing a quote or `=` closes the
14
+ * attribute and opens another, so `x" onmouseover="alert(1)" z` yields a real event handler no
15
+ * matter how carefully the value is escaped. This is the shape an attribute-injection payload
16
+ * takes, and rejecting it outright is simpler and safer than trying to escape a name.
17
+ *
18
+ * The predicate is shared rather than restated because it is now applied at four independent
19
+ * points (the parser's attribute collection, the generator's attribute bag, and two
20
+ * styleMap-driven paths). Each of those still keeps its own skip-list inline: the lists are the
21
+ * same policy expressed for different layers, and collapsing them would erase the defence in
22
+ * depth the surrounding comments describe.
23
+ */
24
+ export declare function isSafeHtmlAttributeName(name: string): boolean;
25
+ /**
26
+ * Whether a `styleMap` `output.tag` may be emitted as an element name.
27
+ *
28
+ * Callers must fall back to their default tag when this returns false, never emit the value.
29
+ */
30
+ export declare function isSafeStyleMapTag(tag: unknown): tag is string;
31
+ /**
32
+ * Escapes text for an HTML text node or a double-quoted attribute value.
33
+ * Includes the single quote so the result is also safe inside single-quoted
34
+ * attributes.
35
+ */
36
+ export declare function escapeHtml(text: string): string;
37
+ /**
38
+ * Escapes text for an XML text node or attribute (XHTML/OPF/NCX). Same as
39
+ * escapeHtml but emits the XML-canonical `&apos;` for the single quote.
40
+ */
41
+ export declare function escapeXml(text: string): string;
42
+ /**
43
+ * Sanitizes a single CSS value (e.g. a color/size/font/alignment pulled from a
44
+ * document) for placement inside a `style="prop: VALUE"` attribute.
45
+ *
46
+ * - Drops the whole value if it contains a resource-fetching or executing
47
+ * construct (`url()`, `expression()`, `@import`, `image-set()`, `javascript:`)
48
+ * or angle brackets that could break out of the attribute/tag.
49
+ * - Strips characters that break out of `prop: value` (`;`, quotes), out of a
50
+ * `<style>` rule (`{}`), CSS escapes (`\`), and control characters.
51
+ *
52
+ * `rgb()/hsl()` and hex/named colors, lengths, and (unquoted) font names all
53
+ * survive; the trade-off is that legitimately quoted font names lose their
54
+ * quotes, which browsers tolerate.
55
+ */
56
+ export declare function sanitizeCssValue(value: string): string;
57
+ /**
58
+ * Escapes a document-supplied URL for use in an href/src attribute. Beyond the
59
+ * usual attribute escaping, this rejects script-executing schemes (javascript:,
60
+ * vbscript:, data:, etc.) so a hyperlink extracted from an untrusted document
61
+ * can't run code when clicked — only http(s)/mailto/tel and relative/fragment
62
+ * URLs are passed through.
63
+ */
64
+ export declare function sanitizeUrl(url: string): string;
65
+ /**
66
+ * Decide whether a non-provider `<iframe>` should be preserved, per
67
+ * `HtmlParserConfig.preserveIframes`. `true` allows any src; an array is a hostname allowlist,
68
+ * where an entry matches the src's host exactly or as a `.`-suffix (so `"vimeo.com"` also matches
69
+ * `player.vimeo.com`). A relative or unparseable src is allowed only under `true`. This is a
70
+ * preservation gate, not a sanitizer - the src is still scheme-checked with `sanitizeUrl` on
71
+ * generation.
72
+ */
73
+ export declare function iframeAllowed(src: string, preserve: boolean | string[] | undefined): boolean;
74
+ /**
75
+ * Like sanitizeUrl but for an <img>/<source> src: additionally permits
76
+ * `data:image/*` URIs (embedded document images) while still rejecting
77
+ * script-executing schemes and non-image data URIs (e.g. data:text/html).
78
+ */
79
+ export declare function sanitizeImageUrl(url: string): string;
80
+ /**
81
+ * Serializes data for embedding inside an inline <script> block. JSON.stringify
82
+ * alone doesn't escape "<", so a value containing "</script>" (e.g. a chart
83
+ * label from attacker-controlled document XML) would close the script early and
84
+ * inject markup. Also escapes the U+2028/U+2029 line separators, which are
85
+ * invalid in JS string literals.
86
+ */
87
+ export declare function serializeForInlineScript(data: unknown): string;
88
+ /**
89
+ * Formats a value for a CSV field: guards against spreadsheet formula/DDE
90
+ * injection (CWE-1236) and applies RFC 4180 quoting.
91
+ *
92
+ * A cell beginning with `= + - @` (or a tab/CR that some apps treat as a
93
+ * formula start) is prefixed with a single quote so Excel/Sheets render it as
94
+ * literal text rather than executing it. Genuine numbers (including negatives)
95
+ * are exempt so numeric columns are preserved.
96
+ */
97
+ export declare function csvSafeCell(value: string, delimiter: string): string;
98
+ /**
99
+ * Validates and escapes a document-supplied URL for an RTF `HYPERLINK` field argument.
100
+ *
101
+ * Mirrors `sanitizeUrl`'s contract (validate the scheme, then encode for the destination, else
102
+ * return `''`) but cannot reuse it: `sanitizeUrl` returns `escapeHtml(...)`, which would emit
103
+ * `&amp;` into an RTF field. The scheme allowlist is deliberately identical to `sanitizeUrl`'s
104
+ * and `sanitizeMarkdownUrl`'s, so all three text generators agree on what a hyperlink may point at.
105
+ *
106
+ * **Additionally rejects UNC paths (`\\host\share`), which the HTML allowlist does not.** In a
107
+ * browser `\\evil.com\share` is an inert relative path; in Word it is a live UNC reference that
108
+ * triggers an SMB fetch and an NTLM handshake on click, which is a credential-leak vector rather
109
+ * than a rendering quirk. That asymmetry is why this is a separate function and not a flag on
110
+ * `sanitizeUrl` - the HTML helper must NOT gain this behaviour, since there the path is harmless
111
+ * and rejecting it would break legitimate relative links.
112
+ *
113
+ * Returns `''` for a rejected URL; callers emit the link text without the field wrapper, matching
114
+ * how HTML degrades to `href=""` and Markdown to `[text]()`.
115
+ */
116
+ export declare function sanitizeRtfUrl(url: string): string;
117
+ /**
118
+ * Escapes text for RTF: neutralizes the control/group metacharacters `\ { }`
119
+ * (which would otherwise inject RTF control words or groups), encodes the double
120
+ * quote (so a hyperlink field argument can't be terminated early), and hex/unicode
121
+ * encodes non-ASCII characters.
122
+ */
123
+ export declare function escapeRtf(text: string): string;
124
+ /**
125
+ * Escapes document text for a Markdown text position. Markdown passes raw HTML
126
+ * through to the renderer, so a `<` that begins an HTML tag or comment must be
127
+ * neutralized to prevent `<script>`/`<img onerror>` injection when the Markdown
128
+ * is later rendered to HTML.
129
+ *
130
+ * Deliberately narrow — only a `<` immediately followed by a letter, `/`, `!` or
131
+ * `?` (i.e. one that actually opens a tag/comment/PI, matching how browsers
132
+ * detect tags) is encoded. A bare `<` (e.g. `a < b`), `>`, `&`, `[]` and other
133
+ * Markdown metacharacters are left untouched: they can't start a tag, and
134
+ * MarkdownParser round-trips this output without decoding entities, so encoding
135
+ * them would corrupt re-parsed content. URL schemes are handled by
136
+ * sanitizeMarkdownUrl.
137
+ */
138
+ export declare function markdownEscapeText(text: string): string;
139
+ /**
140
+ * Sanitizes a document-supplied URL for a Markdown `[text](url)` / `![alt](url)`
141
+ * target. Rejects script-executing schemes (returning '' → a dead link) and
142
+ * percent-encodes the characters that would break out of the `(...)` or inject
143
+ * markup. `&` is preserved so query strings survive; set `allowDataImage` for
144
+ * image targets so embedded `data:image/*` URIs are permitted.
145
+ */
146
+ export declare function sanitizeMarkdownUrl(url: string, opts?: {
147
+ allowDataImage?: boolean;
148
+ }): string;