diff --git a/src/domain/search/providers/remote.ts b/src/domain/search/providers/remote.ts index d323cdabd..00ef79a8b 100644 --- a/src/domain/search/providers/remote.ts +++ b/src/domain/search/providers/remote.ts @@ -197,7 +197,16 @@ export async function embedRemote( batchNumber, ); - const json = (await response.json()) as OpenAIEmbeddingResponse; + let json: OpenAIEmbeddingResponse; + try { + json = (await response.json()) as OpenAIEmbeddingResponse; + } catch (err: unknown) { + throw new EngineError( + `Remote embedding endpoint ${url} returned a response that could not be parsed as JSON: ` + + `${err instanceof Error ? err.message : String(err)}`, + { cause: err instanceof Error ? err : undefined }, + ); + } const mapped = mapRemoteEmbeddingResponse(json, batch, url, dim); dim = mapped.dim; results.push(...mapped.vectors); diff --git a/tests/search/embedding-remote-provider.test.ts b/tests/search/embedding-remote-provider.test.ts index 43591cad6..a2decb3f5 100644 --- a/tests/search/embedding-remote-provider.test.ts +++ b/tests/search/embedding-remote-provider.test.ts @@ -154,6 +154,16 @@ describe('embedRemote', () => { ); }); + it('throws EngineError instead of a raw SyntaxError when the response body is not valid JSON', async () => { + fetchMock.mockResolvedValueOnce( + new Response('Bad Gateway', { status: 200 }), + ); + await expect(embedRemote(['a'], { baseUrl: 'http://x', model: 'm' })).rejects.toMatchObject({ + name: 'EngineError', + message: expect.stringContaining('could not be parsed as JSON'), + }); + }); + it('throws EngineError when the response shape does not match the input length', async () => { fetchMock.mockResolvedValueOnce( new Response(JSON.stringify({ data: [{ embedding: [1], index: 0 }] }), { status: 200 }),