@@ -498,87 +498,6 @@ function summarizeZodErrors(error: ZodError): string {
498498 return lines . join ( '\n' ) ;
499499}
500500
501- /**
502- * Re-parses string-encoded containers where the schema expects an array or
503- * object but the model produced a JSON STRING containing one.
504- *
505- * Models on the tool-use / structured-output path intermittently
506- * double-encode a nested container — `{"verdicts": "[{...}]"}` instead of
507- * `{"verdicts": [{...}]}` — most often on long, deeply nested payloads. The
508- * raw text extracts as valid JSON, so only Zod sees the defect
509- * (`invalid_type: expected array, received string`), and a retry re-rolls
510- * the same dice with the same prompt.
511- *
512- * Driven strictly by the validation issues: for each `invalid_type` issue
513- * expecting a container whose actual value is a string that syntactically
514- * starts like that container, `JSON.parse` the string in place on a clone.
515- * One pass, no recursion; anything that does not parse is left untouched so
516- * the caller's normal retry/feedback path still applies.
517- *
518- * @param value - The extracted-but-invalid candidate value.
519- * @param error - The Zod error from the failed validation.
520- * @returns The (possibly repaired) value and whether any repair applied.
521- * @internal
522- */
523- function repairStringEncodedContainers (
524- value : unknown ,
525- error : ZodError ,
526- ) : { value : unknown ; repaired : boolean } {
527- type ContainerIssue = { code ?: string ; expected ?: string ; path ?: PropertyKey [ ] } ;
528- const targets = ( error . issues as unknown as ContainerIssue [ ] ) . filter (
529- issue =>
530- issue . code === 'invalid_type' &&
531- ( issue . expected === 'array' || issue . expected === 'object' ) ,
532- ) ;
533- if ( targets . length === 0 ) return { value, repaired : false } ;
534-
535- let root : unknown ;
536- try {
537- root = structuredClone ( value ) ;
538- } catch {
539- // Non-cloneable input (should never happen for JSON-derived data) —
540- // repair is best-effort, never a new failure mode.
541- return { value, repaired : false } ;
542- }
543-
544- let repaired = false ;
545- for ( const issue of targets ) {
546- const path = issue . path ?? [ ] ;
547- // Resolve the offending node and its parent inside the clone.
548- let parent : Record < PropertyKey , unknown > | null = null ;
549- let node : unknown = root ;
550- for ( const key of path ) {
551- if ( node === null || typeof node !== 'object' ) {
552- node = undefined ;
553- break ;
554- }
555- parent = node as Record < PropertyKey , unknown > ;
556- node = parent [ key ] ;
557- }
558- if ( typeof node !== 'string' ) continue ;
559- const trimmed = node . trim ( ) ;
560- const expectsArray = issue . expected === 'array' ;
561- if ( expectsArray ? ! trimmed . startsWith ( '[' ) : ! trimmed . startsWith ( '{' ) ) continue ;
562- let inner : unknown ;
563- try {
564- inner = JSON . parse ( trimmed ) ;
565- } catch {
566- continue ;
567- }
568- if ( path . length === 0 ) {
569- // The root itself was string-encoded.
570- root = inner ;
571- repaired = true ;
572- continue ;
573- }
574- if ( parent ) {
575- parent [ path [ path . length - 1 ] as PropertyKey ] = inner ;
576- repaired = true ;
577- }
578- }
579- return { value : root , repaired } ;
580- }
581-
582501/**
583502 * Grows the output-token budget after a truncated attempt, capped at
584503 * {@link TRUNCATION_RETRY_MAX_TOKENS}. The provider layer clamps the result to
0 commit comments