@@ -12,6 +12,7 @@ import { ToolCallProviderType } from "../../shared/tools"
12
12
* Defines the possible states of the JSON parser.
13
13
*/
14
14
enum ParserState {
15
+ EXPECT_ROOT , // Expecting root object or array
15
16
EXPECT_VALUE ,
16
17
EXPECT_KEY ,
17
18
EXPECT_COLON ,
@@ -41,7 +42,7 @@ class ToolCallProcessingState {
41
42
cursor = 0
42
43
43
44
// The current state of the parser FSM (Finite State Machine).
44
- parserState = ParserState . EXPECT_VALUE
45
+ parserState = ParserState . EXPECT_ROOT
45
46
46
47
// Flags for handling string parsing.
47
48
inString = false
@@ -54,6 +55,8 @@ class ToolCallProcessingState {
54
55
xmlTagStack : string [ ] = [ ]
55
56
// Buffer for the current string literal (key or value) being parsed.
56
57
currentString = ""
58
+ // Buffer for accumulating primitive values across chunks
59
+ primitiveBuffer = ""
57
60
// Flag to track if we're at the start of an array to prevent duplicate tags.
58
61
justOpenedArray = false
59
62
}
@@ -315,24 +318,46 @@ export class StreamingToolCallProcessor {
315
318
continue
316
319
}
317
320
318
- // Handle primitives (numbers, booleans, null)
321
+ // Handle primitives - accumulate characters until we hit a delimiter
319
322
if ( state . parserState === ParserState . EXPECT_VALUE ) {
320
- const primitiveMatch = args . substring ( state . cursor ) . match ( / ^ - ? \d + ( \. \d + ) ? | t r u e | f a l s e | n u l l / )
321
- if ( primitiveMatch ) {
322
- const value = primitiveMatch [ 0 ]
323
- const tag = state . xmlTagStack . pop ( ) !
324
- if ( tag ) {
325
- xml += `${ value } ${ this . onCloseTag ( tag , toolName ) } `
326
- }
327
- state . parserState = ParserState . EXPECT_COMMA_OR_CLOSING
328
- state . cursor += value . length
323
+ // Check if this character could be part of a primitive value
324
+ if (
325
+ ( char >= "0" && char <= "9" ) ||
326
+ char === "-" ||
327
+ char === "." ||
328
+ ( char >= "a" && char <= "z" ) ||
329
+ ( char >= "A" && char <= "Z" )
330
+ ) {
331
+ // Accumulate the character
332
+ state . primitiveBuffer += char
333
+ state . cursor ++
329
334
continue
335
+ } else if ( state . primitiveBuffer . length > 0 ) {
336
+ // We've hit a delimiter, check if we have a complete primitive
337
+ const value = state . primitiveBuffer . trim ( )
338
+ if ( value === "true" || value === "false" || value === "null" || / ^ - ? \d + ( \. \d + ) ? $ / . test ( value ) ) {
339
+ // We have a valid primitive
340
+ const tag = state . xmlTagStack . pop ( ) !
341
+ if ( tag ) {
342
+ xml += `${ value } ${ this . onCloseTag ( tag , toolName ) } `
343
+ }
344
+ state . parserState = ParserState . EXPECT_COMMA_OR_CLOSING
345
+ state . primitiveBuffer = ""
346
+ // Don't increment cursor - let the delimiter be processed in the switch
347
+ continue
348
+ } else {
349
+ // Invalid primitive, reset buffer and continue
350
+ state . primitiveBuffer = ""
351
+ }
330
352
}
331
353
}
332
354
333
355
switch ( char ) {
334
356
case "{" :
335
- if ( state . parserState === ParserState . EXPECT_VALUE ) {
357
+ if (
358
+ state . parserState === ParserState . EXPECT_VALUE ||
359
+ state . parserState === ParserState . EXPECT_ROOT
360
+ ) {
336
361
const parent = state . bracketStack [ state . bracketStack . length - 1 ]
337
362
if ( parent === "[" ) {
338
363
// For an object inside an array, we might need to add the repeating tag.
@@ -381,7 +406,10 @@ export class StreamingToolCallProcessor {
381
406
}
382
407
break
383
408
case "[" :
384
- if ( state . parserState === ParserState . EXPECT_VALUE ) {
409
+ if (
410
+ state . parserState === ParserState . EXPECT_VALUE ||
411
+ state . parserState === ParserState . EXPECT_ROOT
412
+ ) {
385
413
state . bracketStack . push ( "[" )
386
414
state . parserState = ParserState . EXPECT_VALUE // An array contains values
387
415
state . justOpenedArray = true
@@ -401,11 +429,12 @@ export class StreamingToolCallProcessor {
401
429
if ( state . parserState === ParserState . EXPECT_VALUE ) {
402
430
// We've encountered the start of a string that is a JSON value.
403
431
state . isStreamingStringValue = true
404
- } else {
432
+ state . inString = true
433
+ } else if ( state . parserState === ParserState . EXPECT_KEY ) {
405
434
// This is the start of a string that is a JSON key.
406
435
state . isStreamingStringValue = false
436
+ state . inString = true
407
437
}
408
- state . inString = true
409
438
break
410
439
case ":" :
411
440
if ( state . parserState === ParserState . EXPECT_COLON ) {
0 commit comments