gheim 0.1.8 → 0.1.10

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.
@@ -39,13 +39,13 @@ var __export = (target, all) => {
39
39
  // src/openai/index.ts
40
40
  var exports_openai = {};
41
41
  __export(exports_openai, {
42
- deanonymizeOpenAIStream: () => deanonymizeOpenAIStream,
43
- deanonymizeOpenAIResponse: () => deanonymizeOpenAIResponse,
44
- anonymizeOpenAIMessages: () => anonymizeOpenAIMessages,
45
- STRICT_REASONS: () => STRICT_REASONS,
46
- OpenAI: () => OpenAI,
42
+ AsyncOpenAI: () => AsyncOpenAI,
47
43
  GheimStrictError: () => GheimStrictError,
48
- AsyncOpenAI: () => AsyncOpenAI
44
+ OpenAI: () => OpenAI,
45
+ STRICT_REASONS: () => STRICT_REASONS,
46
+ anonymizeOpenAIMessages: () => anonymizeOpenAIMessages,
47
+ deanonymizeOpenAIResponse: () => deanonymizeOpenAIResponse,
48
+ deanonymizeOpenAIStream: () => deanonymizeOpenAIStream
49
49
  });
50
50
  module.exports = __toCommonJS(exports_openai);
51
51
 
@@ -207,10 +207,40 @@ function trimSpan(text, start, end) {
207
207
  return null;
208
208
  return { start, end };
209
209
  }
210
+ var WORD_CHAR = /[\p{L}\p{N}_]/u;
211
+ var isWordChar = (ch) => ch !== undefined && WORD_CHAR.test(ch);
212
+ function snapToWords(text, spans) {
213
+ const snapped = spans.map((sp) => {
214
+ let { start, end } = sp;
215
+ while (start > 0 && isWordChar(text[start - 1]) && isWordChar(text[start]))
216
+ start -= 1;
217
+ while (end < text.length && isWordChar(text[end - 1]) && isWordChar(text[end]))
218
+ end += 1;
219
+ return start === sp.start && end === sp.end ? sp : { ...sp, start, end, text: text.slice(start, end) };
220
+ });
221
+ snapped.sort((a, b) => a.start - b.start || a.end - b.end);
222
+ const merged = [];
223
+ const lastByLabel = new Map;
224
+ for (const sp of snapped) {
225
+ const i = lastByLabel.get(sp.label);
226
+ const prev = i === undefined ? undefined : merged[i];
227
+ if (i !== undefined && prev !== undefined && sp.start <= prev.end) {
228
+ const end = Math.max(prev.end, sp.end);
229
+ const wPrev = prev.end - prev.start;
230
+ const wSp = sp.end - sp.start;
231
+ const score = ((prev.score ?? 1) * wPrev + (sp.score ?? 1) * wSp) / (wPrev + wSp);
232
+ merged[i] = { ...prev, end, text: text.slice(prev.start, end), score };
233
+ } else {
234
+ lastByLabel.set(sp.label, merged.length);
235
+ merged.push(sp);
236
+ }
237
+ }
238
+ return merged.sort((a, b) => a.start - b.start || a.end - b.end);
239
+ }
210
240
 
211
241
  // src/detectors/remote.ts
212
242
  var DEFAULT_BASE_URL = "https://api.gheim.ch";
213
- var DEFAULT_MODEL = "openai/privacy-filter";
243
+ var DEFAULT_MODEL = "joelbarmettler/gheim-ch-560m";
214
244
 
215
245
  class RemoteDetector {
216
246
  baseUrl;
@@ -455,10 +485,7 @@ class LocalDetector {
455
485
  });
456
486
  }
457
487
  }
458
- for (const spans of out) {
459
- spans.sort((a, b) => a.start - b.start || a.end - b.end);
460
- }
461
- return out;
488
+ return out.map((spans, i) => snapToWords(texts[i] ?? "", spans));
462
489
  }
463
490
  async chunkText(text, tokenizer) {
464
491
  if (tokenizer) {
@@ -735,21 +762,136 @@ function maskSpans(text, spans) {
735
762
  return chars.join("");
736
763
  }
737
764
 
765
+ class CustomPattern {
766
+ label;
767
+ group;
768
+ name;
769
+ regex;
770
+ validator;
771
+ constructor(opts) {
772
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(opts.label)) {
773
+ throw new Error(`CustomPattern label ${JSON.stringify(opts.label)} must start with a letter and contain ` + "only letters, digits and underscores (it becomes a <TAG_1> placeholder).");
774
+ }
775
+ this.label = opts.label;
776
+ this.name = opts.name ?? opts.label;
777
+ this.group = opts.group ?? 0;
778
+ this.validator = opts.validator;
779
+ const source = typeof opts.regex === "string" ? opts.regex : opts.regex.source;
780
+ const base = typeof opts.regex === "string" ? opts.flags ?? "i" : opts.regex.flags;
781
+ const flags = [...new Set([...base, "g", "d"])].join("");
782
+ this.regex = new RegExp(source, flags);
783
+ const groups = (new RegExp(`${source}|`, flags.replace(/[gd]/g, "")).exec("") ?? []).length - 1;
784
+ if (!Number.isInteger(this.group) || this.group < 0 || this.group > groups) {
785
+ throw new Error(`CustomPattern ${JSON.stringify(this.name)}: group ${this.group} does not exist ` + `(the pattern has ${groups}).`);
786
+ }
787
+ }
788
+ find(text) {
789
+ const out = [];
790
+ this.regex.lastIndex = 0;
791
+ let m;
792
+ while ((m = this.regex.exec(text)) !== null) {
793
+ if (m[0] === "") {
794
+ this.regex.lastIndex++;
795
+ continue;
796
+ }
797
+ const range = m.indices[this.group];
798
+ if (!range || range[0] === range[1])
799
+ continue;
800
+ const [start, end] = range;
801
+ const value = text.slice(start, end);
802
+ if (this.validator && !this.validator(value))
803
+ continue;
804
+ out.push({ start, end, label: this.label, text: value, score: 1 });
805
+ }
806
+ return out;
807
+ }
808
+ }
809
+ var NAME_PART = /[A-ZÀ-Ý][\p{L}\p{N}_'’-]{3,}/gu;
810
+ var HONORIFICS = new Set([
811
+ "frau",
812
+ "herr",
813
+ "herrn",
814
+ "madame",
815
+ "monsieur",
816
+ "signora",
817
+ "signor",
818
+ "signore",
819
+ "dott",
820
+ "dottor",
821
+ "dottoressa",
822
+ "doktor",
823
+ "docteur",
824
+ "prof",
825
+ "professor",
826
+ "mister",
827
+ "miss",
828
+ "mrs"
829
+ ]);
830
+ function escapeRegExp(s) {
831
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
832
+ }
833
+ function propagateNames(text, spans, surnames) {
834
+ const names = new Set;
835
+ for (const sp of spans) {
836
+ if (sp.label !== "private_person")
837
+ continue;
838
+ const value = text.slice(sp.start, sp.end).trim();
839
+ if (value.length >= 4)
840
+ names.add(value);
841
+ if (surnames) {
842
+ for (const part of value.match(NAME_PART) ?? []) {
843
+ if (!HONORIFICS.has(part.toLowerCase()))
844
+ names.add(part);
845
+ }
846
+ }
847
+ }
848
+ let result = [...spans];
849
+ for (const name of [...names].sort((a, b) => b.length - a.length)) {
850
+ const re = new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegExp(name)}(?![\\p{L}\\p{N}_])`, "gu");
851
+ let m;
852
+ while ((m = re.exec(text)) !== null) {
853
+ const start = m.index;
854
+ const end = start + m[0].length;
855
+ const hits = result.filter((sp) => start < sp.end && sp.start < end);
856
+ if (hits.some((sp) => sp.start <= start && end <= sp.end))
857
+ continue;
858
+ if (hits.some((sp) => sp.start < start || end < sp.end))
859
+ continue;
860
+ result = result.filter((sp) => !hits.includes(sp));
861
+ result.push({ start, end, label: "private_person", text: m[0], score: 1 });
862
+ }
863
+ }
864
+ return result;
865
+ }
866
+
738
867
  class CompositeDetector {
739
868
  model;
869
+ patterns;
870
+ propagateNames;
871
+ propagateSurnames;
740
872
  constructor(opts) {
741
873
  if (!opts || !opts.model) {
742
874
  throw new Error("CompositeDetector requires { model: Detector } — pass any object with " + "a .detect(text) method (e.g. new LocalDetector({...})).");
743
875
  }
744
876
  this.model = opts.model;
877
+ this.patterns = opts.patterns ?? [];
878
+ this.propagateNames = opts.propagateNames ?? true;
879
+ this.propagateSurnames = opts.propagateSurnames ?? false;
745
880
  }
746
881
  async detect(text, opts) {
747
882
  if (!text)
748
883
  return [];
749
884
  const regexSpans = findRegexSpans(text);
885
+ const extra = this.patterns.flatMap((p) => p.find(text));
886
+ extra.sort((a, b) => a.start !== b.start ? a.start - b.start : b.end - b.start - (a.end - a.start));
887
+ for (const sp of extra) {
888
+ if (!regexSpans.some((o) => sp.start < o.end && o.start < sp.end))
889
+ regexSpans.push(sp);
890
+ }
891
+ regexSpans.sort((a, b) => a.start - b.start);
750
892
  const masked = maskSpans(text, regexSpans);
751
893
  const modelSpans = await this.model.detect(masked, opts);
752
- const out = [...regexSpans];
894
+ let out = [...regexSpans];
753
895
  const regexRanges = regexSpans.map((s) => [s.start, s.end]);
754
896
  for (const sp of modelSpans) {
755
897
  const overlaps = regexRanges.some(([rs, re]) => sp.start < re && rs < sp.end);
@@ -757,6 +899,8 @@ class CompositeDetector {
757
899
  continue;
758
900
  out.push(sp);
759
901
  }
902
+ if (this.propagateNames)
903
+ out = propagateNames(text, out, this.propagateSurnames);
760
904
  out.sort((a, b) => a.start !== b.start ? a.start - b.start : b.end - b.start - (a.end - a.start));
761
905
  return out;
762
906
  }
@@ -846,7 +990,7 @@ class CalibratedDetector {
846
990
  if (!text)
847
991
  return [];
848
992
  await this.load();
849
- return this._detectChunked(text);
993
+ return snapToWords(text, await this._detectChunked(text));
850
994
  }
851
995
  async _detectChunked(text) {
852
996
  if (text.length <= 1500) {
@@ -913,15 +1057,21 @@ class CalibratedDetector {
913
1057
  maxIdx = i;
914
1058
  }
915
1059
  }
916
- tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
1060
+ let denom = 0;
1061
+ for (let i = 0;i < row.length; i++)
1062
+ denom += Math.exp(row[i] - maxVal);
1063
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O", prob: 1 / denom });
917
1064
  }
918
1065
  const spans = [];
919
1066
  let cursor = 0;
920
1067
  let groupIds = [];
1068
+ let groupProbs = [];
921
1069
  let groupCat = null;
922
1070
  const flush = () => {
923
1071
  if (groupCat === null || groupIds.length === 0)
924
1072
  return;
1073
+ const score = groupProbs.reduce((a, b) => a + b, 0) / groupProbs.length;
1074
+ groupProbs = [];
925
1075
  const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
926
1076
  groupIds = [];
927
1077
  const cat = groupCat;
@@ -943,17 +1093,19 @@ class CalibratedDetector {
943
1093
  end: e + charOffset,
944
1094
  label: cat,
945
1095
  text: text.slice(s, e),
946
- score: 1
1096
+ score
947
1097
  });
948
1098
  };
949
- for (const { id, label } of tokenLabels) {
1099
+ for (const { id, label, prob } of tokenLabels) {
950
1100
  const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
951
1101
  if (cat === groupCat && cat !== null) {
952
1102
  groupIds.push(id);
1103
+ groupProbs.push(prob);
953
1104
  } else {
954
1105
  flush();
955
1106
  if (cat !== null) {
956
1107
  groupIds.push(id);
1108
+ groupProbs.push(prob);
957
1109
  groupCat = cat;
958
1110
  }
959
1111
  }
@@ -1039,7 +1191,7 @@ function readLogits(tensor) {
1039
1191
  // src/detectors/index.ts
1040
1192
  function defaultDetector() {
1041
1193
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
1042
- return hasKey ? new RemoteDetector : new CalibratedDetector;
1194
+ return hasKey ? new RemoteDetector : new CompositeDetector({ model: new CalibratedDetector });
1043
1195
  }
1044
1196
 
1045
1197
  // src/core/session.ts
package/dist/esm/index.js CHANGED
@@ -1,11 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined")
5
- return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
-
9
1
  // src/core/sentinels.ts
10
2
  var LABEL_TO_TAG = {
11
3
  private_person: "PERSON",
@@ -264,10 +256,40 @@ function trimSpan(text, start, end) {
264
256
  return null;
265
257
  return { start, end };
266
258
  }
259
+ var WORD_CHAR = /[\p{L}\p{N}_]/u;
260
+ var isWordChar = (ch) => ch !== undefined && WORD_CHAR.test(ch);
261
+ function snapToWords(text, spans) {
262
+ const snapped = spans.map((sp) => {
263
+ let { start, end } = sp;
264
+ while (start > 0 && isWordChar(text[start - 1]) && isWordChar(text[start]))
265
+ start -= 1;
266
+ while (end < text.length && isWordChar(text[end - 1]) && isWordChar(text[end]))
267
+ end += 1;
268
+ return start === sp.start && end === sp.end ? sp : { ...sp, start, end, text: text.slice(start, end) };
269
+ });
270
+ snapped.sort((a, b) => a.start - b.start || a.end - b.end);
271
+ const merged = [];
272
+ const lastByLabel = new Map;
273
+ for (const sp of snapped) {
274
+ const i = lastByLabel.get(sp.label);
275
+ const prev = i === undefined ? undefined : merged[i];
276
+ if (i !== undefined && prev !== undefined && sp.start <= prev.end) {
277
+ const end = Math.max(prev.end, sp.end);
278
+ const wPrev = prev.end - prev.start;
279
+ const wSp = sp.end - sp.start;
280
+ const score = ((prev.score ?? 1) * wPrev + (sp.score ?? 1) * wSp) / (wPrev + wSp);
281
+ merged[i] = { ...prev, end, text: text.slice(prev.start, end), score };
282
+ } else {
283
+ lastByLabel.set(sp.label, merged.length);
284
+ merged.push(sp);
285
+ }
286
+ }
287
+ return merged.sort((a, b) => a.start - b.start || a.end - b.end);
288
+ }
267
289
 
268
290
  // src/detectors/remote.ts
269
291
  var DEFAULT_BASE_URL = "https://api.gheim.ch";
270
- var DEFAULT_MODEL = "openai/privacy-filter";
292
+ var DEFAULT_MODEL = "joelbarmettler/gheim-ch-560m";
271
293
 
272
294
  class RemoteDetector {
273
295
  baseUrl;
@@ -512,10 +534,7 @@ class LocalDetector {
512
534
  });
513
535
  }
514
536
  }
515
- for (const spans of out) {
516
- spans.sort((a, b) => a.start - b.start || a.end - b.end);
517
- }
518
- return out;
537
+ return out.map((spans, i) => snapToWords(texts[i] ?? "", spans));
519
538
  }
520
539
  async chunkText(text, tokenizer) {
521
540
  if (tokenizer) {
@@ -792,21 +811,136 @@ function maskSpans(text, spans) {
792
811
  return chars.join("");
793
812
  }
794
813
 
814
+ class CustomPattern {
815
+ label;
816
+ group;
817
+ name;
818
+ regex;
819
+ validator;
820
+ constructor(opts) {
821
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(opts.label)) {
822
+ throw new Error(`CustomPattern label ${JSON.stringify(opts.label)} must start with a letter and contain ` + "only letters, digits and underscores (it becomes a <TAG_1> placeholder).");
823
+ }
824
+ this.label = opts.label;
825
+ this.name = opts.name ?? opts.label;
826
+ this.group = opts.group ?? 0;
827
+ this.validator = opts.validator;
828
+ const source = typeof opts.regex === "string" ? opts.regex : opts.regex.source;
829
+ const base = typeof opts.regex === "string" ? opts.flags ?? "i" : opts.regex.flags;
830
+ const flags = [...new Set([...base, "g", "d"])].join("");
831
+ this.regex = new RegExp(source, flags);
832
+ const groups = (new RegExp(`${source}|`, flags.replace(/[gd]/g, "")).exec("") ?? []).length - 1;
833
+ if (!Number.isInteger(this.group) || this.group < 0 || this.group > groups) {
834
+ throw new Error(`CustomPattern ${JSON.stringify(this.name)}: group ${this.group} does not exist ` + `(the pattern has ${groups}).`);
835
+ }
836
+ }
837
+ find(text) {
838
+ const out = [];
839
+ this.regex.lastIndex = 0;
840
+ let m;
841
+ while ((m = this.regex.exec(text)) !== null) {
842
+ if (m[0] === "") {
843
+ this.regex.lastIndex++;
844
+ continue;
845
+ }
846
+ const range = m.indices[this.group];
847
+ if (!range || range[0] === range[1])
848
+ continue;
849
+ const [start, end] = range;
850
+ const value = text.slice(start, end);
851
+ if (this.validator && !this.validator(value))
852
+ continue;
853
+ out.push({ start, end, label: this.label, text: value, score: 1 });
854
+ }
855
+ return out;
856
+ }
857
+ }
858
+ var NAME_PART = /[A-ZÀ-Ý][\p{L}\p{N}_'’-]{3,}/gu;
859
+ var HONORIFICS = new Set([
860
+ "frau",
861
+ "herr",
862
+ "herrn",
863
+ "madame",
864
+ "monsieur",
865
+ "signora",
866
+ "signor",
867
+ "signore",
868
+ "dott",
869
+ "dottor",
870
+ "dottoressa",
871
+ "doktor",
872
+ "docteur",
873
+ "prof",
874
+ "professor",
875
+ "mister",
876
+ "miss",
877
+ "mrs"
878
+ ]);
879
+ function escapeRegExp(s) {
880
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
881
+ }
882
+ function propagateNames(text, spans, surnames) {
883
+ const names = new Set;
884
+ for (const sp of spans) {
885
+ if (sp.label !== "private_person")
886
+ continue;
887
+ const value = text.slice(sp.start, sp.end).trim();
888
+ if (value.length >= 4)
889
+ names.add(value);
890
+ if (surnames) {
891
+ for (const part of value.match(NAME_PART) ?? []) {
892
+ if (!HONORIFICS.has(part.toLowerCase()))
893
+ names.add(part);
894
+ }
895
+ }
896
+ }
897
+ let result = [...spans];
898
+ for (const name of [...names].sort((a, b) => b.length - a.length)) {
899
+ const re = new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegExp(name)}(?![\\p{L}\\p{N}_])`, "gu");
900
+ let m;
901
+ while ((m = re.exec(text)) !== null) {
902
+ const start = m.index;
903
+ const end = start + m[0].length;
904
+ const hits = result.filter((sp) => start < sp.end && sp.start < end);
905
+ if (hits.some((sp) => sp.start <= start && end <= sp.end))
906
+ continue;
907
+ if (hits.some((sp) => sp.start < start || end < sp.end))
908
+ continue;
909
+ result = result.filter((sp) => !hits.includes(sp));
910
+ result.push({ start, end, label: "private_person", text: m[0], score: 1 });
911
+ }
912
+ }
913
+ return result;
914
+ }
915
+
795
916
  class CompositeDetector {
796
917
  model;
918
+ patterns;
919
+ propagateNames;
920
+ propagateSurnames;
797
921
  constructor(opts) {
798
922
  if (!opts || !opts.model) {
799
923
  throw new Error("CompositeDetector requires { model: Detector } — pass any object with " + "a .detect(text) method (e.g. new LocalDetector({...})).");
800
924
  }
801
925
  this.model = opts.model;
926
+ this.patterns = opts.patterns ?? [];
927
+ this.propagateNames = opts.propagateNames ?? true;
928
+ this.propagateSurnames = opts.propagateSurnames ?? false;
802
929
  }
803
930
  async detect(text, opts) {
804
931
  if (!text)
805
932
  return [];
806
933
  const regexSpans = findRegexSpans(text);
934
+ const extra = this.patterns.flatMap((p) => p.find(text));
935
+ extra.sort((a, b) => a.start !== b.start ? a.start - b.start : b.end - b.start - (a.end - a.start));
936
+ for (const sp of extra) {
937
+ if (!regexSpans.some((o) => sp.start < o.end && o.start < sp.end))
938
+ regexSpans.push(sp);
939
+ }
940
+ regexSpans.sort((a, b) => a.start - b.start);
807
941
  const masked = maskSpans(text, regexSpans);
808
942
  const modelSpans = await this.model.detect(masked, opts);
809
- const out = [...regexSpans];
943
+ let out = [...regexSpans];
810
944
  const regexRanges = regexSpans.map((s) => [s.start, s.end]);
811
945
  for (const sp of modelSpans) {
812
946
  const overlaps = regexRanges.some(([rs, re]) => sp.start < re && rs < sp.end);
@@ -814,6 +948,8 @@ class CompositeDetector {
814
948
  continue;
815
949
  out.push(sp);
816
950
  }
951
+ if (this.propagateNames)
952
+ out = propagateNames(text, out, this.propagateSurnames);
817
953
  out.sort((a, b) => a.start !== b.start ? a.start - b.start : b.end - b.start - (a.end - a.start));
818
954
  return out;
819
955
  }
@@ -903,7 +1039,7 @@ class CalibratedDetector {
903
1039
  if (!text)
904
1040
  return [];
905
1041
  await this.load();
906
- return this._detectChunked(text);
1042
+ return snapToWords(text, await this._detectChunked(text));
907
1043
  }
908
1044
  async _detectChunked(text) {
909
1045
  if (text.length <= 1500) {
@@ -970,15 +1106,21 @@ class CalibratedDetector {
970
1106
  maxIdx = i;
971
1107
  }
972
1108
  }
973
- tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
1109
+ let denom = 0;
1110
+ for (let i = 0;i < row.length; i++)
1111
+ denom += Math.exp(row[i] - maxVal);
1112
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O", prob: 1 / denom });
974
1113
  }
975
1114
  const spans = [];
976
1115
  let cursor = 0;
977
1116
  let groupIds = [];
1117
+ let groupProbs = [];
978
1118
  let groupCat = null;
979
1119
  const flush = () => {
980
1120
  if (groupCat === null || groupIds.length === 0)
981
1121
  return;
1122
+ const score = groupProbs.reduce((a, b) => a + b, 0) / groupProbs.length;
1123
+ groupProbs = [];
982
1124
  const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
983
1125
  groupIds = [];
984
1126
  const cat = groupCat;
@@ -1000,17 +1142,19 @@ class CalibratedDetector {
1000
1142
  end: e + charOffset,
1001
1143
  label: cat,
1002
1144
  text: text.slice(s, e),
1003
- score: 1
1145
+ score
1004
1146
  });
1005
1147
  };
1006
- for (const { id, label } of tokenLabels) {
1148
+ for (const { id, label, prob } of tokenLabels) {
1007
1149
  const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
1008
1150
  if (cat === groupCat && cat !== null) {
1009
1151
  groupIds.push(id);
1152
+ groupProbs.push(prob);
1010
1153
  } else {
1011
1154
  flush();
1012
1155
  if (cat !== null) {
1013
1156
  groupIds.push(id);
1157
+ groupProbs.push(prob);
1014
1158
  groupCat = cat;
1015
1159
  }
1016
1160
  }
@@ -1096,7 +1240,7 @@ function readLogits(tensor) {
1096
1240
  // src/detectors/index.ts
1097
1241
  function defaultDetector() {
1098
1242
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
1099
- return hasKey ? new RemoteDetector : new CalibratedDetector;
1243
+ return hasKey ? new RemoteDetector : new CompositeDetector({ model: new CalibratedDetector });
1100
1244
  }
1101
1245
  // src/plain.ts
1102
1246
  function resolve(session, detector) {
@@ -1149,25 +1293,26 @@ async function* deanonymizeStream(chunks, session) {
1149
1293
  // src/index.ts
1150
1294
  var VERSION = "0.1.6";
1151
1295
  export {
1152
- renderSentinel,
1153
- parseSentinel,
1154
- mergeAdjacent,
1155
- labelTag,
1156
- isPossibleSentinelPrefix,
1157
- defaultDetector,
1158
- deanonymizeText,
1159
- deanonymizeStream,
1160
- anonymizeText,
1161
- anonymizeMessages,
1162
- VERSION,
1163
- StreamDeanonymizer,
1164
- Session,
1165
- SENTINEL_RE,
1166
- RemoteDetector,
1167
- DEFAULT_MODEL as REMOTE_DEFAULT_MODEL,
1168
- LocalDetector,
1169
- LABEL_TO_TAG,
1170
- DEFAULT_BASE_URL,
1296
+ CalibratedDetector,
1171
1297
  CompositeDetector,
1172
- CalibratedDetector
1298
+ CustomPattern,
1299
+ DEFAULT_BASE_URL,
1300
+ LABEL_TO_TAG,
1301
+ LocalDetector,
1302
+ DEFAULT_MODEL as REMOTE_DEFAULT_MODEL,
1303
+ RemoteDetector,
1304
+ SENTINEL_RE,
1305
+ Session,
1306
+ StreamDeanonymizer,
1307
+ VERSION,
1308
+ anonymizeMessages,
1309
+ anonymizeText,
1310
+ deanonymizeStream,
1311
+ deanonymizeText,
1312
+ defaultDetector,
1313
+ isPossibleSentinelPrefix,
1314
+ labelTag,
1315
+ mergeAdjacent,
1316
+ parseSentinel,
1317
+ renderSentinel
1173
1318
  };