Skip to content

Commit 30421af

Browse files
committed
fix(media): keep input_file text truncation UTF-16 safe
1 parent 6433b82 commit 30421af

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/gateway/openresponses-http.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,43 @@ describe("OpenResponses HTTP API (e2e)", () => {
18911891
await ensureResponseConsumed(res);
18921892
});
18931893

1894+
it("keeps base64 input_file text truncation UTF-16 safe", async () => {
1895+
const port = enabledPort;
1896+
const text = `${"a".repeat(59_999)}😀tail`;
1897+
agentCommand.mockClear();
1898+
agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never);
1899+
1900+
const res = await postResponses(port, {
1901+
model: "openclaw",
1902+
input: [
1903+
{
1904+
type: "message",
1905+
role: "user",
1906+
content: [
1907+
{
1908+
type: "input_file",
1909+
source: {
1910+
type: "base64",
1911+
media_type: "text/plain",
1912+
data: Buffer.from(text).toString("base64"),
1913+
filename: "emoji-boundary.txt",
1914+
},
1915+
},
1916+
],
1917+
},
1918+
],
1919+
});
1920+
1921+
expect(res.status).toBe(200);
1922+
expect(agentCommand).toHaveBeenCalledTimes(1);
1923+
const opts = firstAgentOpts();
1924+
const extraSystemPrompt = (opts as { extraSystemPrompt?: string }).extraSystemPrompt ?? "";
1925+
expect(extraSystemPrompt).toContain('<file name="emoji-boundary.txt">');
1926+
expect(extraSystemPrompt).not.toContain("😀");
1927+
expect(extraSystemPrompt).not.toMatch(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/u);
1928+
await ensureResponseConsumed(res);
1929+
});
1930+
18941931
it("still rejects input with neither text nor image", async () => {
18951932
const port = enabledPort;
18961933
agentCommand.mockClear();

src/media/input-files.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
normalizeOptionalLowercaseString,
77
normalizeOptionalString,
88
} from "@openclaw/normalization-core/string-coerce";
9+
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
910
import type { OpenClawConfig } from "../config/types.openclaw.js";
1011
import { readResponseWithLimit } from "../infra/http-body.js";
1112
import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
@@ -273,7 +274,7 @@ function clampText(text: string, maxChars: number): string {
273274
if (text.length <= maxChars) {
274275
return text;
275276
}
276-
return text.slice(0, maxChars);
277+
return truncateUtf16Safe(text, maxChars);
277278
}
278279

279280
function withInputFileTimeout<T>(params: {

0 commit comments

Comments
 (0)