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/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 response = await fetch(endpoint, {
1164
- method: "POST",
1165
- headers,
1166
- body
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 applySingleBeforeMiddleware = async (middleware, current, model, temperature) => {
1360
- if (!middleware.beforeRequest) return current;
1361
- const result = await middleware.beforeRequest({
1362
- messages: current.messages,
1363
- model,
1364
- temperature,
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(middleware, current, context.model, context.temperature);
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 result = await middleware.afterResponse({
1391
- ...baseContext,
1392
- responseText: current.responseText,
1393
- metadata: current.metadata
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
- const matchProvider = list.find((p) => p.provider === provider);
1450
- if (matchProvider) return matchProvider;
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
- const matchModel = list.find((p) => p.model === model);
1454
- if (matchModel) return matchModel;
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 Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
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
- checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider) => {
1500
- if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
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 (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
1744
+ `[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
1503
1745
  );
1504
1746
  }
1505
- if (this.config.maxCostUSD !== void 0) {
1506
- const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
1507
- const providerCfg = this.findProviderConfig(provider, targetModel);
1508
- const price = await resolveModelPriceAsync(targetModel, {
1509
- provider: provider ?? providerCfg?.provider,
1510
- providerPricing: providerCfg?.pricing,
1511
- customPricing: this.config.customPricing,
1512
- adapter: this.cachedPricingAdapter
1513
- });
1514
- if (!isLocalFree && !price) {
1515
- throw new ConfigurationError(
1516
- `[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.`
1517
- );
1518
- }
1519
- const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
1520
- if (estimatedPromptCost > this.config.maxCostUSD) {
1521
- throw new BudgetExceededError(
1522
- `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
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(this.config.middlewares, {
1728
- messages: beforeState.messages,
1729
- responseText: output.responseText,
1730
- modelUsed: output.modelUsed,
1731
- tokens,
1732
- costUSD,
1733
- attempts: output.attempts,
1734
- failoverOccurred: output.failoverOccurred,
1735
- metadata: beforeState.metadata
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(this.config.middlewares, {
1741
- messages: inputMessages,
1742
- model: this.config.primary.model,
1743
- temperature: options.temperature,
1744
- metadata: options.metadata
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
- await this.verifyPromptBudget(beforeState.messages, this.config.primary.model);
1750
- const output = await this.dispatchPromptExecution(beforeState.messages, options.temperature, options.providerOverride);
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
- return this.finalizeExecutionResult(output, afterState.responseText);
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(this.config.middlewares, {
1914
- messages,
1915
- responseText: output.responseText,
1916
- modelUsed: output.modelUsed,
1917
- tokens,
1918
- costUSD,
1919
- attempts: attempt,
1920
- failoverOccurred: output.failoverOccurred
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(this.config.middlewares, {
1956
- messages: processedMessages,
1957
- model: targetModel,
1958
- temperature: options.temperature
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
- await this.verifyPromptBudget(beforeState.messages, targetModel);
1964
- return this.executeStructuredLoop(options, beforeState.messages, targetModel, maxRetries);
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 import_zod = require("zod");
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 import_zod.z.ZodObject) {
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
- const userRoles = [
2397
- ...context.roles ?? [],
2398
- ...context.role ? [context.role] : []
2399
- ];
2400
- const hasAllowedRole = configuredRoles.some((role) => userRoles.includes(role));
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 config.execute(args, updatedContext);
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,