avantgate 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/errors.d.ts +14 -0
- package/dist/agent/errors.d.ts.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +317 -49
- package/dist/agent/index.mjs +51 -44
- package/dist/agent/isolated-tool.d.ts.map +1 -1
- package/dist/agent/registry.d.ts +2 -15
- package/dist/agent/registry.d.ts.map +1 -1
- package/dist/agent/strategy.d.ts.map +1 -1
- package/dist/agent/tool-resilience.d.ts +5 -0
- package/dist/agent/tool-resilience.d.ts.map +1 -0
- package/dist/agent/types.d.ts +66 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/{chunk-W27A25DK.mjs → chunk-B22KKIQQ.mjs} +271 -6
- package/dist/{chunk-AH5XXSS4.mjs → chunk-KEJ4VNSN.mjs} +1 -1
- package/dist/control-layer.d.ts +11 -2
- package/dist/control-layer.d.ts.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +723 -106
- package/dist/index.mjs +452 -102
- package/dist/middleware.d.ts +4 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/providers/http-client.d.ts +3 -0
- package/dist/providers/http-client.d.ts.map +1 -1
- package/dist/types.d.ts +53 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/immutability.d.ts +5 -0
- package/dist/utils/immutability.d.ts.map +1 -0
- package/dist/workflow/index.js +264 -6
- package/dist/workflow/index.mjs +2 -2
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -36,14 +36,22 @@ __export(index_exports, {
|
|
|
36
36
|
AvantGateSecretLeakBlockedError: () => SecretLeakBlockedError,
|
|
37
37
|
BudgetExceededError: () => BudgetExceededError,
|
|
38
38
|
CachedPricingAdapter: () => CachedPricingAdapter,
|
|
39
|
+
ChatMessageSchema: () => ChatMessageSchema,
|
|
40
|
+
ChatMessagesArraySchema: () => ChatMessagesArraySchema,
|
|
41
|
+
ChatRoleSchema: () => ChatRoleSchema,
|
|
39
42
|
ConfigurationError: () => ConfigurationError,
|
|
40
43
|
DEFAULT_MODEL_PRICES: () => DEFAULT_MODEL_PRICES,
|
|
41
44
|
HttpProviderClient: () => HttpProviderClient,
|
|
45
|
+
InvalidMessageSchemaError: () => InvalidMessageSchemaError,
|
|
46
|
+
MiddlewareTimeoutError: () => MiddlewareTimeoutError,
|
|
42
47
|
PRESETS: () => PRESETS,
|
|
43
48
|
PricingRegistry: () => PricingRegistry,
|
|
44
49
|
PromptBuilder: () => PromptBuilder,
|
|
50
|
+
PromptInjectionError: () => PromptInjectionError,
|
|
45
51
|
PromptRegistry: () => PromptRegistry,
|
|
46
52
|
PromptTemplate: () => PromptTemplate,
|
|
53
|
+
ProviderIdleTimeoutError: () => ProviderIdleTimeoutError,
|
|
54
|
+
ProviderTTFTTimeoutError: () => ProviderTTFTTimeoutError,
|
|
47
55
|
SEED_MODEL_PRICES: () => SEED_MODEL_PRICES,
|
|
48
56
|
SecretLeakBlockedError: () => SecretLeakBlockedError,
|
|
49
57
|
SqlitePricingAdapter: () => SqlitePricingAdapter,
|
|
@@ -61,6 +69,7 @@ __export(index_exports, {
|
|
|
61
69
|
createHttpProviderClient: () => createHttpProviderClient,
|
|
62
70
|
createLLMControlLayer: () => createLLMControlLayer,
|
|
63
71
|
createLaunchSafeConfig: () => createLaunchSafeConfig,
|
|
72
|
+
deepFreeze: () => deepFreeze,
|
|
64
73
|
defineWorkflow: () => defineWorkflow,
|
|
65
74
|
executeAfterResponseHooks: () => executeAfterResponseHooks,
|
|
66
75
|
executeBeforeRequestHooks: () => executeBeforeRequestHooks,
|
|
@@ -78,6 +87,13 @@ __export(index_exports, {
|
|
|
78
87
|
module.exports = __toCommonJS(index_exports);
|
|
79
88
|
|
|
80
89
|
// src/types.ts
|
|
90
|
+
var import_zod = require("zod");
|
|
91
|
+
var ChatRoleSchema = import_zod.z.enum(["system", "user", "assistant", "tool"]);
|
|
92
|
+
var ChatMessageSchema = import_zod.z.object({
|
|
93
|
+
role: ChatRoleSchema,
|
|
94
|
+
content: import_zod.z.string().min(1, "Message content cannot be empty")
|
|
95
|
+
}).strict();
|
|
96
|
+
var ChatMessagesArraySchema = import_zod.z.array(ChatMessageSchema);
|
|
81
97
|
var ConfigurationError = class extends Error {
|
|
82
98
|
constructor(message) {
|
|
83
99
|
super(message);
|
|
@@ -90,6 +106,22 @@ var BudgetExceededError = class extends Error {
|
|
|
90
106
|
this.name = "BudgetExceededError";
|
|
91
107
|
}
|
|
92
108
|
};
|
|
109
|
+
var ProviderTTFTTimeoutError = class extends Error {
|
|
110
|
+
ttftTimeoutMs;
|
|
111
|
+
constructor(message, ttftTimeoutMs) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "ProviderTTFTTimeoutError";
|
|
114
|
+
this.ttftTimeoutMs = ttftTimeoutMs;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var ProviderIdleTimeoutError = class extends Error {
|
|
118
|
+
idleTimeoutMs;
|
|
119
|
+
constructor(message, idleTimeoutMs) {
|
|
120
|
+
super(message);
|
|
121
|
+
this.name = "ProviderIdleTimeoutError";
|
|
122
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
93
125
|
var SecretLeakBlockedError = class extends Error {
|
|
94
126
|
detections;
|
|
95
127
|
constructor(message, detections) {
|
|
@@ -1107,11 +1139,15 @@ var HttpProviderClient = class {
|
|
|
1107
1139
|
baseUrl;
|
|
1108
1140
|
apiKey;
|
|
1109
1141
|
providerType;
|
|
1142
|
+
ttftTimeoutMs;
|
|
1143
|
+
idleTimeoutMs;
|
|
1110
1144
|
constructor(config) {
|
|
1111
1145
|
this.providerType = config.provider;
|
|
1112
1146
|
this.name = `${config.provider}-http`;
|
|
1113
1147
|
this.apiKey = config.apiKey;
|
|
1114
1148
|
this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
|
|
1149
|
+
this.ttftTimeoutMs = config.ttftTimeoutMs ?? 15e3;
|
|
1150
|
+
this.idleTimeoutMs = config.idleTimeoutMs ?? 5e3;
|
|
1115
1151
|
}
|
|
1116
1152
|
resolveEndpoint = () => {
|
|
1117
1153
|
const trimmed = this.baseUrl.replace(/\/+$/, "");
|
|
@@ -1133,7 +1169,7 @@ var HttpProviderClient = class {
|
|
|
1133
1169
|
}
|
|
1134
1170
|
return headers;
|
|
1135
1171
|
};
|
|
1136
|
-
buildPayload = (options, model) => {
|
|
1172
|
+
buildPayload = (options, model, stream = false) => {
|
|
1137
1173
|
return {
|
|
1138
1174
|
model,
|
|
1139
1175
|
messages: options.messages.map((m) => ({
|
|
@@ -1141,6 +1177,7 @@ var HttpProviderClient = class {
|
|
|
1141
1177
|
content: m.content
|
|
1142
1178
|
})),
|
|
1143
1179
|
temperature: options.temperature ?? 0.2,
|
|
1180
|
+
stream,
|
|
1144
1181
|
...options.responseFormat ? { response_format: options.responseFormat } : {}
|
|
1145
1182
|
};
|
|
1146
1183
|
};
|
|
@@ -1155,22 +1192,101 @@ var HttpProviderClient = class {
|
|
|
1155
1192
|
promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
|
|
1156
1193
|
};
|
|
1157
1194
|
};
|
|
1195
|
+
readStreamWithHeartbeat = async (reader, controller) => {
|
|
1196
|
+
const decoder = new TextDecoder();
|
|
1197
|
+
let accumulatedText = "";
|
|
1198
|
+
let isFirstTokenReceived = false;
|
|
1199
|
+
let activeTimer = setTimeout(() => {
|
|
1200
|
+
controller.abort();
|
|
1201
|
+
}, this.ttftTimeoutMs);
|
|
1202
|
+
try {
|
|
1203
|
+
while (true) {
|
|
1204
|
+
const { done, value } = await reader.read();
|
|
1205
|
+
if (controller.signal.aborted) {
|
|
1206
|
+
throw new Error("Aborted");
|
|
1207
|
+
}
|
|
1208
|
+
if (done) {
|
|
1209
|
+
if (activeTimer) clearTimeout(activeTimer);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
if (value && value.length > 0) {
|
|
1213
|
+
if (activeTimer) clearTimeout(activeTimer);
|
|
1214
|
+
if (!isFirstTokenReceived) {
|
|
1215
|
+
isFirstTokenReceived = true;
|
|
1216
|
+
}
|
|
1217
|
+
activeTimer = setTimeout(() => {
|
|
1218
|
+
controller.abort();
|
|
1219
|
+
reader.cancel().catch(() => {
|
|
1220
|
+
});
|
|
1221
|
+
}, this.idleTimeoutMs);
|
|
1222
|
+
accumulatedText += decoder.decode(value, { stream: true });
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
if (controller.signal.aborted) {
|
|
1227
|
+
if (!isFirstTokenReceived) {
|
|
1228
|
+
throw new ProviderTTFTTimeoutError(
|
|
1229
|
+
`[AvantGate HTTP Provider ${this.providerType}] Time-to-First-Token (TTFT) timeout of ${this.ttftTimeoutMs}ms exceeded without receiving initial response bytes.`,
|
|
1230
|
+
this.ttftTimeoutMs
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
throw new ProviderIdleTimeoutError(
|
|
1234
|
+
`[AvantGate HTTP Provider ${this.providerType}] Idle read timeout of ${this.idleTimeoutMs}ms exceeded during stream generation.`,
|
|
1235
|
+
this.idleTimeoutMs
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
throw err;
|
|
1239
|
+
} finally {
|
|
1240
|
+
if (activeTimer) clearTimeout(activeTimer);
|
|
1241
|
+
}
|
|
1242
|
+
return accumulatedText;
|
|
1243
|
+
};
|
|
1158
1244
|
complete = async (options) => {
|
|
1159
1245
|
const endpoint = this.resolveEndpoint();
|
|
1160
1246
|
const model = options.model ?? "default";
|
|
1161
1247
|
const headers = this.buildHeaders();
|
|
1162
1248
|
const body = JSON.stringify(this.buildPayload(options, model));
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1249
|
+
const abortController = new AbortController();
|
|
1250
|
+
const ttftTimer = setTimeout(() => {
|
|
1251
|
+
abortController.abort();
|
|
1252
|
+
}, this.ttftTimeoutMs);
|
|
1253
|
+
let response;
|
|
1254
|
+
try {
|
|
1255
|
+
response = await fetch(endpoint, {
|
|
1256
|
+
method: "POST",
|
|
1257
|
+
headers,
|
|
1258
|
+
body,
|
|
1259
|
+
signal: abortController.signal
|
|
1260
|
+
});
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
if (abortController.signal.aborted) {
|
|
1263
|
+
throw new ProviderTTFTTimeoutError(
|
|
1264
|
+
`[AvantGate HTTP Provider ${this.providerType}] Time-to-First-Token (TTFT) timeout of ${this.ttftTimeoutMs}ms exceeded before receiving headers.`,
|
|
1265
|
+
this.ttftTimeoutMs
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
throw err;
|
|
1269
|
+
} finally {
|
|
1270
|
+
clearTimeout(ttftTimer);
|
|
1271
|
+
}
|
|
1168
1272
|
if (!response.ok) {
|
|
1169
1273
|
const errorText = await response.text();
|
|
1170
1274
|
throw new Error(
|
|
1171
1275
|
`[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
|
|
1172
1276
|
);
|
|
1173
1277
|
}
|
|
1278
|
+
if (response.body) {
|
|
1279
|
+
const reader = response.body.getReader();
|
|
1280
|
+
const rawText = await this.readStreamWithHeartbeat(reader, abortController);
|
|
1281
|
+
try {
|
|
1282
|
+
const data2 = JSON.parse(rawText);
|
|
1283
|
+
const text2 = data2.choices?.[0]?.message?.content ?? "";
|
|
1284
|
+
const usage2 = this.extractUsage(data2.usage);
|
|
1285
|
+
return { text: text2, usage: usage2 };
|
|
1286
|
+
} catch {
|
|
1287
|
+
return { text: rawText };
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1174
1290
|
const data = await response.json();
|
|
1175
1291
|
const text = data.choices?.[0]?.message?.content ?? "";
|
|
1176
1292
|
const usage = this.extractUsage(data.usage);
|
|
@@ -1355,23 +1471,110 @@ var applyOutputGuards = (rawOutput, options) => {
|
|
|
1355
1471
|
};
|
|
1356
1472
|
};
|
|
1357
1473
|
|
|
1474
|
+
// src/utils/immutability.ts
|
|
1475
|
+
var deepFreeze = (target, options, currentDepth = 0, visited = /* @__PURE__ */ new WeakSet()) => {
|
|
1476
|
+
if (target === null || typeof target !== "object") {
|
|
1477
|
+
return target;
|
|
1478
|
+
}
|
|
1479
|
+
if (visited.has(target)) {
|
|
1480
|
+
return target;
|
|
1481
|
+
}
|
|
1482
|
+
const maxDepth = options?.maxDepth ?? 10;
|
|
1483
|
+
if (Object.isFrozen(target) || currentDepth >= maxDepth) {
|
|
1484
|
+
return target;
|
|
1485
|
+
}
|
|
1486
|
+
if (target instanceof Date || target instanceof RegExp || target instanceof Promise) {
|
|
1487
|
+
return Object.freeze(target);
|
|
1488
|
+
}
|
|
1489
|
+
visited.add(target);
|
|
1490
|
+
const values = Array.isArray(target) ? target : Object.values(target);
|
|
1491
|
+
for (const value of values) {
|
|
1492
|
+
if (value && typeof value === "object") {
|
|
1493
|
+
deepFreeze(value, options, currentDepth + 1, visited);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return Object.freeze(target);
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
// src/errors.ts
|
|
1500
|
+
var MiddlewareTimeoutError = class extends Error {
|
|
1501
|
+
middlewareName;
|
|
1502
|
+
timeoutMs;
|
|
1503
|
+
phase;
|
|
1504
|
+
constructor(middlewareName, phase, timeoutMs) {
|
|
1505
|
+
super(`Middleware "${middlewareName}" execution timed out during ${phase} after ${timeoutMs}ms.`);
|
|
1506
|
+
this.name = "MiddlewareTimeoutError";
|
|
1507
|
+
this.middlewareName = middlewareName;
|
|
1508
|
+
this.phase = phase;
|
|
1509
|
+
this.timeoutMs = timeoutMs;
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
var InvalidMessageSchemaError = class extends Error {
|
|
1513
|
+
middlewareName;
|
|
1514
|
+
validationErrors;
|
|
1515
|
+
constructor(middlewareName, validationErrors) {
|
|
1516
|
+
super(`Middleware "${middlewareName}" returned an invalid messages payload failing ChatMessageSchema validation.`);
|
|
1517
|
+
this.name = "InvalidMessageSchemaError";
|
|
1518
|
+
this.middlewareName = middlewareName;
|
|
1519
|
+
this.validationErrors = validationErrors;
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
var PromptInjectionError = class extends Error {
|
|
1523
|
+
reason;
|
|
1524
|
+
constructor(reason) {
|
|
1525
|
+
super(`[AvantGate Security Guard] Request blocked: ${reason}`);
|
|
1526
|
+
this.name = "PromptInjectionError";
|
|
1527
|
+
this.reason = reason;
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1358
1531
|
// src/middleware.ts
|
|
1359
|
-
var
|
|
1360
|
-
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
metadata: current.metadata
|
|
1532
|
+
var executeWithTimeout = async (action, timeoutMs, middlewareName, phase) => {
|
|
1533
|
+
let timer;
|
|
1534
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1535
|
+
timer = setTimeout(() => {
|
|
1536
|
+
reject(new MiddlewareTimeoutError(middlewareName, phase, timeoutMs));
|
|
1537
|
+
}, timeoutMs);
|
|
1366
1538
|
});
|
|
1539
|
+
try {
|
|
1540
|
+
return await Promise.race([Promise.resolve(action()), timeoutPromise]);
|
|
1541
|
+
} finally {
|
|
1542
|
+
if (timer) {
|
|
1543
|
+
clearTimeout(timer);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
var validateReturnedMessages = (messages, middlewareName) => {
|
|
1548
|
+
if (!messages) return;
|
|
1549
|
+
const parseResult = ChatMessagesArraySchema.safeParse(messages);
|
|
1550
|
+
if (!parseResult.success) {
|
|
1551
|
+
throw new InvalidMessageSchemaError(middlewareName, parseResult.error.format());
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
var applySingleBeforeMiddleware = async (middleware, current, model, temperature, timeoutMs = 5e3) => {
|
|
1555
|
+
if (!middleware.beforeRequest) return current;
|
|
1556
|
+
const frozenMessages = deepFreeze([...current.messages]);
|
|
1557
|
+
const frozenMetadata = current.metadata ? deepFreeze({ ...current.metadata }) : void 0;
|
|
1558
|
+
const result = await executeWithTimeout(
|
|
1559
|
+
() => middleware.beforeRequest({
|
|
1560
|
+
messages: frozenMessages,
|
|
1561
|
+
model,
|
|
1562
|
+
temperature,
|
|
1563
|
+
metadata: frozenMetadata
|
|
1564
|
+
}),
|
|
1565
|
+
timeoutMs,
|
|
1566
|
+
middleware.name,
|
|
1567
|
+
"beforeRequest"
|
|
1568
|
+
);
|
|
1367
1569
|
if (!result) return current;
|
|
1570
|
+
validateReturnedMessages(result.messages, middleware.name);
|
|
1368
1571
|
return {
|
|
1369
1572
|
messages: result.messages ?? current.messages,
|
|
1370
1573
|
shortCircuit: result.shortCircuit,
|
|
1371
1574
|
metadata: current.metadata
|
|
1372
1575
|
};
|
|
1373
1576
|
};
|
|
1374
|
-
var executeBeforeRequestHooks = async (middlewares, context) => {
|
|
1577
|
+
var executeBeforeRequestHooks = async (middlewares, context, timeoutMs = 5e3) => {
|
|
1375
1578
|
if (!middlewares || middlewares.length === 0) {
|
|
1376
1579
|
return { messages: context.messages, metadata: context.metadata };
|
|
1377
1580
|
}
|
|
@@ -1380,25 +1583,37 @@ var executeBeforeRequestHooks = async (middlewares, context) => {
|
|
|
1380
1583
|
metadata: context.metadata
|
|
1381
1584
|
};
|
|
1382
1585
|
for (const middleware of middlewares) {
|
|
1383
|
-
current = await applySingleBeforeMiddleware(
|
|
1586
|
+
current = await applySingleBeforeMiddleware(
|
|
1587
|
+
middleware,
|
|
1588
|
+
current,
|
|
1589
|
+
context.model,
|
|
1590
|
+
context.temperature,
|
|
1591
|
+
timeoutMs
|
|
1592
|
+
);
|
|
1384
1593
|
if (current.shortCircuit) return current;
|
|
1385
1594
|
}
|
|
1386
1595
|
return current;
|
|
1387
1596
|
};
|
|
1388
|
-
var applySingleAfterMiddleware = async (middleware, current, baseContext) => {
|
|
1597
|
+
var applySingleAfterMiddleware = async (middleware, current, baseContext, timeoutMs = 5e3) => {
|
|
1389
1598
|
if (!middleware.afterResponse) return current;
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1599
|
+
const frozenMetadata = current.metadata ? deepFreeze({ ...current.metadata }) : void 0;
|
|
1600
|
+
const result = await executeWithTimeout(
|
|
1601
|
+
() => middleware.afterResponse({
|
|
1602
|
+
...baseContext,
|
|
1603
|
+
responseText: current.responseText,
|
|
1604
|
+
metadata: frozenMetadata
|
|
1605
|
+
}),
|
|
1606
|
+
timeoutMs,
|
|
1607
|
+
middleware.name,
|
|
1608
|
+
"afterResponse"
|
|
1609
|
+
);
|
|
1395
1610
|
if (!result) return current;
|
|
1396
1611
|
return {
|
|
1397
1612
|
responseText: result.responseText ?? current.responseText,
|
|
1398
1613
|
metadata: result.metadata ?? current.metadata
|
|
1399
1614
|
};
|
|
1400
1615
|
};
|
|
1401
|
-
var executeAfterResponseHooks = async (middlewares, context) => {
|
|
1616
|
+
var executeAfterResponseHooks = async (middlewares, context, timeoutMs = 5e3) => {
|
|
1402
1617
|
if (!middlewares || middlewares.length === 0) {
|
|
1403
1618
|
return { responseText: context.responseText, metadata: context.metadata };
|
|
1404
1619
|
}
|
|
@@ -1407,7 +1622,7 @@ var executeAfterResponseHooks = async (middlewares, context) => {
|
|
|
1407
1622
|
metadata: context.metadata
|
|
1408
1623
|
};
|
|
1409
1624
|
for (const middleware of middlewares) {
|
|
1410
|
-
current = await applySingleAfterMiddleware(middleware, current, context);
|
|
1625
|
+
current = await applySingleAfterMiddleware(middleware, current, context, timeoutMs);
|
|
1411
1626
|
}
|
|
1412
1627
|
return current;
|
|
1413
1628
|
};
|
|
@@ -1416,6 +1631,8 @@ var executeAfterResponseHooks = async (middlewares, context) => {
|
|
|
1416
1631
|
var AvantGateControlLayer = class {
|
|
1417
1632
|
config;
|
|
1418
1633
|
cachedPricingAdapter;
|
|
1634
|
+
reservations = /* @__PURE__ */ new Map();
|
|
1635
|
+
cachedProviderChain;
|
|
1419
1636
|
constructor(config) {
|
|
1420
1637
|
this.config = {
|
|
1421
1638
|
...config,
|
|
@@ -1423,6 +1640,11 @@ var AvantGateControlLayer = class {
|
|
|
1423
1640
|
fallback: this.resolveProviderConfig(config.fallback),
|
|
1424
1641
|
emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
|
|
1425
1642
|
};
|
|
1643
|
+
this.cachedProviderChain = [
|
|
1644
|
+
this.config.primary,
|
|
1645
|
+
this.config.fallback,
|
|
1646
|
+
this.config.emergencyFallback
|
|
1647
|
+
].filter((provider) => Boolean(provider?.client));
|
|
1426
1648
|
if (config.pricingAdapter) {
|
|
1427
1649
|
this.cachedPricingAdapter = new CachedPricingAdapter(
|
|
1428
1650
|
config.pricingAdapter,
|
|
@@ -1430,6 +1652,12 @@ var AvantGateControlLayer = class {
|
|
|
1430
1652
|
);
|
|
1431
1653
|
}
|
|
1432
1654
|
}
|
|
1655
|
+
getReservation = (id) => {
|
|
1656
|
+
return this.reservations.get(id);
|
|
1657
|
+
};
|
|
1658
|
+
getAllReservations = () => {
|
|
1659
|
+
return Array.from(this.reservations.values());
|
|
1660
|
+
};
|
|
1433
1661
|
resolveProviderConfig = (provider) => {
|
|
1434
1662
|
if (!provider) return void 0;
|
|
1435
1663
|
if (provider.client) return provider;
|
|
@@ -1442,16 +1670,15 @@ var AvantGateControlLayer = class {
|
|
|
1442
1670
|
return provider;
|
|
1443
1671
|
};
|
|
1444
1672
|
findProviderConfig = (provider, model) => {
|
|
1445
|
-
const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
|
|
1446
|
-
(p) => Boolean(p)
|
|
1447
|
-
);
|
|
1448
1673
|
if (provider) {
|
|
1449
|
-
|
|
1450
|
-
if (
|
|
1674
|
+
if (this.config.primary.provider === provider) return this.config.primary;
|
|
1675
|
+
if (this.config.fallback?.provider === provider) return this.config.fallback;
|
|
1676
|
+
if (this.config.emergencyFallback?.provider === provider) return this.config.emergencyFallback;
|
|
1451
1677
|
}
|
|
1452
1678
|
if (model) {
|
|
1453
|
-
|
|
1454
|
-
if (
|
|
1679
|
+
if (this.config.primary.model === model) return this.config.primary;
|
|
1680
|
+
if (this.config.fallback?.model === model) return this.config.fallback;
|
|
1681
|
+
if (this.config.emergencyFallback?.model === model) return this.config.emergencyFallback;
|
|
1455
1682
|
}
|
|
1456
1683
|
return this.config.primary;
|
|
1457
1684
|
};
|
|
@@ -1470,7 +1697,7 @@ var AvantGateControlLayer = class {
|
|
|
1470
1697
|
maxLength: this.config.security?.maxInputLength
|
|
1471
1698
|
});
|
|
1472
1699
|
if (!guard.valid) {
|
|
1473
|
-
throw new
|
|
1700
|
+
throw new PromptInjectionError(guard.blockedReason ?? "Prompt injection detected");
|
|
1474
1701
|
}
|
|
1475
1702
|
let processed = userQuery;
|
|
1476
1703
|
if (this.config.security?.customRedactionTerms && this.config.security.customRedactionTerms.length > 0) {
|
|
@@ -1481,6 +1708,21 @@ var AvantGateControlLayer = class {
|
|
|
1481
1708
|
}
|
|
1482
1709
|
return processed;
|
|
1483
1710
|
};
|
|
1711
|
+
reScanMessagesAfterHooks = (originalMessages, updatedMessages) => {
|
|
1712
|
+
if (this.config.reScanIngressAfterHooks === false || originalMessages === updatedMessages) {
|
|
1713
|
+
return updatedMessages;
|
|
1714
|
+
}
|
|
1715
|
+
return updatedMessages.map((msg, idx) => {
|
|
1716
|
+
const orig = originalMessages[idx];
|
|
1717
|
+
if (orig && (orig === msg || orig.role === msg.role && orig.content === msg.content)) {
|
|
1718
|
+
return msg;
|
|
1719
|
+
}
|
|
1720
|
+
return {
|
|
1721
|
+
...msg,
|
|
1722
|
+
content: this.applySecurityGuards(msg.content)
|
|
1723
|
+
};
|
|
1724
|
+
});
|
|
1725
|
+
};
|
|
1484
1726
|
applyOutputSecurityGuards = (rawOutput) => {
|
|
1485
1727
|
const isDlpEnabled = this.config.security?.outputDLP ?? this.config.security?.maskPII ?? false;
|
|
1486
1728
|
const shouldCheckSecrets = this.config.security?.blockSecretLeaks ?? true;
|
|
@@ -1496,33 +1738,79 @@ var AvantGateControlLayer = class {
|
|
|
1496
1738
|
});
|
|
1497
1739
|
return guardResult.text;
|
|
1498
1740
|
};
|
|
1499
|
-
|
|
1500
|
-
if (this.config.maxTokenBudget !== void 0 &&
|
|
1741
|
+
validateTokenBudget = (estimatedTokens) => {
|
|
1742
|
+
if (this.config.maxTokenBudget !== void 0 && estimatedTokens > this.config.maxTokenBudget) {
|
|
1501
1743
|
throw new BudgetExceededError(
|
|
1502
|
-
`[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${
|
|
1744
|
+
`[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
|
|
1503
1745
|
);
|
|
1504
1746
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1747
|
+
};
|
|
1748
|
+
validatePricingConfigured = async (targetModel, provider) => {
|
|
1749
|
+
const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
|
|
1750
|
+
if (isLocalFree) return;
|
|
1751
|
+
const providerCfg = this.findProviderConfig(provider, targetModel);
|
|
1752
|
+
const price = await resolveModelPriceAsync(targetModel, {
|
|
1753
|
+
provider: provider ?? providerCfg?.provider,
|
|
1754
|
+
providerPricing: providerCfg?.pricing,
|
|
1755
|
+
customPricing: this.config.customPricing,
|
|
1756
|
+
adapter: this.cachedPricingAdapter
|
|
1757
|
+
});
|
|
1758
|
+
if (!price) {
|
|
1759
|
+
throw new ConfigurationError(
|
|
1760
|
+
`[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.`
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
evaluatePessimisticSolvency = (estimatedPromptCost, parentRunId) => {
|
|
1765
|
+
if (!parentRunId || this.config.maxCostUSD === void 0) return;
|
|
1766
|
+
let hasUnconfirmed = false;
|
|
1767
|
+
let settledSpend = 0;
|
|
1768
|
+
for (const r of this.reservations.values()) {
|
|
1769
|
+
if (r.parentRunId !== parentRunId) continue;
|
|
1770
|
+
if (r.status === "UNCONFIRMED_TIMEOUT") hasUnconfirmed = true;
|
|
1771
|
+
if (r.status === "SETTLED") {
|
|
1772
|
+
settledSpend += r.settledCostUsd ?? r.estimatedCostUsd;
|
|
1524
1773
|
}
|
|
1525
1774
|
}
|
|
1775
|
+
if (!hasUnconfirmed) return;
|
|
1776
|
+
const totalWorstCaseCost = settledSpend + 2 * estimatedPromptCost;
|
|
1777
|
+
if (totalWorstCaseCost > this.config.maxCostUSD) {
|
|
1778
|
+
throw new BudgetExceededError(
|
|
1779
|
+
`[AvantGate FinOps Guard] Retry rejected: worst-case cumulative spend ($${totalWorstCaseCost.toFixed(4)}) exceeds max budget ($${this.config.maxCostUSD.toFixed(4)}).`
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
createBudgetReservation = (estimatedCostUsd, parentRunId) => {
|
|
1784
|
+
const reservationId = `res_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
1785
|
+
const reservation = {
|
|
1786
|
+
id: reservationId,
|
|
1787
|
+
parentRunId,
|
|
1788
|
+
estimatedCostUsd,
|
|
1789
|
+
status: "RESERVED",
|
|
1790
|
+
createdAt: Date.now(),
|
|
1791
|
+
updatedAt: Date.now()
|
|
1792
|
+
};
|
|
1793
|
+
if (this.reservations.size >= 1e4) {
|
|
1794
|
+
const oldestKey = this.reservations.keys().next().value;
|
|
1795
|
+
if (oldestKey) this.reservations.delete(oldestKey);
|
|
1796
|
+
}
|
|
1797
|
+
this.reservations.set(reservationId, reservation);
|
|
1798
|
+
return reservation;
|
|
1799
|
+
};
|
|
1800
|
+
checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider, parentRunId) => {
|
|
1801
|
+
this.validateTokenBudget(estimatedPromptTokens);
|
|
1802
|
+
if (this.config.maxCostUSD === void 0) {
|
|
1803
|
+
return void 0;
|
|
1804
|
+
}
|
|
1805
|
+
await this.validatePricingConfigured(targetModel, provider);
|
|
1806
|
+
const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
|
|
1807
|
+
this.evaluatePessimisticSolvency(estimatedPromptCost, parentRunId);
|
|
1808
|
+
if (estimatedPromptCost > this.config.maxCostUSD) {
|
|
1809
|
+
throw new BudgetExceededError(
|
|
1810
|
+
`[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
return this.createBudgetReservation(estimatedPromptCost, parentRunId);
|
|
1526
1814
|
};
|
|
1527
1815
|
checkPostExecutionBudget = (tokensTotal, costUSD) => {
|
|
1528
1816
|
if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
|
|
@@ -1556,11 +1844,7 @@ var AvantGateControlLayer = class {
|
|
|
1556
1844
|
};
|
|
1557
1845
|
};
|
|
1558
1846
|
getProviderChain = () => {
|
|
1559
|
-
return
|
|
1560
|
-
this.config.primary,
|
|
1561
|
-
this.config.fallback,
|
|
1562
|
-
this.config.emergencyFallback
|
|
1563
|
-
].filter((provider) => Boolean(provider?.client));
|
|
1847
|
+
return this.cachedProviderChain;
|
|
1564
1848
|
};
|
|
1565
1849
|
executeSimulation = (query, systemPrompt, modelOverride) => {
|
|
1566
1850
|
const targetModel = modelOverride ?? this.config.primary.model;
|
|
@@ -1724,32 +2008,69 @@ var AvantGateControlLayer = class {
|
|
|
1724
2008
|
runAfterHooks = async (beforeState, output) => {
|
|
1725
2009
|
const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
|
|
1726
2010
|
const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens);
|
|
1727
|
-
return executeAfterResponseHooks(
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2011
|
+
return executeAfterResponseHooks(
|
|
2012
|
+
this.config.middlewares,
|
|
2013
|
+
{
|
|
2014
|
+
messages: beforeState.messages,
|
|
2015
|
+
responseText: output.responseText,
|
|
2016
|
+
modelUsed: output.modelUsed,
|
|
2017
|
+
tokens,
|
|
2018
|
+
costUSD,
|
|
2019
|
+
attempts: output.attempts,
|
|
2020
|
+
failoverOccurred: output.failoverOccurred,
|
|
2021
|
+
metadata: beforeState.metadata
|
|
2022
|
+
},
|
|
2023
|
+
this.config.middlewareTimeoutMs ?? 5e3
|
|
2024
|
+
);
|
|
2025
|
+
};
|
|
2026
|
+
verifyPromptBudget = async (messages, model, parentRunId) => {
|
|
2027
|
+
const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
|
|
2028
|
+
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
2029
|
+
return this.checkPreflightBudget(estimatedPromptTokens, model, void 0, parentRunId);
|
|
1737
2030
|
};
|
|
1738
2031
|
execute = async (options) => {
|
|
1739
2032
|
const inputMessages = this.prepareInputMessages(options);
|
|
1740
|
-
const beforeState = await executeBeforeRequestHooks(
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2033
|
+
const beforeState = await executeBeforeRequestHooks(
|
|
2034
|
+
this.config.middlewares,
|
|
2035
|
+
{
|
|
2036
|
+
messages: inputMessages,
|
|
2037
|
+
model: this.config.primary.model,
|
|
2038
|
+
temperature: options.temperature,
|
|
2039
|
+
metadata: options.metadata
|
|
2040
|
+
},
|
|
2041
|
+
this.config.middlewareTimeoutMs ?? 5e3
|
|
2042
|
+
);
|
|
1746
2043
|
if (beforeState.shortCircuit) {
|
|
1747
2044
|
return this.handleShortCircuit(beforeState.shortCircuit, this.config.primary.model);
|
|
1748
2045
|
}
|
|
1749
|
-
|
|
1750
|
-
const
|
|
2046
|
+
beforeState.messages = this.reScanMessagesAfterHooks(inputMessages, beforeState.messages);
|
|
2047
|
+
const parentRunId = beforeState.metadata?.parentRunId ?? options.metadata?.parentRunId;
|
|
2048
|
+
const reservation = await this.verifyPromptBudget(beforeState.messages, this.config.primary.model, parentRunId);
|
|
2049
|
+
let output;
|
|
2050
|
+
try {
|
|
2051
|
+
output = await this.dispatchPromptExecution(beforeState.messages, options.temperature, options.providerOverride);
|
|
2052
|
+
} catch (err) {
|
|
2053
|
+
if (reservation) {
|
|
2054
|
+
const errorMsg = String(err);
|
|
2055
|
+
const isClientError = errorMsg.includes("HTTP 400") || errorMsg.includes("HTTP 401") || errorMsg.includes("HTTP 403") || errorMsg.includes("HTTP 422");
|
|
2056
|
+
if (isClientError) {
|
|
2057
|
+
reservation.status = "RELEASED";
|
|
2058
|
+
reservation.updatedAt = Date.now();
|
|
2059
|
+
} else {
|
|
2060
|
+
reservation.status = "UNCONFIRMED_TIMEOUT";
|
|
2061
|
+
reservation.updatedAt = Date.now();
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
throw err;
|
|
2065
|
+
}
|
|
1751
2066
|
const afterState = await this.runAfterHooks(beforeState, output);
|
|
1752
|
-
|
|
2067
|
+
const finalResult = await this.finalizeExecutionResult(output, afterState.responseText);
|
|
2068
|
+
if (reservation) {
|
|
2069
|
+
reservation.status = "SETTLED";
|
|
2070
|
+
reservation.settledCostUsd = finalResult.costUSD;
|
|
2071
|
+
reservation.updatedAt = Date.now();
|
|
2072
|
+
}
|
|
2073
|
+
return finalResult;
|
|
1753
2074
|
};
|
|
1754
2075
|
executePrompt = this.execute;
|
|
1755
2076
|
executeStructured = async (options) => {
|
|
@@ -1783,11 +2104,6 @@ var AvantGateControlLayer = class {
|
|
|
1783
2104
|
return msg;
|
|
1784
2105
|
});
|
|
1785
2106
|
};
|
|
1786
|
-
verifyPromptBudget = async (messages, model) => {
|
|
1787
|
-
const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
|
|
1788
|
-
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
1789
|
-
await this.checkPreflightBudget(estimatedPromptTokens, model);
|
|
1790
|
-
};
|
|
1791
2107
|
dispatchOverrideAttempt = async (override, messages, model, temperature) => {
|
|
1792
2108
|
const res = await override.complete({ model, messages, temperature: temperature ?? 0.1 });
|
|
1793
2109
|
const usage = this.resolveTokens(res.usage, JSON.stringify(messages), res.text);
|
|
@@ -1910,15 +2226,19 @@ var AvantGateControlLayer = class {
|
|
|
1910
2226
|
runStructuredAfterHooks = async (messages, output, attempt) => {
|
|
1911
2227
|
const tokens = { prompt: output.promptTokens, completion: output.completionTokens, total: output.promptTokens + output.completionTokens };
|
|
1912
2228
|
const costUSD = this.calculateCost(output.modelUsed, output.promptTokens, output.completionTokens);
|
|
1913
|
-
return executeAfterResponseHooks(
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2229
|
+
return executeAfterResponseHooks(
|
|
2230
|
+
this.config.middlewares,
|
|
2231
|
+
{
|
|
2232
|
+
messages,
|
|
2233
|
+
responseText: output.responseText,
|
|
2234
|
+
modelUsed: output.modelUsed,
|
|
2235
|
+
tokens,
|
|
2236
|
+
costUSD,
|
|
2237
|
+
attempts: attempt,
|
|
2238
|
+
failoverOccurred: output.failoverOccurred
|
|
2239
|
+
},
|
|
2240
|
+
this.config.middlewareTimeoutMs ?? 5e3
|
|
2241
|
+
);
|
|
1922
2242
|
};
|
|
1923
2243
|
executeStructuredLoop = async (options, messages, targetModel, maxRetries) => {
|
|
1924
2244
|
let lastError;
|
|
@@ -1952,16 +2272,42 @@ var AvantGateControlLayer = class {
|
|
|
1952
2272
|
const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
|
|
1953
2273
|
const targetModel = options.model ?? this.config.primary.model;
|
|
1954
2274
|
const processedMessages = this.sanitizeIncomingMessages(options.messages);
|
|
1955
|
-
const beforeState = await executeBeforeRequestHooks(
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2275
|
+
const beforeState = await executeBeforeRequestHooks(
|
|
2276
|
+
this.config.middlewares,
|
|
2277
|
+
{
|
|
2278
|
+
messages: processedMessages,
|
|
2279
|
+
model: targetModel,
|
|
2280
|
+
temperature: options.temperature
|
|
2281
|
+
},
|
|
2282
|
+
this.config.middlewareTimeoutMs ?? 5e3
|
|
2283
|
+
);
|
|
1960
2284
|
if (beforeState.shortCircuit) {
|
|
1961
2285
|
return this.handleStructuredShortCircuit(beforeState.shortCircuit, options, targetModel);
|
|
1962
2286
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
2287
|
+
beforeState.messages = this.reScanMessagesAfterHooks(processedMessages, beforeState.messages);
|
|
2288
|
+
const reservation = await this.verifyPromptBudget(beforeState.messages, targetModel);
|
|
2289
|
+
try {
|
|
2290
|
+
const result = await this.executeStructuredLoop(options, beforeState.messages, targetModel, maxRetries);
|
|
2291
|
+
if (reservation) {
|
|
2292
|
+
reservation.status = "SETTLED";
|
|
2293
|
+
reservation.settledCostUsd = result.costUSD;
|
|
2294
|
+
reservation.updatedAt = Date.now();
|
|
2295
|
+
}
|
|
2296
|
+
return result;
|
|
2297
|
+
} catch (err) {
|
|
2298
|
+
if (reservation) {
|
|
2299
|
+
const errorMsg = String(err);
|
|
2300
|
+
const isClientError = errorMsg.includes("HTTP 400") || errorMsg.includes("HTTP 401") || errorMsg.includes("HTTP 403") || errorMsg.includes("HTTP 422");
|
|
2301
|
+
if (isClientError) {
|
|
2302
|
+
reservation.status = "RELEASED";
|
|
2303
|
+
reservation.updatedAt = Date.now();
|
|
2304
|
+
} else {
|
|
2305
|
+
reservation.status = "UNCONFIRMED_TIMEOUT";
|
|
2306
|
+
reservation.updatedAt = Date.now();
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
throw err;
|
|
2310
|
+
}
|
|
1965
2311
|
};
|
|
1966
2312
|
};
|
|
1967
2313
|
var createLLMControlLayer = (config) => {
|
|
@@ -2050,7 +2396,7 @@ var PromptTemplate = class {
|
|
|
2050
2396
|
};
|
|
2051
2397
|
|
|
2052
2398
|
// src/prompts/prompt-builder.ts
|
|
2053
|
-
var
|
|
2399
|
+
var import_zod2 = require("zod");
|
|
2054
2400
|
var PromptBuilder = class {
|
|
2055
2401
|
personaSlot = "";
|
|
2056
2402
|
rulesSlot = [];
|
|
@@ -2106,7 +2452,7 @@ var PromptBuilder = class {
|
|
|
2106
2452
|
}
|
|
2107
2453
|
schemaContract(schema, options) {
|
|
2108
2454
|
let shapeDescription = "JSON Object";
|
|
2109
|
-
if (schema instanceof
|
|
2455
|
+
if (schema instanceof import_zod2.z.ZodObject) {
|
|
2110
2456
|
shapeDescription = JSON.stringify(Object.keys(schema.shape));
|
|
2111
2457
|
}
|
|
2112
2458
|
this.schemaContractText = [
|
|
@@ -2335,6 +2681,25 @@ var DtoValidationError = class extends Error {
|
|
|
2335
2681
|
this.issues = issues;
|
|
2336
2682
|
}
|
|
2337
2683
|
};
|
|
2684
|
+
var AmbiguousToolExecutionError = class extends Error {
|
|
2685
|
+
toolCallId;
|
|
2686
|
+
toolName;
|
|
2687
|
+
impact;
|
|
2688
|
+
originalError;
|
|
2689
|
+
constructor(details, toolName, impact) {
|
|
2690
|
+
const isStr = typeof details === "string";
|
|
2691
|
+
const callId = isStr ? details : details.toolCallId;
|
|
2692
|
+
const name = isStr ? toolName : details.toolName;
|
|
2693
|
+
const imp = isStr ? impact ?? "MUTATIVE" : details.impact ?? "MUTATIVE";
|
|
2694
|
+
const msg = isStr ? "" : details.message ? `: ${details.message}` : "";
|
|
2695
|
+
super(`Ambiguous tool execution detected for [${name || "tool"}::${callId}] (${imp})${msg}. Re-execution blocked without reconciliation.`);
|
|
2696
|
+
this.name = "AmbiguousToolExecutionError";
|
|
2697
|
+
this.toolCallId = callId;
|
|
2698
|
+
this.toolName = name;
|
|
2699
|
+
this.impact = imp;
|
|
2700
|
+
this.originalError = isStr ? void 0 : details.originalError;
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2338
2703
|
|
|
2339
2704
|
// src/agent/guardrails.ts
|
|
2340
2705
|
function sanitizeString(value) {
|
|
@@ -2388,16 +2753,220 @@ function auditToolResult(data, options) {
|
|
|
2388
2753
|
};
|
|
2389
2754
|
}
|
|
2390
2755
|
|
|
2756
|
+
// src/agent/tool-resilience.ts
|
|
2757
|
+
var MAX_FALLBACK_CAP = 1e3;
|
|
2758
|
+
var inMemoryFallbackStore = /* @__PURE__ */ new Map();
|
|
2759
|
+
var setFallbackRecord = (toolCallId, record) => {
|
|
2760
|
+
if (inMemoryFallbackStore.size >= MAX_FALLBACK_CAP) {
|
|
2761
|
+
const firstKey = inMemoryFallbackStore.keys().next().value;
|
|
2762
|
+
if (firstKey) inMemoryFallbackStore.delete(firstKey);
|
|
2763
|
+
}
|
|
2764
|
+
inMemoryFallbackStore.set(toolCallId, record);
|
|
2765
|
+
};
|
|
2766
|
+
var isDeterministicClientError = (error) => {
|
|
2767
|
+
if (!error || typeof error !== "object") {
|
|
2768
|
+
return false;
|
|
2769
|
+
}
|
|
2770
|
+
const err = error;
|
|
2771
|
+
const status = Number(err.status ?? err.statusCode);
|
|
2772
|
+
if (status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
2773
|
+
return true;
|
|
2774
|
+
}
|
|
2775
|
+
return err.name === "DtoValidationError" || err.name === "ToolAccessDeniedError";
|
|
2776
|
+
};
|
|
2777
|
+
var isAmbiguousTimeoutError = (error, isTimedOut) => {
|
|
2778
|
+
if (isTimedOut) return true;
|
|
2779
|
+
if (!error || typeof error !== "object") return false;
|
|
2780
|
+
const err = error;
|
|
2781
|
+
const name = String(err.name ?? "");
|
|
2782
|
+
const status = Number(err.status ?? err.statusCode);
|
|
2783
|
+
if (name === "AbortError" || name === "TimeoutError") return true;
|
|
2784
|
+
if (status === 408 || status === 502 || status === 503 || status === 504) return true;
|
|
2785
|
+
const msg = String(err.message ?? "").toLowerCase();
|
|
2786
|
+
const code = String(err.code ?? "").toLowerCase();
|
|
2787
|
+
return ["timeout", "etimedout", "econnreset", "socket hang up", "network error", "fetch failed"].some((key) => msg.includes(key) || code.includes(key));
|
|
2788
|
+
};
|
|
2789
|
+
var classifyErrorStatus = (error, isTimedOut) => {
|
|
2790
|
+
if (isDeterministicClientError(error)) {
|
|
2791
|
+
return "FAILED";
|
|
2792
|
+
}
|
|
2793
|
+
if (isAmbiguousTimeoutError(error, isTimedOut)) {
|
|
2794
|
+
return "UNCONFIRMED_TIMEOUT";
|
|
2795
|
+
}
|
|
2796
|
+
return "FAILED";
|
|
2797
|
+
};
|
|
2798
|
+
var getToolExecutionRecord = async (storage, toolCallId) => {
|
|
2799
|
+
if (!storage) {
|
|
2800
|
+
return inMemoryFallbackStore.get(toolCallId) ?? null;
|
|
2801
|
+
}
|
|
2802
|
+
if (storage.getStateValue) {
|
|
2803
|
+
const val = await storage.getStateValue(`tool_resilience:${toolCallId}`);
|
|
2804
|
+
if (val) return val;
|
|
2805
|
+
const directVal = await storage.getStateValue(toolCallId);
|
|
2806
|
+
if (directVal) return directVal;
|
|
2807
|
+
}
|
|
2808
|
+
const step = await storage.getStep("_tool_resilience", toolCallId);
|
|
2809
|
+
return step?.metadata?.resilienceRecord ?? null;
|
|
2810
|
+
};
|
|
2811
|
+
var saveToStateStorage = async (storage, toolCallId, record) => {
|
|
2812
|
+
if (!storage.setStateValue) return;
|
|
2813
|
+
await storage.setStateValue(`tool_resilience:${toolCallId}`, record);
|
|
2814
|
+
await storage.setStateValue(toolCallId, record);
|
|
2815
|
+
};
|
|
2816
|
+
var saveResilienceRecord = async (storage, record) => {
|
|
2817
|
+
if (!storage) {
|
|
2818
|
+
setFallbackRecord(record.toolCallId, record);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
await saveToStateStorage(storage, record.toolCallId, record);
|
|
2822
|
+
const status = record.status === "COMPLETED" ? "COMPLETED" : record.status === "FAILED" ? "FAILED" : "RUNNING";
|
|
2823
|
+
await storage.saveStep({
|
|
2824
|
+
workflowId: "_tool_resilience",
|
|
2825
|
+
stepId: record.toolCallId,
|
|
2826
|
+
status,
|
|
2827
|
+
result: record.result,
|
|
2828
|
+
error: record.error,
|
|
2829
|
+
metadata: { resilienceRecord: record },
|
|
2830
|
+
createdAt: new Date(record.startedAt).toISOString(),
|
|
2831
|
+
updatedAt: new Date(record.updatedAt).toISOString()
|
|
2832
|
+
});
|
|
2833
|
+
};
|
|
2834
|
+
var handleAmbiguousFailure = (options, originalError) => {
|
|
2835
|
+
const workflowId = options.workflowId ?? "_tool_resilience";
|
|
2836
|
+
if (options.suspendOnAmbiguous) {
|
|
2837
|
+
throw new StepSuspendedError(options.toolCallId, workflowId, {
|
|
2838
|
+
toolCallId: options.toolCallId,
|
|
2839
|
+
impact: options.impact,
|
|
2840
|
+
reason: "Ambiguous mutation requires human intervention",
|
|
2841
|
+
originalError
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
throw new AmbiguousToolExecutionError({
|
|
2845
|
+
toolCallId: options.toolCallId,
|
|
2846
|
+
toolName: options.toolName,
|
|
2847
|
+
impact: options.impact,
|
|
2848
|
+
message: "Ambiguous execution state (UNCONFIRMED_TIMEOUT). Re-execution blocked without reconciliation.",
|
|
2849
|
+
originalError
|
|
2850
|
+
});
|
|
2851
|
+
};
|
|
2852
|
+
var saveReconciledRecord = async (options, result) => {
|
|
2853
|
+
await saveResilienceRecord(options.storage, {
|
|
2854
|
+
toolCallId: options.toolCallId,
|
|
2855
|
+
status: "COMPLETED",
|
|
2856
|
+
result,
|
|
2857
|
+
impact: options.impact,
|
|
2858
|
+
startedAt: Date.now(),
|
|
2859
|
+
updatedAt: Date.now()
|
|
2860
|
+
});
|
|
2861
|
+
return { result, isCached: true, status: "COMPLETED" };
|
|
2862
|
+
};
|
|
2863
|
+
var tryReconciliation = async (options) => {
|
|
2864
|
+
if (!options.onAmbiguousRetry) {
|
|
2865
|
+
return handleAmbiguousFailure(options);
|
|
2866
|
+
}
|
|
2867
|
+
try {
|
|
2868
|
+
const outcome = await options.onAmbiguousRetry({ toolCallId: options.toolCallId, args: options.args });
|
|
2869
|
+
if (!outcome.reconciled) {
|
|
2870
|
+
return handleAmbiguousFailure(options);
|
|
2871
|
+
}
|
|
2872
|
+
return await saveReconciledRecord(options, outcome.result);
|
|
2873
|
+
} catch (err) {
|
|
2874
|
+
if (err instanceof StepSuspendedError || err instanceof AmbiguousToolExecutionError) throw err;
|
|
2875
|
+
return handleAmbiguousFailure(options, err);
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
var checkExistingState = async (options) => {
|
|
2879
|
+
const existing = await getToolExecutionRecord(options.storage, options.toolCallId);
|
|
2880
|
+
if (!existing) {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
if (existing.status === "COMPLETED") {
|
|
2884
|
+
return { result: existing.result, isCached: true, status: "COMPLETED" };
|
|
2885
|
+
}
|
|
2886
|
+
if (existing.status === "UNCONFIRMED_TIMEOUT") {
|
|
2887
|
+
if (options.impact === "READ_ONLY") {
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
return await tryReconciliation(options);
|
|
2891
|
+
}
|
|
2892
|
+
return null;
|
|
2893
|
+
};
|
|
2894
|
+
var executeActionWithTimeout = async (action, timeoutMs, idempotencyKey) => {
|
|
2895
|
+
const controller = new AbortController();
|
|
2896
|
+
let isTimedOut = false;
|
|
2897
|
+
const timer = setTimeout(() => {
|
|
2898
|
+
isTimedOut = true;
|
|
2899
|
+
controller.abort(new Error(`Tool execution timed out after ${timeoutMs}ms`));
|
|
2900
|
+
}, timeoutMs);
|
|
2901
|
+
try {
|
|
2902
|
+
const result = await action(controller.signal, idempotencyKey);
|
|
2903
|
+
return { result, isTimedOut: false };
|
|
2904
|
+
} catch (error) {
|
|
2905
|
+
return { error, isTimedOut };
|
|
2906
|
+
} finally {
|
|
2907
|
+
clearTimeout(timer);
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
var saveFailureRecord = async (options, startedAt, error, isTimedOut) => {
|
|
2911
|
+
const status = classifyErrorStatus(error, isTimedOut);
|
|
2912
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2913
|
+
await saveResilienceRecord(options.storage, {
|
|
2914
|
+
toolCallId: options.toolCallId,
|
|
2915
|
+
status,
|
|
2916
|
+
error: errorMsg,
|
|
2917
|
+
impact: options.impact,
|
|
2918
|
+
startedAt,
|
|
2919
|
+
updatedAt: Date.now()
|
|
2920
|
+
});
|
|
2921
|
+
throw error;
|
|
2922
|
+
};
|
|
2923
|
+
var saveSuccessRecord = async (options, startedAt, result) => {
|
|
2924
|
+
await saveResilienceRecord(options.storage, {
|
|
2925
|
+
toolCallId: options.toolCallId,
|
|
2926
|
+
status: "COMPLETED",
|
|
2927
|
+
result,
|
|
2928
|
+
impact: options.impact,
|
|
2929
|
+
startedAt,
|
|
2930
|
+
updatedAt: Date.now()
|
|
2931
|
+
});
|
|
2932
|
+
return { result, isCached: false, status: "COMPLETED" };
|
|
2933
|
+
};
|
|
2934
|
+
var claimExecutionIntent = async (options, startedAt) => {
|
|
2935
|
+
await saveResilienceRecord(options.storage, {
|
|
2936
|
+
toolCallId: options.toolCallId,
|
|
2937
|
+
status: "CLAIMED",
|
|
2938
|
+
impact: options.impact,
|
|
2939
|
+
startedAt,
|
|
2940
|
+
updatedAt: startedAt
|
|
2941
|
+
});
|
|
2942
|
+
};
|
|
2943
|
+
var withToolResilience = async (options) => {
|
|
2944
|
+
const existingResult = await checkExistingState(options);
|
|
2945
|
+
if (existingResult) {
|
|
2946
|
+
return existingResult;
|
|
2947
|
+
}
|
|
2948
|
+
const startedAt = Date.now();
|
|
2949
|
+
const idempotencyKey = options.idempotencyKey || options.toolCallId;
|
|
2950
|
+
await claimExecutionIntent(options, startedAt);
|
|
2951
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
2952
|
+
const outcome = await executeActionWithTimeout(options.action, timeoutMs, idempotencyKey);
|
|
2953
|
+
if (outcome.error) {
|
|
2954
|
+
return await saveFailureRecord(options, startedAt, outcome.error, outcome.isTimedOut);
|
|
2955
|
+
}
|
|
2956
|
+
return await saveSuccessRecord(options, startedAt, outcome.result);
|
|
2957
|
+
};
|
|
2958
|
+
|
|
2391
2959
|
// src/agent/isolated-tool.ts
|
|
2392
2960
|
var verifyRoles = (configuredRoles, context, toolIdentifier) => {
|
|
2393
2961
|
if (!configuredRoles || configuredRoles.length === 0) {
|
|
2394
2962
|
return;
|
|
2395
2963
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
];
|
|
2400
|
-
|
|
2964
|
+
if (!context.role && (!context.roles || context.roles.length === 0)) {
|
|
2965
|
+
throw new ToolAccessDeniedError(toolIdentifier, "Access denied: insufficient role permissions");
|
|
2966
|
+
}
|
|
2967
|
+
const roleSet = new Set(context.roles ?? []);
|
|
2968
|
+
if (context.role) roleSet.add(context.role);
|
|
2969
|
+
const hasAllowedRole = configuredRoles.some((role) => roleSet.has(role));
|
|
2401
2970
|
if (!hasAllowedRole) {
|
|
2402
2971
|
throw new ToolAccessDeniedError(
|
|
2403
2972
|
toolIdentifier,
|
|
@@ -2514,6 +3083,35 @@ var processLlmPayload = async (rawResult, args, context, config, toolIdentifier)
|
|
|
2514
3083
|
config.throwOnPii === true
|
|
2515
3084
|
);
|
|
2516
3085
|
};
|
|
3086
|
+
var buildResilientContext = (config, args, context) => {
|
|
3087
|
+
const toolCallId = context.toolCallId || `call_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
3088
|
+
const genKey = config.resilience?.idempotencyKeyGenerator?.(args, context);
|
|
3089
|
+
const idempotencyKey = context.idempotencyKey || genKey || toolCallId;
|
|
3090
|
+
const resilientContext = { ...context, toolCallId, idempotencyKey };
|
|
3091
|
+
return { toolCallId, idempotencyKey, resilientContext };
|
|
3092
|
+
};
|
|
3093
|
+
var executeWithResilienceGuard = async (config, args, context, toolIdentifier) => {
|
|
3094
|
+
if (!config.resilience) {
|
|
3095
|
+
return await config.execute(args, context);
|
|
3096
|
+
}
|
|
3097
|
+
const { toolCallId, idempotencyKey, resilientContext } = buildResilientContext(config, args, context);
|
|
3098
|
+
const outcome = await withToolResilience({
|
|
3099
|
+
toolCallId,
|
|
3100
|
+
toolName: toolIdentifier,
|
|
3101
|
+
storage: context.storage,
|
|
3102
|
+
impact: config.impact ?? "READ_ONLY",
|
|
3103
|
+
idempotencyKey,
|
|
3104
|
+
timeoutMs: config.resilience.timeoutMs,
|
|
3105
|
+
suspendOnAmbiguous: config.resilience.suspendOnAmbiguous,
|
|
3106
|
+
workflowId: context.workflowId,
|
|
3107
|
+
args,
|
|
3108
|
+
action: async (signal, idKey) => {
|
|
3109
|
+
return await config.execute(args, { ...resilientContext, abortSignal: signal, idempotencyKey: idKey });
|
|
3110
|
+
},
|
|
3111
|
+
onAmbiguousRetry: config.resilience.onAmbiguousRetry
|
|
3112
|
+
});
|
|
3113
|
+
return outcome.result;
|
|
3114
|
+
};
|
|
2517
3115
|
var createIsolatedTool = (config) => {
|
|
2518
3116
|
const toolId = config.id || config.name;
|
|
2519
3117
|
const toolAlias = config.alias;
|
|
@@ -2535,6 +3133,7 @@ var createIsolatedTool = (config) => {
|
|
|
2535
3133
|
_roles: config.roles ? Object.freeze([...config.roles]) : void 0,
|
|
2536
3134
|
_impact: toolImpact,
|
|
2537
3135
|
_requireApproval: requireApproval,
|
|
3136
|
+
_resilience: config.resilience,
|
|
2538
3137
|
_lastPiiFilteredCount: 0,
|
|
2539
3138
|
_lastInvalidationTags: void 0,
|
|
2540
3139
|
execute: async (args, context) => {
|
|
@@ -2549,7 +3148,12 @@ var createIsolatedTool = (config) => {
|
|
|
2549
3148
|
updatedContext,
|
|
2550
3149
|
toolIdentifier
|
|
2551
3150
|
);
|
|
2552
|
-
const rawResult = await
|
|
3151
|
+
const rawResult = await executeWithResilienceGuard(
|
|
3152
|
+
config,
|
|
3153
|
+
args,
|
|
3154
|
+
updatedContext,
|
|
3155
|
+
toolIdentifier
|
|
3156
|
+
);
|
|
2553
3157
|
verifyTenantIsolation(config.assertTenant, rawResult, updatedContext, toolIdentifier);
|
|
2554
3158
|
await verifyResourceOwnership(
|
|
2555
3159
|
config.assertOwnership,
|
|
@@ -2884,6 +3488,8 @@ var createLaunchSafeConfig = (options) => {
|
|
|
2884
3488
|
return {
|
|
2885
3489
|
maxTokenBudget: 8192,
|
|
2886
3490
|
maxCostUSD: 0.5,
|
|
3491
|
+
middlewareTimeoutMs: 5e3,
|
|
3492
|
+
reScanIngressAfterHooks: true,
|
|
2887
3493
|
...options,
|
|
2888
3494
|
retryOptions: { ...defaultRetries, ...options.retryOptions },
|
|
2889
3495
|
security: { ...defaultSecurity, ...options.security }
|
|
@@ -2905,6 +3511,8 @@ var createEnterpriseStrictConfig = (options) => {
|
|
|
2905
3511
|
return {
|
|
2906
3512
|
maxTokenBudget: 4096,
|
|
2907
3513
|
maxCostUSD: 0.15,
|
|
3514
|
+
middlewareTimeoutMs: 3e3,
|
|
3515
|
+
reScanIngressAfterHooks: true,
|
|
2908
3516
|
...options,
|
|
2909
3517
|
retryOptions: { ...defaultRetries, ...options.retryOptions },
|
|
2910
3518
|
security: { ...defaultSecurity, ...options.security }
|
|
@@ -2941,14 +3549,22 @@ var PRESETS = {
|
|
|
2941
3549
|
AvantGateSecretLeakBlockedError,
|
|
2942
3550
|
BudgetExceededError,
|
|
2943
3551
|
CachedPricingAdapter,
|
|
3552
|
+
ChatMessageSchema,
|
|
3553
|
+
ChatMessagesArraySchema,
|
|
3554
|
+
ChatRoleSchema,
|
|
2944
3555
|
ConfigurationError,
|
|
2945
3556
|
DEFAULT_MODEL_PRICES,
|
|
2946
3557
|
HttpProviderClient,
|
|
3558
|
+
InvalidMessageSchemaError,
|
|
3559
|
+
MiddlewareTimeoutError,
|
|
2947
3560
|
PRESETS,
|
|
2948
3561
|
PricingRegistry,
|
|
2949
3562
|
PromptBuilder,
|
|
3563
|
+
PromptInjectionError,
|
|
2950
3564
|
PromptRegistry,
|
|
2951
3565
|
PromptTemplate,
|
|
3566
|
+
ProviderIdleTimeoutError,
|
|
3567
|
+
ProviderTTFTTimeoutError,
|
|
2952
3568
|
SEED_MODEL_PRICES,
|
|
2953
3569
|
SecretLeakBlockedError,
|
|
2954
3570
|
SqlitePricingAdapter,
|
|
@@ -2966,6 +3582,7 @@ var PRESETS = {
|
|
|
2966
3582
|
createHttpProviderClient,
|
|
2967
3583
|
createLLMControlLayer,
|
|
2968
3584
|
createLaunchSafeConfig,
|
|
3585
|
+
deepFreeze,
|
|
2969
3586
|
defineWorkflow,
|
|
2970
3587
|
executeAfterResponseHooks,
|
|
2971
3588
|
executeBeforeRequestHooks,
|