avantgate 1.0.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/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/index.d.mts +184 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +361 -0
- package/dist/index.mjs +325 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AvantGateControlLayer: () => AvantGateControlLayer,
|
|
24
|
+
DEFAULT_MODEL_PRICES: () => DEFAULT_MODEL_PRICES,
|
|
25
|
+
ZenLLMControlLayer: () => AvantGateControlLayer,
|
|
26
|
+
calculateCostUSD: () => calculateCostUSD,
|
|
27
|
+
createAvantGate: () => createAvantGate,
|
|
28
|
+
createLLMControlLayer: () => createLLMControlLayer,
|
|
29
|
+
extractAndCleanJSON: () => extractAndCleanJSON,
|
|
30
|
+
sanitizePII: () => sanitizePII,
|
|
31
|
+
validateUserInput: () => validateUserInput,
|
|
32
|
+
validateWithZod: () => validateWithZod
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// 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
|
|
57
|
+
},
|
|
58
|
+
// OpenAI
|
|
59
|
+
"gpt-4o-mini": {
|
|
60
|
+
promptUSDPerMillion: 0.15,
|
|
61
|
+
completionUSDPerMillion: 0.6
|
|
62
|
+
},
|
|
63
|
+
"gpt-4o": {
|
|
64
|
+
promptUSDPerMillion: 2.5,
|
|
65
|
+
completionUSDPerMillion: 10
|
|
66
|
+
},
|
|
67
|
+
// Ollama (local)
|
|
68
|
+
"ollama": {
|
|
69
|
+
promptUSDPerMillion: 0,
|
|
70
|
+
completionUSDPerMillion: 0
|
|
71
|
+
}
|
|
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
|
+
const promptCost = promptTokens / 1e6 * pricing.promptUSDPerMillion;
|
|
79
|
+
const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
|
|
80
|
+
const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
|
|
81
|
+
return Math.max(0, promptCost + completionCost - cacheDiscount);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/sanitizer.ts
|
|
85
|
+
var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
|
|
86
|
+
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;
|
|
89
|
+
function sanitizePII(input) {
|
|
90
|
+
let count = 0;
|
|
91
|
+
let result = input;
|
|
92
|
+
result = result.replace(EMAIL_REGEX, () => {
|
|
93
|
+
count++;
|
|
94
|
+
return "[REDACTED_EMAIL]";
|
|
95
|
+
});
|
|
96
|
+
result = result.replace(PHONE_FR_REGEX, () => {
|
|
97
|
+
count++;
|
|
98
|
+
return "[REDACTED_PHONE]";
|
|
99
|
+
});
|
|
100
|
+
result = result.replace(NIR_SSN_REGEX, () => {
|
|
101
|
+
count++;
|
|
102
|
+
return "[REDACTED_NIR]";
|
|
103
|
+
});
|
|
104
|
+
result = result.replace(IBAN_REGEX, () => {
|
|
105
|
+
count++;
|
|
106
|
+
return "[REDACTED_IBAN]";
|
|
107
|
+
});
|
|
108
|
+
return { text: result, maskedCount: count };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/input-guard.ts
|
|
112
|
+
var INJECTION_PATTERNS = [
|
|
113
|
+
/ignore\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts|rules)/i,
|
|
114
|
+
/disregard\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts)/i,
|
|
115
|
+
/you\s+are\s+now\s+(?:dan|unrestricted|in\s+developer\s+mode)/i,
|
|
116
|
+
/bypass\s+(?:all\s+)?(?:content\s+filters|safety\s+guidelines)/i,
|
|
117
|
+
/output\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions)/i,
|
|
118
|
+
/repeat\s+(?:the\s+words\s+above|everything\s+above)/i
|
|
119
|
+
];
|
|
120
|
+
function validateUserInput(input, options) {
|
|
121
|
+
const maxLength = options?.maxLength ?? 5e4;
|
|
122
|
+
if (input.length > maxLength) {
|
|
123
|
+
return {
|
|
124
|
+
valid: false,
|
|
125
|
+
blockedReason: `Input length (${input.length}) exceeds max allowed (${maxLength})`
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (options?.detectInjection !== false) {
|
|
129
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
130
|
+
if (pattern.test(input)) {
|
|
131
|
+
return {
|
|
132
|
+
valid: false,
|
|
133
|
+
blockedReason: `Prompt injection or jailbreak detected: matches security rule [${pattern.source}]`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { valid: true };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/response-validator.ts
|
|
142
|
+
function extractAndCleanJSON(rawText) {
|
|
143
|
+
let cleaned = rawText.trim();
|
|
144
|
+
cleaned = cleaned.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
|
|
145
|
+
const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
146
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
147
|
+
cleaned = codeBlockMatch[1].trim();
|
|
148
|
+
}
|
|
149
|
+
const firstBrace = cleaned.indexOf("{");
|
|
150
|
+
const firstBracket = cleaned.indexOf("[");
|
|
151
|
+
let startIndex = -1;
|
|
152
|
+
if (firstBrace !== -1 && firstBracket !== -1) {
|
|
153
|
+
startIndex = Math.min(firstBrace, firstBracket);
|
|
154
|
+
} else if (firstBrace !== -1) {
|
|
155
|
+
startIndex = firstBrace;
|
|
156
|
+
} else if (firstBracket !== -1) {
|
|
157
|
+
startIndex = firstBracket;
|
|
158
|
+
}
|
|
159
|
+
const lastBrace = cleaned.lastIndexOf("}");
|
|
160
|
+
const lastBracket = cleaned.lastIndexOf("]");
|
|
161
|
+
const endIndex = Math.max(lastBrace, lastBracket);
|
|
162
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
163
|
+
cleaned = cleaned.slice(startIndex, endIndex + 1);
|
|
164
|
+
}
|
|
165
|
+
return cleaned;
|
|
166
|
+
}
|
|
167
|
+
function validateWithZod(rawText, schema) {
|
|
168
|
+
const jsonString = extractAndCleanJSON(rawText);
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
parsed = JSON.parse(jsonString);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const sanitized = jsonString.replace(/,\s*([}\]])/g, "$1").replace(/'/g, '"');
|
|
174
|
+
parsed = JSON.parse(sanitized);
|
|
175
|
+
}
|
|
176
|
+
return schema.parse(parsed);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/control-layer.ts
|
|
180
|
+
var AvantGateControlLayer = class {
|
|
181
|
+
config;
|
|
182
|
+
constructor(config) {
|
|
183
|
+
this.config = config;
|
|
184
|
+
}
|
|
185
|
+
applySecurityGuards(userQuery) {
|
|
186
|
+
const guard = validateUserInput(userQuery, {
|
|
187
|
+
detectInjection: this.config.security?.detectPromptInjection,
|
|
188
|
+
maxLength: this.config.security?.maxInputLength
|
|
189
|
+
});
|
|
190
|
+
if (!guard.valid) {
|
|
191
|
+
throw new Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
|
|
192
|
+
}
|
|
193
|
+
if (this.config.security?.maskPII) {
|
|
194
|
+
return sanitizePII(userQuery).text;
|
|
195
|
+
}
|
|
196
|
+
return userQuery;
|
|
197
|
+
}
|
|
198
|
+
buildMessages(systemPrompt, query) {
|
|
199
|
+
const messages = [];
|
|
200
|
+
if (systemPrompt) {
|
|
201
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
202
|
+
}
|
|
203
|
+
messages.push({ role: "user", content: query });
|
|
204
|
+
return messages;
|
|
205
|
+
}
|
|
206
|
+
resolveTokens(rawUsage, query, text) {
|
|
207
|
+
const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
|
|
208
|
+
const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
|
|
209
|
+
const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
|
|
210
|
+
return { promptTokens, completionTokens, totalTokens };
|
|
211
|
+
}
|
|
212
|
+
getProviderChain() {
|
|
213
|
+
return [
|
|
214
|
+
this.config.primary,
|
|
215
|
+
this.config.fallback,
|
|
216
|
+
this.config.emergencyFallback
|
|
217
|
+
].filter((provider) => Boolean(provider?.client));
|
|
218
|
+
}
|
|
219
|
+
executeSimulation(query, systemPrompt) {
|
|
220
|
+
const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
|
|
221
|
+
const completionTokens = 50;
|
|
222
|
+
const cost = calculateCostUSD(this.config.primary.model, promptTokens, completionTokens);
|
|
223
|
+
return {
|
|
224
|
+
text: `[AvantGate In-Process Engine] Response simulation for model: ${this.config.primary.model}`,
|
|
225
|
+
tokens: {
|
|
226
|
+
prompt: promptTokens,
|
|
227
|
+
completion: completionTokens,
|
|
228
|
+
total: promptTokens + completionTokens
|
|
229
|
+
},
|
|
230
|
+
costUSD: cost,
|
|
231
|
+
modelUsed: this.config.primary.model,
|
|
232
|
+
failoverOccurred: false,
|
|
233
|
+
attempts: 1
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async executeOverrideProvider(provider, messages, query, temperature) {
|
|
237
|
+
const response = await provider.complete({
|
|
238
|
+
model: this.config.primary.model,
|
|
239
|
+
messages,
|
|
240
|
+
temperature: temperature ?? 0.2
|
|
241
|
+
});
|
|
242
|
+
const usage = this.resolveTokens(response.usage, query, response.text);
|
|
243
|
+
return {
|
|
244
|
+
responseText: response.text,
|
|
245
|
+
usage,
|
|
246
|
+
modelUsed: this.config.primary.model,
|
|
247
|
+
failoverOccurred: false,
|
|
248
|
+
attempts: 1
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async executeProviderPipeline(messages, query, temperature) {
|
|
252
|
+
const chain = this.getProviderChain();
|
|
253
|
+
let lastError;
|
|
254
|
+
for (let index = 0; index < chain.length; index++) {
|
|
255
|
+
const providerConfig = chain[index];
|
|
256
|
+
try {
|
|
257
|
+
const response = await providerConfig.client.complete({
|
|
258
|
+
model: providerConfig.model,
|
|
259
|
+
messages,
|
|
260
|
+
temperature: temperature ?? 0.2
|
|
261
|
+
});
|
|
262
|
+
const usage = this.resolveTokens(response.usage, query, response.text);
|
|
263
|
+
return {
|
|
264
|
+
responseText: response.text,
|
|
265
|
+
usage,
|
|
266
|
+
modelUsed: providerConfig.model,
|
|
267
|
+
failoverOccurred: index > 0,
|
|
268
|
+
attempts: index + 1
|
|
269
|
+
};
|
|
270
|
+
} catch (err) {
|
|
271
|
+
lastError = err;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
throw lastError;
|
|
275
|
+
}
|
|
276
|
+
assembleResult(output) {
|
|
277
|
+
const costUSD = calculateCostUSD(
|
|
278
|
+
output.modelUsed,
|
|
279
|
+
output.usage.promptTokens,
|
|
280
|
+
output.usage.completionTokens
|
|
281
|
+
);
|
|
282
|
+
return {
|
|
283
|
+
text: output.responseText,
|
|
284
|
+
tokens: {
|
|
285
|
+
prompt: output.usage.promptTokens,
|
|
286
|
+
completion: output.usage.completionTokens,
|
|
287
|
+
total: output.usage.totalTokens
|
|
288
|
+
},
|
|
289
|
+
costUSD,
|
|
290
|
+
modelUsed: output.modelUsed,
|
|
291
|
+
failoverOccurred: output.failoverOccurred,
|
|
292
|
+
attempts: output.attempts
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async notifyAuditSink(result) {
|
|
296
|
+
if (!this.config.auditSink) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
await this.config.auditSink.log({
|
|
300
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
301
|
+
model: result.modelUsed,
|
|
302
|
+
tokens: result.tokens,
|
|
303
|
+
costUSD: result.costUSD,
|
|
304
|
+
failoverOccurred: result.failoverOccurred,
|
|
305
|
+
attempts: result.attempts
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Exécute une requête avec garde d'entrée, masquage PII, et calcul des coûts.
|
|
310
|
+
*/
|
|
311
|
+
async execute(options) {
|
|
312
|
+
const sanitizedQuery = this.applySecurityGuards(options.userQuery);
|
|
313
|
+
const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
|
|
314
|
+
if (!options.providerOverride && this.getProviderChain().length === 0) {
|
|
315
|
+
const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
|
|
316
|
+
await this.notifyAuditSink(simResult);
|
|
317
|
+
return simResult;
|
|
318
|
+
}
|
|
319
|
+
const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
|
|
320
|
+
const result = this.assembleResult(output);
|
|
321
|
+
await this.notifyAuditSink(result);
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Exécute une requête et valide/répare le résultat selon un schéma Zod.
|
|
326
|
+
*/
|
|
327
|
+
async executeStructured(options) {
|
|
328
|
+
const rawResult = await this.execute({
|
|
329
|
+
userQuery: options.userQuery,
|
|
330
|
+
systemPrompt: options.systemPrompt,
|
|
331
|
+
temperature: options.temperature ?? 0.1,
|
|
332
|
+
providerOverride: options.providerOverride
|
|
333
|
+
});
|
|
334
|
+
const parsedData = validateWithZod(rawResult.text, options.schema);
|
|
335
|
+
return {
|
|
336
|
+
data: parsedData,
|
|
337
|
+
rawText: rawResult.text,
|
|
338
|
+
tokens: rawResult.tokens,
|
|
339
|
+
costUSD: rawResult.costUSD,
|
|
340
|
+
modelUsed: rawResult.modelUsed,
|
|
341
|
+
failoverOccurred: rawResult.failoverOccurred
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
function createLLMControlLayer(config) {
|
|
346
|
+
return new AvantGateControlLayer(config);
|
|
347
|
+
}
|
|
348
|
+
var createAvantGate = createLLMControlLayer;
|
|
349
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
350
|
+
0 && (module.exports = {
|
|
351
|
+
AvantGateControlLayer,
|
|
352
|
+
DEFAULT_MODEL_PRICES,
|
|
353
|
+
ZenLLMControlLayer,
|
|
354
|
+
calculateCostUSD,
|
|
355
|
+
createAvantGate,
|
|
356
|
+
createLLMControlLayer,
|
|
357
|
+
extractAndCleanJSON,
|
|
358
|
+
sanitizePII,
|
|
359
|
+
validateUserInput,
|
|
360
|
+
validateWithZod
|
|
361
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// src/pricing.ts
|
|
2
|
+
var DEFAULT_MODEL_PRICES = {
|
|
3
|
+
// DeepSeek
|
|
4
|
+
"deepseek-chat": {
|
|
5
|
+
promptUSDPerMillion: 0.14,
|
|
6
|
+
completionUSDPerMillion: 0.28,
|
|
7
|
+
cacheHitUSDPerMillion: 0.014
|
|
8
|
+
},
|
|
9
|
+
"deepseek-reasoner": {
|
|
10
|
+
promptUSDPerMillion: 0.55,
|
|
11
|
+
completionUSDPerMillion: 2.19,
|
|
12
|
+
cacheHitUSDPerMillion: 0.14
|
|
13
|
+
},
|
|
14
|
+
// Mistral
|
|
15
|
+
"mistral-small-latest": {
|
|
16
|
+
promptUSDPerMillion: 0.2,
|
|
17
|
+
completionUSDPerMillion: 0.6
|
|
18
|
+
},
|
|
19
|
+
"mistral-large-latest": {
|
|
20
|
+
promptUSDPerMillion: 2,
|
|
21
|
+
completionUSDPerMillion: 6
|
|
22
|
+
},
|
|
23
|
+
// OpenAI
|
|
24
|
+
"gpt-4o-mini": {
|
|
25
|
+
promptUSDPerMillion: 0.15,
|
|
26
|
+
completionUSDPerMillion: 0.6
|
|
27
|
+
},
|
|
28
|
+
"gpt-4o": {
|
|
29
|
+
promptUSDPerMillion: 2.5,
|
|
30
|
+
completionUSDPerMillion: 10
|
|
31
|
+
},
|
|
32
|
+
// Ollama (local)
|
|
33
|
+
"ollama": {
|
|
34
|
+
promptUSDPerMillion: 0,
|
|
35
|
+
completionUSDPerMillion: 0
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0) {
|
|
39
|
+
const pricing = DEFAULT_MODEL_PRICES[model] ?? {
|
|
40
|
+
promptUSDPerMillion: 0.5,
|
|
41
|
+
completionUSDPerMillion: 1.5
|
|
42
|
+
};
|
|
43
|
+
const promptCost = promptTokens / 1e6 * pricing.promptUSDPerMillion;
|
|
44
|
+
const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
|
|
45
|
+
const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
|
|
46
|
+
return Math.max(0, promptCost + completionCost - cacheDiscount);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/sanitizer.ts
|
|
50
|
+
var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
|
|
51
|
+
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;
|
|
54
|
+
function sanitizePII(input) {
|
|
55
|
+
let count = 0;
|
|
56
|
+
let result = input;
|
|
57
|
+
result = result.replace(EMAIL_REGEX, () => {
|
|
58
|
+
count++;
|
|
59
|
+
return "[REDACTED_EMAIL]";
|
|
60
|
+
});
|
|
61
|
+
result = result.replace(PHONE_FR_REGEX, () => {
|
|
62
|
+
count++;
|
|
63
|
+
return "[REDACTED_PHONE]";
|
|
64
|
+
});
|
|
65
|
+
result = result.replace(NIR_SSN_REGEX, () => {
|
|
66
|
+
count++;
|
|
67
|
+
return "[REDACTED_NIR]";
|
|
68
|
+
});
|
|
69
|
+
result = result.replace(IBAN_REGEX, () => {
|
|
70
|
+
count++;
|
|
71
|
+
return "[REDACTED_IBAN]";
|
|
72
|
+
});
|
|
73
|
+
return { text: result, maskedCount: count };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/input-guard.ts
|
|
77
|
+
var INJECTION_PATTERNS = [
|
|
78
|
+
/ignore\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts|rules)/i,
|
|
79
|
+
/disregard\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts)/i,
|
|
80
|
+
/you\s+are\s+now\s+(?:dan|unrestricted|in\s+developer\s+mode)/i,
|
|
81
|
+
/bypass\s+(?:all\s+)?(?:content\s+filters|safety\s+guidelines)/i,
|
|
82
|
+
/output\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions)/i,
|
|
83
|
+
/repeat\s+(?:the\s+words\s+above|everything\s+above)/i
|
|
84
|
+
];
|
|
85
|
+
function validateUserInput(input, options) {
|
|
86
|
+
const maxLength = options?.maxLength ?? 5e4;
|
|
87
|
+
if (input.length > maxLength) {
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
blockedReason: `Input length (${input.length}) exceeds max allowed (${maxLength})`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (options?.detectInjection !== false) {
|
|
94
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
95
|
+
if (pattern.test(input)) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
blockedReason: `Prompt injection or jailbreak detected: matches security rule [${pattern.source}]`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { valid: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
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
|
+
// src/control-layer.ts
|
|
145
|
+
var AvantGateControlLayer = class {
|
|
146
|
+
config;
|
|
147
|
+
constructor(config) {
|
|
148
|
+
this.config = config;
|
|
149
|
+
}
|
|
150
|
+
applySecurityGuards(userQuery) {
|
|
151
|
+
const guard = validateUserInput(userQuery, {
|
|
152
|
+
detectInjection: this.config.security?.detectPromptInjection,
|
|
153
|
+
maxLength: this.config.security?.maxInputLength
|
|
154
|
+
});
|
|
155
|
+
if (!guard.valid) {
|
|
156
|
+
throw new Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
|
|
157
|
+
}
|
|
158
|
+
if (this.config.security?.maskPII) {
|
|
159
|
+
return sanitizePII(userQuery).text;
|
|
160
|
+
}
|
|
161
|
+
return userQuery;
|
|
162
|
+
}
|
|
163
|
+
buildMessages(systemPrompt, query) {
|
|
164
|
+
const messages = [];
|
|
165
|
+
if (systemPrompt) {
|
|
166
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
167
|
+
}
|
|
168
|
+
messages.push({ role: "user", content: query });
|
|
169
|
+
return messages;
|
|
170
|
+
}
|
|
171
|
+
resolveTokens(rawUsage, query, text) {
|
|
172
|
+
const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
|
|
173
|
+
const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
|
|
174
|
+
const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
|
|
175
|
+
return { promptTokens, completionTokens, totalTokens };
|
|
176
|
+
}
|
|
177
|
+
getProviderChain() {
|
|
178
|
+
return [
|
|
179
|
+
this.config.primary,
|
|
180
|
+
this.config.fallback,
|
|
181
|
+
this.config.emergencyFallback
|
|
182
|
+
].filter((provider) => Boolean(provider?.client));
|
|
183
|
+
}
|
|
184
|
+
executeSimulation(query, systemPrompt) {
|
|
185
|
+
const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
|
|
186
|
+
const completionTokens = 50;
|
|
187
|
+
const cost = calculateCostUSD(this.config.primary.model, promptTokens, completionTokens);
|
|
188
|
+
return {
|
|
189
|
+
text: `[AvantGate In-Process Engine] Response simulation for model: ${this.config.primary.model}`,
|
|
190
|
+
tokens: {
|
|
191
|
+
prompt: promptTokens,
|
|
192
|
+
completion: completionTokens,
|
|
193
|
+
total: promptTokens + completionTokens
|
|
194
|
+
},
|
|
195
|
+
costUSD: cost,
|
|
196
|
+
modelUsed: this.config.primary.model,
|
|
197
|
+
failoverOccurred: false,
|
|
198
|
+
attempts: 1
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async executeOverrideProvider(provider, messages, query, temperature) {
|
|
202
|
+
const response = await provider.complete({
|
|
203
|
+
model: this.config.primary.model,
|
|
204
|
+
messages,
|
|
205
|
+
temperature: temperature ?? 0.2
|
|
206
|
+
});
|
|
207
|
+
const usage = this.resolveTokens(response.usage, query, response.text);
|
|
208
|
+
return {
|
|
209
|
+
responseText: response.text,
|
|
210
|
+
usage,
|
|
211
|
+
modelUsed: this.config.primary.model,
|
|
212
|
+
failoverOccurred: false,
|
|
213
|
+
attempts: 1
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async executeProviderPipeline(messages, query, temperature) {
|
|
217
|
+
const chain = this.getProviderChain();
|
|
218
|
+
let lastError;
|
|
219
|
+
for (let index = 0; index < chain.length; index++) {
|
|
220
|
+
const providerConfig = chain[index];
|
|
221
|
+
try {
|
|
222
|
+
const response = await providerConfig.client.complete({
|
|
223
|
+
model: providerConfig.model,
|
|
224
|
+
messages,
|
|
225
|
+
temperature: temperature ?? 0.2
|
|
226
|
+
});
|
|
227
|
+
const usage = this.resolveTokens(response.usage, query, response.text);
|
|
228
|
+
return {
|
|
229
|
+
responseText: response.text,
|
|
230
|
+
usage,
|
|
231
|
+
modelUsed: providerConfig.model,
|
|
232
|
+
failoverOccurred: index > 0,
|
|
233
|
+
attempts: index + 1
|
|
234
|
+
};
|
|
235
|
+
} catch (err) {
|
|
236
|
+
lastError = err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
throw lastError;
|
|
240
|
+
}
|
|
241
|
+
assembleResult(output) {
|
|
242
|
+
const costUSD = calculateCostUSD(
|
|
243
|
+
output.modelUsed,
|
|
244
|
+
output.usage.promptTokens,
|
|
245
|
+
output.usage.completionTokens
|
|
246
|
+
);
|
|
247
|
+
return {
|
|
248
|
+
text: output.responseText,
|
|
249
|
+
tokens: {
|
|
250
|
+
prompt: output.usage.promptTokens,
|
|
251
|
+
completion: output.usage.completionTokens,
|
|
252
|
+
total: output.usage.totalTokens
|
|
253
|
+
},
|
|
254
|
+
costUSD,
|
|
255
|
+
modelUsed: output.modelUsed,
|
|
256
|
+
failoverOccurred: output.failoverOccurred,
|
|
257
|
+
attempts: output.attempts
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async notifyAuditSink(result) {
|
|
261
|
+
if (!this.config.auditSink) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
await this.config.auditSink.log({
|
|
265
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
266
|
+
model: result.modelUsed,
|
|
267
|
+
tokens: result.tokens,
|
|
268
|
+
costUSD: result.costUSD,
|
|
269
|
+
failoverOccurred: result.failoverOccurred,
|
|
270
|
+
attempts: result.attempts
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Exécute une requête avec garde d'entrée, masquage PII, et calcul des coûts.
|
|
275
|
+
*/
|
|
276
|
+
async execute(options) {
|
|
277
|
+
const sanitizedQuery = this.applySecurityGuards(options.userQuery);
|
|
278
|
+
const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
|
|
279
|
+
if (!options.providerOverride && this.getProviderChain().length === 0) {
|
|
280
|
+
const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
|
|
281
|
+
await this.notifyAuditSink(simResult);
|
|
282
|
+
return simResult;
|
|
283
|
+
}
|
|
284
|
+
const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
|
|
285
|
+
const result = this.assembleResult(output);
|
|
286
|
+
await this.notifyAuditSink(result);
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Exécute une requête et valide/répare le résultat selon un schéma Zod.
|
|
291
|
+
*/
|
|
292
|
+
async executeStructured(options) {
|
|
293
|
+
const rawResult = await this.execute({
|
|
294
|
+
userQuery: options.userQuery,
|
|
295
|
+
systemPrompt: options.systemPrompt,
|
|
296
|
+
temperature: options.temperature ?? 0.1,
|
|
297
|
+
providerOverride: options.providerOverride
|
|
298
|
+
});
|
|
299
|
+
const parsedData = validateWithZod(rawResult.text, options.schema);
|
|
300
|
+
return {
|
|
301
|
+
data: parsedData,
|
|
302
|
+
rawText: rawResult.text,
|
|
303
|
+
tokens: rawResult.tokens,
|
|
304
|
+
costUSD: rawResult.costUSD,
|
|
305
|
+
modelUsed: rawResult.modelUsed,
|
|
306
|
+
failoverOccurred: rawResult.failoverOccurred
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function createLLMControlLayer(config) {
|
|
311
|
+
return new AvantGateControlLayer(config);
|
|
312
|
+
}
|
|
313
|
+
var createAvantGate = createLLMControlLayer;
|
|
314
|
+
export {
|
|
315
|
+
AvantGateControlLayer,
|
|
316
|
+
DEFAULT_MODEL_PRICES,
|
|
317
|
+
AvantGateControlLayer as ZenLLMControlLayer,
|
|
318
|
+
calculateCostUSD,
|
|
319
|
+
createAvantGate,
|
|
320
|
+
createLLMControlLayer,
|
|
321
|
+
extractAndCleanJSON,
|
|
322
|
+
sanitizePII,
|
|
323
|
+
validateUserInput,
|
|
324
|
+
validateWithZod
|
|
325
|
+
};
|