avantgate 1.0.0 → 1.1.0

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/index.js CHANGED
@@ -22,6 +22,9 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AvantGateControlLayer: () => AvantGateControlLayer,
24
24
  DEFAULT_MODEL_PRICES: () => DEFAULT_MODEL_PRICES,
25
+ PromptBuilder: () => PromptBuilder,
26
+ PromptRegistry: () => PromptRegistry,
27
+ PromptTemplate: () => PromptTemplate,
25
28
  ZenLLMControlLayer: () => AvantGateControlLayer,
26
29
  calculateCostUSD: () => calculateCostUSD,
27
30
  createAvantGate: () => createAvantGate,
@@ -84,8 +87,11 @@ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens
84
87
  // src/sanitizer.ts
85
88
  var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
86
89
  var PHONE_FR_REGEX = /\b(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}\b/g;
87
- var NIR_SSN_REGEX = /\b[12]\s*\d{2}\s*\d{2}\s*\d{2}\s*\d{3}\s*\d{3}(?:\s*\d{2})?\b/g;
88
- var IBAN_REGEX = /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g;
90
+ var NIR_SSN_REGEX = /\b[12]\s*\d{2}\s*(?:0[1-9]|1[0-2]|[2-9]\d)\s*(?:0[1-9]|[1-8]\d|9[0-8]|2[ABab])\s*(?!000)\d{3}\s*(?!000)\d{3}(?:\s*\d{2})?\b/g;
91
+ var SPI_LABELLED_REGEX = /(?:(?:num[ée]ro\s+fiscal|spi|n[°o]\s*fiscal|d[ée]clarant(?: fiscal)?)\s*[:=]?\s*)\b(\d{2}(?:[\s.-]?\d{2}){5}[\s.-]?\d|\d{13})\b/gi;
92
+ var SPI_FORMATTED_REGEX = /\b[0-3]\d(?:\s+\d{2}){5}\s+\d\b/g;
93
+ var IBAN_REGEX = /\b[A-Z]{2}\s*[0-9]{2}(?:[\s\r\n.-]*[A-Z0-9]){11,30}\b/g;
94
+ var BIC_LABELLED_REGEX = /(?:(?:bic|swift)\s*[:=]?\s*)\b([A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?)\b/gi;
89
95
  function sanitizePII(input) {
90
96
  let count = 0;
91
97
  let result = input;
@@ -97,13 +103,36 @@ function sanitizePII(input) {
97
103
  count++;
98
104
  return "[REDACTED_PHONE]";
99
105
  });
100
- result = result.replace(NIR_SSN_REGEX, () => {
106
+ result = result.replace(IBAN_REGEX, (match) => {
107
+ const cleanChars = match.replace(/[\s\r\n.-]/g, "");
108
+ if (cleanChars.length >= 15 && cleanChars.length <= 34) {
109
+ count++;
110
+ return "[REDACTED_IBAN]";
111
+ }
112
+ return match;
113
+ });
114
+ result = result.replace(BIC_LABELLED_REGEX, (_, bicCode) => {
101
115
  count++;
102
- return "[REDACTED_NIR]";
116
+ return `[REDACTED_BIC: ${bicCode.slice(0, 4)}****]`;
103
117
  });
104
- result = result.replace(IBAN_REGEX, () => {
118
+ result = result.replace(SPI_LABELLED_REGEX, (fullMatch, digits) => {
105
119
  count++;
106
- return "[REDACTED_IBAN]";
120
+ return fullMatch.replace(digits, "[REDACTED_SPI]");
121
+ });
122
+ result = result.replace(NIR_SSN_REGEX, (match) => {
123
+ const rawDigits = match.replace(/\s+/g, "");
124
+ if (rawDigits.length === 13 || rawDigits.length === 15) {
125
+ count++;
126
+ return "[REDACTED_NIR]";
127
+ }
128
+ return match;
129
+ });
130
+ result = result.replace(SPI_FORMATTED_REGEX, (match) => {
131
+ if (!match.includes("[REDACTED")) {
132
+ count++;
133
+ return "[REDACTED_SPI]";
134
+ }
135
+ return match;
107
136
  });
108
137
  return { text: result, maskedCount: count };
109
138
  }
@@ -138,6 +167,469 @@ function validateUserInput(input, options) {
138
167
  return { valid: true };
139
168
  }
140
169
 
170
+ // src/finance/strategies/french-pcg.strategy.ts
171
+ var FrenchPCGStrategy = class {
172
+ jurisdictionCode = "FR";
173
+ standard = "PCG";
174
+ defaultCurrency = "EUR";
175
+ cleanNumber(value) {
176
+ if (typeof value === "number") {
177
+ return Number.isFinite(value) ? value : 0;
178
+ }
179
+ if (!value || typeof value !== "string") {
180
+ return 0;
181
+ }
182
+ let str = value.trim();
183
+ let isNegative = false;
184
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
185
+ if (parenMatch) {
186
+ isNegative = true;
187
+ str = parenMatch[1];
188
+ } else if (str.startsWith("-")) {
189
+ isNegative = true;
190
+ str = str.substring(1).trim();
191
+ }
192
+ let multiplier = 1;
193
+ if (/[kK](?:€|eur)?\b/i.test(str)) {
194
+ multiplier = 1e3;
195
+ str = str.replace(/[kK](?:€|eur)?\b/gi, "").trim();
196
+ } else if (/[mM](?:€|eur)?\b/i.test(str)) {
197
+ multiplier = 1e6;
198
+ str = str.replace(/[mM](?:€|eur)?\b/gi, "").trim();
199
+ }
200
+ str = str.replace(/[€$£]/g, "").replace(/\s+/g, "").replace(/\u00A0/g, "").trim();
201
+ str = str.replace(",", ".");
202
+ const parsed = parseFloat(str);
203
+ if (isNaN(parsed)) {
204
+ return 0;
205
+ }
206
+ const finalValue = parsed * multiplier;
207
+ return isNegative ? -Math.abs(finalValue) : finalValue;
208
+ }
209
+ cleanJSON(rawText) {
210
+ let result = rawText;
211
+ result = result.replace(
212
+ /(:\s*)\(\s*([0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€)?)\s*\)/gi,
213
+ (_, prefix, amountStr) => {
214
+ const num = this.cleanNumber(`(${amountStr})`);
215
+ return `${prefix}${num}`;
216
+ }
217
+ );
218
+ result = result.replace(
219
+ /(:\s*)"\s*\(\s*([0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€)?)\s*\)\s*"/gi,
220
+ (_, prefix, amountStr) => {
221
+ const num = this.cleanNumber(`(${amountStr})`);
222
+ return `${prefix}${num}`;
223
+ }
224
+ );
225
+ result = result.replace(
226
+ /(:\s*)"\s*([+-]?[0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€))\s*"/gi,
227
+ (_, prefix, amountStr) => {
228
+ const num = this.cleanNumber(amountStr);
229
+ return `${prefix}${num}`;
230
+ }
231
+ );
232
+ result = result.replace(
233
+ /(:\s*)"\s*([+-]?(?:[0-9]{1,3}(?:\s+[0-9]{3})+(?:,[0-9]+)?|[0-9]+,[0-9]+))\s*"/g,
234
+ (_, prefix, amountStr) => {
235
+ const num = this.cleanNumber(amountStr);
236
+ return `${prefix}${num}`;
237
+ }
238
+ );
239
+ return result;
240
+ }
241
+ detect(text) {
242
+ const lower = text.toLowerCase();
243
+ const frPatterns = [
244
+ /\bcerfa\b/i,
245
+ /\bliasse\s+fiscale\b/i,
246
+ /\bbilan\s+(actif|passif)\b/i,
247
+ /\bcompte\s+de\s+r[ée]sultat\b/i,
248
+ /\bplan\s+comptable\s+g[ée]n[ée]ral\b/i,
249
+ /\bpcg\b/i,
250
+ /\bsiren\b/i,
251
+ /\bsiret\b/i,
252
+ /\b[0-9\s.,]+(?:k€|m€)\b/i,
253
+ /\beur\b/i,
254
+ /€/
255
+ ];
256
+ return frPatterns.some((pattern) => pattern.test(lower));
257
+ }
258
+ };
259
+
260
+ // src/finance/strategies/us-gaap.strategy.ts
261
+ var UsGAAPStrategy = class {
262
+ jurisdictionCode = "US";
263
+ standard = "US_GAAP";
264
+ defaultCurrency = "USD";
265
+ cleanNumber(value) {
266
+ if (typeof value === "number") {
267
+ return Number.isFinite(value) ? value : 0;
268
+ }
269
+ if (!value || typeof value !== "string") {
270
+ return 0;
271
+ }
272
+ let str = value.trim();
273
+ let isNegative = false;
274
+ const parenMatch = str.match(/^[(\[]\s*(.+?)\s*[)\]]$/);
275
+ if (parenMatch) {
276
+ isNegative = true;
277
+ str = parenMatch[1];
278
+ } else if (str.startsWith("-")) {
279
+ isNegative = true;
280
+ str = str.substring(1).trim();
281
+ }
282
+ let multiplier = 1;
283
+ if (/[kK]\b/.test(str)) {
284
+ multiplier = 1e3;
285
+ str = str.replace(/[kK]\b/g, "").trim();
286
+ } else if (/[mM]\b/.test(str)) {
287
+ multiplier = 1e6;
288
+ str = str.replace(/[mM]\b/g, "").trim();
289
+ } else if (/[bB]\b/.test(str)) {
290
+ multiplier = 1e9;
291
+ str = str.replace(/[bB]\b/g, "").trim();
292
+ }
293
+ str = str.replace(/[$]/g, "").replace(/\busd\b/gi, "").replace(/,/g, "").replace(/\s+/g, "").trim();
294
+ const parsed = parseFloat(str);
295
+ if (isNaN(parsed)) {
296
+ return 0;
297
+ }
298
+ const finalVal = parsed * multiplier;
299
+ return isNegative ? -Math.abs(finalVal) : finalVal;
300
+ }
301
+ cleanJSON(rawText) {
302
+ let result = rawText;
303
+ result = result.replace(
304
+ /(:\s*)[(\[]\s*([0-9][0-9,.]*(?:k|m|b|\$)?)\s*[)\]]/gi,
305
+ (_, prefix, amountStr) => {
306
+ const num = this.cleanNumber(`(${amountStr})`);
307
+ return `${prefix}${num}`;
308
+ }
309
+ );
310
+ result = result.replace(
311
+ /(:\s*)"\s*[(\[]\s*([0-9][0-9,.]*(?:k|m|b|\$)?)\s*[)\]]\s*"/gi,
312
+ (_, prefix, amountStr) => {
313
+ const num = this.cleanNumber(`(${amountStr})`);
314
+ return `${prefix}${num}`;
315
+ }
316
+ );
317
+ result = result.replace(
318
+ /(:\s*)"\s*([+-]?\$?\s*[0-9][0-9,.]*\s*(?:k|m|b|\$)?)\s*"/gi,
319
+ (_, prefix, amountStr) => {
320
+ if (!amountStr.includes("$") && !/[kmb]/i.test(amountStr)) {
321
+ return `${prefix}"${amountStr}"`;
322
+ }
323
+ const num = this.cleanNumber(amountStr);
324
+ return `${prefix}${num}`;
325
+ }
326
+ );
327
+ return result;
328
+ }
329
+ detect(text) {
330
+ const lower = text.toLowerCase();
331
+ const usPatterns = [
332
+ /\b10-[kq]\b/i,
333
+ /\bsec\s+filing\b/i,
334
+ /\bus[\s_-]?gaap\b/i,
335
+ /\bbalance\s+sheet\b/i,
336
+ /\bincome\s+statement\b/i,
337
+ /\bstatement\s+of\s+cash\s+flows\b/i,
338
+ /\$|usd/i
339
+ ];
340
+ return usPatterns.some((pattern) => pattern.test(lower));
341
+ }
342
+ };
343
+
344
+ // src/finance/strategies/uk-ifrs.strategy.ts
345
+ var UkIFRSStrategy = class {
346
+ jurisdictionCode = "UK";
347
+ standard = "IFRS";
348
+ defaultCurrency = "GBP";
349
+ cleanNumber(value) {
350
+ if (typeof value === "number") {
351
+ return Number.isFinite(value) ? value : 0;
352
+ }
353
+ if (!value || typeof value !== "string") {
354
+ return 0;
355
+ }
356
+ let str = value.trim();
357
+ let isNegative = false;
358
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
359
+ if (parenMatch) {
360
+ isNegative = true;
361
+ str = parenMatch[1];
362
+ } else if (str.startsWith("-")) {
363
+ isNegative = true;
364
+ str = str.substring(1).trim();
365
+ }
366
+ let multiplier = 1;
367
+ if (/[kK]\b/.test(str)) {
368
+ multiplier = 1e3;
369
+ str = str.replace(/[kK]\b/g, "").trim();
370
+ } else if (/[mM]\b/.test(str)) {
371
+ multiplier = 1e6;
372
+ str = str.replace(/[mM]\b/g, "").trim();
373
+ }
374
+ str = str.replace(/[£]/g, "").replace(/\bgbp\b/gi, "").replace(/,/g, "").replace(/\s+/g, "").trim();
375
+ const parsed = parseFloat(str);
376
+ if (isNaN(parsed)) {
377
+ return 0;
378
+ }
379
+ const finalVal = parsed * multiplier;
380
+ return isNegative ? -Math.abs(finalVal) : finalVal;
381
+ }
382
+ cleanJSON(rawText) {
383
+ let result = rawText;
384
+ result = result.replace(
385
+ /(:\s*)\(\s*([0-9][0-9,.]*(?:k|m|£)?)\s*\)/gi,
386
+ (_, prefix, amountStr) => {
387
+ const num = this.cleanNumber(`(${amountStr})`);
388
+ return `${prefix}${num}`;
389
+ }
390
+ );
391
+ result = result.replace(
392
+ /(:\s*)"\s*\(\s*([0-9][0-9,.]*(?:k|m|£)?)\s*\)\s*"/gi,
393
+ (_, prefix, amountStr) => {
394
+ const num = this.cleanNumber(`(${amountStr})`);
395
+ return `${prefix}${num}`;
396
+ }
397
+ );
398
+ result = result.replace(
399
+ /(:\s*)"\s*([+-]?£\s*[0-9][0-9,.]*\s*(?:k|m)?)\s*"/gi,
400
+ (_, prefix, amountStr) => {
401
+ const num = this.cleanNumber(amountStr);
402
+ return `${prefix}${num}`;
403
+ }
404
+ );
405
+ return result;
406
+ }
407
+ detect(text) {
408
+ const lower = text.toLowerCase();
409
+ const ukPatterns = [
410
+ /\bcompanies\s+house\b/i,
411
+ /\bfrs\s*102\b/i,
412
+ /\bhmrc\b/i,
413
+ /\bprofit\s+and\s+loss\b/i,
414
+ /£|\bgbp\b/i
415
+ ];
416
+ return ukPatterns.some((pattern) => pattern.test(lower));
417
+ }
418
+ };
419
+
420
+ // src/finance/strategies/swiss-co.strategy.ts
421
+ var SwissCOStrategy = class {
422
+ jurisdictionCode = "CH";
423
+ standard = "SWISS_CO";
424
+ defaultCurrency = "CHF";
425
+ cleanNumber(value) {
426
+ if (typeof value === "number") {
427
+ return Number.isFinite(value) ? value : 0;
428
+ }
429
+ if (!value || typeof value !== "string") {
430
+ return 0;
431
+ }
432
+ let str = value.trim();
433
+ let isNegative = false;
434
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
435
+ if (parenMatch) {
436
+ isNegative = true;
437
+ str = parenMatch[1];
438
+ } else if (str.startsWith("-")) {
439
+ isNegative = true;
440
+ str = str.substring(1).trim();
441
+ }
442
+ let multiplier = 1;
443
+ if (/[kK]\b/.test(str)) {
444
+ multiplier = 1e3;
445
+ str = str.replace(/[kK]\b/g, "").trim();
446
+ } else if (/[mM]\b/.test(str)) {
447
+ multiplier = 1e6;
448
+ str = str.replace(/[mM]\b/g, "").trim();
449
+ }
450
+ str = str.replace(/\bchf\b/gi, "").replace(/['’]/g, "").replace(/\s+/g, "").trim();
451
+ str = str.replace(",", ".");
452
+ const parsed = parseFloat(str);
453
+ if (isNaN(parsed)) {
454
+ return 0;
455
+ }
456
+ const finalVal = parsed * multiplier;
457
+ return isNegative ? -Math.abs(finalVal) : finalVal;
458
+ }
459
+ cleanJSON(rawText) {
460
+ let result = rawText;
461
+ result = result.replace(
462
+ /(:\s*)\(\s*([0-9][0-9'’.,\s]*(?:k|m|chf)?)\s*\)/gi,
463
+ (_, prefix, amountStr) => {
464
+ const num = this.cleanNumber(`(${amountStr})`);
465
+ return `${prefix}${num}`;
466
+ }
467
+ );
468
+ result = result.replace(
469
+ /(:\s*)"\s*\(\s*([0-9][0-9'’.,\s]*(?:k|m|chf)?)\s*\)\s*"/gi,
470
+ (_, prefix, amountStr) => {
471
+ const num = this.cleanNumber(`(${amountStr})`);
472
+ return `${prefix}${num}`;
473
+ }
474
+ );
475
+ result = result.replace(
476
+ /(:\s*)"\s*([+-]?[0-9][0-9'’.,\s]*(?:k|m|chf))\s*"/gi,
477
+ (_, prefix, amountStr) => {
478
+ const num = this.cleanNumber(amountStr);
479
+ return `${prefix}${num}`;
480
+ }
481
+ );
482
+ return result;
483
+ }
484
+ detect(text) {
485
+ const lower = text.toLowerCase();
486
+ const chPatterns = [
487
+ /\bcode\s+des\s+obligations\b/i,
488
+ /\bart\.?\s*725\b/i,
489
+ /\bche-[0-9]{3}\.[0-9]{3}\.[0-9]{3}\b/i,
490
+ /\bchf\b/i,
491
+ /\b[0-9]{1,3}(?:'[0-9]{3})+\b/
492
+ ];
493
+ return chPatterns.some((pattern) => pattern.test(lower));
494
+ }
495
+ };
496
+
497
+ // src/finance/strategies/international.strategy.ts
498
+ var InternationalAccountingStrategy = class {
499
+ jurisdictionCode = "INTERNATIONAL";
500
+ standard = "OTHER";
501
+ defaultCurrency = "EUR";
502
+ cleanNumber(value) {
503
+ if (typeof value === "number") {
504
+ return Number.isFinite(value) ? value : 0;
505
+ }
506
+ if (!value || typeof value !== "string") {
507
+ return 0;
508
+ }
509
+ let str = value.trim();
510
+ let isNegative = false;
511
+ const parenMatch = str.match(/^[(\[]\s*(.+?)\s*[)\]]$/);
512
+ if (parenMatch) {
513
+ isNegative = true;
514
+ str = parenMatch[1];
515
+ } else if (str.startsWith("-")) {
516
+ isNegative = true;
517
+ str = str.substring(1).trim();
518
+ }
519
+ let multiplier = 1;
520
+ if (/[kK]\b/.test(str)) {
521
+ multiplier = 1e3;
522
+ str = str.replace(/[kK]\b/g, "").trim();
523
+ } else if (/[mM]\b/.test(str)) {
524
+ multiplier = 1e6;
525
+ str = str.replace(/[mM]\b/g, "").trim();
526
+ }
527
+ str = str.replace(/[€$£]/g, "").replace(/\b(?:eur|usd|gbp|chf)\b/gi, "").trim();
528
+ if (str.includes(",") && str.includes(".")) {
529
+ if (str.lastIndexOf(",") > str.lastIndexOf(".")) {
530
+ str = str.replace(/\./g, "").replace(",", ".");
531
+ } else {
532
+ str = str.replace(/,/g, "");
533
+ }
534
+ } else if (str.includes(",")) {
535
+ if (/,\d{1,2}$/.test(str)) {
536
+ str = str.replace(",", ".");
537
+ } else {
538
+ str = str.replace(",", "");
539
+ }
540
+ }
541
+ str = str.replace(/\s+/g, "");
542
+ const parsed = parseFloat(str);
543
+ if (isNaN(parsed)) {
544
+ return 0;
545
+ }
546
+ const finalVal = parsed * multiplier;
547
+ return isNegative ? -Math.abs(finalVal) : finalVal;
548
+ }
549
+ cleanJSON(rawText) {
550
+ let result = rawText;
551
+ result = result.replace(
552
+ /(:\s*)[(\[]\s*([0-9][0-9,.\s]*(?:k|m|€|\$|£)?)\s*[)\]]/gi,
553
+ (_, prefix, amountStr) => {
554
+ const num = this.cleanNumber(`(${amountStr})`);
555
+ return `${prefix}${num}`;
556
+ }
557
+ );
558
+ result = result.replace(
559
+ /(:\s*)"\s*[(\[]\s*([0-9][0-9,.\s]*(?:k|m|€|\$|£)?)\s*[)\]]\s*"/gi,
560
+ (_, prefix, amountStr) => {
561
+ const num = this.cleanNumber(`(${amountStr})`);
562
+ return `${prefix}${num}`;
563
+ }
564
+ );
565
+ return result;
566
+ }
567
+ detect(_text) {
568
+ return true;
569
+ }
570
+ };
571
+
572
+ // src/finance/factory.ts
573
+ var AccountingFactory = class {
574
+ static strategies = /* @__PURE__ */ new Map();
575
+ static detectionOrder = [];
576
+ static {
577
+ this.initDefaults();
578
+ }
579
+ static initDefaults() {
580
+ const fr = new FrenchPCGStrategy();
581
+ const us = new UsGAAPStrategy();
582
+ const uk = new UkIFRSStrategy();
583
+ const ch = new SwissCOStrategy();
584
+ const intl = new InternationalAccountingStrategy();
585
+ this.registerStrategy(ch);
586
+ this.registerStrategy(uk);
587
+ this.registerStrategy(us);
588
+ this.registerStrategy(fr);
589
+ this.registerStrategy(intl);
590
+ }
591
+ static registerStrategy(strategy) {
592
+ this.strategies.set(strategy.jurisdictionCode, strategy);
593
+ this.detectionOrder = [
594
+ strategy,
595
+ ...this.detectionOrder.filter((s) => s.jurisdictionCode !== strategy.jurisdictionCode)
596
+ ];
597
+ }
598
+ static getStrategy(code) {
599
+ if (!code) {
600
+ return this.strategies.get("FR") || new FrenchPCGStrategy();
601
+ }
602
+ const strategy = this.strategies.get(code);
603
+ if (!strategy) {
604
+ return this.strategies.get("FR") || new FrenchPCGStrategy();
605
+ }
606
+ return strategy;
607
+ }
608
+ static detectStrategy(documentText) {
609
+ for (const strategy of this.detectionOrder) {
610
+ if (strategy.jurisdictionCode !== "INTERNATIONAL" && strategy.detect(documentText)) {
611
+ return strategy;
612
+ }
613
+ }
614
+ return this.getStrategy("FR");
615
+ }
616
+ static resetDefaults() {
617
+ this.strategies.clear();
618
+ this.detectionOrder = [];
619
+ this.initDefaults();
620
+ }
621
+ };
622
+
623
+ // src/finance/normalizer.ts
624
+ function cleanFinancialJSON(rawText, options) {
625
+ if (!rawText) {
626
+ return "";
627
+ }
628
+ const jsonBlock = extractAndCleanJSON(rawText);
629
+ const strategy = options?.jurisdiction ? AccountingFactory.getStrategy(options.jurisdiction) : options?.autoDetect !== false ? AccountingFactory.detectStrategy(rawText) : AccountingFactory.getStrategy("FR");
630
+ return strategy.cleanJSON(jsonBlock);
631
+ }
632
+
141
633
  // src/response-validator.ts
142
634
  function extractAndCleanJSON(rawText) {
143
635
  let cleaned = rawText.trim();
@@ -164,12 +656,17 @@ function extractAndCleanJSON(rawText) {
164
656
  }
165
657
  return cleaned;
166
658
  }
167
- function validateWithZod(rawText, schema) {
168
- const jsonString = extractAndCleanJSON(rawText);
659
+ function validateWithZod(rawText, schema, options) {
660
+ let jsonString = extractAndCleanJSON(rawText);
661
+ if (options?.normalizer) {
662
+ jsonString = options.normalizer(jsonString);
663
+ } else if (options?.financialNormalizer) {
664
+ jsonString = cleanFinancialJSON(jsonString, { jurisdiction: options.jurisdiction });
665
+ }
169
666
  let parsed;
170
667
  try {
171
668
  parsed = JSON.parse(jsonString);
172
- } catch (error) {
669
+ } catch (_error) {
173
670
  const sanitized = jsonString.replace(/,\s*([}\]])/g, "$1").replace(/'/g, '"');
174
671
  parsed = JSON.parse(sanitized);
175
672
  }
@@ -248,14 +745,15 @@ var AvantGateControlLayer = class {
248
745
  attempts: 1
249
746
  };
250
747
  }
251
- async executeProviderPipeline(messages, query, temperature) {
748
+ async executeProviderPipeline(messages, query, temperature, modelOverride) {
252
749
  const chain = this.getProviderChain();
253
750
  let lastError;
254
751
  for (let index = 0; index < chain.length; index++) {
255
752
  const providerConfig = chain[index];
753
+ const targetModel = index === 0 && modelOverride ? modelOverride : providerConfig.model;
256
754
  try {
257
755
  const response = await providerConfig.client.complete({
258
- model: providerConfig.model,
756
+ model: targetModel,
259
757
  messages,
260
758
  temperature: temperature ?? 0.2
261
759
  });
@@ -263,7 +761,7 @@ var AvantGateControlLayer = class {
263
761
  return {
264
762
  responseText: response.text,
265
763
  usage,
266
- modelUsed: providerConfig.model,
764
+ modelUsed: targetModel,
267
765
  failoverOccurred: index > 0,
268
766
  attempts: index + 1
269
767
  };
@@ -331,7 +829,13 @@ var AvantGateControlLayer = class {
331
829
  temperature: options.temperature ?? 0.1,
332
830
  providerOverride: options.providerOverride
333
831
  });
334
- const parsedData = validateWithZod(rawResult.text, options.schema);
832
+ const isFinancial = Boolean(
833
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
834
+ );
835
+ const parsedData = validateWithZod(rawResult.text, options.schema, {
836
+ financialNormalizer: isFinancial,
837
+ jurisdiction: this.config.features?.finance?.jurisdiction
838
+ });
335
839
  return {
336
840
  data: parsedData,
337
841
  rawText: rawResult.text,
@@ -341,15 +845,427 @@ var AvantGateControlLayer = class {
341
845
  failoverOccurred: rawResult.failoverOccurred
342
846
  };
343
847
  }
848
+ /**
849
+ * Méthode unifiée de premier niveau pour l'extraction structurée sans code boilerplate.
850
+ * Gère le failover multi-fournisseurs, les retries, la validation Zod et la normalisation financière.
851
+ */
852
+ async generateStructuredOutput(options) {
853
+ const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
854
+ const modelToUse = options.model ?? this.config.primary.model;
855
+ const processedMessages = options.messages.map((msg) => {
856
+ if (msg.role === "user") {
857
+ return { ...msg, content: this.applySecurityGuards(msg.content) };
858
+ }
859
+ return msg;
860
+ });
861
+ let lastError;
862
+ let accumulatedPromptTokens = 0;
863
+ let accumulatedCompletionTokens = 0;
864
+ let failoverOccurred = false;
865
+ let modelUsed = modelToUse;
866
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
867
+ try {
868
+ let responseText = "";
869
+ let attemptPromptTokens = 0;
870
+ let attemptCompletionTokens = 0;
871
+ if (options.providerOverride) {
872
+ const res = await options.providerOverride.complete({
873
+ model: modelToUse,
874
+ messages: processedMessages,
875
+ temperature: options.temperature ?? 0.1
876
+ });
877
+ responseText = res.text;
878
+ const usage = this.resolveTokens(res.usage, JSON.stringify(processedMessages), responseText);
879
+ attemptPromptTokens = usage.promptTokens;
880
+ attemptCompletionTokens = usage.completionTokens;
881
+ modelUsed = modelToUse;
882
+ } else if (this.getProviderChain().length === 0) {
883
+ const sim = this.executeSimulation(JSON.stringify(processedMessages));
884
+ responseText = sim.text;
885
+ attemptPromptTokens = sim.tokens.prompt;
886
+ attemptCompletionTokens = sim.tokens.completion;
887
+ } else {
888
+ const output = await this.executeProviderPipeline(
889
+ processedMessages,
890
+ JSON.stringify(processedMessages),
891
+ options.temperature ?? 0.1,
892
+ options.model
893
+ );
894
+ responseText = output.responseText;
895
+ attemptPromptTokens = output.usage.promptTokens;
896
+ attemptCompletionTokens = output.usage.completionTokens;
897
+ modelUsed = output.modelUsed;
898
+ failoverOccurred = output.failoverOccurred;
899
+ }
900
+ accumulatedPromptTokens += attemptPromptTokens;
901
+ accumulatedCompletionTokens += attemptCompletionTokens;
902
+ const isFinancial = options.financialNormalizer ?? Boolean(
903
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
904
+ );
905
+ const parsedData = validateWithZod(responseText, options.schema, {
906
+ financialNormalizer: isFinancial,
907
+ jurisdiction: this.config.features?.finance?.jurisdiction
908
+ });
909
+ const totalTokens = accumulatedPromptTokens + accumulatedCompletionTokens;
910
+ const costUSD = calculateCostUSD(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
911
+ const result = {
912
+ data: parsedData,
913
+ rawText: responseText,
914
+ tokens: {
915
+ prompt: accumulatedPromptTokens,
916
+ completion: accumulatedCompletionTokens,
917
+ total: totalTokens
918
+ },
919
+ costUSD,
920
+ modelUsed,
921
+ failoverOccurred
922
+ };
923
+ await this.notifyAuditSink({
924
+ text: responseText,
925
+ tokens: result.tokens,
926
+ costUSD: result.costUSD,
927
+ modelUsed: result.modelUsed,
928
+ failoverOccurred: result.failoverOccurred,
929
+ attempts: attempt + 1
930
+ });
931
+ return result;
932
+ } catch (err) {
933
+ lastError = err;
934
+ if (attempt < maxRetries) {
935
+ const delay = (this.config.retryOptions?.initialDelayMs ?? 200) * Math.pow(this.config.retryOptions?.backoffFactor ?? 1.5, attempt);
936
+ await new Promise((resolve) => setTimeout(resolve, delay));
937
+ }
938
+ }
939
+ }
940
+ throw lastError;
941
+ }
344
942
  };
345
943
  function createLLMControlLayer(config) {
346
944
  return new AvantGateControlLayer(config);
347
945
  }
348
946
  var createAvantGate = createLLMControlLayer;
947
+
948
+ // src/prompts/prompt-template.ts
949
+ var DEFAULT_JAILBREAK_PATTERNS = [
950
+ /ignore\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
951
+ /disregard\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
952
+ /system\s+override/i,
953
+ /you\s+are\s+now\s+(unrestricted|DAN|jailbroken)/i,
954
+ /<\s*\|\s*im_start\s*\|>/i,
955
+ /\[SYSTEM_PROMPT\]/i
956
+ ];
957
+ var PromptTemplate = class {
958
+ id;
959
+ version;
960
+ label;
961
+ description;
962
+ template;
963
+ inputSchema;
964
+ blockPatterns;
965
+ antiInjectionEnabled;
966
+ customSanitizer;
967
+ constructor(options) {
968
+ this.id = options.id;
969
+ this.version = options.version;
970
+ this.label = options.label || "production";
971
+ this.description = options.description;
972
+ this.template = options.template;
973
+ this.inputSchema = options.inputSchema;
974
+ this.antiInjectionEnabled = options.antiInjection?.enabled !== false;
975
+ this.blockPatterns = options.antiInjection?.blockPatterns || DEFAULT_JAILBREAK_PATTERNS;
976
+ this.customSanitizer = options.antiInjection?.sanitizer;
977
+ }
978
+ validateUserInput(variables) {
979
+ const threats = [];
980
+ if (this.inputSchema) {
981
+ const parsed = this.inputSchema.safeParse(variables);
982
+ if (!parsed.success) {
983
+ return {
984
+ isValid: false,
985
+ threats: parsed.error.issues.map(
986
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
987
+ )
988
+ };
989
+ }
990
+ }
991
+ if (this.antiInjectionEnabled) {
992
+ for (const [key, val] of Object.entries(variables)) {
993
+ if (typeof val === "string") {
994
+ for (const pattern of this.blockPatterns) {
995
+ if (pattern.test(val)) {
996
+ threats.push(
997
+ `Potential prompt injection in variable "${key}" matching pattern ${pattern}`
998
+ );
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+ return { isValid: threats.length === 0, threats };
1005
+ }
1006
+ format(variables) {
1007
+ const check = this.validateUserInput(variables);
1008
+ if (!check.isValid) {
1009
+ throw new Error(
1010
+ `[PromptTemplate:${this.id}] Validation failed: ${check.threats.join(", ")}`
1011
+ );
1012
+ }
1013
+ const interpolated = this.template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
1014
+ let value = variables[key];
1015
+ if (value === void 0 || value === null) {
1016
+ return "";
1017
+ }
1018
+ let stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
1019
+ if (this.customSanitizer) {
1020
+ stringValue = this.customSanitizer(stringValue);
1021
+ }
1022
+ return stringValue;
1023
+ });
1024
+ return interpolated.split("\n").filter((line, index, arr) => line.trim() !== "" || index > 0 && arr[index - 1].trim() !== "").join("\n");
1025
+ }
1026
+ };
1027
+
1028
+ // src/prompts/prompt-builder.ts
1029
+ var import_zod = require("zod");
1030
+ var PromptBuilder = class {
1031
+ personaSlot = "";
1032
+ rulesSlot = [];
1033
+ retryHintSlot = "";
1034
+ fewShotSlot = [];
1035
+ pinnedFactsSlot = "";
1036
+ contextSlot = "";
1037
+ userPayloadSlot = "";
1038
+ schemaContractText = "";
1039
+ constructor(templateOrId) {
1040
+ if (templateOrId instanceof PromptTemplate) {
1041
+ this.personaSlot = templateOrId.template;
1042
+ } else if (typeof templateOrId === "string") {
1043
+ this.personaSlot = templateOrId.trim();
1044
+ }
1045
+ }
1046
+ withPersona(persona) {
1047
+ this.personaSlot = persona.trim();
1048
+ return this;
1049
+ }
1050
+ withRules(rules) {
1051
+ if (Array.isArray(rules)) {
1052
+ this.rulesSlot.push(...rules.map((rule) => rule.trim()).filter(Boolean));
1053
+ } else if (rules) {
1054
+ this.rulesSlot.push(rules.trim());
1055
+ }
1056
+ return this;
1057
+ }
1058
+ withRetryHint(hint) {
1059
+ this.retryHintSlot = hint.trim();
1060
+ return this;
1061
+ }
1062
+ withFewShot(examples) {
1063
+ this.fewShotSlot = examples;
1064
+ return this;
1065
+ }
1066
+ withPinnedFacts(facts) {
1067
+ if (!facts) return this;
1068
+ if (typeof facts === "string") {
1069
+ this.pinnedFactsSlot = facts.trim();
1070
+ return this;
1071
+ }
1072
+ this.pinnedFactsSlot = Object.entries(facts).filter(([, val]) => val !== void 0 && val !== null && val !== "").map(([key, val]) => `\u2022 ${key} : ${Array.isArray(val) ? val.join(", ") : String(val)}`).join("\n");
1073
+ return this;
1074
+ }
1075
+ withContext(context) {
1076
+ this.contextSlot = context.trim();
1077
+ return this;
1078
+ }
1079
+ withUserPayload(payload) {
1080
+ this.userPayloadSlot = payload.trim();
1081
+ return this;
1082
+ }
1083
+ schemaContract(schema, options) {
1084
+ let shapeDescription = "JSON Object";
1085
+ if (schema instanceof import_zod.z.ZodObject) {
1086
+ shapeDescription = JSON.stringify(Object.keys(schema.shape));
1087
+ }
1088
+ this.schemaContractText = [
1089
+ `[DIRECTIVE DE CONTRAT DE SORTIE JSON STRICT]`,
1090
+ `Tu DOIS renvoyer UNIQUEMENT un objet JSON valide, sans balises markdown (\`\`\`json), sans texte avant ou apr\xE8s.`,
1091
+ `Champs obligatoires et types attendus :`,
1092
+ options?.schemaName ? `Sch\xE9ma : ${options.schemaName}` : "",
1093
+ shapeDescription
1094
+ ].filter(Boolean).join("\n");
1095
+ return this;
1096
+ }
1097
+ assembleMessages(pinnedFacts, context, userPayload) {
1098
+ const messages = [];
1099
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
1100
+ if (this.schemaContractText) {
1101
+ system0 += `
1102
+
1103
+ ${this.schemaContractText}`;
1104
+ }
1105
+ messages.push({ role: "system", content: system0 });
1106
+ const dynamicParts = [];
1107
+ if (this.rulesSlot.length > 0) {
1108
+ dynamicParts.push(
1109
+ `[R\xC8GLES ET CONSIGNES M\xC9TIER]
1110
+ ${this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n")}`
1111
+ );
1112
+ }
1113
+ if (this.retryHintSlot) {
1114
+ dynamicParts.push(`[INSTRUCTION DE CORRECTION / RETRY]
1115
+ ${this.retryHintSlot}`);
1116
+ }
1117
+ if (dynamicParts.length > 0) {
1118
+ messages.push({ role: "system", content: dynamicParts.join("\n\n") });
1119
+ }
1120
+ if (this.fewShotSlot.length > 0) {
1121
+ const examplesContent = this.fewShotSlot.map(
1122
+ (ex, idx) => `[Exemple ${idx + 1}]
1123
+ Question: ${ex.question}
1124
+ R\xE9ponse attendue: ${ex.answer}`
1125
+ ).join("\n\n");
1126
+ messages.push({ role: "system", content: `[EXEMPLES DE R\xC9F\xC9RENCE]
1127
+ ${examplesContent}` });
1128
+ }
1129
+ const userParts = [];
1130
+ if (pinnedFacts) {
1131
+ userParts.push(`[FAITS STRUCTUR\xC9S]
1132
+ ${pinnedFacts}`);
1133
+ }
1134
+ if (context) {
1135
+ userParts.push(`[CONTEXTE DOCUMENTAIRE]
1136
+ ${context}`);
1137
+ }
1138
+ if (userPayload) {
1139
+ const labelNeeded = Boolean(pinnedFacts || context);
1140
+ userParts.push(labelNeeded ? `[DONN\xC9ES \xC0 ANALYSER]
1141
+ ${userPayload}` : userPayload);
1142
+ }
1143
+ messages.push({ role: "user", content: userParts.join("\n\n") });
1144
+ return messages;
1145
+ }
1146
+ toMessages() {
1147
+ return this.assembleMessages(this.pinnedFactsSlot, this.contextSlot, this.userPayloadSlot);
1148
+ }
1149
+ build(budget) {
1150
+ if (!budget) {
1151
+ const messages2 = this.toMessages();
1152
+ const promptText2 = messages2.map((msg) => `${msg.role.toUpperCase()}:
1153
+ ${msg.content}`).join("\n\n");
1154
+ return {
1155
+ messages: messages2,
1156
+ promptText: promptText2,
1157
+ allocatedTokens: Math.ceil(promptText2.length / 4),
1158
+ isTruncated: false
1159
+ };
1160
+ }
1161
+ budget.reset();
1162
+ let isTruncated = false;
1163
+ let truncatedSlot;
1164
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
1165
+ if (this.schemaContractText) {
1166
+ system0 += `
1167
+
1168
+ ${this.schemaContractText}`;
1169
+ }
1170
+ budget.forceReserve("persona", system0);
1171
+ if (this.rulesSlot.length > 0 || this.retryHintSlot) {
1172
+ const rulesText = this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n");
1173
+ budget.forceReserve("rules", `${rulesText}
1174
+ ${this.retryHintSlot}`);
1175
+ }
1176
+ let payloadToUse = this.userPayloadSlot;
1177
+ let factsToUse = this.pinnedFactsSlot;
1178
+ let contextToUse = this.contextSlot;
1179
+ if (payloadToUse) {
1180
+ const payloadCost = budget.count(payloadToUse);
1181
+ if (payloadCost > budget.remaining()) {
1182
+ const charLimit = Math.max(100, budget.remaining() * 4);
1183
+ payloadToUse = payloadToUse.slice(0, charLimit);
1184
+ isTruncated = true;
1185
+ truncatedSlot = "user";
1186
+ }
1187
+ budget.reserve("user", payloadToUse);
1188
+ }
1189
+ if (factsToUse) {
1190
+ const factsCost = budget.count(factsToUse);
1191
+ if (factsCost > budget.remaining()) {
1192
+ const charLimit = Math.max(50, budget.remaining() * 4);
1193
+ factsToUse = factsToUse.slice(0, charLimit);
1194
+ isTruncated = true;
1195
+ truncatedSlot = "pinnedFacts";
1196
+ }
1197
+ budget.reserve("pinnedFacts", factsToUse);
1198
+ }
1199
+ if (contextToUse) {
1200
+ const contextCost = budget.count(contextToUse);
1201
+ if (contextCost > budget.remaining()) {
1202
+ const charLimit = Math.max(0, budget.remaining() * 4);
1203
+ contextToUse = contextToUse.slice(0, charLimit);
1204
+ isTruncated = true;
1205
+ truncatedSlot = "context";
1206
+ }
1207
+ budget.reserve("context", contextToUse);
1208
+ }
1209
+ const messages = this.assembleMessages(factsToUse, contextToUse, payloadToUse);
1210
+ const promptText = messages.map((msg) => `${msg.role.toUpperCase()}:
1211
+ ${msg.content}`).join("\n\n");
1212
+ return {
1213
+ messages,
1214
+ promptText,
1215
+ allocatedTokens: budget.getAllocated(),
1216
+ isTruncated,
1217
+ truncatedSlot
1218
+ };
1219
+ }
1220
+ };
1221
+
1222
+ // src/prompts/prompt-registry.ts
1223
+ var PromptRegistry = class {
1224
+ static registry = /* @__PURE__ */ new Map();
1225
+ static register(templateOrOptions) {
1226
+ const template = templateOrOptions instanceof PromptTemplate ? templateOrOptions : new PromptTemplate(templateOrOptions);
1227
+ const existing = this.registry.get(template.id) || [];
1228
+ const filtered = existing.filter((item) => item.version !== template.version);
1229
+ filtered.push(template);
1230
+ filtered.sort((a, b) => b.version - a.version);
1231
+ this.registry.set(template.id, filtered);
1232
+ }
1233
+ static get(id, options) {
1234
+ const templates = this.registry.get(id);
1235
+ if (!templates || templates.length === 0) {
1236
+ throw new Error(`[PromptRegistry] No prompt template registered with id: "${id}"`);
1237
+ }
1238
+ if (options?.version !== void 0) {
1239
+ const match = templates.find((item) => item.version === options.version);
1240
+ if (!match) {
1241
+ throw new Error(
1242
+ `[PromptRegistry] Template "${id}" with version ${options.version} not found`
1243
+ );
1244
+ }
1245
+ return match;
1246
+ }
1247
+ if (options?.label) {
1248
+ const match = templates.find((item) => item.label === options.label);
1249
+ if (match) {
1250
+ return match;
1251
+ }
1252
+ }
1253
+ return templates[0];
1254
+ }
1255
+ static has(id) {
1256
+ return this.registry.has(id);
1257
+ }
1258
+ static clear() {
1259
+ this.registry.clear();
1260
+ }
1261
+ };
349
1262
  // Annotate the CommonJS export names for ESM import in node:
350
1263
  0 && (module.exports = {
351
1264
  AvantGateControlLayer,
352
1265
  DEFAULT_MODEL_PRICES,
1266
+ PromptBuilder,
1267
+ PromptRegistry,
1268
+ PromptTemplate,
353
1269
  ZenLLMControlLayer,
354
1270
  calculateCostUSD,
355
1271
  createAvantGate,