@doccloak/core 0.11.0 → 0.12.1
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.
- package/CHANGELOG.md +148 -2
- package/README.md +252 -19
- package/dist/doc.d.ts +68 -1
- package/dist/doc.js +447 -62
- package/dist/dom/docx.d.ts +135 -7
- package/dist/dom/docx.js +752 -125
- package/dist/dom/drawingml.d.ts +59 -0
- package/dist/dom/drawingml.js +176 -0
- package/dist/dom/errors.d.ts +35 -0
- package/dist/dom/errors.js +24 -0
- package/dist/dom/index.d.ts +3 -0
- package/dist/dom/index.js +6 -0
- package/dist/dom/office.d.ts +56 -0
- package/dist/dom/office.js +76 -23
- package/dist/dom/opc.d.ts +135 -7
- package/dist/dom/opc.js +407 -14
- package/dist/dom/package-policy.d.ts +137 -0
- package/dist/dom/package-policy.js +355 -0
- package/dist/dom/xlsx.d.ts +64 -6
- package/dist/dom/xlsx.js +130 -20
- package/dist/engine.d.ts +18 -0
- package/dist/engine.js +37 -13
- package/dist/env.d.ts +21 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +10 -5
- package/dist/model-loader.d.ts +61 -6
- package/dist/model-loader.js +122 -17
- package/dist/pipeline.d.ts +87 -8
- package/dist/pipeline.js +249 -50
- package/dist/providers/bardsai.d.ts +8 -1
- package/dist/providers/bardsai.js +22 -2
- package/dist/providers/gliner-base.d.ts +8 -1
- package/dist/providers/gliner-base.js +22 -2
- package/dist/providers/gliner.d.ts +8 -1
- package/dist/providers/gliner.js +22 -2
- package/dist/regex/loader.d.ts +2 -0
- package/dist/regex/loader.js +2 -0
- package/dist/regex/rules.data.js +15 -14
- package/dist/regex/types.d.ts +6 -0
- package/dist/session.d.ts +101 -3
- package/dist/session.js +397 -43
- package/dist/surrogates.js +57 -45
- package/dist/worker-protocol.d.ts +13 -1
- package/dist/worker-protocol.js +164 -12
- package/package.json +1 -1
- package/rules/at.json +1 -1
- package/rules/be.json +1 -1
- package/rules/ch.json +1 -1
- package/rules/de.json +1 -1
- package/rules/dk.json +1 -1
- package/rules/es.json +1 -1
- package/rules/fi.json +1 -1
- package/rules/fr.json +1 -1
- package/rules/it.json +1 -1
- package/rules/no.json +1 -1
- package/rules/pl.json +1 -1
- package/rules/pt.json +1 -1
- package/rules/schema.json +4 -0
- package/rules/se.json +1 -1
- package/rules/universal.json +2 -1
package/dist/model-loader.js
CHANGED
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
* - Verifies downloads against a pinned SHA-256 when the caller provides one
|
|
18
18
|
* (supply-chain hardening, T116): mismatches evict the cache entry and
|
|
19
19
|
* reject with ModelIntegrityError; verified cache entries carry a marker
|
|
20
|
-
* so they are not re-hashed on every startup.
|
|
20
|
+
* so they are not re-hashed on every startup. The marker records the
|
|
21
|
+
* verified byte size (T185): a cache hit whose blob size no longer matches
|
|
22
|
+
* the marker is re-hashed instead of trusted.
|
|
23
|
+
* - Fetches the pinned tokenizer files (tokenizer.json, tokenizer_config.json)
|
|
24
|
+
* through the same path (loadPinnedTokenizer, T185) so tokenizers get the
|
|
25
|
+
* same resume, retry, cache and integrity treatment as the model.
|
|
21
26
|
*
|
|
22
27
|
* Environment-agnostic: all host capabilities (blob cache, fetch, persistent
|
|
23
28
|
* storage request) are injected via ModelLoaderEnv. This module never touches
|
|
@@ -62,6 +67,26 @@ export function verificationMarkerKey(url, sha256) {
|
|
|
62
67
|
const sep = url.includes('?') ? '&' : '?';
|
|
63
68
|
return `${url}${sep}doccloak-sha256-verified=${sha256}`;
|
|
64
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Value stored in a verification marker: `${sha256}:${size}`, binding the
|
|
72
|
+
* marker to the byte size of the blob that was actually hashed (T185, S4).
|
|
73
|
+
* A cache hit is only trusted without re-hashing when the cached blob's
|
|
74
|
+
* size equals the recorded one. Markers written by 0.11.0 hold the single
|
|
75
|
+
* byte '1' (size unknown): they are upgraded by hashing once and rewriting.
|
|
76
|
+
*/
|
|
77
|
+
export function verificationMarkerValue(sha256, size) {
|
|
78
|
+
return `${sha256.toLowerCase()}:${size}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Size recorded in a marker value for the given hash, or null when the
|
|
82
|
+
* marker is the legacy 0.11.0 '1' or otherwise unreadable (size unknown).
|
|
83
|
+
*/
|
|
84
|
+
export function parseVerificationMarker(value, sha256) {
|
|
85
|
+
const match = /^([0-9a-f]{64}):(\d{1,15})$/.exec(value.trim());
|
|
86
|
+
if (!match || match[1] !== sha256.toLowerCase())
|
|
87
|
+
return null;
|
|
88
|
+
return Number(match[2]);
|
|
89
|
+
}
|
|
65
90
|
/**
|
|
66
91
|
* SHA-256 of a blob as lowercase hex. WebCrypto has no streaming digest,
|
|
67
92
|
* so the blob is hashed as one contiguous buffer: peak cost is one extra
|
|
@@ -73,13 +98,25 @@ async function sha256Hex(blob) {
|
|
|
73
98
|
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
74
99
|
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
75
100
|
}
|
|
76
|
-
/** Best-effort write of the verification marker (
|
|
77
|
-
async function markVerified(cache, url, sha256) {
|
|
101
|
+
/** Best-effort write of the verification marker (`${sha256}:${size}`). */
|
|
102
|
+
async function markVerified(cache, url, sha256, size) {
|
|
78
103
|
try {
|
|
79
|
-
await cache.put(verificationMarkerKey(url, sha256), new Blob([
|
|
104
|
+
await cache.put(verificationMarkerKey(url, sha256), new Blob([verificationMarkerValue(sha256, size)]));
|
|
80
105
|
}
|
|
81
106
|
catch { /* marker is an optimisation - next startup just re-hashes */ }
|
|
82
107
|
}
|
|
108
|
+
/** Size recorded by the marker for this url + hash; null if absent, legacy or unreadable. */
|
|
109
|
+
async function readMarkerSize(cache, url, sha256) {
|
|
110
|
+
const marker = await readFromCache(cache, verificationMarkerKey(url, sha256));
|
|
111
|
+
if (!marker)
|
|
112
|
+
return { present: false, size: null };
|
|
113
|
+
try {
|
|
114
|
+
return { present: true, size: parseVerificationMarker(await marker.text(), sha256) };
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { present: true, size: null };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
83
120
|
function isRetryable(err) {
|
|
84
121
|
if (err instanceof HttpError) {
|
|
85
122
|
return err.status === 408 || err.status === 429 || err.status >= 500;
|
|
@@ -203,6 +240,7 @@ export async function fetchModelBlob(env, url, onProgress, options) {
|
|
|
203
240
|
const maxAttempts = options?.maxAttempts ?? MAX_ATTEMPTS;
|
|
204
241
|
const retryBaseDelayMs = options?.retryBaseDelayMs ?? RETRY_BASE_DELAY_MS;
|
|
205
242
|
const expectedSha256 = options?.sha256?.toLowerCase();
|
|
243
|
+
const pinnedSize = options?.size;
|
|
206
244
|
const cache = env.cache;
|
|
207
245
|
const cachedBlob = await readFromCache(cache, url);
|
|
208
246
|
if (cachedBlob) {
|
|
@@ -210,18 +248,24 @@ export async function fetchModelBlob(env, url, onProgress, options) {
|
|
|
210
248
|
onProgress?.(cachedBlob.size, cachedBlob.size);
|
|
211
249
|
return cachedBlob;
|
|
212
250
|
}
|
|
213
|
-
// Previously verified against this exact pin
|
|
251
|
+
// Previously verified against this exact pin AND the cached blob still
|
|
252
|
+
// has the size recorded at verification time: skip the re-hash so
|
|
214
253
|
// startup cost is unchanged.
|
|
215
|
-
const marker = await
|
|
216
|
-
if (marker) {
|
|
254
|
+
const marker = await readMarkerSize(cache, url, expectedSha256);
|
|
255
|
+
if (marker.present && marker.size === cachedBlob.size) {
|
|
217
256
|
onProgress?.(cachedBlob.size, cachedBlob.size);
|
|
218
257
|
options?.onVerified?.({ url, sha256: expectedSha256 });
|
|
219
258
|
return cachedBlob;
|
|
220
259
|
}
|
|
221
|
-
|
|
260
|
+
if (marker.present && marker.size !== null) {
|
|
261
|
+
console.warn(`[DocCloak] Cached model size ${cachedBlob.size} differs from its verification marker (${marker.size}), re-hashing`);
|
|
262
|
+
}
|
|
263
|
+
// No marker (cache predates pinning, or the pin changed), a legacy
|
|
264
|
+
// 0.11.0 marker without a size, or a size mismatch: hash once and
|
|
265
|
+
// (re)write the marker with the verified size.
|
|
222
266
|
const actualSha256 = await sha256Hex(cachedBlob);
|
|
223
267
|
if (actualSha256 === expectedSha256) {
|
|
224
|
-
await markVerified(cache, url, expectedSha256);
|
|
268
|
+
await markVerified(cache, url, expectedSha256, cachedBlob.size);
|
|
225
269
|
onProgress?.(cachedBlob.size, cachedBlob.size);
|
|
226
270
|
options?.onVerified?.({ url, sha256: expectedSha256 });
|
|
227
271
|
return cachedBlob;
|
|
@@ -237,7 +281,8 @@ export async function fetchModelBlob(env, url, onProgress, options) {
|
|
|
237
281
|
if (!force && now - lastProgressAt < PROGRESS_INTERVAL_MS)
|
|
238
282
|
return;
|
|
239
283
|
lastProgressAt = now;
|
|
240
|
-
|
|
284
|
+
// The pinned size stands in for the total when the server sends none.
|
|
285
|
+
onProgress?.(downloaded, total || pinnedSize || 0);
|
|
241
286
|
};
|
|
242
287
|
let chunks = [];
|
|
243
288
|
let downloaded = 0;
|
|
@@ -289,16 +334,17 @@ export async function fetchModelBlob(env, url, onProgress, options) {
|
|
|
289
334
|
chunks = [];
|
|
290
335
|
if (expectedSha256) {
|
|
291
336
|
// Verify the final assembled blob (covers both fresh and resumed
|
|
292
|
-
// downloads) BEFORE it is cached or handed to the runtime.
|
|
337
|
+
// downloads) BEFORE it is cached or handed to the runtime. A pinned
|
|
338
|
+
// size that disagrees is an integrity failure too.
|
|
293
339
|
const actualSha256 = await sha256Hex(blob);
|
|
294
|
-
if (actualSha256 !== expectedSha256) {
|
|
340
|
+
if (actualSha256 !== expectedSha256 || (pinnedSize !== undefined && blob.size !== pinnedSize)) {
|
|
295
341
|
await evictModelFromCache(env, url, expectedSha256);
|
|
296
342
|
throw new ModelIntegrityError(url, expectedSha256, actualSha256);
|
|
297
343
|
}
|
|
298
344
|
}
|
|
299
345
|
const stored = await tryCachePut(cache, url, blob);
|
|
300
346
|
if (expectedSha256 && stored)
|
|
301
|
-
await markVerified(cache, url, expectedSha256);
|
|
347
|
+
await markVerified(cache, url, expectedSha256, blob.size);
|
|
302
348
|
// Prefer the cached (disk-backed) copy so the in-memory chunks can be
|
|
303
349
|
// collected before ONNX Runtime allocates its own copy of the model.
|
|
304
350
|
const diskBlob = await readFromCache(cache, url);
|
|
@@ -325,10 +371,10 @@ export async function evictModelFromCache(env, url, sha256) {
|
|
|
325
371
|
}
|
|
326
372
|
}
|
|
327
373
|
/**
|
|
328
|
-
* Retry an async operation with exponential backoff.
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
374
|
+
* Retry an async operation with exponential backoff. Kept for the
|
|
375
|
+
* deprecated CoreEnv.loadTokenizer path (a host library without its own
|
|
376
|
+
* retry handling) and for hosts composing their own downloads; the pinned
|
|
377
|
+
* tokenizer files now go through fetchModelBlob via loadPinnedTokenizer.
|
|
332
378
|
*/
|
|
333
379
|
export async function retryAsync(operation, label, options) {
|
|
334
380
|
const maxAttempts = options?.maxAttempts ?? MAX_ATTEMPTS;
|
|
@@ -345,3 +391,62 @@ export async function retryAsync(operation, label, options) {
|
|
|
345
391
|
}
|
|
346
392
|
}
|
|
347
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Download (or serve from cache) one pinned JSON file through fetchModelBlob
|
|
396
|
+
* with its sha256 and size, then parse it. Integrity failures surface as
|
|
397
|
+
* ModelIntegrityError with the entry evicted, like the model itself.
|
|
398
|
+
*/
|
|
399
|
+
export async function fetchPinnedJson(env, file, options) {
|
|
400
|
+
const blob = await fetchModelBlob(env, file.url, undefined, {
|
|
401
|
+
...options,
|
|
402
|
+
sha256: file.sha256,
|
|
403
|
+
size: file.size,
|
|
404
|
+
});
|
|
405
|
+
const text = await blob.text();
|
|
406
|
+
try {
|
|
407
|
+
return JSON.parse(text);
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
throw new Error(`Pinned file is not valid JSON (${file.url}): ${err instanceof Error ? err.message : String(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
let warnedDeprecatedLoadTokenizer = false;
|
|
414
|
+
/**
|
|
415
|
+
* Resolve a provider's tokenizer through the host env.
|
|
416
|
+
*
|
|
417
|
+
* Preferred path: fetch both pinned files via fetchPinnedJson (resume,
|
|
418
|
+
* retry, blob cache, verification marker, ModelIntegrityError) and hand the
|
|
419
|
+
* parsed objects to CoreEnv.buildTokenizer. No library probes mutable
|
|
420
|
+
* resolve/main, no second cache bucket, and a warm cache works offline.
|
|
421
|
+
*
|
|
422
|
+
* Fallback (deprecated, removed in 0.13.0): hosts that only implement
|
|
423
|
+
* CoreEnv.loadTokenizer(hfModelId) are still served through it with the
|
|
424
|
+
* previous retry wrapper.
|
|
425
|
+
*/
|
|
426
|
+
export async function loadPinnedTokenizer(env, files, legacyHfModelId, options) {
|
|
427
|
+
if (typeof env.buildTokenizer === 'function') {
|
|
428
|
+
const loaderEnv = {
|
|
429
|
+
cache: env.modelCache,
|
|
430
|
+
fetch: env.fetch,
|
|
431
|
+
persistStorage: env.persistStorage,
|
|
432
|
+
};
|
|
433
|
+
const [tokenizerJson, tokenizerConfig] = await Promise.all([
|
|
434
|
+
fetchPinnedJson(loaderEnv, files[0], options),
|
|
435
|
+
fetchPinnedJson(loaderEnv, files[1], options),
|
|
436
|
+
]);
|
|
437
|
+
const tokenizer = await env.buildTokenizer(tokenizerJson, tokenizerConfig);
|
|
438
|
+
if (tokenizer === null || tokenizer === undefined) {
|
|
439
|
+
throw new Error('CoreEnv.buildTokenizer returned no tokenizer');
|
|
440
|
+
}
|
|
441
|
+
return tokenizer;
|
|
442
|
+
}
|
|
443
|
+
if (typeof env.loadTokenizer === 'function') {
|
|
444
|
+
if (!warnedDeprecatedLoadTokenizer) {
|
|
445
|
+
warnedDeprecatedLoadTokenizer = true;
|
|
446
|
+
console.warn('[DocCloak] CoreEnv.loadTokenizer is deprecated and will be removed in @doccloak/core 0.13.0; implement buildTokenizer(tokenizerJson, tokenizerConfig) instead');
|
|
447
|
+
}
|
|
448
|
+
// Call through env so host implementations keep their receiver.
|
|
449
|
+
return retryAsync(() => env.loadTokenizer(legacyHfModelId), 'Tokenizer download', options);
|
|
450
|
+
}
|
|
451
|
+
throw new Error('CoreEnv must implement buildTokenizer(tokenizerJson, tokenizerConfig); loadTokenizer(hfModelId) is deprecated');
|
|
452
|
+
}
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,19 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure entity pipeline (T005).
|
|
3
3
|
*
|
|
4
|
-
* Extracted
|
|
5
|
-
* entities
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Extracted from the web app's detection.worker.ts: text and entities in,
|
|
5
|
+
* entities out. No worker messaging, no providers, no environment access.
|
|
6
|
+
* The worker (and any other host) composes these functions around its own
|
|
7
|
+
* ML provider call.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* of
|
|
9
|
+
* Security pass 2026-09:
|
|
10
|
+
* - T180: detectWithRegex runs every rule line by line (rules flagged
|
|
11
|
+
* `multiline` see the whole text), so quadratic patterns cost O(line^2)
|
|
12
|
+
* instead of O(document^2).
|
|
13
|
+
* - T181: filterFalsePositives is script-aware (CJK/Thai and PERSON values
|
|
14
|
+
* of two characters survive) and propagateEntities uses Unicode letter/
|
|
15
|
+
* digit boundaries, exact offsets on the original text and a common-word
|
|
16
|
+
* guard for lowercase PERSON matches.
|
|
13
17
|
*/
|
|
14
18
|
import type { DetectedEntity } from './types.js';
|
|
19
|
+
/** One newline-free slice of the input plus its offset in the original text. */
|
|
20
|
+
interface TextSegment {
|
|
21
|
+
text: string;
|
|
22
|
+
offset: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Split on '\n' keeping offsets. '\r' stays inside the line (a CRLF line then
|
|
26
|
+
* ends with '\r', exactly as it did when the whole text was matched at once).
|
|
27
|
+
* Empty lines are skipped: no rule can match the empty string.
|
|
28
|
+
*/
|
|
29
|
+
export declare function segmentLines(text: string): TextSegment[];
|
|
30
|
+
/**
|
|
31
|
+
* Run the regex rules of a region over the text.
|
|
32
|
+
*
|
|
33
|
+
* Rules without `multiline` run on each line separately (offsets are added
|
|
34
|
+
* back, so spans are document-absolute); rules with `multiline: true` run on
|
|
35
|
+
* the whole text. Output order is rule-major, then position, as before.
|
|
36
|
+
*
|
|
37
|
+
* Complexity note (T180): several street/company rules are quadratic in the
|
|
38
|
+
* length of their input. Per-line matching bounds that to the longest line,
|
|
39
|
+
* so 100 KB of ordinary prose is fast; a single 100 KB line without newlines
|
|
40
|
+
* is still slow until the patterns themselves are bounded (T198).
|
|
41
|
+
*/
|
|
15
42
|
export declare function detectWithRegex(text: string, regexRegion?: string): DetectedEntity[];
|
|
16
43
|
export declare function resolveOverlaps(entities: DetectedEntity[]): DetectedEntity[];
|
|
44
|
+
/**
|
|
45
|
+
* Word-level providers (BardS.ai splits on whitespace) return spans that
|
|
46
|
+
* carry the punctuation glued to the last word: "Anna Nowak," or
|
|
47
|
+
* "jan@example.com.". The redacted text then loses that punctuation and the
|
|
48
|
+
* mapping stores it as part of the value. Trim a fixed set of opening and
|
|
49
|
+
* closing punctuation at both ends of every span (T204); a span that would
|
|
50
|
+
* become empty is dropped. Regex spans are not passed through this: their
|
|
51
|
+
* patterns already stop at the identifier.
|
|
52
|
+
*/
|
|
53
|
+
export declare function trimSpanPunctuation(entities: DetectedEntity[]): DetectedEntity[];
|
|
54
|
+
/**
|
|
55
|
+
* Two-character PERSON values that are titles, suffixes or abbreviations,
|
|
56
|
+
* never names. Compared against the lowercased trimmed value (T181, R2).
|
|
57
|
+
*/
|
|
58
|
+
export declare const PERSON_SHORT_STOPLIST: ReadonlySet<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Drop ML spans that are too short to be real entities (R2):
|
|
61
|
+
* - values containing CJK or Thai characters: at least 2 code points, any type
|
|
62
|
+
* - PERSON: at least 2 characters, unless the value is a known 2-letter
|
|
63
|
+
* title/suffix (PERSON_SHORT_STOPLIST); initials like "J." pass
|
|
64
|
+
* - every other type: at least 3 characters (unchanged)
|
|
65
|
+
*/
|
|
17
66
|
export declare function filterFalsePositives(entities: DetectedEntity[]): DetectedEntity[];
|
|
67
|
+
/**
|
|
68
|
+
* Words that are also given names or surnames, per language (T181, R15).
|
|
69
|
+
* A PERSON term found in the text with DIFFERENT casing than the detected
|
|
70
|
+
* entity, and NOT starting with an uppercase letter, is skipped when its
|
|
71
|
+
* lowercase form is listed here: "Bill" the person never turns "the bill is
|
|
72
|
+
* due" into "[PERSON_1] is due". Capitalised and ALL-CAPS mentions always
|
|
73
|
+
* propagate, and lowercase mentions of ordinary names ("thanks john") are
|
|
74
|
+
* still caught (golden prop-03-case-insensitive).
|
|
75
|
+
*
|
|
76
|
+
* Only words that appear in lowercase in running prose belong here. German
|
|
77
|
+
* nouns are always capitalised in prose, so the DE list holds adjectives and
|
|
78
|
+
* month names that double as surnames or first names; the PL list holds
|
|
79
|
+
* nouns (animals, months, plants) that are frequent Polish surnames and
|
|
80
|
+
* first names.
|
|
81
|
+
*/
|
|
82
|
+
export declare const COMMON_WORD_NAMES: Readonly<Record<'en' | 'pl' | 'de', readonly string[]>>;
|
|
83
|
+
/**
|
|
84
|
+
* Find every further occurrence of each detected value (and of the words of
|
|
85
|
+
* multi-word values) in the text and emit it as a 'propagated' entity.
|
|
86
|
+
*
|
|
87
|
+
* Terms: whole values of at least 2 characters; words of multi-word values
|
|
88
|
+
* of at least 4 characters (trailing punctuation stripped). Terms of at most
|
|
89
|
+
* 2 characters match with exact case; longer terms match case-insensitively
|
|
90
|
+
* (Unicode case folding via the 'iu' flags) on the ORIGINAL text, so offsets
|
|
91
|
+
* are exact even after characters whose lowercase form has a different
|
|
92
|
+
* length (Turkish dotted İ). A hit needs a non-letter, non-digit neighbour
|
|
93
|
+
* on both sides ([\p{L}\p{N}] complement), except on a side where the term
|
|
94
|
+
* itself ends in an unspaced script (CJK, Thai).
|
|
95
|
+
*/
|
|
18
96
|
export declare function propagateEntities(text: string, entities: DetectedEntity[]): DetectedEntity[];
|
|
19
97
|
export declare function detectEntities(text: string, mlEntities: DetectedEntity[], regexEntities: DetectedEntity[]): DetectedEntity[];
|
|
98
|
+
export {};
|