agentid-sdk 0.1.7 → 0.1.9
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/README.md +5 -0
- package/dist/{chunk-LWL2WG5B.mjs → chunk-DXUA5DKG.mjs} +20 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1287 -178
- package/dist/index.mjs +1268 -177
- package/dist/{langchain-C6HJAK2b.d.mts → langchain-DVPOWfCC.d.mts} +9 -0
- package/dist/{langchain-C6HJAK2b.d.ts → langchain-DVPOWfCC.d.ts} +9 -0
- package/dist/langchain.d.mts +1 -1
- package/dist/langchain.d.ts +1 -1
- package/dist/langchain.js +20 -2
- package/dist/langchain.mjs +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentIDCallbackHandler
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DXUA5DKG.mjs";
|
|
4
4
|
|
|
5
5
|
// src/adapters.ts
|
|
6
6
|
var OpenAIAdapter = class {
|
|
@@ -44,8 +44,863 @@ var OpenAIAdapter = class {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
// src/pii.ts
|
|
47
|
+
// src/pii-national-identifiers.ts
|
|
48
|
+
var MAX_CANDIDATES_PER_RULE = 256;
|
|
49
|
+
var MAX_PREFILTER_HITS = 512;
|
|
50
|
+
var DEFAULT_DEADLINE_MS = 100;
|
|
51
|
+
var DEFAULT_PREFILTER_DEADLINE_MS = 12;
|
|
52
|
+
var ANCHOR_WINDOW_CHARS = 20;
|
|
53
|
+
var ADDRESS_WINDOW_CHARS = 40;
|
|
54
|
+
var symbolHintRegex = /[\/<\-]/;
|
|
55
|
+
var longDigitHintRegex = /\d{8,}/;
|
|
56
|
+
var czBirthNumberRegex = /\b\d{2}(?:0[1-9]|1[0-2]|2[1-9]|3[0-2]|5[1-9]|6[0-2]|7[1-9]|8[0-2])(?:0[1-9]|[12]\d|3[01])\/?\d{3,4}\b/g;
|
|
57
|
+
var czBirthNumberContextRegex = /\b(?:rodne?\s*(?:cislo|c\.)|r\.?\s*c\.?)\b[^0-9]{0,12}\d{6}(?:\/?\d{0,4})?\b/iu;
|
|
58
|
+
var peselRegex = /\b\d{11}\b/g;
|
|
59
|
+
var cnpRegex = /\b[1-9]\d{12}\b/g;
|
|
60
|
+
var deTaxIdRegex = /\b\d{11}\b/g;
|
|
61
|
+
var huTajRegex = /\b\d{9}\b/g;
|
|
62
|
+
var plNipRegex = /\b\d{10}\b/g;
|
|
63
|
+
var ukNinoRegex = /\b[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]\b/gi;
|
|
64
|
+
var frInseeRegex = /\b[12]\s?\d{2}\s?(0[1-9]|1[0-2]|[23]\d|4[0-2]|[5-9]\d)\s?(2[AB]|\d{2})\s?\d{3}\s?\d{3}\s?\d{2}\b/gi;
|
|
65
|
+
var esDniRegex = /\b\d{8}[A-Z]\b/gi;
|
|
66
|
+
var esNieRegex = /\b[XYZ]\d{7}[A-Z]\b/gi;
|
|
67
|
+
var itCodiceFiscaleRegex = /\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b/gi;
|
|
68
|
+
var nlBsnRegex = /\b\d{8,9}\b/g;
|
|
69
|
+
var atSvnrRegex = /\b\d{4}\s?\d{6}\b/g;
|
|
70
|
+
var euVatRegex = /\b[A-Z]{2}[A-Z0-9]{8,12}\b/g;
|
|
71
|
+
var ukDriverRegex = /\b[A-Z9]{5}\d{6}[A-Z9]{2}\d[A-Z]{2}\b/g;
|
|
72
|
+
var genericDriverRegex = /\b[A-Z0-9]{8,18}\b/g;
|
|
73
|
+
var postalCodeRegex = /\b(?:\d{3}\s?\d{2}|\d{4}|\d{5})\b/g;
|
|
74
|
+
var mrzBlockRegex = /(?:^|\r?\n)([A-Z0-9< ]{10,44}(?:\r?\n[A-Z0-9< ]{10,44}){1,5})(?=\r?\n|$)/g;
|
|
75
|
+
var peselChecksumWeights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
|
|
76
|
+
var cnpChecksumWeights = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9];
|
|
77
|
+
var polishNipWeights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
|
|
78
|
+
var atSvnrWeights = [3, 7, 9, 5, 8, 4, 2, 1, 6];
|
|
79
|
+
var mrzWeights = [7, 3, 1];
|
|
80
|
+
var dniNieControlLetters = "TRWAGMYFPDXBNJZSQVHLCKE";
|
|
81
|
+
var italianOddControlMap = {
|
|
82
|
+
"0": 1,
|
|
83
|
+
"1": 0,
|
|
84
|
+
"2": 5,
|
|
85
|
+
"3": 7,
|
|
86
|
+
"4": 9,
|
|
87
|
+
"5": 13,
|
|
88
|
+
"6": 15,
|
|
89
|
+
"7": 17,
|
|
90
|
+
"8": 19,
|
|
91
|
+
"9": 21,
|
|
92
|
+
A: 1,
|
|
93
|
+
B: 0,
|
|
94
|
+
C: 5,
|
|
95
|
+
D: 7,
|
|
96
|
+
E: 9,
|
|
97
|
+
F: 13,
|
|
98
|
+
G: 15,
|
|
99
|
+
H: 17,
|
|
100
|
+
I: 19,
|
|
101
|
+
J: 21,
|
|
102
|
+
K: 2,
|
|
103
|
+
L: 4,
|
|
104
|
+
M: 18,
|
|
105
|
+
N: 20,
|
|
106
|
+
O: 11,
|
|
107
|
+
P: 3,
|
|
108
|
+
Q: 6,
|
|
109
|
+
R: 8,
|
|
110
|
+
S: 12,
|
|
111
|
+
T: 14,
|
|
112
|
+
U: 16,
|
|
113
|
+
V: 10,
|
|
114
|
+
W: 22,
|
|
115
|
+
X: 25,
|
|
116
|
+
Y: 24,
|
|
117
|
+
Z: 23
|
|
118
|
+
};
|
|
119
|
+
var REGION_ANCHORS = {
|
|
120
|
+
czsk: ["rodne cislo", "rc", "cislo", "birth number", "personal number"],
|
|
121
|
+
pl: ["pesel", "nip", "dowod", "poland"],
|
|
122
|
+
ro: ["cnp", "romania"],
|
|
123
|
+
de: ["steuer id", "steuer-id", "steueridentifikationsnummer", "ust-idnr", "deutschland"],
|
|
124
|
+
hu: ["taj", "szemelyi", "hungary"],
|
|
125
|
+
gb: ["nino", "national insurance", "gb", "great britain", "driving licence", "driver licence"],
|
|
126
|
+
fr: ["insee", "nir", "securite sociale", "france"],
|
|
127
|
+
es: ["dni", "nie", "nif", "cif", "espana", "spain"],
|
|
128
|
+
it: ["codice fiscale", "fiscal code", "italia", "italy"],
|
|
129
|
+
nl: ["bsn", "burgerservicenummer", "nederland", "netherlands"],
|
|
130
|
+
at: ["svnr", "sozialversicherung", "sozialversicherungsnummer", "austria", "oesterreich"]
|
|
131
|
+
};
|
|
132
|
+
var TAX_ANCHORS = [
|
|
133
|
+
"vat",
|
|
134
|
+
"vat number",
|
|
135
|
+
"tax",
|
|
136
|
+
"tax id",
|
|
137
|
+
"tin",
|
|
138
|
+
"dic",
|
|
139
|
+
"nip",
|
|
140
|
+
"nino",
|
|
141
|
+
"steuernummer",
|
|
142
|
+
"steuer id",
|
|
143
|
+
"steuer-id",
|
|
144
|
+
"ust-idnr",
|
|
145
|
+
"insee",
|
|
146
|
+
"nir",
|
|
147
|
+
"dni",
|
|
148
|
+
"nie",
|
|
149
|
+
"nif",
|
|
150
|
+
"cif",
|
|
151
|
+
"codice fiscale",
|
|
152
|
+
"fiscal code",
|
|
153
|
+
"bsn",
|
|
154
|
+
"burgerservicenummer",
|
|
155
|
+
"svnr",
|
|
156
|
+
"sozialversicherung",
|
|
157
|
+
"sozialversicherungsnummer",
|
|
158
|
+
"fiscal"
|
|
159
|
+
];
|
|
160
|
+
var NL_BSN_ANCHORS = ["bsn", "burgerservicenummer"];
|
|
161
|
+
var AT_SVNR_ANCHORS = ["svnr", "sozialversicherung", "sozialversicherungsnummer", "ssn"];
|
|
162
|
+
var DRIVER_ANCHORS = [
|
|
163
|
+
"driver licence",
|
|
164
|
+
"driving licence",
|
|
165
|
+
"drivers license",
|
|
166
|
+
"driving license",
|
|
167
|
+
"ridicsky prukaz",
|
|
168
|
+
"vodicsky preukaz",
|
|
169
|
+
"prawo jazdy",
|
|
170
|
+
"fuhrerschein",
|
|
171
|
+
"fuehrerschein",
|
|
172
|
+
"permis",
|
|
173
|
+
"driving permit"
|
|
174
|
+
];
|
|
175
|
+
var ADDRESS_ANCHORS = [
|
|
176
|
+
"street",
|
|
177
|
+
"st.",
|
|
178
|
+
"strasse",
|
|
179
|
+
"str.",
|
|
180
|
+
"ulice",
|
|
181
|
+
"ul.",
|
|
182
|
+
"ulica",
|
|
183
|
+
"road",
|
|
184
|
+
"rd.",
|
|
185
|
+
"avenue",
|
|
186
|
+
"ave.",
|
|
187
|
+
"postcode",
|
|
188
|
+
"postal code",
|
|
189
|
+
"zip",
|
|
190
|
+
"psc",
|
|
191
|
+
"plz"
|
|
192
|
+
];
|
|
193
|
+
var MRZ_ANCHORS = ["mrz", "machine readable zone", "passport", "id card", "travel document"];
|
|
194
|
+
var ALL_ANCHORS = dedupeAnchors([
|
|
195
|
+
...TAX_ANCHORS,
|
|
196
|
+
...DRIVER_ANCHORS,
|
|
197
|
+
...ADDRESS_ANCHORS,
|
|
198
|
+
...MRZ_ANCHORS,
|
|
199
|
+
...REGION_ANCHORS.czsk,
|
|
200
|
+
...REGION_ANCHORS.pl,
|
|
201
|
+
...REGION_ANCHORS.ro,
|
|
202
|
+
...REGION_ANCHORS.de,
|
|
203
|
+
...REGION_ANCHORS.hu,
|
|
204
|
+
...REGION_ANCHORS.gb,
|
|
205
|
+
...REGION_ANCHORS.fr,
|
|
206
|
+
...REGION_ANCHORS.es,
|
|
207
|
+
...REGION_ANCHORS.it,
|
|
208
|
+
...REGION_ANCHORS.nl,
|
|
209
|
+
...REGION_ANCHORS.at
|
|
210
|
+
]);
|
|
211
|
+
var TAX_ANCHOR_SET = new Set(TAX_ANCHORS.map(normalizeAnchorWord));
|
|
212
|
+
var DRIVER_ANCHOR_SET = new Set(DRIVER_ANCHORS.map(normalizeAnchorWord));
|
|
213
|
+
var ADDRESS_ANCHOR_SET = new Set(ADDRESS_ANCHORS.map(normalizeAnchorWord));
|
|
214
|
+
var MRZ_ANCHOR_SET = new Set(MRZ_ANCHORS.map(normalizeAnchorWord));
|
|
215
|
+
var NL_BSN_ANCHOR_SET = new Set(NL_BSN_ANCHORS.map(normalizeAnchorWord));
|
|
216
|
+
var AT_SVNR_ANCHOR_SET = new Set(AT_SVNR_ANCHORS.map(normalizeAnchorWord));
|
|
217
|
+
var anchorTrie = buildTrie(ALL_ANCHORS);
|
|
218
|
+
function dedupeAnchors(anchors) {
|
|
219
|
+
const unique = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const anchor of anchors) {
|
|
221
|
+
const normalized = normalizeAnchorWord(anchor);
|
|
222
|
+
if (normalized.length > 0) unique.add(normalized);
|
|
223
|
+
}
|
|
224
|
+
return Array.from(unique);
|
|
225
|
+
}
|
|
226
|
+
function foldForMatching(value) {
|
|
227
|
+
return value.toLowerCase().normalize("NFD").replace(/\p{M}+/gu, "");
|
|
228
|
+
}
|
|
229
|
+
function normalizeAnchorWord(value) {
|
|
230
|
+
return foldForMatching(value).trim().replace(/\s+/g, " ");
|
|
231
|
+
}
|
|
232
|
+
function createTrieNode() {
|
|
233
|
+
return { children: /* @__PURE__ */ new Map(), terminalWords: [] };
|
|
234
|
+
}
|
|
235
|
+
function buildTrie(words) {
|
|
236
|
+
const root = createTrieNode();
|
|
237
|
+
for (const word of words) {
|
|
238
|
+
let node = root;
|
|
239
|
+
for (const char of word) {
|
|
240
|
+
const next = node.children.get(char);
|
|
241
|
+
if (next) {
|
|
242
|
+
node = next;
|
|
243
|
+
} else {
|
|
244
|
+
const created = createTrieNode();
|
|
245
|
+
node.children.set(char, created);
|
|
246
|
+
node = created;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
node.terminalWords.push(word);
|
|
250
|
+
}
|
|
251
|
+
return root;
|
|
252
|
+
}
|
|
253
|
+
function isWordChar(char) {
|
|
254
|
+
if (!char) return false;
|
|
255
|
+
return /[a-z0-9]/i.test(char);
|
|
256
|
+
}
|
|
257
|
+
function hasWordBoundaries(text, start, end) {
|
|
258
|
+
const before = start > 0 ? text[start - 1] : void 0;
|
|
259
|
+
const after = end < text.length ? text[end] : void 0;
|
|
260
|
+
return !isWordChar(before) && !isWordChar(after);
|
|
261
|
+
}
|
|
262
|
+
function hasExpired(startTimeMs, deadlineMs) {
|
|
263
|
+
return Date.now() - startTimeMs > deadlineMs;
|
|
264
|
+
}
|
|
265
|
+
function toInt(value) {
|
|
266
|
+
return Number.parseInt(value, 10);
|
|
267
|
+
}
|
|
48
268
|
function countDigits(value) {
|
|
269
|
+
let count = 0;
|
|
270
|
+
for (const char of value) {
|
|
271
|
+
if (char >= "0" && char <= "9") count += 1;
|
|
272
|
+
}
|
|
273
|
+
return count;
|
|
274
|
+
}
|
|
275
|
+
function countLetters(value) {
|
|
276
|
+
let count = 0;
|
|
277
|
+
for (const char of value) {
|
|
278
|
+
if (char >= "A" && char <= "Z" || char >= "a" && char <= "z") count += 1;
|
|
279
|
+
}
|
|
280
|
+
return count;
|
|
281
|
+
}
|
|
282
|
+
function normalizeCompactIdentifier(value) {
|
|
283
|
+
return foldForMatching(value).toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
284
|
+
}
|
|
285
|
+
function alphaNumericOrdinalValue(char) {
|
|
286
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
287
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 65;
|
|
288
|
+
return -1;
|
|
289
|
+
}
|
|
290
|
+
function mod97FromDigits(value) {
|
|
291
|
+
let remainder = 0;
|
|
292
|
+
for (const char of value) {
|
|
293
|
+
if (char < "0" || char > "9") return -1;
|
|
294
|
+
remainder = (remainder * 10 + Number(char)) % 97;
|
|
295
|
+
}
|
|
296
|
+
return remainder;
|
|
297
|
+
}
|
|
298
|
+
function daysInMonth(year, month) {
|
|
299
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
300
|
+
}
|
|
301
|
+
function isValidDate(year, month, day) {
|
|
302
|
+
if (!Number.isInteger(year) || year < 1800 || year > 2299) return false;
|
|
303
|
+
if (!Number.isInteger(month) || month < 1 || month > 12) return false;
|
|
304
|
+
if (!Number.isInteger(day) || day < 1 || day > 31) return false;
|
|
305
|
+
return day <= daysInMonth(year, month);
|
|
306
|
+
}
|
|
307
|
+
function isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits) {
|
|
308
|
+
return isValidDate(1900 + yearTwoDigits, month, day) || isValidDate(2e3 + yearTwoDigits, month, day);
|
|
309
|
+
}
|
|
310
|
+
function resolveCzechMonth(rawMonth) {
|
|
311
|
+
if (rawMonth >= 1 && rawMonth <= 12) return rawMonth;
|
|
312
|
+
if (rawMonth >= 21 && rawMonth <= 32) return rawMonth - 20;
|
|
313
|
+
if (rawMonth >= 51 && rawMonth <= 62) return rawMonth - 50;
|
|
314
|
+
if (rawMonth >= 71 && rawMonth <= 82) return rawMonth - 70;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function resolveCzechYear(twoDigits, length) {
|
|
318
|
+
if (length === 9) return 1900 + twoDigits;
|
|
319
|
+
return twoDigits >= 54 ? 1900 + twoDigits : 2e3 + twoDigits;
|
|
320
|
+
}
|
|
321
|
+
function resolveCnpYear(centuryCode, twoDigits) {
|
|
322
|
+
if (centuryCode === 1 || centuryCode === 2) return 1900 + twoDigits;
|
|
323
|
+
if (centuryCode === 3 || centuryCode === 4) return 1800 + twoDigits;
|
|
324
|
+
if (centuryCode === 5 || centuryCode === 6) return 2e3 + twoDigits;
|
|
325
|
+
if (centuryCode === 7 || centuryCode === 8) return 2e3 + twoDigits;
|
|
326
|
+
if (centuryCode === 9) return 1900 + twoDigits;
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function pushUnique(items, candidate) {
|
|
330
|
+
const exists = items.some(
|
|
331
|
+
(item) => item.start === candidate.start && item.end === candidate.end && item.type === candidate.type
|
|
332
|
+
);
|
|
333
|
+
if (!exists) items.push(candidate);
|
|
334
|
+
}
|
|
335
|
+
function scanRegexMatches(regex, text, startTimeMs, deadlineMs, onMatch) {
|
|
336
|
+
regex.lastIndex = 0;
|
|
337
|
+
let scanned = 0;
|
|
338
|
+
let match;
|
|
339
|
+
while ((match = regex.exec(text)) !== null) {
|
|
340
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
341
|
+
scanned += 1;
|
|
342
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
343
|
+
const value = match[0] ?? "";
|
|
344
|
+
const start = match.index;
|
|
345
|
+
const end = start + value.length;
|
|
346
|
+
onMatch(value, start, end);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function scanAnchorHits(text, startTimeMs, deadlineMs) {
|
|
350
|
+
const normalized = foldForMatching(text);
|
|
351
|
+
const hits = [];
|
|
352
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
353
|
+
if (hasExpired(startTimeMs, deadlineMs) || hits.length >= MAX_PREFILTER_HITS) {
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
let node = anchorTrie.children.get(normalized[i] ?? "");
|
|
357
|
+
if (!node) continue;
|
|
358
|
+
let cursor = i;
|
|
359
|
+
while (node) {
|
|
360
|
+
if (node.terminalWords.length > 0) {
|
|
361
|
+
for (const word of node.terminalWords) {
|
|
362
|
+
const end = i + word.length;
|
|
363
|
+
if (end > normalized.length) continue;
|
|
364
|
+
if (normalized.slice(i, end) !== word) continue;
|
|
365
|
+
if (!hasWordBoundaries(normalized, i, end)) continue;
|
|
366
|
+
hits.push({ word, start: i, end });
|
|
367
|
+
if (hits.length >= MAX_PREFILTER_HITS) break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
cursor += 1;
|
|
371
|
+
if (cursor >= normalized.length) break;
|
|
372
|
+
node = node.children.get(normalized[cursor] ?? "");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return hits;
|
|
376
|
+
}
|
|
377
|
+
function hasAnchorNear(hits, start, end, anchorSet, windowChars = ANCHOR_WINDOW_CHARS) {
|
|
378
|
+
const left = Math.max(0, start - windowChars);
|
|
379
|
+
const right = end + windowChars;
|
|
380
|
+
for (const hit of hits) {
|
|
381
|
+
if (!anchorSet.has(hit.word)) continue;
|
|
382
|
+
if (hit.end < left) continue;
|
|
383
|
+
if (hit.start > right) continue;
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
function hasAnyAnchor(hits) {
|
|
389
|
+
return hits.length > 0;
|
|
390
|
+
}
|
|
391
|
+
function buildPrefilterState(text, startTimeMs, deadlineMs) {
|
|
392
|
+
const hits = scanAnchorHits(text, startTimeMs, deadlineMs);
|
|
393
|
+
return {
|
|
394
|
+
hits,
|
|
395
|
+
hasLongDigitHint: longDigitHintRegex.test(text),
|
|
396
|
+
hasSymbolHint: symbolHintRegex.test(text)
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function parseLocaleRegion(locale) {
|
|
400
|
+
if (!locale) return null;
|
|
401
|
+
const normalized = locale.toLowerCase();
|
|
402
|
+
if (normalized.startsWith("cs") || normalized.startsWith("sk")) return "czsk";
|
|
403
|
+
if (normalized.startsWith("pl")) return "pl";
|
|
404
|
+
if (normalized.startsWith("ro")) return "ro";
|
|
405
|
+
if (normalized.startsWith("de")) return "de";
|
|
406
|
+
if (normalized.startsWith("hu")) return "hu";
|
|
407
|
+
if (normalized === "en" || normalized.startsWith("en-gb") || normalized === "gb" || normalized.startsWith("gb-")) {
|
|
408
|
+
return "gb";
|
|
409
|
+
}
|
|
410
|
+
if (normalized.startsWith("fr")) return "fr";
|
|
411
|
+
if (normalized.startsWith("es")) return "es";
|
|
412
|
+
if (normalized.startsWith("it")) return "it";
|
|
413
|
+
if (normalized.startsWith("nl")) return "nl";
|
|
414
|
+
if (normalized.startsWith("at")) return "at";
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
function inferRegionsFromAnchors(hits) {
|
|
418
|
+
const regions = /* @__PURE__ */ new Set();
|
|
419
|
+
const regionEntries = Object.entries(REGION_ANCHORS);
|
|
420
|
+
for (const hit of hits) {
|
|
421
|
+
for (const [region, words] of regionEntries) {
|
|
422
|
+
if (words.includes(hit.word)) {
|
|
423
|
+
regions.add(region);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return Array.from(regions);
|
|
428
|
+
}
|
|
429
|
+
function resolveRegions(text, locale) {
|
|
430
|
+
const localeRegion = parseLocaleRegion(locale);
|
|
431
|
+
if (localeRegion) return [localeRegion];
|
|
432
|
+
const normalizedLocale = locale?.toLowerCase().trim() ?? "";
|
|
433
|
+
const inferred = inferRegionsFromAnchors(
|
|
434
|
+
scanAnchorHits(text, Date.now(), DEFAULT_PREFILTER_DEADLINE_MS)
|
|
435
|
+
);
|
|
436
|
+
if (inferred.length > 0) return inferred;
|
|
437
|
+
if (normalizedLocale.startsWith("uk")) {
|
|
438
|
+
return ["czsk", "pl", "ro", "de", "hu", "fr", "es", "it", "nl", "at"];
|
|
439
|
+
}
|
|
440
|
+
return ["czsk", "pl", "ro", "de", "hu", "gb", "fr", "es", "it", "nl", "at"];
|
|
441
|
+
}
|
|
442
|
+
function mrzCharValue(char) {
|
|
443
|
+
if (char === "<") return 0;
|
|
444
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
445
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 55;
|
|
446
|
+
return -1;
|
|
447
|
+
}
|
|
448
|
+
function computeMrzCheckDigit(payload) {
|
|
449
|
+
let sum = 0;
|
|
450
|
+
for (let i = 0; i < payload.length; i += 1) {
|
|
451
|
+
const value = mrzCharValue(payload[i] ?? "");
|
|
452
|
+
if (value < 0) return -1;
|
|
453
|
+
sum += value * mrzWeights[i % mrzWeights.length];
|
|
454
|
+
}
|
|
455
|
+
return sum % 10;
|
|
456
|
+
}
|
|
457
|
+
function isValidMrzCheck(payload, checkChar) {
|
|
458
|
+
if (!/^\d$/.test(checkChar)) return false;
|
|
459
|
+
const expected = computeMrzCheckDigit(payload);
|
|
460
|
+
return expected >= 0 && expected === Number(checkChar);
|
|
461
|
+
}
|
|
462
|
+
function isValidOptionalMrzCheck(payload, checkChar) {
|
|
463
|
+
if (checkChar === "<") {
|
|
464
|
+
return /^[<]+$/.test(payload);
|
|
465
|
+
}
|
|
466
|
+
return isValidMrzCheck(payload, checkChar);
|
|
467
|
+
}
|
|
468
|
+
function validateTd3(lines) {
|
|
469
|
+
if (lines.length !== 2) return false;
|
|
470
|
+
const [line1, line2] = lines;
|
|
471
|
+
if (line1.length !== 44 || line2.length !== 44) return false;
|
|
472
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
473
|
+
const passportNumber = line2.slice(0, 9);
|
|
474
|
+
const passportCheck = line2[9] ?? "";
|
|
475
|
+
const birthDate = line2.slice(13, 19);
|
|
476
|
+
const birthCheck = line2[19] ?? "";
|
|
477
|
+
const expiryDate = line2.slice(21, 27);
|
|
478
|
+
const expiryCheck = line2[27] ?? "";
|
|
479
|
+
const optionalData = line2.slice(28, 42);
|
|
480
|
+
const optionalCheck = line2[42] ?? "";
|
|
481
|
+
const finalCheck = line2[43] ?? "";
|
|
482
|
+
if (!isValidMrzCheck(passportNumber, passportCheck)) return false;
|
|
483
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
484
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
485
|
+
if (!isValidOptionalMrzCheck(optionalData, optionalCheck)) return false;
|
|
486
|
+
const composite = `${line2.slice(0, 10)}${line2.slice(13, 20)}${line2.slice(21, 43)}`;
|
|
487
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
488
|
+
}
|
|
489
|
+
function validateTd2(lines) {
|
|
490
|
+
if (lines.length !== 2) return false;
|
|
491
|
+
const [line1, line2] = lines;
|
|
492
|
+
if (line1.length !== 36 || line2.length !== 36) return false;
|
|
493
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
494
|
+
const documentNumber = line2.slice(0, 9);
|
|
495
|
+
const documentCheck = line2[9] ?? "";
|
|
496
|
+
const birthDate = line2.slice(13, 19);
|
|
497
|
+
const birthCheck = line2[19] ?? "";
|
|
498
|
+
const expiryDate = line2.slice(21, 27);
|
|
499
|
+
const expiryCheck = line2[27] ?? "";
|
|
500
|
+
const optionalData = line2.slice(28, 35);
|
|
501
|
+
const optionalCheck = line2[35] ?? "";
|
|
502
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
503
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
504
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
505
|
+
return isValidOptionalMrzCheck(optionalData, optionalCheck);
|
|
506
|
+
}
|
|
507
|
+
function validateTd1(lines) {
|
|
508
|
+
if (lines.length !== 3) return false;
|
|
509
|
+
const [line1, line2, line3] = lines;
|
|
510
|
+
if (line1.length !== 30 || line2.length !== 30 || line3.length !== 30) return false;
|
|
511
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
512
|
+
const documentNumber = line1.slice(5, 14);
|
|
513
|
+
const documentCheck = line1[14] ?? "";
|
|
514
|
+
const birthDate = line2.slice(0, 6);
|
|
515
|
+
const birthCheck = line2[6] ?? "";
|
|
516
|
+
const expiryDate = line2.slice(8, 14);
|
|
517
|
+
const expiryCheck = line2[14] ?? "";
|
|
518
|
+
const composite = `${line1.slice(5, 30)}${line2.slice(0, 7)}${line2.slice(8, 15)}${line2.slice(18, 29)}`;
|
|
519
|
+
const finalCheck = line2[29] ?? "";
|
|
520
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
521
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
522
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
523
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
524
|
+
}
|
|
525
|
+
function validateMrzLines(lines) {
|
|
526
|
+
const normalized = lines.map((line) => line.replace(/\s+/g, "").toUpperCase()).filter((line) => line.length > 0);
|
|
527
|
+
const merged = normalized.join("");
|
|
528
|
+
const reframed = normalized.length >= 2 && normalized.length <= 3 ? normalized : merged.length === 88 ? [merged.slice(0, 44), merged.slice(44)] : merged.length === 72 ? [merged.slice(0, 36), merged.slice(36)] : merged.length === 90 ? [merged.slice(0, 30), merged.slice(30, 60), merged.slice(60)] : normalized;
|
|
529
|
+
if (reframed.length === 2 && reframed[0]?.length === 44 && reframed[1]?.length === 44) {
|
|
530
|
+
return validateTd3(reframed);
|
|
531
|
+
}
|
|
532
|
+
if (reframed.length === 2 && reframed[0]?.length === 36 && reframed[1]?.length === 36) {
|
|
533
|
+
return validateTd2(reframed);
|
|
534
|
+
}
|
|
535
|
+
if (reframed.length === 3 && reframed[0]?.length === 30 && reframed[1]?.length === 30 && reframed[2]?.length === 30) {
|
|
536
|
+
return validateTd1(reframed);
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
function isValidPesel(value) {
|
|
541
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
542
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
543
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
544
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
545
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
546
|
+
const centuryOffset = Math.floor((mmRaw - 1) / 20);
|
|
547
|
+
const month = (mmRaw - 1) % 20 + 1;
|
|
548
|
+
const century = centuryOffset === 0 ? 1900 : centuryOffset === 1 ? 2e3 : centuryOffset === 2 ? 2100 : centuryOffset === 3 ? 2200 : centuryOffset === 4 ? 1800 : null;
|
|
549
|
+
if (century === null) return false;
|
|
550
|
+
if (!isValidDate(century + yy, month, dd)) return false;
|
|
551
|
+
let sum = 0;
|
|
552
|
+
for (let i = 0; i < 10; i += 1) {
|
|
553
|
+
sum += toInt(digitsOnly[i] ?? "0") * peselChecksumWeights[i];
|
|
554
|
+
}
|
|
555
|
+
const checkDigit = (10 - sum % 10) % 10;
|
|
556
|
+
return checkDigit === toInt(digitsOnly[10] ?? "0");
|
|
557
|
+
}
|
|
558
|
+
function isValidRomanianCnp(value) {
|
|
559
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
560
|
+
if (!/^\d{13}$/.test(digitsOnly)) return false;
|
|
561
|
+
const centuryCode = toInt(digitsOnly[0] ?? "0");
|
|
562
|
+
const yy = toInt(digitsOnly.slice(1, 3));
|
|
563
|
+
const mm = toInt(digitsOnly.slice(3, 5));
|
|
564
|
+
const dd = toInt(digitsOnly.slice(5, 7));
|
|
565
|
+
const year = resolveCnpYear(centuryCode, yy);
|
|
566
|
+
if (year === null) return false;
|
|
567
|
+
if (!isValidDate(year, mm, dd)) return false;
|
|
568
|
+
let sum = 0;
|
|
569
|
+
for (let i = 0; i < 12; i += 1) {
|
|
570
|
+
sum += toInt(digitsOnly[i] ?? "0") * cnpChecksumWeights[i];
|
|
571
|
+
}
|
|
572
|
+
let checkDigit = sum % 11;
|
|
573
|
+
if (checkDigit === 10) checkDigit = 1;
|
|
574
|
+
return checkDigit === toInt(digitsOnly[12] ?? "0");
|
|
575
|
+
}
|
|
576
|
+
function isValidCzechBirthNumber(value) {
|
|
577
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
578
|
+
if (!/^\d{9,10}$/.test(digitsOnly)) return false;
|
|
579
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
580
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
581
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
582
|
+
const month = resolveCzechMonth(mmRaw);
|
|
583
|
+
if (month === null) return false;
|
|
584
|
+
const year = resolveCzechYear(yy, digitsOnly.length);
|
|
585
|
+
if (!isValidDate(year, month, dd)) return false;
|
|
586
|
+
if (digitsOnly.length === 10) {
|
|
587
|
+
const body = toInt(digitsOnly.slice(0, 9));
|
|
588
|
+
const expected = body % 11 === 10 ? 0 : body % 11;
|
|
589
|
+
const check = toInt(digitsOnly[9] ?? "0");
|
|
590
|
+
return expected === check;
|
|
591
|
+
}
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
function isValidGermanTaxId(value) {
|
|
595
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
596
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
597
|
+
if (/^(\d)\1{10}$/.test(digitsOnly)) return false;
|
|
598
|
+
let product = 10;
|
|
599
|
+
for (let i = 0; i < 10; i += 1) {
|
|
600
|
+
let sum = (toInt(digitsOnly[i] ?? "0") + product) % 10;
|
|
601
|
+
if (sum === 0) sum = 10;
|
|
602
|
+
product = 2 * sum % 11;
|
|
603
|
+
}
|
|
604
|
+
let check = 11 - product;
|
|
605
|
+
if (check === 10 || check === 11) check = 0;
|
|
606
|
+
return check === toInt(digitsOnly[10] ?? "0");
|
|
607
|
+
}
|
|
608
|
+
function isValidHungarianTaj(value) {
|
|
609
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
610
|
+
if (!/^\d{9}$/.test(digitsOnly)) return false;
|
|
611
|
+
let sum = 0;
|
|
612
|
+
for (let i = 0; i < 8; i += 1) {
|
|
613
|
+
const weight = i % 2 === 0 ? 3 : 7;
|
|
614
|
+
sum += toInt(digitsOnly[i] ?? "0") * weight;
|
|
615
|
+
}
|
|
616
|
+
return sum % 10 === toInt(digitsOnly[8] ?? "0");
|
|
617
|
+
}
|
|
618
|
+
function isValidPolishNip(value) {
|
|
619
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
620
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
621
|
+
let sum = 0;
|
|
622
|
+
for (let i = 0; i < 9; i += 1) {
|
|
623
|
+
sum += toInt(digitsOnly[i] ?? "0") * polishNipWeights[i];
|
|
624
|
+
}
|
|
625
|
+
const check = sum % 11;
|
|
626
|
+
if (check === 10) return false;
|
|
627
|
+
return check === toInt(digitsOnly[9] ?? "0");
|
|
628
|
+
}
|
|
629
|
+
function isLikelyUkNino(value) {
|
|
630
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
631
|
+
if (!/^[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]$/.test(normalized)) return false;
|
|
632
|
+
const prefix = normalized.slice(0, 2);
|
|
633
|
+
const disallowedPrefixes = /* @__PURE__ */ new Set(["BG", "GB", "NK", "KN", "TN", "NT", "ZZ"]);
|
|
634
|
+
if (disallowedPrefixes.has(prefix)) return false;
|
|
635
|
+
if (normalized[0] === "D" || normalized[0] === "F" || normalized[0] === "I" || normalized[0] === "Q" || normalized[0] === "U" || normalized[0] === "V") {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
if (normalized[1] === "D" || normalized[1] === "F" || normalized[1] === "I" || normalized[1] === "O" || normalized[1] === "Q" || normalized[1] === "U" || normalized[1] === "V") {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
function isValidFrenchInsee(value) {
|
|
644
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
645
|
+
if (!/^[12]\d{2}(0[1-9]|1[0-2]|[23]\d|4[0-2]|[5-9]\d)(2A|2B|\d{2})\d{3}\d{3}\d{2}$/.test(normalized)) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
const body = normalized.slice(0, 13);
|
|
649
|
+
const checkDigits = normalized.slice(13);
|
|
650
|
+
const bodyForChecksum = body.replace("2A", "19").replace("2B", "18");
|
|
651
|
+
if (!/^\d{13}$/.test(bodyForChecksum)) return false;
|
|
652
|
+
const remainder = mod97FromDigits(bodyForChecksum);
|
|
653
|
+
if (remainder < 0) return false;
|
|
654
|
+
const expectedNumber = 97 - remainder;
|
|
655
|
+
return expectedNumber === toInt(checkDigits);
|
|
656
|
+
}
|
|
657
|
+
function isValidSpanishDniNie(value) {
|
|
658
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
659
|
+
let numberPortion = "";
|
|
660
|
+
let checkLetter = "";
|
|
661
|
+
if (/^\d{8}[A-Z]$/.test(normalized)) {
|
|
662
|
+
numberPortion = normalized.slice(0, 8);
|
|
663
|
+
checkLetter = normalized[8] ?? "";
|
|
664
|
+
} else if (/^[XYZ]\d{7}[A-Z]$/.test(normalized)) {
|
|
665
|
+
const prefixMap = { X: "0", Y: "1", Z: "2" };
|
|
666
|
+
numberPortion = `${prefixMap[normalized[0] ?? ""] ?? ""}${normalized.slice(1, 8)}`;
|
|
667
|
+
checkLetter = normalized[8] ?? "";
|
|
668
|
+
} else {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
const expected = dniNieControlLetters[toInt(numberPortion) % 23] ?? "";
|
|
672
|
+
return checkLetter === expected;
|
|
673
|
+
}
|
|
674
|
+
function isValidItalianCodiceFiscale(value) {
|
|
675
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
676
|
+
if (!/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/.test(normalized)) return false;
|
|
677
|
+
let sum = 0;
|
|
678
|
+
for (let i = 0; i < 15; i += 1) {
|
|
679
|
+
const char = normalized[i] ?? "";
|
|
680
|
+
const position = i + 1;
|
|
681
|
+
if (position % 2 === 0) {
|
|
682
|
+
const evenValue = alphaNumericOrdinalValue(char);
|
|
683
|
+
if (evenValue < 0) return false;
|
|
684
|
+
sum += evenValue;
|
|
685
|
+
} else {
|
|
686
|
+
const oddValue = italianOddControlMap[char];
|
|
687
|
+
if (typeof oddValue !== "number") return false;
|
|
688
|
+
sum += oddValue;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const expected = String.fromCharCode(65 + sum % 26);
|
|
692
|
+
return normalized[15] === expected;
|
|
693
|
+
}
|
|
694
|
+
function isValidDutchBsn(value) {
|
|
695
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
696
|
+
if (!/^\d{8,9}$/.test(digitsOnly)) return false;
|
|
697
|
+
const normalized = digitsOnly.padStart(9, "0");
|
|
698
|
+
if (normalized === "000000000") return false;
|
|
699
|
+
let sum = 0;
|
|
700
|
+
for (let i = 0; i < 8; i += 1) {
|
|
701
|
+
const weight = 9 - i;
|
|
702
|
+
sum += toInt(normalized[i] ?? "0") * weight;
|
|
703
|
+
}
|
|
704
|
+
sum -= toInt(normalized[8] ?? "0");
|
|
705
|
+
return sum % 11 === 0;
|
|
706
|
+
}
|
|
707
|
+
function isValidAustrianSvnr(value) {
|
|
708
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
709
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
710
|
+
const day = toInt(digitsOnly.slice(4, 6));
|
|
711
|
+
const month = toInt(digitsOnly.slice(6, 8));
|
|
712
|
+
const yearTwoDigits = toInt(digitsOnly.slice(8, 10));
|
|
713
|
+
if (!isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits)) return false;
|
|
714
|
+
const bodyIndexes = [0, 1, 2, 4, 5, 6, 7, 8, 9];
|
|
715
|
+
let sum = 0;
|
|
716
|
+
for (let i = 0; i < bodyIndexes.length; i += 1) {
|
|
717
|
+
const digit = toInt(digitsOnly[bodyIndexes[i] ?? 0] ?? "0");
|
|
718
|
+
sum += digit * atSvnrWeights[i];
|
|
719
|
+
}
|
|
720
|
+
const expected = sum % 11;
|
|
721
|
+
if (expected === 10) return false;
|
|
722
|
+
return expected === toInt(digitsOnly[3] ?? "0");
|
|
723
|
+
}
|
|
724
|
+
function isLikelyEuVat(value) {
|
|
725
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
726
|
+
if (!/^[A-Z]{2}[A-Z0-9]{8,12}$/.test(normalized)) return false;
|
|
727
|
+
const country = normalized.slice(0, 2);
|
|
728
|
+
return country !== "XX";
|
|
729
|
+
}
|
|
730
|
+
function isLikelyGenericDriverId(value) {
|
|
731
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
732
|
+
if (normalized.length < 8 || normalized.length > 18) return false;
|
|
733
|
+
const letters = countLetters(normalized);
|
|
734
|
+
const digits = countDigits(normalized);
|
|
735
|
+
if (letters < 2 || digits < 4) return false;
|
|
736
|
+
if (!/^[A-Z0-9]+$/.test(normalized)) return false;
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
function detectMrzBlocks(text, startTimeMs, deadlineMs, results) {
|
|
740
|
+
mrzBlockRegex.lastIndex = 0;
|
|
741
|
+
let scanned = 0;
|
|
742
|
+
let match;
|
|
743
|
+
while ((match = mrzBlockRegex.exec(text)) !== null) {
|
|
744
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
745
|
+
scanned += 1;
|
|
746
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
747
|
+
const rawBlock = match[1] ?? "";
|
|
748
|
+
const lines = rawBlock.split(/\r?\n/);
|
|
749
|
+
if (!validateMrzLines(lines)) continue;
|
|
750
|
+
const wholeMatch = match[0] ?? "";
|
|
751
|
+
const startsWithCrlf = wholeMatch.startsWith("\r\n");
|
|
752
|
+
const startsWithLf = !startsWithCrlf && wholeMatch.startsWith("\n");
|
|
753
|
+
const prefixShift = startsWithCrlf ? 2 : startsWithLf ? 1 : 0;
|
|
754
|
+
const start = (match.index ?? 0) + prefixShift;
|
|
755
|
+
const end = start + rawBlock.length;
|
|
756
|
+
pushUnique(results, {
|
|
757
|
+
type: "mrz_document",
|
|
758
|
+
value: rawBlock,
|
|
759
|
+
start,
|
|
760
|
+
end
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function detectNationalIdentifiers(text, options = {}) {
|
|
765
|
+
if (!text) return [];
|
|
766
|
+
const startTimeMs = options.startTimeMs ?? Date.now();
|
|
767
|
+
const deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
768
|
+
const prefilter = buildPrefilterState(text, startTimeMs, deadlineMs);
|
|
769
|
+
if (!hasAnyAnchor(prefilter.hits) && !prefilter.hasLongDigitHint && !prefilter.hasSymbolHint) {
|
|
770
|
+
return [];
|
|
771
|
+
}
|
|
772
|
+
const results = [];
|
|
773
|
+
const regions = resolveRegions(text, options.locale ?? null);
|
|
774
|
+
const hasTaxAnchors = prefilter.hits.some((hit) => TAX_ANCHOR_SET.has(hit.word));
|
|
775
|
+
const hasNlBsnAnchors = prefilter.hits.some((hit) => NL_BSN_ANCHOR_SET.has(hit.word));
|
|
776
|
+
const hasAtSvnrAnchors = prefilter.hits.some((hit) => AT_SVNR_ANCHOR_SET.has(hit.word));
|
|
777
|
+
const hasDriverAnchors = prefilter.hits.some((hit) => DRIVER_ANCHOR_SET.has(hit.word));
|
|
778
|
+
const hasAddressAnchors = prefilter.hits.some((hit) => ADDRESS_ANCHOR_SET.has(hit.word));
|
|
779
|
+
const hasMrzAnchors = prefilter.hits.some((hit) => MRZ_ANCHOR_SET.has(hit.word));
|
|
780
|
+
if (prefilter.hasSymbolHint || hasMrzAnchors) {
|
|
781
|
+
detectMrzBlocks(text, startTimeMs, deadlineMs, results);
|
|
782
|
+
}
|
|
783
|
+
if (regions.includes("czsk")) {
|
|
784
|
+
scanRegexMatches(czBirthNumberRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
785
|
+
if (!isValidCzechBirthNumber(value)) return;
|
|
786
|
+
pushUnique(results, { type: "birth_number", value, start, end });
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (regions.includes("pl")) {
|
|
790
|
+
scanRegexMatches(peselRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
791
|
+
if (!isValidPesel(value)) return;
|
|
792
|
+
pushUnique(results, { type: "pl_pesel", value, start, end });
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if (regions.includes("ro")) {
|
|
796
|
+
scanRegexMatches(cnpRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
797
|
+
if (!isValidRomanianCnp(value)) return;
|
|
798
|
+
pushUnique(results, { type: "ro_cnp", value, start, end });
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (regions.includes("de") && hasTaxAnchors) {
|
|
802
|
+
scanRegexMatches(deTaxIdRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
803
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
804
|
+
if (!isValidGermanTaxId(value)) return;
|
|
805
|
+
pushUnique(results, { type: "de_tax_id", value, start, end });
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
if (regions.includes("hu")) {
|
|
809
|
+
scanRegexMatches(huTajRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
810
|
+
if (!isValidHungarianTaj(value)) return;
|
|
811
|
+
pushUnique(results, { type: "hu_taj", value, start, end });
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (regions.includes("pl") && hasTaxAnchors) {
|
|
815
|
+
scanRegexMatches(plNipRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
816
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
817
|
+
if (!isValidPolishNip(value)) return;
|
|
818
|
+
pushUnique(results, { type: "pl_nip", value, start, end });
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (regions.includes("gb")) {
|
|
822
|
+
scanRegexMatches(ukNinoRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
823
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
824
|
+
if (!isLikelyUkNino(value)) return;
|
|
825
|
+
pushUnique(results, { type: "uk_nino", value, start, end });
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (regions.includes("fr")) {
|
|
829
|
+
scanRegexMatches(frInseeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
830
|
+
if (!isValidFrenchInsee(value)) return;
|
|
831
|
+
pushUnique(results, { type: "fr_insee", value, start, end });
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (regions.includes("es")) {
|
|
835
|
+
scanRegexMatches(esDniRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
836
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
837
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
838
|
+
});
|
|
839
|
+
scanRegexMatches(esNieRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
840
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
841
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
if (regions.includes("it")) {
|
|
845
|
+
scanRegexMatches(itCodiceFiscaleRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
846
|
+
if (!isValidItalianCodiceFiscale(value)) return;
|
|
847
|
+
pushUnique(results, { type: "it_codice_fiscale", value, start, end });
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
if (regions.includes("nl") && hasNlBsnAnchors) {
|
|
851
|
+
scanRegexMatches(nlBsnRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
852
|
+
if (!hasAnchorNear(prefilter.hits, start, end, NL_BSN_ANCHOR_SET)) return;
|
|
853
|
+
if (!isValidDutchBsn(value)) return;
|
|
854
|
+
pushUnique(results, { type: "nl_bsn", value, start, end });
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
if (regions.includes("at") && hasAtSvnrAnchors) {
|
|
858
|
+
scanRegexMatches(atSvnrRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
859
|
+
if (!hasAnchorNear(prefilter.hits, start, end, AT_SVNR_ANCHOR_SET)) return;
|
|
860
|
+
if (!isValidAustrianSvnr(value)) return;
|
|
861
|
+
pushUnique(results, { type: "at_svnr", value, start, end });
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (hasTaxAnchors) {
|
|
865
|
+
scanRegexMatches(euVatRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
866
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
867
|
+
if (!isLikelyEuVat(value)) return;
|
|
868
|
+
pushUnique(results, { type: "eu_vat", value, start, end });
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
if (hasDriverAnchors) {
|
|
872
|
+
scanRegexMatches(ukDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
873
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
874
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
875
|
+
});
|
|
876
|
+
scanRegexMatches(genericDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
877
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
878
|
+
if (!isLikelyGenericDriverId(value)) return;
|
|
879
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
if (hasAddressAnchors) {
|
|
883
|
+
scanRegexMatches(postalCodeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
884
|
+
if (!hasAnchorNear(prefilter.hits, start, end, ADDRESS_ANCHOR_SET, ADDRESS_WINDOW_CHARS)) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
pushUnique(results, { type: "address_postal", value, start, end });
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
if (options.allowContextBirthNumberFallback && !results.some((item) => item.type === "birth_number") && !/\d{6}\/?\d{3,4}/.test(text) && czBirthNumberContextRegex.test(text)) {
|
|
891
|
+
pushUnique(results, {
|
|
892
|
+
type: "birth_number",
|
|
893
|
+
value: "contextual_birth_number",
|
|
894
|
+
start: 0,
|
|
895
|
+
end: 0
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
return results;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/pii.ts
|
|
902
|
+
var defaultScanDeadlineMs = 100;
|
|
903
|
+
function countDigits2(value) {
|
|
49
904
|
let count = 0;
|
|
50
905
|
for (const ch of value) {
|
|
51
906
|
if (ch >= "0" && ch <= "9") count += 1;
|
|
@@ -80,111 +935,36 @@ function normalizeDetections(text, detections) {
|
|
|
80
935
|
return kept;
|
|
81
936
|
}
|
|
82
937
|
var PHONE_CONTEXT_KEYWORDS = [
|
|
83
|
-
// --- 🌍 GLOBAL / SYMBOLS / TECH ---
|
|
84
938
|
"tel",
|
|
85
939
|
"phone",
|
|
86
940
|
"mobile",
|
|
87
|
-
"mobil",
|
|
88
941
|
"cell",
|
|
89
942
|
"call",
|
|
90
|
-
"fax",
|
|
91
|
-
"sms",
|
|
92
|
-
"whatsapp",
|
|
93
|
-
"signal",
|
|
94
|
-
"telegram",
|
|
95
|
-
"viber",
|
|
96
|
-
"skype",
|
|
97
|
-
"dial",
|
|
98
943
|
"contact",
|
|
99
944
|
"number",
|
|
100
945
|
"hotline",
|
|
101
|
-
"helpdesk",
|
|
102
946
|
"support",
|
|
103
947
|
"infoline",
|
|
104
948
|
"customer service",
|
|
105
949
|
"client service",
|
|
106
|
-
"reach me",
|
|
107
|
-
"text me",
|
|
108
|
-
// --- 🇨🇿 CS (Czech) / 🇸🇰 SK (Slovak) ---
|
|
109
|
-
"volat",
|
|
110
|
-
"vola\u0165",
|
|
111
950
|
"telefon",
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"cislo",
|
|
115
|
-
"kontakt",
|
|
116
|
-
"mobiln\xED",
|
|
117
|
-
"mobiln\xFD",
|
|
118
|
-
"ozvi se",
|
|
119
|
-
"ozvi sa",
|
|
120
|
-
"napi\u0161",
|
|
121
|
-
"nap\xED\u0161",
|
|
122
|
-
"zavolej",
|
|
123
|
-
"zavolaj",
|
|
124
|
-
"pevn\xE1 linka",
|
|
125
|
-
"klapka",
|
|
126
|
-
"spojovatelka",
|
|
127
|
-
"z\xE1kaznick\xE1 linka",
|
|
128
|
-
"podpora",
|
|
129
|
-
// --- 🇩🇪 DE (German) ---
|
|
951
|
+
"telefonu",
|
|
952
|
+
"telefonszam",
|
|
130
953
|
"telefonnummer",
|
|
131
|
-
"handy",
|
|
132
|
-
"anruf",
|
|
133
|
-
"klingeln",
|
|
134
954
|
"rufnummer",
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"erreichen",
|
|
138
|
-
"kundenservice",
|
|
139
|
-
"support",
|
|
140
|
-
"mobilfunk",
|
|
141
|
-
"festnetz",
|
|
142
|
-
"durchwahl",
|
|
143
|
-
// --- 🇵🇱 PL (Polish) ---
|
|
144
|
-
"telefon",
|
|
145
|
-
"telefonu",
|
|
146
|
-
"kom\xF3rka",
|
|
147
|
-
"komorkowy",
|
|
148
|
-
"zadzwo\u0144",
|
|
149
|
-
"numer",
|
|
150
|
-
"kontakt",
|
|
151
|
-
"infolinia",
|
|
955
|
+
"zadzwon",
|
|
956
|
+
"hivas",
|
|
152
957
|
"wsparcie",
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
// --- 🇭🇺 HU (Hungarian) ---
|
|
157
|
-
"h\xEDv\xE1s",
|
|
158
|
-
"telefonsz\xE1m",
|
|
159
|
-
"sz\xE1m",
|
|
160
|
-
"mobiltelefon",
|
|
161
|
-
"mobil",
|
|
162
|
-
"vezet\xE9kes",
|
|
163
|
-
"el\xE9rhet\u0151s\xE9g",
|
|
164
|
-
"\xFCgyf\xE9lszolg\xE1lat",
|
|
165
|
-
"fax",
|
|
166
|
-
"t\xE1rcs\xE1zza",
|
|
167
|
-
"cs\xF6r\xF6gj\xF6n",
|
|
168
|
-
// --- 🇺🇦 UA (Ukrainian) ---
|
|
169
|
-
"\u0442\u0435\u043B\u0435\u0444\u043E\u043D",
|
|
170
|
-
"\u043C\u043E\u0431\u0456\u043B\u044C\u043D\u0438\u0439",
|
|
171
|
-
"\u0434\u0437\u0432\u043E\u043D\u0438\u0442\u0438",
|
|
172
|
-
"\u043D\u043E\u043C\u0435\u0440",
|
|
173
|
-
"\u043A\u043E\u043D\u0442\u0430\u043A\u0442",
|
|
174
|
-
"\u0437\u0432'\u044F\u0437\u043E\u043A",
|
|
175
|
-
"\u0433\u0430\u0440\u044F\u0447\u0430 \u043B\u0456\u043D\u0456\u044F",
|
|
176
|
-
"\u043F\u0456\u0434\u0442\u0440\u0438\u043C\u043A\u0430",
|
|
177
|
-
"\u0441\u043C\u0441",
|
|
178
|
-
"\u0444\u0430\u043A\u0441",
|
|
179
|
-
"\u043F\u043E\u0437\u0432\u043E\u043D\u0438\u0442\u044C",
|
|
180
|
-
"\u043D\u0430\u0431\u0440\u0430\u0442\u0438"
|
|
958
|
+
"podpora",
|
|
959
|
+
"kontakt",
|
|
960
|
+
"dial"
|
|
181
961
|
];
|
|
182
962
|
function escapeRegex(value) {
|
|
183
963
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
184
964
|
}
|
|
185
965
|
var PHONE_CONTEXT_RE = new RegExp(
|
|
186
966
|
`(?:^|[^\\p{L}])(?:${PHONE_CONTEXT_KEYWORDS.map(escapeRegex).join("|")})(?:$|[^\\p{L}])`,
|
|
187
|
-
"
|
|
967
|
+
"iu"
|
|
188
968
|
);
|
|
189
969
|
function hasPhoneContext(text, matchStartIndex, windowSize = 50) {
|
|
190
970
|
const start = Math.max(0, matchStartIndex - windowSize);
|
|
@@ -195,62 +975,79 @@ var PIIManager = class {
|
|
|
195
975
|
/**
|
|
196
976
|
* Reversible local-first masking using <TYPE_INDEX> placeholders.
|
|
197
977
|
*
|
|
198
|
-
* Zero-
|
|
978
|
+
* Zero-dependency fallback with strict checksum validation for CEE national IDs.
|
|
199
979
|
*/
|
|
200
980
|
anonymize(text) {
|
|
201
981
|
if (!text) return { maskedText: text, mapping: {} };
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const ibanRe = /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/gi;
|
|
209
|
-
for (const m of text.matchAll(ibanRe)) {
|
|
210
|
-
if (m.index == null) continue;
|
|
211
|
-
detections.push({ start: m.index, end: m.index + m[0].length, type: "IBAN", text: m[0] });
|
|
212
|
-
}
|
|
213
|
-
const ccRe = /(?:\b\d[\d -]{10,22}\d\b)/g;
|
|
214
|
-
for (const m of text.matchAll(ccRe)) {
|
|
215
|
-
if (m.index == null) continue;
|
|
216
|
-
const digits = countDigits(m[0]);
|
|
217
|
-
if (digits < 12 || digits > 19) continue;
|
|
218
|
-
if (!luhnCheck(m[0])) continue;
|
|
219
|
-
detections.push({ start: m.index, end: m.index + m[0].length, type: "CREDIT_CARD", text: m[0] });
|
|
220
|
-
}
|
|
221
|
-
const phoneRe = /(?<!\d)(?:\+?\d[\d\s().-]{7,}\d)(?!\d)/g;
|
|
222
|
-
for (const m of text.matchAll(phoneRe)) {
|
|
223
|
-
if (m.index == null) continue;
|
|
224
|
-
const candidate = m[0];
|
|
225
|
-
const digits = countDigits(candidate);
|
|
226
|
-
if (digits < 9 || digits > 15) continue;
|
|
227
|
-
const isStrongInternational = candidate.startsWith("+") || candidate.startsWith("00");
|
|
228
|
-
if (!isStrongInternational) {
|
|
229
|
-
const hasContext = hasPhoneContext(text, m.index);
|
|
230
|
-
if (!hasContext) continue;
|
|
982
|
+
try {
|
|
983
|
+
const detections = [];
|
|
984
|
+
const emailRe = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
985
|
+
for (const m of text.matchAll(emailRe)) {
|
|
986
|
+
if (m.index == null) continue;
|
|
987
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "EMAIL", text: m[0] });
|
|
231
988
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
989
|
+
const ibanRe = /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/gi;
|
|
990
|
+
for (const m of text.matchAll(ibanRe)) {
|
|
991
|
+
if (m.index == null) continue;
|
|
992
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "IBAN", text: m[0] });
|
|
993
|
+
}
|
|
994
|
+
const ccRe = /(?:\b\d[\d -]{10,22}\d\b)/g;
|
|
995
|
+
for (const m of text.matchAll(ccRe)) {
|
|
996
|
+
if (m.index == null) continue;
|
|
997
|
+
const digits = countDigits2(m[0]);
|
|
998
|
+
if (digits < 12 || digits > 19) continue;
|
|
999
|
+
if (!luhnCheck(m[0])) continue;
|
|
1000
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "CREDIT_CARD", text: m[0] });
|
|
1001
|
+
}
|
|
1002
|
+
const phoneRe = /(?<!\d)(?:\+?\d[\d\s().-]{7,}\d)(?!\d)/g;
|
|
1003
|
+
for (const m of text.matchAll(phoneRe)) {
|
|
1004
|
+
if (m.index == null) continue;
|
|
1005
|
+
const candidate = m[0];
|
|
1006
|
+
const digits = countDigits2(candidate);
|
|
1007
|
+
if (digits < 9 || digits > 15) continue;
|
|
1008
|
+
const isStrongInternational = candidate.startsWith("+") || candidate.startsWith("00");
|
|
1009
|
+
if (!isStrongInternational) {
|
|
1010
|
+
const hasContext = hasPhoneContext(text, m.index);
|
|
1011
|
+
if (!hasContext) continue;
|
|
1012
|
+
}
|
|
1013
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "PHONE", text: m[0] });
|
|
1014
|
+
}
|
|
1015
|
+
const personRe = /(?<!\p{L})\p{Lu}\p{Ll}{2,}\s+\p{Lu}\p{Ll}{2,}(?!\p{L})/gu;
|
|
1016
|
+
for (const m of text.matchAll(personRe)) {
|
|
1017
|
+
if (m.index == null) continue;
|
|
1018
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "PERSON", text: m[0] });
|
|
1019
|
+
}
|
|
1020
|
+
const nationalIdMatches = detectNationalIdentifiers(text, {
|
|
1021
|
+
deadlineMs: defaultScanDeadlineMs,
|
|
1022
|
+
allowContextBirthNumberFallback: false
|
|
1023
|
+
});
|
|
1024
|
+
for (const match of nationalIdMatches) {
|
|
1025
|
+
if (match.start < 0 || match.end <= match.start) continue;
|
|
1026
|
+
detections.push({
|
|
1027
|
+
start: match.start,
|
|
1028
|
+
end: match.end,
|
|
1029
|
+
type: "NATIONAL_ID",
|
|
1030
|
+
text: text.slice(match.start, match.end)
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
const kept = normalizeDetections(text, detections);
|
|
1034
|
+
if (!kept.length) return { maskedText: text, mapping: {} };
|
|
1035
|
+
const counters = {};
|
|
1036
|
+
const mapping = {};
|
|
1037
|
+
const replacements = kept.map((d) => {
|
|
1038
|
+
counters[d.type] = (counters[d.type] ?? 0) + 1;
|
|
1039
|
+
const placeholder = `<${d.type}_${counters[d.type]}>`;
|
|
1040
|
+
mapping[placeholder] = d.text;
|
|
1041
|
+
return { ...d, placeholder };
|
|
1042
|
+
});
|
|
1043
|
+
let masked = text;
|
|
1044
|
+
for (const r of replacements.sort((a, b) => b.start - a.start)) {
|
|
1045
|
+
masked = masked.slice(0, r.start) + r.placeholder + masked.slice(r.end);
|
|
1046
|
+
}
|
|
1047
|
+
return { maskedText: masked, mapping };
|
|
1048
|
+
} catch {
|
|
1049
|
+
return { maskedText: text, mapping: {} };
|
|
252
1050
|
}
|
|
253
|
-
return { maskedText: masked, mapping };
|
|
254
1051
|
}
|
|
255
1052
|
deanonymize(text, mapping) {
|
|
256
1053
|
if (!text || !mapping || !Object.keys(mapping).length) return text;
|
|
@@ -269,6 +1066,7 @@ var PIIManager = class {
|
|
|
269
1066
|
|
|
270
1067
|
// src/local-security-enforcer.ts
|
|
271
1068
|
var DEFAULT_STRICT_CONFIG = {
|
|
1069
|
+
shadow_mode: false,
|
|
272
1070
|
block_pii_leakage: true,
|
|
273
1071
|
block_db_access: true,
|
|
274
1072
|
block_code_execution: true,
|
|
@@ -315,6 +1113,12 @@ var LocalSecurityEnforcer = class {
|
|
|
315
1113
|
}
|
|
316
1114
|
enforce(params) {
|
|
317
1115
|
const { input, stream, config } = params;
|
|
1116
|
+
if (config.shadow_mode) {
|
|
1117
|
+
return {
|
|
1118
|
+
sanitizedInput: input,
|
|
1119
|
+
events: []
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
318
1122
|
const violationType = detectCapabilityViolation(input, config);
|
|
319
1123
|
if (violationType) {
|
|
320
1124
|
throw new SecurityPolicyViolationError(
|
|
@@ -380,12 +1184,23 @@ function readBooleanField(body, primaryKey, aliasKey) {
|
|
|
380
1184
|
}
|
|
381
1185
|
throw new Error(`Missing config field: ${primaryKey}`);
|
|
382
1186
|
}
|
|
1187
|
+
function readOptionalBooleanField(body, key, fallback) {
|
|
1188
|
+
if (!(key in body)) {
|
|
1189
|
+
return fallback;
|
|
1190
|
+
}
|
|
1191
|
+
const value = body[key];
|
|
1192
|
+
if (typeof value === "boolean") {
|
|
1193
|
+
return value;
|
|
1194
|
+
}
|
|
1195
|
+
throw new Error(`Invalid config field: ${key}`);
|
|
1196
|
+
}
|
|
383
1197
|
function normalizeCapabilityConfig(payload) {
|
|
384
1198
|
if (!payload || typeof payload !== "object") {
|
|
385
1199
|
throw new Error("Invalid config payload");
|
|
386
1200
|
}
|
|
387
1201
|
const body = payload;
|
|
388
1202
|
return {
|
|
1203
|
+
shadow_mode: readOptionalBooleanField(body, "shadow_mode", false),
|
|
389
1204
|
block_pii_leakage: readBooleanField(body, "block_pii_leakage", "block_pii"),
|
|
390
1205
|
block_db_access: readBooleanField(body, "block_db_access", "block_db"),
|
|
391
1206
|
block_code_execution: readBooleanField(
|
|
@@ -943,9 +1758,9 @@ function getInjectionScanner() {
|
|
|
943
1758
|
|
|
944
1759
|
// src/agentid.ts
|
|
945
1760
|
var AGENTID_SDK_VERSION_HEADER2 = "js-1.0.7";
|
|
946
|
-
var DEFAULT_GUARD_TIMEOUT_MS =
|
|
947
|
-
var MIN_GUARD_TIMEOUT_MS =
|
|
948
|
-
var MAX_GUARD_TIMEOUT_MS =
|
|
1761
|
+
var DEFAULT_GUARD_TIMEOUT_MS = 800;
|
|
1762
|
+
var MIN_GUARD_TIMEOUT_MS = 500;
|
|
1763
|
+
var MAX_GUARD_TIMEOUT_MS = 1e4;
|
|
949
1764
|
function normalizeBaseUrl3(baseUrl) {
|
|
950
1765
|
return baseUrl.replace(/\/+$/, "");
|
|
951
1766
|
}
|
|
@@ -954,6 +1769,12 @@ function isAbortSignalLike(value) {
|
|
|
954
1769
|
const candidate = value;
|
|
955
1770
|
return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function";
|
|
956
1771
|
}
|
|
1772
|
+
function isAsyncIterable(value) {
|
|
1773
|
+
if (!value || typeof value !== "object" && typeof value !== "function") {
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
return typeof value[Symbol.asyncIterator] === "function";
|
|
1777
|
+
}
|
|
957
1778
|
function normalizeOpenAICreateArgs(rawArgs) {
|
|
958
1779
|
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
959
1780
|
return rawArgs;
|
|
@@ -1014,6 +1835,70 @@ async function safeReadJson2(response) {
|
|
|
1014
1835
|
return null;
|
|
1015
1836
|
}
|
|
1016
1837
|
}
|
|
1838
|
+
function createCompletionChunkCollector() {
|
|
1839
|
+
if (typeof TransformStream === "function") {
|
|
1840
|
+
const stream = new TransformStream({
|
|
1841
|
+
transform(chunk, controller) {
|
|
1842
|
+
controller.enqueue(chunk);
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
const writer = stream.writable.getWriter();
|
|
1846
|
+
const result2 = (async () => {
|
|
1847
|
+
const reader = stream.readable.getReader();
|
|
1848
|
+
let combined = "";
|
|
1849
|
+
try {
|
|
1850
|
+
while (true) {
|
|
1851
|
+
const { value, done: done2 } = await reader.read();
|
|
1852
|
+
if (done2) break;
|
|
1853
|
+
if (typeof value === "string") {
|
|
1854
|
+
combined += value;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return combined;
|
|
1858
|
+
} finally {
|
|
1859
|
+
reader.releaseLock();
|
|
1860
|
+
}
|
|
1861
|
+
})();
|
|
1862
|
+
return {
|
|
1863
|
+
push: async (chunk) => {
|
|
1864
|
+
if (!chunk) return;
|
|
1865
|
+
await writer.write(chunk);
|
|
1866
|
+
},
|
|
1867
|
+
close: async () => {
|
|
1868
|
+
await writer.close();
|
|
1869
|
+
},
|
|
1870
|
+
abort: async (reason) => {
|
|
1871
|
+
await writer.abort(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1872
|
+
},
|
|
1873
|
+
result: result2
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
let done = false;
|
|
1877
|
+
const chunks = [];
|
|
1878
|
+
let resolveResult = null;
|
|
1879
|
+
let rejectResult = null;
|
|
1880
|
+
const result = new Promise((resolve, reject) => {
|
|
1881
|
+
resolveResult = resolve;
|
|
1882
|
+
rejectResult = reject;
|
|
1883
|
+
});
|
|
1884
|
+
return {
|
|
1885
|
+
push: async (chunk) => {
|
|
1886
|
+
if (done || !chunk) return;
|
|
1887
|
+
chunks.push(chunk);
|
|
1888
|
+
},
|
|
1889
|
+
close: async () => {
|
|
1890
|
+
if (done) return;
|
|
1891
|
+
done = true;
|
|
1892
|
+
resolveResult?.(chunks.join(""));
|
|
1893
|
+
},
|
|
1894
|
+
abort: async (reason) => {
|
|
1895
|
+
if (done) return;
|
|
1896
|
+
done = true;
|
|
1897
|
+
rejectResult?.(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1898
|
+
},
|
|
1899
|
+
result
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1017
1902
|
var AgentID = class {
|
|
1018
1903
|
constructor(config) {
|
|
1019
1904
|
this.injectionScanner = getInjectionScanner();
|
|
@@ -1169,6 +2054,28 @@ var AgentID = class {
|
|
|
1169
2054
|
}
|
|
1170
2055
|
}, { apiKey: params.apiKey });
|
|
1171
2056
|
}
|
|
2057
|
+
logGuardFallback(params) {
|
|
2058
|
+
this.log(
|
|
2059
|
+
{
|
|
2060
|
+
system_id: params.guardParams.system_id,
|
|
2061
|
+
user_id: params.guardParams.user_id,
|
|
2062
|
+
input: params.guardParams.input,
|
|
2063
|
+
output: "",
|
|
2064
|
+
model: params.guardParams.model ?? "unknown",
|
|
2065
|
+
event_type: "security_alert",
|
|
2066
|
+
severity: "warning",
|
|
2067
|
+
metadata: {
|
|
2068
|
+
source: "guard",
|
|
2069
|
+
status: params.status,
|
|
2070
|
+
guard_reason: params.reason,
|
|
2071
|
+
shadow_mode: false,
|
|
2072
|
+
suppress_ui: true,
|
|
2073
|
+
internal_retry: true
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
{ apiKey: params.apiKey }
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
1172
2079
|
/**
|
|
1173
2080
|
* GUARD: Checks limits, PII, and security before execution.
|
|
1174
2081
|
* strictMode=false (default): FAIL-OPEN on connectivity/timeouts.
|
|
@@ -1196,10 +2103,22 @@ var AgentID = class {
|
|
|
1196
2103
|
const responseBody = await safeReadJson2(res);
|
|
1197
2104
|
if (responseBody && typeof responseBody.allowed === "boolean") {
|
|
1198
2105
|
const verdict = responseBody;
|
|
1199
|
-
if (
|
|
2106
|
+
if (verdict.allowed === false && (isInfrastructureGuardReason(verdict.reason) || res.status >= 500)) {
|
|
2107
|
+
if (this.strictMode) {
|
|
2108
|
+
console.warn(
|
|
2109
|
+
`[AgentID] Guard API infrastructure failure in strict mode (${verdict.reason ?? `http_${res.status}`}). Blocking request.`
|
|
2110
|
+
);
|
|
2111
|
+
return { allowed: false, reason: verdict.reason ?? "network_error_strict_mode" };
|
|
2112
|
+
}
|
|
1200
2113
|
console.warn(
|
|
1201
2114
|
`[AgentID] Guard API infrastructure fallback in fail-open mode (${verdict.reason ?? `http_${res.status}`}).`
|
|
1202
2115
|
);
|
|
2116
|
+
this.logGuardFallback({
|
|
2117
|
+
reason: verdict.reason ?? `http_${res.status}`,
|
|
2118
|
+
status: "upstream_error",
|
|
2119
|
+
guardParams: params,
|
|
2120
|
+
apiKey: effectiveApiKey
|
|
2121
|
+
});
|
|
1203
2122
|
return { allowed: true, reason: "system_failure_fail_open" };
|
|
1204
2123
|
}
|
|
1205
2124
|
return verdict;
|
|
@@ -1212,55 +2131,168 @@ var AgentID = class {
|
|
|
1212
2131
|
const isAbortError2 = error && typeof error === "object" && error.name === "AbortError";
|
|
1213
2132
|
if (isAbortError2) {
|
|
1214
2133
|
const timeoutMessage = "AgentID API Warning: Connection timeout exceeded.";
|
|
2134
|
+
console.warn(timeoutMessage);
|
|
2135
|
+
this.logGuardFallback({
|
|
2136
|
+
reason: "timeout_fallback",
|
|
2137
|
+
status: "latency_timeout",
|
|
2138
|
+
guardParams: params,
|
|
2139
|
+
apiKey: effectiveApiKey
|
|
2140
|
+
});
|
|
1215
2141
|
if (this.strictMode) {
|
|
1216
|
-
|
|
2142
|
+
return { allowed: false, reason: "network_error_strict_mode" };
|
|
1217
2143
|
}
|
|
1218
|
-
console.warn(timeoutMessage);
|
|
1219
2144
|
return { allowed: true, reason: "timeout_fallback" };
|
|
1220
2145
|
}
|
|
2146
|
+
console.warn(
|
|
2147
|
+
this.strictMode ? "[AgentID] Guard check failed (Strict mode active):" : "[AgentID] Guard check failed (Fail-Open active):",
|
|
2148
|
+
error
|
|
2149
|
+
);
|
|
2150
|
+
this.logGuardFallback({
|
|
2151
|
+
reason: "guard_unreachable",
|
|
2152
|
+
status: "guard_unreachable",
|
|
2153
|
+
guardParams: params,
|
|
2154
|
+
apiKey: effectiveApiKey
|
|
2155
|
+
});
|
|
1221
2156
|
if (this.strictMode) {
|
|
1222
|
-
|
|
1223
|
-
throw error;
|
|
1224
|
-
}
|
|
1225
|
-
throw new Error("AgentID API Error: Guard request failed.");
|
|
2157
|
+
return { allowed: false, reason: "network_error_strict_mode" };
|
|
1226
2158
|
}
|
|
1227
|
-
console.warn("[AgentID] Guard check failed (Fail-Open active):", error);
|
|
1228
2159
|
return { allowed: true, reason: "guard_unreachable" };
|
|
1229
2160
|
} finally {
|
|
1230
2161
|
clearTimeout(timeoutId);
|
|
1231
2162
|
}
|
|
1232
2163
|
}
|
|
2164
|
+
extractStreamChunkText(chunk) {
|
|
2165
|
+
if (typeof chunk === "string") {
|
|
2166
|
+
return chunk;
|
|
2167
|
+
}
|
|
2168
|
+
if (!chunk || typeof chunk !== "object") {
|
|
2169
|
+
return "";
|
|
2170
|
+
}
|
|
2171
|
+
const choices = chunk.choices;
|
|
2172
|
+
if (!Array.isArray(choices)) {
|
|
2173
|
+
return "";
|
|
2174
|
+
}
|
|
2175
|
+
let text = "";
|
|
2176
|
+
for (const choice of choices) {
|
|
2177
|
+
if (!choice || typeof choice !== "object") continue;
|
|
2178
|
+
const delta = choice.delta;
|
|
2179
|
+
const message = choice.message;
|
|
2180
|
+
const contentCandidate = delta?.content ?? message?.content;
|
|
2181
|
+
if (typeof contentCandidate === "string") {
|
|
2182
|
+
text += contentCandidate;
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (Array.isArray(contentCandidate)) {
|
|
2186
|
+
for (const part of contentCandidate) {
|
|
2187
|
+
if (!part || typeof part !== "object") continue;
|
|
2188
|
+
const partText = part.text;
|
|
2189
|
+
if (typeof partText === "string") {
|
|
2190
|
+
text += partText;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return text;
|
|
2196
|
+
}
|
|
2197
|
+
wrapCompletion(completion) {
|
|
2198
|
+
if (typeof completion === "string") {
|
|
2199
|
+
const masked = this.pii.anonymize(completion);
|
|
2200
|
+
return {
|
|
2201
|
+
mode: "static",
|
|
2202
|
+
rawOutput: completion,
|
|
2203
|
+
transformedOutput: masked.maskedText,
|
|
2204
|
+
outputMasked: masked.maskedText !== completion
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
if (!isAsyncIterable(completion)) {
|
|
2208
|
+
const asText = String(completion ?? "");
|
|
2209
|
+
const masked = this.pii.anonymize(asText);
|
|
2210
|
+
return {
|
|
2211
|
+
mode: "static",
|
|
2212
|
+
rawOutput: asText,
|
|
2213
|
+
transformedOutput: masked.maskedText,
|
|
2214
|
+
outputMasked: masked.maskedText !== asText
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
const source = completion;
|
|
2218
|
+
const collector = createCompletionChunkCollector();
|
|
2219
|
+
const self = this;
|
|
2220
|
+
let resolveDone = null;
|
|
2221
|
+
let rejectDone = null;
|
|
2222
|
+
const done = new Promise((resolve, reject) => {
|
|
2223
|
+
resolveDone = resolve;
|
|
2224
|
+
rejectDone = reject;
|
|
2225
|
+
});
|
|
2226
|
+
const wrapped = {
|
|
2227
|
+
[Symbol.asyncIterator]: async function* () {
|
|
2228
|
+
try {
|
|
2229
|
+
for await (const chunk of source) {
|
|
2230
|
+
const chunkText = self.extractStreamChunkText(chunk);
|
|
2231
|
+
if (chunkText) {
|
|
2232
|
+
await collector.push(chunkText);
|
|
2233
|
+
}
|
|
2234
|
+
yield chunk;
|
|
2235
|
+
}
|
|
2236
|
+
await collector.close();
|
|
2237
|
+
const rawOutput = await collector.result;
|
|
2238
|
+
const masked = self.pii.anonymize(rawOutput);
|
|
2239
|
+
resolveDone?.({
|
|
2240
|
+
mode: "static",
|
|
2241
|
+
rawOutput,
|
|
2242
|
+
transformedOutput: masked.maskedText,
|
|
2243
|
+
outputMasked: masked.maskedText !== rawOutput
|
|
2244
|
+
});
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
await collector.abort(error);
|
|
2247
|
+
rejectDone?.(error instanceof Error ? error : new Error(String(error)));
|
|
2248
|
+
throw error;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
return {
|
|
2253
|
+
mode: "stream",
|
|
2254
|
+
completion: wrapped,
|
|
2255
|
+
done
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
1233
2258
|
/**
|
|
1234
2259
|
* LOG: Sends telemetry after execution.
|
|
1235
2260
|
* Non-blocking / Fire-and-forget.
|
|
1236
2261
|
*/
|
|
1237
2262
|
log(params, options) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
2263
|
+
queueMicrotask(() => {
|
|
2264
|
+
void (async () => {
|
|
2265
|
+
try {
|
|
2266
|
+
const effectiveApiKey = this.resolveApiKey(options?.apiKey);
|
|
2267
|
+
const eventId = params.event_id ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
2268
|
+
const timestamp = params.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2269
|
+
void this.getCapabilityConfig(false, { apiKey: effectiveApiKey }).catch(() => void 0);
|
|
2270
|
+
const metadata = {
|
|
2271
|
+
...params.metadata ?? {}
|
|
2272
|
+
};
|
|
2273
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, "agentid_base_url")) {
|
|
2274
|
+
metadata.agentid_base_url = this.baseUrl;
|
|
2275
|
+
}
|
|
2276
|
+
await fetch(`${this.baseUrl}/ingest`, {
|
|
2277
|
+
method: "POST",
|
|
2278
|
+
keepalive: true,
|
|
2279
|
+
headers: {
|
|
2280
|
+
"Content-Type": "application/json",
|
|
2281
|
+
"x-agentid-api-key": effectiveApiKey,
|
|
2282
|
+
"X-AgentID-SDK-Version": AGENTID_SDK_VERSION_HEADER2
|
|
2283
|
+
},
|
|
2284
|
+
body: JSON.stringify({
|
|
2285
|
+
...params,
|
|
2286
|
+
event_id: eventId,
|
|
2287
|
+
timestamp,
|
|
2288
|
+
metadata,
|
|
2289
|
+
client_capabilities: params.client_capabilities ?? this.buildClientCapabilities()
|
|
2290
|
+
})
|
|
2291
|
+
});
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
console.error("[AgentID] Log failed:", error);
|
|
2294
|
+
}
|
|
2295
|
+
})();
|
|
1264
2296
|
});
|
|
1265
2297
|
}
|
|
1266
2298
|
/**
|
|
@@ -1358,10 +2390,13 @@ var AgentID = class {
|
|
|
1358
2390
|
"AgentID: No user message found. Security guard requires string input."
|
|
1359
2391
|
);
|
|
1360
2392
|
}
|
|
2393
|
+
const clientEventId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1361
2394
|
const verdict = await this.guard({
|
|
1362
2395
|
input: maskedText,
|
|
1363
2396
|
system_id: systemId,
|
|
2397
|
+
model: adapter.getModelName(maskedReq),
|
|
1364
2398
|
user_id: options.user_id,
|
|
2399
|
+
client_event_id: clientEventId,
|
|
1365
2400
|
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
1366
2401
|
}, requestOptions);
|
|
1367
2402
|
if (!verdict.allowed) {
|
|
@@ -1369,27 +2404,83 @@ var AgentID = class {
|
|
|
1369
2404
|
`AgentID: Security Blocked (${verdict.reason ?? "guard_denied"})`
|
|
1370
2405
|
);
|
|
1371
2406
|
}
|
|
2407
|
+
const isShadowMode = verdict.shadow_mode === true;
|
|
2408
|
+
const transformedInput = isShadowMode ? maskedText : typeof verdict.transformed_input === "string" && verdict.transformed_input.length > 0 ? verdict.transformed_input : maskedText;
|
|
2409
|
+
if (transformedInput !== maskedText) {
|
|
2410
|
+
maskedText = transformedInput;
|
|
2411
|
+
maskedReq = this.withMaskedOpenAIRequest(
|
|
2412
|
+
req,
|
|
2413
|
+
transformedInput
|
|
2414
|
+
);
|
|
2415
|
+
const nextCreateArgs = [...normalizedCreateArgs];
|
|
2416
|
+
nextCreateArgs[0] = maskedReq;
|
|
2417
|
+
createArgs = nextCreateArgs;
|
|
2418
|
+
}
|
|
1372
2419
|
if (stream) {
|
|
1373
|
-
|
|
1374
|
-
|
|
2420
|
+
const streamResponse = await originalCreate.apply(compTarget, createArgs);
|
|
2421
|
+
const wrappedCompletion = this.wrapCompletion(
|
|
2422
|
+
isAsyncIterable(streamResponse) ? streamResponse : (async function* () {
|
|
2423
|
+
if (typeof streamResponse !== "undefined") {
|
|
2424
|
+
yield streamResponse;
|
|
2425
|
+
}
|
|
2426
|
+
})()
|
|
1375
2427
|
);
|
|
1376
|
-
|
|
2428
|
+
if (maskedText && wrappedCompletion.mode === "stream") {
|
|
2429
|
+
void wrappedCompletion.done.then((result) => {
|
|
2430
|
+
const outputForLog = isShadowMode ? result.rawOutput : result.transformedOutput;
|
|
2431
|
+
this.log({
|
|
2432
|
+
event_id: clientEventId,
|
|
2433
|
+
system_id: systemId,
|
|
2434
|
+
user_id: options.user_id,
|
|
2435
|
+
input: maskedText,
|
|
2436
|
+
output: outputForLog,
|
|
2437
|
+
model: adapter.getModelName(maskedReq),
|
|
2438
|
+
usage: void 0,
|
|
2439
|
+
latency: void 0,
|
|
2440
|
+
metadata: {
|
|
2441
|
+
transformed_input: maskedText,
|
|
2442
|
+
transformed_output: result.transformedOutput,
|
|
2443
|
+
output_masked: result.outputMasked,
|
|
2444
|
+
shadow_mode: isShadowMode,
|
|
2445
|
+
simulated_decision: verdict.simulated_decision ?? null,
|
|
2446
|
+
simulated_output_decision: isShadowMode && result.outputMasked ? "masked" : "allowed",
|
|
2447
|
+
response_streamed: true
|
|
2448
|
+
},
|
|
2449
|
+
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
2450
|
+
}, requestOptions);
|
|
2451
|
+
}).catch((error) => {
|
|
2452
|
+
console.error("[AgentID] Stream completion wrapping failed:", error);
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
return wrappedCompletion.mode === "stream" ? wrappedCompletion.completion : streamResponse;
|
|
1377
2456
|
}
|
|
1378
2457
|
const start = Date.now();
|
|
1379
2458
|
const res = await originalCreate.apply(compTarget, createArgs);
|
|
1380
2459
|
const latency = Date.now() - start;
|
|
1381
2460
|
if (maskedText) {
|
|
1382
2461
|
const output = adapter.extractOutput(res);
|
|
2462
|
+
const wrappedCompletion = this.wrapCompletion(output);
|
|
1383
2463
|
const model = adapter.getModelName(maskedReq, res);
|
|
1384
2464
|
const usage = adapter.getTokenUsage(res);
|
|
2465
|
+
const outputForLog = isShadowMode ? wrappedCompletion.rawOutput : wrappedCompletion.transformedOutput;
|
|
1385
2466
|
this.log({
|
|
2467
|
+
event_id: clientEventId,
|
|
1386
2468
|
system_id: systemId,
|
|
1387
2469
|
user_id: options.user_id,
|
|
1388
2470
|
input: maskedText,
|
|
1389
|
-
output,
|
|
2471
|
+
output: outputForLog,
|
|
1390
2472
|
model,
|
|
1391
2473
|
usage,
|
|
1392
2474
|
latency,
|
|
2475
|
+
metadata: {
|
|
2476
|
+
transformed_input: maskedText,
|
|
2477
|
+
transformed_output: wrappedCompletion.transformedOutput,
|
|
2478
|
+
output_masked: wrappedCompletion.outputMasked,
|
|
2479
|
+
shadow_mode: isShadowMode,
|
|
2480
|
+
simulated_decision: verdict.simulated_decision ?? null,
|
|
2481
|
+
simulated_output_decision: isShadowMode && wrappedCompletion.outputMasked ? "masked" : "allowed",
|
|
2482
|
+
response_streamed: false
|
|
2483
|
+
},
|
|
1393
2484
|
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
1394
2485
|
}, requestOptions);
|
|
1395
2486
|
}
|