diff --git a/CHANGELOG.md b/CHANGELOG.md index 1415ba7dcfb..1683cd75f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,7 @@ Docs: https://docs.openclaw.ai - Tavily: report malformed search and extract API JSON with provider-owned errors instead of leaking raw parser failures. - Perplexity: report malformed Search API and chat completion JSON with provider-owned errors instead of leaking raw parser failures. - Exa: report malformed search API JSON with a provider-owned error instead of leaking raw parser failures. +- Memory host SDK: report malformed remote JSON with caller-scoped errors for POST and batch file upload responses instead of leaking raw parser failures. - Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. - Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures. - Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects. diff --git a/packages/memory-host-sdk/src/host/batch-upload.test.ts b/packages/memory-host-sdk/src/host/batch-upload.test.ts new file mode 100644 index 00000000000..1074e63e769 --- /dev/null +++ b/packages/memory-host-sdk/src/host/batch-upload.test.ts @@ -0,0 +1,41 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { uploadBatchJsonlFile } from "./batch-upload.js"; +import { withRemoteHttpResponse } from "./remote-http.js"; + +vi.mock("./remote-http.js", () => ({ + withRemoteHttpResponse: vi.fn(), +})); + +const remoteHttpMock = vi.mocked(withRemoteHttpResponse); + +function textResponse(body: string, status: number): Response { + return { + ok: status >= 200 && status < 300, + status, + json: async () => JSON.parse(body) as unknown, + text: async () => body, + } as Response; +} + +describe("uploadBatchJsonlFile", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("wraps malformed file-upload JSON with the request error prefix", async () => { + remoteHttpMock.mockImplementationOnce(async (params) => { + return await params.onResponse(textResponse("{ nope", 200)); + }); + + await expect( + uploadBatchJsonlFile({ + client: { + baseUrl: "https://memory.example/v1", + headers: { Authorization: "Bearer test" }, + }, + requests: [{ input: "one" }], + errorPrefix: "file upload failed", + }), + ).rejects.toThrow("file upload failed: malformed JSON response"); + }); +}); diff --git a/packages/memory-host-sdk/src/host/batch-upload.ts b/packages/memory-host-sdk/src/host/batch-upload.ts index 9b5b91531df..e7fcc6eb272 100644 --- a/packages/memory-host-sdk/src/host/batch-upload.ts +++ b/packages/memory-host-sdk/src/host/batch-upload.ts @@ -34,7 +34,11 @@ export async function uploadBatchJsonlFile(params: { const text = await fileRes.text(); throw new Error(`${params.errorPrefix}: ${fileRes.status} ${text}`); } - return (await fileRes.json()) as { id?: string }; + try { + return (await fileRes.json()) as { id?: string }; + } catch (cause) { + throw new Error(`${params.errorPrefix}: malformed JSON response`, { cause }); + } }, }); if (!filePayload.id) { diff --git a/packages/memory-host-sdk/src/host/post-json.test.ts b/packages/memory-host-sdk/src/host/post-json.test.ts index 2a9dd233202..599ffa4c151 100644 --- a/packages/memory-host-sdk/src/host/post-json.test.ts +++ b/packages/memory-host-sdk/src/host/post-json.test.ts @@ -70,4 +70,20 @@ describe("postJson", () => { expect((error as Error).message).toBe("post failed: 502 bad gateway"); expect((error as { status?: unknown }).status).toBe(502); }); + + it("wraps malformed success JSON with the request error prefix", async () => { + remoteHttpMock.mockImplementationOnce(async (params) => { + return await params.onResponse(textResponse("{ nope", 200)); + }); + + await expect( + postJson({ + url: "https://memory.example/v1/post", + headers: {}, + body: {}, + errorPrefix: "post failed", + parse: () => ({}), + }), + ).rejects.toThrow("post failed: malformed JSON response"); + }); }); diff --git a/packages/memory-host-sdk/src/host/post-json.ts b/packages/memory-host-sdk/src/host/post-json.ts index 1e71eec4920..471e8bce1a7 100644 --- a/packages/memory-host-sdk/src/host/post-json.ts +++ b/packages/memory-host-sdk/src/host/post-json.ts @@ -31,7 +31,15 @@ export async function postJson(params: { } throw err; } - return await params.parse(await res.json()); + return await params.parse(await readJsonResponse(res, params.errorPrefix)); }, }); } + +async function readJsonResponse(res: Response, errorPrefix: string): Promise { + try { + return await res.json(); + } catch (cause) { + throw new Error(`${errorPrefix}: malformed JSON response`, { cause }); + } +}