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.mjs CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ extractAndCleanJSON,
3
+ validateWithZod
4
+ } from "./chunk-CO26LNFD.mjs";
5
+
1
6
  // src/pricing.ts
2
7
  var DEFAULT_MODEL_PRICES = {
3
8
  // DeepSeek
@@ -49,8 +54,11 @@ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens
49
54
  // src/sanitizer.ts
50
55
  var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
51
56
  var PHONE_FR_REGEX = /\b(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}\b/g;
52
- 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;
53
- var IBAN_REGEX = /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g;
57
+ 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;
58
+ 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;
59
+ var SPI_FORMATTED_REGEX = /\b[0-3]\d(?:\s+\d{2}){5}\s+\d\b/g;
60
+ var IBAN_REGEX = /\b[A-Z]{2}\s*[0-9]{2}(?:[\s\r\n.-]*[A-Z0-9]){11,30}\b/g;
61
+ 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;
54
62
  function sanitizePII(input) {
55
63
  let count = 0;
56
64
  let result = input;
@@ -62,13 +70,36 @@ function sanitizePII(input) {
62
70
  count++;
63
71
  return "[REDACTED_PHONE]";
64
72
  });
65
- result = result.replace(NIR_SSN_REGEX, () => {
73
+ result = result.replace(IBAN_REGEX, (match) => {
74
+ const cleanChars = match.replace(/[\s\r\n.-]/g, "");
75
+ if (cleanChars.length >= 15 && cleanChars.length <= 34) {
76
+ count++;
77
+ return "[REDACTED_IBAN]";
78
+ }
79
+ return match;
80
+ });
81
+ result = result.replace(BIC_LABELLED_REGEX, (_, bicCode) => {
66
82
  count++;
67
- return "[REDACTED_NIR]";
83
+ return `[REDACTED_BIC: ${bicCode.slice(0, 4)}****]`;
68
84
  });
69
- result = result.replace(IBAN_REGEX, () => {
85
+ result = result.replace(SPI_LABELLED_REGEX, (fullMatch, digits) => {
70
86
  count++;
71
- return "[REDACTED_IBAN]";
87
+ return fullMatch.replace(digits, "[REDACTED_SPI]");
88
+ });
89
+ result = result.replace(NIR_SSN_REGEX, (match) => {
90
+ const rawDigits = match.replace(/\s+/g, "");
91
+ if (rawDigits.length === 13 || rawDigits.length === 15) {
92
+ count++;
93
+ return "[REDACTED_NIR]";
94
+ }
95
+ return match;
96
+ });
97
+ result = result.replace(SPI_FORMATTED_REGEX, (match) => {
98
+ if (!match.includes("[REDACTED")) {
99
+ count++;
100
+ return "[REDACTED_SPI]";
101
+ }
102
+ return match;
72
103
  });
73
104
  return { text: result, maskedCount: count };
74
105
  }
@@ -103,44 +134,6 @@ function validateUserInput(input, options) {
103
134
  return { valid: true };
104
135
  }
105
136
 
106
- // src/response-validator.ts
107
- function extractAndCleanJSON(rawText) {
108
- let cleaned = rawText.trim();
109
- cleaned = cleaned.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
110
- const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
111
- if (codeBlockMatch && codeBlockMatch[1]) {
112
- cleaned = codeBlockMatch[1].trim();
113
- }
114
- const firstBrace = cleaned.indexOf("{");
115
- const firstBracket = cleaned.indexOf("[");
116
- let startIndex = -1;
117
- if (firstBrace !== -1 && firstBracket !== -1) {
118
- startIndex = Math.min(firstBrace, firstBracket);
119
- } else if (firstBrace !== -1) {
120
- startIndex = firstBrace;
121
- } else if (firstBracket !== -1) {
122
- startIndex = firstBracket;
123
- }
124
- const lastBrace = cleaned.lastIndexOf("}");
125
- const lastBracket = cleaned.lastIndexOf("]");
126
- const endIndex = Math.max(lastBrace, lastBracket);
127
- if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
128
- cleaned = cleaned.slice(startIndex, endIndex + 1);
129
- }
130
- return cleaned;
131
- }
132
- function validateWithZod(rawText, schema) {
133
- const jsonString = extractAndCleanJSON(rawText);
134
- let parsed;
135
- try {
136
- parsed = JSON.parse(jsonString);
137
- } catch (error) {
138
- const sanitized = jsonString.replace(/,\s*([}\]])/g, "$1").replace(/'/g, '"');
139
- parsed = JSON.parse(sanitized);
140
- }
141
- return schema.parse(parsed);
142
- }
143
-
144
137
  // src/control-layer.ts
145
138
  var AvantGateControlLayer = class {
146
139
  config;
@@ -213,14 +206,15 @@ var AvantGateControlLayer = class {
213
206
  attempts: 1
214
207
  };
215
208
  }
216
- async executeProviderPipeline(messages, query, temperature) {
209
+ async executeProviderPipeline(messages, query, temperature, modelOverride) {
217
210
  const chain = this.getProviderChain();
218
211
  let lastError;
219
212
  for (let index = 0; index < chain.length; index++) {
220
213
  const providerConfig = chain[index];
214
+ const targetModel = index === 0 && modelOverride ? modelOverride : providerConfig.model;
221
215
  try {
222
216
  const response = await providerConfig.client.complete({
223
- model: providerConfig.model,
217
+ model: targetModel,
224
218
  messages,
225
219
  temperature: temperature ?? 0.2
226
220
  });
@@ -228,7 +222,7 @@ var AvantGateControlLayer = class {
228
222
  return {
229
223
  responseText: response.text,
230
224
  usage,
231
- modelUsed: providerConfig.model,
225
+ modelUsed: targetModel,
232
226
  failoverOccurred: index > 0,
233
227
  attempts: index + 1
234
228
  };
@@ -296,7 +290,13 @@ var AvantGateControlLayer = class {
296
290
  temperature: options.temperature ?? 0.1,
297
291
  providerOverride: options.providerOverride
298
292
  });
299
- const parsedData = validateWithZod(rawResult.text, options.schema);
293
+ const isFinancial = Boolean(
294
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
295
+ );
296
+ const parsedData = validateWithZod(rawResult.text, options.schema, {
297
+ financialNormalizer: isFinancial,
298
+ jurisdiction: this.config.features?.finance?.jurisdiction
299
+ });
300
300
  return {
301
301
  data: parsedData,
302
302
  rawText: rawResult.text,
@@ -306,14 +306,426 @@ var AvantGateControlLayer = class {
306
306
  failoverOccurred: rawResult.failoverOccurred
307
307
  };
308
308
  }
309
+ /**
310
+ * Méthode unifiée de premier niveau pour l'extraction structurée sans code boilerplate.
311
+ * Gère le failover multi-fournisseurs, les retries, la validation Zod et la normalisation financière.
312
+ */
313
+ async generateStructuredOutput(options) {
314
+ const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
315
+ const modelToUse = options.model ?? this.config.primary.model;
316
+ const processedMessages = options.messages.map((msg) => {
317
+ if (msg.role === "user") {
318
+ return { ...msg, content: this.applySecurityGuards(msg.content) };
319
+ }
320
+ return msg;
321
+ });
322
+ let lastError;
323
+ let accumulatedPromptTokens = 0;
324
+ let accumulatedCompletionTokens = 0;
325
+ let failoverOccurred = false;
326
+ let modelUsed = modelToUse;
327
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
328
+ try {
329
+ let responseText = "";
330
+ let attemptPromptTokens = 0;
331
+ let attemptCompletionTokens = 0;
332
+ if (options.providerOverride) {
333
+ const res = await options.providerOverride.complete({
334
+ model: modelToUse,
335
+ messages: processedMessages,
336
+ temperature: options.temperature ?? 0.1
337
+ });
338
+ responseText = res.text;
339
+ const usage = this.resolveTokens(res.usage, JSON.stringify(processedMessages), responseText);
340
+ attemptPromptTokens = usage.promptTokens;
341
+ attemptCompletionTokens = usage.completionTokens;
342
+ modelUsed = modelToUse;
343
+ } else if (this.getProviderChain().length === 0) {
344
+ const sim = this.executeSimulation(JSON.stringify(processedMessages));
345
+ responseText = sim.text;
346
+ attemptPromptTokens = sim.tokens.prompt;
347
+ attemptCompletionTokens = sim.tokens.completion;
348
+ } else {
349
+ const output = await this.executeProviderPipeline(
350
+ processedMessages,
351
+ JSON.stringify(processedMessages),
352
+ options.temperature ?? 0.1,
353
+ options.model
354
+ );
355
+ responseText = output.responseText;
356
+ attemptPromptTokens = output.usage.promptTokens;
357
+ attemptCompletionTokens = output.usage.completionTokens;
358
+ modelUsed = output.modelUsed;
359
+ failoverOccurred = output.failoverOccurred;
360
+ }
361
+ accumulatedPromptTokens += attemptPromptTokens;
362
+ accumulatedCompletionTokens += attemptCompletionTokens;
363
+ const isFinancial = options.financialNormalizer ?? Boolean(
364
+ this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
365
+ );
366
+ const parsedData = validateWithZod(responseText, options.schema, {
367
+ financialNormalizer: isFinancial,
368
+ jurisdiction: this.config.features?.finance?.jurisdiction
369
+ });
370
+ const totalTokens = accumulatedPromptTokens + accumulatedCompletionTokens;
371
+ const costUSD = calculateCostUSD(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
372
+ const result = {
373
+ data: parsedData,
374
+ rawText: responseText,
375
+ tokens: {
376
+ prompt: accumulatedPromptTokens,
377
+ completion: accumulatedCompletionTokens,
378
+ total: totalTokens
379
+ },
380
+ costUSD,
381
+ modelUsed,
382
+ failoverOccurred
383
+ };
384
+ await this.notifyAuditSink({
385
+ text: responseText,
386
+ tokens: result.tokens,
387
+ costUSD: result.costUSD,
388
+ modelUsed: result.modelUsed,
389
+ failoverOccurred: result.failoverOccurred,
390
+ attempts: attempt + 1
391
+ });
392
+ return result;
393
+ } catch (err) {
394
+ lastError = err;
395
+ if (attempt < maxRetries) {
396
+ const delay = (this.config.retryOptions?.initialDelayMs ?? 200) * Math.pow(this.config.retryOptions?.backoffFactor ?? 1.5, attempt);
397
+ await new Promise((resolve) => setTimeout(resolve, delay));
398
+ }
399
+ }
400
+ }
401
+ throw lastError;
402
+ }
309
403
  };
310
404
  function createLLMControlLayer(config) {
311
405
  return new AvantGateControlLayer(config);
312
406
  }
313
407
  var createAvantGate = createLLMControlLayer;
408
+
409
+ // src/prompts/prompt-template.ts
410
+ var DEFAULT_JAILBREAK_PATTERNS = [
411
+ /ignore\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
412
+ /disregard\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
413
+ /system\s+override/i,
414
+ /you\s+are\s+now\s+(unrestricted|DAN|jailbroken)/i,
415
+ /<\s*\|\s*im_start\s*\|>/i,
416
+ /\[SYSTEM_PROMPT\]/i
417
+ ];
418
+ var PromptTemplate = class {
419
+ id;
420
+ version;
421
+ label;
422
+ description;
423
+ template;
424
+ inputSchema;
425
+ blockPatterns;
426
+ antiInjectionEnabled;
427
+ customSanitizer;
428
+ constructor(options) {
429
+ this.id = options.id;
430
+ this.version = options.version;
431
+ this.label = options.label || "production";
432
+ this.description = options.description;
433
+ this.template = options.template;
434
+ this.inputSchema = options.inputSchema;
435
+ this.antiInjectionEnabled = options.antiInjection?.enabled !== false;
436
+ this.blockPatterns = options.antiInjection?.blockPatterns || DEFAULT_JAILBREAK_PATTERNS;
437
+ this.customSanitizer = options.antiInjection?.sanitizer;
438
+ }
439
+ validateUserInput(variables) {
440
+ const threats = [];
441
+ if (this.inputSchema) {
442
+ const parsed = this.inputSchema.safeParse(variables);
443
+ if (!parsed.success) {
444
+ return {
445
+ isValid: false,
446
+ threats: parsed.error.issues.map(
447
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
448
+ )
449
+ };
450
+ }
451
+ }
452
+ if (this.antiInjectionEnabled) {
453
+ for (const [key, val] of Object.entries(variables)) {
454
+ if (typeof val === "string") {
455
+ for (const pattern of this.blockPatterns) {
456
+ if (pattern.test(val)) {
457
+ threats.push(
458
+ `Potential prompt injection in variable "${key}" matching pattern ${pattern}`
459
+ );
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ return { isValid: threats.length === 0, threats };
466
+ }
467
+ format(variables) {
468
+ const check = this.validateUserInput(variables);
469
+ if (!check.isValid) {
470
+ throw new Error(
471
+ `[PromptTemplate:${this.id}] Validation failed: ${check.threats.join(", ")}`
472
+ );
473
+ }
474
+ const interpolated = this.template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
475
+ let value = variables[key];
476
+ if (value === void 0 || value === null) {
477
+ return "";
478
+ }
479
+ let stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
480
+ if (this.customSanitizer) {
481
+ stringValue = this.customSanitizer(stringValue);
482
+ }
483
+ return stringValue;
484
+ });
485
+ return interpolated.split("\n").filter((line, index, arr) => line.trim() !== "" || index > 0 && arr[index - 1].trim() !== "").join("\n");
486
+ }
487
+ };
488
+
489
+ // src/prompts/prompt-builder.ts
490
+ import { z } from "zod";
491
+ var PromptBuilder = class {
492
+ personaSlot = "";
493
+ rulesSlot = [];
494
+ retryHintSlot = "";
495
+ fewShotSlot = [];
496
+ pinnedFactsSlot = "";
497
+ contextSlot = "";
498
+ userPayloadSlot = "";
499
+ schemaContractText = "";
500
+ constructor(templateOrId) {
501
+ if (templateOrId instanceof PromptTemplate) {
502
+ this.personaSlot = templateOrId.template;
503
+ } else if (typeof templateOrId === "string") {
504
+ this.personaSlot = templateOrId.trim();
505
+ }
506
+ }
507
+ withPersona(persona) {
508
+ this.personaSlot = persona.trim();
509
+ return this;
510
+ }
511
+ withRules(rules) {
512
+ if (Array.isArray(rules)) {
513
+ this.rulesSlot.push(...rules.map((rule) => rule.trim()).filter(Boolean));
514
+ } else if (rules) {
515
+ this.rulesSlot.push(rules.trim());
516
+ }
517
+ return this;
518
+ }
519
+ withRetryHint(hint) {
520
+ this.retryHintSlot = hint.trim();
521
+ return this;
522
+ }
523
+ withFewShot(examples) {
524
+ this.fewShotSlot = examples;
525
+ return this;
526
+ }
527
+ withPinnedFacts(facts) {
528
+ if (!facts) return this;
529
+ if (typeof facts === "string") {
530
+ this.pinnedFactsSlot = facts.trim();
531
+ return this;
532
+ }
533
+ 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");
534
+ return this;
535
+ }
536
+ withContext(context) {
537
+ this.contextSlot = context.trim();
538
+ return this;
539
+ }
540
+ withUserPayload(payload) {
541
+ this.userPayloadSlot = payload.trim();
542
+ return this;
543
+ }
544
+ schemaContract(schema, options) {
545
+ let shapeDescription = "JSON Object";
546
+ if (schema instanceof z.ZodObject) {
547
+ shapeDescription = JSON.stringify(Object.keys(schema.shape));
548
+ }
549
+ this.schemaContractText = [
550
+ `[DIRECTIVE DE CONTRAT DE SORTIE JSON STRICT]`,
551
+ `Tu DOIS renvoyer UNIQUEMENT un objet JSON valide, sans balises markdown (\`\`\`json), sans texte avant ou apr\xE8s.`,
552
+ `Champs obligatoires et types attendus :`,
553
+ options?.schemaName ? `Sch\xE9ma : ${options.schemaName}` : "",
554
+ shapeDescription
555
+ ].filter(Boolean).join("\n");
556
+ return this;
557
+ }
558
+ assembleMessages(pinnedFacts, context, userPayload) {
559
+ const messages = [];
560
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
561
+ if (this.schemaContractText) {
562
+ system0 += `
563
+
564
+ ${this.schemaContractText}`;
565
+ }
566
+ messages.push({ role: "system", content: system0 });
567
+ const dynamicParts = [];
568
+ if (this.rulesSlot.length > 0) {
569
+ dynamicParts.push(
570
+ `[R\xC8GLES ET CONSIGNES M\xC9TIER]
571
+ ${this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n")}`
572
+ );
573
+ }
574
+ if (this.retryHintSlot) {
575
+ dynamicParts.push(`[INSTRUCTION DE CORRECTION / RETRY]
576
+ ${this.retryHintSlot}`);
577
+ }
578
+ if (dynamicParts.length > 0) {
579
+ messages.push({ role: "system", content: dynamicParts.join("\n\n") });
580
+ }
581
+ if (this.fewShotSlot.length > 0) {
582
+ const examplesContent = this.fewShotSlot.map(
583
+ (ex, idx) => `[Exemple ${idx + 1}]
584
+ Question: ${ex.question}
585
+ R\xE9ponse attendue: ${ex.answer}`
586
+ ).join("\n\n");
587
+ messages.push({ role: "system", content: `[EXEMPLES DE R\xC9F\xC9RENCE]
588
+ ${examplesContent}` });
589
+ }
590
+ const userParts = [];
591
+ if (pinnedFacts) {
592
+ userParts.push(`[FAITS STRUCTUR\xC9S]
593
+ ${pinnedFacts}`);
594
+ }
595
+ if (context) {
596
+ userParts.push(`[CONTEXTE DOCUMENTAIRE]
597
+ ${context}`);
598
+ }
599
+ if (userPayload) {
600
+ const labelNeeded = Boolean(pinnedFacts || context);
601
+ userParts.push(labelNeeded ? `[DONN\xC9ES \xC0 ANALYSER]
602
+ ${userPayload}` : userPayload);
603
+ }
604
+ messages.push({ role: "user", content: userParts.join("\n\n") });
605
+ return messages;
606
+ }
607
+ toMessages() {
608
+ return this.assembleMessages(this.pinnedFactsSlot, this.contextSlot, this.userPayloadSlot);
609
+ }
610
+ build(budget) {
611
+ if (!budget) {
612
+ const messages2 = this.toMessages();
613
+ const promptText2 = messages2.map((msg) => `${msg.role.toUpperCase()}:
614
+ ${msg.content}`).join("\n\n");
615
+ return {
616
+ messages: messages2,
617
+ promptText: promptText2,
618
+ allocatedTokens: Math.ceil(promptText2.length / 4),
619
+ isTruncated: false
620
+ };
621
+ }
622
+ budget.reset();
623
+ let isTruncated = false;
624
+ let truncatedSlot;
625
+ let system0 = this.personaSlot || "Tu es un assistant expert.";
626
+ if (this.schemaContractText) {
627
+ system0 += `
628
+
629
+ ${this.schemaContractText}`;
630
+ }
631
+ budget.forceReserve("persona", system0);
632
+ if (this.rulesSlot.length > 0 || this.retryHintSlot) {
633
+ const rulesText = this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n");
634
+ budget.forceReserve("rules", `${rulesText}
635
+ ${this.retryHintSlot}`);
636
+ }
637
+ let payloadToUse = this.userPayloadSlot;
638
+ let factsToUse = this.pinnedFactsSlot;
639
+ let contextToUse = this.contextSlot;
640
+ if (payloadToUse) {
641
+ const payloadCost = budget.count(payloadToUse);
642
+ if (payloadCost > budget.remaining()) {
643
+ const charLimit = Math.max(100, budget.remaining() * 4);
644
+ payloadToUse = payloadToUse.slice(0, charLimit);
645
+ isTruncated = true;
646
+ truncatedSlot = "user";
647
+ }
648
+ budget.reserve("user", payloadToUse);
649
+ }
650
+ if (factsToUse) {
651
+ const factsCost = budget.count(factsToUse);
652
+ if (factsCost > budget.remaining()) {
653
+ const charLimit = Math.max(50, budget.remaining() * 4);
654
+ factsToUse = factsToUse.slice(0, charLimit);
655
+ isTruncated = true;
656
+ truncatedSlot = "pinnedFacts";
657
+ }
658
+ budget.reserve("pinnedFacts", factsToUse);
659
+ }
660
+ if (contextToUse) {
661
+ const contextCost = budget.count(contextToUse);
662
+ if (contextCost > budget.remaining()) {
663
+ const charLimit = Math.max(0, budget.remaining() * 4);
664
+ contextToUse = contextToUse.slice(0, charLimit);
665
+ isTruncated = true;
666
+ truncatedSlot = "context";
667
+ }
668
+ budget.reserve("context", contextToUse);
669
+ }
670
+ const messages = this.assembleMessages(factsToUse, contextToUse, payloadToUse);
671
+ const promptText = messages.map((msg) => `${msg.role.toUpperCase()}:
672
+ ${msg.content}`).join("\n\n");
673
+ return {
674
+ messages,
675
+ promptText,
676
+ allocatedTokens: budget.getAllocated(),
677
+ isTruncated,
678
+ truncatedSlot
679
+ };
680
+ }
681
+ };
682
+
683
+ // src/prompts/prompt-registry.ts
684
+ var PromptRegistry = class {
685
+ static registry = /* @__PURE__ */ new Map();
686
+ static register(templateOrOptions) {
687
+ const template = templateOrOptions instanceof PromptTemplate ? templateOrOptions : new PromptTemplate(templateOrOptions);
688
+ const existing = this.registry.get(template.id) || [];
689
+ const filtered = existing.filter((item) => item.version !== template.version);
690
+ filtered.push(template);
691
+ filtered.sort((a, b) => b.version - a.version);
692
+ this.registry.set(template.id, filtered);
693
+ }
694
+ static get(id, options) {
695
+ const templates = this.registry.get(id);
696
+ if (!templates || templates.length === 0) {
697
+ throw new Error(`[PromptRegistry] No prompt template registered with id: "${id}"`);
698
+ }
699
+ if (options?.version !== void 0) {
700
+ const match = templates.find((item) => item.version === options.version);
701
+ if (!match) {
702
+ throw new Error(
703
+ `[PromptRegistry] Template "${id}" with version ${options.version} not found`
704
+ );
705
+ }
706
+ return match;
707
+ }
708
+ if (options?.label) {
709
+ const match = templates.find((item) => item.label === options.label);
710
+ if (match) {
711
+ return match;
712
+ }
713
+ }
714
+ return templates[0];
715
+ }
716
+ static has(id) {
717
+ return this.registry.has(id);
718
+ }
719
+ static clear() {
720
+ this.registry.clear();
721
+ }
722
+ };
314
723
  export {
315
724
  AvantGateControlLayer,
316
725
  DEFAULT_MODEL_PRICES,
726
+ PromptBuilder,
727
+ PromptRegistry,
728
+ PromptTemplate,
317
729
  AvantGateControlLayer as ZenLLMControlLayer,
318
730
  calculateCostUSD,
319
731
  createAvantGate,
@@ -0,0 +1,16 @@
1
+ type FinancialJurisdictionCode = "FR" | "US" | "UK" | "CH" | "INTERNATIONAL";
2
+ type AccountingStandard = "PCG" | "US_GAAP" | "IFRS" | "SWISS_CO" | "OTHER";
3
+ type FinancialCurrency = "EUR" | "USD" | "GBP" | "CHF";
4
+ interface IAccountingStrategy {
5
+ readonly jurisdictionCode: FinancialJurisdictionCode;
6
+ readonly standard: AccountingStandard;
7
+ readonly defaultCurrency: FinancialCurrency;
8
+ /** Normalise les formats de nombres et symboles propres au pays */
9
+ cleanNumber(value: string | number): number;
10
+ /** Assainit et répare la syntaxe JSON selon les spécificités du pays */
11
+ cleanJSON(rawText: string): string;
12
+ /** Heuristique de détection automatique à partir du texte brut */
13
+ detect(text: string): boolean;
14
+ }
15
+
16
+ export type { AccountingStandard as A, FinancialJurisdictionCode as F, IAccountingStrategy as I, FinancialCurrency as a };
@@ -0,0 +1,16 @@
1
+ type FinancialJurisdictionCode = "FR" | "US" | "UK" | "CH" | "INTERNATIONAL";
2
+ type AccountingStandard = "PCG" | "US_GAAP" | "IFRS" | "SWISS_CO" | "OTHER";
3
+ type FinancialCurrency = "EUR" | "USD" | "GBP" | "CHF";
4
+ interface IAccountingStrategy {
5
+ readonly jurisdictionCode: FinancialJurisdictionCode;
6
+ readonly standard: AccountingStandard;
7
+ readonly defaultCurrency: FinancialCurrency;
8
+ /** Normalise les formats de nombres et symboles propres au pays */
9
+ cleanNumber(value: string | number): number;
10
+ /** Assainit et répare la syntaxe JSON selon les spécificités du pays */
11
+ cleanJSON(rawText: string): string;
12
+ /** Heuristique de détection automatique à partir du texte brut */
13
+ detect(text: string): boolean;
14
+ }
15
+
16
+ export type { AccountingStandard as A, FinancialJurisdictionCode as F, IAccountingStrategy as I, FinancialCurrency as a };