avantgate 2.2.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.
Files changed (38) hide show
  1. package/README.md +3 -1
  2. package/dist/agent/errors.d.ts +14 -0
  3. package/dist/agent/errors.d.ts.map +1 -1
  4. package/dist/agent/index.d.ts +1 -0
  5. package/dist/agent/index.d.ts.map +1 -1
  6. package/dist/agent/index.js +317 -49
  7. package/dist/agent/index.mjs +51 -44
  8. package/dist/agent/isolated-tool.d.ts.map +1 -1
  9. package/dist/agent/registry.d.ts +2 -15
  10. package/dist/agent/registry.d.ts.map +1 -1
  11. package/dist/agent/strategy.d.ts.map +1 -1
  12. package/dist/agent/tool-resilience.d.ts +5 -0
  13. package/dist/agent/tool-resilience.d.ts.map +1 -0
  14. package/dist/agent/types.d.ts +66 -0
  15. package/dist/agent/types.d.ts.map +1 -1
  16. package/dist/{chunk-W27A25DK.mjs → chunk-B22KKIQQ.mjs} +271 -6
  17. package/dist/{chunk-AH5XXSS4.mjs → chunk-KEJ4VNSN.mjs} +1 -1
  18. package/dist/control-layer.d.ts +30 -3
  19. package/dist/control-layer.d.ts.map +1 -1
  20. package/dist/errors.d.ts +16 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +954 -83
  25. package/dist/index.mjs +675 -79
  26. package/dist/middleware.d.ts +15 -0
  27. package/dist/middleware.d.ts.map +1 -0
  28. package/dist/presets.d.ts +13 -0
  29. package/dist/presets.d.ts.map +1 -0
  30. package/dist/providers/http-client.d.ts +3 -0
  31. package/dist/providers/http-client.d.ts.map +1 -1
  32. package/dist/types.d.ts +87 -4
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/utils/immutability.d.ts +5 -0
  35. package/dist/utils/immutability.d.ts.map +1 -0
  36. package/dist/workflow/index.js +264 -6
  37. package/dist/workflow/index.mjs +2 -2
  38. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -36,13 +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,
47
+ PRESETS: () => PRESETS,
42
48
  PricingRegistry: () => PricingRegistry,
43
49
  PromptBuilder: () => PromptBuilder,
50
+ PromptInjectionError: () => PromptInjectionError,
44
51
  PromptRegistry: () => PromptRegistry,
45
52
  PromptTemplate: () => PromptTemplate,
53
+ ProviderIdleTimeoutError: () => ProviderIdleTimeoutError,
54
+ ProviderTTFTTimeoutError: () => ProviderTTFTTimeoutError,
46
55
  SEED_MODEL_PRICES: () => SEED_MODEL_PRICES,
47
56
  SecretLeakBlockedError: () => SecretLeakBlockedError,
48
57
  SqlitePricingAdapter: () => SqlitePricingAdapter,
@@ -51,11 +60,19 @@ __export(index_exports, {
51
60
  WorkflowValidationError: () => WorkflowValidationError,
52
61
  ZenLLMControlLayer: () => AvantGateControlLayer,
53
62
  applyOutputGuards: () => applyOutputGuards,
63
+ applySingleAfterMiddleware: () => applySingleAfterMiddleware,
64
+ applySingleBeforeMiddleware: () => applySingleBeforeMiddleware,
54
65
  calculateCostUSD: () => calculateCostUSD,
55
66
  createAvantGate: () => createAvantGate,
67
+ createDevPermissiveConfig: () => createDevPermissiveConfig,
68
+ createEnterpriseStrictConfig: () => createEnterpriseStrictConfig,
56
69
  createHttpProviderClient: () => createHttpProviderClient,
57
70
  createLLMControlLayer: () => createLLMControlLayer,
71
+ createLaunchSafeConfig: () => createLaunchSafeConfig,
72
+ deepFreeze: () => deepFreeze,
58
73
  defineWorkflow: () => defineWorkflow,
74
+ executeAfterResponseHooks: () => executeAfterResponseHooks,
75
+ executeBeforeRequestHooks: () => executeBeforeRequestHooks,
59
76
  executeWorkflow: () => executeWorkflow,
60
77
  extractAndCleanJSON: () => extractAndCleanJSON,
61
78
  isWorkflowAbortSignal: () => isWorkflowAbortSignal,
@@ -70,6 +87,13 @@ __export(index_exports, {
70
87
  module.exports = __toCommonJS(index_exports);
71
88
 
72
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);
73
97
  var ConfigurationError = class extends Error {
74
98
  constructor(message) {
75
99
  super(message);
@@ -82,6 +106,22 @@ var BudgetExceededError = class extends Error {
82
106
  this.name = "BudgetExceededError";
83
107
  }
84
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
+ };
85
125
  var SecretLeakBlockedError = class extends Error {
86
126
  detections;
87
127
  constructor(message, detections) {
@@ -1099,11 +1139,15 @@ var HttpProviderClient = class {
1099
1139
  baseUrl;
1100
1140
  apiKey;
1101
1141
  providerType;
1142
+ ttftTimeoutMs;
1143
+ idleTimeoutMs;
1102
1144
  constructor(config) {
1103
1145
  this.providerType = config.provider;
1104
1146
  this.name = `${config.provider}-http`;
1105
1147
  this.apiKey = config.apiKey;
1106
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;
1107
1151
  }
1108
1152
  resolveEndpoint = () => {
1109
1153
  const trimmed = this.baseUrl.replace(/\/+$/, "");
@@ -1125,7 +1169,7 @@ var HttpProviderClient = class {
1125
1169
  }
1126
1170
  return headers;
1127
1171
  };
1128
- buildPayload = (options, model) => {
1172
+ buildPayload = (options, model, stream = false) => {
1129
1173
  return {
1130
1174
  model,
1131
1175
  messages: options.messages.map((m) => ({
@@ -1133,6 +1177,7 @@ var HttpProviderClient = class {
1133
1177
  content: m.content
1134
1178
  })),
1135
1179
  temperature: options.temperature ?? 0.2,
1180
+ stream,
1136
1181
  ...options.responseFormat ? { response_format: options.responseFormat } : {}
1137
1182
  };
1138
1183
  };
@@ -1147,22 +1192,101 @@ var HttpProviderClient = class {
1147
1192
  promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
1148
1193
  };
1149
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
+ };
1150
1244
  complete = async (options) => {
1151
1245
  const endpoint = this.resolveEndpoint();
1152
1246
  const model = options.model ?? "default";
1153
1247
  const headers = this.buildHeaders();
1154
1248
  const body = JSON.stringify(this.buildPayload(options, model));
1155
- const response = await fetch(endpoint, {
1156
- method: "POST",
1157
- headers,
1158
- body
1159
- });
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
+ }
1160
1272
  if (!response.ok) {
1161
1273
  const errorText = await response.text();
1162
1274
  throw new Error(
1163
1275
  `[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
1164
1276
  );
1165
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
+ }
1166
1290
  const data = await response.json();
1167
1291
  const text = data.choices?.[0]?.message?.content ?? "";
1168
1292
  const usage = this.extractUsage(data.usage);
@@ -1347,10 +1471,168 @@ var applyOutputGuards = (rawOutput, options) => {
1347
1471
  };
1348
1472
  };
1349
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
+
1531
+ // src/middleware.ts
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);
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
+ );
1569
+ if (!result) return current;
1570
+ validateReturnedMessages(result.messages, middleware.name);
1571
+ return {
1572
+ messages: result.messages ?? current.messages,
1573
+ shortCircuit: result.shortCircuit,
1574
+ metadata: current.metadata
1575
+ };
1576
+ };
1577
+ var executeBeforeRequestHooks = async (middlewares, context, timeoutMs = 5e3) => {
1578
+ if (!middlewares || middlewares.length === 0) {
1579
+ return { messages: context.messages, metadata: context.metadata };
1580
+ }
1581
+ let current = {
1582
+ messages: context.messages,
1583
+ metadata: context.metadata
1584
+ };
1585
+ for (const middleware of middlewares) {
1586
+ current = await applySingleBeforeMiddleware(
1587
+ middleware,
1588
+ current,
1589
+ context.model,
1590
+ context.temperature,
1591
+ timeoutMs
1592
+ );
1593
+ if (current.shortCircuit) return current;
1594
+ }
1595
+ return current;
1596
+ };
1597
+ var applySingleAfterMiddleware = async (middleware, current, baseContext, timeoutMs = 5e3) => {
1598
+ if (!middleware.afterResponse) return current;
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
+ );
1610
+ if (!result) return current;
1611
+ return {
1612
+ responseText: result.responseText ?? current.responseText,
1613
+ metadata: result.metadata ?? current.metadata
1614
+ };
1615
+ };
1616
+ var executeAfterResponseHooks = async (middlewares, context, timeoutMs = 5e3) => {
1617
+ if (!middlewares || middlewares.length === 0) {
1618
+ return { responseText: context.responseText, metadata: context.metadata };
1619
+ }
1620
+ let current = {
1621
+ responseText: context.responseText,
1622
+ metadata: context.metadata
1623
+ };
1624
+ for (const middleware of middlewares) {
1625
+ current = await applySingleAfterMiddleware(middleware, current, context, timeoutMs);
1626
+ }
1627
+ return current;
1628
+ };
1629
+
1350
1630
  // src/control-layer.ts
1351
1631
  var AvantGateControlLayer = class {
1352
1632
  config;
1353
1633
  cachedPricingAdapter;
1634
+ reservations = /* @__PURE__ */ new Map();
1635
+ cachedProviderChain;
1354
1636
  constructor(config) {
1355
1637
  this.config = {
1356
1638
  ...config,
@@ -1358,6 +1640,11 @@ var AvantGateControlLayer = class {
1358
1640
  fallback: this.resolveProviderConfig(config.fallback),
1359
1641
  emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
1360
1642
  };
1643
+ this.cachedProviderChain = [
1644
+ this.config.primary,
1645
+ this.config.fallback,
1646
+ this.config.emergencyFallback
1647
+ ].filter((provider) => Boolean(provider?.client));
1361
1648
  if (config.pricingAdapter) {
1362
1649
  this.cachedPricingAdapter = new CachedPricingAdapter(
1363
1650
  config.pricingAdapter,
@@ -1365,6 +1652,12 @@ var AvantGateControlLayer = class {
1365
1652
  );
1366
1653
  }
1367
1654
  }
1655
+ getReservation = (id) => {
1656
+ return this.reservations.get(id);
1657
+ };
1658
+ getAllReservations = () => {
1659
+ return Array.from(this.reservations.values());
1660
+ };
1368
1661
  resolveProviderConfig = (provider) => {
1369
1662
  if (!provider) return void 0;
1370
1663
  if (provider.client) return provider;
@@ -1377,16 +1670,15 @@ var AvantGateControlLayer = class {
1377
1670
  return provider;
1378
1671
  };
1379
1672
  findProviderConfig = (provider, model) => {
1380
- const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
1381
- (p) => Boolean(p)
1382
- );
1383
1673
  if (provider) {
1384
- const matchProvider = list.find((p) => p.provider === provider);
1385
- 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;
1386
1677
  }
1387
1678
  if (model) {
1388
- const matchModel = list.find((p) => p.model === model);
1389
- 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;
1390
1682
  }
1391
1683
  return this.config.primary;
1392
1684
  };
@@ -1405,7 +1697,7 @@ var AvantGateControlLayer = class {
1405
1697
  maxLength: this.config.security?.maxInputLength
1406
1698
  });
1407
1699
  if (!guard.valid) {
1408
- throw new Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
1700
+ throw new PromptInjectionError(guard.blockedReason ?? "Prompt injection detected");
1409
1701
  }
1410
1702
  let processed = userQuery;
1411
1703
  if (this.config.security?.customRedactionTerms && this.config.security.customRedactionTerms.length > 0) {
@@ -1416,6 +1708,21 @@ var AvantGateControlLayer = class {
1416
1708
  }
1417
1709
  return processed;
1418
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
+ };
1419
1726
  applyOutputSecurityGuards = (rawOutput) => {
1420
1727
  const isDlpEnabled = this.config.security?.outputDLP ?? this.config.security?.maskPII ?? false;
1421
1728
  const shouldCheckSecrets = this.config.security?.blockSecretLeaks ?? true;
@@ -1431,33 +1738,79 @@ var AvantGateControlLayer = class {
1431
1738
  });
1432
1739
  return guardResult.text;
1433
1740
  };
1434
- checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider) => {
1435
- if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
1741
+ validateTokenBudget = (estimatedTokens) => {
1742
+ if (this.config.maxTokenBudget !== void 0 && estimatedTokens > this.config.maxTokenBudget) {
1436
1743
  throw new BudgetExceededError(
1437
- `[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}).`
1438
1745
  );
1439
1746
  }
1440
- if (this.config.maxCostUSD !== void 0) {
1441
- const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
1442
- const providerCfg = this.findProviderConfig(provider, targetModel);
1443
- const price = await resolveModelPriceAsync(targetModel, {
1444
- provider: provider ?? providerCfg?.provider,
1445
- providerPricing: providerCfg?.pricing,
1446
- customPricing: this.config.customPricing,
1447
- adapter: this.cachedPricingAdapter
1448
- });
1449
- if (!isLocalFree && !price) {
1450
- throw new ConfigurationError(
1451
- `[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.`
1452
- );
1453
- }
1454
- const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
1455
- if (estimatedPromptCost > this.config.maxCostUSD) {
1456
- throw new BudgetExceededError(
1457
- `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
1458
- );
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;
1459
1773
  }
1460
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);
1461
1814
  };
1462
1815
  checkPostExecutionBudget = (tokensTotal, costUSD) => {
1463
1816
  if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
@@ -1491,11 +1844,7 @@ var AvantGateControlLayer = class {
1491
1844
  };
1492
1845
  };
1493
1846
  getProviderChain = () => {
1494
- return [
1495
- this.config.primary,
1496
- this.config.fallback,
1497
- this.config.emergencyFallback
1498
- ].filter((provider) => Boolean(provider?.client));
1847
+ return this.cachedProviderChain;
1499
1848
  };
1500
1849
  executeSimulation = (query, systemPrompt, modelOverride) => {
1501
1850
  const targetModel = modelOverride ?? this.config.primary.model;
@@ -1590,28 +1939,140 @@ var AvantGateControlLayer = class {
1590
1939
  attempts: result.attempts
1591
1940
  });
1592
1941
  };
1593
- execute = async (options) => {
1594
- const sanitizedQuery = this.applySecurityGuards(options.userQuery);
1595
- const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
1596
- const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
1597
- const estimatedPromptTokens = Math.ceil(promptLength / 4);
1598
- await this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
1599
- if (!options.providerOverride && this.getProviderChain().length === 0) {
1942
+ prepareInputMessages = (options) => {
1943
+ if (options.messages && options.messages.length > 0) {
1944
+ return this.sanitizeIncomingMessages(options.messages);
1945
+ }
1946
+ if (options.userQuery !== void 0) {
1947
+ const sanitizedQuery = this.applySecurityGuards(options.userQuery);
1948
+ return this.buildMessages(options.systemPrompt, sanitizedQuery);
1949
+ }
1950
+ throw new ConfigurationError("[AvantGate Input Guard] Either 'userQuery' or 'messages' must be provided.");
1951
+ };
1952
+ handleShortCircuit = async (shortCircuit, targetModel) => {
1953
+ const sanitizedText = this.applyOutputSecurityGuards(shortCircuit.text);
1954
+ const result = {
1955
+ text: sanitizedText,
1956
+ tokens: {
1957
+ prompt: shortCircuit.tokens?.promptTokens ?? 0,
1958
+ completion: shortCircuit.tokens?.completionTokens ?? 0,
1959
+ total: shortCircuit.tokens?.totalTokens ?? 0
1960
+ },
1961
+ costUSD: shortCircuit.costUSD ?? 0,
1962
+ modelUsed: `${targetModel}:shortcircuit`,
1963
+ failoverOccurred: false,
1964
+ attempts: 0
1965
+ };
1966
+ await this.notifyAuditSink(result);
1967
+ return result;
1968
+ };
1969
+ dispatchPromptExecution = async (messages, temperature, providerOverride) => {
1970
+ const query = messages[messages.length - 1]?.content ?? "";
1971
+ if (providerOverride) {
1972
+ return this.executeOverrideProvider(providerOverride, messages, query, temperature);
1973
+ }
1974
+ if (this.getProviderChain().length === 0) {
1600
1975
  if (this.config.mockSimulation) {
1601
- const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
1602
- await this.notifyAuditSink(simResult);
1603
- return simResult;
1976
+ const sim = this.executeSimulation(query);
1977
+ return {
1978
+ responseText: sim.text,
1979
+ usage: { promptTokens: sim.tokens.prompt, completionTokens: sim.tokens.completion, totalTokens: sim.tokens.total },
1980
+ modelUsed: sim.modelUsed,
1981
+ failoverOccurred: false,
1982
+ attempts: 1
1983
+ };
1604
1984
  }
1605
1985
  throw new ConfigurationError(
1606
1986
  "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
1607
1987
  );
1608
1988
  }
1609
- const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
1610
- const result = this.assembleResult(output);
1611
- this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
1989
+ return this.executeProviderPipeline(messages, query, temperature);
1990
+ };
1991
+ finalizeExecutionResult = async (output, responseText) => {
1992
+ const sanitizedText = this.applyOutputSecurityGuards(responseText);
1993
+ const hitTokens = output.usage.promptCacheHitTokens ?? 0;
1994
+ const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens, hitTokens);
1995
+ this.checkPostExecutionBudget(output.usage.totalTokens, costUSD);
1996
+ const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
1997
+ const result = {
1998
+ text: sanitizedText,
1999
+ tokens,
2000
+ costUSD,
2001
+ modelUsed: output.modelUsed,
2002
+ failoverOccurred: output.failoverOccurred,
2003
+ attempts: output.attempts
2004
+ };
1612
2005
  await this.notifyAuditSink(result);
1613
2006
  return result;
1614
2007
  };
2008
+ runAfterHooks = async (beforeState, output) => {
2009
+ const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
2010
+ const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens);
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);
2030
+ };
2031
+ execute = async (options) => {
2032
+ const inputMessages = this.prepareInputMessages(options);
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
+ );
2043
+ if (beforeState.shortCircuit) {
2044
+ return this.handleShortCircuit(beforeState.shortCircuit, this.config.primary.model);
2045
+ }
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
+ }
2066
+ const afterState = await this.runAfterHooks(beforeState, output);
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;
2074
+ };
2075
+ executePrompt = this.execute;
1615
2076
  executeStructured = async (options) => {
1616
2077
  const rawResult = await this.execute({
1617
2078
  userQuery: options.userQuery,
@@ -1643,11 +2104,6 @@ var AvantGateControlLayer = class {
1643
2104
  return msg;
1644
2105
  });
1645
2106
  };
1646
- verifyPromptBudget = async (messages, model) => {
1647
- const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
1648
- const estimatedPromptTokens = Math.ceil(promptLength / 4);
1649
- await this.checkPreflightBudget(estimatedPromptTokens, model);
1650
- };
1651
2107
  dispatchOverrideAttempt = async (override, messages, model, temperature) => {
1652
2108
  const res = await override.complete({ model, messages, temperature: temperature ?? 0.1 });
1653
2109
  const usage = this.resolveTokens(res.usage, JSON.stringify(messages), res.text);
@@ -1746,28 +2202,60 @@ var AvantGateControlLayer = class {
1746
2202
  const delay = initialDelay * Math.pow(factor, attempt);
1747
2203
  await new Promise((resolve) => setTimeout(resolve, delay));
1748
2204
  };
1749
- generateStructuredOutput = async (options) => {
1750
- const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
1751
- const targetModel = options.model ?? this.config.primary.model;
1752
- const processedMessages = this.sanitizeIncomingMessages(options.messages);
1753
- await this.verifyPromptBudget(processedMessages, targetModel);
2205
+ handleStructuredShortCircuit = async (shortCircuit, options, targetModel) => {
2206
+ const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(
2207
+ shortCircuit.text,
2208
+ options
2209
+ );
2210
+ const tokens = {
2211
+ prompt: shortCircuit.tokens?.promptTokens ?? 0,
2212
+ completion: shortCircuit.tokens?.completionTokens ?? 0,
2213
+ total: shortCircuit.tokens?.totalTokens ?? 0
2214
+ };
2215
+ const result = {
2216
+ data: parsedData,
2217
+ rawText: sanitizedResponse,
2218
+ tokens,
2219
+ costUSD: shortCircuit.costUSD ?? 0,
2220
+ modelUsed: `${targetModel}:shortcircuit`,
2221
+ failoverOccurred: false
2222
+ };
2223
+ await this.notifyAuditSinkSafe(sanitizedResponse, result, 0);
2224
+ return result;
2225
+ };
2226
+ runStructuredAfterHooks = async (messages, output, attempt) => {
2227
+ const tokens = { prompt: output.promptTokens, completion: output.completionTokens, total: output.promptTokens + output.completionTokens };
2228
+ const costUSD = this.calculateCost(output.modelUsed, output.promptTokens, output.completionTokens);
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
+ );
2242
+ };
2243
+ executeStructuredLoop = async (options, messages, targetModel, maxRetries) => {
1754
2244
  let lastError;
1755
- let accumulatedPromptTokens = 0;
1756
- let accumulatedCompletionTokens = 0;
2245
+ let accPrompt = 0;
2246
+ let accComp = 0;
1757
2247
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
1758
2248
  try {
1759
- const output = await this.dispatchProviderAttempt(options, processedMessages, targetModel);
1760
- accumulatedPromptTokens += output.promptTokens;
1761
- accumulatedCompletionTokens += output.completionTokens;
1762
- const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(
1763
- output.responseText,
1764
- options
1765
- );
2249
+ const output = await this.dispatchProviderAttempt(options, messages, targetModel);
2250
+ accPrompt += output.promptTokens;
2251
+ accComp += output.completionTokens;
2252
+ const afterState = await this.runStructuredAfterHooks(messages, output, attempt + 1);
2253
+ const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(afterState.responseText, options);
1766
2254
  const result = this.buildStructuredResult({
1767
2255
  data: parsedData,
1768
2256
  rawText: sanitizedResponse,
1769
- promptTokens: accumulatedPromptTokens,
1770
- completionTokens: accumulatedCompletionTokens,
2257
+ promptTokens: accPrompt,
2258
+ completionTokens: accComp,
1771
2259
  modelUsed: output.modelUsed,
1772
2260
  failoverOccurred: output.failoverOccurred
1773
2261
  });
@@ -1780,6 +2268,47 @@ var AvantGateControlLayer = class {
1780
2268
  }
1781
2269
  throw lastError;
1782
2270
  };
2271
+ generateStructuredOutput = async (options) => {
2272
+ const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
2273
+ const targetModel = options.model ?? this.config.primary.model;
2274
+ const processedMessages = this.sanitizeIncomingMessages(options.messages);
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
+ );
2284
+ if (beforeState.shortCircuit) {
2285
+ return this.handleStructuredShortCircuit(beforeState.shortCircuit, options, targetModel);
2286
+ }
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
+ }
2311
+ };
1783
2312
  };
1784
2313
  var createLLMControlLayer = (config) => {
1785
2314
  return new AvantGateControlLayer(config);
@@ -1867,7 +2396,7 @@ var PromptTemplate = class {
1867
2396
  };
1868
2397
 
1869
2398
  // src/prompts/prompt-builder.ts
1870
- var import_zod = require("zod");
2399
+ var import_zod2 = require("zod");
1871
2400
  var PromptBuilder = class {
1872
2401
  personaSlot = "";
1873
2402
  rulesSlot = [];
@@ -1923,7 +2452,7 @@ var PromptBuilder = class {
1923
2452
  }
1924
2453
  schemaContract(schema, options) {
1925
2454
  let shapeDescription = "JSON Object";
1926
- if (schema instanceof import_zod.z.ZodObject) {
2455
+ if (schema instanceof import_zod2.z.ZodObject) {
1927
2456
  shapeDescription = JSON.stringify(Object.keys(schema.shape));
1928
2457
  }
1929
2458
  this.schemaContractText = [
@@ -2152,6 +2681,25 @@ var DtoValidationError = class extends Error {
2152
2681
  this.issues = issues;
2153
2682
  }
2154
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
+ };
2155
2703
 
2156
2704
  // src/agent/guardrails.ts
2157
2705
  function sanitizeString(value) {
@@ -2205,16 +2753,220 @@ function auditToolResult(data, options) {
2205
2753
  };
2206
2754
  }
2207
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
+
2208
2959
  // src/agent/isolated-tool.ts
2209
2960
  var verifyRoles = (configuredRoles, context, toolIdentifier) => {
2210
2961
  if (!configuredRoles || configuredRoles.length === 0) {
2211
2962
  return;
2212
2963
  }
2213
- const userRoles = [
2214
- ...context.roles ?? [],
2215
- ...context.role ? [context.role] : []
2216
- ];
2217
- 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));
2218
2970
  if (!hasAllowedRole) {
2219
2971
  throw new ToolAccessDeniedError(
2220
2972
  toolIdentifier,
@@ -2331,6 +3083,35 @@ var processLlmPayload = async (rawResult, args, context, config, toolIdentifier)
2331
3083
  config.throwOnPii === true
2332
3084
  );
2333
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
+ };
2334
3115
  var createIsolatedTool = (config) => {
2335
3116
  const toolId = config.id || config.name;
2336
3117
  const toolAlias = config.alias;
@@ -2352,6 +3133,7 @@ var createIsolatedTool = (config) => {
2352
3133
  _roles: config.roles ? Object.freeze([...config.roles]) : void 0,
2353
3134
  _impact: toolImpact,
2354
3135
  _requireApproval: requireApproval,
3136
+ _resilience: config.resilience,
2355
3137
  _lastPiiFilteredCount: 0,
2356
3138
  _lastInvalidationTags: void 0,
2357
3139
  execute: async (args, context) => {
@@ -2366,7 +3148,12 @@ var createIsolatedTool = (config) => {
2366
3148
  updatedContext,
2367
3149
  toolIdentifier
2368
3150
  );
2369
- const rawResult = await config.execute(args, updatedContext);
3151
+ const rawResult = await executeWithResilienceGuard(
3152
+ config,
3153
+ args,
3154
+ updatedContext,
3155
+ toolIdentifier
3156
+ );
2370
3157
  verifyTenantIsolation(config.assertTenant, rawResult, updatedContext, toolIdentifier);
2371
3158
  await verifyResourceOwnership(
2372
3159
  config.assertOwnership,
@@ -2687,6 +3474,73 @@ var defineWorkflow = (config) => {
2687
3474
  }
2688
3475
  };
2689
3476
  };
3477
+
3478
+ // src/presets.ts
3479
+ var createLaunchSafeConfig = (options) => {
3480
+ const defaultSecurity = {
3481
+ detectPromptInjection: true,
3482
+ maskPII: true,
3483
+ outputDLP: true,
3484
+ blockSecretLeaks: true,
3485
+ secretLeakAction: "REDACT"
3486
+ };
3487
+ const defaultRetries = { maxRetries: 2, initialDelayMs: 300, backoffFactor: 1.5 };
3488
+ return {
3489
+ maxTokenBudget: 8192,
3490
+ maxCostUSD: 0.5,
3491
+ middlewareTimeoutMs: 5e3,
3492
+ reScanIngressAfterHooks: true,
3493
+ ...options,
3494
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
3495
+ security: { ...defaultSecurity, ...options.security }
3496
+ };
3497
+ };
3498
+ var createEnterpriseStrictConfig = (options) => {
3499
+ const defaultSecurity = {
3500
+ detectPromptInjection: true,
3501
+ maskPII: true,
3502
+ outputDLP: true,
3503
+ blockSecretLeaks: true,
3504
+ secretLeakAction: "BLOCK"
3505
+ };
3506
+ const defaultRetries = {
3507
+ maxRetries: 3,
3508
+ initialDelayMs: 500,
3509
+ backoffFactor: 2
3510
+ };
3511
+ return {
3512
+ maxTokenBudget: 4096,
3513
+ maxCostUSD: 0.15,
3514
+ middlewareTimeoutMs: 3e3,
3515
+ reScanIngressAfterHooks: true,
3516
+ ...options,
3517
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
3518
+ security: { ...defaultSecurity, ...options.security }
3519
+ };
3520
+ };
3521
+ var createDevPermissiveConfig = (options) => {
3522
+ const defaultSecurity = {
3523
+ detectPromptInjection: false,
3524
+ maskPII: false,
3525
+ outputDLP: false,
3526
+ blockSecretLeaks: false
3527
+ };
3528
+ const defaultRetries = {
3529
+ maxRetries: 1,
3530
+ initialDelayMs: 200,
3531
+ backoffFactor: 1
3532
+ };
3533
+ return {
3534
+ ...options,
3535
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
3536
+ security: { ...defaultSecurity, ...options.security }
3537
+ };
3538
+ };
3539
+ var PRESETS = {
3540
+ LAUNCH_SAFE: createLaunchSafeConfig,
3541
+ ENTERPRISE_STRICT: createEnterpriseStrictConfig,
3542
+ DEV_PERMISSIVE: createDevPermissiveConfig
3543
+ };
2690
3544
  // Annotate the CommonJS export names for ESM import in node:
2691
3545
  0 && (module.exports = {
2692
3546
  AvantGateBudgetExceededError,
@@ -2695,13 +3549,22 @@ var defineWorkflow = (config) => {
2695
3549
  AvantGateSecretLeakBlockedError,
2696
3550
  BudgetExceededError,
2697
3551
  CachedPricingAdapter,
3552
+ ChatMessageSchema,
3553
+ ChatMessagesArraySchema,
3554
+ ChatRoleSchema,
2698
3555
  ConfigurationError,
2699
3556
  DEFAULT_MODEL_PRICES,
2700
3557
  HttpProviderClient,
3558
+ InvalidMessageSchemaError,
3559
+ MiddlewareTimeoutError,
3560
+ PRESETS,
2701
3561
  PricingRegistry,
2702
3562
  PromptBuilder,
3563
+ PromptInjectionError,
2703
3564
  PromptRegistry,
2704
3565
  PromptTemplate,
3566
+ ProviderIdleTimeoutError,
3567
+ ProviderTTFTTimeoutError,
2705
3568
  SEED_MODEL_PRICES,
2706
3569
  SecretLeakBlockedError,
2707
3570
  SqlitePricingAdapter,
@@ -2710,11 +3573,19 @@ var defineWorkflow = (config) => {
2710
3573
  WorkflowValidationError,
2711
3574
  ZenLLMControlLayer,
2712
3575
  applyOutputGuards,
3576
+ applySingleAfterMiddleware,
3577
+ applySingleBeforeMiddleware,
2713
3578
  calculateCostUSD,
2714
3579
  createAvantGate,
3580
+ createDevPermissiveConfig,
3581
+ createEnterpriseStrictConfig,
2715
3582
  createHttpProviderClient,
2716
3583
  createLLMControlLayer,
3584
+ createLaunchSafeConfig,
3585
+ deepFreeze,
2717
3586
  defineWorkflow,
3587
+ executeAfterResponseHooks,
3588
+ executeBeforeRequestHooks,
2718
3589
  executeWorkflow,
2719
3590
  extractAndCleanJSON,
2720
3591
  isWorkflowAbortSignal,