@doccloak/core 0.8.0 → 0.10.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.
@@ -14,6 +14,10 @@
14
14
  * - Serves the model from the injected BlobCache when available and prefers
15
15
  * the cached (typically disk-backed) copy over the in-memory one to reduce
16
16
  * peak RAM, which matters on iOS Safari where large tabs get killed.
17
+ * - Verifies downloads against a pinned SHA-256 when the caller provides one
18
+ * (supply-chain hardening, T116): mismatches evict the cache entry and
19
+ * reject with ModelIntegrityError; verified cache entries carry a marker
20
+ * so they are not re-hashed on every startup.
17
21
  *
18
22
  * Environment-agnostic: all host capabilities (blob cache, fetch, persistent
19
23
  * storage request) are injected via ModelLoaderEnv. This module never touches
@@ -33,9 +37,47 @@ export interface ModelLoaderEnv {
33
37
  /** Best-effort persistent-storage request (navigator.storage.persist() on the web). */
34
38
  persistStorage?: () => Promise<boolean>;
35
39
  }
40
+ /**
41
+ * A downloaded (or cached) model blob did not match the pinned SHA-256.
42
+ * Distinct error type so hosts can show a dedicated "integrity check
43
+ * failed" state instead of a generic download error. The offending cache
44
+ * entry is evicted before this is thrown; the regex/rules tier keeps
45
+ * working (existing degraded-mode behavior).
46
+ */
47
+ export declare class ModelIntegrityError extends Error {
48
+ url: string;
49
+ expectedSha256: string;
50
+ actualSha256: string;
51
+ constructor(url: string, expectedSha256: string, actualSha256: string);
52
+ }
53
+ /** Successful SHA-256 verification of a model blob, surfaced to the model-status UI. */
54
+ export interface ModelVerification {
55
+ url: string;
56
+ /** The verified SHA-256, lowercase hex - equals the pinned hash. */
57
+ sha256: string;
58
+ }
59
+ /**
60
+ * Cache key of the tiny "this URL was verified against this hash" marker.
61
+ * Uses a query parameter (not a URL fragment) because Cache Storage strips
62
+ * fragments, which would collide with the model entry itself. Keyed by
63
+ * url AND hash so bumping a pinned hash automatically invalidates markers
64
+ * written for the previous pin.
65
+ */
66
+ export declare function verificationMarkerKey(url: string, sha256: string): string;
36
67
  export interface FetchModelOptions {
37
68
  maxAttempts?: number;
38
69
  retryBaseDelayMs?: number;
70
+ /**
71
+ * Pinned SHA-256 (lowercase hex) of the model blob. When set, every
72
+ * network download is hashed before use; a mismatch evicts the cache
73
+ * entry and rejects with ModelIntegrityError. Cached blobs that carry a
74
+ * verification marker for this hash skip re-hashing so startup cost is
75
+ * unchanged; cached blobs without a marker are hashed once and either
76
+ * marked verified or evicted and re-downloaded.
77
+ */
78
+ sha256?: string;
79
+ /** Fired once the served blob is known to match the pinned sha256. */
80
+ onVerified?: (verification: ModelVerification) => void;
39
81
  }
40
82
  /**
41
83
  * Download a model with progress reporting, resume and retry.
@@ -43,9 +85,10 @@ export interface FetchModelOptions {
43
85
  */
44
86
  export declare function fetchModelBlob(env: ModelLoaderEnv, url: string, onProgress?: DownloadProgress, options?: FetchModelOptions): Promise<Blob>;
45
87
  /**
46
- * Remove a single model from the cache (e.g. when its URL changes).
88
+ * Remove a single model from the cache (e.g. when its URL changes). When a
89
+ * pinned sha256 is given, the matching verification marker is removed too.
47
90
  */
48
- export declare function evictModelFromCache(env: ModelLoaderEnv, url: string): Promise<void>;
91
+ export declare function evictModelFromCache(env: ModelLoaderEnv, url: string, sha256?: string): Promise<void>;
49
92
  /**
50
93
  * Retry an async operation with exponential backoff. Used for the smaller
51
94
  * companion downloads (tokenizer files) that go through libraries without
@@ -14,6 +14,10 @@
14
14
  * - Serves the model from the injected BlobCache when available and prefers
15
15
  * the cached (typically disk-backed) copy over the in-memory one to reduce
16
16
  * peak RAM, which matters on iOS Safari where large tabs get killed.
17
+ * - Verifies downloads against a pinned SHA-256 when the caller provides one
18
+ * (supply-chain hardening, T116): mismatches evict the cache entry and
19
+ * reject with ModelIntegrityError; verified cache entries carry a marker
20
+ * so they are not re-hashed on every startup.
17
21
  *
18
22
  * Environment-agnostic: all host capabilities (blob cache, fetch, persistent
19
23
  * storage request) are injected via ModelLoaderEnv. This module never touches
@@ -30,6 +34,52 @@ class HttpError extends Error {
30
34
  this.status = status;
31
35
  }
32
36
  }
37
+ /**
38
+ * A downloaded (or cached) model blob did not match the pinned SHA-256.
39
+ * Distinct error type so hosts can show a dedicated "integrity check
40
+ * failed" state instead of a generic download error. The offending cache
41
+ * entry is evicted before this is thrown; the regex/rules tier keeps
42
+ * working (existing degraded-mode behavior).
43
+ */
44
+ export class ModelIntegrityError extends Error {
45
+ constructor(url, expectedSha256, actualSha256) {
46
+ super(`Model failed SHA-256 integrity check: expected ${expectedSha256.slice(0, 12)}..., ` +
47
+ `got ${actualSha256.slice(0, 12)}... (${url}). The downloaded copy was discarded.`);
48
+ this.name = 'ModelIntegrityError';
49
+ this.url = url;
50
+ this.expectedSha256 = expectedSha256;
51
+ this.actualSha256 = actualSha256;
52
+ }
53
+ }
54
+ /**
55
+ * Cache key of the tiny "this URL was verified against this hash" marker.
56
+ * Uses a query parameter (not a URL fragment) because Cache Storage strips
57
+ * fragments, which would collide with the model entry itself. Keyed by
58
+ * url AND hash so bumping a pinned hash automatically invalidates markers
59
+ * written for the previous pin.
60
+ */
61
+ export function verificationMarkerKey(url, sha256) {
62
+ const sep = url.includes('?') ? '&' : '?';
63
+ return `${url}${sep}doccloak-sha256-verified=${sha256}`;
64
+ }
65
+ /**
66
+ * SHA-256 of a blob as lowercase hex. WebCrypto has no streaming digest,
67
+ * so the blob is hashed as one contiguous buffer: peak cost is one extra
68
+ * transient copy of the model, released before ONNX Runtime allocates its
69
+ * own copy.
70
+ */
71
+ async function sha256Hex(blob) {
72
+ const buffer = await blob.arrayBuffer();
73
+ const digest = await crypto.subtle.digest('SHA-256', buffer);
74
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');
75
+ }
76
+ /** Best-effort write of the verification marker (a 1-byte blob). */
77
+ async function markVerified(cache, url, sha256) {
78
+ try {
79
+ await cache.put(verificationMarkerKey(url, sha256), new Blob(['1']));
80
+ }
81
+ catch { /* marker is an optimisation - next startup just re-hashes */ }
82
+ }
33
83
  function isRetryable(err) {
34
84
  if (err instanceof HttpError) {
35
85
  return err.status === 408 || err.status === 429 || err.status >= 500;
@@ -80,9 +130,11 @@ async function tryCachePut(cache, url, blob) {
80
130
  if (!stored) {
81
131
  console.warn('[DocCloak] Model cache put refused (model still loaded in memory)');
82
132
  }
133
+ return stored;
83
134
  }
84
135
  catch (err) {
85
136
  console.warn('[DocCloak] Model cache put failed (model still loaded in memory):', err);
137
+ return false;
86
138
  }
87
139
  }
88
140
  /**
@@ -150,11 +202,33 @@ async function downloadAttempt(fetchFn, url, offset, onChunk, onHeaders) {
150
202
  export async function fetchModelBlob(env, url, onProgress, options) {
151
203
  const maxAttempts = options?.maxAttempts ?? MAX_ATTEMPTS;
152
204
  const retryBaseDelayMs = options?.retryBaseDelayMs ?? RETRY_BASE_DELAY_MS;
205
+ const expectedSha256 = options?.sha256?.toLowerCase();
153
206
  const cache = env.cache;
154
207
  const cachedBlob = await readFromCache(cache, url);
155
208
  if (cachedBlob) {
156
- onProgress?.(cachedBlob.size, cachedBlob.size);
157
- return cachedBlob;
209
+ if (!expectedSha256) {
210
+ onProgress?.(cachedBlob.size, cachedBlob.size);
211
+ return cachedBlob;
212
+ }
213
+ // Previously verified against this exact pin: skip the re-hash so
214
+ // startup cost is unchanged.
215
+ const marker = await readFromCache(cache, verificationMarkerKey(url, expectedSha256));
216
+ if (marker) {
217
+ onProgress?.(cachedBlob.size, cachedBlob.size);
218
+ options?.onVerified?.({ url, sha256: expectedSha256 });
219
+ return cachedBlob;
220
+ }
221
+ // No marker (cache predates pinning, or the pin changed): hash once.
222
+ const actualSha256 = await sha256Hex(cachedBlob);
223
+ if (actualSha256 === expectedSha256) {
224
+ await markVerified(cache, url, expectedSha256);
225
+ onProgress?.(cachedBlob.size, cachedBlob.size);
226
+ options?.onVerified?.({ url, sha256: expectedSha256 });
227
+ return cachedBlob;
228
+ }
229
+ // Stale or corrupt cached copy - evict and fall through to the network.
230
+ console.warn(`[DocCloak] Cached model failed SHA-256 check (expected ${expectedSha256.slice(0, 12)}..., got ${actualSha256.slice(0, 12)}...), re-downloading`);
231
+ await evictModelFromCache(env, url, expectedSha256);
158
232
  }
159
233
  await requestPersistentStorage(env);
160
234
  let lastProgressAt = 0;
@@ -213,22 +287,42 @@ export async function fetchModelBlob(env, url, onProgress, options) {
213
287
  reportProgress(downloaded, total || downloaded, true);
214
288
  let blob = new Blob(chunks);
215
289
  chunks = [];
216
- await tryCachePut(cache, url, blob);
290
+ if (expectedSha256) {
291
+ // Verify the final assembled blob (covers both fresh and resumed
292
+ // downloads) BEFORE it is cached or handed to the runtime.
293
+ const actualSha256 = await sha256Hex(blob);
294
+ if (actualSha256 !== expectedSha256) {
295
+ await evictModelFromCache(env, url, expectedSha256);
296
+ throw new ModelIntegrityError(url, expectedSha256, actualSha256);
297
+ }
298
+ }
299
+ const stored = await tryCachePut(cache, url, blob);
300
+ if (expectedSha256 && stored)
301
+ await markVerified(cache, url, expectedSha256);
217
302
  // Prefer the cached (disk-backed) copy so the in-memory chunks can be
218
303
  // collected before ONNX Runtime allocates its own copy of the model.
219
304
  const diskBlob = await readFromCache(cache, url);
220
305
  if (diskBlob && diskBlob.size === blob.size)
221
306
  blob = diskBlob;
307
+ if (expectedSha256)
308
+ options?.onVerified?.({ url, sha256: expectedSha256 });
222
309
  return blob;
223
310
  }
224
311
  /**
225
- * Remove a single model from the cache (e.g. when its URL changes).
312
+ * Remove a single model from the cache (e.g. when its URL changes). When a
313
+ * pinned sha256 is given, the matching verification marker is removed too.
226
314
  */
227
- export async function evictModelFromCache(env, url) {
315
+ export async function evictModelFromCache(env, url, sha256) {
228
316
  try {
229
317
  await env.cache.delete(url);
230
318
  }
231
319
  catch { /* ignore */ }
320
+ if (sha256) {
321
+ try {
322
+ await env.cache.delete(verificationMarkerKey(url, sha256.toLowerCase()));
323
+ }
324
+ catch { /* ignore */ }
325
+ }
232
326
  }
233
327
  /**
234
328
  * Retry an async operation with exponential backoff. Used for the smaller
@@ -12,6 +12,11 @@
12
12
  */
13
13
  import type { DetectedEntity, DetectionProvider, ProgressCallback } from '../types.js';
14
14
  import type { CoreEnv } from '../env.js';
15
+ import type { ModelVerification } from '../model-loader.js';
16
+ export declare const BARDSAI_MODEL_REVISION = "0e72e19f030ed4e661b1673e549af8e0dd176386";
17
+ /** SHA-256 of onnx/model_quantized.onnx (278,736,360 bytes) at BARDSAI_MODEL_REVISION. */
18
+ export declare const BARDSAI_MODEL_SHA256 = "8c9f555c743ed14eb7e505ff9d9c7785775a1671fd14031af33f424de8bd0e7b";
19
+ export declare const BARDSAI_MODEL_URL = "https://huggingface.co/bardsai/eu-pii-anonimization-multilang/resolve/0e72e19f030ed4e661b1673e549af8e0dd176386/onnx/model_quantized.onnx";
15
20
  export declare class BardsaiProvider implements DetectionProvider {
16
21
  private _name;
17
22
  get name(): string;
@@ -23,7 +28,14 @@ export declare class BardsaiProvider implements DetectionProvider {
23
28
  private loadWaiters;
24
29
  private progressCallback;
25
30
  private threshold;
31
+ private verification;
26
32
  constructor(env: CoreEnv);
33
+ /**
34
+ * SHA-256 verification result of the served model blob (null until the
35
+ * model has been verified against the pinned hash). Surfaced so the
36
+ * model-status UI can show the positive "Model verified" state.
37
+ */
38
+ getVerification(): ModelVerification | null;
27
39
  isLoaded(): boolean;
28
40
  isLoading(): boolean;
29
41
  onProgress(callback: ProgressCallback): void;
@@ -10,10 +10,27 @@
10
10
  * tokenizer all arrive through the injected CoreEnv. This module never touches
11
11
  * import.meta, caches, navigator or @huggingface/transformers directly.
12
12
  */
13
- import * as ort from 'onnxruntime-web';
14
- import { fetchModelBlob, retryAsync } from "../model-loader.js";
13
+ // ort 1.29: the /webgpu entry is the non-deprecated build (native WebGPU EP +
14
+ // wasm fallback, asyncify binary). The bare entry resolves the deprecated JSEP
15
+ // bundle whose wasm exceeds Cloudflare Pages' 25 MiB per-file limit.
16
+ import * as ort from 'onnxruntime-web/webgpu';
17
+ import { evictModelFromCache, fetchModelBlob, retryAsync } from "../model-loader.js";
15
18
  // ── Model config ──────────────────────────────────────────
16
- const MODEL_URL = 'https://huggingface.co/bardsai/eu-pii-anonimization-multilang/resolve/main/onnx/model_quantized.onnx';
19
+ // Supply-chain pinning (T116): the model is fetched from an immutable
20
+ // commit revision, never from mutable resolve/main, and the downloaded
21
+ // blob is verified against a pinned SHA-256. A model update is a
22
+ // deliberate act: bump BARDSAI_MODEL_REVISION and BARDSAI_MODEL_SHA256
23
+ // together in a reviewed commit and update
24
+ // documentation/model-provenance.md.
25
+ // Revision: main of bardsai/eu-pii-anonimization-multilang as of its last
26
+ // modification 2026-05-13; pinned and hashed 2026-08-10.
27
+ export const BARDSAI_MODEL_REVISION = '0e72e19f030ed4e661b1673e549af8e0dd176386';
28
+ /** SHA-256 of onnx/model_quantized.onnx (278,736,360 bytes) at BARDSAI_MODEL_REVISION. */
29
+ export const BARDSAI_MODEL_SHA256 = '8c9f555c743ed14eb7e505ff9d9c7785775a1671fd14031af33f424de8bd0e7b';
30
+ export const BARDSAI_MODEL_URL = `https://huggingface.co/bardsai/eu-pii-anonimization-multilang/resolve/${BARDSAI_MODEL_REVISION}/onnx/model_quantized.onnx`;
31
+ const MODEL_URL = BARDSAI_MODEL_URL;
32
+ /** Pre-pinning download URL; its cache entry is evicted best-effort on load. */
33
+ const LEGACY_MODEL_URL = 'https://huggingface.co/bardsai/eu-pii-anonimization-multilang/resolve/main/onnx/model_quantized.onnx';
17
34
  const TOKENIZER_HF = 'bardsai/eu-pii-anonimization-multilang';
18
35
  const MODEL_NAME = 'BardS.ai EU PII';
19
36
  const DEFAULT_THRESHOLD = 0.5;
@@ -102,8 +119,17 @@ export class BardsaiProvider {
102
119
  this.loadWaiters = [];
103
120
  this.progressCallback = null;
104
121
  this.threshold = DEFAULT_THRESHOLD;
122
+ this.verification = null;
105
123
  this.env = env;
106
124
  }
125
+ /**
126
+ * SHA-256 verification result of the served model blob (null until the
127
+ * model has been verified against the pinned hash). Surfaced so the
128
+ * model-status UI can show the positive "Model verified" state.
129
+ */
130
+ getVerification() {
131
+ return this.verification;
132
+ }
107
133
  isLoaded() {
108
134
  return this.session !== null && this.tokenizer !== null;
109
135
  }
@@ -141,9 +167,18 @@ export class BardsaiProvider {
141
167
  fetch: this.env.fetch,
142
168
  persistStorage: this.env.persistStorage,
143
169
  };
170
+ // Old cache entry from the pre-pinning resolve/main URL - drop it so
171
+ // a 279 MB stale copy does not linger against the origin quota.
172
+ void evictModelFromCache(loaderEnv, LEGACY_MODEL_URL);
144
173
  // Load tokenizer and model in parallel (skip tokenizer if already loaded from a prior session)
145
174
  const tasks = [
146
- fetchModelBlob(loaderEnv, MODEL_URL, (downloaded, total) => this.progressCallback?.(downloaded, total)),
175
+ fetchModelBlob(loaderEnv, MODEL_URL, (downloaded, total) => this.progressCallback?.(downloaded, total), {
176
+ sha256: BARDSAI_MODEL_SHA256,
177
+ onVerified: (verification) => {
178
+ this.verification = verification;
179
+ console.info(`[DocCloak] Model verified: ${this._name} SHA-256 ${verification.sha256.slice(0, 12)}... matches the published hash (see documentation/model-provenance.md)`);
180
+ },
181
+ }),
147
182
  ];
148
183
  if (!this.tokenizer) {
149
184
  tasks.push(retryAsync(() => this.env.loadTokenizer(TOKENIZER_HF), 'Tokenizer download').then((t) => {
@@ -199,13 +234,11 @@ export class BardsaiProvider {
199
234
  }
200
235
  if (words.length === 0)
201
236
  return [];
202
- // Tokenize full text once to count subtokens per word via ▁ prefix
237
+ // Tokenize full text once to count subtokens per word via ▁ prefix.
238
+ // transformers.js 4.x removed tokenizer.model.convert_ids_to_tokens;
239
+ // tokenize() returns the same SentencePiece token strings directly.
203
240
  onProgress?.(0);
204
- const fullEncoded = await this.tokenizer(words.map((w) => w.word).join(' '), {
205
- add_special_tokens: false,
206
- });
207
- const tokenIds = Array.from(fullEncoded.input_ids.data);
208
- const tokenStrings = this.tokenizer.model.convert_ids_to_tokens(tokenIds.map(Number));
241
+ const tokenStrings = this.tokenizer.tokenize(words.map((w) => w.word).join(' '));
209
242
  // Count subtokens per word: ▁ prefix marks word boundaries (SentencePiece)
210
243
  const subtokenCounts = [];
211
244
  let currentCount = 0;
@@ -285,16 +318,15 @@ export class BardsaiProvider {
285
318
  const attentionMask = encoded.attention_mask.data;
286
319
  const seqLen = encoded.input_ids.dims[1];
287
320
  // Build word→subtoken alignment via ▁ prefix (no per-word tokenization needed)
288
- const chunkTokenIds = Array.from(inputIds);
289
- const chunkTokenStrings = this.tokenizer.model.convert_ids_to_tokens(chunkTokenIds.map(Number));
321
+ // transformers.js 4.x removed tokenizer.model.convert_ids_to_tokens;
322
+ // tokenize() returns the token strings without specials, so positions are
323
+ // offset by 1 for the leading <s> in the encoded sequence and the seqLen
324
+ // guard stops at the truncation boundary.
325
+ const chunkTokenStrings = this.tokenizer.tokenize(chunkText);
290
326
  const wordSubtokenStart = [];
291
- // Skip <s> at position 0; first real token at position 1
292
- for (let t = 1; t < seqLen - 1; t++) {
293
- const tok = chunkTokenStrings[t];
294
- if (!tok || tok === '</s>')
295
- break;
296
- if (tok.startsWith('\u2581')) {
297
- wordSubtokenStart.push(t);
327
+ for (let i = 0; i < chunkTokenStrings.length && i + 1 < seqLen - 1; i++) {
328
+ if (chunkTokenStrings[i].startsWith('\u2581')) {
329
+ wordSubtokenStart.push(i + 1);
298
330
  }
299
331
  }
300
332
  // XLM-RoBERTa: only input_ids + attention_mask (no token_type_ids)
@@ -0,0 +1,92 @@
1
+ /**
2
+ * GLiNER PII Base provider (T122).
3
+ *
4
+ * knowledgator/gliner-pii-base-v1.0: deberta-v3-small backbone, zero-shot
5
+ * custom labels, span_mode "markerV0". Unlike the token_level small/edge
6
+ * models (gliner.ts), the ONNX graph takes span enumeration inputs
7
+ * (span_idx + span_mask) and emits span-level logits shaped
8
+ * [batch, num_words, max_width, num_classes] - one score per candidate
9
+ * (start word, width) pair per label. Verified against the pinned model in
10
+ * Node WASM: inputs [input_ids, attention_mask, words_mask, text_lengths,
11
+ * span_idx (int64), span_mask (bool)], output [logits].
12
+ *
13
+ * Environment-agnostic: wasm paths, custom-label persistence (KV), model
14
+ * bytes (blob cache + fetch) and the tokenizer all arrive through the
15
+ * injected CoreEnv. This module never touches import.meta, localStorage,
16
+ * caches, navigator or @huggingface/transformers directly.
17
+ */
18
+ import type { DetectedEntity, DetectionProvider, ProgressCallback } from '../types.js';
19
+ import type { CoreEnv } from '../env.js';
20
+ import type { ModelVerification } from '../model-loader.js';
21
+ import { type RawSpan } from './gliner.js';
22
+ export declare const GLINER_BASE_MODEL_REVISION = "61726e0ad791dcab3e29339bbec3ad42ded65641";
23
+ /** SHA-256 of onnx/model_quint8.onnx (196,757,174 bytes) at GLINER_BASE_MODEL_REVISION. */
24
+ export declare const GLINER_BASE_MODEL_SHA256 = "0514c8fd86d0513ce5351a3267f132b57d5bcd8f99a90d43cde1228092881d19";
25
+ export declare const GLINER_BASE_MODEL_URL = "https://huggingface.co/knowledgator/gliner-pii-base-v1.0/resolve/61726e0ad791dcab3e29339bbec3ad42ded65641/onnx/model_quint8.onnx";
26
+ /** max_width from the repo's gliner_config.json: spans cover 1..12 words. */
27
+ export declare const GLINER_BASE_MAX_WIDTH = 12;
28
+ export interface SpanIndices {
29
+ /** Flattened (start, end) word pairs, shape [1, numSpans, 2]. */
30
+ spanIdx: BigInt64Array;
31
+ /** 1 for valid spans (end < textLength), 0 for padding, shape [1, numSpans]. */
32
+ spanMask: Uint8Array;
33
+ numSpans: number;
34
+ }
35
+ /**
36
+ * Enumerate all candidate word spans exactly like the python GLiNER
37
+ * SpanProcessor: start-major, width-minor - for every start word i, the
38
+ * pairs (i, i+0) .. (i, i+maxWidth-1). Spans that would run past the end
39
+ * of the text are masked out and their indices zeroed (mirroring
40
+ * span_idx * span_mask in the python collate).
41
+ */
42
+ export declare function buildSpanIndices(textLength: number, maxWidth?: number): SpanIndices;
43
+ /**
44
+ * Decode markerV0 span logits (shape [1, numWords, maxWidth, numClasses])
45
+ * into raw character spans: sigmoid per (start, width, class) cell,
46
+ * threshold, then map word indices to character offsets. Non-overlap
47
+ * selection (greedySelect) happens at the caller across chunks.
48
+ */
49
+ export declare function decodeSpanLogits(logits: Float32Array, dims: readonly number[], textLength: number, labels: string[], threshold: number, charStarts: number[], charEnds: number[], fullText: string): RawSpan[];
50
+ export declare class GlinerBaseProvider implements DetectionProvider {
51
+ private _name;
52
+ get name(): string;
53
+ private env;
54
+ private tokenizer;
55
+ private session;
56
+ private loading;
57
+ private loadError;
58
+ private loadWaiters;
59
+ private progressCallback;
60
+ private threshold;
61
+ private customLabels;
62
+ private verification;
63
+ constructor(env: CoreEnv);
64
+ /**
65
+ * SHA-256 verification result of the served model blob (null until the
66
+ * model has been verified against the pinned hash). Surfaced so the
67
+ * model-status UI can show the positive "Model verified" state.
68
+ */
69
+ getVerification(): ModelVerification | null;
70
+ isLoaded(): boolean;
71
+ isLoading(): boolean;
72
+ onProgress(callback: ProgressCallback): void;
73
+ setThreshold(value: number): void;
74
+ getThreshold(): number;
75
+ /** Get the combined list of all active labels (built-in + custom). */
76
+ private getActiveLabels;
77
+ /** Get user-defined custom labels. */
78
+ getCustomLabels(): string[];
79
+ /**
80
+ * Set custom labels and persist through the injected KV store. The
81
+ * in-memory list updates synchronously (before the first await), so
82
+ * fire-and-forget callers keep the previous behavior.
83
+ */
84
+ setCustomLabels(labels: string[]): Promise<void>;
85
+ /** Restore custom labels from the injected KV store on startup. */
86
+ restoreCustomLabels(): Promise<void>;
87
+ load(): Promise<void>;
88
+ release(): void;
89
+ detect(text: string, onProgress?: (progress: number) => void): Promise<DetectedEntity[]>;
90
+ private buildPromptTokens;
91
+ private inferChunk;
92
+ }