avantgate 1.6.1 → 1.7.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/README.md +15 -1
- package/dist/agent/index.js +97 -24
- package/dist/agent/index.mjs +5 -2
- package/dist/agent/isolated-tool.d.ts +8 -3
- package/dist/agent/isolated-tool.d.ts.map +1 -1
- package/dist/agent/types.d.ts +16 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/{chunk-CO26LNFD.mjs → chunk-36ULFTM2.mjs} +8 -8
- package/dist/{chunk-4XWO3NZW.mjs → chunk-AH5XXSS4.mjs} +1 -1
- package/dist/{chunk-5VA3EKPJ.mjs → chunk-W27A25DK.mjs} +97 -25
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/client/index.mjs +2 -0
- package/dist/control-layer.d.ts +7 -7
- package/dist/control-layer.d.ts.map +1 -1
- package/dist/finance/index.js +6 -6
- package/dist/finance/index.mjs +2 -1
- package/dist/finance/normalizer.d.ts +2 -2
- package/dist/finance/normalizer.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +421 -130
- package/dist/index.mjs +318 -103
- package/dist/input-guard.d.ts +2 -2
- package/dist/input-guard.d.ts.map +1 -1
- package/dist/pricing.d.ts +17 -16
- package/dist/pricing.d.ts.map +1 -1
- package/dist/providers/http-client.d.ts +2 -2
- package/dist/providers/http-client.d.ts.map +1 -1
- package/dist/response-validator.d.ts +2 -2
- package/dist/response-validator.d.ts.map +1 -1
- package/dist/sanitizer.d.ts +1 -1
- package/dist/sanitizer.d.ts.map +1 -1
- package/dist/sqlite-pricing-adapter.d.ts +87 -0
- package/dist/sqlite-pricing-adapter.d.ts.map +1 -0
- package/dist/workflow/index.js +92 -24
- package/dist/workflow/index.mjs +3 -2
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -33,6 +43,7 @@ __export(index_exports, {
|
|
|
33
43
|
PromptRegistry: () => PromptRegistry,
|
|
34
44
|
PromptTemplate: () => PromptTemplate,
|
|
35
45
|
SEED_MODEL_PRICES: () => SEED_MODEL_PRICES,
|
|
46
|
+
SqlitePricingAdapter: () => SqlitePricingAdapter,
|
|
36
47
|
WorkflowAbortSignal: () => WorkflowAbortSignal,
|
|
37
48
|
WorkflowSagaRollbackError: () => WorkflowSagaRollbackError,
|
|
38
49
|
WorkflowValidationError: () => WorkflowValidationError,
|
|
@@ -67,6 +78,213 @@ var BudgetExceededError = class extends Error {
|
|
|
67
78
|
}
|
|
68
79
|
};
|
|
69
80
|
|
|
81
|
+
// src/sqlite-pricing-adapter.ts
|
|
82
|
+
var import_fs = __toESM(require("fs"));
|
|
83
|
+
var import_path = __toESM(require("path"));
|
|
84
|
+
var normalizeIdentifier = (val) => {
|
|
85
|
+
return val.trim().toLowerCase().replace(":", "/");
|
|
86
|
+
};
|
|
87
|
+
var SqlitePricingAdapter = class {
|
|
88
|
+
db;
|
|
89
|
+
tableName;
|
|
90
|
+
constructor(options = {}) {
|
|
91
|
+
this.tableName = options.tableName ?? "model_pricing";
|
|
92
|
+
this.db = options.db ?? this.initDatabase(options.dbPath ?? ":memory:");
|
|
93
|
+
this.initSchema();
|
|
94
|
+
if (options.autoSeed) {
|
|
95
|
+
this.seedDefaultPrices(false);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
initDatabase = (dbPath) => {
|
|
99
|
+
if (dbPath !== ":memory:") {
|
|
100
|
+
const dir = import_path.default.dirname(import_path.default.resolve(dbPath));
|
|
101
|
+
if (!import_fs.default.existsSync(dir)) {
|
|
102
|
+
import_fs.default.mkdirSync(dir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const { DatabaseSync } = require("sqlite");
|
|
107
|
+
return new DatabaseSync(dbPath);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw new ConfigurationError(
|
|
110
|
+
`[avantGate] Failed to initialize native SQLite at '${dbPath}'. Ensure you are running on Node.js 22+ or provide a custom 'db' instance in options. Error: ${String(err)}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
initSchema = () => {
|
|
115
|
+
this.db.exec(`
|
|
116
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
117
|
+
provider TEXT NOT NULL,
|
|
118
|
+
model TEXT NOT NULL,
|
|
119
|
+
prompt_usd_per_million REAL NOT NULL,
|
|
120
|
+
completion_usd_per_million REAL NOT NULL,
|
|
121
|
+
cache_hit_usd_per_million REAL,
|
|
122
|
+
is_active INTEGER DEFAULT 1,
|
|
123
|
+
updated_at TEXT NOT NULL,
|
|
124
|
+
PRIMARY KEY (provider, model)
|
|
125
|
+
);
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_lookup
|
|
127
|
+
ON ${this.tableName}(provider, model, is_active);
|
|
128
|
+
`);
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Implémentation du port avantGate PricingAdapter.
|
|
132
|
+
* Résolution hiérarchique : (provider, model) -> (provider/model) -> (model).
|
|
133
|
+
*/
|
|
134
|
+
fetchPrice = (model, provider) => {
|
|
135
|
+
const normModel = normalizeIdentifier(model);
|
|
136
|
+
const normProvider = provider ? normalizeIdentifier(provider) : void 0;
|
|
137
|
+
if (normProvider) {
|
|
138
|
+
const row = this.queryActiveRow(normProvider, normModel);
|
|
139
|
+
if (row) return this.mapRowToPrice(row);
|
|
140
|
+
}
|
|
141
|
+
if (normModel.includes("/")) {
|
|
142
|
+
const firstSlashIdx = normModel.indexOf("/");
|
|
143
|
+
const extractedProvider = normModel.slice(0, firstSlashIdx);
|
|
144
|
+
const extractedModel = normModel.slice(firstSlashIdx + 1);
|
|
145
|
+
const splitRow = this.queryActiveRow(extractedProvider, extractedModel);
|
|
146
|
+
if (splitRow) return this.mapRowToPrice(splitRow);
|
|
147
|
+
}
|
|
148
|
+
const directRow = this.queryByModelOnly(normModel);
|
|
149
|
+
if (directRow) return this.mapRowToPrice(directRow);
|
|
150
|
+
return void 0;
|
|
151
|
+
};
|
|
152
|
+
queryActiveRow = (provider, model) => {
|
|
153
|
+
const stmt = this.db.prepare(`
|
|
154
|
+
SELECT provider, model, prompt_usd_per_million, completion_usd_per_million, cache_hit_usd_per_million, is_active, updated_at
|
|
155
|
+
FROM ${this.tableName}
|
|
156
|
+
WHERE provider = ? AND model = ? AND is_active = 1
|
|
157
|
+
LIMIT 1
|
|
158
|
+
`);
|
|
159
|
+
return stmt.get(provider, model);
|
|
160
|
+
};
|
|
161
|
+
queryByModelOnly = (model) => {
|
|
162
|
+
const stmt = this.db.prepare(`
|
|
163
|
+
SELECT provider, model, prompt_usd_per_million, completion_usd_per_million, cache_hit_usd_per_million, is_active, updated_at
|
|
164
|
+
FROM ${this.tableName}
|
|
165
|
+
WHERE model = ? AND is_active = 1
|
|
166
|
+
LIMIT 1
|
|
167
|
+
`);
|
|
168
|
+
return stmt.get(model);
|
|
169
|
+
};
|
|
170
|
+
mapRowToPrice = (row) => {
|
|
171
|
+
return {
|
|
172
|
+
promptUSDPerMillion: Number(row.prompt_usd_per_million),
|
|
173
|
+
completionUSDPerMillion: Number(row.completion_usd_per_million),
|
|
174
|
+
cacheHitUSDPerMillion: row.cache_hit_usd_per_million != null ? Number(row.cache_hit_usd_per_million) : void 0
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Enregistre ou met à jour la tarification d'un modèle.
|
|
179
|
+
*/
|
|
180
|
+
upsertPrice = (provider, model, price, isActive = true) => {
|
|
181
|
+
const normProvider = normalizeIdentifier(provider);
|
|
182
|
+
const normModel = normalizeIdentifier(model);
|
|
183
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
184
|
+
const stmt = this.db.prepare(`
|
|
185
|
+
INSERT OR REPLACE INTO ${this.tableName}
|
|
186
|
+
(provider, model, prompt_usd_per_million, completion_usd_per_million, cache_hit_usd_per_million, is_active, updated_at)
|
|
187
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
188
|
+
`);
|
|
189
|
+
stmt.run(
|
|
190
|
+
normProvider,
|
|
191
|
+
normModel,
|
|
192
|
+
price.promptUSDPerMillion,
|
|
193
|
+
price.completionUSDPerMillion,
|
|
194
|
+
price.cacheHitUSDPerMillion ?? null,
|
|
195
|
+
isActive ? 1 : 0,
|
|
196
|
+
now
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Supprime une règle tarifaire de la base de données.
|
|
201
|
+
*/
|
|
202
|
+
deletePrice = (provider, model) => {
|
|
203
|
+
const normProvider = normalizeIdentifier(provider);
|
|
204
|
+
const normModel = normalizeIdentifier(model);
|
|
205
|
+
const stmt = this.db.prepare(`
|
|
206
|
+
DELETE FROM ${this.tableName}
|
|
207
|
+
WHERE provider = ? AND model = ?
|
|
208
|
+
`);
|
|
209
|
+
const result = stmt.run(normProvider, normModel);
|
|
210
|
+
return Boolean(result && typeof result.changes === "number" && result.changes > 0);
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Retourne l'ensemble des règles tarifaires enregistrées.
|
|
214
|
+
*/
|
|
215
|
+
getAllPrices = () => {
|
|
216
|
+
const stmt = this.db.prepare(`
|
|
217
|
+
SELECT provider, model, prompt_usd_per_million, completion_usd_per_million, cache_hit_usd_per_million, is_active, updated_at
|
|
218
|
+
FROM ${this.tableName}
|
|
219
|
+
ORDER BY provider ASC, model ASC
|
|
220
|
+
`);
|
|
221
|
+
const rows = stmt.all();
|
|
222
|
+
return rows.map((row) => ({
|
|
223
|
+
provider: row.provider,
|
|
224
|
+
model: row.model,
|
|
225
|
+
price: this.mapRowToPrice(row),
|
|
226
|
+
isActive: Boolean(row.is_active),
|
|
227
|
+
updatedAt: row.updated_at
|
|
228
|
+
}));
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Remplit la base avec le catalogue de départ SEED_MODEL_PRICES.
|
|
232
|
+
* @param force Si true, écrase les tarifs existants. Si false, insère uniquement les modèles manquants.
|
|
233
|
+
* @returns Le nombre d'enregistrements insérés.
|
|
234
|
+
*/
|
|
235
|
+
seedDefaultPrices = (force = false) => {
|
|
236
|
+
const conflictClause = force ? "INSERT OR REPLACE" : "INSERT OR IGNORE";
|
|
237
|
+
const stmt = this.db.prepare(`
|
|
238
|
+
${conflictClause} INTO ${this.tableName}
|
|
239
|
+
(provider, model, prompt_usd_per_million, completion_usd_per_million, cache_hit_usd_per_million, is_active, updated_at)
|
|
240
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
241
|
+
`);
|
|
242
|
+
let inserted = 0;
|
|
243
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
244
|
+
for (const [key, price] of Object.entries(SEED_MODEL_PRICES)) {
|
|
245
|
+
const normKey = normalizeIdentifier(key);
|
|
246
|
+
let provider = "direct";
|
|
247
|
+
let model = normKey;
|
|
248
|
+
if (normKey.includes("/")) {
|
|
249
|
+
const slashIdx = normKey.indexOf("/");
|
|
250
|
+
provider = normKey.slice(0, slashIdx);
|
|
251
|
+
model = normKey.slice(slashIdx + 1);
|
|
252
|
+
}
|
|
253
|
+
const res1 = stmt.run(
|
|
254
|
+
provider,
|
|
255
|
+
model,
|
|
256
|
+
price.promptUSDPerMillion,
|
|
257
|
+
price.completionUSDPerMillion,
|
|
258
|
+
price.cacheHitUSDPerMillion ?? null,
|
|
259
|
+
1,
|
|
260
|
+
now
|
|
261
|
+
);
|
|
262
|
+
if (res1?.changes && res1.changes > 0) inserted++;
|
|
263
|
+
if (provider !== "direct") {
|
|
264
|
+
const res2 = stmt.run(
|
|
265
|
+
"direct",
|
|
266
|
+
normKey,
|
|
267
|
+
price.promptUSDPerMillion,
|
|
268
|
+
price.completionUSDPerMillion,
|
|
269
|
+
price.cacheHitUSDPerMillion ?? null,
|
|
270
|
+
1,
|
|
271
|
+
now
|
|
272
|
+
);
|
|
273
|
+
if (res2?.changes && res2.changes > 0) inserted++;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return inserted;
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* Ferme la connexion SQLite sous-jacente si supporté.
|
|
280
|
+
*/
|
|
281
|
+
close = () => {
|
|
282
|
+
if (typeof this.db.close === "function") {
|
|
283
|
+
this.db.close();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
70
288
|
// src/pricing.ts
|
|
71
289
|
var SEED_MODEL_PRICES = {
|
|
72
290
|
// DeepSeek Direct
|
|
@@ -91,9 +309,9 @@ var SEED_MODEL_PRICES = {
|
|
|
91
309
|
// Local Ollama (Toujours $0)
|
|
92
310
|
"ollama": { promptUSDPerMillion: 0, completionUSDPerMillion: 0 }
|
|
93
311
|
};
|
|
94
|
-
|
|
312
|
+
var normalizeKey = (identifier) => {
|
|
95
313
|
return identifier.trim().toLowerCase().replace(":", "/");
|
|
96
|
-
}
|
|
314
|
+
};
|
|
97
315
|
var CachedPricingAdapter = class {
|
|
98
316
|
cache = /* @__PURE__ */ new Map();
|
|
99
317
|
ttlMs;
|
|
@@ -102,7 +320,7 @@ var CachedPricingAdapter = class {
|
|
|
102
320
|
this.delegate = delegate;
|
|
103
321
|
this.ttlMs = ttlMs;
|
|
104
322
|
}
|
|
105
|
-
async
|
|
323
|
+
fetchPrice = async (model, provider) => {
|
|
106
324
|
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
107
325
|
const cached = this.cache.get(key);
|
|
108
326
|
if (cached && Date.now() < cached.expiresAt) {
|
|
@@ -120,72 +338,76 @@ var CachedPricingAdapter = class {
|
|
|
120
338
|
}
|
|
121
339
|
}
|
|
122
340
|
return void 0;
|
|
123
|
-
}
|
|
124
|
-
peek(model, provider) {
|
|
341
|
+
};
|
|
342
|
+
peek = (model, provider) => {
|
|
125
343
|
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
344
|
+
const cached = this.cache.get(key);
|
|
345
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
346
|
+
return cached.price;
|
|
347
|
+
}
|
|
348
|
+
return void 0;
|
|
349
|
+
};
|
|
350
|
+
clearCache = () => {
|
|
129
351
|
this.cache.clear();
|
|
130
|
-
}
|
|
131
|
-
invalidate(model, provider) {
|
|
352
|
+
};
|
|
353
|
+
invalidate = (model, provider) => {
|
|
132
354
|
if (!model) {
|
|
133
355
|
this.clearCache();
|
|
134
356
|
return;
|
|
135
357
|
}
|
|
136
358
|
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
137
359
|
this.cache.delete(key);
|
|
138
|
-
}
|
|
360
|
+
};
|
|
139
361
|
};
|
|
140
|
-
var PricingRegistry = class {
|
|
362
|
+
var PricingRegistry = class _PricingRegistry {
|
|
141
363
|
static prices = /* @__PURE__ */ new Map();
|
|
142
364
|
static adapter;
|
|
143
|
-
static registerPrice(identifier, price) {
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
static registerDistributorPrices(distributor, priceMap) {
|
|
365
|
+
static registerPrice = (identifier, price) => {
|
|
366
|
+
_PricingRegistry.prices.set(normalizeKey(identifier), price);
|
|
367
|
+
};
|
|
368
|
+
static registerDistributorPrices = (distributor, priceMap) => {
|
|
147
369
|
for (const [model, price] of Object.entries(priceMap)) {
|
|
148
|
-
|
|
370
|
+
_PricingRegistry.prices.set(normalizeKey(`${distributor}/${model}`), price);
|
|
149
371
|
}
|
|
150
|
-
}
|
|
151
|
-
static registerPrices(prices) {
|
|
372
|
+
};
|
|
373
|
+
static registerPrices = (prices) => {
|
|
152
374
|
for (const [key, price] of Object.entries(prices)) {
|
|
153
|
-
|
|
375
|
+
_PricingRegistry.prices.set(normalizeKey(key), price);
|
|
154
376
|
}
|
|
155
|
-
}
|
|
156
|
-
static getPrice(model, provider) {
|
|
377
|
+
};
|
|
378
|
+
static getPrice = (model, provider) => {
|
|
157
379
|
if (provider) {
|
|
158
380
|
const distributorKey = normalizeKey(`${provider}/${model}`);
|
|
159
|
-
const directDistributorPrice =
|
|
381
|
+
const directDistributorPrice = _PricingRegistry.prices.get(distributorKey);
|
|
160
382
|
if (directDistributorPrice) {
|
|
161
383
|
return directDistributorPrice;
|
|
162
384
|
}
|
|
163
385
|
}
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
static setAdapter(adapter) {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
static getAdapter() {
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
static clear() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
386
|
+
return _PricingRegistry.prices.get(normalizeKey(model));
|
|
387
|
+
};
|
|
388
|
+
static setAdapter = (adapter) => {
|
|
389
|
+
_PricingRegistry.adapter = adapter;
|
|
390
|
+
};
|
|
391
|
+
static getAdapter = () => {
|
|
392
|
+
return _PricingRegistry.adapter;
|
|
393
|
+
};
|
|
394
|
+
static clear = () => {
|
|
395
|
+
_PricingRegistry.prices.clear();
|
|
396
|
+
_PricingRegistry.adapter = void 0;
|
|
397
|
+
};
|
|
176
398
|
/**
|
|
177
399
|
* Réinitialise le registre en chargeant le catalogue d'exemple SEED_MODEL_PRICES.
|
|
178
400
|
*/
|
|
179
|
-
static loadSeedPrices() {
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
static getAllPrices() {
|
|
401
|
+
static loadSeedPrices = () => {
|
|
402
|
+
_PricingRegistry.registerPrices(SEED_MODEL_PRICES);
|
|
403
|
+
};
|
|
404
|
+
static getAllPrices = () => {
|
|
183
405
|
const out = {};
|
|
184
|
-
for (const [k, v] of
|
|
406
|
+
for (const [k, v] of _PricingRegistry.prices.entries()) {
|
|
185
407
|
out[k] = v;
|
|
186
408
|
}
|
|
187
409
|
return out;
|
|
188
|
-
}
|
|
410
|
+
};
|
|
189
411
|
};
|
|
190
412
|
var DEFAULT_MODEL_PRICES = new Proxy({}, {
|
|
191
413
|
get(_target, prop) {
|
|
@@ -205,7 +427,7 @@ var DEFAULT_MODEL_PRICES = new Proxy({}, {
|
|
|
205
427
|
return void 0;
|
|
206
428
|
}
|
|
207
429
|
});
|
|
208
|
-
|
|
430
|
+
var resolveModelPrice = (model, options) => {
|
|
209
431
|
const opts = typeof options === "string" ? { provider: options } : options ?? {};
|
|
210
432
|
if (opts.providerPricing) {
|
|
211
433
|
return opts.providerPricing;
|
|
@@ -236,8 +458,8 @@ function resolveModelPrice(model, options) {
|
|
|
236
458
|
return { promptUSDPerMillion: 0, completionUSDPerMillion: 0 };
|
|
237
459
|
}
|
|
238
460
|
return void 0;
|
|
239
|
-
}
|
|
240
|
-
async
|
|
461
|
+
};
|
|
462
|
+
var resolveModelPriceAsync = async (model, options) => {
|
|
241
463
|
const opts = typeof options === "string" ? { provider: options } : options ?? {};
|
|
242
464
|
const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
|
|
243
465
|
if (activeAdapter) {
|
|
@@ -250,8 +472,8 @@ async function resolveModelPriceAsync(model, options) {
|
|
|
250
472
|
}
|
|
251
473
|
}
|
|
252
474
|
return resolveModelPrice(model, options);
|
|
253
|
-
}
|
|
254
|
-
|
|
475
|
+
};
|
|
476
|
+
var calculateCostUSD = (model, promptTokens, completionTokens, cacheHitTokens = 0, options) => {
|
|
255
477
|
const pricing = resolveModelPrice(model, options);
|
|
256
478
|
if (!pricing) {
|
|
257
479
|
return 0;
|
|
@@ -260,7 +482,7 @@ function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens
|
|
|
260
482
|
const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
|
|
261
483
|
const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
|
|
262
484
|
return Math.max(0, promptCost + completionCost - cacheDiscount);
|
|
263
|
-
}
|
|
485
|
+
};
|
|
264
486
|
|
|
265
487
|
// src/sanitizer.ts
|
|
266
488
|
var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
|
|
@@ -270,7 +492,7 @@ var SPI_LABELLED_REGEX = /(?:(?:num[ée]ro\s+fiscal|spi|n[°o]\s*fiscal|d[ée]cl
|
|
|
270
492
|
var SPI_FORMATTED_REGEX = /\b[0-3]\d(?:\s+\d{2}){5}\s+\d\b/g;
|
|
271
493
|
var IBAN_REGEX = /\b[A-Z]{2}\s*[0-9]{2}(?:[\s\r\n.-]*[A-Z0-9]){11,30}\b/g;
|
|
272
494
|
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;
|
|
273
|
-
|
|
495
|
+
var sanitizePII = (input) => {
|
|
274
496
|
let count = 0;
|
|
275
497
|
let result = input;
|
|
276
498
|
result = result.replace(EMAIL_REGEX, () => {
|
|
@@ -313,7 +535,7 @@ function sanitizePII(input) {
|
|
|
313
535
|
return match;
|
|
314
536
|
});
|
|
315
537
|
return { text: result, maskedCount: count };
|
|
316
|
-
}
|
|
538
|
+
};
|
|
317
539
|
|
|
318
540
|
// src/input-guard.ts
|
|
319
541
|
var INJECTION_PATTERNS = [
|
|
@@ -324,7 +546,7 @@ var INJECTION_PATTERNS = [
|
|
|
324
546
|
/output\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions)/i,
|
|
325
547
|
/repeat\s+(?:the\s+words\s+above|everything\s+above)/i
|
|
326
548
|
];
|
|
327
|
-
|
|
549
|
+
var validateUserInput = (input, options) => {
|
|
328
550
|
const maxLength = options?.maxLength ?? 5e4;
|
|
329
551
|
if (input.length > maxLength) {
|
|
330
552
|
return {
|
|
@@ -343,7 +565,7 @@ function validateUserInput(input, options) {
|
|
|
343
565
|
}
|
|
344
566
|
}
|
|
345
567
|
return { valid: true };
|
|
346
|
-
}
|
|
568
|
+
};
|
|
347
569
|
|
|
348
570
|
// src/finance/strategies/french-pcg.strategy.ts
|
|
349
571
|
var FrenchPCGStrategy = class {
|
|
@@ -799,17 +1021,17 @@ var AccountingFactory = class {
|
|
|
799
1021
|
};
|
|
800
1022
|
|
|
801
1023
|
// src/finance/normalizer.ts
|
|
802
|
-
|
|
1024
|
+
var cleanFinancialJSON = (rawText, options) => {
|
|
803
1025
|
if (!rawText) {
|
|
804
1026
|
return "";
|
|
805
1027
|
}
|
|
806
1028
|
const jsonBlock = extractAndCleanJSON(rawText);
|
|
807
1029
|
const strategy = options?.jurisdiction ? AccountingFactory.getStrategy(options.jurisdiction) : options?.autoDetect !== false ? AccountingFactory.detectStrategy(rawText) : AccountingFactory.getStrategy("FR");
|
|
808
1030
|
return strategy.cleanJSON(jsonBlock);
|
|
809
|
-
}
|
|
1031
|
+
};
|
|
810
1032
|
|
|
811
1033
|
// src/response-validator.ts
|
|
812
|
-
|
|
1034
|
+
var extractAndCleanJSON = (rawText) => {
|
|
813
1035
|
let cleaned = rawText.trim();
|
|
814
1036
|
cleaned = cleaned.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
|
|
815
1037
|
const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
@@ -833,8 +1055,8 @@ function extractAndCleanJSON(rawText) {
|
|
|
833
1055
|
cleaned = cleaned.slice(startIndex, endIndex + 1);
|
|
834
1056
|
}
|
|
835
1057
|
return cleaned;
|
|
836
|
-
}
|
|
837
|
-
|
|
1058
|
+
};
|
|
1059
|
+
var validateWithZod = (rawText, schema, options) => {
|
|
838
1060
|
let jsonString = extractAndCleanJSON(rawText);
|
|
839
1061
|
if (options?.normalizer) {
|
|
840
1062
|
jsonString = options.normalizer(jsonString);
|
|
@@ -849,7 +1071,7 @@ function validateWithZod(rawText, schema, options) {
|
|
|
849
1071
|
parsed = JSON.parse(sanitized);
|
|
850
1072
|
}
|
|
851
1073
|
return schema.parse(parsed);
|
|
852
|
-
}
|
|
1074
|
+
};
|
|
853
1075
|
|
|
854
1076
|
// src/providers/http-client.ts
|
|
855
1077
|
var DEFAULT_BASE_URLS = {
|
|
@@ -870,14 +1092,14 @@ var HttpProviderClient = class {
|
|
|
870
1092
|
this.apiKey = config.apiKey;
|
|
871
1093
|
this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
|
|
872
1094
|
}
|
|
873
|
-
resolveEndpoint() {
|
|
1095
|
+
resolveEndpoint = () => {
|
|
874
1096
|
const trimmed = this.baseUrl.replace(/\/+$/, "");
|
|
875
1097
|
if (trimmed.endsWith("/chat/completions")) {
|
|
876
1098
|
return trimmed;
|
|
877
1099
|
}
|
|
878
1100
|
return `${trimmed}/chat/completions`;
|
|
879
|
-
}
|
|
880
|
-
buildHeaders() {
|
|
1101
|
+
};
|
|
1102
|
+
buildHeaders = () => {
|
|
881
1103
|
const headers = {
|
|
882
1104
|
"Content-Type": "application/json"
|
|
883
1105
|
};
|
|
@@ -889,8 +1111,8 @@ var HttpProviderClient = class {
|
|
|
889
1111
|
headers["X-Title"] = "AvantGate";
|
|
890
1112
|
}
|
|
891
1113
|
return headers;
|
|
892
|
-
}
|
|
893
|
-
buildPayload(options, model) {
|
|
1114
|
+
};
|
|
1115
|
+
buildPayload = (options, model) => {
|
|
894
1116
|
return {
|
|
895
1117
|
model,
|
|
896
1118
|
messages: options.messages.map((m) => ({
|
|
@@ -900,8 +1122,8 @@ var HttpProviderClient = class {
|
|
|
900
1122
|
temperature: options.temperature ?? 0.2,
|
|
901
1123
|
...options.responseFormat ? { response_format: options.responseFormat } : {}
|
|
902
1124
|
};
|
|
903
|
-
}
|
|
904
|
-
extractUsage(usageData) {
|
|
1125
|
+
};
|
|
1126
|
+
extractUsage = (usageData) => {
|
|
905
1127
|
if (!usageData) {
|
|
906
1128
|
return void 0;
|
|
907
1129
|
}
|
|
@@ -911,8 +1133,8 @@ var HttpProviderClient = class {
|
|
|
911
1133
|
totalTokens: usageData.total_tokens,
|
|
912
1134
|
promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
|
|
913
1135
|
};
|
|
914
|
-
}
|
|
915
|
-
async
|
|
1136
|
+
};
|
|
1137
|
+
complete = async (options) => {
|
|
916
1138
|
const endpoint = this.resolveEndpoint();
|
|
917
1139
|
const model = options.model ?? "default";
|
|
918
1140
|
const headers = this.buildHeaders();
|
|
@@ -932,11 +1154,11 @@ var HttpProviderClient = class {
|
|
|
932
1154
|
const text = data.choices?.[0]?.message?.content ?? "";
|
|
933
1155
|
const usage = this.extractUsage(data.usage);
|
|
934
1156
|
return { text, usage };
|
|
935
|
-
}
|
|
1157
|
+
};
|
|
936
1158
|
};
|
|
937
|
-
|
|
1159
|
+
var createHttpProviderClient = (config) => {
|
|
938
1160
|
return new HttpProviderClient(config);
|
|
939
|
-
}
|
|
1161
|
+
};
|
|
940
1162
|
|
|
941
1163
|
// src/control-layer.ts
|
|
942
1164
|
var AvantGateControlLayer = class {
|
|
@@ -956,7 +1178,7 @@ var AvantGateControlLayer = class {
|
|
|
956
1178
|
);
|
|
957
1179
|
}
|
|
958
1180
|
}
|
|
959
|
-
resolveProviderConfig(provider) {
|
|
1181
|
+
resolveProviderConfig = (provider) => {
|
|
960
1182
|
if (!provider) return void 0;
|
|
961
1183
|
if (provider.client) return provider;
|
|
962
1184
|
if (provider.apiKey || provider.baseUrl || provider.provider === "ollama") {
|
|
@@ -966,8 +1188,8 @@ var AvantGateControlLayer = class {
|
|
|
966
1188
|
};
|
|
967
1189
|
}
|
|
968
1190
|
return provider;
|
|
969
|
-
}
|
|
970
|
-
findProviderConfig(provider, model) {
|
|
1191
|
+
};
|
|
1192
|
+
findProviderConfig = (provider, model) => {
|
|
971
1193
|
const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
|
|
972
1194
|
(p) => Boolean(p)
|
|
973
1195
|
);
|
|
@@ -980,8 +1202,8 @@ var AvantGateControlLayer = class {
|
|
|
980
1202
|
if (matchModel) return matchModel;
|
|
981
1203
|
}
|
|
982
1204
|
return this.config.primary;
|
|
983
|
-
}
|
|
984
|
-
calculateCost(model, promptTokens, completionTokens, cacheHitTokens = 0, provider) {
|
|
1205
|
+
};
|
|
1206
|
+
calculateCost = (model, promptTokens, completionTokens, cacheHitTokens = 0, provider) => {
|
|
985
1207
|
const providerCfg = this.findProviderConfig(provider, model);
|
|
986
1208
|
return calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens, {
|
|
987
1209
|
provider: provider ?? providerCfg?.provider,
|
|
@@ -989,8 +1211,8 @@ var AvantGateControlLayer = class {
|
|
|
989
1211
|
customPricing: this.config.customPricing,
|
|
990
1212
|
adapter: this.cachedPricingAdapter
|
|
991
1213
|
});
|
|
992
|
-
}
|
|
993
|
-
applySecurityGuards(userQuery) {
|
|
1214
|
+
};
|
|
1215
|
+
applySecurityGuards = (userQuery) => {
|
|
994
1216
|
const guard = validateUserInput(userQuery, {
|
|
995
1217
|
detectInjection: this.config.security?.detectPromptInjection,
|
|
996
1218
|
maxLength: this.config.security?.maxInputLength
|
|
@@ -1002,8 +1224,8 @@ var AvantGateControlLayer = class {
|
|
|
1002
1224
|
return sanitizePII(userQuery).text;
|
|
1003
1225
|
}
|
|
1004
1226
|
return userQuery;
|
|
1005
|
-
}
|
|
1006
|
-
checkPreflightBudget(estimatedPromptTokens, targetModel, provider) {
|
|
1227
|
+
};
|
|
1228
|
+
checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider) => {
|
|
1007
1229
|
if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
|
|
1008
1230
|
throw new BudgetExceededError(
|
|
1009
1231
|
`[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
|
|
@@ -1012,7 +1234,7 @@ var AvantGateControlLayer = class {
|
|
|
1012
1234
|
if (this.config.maxCostUSD !== void 0) {
|
|
1013
1235
|
const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
|
|
1014
1236
|
const providerCfg = this.findProviderConfig(provider, targetModel);
|
|
1015
|
-
const price =
|
|
1237
|
+
const price = await resolveModelPriceAsync(targetModel, {
|
|
1016
1238
|
provider: provider ?? providerCfg?.provider,
|
|
1017
1239
|
providerPricing: providerCfg?.pricing,
|
|
1018
1240
|
customPricing: this.config.customPricing,
|
|
@@ -1030,8 +1252,8 @@ var AvantGateControlLayer = class {
|
|
|
1030
1252
|
);
|
|
1031
1253
|
}
|
|
1032
1254
|
}
|
|
1033
|
-
}
|
|
1034
|
-
checkPostExecutionBudget(tokensTotal, costUSD) {
|
|
1255
|
+
};
|
|
1256
|
+
checkPostExecutionBudget = (tokensTotal, costUSD) => {
|
|
1035
1257
|
if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
|
|
1036
1258
|
throw new BudgetExceededError(
|
|
1037
1259
|
`[AvantGate Budget Guard] Execution total tokens (${tokensTotal}) exceeded maxTokenBudget (${this.config.maxTokenBudget}).`
|
|
@@ -1042,16 +1264,16 @@ var AvantGateControlLayer = class {
|
|
|
1042
1264
|
`[AvantGate Budget Guard] Execution cost ($${costUSD.toFixed(6)}) exceeded maxCostUSD ($${this.config.maxCostUSD}).`
|
|
1043
1265
|
);
|
|
1044
1266
|
}
|
|
1045
|
-
}
|
|
1046
|
-
buildMessages(systemPrompt, query) {
|
|
1267
|
+
};
|
|
1268
|
+
buildMessages = (systemPrompt, query) => {
|
|
1047
1269
|
const messages = [];
|
|
1048
1270
|
if (systemPrompt) {
|
|
1049
1271
|
messages.push({ role: "system", content: systemPrompt });
|
|
1050
1272
|
}
|
|
1051
1273
|
messages.push({ role: "user", content: query });
|
|
1052
1274
|
return messages;
|
|
1053
|
-
}
|
|
1054
|
-
resolveTokens(rawUsage, query, text) {
|
|
1275
|
+
};
|
|
1276
|
+
resolveTokens = (rawUsage, query, text) => {
|
|
1055
1277
|
const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
|
|
1056
1278
|
const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
|
|
1057
1279
|
const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
|
|
@@ -1061,15 +1283,15 @@ var AvantGateControlLayer = class {
|
|
|
1061
1283
|
totalTokens,
|
|
1062
1284
|
promptCacheHitTokens: rawUsage?.promptCacheHitTokens
|
|
1063
1285
|
};
|
|
1064
|
-
}
|
|
1065
|
-
getProviderChain() {
|
|
1286
|
+
};
|
|
1287
|
+
getProviderChain = () => {
|
|
1066
1288
|
return [
|
|
1067
1289
|
this.config.primary,
|
|
1068
1290
|
this.config.fallback,
|
|
1069
1291
|
this.config.emergencyFallback
|
|
1070
1292
|
].filter((provider) => Boolean(provider?.client));
|
|
1071
|
-
}
|
|
1072
|
-
executeSimulation(query, systemPrompt) {
|
|
1293
|
+
};
|
|
1294
|
+
executeSimulation = (query, systemPrompt) => {
|
|
1073
1295
|
const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
|
|
1074
1296
|
const completionTokens = 50;
|
|
1075
1297
|
const cost = this.calculateCost(this.config.primary.model, promptTokens, completionTokens);
|
|
@@ -1085,8 +1307,8 @@ var AvantGateControlLayer = class {
|
|
|
1085
1307
|
failoverOccurred: false,
|
|
1086
1308
|
attempts: 1
|
|
1087
1309
|
};
|
|
1088
|
-
}
|
|
1089
|
-
async
|
|
1310
|
+
};
|
|
1311
|
+
executeOverrideProvider = async (provider, messages, query, temperature) => {
|
|
1090
1312
|
const response = await provider.complete({
|
|
1091
1313
|
model: this.config.primary.model,
|
|
1092
1314
|
messages,
|
|
@@ -1100,8 +1322,8 @@ var AvantGateControlLayer = class {
|
|
|
1100
1322
|
failoverOccurred: false,
|
|
1101
1323
|
attempts: 1
|
|
1102
1324
|
};
|
|
1103
|
-
}
|
|
1104
|
-
async
|
|
1325
|
+
};
|
|
1326
|
+
executeProviderPipeline = async (messages, query, temperature, modelOverride) => {
|
|
1105
1327
|
const chain = this.getProviderChain();
|
|
1106
1328
|
let lastError;
|
|
1107
1329
|
for (let index = 0; index < chain.length; index++) {
|
|
@@ -1126,8 +1348,8 @@ var AvantGateControlLayer = class {
|
|
|
1126
1348
|
}
|
|
1127
1349
|
}
|
|
1128
1350
|
throw lastError;
|
|
1129
|
-
}
|
|
1130
|
-
assembleResult(output) {
|
|
1351
|
+
};
|
|
1352
|
+
assembleResult = (output) => {
|
|
1131
1353
|
const costUSD = this.calculateCost(
|
|
1132
1354
|
output.modelUsed,
|
|
1133
1355
|
output.usage.promptTokens,
|
|
@@ -1146,8 +1368,8 @@ var AvantGateControlLayer = class {
|
|
|
1146
1368
|
failoverOccurred: output.failoverOccurred,
|
|
1147
1369
|
attempts: output.attempts
|
|
1148
1370
|
};
|
|
1149
|
-
}
|
|
1150
|
-
async
|
|
1371
|
+
};
|
|
1372
|
+
notifyAuditSink = async (result) => {
|
|
1151
1373
|
if (!this.config.auditSink) {
|
|
1152
1374
|
return;
|
|
1153
1375
|
}
|
|
@@ -1159,16 +1381,16 @@ var AvantGateControlLayer = class {
|
|
|
1159
1381
|
failoverOccurred: result.failoverOccurred,
|
|
1160
1382
|
attempts: result.attempts
|
|
1161
1383
|
});
|
|
1162
|
-
}
|
|
1384
|
+
};
|
|
1163
1385
|
/**
|
|
1164
1386
|
* Exécute une requête avec garde d'entrée, masquage PII, garde pré-vol et calcul des coûts.
|
|
1165
1387
|
*/
|
|
1166
|
-
async
|
|
1388
|
+
execute = async (options) => {
|
|
1167
1389
|
const sanitizedQuery = this.applySecurityGuards(options.userQuery);
|
|
1168
1390
|
const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
|
|
1169
1391
|
const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
|
|
1170
1392
|
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
1171
|
-
this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
|
|
1393
|
+
await this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
|
|
1172
1394
|
if (!options.providerOverride && this.getProviderChain().length === 0) {
|
|
1173
1395
|
if (this.config.mockSimulation) {
|
|
1174
1396
|
const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
|
|
@@ -1184,11 +1406,11 @@ var AvantGateControlLayer = class {
|
|
|
1184
1406
|
this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
|
|
1185
1407
|
await this.notifyAuditSink(result);
|
|
1186
1408
|
return result;
|
|
1187
|
-
}
|
|
1409
|
+
};
|
|
1188
1410
|
/**
|
|
1189
1411
|
* Exécute une requête et valide/répare le résultat selon un schéma Zod.
|
|
1190
1412
|
*/
|
|
1191
|
-
async
|
|
1413
|
+
executeStructured = async (options) => {
|
|
1192
1414
|
const rawResult = await this.execute({
|
|
1193
1415
|
userQuery: options.userQuery,
|
|
1194
1416
|
systemPrompt: options.systemPrompt,
|
|
@@ -1210,12 +1432,12 @@ var AvantGateControlLayer = class {
|
|
|
1210
1432
|
modelUsed: rawResult.modelUsed,
|
|
1211
1433
|
failoverOccurred: rawResult.failoverOccurred
|
|
1212
1434
|
};
|
|
1213
|
-
}
|
|
1435
|
+
};
|
|
1214
1436
|
/**
|
|
1215
1437
|
* Méthode unifiée de premier niveau pour l'extraction structurée sans code boilerplate.
|
|
1216
1438
|
* Gère le failover multi-fournisseurs, les retries, la validation Zod et la normalisation financière.
|
|
1217
1439
|
*/
|
|
1218
|
-
async
|
|
1440
|
+
generateStructuredOutput = async (options) => {
|
|
1219
1441
|
const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
|
|
1220
1442
|
const modelToUse = options.model ?? this.config.primary.model;
|
|
1221
1443
|
const processedMessages = options.messages.map((msg) => {
|
|
@@ -1226,7 +1448,7 @@ var AvantGateControlLayer = class {
|
|
|
1226
1448
|
});
|
|
1227
1449
|
const promptLength = processedMessages.reduce((sum, msg) => sum + msg.content.length, 0);
|
|
1228
1450
|
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
1229
|
-
this.checkPreflightBudget(estimatedPromptTokens, modelToUse);
|
|
1451
|
+
await this.checkPreflightBudget(estimatedPromptTokens, modelToUse);
|
|
1230
1452
|
let lastError;
|
|
1231
1453
|
let accumulatedPromptTokens = 0;
|
|
1232
1454
|
let accumulatedCompletionTokens = 0;
|
|
@@ -1314,11 +1536,11 @@ var AvantGateControlLayer = class {
|
|
|
1314
1536
|
}
|
|
1315
1537
|
}
|
|
1316
1538
|
throw lastError;
|
|
1317
|
-
}
|
|
1539
|
+
};
|
|
1318
1540
|
};
|
|
1319
|
-
|
|
1541
|
+
var createLLMControlLayer = (config) => {
|
|
1320
1542
|
return new AvantGateControlLayer(config);
|
|
1321
|
-
}
|
|
1543
|
+
};
|
|
1322
1544
|
var createAvantGate = createLLMControlLayer;
|
|
1323
1545
|
|
|
1324
1546
|
// src/prompts/prompt-template.ts
|
|
@@ -1741,7 +1963,23 @@ function auditToolResult(data, options) {
|
|
|
1741
1963
|
}
|
|
1742
1964
|
|
|
1743
1965
|
// src/agent/isolated-tool.ts
|
|
1744
|
-
|
|
1966
|
+
var verifyRoles = (configuredRoles, context, toolIdentifier) => {
|
|
1967
|
+
if (!configuredRoles || configuredRoles.length === 0) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const userRoles = [
|
|
1971
|
+
...context.roles ?? [],
|
|
1972
|
+
...context.role ? [context.role] : []
|
|
1973
|
+
];
|
|
1974
|
+
const hasAllowedRole = configuredRoles.some((role) => userRoles.includes(role));
|
|
1975
|
+
if (!hasAllowedRole) {
|
|
1976
|
+
throw new ToolAccessDeniedError(
|
|
1977
|
+
toolIdentifier,
|
|
1978
|
+
"Access denied: insufficient role permissions"
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
var verifyDataAccessGuard = async (guard, args, context, toolIdentifier) => {
|
|
1745
1983
|
if (!guard) {
|
|
1746
1984
|
return;
|
|
1747
1985
|
}
|
|
@@ -1749,8 +1987,55 @@ async function verifyDataAccessGuard(guard, args, context, toolIdentifier) {
|
|
|
1749
1987
|
if (!isAllowed) {
|
|
1750
1988
|
throw new ToolAccessDeniedError(toolIdentifier, "Access denied by data access guard");
|
|
1751
1989
|
}
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1990
|
+
};
|
|
1991
|
+
var verifyTenantIsolation = (assertTenant, rawResult, context, toolIdentifier) => {
|
|
1992
|
+
if (!assertTenant) {
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
if (!context.tenantId) {
|
|
1996
|
+
throw new ToolAccessDeniedError(
|
|
1997
|
+
toolIdentifier,
|
|
1998
|
+
"Access denied: missing session tenantId in execution context for multi-tenant tool"
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
const recordTenantId = assertTenant(rawResult);
|
|
2002
|
+
if (recordTenantId && recordTenantId !== context.tenantId) {
|
|
2003
|
+
throw new ToolAccessDeniedError(
|
|
2004
|
+
toolIdentifier,
|
|
2005
|
+
`Cross-tenant IDOR access violation: record tenant '${recordTenantId}' does not match session tenant '${context.tenantId}'`
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
var verifyResourceOwnership = async (assertOwnership, rawResult, context, toolIdentifier) => {
|
|
2010
|
+
if (!assertOwnership) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
const isOwner = await assertOwnership(rawResult, context);
|
|
2014
|
+
if (!isOwner) {
|
|
2015
|
+
throw new ToolAccessDeniedError(
|
|
2016
|
+
toolIdentifier,
|
|
2017
|
+
"Ownership IDOR access violation: user does not own or have access to this resource"
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
var checkSuspiciousParameters = (parameters, toolName) => {
|
|
2022
|
+
if (!parameters || typeof parameters !== "object") {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
const zodShape = parameters.shape;
|
|
2026
|
+
if (!zodShape || typeof zodShape !== "object") {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const suspiciousKeys = ["tenantId", "tenant_id", "ownerTenantId"];
|
|
2030
|
+
for (const key of suspiciousKeys) {
|
|
2031
|
+
if (key in zodShape) {
|
|
2032
|
+
console.warn(
|
|
2033
|
+
`[avantGate Security Warning] Tool '${toolName}' defines '${key}' as a model parameter. Tenancy should be inferred from the trusted server context (context.tenantId) rather than supplied by the untrusted model to prevent IDOR attacks.`
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
var resolveInvalidationTags = async (config, args, rawResult, context) => {
|
|
1754
2039
|
if (!config.invalidationTags) {
|
|
1755
2040
|
return void 0;
|
|
1756
2041
|
}
|
|
@@ -1759,33 +2044,29 @@ async function resolveInvalidationTags(config, args, rawResult, context) {
|
|
|
1759
2044
|
await context.onInvalidationTags(tags);
|
|
1760
2045
|
}
|
|
1761
2046
|
return tags;
|
|
1762
|
-
}
|
|
1763
|
-
async
|
|
2047
|
+
};
|
|
2048
|
+
var dispatchClientData = async (rawResult, callback) => {
|
|
1764
2049
|
if (!callback) {
|
|
1765
2050
|
return;
|
|
1766
2051
|
}
|
|
1767
2052
|
await callback(rawResult);
|
|
1768
|
-
}
|
|
1769
|
-
async
|
|
2053
|
+
};
|
|
2054
|
+
var produceLlmPayload = async (rawResult, args, context, transformer) => {
|
|
1770
2055
|
if (transformer) {
|
|
1771
2056
|
return await transformer(rawResult, args, context);
|
|
1772
2057
|
}
|
|
1773
2058
|
return rawResult;
|
|
1774
|
-
}
|
|
1775
|
-
|
|
2059
|
+
};
|
|
2060
|
+
var validateLlmDto = (payload, schema, toolIdentifier) => {
|
|
1776
2061
|
const parseResult = schema.safeParse(payload);
|
|
1777
2062
|
if (!parseResult.success) {
|
|
1778
2063
|
const issues = parseResult.error.issues ?? [];
|
|
1779
2064
|
const errorMessages = issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join(", ");
|
|
1780
|
-
throw new DtoValidationError(
|
|
1781
|
-
toolIdentifier,
|
|
1782
|
-
errorMessages,
|
|
1783
|
-
issues
|
|
1784
|
-
);
|
|
2065
|
+
throw new DtoValidationError(toolIdentifier, errorMessages, issues);
|
|
1785
2066
|
}
|
|
1786
2067
|
return parseResult.data;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
2068
|
+
};
|
|
2069
|
+
var protectLlmPayload = (payload, toolIdentifier, sanitizePii = true, throwOnPii = false) => {
|
|
1789
2070
|
if (!sanitizePii) {
|
|
1790
2071
|
return { sanitized: payload, count: 0 };
|
|
1791
2072
|
}
|
|
@@ -1794,8 +2075,8 @@ function protectLlmPayload(payload, toolIdentifier, sanitizePii = true, throwOnP
|
|
|
1794
2075
|
throwOnPii
|
|
1795
2076
|
});
|
|
1796
2077
|
return { sanitized: sanitizedData, count: maskedCount };
|
|
1797
|
-
}
|
|
1798
|
-
async
|
|
2078
|
+
};
|
|
2079
|
+
var processLlmPayload = async (rawResult, args, context, config, toolIdentifier) => {
|
|
1799
2080
|
let payload = await produceLlmPayload(rawResult, args, context, config.llmDto);
|
|
1800
2081
|
if (config.llmDtoSchema) {
|
|
1801
2082
|
payload = validateLlmDto(payload, config.llmDtoSchema, toolIdentifier);
|
|
@@ -1806,14 +2087,15 @@ async function processLlmPayload(rawResult, args, context, config, toolIdentifie
|
|
|
1806
2087
|
config.sanitizePii !== false,
|
|
1807
2088
|
config.throwOnPii === true
|
|
1808
2089
|
);
|
|
1809
|
-
}
|
|
1810
|
-
|
|
2090
|
+
};
|
|
2091
|
+
var createIsolatedTool = (config) => {
|
|
1811
2092
|
const toolId = config.id || config.name;
|
|
1812
2093
|
const toolAlias = config.alias;
|
|
1813
2094
|
const clientCallback = config.clientDto;
|
|
1814
2095
|
const toolIdentifier = toolAlias || config.name;
|
|
1815
2096
|
const toolImpact = config.impact ?? "READ_ONLY";
|
|
1816
2097
|
const requireApproval = config.requireApproval ?? toolImpact === "DESTRUCTIVE";
|
|
2098
|
+
checkSuspiciousParameters(config.parameters, config.name);
|
|
1817
2099
|
const tool = {
|
|
1818
2100
|
description: config.description,
|
|
1819
2101
|
parameters: config.parameters,
|
|
@@ -1829,11 +2111,12 @@ function createIsolatedTool(config) {
|
|
|
1829
2111
|
_requireApproval: requireApproval,
|
|
1830
2112
|
_lastPiiFilteredCount: 0,
|
|
1831
2113
|
_lastInvalidationTags: void 0,
|
|
1832
|
-
async
|
|
2114
|
+
execute: async (args, context) => {
|
|
1833
2115
|
const updatedContext = {
|
|
1834
2116
|
...context,
|
|
1835
2117
|
callChain: context?.callChain ?? Object.freeze([toolId])
|
|
1836
2118
|
};
|
|
2119
|
+
verifyRoles(config.roles, updatedContext, toolIdentifier);
|
|
1837
2120
|
await verifyDataAccessGuard(
|
|
1838
2121
|
config.dataAccessGuard,
|
|
1839
2122
|
args,
|
|
@@ -1841,6 +2124,13 @@ function createIsolatedTool(config) {
|
|
|
1841
2124
|
toolIdentifier
|
|
1842
2125
|
);
|
|
1843
2126
|
const rawResult = await config.execute(args, updatedContext);
|
|
2127
|
+
verifyTenantIsolation(config.assertTenant, rawResult, updatedContext, toolIdentifier);
|
|
2128
|
+
await verifyResourceOwnership(
|
|
2129
|
+
config.assertOwnership,
|
|
2130
|
+
rawResult,
|
|
2131
|
+
updatedContext,
|
|
2132
|
+
toolIdentifier
|
|
2133
|
+
);
|
|
1844
2134
|
const tags = await resolveInvalidationTags(
|
|
1845
2135
|
config,
|
|
1846
2136
|
args,
|
|
@@ -1863,7 +2153,7 @@ function createIsolatedTool(config) {
|
|
|
1863
2153
|
}
|
|
1864
2154
|
};
|
|
1865
2155
|
return tool;
|
|
1866
|
-
}
|
|
2156
|
+
};
|
|
1867
2157
|
|
|
1868
2158
|
// src/workflow/errors.ts
|
|
1869
2159
|
var WorkflowSagaRollbackError = class extends Error {
|
|
@@ -2169,6 +2459,7 @@ var defineWorkflow = (config) => {
|
|
|
2169
2459
|
PromptRegistry,
|
|
2170
2460
|
PromptTemplate,
|
|
2171
2461
|
SEED_MODEL_PRICES,
|
|
2462
|
+
SqlitePricingAdapter,
|
|
2172
2463
|
WorkflowAbortSignal,
|
|
2173
2464
|
WorkflowSagaRollbackError,
|
|
2174
2465
|
WorkflowValidationError,
|