Skip to content

Commit 5624769

Browse files
Merge 39f1ee1 into 363cf04
2 parents 363cf04 + 39f1ee1 commit 5624769

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,31 @@ import {
66
createProviderHttpError,
77
extractProviderErrorDetail,
88
extractProviderRequestId,
9+
formatProviderErrorPayload,
910
ProviderHttpError,
1011
readProviderBinaryResponse,
1112
readProviderJsonResponse,
1213
readProviderTextResponse,
1314
readResponseTextLimited,
1415
} from "./provider-http-errors.js";
1516

17+
function hasLoneSurrogate(s: string): boolean {
18+
for (let i = 0; i < s.length; i += 1) {
19+
const c = s.charCodeAt(i);
20+
if (c >= 0xd800 && c <= 0xdbff) {
21+
if (i + 1 >= s.length || s.charCodeAt(i + 1) < 0xdc00 || s.charCodeAt(i + 1) > 0xdfff) {
22+
return true;
23+
}
24+
}
25+
if (c >= 0xdc00 && c <= 0xdfff) {
26+
if (i === 0 || s.charCodeAt(i - 1) < 0xd800 || s.charCodeAt(i - 1) > 0xdbff) {
27+
return true;
28+
}
29+
}
30+
}
31+
return false;
32+
}
33+
1634
function createStreamingBinaryResponse(params: {
1735
chunkCount: number;
1836
chunkSize: number;
@@ -136,6 +154,21 @@ describe("provider error utils", () => {
136154
);
137155
});
138156

157+
it("does not split UTF-16 surrogate pairs when truncating provider error details", async () => {
158+
const message = "a".repeat(218) + "😀" + "suffix";
159+
const response = new Response(
160+
JSON.stringify({
161+
error: { message, code: "utf16_test" },
162+
}),
163+
{ status: 400 },
164+
);
165+
166+
await expect(assertOkOrThrowProviderError(response, "Provider API error")).rejects.toThrow();
167+
const detail = formatProviderErrorPayload({ error: { message, code: "utf16_test" } });
168+
expect(detail).toContain("utf16_test");
169+
expect(hasLoneSurrogate(detail ?? "")).toBe(false);
170+
});
171+
139172
it("keeps HTTP status metadata when error body reads fail", async () => {
140173
const response = {
141174
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)