@ai-sdk/provider-utils 4.0.28 → 4.0.30
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 +29 -0
- package/dist/index.d.mts +73 -1
- package/dist/index.d.ts +73 -1
- package/dist/index.js +195 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -83
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cancel-response-body.ts +19 -0
- package/src/download-blob.ts +8 -9
- package/src/fetch-with-validated-redirects.ts +87 -0
- package/src/index.ts +4 -0
- package/src/is-browser-runtime.ts +13 -0
- package/src/is-same-origin.ts +19 -0
- package/src/read-response-with-size-limit.ts +4 -0
- package/src/validate-download-url.ts +113 -31
package/dist/index.mjs
CHANGED
|
@@ -208,6 +208,15 @@ function convertToFormData(input, options = {}) {
|
|
|
208
208
|
return formData;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// src/cancel-response-body.ts
|
|
212
|
+
async function cancelResponseBody(response) {
|
|
213
|
+
var _a2;
|
|
214
|
+
try {
|
|
215
|
+
await ((_a2 = response.body) == null ? void 0 : _a2.cancel());
|
|
216
|
+
} catch (e) {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
211
220
|
// src/download-error.ts
|
|
212
221
|
import { AISDKError } from "@ai-sdk/provider";
|
|
213
222
|
var name = "AI_DownloadError";
|
|
@@ -233,59 +242,9 @@ var DownloadError = class extends (_b = AISDKError, _a = symbol, _b) {
|
|
|
233
242
|
}
|
|
234
243
|
};
|
|
235
244
|
|
|
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;
|
|
245
|
+
// src/is-browser-runtime.ts
|
|
246
|
+
function isBrowserRuntime(globalThisAny = globalThis) {
|
|
247
|
+
return globalThisAny.window != null;
|
|
289
248
|
}
|
|
290
249
|
|
|
291
250
|
// src/validate-download-url.ts
|
|
@@ -308,7 +267,7 @@ function validateDownloadUrl(url) {
|
|
|
308
267
|
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
|
|
309
268
|
});
|
|
310
269
|
}
|
|
311
|
-
const hostname = parsed.hostname;
|
|
270
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, "");
|
|
312
271
|
if (!hostname) {
|
|
313
272
|
throw new DownloadError({
|
|
314
273
|
url,
|
|
@@ -351,54 +310,190 @@ function isIPv4(hostname) {
|
|
|
351
310
|
}
|
|
352
311
|
function isPrivateIPv4(ip) {
|
|
353
312
|
const parts = ip.split(".").map(Number);
|
|
354
|
-
const [a, b] = parts;
|
|
313
|
+
const [a, b, c] = parts;
|
|
355
314
|
if (a === 0) return true;
|
|
356
315
|
if (a === 10) return true;
|
|
316
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
357
317
|
if (a === 127) return true;
|
|
358
318
|
if (a === 169 && b === 254) return true;
|
|
359
319
|
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
320
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
360
321
|
if (a === 192 && b === 168) return true;
|
|
322
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
323
|
+
if (a >= 240) return true;
|
|
361
324
|
return false;
|
|
362
325
|
}
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
326
|
+
function parseIPv6(ip) {
|
|
327
|
+
let address = ip.toLowerCase();
|
|
328
|
+
const zoneIndex = address.indexOf("%");
|
|
329
|
+
if (zoneIndex !== -1) {
|
|
330
|
+
address = address.slice(0, zoneIndex);
|
|
331
|
+
}
|
|
332
|
+
const halves = address.split("::");
|
|
333
|
+
if (halves.length > 2) return null;
|
|
334
|
+
const toGroups = (segment) => {
|
|
335
|
+
if (segment === "") return [];
|
|
336
|
+
const groups = [];
|
|
337
|
+
const parts = segment.split(":");
|
|
338
|
+
for (let i = 0; i < parts.length; i++) {
|
|
339
|
+
const part = parts[i];
|
|
340
|
+
if (part.includes(".")) {
|
|
341
|
+
if (i !== parts.length - 1 || !isIPv4(part)) return null;
|
|
342
|
+
const [a, b, c, d] = part.split(".").map(Number);
|
|
343
|
+
groups.push(a << 8 | b, c << 8 | d);
|
|
344
|
+
continue;
|
|
382
345
|
}
|
|
346
|
+
if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
|
|
347
|
+
groups.push(parseInt(part, 16));
|
|
383
348
|
}
|
|
349
|
+
return groups;
|
|
350
|
+
};
|
|
351
|
+
const head = toGroups(halves[0]);
|
|
352
|
+
if (head === null) return null;
|
|
353
|
+
if (halves.length === 2) {
|
|
354
|
+
const tail = toGroups(halves[1]);
|
|
355
|
+
if (tail === null) return null;
|
|
356
|
+
const fill = 8 - head.length - tail.length;
|
|
357
|
+
if (fill < 0) return null;
|
|
358
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
359
|
+
}
|
|
360
|
+
return head.length === 8 ? head : null;
|
|
361
|
+
}
|
|
362
|
+
function isPrivateIPv6(ip) {
|
|
363
|
+
const groups = parseIPv6(ip);
|
|
364
|
+
if (groups === null) return true;
|
|
365
|
+
const topZero = (count) => groups.slice(0, count).every((group) => group === 0);
|
|
366
|
+
if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
|
|
367
|
+
if ((groups[0] & 65024) === 64512) return true;
|
|
368
|
+
if ((groups[0] & 65472) === 65152) return true;
|
|
369
|
+
if ((groups[0] & 65472) === 65216) return true;
|
|
370
|
+
if ((groups[0] & 65280) === 65280) return true;
|
|
371
|
+
const embedsIPv4 = (
|
|
372
|
+
// ::/96 — IPv4-compatible (deprecated)
|
|
373
|
+
topZero(6) || // ::ffff:0:0/96 — IPv4-mapped (ffff in group 5)
|
|
374
|
+
topZero(5) && groups[5] === 65535 || // ::ffff:0:0/96 — IPv4-translated form (ffff in group 4, group 5 zero)
|
|
375
|
+
topZero(4) && groups[4] === 65535 && groups[5] === 0 || // 64:ff9b::/96 — NAT64 well-known prefix
|
|
376
|
+
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
|
|
377
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 1
|
|
378
|
+
);
|
|
379
|
+
if (embedsIPv4) {
|
|
380
|
+
const a = groups[6] >> 8 & 255;
|
|
381
|
+
const b = groups[6] & 255;
|
|
382
|
+
const c = groups[7] >> 8 & 255;
|
|
383
|
+
const d = groups[7] & 255;
|
|
384
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
384
385
|
}
|
|
385
|
-
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
386
|
-
if (normalized.startsWith("fe80")) return true;
|
|
387
386
|
return false;
|
|
388
387
|
}
|
|
389
388
|
|
|
389
|
+
// src/fetch-with-validated-redirects.ts
|
|
390
|
+
var MAX_DOWNLOAD_REDIRECTS = 10;
|
|
391
|
+
async function fetchWithValidatedRedirects({
|
|
392
|
+
url,
|
|
393
|
+
headers,
|
|
394
|
+
abortSignal,
|
|
395
|
+
maxRedirects = MAX_DOWNLOAD_REDIRECTS
|
|
396
|
+
}) {
|
|
397
|
+
const baseInit = { signal: abortSignal };
|
|
398
|
+
if (headers !== void 0) {
|
|
399
|
+
baseInit.headers = headers;
|
|
400
|
+
}
|
|
401
|
+
let currentUrl = url;
|
|
402
|
+
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
403
|
+
validateDownloadUrl(currentUrl);
|
|
404
|
+
const response = await fetch(currentUrl, {
|
|
405
|
+
...baseInit,
|
|
406
|
+
redirect: "manual"
|
|
407
|
+
});
|
|
408
|
+
if (response.type === "opaqueredirect") {
|
|
409
|
+
if (!isBrowserRuntime()) {
|
|
410
|
+
throw new DownloadError({
|
|
411
|
+
url,
|
|
412
|
+
message: `Redirect from ${currentUrl} could not be validated and was blocked`
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return await fetch(currentUrl, { ...baseInit, redirect: "follow" });
|
|
416
|
+
}
|
|
417
|
+
const location = response.headers.get("location");
|
|
418
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
419
|
+
await cancelResponseBody(response);
|
|
420
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
return response;
|
|
424
|
+
}
|
|
425
|
+
throw new DownloadError({
|
|
426
|
+
url,
|
|
427
|
+
message: `Too many redirects (max ${maxRedirects})`
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/read-response-with-size-limit.ts
|
|
432
|
+
var DEFAULT_MAX_DOWNLOAD_SIZE = 2 * 1024 * 1024 * 1024;
|
|
433
|
+
async function readResponseWithSizeLimit({
|
|
434
|
+
response,
|
|
435
|
+
url,
|
|
436
|
+
maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE
|
|
437
|
+
}) {
|
|
438
|
+
const contentLength = response.headers.get("content-length");
|
|
439
|
+
if (contentLength != null) {
|
|
440
|
+
const length = parseInt(contentLength, 10);
|
|
441
|
+
if (!isNaN(length) && length > maxBytes) {
|
|
442
|
+
await cancelResponseBody(response);
|
|
443
|
+
throw new DownloadError({
|
|
444
|
+
url,
|
|
445
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const body = response.body;
|
|
450
|
+
if (body == null) {
|
|
451
|
+
return new Uint8Array(0);
|
|
452
|
+
}
|
|
453
|
+
const reader = body.getReader();
|
|
454
|
+
const chunks = [];
|
|
455
|
+
let totalBytes = 0;
|
|
456
|
+
try {
|
|
457
|
+
while (true) {
|
|
458
|
+
const { done, value } = await reader.read();
|
|
459
|
+
if (done) {
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
totalBytes += value.length;
|
|
463
|
+
if (totalBytes > maxBytes) {
|
|
464
|
+
throw new DownloadError({
|
|
465
|
+
url,
|
|
466
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
chunks.push(value);
|
|
470
|
+
}
|
|
471
|
+
} finally {
|
|
472
|
+
try {
|
|
473
|
+
await reader.cancel();
|
|
474
|
+
} finally {
|
|
475
|
+
reader.releaseLock();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const result = new Uint8Array(totalBytes);
|
|
479
|
+
let offset = 0;
|
|
480
|
+
for (const chunk of chunks) {
|
|
481
|
+
result.set(chunk, offset);
|
|
482
|
+
offset += chunk.length;
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
|
|
390
487
|
// src/download-blob.ts
|
|
391
488
|
async function downloadBlob(url, options) {
|
|
392
489
|
var _a2, _b2;
|
|
393
|
-
validateDownloadUrl(url);
|
|
394
490
|
try {
|
|
395
|
-
const response = await
|
|
396
|
-
|
|
491
|
+
const response = await fetchWithValidatedRedirects({
|
|
492
|
+
url,
|
|
493
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
397
494
|
});
|
|
398
|
-
if (response.redirected) {
|
|
399
|
-
validateDownloadUrl(response.url);
|
|
400
|
-
}
|
|
401
495
|
if (!response.ok) {
|
|
496
|
+
await cancelResponseBody(response);
|
|
402
497
|
throw new DownloadError({
|
|
403
498
|
url,
|
|
404
499
|
statusCode: response.status,
|
|
@@ -582,7 +677,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
582
677
|
}
|
|
583
678
|
|
|
584
679
|
// src/version.ts
|
|
585
|
-
var VERSION = true ? "4.0.
|
|
680
|
+
var VERSION = true ? "4.0.30" : "0.0.0-test";
|
|
586
681
|
|
|
587
682
|
// src/get-from-api.ts
|
|
588
683
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -698,6 +793,15 @@ function isNonNullable(value) {
|
|
|
698
793
|
return value != null;
|
|
699
794
|
}
|
|
700
795
|
|
|
796
|
+
// src/is-same-origin.ts
|
|
797
|
+
function isSameOrigin(url, baseUrl) {
|
|
798
|
+
try {
|
|
799
|
+
return new URL(url).origin === new URL(baseUrl).origin;
|
|
800
|
+
} catch (e) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
701
805
|
// src/is-url-supported.ts
|
|
702
806
|
function isUrlSupported({
|
|
703
807
|
mediaType,
|
|
@@ -2705,6 +2809,7 @@ export {
|
|
|
2705
2809
|
EventSourceParserStream2 as EventSourceParserStream,
|
|
2706
2810
|
VERSION,
|
|
2707
2811
|
asSchema,
|
|
2812
|
+
cancelResponseBody,
|
|
2708
2813
|
combineHeaders,
|
|
2709
2814
|
convertAsyncIteratorToReadableStream,
|
|
2710
2815
|
convertBase64ToUint8Array,
|
|
@@ -2726,14 +2831,17 @@ export {
|
|
|
2726
2831
|
dynamicTool,
|
|
2727
2832
|
executeTool,
|
|
2728
2833
|
extractResponseHeaders,
|
|
2834
|
+
fetchWithValidatedRedirects,
|
|
2729
2835
|
generateId,
|
|
2730
2836
|
getErrorMessage,
|
|
2731
2837
|
getFromApi,
|
|
2732
2838
|
getRuntimeEnvironmentUserAgent,
|
|
2733
2839
|
injectJsonInstructionIntoMessages,
|
|
2734
2840
|
isAbortError,
|
|
2841
|
+
isBrowserRuntime,
|
|
2735
2842
|
isNonNullable,
|
|
2736
2843
|
isParsableJson,
|
|
2844
|
+
isSameOrigin,
|
|
2737
2845
|
isUrlSupported,
|
|
2738
2846
|
jsonSchema,
|
|
2739
2847
|
lazySchema,
|