From d853f25fbff9bb1796e113d627b8d1f8e310bc9a Mon Sep 17 00:00:00 2001 From: wahl <592211009@qq.com> Date: Sat, 10 Oct 2026 09:10:27 +0800 Subject: [PATCH] fix(core): let JacksonJsonCodec read back every string it can write Jackson's default StreamReadConstraints reject any single string value above 20,000,000 characters on read, while serialization imposes no cap at all. Every AgentStateStore -- JSON file, JDBC, Redis, MongoDB, OSS, COS, the Service JPA store -- persists through JsonUtils.getJsonCodec(), i.e. this codec's default ObjectMapper, and an AgentState carrying inline Base64 media (a 25 MB video is 33.5 million characters) saved successfully and then failed to load on the very next turn. Because the load happens before the model is called, every later turn of that session failed too, text-only ones included: a session bricked by its own persisted state. Configure the default ObjectMapper's JsonFactory with maxStringLength set to Integer.MAX_VALUE so the reader accepts whatever the writer emitted. Nesting depth, number length and name length keep their Jackson defaults; a caller- supplied ObjectMapper is left untouched. Strings this large only ever reach the codec from the framework's own persisted state -- model output and tool arguments are bounded far below the old limit -- so no untrusted boundary loses protection. Offloading media out of AgentState (#2867) remains the right long-term answer; this restores the round-trip invariant the stores already rely on. Fixes #3465 --- .../core/util/JacksonJsonCodec.java | 20 ++++++++++++++++++- .../core/util/JacksonJsonCodecTest.java | 16 +++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) 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); + } }