Skip to content

Commit 8557283

Browse files
committed
fix(agents): preserve UTF-16 tool summary truncation
1 parent be2c4c6 commit 8557283

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
describeToolForVerbose,
4+
summarizeToolDescriptionText,
5+
} from "./tool-description-summary.js";
6+
7+
function hasDanglingSurrogate(value: string): boolean {
8+
return /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value);
9+
}
10+
11+
describe("tool description summaries", () => {
12+
it("keeps compact summaries UTF-16 safe at truncation boundaries", () => {
13+
const summary = summarizeToolDescriptionText({
14+
displaySummary: "abcd😀 efgh",
15+
maxLen: 8,
16+
});
17+
18+
expect(summary).toBe("abcd...");
19+
expect(hasDanglingSurrogate(summary)).toBe(false);
20+
});
21+
22+
it("keeps verbose descriptions UTF-16 safe at truncation boundaries", () => {
23+
const description = describeToolForVerbose({
24+
rawDescription: "abcd😀 efgh",
25+
fallback: "Tool",
26+
maxLen: 8,
27+
});
28+
29+
expect(description).toBe("abcd...");
30+
expect(hasDanglingSurrogate(description)).toBe(false);
31+
});
32+
});

src/agents/tool-description-summary.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
77
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
8+
import { truncateUtf16Safe } from "../shared/utf16-slice.js";
89

910
function normalizeSummaryWhitespace(value: string): string {
1011
return value.replace(/\s+/g, " ").trim();
@@ -14,7 +15,7 @@ function truncateSummary(value: string, maxLen = 120): string {
1415
if (value.length <= maxLen) {
1516
return value;
1617
}
17-
const sliced = value.slice(0, maxLen - 3);
18+
const sliced = truncateUtf16Safe(value, maxLen - 3);
1819
const boundary = sliced.lastIndexOf(" ");
1920
const trimmed = (boundary >= 48 ? sliced.slice(0, boundary) : sliced).trimEnd();
2021
return `${trimmed}...`;
@@ -136,7 +137,7 @@ export function describeToolForVerbose(params: {
136137
if (normalized.length <= maxLen) {
137138
return normalized;
138139
}
139-
const sliced = normalized.slice(0, maxLen - 3);
140+
const sliced = truncateUtf16Safe(normalized, maxLen - 3);
140141
const boundary = sliced.lastIndexOf(" ");
141142
return `${(boundary >= Math.floor(maxLen / 2) ? sliced.slice(0, boundary) : sliced).trimEnd()}...`;
142143
}

0 commit comments

Comments
 (0)