Skip to content

Commit 51bb5f6

Browse files
chengzhichao-xydtclaudesteipete
authored
fix(agents): keep provider error detail truncation UTF-16 safe (#102496)
* fix(agents): keep provider error detail truncation UTF-16 safe Replace the raw detail.slice(0, limit - 1) in truncateErrorDetail with truncateUtf16Safe so provider error previews do not emit lone surrogates when an emoji falls on the truncation boundary. Adds a regression test that places an emoji at the default truncation boundary and asserts the formatted detail contains no lone surrogates. Co-Authored-By: Claude Opus 4.8 <[email protected]> * test(agents): prove provider error UTF-16 boundary --------- Co-authored-by: chengzhichao-xydt <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent bacf048 commit 51bb5f6

2 files changed

Lines changed: 17 additions & 1 deletion

File tree

src/agents/provider-http-errors.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ describe("provider error utils", () => {
136136
);
137137
});
138138

139+
it("does not split UTF-16 surrogate pairs when truncating provider error details", async () => {
140+
const safePrefix = "a".repeat(218);
141+
const message = `${safePrefix}😀suffix`;
142+
const response = new Response(
143+
JSON.stringify({
144+
error: { message, code: "utf16_test" },
145+
}),
146+
{ status: 400 },
147+
);
148+
149+
await expect(assertOkOrThrowProviderError(response, "Provider API error")).rejects.toThrow(
150+
`Provider API error (400): ${safePrefix}… [code=utf16_test]`,
151+
);
152+
});
153+
139154
it("keeps HTTP status metadata when error body reads fail", async () => {
140155
const response = {
141156
ok: false,

src/agents/provider-http-errors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Transport adapters use this module to turn provider-specific response bodies,
55
* request ids, and binary payload guardrails into stable OpenClaw error shapes.
66
*/
7+
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
78
export { asFiniteNumber } from "../../packages/normalization-core/src/number-coercion.js";
89
import { normalizeOptionalString as trimToUndefined } from "../../packages/normalization-core/src/string-coerce.js";
910
import { readResponseWithLimit } from "../infra/http-body.js";
@@ -25,7 +26,7 @@ export function asObject(value: unknown): Record<string, unknown> | undefined {
2526

2627
/** Trims provider error details to a log- and prompt-safe preview length. */
2728
export function truncateErrorDetail(detail: string, limit = 220): string {
28-
return detail.length <= limit ? detail : `${detail.slice(0, limit - 1)}…`;
29+
return detail.length <= limit ? detail : `${truncateUtf16Safe(detail, limit - 1)}…`;
2930
}
3031

3132
/** Redacts secrets before preserving a bounded provider error body preview. */

0 commit comments

Comments
 (0)