@@ -258,59 +258,96 @@ export async function compactSubflowResults<T>(
258258/**
259259 * Compacts a trace span tree without collapsing its structure. Readers walk
260260 * `children` and `output.childTraceSpans` as arrays, so those stay arrays and
261- * only each span's payload fields are spilled when oversized. Size of the tree
262- * as a whole is bounded where the log is stored, not here.
261+ * only each span's payload fields are spilled when oversized. Spans are log
262+ * data: the tree as a whole is bounded where the log is stored, not here.
263263 */
264264async function compactTraceSpanTree (
265265 spans : unknown ,
266- options : CompactExecutionPayloadOptions
266+ options : CompactExecutionPayloadOptions ,
267+ seen : WeakSet < object >
267268) : Promise < unknown > {
268269 if ( ! Array . isArray ( spans ) ) {
269270 return compactExecutionPayload ( spans , options )
270271 }
271- return Promise . all ( spans . map ( ( span ) => compactTraceSpan ( span , options ) ) )
272+ return Promise . all ( spans . map ( ( span ) => compactTraceSpan ( span , options , seen ) ) )
272273}
273274
274275async function compactTraceSpan (
275276 span : unknown ,
276- options : CompactExecutionPayloadOptions
277+ options : CompactExecutionPayloadOptions ,
278+ seen : WeakSet < object >
277279) : Promise < unknown > {
278280 if ( ! isRecordLike ( span ) ) {
279281 return compactExecutionPayload ( span , options )
280282 }
283+ if ( seen . has ( span ) ) {
284+ return span
285+ }
286+ seen . add ( span )
281287 return Object . fromEntries (
282288 await Promise . all (
283289 Object . entries ( span ) . map ( async ( [ key , value ] ) => [
284290 key ,
285291 key === 'children'
286- ? await compactTraceSpanTree ( value , options )
292+ ? await compactTraceSpanTree ( value , options , seen )
287293 : key === 'output'
288- ? await compactBlockOutput ( value , options )
294+ ? await compactLoggedOutput ( value , options , seen )
289295 : await compactExecutionPayload ( value , options ) ,
290296 ] )
291297 )
292298 )
293299}
294300
295301/**
296- * Compacts a block output, keeping any `childTraceSpans` shaped as a span tree
297- * (see {@link compactTraceSpanTree}). Everything else compacts as a normal
298- * execution payload, except that an output carrying spans keeps its root so
299- * the spans stay attached to it; its fields still spill individually.
302+ * Compacts a span or block log output. One carrying `childTraceSpans` keeps its
303+ * root so the spans stay attached; its other fields spill individually.
304+ */
305+ async function compactLoggedOutput (
306+ output : unknown ,
307+ options : CompactExecutionPayloadOptions ,
308+ seen : WeakSet < object >
309+ ) : Promise < unknown > {
310+ if ( ! isRecordLike ( output ) || ! ( 'childTraceSpans' in output ) ) {
311+ return compactExecutionPayload ( output , options )
312+ }
313+ return Object . fromEntries (
314+ await Promise . all (
315+ Object . entries ( output ) . map ( async ( [ key , value ] ) => [
316+ key ,
317+ key === 'childTraceSpans'
318+ ? await compactTraceSpanTree ( value , options , seen )
319+ : await compactExecutionPayload ( value , options ) ,
320+ ] )
321+ )
322+ )
323+ }
324+
325+ export interface CompactedBlockOutput < T > {
326+ /** The output without `childTraceSpans`, compacted as execution state. */
327+ output : T
328+ /** The output's child span tree, compacted as log data. */
329+ childTraceSpans ?: unknown
330+ }
331+
332+ /**
333+ * Compacts a block output for execution state and splits off its
334+ * `childTraceSpans`, which belong to the block log rather than state. The
335+ * output compacts as any execution payload, so an oversized one still spills
336+ * whole; the spans compact as a tree (see {@link compactTraceSpanTree}).
300337 */
301338export async function compactBlockOutput < T > (
302339 output : T ,
303340 options : CompactExecutionPayloadOptions = { }
304- ) : Promise < T > {
341+ ) : Promise < CompactedBlockOutput < T > > {
305342 if ( ! isRecordLike ( output ) || ! ( 'childTraceSpans' in output ) ) {
306- return compactExecutionPayload ( output , options )
343+ return { output : await compactExecutionPayload ( output , options ) }
307344 }
308345 const { childTraceSpans, ...rest } = output
309- const [ compactedRest , compactedSpans ] = await Promise . all ( [
310- compactExecutionPayload ( rest , { ... options , preserveRoot : true } ) ,
311- compactTraceSpanTree ( childTraceSpans , options ) ,
346+ const [ compactedOutput , compactedSpans ] = await Promise . all ( [
347+ compactExecutionPayload ( rest , options ) ,
348+ compactTraceSpanTree ( childTraceSpans , options , new WeakSet < object > ( ) ) ,
312349 ] )
313- return { ... compactedRest , childTraceSpans : compactedSpans } as T
350+ return { output : compactedOutput as T , childTraceSpans : compactedSpans }
314351}
315352
316353export async function compactBlockLogs (
@@ -335,12 +372,17 @@ export async function compactBlockLogs(
335372 compactedLog . input = await compactExecutionPayload ( compactedLog . input , options )
336373 }
337374 if ( 'output' in compactedLog ) {
338- compactedLog . output = await compactBlockOutput ( compactedLog . output , options )
375+ compactedLog . output = ( await compactLoggedOutput (
376+ compactedLog . output ,
377+ options ,
378+ new WeakSet < object > ( )
379+ ) ) as BlockLog [ 'output' ]
339380 }
340381 if ( 'childTraceSpans' in compactedLog ) {
341382 compactedLog . childTraceSpans = ( await compactTraceSpanTree (
342383 compactedLog . childTraceSpans ,
343- options
384+ options ,
385+ new WeakSet < object > ( )
344386 ) ) as BlockLog [ 'childTraceSpans' ]
345387 }
346388 compactedLogs [ index ] = compactedLog
0 commit comments