agentid-sdk 0.1.34 → 0.1.35
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/dist/{agentid-M5I7-YqI.d.mts → agentid-IonlG0NB.d.mts} +23 -2
- package/dist/{agentid-M5I7-YqI.d.ts → agentid-IonlG0NB.d.ts} +23 -2
- package/dist/{chunk-FCOSLPMF.mjs → chunk-XFSIAH3F.mjs} +288 -34
- package/dist/index.d.mts +3 -16
- package/dist/index.d.ts +3 -16
- package/dist/index.js +288 -34
- package/dist/index.mjs +1 -1
- package/dist/langchain.d.mts +1 -1
- package/dist/langchain.d.ts +1 -1
- package/dist/langchain.js +1040 -5
- package/dist/langchain.mjs +102 -5
- package/dist/transparency-badge.d.mts +1 -1
- package/dist/transparency-badge.d.ts +1 -1
- package/package.json +1 -1
package/dist/langchain.js
CHANGED
|
@@ -27,9 +27,80 @@ var import_base = require("@langchain/core/callbacks/base");
|
|
|
27
27
|
|
|
28
28
|
// src/sdk-version.ts
|
|
29
29
|
var FALLBACK_SDK_VERSION = "js-0.0.0-dev";
|
|
30
|
-
var AGENTID_SDK_VERSION_HEADER = "js-0.1.
|
|
30
|
+
var AGENTID_SDK_VERSION_HEADER = "js-0.1.35".trim().length > 0 ? "js-0.1.35" : FALLBACK_SDK_VERSION;
|
|
31
31
|
|
|
32
32
|
// src/pii-national-identifiers.ts
|
|
33
|
+
var MAX_CANDIDATES_PER_RULE = 256;
|
|
34
|
+
var MAX_PREFILTER_HITS = 512;
|
|
35
|
+
var DEFAULT_DEADLINE_MS = 100;
|
|
36
|
+
var DEFAULT_PREFILTER_DEADLINE_MS = 12;
|
|
37
|
+
var ANCHOR_WINDOW_CHARS = 20;
|
|
38
|
+
var ADDRESS_WINDOW_CHARS = 40;
|
|
39
|
+
var symbolHintRegex = /[\/<\-]/;
|
|
40
|
+
var longDigitHintRegex = /\d{8,}/;
|
|
41
|
+
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;
|
|
42
|
+
var czBirthNumberContextRegex = /\b(?:rodne?\s*(?:cislo|c\.)|r\.?\s*c\.?)\b[^0-9]{0,12}\d{6}(?:\/?\d{0,4})?\b/iu;
|
|
43
|
+
var peselRegex = /\b\d{11}\b/g;
|
|
44
|
+
var cnpRegex = /\b[1-9]\d{12}\b/g;
|
|
45
|
+
var deTaxIdRegex = /\b\d{11}\b/g;
|
|
46
|
+
var huTajRegex = /\b\d{9}\b/g;
|
|
47
|
+
var plNipRegex = /\b\d{10}\b/g;
|
|
48
|
+
var ukNinoRegex = /\b[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]\b/gi;
|
|
49
|
+
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;
|
|
50
|
+
var esDniRegex = /\b\d{8}[A-Z]\b/gi;
|
|
51
|
+
var esNieRegex = /\b[XYZ]\d{7}[A-Z]\b/gi;
|
|
52
|
+
var itCodiceFiscaleRegex = /\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b/gi;
|
|
53
|
+
var nlBsnRegex = /\b\d{8,9}\b/g;
|
|
54
|
+
var atSvnrRegex = /\b\d{4}\s?\d{6}\b/g;
|
|
55
|
+
var euVatRegex = /\b[A-Z]{2}[A-Z0-9]{8,12}\b/g;
|
|
56
|
+
var ukDriverRegex = /\b[A-Z9]{5}\d{6}[A-Z9]{2}\d[A-Z]{2}\b/g;
|
|
57
|
+
var genericDriverRegex = /\b[A-Z0-9]{8,18}\b/g;
|
|
58
|
+
var postalCodeRegex = /\b(?:\d{3}\s?\d{2}|\d{4}|\d{5})\b/g;
|
|
59
|
+
var mrzBlockRegex = /(?:^|\r?\n)([A-Z0-9< ]{10,44}(?:\r?\n[A-Z0-9< ]{10,44}){1,5})(?=\r?\n|$)/g;
|
|
60
|
+
var peselChecksumWeights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
|
|
61
|
+
var cnpChecksumWeights = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9];
|
|
62
|
+
var polishNipWeights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
|
|
63
|
+
var atSvnrWeights = [3, 7, 9, 5, 8, 4, 2, 1, 6];
|
|
64
|
+
var mrzWeights = [7, 3, 1];
|
|
65
|
+
var dniNieControlLetters = "TRWAGMYFPDXBNJZSQVHLCKE";
|
|
66
|
+
var italianOddControlMap = {
|
|
67
|
+
"0": 1,
|
|
68
|
+
"1": 0,
|
|
69
|
+
"2": 5,
|
|
70
|
+
"3": 7,
|
|
71
|
+
"4": 9,
|
|
72
|
+
"5": 13,
|
|
73
|
+
"6": 15,
|
|
74
|
+
"7": 17,
|
|
75
|
+
"8": 19,
|
|
76
|
+
"9": 21,
|
|
77
|
+
A: 1,
|
|
78
|
+
B: 0,
|
|
79
|
+
C: 5,
|
|
80
|
+
D: 7,
|
|
81
|
+
E: 9,
|
|
82
|
+
F: 13,
|
|
83
|
+
G: 15,
|
|
84
|
+
H: 17,
|
|
85
|
+
I: 19,
|
|
86
|
+
J: 21,
|
|
87
|
+
K: 2,
|
|
88
|
+
L: 4,
|
|
89
|
+
M: 18,
|
|
90
|
+
N: 20,
|
|
91
|
+
O: 11,
|
|
92
|
+
P: 3,
|
|
93
|
+
Q: 6,
|
|
94
|
+
R: 8,
|
|
95
|
+
S: 12,
|
|
96
|
+
T: 14,
|
|
97
|
+
U: 16,
|
|
98
|
+
V: 10,
|
|
99
|
+
W: 22,
|
|
100
|
+
X: 25,
|
|
101
|
+
Y: 24,
|
|
102
|
+
Z: 23
|
|
103
|
+
};
|
|
33
104
|
var REGION_ANCHORS = {
|
|
34
105
|
czsk: ["rodne cislo", "rc", "cislo", "birth number", "personal number"],
|
|
35
106
|
pl: ["pesel", "nip", "dowod", "poland"],
|
|
@@ -164,8 +235,698 @@ function buildTrie(words) {
|
|
|
164
235
|
}
|
|
165
236
|
return root;
|
|
166
237
|
}
|
|
238
|
+
function isWordChar(char) {
|
|
239
|
+
if (!char) return false;
|
|
240
|
+
return /[a-z0-9]/i.test(char);
|
|
241
|
+
}
|
|
242
|
+
function hasWordBoundaries(text, start, end) {
|
|
243
|
+
const before = start > 0 ? text[start - 1] : void 0;
|
|
244
|
+
const after = end < text.length ? text[end] : void 0;
|
|
245
|
+
return !isWordChar(before) && !isWordChar(after);
|
|
246
|
+
}
|
|
247
|
+
function hasExpired(startTimeMs, deadlineMs) {
|
|
248
|
+
return Date.now() - startTimeMs > deadlineMs;
|
|
249
|
+
}
|
|
250
|
+
function toInt(value) {
|
|
251
|
+
return Number.parseInt(value, 10);
|
|
252
|
+
}
|
|
253
|
+
function countDigits(value) {
|
|
254
|
+
let count = 0;
|
|
255
|
+
for (const char of value) {
|
|
256
|
+
if (char >= "0" && char <= "9") count += 1;
|
|
257
|
+
}
|
|
258
|
+
return count;
|
|
259
|
+
}
|
|
260
|
+
function countLetters(value) {
|
|
261
|
+
let count = 0;
|
|
262
|
+
for (const char of value) {
|
|
263
|
+
if (char >= "A" && char <= "Z" || char >= "a" && char <= "z") count += 1;
|
|
264
|
+
}
|
|
265
|
+
return count;
|
|
266
|
+
}
|
|
267
|
+
function normalizeCompactIdentifier(value) {
|
|
268
|
+
return foldForMatching(value).toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
269
|
+
}
|
|
270
|
+
function alphaNumericOrdinalValue(char) {
|
|
271
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
272
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 65;
|
|
273
|
+
return -1;
|
|
274
|
+
}
|
|
275
|
+
function mod97FromDigits(value) {
|
|
276
|
+
let remainder = 0;
|
|
277
|
+
for (const char of value) {
|
|
278
|
+
if (char < "0" || char > "9") return -1;
|
|
279
|
+
remainder = (remainder * 10 + Number(char)) % 97;
|
|
280
|
+
}
|
|
281
|
+
return remainder;
|
|
282
|
+
}
|
|
283
|
+
function daysInMonth(year, month) {
|
|
284
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
285
|
+
}
|
|
286
|
+
function isValidDate(year, month, day) {
|
|
287
|
+
if (!Number.isInteger(year) || year < 1800 || year > 2299) return false;
|
|
288
|
+
if (!Number.isInteger(month) || month < 1 || month > 12) return false;
|
|
289
|
+
if (!Number.isInteger(day) || day < 1 || day > 31) return false;
|
|
290
|
+
return day <= daysInMonth(year, month);
|
|
291
|
+
}
|
|
292
|
+
function isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits) {
|
|
293
|
+
return isValidDate(1900 + yearTwoDigits, month, day) || isValidDate(2e3 + yearTwoDigits, month, day);
|
|
294
|
+
}
|
|
295
|
+
function resolveCzechMonth(rawMonth) {
|
|
296
|
+
if (rawMonth >= 1 && rawMonth <= 12) return rawMonth;
|
|
297
|
+
if (rawMonth >= 21 && rawMonth <= 32) return rawMonth - 20;
|
|
298
|
+
if (rawMonth >= 51 && rawMonth <= 62) return rawMonth - 50;
|
|
299
|
+
if (rawMonth >= 71 && rawMonth <= 82) return rawMonth - 70;
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
function resolveCzechYear(twoDigits, length) {
|
|
303
|
+
if (length === 9) return 1900 + twoDigits;
|
|
304
|
+
return twoDigits >= 54 ? 1900 + twoDigits : 2e3 + twoDigits;
|
|
305
|
+
}
|
|
306
|
+
function resolveCnpYear(centuryCode, twoDigits) {
|
|
307
|
+
if (centuryCode === 1 || centuryCode === 2) return 1900 + twoDigits;
|
|
308
|
+
if (centuryCode === 3 || centuryCode === 4) return 1800 + twoDigits;
|
|
309
|
+
if (centuryCode === 5 || centuryCode === 6) return 2e3 + twoDigits;
|
|
310
|
+
if (centuryCode === 7 || centuryCode === 8) return 2e3 + twoDigits;
|
|
311
|
+
if (centuryCode === 9) return 1900 + twoDigits;
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function pushUnique(items, candidate) {
|
|
315
|
+
const exists = items.some(
|
|
316
|
+
(item) => item.start === candidate.start && item.end === candidate.end && item.type === candidate.type
|
|
317
|
+
);
|
|
318
|
+
if (!exists) items.push(candidate);
|
|
319
|
+
}
|
|
320
|
+
function scanRegexMatches(regex, text, startTimeMs, deadlineMs, onMatch) {
|
|
321
|
+
regex.lastIndex = 0;
|
|
322
|
+
let scanned = 0;
|
|
323
|
+
let match;
|
|
324
|
+
while ((match = regex.exec(text)) !== null) {
|
|
325
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
326
|
+
scanned += 1;
|
|
327
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
328
|
+
const value = match[0] ?? "";
|
|
329
|
+
const start = match.index;
|
|
330
|
+
const end = start + value.length;
|
|
331
|
+
onMatch(value, start, end);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function scanAnchorHits(text, startTimeMs, deadlineMs) {
|
|
335
|
+
const normalized = foldForMatching(text);
|
|
336
|
+
const hits = [];
|
|
337
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
338
|
+
if (hasExpired(startTimeMs, deadlineMs) || hits.length >= MAX_PREFILTER_HITS) {
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
let node = anchorTrie.children.get(normalized[i] ?? "");
|
|
342
|
+
if (!node) continue;
|
|
343
|
+
let cursor = i;
|
|
344
|
+
while (node) {
|
|
345
|
+
if (node.terminalWords.length > 0) {
|
|
346
|
+
for (const word of node.terminalWords) {
|
|
347
|
+
const end = i + word.length;
|
|
348
|
+
if (end > normalized.length) continue;
|
|
349
|
+
if (normalized.slice(i, end) !== word) continue;
|
|
350
|
+
if (!hasWordBoundaries(normalized, i, end)) continue;
|
|
351
|
+
hits.push({ word, start: i, end });
|
|
352
|
+
if (hits.length >= MAX_PREFILTER_HITS) break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
cursor += 1;
|
|
356
|
+
if (cursor >= normalized.length) break;
|
|
357
|
+
node = node.children.get(normalized[cursor] ?? "");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return hits;
|
|
361
|
+
}
|
|
362
|
+
function hasAnchorNear(hits, start, end, anchorSet, windowChars = ANCHOR_WINDOW_CHARS) {
|
|
363
|
+
const left = Math.max(0, start - windowChars);
|
|
364
|
+
const right = end + windowChars;
|
|
365
|
+
for (const hit of hits) {
|
|
366
|
+
if (!anchorSet.has(hit.word)) continue;
|
|
367
|
+
if (hit.end < left) continue;
|
|
368
|
+
if (hit.start > right) continue;
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
function hasAnyAnchor(hits) {
|
|
374
|
+
return hits.length > 0;
|
|
375
|
+
}
|
|
376
|
+
function buildPrefilterState(text, startTimeMs, deadlineMs) {
|
|
377
|
+
const hits = scanAnchorHits(text, startTimeMs, deadlineMs);
|
|
378
|
+
return {
|
|
379
|
+
hits,
|
|
380
|
+
hasLongDigitHint: longDigitHintRegex.test(text),
|
|
381
|
+
hasSymbolHint: symbolHintRegex.test(text)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function parseLocaleRegion(locale) {
|
|
385
|
+
if (!locale) return null;
|
|
386
|
+
const normalized = locale.toLowerCase();
|
|
387
|
+
if (normalized.startsWith("cs") || normalized.startsWith("sk")) return "czsk";
|
|
388
|
+
if (normalized.startsWith("pl")) return "pl";
|
|
389
|
+
if (normalized.startsWith("ro")) return "ro";
|
|
390
|
+
if (normalized.startsWith("de")) return "de";
|
|
391
|
+
if (normalized.startsWith("hu")) return "hu";
|
|
392
|
+
if (normalized === "en" || normalized.startsWith("en-gb") || normalized === "gb" || normalized.startsWith("gb-")) {
|
|
393
|
+
return "gb";
|
|
394
|
+
}
|
|
395
|
+
if (normalized.startsWith("fr")) return "fr";
|
|
396
|
+
if (normalized.startsWith("es")) return "es";
|
|
397
|
+
if (normalized.startsWith("it")) return "it";
|
|
398
|
+
if (normalized.startsWith("nl")) return "nl";
|
|
399
|
+
if (normalized.startsWith("at")) return "at";
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
function inferRegionsFromAnchors(hits) {
|
|
403
|
+
const regions = /* @__PURE__ */ new Set();
|
|
404
|
+
const regionEntries = Object.entries(REGION_ANCHORS);
|
|
405
|
+
for (const hit of hits) {
|
|
406
|
+
for (const [region, words] of regionEntries) {
|
|
407
|
+
if (words.includes(hit.word)) {
|
|
408
|
+
regions.add(region);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return Array.from(regions);
|
|
413
|
+
}
|
|
414
|
+
function resolveRegions(text, locale) {
|
|
415
|
+
const localeRegion = parseLocaleRegion(locale);
|
|
416
|
+
const normalizedLocale = locale?.toLowerCase().trim() ?? "";
|
|
417
|
+
const inferred = inferRegionsFromAnchors(
|
|
418
|
+
scanAnchorHits(text, Date.now(), DEFAULT_PREFILTER_DEADLINE_MS)
|
|
419
|
+
);
|
|
420
|
+
const regions = /* @__PURE__ */ new Set();
|
|
421
|
+
if (localeRegion) {
|
|
422
|
+
regions.add(localeRegion);
|
|
423
|
+
}
|
|
424
|
+
for (const region of inferred) {
|
|
425
|
+
regions.add(region);
|
|
426
|
+
}
|
|
427
|
+
if (regions.size > 0) {
|
|
428
|
+
return Array.from(regions);
|
|
429
|
+
}
|
|
430
|
+
if (normalizedLocale.startsWith("uk")) {
|
|
431
|
+
return ["czsk", "pl", "ro", "de", "hu", "fr", "es", "it", "nl", "at"];
|
|
432
|
+
}
|
|
433
|
+
return ["czsk", "pl", "ro", "de", "hu", "gb", "fr", "es", "it", "nl", "at"];
|
|
434
|
+
}
|
|
435
|
+
function mrzCharValue(char) {
|
|
436
|
+
if (char === "<") return 0;
|
|
437
|
+
if (char >= "0" && char <= "9") return Number(char);
|
|
438
|
+
if (char >= "A" && char <= "Z") return char.charCodeAt(0) - 55;
|
|
439
|
+
return -1;
|
|
440
|
+
}
|
|
441
|
+
function computeMrzCheckDigit(payload) {
|
|
442
|
+
let sum = 0;
|
|
443
|
+
for (let i = 0; i < payload.length; i += 1) {
|
|
444
|
+
const value = mrzCharValue(payload[i] ?? "");
|
|
445
|
+
if (value < 0) return -1;
|
|
446
|
+
sum += value * mrzWeights[i % mrzWeights.length];
|
|
447
|
+
}
|
|
448
|
+
return sum % 10;
|
|
449
|
+
}
|
|
450
|
+
function isValidMrzCheck(payload, checkChar) {
|
|
451
|
+
if (!/^\d$/.test(checkChar)) return false;
|
|
452
|
+
const expected = computeMrzCheckDigit(payload);
|
|
453
|
+
return expected >= 0 && expected === Number(checkChar);
|
|
454
|
+
}
|
|
455
|
+
function isValidOptionalMrzCheck(payload, checkChar) {
|
|
456
|
+
if (checkChar === "<") {
|
|
457
|
+
return /^[<]+$/.test(payload);
|
|
458
|
+
}
|
|
459
|
+
return isValidMrzCheck(payload, checkChar);
|
|
460
|
+
}
|
|
461
|
+
function validateTd3(lines) {
|
|
462
|
+
if (lines.length !== 2) return false;
|
|
463
|
+
const [line1, line2] = lines;
|
|
464
|
+
if (line1.length !== 44 || line2.length !== 44) return false;
|
|
465
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
466
|
+
const passportNumber = line2.slice(0, 9);
|
|
467
|
+
const passportCheck = line2[9] ?? "";
|
|
468
|
+
const birthDate = line2.slice(13, 19);
|
|
469
|
+
const birthCheck = line2[19] ?? "";
|
|
470
|
+
const expiryDate = line2.slice(21, 27);
|
|
471
|
+
const expiryCheck = line2[27] ?? "";
|
|
472
|
+
const optionalData = line2.slice(28, 42);
|
|
473
|
+
const optionalCheck = line2[42] ?? "";
|
|
474
|
+
const finalCheck = line2[43] ?? "";
|
|
475
|
+
if (!isValidMrzCheck(passportNumber, passportCheck)) return false;
|
|
476
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
477
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
478
|
+
if (!isValidOptionalMrzCheck(optionalData, optionalCheck)) return false;
|
|
479
|
+
const composite = `${line2.slice(0, 10)}${line2.slice(13, 20)}${line2.slice(21, 43)}`;
|
|
480
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
481
|
+
}
|
|
482
|
+
function validateTd2(lines) {
|
|
483
|
+
if (lines.length !== 2) return false;
|
|
484
|
+
const [line1, line2] = lines;
|
|
485
|
+
if (line1.length !== 36 || line2.length !== 36) return false;
|
|
486
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
487
|
+
const documentNumber = line2.slice(0, 9);
|
|
488
|
+
const documentCheck = line2[9] ?? "";
|
|
489
|
+
const birthDate = line2.slice(13, 19);
|
|
490
|
+
const birthCheck = line2[19] ?? "";
|
|
491
|
+
const expiryDate = line2.slice(21, 27);
|
|
492
|
+
const expiryCheck = line2[27] ?? "";
|
|
493
|
+
const optionalData = line2.slice(28, 35);
|
|
494
|
+
const optionalCheck = line2[35] ?? "";
|
|
495
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
496
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
497
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
498
|
+
return isValidOptionalMrzCheck(optionalData, optionalCheck);
|
|
499
|
+
}
|
|
500
|
+
function validateTd1(lines) {
|
|
501
|
+
if (lines.length !== 3) return false;
|
|
502
|
+
const [line1, line2, line3] = lines;
|
|
503
|
+
if (line1.length !== 30 || line2.length !== 30 || line3.length !== 30) return false;
|
|
504
|
+
if (!/^[A-Z][<A-Z]/.test(line1.slice(0, 2))) return false;
|
|
505
|
+
const documentNumber = line1.slice(5, 14);
|
|
506
|
+
const documentCheck = line1[14] ?? "";
|
|
507
|
+
const birthDate = line2.slice(0, 6);
|
|
508
|
+
const birthCheck = line2[6] ?? "";
|
|
509
|
+
const expiryDate = line2.slice(8, 14);
|
|
510
|
+
const expiryCheck = line2[14] ?? "";
|
|
511
|
+
const composite = `${line1.slice(5, 30)}${line2.slice(0, 7)}${line2.slice(8, 15)}${line2.slice(18, 29)}`;
|
|
512
|
+
const finalCheck = line2[29] ?? "";
|
|
513
|
+
if (!isValidMrzCheck(documentNumber, documentCheck)) return false;
|
|
514
|
+
if (!isValidMrzCheck(birthDate, birthCheck)) return false;
|
|
515
|
+
if (!isValidMrzCheck(expiryDate, expiryCheck)) return false;
|
|
516
|
+
return isValidMrzCheck(composite, finalCheck);
|
|
517
|
+
}
|
|
518
|
+
function validateMrzLines(lines) {
|
|
519
|
+
const normalized = lines.map((line) => line.replace(/\s+/g, "").toUpperCase()).filter((line) => line.length > 0);
|
|
520
|
+
const merged = normalized.join("");
|
|
521
|
+
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;
|
|
522
|
+
if (reframed.length === 2 && reframed[0]?.length === 44 && reframed[1]?.length === 44) {
|
|
523
|
+
return validateTd3(reframed);
|
|
524
|
+
}
|
|
525
|
+
if (reframed.length === 2 && reframed[0]?.length === 36 && reframed[1]?.length === 36) {
|
|
526
|
+
return validateTd2(reframed);
|
|
527
|
+
}
|
|
528
|
+
if (reframed.length === 3 && reframed[0]?.length === 30 && reframed[1]?.length === 30 && reframed[2]?.length === 30) {
|
|
529
|
+
return validateTd1(reframed);
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
function isValidPesel(value) {
|
|
534
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
535
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
536
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
537
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
538
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
539
|
+
const centuryOffset = Math.floor((mmRaw - 1) / 20);
|
|
540
|
+
const month = (mmRaw - 1) % 20 + 1;
|
|
541
|
+
const century = centuryOffset === 0 ? 1900 : centuryOffset === 1 ? 2e3 : centuryOffset === 2 ? 2100 : centuryOffset === 3 ? 2200 : centuryOffset === 4 ? 1800 : null;
|
|
542
|
+
if (century === null) return false;
|
|
543
|
+
if (!isValidDate(century + yy, month, dd)) return false;
|
|
544
|
+
let sum = 0;
|
|
545
|
+
for (let i = 0; i < 10; i += 1) {
|
|
546
|
+
sum += toInt(digitsOnly[i] ?? "0") * peselChecksumWeights[i];
|
|
547
|
+
}
|
|
548
|
+
const checkDigit = (10 - sum % 10) % 10;
|
|
549
|
+
return checkDigit === toInt(digitsOnly[10] ?? "0");
|
|
550
|
+
}
|
|
551
|
+
function isValidRomanianCnp(value) {
|
|
552
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
553
|
+
if (!/^\d{13}$/.test(digitsOnly)) return false;
|
|
554
|
+
const centuryCode = toInt(digitsOnly[0] ?? "0");
|
|
555
|
+
const yy = toInt(digitsOnly.slice(1, 3));
|
|
556
|
+
const mm = toInt(digitsOnly.slice(3, 5));
|
|
557
|
+
const dd = toInt(digitsOnly.slice(5, 7));
|
|
558
|
+
const year = resolveCnpYear(centuryCode, yy);
|
|
559
|
+
if (year === null) return false;
|
|
560
|
+
if (!isValidDate(year, mm, dd)) return false;
|
|
561
|
+
let sum = 0;
|
|
562
|
+
for (let i = 0; i < 12; i += 1) {
|
|
563
|
+
sum += toInt(digitsOnly[i] ?? "0") * cnpChecksumWeights[i];
|
|
564
|
+
}
|
|
565
|
+
let checkDigit = sum % 11;
|
|
566
|
+
if (checkDigit === 10) checkDigit = 1;
|
|
567
|
+
return checkDigit === toInt(digitsOnly[12] ?? "0");
|
|
568
|
+
}
|
|
569
|
+
function isValidCzechBirthNumber(value) {
|
|
570
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
571
|
+
if (!/^\d{9,10}$/.test(digitsOnly)) return false;
|
|
572
|
+
const yy = toInt(digitsOnly.slice(0, 2));
|
|
573
|
+
const mmRaw = toInt(digitsOnly.slice(2, 4));
|
|
574
|
+
const dd = toInt(digitsOnly.slice(4, 6));
|
|
575
|
+
const month = resolveCzechMonth(mmRaw);
|
|
576
|
+
if (month === null) return false;
|
|
577
|
+
const year = resolveCzechYear(yy, digitsOnly.length);
|
|
578
|
+
if (!isValidDate(year, month, dd)) return false;
|
|
579
|
+
if (digitsOnly.length === 10) {
|
|
580
|
+
const body = toInt(digitsOnly.slice(0, 9));
|
|
581
|
+
const expected = body % 11 === 10 ? 0 : body % 11;
|
|
582
|
+
const check = toInt(digitsOnly[9] ?? "0");
|
|
583
|
+
return expected === check;
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
function isValidGermanTaxId(value) {
|
|
588
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
589
|
+
if (!/^\d{11}$/.test(digitsOnly)) return false;
|
|
590
|
+
if (/^(\d)\1{10}$/.test(digitsOnly)) return false;
|
|
591
|
+
let product = 10;
|
|
592
|
+
for (let i = 0; i < 10; i += 1) {
|
|
593
|
+
let sum = (toInt(digitsOnly[i] ?? "0") + product) % 10;
|
|
594
|
+
if (sum === 0) sum = 10;
|
|
595
|
+
product = 2 * sum % 11;
|
|
596
|
+
}
|
|
597
|
+
let check = 11 - product;
|
|
598
|
+
if (check === 10 || check === 11) check = 0;
|
|
599
|
+
return check === toInt(digitsOnly[10] ?? "0");
|
|
600
|
+
}
|
|
601
|
+
function isValidHungarianTaj(value) {
|
|
602
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
603
|
+
if (!/^\d{9}$/.test(digitsOnly)) return false;
|
|
604
|
+
let sum = 0;
|
|
605
|
+
for (let i = 0; i < 8; i += 1) {
|
|
606
|
+
const weight = i % 2 === 0 ? 3 : 7;
|
|
607
|
+
sum += toInt(digitsOnly[i] ?? "0") * weight;
|
|
608
|
+
}
|
|
609
|
+
return sum % 10 === toInt(digitsOnly[8] ?? "0");
|
|
610
|
+
}
|
|
611
|
+
function isValidPolishNip(value) {
|
|
612
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
613
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
614
|
+
let sum = 0;
|
|
615
|
+
for (let i = 0; i < 9; i += 1) {
|
|
616
|
+
sum += toInt(digitsOnly[i] ?? "0") * polishNipWeights[i];
|
|
617
|
+
}
|
|
618
|
+
const check = sum % 11;
|
|
619
|
+
if (check === 10) return false;
|
|
620
|
+
return check === toInt(digitsOnly[9] ?? "0");
|
|
621
|
+
}
|
|
622
|
+
function isLikelyUkNino(value) {
|
|
623
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
624
|
+
if (!/^[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]$/.test(normalized)) return false;
|
|
625
|
+
const prefix = normalized.slice(0, 2);
|
|
626
|
+
const disallowedPrefixes = /* @__PURE__ */ new Set(["BG", "GB", "NK", "KN", "TN", "NT", "ZZ"]);
|
|
627
|
+
if (disallowedPrefixes.has(prefix)) return false;
|
|
628
|
+
if (normalized[0] === "D" || normalized[0] === "F" || normalized[0] === "I" || normalized[0] === "Q" || normalized[0] === "U" || normalized[0] === "V") {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if (normalized[1] === "D" || normalized[1] === "F" || normalized[1] === "I" || normalized[1] === "O" || normalized[1] === "Q" || normalized[1] === "U" || normalized[1] === "V") {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
function isValidFrenchInsee(value) {
|
|
637
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
638
|
+
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)) {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
const body = normalized.slice(0, 13);
|
|
642
|
+
const checkDigits = normalized.slice(13);
|
|
643
|
+
const bodyForChecksum = body.replace("2A", "19").replace("2B", "18");
|
|
644
|
+
if (!/^\d{13}$/.test(bodyForChecksum)) return false;
|
|
645
|
+
const remainder = mod97FromDigits(bodyForChecksum);
|
|
646
|
+
if (remainder < 0) return false;
|
|
647
|
+
const expectedNumber = 97 - remainder;
|
|
648
|
+
return expectedNumber === toInt(checkDigits);
|
|
649
|
+
}
|
|
650
|
+
function isValidSpanishDniNie(value) {
|
|
651
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
652
|
+
let numberPortion = "";
|
|
653
|
+
let checkLetter = "";
|
|
654
|
+
if (/^\d{8}[A-Z]$/.test(normalized)) {
|
|
655
|
+
numberPortion = normalized.slice(0, 8);
|
|
656
|
+
checkLetter = normalized[8] ?? "";
|
|
657
|
+
} else if (/^[XYZ]\d{7}[A-Z]$/.test(normalized)) {
|
|
658
|
+
const prefixMap = { X: "0", Y: "1", Z: "2" };
|
|
659
|
+
numberPortion = `${prefixMap[normalized[0] ?? ""] ?? ""}${normalized.slice(1, 8)}`;
|
|
660
|
+
checkLetter = normalized[8] ?? "";
|
|
661
|
+
} else {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
const expected = dniNieControlLetters[toInt(numberPortion) % 23] ?? "";
|
|
665
|
+
return checkLetter === expected;
|
|
666
|
+
}
|
|
667
|
+
function isValidItalianCodiceFiscale(value) {
|
|
668
|
+
const normalized = normalizeCompactIdentifier(value);
|
|
669
|
+
if (!/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/.test(normalized)) return false;
|
|
670
|
+
let sum = 0;
|
|
671
|
+
for (let i = 0; i < 15; i += 1) {
|
|
672
|
+
const char = normalized[i] ?? "";
|
|
673
|
+
const position = i + 1;
|
|
674
|
+
if (position % 2 === 0) {
|
|
675
|
+
const evenValue = alphaNumericOrdinalValue(char);
|
|
676
|
+
if (evenValue < 0) return false;
|
|
677
|
+
sum += evenValue;
|
|
678
|
+
} else {
|
|
679
|
+
const oddValue = italianOddControlMap[char];
|
|
680
|
+
if (typeof oddValue !== "number") return false;
|
|
681
|
+
sum += oddValue;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const expected = String.fromCharCode(65 + sum % 26);
|
|
685
|
+
return normalized[15] === expected;
|
|
686
|
+
}
|
|
687
|
+
function isValidDutchBsn(value) {
|
|
688
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
689
|
+
if (!/^\d{8,9}$/.test(digitsOnly)) return false;
|
|
690
|
+
const normalized = digitsOnly.padStart(9, "0");
|
|
691
|
+
if (normalized === "000000000") return false;
|
|
692
|
+
let sum = 0;
|
|
693
|
+
for (let i = 0; i < 8; i += 1) {
|
|
694
|
+
const weight = 9 - i;
|
|
695
|
+
sum += toInt(normalized[i] ?? "0") * weight;
|
|
696
|
+
}
|
|
697
|
+
sum -= toInt(normalized[8] ?? "0");
|
|
698
|
+
return sum % 11 === 0;
|
|
699
|
+
}
|
|
700
|
+
function isValidAustrianSvnr(value) {
|
|
701
|
+
const digitsOnly = value.replace(/\D/g, "");
|
|
702
|
+
if (!/^\d{10}$/.test(digitsOnly)) return false;
|
|
703
|
+
const day = toInt(digitsOnly.slice(4, 6));
|
|
704
|
+
const month = toInt(digitsOnly.slice(6, 8));
|
|
705
|
+
const yearTwoDigits = toInt(digitsOnly.slice(8, 10));
|
|
706
|
+
if (!isValidDayMonthWithTwoDigitYear(day, month, yearTwoDigits)) return false;
|
|
707
|
+
const bodyIndexes = [0, 1, 2, 4, 5, 6, 7, 8, 9];
|
|
708
|
+
let sum = 0;
|
|
709
|
+
for (let i = 0; i < bodyIndexes.length; i += 1) {
|
|
710
|
+
const digit = toInt(digitsOnly[bodyIndexes[i] ?? 0] ?? "0");
|
|
711
|
+
sum += digit * atSvnrWeights[i];
|
|
712
|
+
}
|
|
713
|
+
const expected = sum % 11;
|
|
714
|
+
if (expected === 10) return false;
|
|
715
|
+
return expected === toInt(digitsOnly[3] ?? "0");
|
|
716
|
+
}
|
|
717
|
+
function isLikelyEuVat(value) {
|
|
718
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
719
|
+
if (!/^[A-Z]{2}[A-Z0-9]{8,12}$/.test(normalized)) return false;
|
|
720
|
+
const country = normalized.slice(0, 2);
|
|
721
|
+
return country !== "XX";
|
|
722
|
+
}
|
|
723
|
+
function isLikelyGenericDriverId(value) {
|
|
724
|
+
const normalized = value.replace(/\s+/g, "").toUpperCase();
|
|
725
|
+
if (normalized.length < 8 || normalized.length > 18) return false;
|
|
726
|
+
const letters = countLetters(normalized);
|
|
727
|
+
const digits = countDigits(normalized);
|
|
728
|
+
if (letters < 2 || digits < 4) return false;
|
|
729
|
+
if (!/^[A-Z0-9]+$/.test(normalized)) return false;
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
function detectMrzBlocks(text, startTimeMs, deadlineMs, results) {
|
|
733
|
+
mrzBlockRegex.lastIndex = 0;
|
|
734
|
+
let scanned = 0;
|
|
735
|
+
let match;
|
|
736
|
+
while ((match = mrzBlockRegex.exec(text)) !== null) {
|
|
737
|
+
if (hasExpired(startTimeMs, deadlineMs)) return;
|
|
738
|
+
scanned += 1;
|
|
739
|
+
if (scanned > MAX_CANDIDATES_PER_RULE) return;
|
|
740
|
+
const rawBlock = match[1] ?? "";
|
|
741
|
+
const lines = rawBlock.split(/\r?\n/);
|
|
742
|
+
if (!validateMrzLines(lines)) continue;
|
|
743
|
+
const wholeMatch = match[0] ?? "";
|
|
744
|
+
const startsWithCrlf = wholeMatch.startsWith("\r\n");
|
|
745
|
+
const startsWithLf = !startsWithCrlf && wholeMatch.startsWith("\n");
|
|
746
|
+
const prefixShift = startsWithCrlf ? 2 : startsWithLf ? 1 : 0;
|
|
747
|
+
const start = (match.index ?? 0) + prefixShift;
|
|
748
|
+
const end = start + rawBlock.length;
|
|
749
|
+
pushUnique(results, {
|
|
750
|
+
type: "mrz_document",
|
|
751
|
+
value: rawBlock,
|
|
752
|
+
start,
|
|
753
|
+
end
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function detectNationalIdentifiers(text, options = {}) {
|
|
758
|
+
if (!text) return [];
|
|
759
|
+
const startTimeMs = options.startTimeMs ?? Date.now();
|
|
760
|
+
const deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
761
|
+
const prefilter = buildPrefilterState(text, startTimeMs, deadlineMs);
|
|
762
|
+
if (!hasAnyAnchor(prefilter.hits) && !prefilter.hasLongDigitHint && !prefilter.hasSymbolHint) {
|
|
763
|
+
return [];
|
|
764
|
+
}
|
|
765
|
+
const results = [];
|
|
766
|
+
const regions = resolveRegions(text, options.locale ?? null);
|
|
767
|
+
const hasTaxAnchors = prefilter.hits.some((hit) => TAX_ANCHOR_SET.has(hit.word));
|
|
768
|
+
const hasNlBsnAnchors = prefilter.hits.some((hit) => NL_BSN_ANCHOR_SET.has(hit.word));
|
|
769
|
+
const hasAtSvnrAnchors = prefilter.hits.some((hit) => AT_SVNR_ANCHOR_SET.has(hit.word));
|
|
770
|
+
const hasDriverAnchors = prefilter.hits.some((hit) => DRIVER_ANCHOR_SET.has(hit.word));
|
|
771
|
+
const hasAddressAnchors = prefilter.hits.some((hit) => ADDRESS_ANCHOR_SET.has(hit.word));
|
|
772
|
+
const hasMrzAnchors = prefilter.hits.some((hit) => MRZ_ANCHOR_SET.has(hit.word));
|
|
773
|
+
if (prefilter.hasSymbolHint || hasMrzAnchors) {
|
|
774
|
+
detectMrzBlocks(text, startTimeMs, deadlineMs, results);
|
|
775
|
+
}
|
|
776
|
+
if (regions.includes("czsk")) {
|
|
777
|
+
scanRegexMatches(czBirthNumberRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
778
|
+
if (!isValidCzechBirthNumber(value)) return;
|
|
779
|
+
pushUnique(results, { type: "birth_number", value, start, end });
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
if (regions.includes("pl")) {
|
|
783
|
+
scanRegexMatches(peselRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
784
|
+
if (!isValidPesel(value)) return;
|
|
785
|
+
pushUnique(results, { type: "pl_pesel", value, start, end });
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
if (regions.includes("ro")) {
|
|
789
|
+
scanRegexMatches(cnpRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
790
|
+
if (!isValidRomanianCnp(value)) return;
|
|
791
|
+
pushUnique(results, { type: "ro_cnp", value, start, end });
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (regions.includes("de") && hasTaxAnchors) {
|
|
795
|
+
scanRegexMatches(deTaxIdRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
796
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
797
|
+
if (!isValidGermanTaxId(value)) return;
|
|
798
|
+
pushUnique(results, { type: "de_tax_id", value, start, end });
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (regions.includes("hu")) {
|
|
802
|
+
scanRegexMatches(huTajRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
803
|
+
if (!isValidHungarianTaj(value)) return;
|
|
804
|
+
pushUnique(results, { type: "hu_taj", value, start, end });
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (regions.includes("pl") && hasTaxAnchors) {
|
|
808
|
+
scanRegexMatches(plNipRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
809
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
810
|
+
if (!isValidPolishNip(value)) return;
|
|
811
|
+
pushUnique(results, { type: "pl_nip", value, start, end });
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (regions.includes("gb")) {
|
|
815
|
+
scanRegexMatches(ukNinoRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
816
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
817
|
+
if (!isLikelyUkNino(value)) return;
|
|
818
|
+
pushUnique(results, { type: "uk_nino", value, start, end });
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (regions.includes("fr")) {
|
|
822
|
+
scanRegexMatches(frInseeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
823
|
+
if (!isValidFrenchInsee(value)) return;
|
|
824
|
+
pushUnique(results, { type: "fr_insee", value, start, end });
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
if (regions.includes("es")) {
|
|
828
|
+
scanRegexMatches(esDniRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
829
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
830
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
831
|
+
});
|
|
832
|
+
scanRegexMatches(esNieRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
833
|
+
if (!isValidSpanishDniNie(value)) return;
|
|
834
|
+
pushUnique(results, { type: "es_dni_nie", value, start, end });
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
if (regions.includes("it")) {
|
|
838
|
+
scanRegexMatches(itCodiceFiscaleRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
839
|
+
if (!isValidItalianCodiceFiscale(value)) return;
|
|
840
|
+
pushUnique(results, { type: "it_codice_fiscale", value, start, end });
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
if (regions.includes("nl") && hasNlBsnAnchors) {
|
|
844
|
+
scanRegexMatches(nlBsnRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
845
|
+
if (!hasAnchorNear(prefilter.hits, start, end, NL_BSN_ANCHOR_SET)) return;
|
|
846
|
+
if (!isValidDutchBsn(value)) return;
|
|
847
|
+
pushUnique(results, { type: "nl_bsn", value, start, end });
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
if (regions.includes("at") && hasAtSvnrAnchors) {
|
|
851
|
+
scanRegexMatches(atSvnrRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
852
|
+
if (!hasAnchorNear(prefilter.hits, start, end, AT_SVNR_ANCHOR_SET)) return;
|
|
853
|
+
if (!isValidAustrianSvnr(value)) return;
|
|
854
|
+
pushUnique(results, { type: "at_svnr", value, start, end });
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
if (hasTaxAnchors) {
|
|
858
|
+
scanRegexMatches(euVatRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
859
|
+
if (!hasAnchorNear(prefilter.hits, start, end, TAX_ANCHOR_SET)) return;
|
|
860
|
+
if (!isLikelyEuVat(value)) return;
|
|
861
|
+
pushUnique(results, { type: "eu_vat", value, start, end });
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (hasDriverAnchors) {
|
|
865
|
+
scanRegexMatches(ukDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
866
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
867
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
868
|
+
});
|
|
869
|
+
scanRegexMatches(genericDriverRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
870
|
+
if (!hasAnchorNear(prefilter.hits, start, end, DRIVER_ANCHOR_SET)) return;
|
|
871
|
+
if (!isLikelyGenericDriverId(value)) return;
|
|
872
|
+
pushUnique(results, { type: "driver_license", value, start, end });
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
if (hasAddressAnchors) {
|
|
876
|
+
scanRegexMatches(postalCodeRegex, text, startTimeMs, deadlineMs, (value, start, end) => {
|
|
877
|
+
if (!hasAnchorNear(prefilter.hits, start, end, ADDRESS_ANCHOR_SET, ADDRESS_WINDOW_CHARS)) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
pushUnique(results, { type: "address_postal", value, start, end });
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (options.allowContextBirthNumberFallback && !results.some((item) => item.type === "birth_number") && !/\d{6}\/?\d{3,4}/.test(text) && czBirthNumberContextRegex.test(text)) {
|
|
884
|
+
pushUnique(results, {
|
|
885
|
+
type: "birth_number",
|
|
886
|
+
value: "contextual_birth_number",
|
|
887
|
+
start: 0,
|
|
888
|
+
end: 0
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
return results;
|
|
892
|
+
}
|
|
167
893
|
|
|
168
894
|
// src/pii.ts
|
|
895
|
+
var defaultScanDeadlineMs = 100;
|
|
896
|
+
function countDigits2(value) {
|
|
897
|
+
let count = 0;
|
|
898
|
+
for (const ch of value) {
|
|
899
|
+
if (ch >= "0" && ch <= "9") count += 1;
|
|
900
|
+
}
|
|
901
|
+
return count;
|
|
902
|
+
}
|
|
903
|
+
function luhnCheck(value) {
|
|
904
|
+
const digits = value.replace(/\D/g, "");
|
|
905
|
+
if (digits.length < 12 || digits.length > 19) return false;
|
|
906
|
+
let sum = 0;
|
|
907
|
+
let doubleNext = false;
|
|
908
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
909
|
+
let digit = Number(digits[i]);
|
|
910
|
+
if (doubleNext) {
|
|
911
|
+
digit *= 2;
|
|
912
|
+
if (digit > 9) digit -= 9;
|
|
913
|
+
}
|
|
914
|
+
sum += digit;
|
|
915
|
+
doubleNext = !doubleNext;
|
|
916
|
+
}
|
|
917
|
+
return sum % 10 === 0;
|
|
918
|
+
}
|
|
919
|
+
function normalizeDetections(text, detections) {
|
|
920
|
+
const sorted = detections.filter((d) => d.start >= 0 && d.end > d.start && d.end <= text.length).sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
|
|
921
|
+
const kept = [];
|
|
922
|
+
let cursor = 0;
|
|
923
|
+
for (const d of sorted) {
|
|
924
|
+
if (d.start < cursor) continue;
|
|
925
|
+
kept.push(d);
|
|
926
|
+
cursor = d.end;
|
|
927
|
+
}
|
|
928
|
+
return kept;
|
|
929
|
+
}
|
|
169
930
|
var PHONE_CONTEXT_KEYWORDS = [
|
|
170
931
|
"tel",
|
|
171
932
|
"phone",
|
|
@@ -198,6 +959,184 @@ var PHONE_CONTEXT_RE = new RegExp(
|
|
|
198
959
|
`(?:^|[^\\p{L}])(?:${PHONE_CONTEXT_KEYWORDS.map(escapeRegex).join("|")})(?:$|[^\\p{L}])`,
|
|
199
960
|
"iu"
|
|
200
961
|
);
|
|
962
|
+
var PERSON_NAME_STOPWORDS = /* @__PURE__ */ new Set([
|
|
963
|
+
"write",
|
|
964
|
+
"code",
|
|
965
|
+
"script",
|
|
966
|
+
"query",
|
|
967
|
+
"curl",
|
|
968
|
+
"http",
|
|
969
|
+
"https",
|
|
970
|
+
"import",
|
|
971
|
+
"python",
|
|
972
|
+
"javascript",
|
|
973
|
+
"typescript",
|
|
974
|
+
"node",
|
|
975
|
+
"bash",
|
|
976
|
+
"powershell",
|
|
977
|
+
"shell",
|
|
978
|
+
"command",
|
|
979
|
+
"example",
|
|
980
|
+
"demo",
|
|
981
|
+
"developer",
|
|
982
|
+
"mode",
|
|
983
|
+
"system",
|
|
984
|
+
"prompt",
|
|
985
|
+
"policy",
|
|
986
|
+
"security",
|
|
987
|
+
"instructions",
|
|
988
|
+
"instruction",
|
|
989
|
+
"rules",
|
|
990
|
+
"rule",
|
|
991
|
+
"json",
|
|
992
|
+
"output",
|
|
993
|
+
"input",
|
|
994
|
+
"database",
|
|
995
|
+
"sql",
|
|
996
|
+
"mongo",
|
|
997
|
+
"openai",
|
|
998
|
+
"agentid",
|
|
999
|
+
"risk",
|
|
1000
|
+
"score",
|
|
1001
|
+
"summary"
|
|
1002
|
+
]);
|
|
1003
|
+
var TECHNICAL_CONTEXT_WORD_REGEX = /\b(?:curl|http|https|import|python|javascript|typescript|sql|nosql|mongo|database|query|script|code|os\.system|eval|exec|node|npm|api|endpoint|regex|json|xml|yaml|bash|powershell)\b/i;
|
|
1004
|
+
var TECHNICAL_CONTEXT_SYMBOL_REGEX = /:\/\/|`|\{|\}|\[|\]|\(|\)|;|\$|=>|::|\/\//;
|
|
1005
|
+
function hasPhoneContext(text, matchStartIndex, windowSize = 50) {
|
|
1006
|
+
const start = Math.max(0, matchStartIndex - windowSize);
|
|
1007
|
+
const windowLower = text.slice(start, matchStartIndex).toLowerCase();
|
|
1008
|
+
return PHONE_CONTEXT_RE.test(windowLower);
|
|
1009
|
+
}
|
|
1010
|
+
function normalizePersonWord(value) {
|
|
1011
|
+
return value.normalize("NFD").replace(/\p{M}+/gu, "").toLowerCase();
|
|
1012
|
+
}
|
|
1013
|
+
function buildContextWindow(source, index, length) {
|
|
1014
|
+
const start = Math.max(0, index - 28);
|
|
1015
|
+
const end = Math.min(source.length, index + length + 28);
|
|
1016
|
+
return source.slice(start, end);
|
|
1017
|
+
}
|
|
1018
|
+
function isTechnicalContext(contextWindow) {
|
|
1019
|
+
return TECHNICAL_CONTEXT_WORD_REGEX.test(contextWindow) || TECHNICAL_CONTEXT_SYMBOL_REGEX.test(contextWindow);
|
|
1020
|
+
}
|
|
1021
|
+
function isLikelyPersonNameCandidate(candidate, contextWindow) {
|
|
1022
|
+
const words = candidate.trim().split(/\s+/);
|
|
1023
|
+
if (words.length !== 2) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
if (isTechnicalContext(contextWindow)) {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
for (const rawWord of words) {
|
|
1030
|
+
const normalized = normalizePersonWord(rawWord);
|
|
1031
|
+
if (normalized.length < 2) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
if (PERSON_NAME_STOPWORDS.has(normalized)) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
if (!/^\p{L}[\p{L}'-]+$/u.test(rawWord)) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
var PIIManager = class {
|
|
1044
|
+
/**
|
|
1045
|
+
* Reversible local-first masking using <TYPE_INDEX> placeholders.
|
|
1046
|
+
*
|
|
1047
|
+
* Zero-dependency fallback with strict checksum validation for CEE national IDs.
|
|
1048
|
+
*/
|
|
1049
|
+
anonymize(text) {
|
|
1050
|
+
if (!text) return { maskedText: text, mapping: {} };
|
|
1051
|
+
try {
|
|
1052
|
+
const detections = [];
|
|
1053
|
+
const emailRe = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
1054
|
+
for (const m of text.matchAll(emailRe)) {
|
|
1055
|
+
if (m.index == null) continue;
|
|
1056
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "EMAIL", text: m[0] });
|
|
1057
|
+
}
|
|
1058
|
+
const ibanRe = /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/gi;
|
|
1059
|
+
for (const m of text.matchAll(ibanRe)) {
|
|
1060
|
+
if (m.index == null) continue;
|
|
1061
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "IBAN", text: m[0] });
|
|
1062
|
+
}
|
|
1063
|
+
const ccRe = /(?:\b\d[\d -]{10,22}\d\b)/g;
|
|
1064
|
+
for (const m of text.matchAll(ccRe)) {
|
|
1065
|
+
if (m.index == null) continue;
|
|
1066
|
+
const digits = countDigits2(m[0]);
|
|
1067
|
+
if (digits < 12 || digits > 19) continue;
|
|
1068
|
+
if (!luhnCheck(m[0])) continue;
|
|
1069
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "CREDIT_CARD", text: m[0] });
|
|
1070
|
+
}
|
|
1071
|
+
const phoneRe = /(?<!\d)(?:\+?\d[\d\s().-]{7,}\d)(?!\d)/g;
|
|
1072
|
+
for (const m of text.matchAll(phoneRe)) {
|
|
1073
|
+
if (m.index == null) continue;
|
|
1074
|
+
const candidate = m[0];
|
|
1075
|
+
const digits = countDigits2(candidate);
|
|
1076
|
+
if (digits < 9 || digits > 15) continue;
|
|
1077
|
+
const isStrongInternational = candidate.startsWith("+") || candidate.startsWith("00");
|
|
1078
|
+
if (!isStrongInternational) {
|
|
1079
|
+
const hasContext = hasPhoneContext(text, m.index);
|
|
1080
|
+
if (!hasContext) continue;
|
|
1081
|
+
}
|
|
1082
|
+
detections.push({ start: m.index, end: m.index + m[0].length, type: "PHONE", text: m[0] });
|
|
1083
|
+
}
|
|
1084
|
+
const personRe = /(?<!\p{L})\p{Lu}\p{Ll}{2,}\s+\p{Lu}\p{Ll}{2,}(?!\p{L})/gu;
|
|
1085
|
+
for (const m of text.matchAll(personRe)) {
|
|
1086
|
+
if (m.index == null) continue;
|
|
1087
|
+
const candidate = m[0];
|
|
1088
|
+
const contextWindow = buildContextWindow(text, m.index, candidate.length);
|
|
1089
|
+
if (!isLikelyPersonNameCandidate(candidate, contextWindow)) {
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
detections.push({ start: m.index, end: m.index + candidate.length, type: "PERSON", text: candidate });
|
|
1093
|
+
}
|
|
1094
|
+
const nationalIdMatches = detectNationalIdentifiers(text, {
|
|
1095
|
+
deadlineMs: defaultScanDeadlineMs,
|
|
1096
|
+
allowContextBirthNumberFallback: false
|
|
1097
|
+
});
|
|
1098
|
+
for (const match of nationalIdMatches) {
|
|
1099
|
+
if (match.start < 0 || match.end <= match.start) continue;
|
|
1100
|
+
detections.push({
|
|
1101
|
+
start: match.start,
|
|
1102
|
+
end: match.end,
|
|
1103
|
+
type: "NATIONAL_ID",
|
|
1104
|
+
text: text.slice(match.start, match.end)
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
const kept = normalizeDetections(text, detections);
|
|
1108
|
+
if (!kept.length) return { maskedText: text, mapping: {} };
|
|
1109
|
+
const counters = {};
|
|
1110
|
+
const mapping = {};
|
|
1111
|
+
const replacements = kept.map((d) => {
|
|
1112
|
+
counters[d.type] = (counters[d.type] ?? 0) + 1;
|
|
1113
|
+
const placeholder = `<${d.type}_${counters[d.type]}>`;
|
|
1114
|
+
mapping[placeholder] = d.text;
|
|
1115
|
+
return { ...d, placeholder };
|
|
1116
|
+
});
|
|
1117
|
+
let masked = text;
|
|
1118
|
+
for (const r of replacements.sort((a, b) => b.start - a.start)) {
|
|
1119
|
+
masked = masked.slice(0, r.start) + r.placeholder + masked.slice(r.end);
|
|
1120
|
+
}
|
|
1121
|
+
return { maskedText: masked, mapping };
|
|
1122
|
+
} catch {
|
|
1123
|
+
return { maskedText: text, mapping: {} };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
deanonymize(text, mapping) {
|
|
1127
|
+
if (!text || !mapping || !Object.keys(mapping).length) return text;
|
|
1128
|
+
try {
|
|
1129
|
+
const keys = Object.keys(mapping).sort((a, b) => b.length - a.length);
|
|
1130
|
+
let out = text;
|
|
1131
|
+
for (const key of keys) {
|
|
1132
|
+
out = out.split(key).join(mapping[key]);
|
|
1133
|
+
}
|
|
1134
|
+
return out;
|
|
1135
|
+
} catch {
|
|
1136
|
+
return text;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
201
1140
|
|
|
202
1141
|
// src/capability-config.ts
|
|
203
1142
|
var CONFIG_TTL_MS = 15 * 1e3;
|
|
@@ -213,6 +1152,7 @@ var SecurityBlockError = class extends Error {
|
|
|
213
1152
|
};
|
|
214
1153
|
|
|
215
1154
|
// src/langchain.ts
|
|
1155
|
+
var piiManager = new PIIManager();
|
|
216
1156
|
function safeString(val) {
|
|
217
1157
|
return typeof val === "string" ? val : "";
|
|
218
1158
|
}
|
|
@@ -414,6 +1354,83 @@ function extractOutputText(output) {
|
|
|
414
1354
|
const text = first?.text ?? first?.message?.content;
|
|
415
1355
|
return typeof text === "string" ? text : "";
|
|
416
1356
|
}
|
|
1357
|
+
function normalizePiiMapping(value) {
|
|
1358
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1359
|
+
return void 0;
|
|
1360
|
+
}
|
|
1361
|
+
const entries = Object.entries(value).filter(
|
|
1362
|
+
([placeholder, replacement]) => placeholder.length > 0 && typeof replacement === "string"
|
|
1363
|
+
);
|
|
1364
|
+
if (entries.length === 0) {
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
return Object.fromEntries(entries);
|
|
1368
|
+
}
|
|
1369
|
+
function deanonymizeText(text, mapping) {
|
|
1370
|
+
if (!text || !mapping) {
|
|
1371
|
+
return text;
|
|
1372
|
+
}
|
|
1373
|
+
return piiManager.deanonymize(text, mapping);
|
|
1374
|
+
}
|
|
1375
|
+
function deanonymizeContent(content, mapping) {
|
|
1376
|
+
if (!mapping) {
|
|
1377
|
+
return content;
|
|
1378
|
+
}
|
|
1379
|
+
if (typeof content === "string") {
|
|
1380
|
+
return deanonymizeText(content, mapping);
|
|
1381
|
+
}
|
|
1382
|
+
if (!Array.isArray(content)) {
|
|
1383
|
+
return content;
|
|
1384
|
+
}
|
|
1385
|
+
return content.map((item) => {
|
|
1386
|
+
if (typeof item === "string") {
|
|
1387
|
+
return deanonymizeText(item, mapping);
|
|
1388
|
+
}
|
|
1389
|
+
if (!item || typeof item !== "object") {
|
|
1390
|
+
return item;
|
|
1391
|
+
}
|
|
1392
|
+
const record = { ...item };
|
|
1393
|
+
if (typeof record.text === "string") {
|
|
1394
|
+
record.text = deanonymizeText(record.text, mapping);
|
|
1395
|
+
}
|
|
1396
|
+
if (typeof record.content === "string") {
|
|
1397
|
+
record.content = deanonymizeText(record.content, mapping);
|
|
1398
|
+
}
|
|
1399
|
+
return record;
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
function deanonymizeLangChainOutputForClient(output, mapping) {
|
|
1403
|
+
if (!mapping) {
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const generations = output?.generations;
|
|
1407
|
+
if (!Array.isArray(generations)) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
for (const generationGroup of generations) {
|
|
1411
|
+
if (!Array.isArray(generationGroup)) {
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
for (const generation of generationGroup) {
|
|
1415
|
+
if (!generation || typeof generation !== "object") {
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (typeof generation.text === "string") {
|
|
1419
|
+
generation.text = deanonymizeText(generation.text, mapping);
|
|
1420
|
+
}
|
|
1421
|
+
const message = generation.message;
|
|
1422
|
+
if (message && typeof message === "object") {
|
|
1423
|
+
const typedMessage = message;
|
|
1424
|
+
if ("content" in typedMessage) {
|
|
1425
|
+
typedMessage.content = deanonymizeContent(typedMessage.content, mapping);
|
|
1426
|
+
}
|
|
1427
|
+
if (typeof typedMessage.text === "string") {
|
|
1428
|
+
typedMessage.text = deanonymizeText(typedMessage.text, mapping);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
417
1434
|
function extractTokenUsage(output) {
|
|
418
1435
|
const llmOutput = output?.llmOutput ?? output?.llm_output;
|
|
419
1436
|
const usage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? void 0;
|
|
@@ -578,7 +1595,11 @@ var AgentIDCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
578
1595
|
model: modelName,
|
|
579
1596
|
clientEventId: canonicalClientEventId,
|
|
580
1597
|
guardEventId,
|
|
581
|
-
transparency
|
|
1598
|
+
transparency,
|
|
1599
|
+
piiMapping: normalizePiiMapping(prepared.piiMapping),
|
|
1600
|
+
shouldDeanonymize: prepared.shouldDeanonymize === true,
|
|
1601
|
+
responseStreamed: stream,
|
|
1602
|
+
sdkConfigVersion: prepared.capabilityConfig?.version ?? null
|
|
582
1603
|
});
|
|
583
1604
|
logCallbackDebug("handleLLMStart state_set", {
|
|
584
1605
|
runId: id,
|
|
@@ -667,7 +1688,11 @@ var AgentIDCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
667
1688
|
model: modelName,
|
|
668
1689
|
clientEventId: canonicalClientEventId,
|
|
669
1690
|
guardEventId,
|
|
670
|
-
transparency
|
|
1691
|
+
transparency,
|
|
1692
|
+
piiMapping: normalizePiiMapping(prepared.piiMapping),
|
|
1693
|
+
shouldDeanonymize: prepared.shouldDeanonymize === true,
|
|
1694
|
+
responseStreamed: stream,
|
|
1695
|
+
sdkConfigVersion: prepared.capabilityConfig?.version ?? null
|
|
671
1696
|
});
|
|
672
1697
|
logCallbackDebug("handleChatModelStart state_set", {
|
|
673
1698
|
runId: id,
|
|
@@ -689,7 +1714,11 @@ var AgentIDCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
689
1714
|
0,
|
|
690
1715
|
Date.now() - (state.pipelineStartedAtMs ?? state.startedAtMs)
|
|
691
1716
|
);
|
|
692
|
-
const
|
|
1717
|
+
const maskedOutputText = extractOutputText(output);
|
|
1718
|
+
if (state.shouldDeanonymize && state.piiMapping) {
|
|
1719
|
+
deanonymizeLangChainOutputForClient(output, state.piiMapping);
|
|
1720
|
+
}
|
|
1721
|
+
const clientOutputText = extractOutputText(output);
|
|
693
1722
|
const usage = extractTokenUsage(output);
|
|
694
1723
|
const metadata = {};
|
|
695
1724
|
if (state.clientEventId) {
|
|
@@ -709,13 +1738,19 @@ var AgentIDCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
709
1738
|
if (state.transparency) {
|
|
710
1739
|
metadata.transparency = state.transparency;
|
|
711
1740
|
}
|
|
1741
|
+
if (typeof state.sdkConfigVersion === "number" && Number.isFinite(state.sdkConfigVersion)) {
|
|
1742
|
+
metadata.sdk_config_version = Math.trunc(state.sdkConfigVersion);
|
|
1743
|
+
}
|
|
1744
|
+
metadata.response_streamed = state.responseStreamed === true;
|
|
1745
|
+
metadata.transformed_output = maskedOutputText;
|
|
1746
|
+
metadata.output_masked = maskedOutputText !== clientOutputText;
|
|
712
1747
|
metadata.model_latency_ms = modelLatencyMs;
|
|
713
1748
|
metadata.total_pipeline_latency_ms = totalPipelineLatencyMs;
|
|
714
1749
|
const resolvedModel = state.model ?? extractModelFromOutput(output) ?? "unknown";
|
|
715
1750
|
await this.agent.log({
|
|
716
1751
|
system_id: this.systemId,
|
|
717
1752
|
input: state.input,
|
|
718
|
-
output:
|
|
1753
|
+
output: maskedOutputText,
|
|
719
1754
|
event_id: state.clientEventId,
|
|
720
1755
|
model: resolvedModel,
|
|
721
1756
|
usage,
|