@@ -59,13 +59,18 @@ export const fromData = (protos: Prototypes, value: unknown, label: string): unk
5959 * non-finite numbers become null, and array holes become null. `undefined` object properties are
6060 * dropped ("json") or become null ("result", for program results where the consumer must never see
6161 * undefined); a bare `undefined` follows the same rule.
62+ *
63+ * At the host boundary (tool arguments and program results) `__proto__` keys are dropped and values
64+ * `JSON.stringify` would flatten to `{}` cross in a useful form instead: a Set as an array, a RegExp
65+ * and URLSearchParams as their strings. `JSON.stringify` itself passes `boundary: false` to keep JS
66+ * behavior.
6267 */
6368export const toData = (
6469 value : unknown ,
6570 label : string ,
6671 undefinedAs : "json" | "result" = "json" ,
67- stripProto = true ,
68- ) : unknown => copy ( value , label , undefinedAs , 0 , new Set ( ) , undefined , stripProto )
72+ boundary = true ,
73+ ) : unknown => copy ( value , label , undefinedAs , 0 , new Set ( ) , undefined , boundary )
6974
7075// "program" and "data" build program objects; "json" and "result" build ordinary objects for the host.
7176type Mode = "program" | "data" | "json" | "result"
@@ -77,9 +82,9 @@ const copy = (
7782 depth : number ,
7883 seen : Set < object > ,
7984 protos ?: Prototypes ,
80- stripProto = true ,
85+ boundary = true ,
8186) : unknown => {
82- const next = ( item : unknown ) => copy ( item , label , mode , depth + 1 , seen , protos , stripProto )
87+ const next = ( item : unknown ) => copy ( item , label , mode , depth + 1 , seen , protos , boundary )
8388 if ( depth > MAX_VALUE_DEPTH ) {
8489 throw new ToolRuntimeError ( "InvalidDataValue" , `${ label } exceeds the maximum value depth of ${ MAX_VALUE_DEPTH } .` )
8590 }
@@ -132,6 +137,17 @@ const copy = (
132137 if ( value instanceof Date ) return Number . isFinite ( value . getTime ( ) ) ? value . toISOString ( ) : null
133138 if ( value instanceof ProgramURL ) return value . url . href
134139 if ( value instanceof URL ) return value . href
140+ if ( boundary && protos === undefined ) {
141+ if ( value instanceof ProgramRegExp ) return String ( value . regex )
142+ if ( value instanceof ProgramURLSearchParams ) return value . params . toString ( )
143+ if ( value instanceof ProgramSet ) {
144+ if ( seen . has ( value ) ) throw new ToolRuntimeError ( "InvalidDataValue" , `${ label } contains a circular value.` )
145+ seen . add ( value )
146+ const copied = Array . from ( value . set , ( item ) => next ( item ) ?? null )
147+ seen . delete ( value )
148+ return copied
149+ }
150+ }
135151 // Remaining wrappers and their host counterparts serialize as empty objects, like JSON.stringify.
136152 if (
137153 isWrapper ( value ) ||
@@ -161,7 +177,7 @@ const copy = (
161177 defineHost ( copied , "message" , next ( get ( value , "message" ) ) )
162178 }
163179 for ( const [ key , item ] of entries ( value ) ) {
164- if ( stripProto && key === "__proto__" ) continue
180+ if ( boundary && key === "__proto__" ) continue
165181 const copiedItem = next ( item )
166182 if ( copiedItem === undefined && mode === "json" ) continue
167183 defineHost ( copied , key , copiedItem )
@@ -197,7 +213,7 @@ const copy = (
197213 }
198214 const copied : Record < string , unknown > = { }
199215 for ( const [ key , item ] of Object . entries ( value ) ) {
200- if ( stripProto && key === "__proto__" ) continue
216+ if ( boundary && key === "__proto__" ) continue
201217 const copiedItem = next ( item )
202218 if ( copiedItem === undefined && mode === "json" ) continue
203219 defineHost ( copied , key , copiedItem )
0 commit comments