avantgate 1.1.0 → 1.1.2

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,109 +1,220 @@
1
+ import {
2
+ sanitizePII
3
+ } from "./chunk-DVCF4CSV.mjs";
1
4
  import {
2
5
  extractAndCleanJSON,
3
6
  validateWithZod
4
7
  } from "./chunk-CO26LNFD.mjs";
5
8
 
9
+ // src/types.ts
10
+ var ConfigurationError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ConfigurationError";
14
+ }
15
+ };
16
+ var BudgetExceededError = class extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "BudgetExceededError";
20
+ }
21
+ };
22
+
6
23
  // src/pricing.ts
7
- var DEFAULT_MODEL_PRICES = {
8
- // DeepSeek
9
- "deepseek-chat": {
10
- promptUSDPerMillion: 0.14,
11
- completionUSDPerMillion: 0.28,
12
- cacheHitUSDPerMillion: 0.014
13
- },
14
- "deepseek-reasoner": {
15
- promptUSDPerMillion: 0.55,
16
- completionUSDPerMillion: 2.19,
17
- cacheHitUSDPerMillion: 0.14
18
- },
19
- // Mistral
20
- "mistral-small-latest": {
21
- promptUSDPerMillion: 0.2,
22
- completionUSDPerMillion: 0.6
23
- },
24
- "mistral-large-latest": {
25
- promptUSDPerMillion: 2,
26
- completionUSDPerMillion: 6
24
+ var SEED_MODEL_PRICES = {
25
+ // DeepSeek Direct
26
+ "deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
27
+ "deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
28
+ "deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
29
+ "deepseek/deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
30
+ // Mistral AI Direct
31
+ "mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
32
+ "mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
33
+ "mistral/mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
34
+ "mistral/mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
35
+ // OpenAI Direct
36
+ "gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
37
+ "gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
38
+ "openai/gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
39
+ "openai/gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
40
+ // OpenRouter Direct
41
+ "openrouter/deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28 },
42
+ "openrouter/deepseek/deepseek-r1": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19 },
43
+ "openrouter/anthropic/claude-3.5-sonnet": { promptUSDPerMillion: 3, completionUSDPerMillion: 15 },
44
+ // Local Ollama (Toujours $0)
45
+ "ollama": { promptUSDPerMillion: 0, completionUSDPerMillion: 0 }
46
+ };
47
+ function normalizeKey(identifier) {
48
+ return identifier.trim().toLowerCase().replace(":", "/");
49
+ }
50
+ var CachedPricingAdapter = class {
51
+ cache = /* @__PURE__ */ new Map();
52
+ ttlMs;
53
+ delegate;
54
+ constructor(delegate, ttlMs = 5 * 60 * 1e3) {
55
+ this.delegate = delegate;
56
+ this.ttlMs = ttlMs;
57
+ }
58
+ async fetchPrice(model, provider) {
59
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
60
+ const cached = this.cache.get(key);
61
+ if (cached && Date.now() < cached.expiresAt) {
62
+ return cached.price;
63
+ }
64
+ try {
65
+ const freshPrice = await this.delegate.fetchPrice(model, provider);
66
+ if (freshPrice) {
67
+ this.cache.set(key, { price: freshPrice, expiresAt: Date.now() + this.ttlMs });
68
+ return freshPrice;
69
+ }
70
+ } catch {
71
+ if (cached) {
72
+ return cached.price;
73
+ }
74
+ }
75
+ return void 0;
76
+ }
77
+ peek(model, provider) {
78
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
79
+ return this.cache.get(key)?.price;
80
+ }
81
+ clearCache() {
82
+ this.cache.clear();
83
+ }
84
+ invalidate(model, provider) {
85
+ if (!model) {
86
+ this.clearCache();
87
+ return;
88
+ }
89
+ const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
90
+ this.cache.delete(key);
91
+ }
92
+ };
93
+ var PricingRegistry = class {
94
+ static prices = /* @__PURE__ */ new Map();
95
+ static adapter;
96
+ static registerPrice(identifier, price) {
97
+ this.prices.set(normalizeKey(identifier), price);
98
+ }
99
+ static registerDistributorPrices(distributor, priceMap) {
100
+ for (const [model, price] of Object.entries(priceMap)) {
101
+ this.prices.set(normalizeKey(`${distributor}/${model}`), price);
102
+ }
103
+ }
104
+ static registerPrices(prices) {
105
+ for (const [key, price] of Object.entries(prices)) {
106
+ this.prices.set(normalizeKey(key), price);
107
+ }
108
+ }
109
+ static getPrice(model, provider) {
110
+ if (provider) {
111
+ const distributorKey = normalizeKey(`${provider}/${model}`);
112
+ const directDistributorPrice = this.prices.get(distributorKey);
113
+ if (directDistributorPrice) {
114
+ return directDistributorPrice;
115
+ }
116
+ }
117
+ return this.prices.get(normalizeKey(model));
118
+ }
119
+ static setAdapter(adapter) {
120
+ this.adapter = adapter;
121
+ }
122
+ static getAdapter() {
123
+ return this.adapter;
124
+ }
125
+ static clear() {
126
+ this.prices.clear();
127
+ this.adapter = void 0;
128
+ }
129
+ /**
130
+ * Réinitialise le registre en chargeant le catalogue d'exemple SEED_MODEL_PRICES.
131
+ */
132
+ static loadSeedPrices() {
133
+ this.registerPrices(SEED_MODEL_PRICES);
134
+ }
135
+ static getAllPrices() {
136
+ const out = {};
137
+ for (const [k, v] of this.prices.entries()) {
138
+ out[k] = v;
139
+ }
140
+ return out;
141
+ }
142
+ };
143
+ var DEFAULT_MODEL_PRICES = new Proxy({}, {
144
+ get(_target, prop) {
145
+ return PricingRegistry.getPrice(prop);
27
146
  },
28
- // OpenAI
29
- "gpt-4o-mini": {
30
- promptUSDPerMillion: 0.15,
31
- completionUSDPerMillion: 0.6
147
+ has(_target, prop) {
148
+ return Boolean(PricingRegistry.getPrice(prop));
32
149
  },
33
- "gpt-4o": {
34
- promptUSDPerMillion: 2.5,
35
- completionUSDPerMillion: 10
150
+ ownKeys() {
151
+ return Object.keys(PricingRegistry.getAllPrices());
36
152
  },
37
- // Ollama (local)
38
- "ollama": {
39
- promptUSDPerMillion: 0,
40
- completionUSDPerMillion: 0
153
+ getOwnPropertyDescriptor(_target, prop) {
154
+ const price = PricingRegistry.getPrice(prop);
155
+ if (price) {
156
+ return { value: price, enumerable: true, configurable: true, writable: false };
157
+ }
158
+ return void 0;
159
+ }
160
+ });
161
+ function resolveModelPrice(model, options) {
162
+ const opts = typeof options === "string" ? { provider: options } : options ?? {};
163
+ if (opts.providerPricing) {
164
+ return opts.providerPricing;
165
+ }
166
+ if (opts.customPricing) {
167
+ if (opts.provider) {
168
+ const customDistributorKey = normalizeKey(`${opts.provider}/${model}`);
169
+ if (opts.customPricing[customDistributorKey]) {
170
+ return opts.customPricing[customDistributorKey];
171
+ }
172
+ }
173
+ if (opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model]) {
174
+ return opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model];
175
+ }
176
+ }
177
+ const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
178
+ if (activeAdapter instanceof CachedPricingAdapter) {
179
+ const cached = activeAdapter.peek(model, opts.provider);
180
+ if (cached) {
181
+ return cached;
182
+ }
183
+ }
184
+ const registeredPrice = PricingRegistry.getPrice(model, opts.provider);
185
+ if (registeredPrice) {
186
+ return registeredPrice;
187
+ }
188
+ if (opts.provider === "ollama" || model.toLowerCase().includes("ollama")) {
189
+ return { promptUSDPerMillion: 0, completionUSDPerMillion: 0 };
190
+ }
191
+ return void 0;
192
+ }
193
+ async function resolveModelPriceAsync(model, options) {
194
+ const opts = typeof options === "string" ? { provider: options } : options ?? {};
195
+ const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
196
+ if (activeAdapter) {
197
+ try {
198
+ const adapterPrice = await activeAdapter.fetchPrice(model, opts.provider);
199
+ if (adapterPrice) {
200
+ return adapterPrice;
201
+ }
202
+ } catch {
203
+ }
204
+ }
205
+ return resolveModelPrice(model, options);
206
+ }
207
+ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0, options) {
208
+ const pricing = resolveModelPrice(model, options);
209
+ if (!pricing) {
210
+ return 0;
41
211
  }
42
- };
43
- function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0) {
44
- const pricing = DEFAULT_MODEL_PRICES[model] ?? {
45
- promptUSDPerMillion: 0.5,
46
- completionUSDPerMillion: 1.5
47
- };
48
212
  const promptCost = promptTokens / 1e6 * pricing.promptUSDPerMillion;
49
213
  const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
50
214
  const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
51
215
  return Math.max(0, promptCost + completionCost - cacheDiscount);
52
216
  }
53
217
 
54
- // src/sanitizer.ts
55
- var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
56
- var PHONE_FR_REGEX = /\b(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}\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;
62
- function sanitizePII(input) {
63
- let count = 0;
64
- let result = input;
65
- result = result.replace(EMAIL_REGEX, () => {
66
- count++;
67
- return "[REDACTED_EMAIL]";
68
- });
69
- result = result.replace(PHONE_FR_REGEX, () => {
70
- count++;
71
- return "[REDACTED_PHONE]";
72
- });
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) => {
82
- count++;
83
- return `[REDACTED_BIC: ${bicCode.slice(0, 4)}****]`;
84
- });
85
- result = result.replace(SPI_LABELLED_REGEX, (fullMatch, digits) => {
86
- count++;
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;
103
- });
104
- return { text: result, maskedCount: count };
105
- }
106
-
107
218
  // src/input-guard.ts
108
219
  var INJECTION_PATTERNS = [
109
220
  /ignore\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts|rules)/i,
@@ -134,11 +245,144 @@ function validateUserInput(input, options) {
134
245
  return { valid: true };
135
246
  }
136
247
 
248
+ // src/providers/http-client.ts
249
+ var DEFAULT_BASE_URLS = {
250
+ deepseek: "https://api.deepseek.com/v1",
251
+ mistral: "https://api.mistral.ai/v1",
252
+ openai: "https://api.openai.com/v1",
253
+ ollama: "http://localhost:11434/v1",
254
+ openrouter: "https://openrouter.ai/api/v1"
255
+ };
256
+ var HttpProviderClient = class {
257
+ name;
258
+ baseUrl;
259
+ apiKey;
260
+ providerType;
261
+ constructor(config) {
262
+ this.providerType = config.provider;
263
+ this.name = `${config.provider}-http`;
264
+ this.apiKey = config.apiKey;
265
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
266
+ }
267
+ resolveEndpoint() {
268
+ const trimmed = this.baseUrl.replace(/\/+$/, "");
269
+ if (trimmed.endsWith("/chat/completions")) {
270
+ return trimmed;
271
+ }
272
+ return `${trimmed}/chat/completions`;
273
+ }
274
+ buildHeaders() {
275
+ const headers = {
276
+ "Content-Type": "application/json"
277
+ };
278
+ if (this.apiKey) {
279
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
280
+ }
281
+ if (this.providerType === "openrouter") {
282
+ headers["HTTP-Referer"] = "https://avantgate.dev";
283
+ headers["X-Title"] = "AvantGate";
284
+ }
285
+ return headers;
286
+ }
287
+ buildPayload(options, model) {
288
+ return {
289
+ model,
290
+ messages: options.messages.map((m) => ({
291
+ role: m.role,
292
+ content: m.content
293
+ })),
294
+ temperature: options.temperature ?? 0.2,
295
+ ...options.responseFormat ? { response_format: options.responseFormat } : {}
296
+ };
297
+ }
298
+ extractUsage(usageData) {
299
+ if (!usageData) {
300
+ return void 0;
301
+ }
302
+ return {
303
+ promptTokens: usageData.prompt_tokens,
304
+ completionTokens: usageData.completion_tokens,
305
+ totalTokens: usageData.total_tokens,
306
+ promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
307
+ };
308
+ }
309
+ async complete(options) {
310
+ const endpoint = this.resolveEndpoint();
311
+ const model = options.model ?? "default";
312
+ const headers = this.buildHeaders();
313
+ const body = JSON.stringify(this.buildPayload(options, model));
314
+ const response = await fetch(endpoint, {
315
+ method: "POST",
316
+ headers,
317
+ body
318
+ });
319
+ if (!response.ok) {
320
+ const errorText = await response.text();
321
+ throw new Error(
322
+ `[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
323
+ );
324
+ }
325
+ const data = await response.json();
326
+ const text = data.choices?.[0]?.message?.content ?? "";
327
+ const usage = this.extractUsage(data.usage);
328
+ return { text, usage };
329
+ }
330
+ };
331
+ function createHttpProviderClient(config) {
332
+ return new HttpProviderClient(config);
333
+ }
334
+
137
335
  // src/control-layer.ts
138
336
  var AvantGateControlLayer = class {
139
337
  config;
338
+ cachedPricingAdapter;
140
339
  constructor(config) {
141
- this.config = config;
340
+ this.config = {
341
+ ...config,
342
+ primary: this.resolveProviderConfig(config.primary),
343
+ fallback: this.resolveProviderConfig(config.fallback),
344
+ emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
345
+ };
346
+ if (config.pricingAdapter) {
347
+ this.cachedPricingAdapter = new CachedPricingAdapter(
348
+ config.pricingAdapter,
349
+ config.pricingCacheTtlMs ?? 5 * 60 * 1e3
350
+ );
351
+ }
352
+ }
353
+ resolveProviderConfig(provider) {
354
+ if (!provider) return void 0;
355
+ if (provider.client) return provider;
356
+ if (provider.apiKey || provider.baseUrl || provider.provider === "ollama") {
357
+ return {
358
+ ...provider,
359
+ client: createHttpProviderClient(provider)
360
+ };
361
+ }
362
+ return provider;
363
+ }
364
+ findProviderConfig(provider, model) {
365
+ const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
366
+ (p) => Boolean(p)
367
+ );
368
+ if (provider) {
369
+ const matchProvider = list.find((p) => p.provider === provider);
370
+ if (matchProvider) return matchProvider;
371
+ }
372
+ if (model) {
373
+ const matchModel = list.find((p) => p.model === model);
374
+ if (matchModel) return matchModel;
375
+ }
376
+ return this.config.primary;
377
+ }
378
+ calculateCost(model, promptTokens, completionTokens, cacheHitTokens = 0, provider) {
379
+ const providerCfg = this.findProviderConfig(provider, model);
380
+ return calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens, {
381
+ provider: provider ?? providerCfg?.provider,
382
+ providerPricing: providerCfg?.pricing,
383
+ customPricing: this.config.customPricing,
384
+ adapter: this.cachedPricingAdapter
385
+ });
142
386
  }
143
387
  applySecurityGuards(userQuery) {
144
388
  const guard = validateUserInput(userQuery, {
@@ -153,6 +397,46 @@ var AvantGateControlLayer = class {
153
397
  }
154
398
  return userQuery;
155
399
  }
400
+ checkPreflightBudget(estimatedPromptTokens, targetModel, provider) {
401
+ if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
402
+ throw new BudgetExceededError(
403
+ `[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
404
+ );
405
+ }
406
+ if (this.config.maxCostUSD !== void 0) {
407
+ const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
408
+ const providerCfg = this.findProviderConfig(provider, targetModel);
409
+ const price = resolveModelPrice(targetModel, {
410
+ provider: provider ?? providerCfg?.provider,
411
+ providerPricing: providerCfg?.pricing,
412
+ customPricing: this.config.customPricing,
413
+ adapter: this.cachedPricingAdapter
414
+ });
415
+ if (!isLocalFree && !price) {
416
+ throw new ConfigurationError(
417
+ `[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.`
418
+ );
419
+ }
420
+ const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
421
+ if (estimatedPromptCost > this.config.maxCostUSD) {
422
+ throw new BudgetExceededError(
423
+ `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
424
+ );
425
+ }
426
+ }
427
+ }
428
+ checkPostExecutionBudget(tokensTotal, costUSD) {
429
+ if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
430
+ throw new BudgetExceededError(
431
+ `[AvantGate Budget Guard] Execution total tokens (${tokensTotal}) exceeded maxTokenBudget (${this.config.maxTokenBudget}).`
432
+ );
433
+ }
434
+ if (this.config.maxCostUSD !== void 0 && costUSD > this.config.maxCostUSD) {
435
+ throw new BudgetExceededError(
436
+ `[AvantGate Budget Guard] Execution cost ($${costUSD.toFixed(6)}) exceeded maxCostUSD ($${this.config.maxCostUSD}).`
437
+ );
438
+ }
439
+ }
156
440
  buildMessages(systemPrompt, query) {
157
441
  const messages = [];
158
442
  if (systemPrompt) {
@@ -165,7 +449,12 @@ var AvantGateControlLayer = class {
165
449
  const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
166
450
  const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
167
451
  const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
168
- return { promptTokens, completionTokens, totalTokens };
452
+ return {
453
+ promptTokens,
454
+ completionTokens,
455
+ totalTokens,
456
+ promptCacheHitTokens: rawUsage?.promptCacheHitTokens
457
+ };
169
458
  }
170
459
  getProviderChain() {
171
460
  return [
@@ -177,7 +466,7 @@ var AvantGateControlLayer = class {
177
466
  executeSimulation(query, systemPrompt) {
178
467
  const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
179
468
  const completionTokens = 50;
180
- const cost = calculateCostUSD(this.config.primary.model, promptTokens, completionTokens);
469
+ const cost = this.calculateCost(this.config.primary.model, promptTokens, completionTokens);
181
470
  return {
182
471
  text: `[AvantGate In-Process Engine] Response simulation for model: ${this.config.primary.model}`,
183
472
  tokens: {
@@ -233,10 +522,11 @@ var AvantGateControlLayer = class {
233
522
  throw lastError;
234
523
  }
235
524
  assembleResult(output) {
236
- const costUSD = calculateCostUSD(
525
+ const costUSD = this.calculateCost(
237
526
  output.modelUsed,
238
527
  output.usage.promptTokens,
239
- output.usage.completionTokens
528
+ output.usage.completionTokens,
529
+ output.usage.promptCacheHitTokens ?? 0
240
530
  );
241
531
  return {
242
532
  text: output.responseText,
@@ -265,18 +555,27 @@ var AvantGateControlLayer = class {
265
555
  });
266
556
  }
267
557
  /**
268
- * Exécute une requête avec garde d'entrée, masquage PII, et calcul des coûts.
558
+ * Exécute une requête avec garde d'entrée, masquage PII, garde pré-vol et calcul des coûts.
269
559
  */
270
560
  async execute(options) {
271
561
  const sanitizedQuery = this.applySecurityGuards(options.userQuery);
272
562
  const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
563
+ const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
564
+ const estimatedPromptTokens = Math.ceil(promptLength / 4);
565
+ this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
273
566
  if (!options.providerOverride && this.getProviderChain().length === 0) {
274
- const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
275
- await this.notifyAuditSink(simResult);
276
- return simResult;
567
+ if (this.config.mockSimulation) {
568
+ const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
569
+ await this.notifyAuditSink(simResult);
570
+ return simResult;
571
+ }
572
+ throw new ConfigurationError(
573
+ "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
574
+ );
277
575
  }
278
576
  const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
279
577
  const result = this.assembleResult(output);
578
+ this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
280
579
  await this.notifyAuditSink(result);
281
580
  return result;
282
581
  }
@@ -319,6 +618,9 @@ var AvantGateControlLayer = class {
319
618
  }
320
619
  return msg;
321
620
  });
621
+ const promptLength = processedMessages.reduce((sum, msg) => sum + msg.content.length, 0);
622
+ const estimatedPromptTokens = Math.ceil(promptLength / 4);
623
+ this.checkPreflightBudget(estimatedPromptTokens, modelToUse);
322
624
  let lastError;
323
625
  let accumulatedPromptTokens = 0;
324
626
  let accumulatedCompletionTokens = 0;
@@ -341,10 +643,16 @@ var AvantGateControlLayer = class {
341
643
  attemptCompletionTokens = usage.completionTokens;
342
644
  modelUsed = modelToUse;
343
645
  } 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;
646
+ if (this.config.mockSimulation) {
647
+ const sim = this.executeSimulation(JSON.stringify(processedMessages));
648
+ responseText = sim.text;
649
+ attemptPromptTokens = sim.tokens.prompt;
650
+ attemptCompletionTokens = sim.tokens.completion;
651
+ } else {
652
+ throw new ConfigurationError(
653
+ "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
654
+ );
655
+ }
348
656
  } else {
349
657
  const output = await this.executeProviderPipeline(
350
658
  processedMessages,
@@ -368,7 +676,8 @@ var AvantGateControlLayer = class {
368
676
  jurisdiction: this.config.features?.finance?.jurisdiction
369
677
  });
370
678
  const totalTokens = accumulatedPromptTokens + accumulatedCompletionTokens;
371
- const costUSD = calculateCostUSD(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
679
+ const costUSD = this.calculateCost(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
680
+ this.checkPostExecutionBudget(totalTokens, costUSD);
372
681
  const result = {
373
682
  data: parsedData,
374
683
  rawText: responseText,
@@ -721,16 +1030,27 @@ var PromptRegistry = class {
721
1030
  }
722
1031
  };
723
1032
  export {
1033
+ BudgetExceededError as AvantGateBudgetExceededError,
1034
+ ConfigurationError as AvantGateConfigurationError,
724
1035
  AvantGateControlLayer,
1036
+ BudgetExceededError,
1037
+ CachedPricingAdapter,
1038
+ ConfigurationError,
725
1039
  DEFAULT_MODEL_PRICES,
1040
+ HttpProviderClient,
1041
+ PricingRegistry,
726
1042
  PromptBuilder,
727
1043
  PromptRegistry,
728
1044
  PromptTemplate,
1045
+ SEED_MODEL_PRICES,
729
1046
  AvantGateControlLayer as ZenLLMControlLayer,
730
1047
  calculateCostUSD,
731
1048
  createAvantGate,
1049
+ createHttpProviderClient,
732
1050
  createLLMControlLayer,
733
1051
  extractAndCleanJSON,
1052
+ resolveModelPrice,
1053
+ resolveModelPriceAsync,
734
1054
  sanitizePII,
735
1055
  validateUserInput,
736
1056
  validateWithZod
package/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "avantgate",
3
- "version": "1.1.0",
4
- "description": "Zero-infrastructure, in-process LLM control plane: real-time cost control, token budgets, PII redaction, prompt guardrails, and multi-model failover without hosting servers.",
3
+ "version": "1.1.2",
4
+ "description": "Zero-infrastructure, in-process LLM control plane & SaaS observability bridge: real-time cost control, token budgets, PII redaction, prompt guardrails, multi-model failover, durable agent workflow harness, and HTTP telemetry without hosting heavy servers.",
5
5
  "author": "Antigravity & LexTalk Team",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "avantgate",
9
9
  "llm",
10
10
  "ai",
11
+ "agent",
12
+ "observability",
13
+ "telemetry",
14
+ "session-replay",
15
+ "helicone-alternative",
16
+ "agentops-alternative",
17
+ "durable-execution",
18
+ "human-in-the-loop",
19
+ "dual-channel",
11
20
  "cost-control",
12
21
  "token-budget",
13
22
  "guardrails",
@@ -37,6 +46,11 @@
37
46
  "import": "./dist/finance/index.mjs",
38
47
  "require": "./dist/finance/index.js"
39
48
  },
49
+ "./agent": {
50
+ "types": "./dist/agent/index.d.ts",
51
+ "import": "./dist/agent/index.mjs",
52
+ "require": "./dist/agent/index.js"
53
+ },
40
54
  "./package.json": "./package.json"
41
55
  },
42
56
  "files": [
@@ -45,11 +59,12 @@
45
59
  "LICENSE"
46
60
  ],
47
61
  "scripts": {
48
- "build": "tsup src/index.ts src/finance/index.ts --format cjs --format esm --dts --clean",
49
- "test": "tsx tests/avantgate.test.ts && tsx tests/financial-normalizer.test.ts && tsx tests/prompt-builder.test.ts && tsx tests/pii-extended.test.ts",
62
+ "build": "tsup src/index.ts src/finance/index.ts src/agent/index.ts --format cjs --format esm --dts --clean",
63
+ "test": "tsx tests/avantgate.test.ts && tsx tests/preflight-budget.test.ts && tsx tests/financial-normalizer.test.ts && tsx tests/prompt-builder.test.ts && tsx tests/pii-extended.test.ts && npm run test:agent",
50
64
  "test:finance": "tsx tests/financial-normalizer.test.ts",
51
65
  "test:prompts": "tsx tests/prompt-builder.test.ts",
52
66
  "test:pii": "tsx tests/pii-extended.test.ts",
67
+ "test:agent": "tsx tests/agent/step-runner.test.ts && tsx tests/agent/isolated-tool.test.ts && tsx tests/agent/dto-pattern.test.ts && tsx tests/agent/tool-patterns.test.ts && tsx tests/agent/storage-adapters.test.ts && tsx tests/agent/tool-aliasing.test.ts && tsx tests/agent/tool-chaining.test.ts && tsx tests/agent/tool-storage.test.ts && tsx tests/agent/telemetry-exporter.test.ts && tsx tests/agent/platform-adapter.test.ts",
53
68
  "lint": "tsc --noEmit",
54
69
  "prepublishOnly": "npm run build && npm test"
55
70
  },