diff --git a/typescript-sdk/packages/client/src/run/http-request.ts b/typescript-sdk/packages/client/src/run/http-request.ts index 3e755a0be..0243fab47 100644 --- a/typescript-sdk/packages/client/src/run/http-request.ts +++ b/typescript-sdk/packages/client/src/run/http-request.ts @@ -76,7 +76,13 @@ export const runHttpRequest = (url: string, requestInit: RequestInit): Observabl })(); return () => { - reader.cancel(); + reader.cancel().catch((error) => { + if ((error as DOMException)?.name === "AbortError") { + return; + } + + throw error; + }); }; }); }), diff --git a/typescript-sdk/packages/client/src/transform/__tests__/http.test.ts b/typescript-sdk/packages/client/src/transform/__tests__/http.test.ts index c78b4036b..64b9c825a 100644 --- a/typescript-sdk/packages/client/src/transform/__tests__/http.test.ts +++ b/typescript-sdk/packages/client/src/transform/__tests__/http.test.ts @@ -54,6 +54,40 @@ describe("transformHttpEventStream", () => { expect(receivedEvents).toEqual([mockBaseEvent]); }); + test("should emit RUN_ERROR and complete on AbortError without erroring", () => { + const mockHttpSource = new Subject(); + const receivedEvents: BaseEvent[] = []; + let completed = false; + let receivedError: unknown = undefined; + + const result$ = transformHttpEventStream(mockHttpSource); + result$.subscribe({ + next: (event) => receivedEvents.push(event), + error: (err) => { + receivedError = err; + }, + complete: () => { + completed = true; + }, + }); + + mockHttpSource.next({ + type: HttpEventType.HEADERS, + status: 200, + headers: new Headers([["content-type", "text/event-stream"]]), + }); + + const abortError = { name: "AbortError" } as DOMException; + mockHttpSource.error(abortError); + + expect(receivedEvents).toHaveLength(1); + expect(receivedEvents[0].type).toBe(EventType.RUN_ERROR); + const runErrorEvent = receivedEvents[0] as any; + expect(runErrorEvent.rawEvent).toBe(abortError); + expect(completed).toBe(true); + expect(receivedError).toBeUndefined(); + }); + test("should handle parseProtoStream errors", (done) => { // Given const mockHttpSource = new Subject(); diff --git a/typescript-sdk/packages/client/src/transform/http.ts b/typescript-sdk/packages/client/src/transform/http.ts index 3d4f594f2..a7a0e896d 100644 --- a/typescript-sdk/packages/client/src/transform/http.ts +++ b/typescript-sdk/packages/client/src/transform/http.ts @@ -4,6 +4,7 @@ import { HttpEvent, HttpEventType } from "../run/http-request"; import { parseSSEStream } from "./sse"; import { parseProtoStream } from "./proto"; import * as proto from "@ag-ui/proto"; +import { EventType } from "@ag-ui/core"; /** * Transforms HTTP events into BaseEvents using the appropriate format parser based on content type. @@ -47,7 +48,17 @@ export const transformHttpEventStream = (source$: Observable): Observ eventSubject.error(err); } }, - error: (err) => eventSubject.error(err), + error: (err) => { + if ((err as DOMException)?.name === "AbortError") { + eventSubject.next({ + type: EventType.RUN_ERROR, + rawEvent: err, + }); + eventSubject.complete(); + return; + } + return eventSubject.error(err) + }, complete: () => eventSubject.complete(), }); }