@juspay/neurolink 7.7.1 → 7.8.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 +12 -0
- package/README.md +33 -2
- package/dist/cli/commands/config.d.ts +3 -3
- package/dist/cli/commands/sagemaker.d.ts +11 -0
- package/dist/cli/commands/sagemaker.js +778 -0
- package/dist/cli/factories/commandFactory.js +1 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/utils/interactiveSetup.js +28 -0
- package/dist/core/baseProvider.d.ts +2 -2
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.js +1 -0
- package/dist/factories/providerRegistry.js +5 -0
- package/dist/lib/core/baseProvider.d.ts +2 -2
- package/dist/lib/core/types.d.ts +1 -0
- package/dist/lib/core/types.js +1 -0
- package/dist/lib/factories/providerRegistry.js +5 -0
- package/dist/lib/providers/amazonSagemaker.d.ts +67 -0
- package/dist/lib/providers/amazonSagemaker.js +149 -0
- package/dist/lib/providers/index.d.ts +4 -0
- package/dist/lib/providers/index.js +4 -0
- package/dist/lib/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
- package/dist/lib/providers/sagemaker/adaptive-semaphore.js +212 -0
- package/dist/lib/providers/sagemaker/client.d.ts +156 -0
- package/dist/lib/providers/sagemaker/client.js +462 -0
- package/dist/lib/providers/sagemaker/config.d.ts +73 -0
- package/dist/lib/providers/sagemaker/config.js +308 -0
- package/dist/lib/providers/sagemaker/detection.d.ts +176 -0
- package/dist/lib/providers/sagemaker/detection.js +596 -0
- package/dist/lib/providers/sagemaker/diagnostics.d.ts +37 -0
- package/dist/lib/providers/sagemaker/diagnostics.js +137 -0
- package/dist/lib/providers/sagemaker/error-constants.d.ts +78 -0
- package/dist/lib/providers/sagemaker/error-constants.js +227 -0
- package/dist/lib/providers/sagemaker/errors.d.ts +83 -0
- package/dist/lib/providers/sagemaker/errors.js +216 -0
- package/dist/lib/providers/sagemaker/index.d.ts +35 -0
- package/dist/lib/providers/sagemaker/index.js +67 -0
- package/dist/lib/providers/sagemaker/language-model.d.ts +182 -0
- package/dist/lib/providers/sagemaker/language-model.js +755 -0
- package/dist/lib/providers/sagemaker/parsers.d.ts +136 -0
- package/dist/lib/providers/sagemaker/parsers.js +625 -0
- package/dist/lib/providers/sagemaker/streaming.d.ts +39 -0
- package/dist/lib/providers/sagemaker/streaming.js +320 -0
- package/dist/lib/providers/sagemaker/structured-parser.d.ts +117 -0
- package/dist/lib/providers/sagemaker/structured-parser.js +625 -0
- package/dist/lib/providers/sagemaker/types.d.ts +456 -0
- package/dist/lib/providers/sagemaker/types.js +7 -0
- package/dist/lib/types/cli.d.ts +36 -1
- package/dist/providers/amazonSagemaker.d.ts +67 -0
- package/dist/providers/amazonSagemaker.js +149 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
- package/dist/providers/sagemaker/adaptive-semaphore.js +212 -0
- package/dist/providers/sagemaker/client.d.ts +156 -0
- package/dist/providers/sagemaker/client.js +462 -0
- package/dist/providers/sagemaker/config.d.ts +73 -0
- package/dist/providers/sagemaker/config.js +308 -0
- package/dist/providers/sagemaker/detection.d.ts +176 -0
- package/dist/providers/sagemaker/detection.js +596 -0
- package/dist/providers/sagemaker/diagnostics.d.ts +37 -0
- package/dist/providers/sagemaker/diagnostics.js +137 -0
- package/dist/providers/sagemaker/error-constants.d.ts +78 -0
- package/dist/providers/sagemaker/error-constants.js +227 -0
- package/dist/providers/sagemaker/errors.d.ts +83 -0
- package/dist/providers/sagemaker/errors.js +216 -0
- package/dist/providers/sagemaker/index.d.ts +35 -0
- package/dist/providers/sagemaker/index.js +67 -0
- package/dist/providers/sagemaker/language-model.d.ts +182 -0
- package/dist/providers/sagemaker/language-model.js +755 -0
- package/dist/providers/sagemaker/parsers.d.ts +136 -0
- package/dist/providers/sagemaker/parsers.js +625 -0
- package/dist/providers/sagemaker/streaming.d.ts +39 -0
- package/dist/providers/sagemaker/streaming.js +320 -0
- package/dist/providers/sagemaker/structured-parser.d.ts +117 -0
- package/dist/providers/sagemaker/structured-parser.js +625 -0
- package/dist/providers/sagemaker/types.d.ts +456 -0
- package/dist/providers/sagemaker/types.js +7 -0
- package/dist/types/cli.d.ts +36 -1
- package/package.json +4 -1
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Output Streaming Parser (Phase 2.3)
|
|
3
|
+
*
|
|
4
|
+
* This module provides partial JSON parsing for streaming structured output
|
|
5
|
+
* responses from SageMaker endpoints with real-time validation.
|
|
6
|
+
*/
|
|
7
|
+
import { logger } from "../../utils/logger.js";
|
|
8
|
+
import { processBracketCharacter, } from "./parsers.js";
|
|
9
|
+
/**
|
|
10
|
+
* Partial JSON parser for streaming structured output
|
|
11
|
+
*/
|
|
12
|
+
export class StructuredOutputParser {
|
|
13
|
+
buffer = "";
|
|
14
|
+
currentObject = {};
|
|
15
|
+
currentPath = [];
|
|
16
|
+
schema;
|
|
17
|
+
// Removed bracketStack: Redundant with bracketTypeStack, causes O(n) memory usage
|
|
18
|
+
inString = false;
|
|
19
|
+
escapeNext = false;
|
|
20
|
+
// Efficient bracket counting using counters instead of array operations
|
|
21
|
+
bracketCount = 0;
|
|
22
|
+
arrayBracketCount = 0;
|
|
23
|
+
lastProcessedLength = 0;
|
|
24
|
+
lastKeyValueParsePosition = 0; // Track position in key-value parsing to prevent O(n²)
|
|
25
|
+
// Simple string-based bracket tracking for better readability
|
|
26
|
+
bracketTypeStack = []; // Stack of bracket types: '{' or '['
|
|
27
|
+
constructor(schema) {
|
|
28
|
+
this.schema = schema;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse a chunk of JSON text and return structured output info
|
|
32
|
+
*/
|
|
33
|
+
parseChunk(chunk) {
|
|
34
|
+
this.buffer += chunk;
|
|
35
|
+
// Process bracket structure to update our state first
|
|
36
|
+
const partialResult = this.parsePartialJSON(chunk);
|
|
37
|
+
// Use efficient bracket counting to check completeness before expensive JSON.parse
|
|
38
|
+
if (this.isObjectComplete() && this.buffer.trim().length > 0) {
|
|
39
|
+
try {
|
|
40
|
+
// Only attempt JSON.parse when bracket counting indicates completeness
|
|
41
|
+
const completeObject = JSON.parse(this.buffer);
|
|
42
|
+
// If successful, it's complete
|
|
43
|
+
return {
|
|
44
|
+
partialObject: completeObject,
|
|
45
|
+
jsonDelta: chunk,
|
|
46
|
+
currentPath: this.currentPath.join("."),
|
|
47
|
+
complete: true,
|
|
48
|
+
schema: this.schema,
|
|
49
|
+
validationErrors: this.validateAgainstSchema(completeObject),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// JSON.parse failed despite bracket completeness - treat as partial
|
|
54
|
+
return {
|
|
55
|
+
partialObject: this.currentObject,
|
|
56
|
+
jsonDelta: chunk,
|
|
57
|
+
currentPath: this.currentPath.join("."),
|
|
58
|
+
complete: false,
|
|
59
|
+
schema: this.schema,
|
|
60
|
+
validationErrors: this.validatePartialObject(),
|
|
61
|
+
...partialResult,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// JSON is incomplete based on bracket counting - avoid expensive JSON.parse
|
|
67
|
+
return {
|
|
68
|
+
partialObject: this.currentObject,
|
|
69
|
+
jsonDelta: chunk,
|
|
70
|
+
currentPath: this.currentPath.join("."),
|
|
71
|
+
complete: false,
|
|
72
|
+
schema: this.schema,
|
|
73
|
+
validationErrors: this.validatePartialObject(),
|
|
74
|
+
...partialResult,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse partial JSON by tracking structure with consolidated bracket counting
|
|
80
|
+
*/
|
|
81
|
+
parsePartialJSON(chunk) {
|
|
82
|
+
// Use consolidated bracket tracking for both counting and path navigation
|
|
83
|
+
this.processBracketStructure(chunk);
|
|
84
|
+
// Try to extract partial object from valid JSON fragments
|
|
85
|
+
this.extractPartialObject();
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Consolidated bracket structure processing - handles both counting and path navigation
|
|
90
|
+
* Uses shared bracket counting logic to reduce code duplication
|
|
91
|
+
*/
|
|
92
|
+
processBracketStructure(chunk) {
|
|
93
|
+
// Create a state object compatible with the shared bracket counting logic
|
|
94
|
+
const sharedState = {
|
|
95
|
+
braceCount: this.bracketCount,
|
|
96
|
+
bracketCount: this.arrayBracketCount,
|
|
97
|
+
inString: this.inString,
|
|
98
|
+
escapeNext: this.escapeNext,
|
|
99
|
+
};
|
|
100
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
101
|
+
const char = chunk[i];
|
|
102
|
+
// Use shared bracket counting logic for core functionality
|
|
103
|
+
const result = processBracketCharacter(char, sharedState);
|
|
104
|
+
if (!result.isValid) {
|
|
105
|
+
logger.debug("Invalid bracket structure detected", {
|
|
106
|
+
reason: result.reason,
|
|
107
|
+
position: i,
|
|
108
|
+
char,
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Handle path navigation and stack management (parser-specific logic)
|
|
113
|
+
if (!sharedState.inString) {
|
|
114
|
+
switch (char) {
|
|
115
|
+
case "{":
|
|
116
|
+
this.bracketTypeStack.push("{");
|
|
117
|
+
break;
|
|
118
|
+
case "}":
|
|
119
|
+
// Check for matching opening brace
|
|
120
|
+
if (this.bracketTypeStack.length > 0 &&
|
|
121
|
+
this.bracketTypeStack[this.bracketTypeStack.length - 1] === "{") {
|
|
122
|
+
this.bracketTypeStack.pop();
|
|
123
|
+
if (this.currentPath.length > 0) {
|
|
124
|
+
this.currentPath.pop();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case "[":
|
|
129
|
+
this.bracketTypeStack.push("[");
|
|
130
|
+
break;
|
|
131
|
+
case "]":
|
|
132
|
+
// Check for matching opening bracket
|
|
133
|
+
if (this.bracketTypeStack.length > 0 &&
|
|
134
|
+
this.bracketTypeStack[this.bracketTypeStack.length - 1] === "[") {
|
|
135
|
+
this.bracketTypeStack.pop();
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case ":":
|
|
139
|
+
// Entering a value
|
|
140
|
+
break;
|
|
141
|
+
case ",":
|
|
142
|
+
// Moving to next property
|
|
143
|
+
if (this.currentPath.length > 0) {
|
|
144
|
+
this.currentPath.pop();
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Update instance state from shared state
|
|
151
|
+
this.bracketCount = sharedState.braceCount;
|
|
152
|
+
this.arrayBracketCount = sharedState.bracketCount;
|
|
153
|
+
this.inString = sharedState.inString;
|
|
154
|
+
this.escapeNext = sharedState.escapeNext;
|
|
155
|
+
this.lastProcessedLength = this.buffer.length;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract partial object from buffer by finding valid JSON fragments
|
|
159
|
+
* Optimized for large JSON strings using true incremental parsing to prevent O(n²) performance
|
|
160
|
+
*/
|
|
161
|
+
extractPartialObject() {
|
|
162
|
+
try {
|
|
163
|
+
// Only process new content since last parse to avoid O(n²) performance
|
|
164
|
+
const newContentStart = this.lastKeyValueParsePosition;
|
|
165
|
+
const newContentLength = this.buffer.length - newContentStart;
|
|
166
|
+
// Skip processing if no new content
|
|
167
|
+
if (newContentLength <= 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const tempObject = {};
|
|
171
|
+
// Use incremental parsing that only processes new content
|
|
172
|
+
this.parseKeyValuePairsIncrementally(this.buffer, tempObject);
|
|
173
|
+
// Update current object with new properties
|
|
174
|
+
Object.assign(this.currentObject, tempObject);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
logger.debug("Error extracting partial object", {
|
|
178
|
+
error: error instanceof Error ? error.message : String(error),
|
|
179
|
+
buffer: this.buffer.substring(0, 100),
|
|
180
|
+
lastProcessedLength: this.lastProcessedLength,
|
|
181
|
+
newContentStart: this.lastKeyValueParsePosition,
|
|
182
|
+
bufferLength: this.buffer.length,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Efficiently parse key-value pairs from JSON buffer using true incremental approach
|
|
188
|
+
* Optimized for large JSON strings to avoid O(n²) performance by only processing new content
|
|
189
|
+
*/
|
|
190
|
+
parseKeyValuePairsIncrementally(buffer, targetObject) {
|
|
191
|
+
// Start from where we left off in key-value parsing to avoid reprocessing
|
|
192
|
+
let i = Math.max(0, this.lastKeyValueParsePosition);
|
|
193
|
+
const length = buffer.length;
|
|
194
|
+
// If no new content to parse, return early
|
|
195
|
+
if (i >= length) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Track if we're in the middle of parsing a key-value pair
|
|
199
|
+
let parsingState = "seeking_key";
|
|
200
|
+
let currentKey = null;
|
|
201
|
+
while (i < length) {
|
|
202
|
+
// Skip whitespace - optimized character check instead of regex for performance
|
|
203
|
+
while (i < length && this.isWhitespace(buffer[i])) {
|
|
204
|
+
i++;
|
|
205
|
+
}
|
|
206
|
+
if (i >= length) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
switch (parsingState) {
|
|
210
|
+
case "seeking_key":
|
|
211
|
+
// Look for opening quote of key
|
|
212
|
+
if (buffer[i] === '"') {
|
|
213
|
+
const keyResult = this.parseQuotedString(buffer, i);
|
|
214
|
+
if (!keyResult) {
|
|
215
|
+
// Incomplete key, save position and exit
|
|
216
|
+
this.lastKeyValueParsePosition = i;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
currentKey = keyResult.value;
|
|
220
|
+
i = keyResult.endIndex + 1;
|
|
221
|
+
parsingState = "seeking_colon";
|
|
222
|
+
}
|
|
223
|
+
else if (buffer[i] === "{" || buffer[i] === "[") {
|
|
224
|
+
// Skip nested objects/arrays for now - they need separate handling
|
|
225
|
+
i++;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case "seeking_colon":
|
|
232
|
+
if (buffer[i] === ":") {
|
|
233
|
+
i++;
|
|
234
|
+
parsingState = "seeking_value";
|
|
235
|
+
}
|
|
236
|
+
else if (!this.isWhitespace(buffer[i])) {
|
|
237
|
+
// Invalid character, reset state
|
|
238
|
+
parsingState = "seeking_key";
|
|
239
|
+
currentKey = null;
|
|
240
|
+
i++;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
i++;
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
case "seeking_value": {
|
|
247
|
+
// Parse value
|
|
248
|
+
const valueResult = this.parseJsonValue(buffer, i);
|
|
249
|
+
if (valueResult && currentKey) {
|
|
250
|
+
targetObject[currentKey] = valueResult.value;
|
|
251
|
+
i = valueResult.endIndex + 1;
|
|
252
|
+
parsingState = "seeking_key";
|
|
253
|
+
currentKey = null;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Incomplete value, save position for next chunk
|
|
257
|
+
this.lastKeyValueParsePosition = i;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
default:
|
|
263
|
+
i++;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Update the position we've processed to avoid reprocessing in future calls
|
|
267
|
+
this.lastKeyValueParsePosition = i;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Parse a quoted string from buffer starting at given index
|
|
271
|
+
*/
|
|
272
|
+
parseQuotedString(buffer, startIndex) {
|
|
273
|
+
if (buffer[startIndex] !== '"') {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
let i = startIndex + 1;
|
|
277
|
+
let result = "";
|
|
278
|
+
while (i < buffer.length) {
|
|
279
|
+
const char = buffer[i];
|
|
280
|
+
if (char === '"') {
|
|
281
|
+
return { value: result, endIndex: i };
|
|
282
|
+
}
|
|
283
|
+
else if (char === "\\" && i + 1 < buffer.length) {
|
|
284
|
+
// Handle escaped characters
|
|
285
|
+
const nextChar = buffer[i + 1];
|
|
286
|
+
switch (nextChar) {
|
|
287
|
+
case '"':
|
|
288
|
+
case "\\":
|
|
289
|
+
case "/":
|
|
290
|
+
result += nextChar;
|
|
291
|
+
break;
|
|
292
|
+
case "b":
|
|
293
|
+
result += "\b";
|
|
294
|
+
break;
|
|
295
|
+
case "f":
|
|
296
|
+
result += "\f";
|
|
297
|
+
break;
|
|
298
|
+
case "n":
|
|
299
|
+
result += "\n";
|
|
300
|
+
break;
|
|
301
|
+
case "r":
|
|
302
|
+
result += "\r";
|
|
303
|
+
break;
|
|
304
|
+
case "t":
|
|
305
|
+
result += "\t";
|
|
306
|
+
break;
|
|
307
|
+
case "u":
|
|
308
|
+
// Unicode escape - simplified handling with optimized validation
|
|
309
|
+
if (i + 5 < buffer.length) {
|
|
310
|
+
const unicodeStr = buffer.substring(i + 2, i + 6);
|
|
311
|
+
if (this.isValidHexString(unicodeStr)) {
|
|
312
|
+
result += String.fromCharCode(parseInt(unicodeStr, 16));
|
|
313
|
+
i += 4; // Skip additional unicode chars
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
result += nextChar; // Fallback
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
result += nextChar; // Fallback
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
default:
|
|
324
|
+
result += nextChar;
|
|
325
|
+
}
|
|
326
|
+
i += 2;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
result += char;
|
|
330
|
+
i++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Unterminated string - return partial for streaming
|
|
334
|
+
return { value: result, endIndex: i - 1 };
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Parse a JSON value (string, number, boolean, null) from buffer
|
|
338
|
+
*/
|
|
339
|
+
parseJsonValue(buffer, startIndex) {
|
|
340
|
+
const char = buffer[startIndex];
|
|
341
|
+
if (char === '"') {
|
|
342
|
+
// String value
|
|
343
|
+
const stringResult = this.parseQuotedString(buffer, startIndex);
|
|
344
|
+
if (stringResult) {
|
|
345
|
+
return { value: stringResult.value, endIndex: stringResult.endIndex };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (char === "t" &&
|
|
349
|
+
buffer.substring(startIndex, startIndex + 4) === "true") {
|
|
350
|
+
return { value: true, endIndex: startIndex + 3 };
|
|
351
|
+
}
|
|
352
|
+
else if (char === "f" &&
|
|
353
|
+
buffer.substring(startIndex, startIndex + 5) === "false") {
|
|
354
|
+
return { value: false, endIndex: startIndex + 4 };
|
|
355
|
+
}
|
|
356
|
+
else if (char === "n" &&
|
|
357
|
+
buffer.substring(startIndex, startIndex + 4) === "null") {
|
|
358
|
+
return { value: null, endIndex: startIndex + 3 };
|
|
359
|
+
}
|
|
360
|
+
else if (char === "-" || this.isDigit(char)) {
|
|
361
|
+
// Number value
|
|
362
|
+
const numberResult = this.parseNumber(buffer, startIndex);
|
|
363
|
+
if (numberResult) {
|
|
364
|
+
return numberResult;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Parse a number from buffer starting at given index
|
|
371
|
+
*/
|
|
372
|
+
parseNumber(buffer, startIndex) {
|
|
373
|
+
let i = startIndex;
|
|
374
|
+
let numberStr = "";
|
|
375
|
+
// Handle negative sign
|
|
376
|
+
if (buffer[i] === "-") {
|
|
377
|
+
numberStr += "-";
|
|
378
|
+
i++;
|
|
379
|
+
}
|
|
380
|
+
// Parse integer part
|
|
381
|
+
if (i >= buffer.length || !this.isDigit(buffer[i])) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
while (i < buffer.length && this.isDigit(buffer[i])) {
|
|
385
|
+
numberStr += buffer[i];
|
|
386
|
+
i++;
|
|
387
|
+
}
|
|
388
|
+
// Parse decimal part
|
|
389
|
+
if (i < buffer.length && buffer[i] === ".") {
|
|
390
|
+
numberStr += ".";
|
|
391
|
+
i++;
|
|
392
|
+
if (i >= buffer.length || !this.isDigit(buffer[i])) {
|
|
393
|
+
return null; // Invalid decimal
|
|
394
|
+
}
|
|
395
|
+
while (i < buffer.length && this.isDigit(buffer[i])) {
|
|
396
|
+
numberStr += buffer[i];
|
|
397
|
+
i++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Parse exponent part
|
|
401
|
+
if (i < buffer.length && (buffer[i] === "e" || buffer[i] === "E")) {
|
|
402
|
+
numberStr += buffer[i];
|
|
403
|
+
i++;
|
|
404
|
+
if (i < buffer.length && (buffer[i] === "+" || buffer[i] === "-")) {
|
|
405
|
+
numberStr += buffer[i];
|
|
406
|
+
i++;
|
|
407
|
+
}
|
|
408
|
+
if (i >= buffer.length || !this.isDigit(buffer[i])) {
|
|
409
|
+
return null; // Invalid exponent
|
|
410
|
+
}
|
|
411
|
+
while (i < buffer.length && this.isDigit(buffer[i])) {
|
|
412
|
+
numberStr += buffer[i];
|
|
413
|
+
i++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const parsedNumber = parseFloat(numberStr);
|
|
417
|
+
if (isNaN(parsedNumber)) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
return { value: parsedNumber, endIndex: i - 1 };
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Validate partial object against schema
|
|
424
|
+
*/
|
|
425
|
+
validatePartialObject() {
|
|
426
|
+
if (!this.schema) {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
const errors = [];
|
|
430
|
+
try {
|
|
431
|
+
// Basic schema validation for partial objects
|
|
432
|
+
const schemaProperties = this.schema.properties;
|
|
433
|
+
const required = this.schema.required || [];
|
|
434
|
+
if (schemaProperties) {
|
|
435
|
+
for (const [key, value] of Object.entries(this.currentObject)) {
|
|
436
|
+
const propertySchema = schemaProperties[key];
|
|
437
|
+
if (propertySchema) {
|
|
438
|
+
const validationError = this.validateProperty(key, value, propertySchema);
|
|
439
|
+
if (validationError) {
|
|
440
|
+
errors.push(validationError);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Check for missing required properties (only for complete objects)
|
|
446
|
+
if (this.isObjectComplete()) {
|
|
447
|
+
for (const requiredProp of required) {
|
|
448
|
+
if (!(requiredProp in this.currentObject)) {
|
|
449
|
+
errors.push(`Missing required property: ${requiredProp}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
errors.push(`Schema validation error: ${error instanceof Error ? error.message : String(error)}`);
|
|
456
|
+
}
|
|
457
|
+
return errors;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Validate complete object against schema
|
|
461
|
+
*/
|
|
462
|
+
validateAgainstSchema(obj) {
|
|
463
|
+
if (!this.schema) {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
const errors = [];
|
|
467
|
+
try {
|
|
468
|
+
const schemaProperties = this.schema.properties;
|
|
469
|
+
const required = this.schema.required || [];
|
|
470
|
+
if (schemaProperties) {
|
|
471
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
472
|
+
const propertySchema = schemaProperties[key];
|
|
473
|
+
if (propertySchema) {
|
|
474
|
+
const validationError = this.validateProperty(key, value, propertySchema);
|
|
475
|
+
if (validationError) {
|
|
476
|
+
errors.push(validationError);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Check required properties
|
|
482
|
+
for (const requiredProp of required) {
|
|
483
|
+
if (!(requiredProp in obj)) {
|
|
484
|
+
errors.push(`Missing required property: ${requiredProp}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
errors.push(`Schema validation error: ${error instanceof Error ? error.message : String(error)}`);
|
|
490
|
+
}
|
|
491
|
+
return errors;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Validate a single property against its schema
|
|
495
|
+
*/
|
|
496
|
+
validateProperty(key, value, propertySchema) {
|
|
497
|
+
const expectedType = propertySchema.type;
|
|
498
|
+
const actualType = typeof value;
|
|
499
|
+
if (expectedType && actualType !== expectedType) {
|
|
500
|
+
return `Property ${key}: expected ${expectedType}, got ${actualType}`;
|
|
501
|
+
}
|
|
502
|
+
// Additional validations
|
|
503
|
+
if (expectedType === "string" &&
|
|
504
|
+
typeof propertySchema.minLength === "number" &&
|
|
505
|
+
typeof value === "string" &&
|
|
506
|
+
value.length < propertySchema.minLength) {
|
|
507
|
+
return `Property ${key}: string too short (min ${propertySchema.minLength})`;
|
|
508
|
+
}
|
|
509
|
+
if (expectedType === "string" &&
|
|
510
|
+
typeof propertySchema.maxLength === "number" &&
|
|
511
|
+
typeof value === "string" &&
|
|
512
|
+
value.length > propertySchema.maxLength) {
|
|
513
|
+
return `Property ${key}: string too long (max ${propertySchema.maxLength})`;
|
|
514
|
+
}
|
|
515
|
+
if (expectedType === "number" &&
|
|
516
|
+
typeof propertySchema.minimum === "number" &&
|
|
517
|
+
typeof value === "number" &&
|
|
518
|
+
value < propertySchema.minimum) {
|
|
519
|
+
return `Property ${key}: value too small (min ${propertySchema.minimum})`;
|
|
520
|
+
}
|
|
521
|
+
if (expectedType === "number" &&
|
|
522
|
+
typeof propertySchema.maximum === "number" &&
|
|
523
|
+
typeof value === "number" &&
|
|
524
|
+
value > propertySchema.maximum) {
|
|
525
|
+
return `Property ${key}: value too large (max ${propertySchema.maximum})`;
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Check if the current object appears to be complete using efficient bracket counting
|
|
531
|
+
*/
|
|
532
|
+
isObjectComplete() {
|
|
533
|
+
// Use efficient bracket counters instead of array length for better performance
|
|
534
|
+
return (this.bracketCount === 0 && this.arrayBracketCount === 0 && !this.inString);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Optimized whitespace check to replace regex for performance with large strings
|
|
538
|
+
*/
|
|
539
|
+
isWhitespace(char) {
|
|
540
|
+
// Common whitespace characters: space, tab, newline, carriage return
|
|
541
|
+
return char === " " || char === "\t" || char === "\n" || char === "\r";
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Optimized digit check to replace regex for performance with large strings
|
|
545
|
+
*/
|
|
546
|
+
isDigit(char) {
|
|
547
|
+
// Check if character is between '0' and '9'
|
|
548
|
+
return char >= "0" && char <= "9";
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Optimized hex string validation to replace regex for unicode escape sequences
|
|
552
|
+
*/
|
|
553
|
+
isValidHexString(str) {
|
|
554
|
+
if (str.length !== 4) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
for (let i = 0; i < 4; i++) {
|
|
558
|
+
const char = str[i];
|
|
559
|
+
if (!((char >= "0" && char <= "9") ||
|
|
560
|
+
(char >= "a" && char <= "f") ||
|
|
561
|
+
(char >= "A" && char <= "F"))) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Reset parser state
|
|
569
|
+
*/
|
|
570
|
+
reset() {
|
|
571
|
+
this.buffer = "";
|
|
572
|
+
this.currentObject = {};
|
|
573
|
+
this.currentPath = [];
|
|
574
|
+
this.inString = false;
|
|
575
|
+
this.escapeNext = false;
|
|
576
|
+
// Reset efficient bracket counters and parsing position trackers
|
|
577
|
+
this.bracketCount = 0;
|
|
578
|
+
this.arrayBracketCount = 0;
|
|
579
|
+
this.lastProcessedLength = 0;
|
|
580
|
+
this.lastKeyValueParsePosition = 0;
|
|
581
|
+
this.bracketTypeStack = [];
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get current parsing state for debugging
|
|
585
|
+
*/
|
|
586
|
+
getState() {
|
|
587
|
+
return {
|
|
588
|
+
bufferLength: this.buffer.length,
|
|
589
|
+
currentPath: [...this.currentPath],
|
|
590
|
+
inString: this.inString,
|
|
591
|
+
objectKeys: Object.keys(this.currentObject),
|
|
592
|
+
bracketCount: this.bracketCount,
|
|
593
|
+
arrayBracketCount: this.arrayBracketCount,
|
|
594
|
+
lastProcessedLength: this.lastProcessedLength,
|
|
595
|
+
lastKeyValueParsePosition: this.lastKeyValueParsePosition,
|
|
596
|
+
bracketTypeStack: [...this.bracketTypeStack],
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Factory function to create structured output parser
|
|
602
|
+
*/
|
|
603
|
+
export function createStructuredOutputParser(schema) {
|
|
604
|
+
return new StructuredOutputParser(schema);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Utility function to detect if content is structured JSON
|
|
608
|
+
*/
|
|
609
|
+
export function isStructuredContent(content) {
|
|
610
|
+
const trimmed = content.trim();
|
|
611
|
+
return (trimmed.startsWith("{") || (trimmed.includes('"') && trimmed.includes(":")));
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Utility function to extract JSON schema from response format
|
|
615
|
+
*/
|
|
616
|
+
export function extractSchemaFromResponseFormat(responseFormat) {
|
|
617
|
+
if (responseFormat.type === "json_schema" && responseFormat.json_schema) {
|
|
618
|
+
const schemaObj = responseFormat.json_schema;
|
|
619
|
+
return schemaObj.schema;
|
|
620
|
+
}
|
|
621
|
+
if (responseFormat.type === "json_object" && responseFormat.schema) {
|
|
622
|
+
return responseFormat.schema;
|
|
623
|
+
}
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|