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.mjs CHANGED
@@ -9,15 +9,22 @@ import {
9
9
  defineWorkflow,
10
10
  executeWorkflow,
11
11
  isWorkflowAbortSignal
12
- } from "./chunk-AH5XXSS4.mjs";
12
+ } from "./chunk-KEJ4VNSN.mjs";
13
13
  import {
14
14
  sanitizePII
15
- } from "./chunk-W27A25DK.mjs";
15
+ } from "./chunk-B22KKIQQ.mjs";
16
16
  import {
17
17
  __require
18
18
  } from "./chunk-Y6FXYEAI.mjs";
19
19
 
20
20
  // src/types.ts
21
+ import { z } from "zod";
22
+ var ChatRoleSchema = z.enum(["system", "user", "assistant", "tool"]);
23
+ var ChatMessageSchema = z.object({
24
+ role: ChatRoleSchema,
25
+ content: z.string().min(1, "Message content cannot be empty")
26
+ }).strict();
27
+ var ChatMessagesArraySchema = z.array(ChatMessageSchema);
21
28
  var ConfigurationError = class extends Error {
22
29
  constructor(message) {
23
30
  super(message);
@@ -30,6 +37,22 @@ var BudgetExceededError = class extends Error {
30
37
  this.name = "BudgetExceededError";
31
38
  }
32
39
  };
40
+ var ProviderTTFTTimeoutError = class extends Error {
41
+ ttftTimeoutMs;
42
+ constructor(message, ttftTimeoutMs) {
43
+ super(message);
44
+ this.name = "ProviderTTFTTimeoutError";
45
+ this.ttftTimeoutMs = ttftTimeoutMs;
46
+ }
47
+ };
48
+ var ProviderIdleTimeoutError = class extends Error {
49
+ idleTimeoutMs;
50
+ constructor(message, idleTimeoutMs) {
51
+ super(message);
52
+ this.name = "ProviderIdleTimeoutError";
53
+ this.idleTimeoutMs = idleTimeoutMs;
54
+ }
55
+ };
33
56
  var SecretLeakBlockedError = class extends Error {
34
57
  detections;
35
58
  constructor(message, detections) {
@@ -488,11 +511,15 @@ var HttpProviderClient = class {
488
511
  baseUrl;
489
512
  apiKey;
490
513
  providerType;
514
+ ttftTimeoutMs;
515
+ idleTimeoutMs;
491
516
  constructor(config) {
492
517
  this.providerType = config.provider;
493
518
  this.name = `${config.provider}-http`;
494
519
  this.apiKey = config.apiKey;
495
520
  this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
521
+ this.ttftTimeoutMs = config.ttftTimeoutMs ?? 15e3;
522
+ this.idleTimeoutMs = config.idleTimeoutMs ?? 5e3;
496
523
  }
497
524
  resolveEndpoint = () => {
498
525
  const trimmed = this.baseUrl.replace(/\/+$/, "");
@@ -514,7 +541,7 @@ var HttpProviderClient = class {
514
541
  }
515
542
  return headers;
516
543
  };
517
- buildPayload = (options, model) => {
544
+ buildPayload = (options, model, stream = false) => {
518
545
  return {
519
546
  model,
520
547
  messages: options.messages.map((m) => ({
@@ -522,6 +549,7 @@ var HttpProviderClient = class {
522
549
  content: m.content
523
550
  })),
524
551
  temperature: options.temperature ?? 0.2,
552
+ stream,
525
553
  ...options.responseFormat ? { response_format: options.responseFormat } : {}
526
554
  };
527
555
  };
@@ -536,22 +564,101 @@ var HttpProviderClient = class {
536
564
  promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
537
565
  };
538
566
  };
567
+ readStreamWithHeartbeat = async (reader, controller) => {
568
+ const decoder = new TextDecoder();
569
+ let accumulatedText = "";
570
+ let isFirstTokenReceived = false;
571
+ let activeTimer = setTimeout(() => {
572
+ controller.abort();
573
+ }, this.ttftTimeoutMs);
574
+ try {
575
+ while (true) {
576
+ const { done, value } = await reader.read();
577
+ if (controller.signal.aborted) {
578
+ throw new Error("Aborted");
579
+ }
580
+ if (done) {
581
+ if (activeTimer) clearTimeout(activeTimer);
582
+ break;
583
+ }
584
+ if (value && value.length > 0) {
585
+ if (activeTimer) clearTimeout(activeTimer);
586
+ if (!isFirstTokenReceived) {
587
+ isFirstTokenReceived = true;
588
+ }
589
+ activeTimer = setTimeout(() => {
590
+ controller.abort();
591
+ reader.cancel().catch(() => {
592
+ });
593
+ }, this.idleTimeoutMs);
594
+ accumulatedText += decoder.decode(value, { stream: true });
595
+ }
596
+ }
597
+ } catch (err) {
598
+ if (controller.signal.aborted) {
599
+ if (!isFirstTokenReceived) {
600
+ throw new ProviderTTFTTimeoutError(
601
+ `[AvantGate HTTP Provider ${this.providerType}] Time-to-First-Token (TTFT) timeout of ${this.ttftTimeoutMs}ms exceeded without receiving initial response bytes.`,
602
+ this.ttftTimeoutMs
603
+ );
604
+ }
605
+ throw new ProviderIdleTimeoutError(
606
+ `[AvantGate HTTP Provider ${this.providerType}] Idle read timeout of ${this.idleTimeoutMs}ms exceeded during stream generation.`,
607
+ this.idleTimeoutMs
608
+ );
609
+ }
610
+ throw err;
611
+ } finally {
612
+ if (activeTimer) clearTimeout(activeTimer);
613
+ }
614
+ return accumulatedText;
615
+ };
539
616
  complete = async (options) => {
540
617
  const endpoint = this.resolveEndpoint();
541
618
  const model = options.model ?? "default";
542
619
  const headers = this.buildHeaders();
543
620
  const body = JSON.stringify(this.buildPayload(options, model));
544
- const response = await fetch(endpoint, {
545
- method: "POST",
546
- headers,
547
- body
548
- });
621
+ const abortController = new AbortController();
622
+ const ttftTimer = setTimeout(() => {
623
+ abortController.abort();
624
+ }, this.ttftTimeoutMs);
625
+ let response;
626
+ try {
627
+ response = await fetch(endpoint, {
628
+ method: "POST",
629
+ headers,
630
+ body,
631
+ signal: abortController.signal
632
+ });
633
+ } catch (err) {
634
+ if (abortController.signal.aborted) {
635
+ throw new ProviderTTFTTimeoutError(
636
+ `[AvantGate HTTP Provider ${this.providerType}] Time-to-First-Token (TTFT) timeout of ${this.ttftTimeoutMs}ms exceeded before receiving headers.`,
637
+ this.ttftTimeoutMs
638
+ );
639
+ }
640
+ throw err;
641
+ } finally {
642
+ clearTimeout(ttftTimer);
643
+ }
549
644
  if (!response.ok) {
550
645
  const errorText = await response.text();
551
646
  throw new Error(
552
647
  `[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
553
648
  );
554
649
  }
650
+ if (response.body) {
651
+ const reader = response.body.getReader();
652
+ const rawText = await this.readStreamWithHeartbeat(reader, abortController);
653
+ try {
654
+ const data2 = JSON.parse(rawText);
655
+ const text2 = data2.choices?.[0]?.message?.content ?? "";
656
+ const usage2 = this.extractUsage(data2.usage);
657
+ return { text: text2, usage: usage2 };
658
+ } catch {
659
+ return { text: rawText };
660
+ }
661
+ }
555
662
  const data = await response.json();
556
663
  const text = data.choices?.[0]?.message?.content ?? "";
557
664
  const usage = this.extractUsage(data.usage);
@@ -736,10 +843,168 @@ var applyOutputGuards = (rawOutput, options) => {
736
843
  };
737
844
  };
738
845
 
846
+ // src/utils/immutability.ts
847
+ var deepFreeze = (target, options, currentDepth = 0, visited = /* @__PURE__ */ new WeakSet()) => {
848
+ if (target === null || typeof target !== "object") {
849
+ return target;
850
+ }
851
+ if (visited.has(target)) {
852
+ return target;
853
+ }
854
+ const maxDepth = options?.maxDepth ?? 10;
855
+ if (Object.isFrozen(target) || currentDepth >= maxDepth) {
856
+ return target;
857
+ }
858
+ if (target instanceof Date || target instanceof RegExp || target instanceof Promise) {
859
+ return Object.freeze(target);
860
+ }
861
+ visited.add(target);
862
+ const values = Array.isArray(target) ? target : Object.values(target);
863
+ for (const value of values) {
864
+ if (value && typeof value === "object") {
865
+ deepFreeze(value, options, currentDepth + 1, visited);
866
+ }
867
+ }
868
+ return Object.freeze(target);
869
+ };
870
+
871
+ // src/errors.ts
872
+ var MiddlewareTimeoutError = class extends Error {
873
+ middlewareName;
874
+ timeoutMs;
875
+ phase;
876
+ constructor(middlewareName, phase, timeoutMs) {
877
+ super(`Middleware "${middlewareName}" execution timed out during ${phase} after ${timeoutMs}ms.`);
878
+ this.name = "MiddlewareTimeoutError";
879
+ this.middlewareName = middlewareName;
880
+ this.phase = phase;
881
+ this.timeoutMs = timeoutMs;
882
+ }
883
+ };
884
+ var InvalidMessageSchemaError = class extends Error {
885
+ middlewareName;
886
+ validationErrors;
887
+ constructor(middlewareName, validationErrors) {
888
+ super(`Middleware "${middlewareName}" returned an invalid messages payload failing ChatMessageSchema validation.`);
889
+ this.name = "InvalidMessageSchemaError";
890
+ this.middlewareName = middlewareName;
891
+ this.validationErrors = validationErrors;
892
+ }
893
+ };
894
+ var PromptInjectionError = class extends Error {
895
+ reason;
896
+ constructor(reason) {
897
+ super(`[AvantGate Security Guard] Request blocked: ${reason}`);
898
+ this.name = "PromptInjectionError";
899
+ this.reason = reason;
900
+ }
901
+ };
902
+
903
+ // src/middleware.ts
904
+ var executeWithTimeout = async (action, timeoutMs, middlewareName, phase) => {
905
+ let timer;
906
+ const timeoutPromise = new Promise((_, reject) => {
907
+ timer = setTimeout(() => {
908
+ reject(new MiddlewareTimeoutError(middlewareName, phase, timeoutMs));
909
+ }, timeoutMs);
910
+ });
911
+ try {
912
+ return await Promise.race([Promise.resolve(action()), timeoutPromise]);
913
+ } finally {
914
+ if (timer) {
915
+ clearTimeout(timer);
916
+ }
917
+ }
918
+ };
919
+ var validateReturnedMessages = (messages, middlewareName) => {
920
+ if (!messages) return;
921
+ const parseResult = ChatMessagesArraySchema.safeParse(messages);
922
+ if (!parseResult.success) {
923
+ throw new InvalidMessageSchemaError(middlewareName, parseResult.error.format());
924
+ }
925
+ };
926
+ var applySingleBeforeMiddleware = async (middleware, current, model, temperature, timeoutMs = 5e3) => {
927
+ if (!middleware.beforeRequest) return current;
928
+ const frozenMessages = deepFreeze([...current.messages]);
929
+ const frozenMetadata = current.metadata ? deepFreeze({ ...current.metadata }) : void 0;
930
+ const result = await executeWithTimeout(
931
+ () => middleware.beforeRequest({
932
+ messages: frozenMessages,
933
+ model,
934
+ temperature,
935
+ metadata: frozenMetadata
936
+ }),
937
+ timeoutMs,
938
+ middleware.name,
939
+ "beforeRequest"
940
+ );
941
+ if (!result) return current;
942
+ validateReturnedMessages(result.messages, middleware.name);
943
+ return {
944
+ messages: result.messages ?? current.messages,
945
+ shortCircuit: result.shortCircuit,
946
+ metadata: current.metadata
947
+ };
948
+ };
949
+ var executeBeforeRequestHooks = async (middlewares, context, timeoutMs = 5e3) => {
950
+ if (!middlewares || middlewares.length === 0) {
951
+ return { messages: context.messages, metadata: context.metadata };
952
+ }
953
+ let current = {
954
+ messages: context.messages,
955
+ metadata: context.metadata
956
+ };
957
+ for (const middleware of middlewares) {
958
+ current = await applySingleBeforeMiddleware(
959
+ middleware,
960
+ current,
961
+ context.model,
962
+ context.temperature,
963
+ timeoutMs
964
+ );
965
+ if (current.shortCircuit) return current;
966
+ }
967
+ return current;
968
+ };
969
+ var applySingleAfterMiddleware = async (middleware, current, baseContext, timeoutMs = 5e3) => {
970
+ if (!middleware.afterResponse) return current;
971
+ const frozenMetadata = current.metadata ? deepFreeze({ ...current.metadata }) : void 0;
972
+ const result = await executeWithTimeout(
973
+ () => middleware.afterResponse({
974
+ ...baseContext,
975
+ responseText: current.responseText,
976
+ metadata: frozenMetadata
977
+ }),
978
+ timeoutMs,
979
+ middleware.name,
980
+ "afterResponse"
981
+ );
982
+ if (!result) return current;
983
+ return {
984
+ responseText: result.responseText ?? current.responseText,
985
+ metadata: result.metadata ?? current.metadata
986
+ };
987
+ };
988
+ var executeAfterResponseHooks = async (middlewares, context, timeoutMs = 5e3) => {
989
+ if (!middlewares || middlewares.length === 0) {
990
+ return { responseText: context.responseText, metadata: context.metadata };
991
+ }
992
+ let current = {
993
+ responseText: context.responseText,
994
+ metadata: context.metadata
995
+ };
996
+ for (const middleware of middlewares) {
997
+ current = await applySingleAfterMiddleware(middleware, current, context, timeoutMs);
998
+ }
999
+ return current;
1000
+ };
1001
+
739
1002
  // src/control-layer.ts
740
1003
  var AvantGateControlLayer = class {
741
1004
  config;
742
1005
  cachedPricingAdapter;
1006
+ reservations = /* @__PURE__ */ new Map();
1007
+ cachedProviderChain;
743
1008
  constructor(config) {
744
1009
  this.config = {
745
1010
  ...config,
@@ -747,6 +1012,11 @@ var AvantGateControlLayer = class {
747
1012
  fallback: this.resolveProviderConfig(config.fallback),
748
1013
  emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
749
1014
  };
1015
+ this.cachedProviderChain = [
1016
+ this.config.primary,
1017
+ this.config.fallback,
1018
+ this.config.emergencyFallback
1019
+ ].filter((provider) => Boolean(provider?.client));
750
1020
  if (config.pricingAdapter) {
751
1021
  this.cachedPricingAdapter = new CachedPricingAdapter(
752
1022
  config.pricingAdapter,
@@ -754,6 +1024,12 @@ var AvantGateControlLayer = class {
754
1024
  );
755
1025
  }
756
1026
  }
1027
+ getReservation = (id) => {
1028
+ return this.reservations.get(id);
1029
+ };
1030
+ getAllReservations = () => {
1031
+ return Array.from(this.reservations.values());
1032
+ };
757
1033
  resolveProviderConfig = (provider) => {
758
1034
  if (!provider) return void 0;
759
1035
  if (provider.client) return provider;
@@ -766,16 +1042,15 @@ var AvantGateControlLayer = class {
766
1042
  return provider;
767
1043
  };
768
1044
  findProviderConfig = (provider, model) => {
769
- const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
770
- (p) => Boolean(p)
771
- );
772
1045
  if (provider) {
773
- const matchProvider = list.find((p) => p.provider === provider);
774
- if (matchProvider) return matchProvider;
1046
+ if (this.config.primary.provider === provider) return this.config.primary;
1047
+ if (this.config.fallback?.provider === provider) return this.config.fallback;
1048
+ if (this.config.emergencyFallback?.provider === provider) return this.config.emergencyFallback;
775
1049
  }
776
1050
  if (model) {
777
- const matchModel = list.find((p) => p.model === model);
778
- if (matchModel) return matchModel;
1051
+ if (this.config.primary.model === model) return this.config.primary;
1052
+ if (this.config.fallback?.model === model) return this.config.fallback;
1053
+ if (this.config.emergencyFallback?.model === model) return this.config.emergencyFallback;
779
1054
  }
780
1055
  return this.config.primary;
781
1056
  };
@@ -794,7 +1069,7 @@ var AvantGateControlLayer = class {
794
1069
  maxLength: this.config.security?.maxInputLength
795
1070
  });
796
1071
  if (!guard.valid) {
797
- throw new Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
1072
+ throw new PromptInjectionError(guard.blockedReason ?? "Prompt injection detected");
798
1073
  }
799
1074
  let processed = userQuery;
800
1075
  if (this.config.security?.customRedactionTerms && this.config.security.customRedactionTerms.length > 0) {
@@ -805,6 +1080,21 @@ var AvantGateControlLayer = class {
805
1080
  }
806
1081
  return processed;
807
1082
  };
1083
+ reScanMessagesAfterHooks = (originalMessages, updatedMessages) => {
1084
+ if (this.config.reScanIngressAfterHooks === false || originalMessages === updatedMessages) {
1085
+ return updatedMessages;
1086
+ }
1087
+ return updatedMessages.map((msg, idx) => {
1088
+ const orig = originalMessages[idx];
1089
+ if (orig && (orig === msg || orig.role === msg.role && orig.content === msg.content)) {
1090
+ return msg;
1091
+ }
1092
+ return {
1093
+ ...msg,
1094
+ content: this.applySecurityGuards(msg.content)
1095
+ };
1096
+ });
1097
+ };
808
1098
  applyOutputSecurityGuards = (rawOutput) => {
809
1099
  const isDlpEnabled = this.config.security?.outputDLP ?? this.config.security?.maskPII ?? false;
810
1100
  const shouldCheckSecrets = this.config.security?.blockSecretLeaks ?? true;
@@ -820,33 +1110,79 @@ var AvantGateControlLayer = class {
820
1110
  });
821
1111
  return guardResult.text;
822
1112
  };
823
- checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider) => {
824
- if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
1113
+ validateTokenBudget = (estimatedTokens) => {
1114
+ if (this.config.maxTokenBudget !== void 0 && estimatedTokens > this.config.maxTokenBudget) {
825
1115
  throw new BudgetExceededError(
826
- `[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
1116
+ `[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
827
1117
  );
828
1118
  }
829
- if (this.config.maxCostUSD !== void 0) {
830
- const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
831
- const providerCfg = this.findProviderConfig(provider, targetModel);
832
- const price = await resolveModelPriceAsync(targetModel, {
833
- provider: provider ?? providerCfg?.provider,
834
- providerPricing: providerCfg?.pricing,
835
- customPricing: this.config.customPricing,
836
- adapter: this.cachedPricingAdapter
837
- });
838
- if (!isLocalFree && !price) {
839
- throw new ConfigurationError(
840
- `[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.`
841
- );
842
- }
843
- const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
844
- if (estimatedPromptCost > this.config.maxCostUSD) {
845
- throw new BudgetExceededError(
846
- `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
847
- );
1119
+ };
1120
+ validatePricingConfigured = async (targetModel, provider) => {
1121
+ const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
1122
+ if (isLocalFree) return;
1123
+ const providerCfg = this.findProviderConfig(provider, targetModel);
1124
+ const price = await resolveModelPriceAsync(targetModel, {
1125
+ provider: provider ?? providerCfg?.provider,
1126
+ providerPricing: providerCfg?.pricing,
1127
+ customPricing: this.config.customPricing,
1128
+ adapter: this.cachedPricingAdapter
1129
+ });
1130
+ if (!price) {
1131
+ throw new ConfigurationError(
1132
+ `[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.`
1133
+ );
1134
+ }
1135
+ };
1136
+ evaluatePessimisticSolvency = (estimatedPromptCost, parentRunId) => {
1137
+ if (!parentRunId || this.config.maxCostUSD === void 0) return;
1138
+ let hasUnconfirmed = false;
1139
+ let settledSpend = 0;
1140
+ for (const r of this.reservations.values()) {
1141
+ if (r.parentRunId !== parentRunId) continue;
1142
+ if (r.status === "UNCONFIRMED_TIMEOUT") hasUnconfirmed = true;
1143
+ if (r.status === "SETTLED") {
1144
+ settledSpend += r.settledCostUsd ?? r.estimatedCostUsd;
848
1145
  }
849
1146
  }
1147
+ if (!hasUnconfirmed) return;
1148
+ const totalWorstCaseCost = settledSpend + 2 * estimatedPromptCost;
1149
+ if (totalWorstCaseCost > this.config.maxCostUSD) {
1150
+ throw new BudgetExceededError(
1151
+ `[AvantGate FinOps Guard] Retry rejected: worst-case cumulative spend ($${totalWorstCaseCost.toFixed(4)}) exceeds max budget ($${this.config.maxCostUSD.toFixed(4)}).`
1152
+ );
1153
+ }
1154
+ };
1155
+ createBudgetReservation = (estimatedCostUsd, parentRunId) => {
1156
+ const reservationId = `res_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
1157
+ const reservation = {
1158
+ id: reservationId,
1159
+ parentRunId,
1160
+ estimatedCostUsd,
1161
+ status: "RESERVED",
1162
+ createdAt: Date.now(),
1163
+ updatedAt: Date.now()
1164
+ };
1165
+ if (this.reservations.size >= 1e4) {
1166
+ const oldestKey = this.reservations.keys().next().value;
1167
+ if (oldestKey) this.reservations.delete(oldestKey);
1168
+ }
1169
+ this.reservations.set(reservationId, reservation);
1170
+ return reservation;
1171
+ };
1172
+ checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider, parentRunId) => {
1173
+ this.validateTokenBudget(estimatedPromptTokens);
1174
+ if (this.config.maxCostUSD === void 0) {
1175
+ return void 0;
1176
+ }
1177
+ await this.validatePricingConfigured(targetModel, provider);
1178
+ const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
1179
+ this.evaluatePessimisticSolvency(estimatedPromptCost, parentRunId);
1180
+ if (estimatedPromptCost > this.config.maxCostUSD) {
1181
+ throw new BudgetExceededError(
1182
+ `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
1183
+ );
1184
+ }
1185
+ return this.createBudgetReservation(estimatedPromptCost, parentRunId);
850
1186
  };
851
1187
  checkPostExecutionBudget = (tokensTotal, costUSD) => {
852
1188
  if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
@@ -880,11 +1216,7 @@ var AvantGateControlLayer = class {
880
1216
  };
881
1217
  };
882
1218
  getProviderChain = () => {
883
- return [
884
- this.config.primary,
885
- this.config.fallback,
886
- this.config.emergencyFallback
887
- ].filter((provider) => Boolean(provider?.client));
1219
+ return this.cachedProviderChain;
888
1220
  };
889
1221
  executeSimulation = (query, systemPrompt, modelOverride) => {
890
1222
  const targetModel = modelOverride ?? this.config.primary.model;
@@ -979,28 +1311,140 @@ var AvantGateControlLayer = class {
979
1311
  attempts: result.attempts
980
1312
  });
981
1313
  };
982
- execute = async (options) => {
983
- const sanitizedQuery = this.applySecurityGuards(options.userQuery);
984
- const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
985
- const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
986
- const estimatedPromptTokens = Math.ceil(promptLength / 4);
987
- await this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
988
- if (!options.providerOverride && this.getProviderChain().length === 0) {
1314
+ prepareInputMessages = (options) => {
1315
+ if (options.messages && options.messages.length > 0) {
1316
+ return this.sanitizeIncomingMessages(options.messages);
1317
+ }
1318
+ if (options.userQuery !== void 0) {
1319
+ const sanitizedQuery = this.applySecurityGuards(options.userQuery);
1320
+ return this.buildMessages(options.systemPrompt, sanitizedQuery);
1321
+ }
1322
+ throw new ConfigurationError("[AvantGate Input Guard] Either 'userQuery' or 'messages' must be provided.");
1323
+ };
1324
+ handleShortCircuit = async (shortCircuit, targetModel) => {
1325
+ const sanitizedText = this.applyOutputSecurityGuards(shortCircuit.text);
1326
+ const result = {
1327
+ text: sanitizedText,
1328
+ tokens: {
1329
+ prompt: shortCircuit.tokens?.promptTokens ?? 0,
1330
+ completion: shortCircuit.tokens?.completionTokens ?? 0,
1331
+ total: shortCircuit.tokens?.totalTokens ?? 0
1332
+ },
1333
+ costUSD: shortCircuit.costUSD ?? 0,
1334
+ modelUsed: `${targetModel}:shortcircuit`,
1335
+ failoverOccurred: false,
1336
+ attempts: 0
1337
+ };
1338
+ await this.notifyAuditSink(result);
1339
+ return result;
1340
+ };
1341
+ dispatchPromptExecution = async (messages, temperature, providerOverride) => {
1342
+ const query = messages[messages.length - 1]?.content ?? "";
1343
+ if (providerOverride) {
1344
+ return this.executeOverrideProvider(providerOverride, messages, query, temperature);
1345
+ }
1346
+ if (this.getProviderChain().length === 0) {
989
1347
  if (this.config.mockSimulation) {
990
- const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
991
- await this.notifyAuditSink(simResult);
992
- return simResult;
1348
+ const sim = this.executeSimulation(query);
1349
+ return {
1350
+ responseText: sim.text,
1351
+ usage: { promptTokens: sim.tokens.prompt, completionTokens: sim.tokens.completion, totalTokens: sim.tokens.total },
1352
+ modelUsed: sim.modelUsed,
1353
+ failoverOccurred: false,
1354
+ attempts: 1
1355
+ };
993
1356
  }
994
1357
  throw new ConfigurationError(
995
1358
  "[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
996
1359
  );
997
1360
  }
998
- const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
999
- const result = this.assembleResult(output);
1000
- this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
1361
+ return this.executeProviderPipeline(messages, query, temperature);
1362
+ };
1363
+ finalizeExecutionResult = async (output, responseText) => {
1364
+ const sanitizedText = this.applyOutputSecurityGuards(responseText);
1365
+ const hitTokens = output.usage.promptCacheHitTokens ?? 0;
1366
+ const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens, hitTokens);
1367
+ this.checkPostExecutionBudget(output.usage.totalTokens, costUSD);
1368
+ const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
1369
+ const result = {
1370
+ text: sanitizedText,
1371
+ tokens,
1372
+ costUSD,
1373
+ modelUsed: output.modelUsed,
1374
+ failoverOccurred: output.failoverOccurred,
1375
+ attempts: output.attempts
1376
+ };
1001
1377
  await this.notifyAuditSink(result);
1002
1378
  return result;
1003
1379
  };
1380
+ runAfterHooks = async (beforeState, output) => {
1381
+ const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
1382
+ const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens);
1383
+ return executeAfterResponseHooks(
1384
+ this.config.middlewares,
1385
+ {
1386
+ messages: beforeState.messages,
1387
+ responseText: output.responseText,
1388
+ modelUsed: output.modelUsed,
1389
+ tokens,
1390
+ costUSD,
1391
+ attempts: output.attempts,
1392
+ failoverOccurred: output.failoverOccurred,
1393
+ metadata: beforeState.metadata
1394
+ },
1395
+ this.config.middlewareTimeoutMs ?? 5e3
1396
+ );
1397
+ };
1398
+ verifyPromptBudget = async (messages, model, parentRunId) => {
1399
+ const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
1400
+ const estimatedPromptTokens = Math.ceil(promptLength / 4);
1401
+ return this.checkPreflightBudget(estimatedPromptTokens, model, void 0, parentRunId);
1402
+ };
1403
+ execute = async (options) => {
1404
+ const inputMessages = this.prepareInputMessages(options);
1405
+ const beforeState = await executeBeforeRequestHooks(
1406
+ this.config.middlewares,
1407
+ {
1408
+ messages: inputMessages,
1409
+ model: this.config.primary.model,
1410
+ temperature: options.temperature,
1411
+ metadata: options.metadata
1412
+ },
1413
+ this.config.middlewareTimeoutMs ?? 5e3
1414
+ );
1415
+ if (beforeState.shortCircuit) {
1416
+ return this.handleShortCircuit(beforeState.shortCircuit, this.config.primary.model);
1417
+ }
1418
+ beforeState.messages = this.reScanMessagesAfterHooks(inputMessages, beforeState.messages);
1419
+ const parentRunId = beforeState.metadata?.parentRunId ?? options.metadata?.parentRunId;
1420
+ const reservation = await this.verifyPromptBudget(beforeState.messages, this.config.primary.model, parentRunId);
1421
+ let output;
1422
+ try {
1423
+ output = await this.dispatchPromptExecution(beforeState.messages, options.temperature, options.providerOverride);
1424
+ } catch (err) {
1425
+ if (reservation) {
1426
+ const errorMsg = String(err);
1427
+ const isClientError = errorMsg.includes("HTTP 400") || errorMsg.includes("HTTP 401") || errorMsg.includes("HTTP 403") || errorMsg.includes("HTTP 422");
1428
+ if (isClientError) {
1429
+ reservation.status = "RELEASED";
1430
+ reservation.updatedAt = Date.now();
1431
+ } else {
1432
+ reservation.status = "UNCONFIRMED_TIMEOUT";
1433
+ reservation.updatedAt = Date.now();
1434
+ }
1435
+ }
1436
+ throw err;
1437
+ }
1438
+ const afterState = await this.runAfterHooks(beforeState, output);
1439
+ const finalResult = await this.finalizeExecutionResult(output, afterState.responseText);
1440
+ if (reservation) {
1441
+ reservation.status = "SETTLED";
1442
+ reservation.settledCostUsd = finalResult.costUSD;
1443
+ reservation.updatedAt = Date.now();
1444
+ }
1445
+ return finalResult;
1446
+ };
1447
+ executePrompt = this.execute;
1004
1448
  executeStructured = async (options) => {
1005
1449
  const rawResult = await this.execute({
1006
1450
  userQuery: options.userQuery,
@@ -1032,11 +1476,6 @@ var AvantGateControlLayer = class {
1032
1476
  return msg;
1033
1477
  });
1034
1478
  };
1035
- verifyPromptBudget = async (messages, model) => {
1036
- const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
1037
- const estimatedPromptTokens = Math.ceil(promptLength / 4);
1038
- await this.checkPreflightBudget(estimatedPromptTokens, model);
1039
- };
1040
1479
  dispatchOverrideAttempt = async (override, messages, model, temperature) => {
1041
1480
  const res = await override.complete({ model, messages, temperature: temperature ?? 0.1 });
1042
1481
  const usage = this.resolveTokens(res.usage, JSON.stringify(messages), res.text);
@@ -1135,28 +1574,60 @@ var AvantGateControlLayer = class {
1135
1574
  const delay = initialDelay * Math.pow(factor, attempt);
1136
1575
  await new Promise((resolve) => setTimeout(resolve, delay));
1137
1576
  };
1138
- generateStructuredOutput = async (options) => {
1139
- const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
1140
- const targetModel = options.model ?? this.config.primary.model;
1141
- const processedMessages = this.sanitizeIncomingMessages(options.messages);
1142
- await this.verifyPromptBudget(processedMessages, targetModel);
1577
+ handleStructuredShortCircuit = async (shortCircuit, options, targetModel) => {
1578
+ const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(
1579
+ shortCircuit.text,
1580
+ options
1581
+ );
1582
+ const tokens = {
1583
+ prompt: shortCircuit.tokens?.promptTokens ?? 0,
1584
+ completion: shortCircuit.tokens?.completionTokens ?? 0,
1585
+ total: shortCircuit.tokens?.totalTokens ?? 0
1586
+ };
1587
+ const result = {
1588
+ data: parsedData,
1589
+ rawText: sanitizedResponse,
1590
+ tokens,
1591
+ costUSD: shortCircuit.costUSD ?? 0,
1592
+ modelUsed: `${targetModel}:shortcircuit`,
1593
+ failoverOccurred: false
1594
+ };
1595
+ await this.notifyAuditSinkSafe(sanitizedResponse, result, 0);
1596
+ return result;
1597
+ };
1598
+ runStructuredAfterHooks = async (messages, output, attempt) => {
1599
+ const tokens = { prompt: output.promptTokens, completion: output.completionTokens, total: output.promptTokens + output.completionTokens };
1600
+ const costUSD = this.calculateCost(output.modelUsed, output.promptTokens, output.completionTokens);
1601
+ return executeAfterResponseHooks(
1602
+ this.config.middlewares,
1603
+ {
1604
+ messages,
1605
+ responseText: output.responseText,
1606
+ modelUsed: output.modelUsed,
1607
+ tokens,
1608
+ costUSD,
1609
+ attempts: attempt,
1610
+ failoverOccurred: output.failoverOccurred
1611
+ },
1612
+ this.config.middlewareTimeoutMs ?? 5e3
1613
+ );
1614
+ };
1615
+ executeStructuredLoop = async (options, messages, targetModel, maxRetries) => {
1143
1616
  let lastError;
1144
- let accumulatedPromptTokens = 0;
1145
- let accumulatedCompletionTokens = 0;
1617
+ let accPrompt = 0;
1618
+ let accComp = 0;
1146
1619
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
1147
1620
  try {
1148
- const output = await this.dispatchProviderAttempt(options, processedMessages, targetModel);
1149
- accumulatedPromptTokens += output.promptTokens;
1150
- accumulatedCompletionTokens += output.completionTokens;
1151
- const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(
1152
- output.responseText,
1153
- options
1154
- );
1621
+ const output = await this.dispatchProviderAttempt(options, messages, targetModel);
1622
+ accPrompt += output.promptTokens;
1623
+ accComp += output.completionTokens;
1624
+ const afterState = await this.runStructuredAfterHooks(messages, output, attempt + 1);
1625
+ const { parsedData, sanitizedResponse } = this.parseAndValidateStructuredResult(afterState.responseText, options);
1155
1626
  const result = this.buildStructuredResult({
1156
1627
  data: parsedData,
1157
1628
  rawText: sanitizedResponse,
1158
- promptTokens: accumulatedPromptTokens,
1159
- completionTokens: accumulatedCompletionTokens,
1629
+ promptTokens: accPrompt,
1630
+ completionTokens: accComp,
1160
1631
  modelUsed: output.modelUsed,
1161
1632
  failoverOccurred: output.failoverOccurred
1162
1633
  });
@@ -1169,6 +1640,47 @@ var AvantGateControlLayer = class {
1169
1640
  }
1170
1641
  throw lastError;
1171
1642
  };
1643
+ generateStructuredOutput = async (options) => {
1644
+ const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
1645
+ const targetModel = options.model ?? this.config.primary.model;
1646
+ const processedMessages = this.sanitizeIncomingMessages(options.messages);
1647
+ const beforeState = await executeBeforeRequestHooks(
1648
+ this.config.middlewares,
1649
+ {
1650
+ messages: processedMessages,
1651
+ model: targetModel,
1652
+ temperature: options.temperature
1653
+ },
1654
+ this.config.middlewareTimeoutMs ?? 5e3
1655
+ );
1656
+ if (beforeState.shortCircuit) {
1657
+ return this.handleStructuredShortCircuit(beforeState.shortCircuit, options, targetModel);
1658
+ }
1659
+ beforeState.messages = this.reScanMessagesAfterHooks(processedMessages, beforeState.messages);
1660
+ const reservation = await this.verifyPromptBudget(beforeState.messages, targetModel);
1661
+ try {
1662
+ const result = await this.executeStructuredLoop(options, beforeState.messages, targetModel, maxRetries);
1663
+ if (reservation) {
1664
+ reservation.status = "SETTLED";
1665
+ reservation.settledCostUsd = result.costUSD;
1666
+ reservation.updatedAt = Date.now();
1667
+ }
1668
+ return result;
1669
+ } catch (err) {
1670
+ if (reservation) {
1671
+ const errorMsg = String(err);
1672
+ const isClientError = errorMsg.includes("HTTP 400") || errorMsg.includes("HTTP 401") || errorMsg.includes("HTTP 403") || errorMsg.includes("HTTP 422");
1673
+ if (isClientError) {
1674
+ reservation.status = "RELEASED";
1675
+ reservation.updatedAt = Date.now();
1676
+ } else {
1677
+ reservation.status = "UNCONFIRMED_TIMEOUT";
1678
+ reservation.updatedAt = Date.now();
1679
+ }
1680
+ }
1681
+ throw err;
1682
+ }
1683
+ };
1172
1684
  };
1173
1685
  var createLLMControlLayer = (config) => {
1174
1686
  return new AvantGateControlLayer(config);
@@ -1256,7 +1768,7 @@ var PromptTemplate = class {
1256
1768
  };
1257
1769
 
1258
1770
  // src/prompts/prompt-builder.ts
1259
- import { z } from "zod";
1771
+ import { z as z2 } from "zod";
1260
1772
  var PromptBuilder = class {
1261
1773
  personaSlot = "";
1262
1774
  rulesSlot = [];
@@ -1312,7 +1824,7 @@ var PromptBuilder = class {
1312
1824
  }
1313
1825
  schemaContract(schema, options) {
1314
1826
  let shapeDescription = "JSON Object";
1315
- if (schema instanceof z.ZodObject) {
1827
+ if (schema instanceof z2.ZodObject) {
1316
1828
  shapeDescription = JSON.stringify(Object.keys(schema.shape));
1317
1829
  }
1318
1830
  this.schemaContractText = [
@@ -1489,6 +2001,73 @@ var PromptRegistry = class {
1489
2001
  this.registry.clear();
1490
2002
  }
1491
2003
  };
2004
+
2005
+ // src/presets.ts
2006
+ var createLaunchSafeConfig = (options) => {
2007
+ const defaultSecurity = {
2008
+ detectPromptInjection: true,
2009
+ maskPII: true,
2010
+ outputDLP: true,
2011
+ blockSecretLeaks: true,
2012
+ secretLeakAction: "REDACT"
2013
+ };
2014
+ const defaultRetries = { maxRetries: 2, initialDelayMs: 300, backoffFactor: 1.5 };
2015
+ return {
2016
+ maxTokenBudget: 8192,
2017
+ maxCostUSD: 0.5,
2018
+ middlewareTimeoutMs: 5e3,
2019
+ reScanIngressAfterHooks: true,
2020
+ ...options,
2021
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
2022
+ security: { ...defaultSecurity, ...options.security }
2023
+ };
2024
+ };
2025
+ var createEnterpriseStrictConfig = (options) => {
2026
+ const defaultSecurity = {
2027
+ detectPromptInjection: true,
2028
+ maskPII: true,
2029
+ outputDLP: true,
2030
+ blockSecretLeaks: true,
2031
+ secretLeakAction: "BLOCK"
2032
+ };
2033
+ const defaultRetries = {
2034
+ maxRetries: 3,
2035
+ initialDelayMs: 500,
2036
+ backoffFactor: 2
2037
+ };
2038
+ return {
2039
+ maxTokenBudget: 4096,
2040
+ maxCostUSD: 0.15,
2041
+ middlewareTimeoutMs: 3e3,
2042
+ reScanIngressAfterHooks: true,
2043
+ ...options,
2044
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
2045
+ security: { ...defaultSecurity, ...options.security }
2046
+ };
2047
+ };
2048
+ var createDevPermissiveConfig = (options) => {
2049
+ const defaultSecurity = {
2050
+ detectPromptInjection: false,
2051
+ maskPII: false,
2052
+ outputDLP: false,
2053
+ blockSecretLeaks: false
2054
+ };
2055
+ const defaultRetries = {
2056
+ maxRetries: 1,
2057
+ initialDelayMs: 200,
2058
+ backoffFactor: 1
2059
+ };
2060
+ return {
2061
+ ...options,
2062
+ retryOptions: { ...defaultRetries, ...options.retryOptions },
2063
+ security: { ...defaultSecurity, ...options.security }
2064
+ };
2065
+ };
2066
+ var PRESETS = {
2067
+ LAUNCH_SAFE: createLaunchSafeConfig,
2068
+ ENTERPRISE_STRICT: createEnterpriseStrictConfig,
2069
+ DEV_PERMISSIVE: createDevPermissiveConfig
2070
+ };
1492
2071
  export {
1493
2072
  BudgetExceededError as AvantGateBudgetExceededError,
1494
2073
  ConfigurationError as AvantGateConfigurationError,
@@ -1496,13 +2075,22 @@ export {
1496
2075
  SecretLeakBlockedError as AvantGateSecretLeakBlockedError,
1497
2076
  BudgetExceededError,
1498
2077
  CachedPricingAdapter,
2078
+ ChatMessageSchema,
2079
+ ChatMessagesArraySchema,
2080
+ ChatRoleSchema,
1499
2081
  ConfigurationError,
1500
2082
  DEFAULT_MODEL_PRICES,
1501
2083
  HttpProviderClient,
2084
+ InvalidMessageSchemaError,
2085
+ MiddlewareTimeoutError,
2086
+ PRESETS,
1502
2087
  PricingRegistry,
1503
2088
  PromptBuilder,
2089
+ PromptInjectionError,
1504
2090
  PromptRegistry,
1505
2091
  PromptTemplate,
2092
+ ProviderIdleTimeoutError,
2093
+ ProviderTTFTTimeoutError,
1506
2094
  SEED_MODEL_PRICES,
1507
2095
  SecretLeakBlockedError,
1508
2096
  SqlitePricingAdapter,
@@ -1511,11 +2099,19 @@ export {
1511
2099
  WorkflowValidationError,
1512
2100
  AvantGateControlLayer as ZenLLMControlLayer,
1513
2101
  applyOutputGuards,
2102
+ applySingleAfterMiddleware,
2103
+ applySingleBeforeMiddleware,
1514
2104
  calculateCostUSD,
1515
2105
  createAvantGate,
2106
+ createDevPermissiveConfig,
2107
+ createEnterpriseStrictConfig,
1516
2108
  createHttpProviderClient,
1517
2109
  createLLMControlLayer,
2110
+ createLaunchSafeConfig,
2111
+ deepFreeze,
1518
2112
  defineWorkflow,
2113
+ executeAfterResponseHooks,
2114
+ executeBeforeRequestHooks,
1519
2115
  executeWorkflow,
1520
2116
  extractAndCleanJSON,
1521
2117
  isWorkflowAbortSignal,