fix(agents): keep web search error serialization surrogate-safe (#111327)

* fix(agents): keep web search error serialization surrogate-safe

* test(agents): pin web search truncation prefix

Preserve the pre-existing provider_error fallback while proving the exact UTF-16-safe retained prefix.\n\nCo-authored-by: zenglingbiao <zeng.lingbiao@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
zengLingbiao
2026-07-19 09:39:04 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent 269099585a
commit a70f7702a9
2 changed files with 19 additions and 1 deletions
+2 -1
View File
@@ -8,6 +8,7 @@
* spoof the trust marker and transport-specific extras never reach the model.
*/
import { isRecord } from "@openclaw/normalization-core/record-coerce";
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import type { Static } from "typebox";
import { Type } from "typebox";
import { wrapWebContent } from "../../security/external-content.js";
@@ -211,7 +212,7 @@ export function normalizeWebSearchOutput(params: {
const rawError =
typeof result.error === "string"
? result.error
: (JSON.stringify(result.error)?.slice(0, 2_000) ?? "provider_error");
: truncateUtf16Safe(JSON.stringify(result.error) ?? "provider_error", 2_000);
const rawMessage = typeof result.message === "string" ? result.message : rawError;
const docs = typeof result.docs === "string" ? toHttpUrl(result.docs) : undefined;
return {
+17
View File
@@ -721,6 +721,23 @@ describe("web_search normalized output contract", () => {
expect(normalized.message).toContain("quota exceeded");
});
it("keeps structured provider error serialization surrogate-safe at the cap", () => {
const normalized = normalizeWebSearchOutput({
provider: "external-demo",
query: "emoji error",
// The emoji straddles the 2000-unit cut: a plain slice would keep only
// its high surrogate half.
result: { error: { message: `${"x".repeat(1_987)}🤖` } },
});
if (normalized.kind !== "error") {
throw new Error("expected error branch");
}
const expectedPrefix = `{"message":"${"x".repeat(1_987)}`;
expect(stripWrapMarkers(normalized.message)).toBe(expectedPrefix);
expect(expectedPrefix).toHaveLength(1_999);
});
it("keeps ordinary Source attribution lines in answer content", () => {
const normalized = normalizeWebSearchOutput({
provider: "external-answer",