avantgate 1.0.0 → 1.1.1

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
@@ -20,61 +20,236 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AvantGateBudgetExceededError: () => BudgetExceededError,
24
+ AvantGateConfigurationError: () => ConfigurationError,
23
25
  AvantGateControlLayer: () => AvantGateControlLayer,
26
+ BudgetExceededError: () => BudgetExceededError,
27
+ CachedPricingAdapter: () => CachedPricingAdapter,
28
+ ConfigurationError: () => ConfigurationError,
24
29
  DEFAULT_MODEL_PRICES: () => DEFAULT_MODEL_PRICES,
30
+ HttpProviderClient: () => HttpProviderClient,
31
+ PricingRegistry: () => PricingRegistry,
32
+ PromptBuilder: () => PromptBuilder,
33
+ PromptRegistry: () => PromptRegistry,
34
+ PromptTemplate: () => PromptTemplate,
35
+ SEED_MODEL_PRICES: () => SEED_MODEL_PRICES,
25
36
  ZenLLMControlLayer: () => AvantGateControlLayer,
26
37
  calculateCostUSD: () => calculateCostUSD,
27
38
  createAvantGate: () => createAvantGate,
39
+ createHttpProviderClient: () => createHttpProviderClient,
28
40
  createLLMControlLayer: () => createLLMControlLayer,
29
41
  extractAndCleanJSON: () => extractAndCleanJSON,
42
+ resolveModelPrice: () => resolveModelPrice,
43
+ resolveModelPriceAsync: () => resolveModelPriceAsync,
30
44
  sanitizePII: () => sanitizePII,
31
45
  validateUserInput: () => validateUserInput,
32
46
  validateWithZod: () => validateWithZod
33
47
  });
34
48
  module.exports = __toCommonJS(index_exports);
35
49
 
50
+ // src/types.ts
51
+ var ConfigurationError = class extends Error {
52
+ constructor(message) {
53
+ super(message);
54
+ this.name = "ConfigurationError";
55
+ }
56
+ };
57
+ var BudgetExceededError = class extends Error {
58
+ constructor(message) {
59
+ super(message);
60
+ this.name = "BudgetExceededError";
61
+ }
62
+ };
63
+
36
64
  // src/pricing.ts
37
- var DEFAULT_MODEL_PRICES = {
38
- // DeepSeek
39
- "deepseek-chat": {
40
- promptUSDPerMillion: 0.14,
41
- completionUSDPerMillion: 0.28,
42
- cacheHitUSDPerMillion: 0.014
43
- },
44
- "deepseek-reasoner": {
45
- promptUSDPerMillion: 0.55,
46
- completionUSDPerMillion: 2.19,
47
- cacheHitUSDPerMillion: 0.14
48
- },
49
- // Mistral
50
- "mistral-small-latest": {
51
- promptUSDPerMillion: 0.2,
52
- completionUSDPerMillion: 0.6
53
- },
54
- "mistral-large-latest": {
55
- promptUSDPerMillion: 2,
56
- completionUSDPerMillion: 6
65
+ var SEED_MODEL_PRICES = {
66
+ // DeepSeek Direct
67
+ "deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
68
+ "deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
69
+ "deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
70
+ "deepseek/deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
71
+ // Mistral AI Direct
72
+ "mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
73
+ "mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
74
+ "mistral/mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
75
+ "mistral/mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
76
+ // OpenAI Direct
77
+ "gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
78
+ "gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
79
+ "openai/gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
80
+ "openai/gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
81
+ // OpenRouter Direct
82
+ "openrouter/deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28 },
83
+ "openrouter/deepseek/deepseek-r1": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19 },
84
+ "openrouter/anthropic/claude-3.5-sonnet": { promptUSDPerMillion: 3, completionUSDPerMillion: 15 },
85
+ // Local Ollama (Toujours $0)
86
+ "ollama": { promptUSDPerMillion: 0, completionUSDPerMillion: 0 }
87
+ };
88
+ function normalizeKey(identifier) {
89
+ return identifier.trim().toLowerCase().replace(":", "/");
90
+ }
91
+ var CachedPricingAdapter = class {
92
+ cache = /* @__PURE__ */ new Map();
93
+ ttlMs;
94
+ delegate;
95
+ constructor(delegate, ttlMs = 5 * 60 * 1e3) {
96
+ this.delegate = delegate;
97
+ this.ttlMs = ttlMs;
98
+ }
99
+ async fetchPrice(model, provider) {
100
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
101
+ const cached = this.cache.get(key);
102
+ if (cached && Date.now() < cached.expiresAt) {
103
+ return cached.price;
104
+ }
105
+ try {
106
+ const freshPrice = await this.delegate.fetchPrice(model, provider);
107
+ if (freshPrice) {
108
+ this.cache.set(key, { price: freshPrice, expiresAt: Date.now() + this.ttlMs });
109
+ return freshPrice;
110
+ }
111
+ } catch {
112
+ if (cached) {
113
+ return cached.price;
114
+ }
115
+ }
116
+ return void 0;
117
+ }
118
+ peek(model, provider) {
119
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
120
+ return this.cache.get(key)?.price;
121
+ }
122
+ clearCache() {
123
+ this.cache.clear();
124
+ }
125
+ invalidate(model, provider) {
126
+ if (!model) {
127
+ this.clearCache();
128
+ return;
129
+ }
130
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
131
+ this.cache.delete(key);
132
+ }
133
+ };
134
+ var PricingRegistry = class {
135
+ static prices = /* @__PURE__ */ new Map();
136
+ static adapter;
137
+ static registerPrice(identifier, price) {
138
+ this.prices.set(normalizeKey(identifier), price);
139
+ }
140
+ static registerDistributorPrices(distributor, priceMap) {
141
+ for (const [model, price] of Object.entries(priceMap)) {
142
+ this.prices.set(normalizeKey(`${distributor}/${model}`), price);
143
+ }
144
+ }
145
+ static registerPrices(prices) {
146
+ for (const [key, price] of Object.entries(prices)) {
147
+ this.prices.set(normalizeKey(key), price);
148
+ }
149
+ }
150
+ static getPrice(model, provider) {
151
+ if (provider) {
152
+ const distributorKey = normalizeKey(`${provider}/${model}`);
153
+ const directDistributorPrice = this.prices.get(distributorKey);
154
+ if (directDistributorPrice) {
155
+ return directDistributorPrice;
156
+ }
157
+ }
158
+ return this.prices.get(normalizeKey(model));
159
+ }
160
+ static setAdapter(adapter) {
161
+ this.adapter = adapter;
162
+ }
163
+ static getAdapter() {
164
+ return this.adapter;
165
+ }
166
+ static clear() {
167
+ this.prices.clear();
168
+ this.adapter = void 0;
169
+ }
170
+ /**
171
+ * Réinitialise le registre en chargeant le catalogue d'exemple SEED_MODEL_PRICES.
172
+ */
173
+ static loadSeedPrices() {
174
+ this.registerPrices(SEED_MODEL_PRICES);
175
+ }
176
+ static getAllPrices() {
177
+ const out = {};
178
+ for (const [k, v] of this.prices.entries()) {
179
+ out[k] = v;
180
+ }
181
+ return out;
182
+ }
183
+ };
184
+ var DEFAULT_MODEL_PRICES = new Proxy({}, {
185
+ get(_target, prop) {
186
+ return PricingRegistry.getPrice(prop);
57
187
  },
58
- // OpenAI
59
- "gpt-4o-mini": {
60
- promptUSDPerMillion: 0.15,
61
- completionUSDPerMillion: 0.6
188
+ has(_target, prop) {
189
+ return Boolean(PricingRegistry.getPrice(prop));
62
190
  },
63
- "gpt-4o": {
64
- promptUSDPerMillion: 2.5,
65
- completionUSDPerMillion: 10
191
+ ownKeys() {
192
+ return Object.keys(PricingRegistry.getAllPrices());
66
193
  },
67
- // Ollama (local)
68
- "ollama": {
69
- promptUSDPerMillion: 0,
70
- completionUSDPerMillion: 0
194
+ getOwnPropertyDescriptor(_target, prop) {
195
+ const price = PricingRegistry.getPrice(prop);
196
+ if (price) {
197
+ return { value: price, enumerable: true, configurable: true, writable: false };
198
+ }
199
+ return void 0;
200
+ }
201
+ });
202
+ function resolveModelPrice(model, options) {
203
+ const opts = typeof options === "string" ? { provider: options } : options ?? {};
204
+ if (opts.providerPricing) {
205
+ return opts.providerPricing;
206
+ }
207
+ if (opts.customPricing) {
208
+ if (opts.provider) {
209
+ const customDistributorKey = normalizeKey(`${opts.provider}/${model}`);
210
+ if (opts.customPricing[customDistributorKey]) {
211
+ return opts.customPricing[customDistributorKey];
212
+ }
213
+ }
214
+ if (opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model]) {
215
+ return opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model];
216
+ }
217
+ }
218
+ const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
219
+ if (activeAdapter instanceof CachedPricingAdapter) {
220
+ const cached = activeAdapter.peek(model, opts.provider);
221
+ if (cached) {
222
+ return cached;
223
+ }
224
+ }
225
+ const registeredPrice = PricingRegistry.getPrice(model, opts.provider);
226
+ if (registeredPrice) {
227
+ return registeredPrice;
228
+ }
229
+ if (opts.provider === "ollama" || model.toLowerCase().includes("ollama")) {
230
+ return { promptUSDPerMillion: 0, completionUSDPerMillion: 0 };
231
+ }
232
+ return void 0;
233
+ }
234
+ async function resolveModelPriceAsync(model, options) {
235
+ const opts = typeof options === "string" ? { provider: options } : options ?? {};
236
+ const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
237
+ if (activeAdapter) {
238
+ try {
239
+ const adapterPrice = await activeAdapter.fetchPrice(model, opts.provider);
240
+ if (adapterPrice) {
241
+ return adapterPrice;
242
+ }
243
+ } catch {
244
+ }
245
+ }
246
+ return resolveModelPrice(model, options);
247
+ }
248
+ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0, options) {
249
+ const pricing = resolveModelPrice(model, options);
250
+ if (!pricing) {
251
+ return 0;
71
252
  }
72
- };
73
- function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0) {
74
- const pricing = DEFAULT_MODEL_PRICES[model] ?? {
75
- promptUSDPerMillion: 0.5,
76
- completionUSDPerMillion: 1.5
77
- };
78
253
  const promptCost = promptTokens / 1e6 * pricing.promptUSDPerMillion;
79
254
  const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
80
255
  const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
@@ -84,8 +259,11 @@ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens
84
259
  // src/sanitizer.ts
85
260
  var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
86
261
  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;
262
+ 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;
263
+ 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;
264
+ var SPI_FORMATTED_REGEX = /\b[0-3]\d(?:\s+\d{2}){5}\s+\d\b/g;
265
+ var IBAN_REGEX = /\b[A-Z]{2}\s*[0-9]{2}(?:[\s\r\n.-]*[A-Z0-9]){11,30}\b/g;
266
+ 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
267
  function sanitizePII(input) {
90
268
  let count = 0;
91
269
  let result = input;
@@ -97,13 +275,36 @@ function sanitizePII(input) {
97
275
  count++;
98
276
  return "[REDACTED_PHONE]";
99
277
  });
100
- result = result.replace(NIR_SSN_REGEX, () => {
278
+ result = result.replace(IBAN_REGEX, (match) => {
279
+ const cleanChars = match.replace(/[\s\r\n.-]/g, "");
280
+ if (cleanChars.length >= 15 && cleanChars.length <= 34) {
281
+ count++;
282
+ return "[REDACTED_IBAN]";
283
+ }
284
+ return match;
285
+ });
286
+ result = result.replace(BIC_LABELLED_REGEX, (_, bicCode) => {
101
287
  count++;
102
- return "[REDACTED_NIR]";
288
+ return `[REDACTED_BIC: ${bicCode.slice(0, 4)}****]`;
103
289
  });
104
- result = result.replace(IBAN_REGEX, () => {
290
+ result = result.replace(SPI_LABELLED_REGEX, (fullMatch, digits) => {
105
291
  count++;
106
- return "[REDACTED_IBAN]";
292
+ return fullMatch.replace(digits, "[REDACTED_SPI]");
293
+ });
294
+ result = result.replace(NIR_SSN_REGEX, (match) => {
295
+ const rawDigits = match.replace(/\s+/g, "");
296
+ if (rawDigits.length === 13 || rawDigits.length === 15) {
297
+ count++;
298
+ return "[REDACTED_NIR]";
299
+ }
300
+ return match;
301
+ });
302
+ result = result.replace(SPI_FORMATTED_REGEX, (match) => {
303
+ if (!match.includes("[REDACTED")) {
304
+ count++;
305
+ return "[REDACTED_SPI]";
306
+ }
307
+ return match;
107
308
  });
108
309
  return { text: result, maskedCount: count };
109
310
  }
@@ -138,6 +339,469 @@ function validateUserInput(input, options) {
138
339
  return { valid: true };
139
340
  }
140
341
 
342
+ // src/finance/strategies/french-pcg.strategy.ts
343
+ var FrenchPCGStrategy = class {
344
+ jurisdictionCode = "FR";
345
+ standard = "PCG";
346
+ defaultCurrency = "EUR";
347
+ cleanNumber(value) {
348
+ if (typeof value === "number") {
349
+ return Number.isFinite(value) ? value : 0;
350
+ }
351
+ if (!value || typeof value !== "string") {
352
+ return 0;
353
+ }
354
+ let str = value.trim();
355
+ let isNegative = false;
356
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
357
+ if (parenMatch) {
358
+ isNegative = true;
359
+ str = parenMatch[1];
360
+ } else if (str.startsWith("-")) {
361
+ isNegative = true;
362
+ str = str.substring(1).trim();
363
+ }
364
+ let multiplier = 1;
365
+ if (/[kK](?:€|eur)?\b/i.test(str)) {
366
+ multiplier = 1e3;
367
+ str = str.replace(/[kK](?:€|eur)?\b/gi, "").trim();
368
+ } else if (/[mM](?:€|eur)?\b/i.test(str)) {
369
+ multiplier = 1e6;
370
+ str = str.replace(/[mM](?:€|eur)?\b/gi, "").trim();
371
+ }
372
+ str = str.replace(/[€$£]/g, "").replace(/\s+/g, "").replace(/\u00A0/g, "").trim();
373
+ str = str.replace(",", ".");
374
+ const parsed = parseFloat(str);
375
+ if (isNaN(parsed)) {
376
+ return 0;
377
+ }
378
+ const finalValue = parsed * multiplier;
379
+ return isNegative ? -Math.abs(finalValue) : finalValue;
380
+ }
381
+ cleanJSON(rawText) {
382
+ let result = rawText;
383
+ result = result.replace(
384
+ /(:\s*)\(\s*([0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€)?)\s*\)/gi,
385
+ (_, prefix, amountStr) => {
386
+ const num = this.cleanNumber(`(${amountStr})`);
387
+ return `${prefix}${num}`;
388
+ }
389
+ );
390
+ result = result.replace(
391
+ /(:\s*)"\s*\(\s*([0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€)?)\s*\)\s*"/gi,
392
+ (_, prefix, amountStr) => {
393
+ const num = this.cleanNumber(`(${amountStr})`);
394
+ return `${prefix}${num}`;
395
+ }
396
+ );
397
+ result = result.replace(
398
+ /(:\s*)"\s*([+-]?[0-9][0-9\s.,]*(?:k€|k|keur|m€|m|meur|€))\s*"/gi,
399
+ (_, prefix, amountStr) => {
400
+ const num = this.cleanNumber(amountStr);
401
+ return `${prefix}${num}`;
402
+ }
403
+ );
404
+ result = result.replace(
405
+ /(:\s*)"\s*([+-]?(?:[0-9]{1,3}(?:\s+[0-9]{3})+(?:,[0-9]+)?|[0-9]+,[0-9]+))\s*"/g,
406
+ (_, prefix, amountStr) => {
407
+ const num = this.cleanNumber(amountStr);
408
+ return `${prefix}${num}`;
409
+ }
410
+ );
411
+ return result;
412
+ }
413
+ detect(text) {
414
+ const lower = text.toLowerCase();
415
+ const frPatterns = [
416
+ /\bcerfa\b/i,
417
+ /\bliasse\s+fiscale\b/i,
418
+ /\bbilan\s+(actif|passif)\b/i,
419
+ /\bcompte\s+de\s+r[ée]sultat\b/i,
420
+ /\bplan\s+comptable\s+g[ée]n[ée]ral\b/i,
421
+ /\bpcg\b/i,
422
+ /\bsiren\b/i,
423
+ /\bsiret\b/i,
424
+ /\b[0-9\s.,]+(?:k€|m€)\b/i,
425
+ /\beur\b/i,
426
+ /€/
427
+ ];
428
+ return frPatterns.some((pattern) => pattern.test(lower));
429
+ }
430
+ };
431
+
432
+ // src/finance/strategies/us-gaap.strategy.ts
433
+ var UsGAAPStrategy = class {
434
+ jurisdictionCode = "US";
435
+ standard = "US_GAAP";
436
+ defaultCurrency = "USD";
437
+ cleanNumber(value) {
438
+ if (typeof value === "number") {
439
+ return Number.isFinite(value) ? value : 0;
440
+ }
441
+ if (!value || typeof value !== "string") {
442
+ return 0;
443
+ }
444
+ let str = value.trim();
445
+ let isNegative = false;
446
+ const parenMatch = str.match(/^[(\[]\s*(.+?)\s*[)\]]$/);
447
+ if (parenMatch) {
448
+ isNegative = true;
449
+ str = parenMatch[1];
450
+ } else if (str.startsWith("-")) {
451
+ isNegative = true;
452
+ str = str.substring(1).trim();
453
+ }
454
+ let multiplier = 1;
455
+ if (/[kK]\b/.test(str)) {
456
+ multiplier = 1e3;
457
+ str = str.replace(/[kK]\b/g, "").trim();
458
+ } else if (/[mM]\b/.test(str)) {
459
+ multiplier = 1e6;
460
+ str = str.replace(/[mM]\b/g, "").trim();
461
+ } else if (/[bB]\b/.test(str)) {
462
+ multiplier = 1e9;
463
+ str = str.replace(/[bB]\b/g, "").trim();
464
+ }
465
+ str = str.replace(/[$]/g, "").replace(/\busd\b/gi, "").replace(/,/g, "").replace(/\s+/g, "").trim();
466
+ const parsed = parseFloat(str);
467
+ if (isNaN(parsed)) {
468
+ return 0;
469
+ }
470
+ const finalVal = parsed * multiplier;
471
+ return isNegative ? -Math.abs(finalVal) : finalVal;
472
+ }
473
+ cleanJSON(rawText) {
474
+ let result = rawText;
475
+ result = result.replace(
476
+ /(:\s*)[(\[]\s*([0-9][0-9,.]*(?:k|m|b|\$)?)\s*[)\]]/gi,
477
+ (_, prefix, amountStr) => {
478
+ const num = this.cleanNumber(`(${amountStr})`);
479
+ return `${prefix}${num}`;
480
+ }
481
+ );
482
+ result = result.replace(
483
+ /(:\s*)"\s*[(\[]\s*([0-9][0-9,.]*(?:k|m|b|\$)?)\s*[)\]]\s*"/gi,
484
+ (_, prefix, amountStr) => {
485
+ const num = this.cleanNumber(`(${amountStr})`);
486
+ return `${prefix}${num}`;
487
+ }
488
+ );
489
+ result = result.replace(
490
+ /(:\s*)"\s*([+-]?\$?\s*[0-9][0-9,.]*\s*(?:k|m|b|\$)?)\s*"/gi,
491
+ (_, prefix, amountStr) => {
492
+ if (!amountStr.includes("$") && !/[kmb]/i.test(amountStr)) {
493
+ return `${prefix}"${amountStr}"`;
494
+ }
495
+ const num = this.cleanNumber(amountStr);
496
+ return `${prefix}${num}`;
497
+ }
498
+ );
499
+ return result;
500
+ }
501
+ detect(text) {
502
+ const lower = text.toLowerCase();
503
+ const usPatterns = [
504
+ /\b10-[kq]\b/i,
505
+ /\bsec\s+filing\b/i,
506
+ /\bus[\s_-]?gaap\b/i,
507
+ /\bbalance\s+sheet\b/i,
508
+ /\bincome\s+statement\b/i,
509
+ /\bstatement\s+of\s+cash\s+flows\b/i,
510
+ /\$|usd/i
511
+ ];
512
+ return usPatterns.some((pattern) => pattern.test(lower));
513
+ }
514
+ };
515
+
516
+ // src/finance/strategies/uk-ifrs.strategy.ts
517
+ var UkIFRSStrategy = class {
518
+ jurisdictionCode = "UK";
519
+ standard = "IFRS";
520
+ defaultCurrency = "GBP";
521
+ cleanNumber(value) {
522
+ if (typeof value === "number") {
523
+ return Number.isFinite(value) ? value : 0;
524
+ }
525
+ if (!value || typeof value !== "string") {
526
+ return 0;
527
+ }
528
+ let str = value.trim();
529
+ let isNegative = false;
530
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
531
+ if (parenMatch) {
532
+ isNegative = true;
533
+ str = parenMatch[1];
534
+ } else if (str.startsWith("-")) {
535
+ isNegative = true;
536
+ str = str.substring(1).trim();
537
+ }
538
+ let multiplier = 1;
539
+ if (/[kK]\b/.test(str)) {
540
+ multiplier = 1e3;
541
+ str = str.replace(/[kK]\b/g, "").trim();
542
+ } else if (/[mM]\b/.test(str)) {
543
+ multiplier = 1e6;
544
+ str = str.replace(/[mM]\b/g, "").trim();
545
+ }
546
+ str = str.replace(/[£]/g, "").replace(/\bgbp\b/gi, "").replace(/,/g, "").replace(/\s+/g, "").trim();
547
+ const parsed = parseFloat(str);
548
+ if (isNaN(parsed)) {
549
+ return 0;
550
+ }
551
+ const finalVal = parsed * multiplier;
552
+ return isNegative ? -Math.abs(finalVal) : finalVal;
553
+ }
554
+ cleanJSON(rawText) {
555
+ let result = rawText;
556
+ result = result.replace(
557
+ /(:\s*)\(\s*([0-9][0-9,.]*(?:k|m|£)?)\s*\)/gi,
558
+ (_, prefix, amountStr) => {
559
+ const num = this.cleanNumber(`(${amountStr})`);
560
+ return `${prefix}${num}`;
561
+ }
562
+ );
563
+ result = result.replace(
564
+ /(:\s*)"\s*\(\s*([0-9][0-9,.]*(?:k|m|£)?)\s*\)\s*"/gi,
565
+ (_, prefix, amountStr) => {
566
+ const num = this.cleanNumber(`(${amountStr})`);
567
+ return `${prefix}${num}`;
568
+ }
569
+ );
570
+ result = result.replace(
571
+ /(:\s*)"\s*([+-]?£\s*[0-9][0-9,.]*\s*(?:k|m)?)\s*"/gi,
572
+ (_, prefix, amountStr) => {
573
+ const num = this.cleanNumber(amountStr);
574
+ return `${prefix}${num}`;
575
+ }
576
+ );
577
+ return result;
578
+ }
579
+ detect(text) {
580
+ const lower = text.toLowerCase();
581
+ const ukPatterns = [
582
+ /\bcompanies\s+house\b/i,
583
+ /\bfrs\s*102\b/i,
584
+ /\bhmrc\b/i,
585
+ /\bprofit\s+and\s+loss\b/i,
586
+ /£|\bgbp\b/i
587
+ ];
588
+ return ukPatterns.some((pattern) => pattern.test(lower));
589
+ }
590
+ };
591
+
592
+ // src/finance/strategies/swiss-co.strategy.ts
593
+ var SwissCOStrategy = class {
594
+ jurisdictionCode = "CH";
595
+ standard = "SWISS_CO";
596
+ defaultCurrency = "CHF";
597
+ cleanNumber(value) {
598
+ if (typeof value === "number") {
599
+ return Number.isFinite(value) ? value : 0;
600
+ }
601
+ if (!value || typeof value !== "string") {
602
+ return 0;
603
+ }
604
+ let str = value.trim();
605
+ let isNegative = false;
606
+ const parenMatch = str.match(/^\(\s*(.+?)\s*\)$/);
607
+ if (parenMatch) {
608
+ isNegative = true;
609
+ str = parenMatch[1];
610
+ } else if (str.startsWith("-")) {
611
+ isNegative = true;
612
+ str = str.substring(1).trim();
613
+ }
614
+ let multiplier = 1;
615
+ if (/[kK]\b/.test(str)) {
616
+ multiplier = 1e3;
617
+ str = str.replace(/[kK]\b/g, "").trim();
618
+ } else if (/[mM]\b/.test(str)) {
619
+ multiplier = 1e6;
620
+ str = str.replace(/[mM]\b/g, "").trim();
621
+ }
622
+ str = str.replace(/\bchf\b/gi, "").replace(/['’]/g, "").replace(/\s+/g, "").trim();
623
+ str = str.replace(",", ".");
624
+ const parsed = parseFloat(str);
625
+ if (isNaN(parsed)) {
626
+ return 0;
627
+ }
628
+ const finalVal = parsed * multiplier;
629
+ return isNegative ? -Math.abs(finalVal) : finalVal;
630
+ }
631
+ cleanJSON(rawText) {
632
+ let result = rawText;
633
+ result = result.replace(
634
+ /(:\s*)\(\s*([0-9][0-9'’.,\s]*(?:k|m|chf)?)\s*\)/gi,
635
+ (_, prefix, amountStr) => {
636
+ const num = this.cleanNumber(`(${amountStr})`);
637
+ return `${prefix}${num}`;
638
+ }
639
+ );
640
+ result = result.replace(
641
+ /(:\s*)"\s*\(\s*([0-9][0-9'’.,\s]*(?:k|m|chf)?)\s*\)\s*"/gi,
642
+ (_, prefix, amountStr) => {
643
+ const num = this.cleanNumber(`(${amountStr})`);
644
+ return `${prefix}${num}`;
645
+ }
646
+ );
647
+ result = result.replace(
648
+ /(:\s*)"\s*([+-]?[0-9][0-9'’.,\s]*(?:k|m|chf))\s*"/gi,
649
+ (_, prefix, amountStr) => {
650
+ const num = this.cleanNumber(amountStr);
651
+ return `${prefix}${num}`;
652
+ }
653
+ );
654
+ return result;
655
+ }
656
+ detect(text) {
657
+ const lower = text.toLowerCase();
658
+ const chPatterns = [
659
+ /\bcode\s+des\s+obligations\b/i,
660
+ /\bart\.?\s*725\b/i,
661
+ /\bche-[0-9]{3}\.[0-9]{3}\.[0-9]{3}\b/i,
662
+ /\bchf\b/i,
663
+ /\b[0-9]{1,3}(?:'[0-9]{3})+\b/
664
+ ];
665
+ return chPatterns.some((pattern) => pattern.test(lower));
666
+ }
667
+ };
668
+
669
+ // src/finance/strategies/international.strategy.ts
670
+ var InternationalAccountingStrategy = class {
671
+ jurisdictionCode = "INTERNATIONAL";
672
+ standard = "OTHER";
673
+ defaultCurrency = "EUR";
674
+ cleanNumber(value) {
675
+ if (typeof value === "number") {
676
+ return Number.isFinite(value) ? value : 0;
677
+ }
678
+ if (!value || typeof value !== "string") {
679
+ return 0;
680
+ }
681
+ let str = value.trim();
682
+ let isNegative = false;
683
+ const parenMatch = str.match(/^[(\[]\s*(.+?)\s*[)\]]$/);
684
+ if (parenMatch) {
685
+ isNegative = true;
686
+ str = parenMatch[1];
687
+ } else if (str.startsWith("-")) {
688
+ isNegative = true;
689
+ str = str.substring(1).trim();
690
+ }
691
+ let multiplier = 1;
692
+ if (/[kK]\b/.test(str)) {
693
+ multiplier = 1e3;
694
+ str = str.replace(/[kK]\b/g, "").trim();
695
+ } else if (/[mM]\b/.test(str)) {
696
+ multiplier = 1e6;
697
+ str = str.replace(/[mM]\b/g, "").trim();
698
+ }
699
+ str = str.replace(/[€$£]/g, "").replace(/\b(?:eur|usd|gbp|chf)\b/gi, "").trim();
700
+ if (str.includes(",") && str.includes(".")) {
701
+ if (str.lastIndexOf(",") > str.lastIndexOf(".")) {
702
+ str = str.replace(/\./g, "").replace(",", ".");
703
+ } else {
704
+ str = str.replace(/,/g, "");
705
+ }
706
+ } else if (str.includes(",")) {
707
+ if (/,\d{1,2}$/.test(str)) {
708
+ str = str.replace(",", ".");
709
+ } else {
710
+ str = str.replace(",", "");
711
+ }
712
+ }
713
+ str = str.replace(/\s+/g, "");
714
+ const parsed = parseFloat(str);
715
+ if (isNaN(parsed)) {
716
+ return 0;
717
+ }
718
+ const finalVal = parsed * multiplier;
719
+ return isNegative ? -Math.abs(finalVal) : finalVal;
720
+ }
721
+ cleanJSON(rawText) {
722
+ let result = rawText;
723
+ result = result.replace(
724
+ /(:\s*)[(\[]\s*([0-9][0-9,.\s]*(?:k|m|€|\$|£)?)\s*[)\]]/gi,
725
+ (_, prefix, amountStr) => {
726
+ const num = this.cleanNumber(`(${amountStr})`);
727
+ return `${prefix}${num}`;
728
+ }
729
+ );
730
+ result = result.replace(
731
+ /(:\s*)"\s*[(\[]\s*([0-9][0-9,.\s]*(?:k|m|€|\$|£)?)\s*[)\]]\s*"/gi,
732
+ (_, prefix, amountStr) => {
733
+ const num = this.cleanNumber(`(${amountStr})`);
734
+ return `${prefix}${num}`;
735
+ }
736
+ );
737
+ return result;
738
+ }
739
+ detect(_text) {
740
+ return true;
741
+ }
742
+ };
743
+
744
+ // src/finance/factory.ts
745
+ var AccountingFactory = class {
746
+ static strategies = /* @__PURE__ */ new Map();
747
+ static detectionOrder = [];
748
+ static {
749
+ this.initDefaults();
750
+ }
751
+ static initDefaults() {
752
+ const fr = new FrenchPCGStrategy();
753
+ const us = new UsGAAPStrategy();
754
+ const uk = new UkIFRSStrategy();
755
+ const ch = new SwissCOStrategy();
756
+ const intl = new InternationalAccountingStrategy();
757
+ this.registerStrategy(ch);
758
+ this.registerStrategy(uk);
759
+ this.registerStrategy(us);
760
+ this.registerStrategy(fr);
761
+ this.registerStrategy(intl);
762
+ }
763
+ static registerStrategy(strategy) {
764
+ this.strategies.set(strategy.jurisdictionCode, strategy);
765
+ this.detectionOrder = [
766
+ strategy,
767
+ ...this.detectionOrder.filter((s) => s.jurisdictionCode !== strategy.jurisdictionCode)
768
+ ];
769
+ }
770
+ static getStrategy(code) {
771
+ if (!code) {
772
+ return this.strategies.get("FR") || new FrenchPCGStrategy();
773
+ }
774
+ const strategy = this.strategies.get(code);
775
+ if (!strategy) {
776
+ return this.strategies.get("FR") || new FrenchPCGStrategy();
777
+ }
778
+ return strategy;
779
+ }
780
+ static detectStrategy(documentText) {
781
+ for (const strategy of this.detectionOrder) {
782
+ if (strategy.jurisdictionCode !== "INTERNATIONAL" && strategy.detect(documentText)) {
783
+ return strategy;
784
+ }
785
+ }
786
+ return this.getStrategy("FR");
787
+ }
788
+ static resetDefaults() {
789
+ this.strategies.clear();
790
+ this.detectionOrder = [];
791
+ this.initDefaults();
792
+ }
793
+ };
794
+
795
+ // src/finance/normalizer.ts
796
+ function cleanFinancialJSON(rawText, options) {
797
+ if (!rawText) {
798
+ return "";
799
+ }
800
+ const jsonBlock = extractAndCleanJSON(rawText);
801
+ const strategy = options?.jurisdiction ? AccountingFactory.getStrategy(options.jurisdiction) : options?.autoDetect !== false ? AccountingFactory.detectStrategy(rawText) : AccountingFactory.getStrategy("FR");
802
+ return strategy.cleanJSON(jsonBlock);
803
+ }
804
+
141
805
  // src/response-validator.ts
142
806
  function extractAndCleanJSON(rawText) {
143
807
  let cleaned = rawText.trim();
@@ -164,23 +828,161 @@ function extractAndCleanJSON(rawText) {
164
828
  }
165
829
  return cleaned;
166
830
  }
167
- function validateWithZod(rawText, schema) {
168
- const jsonString = extractAndCleanJSON(rawText);
831
+ function validateWithZod(rawText, schema, options) {
832
+ let jsonString = extractAndCleanJSON(rawText);
833
+ if (options?.normalizer) {
834
+ jsonString = options.normalizer(jsonString);
835
+ } else if (options?.financialNormalizer) {
836
+ jsonString = cleanFinancialJSON(jsonString, { jurisdiction: options.jurisdiction });
837
+ }
169
838
  let parsed;
170
839
  try {
171
840
  parsed = JSON.parse(jsonString);
172
- } catch (error) {
841
+ } catch (_error) {
173
842
  const sanitized = jsonString.replace(/,\s*([}\]])/g, "$1").replace(/'/g, '"');
174
843
  parsed = JSON.parse(sanitized);
175
844
  }
176
845
  return schema.parse(parsed);
177
846
  }
178
847
 
848
+ // src/providers/http-client.ts
849
+ var DEFAULT_BASE_URLS = {
850
+ deepseek: "https://api.deepseek.com/v1",
851
+ mistral: "https://api.mistral.ai/v1",
852
+ openai: "https://api.openai.com/v1",
853
+ ollama: "http://localhost:11434/v1",
854
+ openrouter: "https://openrouter.ai/api/v1"
855
+ };
856
+ var HttpProviderClient = class {
857
+ name;
858
+ baseUrl;
859
+ apiKey;
860
+ providerType;
861
+ constructor(config) {
862
+ this.providerType = config.provider;
863
+ this.name = `${config.provider}-http`;
864
+ this.apiKey = config.apiKey;
865
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
866
+ }
867
+ resolveEndpoint() {
868
+ const trimmed = this.baseUrl.replace(/\/+$/, "");
869
+ if (trimmed.endsWith("/chat/completions")) {
870
+ return trimmed;
871
+ }
872
+ return `${trimmed}/chat/completions`;
873
+ }
874
+ buildHeaders() {
875
+ const headers = {
876
+ "Content-Type": "application/json"
877
+ };
878
+ if (this.apiKey) {
879
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
880
+ }
881
+ if (this.providerType === "openrouter") {
882
+ headers["HTTP-Referer"] = "https://avantgate.dev";
883
+ headers["X-Title"] = "AvantGate";
884
+ }
885
+ return headers;
886
+ }
887
+ buildPayload(options, model) {
888
+ return {
889
+ model,
890
+ messages: options.messages.map((m) => ({
891
+ role: m.role,
892
+ content: m.content
893
+ })),
894
+ temperature: options.temperature ?? 0.2,
895
+ ...options.responseFormat ? { response_format: options.responseFormat } : {}
896
+ };
897
+ }
898
+ extractUsage(usageData) {
899
+ if (!usageData) {
900
+ return void 0;
901
+ }
902
+ return {
903
+ promptTokens: usageData.prompt_tokens,
904
+ completionTokens: usageData.completion_tokens,
905
+ totalTokens: usageData.total_tokens,
906
+ promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
907
+ };
908
+ }
909
+ async complete(options) {
910
+ const endpoint = this.resolveEndpoint();
911
+ const model = options.model ?? "default";
912
+ const headers = this.buildHeaders();
913
+ const body = JSON.stringify(this.buildPayload(options, model));
914
+ const response = await fetch(endpoint, {
915
+ method: "POST",
916
+ headers,
917
+ body
918
+ });
919
+ if (!response.ok) {
920
+ const errorText = await response.text();
921
+ throw new Error(
922
+ `[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
923
+ );
924
+ }
925
+ const data = await response.json();
926
+ const text = data.choices?.[0]?.message?.content ?? "";
927
+ const usage = this.extractUsage(data.usage);
928
+ return { text, usage };
929
+ }
930
+ };
931
+ function createHttpProviderClient(config) {
932
+ return new HttpProviderClient(config);
933
+ }
934
+
179
935
  // src/control-layer.ts
180
936
  var AvantGateControlLayer = class {
181
937
  config;
938
+ cachedPricingAdapter;
182
939
  constructor(config) {
183
- this.config = config;
940
+ this.config = {
941
+ ...config,
942
+ primary: this.resolveProviderConfig(config.primary),
943
+ fallback: this.resolveProviderConfig(config.fallback),
944
+ emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
945
+ };
946
+ if (config.pricingAdapter) {
947
+ this.cachedPricingAdapter = new CachedPricingAdapter(
948
+ config.pricingAdapter,
949
+ config.pricingCacheTtlMs ?? 5 * 60 * 1e3
950
+ );
951
+ }
952
+ }
953
+ resolveProviderConfig(provider) {
954
+ if (!provider) return void 0;
955
+ if (provider.client) return provider;
956
+ if (provider.apiKey || provider.baseUrl || provider.provider === "ollama") {
957
+ return {
958
+ ...provider,
959
+ client: createHttpProviderClient(provider)
960
+ };
961
+ }
962
+ return provider;
963
+ }
964
+ findProviderConfig(provider, model) {
965
+ const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
966
+ (p) => Boolean(p)
967
+ );
968
+ if (provider) {
969
+ const matchProvider = list.find((p) => p.provider === provider);
970
+ if (matchProvider) return matchProvider;
971
+ }
972
+ if (model) {
973
+ const matchModel = list.find((p) => p.model === model);
974
+ if (matchModel) return matchModel;
975
+ }
976
+ return this.config.primary;
977
+ }
978
+ calculateCost(model, promptTokens, completionTokens, cacheHitTokens = 0, provider) {
979
+ const providerCfg = this.findProviderConfig(provider, model);
980
+ return calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens, {
981
+ provider: provider ?? providerCfg?.provider,
982
+ providerPricing: providerCfg?.pricing,
983
+ customPricing: this.config.customPricing,
984
+ adapter: this.cachedPricingAdapter
985
+ });
184
986
  }
185
987
  applySecurityGuards(userQuery) {
186
988
  const guard = validateUserInput(userQuery, {
@@ -195,6 +997,46 @@ var AvantGateControlLayer = class {
195
997
  }
196
998
  return userQuery;
197
999
  }
1000
+ checkPreflightBudget(estimatedPromptTokens, targetModel, provider) {
1001
+ if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
1002
+ throw new BudgetExceededError(
1003
+ `[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
1004
+ );
1005
+ }
1006
+ if (this.config.maxCostUSD !== void 0) {
1007
+ const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
1008
+ const providerCfg = this.findProviderConfig(provider, targetModel);
1009
+ const price = resolveModelPrice(targetModel, {
1010
+ provider: provider ?? providerCfg?.provider,
1011
+ providerPricing: providerCfg?.pricing,
1012
+ customPricing: this.config.customPricing,
1013
+ adapter: this.cachedPricingAdapter
1014
+ });
1015
+ if (!isLocalFree && !price) {
1016
+ throw new ConfigurationError(
1017
+ `[AvantGate Configuration Error] 'maxCostUSD' was set to $${this.config.maxCostUSD}, but no pricing was configured for model '${targetModel}'. Please define pricing in ProviderConfig, customPricing, or via PricingAdapter.`
1018
+ );
1019
+ }
1020
+ const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
1021
+ if (estimatedPromptCost > this.config.maxCostUSD) {
1022
+ throw new BudgetExceededError(
1023
+ `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
1024
+ );
1025
+ }
1026
+ }
1027
+ }
1028
+ checkPostExecutionBudget(tokensTotal, costUSD) {
1029
+ if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
1030
+ throw new BudgetExceededError(
1031
+ `[AvantGate Budget Guard] Execution total tokens (${tokensTotal}) exceeded maxTokenBudget (${this.config.maxTokenBudget}).`
1032
+ );
1033
+ }
1034
+ if (this.config.maxCostUSD !== void 0 && costUSD > this.config.maxCostUSD) {
1035
+ throw new BudgetExceededError(
1036
+ `[AvantGate Budget Guard] Execution cost ($${costUSD.toFixed(6)}) exceeded maxCostUSD ($${this.config.maxCostUSD}).`
1037
+ );
1038
+ }
1039
+ }
198
1040
  buildMessages(systemPrompt, query) {
199
1041
  const messages = [];
200
1042
  if (systemPrompt) {
@@ -207,7 +1049,12 @@ var AvantGateControlLayer = class {
207
1049
  const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
208
1050
  const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
209
1051
  const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
210
- return { promptTokens, completionTokens, totalTokens };
1052
+ return {
1053
+ promptTokens,
1054
+ completionTokens,
1055
+ totalTokens,
1056
+ promptCacheHitTokens: rawUsage?.promptCacheHitTokens
1057
+ };
211
1058
  }
212
1059
  getProviderChain() {
213
1060
  return [
@@ -219,7 +1066,7 @@ var AvantGateControlLayer = class {
219
1066
  executeSimulation(query, systemPrompt) {
220
1067
  const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
221
1068
  const completionTokens = 50;
222
- const cost = calculateCostUSD(this.config.primary.model, promptTokens, completionTokens);
1069
+ const cost = this.calculateCost(this.config.primary.model, promptTokens, completionTokens);
223
1070
  return {
224
1071
  text: `[AvantGate In-Process Engine] Response simulation for model: ${this.config.primary.model}`,
225
1072
  tokens: {
@@ -248,14 +1095,15 @@ var AvantGateControlLayer = class {
248
1095
  attempts: 1
249
1096
  };
250
1097
  }
251
- async executeProviderPipeline(messages, query, temperature) {
1098
+ async executeProviderPipeline(messages, query, temperature, modelOverride) {
252
1099
  const chain = this.getProviderChain();
253
1100
  let lastError;
254
1101
  for (let index = 0; index < chain.length; index++) {
255
1102
  const providerConfig = chain[index];
1103
+ const targetModel = index === 0 && modelOverride ? modelOverride : providerConfig.model;
256
1104
  try {
257
1105
  const response = await providerConfig.client.complete({
258
- model: providerConfig.model,
1106
+ model: targetModel,
259
1107
  messages,
260
1108
  temperature: temperature ?? 0.2
261
1109
  });
@@ -263,7 +1111,7 @@ var AvantGateControlLayer = class {
263
1111
  return {
264
1112
  responseText: response.text,
265
1113
  usage,
266
- modelUsed: providerConfig.model,
1114
+ modelUsed: targetModel,
267
1115
  failoverOccurred: index > 0,
268
1116
  attempts: index + 1
269
1117
  };
@@ -274,10 +1122,11 @@ var AvantGateControlLayer = class {
274
1122
  throw lastError;
275
1123
  }
276
1124
  assembleResult(output) {
277
- const costUSD = calculateCostUSD(
1125
+ const costUSD = this.calculateCost(
278
1126
  output.modelUsed,
279
1127
  output.usage.promptTokens,
280
- output.usage.completionTokens
1128
+ output.usage.completionTokens,
1129
+ output.usage.promptCacheHitTokens ?? 0
281
1130
  );
282
1131
  return {
283
1132
  text: output.responseText,
@@ -306,18 +1155,27 @@ var AvantGateControlLayer = class {
306
1155
  });
307
1156
  }
308
1157
  /**
309
- * Exécute une requête avec garde d'entrée, masquage PII, et calcul des coûts.
1158
+ * Exécute une requête avec garde d'entrée, masquage PII, garde pré-vol et calcul des coûts.
310
1159
  */
311
1160
  async execute(options) {
312
1161
  const sanitizedQuery = this.applySecurityGuards(options.userQuery);
313
1162
  const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
1163
+ const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
1164
+ const estimatedPromptTokens = Math.ceil(promptLength / 4);
1165
+ this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
314
1166
  if (!options.providerOverride && this.getProviderChain().length === 0) {
315
- const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
316
- await this.notifyAuditSink(simResult);
317
- return simResult;
1167
+ if (this.config.mockSimulation) {
1168
+ const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
1169
+ await this.notifyAuditSink(simResult);
1170
+ return simResult;
1171
+ }
1172
+ throw new ConfigurationError(
1173
+ "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
1174
+ );
318
1175
  }
319
1176
  const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
320
1177
  const result = this.assembleResult(output);
1178
+ this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
321
1179
  await this.notifyAuditSink(result);
322
1180
  return result;
323
1181
  }
@@ -331,7 +1189,13 @@ var AvantGateControlLayer = class {
331
1189
  temperature: options.temperature ?? 0.1,
332
1190
  providerOverride: options.providerOverride
333
1191
  });
334
- const parsedData = validateWithZod(rawResult.text, options.schema);
1192
+ const isFinancial = Boolean(
1193
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
1194
+ );
1195
+ const parsedData = validateWithZod(rawResult.text, options.schema, {
1196
+ financialNormalizer: isFinancial,
1197
+ jurisdiction: this.config.features?.finance?.jurisdiction
1198
+ });
335
1199
  return {
336
1200
  data: parsedData,
337
1201
  rawText: rawResult.text,
@@ -341,20 +1205,453 @@ var AvantGateControlLayer = class {
341
1205
  failoverOccurred: rawResult.failoverOccurred
342
1206
  };
343
1207
  }
1208
+ /**
1209
+ * Méthode unifiée de premier niveau pour l'extraction structurée sans code boilerplate.
1210
+ * Gère le failover multi-fournisseurs, les retries, la validation Zod et la normalisation financière.
1211
+ */
1212
+ async generateStructuredOutput(options) {
1213
+ const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
1214
+ const modelToUse = options.model ?? this.config.primary.model;
1215
+ const processedMessages = options.messages.map((msg) => {
1216
+ if (msg.role === "user") {
1217
+ return { ...msg, content: this.applySecurityGuards(msg.content) };
1218
+ }
1219
+ return msg;
1220
+ });
1221
+ const promptLength = processedMessages.reduce((sum, msg) => sum + msg.content.length, 0);
1222
+ const estimatedPromptTokens = Math.ceil(promptLength / 4);
1223
+ this.checkPreflightBudget(estimatedPromptTokens, modelToUse);
1224
+ let lastError;
1225
+ let accumulatedPromptTokens = 0;
1226
+ let accumulatedCompletionTokens = 0;
1227
+ let failoverOccurred = false;
1228
+ let modelUsed = modelToUse;
1229
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1230
+ try {
1231
+ let responseText = "";
1232
+ let attemptPromptTokens = 0;
1233
+ let attemptCompletionTokens = 0;
1234
+ if (options.providerOverride) {
1235
+ const res = await options.providerOverride.complete({
1236
+ model: modelToUse,
1237
+ messages: processedMessages,
1238
+ temperature: options.temperature ?? 0.1
1239
+ });
1240
+ responseText = res.text;
1241
+ const usage = this.resolveTokens(res.usage, JSON.stringify(processedMessages), responseText);
1242
+ attemptPromptTokens = usage.promptTokens;
1243
+ attemptCompletionTokens = usage.completionTokens;
1244
+ modelUsed = modelToUse;
1245
+ } else if (this.getProviderChain().length === 0) {
1246
+ if (this.config.mockSimulation) {
1247
+ const sim = this.executeSimulation(JSON.stringify(processedMessages));
1248
+ responseText = sim.text;
1249
+ attemptPromptTokens = sim.tokens.prompt;
1250
+ attemptCompletionTokens = sim.tokens.completion;
1251
+ } else {
1252
+ throw new ConfigurationError(
1253
+ "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
1254
+ );
1255
+ }
1256
+ } else {
1257
+ const output = await this.executeProviderPipeline(
1258
+ processedMessages,
1259
+ JSON.stringify(processedMessages),
1260
+ options.temperature ?? 0.1,
1261
+ options.model
1262
+ );
1263
+ responseText = output.responseText;
1264
+ attemptPromptTokens = output.usage.promptTokens;
1265
+ attemptCompletionTokens = output.usage.completionTokens;
1266
+ modelUsed = output.modelUsed;
1267
+ failoverOccurred = output.failoverOccurred;
1268
+ }
1269
+ accumulatedPromptTokens += attemptPromptTokens;
1270
+ accumulatedCompletionTokens += attemptCompletionTokens;
1271
+ const isFinancial = options.financialNormalizer ?? Boolean(
1272
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
1273
+ );
1274
+ const parsedData = validateWithZod(responseText, options.schema, {
1275
+ financialNormalizer: isFinancial,
1276
+ jurisdiction: this.config.features?.finance?.jurisdiction
1277
+ });
1278
+ const totalTokens = accumulatedPromptTokens + accumulatedCompletionTokens;
1279
+ const costUSD = this.calculateCost(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
1280
+ this.checkPostExecutionBudget(totalTokens, costUSD);
1281
+ const result = {
1282
+ data: parsedData,
1283
+ rawText: responseText,
1284
+ tokens: {
1285
+ prompt: accumulatedPromptTokens,
1286
+ completion: accumulatedCompletionTokens,
1287
+ total: totalTokens
1288
+ },
1289
+ costUSD,
1290
+ modelUsed,
1291
+ failoverOccurred
1292
+ };
1293
+ await this.notifyAuditSink({
1294
+ text: responseText,
1295
+ tokens: result.tokens,
1296
+ costUSD: result.costUSD,
1297
+ modelUsed: result.modelUsed,
1298
+ failoverOccurred: result.failoverOccurred,
1299
+ attempts: attempt + 1
1300
+ });
1301
+ return result;
1302
+ } catch (err) {
1303
+ lastError = err;
1304
+ if (attempt < maxRetries) {
1305
+ const delay = (this.config.retryOptions?.initialDelayMs ?? 200) * Math.pow(this.config.retryOptions?.backoffFactor ?? 1.5, attempt);
1306
+ await new Promise((resolve) => setTimeout(resolve, delay));
1307
+ }
1308
+ }
1309
+ }
1310
+ throw lastError;
1311
+ }
344
1312
  };
345
1313
  function createLLMControlLayer(config) {
346
1314
  return new AvantGateControlLayer(config);
347
1315
  }
348
1316
  var createAvantGate = createLLMControlLayer;
1317
+
1318
+ // src/prompts/prompt-template.ts
1319
+ var DEFAULT_JAILBREAK_PATTERNS = [
1320
+ /ignore\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
1321
+ /disregard\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
1322
+ /system\s+override/i,
1323
+ /you\s+are\s+now\s+(unrestricted|DAN|jailbroken)/i,
1324
+ /<\s*\|\s*im_start\s*\|>/i,
1325
+ /\[SYSTEM_PROMPT\]/i
1326
+ ];
1327
+ var PromptTemplate = class {
1328
+ id;
1329
+ version;
1330
+ label;
1331
+ description;
1332
+ template;
1333
+ inputSchema;
1334
+ blockPatterns;
1335
+ antiInjectionEnabled;
1336
+ customSanitizer;
1337
+ constructor(options) {
1338
+ this.id = options.id;
1339
+ this.version = options.version;
1340
+ this.label = options.label || "production";
1341
+ this.description = options.description;
1342
+ this.template = options.template;
1343
+ this.inputSchema = options.inputSchema;
1344
+ this.antiInjectionEnabled = options.antiInjection?.enabled !== false;
1345
+ this.blockPatterns = options.antiInjection?.blockPatterns || DEFAULT_JAILBREAK_PATTERNS;
1346
+ this.customSanitizer = options.antiInjection?.sanitizer;
1347
+ }
1348
+ validateUserInput(variables) {
1349
+ const threats = [];
1350
+ if (this.inputSchema) {
1351
+ const parsed = this.inputSchema.safeParse(variables);
1352
+ if (!parsed.success) {
1353
+ return {
1354
+ isValid: false,
1355
+ threats: parsed.error.issues.map(
1356
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
1357
+ )
1358
+ };
1359
+ }
1360
+ }
1361
+ if (this.antiInjectionEnabled) {
1362
+ for (const [key, val] of Object.entries(variables)) {
1363
+ if (typeof val === "string") {
1364
+ for (const pattern of this.blockPatterns) {
1365
+ if (pattern.test(val)) {
1366
+ threats.push(
1367
+ `Potential prompt injection in variable "${key}" matching pattern ${pattern}`
1368
+ );
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ return { isValid: threats.length === 0, threats };
1375
+ }
1376
+ format(variables) {
1377
+ const check = this.validateUserInput(variables);
1378
+ if (!check.isValid) {
1379
+ throw new Error(
1380
+ `[PromptTemplate:${this.id}] Validation failed: ${check.threats.join(", ")}`
1381
+ );
1382
+ }
1383
+ const interpolated = this.template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
1384
+ let value = variables[key];
1385
+ if (value === void 0 || value === null) {
1386
+ return "";
1387
+ }
1388
+ let stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
1389
+ if (this.customSanitizer) {
1390
+ stringValue = this.customSanitizer(stringValue);
1391
+ }
1392
+ return stringValue;
1393
+ });
1394
+ return interpolated.split("\n").filter((line, index, arr) => line.trim() !== "" || index > 0 && arr[index - 1].trim() !== "").join("\n");
1395
+ }
1396
+ };
1397
+
1398
+ // src/prompts/prompt-builder.ts
1399
+ var import_zod = require("zod");
1400
+ var PromptBuilder = class {
1401
+ personaSlot = "";
1402
+ rulesSlot = [];
1403
+ retryHintSlot = "";
1404
+ fewShotSlot = [];
1405
+ pinnedFactsSlot = "";
1406
+ contextSlot = "";
1407
+ userPayloadSlot = "";
1408
+ schemaContractText = "";
1409
+ constructor(templateOrId) {
1410
+ if (templateOrId instanceof PromptTemplate) {
1411
+ this.personaSlot = templateOrId.template;
1412
+ } else if (typeof templateOrId === "string") {
1413
+ this.personaSlot = templateOrId.trim();
1414
+ }
1415
+ }
1416
+ withPersona(persona) {
1417
+ this.personaSlot = persona.trim();
1418
+ return this;
1419
+ }
1420
+ withRules(rules) {
1421
+ if (Array.isArray(rules)) {
1422
+ this.rulesSlot.push(...rules.map((rule) => rule.trim()).filter(Boolean));
1423
+ } else if (rules) {
1424
+ this.rulesSlot.push(rules.trim());
1425
+ }
1426
+ return this;
1427
+ }
1428
+ withRetryHint(hint) {
1429
+ this.retryHintSlot = hint.trim();
1430
+ return this;
1431
+ }
1432
+ withFewShot(examples) {
1433
+ this.fewShotSlot = examples;
1434
+ return this;
1435
+ }
1436
+ withPinnedFacts(facts) {
1437
+ if (!facts) return this;
1438
+ if (typeof facts === "string") {
1439
+ this.pinnedFactsSlot = facts.trim();
1440
+ return this;
1441
+ }
1442
+ 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");
1443
+ return this;
1444
+ }
1445
+ withContext(context) {
1446
+ this.contextSlot = context.trim();
1447
+ return this;
1448
+ }
1449
+ withUserPayload(payload) {
1450
+ this.userPayloadSlot = payload.trim();
1451
+ return this;
1452
+ }
1453
+ schemaContract(schema, options) {
1454
+ let shapeDescription = "JSON Object";
1455
+ if (schema instanceof import_zod.z.ZodObject) {
1456
+ shapeDescription = JSON.stringify(Object.keys(schema.shape));
1457
+ }
1458
+ this.schemaContractText = [
1459
+ `[DIRECTIVE DE CONTRAT DE SORTIE JSON STRICT]`,
1460
+ `Tu DOIS renvoyer UNIQUEMENT un objet JSON valide, sans balises markdown (\`\`\`json), sans texte avant ou apr\xE8s.`,
1461
+ `Champs obligatoires et types attendus :`,
1462
+ options?.schemaName ? `Sch\xE9ma : ${options.schemaName}` : "",
1463
+ shapeDescription
1464
+ ].filter(Boolean).join("\n");
1465
+ return this;
1466
+ }
1467
+ assembleMessages(pinnedFacts, context, userPayload) {
1468
+ const messages = [];
1469
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
1470
+ if (this.schemaContractText) {
1471
+ system0 += `
1472
+
1473
+ ${this.schemaContractText}`;
1474
+ }
1475
+ messages.push({ role: "system", content: system0 });
1476
+ const dynamicParts = [];
1477
+ if (this.rulesSlot.length > 0) {
1478
+ dynamicParts.push(
1479
+ `[R\xC8GLES ET CONSIGNES M\xC9TIER]
1480
+ ${this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n")}`
1481
+ );
1482
+ }
1483
+ if (this.retryHintSlot) {
1484
+ dynamicParts.push(`[INSTRUCTION DE CORRECTION / RETRY]
1485
+ ${this.retryHintSlot}`);
1486
+ }
1487
+ if (dynamicParts.length > 0) {
1488
+ messages.push({ role: "system", content: dynamicParts.join("\n\n") });
1489
+ }
1490
+ if (this.fewShotSlot.length > 0) {
1491
+ const examplesContent = this.fewShotSlot.map(
1492
+ (ex, idx) => `[Exemple ${idx + 1}]
1493
+ Question: ${ex.question}
1494
+ R\xE9ponse attendue: ${ex.answer}`
1495
+ ).join("\n\n");
1496
+ messages.push({ role: "system", content: `[EXEMPLES DE R\xC9F\xC9RENCE]
1497
+ ${examplesContent}` });
1498
+ }
1499
+ const userParts = [];
1500
+ if (pinnedFacts) {
1501
+ userParts.push(`[FAITS STRUCTUR\xC9S]
1502
+ ${pinnedFacts}`);
1503
+ }
1504
+ if (context) {
1505
+ userParts.push(`[CONTEXTE DOCUMENTAIRE]
1506
+ ${context}`);
1507
+ }
1508
+ if (userPayload) {
1509
+ const labelNeeded = Boolean(pinnedFacts || context);
1510
+ userParts.push(labelNeeded ? `[DONN\xC9ES \xC0 ANALYSER]
1511
+ ${userPayload}` : userPayload);
1512
+ }
1513
+ messages.push({ role: "user", content: userParts.join("\n\n") });
1514
+ return messages;
1515
+ }
1516
+ toMessages() {
1517
+ return this.assembleMessages(this.pinnedFactsSlot, this.contextSlot, this.userPayloadSlot);
1518
+ }
1519
+ build(budget) {
1520
+ if (!budget) {
1521
+ const messages2 = this.toMessages();
1522
+ const promptText2 = messages2.map((msg) => `${msg.role.toUpperCase()}:
1523
+ ${msg.content}`).join("\n\n");
1524
+ return {
1525
+ messages: messages2,
1526
+ promptText: promptText2,
1527
+ allocatedTokens: Math.ceil(promptText2.length / 4),
1528
+ isTruncated: false
1529
+ };
1530
+ }
1531
+ budget.reset();
1532
+ let isTruncated = false;
1533
+ let truncatedSlot;
1534
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
1535
+ if (this.schemaContractText) {
1536
+ system0 += `
1537
+
1538
+ ${this.schemaContractText}`;
1539
+ }
1540
+ budget.forceReserve("persona", system0);
1541
+ if (this.rulesSlot.length > 0 || this.retryHintSlot) {
1542
+ const rulesText = this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n");
1543
+ budget.forceReserve("rules", `${rulesText}
1544
+ ${this.retryHintSlot}`);
1545
+ }
1546
+ let payloadToUse = this.userPayloadSlot;
1547
+ let factsToUse = this.pinnedFactsSlot;
1548
+ let contextToUse = this.contextSlot;
1549
+ if (payloadToUse) {
1550
+ const payloadCost = budget.count(payloadToUse);
1551
+ if (payloadCost > budget.remaining()) {
1552
+ const charLimit = Math.max(100, budget.remaining() * 4);
1553
+ payloadToUse = payloadToUse.slice(0, charLimit);
1554
+ isTruncated = true;
1555
+ truncatedSlot = "user";
1556
+ }
1557
+ budget.reserve("user", payloadToUse);
1558
+ }
1559
+ if (factsToUse) {
1560
+ const factsCost = budget.count(factsToUse);
1561
+ if (factsCost > budget.remaining()) {
1562
+ const charLimit = Math.max(50, budget.remaining() * 4);
1563
+ factsToUse = factsToUse.slice(0, charLimit);
1564
+ isTruncated = true;
1565
+ truncatedSlot = "pinnedFacts";
1566
+ }
1567
+ budget.reserve("pinnedFacts", factsToUse);
1568
+ }
1569
+ if (contextToUse) {
1570
+ const contextCost = budget.count(contextToUse);
1571
+ if (contextCost > budget.remaining()) {
1572
+ const charLimit = Math.max(0, budget.remaining() * 4);
1573
+ contextToUse = contextToUse.slice(0, charLimit);
1574
+ isTruncated = true;
1575
+ truncatedSlot = "context";
1576
+ }
1577
+ budget.reserve("context", contextToUse);
1578
+ }
1579
+ const messages = this.assembleMessages(factsToUse, contextToUse, payloadToUse);
1580
+ const promptText = messages.map((msg) => `${msg.role.toUpperCase()}:
1581
+ ${msg.content}`).join("\n\n");
1582
+ return {
1583
+ messages,
1584
+ promptText,
1585
+ allocatedTokens: budget.getAllocated(),
1586
+ isTruncated,
1587
+ truncatedSlot
1588
+ };
1589
+ }
1590
+ };
1591
+
1592
+ // src/prompts/prompt-registry.ts
1593
+ var PromptRegistry = class {
1594
+ static registry = /* @__PURE__ */ new Map();
1595
+ static register(templateOrOptions) {
1596
+ const template = templateOrOptions instanceof PromptTemplate ? templateOrOptions : new PromptTemplate(templateOrOptions);
1597
+ const existing = this.registry.get(template.id) || [];
1598
+ const filtered = existing.filter((item) => item.version !== template.version);
1599
+ filtered.push(template);
1600
+ filtered.sort((a, b) => b.version - a.version);
1601
+ this.registry.set(template.id, filtered);
1602
+ }
1603
+ static get(id, options) {
1604
+ const templates = this.registry.get(id);
1605
+ if (!templates || templates.length === 0) {
1606
+ throw new Error(`[PromptRegistry] No prompt template registered with id: "${id}"`);
1607
+ }
1608
+ if (options?.version !== void 0) {
1609
+ const match = templates.find((item) => item.version === options.version);
1610
+ if (!match) {
1611
+ throw new Error(
1612
+ `[PromptRegistry] Template "${id}" with version ${options.version} not found`
1613
+ );
1614
+ }
1615
+ return match;
1616
+ }
1617
+ if (options?.label) {
1618
+ const match = templates.find((item) => item.label === options.label);
1619
+ if (match) {
1620
+ return match;
1621
+ }
1622
+ }
1623
+ return templates[0];
1624
+ }
1625
+ static has(id) {
1626
+ return this.registry.has(id);
1627
+ }
1628
+ static clear() {
1629
+ this.registry.clear();
1630
+ }
1631
+ };
349
1632
  // Annotate the CommonJS export names for ESM import in node:
350
1633
  0 && (module.exports = {
1634
+ AvantGateBudgetExceededError,
1635
+ AvantGateConfigurationError,
351
1636
  AvantGateControlLayer,
1637
+ BudgetExceededError,
1638
+ CachedPricingAdapter,
1639
+ ConfigurationError,
352
1640
  DEFAULT_MODEL_PRICES,
1641
+ HttpProviderClient,
1642
+ PricingRegistry,
1643
+ PromptBuilder,
1644
+ PromptRegistry,
1645
+ PromptTemplate,
1646
+ SEED_MODEL_PRICES,
353
1647
  ZenLLMControlLayer,
354
1648
  calculateCostUSD,
355
1649
  createAvantGate,
1650
+ createHttpProviderClient,
356
1651
  createLLMControlLayer,
357
1652
  extractAndCleanJSON,
1653
+ resolveModelPrice,
1654
+ resolveModelPriceAsync,
358
1655
  sanitizePII,
359
1656
  validateUserInput,
360
1657
  validateWithZod