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/README.md +45 -232
- package/dist/agent/index.d.mts +743 -0
- package/dist/agent/index.d.ts +743 -0
- package/dist/agent/index.js +1803 -0
- package/dist/agent/index.mjs +1700 -0
- package/dist/chunk-DVCF4CSV.mjs +56 -0
- package/dist/index.d.mts +89 -7
- package/dist/index.d.ts +89 -7
- package/dist/index.js +432 -51
- package/dist/index.mjs +424 -104
- package/package.json +19 -4
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
|
|
8
|
-
// DeepSeek
|
|
9
|
-
"deepseek-chat": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
promptUSDPerMillion: 0.15,
|
|
31
|
-
completionUSDPerMillion: 0.6
|
|
147
|
+
has(_target, prop) {
|
|
148
|
+
return Boolean(PricingRegistry.getPrice(prop));
|
|
32
149
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
completionUSDPerMillion: 10
|
|
150
|
+
ownKeys() {
|
|
151
|
+
return Object.keys(PricingRegistry.getAllPrices());
|
|
36
152
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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 =
|
|
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.
|
|
4
|
-
"description": "Zero-infrastructure, in-process LLM control plane: real-time cost control, token budgets, PII redaction, prompt guardrails,
|
|
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
|
},
|