( url: string | URL | Request, options?: FetchOptions, )
| 153 | * Enhanced fetch wrapper that adds logging, authentication, error handling, and optional compression |
| 154 | */ |
| 155 | export async function monkeyPatchFetch( |
| 156 | url: string | URL | Request, |
| 157 | options?: FetchOptions, |
| 158 | ): Promise<Response> { |
| 159 | const NO_LOG_URLS = [R_ENDPOINT, CONSENT_ENDPOINT, EVENTS_ENDPOINT]; |
| 160 | const urlString = getRequestUrlString(url); |
| 161 | const callerHeaders = getEffectiveHeaders(url, options?.headers); |
| 162 | const isSilent = new Headers(callerHeaders).get('x-promptfoo-silent') === 'true'; |
| 163 | const logEnabled = !NO_LOG_URLS.some((logUrl) => matchesNoLogUrl(urlString, logUrl)) && !isSilent; |
| 164 | |
| 165 | const opts: RequestInit = { |
| 166 | ...options, |
| 167 | }; |
| 168 | |
| 169 | const originalBody = opts.body; |
| 170 | |
| 171 | // Handle compression if requested |
| 172 | if (options?.compress && opts.body && typeof opts.body === 'string') { |
| 173 | try { |
| 174 | const compressed = await gzipAsync(opts.body); |
| 175 | opts.body = compressed as BodyInit; |
| 176 | opts.headers = setHeader(getEffectiveHeaders(url, opts.headers), 'Content-Encoding', 'gzip'); |
| 177 | } catch (e) { |
| 178 | logger.warn(`Failed to compress request body: ${e}`); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | // Attach the saved cloud credential only for cloud-bound requests, and never |
| 183 | // override an Authorization header the caller set explicitly — token |
| 184 | // validation/rotation sends the token being validated, not the saved one. |
| 185 | const cloudAuth = getCloudBearerToken(url); |
| 186 | const effectiveHeaders = getEffectiveHeaders(url, opts.headers); |
| 187 | if (cloudAuth && !hasAuthorizationHeader(effectiveHeaders)) { |
| 188 | opts.headers = setHeader(effectiveHeaders, 'Authorization', cloudAuth); |
| 189 | } |
| 190 | |
| 191 | const cloudTaskTeamId = getCloudTaskTeamId(url); |
| 192 | const headersWithAuth = getEffectiveHeaders(url, opts.headers); |
| 193 | if (cloudTaskTeamId && !hasHeader(headersWithAuth, PROMPTFOO_TEAM_ID_HEADER)) { |
| 194 | opts.headers = setHeader(headersWithAuth, PROMPTFOO_TEAM_ID_HEADER, cloudTaskTeamId); |
| 195 | } |
| 196 | try { |
| 197 | // biome-ignore lint/style/noRestrictedGlobals: we need raw fetch here |
| 198 | const response = await fetch(url, opts); |
| 199 | |
| 200 | if (logEnabled) { |
| 201 | void logRequestResponse({ |
| 202 | url: urlString, |
| 203 | requestBody: originalBody, |
| 204 | requestMethod: opts.method || 'GET', |
| 205 | response, |
| 206 | }); |
| 207 | } |
| 208 | |
| 209 | return response; |
| 210 | } catch (e) { |
| 211 | if (logEnabled) { |
| 212 | void logRequestResponse({ |
no test coverage detected
searching dependent graphs…