@ai-sdk/provider-utils 4.0.27 → 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 +31 -0
- package/dist/index.d.mts +64 -1
- package/dist/index.d.ts +64 -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/types/tool-approval-request.ts +6 -0
- package/src/validate-download-url.ts +113 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @ai-sdk/provider-utils
|
|
2
2
|
|
|
3
|
+
## 4.0.29
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bfa5864: fix: only send provider credentials to same-origin response-supplied URLs
|
|
8
|
+
|
|
9
|
+
Several provider clients followed a URL taken from the provider's API response (a polling/status URL or a final media URL such as `polling_url`, `urls.get`, `result_url`, `result.sample`, or `video.uri`) and reused the authenticated headers — or appended `?key=<API_KEY>` — on that request. Because the host of the response-supplied URL was never validated, the long-lived API key was sent to whatever host the response named (a CDN in the benign case, or an attacker-chosen host if the provider response was tampered with), allowing credential exfiltration.
|
|
10
|
+
|
|
11
|
+
A new `isSameOrigin` helper is added to `@ai-sdk/provider-utils`, and the affected fetches in `@ai-sdk/black-forest-labs`, `@ai-sdk/fireworks`, `@ai-sdk/replicate`, `@ai-sdk/gladia`, `@ai-sdk/fal`, and `@ai-sdk/google` now attach credentials only when the followed URL is same-origin with the provider's configured API origin. Requests to a foreign origin are made without the credential.
|
|
12
|
+
|
|
13
|
+
- f42aa79: fix: harden download URL SSRF guard against hostname and redirect bypasses
|
|
14
|
+
|
|
15
|
+
`validateDownloadUrl` and the file download helpers (`downloadBlob`, `download`) could be bypassed in several ways when handling untrusted URLs:
|
|
16
|
+
|
|
17
|
+
- A fully-qualified hostname with a trailing dot (e.g. `localhost.`, `myhost.local.`) skipped the localhost/`.local` blocklist.
|
|
18
|
+
- IPv6 addresses that embed an IPv4 address in their last 32 bits — IPv4-compatible (`::127.0.0.1`), IPv4-translated (`::ffff:0:127.0.0.1`), and NAT64 (`64:ff9b::127.0.0.1`, including the `64:ff9b:1::/48` local-use prefix) — were not decoded and checked against the private IPv4 ranges.
|
|
19
|
+
- Redirects were validated only _after_ `fetch` had already followed them, so the request to a redirect target (e.g. an internal/metadata address) had already been issued before the check ran.
|
|
20
|
+
- Several reserved/internal address ranges were not blocked: CGNAT (`100.64.0.0/10`, used by some cloud providers for internal traffic), benchmarking (`198.18.0.0/15`), IETF protocol assignments (`192.0.0.0/24`), the reserved `240.0.0.0/4` block (including the `255.255.255.255` broadcast address), and IPv6 site-local (`fec0::/10`) and multicast (`ff00::/8`).
|
|
21
|
+
|
|
22
|
+
The validator now strips trailing dots before the hostname checks and fully expands IPv6 addresses to detect embedded private IPv4 targets. The download helpers now follow redirects manually (`redirect: 'manual'`), re-validating each hop before requesting it, so an unsafe redirect target is never fetched. When a redirect cannot be inspected because the runtime returns an opaque response, the helpers fail closed (reject the redirect) on the server; only in a real browser — where SSRF is not reachable (fetch is constrained by CORS and cannot reach a server's internal network or cloud-metadata endpoints) — is the redirect followed natively so legitimate redirected downloads keep working.
|
|
23
|
+
|
|
24
|
+
## 4.0.28
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 942f2f8: fix(security): re-validate tool approvals from client message history before execution
|
|
29
|
+
|
|
30
|
+
The approval-replay path in `generateText`/`streamText` reconstructed approved tool calls from the client-supplied messages array and executed them without re-validating input against the tool's schema or re-checking that the tool actually requires approval. A client could forge an assistant message with a pre-approved tool-call part and have the server execute a tool with attacker-chosen arguments.
|
|
31
|
+
|
|
32
|
+
The replay path now verifies the HMAC signature (when `experimental_toolApprovalSecret` is configured), re-validates tool-call input against the tool's input schema, and re-resolves whether the tool requires approval before execution.
|
|
33
|
+
|
|
3
34
|
## 4.0.27
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -203,6 +203,36 @@ declare function readResponseWithSizeLimit({ response, url, maxBytes, }: {
|
|
|
203
203
|
maxBytes?: number;
|
|
204
204
|
}): Promise<Uint8Array>;
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Fetches a URL while enforcing the SSRF download guard on every hop.
|
|
208
|
+
*
|
|
209
|
+
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
|
|
210
|
+
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
|
|
211
|
+
* on the default `redirect: 'follow'` would issue the request to a redirect
|
|
212
|
+
* target (e.g. an internal address) before we ever see its URL, defeating the
|
|
213
|
+
* SSRF guard.
|
|
214
|
+
*
|
|
215
|
+
* A `redirect: 'manual'` request yields an unreadable opaque response in the
|
|
216
|
+
* browser (and in other spec-compliant fetch implementations), so the redirect
|
|
217
|
+
* target cannot be validated here. In a real browser this is safe to follow
|
|
218
|
+
* natively because SSRF is not reachable (fetch is constrained by CORS and
|
|
219
|
+
* cannot reach a server's internal network or cloud-metadata). On any other
|
|
220
|
+
* runtime we cannot validate the hop, so we fail closed rather than follow it
|
|
221
|
+
* blindly and bypass the SSRF guard.
|
|
222
|
+
*
|
|
223
|
+
* The returned response is the final (non-redirect) response. The caller is
|
|
224
|
+
* responsible for checking `response.ok` and reading the body.
|
|
225
|
+
*
|
|
226
|
+
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
227
|
+
* a redirect cannot be validated on a non-browser runtime.
|
|
228
|
+
*/
|
|
229
|
+
declare function fetchWithValidatedRedirects({ url, headers, abortSignal, maxRedirects, }: {
|
|
230
|
+
url: string;
|
|
231
|
+
headers?: HeadersInit;
|
|
232
|
+
abortSignal?: AbortSignal;
|
|
233
|
+
maxRedirects?: number;
|
|
234
|
+
}): Promise<Response>;
|
|
235
|
+
|
|
206
236
|
/**
|
|
207
237
|
* Fetch function type (standardizes the version of fetch used).
|
|
208
238
|
*/
|
|
@@ -398,6 +428,16 @@ declare function injectJsonInstructionIntoMessages({ messages, schema, schemaPre
|
|
|
398
428
|
|
|
399
429
|
declare function isAbortError(error: unknown): error is Error;
|
|
400
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Returns `true` when running in a browser.
|
|
433
|
+
*
|
|
434
|
+
* Detection keys on the presence of a global `window`, matching the browser
|
|
435
|
+
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
|
|
436
|
+
* so the SDK has a single, consistent definition of "browser". Server runtimes
|
|
437
|
+
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
|
|
438
|
+
*/
|
|
439
|
+
declare function isBrowserRuntime(globalThisAny?: any): boolean;
|
|
440
|
+
|
|
401
441
|
/**
|
|
402
442
|
* Type guard that checks whether a value is not `null` or `undefined`.
|
|
403
443
|
*
|
|
@@ -407,6 +447,20 @@ declare function isAbortError(error: unknown): error is Error;
|
|
|
407
447
|
*/
|
|
408
448
|
declare function isNonNullable<T>(value: T | undefined | null): value is NonNullable<T>;
|
|
409
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Returns true when `url` has the same origin (scheme + host + port) as
|
|
452
|
+
* `baseUrl`.
|
|
453
|
+
*
|
|
454
|
+
* Used to decide whether provider credentials may be attached to a request to a
|
|
455
|
+
* URL taken from a provider response (e.g. a polling or media-download URL).
|
|
456
|
+
* Credentials must only be sent to the provider's own origin; a response that
|
|
457
|
+
* names a foreign host (a CDN, or an attacker-controlled host if the response
|
|
458
|
+
* is tampered with) must not receive the API key.
|
|
459
|
+
*
|
|
460
|
+
* Returns false if either value is not a valid absolute URL (fail-closed).
|
|
461
|
+
*/
|
|
462
|
+
declare function isSameOrigin(url: string, baseUrl: string): boolean;
|
|
463
|
+
|
|
410
464
|
/**
|
|
411
465
|
* Checks if the given URL is supported natively by the model.
|
|
412
466
|
*
|
|
@@ -874,6 +928,11 @@ type ToolApprovalRequest = {
|
|
|
874
928
|
* ID of the tool call that the approval request is for.
|
|
875
929
|
*/
|
|
876
930
|
toolCallId: string;
|
|
931
|
+
/**
|
|
932
|
+
* HMAC-SHA256 signature binding this approval to its tool call.
|
|
933
|
+
* Present only when `experimental_toolApprovalSecret` is configured.
|
|
934
|
+
*/
|
|
935
|
+
signature?: string;
|
|
877
936
|
};
|
|
878
937
|
|
|
879
938
|
/**
|
|
@@ -1305,6 +1364,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
1305
1364
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
1306
1365
|
* to prevent SSRF attacks.
|
|
1307
1366
|
*
|
|
1367
|
+
* Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
|
|
1368
|
+
* hostname that resolves to a private address is not blocked here (see callers, which
|
|
1369
|
+
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
1370
|
+
*
|
|
1308
1371
|
* @param url - The URL string to validate.
|
|
1309
1372
|
* @throws DownloadError if the URL is unsafe.
|
|
1310
1373
|
*/
|
|
@@ -1441,4 +1504,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
|
|
|
1441
1504
|
*/
|
|
1442
1505
|
type ToolCallOptions = ToolExecutionOptions;
|
|
1443
1506
|
|
|
1444
|
-
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type LazySchema, type MaybePromiseLike, type ModelMessage, type ParseResult, type ProviderOptions, type ProviderToolFactory, type ProviderToolFactoryWithOutputSchema, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolApprovalRequest, type ToolApprovalResponse, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolExecutionOptions, type ToolModelMessage, type ToolNameMapping, type ToolNeedsApprovalFunction, type ToolResult, type ToolResultOutput, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, asSchema, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertImageModelFileToDataUri, convertToBase64, convertToFormData, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createProviderToolFactory, createProviderToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, createToolNameMapping, delay, downloadBlob, dynamicTool, executeTool, extractResponseHeaders, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isNonNullable, isParsableJson, isUrlSupported, jsonSchema, lazySchema, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, stripFileExtension, tool, validateDownloadUrl, validateTypes, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
|
1507
|
+
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type LazySchema, type MaybePromiseLike, type ModelMessage, type ParseResult, type ProviderOptions, type ProviderToolFactory, type ProviderToolFactoryWithOutputSchema, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolApprovalRequest, type ToolApprovalResponse, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolExecutionOptions, type ToolModelMessage, type ToolNameMapping, type ToolNeedsApprovalFunction, type ToolResult, type ToolResultOutput, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, asSchema, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertImageModelFileToDataUri, convertToBase64, convertToFormData, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createProviderToolFactory, createProviderToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, createToolNameMapping, delay, downloadBlob, dynamicTool, executeTool, extractResponseHeaders, fetchWithValidatedRedirects, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isBrowserRuntime, isNonNullable, isParsableJson, isSameOrigin, isUrlSupported, jsonSchema, lazySchema, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, stripFileExtension, tool, validateDownloadUrl, validateTypes, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -203,6 +203,36 @@ declare function readResponseWithSizeLimit({ response, url, maxBytes, }: {
|
|
|
203
203
|
maxBytes?: number;
|
|
204
204
|
}): Promise<Uint8Array>;
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Fetches a URL while enforcing the SSRF download guard on every hop.
|
|
208
|
+
*
|
|
209
|
+
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
|
|
210
|
+
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
|
|
211
|
+
* on the default `redirect: 'follow'` would issue the request to a redirect
|
|
212
|
+
* target (e.g. an internal address) before we ever see its URL, defeating the
|
|
213
|
+
* SSRF guard.
|
|
214
|
+
*
|
|
215
|
+
* A `redirect: 'manual'` request yields an unreadable opaque response in the
|
|
216
|
+
* browser (and in other spec-compliant fetch implementations), so the redirect
|
|
217
|
+
* target cannot be validated here. In a real browser this is safe to follow
|
|
218
|
+
* natively because SSRF is not reachable (fetch is constrained by CORS and
|
|
219
|
+
* cannot reach a server's internal network or cloud-metadata). On any other
|
|
220
|
+
* runtime we cannot validate the hop, so we fail closed rather than follow it
|
|
221
|
+
* blindly and bypass the SSRF guard.
|
|
222
|
+
*
|
|
223
|
+
* The returned response is the final (non-redirect) response. The caller is
|
|
224
|
+
* responsible for checking `response.ok` and reading the body.
|
|
225
|
+
*
|
|
226
|
+
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
227
|
+
* a redirect cannot be validated on a non-browser runtime.
|
|
228
|
+
*/
|
|
229
|
+
declare function fetchWithValidatedRedirects({ url, headers, abortSignal, maxRedirects, }: {
|
|
230
|
+
url: string;
|
|
231
|
+
headers?: HeadersInit;
|
|
232
|
+
abortSignal?: AbortSignal;
|
|
233
|
+
maxRedirects?: number;
|
|
234
|
+
}): Promise<Response>;
|
|
235
|
+
|
|
206
236
|
/**
|
|
207
237
|
* Fetch function type (standardizes the version of fetch used).
|
|
208
238
|
*/
|
|
@@ -398,6 +428,16 @@ declare function injectJsonInstructionIntoMessages({ messages, schema, schemaPre
|
|
|
398
428
|
|
|
399
429
|
declare function isAbortError(error: unknown): error is Error;
|
|
400
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Returns `true` when running in a browser.
|
|
433
|
+
*
|
|
434
|
+
* Detection keys on the presence of a global `window`, matching the browser
|
|
435
|
+
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
|
|
436
|
+
* so the SDK has a single, consistent definition of "browser". Server runtimes
|
|
437
|
+
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
|
|
438
|
+
*/
|
|
439
|
+
declare function isBrowserRuntime(globalThisAny?: any): boolean;
|
|
440
|
+
|
|
401
441
|
/**
|
|
402
442
|
* Type guard that checks whether a value is not `null` or `undefined`.
|
|
403
443
|
*
|
|
@@ -407,6 +447,20 @@ declare function isAbortError(error: unknown): error is Error;
|
|
|
407
447
|
*/
|
|
408
448
|
declare function isNonNullable<T>(value: T | undefined | null): value is NonNullable<T>;
|
|
409
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Returns true when `url` has the same origin (scheme + host + port) as
|
|
452
|
+
* `baseUrl`.
|
|
453
|
+
*
|
|
454
|
+
* Used to decide whether provider credentials may be attached to a request to a
|
|
455
|
+
* URL taken from a provider response (e.g. a polling or media-download URL).
|
|
456
|
+
* Credentials must only be sent to the provider's own origin; a response that
|
|
457
|
+
* names a foreign host (a CDN, or an attacker-controlled host if the response
|
|
458
|
+
* is tampered with) must not receive the API key.
|
|
459
|
+
*
|
|
460
|
+
* Returns false if either value is not a valid absolute URL (fail-closed).
|
|
461
|
+
*/
|
|
462
|
+
declare function isSameOrigin(url: string, baseUrl: string): boolean;
|
|
463
|
+
|
|
410
464
|
/**
|
|
411
465
|
* Checks if the given URL is supported natively by the model.
|
|
412
466
|
*
|
|
@@ -874,6 +928,11 @@ type ToolApprovalRequest = {
|
|
|
874
928
|
* ID of the tool call that the approval request is for.
|
|
875
929
|
*/
|
|
876
930
|
toolCallId: string;
|
|
931
|
+
/**
|
|
932
|
+
* HMAC-SHA256 signature binding this approval to its tool call.
|
|
933
|
+
* Present only when `experimental_toolApprovalSecret` is configured.
|
|
934
|
+
*/
|
|
935
|
+
signature?: string;
|
|
877
936
|
};
|
|
878
937
|
|
|
879
938
|
/**
|
|
@@ -1305,6 +1364,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
1305
1364
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
1306
1365
|
* to prevent SSRF attacks.
|
|
1307
1366
|
*
|
|
1367
|
+
* Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
|
|
1368
|
+
* hostname that resolves to a private address is not blocked here (see callers, which
|
|
1369
|
+
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
1370
|
+
*
|
|
1308
1371
|
* @param url - The URL string to validate.
|
|
1309
1372
|
* @throws DownloadError if the URL is unsafe.
|
|
1310
1373
|
*/
|
|
@@ -1441,4 +1504,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
|
|
|
1441
1504
|
*/
|
|
1442
1505
|
type ToolCallOptions = ToolExecutionOptions;
|
|
1443
1506
|
|
|
1444
|
-
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type LazySchema, type MaybePromiseLike, type ModelMessage, type ParseResult, type ProviderOptions, type ProviderToolFactory, type ProviderToolFactoryWithOutputSchema, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolApprovalRequest, type ToolApprovalResponse, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolExecutionOptions, type ToolModelMessage, type ToolNameMapping, type ToolNeedsApprovalFunction, type ToolResult, type ToolResultOutput, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, asSchema, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertImageModelFileToDataUri, convertToBase64, convertToFormData, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createProviderToolFactory, createProviderToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, createToolNameMapping, delay, downloadBlob, dynamicTool, executeTool, extractResponseHeaders, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isNonNullable, isParsableJson, isUrlSupported, jsonSchema, lazySchema, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, stripFileExtension, tool, validateDownloadUrl, validateTypes, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
|
1507
|
+
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type LazySchema, type MaybePromiseLike, type ModelMessage, type ParseResult, type ProviderOptions, type ProviderToolFactory, type ProviderToolFactoryWithOutputSchema, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolApprovalRequest, type ToolApprovalResponse, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolExecutionOptions, type ToolModelMessage, type ToolNameMapping, type ToolNeedsApprovalFunction, type ToolResult, type ToolResultOutput, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, asSchema, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertImageModelFileToDataUri, convertToBase64, convertToFormData, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createProviderToolFactory, createProviderToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, createToolNameMapping, delay, downloadBlob, dynamicTool, executeTool, extractResponseHeaders, fetchWithValidatedRedirects, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isBrowserRuntime, isNonNullable, isParsableJson, isSameOrigin, isUrlSupported, jsonSchema, lazySchema, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, stripFileExtension, tool, validateDownloadUrl, validateTypes, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
package/dist/index.js
CHANGED
|
@@ -57,14 +57,17 @@ __export(index_exports, {
|
|
|
57
57
|
dynamicTool: () => dynamicTool,
|
|
58
58
|
executeTool: () => executeTool,
|
|
59
59
|
extractResponseHeaders: () => extractResponseHeaders,
|
|
60
|
+
fetchWithValidatedRedirects: () => fetchWithValidatedRedirects,
|
|
60
61
|
generateId: () => generateId,
|
|
61
62
|
getErrorMessage: () => getErrorMessage,
|
|
62
63
|
getFromApi: () => getFromApi,
|
|
63
64
|
getRuntimeEnvironmentUserAgent: () => getRuntimeEnvironmentUserAgent,
|
|
64
65
|
injectJsonInstructionIntoMessages: () => injectJsonInstructionIntoMessages,
|
|
65
66
|
isAbortError: () => isAbortError,
|
|
67
|
+
isBrowserRuntime: () => isBrowserRuntime,
|
|
66
68
|
isNonNullable: () => isNonNullable,
|
|
67
69
|
isParsableJson: () => isParsableJson,
|
|
70
|
+
isSameOrigin: () => isSameOrigin,
|
|
68
71
|
isUrlSupported: () => isUrlSupported,
|
|
69
72
|
jsonSchema: () => jsonSchema,
|
|
70
73
|
lazySchema: () => lazySchema,
|
|
@@ -329,59 +332,9 @@ var DownloadError = class extends (_b = import_provider.AISDKError, _a = symbol,
|
|
|
329
332
|
}
|
|
330
333
|
};
|
|
331
334
|
|
|
332
|
-
// src/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
response,
|
|
336
|
-
url,
|
|
337
|
-
maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE
|
|
338
|
-
}) {
|
|
339
|
-
const contentLength = response.headers.get("content-length");
|
|
340
|
-
if (contentLength != null) {
|
|
341
|
-
const length = parseInt(contentLength, 10);
|
|
342
|
-
if (!isNaN(length) && length > maxBytes) {
|
|
343
|
-
throw new DownloadError({
|
|
344
|
-
url,
|
|
345
|
-
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
const body = response.body;
|
|
350
|
-
if (body == null) {
|
|
351
|
-
return new Uint8Array(0);
|
|
352
|
-
}
|
|
353
|
-
const reader = body.getReader();
|
|
354
|
-
const chunks = [];
|
|
355
|
-
let totalBytes = 0;
|
|
356
|
-
try {
|
|
357
|
-
while (true) {
|
|
358
|
-
const { done, value } = await reader.read();
|
|
359
|
-
if (done) {
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
totalBytes += value.length;
|
|
363
|
-
if (totalBytes > maxBytes) {
|
|
364
|
-
throw new DownloadError({
|
|
365
|
-
url,
|
|
366
|
-
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
chunks.push(value);
|
|
370
|
-
}
|
|
371
|
-
} finally {
|
|
372
|
-
try {
|
|
373
|
-
await reader.cancel();
|
|
374
|
-
} finally {
|
|
375
|
-
reader.releaseLock();
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
const result = new Uint8Array(totalBytes);
|
|
379
|
-
let offset = 0;
|
|
380
|
-
for (const chunk of chunks) {
|
|
381
|
-
result.set(chunk, offset);
|
|
382
|
-
offset += chunk.length;
|
|
383
|
-
}
|
|
384
|
-
return result;
|
|
335
|
+
// src/is-browser-runtime.ts
|
|
336
|
+
function isBrowserRuntime(globalThisAny = globalThis) {
|
|
337
|
+
return globalThisAny.window != null;
|
|
385
338
|
}
|
|
386
339
|
|
|
387
340
|
// src/validate-download-url.ts
|
|
@@ -404,7 +357,7 @@ function validateDownloadUrl(url) {
|
|
|
404
357
|
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
|
|
405
358
|
});
|
|
406
359
|
}
|
|
407
|
-
const hostname = parsed.hostname;
|
|
360
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, "");
|
|
408
361
|
if (!hostname) {
|
|
409
362
|
throw new DownloadError({
|
|
410
363
|
url,
|
|
@@ -447,53 +400,186 @@ function isIPv4(hostname) {
|
|
|
447
400
|
}
|
|
448
401
|
function isPrivateIPv4(ip) {
|
|
449
402
|
const parts = ip.split(".").map(Number);
|
|
450
|
-
const [a, b] = parts;
|
|
403
|
+
const [a, b, c] = parts;
|
|
451
404
|
if (a === 0) return true;
|
|
452
405
|
if (a === 10) return true;
|
|
406
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
453
407
|
if (a === 127) return true;
|
|
454
408
|
if (a === 169 && b === 254) return true;
|
|
455
409
|
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
410
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
456
411
|
if (a === 192 && b === 168) return true;
|
|
412
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
413
|
+
if (a >= 240) return true;
|
|
457
414
|
return false;
|
|
458
415
|
}
|
|
459
|
-
function
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
416
|
+
function parseIPv6(ip) {
|
|
417
|
+
let address = ip.toLowerCase();
|
|
418
|
+
const zoneIndex = address.indexOf("%");
|
|
419
|
+
if (zoneIndex !== -1) {
|
|
420
|
+
address = address.slice(0, zoneIndex);
|
|
421
|
+
}
|
|
422
|
+
const halves = address.split("::");
|
|
423
|
+
if (halves.length > 2) return null;
|
|
424
|
+
const toGroups = (segment) => {
|
|
425
|
+
if (segment === "") return [];
|
|
426
|
+
const groups = [];
|
|
427
|
+
const parts = segment.split(":");
|
|
428
|
+
for (let i = 0; i < parts.length; i++) {
|
|
429
|
+
const part = parts[i];
|
|
430
|
+
if (part.includes(".")) {
|
|
431
|
+
if (i !== parts.length - 1 || !isIPv4(part)) return null;
|
|
432
|
+
const [a, b, c, d] = part.split(".").map(Number);
|
|
433
|
+
groups.push(a << 8 | b, c << 8 | d);
|
|
434
|
+
continue;
|
|
478
435
|
}
|
|
436
|
+
if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
|
|
437
|
+
groups.push(parseInt(part, 16));
|
|
479
438
|
}
|
|
439
|
+
return groups;
|
|
440
|
+
};
|
|
441
|
+
const head = toGroups(halves[0]);
|
|
442
|
+
if (head === null) return null;
|
|
443
|
+
if (halves.length === 2) {
|
|
444
|
+
const tail = toGroups(halves[1]);
|
|
445
|
+
if (tail === null) return null;
|
|
446
|
+
const fill = 8 - head.length - tail.length;
|
|
447
|
+
if (fill < 0) return null;
|
|
448
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
449
|
+
}
|
|
450
|
+
return head.length === 8 ? head : null;
|
|
451
|
+
}
|
|
452
|
+
function isPrivateIPv6(ip) {
|
|
453
|
+
const groups = parseIPv6(ip);
|
|
454
|
+
if (groups === null) return true;
|
|
455
|
+
const topZero = (count) => groups.slice(0, count).every((group) => group === 0);
|
|
456
|
+
if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
|
|
457
|
+
if ((groups[0] & 65024) === 64512) return true;
|
|
458
|
+
if ((groups[0] & 65472) === 65152) return true;
|
|
459
|
+
if ((groups[0] & 65472) === 65216) return true;
|
|
460
|
+
if ((groups[0] & 65280) === 65280) return true;
|
|
461
|
+
const embedsIPv4 = (
|
|
462
|
+
// ::/96 — IPv4-compatible (deprecated)
|
|
463
|
+
topZero(6) || // ::ffff:0:0/96 — IPv4-mapped (ffff in group 5)
|
|
464
|
+
topZero(5) && groups[5] === 65535 || // ::ffff:0:0/96 — IPv4-translated form (ffff in group 4, group 5 zero)
|
|
465
|
+
topZero(4) && groups[4] === 65535 && groups[5] === 0 || // 64:ff9b::/96 — NAT64 well-known prefix
|
|
466
|
+
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
|
|
467
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 1
|
|
468
|
+
);
|
|
469
|
+
if (embedsIPv4) {
|
|
470
|
+
const a = groups[6] >> 8 & 255;
|
|
471
|
+
const b = groups[6] & 255;
|
|
472
|
+
const c = groups[7] >> 8 & 255;
|
|
473
|
+
const d = groups[7] & 255;
|
|
474
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
480
475
|
}
|
|
481
|
-
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
482
|
-
if (normalized.startsWith("fe80")) return true;
|
|
483
476
|
return false;
|
|
484
477
|
}
|
|
485
478
|
|
|
479
|
+
// src/fetch-with-validated-redirects.ts
|
|
480
|
+
var MAX_DOWNLOAD_REDIRECTS = 10;
|
|
481
|
+
async function fetchWithValidatedRedirects({
|
|
482
|
+
url,
|
|
483
|
+
headers,
|
|
484
|
+
abortSignal,
|
|
485
|
+
maxRedirects = MAX_DOWNLOAD_REDIRECTS
|
|
486
|
+
}) {
|
|
487
|
+
const baseInit = { signal: abortSignal };
|
|
488
|
+
if (headers !== void 0) {
|
|
489
|
+
baseInit.headers = headers;
|
|
490
|
+
}
|
|
491
|
+
let currentUrl = url;
|
|
492
|
+
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
493
|
+
validateDownloadUrl(currentUrl);
|
|
494
|
+
const response = await fetch(currentUrl, {
|
|
495
|
+
...baseInit,
|
|
496
|
+
redirect: "manual"
|
|
497
|
+
});
|
|
498
|
+
if (response.type === "opaqueredirect") {
|
|
499
|
+
if (!isBrowserRuntime()) {
|
|
500
|
+
throw new DownloadError({
|
|
501
|
+
url,
|
|
502
|
+
message: `Redirect from ${currentUrl} could not be validated and was blocked`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return await fetch(currentUrl, { ...baseInit, redirect: "follow" });
|
|
506
|
+
}
|
|
507
|
+
const location = response.headers.get("location");
|
|
508
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
509
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
return response;
|
|
513
|
+
}
|
|
514
|
+
throw new DownloadError({
|
|
515
|
+
url,
|
|
516
|
+
message: `Too many redirects (max ${maxRedirects})`
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/read-response-with-size-limit.ts
|
|
521
|
+
var DEFAULT_MAX_DOWNLOAD_SIZE = 2 * 1024 * 1024 * 1024;
|
|
522
|
+
async function readResponseWithSizeLimit({
|
|
523
|
+
response,
|
|
524
|
+
url,
|
|
525
|
+
maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE
|
|
526
|
+
}) {
|
|
527
|
+
const contentLength = response.headers.get("content-length");
|
|
528
|
+
if (contentLength != null) {
|
|
529
|
+
const length = parseInt(contentLength, 10);
|
|
530
|
+
if (!isNaN(length) && length > maxBytes) {
|
|
531
|
+
throw new DownloadError({
|
|
532
|
+
url,
|
|
533
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const body = response.body;
|
|
538
|
+
if (body == null) {
|
|
539
|
+
return new Uint8Array(0);
|
|
540
|
+
}
|
|
541
|
+
const reader = body.getReader();
|
|
542
|
+
const chunks = [];
|
|
543
|
+
let totalBytes = 0;
|
|
544
|
+
try {
|
|
545
|
+
while (true) {
|
|
546
|
+
const { done, value } = await reader.read();
|
|
547
|
+
if (done) {
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
totalBytes += value.length;
|
|
551
|
+
if (totalBytes > maxBytes) {
|
|
552
|
+
throw new DownloadError({
|
|
553
|
+
url,
|
|
554
|
+
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
chunks.push(value);
|
|
558
|
+
}
|
|
559
|
+
} finally {
|
|
560
|
+
try {
|
|
561
|
+
await reader.cancel();
|
|
562
|
+
} finally {
|
|
563
|
+
reader.releaseLock();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const result = new Uint8Array(totalBytes);
|
|
567
|
+
let offset = 0;
|
|
568
|
+
for (const chunk of chunks) {
|
|
569
|
+
result.set(chunk, offset);
|
|
570
|
+
offset += chunk.length;
|
|
571
|
+
}
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
|
|
486
575
|
// src/download-blob.ts
|
|
487
576
|
async function downloadBlob(url, options) {
|
|
488
577
|
var _a2, _b2;
|
|
489
|
-
validateDownloadUrl(url);
|
|
490
578
|
try {
|
|
491
|
-
const response = await
|
|
492
|
-
|
|
579
|
+
const response = await fetchWithValidatedRedirects({
|
|
580
|
+
url,
|
|
581
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
493
582
|
});
|
|
494
|
-
if (response.redirected) {
|
|
495
|
-
validateDownloadUrl(response.url);
|
|
496
|
-
}
|
|
497
583
|
if (!response.ok) {
|
|
498
584
|
throw new DownloadError({
|
|
499
585
|
url,
|
|
@@ -678,7 +764,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
678
764
|
}
|
|
679
765
|
|
|
680
766
|
// src/version.ts
|
|
681
|
-
var VERSION = true ? "4.0.
|
|
767
|
+
var VERSION = true ? "4.0.29" : "0.0.0-test";
|
|
682
768
|
|
|
683
769
|
// src/get-from-api.ts
|
|
684
770
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -794,6 +880,15 @@ function isNonNullable(value) {
|
|
|
794
880
|
return value != null;
|
|
795
881
|
}
|
|
796
882
|
|
|
883
|
+
// src/is-same-origin.ts
|
|
884
|
+
function isSameOrigin(url, baseUrl) {
|
|
885
|
+
try {
|
|
886
|
+
return new URL(url).origin === new URL(baseUrl).origin;
|
|
887
|
+
} catch (e) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
797
892
|
// src/is-url-supported.ts
|
|
798
893
|
function isUrlSupported({
|
|
799
894
|
mediaType,
|
|
@@ -2812,14 +2907,17 @@ var import_stream2 = require("eventsource-parser/stream");
|
|
|
2812
2907
|
dynamicTool,
|
|
2813
2908
|
executeTool,
|
|
2814
2909
|
extractResponseHeaders,
|
|
2910
|
+
fetchWithValidatedRedirects,
|
|
2815
2911
|
generateId,
|
|
2816
2912
|
getErrorMessage,
|
|
2817
2913
|
getFromApi,
|
|
2818
2914
|
getRuntimeEnvironmentUserAgent,
|
|
2819
2915
|
injectJsonInstructionIntoMessages,
|
|
2820
2916
|
isAbortError,
|
|
2917
|
+
isBrowserRuntime,
|
|
2821
2918
|
isNonNullable,
|
|
2822
2919
|
isParsableJson,
|
|
2920
|
+
isSameOrigin,
|
|
2823
2921
|
isUrlSupported,
|
|
2824
2922
|
jsonSchema,
|
|
2825
2923
|
lazySchema,
|