3030import com .google .genai .types .FunctionCall ;
3131import com .google .genai .types .Part ;
3232import java .util .ArrayList ;
33+ import java .util .Collections ;
34+ import java .util .IdentityHashMap ;
3335import java .util .List ;
3436import java .util .Map ;
3537import java .util .Optional ;
@@ -304,8 +306,13 @@ static TruncationResult smartTruncate(Object obj, int maxLength) {
304306 if (obj == null ) {
305307 return TruncationResult .create (mapper .nullNode (), false );
306308 }
309+ if (obj instanceof JsonNode jsonNode ) {
310+ return recursiveSmartTruncate (
311+ jsonNode , maxLength , Collections .newSetFromMap (new IdentityHashMap <>()));
312+ }
307313 try {
308- return recursiveSmartTruncate (mapper .valueToTree (obj ), maxLength );
314+ return recursiveSmartTruncate (
315+ mapper .valueToTree (obj ), maxLength , Collections .newSetFromMap (new IdentityHashMap <>()));
309316 } catch (IllegalArgumentException e ) {
310317 // Fallback for types that mapper can't handle directly as a tree
311318 return truncateWithStatus (String .valueOf (obj ), maxLength );
@@ -324,33 +331,47 @@ static JsonNode convertToJsonNode(Object obj) {
324331 }
325332 }
326333
327- private static TruncationResult recursiveSmartTruncate (JsonNode node , int maxLength ) {
328- boolean isTruncated = false ;
329- if (node .isTextual ()) {
330- String text = node .asText ();
331- if (text .length () > maxLength ) {
332- return TruncationResult .create (mapper .valueToTree (truncate (text , maxLength )), true );
334+ private static TruncationResult recursiveSmartTruncate (
335+ JsonNode node , int maxLength , Set <JsonNode > visited ) {
336+ if (node .isContainerNode ()) {
337+ if (visited .contains (node )) {
338+ return TruncationResult .create (mapper .valueToTree ("[CYCLE DETECTED]" ), true );
333339 }
334- return TruncationResult .create (node , false );
335- } else if (node .isObject ()) {
336- ObjectNode newNode = mapper .createObjectNode ();
337- Set <Map .Entry <String , JsonNode >> properties = node .properties ();
338- for (Map .Entry <String , JsonNode > entry : properties ) {
339- TruncationResult res = recursiveSmartTruncate (entry .getValue (), maxLength );
340- newNode .set (entry .getKey (), res .node ());
341- isTruncated = isTruncated || res .isTruncated ();
340+ visited .add (node );
341+ }
342+
343+ try {
344+ boolean isTruncated = false ;
345+ if (node .isTextual ()) {
346+ String text = node .asText ();
347+ if (text .length () > maxLength ) {
348+ return TruncationResult .create (mapper .valueToTree (truncate (text , maxLength )), true );
349+ }
350+ return TruncationResult .create (node , false );
351+ } else if (node .isObject ()) {
352+ ObjectNode newNode = mapper .createObjectNode ();
353+ Set <Map .Entry <String , JsonNode >> properties = node .properties ();
354+ for (Map .Entry <String , JsonNode > entry : properties ) {
355+ TruncationResult res = recursiveSmartTruncate (entry .getValue (), maxLength , visited );
356+ newNode .set (entry .getKey (), res .node ());
357+ isTruncated = isTruncated || res .isTruncated ();
358+ }
359+ return TruncationResult .create (newNode , isTruncated );
360+ } else if (node .isArray ()) {
361+ ArrayNode newNode = mapper .createArrayNode ();
362+ for (JsonNode element : node ) {
363+ TruncationResult res = recursiveSmartTruncate (element , maxLength , visited );
364+ newNode .add (res .node ());
365+ isTruncated = isTruncated || res .isTruncated ();
366+ }
367+ return TruncationResult .create (newNode , isTruncated );
342368 }
343- return TruncationResult .create (newNode , isTruncated );
344- } else if (node .isArray ()) {
345- ArrayNode newNode = mapper .createArrayNode ();
346- for (JsonNode element : node ) {
347- TruncationResult res = recursiveSmartTruncate (element , maxLength );
348- newNode .add (res .node ());
349- isTruncated = isTruncated || res .isTruncated ();
369+ return TruncationResult .create (node , false );
370+ } finally {
371+ if (node .isContainerNode ()) {
372+ visited .remove (node );
350373 }
351- return TruncationResult .create (newNode , isTruncated );
352374 }
353- return TruncationResult .create (node , false );
354375 }
355376
356377 private static TruncationResult truncateWithStatus (String s , int maxLength ) {
0 commit comments