gheim 0.1.0 → 0.1.2

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.
@@ -41,6 +41,7 @@ var exports_src = {};
41
41
  __export(exports_src, {
42
42
  renderSentinel: () => renderSentinel,
43
43
  parseSentinel: () => parseSentinel,
44
+ mergeAdjacent: () => mergeAdjacent,
44
45
  labelTag: () => labelTag,
45
46
  isPossibleSentinelPrefix: () => isPossibleSentinelPrefix,
46
47
  defaultDetector: () => defaultDetector,
@@ -96,98 +97,6 @@ function isPossibleSentinelPrefix(buffer) {
96
97
  return PARTIAL_SENTINEL_RE.test(buffer);
97
98
  }
98
99
 
99
- // src/normalizers.ts
100
- var import_node_module = require("node:module");
101
- var _require = null;
102
- var _requireResolved = false;
103
- function _getRequire() {
104
- if (_requireResolved)
105
- return _require;
106
- _requireResolved = true;
107
- try {
108
- _require = import_node_module.createRequire("file:///home/joelbarmettler/projects/gheim/packages/gheim-js/src/normalizers.ts");
109
- } catch {
110
- _require = null;
111
- }
112
- return _require;
113
- }
114
- function loadSync(name, errMsg) {
115
- const req = _getRequire();
116
- if (!req)
117
- throw new Error(errMsg);
118
- try {
119
- return req(name);
120
- } catch {
121
- throw new Error(errMsg);
122
- }
123
- }
124
- function loadPhoneLib() {
125
- return loadSync("libphonenumber-js", "the e164 normalizer requires libphonenumber-js. " + "Install with: bun add libphonenumber-js");
126
- }
127
- function e164(opts = {}) {
128
- const lib = loadPhoneLib();
129
- return (s) => {
130
- try {
131
- const parsed = lib.parsePhoneNumberFromString(s, opts.region);
132
- if (!parsed || !parsed.isValid())
133
- return null;
134
- return parsed.format("E.164");
135
- } catch {
136
- return null;
137
- }
138
- };
139
- }
140
- function loadChrono(opts) {
141
- const mod = loadSync("chrono-node", "the isoDate normalizer requires chrono-node. " + "Install with: bun add chrono-node");
142
- if (opts.locale) {
143
- const localeParser = mod[opts.locale];
144
- if (localeParser)
145
- return localeParser;
146
- }
147
- return mod;
148
- }
149
- function isoDate(opts = {}) {
150
- const parser = loadChrono(opts);
151
- return (s) => {
152
- const d = parser.parseDate(s);
153
- if (!d)
154
- return null;
155
- return d.toISOString().slice(0, 10);
156
- };
157
- }
158
- function germanTransliteration() {
159
- const table = {
160
- ä: "ae",
161
- Ä: "ae",
162
- ö: "oe",
163
- Ö: "oe",
164
- ü: "ue",
165
- Ü: "ue",
166
- ß: "ss"
167
- };
168
- return (s) => {
169
- let out = "";
170
- for (const ch of s.normalize("NFKC")) {
171
- out += table[ch] ?? ch;
172
- }
173
- return out.toLowerCase().split(/\s+/).filter(Boolean).join(" ");
174
- };
175
- }
176
- var BUILTINS = {
177
- e164: () => e164(),
178
- iso_date: () => isoDate(),
179
- german: () => germanTransliteration()
180
- };
181
- function resolveNormalizer(spec) {
182
- if (typeof spec === "function")
183
- return spec;
184
- const factory = BUILTINS[spec];
185
- if (!factory) {
186
- throw new Error(`unknown built-in normalizer: ${JSON.stringify(spec)}. ` + `Available: ${Object.keys(BUILTINS).join(", ")}, or pass a callable.`);
187
- }
188
- return factory();
189
- }
190
-
191
100
  // src/core/session.ts
192
101
  function normalizeSurface(s) {
193
102
  return s.normalize("NFKC").toLowerCase().split(/\s+/).filter(Boolean).join(" ");
@@ -200,8 +109,8 @@ class Session {
200
109
  resolvedNormalizers = {};
201
110
  constructor(opts = {}) {
202
111
  if (opts.normalizers) {
203
- for (const [label, spec] of Object.entries(opts.normalizers)) {
204
- this.resolvedNormalizers[label] = resolveNormalizer(spec);
112
+ for (const [label, fn] of Object.entries(opts.normalizers)) {
113
+ this.resolvedNormalizers[label] = fn;
205
114
  }
206
115
  }
207
116
  }
@@ -225,8 +134,11 @@ class Session {
225
134
  return sentinel;
226
135
  }
227
136
  applySpans(text, spans) {
137
+ return this.applySpansDetailed(text, spans).redacted;
138
+ }
139
+ applySpansDetailed(text, spans) {
228
140
  if (spans.length === 0)
229
- return text;
141
+ return { redacted: text, merged: [] };
230
142
  const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
231
143
  const merged = mergeAdjacent(text, sorted);
232
144
  const out = [];
@@ -242,7 +154,7 @@ class Session {
242
154
  cursor = end;
243
155
  }
244
156
  out.push(text.slice(cursor));
245
- return out.join("");
157
+ return { redacted: out.join(""), merged };
246
158
  }
247
159
  restore(text) {
248
160
  if (Object.keys(this.mapping).length === 0)
@@ -440,8 +352,10 @@ class LocalDetector {
440
352
  chunkTokens;
441
353
  chunkOverlapTokens;
442
354
  batchSize;
355
+ actualDevice = null;
443
356
  _pipeline = null;
444
357
  _loadPromise = null;
358
+ _onProgress;
445
359
  constructor(opts = {}) {
446
360
  this.model = opts.model ?? DEFAULT_MODEL2;
447
361
  this.device = opts.device ?? "auto";
@@ -450,11 +364,13 @@ class LocalDetector {
450
364
  this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS;
451
365
  this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS;
452
366
  this.batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;
367
+ this._onProgress = opts.onProgress;
453
368
  if (this.chunkOverlapTokens >= this.chunkTokens) {
454
369
  throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than chunkTokens (${this.chunkTokens})`);
455
370
  }
456
371
  if (opts.pipelineInstance) {
457
372
  this._pipeline = opts.pipelineInstance;
373
+ this.actualDevice = this.device === "auto" ? null : this.device;
458
374
  }
459
375
  }
460
376
  async load() {
@@ -463,24 +379,70 @@ class LocalDetector {
463
379
  if (this._loadPromise)
464
380
  return this._loadPromise;
465
381
  this._loadPromise = (async () => {
466
- let mod;
467
- try {
468
- mod = await import("@huggingface/transformers");
469
- } catch (e) {
470
- throw new Error("LocalDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
382
+ const mod = await loadTransformersJs();
383
+ const sequence = await this._planDeviceSequence();
384
+ let lastErr;
385
+ for (let i = 0;i < sequence.length; i++) {
386
+ const dev = sequence[i] ?? "wasm";
387
+ try {
388
+ const pipe = await mod.pipeline("token-classification", this.model, {
389
+ device: dev,
390
+ dtype: this.dtype,
391
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, dev) : undefined
392
+ });
393
+ this._pipeline = pipe;
394
+ this.actualDevice = dev;
395
+ return pipe;
396
+ } catch (e) {
397
+ lastErr = e;
398
+ if (i < sequence.length - 1)
399
+ continue;
400
+ throw e;
401
+ }
471
402
  }
472
- const pipe = await mod.pipeline("token-classification", this.model, {
473
- device: this.device,
474
- dtype: this.dtype
475
- });
476
- this._pipeline = pipe;
477
- return pipe;
403
+ throw lastErr ?? new Error("LocalDetector.load(): no candidate backends");
478
404
  })();
479
405
  return this._loadPromise;
480
406
  }
407
+ async _planDeviceSequence() {
408
+ if (this.device !== "auto")
409
+ return [this.device];
410
+ const seq = [];
411
+ const nav = typeof navigator !== "undefined" ? navigator : null;
412
+ if (nav?.gpu?.requestAdapter) {
413
+ try {
414
+ const adapter = await nav.gpu.requestAdapter();
415
+ if (adapter)
416
+ seq.push("webgpu");
417
+ } catch {}
418
+ }
419
+ seq.push("wasm");
420
+ return seq;
421
+ }
422
+ _emitProgress(raw, device) {
423
+ if (!this._onProgress)
424
+ return;
425
+ const event = {
426
+ phase: raw.status ?? "progress",
427
+ device
428
+ };
429
+ if (raw.file)
430
+ event.file = raw.file;
431
+ if (raw.status === "progress") {
432
+ if (raw.loaded != null)
433
+ event.loadedBytes = raw.loaded;
434
+ if (raw.total != null)
435
+ event.totalBytes = raw.total;
436
+ if (raw.progress != null) {
437
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
438
+ event.fraction = Math.max(0, Math.min(1, norm));
439
+ }
440
+ }
441
+ this._onProgress(event);
442
+ }
481
443
  async detect(text, opts) {
482
444
  if (opts?.model && opts.model !== this.model) {
483
- throw new Error(`LocalDetector was constructed with model='${this.model}'; per-call model override ('${opts.model}') not supported.`);
445
+ throw new Error(`LocalDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
484
446
  }
485
447
  if (!text)
486
448
  return [];
@@ -489,7 +451,7 @@ class LocalDetector {
489
451
  }
490
452
  async detectBatch(texts, opts) {
491
453
  if (opts?.model && opts.model !== this.model) {
492
- throw new Error(`LocalDetector was constructed with model='${this.model}'; per-call model override ('${opts.model}') not supported.`);
454
+ throw new Error(`LocalDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
493
455
  }
494
456
  if (texts.length === 0)
495
457
  return [];
@@ -650,6 +612,13 @@ function chunkByChars(text, chunkChars, overlapChars) {
650
612
  function normalizeWord(word) {
651
613
  return word.replace(/##/g, "").trim();
652
614
  }
615
+ async function loadTransformersJs() {
616
+ try {
617
+ return await import("@huggingface/transformers");
618
+ } catch {
619
+ throw new Error("LocalDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
620
+ }
621
+ }
653
622
  // src/detectors/index.ts
654
623
  function defaultDetector() {
655
624
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
@@ -704,4 +673,4 @@ async function* deanonymizeStream(chunks, session) {
704
673
  }
705
674
 
706
675
  // src/index.ts
707
- var VERSION = "0.1.0";
676
+ var VERSION = "0.1.2";
@@ -276,8 +276,10 @@ class LocalDetector {
276
276
  chunkTokens;
277
277
  chunkOverlapTokens;
278
278
  batchSize;
279
+ actualDevice = null;
279
280
  _pipeline = null;
280
281
  _loadPromise = null;
282
+ _onProgress;
281
283
  constructor(opts = {}) {
282
284
  this.model = opts.model ?? DEFAULT_MODEL2;
283
285
  this.device = opts.device ?? "auto";
@@ -286,11 +288,13 @@ class LocalDetector {
286
288
  this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS;
287
289
  this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS;
288
290
  this.batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;
291
+ this._onProgress = opts.onProgress;
289
292
  if (this.chunkOverlapTokens >= this.chunkTokens) {
290
293
  throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than chunkTokens (${this.chunkTokens})`);
291
294
  }
292
295
  if (opts.pipelineInstance) {
293
296
  this._pipeline = opts.pipelineInstance;
297
+ this.actualDevice = this.device === "auto" ? null : this.device;
294
298
  }
295
299
  }
296
300
  async load() {
@@ -299,24 +303,70 @@ class LocalDetector {
299
303
  if (this._loadPromise)
300
304
  return this._loadPromise;
301
305
  this._loadPromise = (async () => {
302
- let mod;
303
- try {
304
- mod = await import("@huggingface/transformers");
305
- } catch (e) {
306
- throw new Error("LocalDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
306
+ const mod = await loadTransformersJs();
307
+ const sequence = await this._planDeviceSequence();
308
+ let lastErr;
309
+ for (let i = 0;i < sequence.length; i++) {
310
+ const dev = sequence[i] ?? "wasm";
311
+ try {
312
+ const pipe = await mod.pipeline("token-classification", this.model, {
313
+ device: dev,
314
+ dtype: this.dtype,
315
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, dev) : undefined
316
+ });
317
+ this._pipeline = pipe;
318
+ this.actualDevice = dev;
319
+ return pipe;
320
+ } catch (e) {
321
+ lastErr = e;
322
+ if (i < sequence.length - 1)
323
+ continue;
324
+ throw e;
325
+ }
307
326
  }
308
- const pipe = await mod.pipeline("token-classification", this.model, {
309
- device: this.device,
310
- dtype: this.dtype
311
- });
312
- this._pipeline = pipe;
313
- return pipe;
327
+ throw lastErr ?? new Error("LocalDetector.load(): no candidate backends");
314
328
  })();
315
329
  return this._loadPromise;
316
330
  }
331
+ async _planDeviceSequence() {
332
+ if (this.device !== "auto")
333
+ return [this.device];
334
+ const seq = [];
335
+ const nav = typeof navigator !== "undefined" ? navigator : null;
336
+ if (nav?.gpu?.requestAdapter) {
337
+ try {
338
+ const adapter = await nav.gpu.requestAdapter();
339
+ if (adapter)
340
+ seq.push("webgpu");
341
+ } catch {}
342
+ }
343
+ seq.push("wasm");
344
+ return seq;
345
+ }
346
+ _emitProgress(raw, device) {
347
+ if (!this._onProgress)
348
+ return;
349
+ const event = {
350
+ phase: raw.status ?? "progress",
351
+ device
352
+ };
353
+ if (raw.file)
354
+ event.file = raw.file;
355
+ if (raw.status === "progress") {
356
+ if (raw.loaded != null)
357
+ event.loadedBytes = raw.loaded;
358
+ if (raw.total != null)
359
+ event.totalBytes = raw.total;
360
+ if (raw.progress != null) {
361
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
362
+ event.fraction = Math.max(0, Math.min(1, norm));
363
+ }
364
+ }
365
+ this._onProgress(event);
366
+ }
317
367
  async detect(text, opts) {
318
368
  if (opts?.model && opts.model !== this.model) {
319
- throw new Error(`LocalDetector was constructed with model='${this.model}'; per-call model override ('${opts.model}') not supported.`);
369
+ throw new Error(`LocalDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
320
370
  }
321
371
  if (!text)
322
372
  return [];
@@ -325,7 +375,7 @@ class LocalDetector {
325
375
  }
326
376
  async detectBatch(texts, opts) {
327
377
  if (opts?.model && opts.model !== this.model) {
328
- throw new Error(`LocalDetector was constructed with model='${this.model}'; per-call model override ('${opts.model}') not supported.`);
378
+ throw new Error(`LocalDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
329
379
  }
330
380
  if (texts.length === 0)
331
381
  return [];
@@ -486,102 +536,17 @@ function chunkByChars(text, chunkChars, overlapChars) {
486
536
  function normalizeWord(word) {
487
537
  return word.replace(/##/g, "").trim();
488
538
  }
489
- // src/detectors/index.ts
490
- function defaultDetector() {
491
- const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
492
- return hasKey ? new RemoteDetector : new LocalDetector;
493
- }
494
-
495
- // src/normalizers.ts
496
- var import_node_module = require("node:module");
497
- var _require = null;
498
- var _requireResolved = false;
499
- function _getRequire() {
500
- if (_requireResolved)
501
- return _require;
502
- _requireResolved = true;
503
- try {
504
- _require = import_node_module.createRequire("file:///home/joelbarmettler/projects/gheim/packages/gheim-js/src/normalizers.ts");
505
- } catch {
506
- _require = null;
507
- }
508
- return _require;
509
- }
510
- function loadSync(name, errMsg) {
511
- const req = _getRequire();
512
- if (!req)
513
- throw new Error(errMsg);
539
+ async function loadTransformersJs() {
514
540
  try {
515
- return req(name);
541
+ return await import("@huggingface/transformers");
516
542
  } catch {
517
- throw new Error(errMsg);
543
+ throw new Error("LocalDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
518
544
  }
519
545
  }
520
- function loadPhoneLib() {
521
- return loadSync("libphonenumber-js", "the e164 normalizer requires libphonenumber-js. " + "Install with: bun add libphonenumber-js");
522
- }
523
- function e164(opts = {}) {
524
- const lib = loadPhoneLib();
525
- return (s) => {
526
- try {
527
- const parsed = lib.parsePhoneNumberFromString(s, opts.region);
528
- if (!parsed || !parsed.isValid())
529
- return null;
530
- return parsed.format("E.164");
531
- } catch {
532
- return null;
533
- }
534
- };
535
- }
536
- function loadChrono(opts) {
537
- const mod = loadSync("chrono-node", "the isoDate normalizer requires chrono-node. " + "Install with: bun add chrono-node");
538
- if (opts.locale) {
539
- const localeParser = mod[opts.locale];
540
- if (localeParser)
541
- return localeParser;
542
- }
543
- return mod;
544
- }
545
- function isoDate(opts = {}) {
546
- const parser = loadChrono(opts);
547
- return (s) => {
548
- const d = parser.parseDate(s);
549
- if (!d)
550
- return null;
551
- return d.toISOString().slice(0, 10);
552
- };
553
- }
554
- function germanTransliteration() {
555
- const table = {
556
- ä: "ae",
557
- Ä: "ae",
558
- ö: "oe",
559
- Ö: "oe",
560
- ü: "ue",
561
- Ü: "ue",
562
- ß: "ss"
563
- };
564
- return (s) => {
565
- let out = "";
566
- for (const ch of s.normalize("NFKC")) {
567
- out += table[ch] ?? ch;
568
- }
569
- return out.toLowerCase().split(/\s+/).filter(Boolean).join(" ");
570
- };
571
- }
572
- var BUILTINS = {
573
- e164: () => e164(),
574
- iso_date: () => isoDate(),
575
- german: () => germanTransliteration()
576
- };
577
- function resolveNormalizer(spec) {
578
- if (typeof spec === "function")
579
- return spec;
580
- const factory = BUILTINS[spec];
581
- if (!factory) {
582
- throw new Error(`unknown built-in normalizer: ${JSON.stringify(spec)}. ` + `Available: ${Object.keys(BUILTINS).join(", ")}, or pass a callable.`);
583
- }
584
- return factory();
546
+ // src/detectors/index.ts
547
+ function defaultDetector() {
548
+ const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
549
+ return hasKey ? new RemoteDetector : new LocalDetector;
585
550
  }
586
551
 
587
552
  // src/core/session.ts
@@ -596,8 +561,8 @@ class Session {
596
561
  resolvedNormalizers = {};
597
562
  constructor(opts = {}) {
598
563
  if (opts.normalizers) {
599
- for (const [label, spec] of Object.entries(opts.normalizers)) {
600
- this.resolvedNormalizers[label] = resolveNormalizer(spec);
564
+ for (const [label, fn] of Object.entries(opts.normalizers)) {
565
+ this.resolvedNormalizers[label] = fn;
601
566
  }
602
567
  }
603
568
  }
@@ -621,8 +586,11 @@ class Session {
621
586
  return sentinel;
622
587
  }
623
588
  applySpans(text, spans) {
589
+ return this.applySpansDetailed(text, spans).redacted;
590
+ }
591
+ applySpansDetailed(text, spans) {
624
592
  if (spans.length === 0)
625
- return text;
593
+ return { redacted: text, merged: [] };
626
594
  const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
627
595
  const merged = mergeAdjacent(text, sorted);
628
596
  const out = [];
@@ -638,7 +606,7 @@ class Session {
638
606
  cursor = end;
639
607
  }
640
608
  out.push(text.slice(cursor));
641
- return out.join("");
609
+ return { redacted: out.join(""), merged };
642
610
  }
643
611
  restore(text) {
644
612
  if (Object.keys(this.mapping).length === 0)
@@ -871,6 +839,16 @@ function restoreChatResponse(response, session) {
871
839
  if (msg && typeof msg.content === "string") {
872
840
  msg.content = session.restore(msg.content);
873
841
  }
842
+ if (msg && typeof msg.reasoning === "string") {
843
+ msg.reasoning = session.restore(msg.reasoning);
844
+ }
845
+ if (msg && Array.isArray(msg.reasoning_details)) {
846
+ for (const r of msg.reasoning_details) {
847
+ if (r && typeof r.text === "string") {
848
+ r.text = session.restore(r.text);
849
+ }
850
+ }
851
+ }
874
852
  for (const tc of msg?.tool_calls ?? []) {
875
853
  const fn = tc.function;
876
854
  if (fn && typeof fn.arguments === "string") {
@@ -885,6 +863,16 @@ function patchChatChunk(chunk, buf) {
885
863
  if (typeof delta.content === "string") {
886
864
  delta.content = buf.feed(delta.content);
887
865
  }
866
+ if (typeof delta.reasoning === "string") {
867
+ delta.reasoning = buf.feed(delta.reasoning);
868
+ }
869
+ if (Array.isArray(delta.reasoning_details)) {
870
+ for (const r of delta.reasoning_details) {
871
+ if (r && typeof r.text === "string") {
872
+ r.text = buf.feed(r.text);
873
+ }
874
+ }
875
+ }
888
876
  }
889
877
  }
890
878
  function makeChatTailChunk(template, content) {
@@ -1211,9 +1199,20 @@ class OpenAI {
1211
1199
  gheimStrict;
1212
1200
  _clientPromise;
1213
1201
  constructor(opts = {}) {
1214
- const detector = opts.gheimDetector ?? defaultDetector();
1215
- this.gheimStrict = opts.gheimStrict ?? true;
1216
- this._clientPromise = opts.openaiClient ? Promise.resolve(opts.openaiClient) : loadOpenAI(opts.clientOptions);
1202
+ const {
1203
+ gheimDetector,
1204
+ gheimStrict,
1205
+ openaiClient,
1206
+ clientOptions,
1207
+ ...passthrough
1208
+ } = opts;
1209
+ const detector = gheimDetector ?? defaultDetector();
1210
+ this.gheimStrict = gheimStrict ?? true;
1211
+ const innerOpts = {
1212
+ ...clientOptions ?? {},
1213
+ ...passthrough
1214
+ };
1215
+ this._clientPromise = openaiClient ? Promise.resolve(openaiClient) : loadOpenAI(innerOpts);
1217
1216
  this._clientPromise.then((c) => {
1218
1217
  this.raw = c;
1219
1218
  });