@ai-sdk/provider-utils 4.0.28 → 4.0.29
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/CHANGELOG.md +21 -0
- package/dist/index.d.mts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +181 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -83
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/download-blob.ts +4 -9
- package/src/fetch-with-validated-redirects.ts +82 -0
- package/src/index.ts +3 -0
- package/src/is-browser-runtime.ts +13 -0
- package/src/is-same-origin.ts +19 -0
- package/src/validate-download-url.ts +113 -31
package/dist/index.mjs
CHANGED
|
@@ -233,59 +233,9 @@ var DownloadError = class extends (_b = AISDKError, _a = symbol, _b) {
|
|
|
233
233
|
}
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
-
// src/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
response,
|
|
240
|
-
url,
|
|
241
|
-
maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE
|
|
242
|
-
}) {
|
|
243
|
-
const contentLength = response.headers.get("content-length");
|
|
244
|
-
if (contentLength != null) {
|
|
245
|
-
const length = parseInt(contentLength, 10);
|
|
246
|
-
if (!isNaN(length) && length > maxBytes) {
|
|
247
|
-
throw new DownloadError({
|
|
248
|
-
url,
|
|
249
|
-
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const body = response.body;
|
|
254
|
-
if (body == null) {
|
|
255
|
-
return new Uint8Array(0);
|
|
256
|
-
}
|
|
257
|
-
const reader = body.getReader();
|
|
258
|
-
const chunks = [];
|
|
259
|
-
let totalBytes = 0;
|
|
260
|
-
try {
|
|
261
|
-
while (true) {
|
|
262
|
-
const { done, value } = await reader.read();
|
|
263
|
-
if (done) {
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
totalBytes += value.length;
|
|
267
|
-
if (totalBytes > maxBytes) {
|
|
268
|
-
throw new DownloadError({
|
|
269
|
-
url,
|
|
270
|
-
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
chunks.push(value);
|
|
274
|
-
}
|
|
275
|
-
} finally {
|
|
276
|
-
try {
|
|
277
|
-
await reader.cancel();
|
|
278
|
-
} finally {
|
|
279
|
-
reader.releaseLock();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const result = new Uint8Array(totalBytes);
|
|
283
|
-
let offset = 0;
|
|
284
|
-
for (const chunk of chunks) {
|
|
285
|
-
result.set(chunk, offset);
|
|
286
|
-
offset += chunk.length;
|
|
287
|
-
}
|
|
288
|
-
return result;
|
|
236
|
+
// src/is-browser-runtime.ts
|
|
237
|
+
function isBrowserRuntime(globalThisAny = globalThis) {
|
|
238
|
+
return globalThisAny.window != null;
|
|
289
239
|
}
|
|
290
240
|
|
|
291
241
|
// src/validate-download-url.ts
|
|
@@ -308,7 +258,7 @@ function validateDownloadUrl(url) {
|
|
|
308
258
|
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
|
|
309
259
|
});
|
|
310
260
|
}
|
|
311
|
-
const hostname = parsed.hostname;
|
|
261
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, "");
|
|
312
262
|
if (!hostname) {
|
|
313
263
|
throw new DownloadError({
|
|
314
264
|
url,
|
|
@@ -351,53 +301,186 @@ function isIPv4(hostname) {
|
|
|
351
301
|
}
|
|
352
302
|
function isPrivateIPv4(ip) {
|
|
353
303
|
const parts = ip.split(".").map(Number);
|
|
354
|
-
const [a, b] = parts;
|
|
304
|
+
const [a, b, c] = parts;
|
|
355
305
|
if (a === 0) return true;
|
|
356
306
|
if (a === 10) return true;
|
|
307
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
357
308
|
if (a === 127) return true;
|
|
358
309
|
if (a === 169 && b === 254) return true;
|
|
359
310
|
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
311
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
360
312
|
if (a === 192 && b === 168) return true;
|
|
313
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
314
|
+
if (a >= 240) return true;
|
|
361
315
|
return false;
|
|
362
316
|
}
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
317
|
+
function parseIPv6(ip) {
|
|
318
|
+
let address = ip.toLowerCase();
|
|
319
|
+
const zoneIndex = address.indexOf("%");
|
|
320
|
+
if (zoneIndex !== -1) {
|
|
321
|
+
address = address.slice(0, zoneIndex);
|
|
322
|
+
}
|
|
323
|
+
const halves = address.split("::");
|
|
324
|
+
if (halves.length > 2) return null;
|
|
325
|
+
const toGroups = (segment) => {
|
|
326
|
+
if (segment === "") return [];
|
|
327
|
+
const groups = [];
|
|
328
|
+
const parts = segment.split(":");
|
|
329
|
+
for (let i = 0; i < parts.length; i++) {
|
|
330
|
+
const part = parts[i];
|
|
331
|
+
if (part.includes(".")) {
|
|
332
|
+
if (i !== parts.length - 1 || !isIPv4(part)) return null;
|
|
333
|
+
const [a, b, c, d] = part.split(".").map(Number);
|
|
334
|
+
groups.push(a << 8 | b, c << 8 | d);
|
|
335
|
+
continue;
|
|
382
336
|
}
|
|
337
|
+
if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
|
|
338
|
+
groups.push(parseInt(part, 16));
|
|
383
339
|
}
|
|
340
|
+
return groups;
|
|
341
|
+
};
|
|
342
|
+
const head = toGroups(halves[0]);
|
|
343
|
+
if (head === null) return null;
|
|
344
|
+
if (halves.length === 2) {
|
|
345
|
+
const tail = toGroups(halves[1]);
|
|
346
|
+
if (tail === null) return null;
|
|
347
|
+
const fill = 8 - head.length - tail.length;
|
|
348
|
+
if (fill < 0) return null;
|
|
349
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
350
|
+
}
|
|
351
|
+
return head.length === 8 ? head : null;
|
|
352
|
+
}
|
|
353
|
+
function isPrivateIPv6(ip) {
|
|
354
|
+
const groups = parseIPv6(ip);
|
|
355
|
+
if (groups === null) return true;
|
|
356
|
+
const topZero = (count) => groups.slice(0, count).every((group) => group === 0);
|
|
357
|
+
if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
|
|
358
|
+
if ((groups[0] & 65024) === 64512) return true;
|
|
359
|
+
if ((groups[0] & 65472) === 65152) return true;
|
|
360
|
+
if ((groups[0] & 65472) === 65216) return true;
|
|
361
|
+
if ((groups[0] & 65280) === 65280) return true;
|
|
362
|
+
const embedsIPv4 = (
|
|
363
|
+
// ::/96 — IPv4-compatible (deprecated)
|
|
364
|
+
topZero(6) || // ::ffff:0:0/96 — IPv4-mapped (ffff in group 5)
|
|
365
|
+
topZero(5) && groups[5] === 65535 || // ::ffff:0:0/96 — IPv4-translated form (ffff in group 4, group 5 zero)
|
|
366
|
+
topZero(4) && groups[4] === 65535 && groups[5] === 0 || // 64:ff9b::/96 — NAT64 well-known prefix
|
|
367
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 0 || // 64:ff9b:1::/48 — NAT64 local-use prefix
|
|
368
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 1
|
|
369
|
+
);
|
|
370
|
+
if (embedsIPv4) {
|
|
371
|
+
const a = groups[6] >> 8 & 255;
|
|
372
|
+
const b = groups[6] & 255;
|
|
373
|
+
const c = groups[7] >> 8 & 255;
|
|
374
|
+
const d = groups[7] & 255;
|
|
375
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
384
376
|
}
|
|
385
|
-
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
386
|
-
if (normalized.startsWith("fe80")) return true;
|
|
387
377
|
return false;
|
|
388
378
|
}
|
|
389
379
|
|
|
380
|
+
// src/fetch-with-validated-redirects.ts
|
|
381
|
+
var MAX_DOWNLOAD_REDIRECTS = 10;
|
|
382
|
+
async function fetchWithValidatedRedirects({
|
|
383
|
+
url,
|
|
384
|
+
headers,
|
|
385
|
+
abortSignal,
|
|
386
|
+
maxRedirects = MAX_DOWNLOAD_REDIRECTS
|
|
387
|
+
}) {
|
|
388
|
+
const baseInit = { signal: abortSignal };
|
|
389
|
+
if (headers !== void 0) {
|
|
390
|
+
baseInit.headers = headers;
|
|
391
|
+
}
|
|
392
|
+
let currentUrl = url;
|
|
393
|
+
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
394
|
+
validateDownloadUrl(currentUrl);
|
|
395
|
+
const response = await fetch(currentUrl, {
|
|
396
|
+
...baseInit,
|
|
397
|
+
redirect: "manual"
|
|
398
|
+
});
|
|
399
|
+
if (response.type === "opaqueredirect") {
|
|
400
|
+
if (!isBrowserRuntime()) {
|
|
401
|
+
throw new DownloadError({
|
|
402
|
+
url,
|
|
403
|
+
message: `Redirect from ${currentUrl} could not be validated and was blocked`
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return await fetch(currentUrl, { ...baseInit, redirect: "follow" });
|
|
407
|
+
}
|
|
408
|
+
const location = response.headers.get("location");
|
|
409
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
410
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
return response;
|
|
414
|
+
}
|
|
415
|
+
throw new DownloadError({
|
|
416
|
+
url,
|
|
417
|
+
message: `Too many redirects (max ${maxRedirects})`
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/read-response-with-size-limit.ts
|
|
422
|
+
var DEFAULT_MAX_DOWNLOAD_SIZE = 2 * 1024 * 1024 * 1024;
|
|
423
|
+
async function readResponseWithSizeLimit({
|
|
424
|
+
response,
|
|
425
|
+
url,
|
|
426
|
+
maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE
|
|
427
|
+
}) {
|
|
428
|
+
const contentLength = response.headers.get("content-length");
|
|
429
|
+
if (contentLength != null) {
|
|
430
|
+
const length = parseInt(contentLength, 10);
|
|
431
|
+
if (!isNaN(length) && length > maxBytes) {
|
|
432
|
+
throw new DownloadError({
|
|
433
|
+
url,
|
|
434
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const body = response.body;
|
|
439
|
+
if (body == null) {
|
|
440
|
+
return new Uint8Array(0);
|
|
441
|
+
}
|
|
442
|
+
const reader = body.getReader();
|
|
443
|
+
const chunks = [];
|
|
444
|
+
let totalBytes = 0;
|
|
445
|
+
try {
|
|
446
|
+
while (true) {
|
|
447
|
+
const { done, value } = await reader.read();
|
|
448
|
+
if (done) {
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
totalBytes += value.length;
|
|
452
|
+
if (totalBytes > maxBytes) {
|
|
453
|
+
throw new DownloadError({
|
|
454
|
+
url,
|
|
455
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
chunks.push(value);
|
|
459
|
+
}
|
|
460
|
+
} finally {
|
|
461
|
+
try {
|
|
462
|
+
await reader.cancel();
|
|
463
|
+
} finally {
|
|
464
|
+
reader.releaseLock();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const result = new Uint8Array(totalBytes);
|
|
468
|
+
let offset = 0;
|
|
469
|
+
for (const chunk of chunks) {
|
|
470
|
+
result.set(chunk, offset);
|
|
471
|
+
offset += chunk.length;
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
|
|
390
476
|
// src/download-blob.ts
|
|
391
477
|
async function downloadBlob(url, options) {
|
|
392
478
|
var _a2, _b2;
|
|
393
|
-
validateDownloadUrl(url);
|
|
394
479
|
try {
|
|
395
|
-
const response = await
|
|
396
|
-
|
|
480
|
+
const response = await fetchWithValidatedRedirects({
|
|
481
|
+
url,
|
|
482
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
397
483
|
});
|
|
398
|
-
if (response.redirected) {
|
|
399
|
-
validateDownloadUrl(response.url);
|
|
400
|
-
}
|
|
401
484
|
if (!response.ok) {
|
|
402
485
|
throw new DownloadError({
|
|
403
486
|
url,
|
|
@@ -582,7 +665,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
582
665
|
}
|
|
583
666
|
|
|
584
667
|
// src/version.ts
|
|
585
|
-
var VERSION = true ? "4.0.
|
|
668
|
+
var VERSION = true ? "4.0.29" : "0.0.0-test";
|
|
586
669
|
|
|
587
670
|
// src/get-from-api.ts
|
|
588
671
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -698,6 +781,15 @@ function isNonNullable(value) {
|
|
|
698
781
|
return value != null;
|
|
699
782
|
}
|
|
700
783
|
|
|
784
|
+
// src/is-same-origin.ts
|
|
785
|
+
function isSameOrigin(url, baseUrl) {
|
|
786
|
+
try {
|
|
787
|
+
return new URL(url).origin === new URL(baseUrl).origin;
|
|
788
|
+
} catch (e) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
701
793
|
// src/is-url-supported.ts
|
|
702
794
|
function isUrlSupported({
|
|
703
795
|
mediaType,
|
|
@@ -2726,14 +2818,17 @@ export {
|
|
|
2726
2818
|
dynamicTool,
|
|
2727
2819
|
executeTool,
|
|
2728
2820
|
extractResponseHeaders,
|
|
2821
|
+
fetchWithValidatedRedirects,
|
|
2729
2822
|
generateId,
|
|
2730
2823
|
getErrorMessage,
|
|
2731
2824
|
getFromApi,
|
|
2732
2825
|
getRuntimeEnvironmentUserAgent,
|
|
2733
2826
|
injectJsonInstructionIntoMessages,
|
|
2734
2827
|
isAbortError,
|
|
2828
|
+
isBrowserRuntime,
|
|
2735
2829
|
isNonNullable,
|
|
2736
2830
|
isParsableJson,
|
|
2831
|
+
isSameOrigin,
|
|
2737
2832
|
isUrlSupported,
|
|
2738
2833
|
jsonSchema,
|
|
2739
2834
|
lazySchema,
|