mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(inworld): use truncateUtf16Safe for error body and parse error truncation (#102608)
* fix(inworld): use truncateUtf16Safe for error body and parse error truncation Two .slice(0, N) truncation sites in the Inworld TTS extension may cut UTF-16 surrogate pairs in half when the error body or parse error message contains multi-byte characters such as emoji. Replace both with truncateUtf16Safe to keep the truncated output valid Unicode. * test(inworld): cover UTF-16-safe TTS errors Co-authored-by: lizeyu-xydt <li.zeyu@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
1e377e3660
commit
08663917fa
2 changed files with 15 additions and 6 deletions
|
|
@ -228,6 +228,14 @@ describe("inworldTTS", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("keeps truncated HTTP error bodies UTF-16 safe", async () => {
|
||||
queueGuardedResponse(new Response(`${"e".repeat(399)}😀tail`, { status: 400 }));
|
||||
|
||||
await expect(inworldTTS({ text: "test", apiKey: "test-key" })).rejects.toMatchObject({
|
||||
message: `Inworld TTS API error (400): ${"e".repeat(399)}…`,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws on in-stream errors", async () => {
|
||||
const body = JSON.stringify({
|
||||
error: { code: 3, message: "Invalid voice ID" },
|
||||
|
|
@ -249,11 +257,11 @@ describe("inworldTTS", () => {
|
|||
});
|
||||
|
||||
it("throws descriptive error on non-JSON line in stream", async () => {
|
||||
queueGuardedResponse(new Response("<html>Rate limited</html>", { status: 200 }));
|
||||
queueGuardedResponse(new Response(`${"p".repeat(79)}😀tail`, { status: 200 }));
|
||||
|
||||
await expect(inworldTTS({ text: "test", apiKey: "test-key" })).rejects.toThrow(
|
||||
"Inworld TTS stream parse error: unexpected non-JSON line:",
|
||||
);
|
||||
await expect(inworldTTS({ text: "test", apiKey: "test-key" })).rejects.toMatchObject({
|
||||
message: `Inworld TTS stream parse error: unexpected non-JSON line: ${"p".repeat(79)}`,
|
||||
});
|
||||
});
|
||||
|
||||
it("sends correct request body with defaults", async () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { MAX_AUDIO_BYTES } from "openclaw/plugin-sdk/media-runtime";
|
|||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech-core";
|
||||
import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
|
||||
const DEFAULT_INWORLD_BASE_URL = "https://api.inworld.ai";
|
||||
export const DEFAULT_INWORLD_VOICE_ID = "Sarah";
|
||||
|
|
@ -57,7 +58,7 @@ async function readInworldErrorBodySnippet(response: Response): Promise<string>
|
|||
|
||||
const collapsed = buffer.toString("utf8").replace(/\s+/g, " ").trim();
|
||||
if (collapsed.length > INWORLD_ERROR_BODY_MAX_CHARS) {
|
||||
return `${collapsed.slice(0, INWORLD_ERROR_BODY_MAX_CHARS)}…`;
|
||||
return `${truncateUtf16Safe(collapsed, INWORLD_ERROR_BODY_MAX_CHARS)}…`;
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
|
|
@ -176,7 +177,7 @@ export async function inworldTTS(params: {
|
|||
parsed = JSON.parse(trimmed) as typeof parsed;
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Inworld TTS stream parse error: unexpected non-JSON line: ${trimmed.slice(0, 80)}`,
|
||||
`Inworld TTS stream parse error: unexpected non-JSON line: ${truncateUtf16Safe(trimmed, 80)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue