@juspay/neurolink 9.82.0 → 9.84.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 (127) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/agent/agent.d.ts +104 -0
  3. package/dist/agent/agent.js +401 -0
  4. package/dist/agent/agentNetwork.d.ts +87 -0
  5. package/dist/agent/agentNetwork.js +458 -0
  6. package/dist/agent/communication/index.d.ts +9 -0
  7. package/dist/agent/communication/index.js +9 -0
  8. package/dist/agent/communication/message-bus.d.ts +113 -0
  9. package/dist/agent/communication/message-bus.js +406 -0
  10. package/dist/agent/coordination/coordinator.d.ts +83 -0
  11. package/dist/agent/coordination/coordinator.js +598 -0
  12. package/dist/agent/coordination/index.d.ts +10 -0
  13. package/dist/agent/coordination/index.js +10 -0
  14. package/dist/agent/coordination/task-distributor.d.ts +113 -0
  15. package/dist/agent/coordination/task-distributor.js +585 -0
  16. package/dist/agent/index.d.ts +38 -0
  17. package/dist/agent/index.js +44 -0
  18. package/dist/agent/orchestration/index.d.ts +10 -0
  19. package/dist/agent/orchestration/index.js +10 -0
  20. package/dist/agent/orchestration/orchestrator.d.ts +114 -0
  21. package/dist/agent/orchestration/orchestrator.js +476 -0
  22. package/dist/agent/orchestration/topology.d.ts +164 -0
  23. package/dist/agent/orchestration/topology.js +520 -0
  24. package/dist/agent/prompts/routingPrompts.d.ts +63 -0
  25. package/dist/agent/prompts/routingPrompts.js +201 -0
  26. package/dist/browser/neurolink.min.js +377 -358
  27. package/dist/cli/commands/agent.d.ts +37 -0
  28. package/dist/cli/commands/agent.js +718 -0
  29. package/dist/cli/factories/commandFactory.d.ts +8 -0
  30. package/dist/cli/factories/commandFactory.js +116 -0
  31. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  32. package/dist/cli/parser.js +4 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +19 -0
  35. package/dist/lib/agent/agent.d.ts +104 -0
  36. package/dist/lib/agent/agent.js +402 -0
  37. package/dist/lib/agent/agentNetwork.d.ts +87 -0
  38. package/dist/lib/agent/agentNetwork.js +459 -0
  39. package/dist/lib/agent/communication/index.d.ts +9 -0
  40. package/dist/lib/agent/communication/index.js +10 -0
  41. package/dist/lib/agent/communication/message-bus.d.ts +113 -0
  42. package/dist/lib/agent/communication/message-bus.js +407 -0
  43. package/dist/lib/agent/coordination/coordinator.d.ts +83 -0
  44. package/dist/lib/agent/coordination/coordinator.js +599 -0
  45. package/dist/lib/agent/coordination/index.d.ts +10 -0
  46. package/dist/lib/agent/coordination/index.js +11 -0
  47. package/dist/lib/agent/coordination/task-distributor.d.ts +113 -0
  48. package/dist/lib/agent/coordination/task-distributor.js +586 -0
  49. package/dist/lib/agent/index.d.ts +38 -0
  50. package/dist/lib/agent/index.js +45 -0
  51. package/dist/lib/agent/orchestration/index.d.ts +10 -0
  52. package/dist/lib/agent/orchestration/index.js +11 -0
  53. package/dist/lib/agent/orchestration/orchestrator.d.ts +114 -0
  54. package/dist/lib/agent/orchestration/orchestrator.js +477 -0
  55. package/dist/lib/agent/orchestration/topology.d.ts +164 -0
  56. package/dist/lib/agent/orchestration/topology.js +521 -0
  57. package/dist/lib/agent/prompts/routingPrompts.d.ts +63 -0
  58. package/dist/lib/agent/prompts/routingPrompts.js +202 -0
  59. package/dist/lib/index.d.ts +6 -0
  60. package/dist/lib/index.js +19 -0
  61. package/dist/lib/neurolink.d.ts +129 -0
  62. package/dist/lib/neurolink.js +276 -0
  63. package/dist/lib/processors/config/{fileTypes.js → fileExtensions.js} +1 -1
  64. package/dist/lib/processors/config/index.d.ts +2 -2
  65. package/dist/lib/processors/config/index.js +2 -2
  66. package/dist/lib/processors/config/{mimeTypes.js → mimeConstants.js} +1 -1
  67. package/dist/lib/processors/index.js +8 -0
  68. package/dist/lib/providers/googleAiStudio.js +28 -7
  69. package/dist/lib/providers/googleNativeGemini3.d.ts +18 -0
  70. package/dist/lib/providers/googleNativeGemini3.js +69 -3
  71. package/dist/lib/providers/googleVertex.js +33 -9
  72. package/dist/lib/types/agentNetwork.d.ts +1184 -0
  73. package/dist/lib/types/agentNetwork.js +8 -0
  74. package/dist/lib/types/cli.d.ts +66 -0
  75. package/dist/lib/types/common.d.ts +3 -0
  76. package/dist/lib/types/generate.d.ts +53 -0
  77. package/dist/lib/types/index.d.ts +2 -0
  78. package/dist/lib/types/index.js +3 -0
  79. package/dist/lib/types/ioProcessor.d.ts +119 -0
  80. package/dist/lib/types/ioProcessor.js +10 -0
  81. package/dist/lib/types/providers.d.ts +8 -0
  82. package/dist/lib/types/stream.d.ts +36 -0
  83. package/dist/lib/utils/piiDetector.d.ts +24 -0
  84. package/dist/lib/utils/piiDetector.js +221 -0
  85. package/dist/lib/utils/pricing.js +144 -26
  86. package/dist/lib/utils/responseValidator.d.ts +21 -0
  87. package/dist/lib/utils/responseValidator.js +354 -0
  88. package/dist/lib/utils/tokenUtils.d.ts +11 -0
  89. package/dist/lib/utils/tokenUtils.js +33 -2
  90. package/dist/lib/utils/tripwireEvaluator.d.ts +73 -0
  91. package/dist/lib/utils/tripwireEvaluator.js +285 -0
  92. package/dist/neurolink.d.ts +129 -0
  93. package/dist/neurolink.js +276 -0
  94. package/dist/processors/config/index.d.ts +2 -2
  95. package/dist/processors/config/index.js +2 -2
  96. package/dist/processors/index.js +8 -0
  97. package/dist/providers/googleAiStudio.js +28 -7
  98. package/dist/providers/googleNativeGemini3.d.ts +18 -0
  99. package/dist/providers/googleNativeGemini3.js +69 -3
  100. package/dist/providers/googleVertex.js +33 -9
  101. package/dist/types/agentNetwork.d.ts +1184 -0
  102. package/dist/types/agentNetwork.js +7 -0
  103. package/dist/types/cli.d.ts +66 -0
  104. package/dist/types/common.d.ts +3 -0
  105. package/dist/types/generate.d.ts +53 -0
  106. package/dist/types/index.d.ts +2 -0
  107. package/dist/types/index.js +3 -0
  108. package/dist/types/ioProcessor.d.ts +119 -0
  109. package/dist/types/ioProcessor.js +9 -0
  110. package/dist/types/providers.d.ts +8 -0
  111. package/dist/types/stream.d.ts +36 -0
  112. package/dist/utils/piiDetector.d.ts +24 -0
  113. package/dist/utils/piiDetector.js +220 -0
  114. package/dist/utils/pricing.js +144 -26
  115. package/dist/utils/responseValidator.d.ts +21 -0
  116. package/dist/utils/responseValidator.js +353 -0
  117. package/dist/utils/tokenUtils.d.ts +11 -0
  118. package/dist/utils/tokenUtils.js +33 -2
  119. package/dist/utils/tripwireEvaluator.d.ts +73 -0
  120. package/dist/utils/tripwireEvaluator.js +284 -0
  121. package/package.json +1 -1
  122. /package/dist/lib/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
  123. /package/dist/lib/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
  124. /package/dist/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
  125. /package/dist/processors/config/{fileTypes.js → fileExtensions.js} +0 -0
  126. /package/dist/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
  127. /package/dist/processors/config/{mimeTypes.js → mimeConstants.js} +0 -0
@@ -87,8 +87,18 @@ const PRICING = {
87
87
  cacheRead: 1.5 / 1_000_000,
88
88
  cacheCreation: 18.75 / 1_000_000,
89
89
  },
90
- "claude-3-sonnet": { input: 3.0 / 1_000_000, output: 15.0 / 1_000_000 },
91
- "claude-3-haiku": { input: 0.25 / 1_000_000, output: 1.25 / 1_000_000 },
90
+ "claude-3-sonnet": {
91
+ input: 3.0 / 1_000_000,
92
+ output: 15.0 / 1_000_000,
93
+ cacheRead: 0.3 / 1_000_000,
94
+ cacheCreation: 3.75 / 1_000_000,
95
+ },
96
+ "claude-3-haiku": {
97
+ input: 0.25 / 1_000_000,
98
+ output: 1.25 / 1_000_000,
99
+ cacheRead: 0.025 / 1_000_000,
100
+ cacheCreation: 0.3125 / 1_000_000,
101
+ },
92
102
  },
93
103
  // Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
94
104
  vertex: {
@@ -138,26 +148,95 @@ const PRICING = {
138
148
  // OpenAI — updated March 2026
139
149
  openai: {
140
150
  // GPT-5.x family
141
- "gpt-5.4": { input: 2.5 / 1_000_000, output: 15.0 / 1_000_000 },
142
- "gpt-5.2": { input: 1.75 / 1_000_000, output: 14.0 / 1_000_000 },
143
- "gpt-5.1": { input: 0.625 / 1_000_000, output: 5.0 / 1_000_000 },
144
- "gpt-5.1-codex": { input: 1.25 / 1_000_000, output: 10.0 / 1_000_000 },
145
- "gpt-5": { input: 1.25 / 1_000_000, output: 10.0 / 1_000_000 },
146
- "gpt-5-mini": { input: 0.25 / 1_000_000, output: 2.0 / 1_000_000 },
147
- "gpt-5-nano": { input: 0.05 / 1_000_000, output: 0.4 / 1_000_000 },
151
+ // cacheRead = 0.25x input (cached input tokens; no separate cacheCreation).
152
+ "gpt-5.4": {
153
+ input: 2.5 / 1_000_000,
154
+ output: 15.0 / 1_000_000,
155
+ cacheRead: 0.625 / 1_000_000,
156
+ },
157
+ "gpt-5.2": {
158
+ input: 1.75 / 1_000_000,
159
+ output: 14.0 / 1_000_000,
160
+ cacheRead: 0.4375 / 1_000_000,
161
+ },
162
+ "gpt-5.1": {
163
+ input: 0.625 / 1_000_000,
164
+ output: 5.0 / 1_000_000,
165
+ cacheRead: 0.15625 / 1_000_000,
166
+ },
167
+ "gpt-5.1-codex": {
168
+ input: 1.25 / 1_000_000,
169
+ output: 10.0 / 1_000_000,
170
+ cacheRead: 0.3125 / 1_000_000,
171
+ },
172
+ "gpt-5": {
173
+ input: 1.25 / 1_000_000,
174
+ output: 10.0 / 1_000_000,
175
+ cacheRead: 0.3125 / 1_000_000,
176
+ },
177
+ "gpt-5-mini": {
178
+ input: 0.25 / 1_000_000,
179
+ output: 2.0 / 1_000_000,
180
+ cacheRead: 0.0625 / 1_000_000,
181
+ },
182
+ "gpt-5-nano": {
183
+ input: 0.05 / 1_000_000,
184
+ output: 0.4 / 1_000_000,
185
+ cacheRead: 0.0125 / 1_000_000,
186
+ },
148
187
  // GPT-4.1 family
149
- "gpt-4.1": { input: 2.0 / 1_000_000, output: 8.0 / 1_000_000 },
150
- "gpt-4.1-mini": { input: 0.4 / 1_000_000, output: 1.6 / 1_000_000 },
151
- "gpt-4.1-nano": { input: 0.1 / 1_000_000, output: 0.4 / 1_000_000 },
188
+ "gpt-4.1": {
189
+ input: 2.0 / 1_000_000,
190
+ output: 8.0 / 1_000_000,
191
+ cacheRead: 0.5 / 1_000_000,
192
+ },
193
+ "gpt-4.1-mini": {
194
+ input: 0.4 / 1_000_000,
195
+ output: 1.6 / 1_000_000,
196
+ cacheRead: 0.1 / 1_000_000,
197
+ },
198
+ "gpt-4.1-nano": {
199
+ input: 0.1 / 1_000_000,
200
+ output: 0.4 / 1_000_000,
201
+ cacheRead: 0.025 / 1_000_000,
202
+ },
152
203
  // GPT-4o family
153
- "gpt-4o": { input: 2.5 / 1_000_000, output: 10.0 / 1_000_000 },
154
- "gpt-4o-mini": { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
204
+ "gpt-4o": {
205
+ input: 2.5 / 1_000_000,
206
+ output: 10.0 / 1_000_000,
207
+ cacheRead: 0.625 / 1_000_000,
208
+ },
209
+ "gpt-4o-mini": {
210
+ input: 0.15 / 1_000_000,
211
+ output: 0.6 / 1_000_000,
212
+ cacheRead: 0.0375 / 1_000_000,
213
+ },
155
214
  // o-series reasoning
156
- o3: { input: 2.0 / 1_000_000, output: 8.0 / 1_000_000 },
157
- "o3-mini": { input: 1.1 / 1_000_000, output: 4.4 / 1_000_000 },
158
- "o4-mini": { input: 1.1 / 1_000_000, output: 4.4 / 1_000_000 },
159
- o1: { input: 15.0 / 1_000_000, output: 60.0 / 1_000_000 },
160
- "o1-mini": { input: 0.55 / 1_000_000, output: 2.2 / 1_000_000 },
215
+ o3: {
216
+ input: 2.0 / 1_000_000,
217
+ output: 8.0 / 1_000_000,
218
+ cacheRead: 0.5 / 1_000_000,
219
+ },
220
+ "o3-mini": {
221
+ input: 1.1 / 1_000_000,
222
+ output: 4.4 / 1_000_000,
223
+ cacheRead: 0.275 / 1_000_000,
224
+ },
225
+ "o4-mini": {
226
+ input: 1.1 / 1_000_000,
227
+ output: 4.4 / 1_000_000,
228
+ cacheRead: 0.275 / 1_000_000,
229
+ },
230
+ o1: {
231
+ input: 15.0 / 1_000_000,
232
+ output: 60.0 / 1_000_000,
233
+ cacheRead: 3.75 / 1_000_000,
234
+ },
235
+ "o1-mini": {
236
+ input: 0.55 / 1_000_000,
237
+ output: 2.2 / 1_000_000,
238
+ cacheRead: 0.1375 / 1_000_000,
239
+ },
161
240
  // Legacy
162
241
  "gpt-4-turbo": { input: 10.0 / 1_000_000, output: 30.0 / 1_000_000 },
163
242
  "gpt-4": { input: 30.0 / 1_000_000, output: 60.0 / 1_000_000 },
@@ -166,52 +245,83 @@ const PRICING = {
166
245
  // Google (Gemini) — updated March 2026
167
246
  google: {
168
247
  // Gemini 3.1 family (all require -preview suffix)
248
+ // cacheRead = 0.25x input (cached content tokens; explicit-cache storage is
249
+ // billed separately by TTL/time, so no per-token cacheCreation rate here).
169
250
  "gemini-3.1-pro-preview": {
170
251
  input: 2.0 / 1_000_000,
171
252
  output: 12.0 / 1_000_000,
253
+ cacheRead: 0.5 / 1_000_000,
172
254
  },
173
255
  "gemini-3.1-flash-lite-preview": {
174
256
  input: 0.25 / 1_000_000,
175
257
  output: 1.5 / 1_000_000,
258
+ cacheRead: 0.0625 / 1_000_000,
176
259
  },
177
260
  "gemini-3.1-flash-image-preview": {
178
261
  input: 0.5 / 1_000_000,
179
262
  output: 3.0 / 1_000_000,
263
+ cacheRead: 0.125 / 1_000_000,
180
264
  },
181
265
  "gemini-3.1-pro-preview-customtools": {
182
266
  input: 2.0 / 1_000_000,
183
267
  output: 12.0 / 1_000_000,
268
+ cacheRead: 0.5 / 1_000_000,
184
269
  },
185
270
  // Gemini 3 family
186
271
  "gemini-3-flash-preview": {
187
272
  input: 0.5 / 1_000_000,
188
273
  output: 3.0 / 1_000_000,
274
+ cacheRead: 0.125 / 1_000_000,
189
275
  },
190
276
  "gemini-3-pro-image-preview": {
191
277
  input: 2.0 / 1_000_000,
192
278
  output: 12.0 / 1_000_000,
279
+ cacheRead: 0.5 / 1_000_000,
193
280
  },
194
281
  /** @deprecated SHUT DOWN March 9, 2026. Migrate to gemini-3.1-pro-preview. */
195
282
  "gemini-3-pro-preview": {
196
283
  input: 2.0 / 1_000_000,
197
284
  output: 12.0 / 1_000_000,
285
+ cacheRead: 0.5 / 1_000_000,
198
286
  },
199
287
  // Gemini 2.5 family
200
- "gemini-2.5-flash": { input: 0.3 / 1_000_000, output: 2.5 / 1_000_000 },
201
- "gemini-2.5-pro": { input: 1.25 / 1_000_000, output: 10.0 / 1_000_000 },
288
+ "gemini-2.5-flash": {
289
+ input: 0.3 / 1_000_000,
290
+ output: 2.5 / 1_000_000,
291
+ cacheRead: 0.075 / 1_000_000,
292
+ },
293
+ "gemini-2.5-pro": {
294
+ input: 1.25 / 1_000_000,
295
+ output: 10.0 / 1_000_000,
296
+ cacheRead: 0.3125 / 1_000_000,
297
+ },
202
298
  "gemini-2.5-flash-lite": {
203
299
  input: 0.1 / 1_000_000,
204
300
  output: 0.4 / 1_000_000,
301
+ cacheRead: 0.025 / 1_000_000,
205
302
  },
206
303
  // Gemini 2.0 family (deprecated June 2026)
207
- "gemini-2.0-flash": { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
304
+ "gemini-2.0-flash": {
305
+ input: 0.15 / 1_000_000,
306
+ output: 0.6 / 1_000_000,
307
+ cacheRead: 0.0375 / 1_000_000,
308
+ },
208
309
  "gemini-2.0-flash-lite": {
209
310
  input: 0.075 / 1_000_000,
210
311
  output: 0.3 / 1_000_000,
312
+ cacheRead: 0.01875 / 1_000_000,
211
313
  },
212
314
  // Gemini 1.5 family
213
- "gemini-1.5-pro": { input: 1.25 / 1_000_000, output: 5.0 / 1_000_000 },
214
- "gemini-1.5-flash": { input: 0.075 / 1_000_000, output: 0.3 / 1_000_000 },
315
+ "gemini-1.5-pro": {
316
+ input: 1.25 / 1_000_000,
317
+ output: 5.0 / 1_000_000,
318
+ cacheRead: 0.3125 / 1_000_000,
319
+ },
320
+ "gemini-1.5-flash": {
321
+ input: 0.075 / 1_000_000,
322
+ output: 0.3 / 1_000_000,
323
+ cacheRead: 0.01875 / 1_000_000,
324
+ },
215
325
  },
216
326
  // Mistral AI
217
327
  mistral: {
@@ -594,8 +704,16 @@ export function calculateCost(provider, model, usage) {
594
704
  let cost = 0;
595
705
  cost += (usage.input || 0) * rates.input;
596
706
  cost += (usage.output || 0) * rates.output;
597
- if (usage.cacheReadTokens && rates.cacheRead) {
598
- cost += usage.cacheReadTokens * rates.cacheRead;
707
+ if (usage.cacheReadTokens) {
708
+ // Price cache reads at the discounted cacheRead rate when one exists;
709
+ // otherwise fall back to the full input rate. The overlapping extractor
710
+ // (tokenUtils) moves cached tokens out of `input` into `cacheReadTokens`
711
+ // for OpenAI/Gemini-family providers, so without this fallback a provider
712
+ // that reports cached tokens but has no cacheRead rate would bill those
713
+ // tokens at $0 (silent undercharge). Falling back to the input rate keeps
714
+ // total cost identical to pre-split billing for unpriced providers, while
715
+ // priced providers still get the cheaper cacheRead rate.
716
+ cost += usage.cacheReadTokens * (rates.cacheRead ?? rates.input);
599
717
  }
600
718
  if (usage.cacheCreationTokens && rates.cacheCreation) {
601
719
  cost += usage.cacheCreationTokens * rates.cacheCreation;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Response Validator
3
+ *
4
+ * Standalone utility for validating and optionally mutating LLM response text.
5
+ * Handles length checks (including truncation), phrase checks, JSON schema
6
+ * validation, and custom validators. Returns a structured result telling the
7
+ * caller whether to continue, abort, or retry — the caller owns the retry loop.
8
+ *
9
+ * No async I/O, no telemetry spans, no processor classes.
10
+ */
11
+ import type { ResponseValidationConfig, ResponseValidationResult } from "../types/index.js";
12
+ /**
13
+ * Validate (and optionally mutate) an LLM response.
14
+ *
15
+ * @param responseText - The raw text returned by the model.
16
+ * @param config - Validation rules and action preferences.
17
+ * @param retryCount - Current retry iteration (passed through to result).
18
+ * @returns A {@link ResponseValidationResult} with the (possibly truncated)
19
+ * text, a disposition action, all issues found, and optional feedback.
20
+ */
21
+ export declare function validateResponse(responseText: string, config: ResponseValidationConfig, retryCount?: number): ResponseValidationResult;
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Response Validator
3
+ *
4
+ * Standalone utility for validating and optionally mutating LLM response text.
5
+ * Handles length checks (including truncation), phrase checks, JSON schema
6
+ * validation, and custom validators. Returns a structured result telling the
7
+ * caller whether to continue, abort, or retry — the caller owns the retry loop.
8
+ *
9
+ * No async I/O, no telemetry spans, no processor classes.
10
+ */
11
+ import { logger } from "./logger.js";
12
+ // =============================================================================
13
+ // JSON SCHEMA VALIDATION (internal)
14
+ // =============================================================================
15
+ /**
16
+ * Check that `data` matches the given JSON Schema node.
17
+ * Supports: type, properties, required, items, minimum, maximum,
18
+ * minLength, maxLength, pattern, enum.
19
+ *
20
+ * Bug C5 fix: `typeof null === "object"` — null is never a valid object.
21
+ */
22
+ function matchesJsonSchemaNode(data, schema, path) {
23
+ const issues = [];
24
+ const expectedType = schema["type"];
25
+ if (expectedType !== undefined) {
26
+ const valid = (() => {
27
+ switch (expectedType) {
28
+ case "string":
29
+ return typeof data === "string";
30
+ case "number":
31
+ return typeof data === "number";
32
+ case "integer":
33
+ return typeof data === "number" && Number.isInteger(data);
34
+ case "boolean":
35
+ return typeof data === "boolean";
36
+ case "null":
37
+ return data === null;
38
+ case "array":
39
+ return Array.isArray(data);
40
+ case "object":
41
+ // Bug C5 fix: null must be explicitly excluded
42
+ return (data !== null && typeof data === "object" && !Array.isArray(data));
43
+ default:
44
+ return true;
45
+ }
46
+ })();
47
+ if (!valid) {
48
+ issues.push({
49
+ category: "json_schema",
50
+ severity: "error",
51
+ message: `Expected type "${expectedType}" at "${path}", got ${data === null ? "null" : Array.isArray(data) ? "array" : typeof data}`,
52
+ field: path,
53
+ });
54
+ // Cannot validate sub-constraints if the type is wrong
55
+ return issues;
56
+ }
57
+ }
58
+ // --- string constraints ---
59
+ if (typeof data === "string") {
60
+ const minLen = schema["minLength"];
61
+ const maxLen = schema["maxLength"];
62
+ const pattern = schema["pattern"];
63
+ if (minLen !== undefined && data.length < minLen) {
64
+ issues.push({
65
+ category: "json_schema",
66
+ severity: "error",
67
+ message: `"${path}" length ${data.length} is below minLength ${minLen}`,
68
+ field: path,
69
+ });
70
+ }
71
+ if (maxLen !== undefined && data.length > maxLen) {
72
+ issues.push({
73
+ category: "json_schema",
74
+ severity: "error",
75
+ message: `"${path}" length ${data.length} exceeds maxLength ${maxLen}`,
76
+ field: path,
77
+ });
78
+ }
79
+ if (pattern !== undefined) {
80
+ // JSON schema "pattern" validation is skipped at runtime to avoid
81
+ // dynamic RegExp construction from schema data (CodeQL js/regex-injection).
82
+ // Patterns are validated at schema authoring time, not at response time.
83
+ issues.push({
84
+ category: "json_schema",
85
+ severity: "info",
86
+ message: `"${path}" has a pattern constraint "${pattern}" (not enforced at runtime)`,
87
+ field: path,
88
+ });
89
+ }
90
+ }
91
+ // --- number constraints ---
92
+ if (typeof data === "number") {
93
+ const minimum = schema["minimum"];
94
+ const maximum = schema["maximum"];
95
+ if (minimum !== undefined && data < minimum) {
96
+ issues.push({
97
+ category: "json_schema",
98
+ severity: "error",
99
+ message: `"${path}" value ${data} is below minimum ${minimum}`,
100
+ field: path,
101
+ });
102
+ }
103
+ if (maximum !== undefined && data > maximum) {
104
+ issues.push({
105
+ category: "json_schema",
106
+ severity: "error",
107
+ message: `"${path}" value ${data} exceeds maximum ${maximum}`,
108
+ field: path,
109
+ });
110
+ }
111
+ }
112
+ // --- enum ---
113
+ const enumValues = schema["enum"];
114
+ if (enumValues !== undefined && !enumValues.includes(data)) {
115
+ issues.push({
116
+ category: "json_schema",
117
+ severity: "error",
118
+ message: `"${path}" must be one of [${enumValues.map(String).join(", ")}], got ${String(data)}`,
119
+ field: path,
120
+ });
121
+ }
122
+ // --- object: properties + required ---
123
+ if (data !== null && typeof data === "object" && !Array.isArray(data)) {
124
+ const obj = data;
125
+ const properties = schema["properties"];
126
+ const required = schema["required"];
127
+ if (required) {
128
+ for (const key of required) {
129
+ if (!(key in obj)) {
130
+ issues.push({
131
+ category: "json_schema",
132
+ severity: "error",
133
+ message: `Required property "${key}" is missing at "${path}"`,
134
+ field: `${path}.${key}`,
135
+ });
136
+ }
137
+ }
138
+ }
139
+ if (properties) {
140
+ for (const [key, propSchema] of Object.entries(properties)) {
141
+ if (key in obj) {
142
+ issues.push(...matchesJsonSchemaNode(obj[key], propSchema, path ? `${path}.${key}` : key));
143
+ }
144
+ }
145
+ }
146
+ }
147
+ // --- array: items ---
148
+ if (Array.isArray(data)) {
149
+ const itemSchema = schema["items"];
150
+ if (itemSchema) {
151
+ data.forEach((item, idx) => {
152
+ issues.push(...matchesJsonSchemaNode(item, itemSchema, `${path}[${idx}]`));
153
+ });
154
+ }
155
+ }
156
+ return issues;
157
+ }
158
+ function validateJsonSchema(text, schema) {
159
+ let parsed;
160
+ try {
161
+ parsed = JSON.parse(text);
162
+ }
163
+ catch {
164
+ return [
165
+ {
166
+ category: "json_schema",
167
+ severity: "error",
168
+ message: "Response is not valid JSON",
169
+ field: "$",
170
+ },
171
+ ];
172
+ }
173
+ return matchesJsonSchemaNode(parsed, schema, "$");
174
+ }
175
+ // =============================================================================
176
+ // HELPERS
177
+ // =============================================================================
178
+ function buildFeedback(issues) {
179
+ const errors = issues.filter((i) => i.severity === "error");
180
+ if (errors.length === 0) {
181
+ return "Validation warnings were raised. Please review your response.";
182
+ }
183
+ return ("The response failed validation:\n" +
184
+ errors.map((e) => ` - [${e.category}] ${e.message}`).join("\n"));
185
+ }
186
+ // =============================================================================
187
+ // MAIN EXPORT
188
+ // =============================================================================
189
+ /**
190
+ * Validate (and optionally mutate) an LLM response.
191
+ *
192
+ * @param responseText - The raw text returned by the model.
193
+ * @param config - Validation rules and action preferences.
194
+ * @param retryCount - Current retry iteration (passed through to result).
195
+ * @returns A {@link ResponseValidationResult} with the (possibly truncated)
196
+ * text, a disposition action, all issues found, and optional feedback.
197
+ */
198
+ export function validateResponse(responseText, config, retryCount) {
199
+ const issues = [];
200
+ let text = responseText;
201
+ // -------------------------------------------------------------------------
202
+ // 1. Minimum length
203
+ // -------------------------------------------------------------------------
204
+ if (config.minLength !== undefined && text.length < config.minLength) {
205
+ issues.push({
206
+ category: "length",
207
+ severity: "error",
208
+ message: `Response length ${text.length} is below required minimum of ${config.minLength}`,
209
+ });
210
+ }
211
+ // -------------------------------------------------------------------------
212
+ // 2. Maximum length — applies truncation or records the issue
213
+ // -------------------------------------------------------------------------
214
+ if (config.maxLength !== undefined && text.length > config.maxLength) {
215
+ const action = config.truncationAction ?? "truncate";
216
+ switch (action) {
217
+ case "truncate": {
218
+ // Bug C6 fix: ensure keepLength cannot go negative
219
+ const rawSuffix = config.truncationSuffix ?? "...";
220
+ const suffix = rawSuffix.slice(0, config.maxLength);
221
+ const keepLength = Math.max(config.maxLength - suffix.length, 0);
222
+ text = text.slice(0, keepLength) + suffix;
223
+ issues.push({
224
+ category: "length",
225
+ severity: "info",
226
+ message: `Response was truncated from ${responseText.length} to ${config.maxLength} characters`,
227
+ });
228
+ if (logger.shouldLog("debug")) {
229
+ logger.debug(`[responseValidator] truncated response to ${config.maxLength} chars`);
230
+ }
231
+ break;
232
+ }
233
+ case "abort": {
234
+ issues.push({
235
+ category: "length",
236
+ severity: "error",
237
+ message: `Response length ${text.length} exceeds maximum of ${config.maxLength}; aborting`,
238
+ });
239
+ return {
240
+ text,
241
+ action: "abort",
242
+ issues,
243
+ feedback: `Response exceeded maximum length of ${config.maxLength} characters.`,
244
+ retryCount,
245
+ };
246
+ }
247
+ case "retry": {
248
+ issues.push({
249
+ category: "length",
250
+ severity: "error",
251
+ message: `Response length ${text.length} exceeds maximum of ${config.maxLength}; requesting retry`,
252
+ });
253
+ return {
254
+ text,
255
+ action: "retry",
256
+ issues,
257
+ feedback: "Response exceeded max length, please shorten",
258
+ retryCount,
259
+ };
260
+ }
261
+ case "warn":
262
+ default: {
263
+ issues.push({
264
+ category: "length",
265
+ severity: "warning",
266
+ message: `Response length ${text.length} exceeds maximum of ${config.maxLength} (warn-only)`,
267
+ });
268
+ logger.warn(`[responseValidator] response length ${text.length} exceeds maxLength ${config.maxLength}`);
269
+ break;
270
+ }
271
+ }
272
+ }
273
+ // -------------------------------------------------------------------------
274
+ // 3. Required phrases
275
+ // -------------------------------------------------------------------------
276
+ if (config.requiredPhrases && config.requiredPhrases.length > 0) {
277
+ const lowerText = text.toLowerCase();
278
+ for (const phrase of config.requiredPhrases) {
279
+ if (!lowerText.includes(phrase.toLowerCase())) {
280
+ issues.push({
281
+ category: "phrase",
282
+ severity: "error",
283
+ message: `Required phrase "${phrase}" was not found in the response`,
284
+ });
285
+ }
286
+ }
287
+ }
288
+ // -------------------------------------------------------------------------
289
+ // 4. Forbidden phrases
290
+ // -------------------------------------------------------------------------
291
+ if (config.forbiddenPhrases && config.forbiddenPhrases.length > 0) {
292
+ const lowerText = text.toLowerCase();
293
+ for (const phrase of config.forbiddenPhrases) {
294
+ if (lowerText.includes(phrase.toLowerCase())) {
295
+ issues.push({
296
+ category: "phrase",
297
+ severity: "error",
298
+ message: `Forbidden phrase "${phrase}" was found in the response`,
299
+ });
300
+ }
301
+ }
302
+ }
303
+ // -------------------------------------------------------------------------
304
+ // 5. JSON schema
305
+ // -------------------------------------------------------------------------
306
+ if (config.jsonSchema) {
307
+ const schemaIssues = validateJsonSchema(text, config.jsonSchema);
308
+ issues.push(...schemaIssues);
309
+ }
310
+ // -------------------------------------------------------------------------
311
+ // 6. Custom validator
312
+ // -------------------------------------------------------------------------
313
+ if (config.customValidator) {
314
+ const customIssue = config.customValidator(text);
315
+ if (customIssue !== null) {
316
+ issues.push(customIssue);
317
+ }
318
+ }
319
+ // -------------------------------------------------------------------------
320
+ // 7. Determine final action
321
+ // -------------------------------------------------------------------------
322
+ const hasErrors = issues.some((i) => i.severity === "error");
323
+ if (hasErrors) {
324
+ if (logger.shouldLog("debug")) {
325
+ logger.debug("[responseValidator] validation failed", {
326
+ issueCount: issues.length,
327
+ retryOnFailure: config.retryOnFailure,
328
+ });
329
+ }
330
+ if (config.retryOnFailure) {
331
+ return {
332
+ text,
333
+ action: "retry",
334
+ issues,
335
+ feedback: buildFeedback(issues),
336
+ retryCount,
337
+ };
338
+ }
339
+ return {
340
+ text,
341
+ action: "abort",
342
+ issues,
343
+ feedback: buildFeedback(issues),
344
+ retryCount,
345
+ };
346
+ }
347
+ return {
348
+ text,
349
+ action: "continue",
350
+ issues,
351
+ retryCount,
352
+ };
353
+ }
354
+ //# sourceMappingURL=responseValidator.js.map
@@ -38,6 +38,17 @@ export declare function extractCacheCreationTokens(usage: RawUsageObject): numbe
38
38
  * Supports: cacheReadInputTokens, cacheReadTokens
39
39
  */
40
40
  export declare function extractCacheReadTokens(usage: RawUsageObject): number | undefined;
41
+ /**
42
+ * Extract cache read token count from the OVERLAPPING-convention nested path
43
+ * used by OpenAI / DeepSeek / NIM / OpenAI-compatible providers:
44
+ * `usage.prompt_tokens_details.cached_tokens`.
45
+ *
46
+ * Unlike {@link extractCacheReadTokens} (non-overlapping Anthropic/Vertex
47
+ * convention where cache tokens are reported SEPARATELY from input), the value
48
+ * returned here is a SUBSET already included in `prompt_tokens`. Callers that
49
+ * use this value MUST subtract it from `input` to avoid double-counting.
50
+ */
51
+ export declare function extractCachedInputTokensOverlapping(usage: RawUsageObject): number | undefined;
41
52
  /**
42
53
  * Calculate cache savings percentage
43
54
  *