import { APICallError } from "@ai-sdk/provider"; import { extractResponseHeaders, isAbortError, withUserAgentSuffix } from "@ai-sdk/provider-utils"; import { type FetchFunction, type ResponseHandler } from "@ai-sdk/provider-utils"; const FETCH_FAILED_ERROR_MESSAGES = ["fetch failed", "failed to fetch"]; export function handleFetchError({ error, url, requestBodyValues, }: { error: unknown; url: string; requestBodyValues: unknown; }) { if (isAbortError(error)) { return error; } if ( error instanceof TypeError && FETCH_FAILED_ERROR_MESSAGES.includes(error.message.toLowerCase()) ) { const cause = (error as { cause?: Error }).cause; if (cause) { return new APICallError({ message: `Cannot connect to API: ${cause.message}`, cause, url, requestBodyValues, isRetryable: true, // retry when network error }); } } return error; } // use function to allow for mocking in tests: const getOriginalFetch = () => globalThis.fetch; export const getFromApi = async ({ url, headers = {}, successfulResponseHandler, failedResponseHandler, abortSignal, fetch = getOriginalFetch(), }: { url: string; headers?: Record; failedResponseHandler: ResponseHandler; successfulResponseHandler: ResponseHandler; abortSignal?: AbortSignal; fetch?: FetchFunction; }) => { try { const response = await fetch(url, { method: "GET", headers: withUserAgentSuffix(headers, `cartanova-ai/rtzr-api-provider`), signal: abortSignal, }); const responseHeaders = extractResponseHeaders(response); if (!response.ok) { let errorInformation: { value: Error; responseHeaders?: Record | undefined; }; try { errorInformation = await failedResponseHandler({ response, url, requestBodyValues: {}, }); } catch (error) { if (isAbortError(error) || APICallError.isInstance(error)) { throw error; } throw new APICallError({ message: "Failed to process error response", cause: error, statusCode: response.status, url, responseHeaders, requestBodyValues: {}, }); } throw errorInformation.value; } try { return await successfulResponseHandler({ response, url, requestBodyValues: {}, }); } catch (error) { if (error instanceof Error) { if (isAbortError(error) || APICallError.isInstance(error)) { throw error; } } throw new APICallError({ message: "Failed to process successful response", cause: error, statusCode: response.status, url, responseHeaders, requestBodyValues: {}, }); } } catch (error) { throw handleFetchError({ error, url, requestBodyValues: {} }); } };