import { APICallError, NoResponseBodyError } from '@ai-sdk/provider'; import { EventSourceParserStream, ParsedEvent, } from 'eventsource-parser/stream'; import { ZodSchema } from 'zod'; import { ParseResult, parseJSON, safeParseJSON } from './parse-json'; export type ResponseHandler = (options: { url: string; requestBodyValues: unknown; response: Response; }) => PromiseLike; export const createJsonErrorResponseHandler = ({ errorSchema, errorToMessage, isRetryable, }: { errorSchema: ZodSchema; errorToMessage: (error: T) => string; isRetryable?: (response: Response, error?: T) => boolean; }): ResponseHandler => async ({ response, url, requestBodyValues }) => { const responseBody = await response.text(); // Some providers return an empty response body for some errors: if (responseBody.trim() === '') { return new APICallError({ message: response.statusText, url, requestBodyValues, statusCode: response.status, responseBody, isRetryable: isRetryable?.(response), }); } // resilient parsing in case the response is not JSON or does not match the schema: try { const parsedError = parseJSON({ text: responseBody, schema: errorSchema, }); return new APICallError({ message: errorToMessage(parsedError), url, requestBodyValues, statusCode: response.status, responseBody, data: parsedError, isRetryable: isRetryable?.(response, parsedError), }); } catch (parseError) { return new APICallError({ message: response.statusText, url, requestBodyValues, statusCode: response.status, responseBody, isRetryable: isRetryable?.(response), }); } }; export const createEventSourceResponseHandler = ( chunkSchema: ZodSchema, ): ResponseHandler>> => async ({ response }: { response: Response }) => { if (response.body == null) { throw new NoResponseBodyError(); } return response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()) .pipeThrough( new TransformStream>({ transform({ data }, controller) { // ignore the 'DONE' event that e.g. OpenAI sends: if (data === '[DONE]') { return; } controller.enqueue( safeParseJSON({ text: data, schema: chunkSchema, }), ); }, }), ); }; export const createJsonResponseHandler = (responseSchema: ZodSchema): ResponseHandler => async ({ response, url, requestBodyValues }) => { const responseBody = await response.text(); const parsedResult = safeParseJSON({ text: responseBody, schema: responseSchema, }); if (!parsedResult.success) { throw new APICallError({ message: 'Invalid JSON response', cause: parsedResult.error, statusCode: response.status, responseBody, url, requestBodyValues, }); } return parsedResult.value; };