@ai-sdk/provider-utils 5.0.39 → 5.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/provider-utils",
3
- "version": "5.0.39",
3
+ "version": "5.0.40",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -32,7 +32,7 @@
32
32
  }
33
33
  },
34
34
  "dependencies": {
35
- "@ai-sdk/provider": "4.0.13",
35
+ "@ai-sdk/provider": "4.0.14",
36
36
  "@standard-schema/spec": "^1.1.0",
37
37
  "@workflow/serde": "4.1.0",
38
38
  "eventsource-parser": "^3.0.8",
@@ -3,7 +3,11 @@ import { convertBase64ToUint8Array } from './uint8-utils';
3
3
  const imageMediaTypeSignatures = [
4
4
  {
5
5
  mediaType: 'image/gif' as const,
6
- bytesPrefix: [0x47, 0x49, 0x46], // GIF
6
+ bytesPrefix: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], // GIF87a
7
+ },
8
+ {
9
+ mediaType: 'image/gif' as const,
10
+ bytesPrefix: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], // GIF89a
7
11
  },
8
12
  {
9
13
  mediaType: 'image/png' as const,
@@ -32,7 +36,7 @@ const imageMediaTypeSignatures = [
32
36
  },
33
37
  {
34
38
  mediaType: 'image/bmp' as const,
35
- bytesPrefix: [0x42, 0x4d],
39
+ bytesPrefix: [0x42, 0x4d, null, null, null, null, 0x00, 0x00, 0x00, 0x00],
36
40
  },
37
41
  {
38
42
  mediaType: 'image/tiff' as const,
@@ -14,6 +14,63 @@ const MAX_DOWNLOAD_REDIRECTS = 10;
14
14
  // even when a server attaches a Location header.
15
15
  const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
16
16
 
17
+ async function getValidatedFetch(
18
+ customFetch: FetchFunction | undefined,
19
+ ): Promise<FetchFunction> {
20
+ // Callers commonly pass globalThis.fetch through several abstraction
21
+ // layers. Preserve the DNS-pinned Node.js default in that case rather than
22
+ // accidentally treating it as an intentionally custom fetch.
23
+ return customFetch == null || customFetch === globalThis.fetch
24
+ ? await getDefaultDownloadFetch()
25
+ : customFetch;
26
+ }
27
+
28
+ /**
29
+ * Fetches one validated URL without following redirects.
30
+ *
31
+ * On Node.js, the default fetch validates and pins DNS results at connect time.
32
+ * An injected fetch is responsible for equivalent connect-time validation.
33
+ * Redirects are rejected by default. Callers using `redirect: 'manual'` must
34
+ * validate the Location target before issuing another request.
35
+ */
36
+ export async function fetchWithValidatedEndpoint({
37
+ url,
38
+ init,
39
+ fetch: customFetch,
40
+ trustedOrigin,
41
+ redirect = 'error',
42
+ }: {
43
+ url: string | URL;
44
+ init?: RequestInit;
45
+ fetch?: FetchFunction;
46
+ /**
47
+ * A developer-configured origin that may legitimately resolve to a private
48
+ * address. This must never be derived from untrusted response data.
49
+ */
50
+ trustedOrigin?: string;
51
+ redirect?: 'error' | 'manual';
52
+ }): Promise<Response> {
53
+ const urlText = url.toString();
54
+ const isTrusted =
55
+ trustedOrigin !== undefined && isSameOrigin(urlText, trustedOrigin);
56
+
57
+ if (!isTrusted) {
58
+ validateDownloadUrl(urlText);
59
+ }
60
+
61
+ const fetch =
62
+ isTrusted && customFetch != null
63
+ ? customFetch
64
+ : isTrusted
65
+ ? globalThis.fetch
66
+ : await getValidatedFetch(customFetch);
67
+
68
+ return await fetch(url, {
69
+ ...init,
70
+ redirect,
71
+ });
72
+ }
73
+
17
74
  /**
18
75
  * Fetches a URL while enforcing the download guard on every hop.
19
76
  *
@@ -106,8 +163,11 @@ export async function fetchWithValidatedRedirects({
106
163
  }
107
164
 
108
165
  const fetch =
109
- customFetch ??
110
- (isTrustedHop ? globalThis.fetch : await getDefaultDownloadFetch());
166
+ isTrustedHop && customFetch != null
167
+ ? customFetch
168
+ : isTrustedHop
169
+ ? globalThis.fetch
170
+ : await getValidatedFetch(customFetch);
111
171
 
112
172
  const response = await fetch(currentUrl, perHopInit('manual'));
113
173
 
@@ -121,7 +181,7 @@ export async function fetchWithValidatedRedirects({
121
181
  return await fetch(currentUrl, perHopInit('follow'));
122
182
  }
123
183
 
124
- const location = response.headers.get('location');
184
+ const location = response.headers?.get('location');
125
185
  if (REDIRECT_STATUS_CODES.has(response.status) && location) {
126
186
  // Release the redirect response's connection before moving to the next
127
187
  // hop. Whether that hop is followed or rejected by the guard, an
package/src/index.ts CHANGED
@@ -31,7 +31,10 @@ export {
31
31
  export { downloadBlob } from './download-blob';
32
32
  export { DownloadError } from './download-error';
33
33
  export { EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL as EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL } from './embedding-model-capabilities';
34
- export { fetchWithValidatedRedirects } from './fetch-with-validated-redirects';
34
+ export {
35
+ fetchWithValidatedEndpoint,
36
+ fetchWithValidatedRedirects,
37
+ } from './fetch-with-validated-redirects';
35
38
  export { extractLines } from './extract-lines';
36
39
  export * from './extract-response-headers';
37
40
  export * from './fetch-function';