diff --git a/src/error.ts b/src/error.ts index b49eb8e2..67b045c4 100644 --- a/src/error.ts +++ b/src/error.ts @@ -52,7 +52,7 @@ export class APIError extends AnthropicError { headers: Headers | undefined, ) { if (!status) { - return new APIConnectionError({ cause: castToError(errorResponse) }); + return new APIConnectionError({ message, cause: castToError(errorResponse) }); } const error = errorResponse as Record; @@ -104,7 +104,7 @@ export class APIUserAbortError extends APIError { export class APIConnectionError extends APIError { override readonly status: undefined = undefined; - constructor({ message, cause }: { message?: string; cause?: Error | undefined }) { + constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { super(undefined, undefined, message || 'Connection error.', undefined); // in some environments the 'cause' property is already declared // @ts-ignore diff --git a/src/streaming.ts b/src/streaming.ts index 973f8f8f..8478dcd2 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,7 +1,7 @@ import { ReadableStream, type Response } from './_shims/index'; import { AnthropicError } from './error'; -import { safeJSON, createResponseHeaders } from '@anthropic-ai/sdk/core'; +import { createResponseHeaders } from '@anthropic-ai/sdk/core'; import { APIError } from '@anthropic-ai/sdk/error'; type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; @@ -65,11 +65,12 @@ export class Stream implements AsyncIterable { } if (sse.event === 'error') { - const errText = sse.data; - const errJSON = safeJSON(errText); - const errMessage = errJSON ? undefined : errText; - - throw APIError.generate(undefined, errJSON, errMessage, createResponseHeaders(response.headers)); + throw APIError.generate( + undefined, + `SSE Error: ${sse.data}`, + sse.data, + createResponseHeaders(response.headers), + ); } } done = true; diff --git a/tests/streaming.test.ts b/tests/streaming.test.ts index 3e4b8ac6..d82b7484 100644 --- a/tests/streaming.test.ts +++ b/tests/streaming.test.ts @@ -1,7 +1,8 @@ import { Response } from 'node-fetch'; import { PassThrough } from 'stream'; import assert from 'assert'; -import { _iterSSEMessages, _decodeChunks as decodeChunks } from '@anthropic-ai/sdk/streaming'; +import { Stream, _iterSSEMessages, _decodeChunks as decodeChunks } from '@anthropic-ai/sdk/streaming'; +import { APIConnectionError } from '@anthropic-ai/sdk/error'; describe('line decoder', () => { test('basic', () => { @@ -247,6 +248,28 @@ describe('streaming decoding', () => { }); }); +test('error handling', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: error\n'); + yield Buffer.from('data: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}'); + yield Buffer.from('\n\n'); + } + + const stream = Stream.fromSSEResponse(new Response(await iteratorToStream(body())), new AbortController()); + + const err = expect( + (async () => { + for await (const _event of stream) { + } + })(), + ).rejects; + + await err.toMatchInlineSnapshot( + `[Error: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}]`, + ); + await err.toBeInstanceOf(APIConnectionError); +}); + async function iteratorToStream(iterator: AsyncGenerator): Promise { const parts: unknown[] = [];