@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.
- package/CHANGELOG.md +18 -0
- package/dist/agent/agent.d.ts +104 -0
- package/dist/agent/agent.js +401 -0
- package/dist/agent/agentNetwork.d.ts +87 -0
- package/dist/agent/agentNetwork.js +458 -0
- package/dist/agent/communication/index.d.ts +9 -0
- package/dist/agent/communication/index.js +9 -0
- package/dist/agent/communication/message-bus.d.ts +113 -0
- package/dist/agent/communication/message-bus.js +406 -0
- package/dist/agent/coordination/coordinator.d.ts +83 -0
- package/dist/agent/coordination/coordinator.js +598 -0
- package/dist/agent/coordination/index.d.ts +10 -0
- package/dist/agent/coordination/index.js +10 -0
- package/dist/agent/coordination/task-distributor.d.ts +113 -0
- package/dist/agent/coordination/task-distributor.js +585 -0
- package/dist/agent/index.d.ts +38 -0
- package/dist/agent/index.js +44 -0
- package/dist/agent/orchestration/index.d.ts +10 -0
- package/dist/agent/orchestration/index.js +10 -0
- package/dist/agent/orchestration/orchestrator.d.ts +114 -0
- package/dist/agent/orchestration/orchestrator.js +476 -0
- package/dist/agent/orchestration/topology.d.ts +164 -0
- package/dist/agent/orchestration/topology.js +520 -0
- package/dist/agent/prompts/routingPrompts.d.ts +63 -0
- package/dist/agent/prompts/routingPrompts.js +201 -0
- package/dist/browser/neurolink.min.js +377 -358
- package/dist/cli/commands/agent.d.ts +37 -0
- package/dist/cli/commands/agent.js +718 -0
- package/dist/cli/factories/commandFactory.d.ts +8 -0
- package/dist/cli/factories/commandFactory.js +116 -0
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -0
- package/dist/lib/agent/agent.d.ts +104 -0
- package/dist/lib/agent/agent.js +402 -0
- package/dist/lib/agent/agentNetwork.d.ts +87 -0
- package/dist/lib/agent/agentNetwork.js +459 -0
- package/dist/lib/agent/communication/index.d.ts +9 -0
- package/dist/lib/agent/communication/index.js +10 -0
- package/dist/lib/agent/communication/message-bus.d.ts +113 -0
- package/dist/lib/agent/communication/message-bus.js +407 -0
- package/dist/lib/agent/coordination/coordinator.d.ts +83 -0
- package/dist/lib/agent/coordination/coordinator.js +599 -0
- package/dist/lib/agent/coordination/index.d.ts +10 -0
- package/dist/lib/agent/coordination/index.js +11 -0
- package/dist/lib/agent/coordination/task-distributor.d.ts +113 -0
- package/dist/lib/agent/coordination/task-distributor.js +586 -0
- package/dist/lib/agent/index.d.ts +38 -0
- package/dist/lib/agent/index.js +45 -0
- package/dist/lib/agent/orchestration/index.d.ts +10 -0
- package/dist/lib/agent/orchestration/index.js +11 -0
- package/dist/lib/agent/orchestration/orchestrator.d.ts +114 -0
- package/dist/lib/agent/orchestration/orchestrator.js +477 -0
- package/dist/lib/agent/orchestration/topology.d.ts +164 -0
- package/dist/lib/agent/orchestration/topology.js +521 -0
- package/dist/lib/agent/prompts/routingPrompts.d.ts +63 -0
- package/dist/lib/agent/prompts/routingPrompts.js +202 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +19 -0
- package/dist/lib/neurolink.d.ts +129 -0
- package/dist/lib/neurolink.js +276 -0
- package/dist/lib/processors/config/{fileTypes.js → fileExtensions.js} +1 -1
- package/dist/lib/processors/config/index.d.ts +2 -2
- package/dist/lib/processors/config/index.js +2 -2
- package/dist/lib/processors/config/{mimeTypes.js → mimeConstants.js} +1 -1
- package/dist/lib/processors/index.js +8 -0
- package/dist/lib/providers/googleAiStudio.js +28 -7
- package/dist/lib/providers/googleNativeGemini3.d.ts +18 -0
- package/dist/lib/providers/googleNativeGemini3.js +69 -3
- package/dist/lib/providers/googleVertex.js +33 -9
- package/dist/lib/types/agentNetwork.d.ts +1184 -0
- package/dist/lib/types/agentNetwork.js +8 -0
- package/dist/lib/types/cli.d.ts +66 -0
- package/dist/lib/types/common.d.ts +3 -0
- package/dist/lib/types/generate.d.ts +53 -0
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.js +3 -0
- package/dist/lib/types/ioProcessor.d.ts +119 -0
- package/dist/lib/types/ioProcessor.js +10 -0
- package/dist/lib/types/providers.d.ts +8 -0
- package/dist/lib/types/stream.d.ts +36 -0
- package/dist/lib/utils/piiDetector.d.ts +24 -0
- package/dist/lib/utils/piiDetector.js +221 -0
- package/dist/lib/utils/pricing.js +144 -26
- package/dist/lib/utils/responseValidator.d.ts +21 -0
- package/dist/lib/utils/responseValidator.js +354 -0
- package/dist/lib/utils/tokenUtils.d.ts +11 -0
- package/dist/lib/utils/tokenUtils.js +33 -2
- package/dist/lib/utils/tripwireEvaluator.d.ts +73 -0
- package/dist/lib/utils/tripwireEvaluator.js +285 -0
- package/dist/neurolink.d.ts +129 -0
- package/dist/neurolink.js +276 -0
- package/dist/processors/config/index.d.ts +2 -2
- package/dist/processors/config/index.js +2 -2
- package/dist/processors/index.js +8 -0
- package/dist/providers/googleAiStudio.js +28 -7
- package/dist/providers/googleNativeGemini3.d.ts +18 -0
- package/dist/providers/googleNativeGemini3.js +69 -3
- package/dist/providers/googleVertex.js +33 -9
- package/dist/types/agentNetwork.d.ts +1184 -0
- package/dist/types/agentNetwork.js +7 -0
- package/dist/types/cli.d.ts +66 -0
- package/dist/types/common.d.ts +3 -0
- package/dist/types/generate.d.ts +53 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/ioProcessor.d.ts +119 -0
- package/dist/types/ioProcessor.js +9 -0
- package/dist/types/providers.d.ts +8 -0
- package/dist/types/stream.d.ts +36 -0
- package/dist/utils/piiDetector.d.ts +24 -0
- package/dist/utils/piiDetector.js +220 -0
- package/dist/utils/pricing.js +144 -26
- package/dist/utils/responseValidator.d.ts +21 -0
- package/dist/utils/responseValidator.js +353 -0
- package/dist/utils/tokenUtils.d.ts +11 -0
- package/dist/utils/tokenUtils.js +33 -2
- package/dist/utils/tripwireEvaluator.d.ts +73 -0
- package/dist/utils/tripwireEvaluator.js +284 -0
- package/package.json +1 -1
- /package/dist/lib/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
- /package/dist/lib/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
- /package/dist/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
- /package/dist/processors/config/{fileTypes.js → fileExtensions.js} +0 -0
- /package/dist/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
- /package/dist/processors/config/{mimeTypes.js → mimeConstants.js} +0 -0
package/dist/utils/pricing.js
CHANGED
|
@@ -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": {
|
|
91
|
-
|
|
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
|
-
|
|
142
|
-
"gpt-5.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"gpt-5
|
|
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": {
|
|
150
|
-
|
|
151
|
-
|
|
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": {
|
|
154
|
-
|
|
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: {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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": {
|
|
201
|
-
|
|
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": {
|
|
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": {
|
|
214
|
-
|
|
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
|
|
598
|
-
|
|
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,353 @@
|
|
|
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
|
+
}
|
|
@@ -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
|
*
|