diff --git a/agentscope-core/src/main/java/io/agentscope/core/util/JacksonJsonCodec.java b/agentscope-core/src/main/java/io/agentscope/core/util/JacksonJsonCodec.java index 08d6be9ee5..7a1cdf64dc 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/util/JacksonJsonCodec.java +++ b/agentscope-core/src/main/java/io/agentscope/core/util/JacksonJsonCodec.java @@ -16,7 +16,9 @@ package io.agentscope.core.util; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; @@ -33,6 +35,9 @@ * Jackson's ObjectMapper with the following configuration: *
Users can access the underlying ObjectMapper via {@link #getObjectMapper()} @@ -69,7 +74,20 @@ public JacksonJsonCodec(ObjectMapper objectMapper) { * @return configured ObjectMapper */ private static ObjectMapper createDefaultObjectMapper() { - ObjectMapper mapper = new ObjectMapper(); + // toJson puts no cap on string values, yet Jackson's default read constraints reject + // any single string above 20,000,000 characters. Every AgentStateStore persists + // through this codec and inline Base64 media routinely exceeds that, so the default + // turned a successful save into a state that could never be loaded again (#3465). + // The reader must accept whatever the writer emitted; nesting depth, number and name + // length keep their defaults. + JsonFactory factory = + JsonFactory.builder() + .streamReadConstraints( + StreamReadConstraints.builder() + .maxStringLength(Integer.MAX_VALUE) + .build()) + .build(); + ObjectMapper mapper = new ObjectMapper(factory); mapper.registerModule(new JavaTimeModule()); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return mapper; diff --git a/agentscope-core/src/test/java/io/agentscope/core/util/JacksonJsonCodecTest.java b/agentscope-core/src/test/java/io/agentscope/core/util/JacksonJsonCodecTest.java index 7cd986e211..41299aac54 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/util/JacksonJsonCodecTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/util/JacksonJsonCodecTest.java @@ -273,4 +273,20 @@ void testRoundTripSerializationNested() { assertEquals(original.author.age, deserialized.author.age); assertEquals(original.tags, deserialized.tags); } + + @Test + void roundTripsStringsLongerThanJacksonDefaultReadLimit() { + // Jackson's default StreamReadConstraints reject any single string value above + // 20,000,000 characters on read, while toJson imposes no cap. Every AgentStateStore + // persists through this codec and inline Base64 media routinely exceeds that, so a + // state that saved successfully must stay loadable: what the codec writes, it reads. + String value = "a".repeat(20_000_001); + SimpleModel original = new SimpleModel(value, 1); + + String json = codec.toJson(original); + SimpleModel deserialized = codec.fromJson(json, SimpleModel.class); + + assertEquals(value.length(), deserialized.name.length()); + assertEquals(value, deserialized.name); + } }