diff --git a/ts/packages/aiclient/src/wireApiProvider/messages.types.ts b/ts/packages/aiclient/src/wireApiProvider/messages.types.ts index 02c294ee70..16db7f84d8 100644 --- a/ts/packages/aiclient/src/wireApiProvider/messages.types.ts +++ b/ts/packages/aiclient/src/wireApiProvider/messages.types.ts @@ -198,7 +198,16 @@ export interface RawContentBlockStopEvent { type: "content_block_stop"; } +export interface RawMessageErrorEvent { + type: "error"; + error: { + type: string; + message: string; + }; +} + export type RawMessageStreamEvent = + | RawMessageErrorEvent | RawMessageStartEvent | RawMessageDeltaEvent | RawMessageStopEvent diff --git a/ts/packages/aiclient/src/wireApiProvider/messagesWireApiProvider.ts b/ts/packages/aiclient/src/wireApiProvider/messagesWireApiProvider.ts index e0247298e1..11db3c5576 100644 --- a/ts/packages/aiclient/src/wireApiProvider/messagesWireApiProvider.ts +++ b/ts/packages/aiclient/src/wireApiProvider/messagesWireApiProvider.ts @@ -205,6 +205,11 @@ export class MessagesWireApiProvider implements ProviderAdapter { const evt = JSON.parse(raw) as RawMessageStreamEvent; const piece: StreamPiece = {}; + if (evt.type === "error") { + piece.error = new Error(evt.error.message); + return piece; + } + if ( evt.type === "content_block_delta" && evt.delta.type === "text_delta" diff --git a/ts/packages/aiclient/test/providerAdapter.spec.ts b/ts/packages/aiclient/test/providerAdapter.spec.ts index f22686cb5d..4afc132c89 100644 --- a/ts/packages/aiclient/test/providerAdapter.spec.ts +++ b/ts/packages/aiclient/test/providerAdapter.spec.ts @@ -294,6 +294,39 @@ describe("ChatCompletionsWireApiProvider (default path is byte-identical)", () = describe("MessagesWireApiProvider", () => { const adapter = adapterFor("messages"); + test.each([false, true])( + "stream reports provider errors (partial text: %s)", + (partialText) => { + const decoder = adapter.createStreamDecoder!( + makeRequest({ stream: true }), + ); + if (partialText) { + expect( + decoder.push( + JSON.stringify({ + type: "content_block_delta", + index: 0, + delta: { + type: "text_delta", + text: "Partial answer", + }, + }), + ).text, + ).toBe("Partial answer"); + } + const piece = decoder.push( + JSON.stringify({ + type: "error", + error: { type: "overloaded_error", message: "Overloaded" }, + }), + ); + expect(piece.error).toBeInstanceOf(Error); + expect(piece.error?.message).toBe("Overloaded"); + expect(piece.text).toBeUndefined(); + expect(piece.usage).toBeUndefined(); + }, + ); + test("buildRequestBody splits system prompt, requires max_tokens", () => { const req = makeRequest({ modelName: "claude-3-5-sonnet",