avantgate 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,23 +843,110 @@ 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
+
739
903
  // src/middleware.ts
740
- var applySingleBeforeMiddleware = async (middleware, current, model, temperature) => {
741
- if (!middleware.beforeRequest) return current;
742
- const result = await middleware.beforeRequest({
743
- messages: current.messages,
744
- model,
745
- temperature,
746
- metadata: current.metadata
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);
747
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
+ );
748
941
  if (!result) return current;
942
+ validateReturnedMessages(result.messages, middleware.name);
749
943
  return {
750
944
  messages: result.messages ?? current.messages,
751
945
  shortCircuit: result.shortCircuit,
752
946
  metadata: current.metadata
753
947
  };
754
948
  };
755
- var executeBeforeRequestHooks = async (middlewares, context) => {
949
+ var executeBeforeRequestHooks = async (middlewares, context, timeoutMs = 5e3) => {
756
950
  if (!middlewares || middlewares.length === 0) {
757
951
  return { messages: context.messages, metadata: context.metadata };
758
952
  }
@@ -761,25 +955,37 @@ var executeBeforeRequestHooks = async (middlewares, context) => {
761
955
  metadata: context.metadata
762
956
  };
763
957
  for (const middleware of middlewares) {
764
- current = await applySingleBeforeMiddleware(middleware, current, context.model, context.temperature);
958
+ current = await applySingleBeforeMiddleware(
959
+ middleware,
960
+ current,
961
+ context.model,
962
+ context.temperature,
963
+ timeoutMs
964
+ );
765
965
  if (current.shortCircuit) return current;
766
966
  }
767
967
  return current;
768
968
  };
769
- var applySingleAfterMiddleware = async (middleware, current, baseContext) => {
969
+ var applySingleAfterMiddleware = async (middleware, current, baseContext, timeoutMs = 5e3) => {
770
970
  if (!middleware.afterResponse) return current;
771
- const result = await middleware.afterResponse({
772
- ...baseContext,
773
- responseText: current.responseText,
774
- metadata: current.metadata
775
- });
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
+ );
776
982
  if (!result) return current;
777
983
  return {
778
984
  responseText: result.responseText ?? current.responseText,
779
985
  metadata: result.metadata ?? current.metadata
780
986
  };
781
987
  };
782
- var executeAfterResponseHooks = async (middlewares, context) => {
988
+ var executeAfterResponseHooks = async (middlewares, context, timeoutMs = 5e3) => {
783
989
  if (!middlewares || middlewares.length === 0) {
784
990
  return { responseText: context.responseText, metadata: context.metadata };
785
991
  }
@@ -788,7 +994,7 @@ var executeAfterResponseHooks = async (middlewares, context) => {
788
994
  metadata: context.metadata
789
995
  };
790
996
  for (const middleware of middlewares) {
791
- current = await applySingleAfterMiddleware(middleware, current, context);
997
+ current = await applySingleAfterMiddleware(middleware, current, context, timeoutMs);
792
998
  }
793
999
  return current;
794
1000
  };
@@ -797,6 +1003,8 @@ var executeAfterResponseHooks = async (middlewares, context) => {
797
1003
  var AvantGateControlLayer = class {
798
1004
  config;
799
1005
  cachedPricingAdapter;
1006
+ reservations = /* @__PURE__ */ new Map();
1007
+ cachedProviderChain;
800
1008
  constructor(config) {
801
1009
  this.config = {
802
1010
  ...config,
@@ -804,6 +1012,11 @@ var AvantGateControlLayer = class {
804
1012
  fallback: this.resolveProviderConfig(config.fallback),
805
1013
  emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
806
1014
  };
1015
+ this.cachedProviderChain = [
1016
+ this.config.primary,
1017
+ this.config.fallback,
1018
+ this.config.emergencyFallback
1019
+ ].filter((provider) => Boolean(provider?.client));
807
1020
  if (config.pricingAdapter) {
808
1021
  this.cachedPricingAdapter = new CachedPricingAdapter(
809
1022
  config.pricingAdapter,
@@ -811,6 +1024,12 @@ var AvantGateControlLayer = class {
811
1024
  );
812
1025
  }
813
1026
  }
1027
+ getReservation = (id) => {
1028
+ return this.reservations.get(id);
1029
+ };
1030
+ getAllReservations = () => {
1031
+ return Array.from(this.reservations.values());
1032
+ };
814
1033
  resolveProviderConfig = (provider) => {
815
1034
  if (!provider) return void 0;
816
1035
  if (provider.client) return provider;
@@ -823,16 +1042,15 @@ var AvantGateControlLayer = class {
823
1042
  return provider;
824
1043
  };
825
1044
  findProviderConfig = (provider, model) => {
826
- const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
827
- (p) => Boolean(p)
828
- );
829
1045
  if (provider) {
830
- const matchProvider = list.find((p) => p.provider === provider);
831
- 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;
832
1049
  }
833
1050
  if (model) {
834
- const matchModel = list.find((p) => p.model === model);
835
- 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;
836
1054
  }
837
1055
  return this.config.primary;
838
1056
  };
@@ -851,7 +1069,7 @@ var AvantGateControlLayer = class {
851
1069
  maxLength: this.config.security?.maxInputLength
852
1070
  });
853
1071
  if (!guard.valid) {
854
- throw new Error(`[AvantGate Security Guard] Request blocked: ${guard.blockedReason}`);
1072
+ throw new PromptInjectionError(guard.blockedReason ?? "Prompt injection detected");
855
1073
  }
856
1074
  let processed = userQuery;
857
1075
  if (this.config.security?.customRedactionTerms && this.config.security.customRedactionTerms.length > 0) {
@@ -862,6 +1080,21 @@ var AvantGateControlLayer = class {
862
1080
  }
863
1081
  return processed;
864
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
+ };
865
1098
  applyOutputSecurityGuards = (rawOutput) => {
866
1099
  const isDlpEnabled = this.config.security?.outputDLP ?? this.config.security?.maskPII ?? false;
867
1100
  const shouldCheckSecrets = this.config.security?.blockSecretLeaks ?? true;
@@ -877,33 +1110,79 @@ var AvantGateControlLayer = class {
877
1110
  });
878
1111
  return guardResult.text;
879
1112
  };
880
- checkPreflightBudget = async (estimatedPromptTokens, targetModel, provider) => {
881
- if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
1113
+ validateTokenBudget = (estimatedTokens) => {
1114
+ if (this.config.maxTokenBudget !== void 0 && estimatedTokens > this.config.maxTokenBudget) {
882
1115
  throw new BudgetExceededError(
883
- `[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}).`
884
1117
  );
885
1118
  }
886
- if (this.config.maxCostUSD !== void 0) {
887
- const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
888
- const providerCfg = this.findProviderConfig(provider, targetModel);
889
- const price = await resolveModelPriceAsync(targetModel, {
890
- provider: provider ?? providerCfg?.provider,
891
- providerPricing: providerCfg?.pricing,
892
- customPricing: this.config.customPricing,
893
- adapter: this.cachedPricingAdapter
894
- });
895
- if (!isLocalFree && !price) {
896
- throw new ConfigurationError(
897
- `[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.`
898
- );
899
- }
900
- const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
901
- if (estimatedPromptCost > this.config.maxCostUSD) {
902
- throw new BudgetExceededError(
903
- `[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
904
- );
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;
905
1145
  }
906
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);
907
1186
  };
908
1187
  checkPostExecutionBudget = (tokensTotal, costUSD) => {
909
1188
  if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
@@ -937,11 +1216,7 @@ var AvantGateControlLayer = class {
937
1216
  };
938
1217
  };
939
1218
  getProviderChain = () => {
940
- return [
941
- this.config.primary,
942
- this.config.fallback,
943
- this.config.emergencyFallback
944
- ].filter((provider) => Boolean(provider?.client));
1219
+ return this.cachedProviderChain;
945
1220
  };
946
1221
  executeSimulation = (query, systemPrompt, modelOverride) => {
947
1222
  const targetModel = modelOverride ?? this.config.primary.model;
@@ -1105,32 +1380,69 @@ var AvantGateControlLayer = class {
1105
1380
  runAfterHooks = async (beforeState, output) => {
1106
1381
  const tokens = { prompt: output.usage.promptTokens, completion: output.usage.completionTokens, total: output.usage.totalTokens };
1107
1382
  const costUSD = this.calculateCost(output.modelUsed, output.usage.promptTokens, output.usage.completionTokens);
1108
- return executeAfterResponseHooks(this.config.middlewares, {
1109
- messages: beforeState.messages,
1110
- responseText: output.responseText,
1111
- modelUsed: output.modelUsed,
1112
- tokens,
1113
- costUSD,
1114
- attempts: output.attempts,
1115
- failoverOccurred: output.failoverOccurred,
1116
- metadata: beforeState.metadata
1117
- });
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);
1118
1402
  };
1119
1403
  execute = async (options) => {
1120
1404
  const inputMessages = this.prepareInputMessages(options);
1121
- const beforeState = await executeBeforeRequestHooks(this.config.middlewares, {
1122
- messages: inputMessages,
1123
- model: this.config.primary.model,
1124
- temperature: options.temperature,
1125
- metadata: options.metadata
1126
- });
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
+ );
1127
1415
  if (beforeState.shortCircuit) {
1128
1416
  return this.handleShortCircuit(beforeState.shortCircuit, this.config.primary.model);
1129
1417
  }
1130
- await this.verifyPromptBudget(beforeState.messages, this.config.primary.model);
1131
- const output = await this.dispatchPromptExecution(beforeState.messages, options.temperature, options.providerOverride);
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
+ }
1132
1438
  const afterState = await this.runAfterHooks(beforeState, output);
1133
- return this.finalizeExecutionResult(output, afterState.responseText);
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;
1134
1446
  };
1135
1447
  executePrompt = this.execute;
1136
1448
  executeStructured = async (options) => {
@@ -1164,11 +1476,6 @@ var AvantGateControlLayer = class {
1164
1476
  return msg;
1165
1477
  });
1166
1478
  };
1167
- verifyPromptBudget = async (messages, model) => {
1168
- const promptLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
1169
- const estimatedPromptTokens = Math.ceil(promptLength / 4);
1170
- await this.checkPreflightBudget(estimatedPromptTokens, model);
1171
- };
1172
1479
  dispatchOverrideAttempt = async (override, messages, model, temperature) => {
1173
1480
  const res = await override.complete({ model, messages, temperature: temperature ?? 0.1 });
1174
1481
  const usage = this.resolveTokens(res.usage, JSON.stringify(messages), res.text);
@@ -1291,15 +1598,19 @@ var AvantGateControlLayer = class {
1291
1598
  runStructuredAfterHooks = async (messages, output, attempt) => {
1292
1599
  const tokens = { prompt: output.promptTokens, completion: output.completionTokens, total: output.promptTokens + output.completionTokens };
1293
1600
  const costUSD = this.calculateCost(output.modelUsed, output.promptTokens, output.completionTokens);
1294
- return executeAfterResponseHooks(this.config.middlewares, {
1295
- messages,
1296
- responseText: output.responseText,
1297
- modelUsed: output.modelUsed,
1298
- tokens,
1299
- costUSD,
1300
- attempts: attempt,
1301
- failoverOccurred: output.failoverOccurred
1302
- });
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
+ );
1303
1614
  };
1304
1615
  executeStructuredLoop = async (options, messages, targetModel, maxRetries) => {
1305
1616
  let lastError;
@@ -1333,16 +1644,42 @@ var AvantGateControlLayer = class {
1333
1644
  const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
1334
1645
  const targetModel = options.model ?? this.config.primary.model;
1335
1646
  const processedMessages = this.sanitizeIncomingMessages(options.messages);
1336
- const beforeState = await executeBeforeRequestHooks(this.config.middlewares, {
1337
- messages: processedMessages,
1338
- model: targetModel,
1339
- temperature: options.temperature
1340
- });
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
+ );
1341
1656
  if (beforeState.shortCircuit) {
1342
1657
  return this.handleStructuredShortCircuit(beforeState.shortCircuit, options, targetModel);
1343
1658
  }
1344
- await this.verifyPromptBudget(beforeState.messages, targetModel);
1345
- return this.executeStructuredLoop(options, beforeState.messages, targetModel, maxRetries);
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
+ }
1346
1683
  };
1347
1684
  };
1348
1685
  var createLLMControlLayer = (config) => {
@@ -1431,7 +1768,7 @@ var PromptTemplate = class {
1431
1768
  };
1432
1769
 
1433
1770
  // src/prompts/prompt-builder.ts
1434
- import { z } from "zod";
1771
+ import { z as z2 } from "zod";
1435
1772
  var PromptBuilder = class {
1436
1773
  personaSlot = "";
1437
1774
  rulesSlot = [];
@@ -1487,7 +1824,7 @@ var PromptBuilder = class {
1487
1824
  }
1488
1825
  schemaContract(schema, options) {
1489
1826
  let shapeDescription = "JSON Object";
1490
- if (schema instanceof z.ZodObject) {
1827
+ if (schema instanceof z2.ZodObject) {
1491
1828
  shapeDescription = JSON.stringify(Object.keys(schema.shape));
1492
1829
  }
1493
1830
  this.schemaContractText = [
@@ -1678,6 +2015,8 @@ var createLaunchSafeConfig = (options) => {
1678
2015
  return {
1679
2016
  maxTokenBudget: 8192,
1680
2017
  maxCostUSD: 0.5,
2018
+ middlewareTimeoutMs: 5e3,
2019
+ reScanIngressAfterHooks: true,
1681
2020
  ...options,
1682
2021
  retryOptions: { ...defaultRetries, ...options.retryOptions },
1683
2022
  security: { ...defaultSecurity, ...options.security }
@@ -1699,6 +2038,8 @@ var createEnterpriseStrictConfig = (options) => {
1699
2038
  return {
1700
2039
  maxTokenBudget: 4096,
1701
2040
  maxCostUSD: 0.15,
2041
+ middlewareTimeoutMs: 3e3,
2042
+ reScanIngressAfterHooks: true,
1702
2043
  ...options,
1703
2044
  retryOptions: { ...defaultRetries, ...options.retryOptions },
1704
2045
  security: { ...defaultSecurity, ...options.security }
@@ -1734,14 +2075,22 @@ export {
1734
2075
  SecretLeakBlockedError as AvantGateSecretLeakBlockedError,
1735
2076
  BudgetExceededError,
1736
2077
  CachedPricingAdapter,
2078
+ ChatMessageSchema,
2079
+ ChatMessagesArraySchema,
2080
+ ChatRoleSchema,
1737
2081
  ConfigurationError,
1738
2082
  DEFAULT_MODEL_PRICES,
1739
2083
  HttpProviderClient,
2084
+ InvalidMessageSchemaError,
2085
+ MiddlewareTimeoutError,
1740
2086
  PRESETS,
1741
2087
  PricingRegistry,
1742
2088
  PromptBuilder,
2089
+ PromptInjectionError,
1743
2090
  PromptRegistry,
1744
2091
  PromptTemplate,
2092
+ ProviderIdleTimeoutError,
2093
+ ProviderTTFTTimeoutError,
1745
2094
  SEED_MODEL_PRICES,
1746
2095
  SecretLeakBlockedError,
1747
2096
  SqlitePricingAdapter,
@@ -1759,6 +2108,7 @@ export {
1759
2108
  createHttpProviderClient,
1760
2109
  createLLMControlLayer,
1761
2110
  createLaunchSafeConfig,
2111
+ deepFreeze,
1762
2112
  defineWorkflow,
1763
2113
  executeAfterResponseHooks,
1764
2114
  executeBeforeRequestHooks,