agentid-sdk 0.1.7 → 0.1.8
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 +1268 -180
- package/dist/index.mjs +1249 -179
- package/dist/{langchain-C6HJAK2b.d.mts → langchain-DPMzxdoO.d.mts} +8 -0
- package/dist/{langchain-C6HJAK2b.d.ts → langchain-DPMzxdoO.d.ts} +8 -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.js
CHANGED
|
@@ -82,8 +82,863 @@ var OpenAIAdapter = class {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
// src/pii.ts
|
|
85
|
+
// src/pii-national-identifiers.ts
|
|
86
|
+
var MAX_CANDIDATES_PER_RULE = 256;
|
|
87
|
+
var MAX_PREFILTER_HITS = 512;
|
|
88
|
+
var DEFAULT_DEADLINE_MS = 100;
|
|
89
|
+
var DEFAULT_PREFILTER_DEADLINE_MS = 12;
|
|
90
|
+
var ANCHOR_WINDOW_CHARS = 20;
|
|
91
|
+
var ADDRESS_WINDOW_CHARS = 40;
|
|
92
|
+
var symbolHintRegex = /[\/<\-]/;
|
|
93
|
+
var longDigitHintRegex = /\d{8,}/;
|
|
94
|
+
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;
|
|
95
|
+
var czBirthNumberContextRegex = /\b(?:rodne?\s*(?:cislo|c\.)|r\.?\s*c\.?)\b[^0-9]{0,12}\d{6}(?:\/?\d{0,4})?\b/iu;
|
|
96
|
+
var peselRegex = /\b\d{11}\b/g;
|
|
97
|
+
var cnpRegex = /\b[1-9]\d{12}\b/g;
|
|
98
|
+
var deTaxIdRegex = /\b\d{11}\b/g;
|
|
99
|
+
var huTajRegex = /\b\d{9}\b/g;
|
|
100
|
+
var plNipRegex = /\b\d{10}\b/g;
|
|
101
|
+
var ukNinoRegex = /\b[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]\b/gi;
|
|
102
|
+
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;
|
|
103
|
+
var esDniRegex = /\b\d{8}[A-Z]\b/gi;
|
|
104
|
+
var esNieRegex = /\b[XYZ]\d{7}[A-Z]\b/gi;
|
|
105
|
+
var itCodiceFiscaleRegex = /\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b/gi;
|
|
106
|
+
var nlBsnRegex = /\b\d{8,9}\b/g;
|
|
107
|
+
var atSvnrRegex = /\b\d{4}\s?\d{6}\b/g;
|
|
108
|
+
var euVatRegex = /\b[A-Z]{2}[A-Z0-9]{8,12}\b/g;
|
|
109
|
+
var ukDriverRegex = /\b[A-Z9]{5}\d{6}[A-Z9]{2}\d[A-Z]{2}\b/g;
|
|
110
|
+
var genericDriverRegex = /\b[A-Z0-9]{8,18}\b/g;
|
|
111
|
+
var postalCodeRegex = /\b(?:\d{3}\s?\d{2}|\d{4}|\d{5})\b/g;
|
|
112
|
+
var mrzBlockRegex = /(?:^|\r?\n)([A-Z0-9< ]{10,44}(?:\r?\n[A-Z0-9< ]{10,44}){1,5})(?=\r?\n|$)/g;
|
|
113
|
+
var peselChecksumWeights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
|
|
114
|
+
var cnpChecksumWeights = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9];
|
|
115
|
+
var polishNipWeights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
|
|
116
|
+
var atSvnrWeights = [3, 7, 9, 5, 8, 4, 2, 1, 6];
|
|
117
|
+
var mrzWeights = [7, 3, 1];
|
|
118
|
+
var dniNieControlLetters = "TRWAGMYFPDXBNJZSQVHLCKE";
|
|
119
|
+
var italianOddControlMap = {
|
|
120
|
+
"0": 1,
|
|
121
|
+
"1": 0,
|
|
122
|
+
"2": 5,
|
|
123
|
+
"3": 7,
|
|
124
|
+
"4": 9,
|
|
125
|
+
"5": 13,
|
|
126
|
+
"6": 15,
|
|
127
|
+
"7": 17,
|
|
128
|
+
"8": 19,
|
|
129
|
+
"9": 21,
|
|
130
|
+
A: 1,
|
|
131
|
+
B: 0,
|
|
132
|
+
C: 5,
|
|
133
|
+
D: 7,
|
|
134
|
+
E: 9,
|
|
135
|
+
F: 13,
|
|
136
|
+
G: 15,
|
|
137
|
+
H: 17,
|
|
138
|
+
I: 19,
|
|
139
|
+
J: 21,
|
|
140
|
+
K: 2,
|
|
141
|
+
L: 4,
|
|
142
|
+
M: 18,
|
|
143
|
+
N: 20,
|
|
144
|
+
O: 11,
|
|
145
|
+
P: 3,
|
|
146
|
+
Q: 6,
|
|
147
|
+
R: 8,
|
|
148
|
+
S: 12,
|
|
149
|
+
T: 14,
|
|
150
|
+
U: 16,
|
|
151
|
+
V: 10,
|
|
152
|
+
W: 22,
|
|
153
|
+
X: 25,
|
|
154
|
+
Y: 24,
|
|
155
|
+
Z: 23
|
|
156
|
+
};
|
|
157
|
+
var REGION_ANCHORS = {
|
|
158
|
+
czsk: ["rodne cislo", "rc", "cislo", "birth number", "personal number"],
|
|
159
|
+
pl: ["pesel", "nip", "dowod", "poland"],
|
|
160
|
+
ro: ["cnp", "romania"],
|
|
161
|
+
de: ["steuer id", "steuer-id", "steueridentifikationsnummer", "ust-idnr", "deutschland"],
|
|
162
|
+
hu: ["taj", "szemelyi", "hungary"],
|
|
163
|
+
gb: ["nino", "national insurance", "gb", "great britain", "driving licence", "driver licence"],
|
|
164
|
+
fr: ["insee", "nir", "securite sociale", "france"],
|
|
165
|
+
es: ["dni", "nie", "nif", "cif", "espana", "spain"],
|
|
166
|
+
it: ["codice fiscale", "fiscal code", "italia", "italy"],
|
|
167
|
+
nl: ["bsn", "burgerservicenummer", "nederland", "netherlands"],
|
|
168
|
+
at: ["svnr", "sozialversicherung", "sozialversicherungsnummer", "austria", "oesterreich"]
|
|
169
|
+
};
|
|
170
|
+
var TAX_ANCHORS = [
|
|
171
|
+
"vat",
|
|
172
|
+
"vat number",
|
|
173
|
+
"tax",
|
|
174
|
+
"tax id",
|
|
175
|
+
"tin",
|
|
176
|
+
"dic",
|
|
177
|
+
"nip",
|
|
178
|
+
"nino",
|
|
179
|
+
"steuernummer",
|
|
180
|
+
"steuer id",
|
|
181
|
+
"steuer-id",
|
|
182
|
+
"ust-idnr",
|
|
183
|
+
"insee",
|
|
184
|
+
"nir",
|
|
185
|
+
"dni",
|
|
186
|
+
"nie",
|
|
187
|
+
"nif",
|
|
188
|
+
"cif",
|
|
189
|
+
"codice fiscale",
|
|
190
|
+
"fiscal code",
|
|
191
|
+
"bsn",
|
|
192
|
+
"burgerservicenummer",
|
|
193
|
+
"svnr",
|
|
194
|
+
"sozialversicherung",
|
|
195
|
+
"sozialversicherungsnummer",
|
|
196
|
+
"fiscal"
|
|
197
|
+
];
|
|
198
|
+
var NL_BSN_ANCHORS = ["bsn", "burgerservicenummer"];
|
|
199
|
+
var AT_SVNR_ANCHORS = ["svnr", "sozialversicherung", "sozialversicherungsnummer", "ssn"];
|
|
200
|
+
var DRIVER_ANCHORS = [
|
|
201
|
+
"driver licence",
|
|
202
|
+
"driving licence",
|
|
203
|
+
"drivers license",
|
|
204
|
+
"driving license",
|
|
205
|
+
"ridicsky prukaz",
|
|
206
|
+
"vodicsky preukaz",
|
|
207
|
+
"prawo jazdy",
|
|
208
|
+
"fuhrerschein",
|
|
209
|
+
"fuehrerschein",
|
|
210
|
+
"permis",
|
|
211
|
+
"driving permit"
|
|
212
|
+
];
|
|
213
|
+
var ADDRESS_ANCHORS = [
|
|
214
|
+
"street",
|
|
215
|
+
"st.",
|
|
216
|
+
"strasse",
|
|
217
|
+
"str.",
|
|
218
|
+
"ulice",
|
|
219
|
+
"ul.",
|
|
220
|
+
"ulica",
|
|
221
|
+
"road",
|
|
222
|
+
"rd.",
|
|
223
|
+
"avenue",
|
|
224
|
+
"ave.",
|
|
225
|
+
"postcode",
|
|
226
|
+
"postal code",
|
|
227
|
+
"zip",
|
|
228
|
+
"psc",
|
|
229
|
+
"plz"
|
|
230
|
+
];
|
|
231
|
+
var MRZ_ANCHORS = ["mrz", "machine readable zone", "passport", "id card", "travel document"];
|
|
232
|
+
var ALL_ANCHORS = dedupeAnchors([
|
|
233
|
+
...TAX_ANCHORS,
|
|
234
|
+
...DRIVER_ANCHORS,
|
|
235
|
+
...ADDRESS_ANCHORS,
|
|
236
|
+
...MRZ_ANCHORS,
|
|
237
|
+
...REGION_ANCHORS.czsk,
|
|
238
|
+
...REGION_ANCHORS.pl,
|
|
239
|
+
...REGION_ANCHORS.ro,
|
|
240
|
+
...REGION_ANCHORS.de,
|
|
241
|
+
...REGION_ANCHORS.hu,
|
|
242
|
+
...REGION_ANCHORS.gb,
|
|
243
|
+
...REGION_ANCHORS.fr,
|
|
244
|
+
...REGION_ANCHORS.es,
|
|
245
|
+
...REGION_ANCHORS.it,
|
|
246
|
+
...REGION_ANCHORS.nl,
|
|
247
|
+
...REGION_ANCHORS.at
|
|
248
|
+
]);
|
|
249
|
+
var TAX_ANCHOR_SET = new Set(TAX_ANCHORS.map(normalizeAnchorWord));
|
|
250
|
+
var DRIVER_ANCHOR_SET = new Set(DRIVER_ANCHORS.map(normalizeAnchorWord));
|
|
251
|
+
var ADDRESS_ANCHOR_SET = new Set(ADDRESS_ANCHORS.map(normalizeAnchorWord));
|
|
252
|
+
var MRZ_ANCHOR_SET = new Set(MRZ_ANCHORS.map(normalizeAnchorWord));
|
|
253
|
+
var NL_BSN_ANCHOR_SET = new Set(NL_BSN_ANCHORS.map(normalizeAnchorWord));
|
|
254
|
+
var AT_SVNR_ANCHOR_SET = new Set(AT_SVNR_ANCHORS.map(normalizeAnchorWord));
|
|
255
|
+
var anchorTrie = buildTrie(ALL_ANCHORS);
|
|
256
|
+
function dedupeAnchors(anchors) {
|
|
257
|
+
const unique = /* @__PURE__ */ new Set();
|
|
258
|
+
for (const anchor of anchors) {
|
|
259
|
+
const normalized = normalizeAnchorWord(anchor);
|
|
260
|
+
if (normalized.length > 0) unique.add(normalized);
|
|
261
|
+
}
|
|
262
|
+
return Array.from(unique);
|
|
263
|
+
}
|
|
264
|
+
function foldForMatching(value) {
|
|
265
|
+
return value.toLowerCase().normalize("NFD").replace(/\p{M}+/gu, "");
|
|
266
|
+
}
|
|
267
|
+
function normalizeAnchorWord(value) {
|
|
268
|
+
return foldForMatching(value).trim().replace(/\s+/g, " ");
|
|
269
|
+
}
|
|
270
|
+
function createTrieNode() {
|
|
271
|
+
return { children: /* @__PURE__ */ new Map(), terminalWords: [] };
|
|
272
|
+
}
|
|
273
|
+
function buildTrie(words) {
|
|
274
|
+
const root = createTrieNode();
|
|
275
|
+
for (const word of words) {
|
|
276
|
+
let node = root;
|
|
277
|
+
for (const char of word) {
|
|
278
|
+
const next = node.children.get(char);
|
|
279
|
+
if (next) {
|
|
280
|
+
node = next;
|
|
281
|
+
} else {
|
|
282
|
+
const created = createTrieNode();
|
|
283
|
+
node.children.set(char, created);
|
|
284
|
+
node = created;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
node.terminalWords.push(word);
|
|
288
|
+
}
|
|
289
|
+
return root;
|
|
290
|
+
}
|
|
291
|
+
function isWordChar(char) {
|
|
292
|
+
if (!char) return false;
|
|
293
|
+
return /[a-z0-9]/i.test(char);
|
|
294
|
+
}
|
|
295
|
+
function hasWordBoundaries(text, start, end) {
|
|
296
|
+
const before = start > 0 ? text[start - 1] : void 0;
|
|
297
|
+
const after = end < text.length ? text[end] : void 0;
|
|
298
|
+
return !isWordChar(before) && !isWordChar(after);
|
|
299
|
+
}
|
|
300
|
+
function hasExpired(startTimeMs, deadlineMs) {
|
|
301
|
+
return Date.now() - startTimeMs > deadlineMs;
|
|
302
|
+
}
|
|
303
|
+
function toInt(value) {
|
|
304
|
+
return Number.parseInt(value, 10);
|
|
305
|
+
}
|
|
86
306
|
function countDigits(value) {
|
|
307
|
+
let count = 0;
|
|
308
|
+
for (const char of value) {
|
|
309
|
+
if (char >= "0" && char <= "9") count += 1;
|
|
310
|
+
}
|
|
311
|
+
return count;
|
|
312
|
+
}
|
|
313
|
+
function countLetters(value) {
|
|
314
|
+
let count = 0;
|
|
315
|
+
for (const char of value) {
|
|
316
|
+
if (char >= "A" && char <= "Z" || char >= "a" && char <= "z") count += 1;
|
|
317
|
+
}
|
|
318
|
+
return count;
|
|
319
|
+
}
|
|
320
|
+
function normalizeCompactIdentifier(value) {
|
|
321
|
+
return foldForMatching(value).toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
322
|
+
}
|
|
323
|
+
function alphaNumericOrdinalValue(char) {
|
|
324
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
325
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 65;
|
|
326
|
+
return -1;
|
|
327
|
+
}
|
|
328
|
+
function mod97FromDigits(value) {
|
|
329
|
+
let remainder = 0;
|
|
330
|
+
for (const char of value) {
|
|
331
|
+
if (char < "0" || char > "9") return -1;
|
|
332
|
+
remainder = (remainder * 10 + Number(char)) % 97;
|
|
333
|
+
}
|
|
334
|
+
return remainder;
|
|
335
|
+
}
|
|
336
|
+
function daysInMonth(year, month) {
|
|
337
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
338
|
+
}
|
|
339
|
+
function isValidDate(year, month, day) {
|
|
340
|
+
if (!Number.isInteger(year) || year < 1800 || year > 2299) return false;
|
|
341
|
+
if (!Number.isInteger(month) || month < 1 || month > 12) return false;
|
|
342
|
+
if (!Number.isInteger(day) || day < 1 || day > 31) return false;
|
|
343
|
+
return day <= daysInMonth(year, month);
|
|
344
|
+
}
|
|
345
|
+
function isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits) {
|
|
346
|
+
return isValidDate(1900 + yearTwoDigits, month, day) || isValidDate(2e3 + yearTwoDigits, month, day);
|
|
347
|
+
}
|
|
348
|
+
function resolveCzechMonth(rawMonth) {
|
|
349
|
+
if (rawMonth >= 1 && rawMonth <= 12) return rawMonth;
|
|
350
|
+
if (rawMonth >= 21 && rawMonth <= 32) return rawMonth - 20;
|
|
351
|
+
if (rawMonth >= 51 && rawMonth <= 62) return rawMonth - 50;
|
|
352
|
+
if (rawMonth >= 71 && rawMonth <= 82) return rawMonth - 70;
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function resolveCzechYear(twoDigits, length) {
|
|
356
|
+
if (length === 9) return 1900 + twoDigits;
|
|
357
|
+
return twoDigits >= 54 ? 1900 + twoDigits : 2e3 + twoDigits;
|
|
358
|
+
}
|
|
359
|
+
function resolveCnpYear(centuryCode, twoDigits) {
|
|
360
|
+
if (centuryCode === 1 || centuryCode === 2) return 1900 + twoDigits;
|
|
361
|
+
if (centuryCode === 3 || centuryCode === 4) return 1800 + twoDigits;
|
|
362
|
+
if (centuryCode === 5 || centuryCode === 6) return 2e3 + twoDigits;
|
|
363
|
+
if (centuryCode === 7 || centuryCode === 8) return 2e3 + twoDigits;
|
|
364
|
+
if (centuryCode === 9) return 1900 + twoDigits;
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function pushUnique(items, candidate) {
|
|
368
|
+
const exists = items.some(
|
|
369
|
+
(item) => item.start === candidate.start && item.end === candidate.end && item.type === candidate.type
|
|
370
|
+
);
|
|
371
|
+
if (!exists) items.push(candidate);
|
|
372
|
+
}
|
|
373
|
+
function scanRegexMatches(regex, text, startTimeMs, deadlineMs, onMatch) {
|
|
374
|
+
regex.lastIndex = 0;
|
|
375
|
+
let scanned = 0;
|
|
376
|
+
let match;
|
|
377
|
+
while ((match = regex.exec(text)) !== null) {
|
|
378
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
379
|
+
scanned += 1;
|
|
380
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
381
|
+
const value = match[0] ?? "";
|
|
382
|
+
const start = match.index;
|
|
383
|
+
const end = start + value.length;
|
|
384
|
+
onMatch(value, start, end);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function scanAnchorHits(text, startTimeMs, deadlineMs) {
|
|
388
|
+
const normalized = foldForMatching(text);
|
|
389
|
+
const hits = [];
|
|
390
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
391
|
+
if (hasExpired(startTimeMs, deadlineMs) || hits.length >= MAX_PREFILTER_HITS) {
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
let node = anchorTrie.children.get(normalized[i] ?? "");
|
|
395
|
+
if (!node) continue;
|
|
396
|
+
let cursor = i;
|
|
397
|
+
while (node) {
|
|
398
|
+
if (node.terminalWords.length > 0) {
|
|
399
|
+
for (const word of node.terminalWords) {
|
|
400
|
+
const end = i + word.length;
|
|
401
|
+
if (end > normalized.length) continue;
|
|
402
|
+
if (normalized.slice(i, end) !== word) continue;
|
|
403
|
+
if (!hasWordBoundaries(normalized, i, end)) continue;
|
|
404
|
+
hits.push({ word, start: i, end });
|
|
405
|
+
if (hits.length >= MAX_PREFILTER_HITS) break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
cursor += 1;
|
|
409
|
+
if (cursor >= normalized.length) break;
|
|
410
|
+
node = node.children.get(normalized[cursor] ?? "");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return hits;
|
|
414
|
+
}
|
|
415
|
+
function hasAnchorNear(hits, start, end, anchorSet, windowChars = ANCHOR_WINDOW_CHARS) {
|
|
416
|
+
const left = Math.max(0, start - windowChars);
|
|
417
|
+
const right = end + windowChars;
|
|
418
|
+
for (const hit of hits) {
|
|
419
|
+
if (!anchorSet.has(hit.word)) continue;
|
|
420
|
+
if (hit.end < left) continue;
|
|
421
|
+
if (hit.start > right) continue;
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
function hasAnyAnchor(hits) {
|
|
427
|
+
return hits.length > 0;
|
|
428
|
+
}
|
|
429
|
+
function buildPrefilterState(text, startTimeMs, deadlineMs) {
|
|
430
|
+
const hits = scanAnchorHits(text, startTimeMs, deadlineMs);
|
|
431
|
+
return {
|
|
432
|
+
hits,
|
|
433
|
+
hasLongDigitHint: longDigitHintRegex.test(text),
|
|
434
|
+
hasSymbolHint: symbolHintRegex.test(text)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function parseLocaleRegion(locale) {
|
|
438
|
+
if (!locale) return null;
|
|
439
|
+
const normalized = locale.toLowerCase();
|
|
440
|
+
if (normalized.startsWith("cs") || normalized.startsWith("sk")) return "czsk";
|
|
441
|
+
if (normalized.startsWith("pl")) return "pl";
|
|
442
|
+
if (normalized.startsWith("ro")) return "ro";
|
|
443
|
+
if (normalized.startsWith("de")) return "de";
|
|
444
|
+
if (normalized.startsWith("hu")) return "hu";
|
|
445
|
+
if (normalized === "en" || normalized.startsWith("en-gb") || normalized === "gb" || normalized.startsWith("gb-")) {
|
|
446
|
+
return "gb";
|
|
447
|
+
}
|
|
448
|
+
if (normalized.startsWith("fr")) return "fr";
|
|
449
|
+
if (normalized.startsWith("es")) return "es";
|
|
450
|
+
if (normalized.startsWith("it")) return "it";
|
|
451
|
+
if (normalized.startsWith("nl")) return "nl";
|
|
452
|
+
if (normalized.startsWith("at")) return "at";
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
function inferRegionsFromAnchors(hits) {
|
|
456
|
+
const regions = /* @__PURE__ */ new Set();
|
|
457
|
+
const regionEntries = Object.entries(REGION_ANCHORS);
|
|
458
|
+
for (const hit of hits) {
|
|
459
|
+
for (const [region, words] of regionEntries) {
|
|
460
|
+
if (words.includes(hit.word)) {
|
|
461
|
+
regions.add(region);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return Array.from(regions);
|
|
466
|
+
}
|
|
467
|
+
function resolveRegions(text, locale) {
|
|
468
|
+
const localeRegion = parseLocaleRegion(locale);
|
|
469
|
+
if (localeRegion) return [localeRegion];
|
|
470
|
+
const normalizedLocale = locale?.toLowerCase().trim() ?? "";
|
|
471
|
+
const inferred = inferRegionsFromAnchors(
|
|
472
|
+
scanAnchorHits(text, Date.now(), DEFAULT_PREFILTER_DEADLINE_MS)
|
|
473
|
+
);
|
|
474
|
+
if (inferred.length > 0) return inferred;
|
|
475
|
+
if (normalizedLocale.startsWith("uk")) {
|
|
476
|
+
return ["czsk", "pl", "ro", "de", "hu", "fr", "es", "it", "nl", "at"];
|
|
477
|
+
}
|
|
478
|
+
return ["czsk", "pl", "ro", "de", "hu", "gb", "fr", "es", "it", "nl", "at"];
|
|
479
|
+
}
|
|
480
|
+
function mrzCharValue(char) {
|
|
481
|
+
if (char === "<") return 0;
|
|
482
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
483
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 55;
|
|
484
|
+
return -1;
|
|
485
|
+
}
|
|
486
|
+
function computeMrzCheckDigit(payload) {
|
|
487
|
+
let sum = 0;
|
|
488
|
+
for (let i = 0; i < payload.length; i += 1) {
|
|
489
|
+
const value = mrzCharValue(payload[i] ?? "");
|
|
490
|
+
if (value < 0) return -1;
|
|
491
|
+
sum += value * mrzWeights[i % mrzWeights.length];
|
|
492
|
+
}
|
|
493
|
+
return sum % 10;
|
|
494
|
+
}
|
|
495
|
+
function isValidMrzCheck(payload, checkChar) {
|
|
496
|
+
if (!/^\d$/.test(checkChar)) return false;
|
|
497
|
+
const expected = computeMrzCheckDigit(payload);
|
|
498
|
+
return expected >= 0 && expected === Number(checkChar);
|
|
499
|
+
}
|
|
500
|
+
function isValidOptionalMrzCheck(payload, checkChar) {
|
|
501
|
+
if (checkChar === "<") {
|
|
502
|
+
return /^[<]+$/.test(payload);
|
|
503
|
+
}
|
|
504
|
+
return isValidMrzCheck(payload, checkChar);
|
|
505
|
+
}
|
|
506
|
+
function validateTd3(lines) {
|
|
507
|
+
if (lines.length !== 2) return false;
|
|
508
|
+
const [line1, line2] = lines;
|
|
509
|
+
if (line1.length !== 44 || line2.length !== 44) return false;
|
|
510
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
511
|
+
const passportNumber = line2.slice(0, 9);
|
|
512
|
+
const passportCheck = line2[9] ?? "";
|
|
513
|
+
const birthDate = line2.slice(13, 19);
|
|
514
|
+
const birthCheck = line2[19] ?? "";
|
|
515
|
+
const expiryDate = line2.slice(21, 27);
|
|
516
|
+
const expiryCheck = line2[27] ?? "";
|
|
517
|
+
const optionalData = line2.slice(28, 42);
|
|
518
|
+
const optionalCheck = line2[42] ?? "";
|
|
519
|
+
const finalCheck = line2[43] ?? "";
|
|
520
|
+
if (!isValidMrzCheck(passportNumber, passportCheck)) return false;
|
|
521
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
522
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
523
|
+
if (!isValidOptionalMrzCheck(optionalData, optionalCheck)) return false;
|
|
524
|
+
const composite = `${line2.slice(0, 10)}${line2.slice(13, 20)}${line2.slice(21, 43)}`;
|
|
525
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
526
|
+
}
|
|
527
|
+
function validateTd2(lines) {
|
|
528
|
+
if (lines.length !== 2) return false;
|
|
529
|
+
const [line1, line2] = lines;
|
|
530
|
+
if (line1.length !== 36 || line2.length !== 36) return false;
|
|
531
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
532
|
+
const documentNumber = line2.slice(0, 9);
|
|
533
|
+
const documentCheck = line2[9] ?? "";
|
|
534
|
+
const birthDate = line2.slice(13, 19);
|
|
535
|
+
const birthCheck = line2[19] ?? "";
|
|
536
|
+
const expiryDate = line2.slice(21, 27);
|
|
537
|
+
const expiryCheck = line2[27] ?? "";
|
|
538
|
+
const optionalData = line2.slice(28, 35);
|
|
539
|
+
const optionalCheck = line2[35] ?? "";
|
|
540
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
541
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
542
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
543
|
+
return isValidOptionalMrzCheck(optionalData, optionalCheck);
|
|
544
|
+
}
|
|
545
|
+
function validateTd1(lines) {
|
|
546
|
+
if (lines.length !== 3) return false;
|
|
547
|
+
const [line1, line2, line3] = lines;
|
|
548
|
+
if (line1.length !== 30 || line2.length !== 30 || line3.length !== 30) return false;
|
|
549
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
550
|
+
const documentNumber = line1.slice(5, 14);
|
|
551
|
+
const documentCheck = line1[14] ?? "";
|
|
552
|
+
const birthDate = line2.slice(0, 6);
|
|
553
|
+
const birthCheck = line2[6] ?? "";
|
|
554
|
+
const expiryDate = line2.slice(8, 14);
|
|
555
|
+
const expiryCheck = line2[14] ?? "";
|
|
556
|
+
const composite = `${line1.slice(5, 30)}${line2.slice(0, 7)}${line2.slice(8, 15)}${line2.slice(18, 29)}`;
|
|
557
|
+
const finalCheck = line2[29] ?? "";
|
|
558
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
559
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
560
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
561
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
562
|
+
}
|
|
563
|
+
function validateMrzLines(lines) {
|
|
564
|
+
const normalized = lines.map((line) => line.replace(/\s+/g, "").toUpperCase()).filter((line) => line.length > 0);
|
|
565
|
+
const merged = normalized.join("");
|
|
566
|
+
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;
|
|
567
|
+
if (reframed.length === 2 && reframed[0]?.length === 44 && reframed[1]?.length === 44) {
|
|
568
|
+
return validateTd3(reframed);
|
|
569
|
+
}
|
|
570
|
+
if (reframed.length === 2 && reframed[0]?.length === 36 && reframed[1]?.length === 36) {
|
|
571
|
+
return validateTd2(reframed);
|
|
572
|
+
}
|
|
573
|
+
if (reframed.length === 3 && reframed[0]?.length === 30 && reframed[1]?.length === 30 && reframed[2]?.length === 30) {
|
|
574
|
+
return validateTd1(reframed);
|
|
575
|
+
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
function isValidPesel(value) {
|
|
579
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
580
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
581
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
582
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
583
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
584
|
+
const centuryOffset = Math.floor((mmRaw - 1) / 20);
|
|
585
|
+
const month = (mmRaw - 1) % 20 + 1;
|
|
586
|
+
const century = centuryOffset === 0 ? 1900 : centuryOffset === 1 ? 2e3 : centuryOffset === 2 ? 2100 : centuryOffset === 3 ? 2200 : centuryOffset === 4 ? 1800 : null;
|
|
587
|
+
if (century === null) return false;
|
|
588
|
+
if (!isValidDate(century + yy, month, dd)) return false;
|
|
589
|
+
let sum = 0;
|
|
590
|
+
for (let i = 0; i < 10; i += 1) {
|
|
591
|
+
sum += toInt(digitsOnly[i] ?? "0") * peselChecksumWeights[i];
|
|
592
|
+
}
|
|
593
|
+
const checkDigit = (10 - sum % 10) % 10;
|
|
594
|
+
return checkDigit === toInt(digitsOnly[10] ?? "0");
|
|
595
|
+
}
|
|
596
|
+
function isValidRomanianCnp(value) {
|
|
597
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
598
|
+
if (!/^\d{13}$/.test(digitsOnly)) return false;
|
|
599
|
+
const centuryCode = toInt(digitsOnly[0] ?? "0");
|
|
600
|
+
const yy = toInt(digitsOnly.slice(1, 3));
|
|
601
|
+
const mm = toInt(digitsOnly.slice(3, 5));
|
|
602
|
+
const dd = toInt(digitsOnly.slice(5, 7));
|
|
603
|
+
const year = resolveCnpYear(centuryCode, yy);
|
|
604
|
+
if (year === null) return false;
|
|
605
|
+
if (!isValidDate(year, mm, dd)) return false;
|
|
606
|
+
let sum = 0;
|
|
607
|
+
for (let i = 0; i < 12; i += 1) {
|
|
608
|
+
sum += toInt(digitsOnly[i] ?? "0") * cnpChecksumWeights[i];
|
|
609
|
+
}
|
|
610
|
+
let checkDigit = sum % 11;
|
|
611
|
+
if (checkDigit === 10) checkDigit = 1;
|
|
612
|
+
return checkDigit === toInt(digitsOnly[12] ?? "0");
|
|
613
|
+
}
|
|
614
|
+
function isValidCzechBirthNumber(value) {
|
|
615
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
616
|
+
if (!/^\d{9,10}$/.test(digitsOnly)) return false;
|
|
617
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
618
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
619
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
620
|
+
const month = resolveCzechMonth(mmRaw);
|
|
621
|
+
if (month === null) return false;
|
|
622
|
+
const year = resolveCzechYear(yy, digitsOnly.length);
|
|
623
|
+
if (!isValidDate(year, month, dd)) return false;
|
|
624
|
+
if (digitsOnly.length === 10) {
|
|
625
|
+
const body = toInt(digitsOnly.slice(0, 9));
|
|
626
|
+
const expected = body % 11 === 10 ? 0 : body % 11;
|
|
627
|
+
const check = toInt(digitsOnly[9] ?? "0");
|
|
628
|
+
return expected === check;
|
|
629
|
+
}
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
function isValidGermanTaxId(value) {
|
|
633
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
634
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
635
|
+
if (/^(\d)\1{10}$/.test(digitsOnly)) return false;
|
|
636
|
+
let product = 10;
|
|
637
|
+
for (let i = 0; i < 10; i += 1) {
|
|
638
|
+
let sum = (toInt(digitsOnly[i] ?? "0") + product) % 10;
|
|
639
|
+
if (sum === 0) sum = 10;
|
|
640
|
+
product = 2 * sum % 11;
|
|
641
|
+
}
|
|
642
|
+
let check = 11 - product;
|
|
643
|
+
if (check === 10 || check === 11) check = 0;
|
|
644
|
+
return check === toInt(digitsOnly[10] ?? "0");
|
|
645
|
+
}
|
|
646
|
+
function isValidHungarianTaj(value) {
|
|
647
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
648
|
+
if (!/^\d{9}$/.test(digitsOnly)) return false;
|
|
649
|
+
let sum = 0;
|
|
650
|
+
for (let i = 0; i < 8; i += 1) {
|
|
651
|
+
const weight = i % 2 === 0 ? 3 : 7;
|
|
652
|
+
sum += toInt(digitsOnly[i] ?? "0") * weight;
|
|
653
|
+
}
|
|
654
|
+
return sum % 10 === toInt(digitsOnly[8] ?? "0");
|
|
655
|
+
}
|
|
656
|
+
function isValidPolishNip(value) {
|
|
657
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
658
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
659
|
+
let sum = 0;
|
|
660
|
+
for (let i = 0; i < 9; i += 1) {
|
|
661
|
+
sum += toInt(digitsOnly[i] ?? "0") * polishNipWeights[i];
|
|
662
|
+
}
|
|
663
|
+
const check = sum % 11;
|
|
664
|
+
if (check === 10) return false;
|
|
665
|
+
return check === toInt(digitsOnly[9] ?? "0");
|
|
666
|
+
}
|
|
667
|
+
function isLikelyUkNino(value) {
|
|
668
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
669
|
+
if (!/^[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]$/.test(normalized)) return false;
|
|
670
|
+
const prefix = normalized.slice(0, 2);
|
|
671
|
+
const disallowedPrefixes = /* @__PURE__ */ new Set(["BG", "GB", "NK", "KN", "TN", "NT", "ZZ"]);
|
|
672
|
+
if (disallowedPrefixes.has(prefix)) return false;
|
|
673
|
+
if (normalized[0] === "D" || normalized[0] === "F" || normalized[0] === "I" || normalized[0] === "Q" || normalized[0] === "U" || normalized[0] === "V") {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
if (normalized[1] === "D" || normalized[1] === "F" || normalized[1] === "I" || normalized[1] === "O" || normalized[1] === "Q" || normalized[1] === "U" || normalized[1] === "V") {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
function isValidFrenchInsee(value) {
|
|
682
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
683
|
+
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)) {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
const body = normalized.slice(0, 13);
|
|
687
|
+
const checkDigits = normalized.slice(13);
|
|
688
|
+
const bodyForChecksum = body.replace("2A", "19").replace("2B", "18");
|
|
689
|
+
if (!/^\d{13}$/.test(bodyForChecksum)) return false;
|
|
690
|
+
const remainder = mod97FromDigits(bodyForChecksum);
|
|
691
|
+
if (remainder < 0) return false;
|
|
692
|
+
const expectedNumber = 97 - remainder;
|
|
693
|
+
return expectedNumber === toInt(checkDigits);
|
|
694
|
+
}
|
|
695
|
+
function isValidSpanishDniNie(value) {
|
|
696
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
697
|
+
let numberPortion = "";
|
|
698
|
+
let checkLetter = "";
|
|
699
|
+
if (/^\d{8}[A-Z]$/.test(normalized)) {
|
|
700
|
+
numberPortion = normalized.slice(0, 8);
|
|
701
|
+
checkLetter = normalized[8] ?? "";
|
|
702
|
+
} else if (/^[XYZ]\d{7}[A-Z]$/.test(normalized)) {
|
|
703
|
+
const prefixMap = { X: "0", Y: "1", Z: "2" };
|
|
704
|
+
numberPortion = `${prefixMap[normalized[0] ?? ""] ?? ""}${normalized.slice(1, 8)}`;
|
|
705
|
+
checkLetter = normalized[8] ?? "";
|
|
706
|
+
} else {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
const expected = dniNieControlLetters[toInt(numberPortion) % 23] ?? "";
|
|
710
|
+
return checkLetter === expected;
|
|
711
|
+
}
|
|
712
|
+
function isValidItalianCodiceFiscale(value) {
|
|
713
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
714
|
+
if (!/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/.test(normalized)) return false;
|
|
715
|
+
let sum = 0;
|
|
716
|
+
for (let i = 0; i < 15; i += 1) {
|
|
717
|
+
const char = normalized[i] ?? "";
|
|
718
|
+
const position = i + 1;
|
|
719
|
+
if (position % 2 === 0) {
|
|
720
|
+
const evenValue = alphaNumericOrdinalValue(char);
|
|
721
|
+
if (evenValue < 0) return false;
|
|
722
|
+
sum += evenValue;
|
|
723
|
+
} else {
|
|
724
|
+
const oddValue = italianOddControlMap[char];
|
|
725
|
+
if (typeof oddValue !== "number") return false;
|
|
726
|
+
sum += oddValue;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const expected = String.fromCharCode(65 + sum % 26);
|
|
730
|
+
return normalized[15] === expected;
|
|
731
|
+
}
|
|
732
|
+
function isValidDutchBsn(value) {
|
|
733
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
734
|
+
if (!/^\d{8,9}$/.test(digitsOnly)) return false;
|
|
735
|
+
const normalized = digitsOnly.padStart(9, "0");
|
|
736
|
+
if (normalized === "000000000") return false;
|
|
737
|
+
let sum = 0;
|
|
738
|
+
for (let i = 0; i < 8; i += 1) {
|
|
739
|
+
const weight = 9 - i;
|
|
740
|
+
sum += toInt(normalized[i] ?? "0") * weight;
|
|
741
|
+
}
|
|
742
|
+
sum -= toInt(normalized[8] ?? "0");
|
|
743
|
+
return sum % 11 === 0;
|
|
744
|
+
}
|
|
745
|
+
function isValidAustrianSvnr(value) {
|
|
746
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
747
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
748
|
+
const day = toInt(digitsOnly.slice(4, 6));
|
|
749
|
+
const month = toInt(digitsOnly.slice(6, 8));
|
|
750
|
+
const yearTwoDigits = toInt(digitsOnly.slice(8, 10));
|
|
751
|
+
if (!isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits)) return false;
|
|
752
|
+
const bodyIndexes = [0, 1, 2, 4, 5, 6, 7, 8, 9];
|
|
753
|
+
let sum = 0;
|
|
754
|
+
for (let i = 0; i < bodyIndexes.length; i += 1) {
|
|
755
|
+
const digit = toInt(digitsOnly[bodyIndexes[i] ?? 0] ?? "0");
|
|
756
|
+
sum += digit * atSvnrWeights[i];
|
|
757
|
+
}
|
|
758
|
+
const expected = sum % 11;
|
|
759
|
+
if (expected === 10) return false;
|
|
760
|
+
return expected === toInt(digitsOnly[3] ?? "0");
|
|
761
|
+
}
|
|
762
|
+
function isLikelyEuVat(value) {
|
|
763
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
764
|
+
if (!/^[A-Z]{2}[A-Z0-9]{8,12}$/.test(normalized)) return false;
|
|
765
|
+
const country = normalized.slice(0, 2);
|
|
766
|
+
return country !== "XX";
|
|
767
|
+
}
|
|
768
|
+
function isLikelyGenericDriverId(value) {
|
|
769
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
770
|
+
if (normalized.length < 8 || normalized.length > 18) return false;
|
|
771
|
+
const letters = countLetters(normalized);
|
|
772
|
+
const digits = countDigits(normalized);
|
|
773
|
+
if (letters < 2 || digits < 4) return false;
|
|
774
|
+
if (!/^[A-Z0-9]+$/.test(normalized)) return false;
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
function detectMrzBlocks(text, startTimeMs, deadlineMs, results) {
|
|
778
|
+
mrzBlockRegex.lastIndex = 0;
|
|
779
|
+
let scanned = 0;
|
|
780
|
+
let match;
|
|
781
|
+
while ((match = mrzBlockRegex.exec(text)) !== null) {
|
|
782
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
783
|
+
scanned += 1;
|
|
784
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
785
|
+
const rawBlock = match[1] ?? "";
|
|
786
|
+
const lines = rawBlock.split(/\r?\n/);
|
|
787
|
+
if (!validateMrzLines(lines)) continue;
|
|
788
|
+
const wholeMatch = match[0] ?? "";
|
|
789
|
+
const startsWithCrlf = wholeMatch.startsWith("\r\n");
|
|
790
|
+
const startsWithLf = !startsWithCrlf && wholeMatch.startsWith("\n");
|
|
791
|
+
const prefixShift = startsWithCrlf ? 2 : startsWithLf ? 1 : 0;
|
|
792
|
+
const start = (match.index ?? 0) + prefixShift;
|
|
793
|
+
const end = start + rawBlock.length;
|
|
794
|
+
pushUnique(results, {
|
|
795
|
+
type: "mrz_document",
|
|
796
|
+
value: rawBlock,
|
|
797
|
+
start,
|
|
798
|
+
end
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function detectNationalIdentifiers(text, options = {}) {
|
|
803
|
+
if (!text) return [];
|
|
804
|
+
const startTimeMs = options.startTimeMs ?? Date.now();
|
|
805
|
+
const deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
806
|
+
const prefilter = buildPrefilterState(text, startTimeMs, deadlineMs);
|
|
807
|
+
if (!hasAnyAnchor(prefilter.hits) && !prefilter.hasLongDigitHint && !prefilter.hasSymbolHint) {
|
|
808
|
+
return [];
|
|
809
|
+
}
|
|
810
|
+
const results = [];
|
|
811
|
+
const regions = resolveRegions(text, options.locale ?? null);
|
|
812
|
+
const hasTaxAnchors = prefilter.hits.some((hit) => TAX_ANCHOR_SET.has(hit.word));
|
|
813
|
+
const hasNlBsnAnchors = prefilter.hits.some((hit) => NL_BSN_ANCHOR_SET.has(hit.word));
|
|
814
|
+
const hasAtSvnrAnchors = prefilter.hits.some((hit) => AT_SVNR_ANCHOR_SET.has(hit.word));
|
|
815
|
+
const hasDriverAnchors = prefilter.hits.some((hit) => DRIVER_ANCHOR_SET.has(hit.word));
|
|
816
|
+
const hasAddressAnchors = prefilter.hits.some((hit) => ADDRESS_ANCHOR_SET.has(hit.word));
|
|
817
|
+
const hasMrzAnchors = prefilter.hits.some((hit) => MRZ_ANCHOR_SET.has(hit.word));
|
|
818
|
+
if (prefilter.hasSymbolHint || hasMrzAnchors) {
|
|
819
|
+
detectMrzBlocks(text, startTimeMs, deadlineMs, results);
|
|
820
|
+
}
|
|
821
|
+
if (regions.includes("czsk")) {
|
|
822
|
+
scanRegexMatches(czBirthNumberRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
823
|
+
if (!isValidCzechBirthNumber(value)) return;
|
|
824
|
+
pushUnique(results, { type: "birth_number", value, start, end });
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
if (regions.includes("pl")) {
|
|
828
|
+
scanRegexMatches(peselRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
829
|
+
if (!isValidPesel(value)) return;
|
|
830
|
+
pushUnique(results, { type: "pl_pesel", value, start, end });
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
if (regions.includes("ro")) {
|
|
834
|
+
scanRegexMatches(cnpRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
835
|
+
if (!isValidRomanianCnp(value)) return;
|
|
836
|
+
pushUnique(results, { type: "ro_cnp", value, start, end });
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
if (regions.includes("de") && hasTaxAnchors) {
|
|
840
|
+
scanRegexMatches(deTaxIdRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
841
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
842
|
+
if (!isValidGermanTaxId(value)) return;
|
|
843
|
+
pushUnique(results, { type: "de_tax_id", value, start, end });
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
if (regions.includes("hu")) {
|
|
847
|
+
scanRegexMatches(huTajRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
848
|
+
if (!isValidHungarianTaj(value)) return;
|
|
849
|
+
pushUnique(results, { type: "hu_taj", value, start, end });
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
if (regions.includes("pl") && hasTaxAnchors) {
|
|
853
|
+
scanRegexMatches(plNipRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
854
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
855
|
+
if (!isValidPolishNip(value)) return;
|
|
856
|
+
pushUnique(results, { type: "pl_nip", value, start, end });
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
if (regions.includes("gb")) {
|
|
860
|
+
scanRegexMatches(ukNinoRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
861
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
862
|
+
if (!isLikelyUkNino(value)) return;
|
|
863
|
+
pushUnique(results, { type: "uk_nino", value, start, end });
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (regions.includes("fr")) {
|
|
867
|
+
scanRegexMatches(frInseeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
868
|
+
if (!isValidFrenchInsee(value)) return;
|
|
869
|
+
pushUnique(results, { type: "fr_insee", value, start, end });
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
if (regions.includes("es")) {
|
|
873
|
+
scanRegexMatches(esDniRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
874
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
875
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
876
|
+
});
|
|
877
|
+
scanRegexMatches(esNieRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
878
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
879
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
if (regions.includes("it")) {
|
|
883
|
+
scanRegexMatches(itCodiceFiscaleRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
884
|
+
if (!isValidItalianCodiceFiscale(value)) return;
|
|
885
|
+
pushUnique(results, { type: "it_codice_fiscale", value, start, end });
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
if (regions.includes("nl") && hasNlBsnAnchors) {
|
|
889
|
+
scanRegexMatches(nlBsnRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
890
|
+
if (!hasAnchorNear(prefilter.hits, start, end, NL_BSN_ANCHOR_SET)) return;
|
|
891
|
+
if (!isValidDutchBsn(value)) return;
|
|
892
|
+
pushUnique(results, { type: "nl_bsn", value, start, end });
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
if (regions.includes("at") && hasAtSvnrAnchors) {
|
|
896
|
+
scanRegexMatches(atSvnrRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
897
|
+
if (!hasAnchorNear(prefilter.hits, start, end, AT_SVNR_ANCHOR_SET)) return;
|
|
898
|
+
if (!isValidAustrianSvnr(value)) return;
|
|
899
|
+
pushUnique(results, { type: "at_svnr", value, start, end });
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
if (hasTaxAnchors) {
|
|
903
|
+
scanRegexMatches(euVatRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
904
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
905
|
+
if (!isLikelyEuVat(value)) return;
|
|
906
|
+
pushUnique(results, { type: "eu_vat", value, start, end });
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
if (hasDriverAnchors) {
|
|
910
|
+
scanRegexMatches(ukDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
911
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
912
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
913
|
+
});
|
|
914
|
+
scanRegexMatches(genericDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
915
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
916
|
+
if (!isLikelyGenericDriverId(value)) return;
|
|
917
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
if (hasAddressAnchors) {
|
|
921
|
+
scanRegexMatches(postalCodeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
922
|
+
if (!hasAnchorNear(prefilter.hits, start, end, ADDRESS_ANCHOR_SET, ADDRESS_WINDOW_CHARS)) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
pushUnique(results, { type: "address_postal", value, start, end });
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (options.allowContextBirthNumberFallback && !results.some((item) => item.type === "birth_number") && !/\d{6}\/?\d{3,4}/.test(text) && czBirthNumberContextRegex.test(text)) {
|
|
929
|
+
pushUnique(results, {
|
|
930
|
+
type: "birth_number",
|
|
931
|
+
value: "contextual_birth_number",
|
|
932
|
+
start: 0,
|
|
933
|
+
end: 0
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
return results;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/pii.ts
|
|
940
|
+
var defaultScanDeadlineMs = 100;
|
|
941
|
+
function countDigits2(value) {
|
|
87
942
|
let count = 0;
|
|
88
943
|
for (const ch of value) {
|
|
89
944
|
if (ch >= "0" && ch <= "9") count += 1;
|
|
@@ -118,111 +973,36 @@ function normalizeDetections(text, detections) {
|
|
|
118
973
|
return kept;
|
|
119
974
|
}
|
|
120
975
|
var PHONE_CONTEXT_KEYWORDS = [
|
|
121
|
-
// --- 🌍 GLOBAL / SYMBOLS / TECH ---
|
|
122
976
|
"tel",
|
|
123
977
|
"phone",
|
|
124
978
|
"mobile",
|
|
125
|
-
"mobil",
|
|
126
979
|
"cell",
|
|
127
980
|
"call",
|
|
128
|
-
"fax",
|
|
129
|
-
"sms",
|
|
130
|
-
"whatsapp",
|
|
131
|
-
"signal",
|
|
132
|
-
"telegram",
|
|
133
|
-
"viber",
|
|
134
|
-
"skype",
|
|
135
|
-
"dial",
|
|
136
981
|
"contact",
|
|
137
982
|
"number",
|
|
138
983
|
"hotline",
|
|
139
|
-
"helpdesk",
|
|
140
984
|
"support",
|
|
141
985
|
"infoline",
|
|
142
986
|
"customer service",
|
|
143
987
|
"client service",
|
|
144
|
-
"reach me",
|
|
145
|
-
"text me",
|
|
146
|
-
// --- 🇨🇿 CS (Czech) / 🇸🇰 SK (Slovak) ---
|
|
147
|
-
"volat",
|
|
148
|
-
"vola\u0165",
|
|
149
988
|
"telefon",
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"cislo",
|
|
153
|
-
"kontakt",
|
|
154
|
-
"mobiln\xED",
|
|
155
|
-
"mobiln\xFD",
|
|
156
|
-
"ozvi se",
|
|
157
|
-
"ozvi sa",
|
|
158
|
-
"napi\u0161",
|
|
159
|
-
"nap\xED\u0161",
|
|
160
|
-
"zavolej",
|
|
161
|
-
"zavolaj",
|
|
162
|
-
"pevn\xE1 linka",
|
|
163
|
-
"klapka",
|
|
164
|
-
"spojovatelka",
|
|
165
|
-
"z\xE1kaznick\xE1 linka",
|
|
166
|
-
"podpora",
|
|
167
|
-
// --- 🇩🇪 DE (German) ---
|
|
989
|
+
"telefonu",
|
|
990
|
+
"telefonszam",
|
|
168
991
|
"telefonnummer",
|
|
169
|
-
"handy",
|
|
170
|
-
"anruf",
|
|
171
|
-
"klingeln",
|
|
172
992
|
"rufnummer",
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"erreichen",
|
|
176
|
-
"kundenservice",
|
|
177
|
-
"support",
|
|
178
|
-
"mobilfunk",
|
|
179
|
-
"festnetz",
|
|
180
|
-
"durchwahl",
|
|
181
|
-
// --- 🇵🇱 PL (Polish) ---
|
|
182
|
-
"telefon",
|
|
183
|
-
"telefonu",
|
|
184
|
-
"kom\xF3rka",
|
|
185
|
-
"komorkowy",
|
|
186
|
-
"zadzwo\u0144",
|
|
187
|
-
"numer",
|
|
188
|
-
"kontakt",
|
|
189
|
-
"infolinia",
|
|
993
|
+
"zadzwon",
|
|
994
|
+
"hivas",
|
|
190
995
|
"wsparcie",
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
// --- 🇭🇺 HU (Hungarian) ---
|
|
195
|
-
"h\xEDv\xE1s",
|
|
196
|
-
"telefonsz\xE1m",
|
|
197
|
-
"sz\xE1m",
|
|
198
|
-
"mobiltelefon",
|
|
199
|
-
"mobil",
|
|
200
|
-
"vezet\xE9kes",
|
|
201
|
-
"el\xE9rhet\u0151s\xE9g",
|
|
202
|
-
"\xFCgyf\xE9lszolg\xE1lat",
|
|
203
|
-
"fax",
|
|
204
|
-
"t\xE1rcs\xE1zza",
|
|
205
|
-
"cs\xF6r\xF6gj\xF6n",
|
|
206
|
-
// --- 🇺🇦 UA (Ukrainian) ---
|
|
207
|
-
"\u0442\u0435\u043B\u0435\u0444\u043E\u043D",
|
|
208
|
-
"\u043C\u043E\u0431\u0456\u043B\u044C\u043D\u0438\u0439",
|
|
209
|
-
"\u0434\u0437\u0432\u043E\u043D\u0438\u0442\u0438",
|
|
210
|
-
"\u043D\u043E\u043C\u0435\u0440",
|
|
211
|
-
"\u043A\u043E\u043D\u0442\u0430\u043A\u0442",
|
|
212
|
-
"\u0437\u0432'\u044F\u0437\u043E\u043A",
|
|
213
|
-
"\u0433\u0430\u0440\u044F\u0447\u0430 \u043B\u0456\u043D\u0456\u044F",
|
|
214
|
-
"\u043F\u0456\u0434\u0442\u0440\u0438\u043C\u043A\u0430",
|
|
215
|
-
"\u0441\u043C\u0441",
|
|
216
|
-
"\u0444\u0430\u043A\u0441",
|
|
217
|
-
"\u043F\u043E\u0437\u0432\u043E\u043D\u0438\u0442\u044C",
|
|
218
|
-
"\u043D\u0430\u0431\u0440\u0430\u0442\u0438"
|
|
996
|
+
"podpora",
|
|
997
|
+
"kontakt",
|
|
998
|
+
"dial"
|
|
219
999
|
];
|
|
220
1000
|
function escapeRegex(value) {
|
|
221
1001
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
222
1002
|
}
|
|
223
1003
|
var PHONE_CONTEXT_RE = new RegExp(
|
|
224
1004
|
`(?:^|[^\\p{L}])(?:${PHONE_CONTEXT_KEYWORDS.map(escapeRegex).join("|")})(?:$|[^\\p{L}])`,
|
|
225
|
-
"
|
|
1005
|
+
"iu"
|
|
226
1006
|
);
|
|
227
1007
|
function hasPhoneContext(text, matchStartIndex, windowSize = 50) {
|
|
228
1008
|
const start = Math.max(0, matchStartIndex - windowSize);
|
|
@@ -233,62 +1013,79 @@ var PIIManager = class {
|
|
|
233
1013
|
/**
|
|
234
1014
|
* Reversible local-first masking using <TYPE_INDEX> placeholders.
|
|
235
1015
|
*
|
|
236
|
-
* Zero-
|
|
1016
|
+
* Zero-dependency fallback with strict checksum validation for CEE national IDs.
|
|
237
1017
|
*/
|
|
238
1018
|
anonymize(text) {
|
|
239
1019
|
if (!text) return { maskedText: text, mapping: {} };
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const ibanRe = /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/gi;
|
|
247
|
-
for (const m of text.matchAll(ibanRe)) {
|
|
248
|
-
if (m.index == null) continue;
|
|
249
|
-
detections.push({ start: m.index, end: m.index + m[0].length, type: "IBAN", text: m[0] });
|
|
250
|
-
}
|
|
251
|
-
const ccRe = /(?:\b\d[\d -]{10,22}\d\b)/g;
|
|
252
|
-
for (const m of text.matchAll(ccRe)) {
|
|
253
|
-
if (m.index == null) continue;
|
|
254
|
-
const digits = countDigits(m[0]);
|
|
255
|
-
if (digits < 12 || digits > 19) continue;
|
|
256
|
-
if (!luhnCheck(m[0])) continue;
|
|
257
|
-
detections.push({ start: m.index, end: m.index + m[0].length, type: "CREDIT_CARD", text: m[0] });
|
|
258
|
-
}
|
|
259
|
-
const phoneRe = /(?<!\d)(?:\+?\d[\d\s().-]{7,}\d)(?!\d)/g;
|
|
260
|
-
for (const m of text.matchAll(phoneRe)) {
|
|
261
|
-
if (m.index == null) continue;
|
|
262
|
-
const candidate = m[0];
|
|
263
|
-
const digits = countDigits(candidate);
|
|
264
|
-
if (digits < 9 || digits > 15) continue;
|
|
265
|
-
const isStrongInternational = candidate.startsWith("+") || candidate.startsWith("00");
|
|
266
|
-
if (!isStrongInternational) {
|
|
267
|
-
const hasContext = hasPhoneContext(text, m.index);
|
|
268
|
-
if (!hasContext) continue;
|
|
1020
|
+
try {
|
|
1021
|
+
const detections = [];
|
|
1022
|
+
const emailRe = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
1023
|
+
for (const m of text.matchAll(emailRe)) {
|
|
1024
|
+
if (m.index == null) continue;
|
|
1025
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "EMAIL", text: m[0] });
|
|
269
1026
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1027
|
+
const ibanRe = /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/gi;
|
|
1028
|
+
for (const m of text.matchAll(ibanRe)) {
|
|
1029
|
+
if (m.index == null) continue;
|
|
1030
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "IBAN", text: m[0] });
|
|
1031
|
+
}
|
|
1032
|
+
const ccRe = /(?:\b\d[\d -]{10,22}\d\b)/g;
|
|
1033
|
+
for (const m of text.matchAll(ccRe)) {
|
|
1034
|
+
if (m.index == null) continue;
|
|
1035
|
+
const digits = countDigits2(m[0]);
|
|
1036
|
+
if (digits < 12 || digits > 19) continue;
|
|
1037
|
+
if (!luhnCheck(m[0])) continue;
|
|
1038
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "CREDIT_CARD", text: m[0] });
|
|
1039
|
+
}
|
|
1040
|
+
const phoneRe = /(?<!\d)(?:\+?\d[\d\s().-]{7,}\d)(?!\d)/g;
|
|
1041
|
+
for (const m of text.matchAll(phoneRe)) {
|
|
1042
|
+
if (m.index == null) continue;
|
|
1043
|
+
const candidate = m[0];
|
|
1044
|
+
const digits = countDigits2(candidate);
|
|
1045
|
+
if (digits < 9 || digits > 15) continue;
|
|
1046
|
+
const isStrongInternational = candidate.startsWith("+") || candidate.startsWith("00");
|
|
1047
|
+
if (!isStrongInternational) {
|
|
1048
|
+
const hasContext = hasPhoneContext(text, m.index);
|
|
1049
|
+
if (!hasContext) continue;
|
|
1050
|
+
}
|
|
1051
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "PHONE", text: m[0] });
|
|
1052
|
+
}
|
|
1053
|
+
const personRe = /(?<!\p{L})\p{Lu}\p{Ll}{2,}\s+\p{Lu}\p{Ll}{2,}(?!\p{L})/gu;
|
|
1054
|
+
for (const m of text.matchAll(personRe)) {
|
|
1055
|
+
if (m.index == null) continue;
|
|
1056
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "PERSON", text: m[0] });
|
|
1057
|
+
}
|
|
1058
|
+
const nationalIdMatches = detectNationalIdentifiers(text, {
|
|
1059
|
+
deadlineMs: defaultScanDeadlineMs,
|
|
1060
|
+
allowContextBirthNumberFallback: false
|
|
1061
|
+
});
|
|
1062
|
+
for (const match of nationalIdMatches) {
|
|
1063
|
+
if (match.start < 0 || match.end <= match.start) continue;
|
|
1064
|
+
detections.push({
|
|
1065
|
+
start: match.start,
|
|
1066
|
+
end: match.end,
|
|
1067
|
+
type: "NATIONAL_ID",
|
|
1068
|
+
text: text.slice(match.start, match.end)
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
const kept = normalizeDetections(text, detections);
|
|
1072
|
+
if (!kept.length) return { maskedText: text, mapping: {} };
|
|
1073
|
+
const counters = {};
|
|
1074
|
+
const mapping = {};
|
|
1075
|
+
const replacements = kept.map((d) => {
|
|
1076
|
+
counters[d.type] = (counters[d.type] ?? 0) + 1;
|
|
1077
|
+
const placeholder = `<${d.type}_${counters[d.type]}>`;
|
|
1078
|
+
mapping[placeholder] = d.text;
|
|
1079
|
+
return { ...d, placeholder };
|
|
1080
|
+
});
|
|
1081
|
+
let masked = text;
|
|
1082
|
+
for (const r of replacements.sort((a, b) => b.start - a.start)) {
|
|
1083
|
+
masked = masked.slice(0, r.start) + r.placeholder + masked.slice(r.end);
|
|
1084
|
+
}
|
|
1085
|
+
return { maskedText: masked, mapping };
|
|
1086
|
+
} catch {
|
|
1087
|
+
return { maskedText: text, mapping: {} };
|
|
290
1088
|
}
|
|
291
|
-
return { maskedText: masked, mapping };
|
|
292
1089
|
}
|
|
293
1090
|
deanonymize(text, mapping) {
|
|
294
1091
|
if (!text || !mapping || !Object.keys(mapping).length) return text;
|
|
@@ -307,6 +1104,7 @@ var PIIManager = class {
|
|
|
307
1104
|
|
|
308
1105
|
// src/local-security-enforcer.ts
|
|
309
1106
|
var DEFAULT_STRICT_CONFIG = {
|
|
1107
|
+
shadow_mode: false,
|
|
310
1108
|
block_pii_leakage: true,
|
|
311
1109
|
block_db_access: true,
|
|
312
1110
|
block_code_execution: true,
|
|
@@ -353,6 +1151,12 @@ var LocalSecurityEnforcer = class {
|
|
|
353
1151
|
}
|
|
354
1152
|
enforce(params) {
|
|
355
1153
|
const { input, stream, config } = params;
|
|
1154
|
+
if (config.shadow_mode) {
|
|
1155
|
+
return {
|
|
1156
|
+
sanitizedInput: input,
|
|
1157
|
+
events: []
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
356
1160
|
const violationType = detectCapabilityViolation(input, config);
|
|
357
1161
|
if (violationType) {
|
|
358
1162
|
throw new SecurityPolicyViolationError(
|
|
@@ -418,12 +1222,23 @@ function readBooleanField(body, primaryKey, aliasKey) {
|
|
|
418
1222
|
}
|
|
419
1223
|
throw new Error(`Missing config field: ${primaryKey}`);
|
|
420
1224
|
}
|
|
1225
|
+
function readOptionalBooleanField(body, key, fallback) {
|
|
1226
|
+
if (!(key in body)) {
|
|
1227
|
+
return fallback;
|
|
1228
|
+
}
|
|
1229
|
+
const value = body[key];
|
|
1230
|
+
if (typeof value === "boolean") {
|
|
1231
|
+
return value;
|
|
1232
|
+
}
|
|
1233
|
+
throw new Error(`Invalid config field: ${key}`);
|
|
1234
|
+
}
|
|
421
1235
|
function normalizeCapabilityConfig(payload) {
|
|
422
1236
|
if (!payload || typeof payload !== "object") {
|
|
423
1237
|
throw new Error("Invalid config payload");
|
|
424
1238
|
}
|
|
425
1239
|
const body = payload;
|
|
426
1240
|
return {
|
|
1241
|
+
shadow_mode: readOptionalBooleanField(body, "shadow_mode", false),
|
|
427
1242
|
block_pii_leakage: readBooleanField(body, "block_pii_leakage", "block_pii"),
|
|
428
1243
|
block_db_access: readBooleanField(body, "block_db_access", "block_db"),
|
|
429
1244
|
block_code_execution: readBooleanField(
|
|
@@ -981,9 +1796,9 @@ function getInjectionScanner() {
|
|
|
981
1796
|
|
|
982
1797
|
// src/agentid.ts
|
|
983
1798
|
var AGENTID_SDK_VERSION_HEADER2 = "js-1.0.7";
|
|
984
|
-
var DEFAULT_GUARD_TIMEOUT_MS =
|
|
985
|
-
var MIN_GUARD_TIMEOUT_MS =
|
|
986
|
-
var MAX_GUARD_TIMEOUT_MS =
|
|
1799
|
+
var DEFAULT_GUARD_TIMEOUT_MS = 800;
|
|
1800
|
+
var MIN_GUARD_TIMEOUT_MS = 500;
|
|
1801
|
+
var MAX_GUARD_TIMEOUT_MS = 1e4;
|
|
987
1802
|
function normalizeBaseUrl3(baseUrl) {
|
|
988
1803
|
return baseUrl.replace(/\/+$/, "");
|
|
989
1804
|
}
|
|
@@ -992,6 +1807,12 @@ function isAbortSignalLike(value) {
|
|
|
992
1807
|
const candidate = value;
|
|
993
1808
|
return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function";
|
|
994
1809
|
}
|
|
1810
|
+
function isAsyncIterable(value) {
|
|
1811
|
+
if (!value || typeof value !== "object" && typeof value !== "function") {
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
return typeof value[Symbol.asyncIterator] === "function";
|
|
1815
|
+
}
|
|
995
1816
|
function normalizeOpenAICreateArgs(rawArgs) {
|
|
996
1817
|
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
997
1818
|
return rawArgs;
|
|
@@ -1052,6 +1873,70 @@ async function safeReadJson2(response) {
|
|
|
1052
1873
|
return null;
|
|
1053
1874
|
}
|
|
1054
1875
|
}
|
|
1876
|
+
function createCompletionChunkCollector() {
|
|
1877
|
+
if (typeof TransformStream === "function") {
|
|
1878
|
+
const stream = new TransformStream({
|
|
1879
|
+
transform(chunk, controller) {
|
|
1880
|
+
controller.enqueue(chunk);
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
const writer = stream.writable.getWriter();
|
|
1884
|
+
const result2 = (async () => {
|
|
1885
|
+
const reader = stream.readable.getReader();
|
|
1886
|
+
let combined = "";
|
|
1887
|
+
try {
|
|
1888
|
+
while (true) {
|
|
1889
|
+
const { value, done: done2 } = await reader.read();
|
|
1890
|
+
if (done2) break;
|
|
1891
|
+
if (typeof value === "string") {
|
|
1892
|
+
combined += value;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return combined;
|
|
1896
|
+
} finally {
|
|
1897
|
+
reader.releaseLock();
|
|
1898
|
+
}
|
|
1899
|
+
})();
|
|
1900
|
+
return {
|
|
1901
|
+
push: async (chunk) => {
|
|
1902
|
+
if (!chunk) return;
|
|
1903
|
+
await writer.write(chunk);
|
|
1904
|
+
},
|
|
1905
|
+
close: async () => {
|
|
1906
|
+
await writer.close();
|
|
1907
|
+
},
|
|
1908
|
+
abort: async (reason) => {
|
|
1909
|
+
await writer.abort(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1910
|
+
},
|
|
1911
|
+
result: result2
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
let done = false;
|
|
1915
|
+
const chunks = [];
|
|
1916
|
+
let resolveResult = null;
|
|
1917
|
+
let rejectResult = null;
|
|
1918
|
+
const result = new Promise((resolve, reject) => {
|
|
1919
|
+
resolveResult = resolve;
|
|
1920
|
+
rejectResult = reject;
|
|
1921
|
+
});
|
|
1922
|
+
return {
|
|
1923
|
+
push: async (chunk) => {
|
|
1924
|
+
if (done || !chunk) return;
|
|
1925
|
+
chunks.push(chunk);
|
|
1926
|
+
},
|
|
1927
|
+
close: async () => {
|
|
1928
|
+
if (done) return;
|
|
1929
|
+
done = true;
|
|
1930
|
+
resolveResult?.(chunks.join(""));
|
|
1931
|
+
},
|
|
1932
|
+
abort: async (reason) => {
|
|
1933
|
+
if (done) return;
|
|
1934
|
+
done = true;
|
|
1935
|
+
rejectResult?.(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1936
|
+
},
|
|
1937
|
+
result
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1055
1940
|
var AgentID = class {
|
|
1056
1941
|
constructor(config) {
|
|
1057
1942
|
this.injectionScanner = getInjectionScanner();
|
|
@@ -1207,6 +2092,26 @@ var AgentID = class {
|
|
|
1207
2092
|
}
|
|
1208
2093
|
}, { apiKey: params.apiKey });
|
|
1209
2094
|
}
|
|
2095
|
+
logGuardFallback(params) {
|
|
2096
|
+
this.log(
|
|
2097
|
+
{
|
|
2098
|
+
system_id: params.guardParams.system_id,
|
|
2099
|
+
user_id: params.guardParams.user_id,
|
|
2100
|
+
input: params.guardParams.input,
|
|
2101
|
+
output: "",
|
|
2102
|
+
model: params.guardParams.model ?? "unknown",
|
|
2103
|
+
event_type: "security_alert",
|
|
2104
|
+
severity: "warning",
|
|
2105
|
+
metadata: {
|
|
2106
|
+
source: "guard",
|
|
2107
|
+
status: params.status,
|
|
2108
|
+
guard_reason: params.reason,
|
|
2109
|
+
shadow_mode: false
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
{ apiKey: params.apiKey }
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
1210
2115
|
/**
|
|
1211
2116
|
* GUARD: Checks limits, PII, and security before execution.
|
|
1212
2117
|
* strictMode=false (default): FAIL-OPEN on connectivity/timeouts.
|
|
@@ -1234,10 +2139,16 @@ var AgentID = class {
|
|
|
1234
2139
|
const responseBody = await safeReadJson2(res);
|
|
1235
2140
|
if (responseBody && typeof responseBody.allowed === "boolean") {
|
|
1236
2141
|
const verdict = responseBody;
|
|
1237
|
-
if (
|
|
2142
|
+
if (verdict.allowed === false && (isInfrastructureGuardReason(verdict.reason) || res.status >= 500)) {
|
|
1238
2143
|
console.warn(
|
|
1239
2144
|
`[AgentID] Guard API infrastructure fallback in fail-open mode (${verdict.reason ?? `http_${res.status}`}).`
|
|
1240
2145
|
);
|
|
2146
|
+
this.logGuardFallback({
|
|
2147
|
+
reason: verdict.reason ?? `http_${res.status}`,
|
|
2148
|
+
status: "upstream_error",
|
|
2149
|
+
guardParams: params,
|
|
2150
|
+
apiKey: effectiveApiKey
|
|
2151
|
+
});
|
|
1241
2152
|
return { allowed: true, reason: "system_failure_fail_open" };
|
|
1242
2153
|
}
|
|
1243
2154
|
return verdict;
|
|
@@ -1250,55 +2161,159 @@ var AgentID = class {
|
|
|
1250
2161
|
const isAbortError2 = error && typeof error === "object" && error.name === "AbortError";
|
|
1251
2162
|
if (isAbortError2) {
|
|
1252
2163
|
const timeoutMessage = "AgentID API Warning: Connection timeout exceeded.";
|
|
1253
|
-
if (this.strictMode) {
|
|
1254
|
-
throw new Error(timeoutMessage);
|
|
1255
|
-
}
|
|
1256
2164
|
console.warn(timeoutMessage);
|
|
2165
|
+
this.logGuardFallback({
|
|
2166
|
+
reason: "timeout_fallback",
|
|
2167
|
+
status: "latency_timeout",
|
|
2168
|
+
guardParams: params,
|
|
2169
|
+
apiKey: effectiveApiKey
|
|
2170
|
+
});
|
|
1257
2171
|
return { allowed: true, reason: "timeout_fallback" };
|
|
1258
2172
|
}
|
|
1259
|
-
if (this.strictMode) {
|
|
1260
|
-
if (error instanceof Error) {
|
|
1261
|
-
throw error;
|
|
1262
|
-
}
|
|
1263
|
-
throw new Error("AgentID API Error: Guard request failed.");
|
|
1264
|
-
}
|
|
1265
2173
|
console.warn("[AgentID] Guard check failed (Fail-Open active):", error);
|
|
2174
|
+
this.logGuardFallback({
|
|
2175
|
+
reason: "guard_unreachable",
|
|
2176
|
+
status: "guard_unreachable",
|
|
2177
|
+
guardParams: params,
|
|
2178
|
+
apiKey: effectiveApiKey
|
|
2179
|
+
});
|
|
1266
2180
|
return { allowed: true, reason: "guard_unreachable" };
|
|
1267
2181
|
} finally {
|
|
1268
2182
|
clearTimeout(timeoutId);
|
|
1269
2183
|
}
|
|
1270
2184
|
}
|
|
2185
|
+
extractStreamChunkText(chunk) {
|
|
2186
|
+
if (typeof chunk === "string") {
|
|
2187
|
+
return chunk;
|
|
2188
|
+
}
|
|
2189
|
+
if (!chunk || typeof chunk !== "object") {
|
|
2190
|
+
return "";
|
|
2191
|
+
}
|
|
2192
|
+
const choices = chunk.choices;
|
|
2193
|
+
if (!Array.isArray(choices)) {
|
|
2194
|
+
return "";
|
|
2195
|
+
}
|
|
2196
|
+
let text = "";
|
|
2197
|
+
for (const choice of choices) {
|
|
2198
|
+
if (!choice || typeof choice !== "object") continue;
|
|
2199
|
+
const delta = choice.delta;
|
|
2200
|
+
const message = choice.message;
|
|
2201
|
+
const contentCandidate = delta?.content ?? message?.content;
|
|
2202
|
+
if (typeof contentCandidate === "string") {
|
|
2203
|
+
text += contentCandidate;
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
if (Array.isArray(contentCandidate)) {
|
|
2207
|
+
for (const part of contentCandidate) {
|
|
2208
|
+
if (!part || typeof part !== "object") continue;
|
|
2209
|
+
const partText = part.text;
|
|
2210
|
+
if (typeof partText === "string") {
|
|
2211
|
+
text += partText;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return text;
|
|
2217
|
+
}
|
|
2218
|
+
wrapCompletion(completion) {
|
|
2219
|
+
if (typeof completion === "string") {
|
|
2220
|
+
const masked = this.pii.anonymize(completion);
|
|
2221
|
+
return {
|
|
2222
|
+
mode: "static",
|
|
2223
|
+
rawOutput: completion,
|
|
2224
|
+
transformedOutput: masked.maskedText,
|
|
2225
|
+
outputMasked: masked.maskedText !== completion
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
if (!isAsyncIterable(completion)) {
|
|
2229
|
+
const asText = String(completion ?? "");
|
|
2230
|
+
const masked = this.pii.anonymize(asText);
|
|
2231
|
+
return {
|
|
2232
|
+
mode: "static",
|
|
2233
|
+
rawOutput: asText,
|
|
2234
|
+
transformedOutput: masked.maskedText,
|
|
2235
|
+
outputMasked: masked.maskedText !== asText
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
const source = completion;
|
|
2239
|
+
const collector = createCompletionChunkCollector();
|
|
2240
|
+
const self = this;
|
|
2241
|
+
let resolveDone = null;
|
|
2242
|
+
let rejectDone = null;
|
|
2243
|
+
const done = new Promise((resolve, reject) => {
|
|
2244
|
+
resolveDone = resolve;
|
|
2245
|
+
rejectDone = reject;
|
|
2246
|
+
});
|
|
2247
|
+
const wrapped = {
|
|
2248
|
+
[Symbol.asyncIterator]: async function* () {
|
|
2249
|
+
try {
|
|
2250
|
+
for await (const chunk of source) {
|
|
2251
|
+
const chunkText = self.extractStreamChunkText(chunk);
|
|
2252
|
+
if (chunkText) {
|
|
2253
|
+
await collector.push(chunkText);
|
|
2254
|
+
}
|
|
2255
|
+
yield chunk;
|
|
2256
|
+
}
|
|
2257
|
+
await collector.close();
|
|
2258
|
+
const rawOutput = await collector.result;
|
|
2259
|
+
const masked = self.pii.anonymize(rawOutput);
|
|
2260
|
+
resolveDone?.({
|
|
2261
|
+
mode: "static",
|
|
2262
|
+
rawOutput,
|
|
2263
|
+
transformedOutput: masked.maskedText,
|
|
2264
|
+
outputMasked: masked.maskedText !== rawOutput
|
|
2265
|
+
});
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
await collector.abort(error);
|
|
2268
|
+
rejectDone?.(error instanceof Error ? error : new Error(String(error)));
|
|
2269
|
+
throw error;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
return {
|
|
2274
|
+
mode: "stream",
|
|
2275
|
+
completion: wrapped,
|
|
2276
|
+
done
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
1271
2279
|
/**
|
|
1272
2280
|
* LOG: Sends telemetry after execution.
|
|
1273
2281
|
* Non-blocking / Fire-and-forget.
|
|
1274
2282
|
*/
|
|
1275
2283
|
log(params, options) {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
2284
|
+
queueMicrotask(() => {
|
|
2285
|
+
void (async () => {
|
|
2286
|
+
try {
|
|
2287
|
+
const effectiveApiKey = this.resolveApiKey(options?.apiKey);
|
|
2288
|
+
const eventId = params.event_id ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
2289
|
+
const timestamp = params.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2290
|
+
void this.getCapabilityConfig(false, { apiKey: effectiveApiKey }).catch(() => void 0);
|
|
2291
|
+
const metadata = {
|
|
2292
|
+
...params.metadata ?? {}
|
|
2293
|
+
};
|
|
2294
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, "agentid_base_url")) {
|
|
2295
|
+
metadata.agentid_base_url = this.baseUrl;
|
|
2296
|
+
}
|
|
2297
|
+
await fetch(`${this.baseUrl}/ingest`, {
|
|
2298
|
+
method: "POST",
|
|
2299
|
+
keepalive: true,
|
|
2300
|
+
headers: {
|
|
2301
|
+
"Content-Type": "application/json",
|
|
2302
|
+
"x-agentid-api-key": effectiveApiKey,
|
|
2303
|
+
"X-AgentID-SDK-Version": AGENTID_SDK_VERSION_HEADER2
|
|
2304
|
+
},
|
|
2305
|
+
body: JSON.stringify({
|
|
2306
|
+
...params,
|
|
2307
|
+
event_id: eventId,
|
|
2308
|
+
timestamp,
|
|
2309
|
+
metadata,
|
|
2310
|
+
client_capabilities: params.client_capabilities ?? this.buildClientCapabilities()
|
|
2311
|
+
})
|
|
2312
|
+
});
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
console.error("[AgentID] Log failed:", error);
|
|
2315
|
+
}
|
|
2316
|
+
})();
|
|
1302
2317
|
});
|
|
1303
2318
|
}
|
|
1304
2319
|
/**
|
|
@@ -1399,6 +2414,7 @@ var AgentID = class {
|
|
|
1399
2414
|
const verdict = await this.guard({
|
|
1400
2415
|
input: maskedText,
|
|
1401
2416
|
system_id: systemId,
|
|
2417
|
+
model: adapter.getModelName(maskedReq),
|
|
1402
2418
|
user_id: options.user_id,
|
|
1403
2419
|
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
1404
2420
|
}, requestOptions);
|
|
@@ -1407,27 +2423,81 @@ var AgentID = class {
|
|
|
1407
2423
|
`AgentID: Security Blocked (${verdict.reason ?? "guard_denied"})`
|
|
1408
2424
|
);
|
|
1409
2425
|
}
|
|
2426
|
+
const isShadowMode = verdict.shadow_mode === true;
|
|
2427
|
+
const transformedInput = isShadowMode ? maskedText : typeof verdict.transformed_input === "string" && verdict.transformed_input.length > 0 ? verdict.transformed_input : maskedText;
|
|
2428
|
+
if (transformedInput !== maskedText) {
|
|
2429
|
+
maskedText = transformedInput;
|
|
2430
|
+
maskedReq = this.withMaskedOpenAIRequest(
|
|
2431
|
+
req,
|
|
2432
|
+
transformedInput
|
|
2433
|
+
);
|
|
2434
|
+
const nextCreateArgs = [...normalizedCreateArgs];
|
|
2435
|
+
nextCreateArgs[0] = maskedReq;
|
|
2436
|
+
createArgs = nextCreateArgs;
|
|
2437
|
+
}
|
|
1410
2438
|
if (stream) {
|
|
1411
|
-
|
|
1412
|
-
|
|
2439
|
+
const streamResponse = await originalCreate.apply(compTarget, createArgs);
|
|
2440
|
+
const wrappedCompletion = this.wrapCompletion(
|
|
2441
|
+
isAsyncIterable(streamResponse) ? streamResponse : (async function* () {
|
|
2442
|
+
if (typeof streamResponse !== "undefined") {
|
|
2443
|
+
yield streamResponse;
|
|
2444
|
+
}
|
|
2445
|
+
})()
|
|
1413
2446
|
);
|
|
1414
|
-
|
|
2447
|
+
if (maskedText && wrappedCompletion.mode === "stream") {
|
|
2448
|
+
void wrappedCompletion.done.then((result) => {
|
|
2449
|
+
const outputForLog = isShadowMode ? result.rawOutput : result.transformedOutput;
|
|
2450
|
+
this.log({
|
|
2451
|
+
system_id: systemId,
|
|
2452
|
+
user_id: options.user_id,
|
|
2453
|
+
input: maskedText,
|
|
2454
|
+
output: outputForLog,
|
|
2455
|
+
model: adapter.getModelName(maskedReq),
|
|
2456
|
+
usage: void 0,
|
|
2457
|
+
latency: void 0,
|
|
2458
|
+
metadata: {
|
|
2459
|
+
transformed_input: maskedText,
|
|
2460
|
+
transformed_output: result.transformedOutput,
|
|
2461
|
+
output_masked: result.outputMasked,
|
|
2462
|
+
shadow_mode: isShadowMode,
|
|
2463
|
+
simulated_decision: verdict.simulated_decision ?? null,
|
|
2464
|
+
simulated_output_decision: isShadowMode && result.outputMasked ? "masked" : "allowed",
|
|
2465
|
+
response_streamed: true
|
|
2466
|
+
},
|
|
2467
|
+
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
2468
|
+
}, requestOptions);
|
|
2469
|
+
}).catch((error) => {
|
|
2470
|
+
console.error("[AgentID] Stream completion wrapping failed:", error);
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
return wrappedCompletion.mode === "stream" ? wrappedCompletion.completion : streamResponse;
|
|
1415
2474
|
}
|
|
1416
2475
|
const start = Date.now();
|
|
1417
2476
|
const res = await originalCreate.apply(compTarget, createArgs);
|
|
1418
2477
|
const latency = Date.now() - start;
|
|
1419
2478
|
if (maskedText) {
|
|
1420
2479
|
const output = adapter.extractOutput(res);
|
|
2480
|
+
const wrappedCompletion = this.wrapCompletion(output);
|
|
1421
2481
|
const model = adapter.getModelName(maskedReq, res);
|
|
1422
2482
|
const usage = adapter.getTokenUsage(res);
|
|
2483
|
+
const outputForLog = isShadowMode ? wrappedCompletion.rawOutput : wrappedCompletion.transformedOutput;
|
|
1423
2484
|
this.log({
|
|
1424
2485
|
system_id: systemId,
|
|
1425
2486
|
user_id: options.user_id,
|
|
1426
2487
|
input: maskedText,
|
|
1427
|
-
output,
|
|
2488
|
+
output: outputForLog,
|
|
1428
2489
|
model,
|
|
1429
2490
|
usage,
|
|
1430
2491
|
latency,
|
|
2492
|
+
metadata: {
|
|
2493
|
+
transformed_input: maskedText,
|
|
2494
|
+
transformed_output: wrappedCompletion.transformedOutput,
|
|
2495
|
+
output_masked: wrappedCompletion.outputMasked,
|
|
2496
|
+
shadow_mode: isShadowMode,
|
|
2497
|
+
simulated_decision: verdict.simulated_decision ?? null,
|
|
2498
|
+
simulated_output_decision: isShadowMode && wrappedCompletion.outputMasked ? "masked" : "allowed",
|
|
2499
|
+
response_streamed: false
|
|
2500
|
+
},
|
|
1431
2501
|
client_capabilities: this.buildClientCapabilities("openai", false)
|
|
1432
2502
|
}, requestOptions);
|
|
1433
2503
|
}
|
|
@@ -1638,8 +2708,17 @@ var AgentIDCallbackHandler = class {
|
|
|
1638
2708
|
if (!verdict.allowed) {
|
|
1639
2709
|
throw new Error(`AgentID: Security Blocked (${verdict.reason ?? "guard_denied"})`);
|
|
1640
2710
|
}
|
|
2711
|
+
const transformedInput = typeof verdict.transformed_input === "string" && verdict.transformed_input.length > 0 ? verdict.transformed_input : sanitizedInput;
|
|
2712
|
+
if (transformedInput !== sanitizedInput) {
|
|
2713
|
+
const mutated = setPromptInPrompts(prompts, transformedInput);
|
|
2714
|
+
if (!mutated) {
|
|
2715
|
+
throw new Error(
|
|
2716
|
+
"AgentID: Guard transformed input could not be applied to LangChain prompt payload."
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
1641
2720
|
this.runs.set(id, {
|
|
1642
|
-
input:
|
|
2721
|
+
input: transformedInput,
|
|
1643
2722
|
startedAtMs: Date.now(),
|
|
1644
2723
|
model: extractModel(serialized, extraParams)
|
|
1645
2724
|
});
|
|
@@ -1668,8 +2747,17 @@ var AgentIDCallbackHandler = class {
|
|
|
1668
2747
|
if (!verdict.allowed) {
|
|
1669
2748
|
throw new Error(`AgentID: Security Blocked (${verdict.reason ?? "guard_denied"})`);
|
|
1670
2749
|
}
|
|
2750
|
+
const transformedInput = typeof verdict.transformed_input === "string" && verdict.transformed_input.length > 0 ? verdict.transformed_input : sanitizedInput;
|
|
2751
|
+
if (transformedInput !== sanitizedInput) {
|
|
2752
|
+
const mutated = setPromptInMessages(messages, transformedInput);
|
|
2753
|
+
if (!mutated) {
|
|
2754
|
+
throw new Error(
|
|
2755
|
+
"AgentID: Guard transformed input could not be applied to LangChain message payload."
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
1671
2759
|
this.runs.set(id, {
|
|
1672
|
-
input:
|
|
2760
|
+
input: transformedInput,
|
|
1673
2761
|
startedAtMs: Date.now(),
|
|
1674
2762
|
model: extractModel(serialized, extraParams)
|
|
1675
2763
|
});
|