187 lines
5.0 KiB
TypeScript
187 lines
5.0 KiB
TypeScript
import { APICallError, EmptyResponseBodyError } from '@ai-sdk/provider';
|
|
import { extractResponseHeaders } from './extract-response-headers';
|
|
import { parseJSON, ParseResult, safeParseJSON } from './parse-json';
|
|
import { parseJsonEventStream } from './parse-json-event-stream';
|
|
import { FlexibleSchema } from './schema';
|
|
|
|
export type ResponseHandler<RETURN_TYPE> = (options: {
|
|
url: string;
|
|
requestBodyValues: unknown;
|
|
response: Response;
|
|
}) => PromiseLike<{
|
|
value: RETURN_TYPE;
|
|
rawValue?: unknown;
|
|
responseHeaders?: Record<string, string>;
|
|
}>;
|
|
|
|
export const createJsonErrorResponseHandler =
|
|
<T>({
|
|
errorSchema,
|
|
errorToMessage,
|
|
isRetryable,
|
|
}: {
|
|
errorSchema: FlexibleSchema<T>;
|
|
errorToMessage: (error: T) => string;
|
|
isRetryable?: (response: Response, error?: T) => boolean;
|
|
}): ResponseHandler<APICallError> =>
|
|
async ({ response, url, requestBodyValues }) => {
|
|
const responseBody = await response.text();
|
|
const responseHeaders = extractResponseHeaders(response);
|
|
|
|
// Some providers return an empty response body for some errors:
|
|
if (responseBody.trim() === '') {
|
|
return {
|
|
responseHeaders,
|
|
value: new APICallError({
|
|
message: response.statusText,
|
|
url,
|
|
requestBodyValues,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody,
|
|
isRetryable: isRetryable?.(response),
|
|
}),
|
|
};
|
|
}
|
|
|
|
// resilient parsing in case the response is not JSON or does not match the schema:
|
|
try {
|
|
const parsedError = await parseJSON({
|
|
text: responseBody,
|
|
schema: errorSchema,
|
|
});
|
|
|
|
return {
|
|
responseHeaders,
|
|
value: new APICallError({
|
|
message: errorToMessage(parsedError),
|
|
url,
|
|
requestBodyValues,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody,
|
|
data: parsedError,
|
|
isRetryable: isRetryable?.(response, parsedError),
|
|
}),
|
|
};
|
|
} catch (parseError) {
|
|
return {
|
|
responseHeaders,
|
|
value: new APICallError({
|
|
message: response.statusText,
|
|
url,
|
|
requestBodyValues,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody,
|
|
isRetryable: isRetryable?.(response),
|
|
}),
|
|
};
|
|
}
|
|
};
|
|
|
|
export const createEventSourceResponseHandler =
|
|
<T>(
|
|
chunkSchema: FlexibleSchema<T>,
|
|
): ResponseHandler<ReadableStream<ParseResult<T>>> =>
|
|
async ({ response }: { response: Response }) => {
|
|
const responseHeaders = extractResponseHeaders(response);
|
|
|
|
if (response.body == null) {
|
|
throw new EmptyResponseBodyError({});
|
|
}
|
|
|
|
return {
|
|
responseHeaders,
|
|
value: parseJsonEventStream({
|
|
stream: response.body,
|
|
schema: chunkSchema,
|
|
}),
|
|
};
|
|
};
|
|
|
|
export const createJsonResponseHandler =
|
|
<T>(responseSchema: FlexibleSchema<T>): ResponseHandler<T> =>
|
|
async ({ response, url, requestBodyValues }) => {
|
|
const responseBody = await response.text();
|
|
|
|
const parsedResult = await safeParseJSON({
|
|
text: responseBody,
|
|
schema: responseSchema,
|
|
});
|
|
|
|
const responseHeaders = extractResponseHeaders(response);
|
|
|
|
if (!parsedResult.success) {
|
|
throw new APICallError({
|
|
message: 'Invalid JSON response',
|
|
cause: parsedResult.error,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody,
|
|
url,
|
|
requestBodyValues,
|
|
});
|
|
}
|
|
|
|
return {
|
|
responseHeaders,
|
|
value: parsedResult.value,
|
|
rawValue: parsedResult.rawValue,
|
|
};
|
|
};
|
|
|
|
export const createBinaryResponseHandler =
|
|
(): ResponseHandler<Uint8Array> =>
|
|
async ({ response, url, requestBodyValues }) => {
|
|
const responseHeaders = extractResponseHeaders(response);
|
|
|
|
if (!response.body) {
|
|
throw new APICallError({
|
|
message: 'Response body is empty',
|
|
url,
|
|
requestBodyValues,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody: undefined,
|
|
});
|
|
}
|
|
|
|
try {
|
|
const buffer = await response.arrayBuffer();
|
|
return {
|
|
responseHeaders,
|
|
value: new Uint8Array(buffer),
|
|
};
|
|
} catch (error) {
|
|
throw new APICallError({
|
|
message: 'Failed to read response as array buffer',
|
|
url,
|
|
requestBodyValues,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody: undefined,
|
|
cause: error,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const createStatusCodeErrorResponseHandler =
|
|
(): ResponseHandler<APICallError> =>
|
|
async ({ response, url, requestBodyValues }) => {
|
|
const responseHeaders = extractResponseHeaders(response);
|
|
const responseBody = await response.text();
|
|
|
|
return {
|
|
responseHeaders,
|
|
value: new APICallError({
|
|
message: response.statusText,
|
|
url,
|
|
requestBodyValues: requestBodyValues as Record<string, unknown>,
|
|
statusCode: response.status,
|
|
responseHeaders,
|
|
responseBody,
|
|
}),
|
|
};
|
|
};
|