mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(agents): keep tool-result context guard truncation UTF-16 safe (#102466)
* fix(agents): keep tool-result context guard truncation UTF-16 safe Replace the raw text.slice(0, cutPoint) in truncateTextToBudget with truncateUtf16Safe so oversized tool results do not emit lone surrogates when an emoji falls on the truncation boundary. Adds a regression test that places an emoji exactly at the legacy cut point and asserts the truncated output contains no lone surrogates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(agents): report surrogate-safe omitted count --------- Co-authored-by: chengzhichao-xydt <chengzhichao-xydt@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
committed by
GitHub
parent
00e323c990
commit
bc6a4bdd94
@@ -327,6 +327,26 @@ describe("installToolResultContextGuard", () => {
|
||||
expectOpenClawTruncation(getToolResultText(transformed[0]));
|
||||
});
|
||||
|
||||
it("truncates UTF-16 tool results without splitting surrogate pairs", async () => {
|
||||
// With contextWindowTokens=1000, maxSingleToolResultChars=1024 and the
|
||||
// text budget becomes 512. The legacy cut point falls inside the emoji
|
||||
// at index 439, which used to emit a lone high surrogate.
|
||||
const agent = makeGuardableAgent();
|
||||
const text = "a".repeat(439) + "😀" + "b".repeat(1_000);
|
||||
const contextForNextCall = [makeToolResult("call_utf16", text)];
|
||||
|
||||
const transformed = (await applyGuardToContext(
|
||||
agent,
|
||||
contextForNextCall,
|
||||
1_000,
|
||||
)) as AgentMessage[];
|
||||
|
||||
expect(getToolResultText(transformed[0])).toBe(
|
||||
"a".repeat(439) + formatContextLimitTruncationNotice(1_002),
|
||||
);
|
||||
expect(getToolResultText(contextForNextCall[0])).toBe(text);
|
||||
});
|
||||
|
||||
it("raises a structured mid-turn precheck signal after a new tool result overflows", async () => {
|
||||
// The signal carries route metadata so the run loop can compact/truncate
|
||||
// without guessing from a generic overflow error.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
/**
|
||||
* Installs context guards for oversized tool-result histories.
|
||||
*/
|
||||
@@ -164,8 +165,8 @@ function truncateTextToBudget(text: string, maxChars: number): string {
|
||||
cutPoint = newline;
|
||||
}
|
||||
|
||||
const omittedChars = text.length - cutPoint;
|
||||
return text.slice(0, cutPoint) + formatContextLimitTruncationNotice(omittedChars);
|
||||
const prefix = truncateUtf16Safe(text, cutPoint);
|
||||
return prefix + formatContextLimitTruncationNotice(text.length - prefix.length);
|
||||
}
|
||||
|
||||
function replaceToolResultText(msg: AgentMessage, text: string): AgentMessage {
|
||||
|
||||
Reference in New Issue
Block a user