@ai-sdk/provider-utils 5.0.0-beta.30 → 5.0.0-beta.49
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 +153 -0
- package/dist/index.d.ts +419 -41
- package/dist/index.js +222 -93
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/cancel-response-body.ts +19 -0
- package/src/download-blob.ts +8 -9
- package/src/extract-lines.ts +31 -0
- package/src/fetch-with-validated-redirects.ts +87 -0
- package/src/index.ts +5 -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/schema.ts +5 -1
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +6 -4
- package/src/types/content-part.ts +67 -6
- package/src/types/index.ts +10 -7
- package/src/types/infer-tool-context.ts +33 -4
- package/src/types/infer-tool-set-context.ts +36 -7
- package/src/types/sandbox.ts +217 -0
- package/src/types/tool-approval-request.ts +6 -0
- package/src/types/tool-execute-function.ts +6 -0
- package/src/types/tool.ts +44 -18
- package/src/validate-download-url.ts +113 -31
- package/src/types/sensitive-context.ts +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/provider-utils",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"@standard-schema/spec": "^1.1.0",
|
|
36
36
|
"@workflow/serde": "4.1.0",
|
|
37
37
|
"eventsource-parser": "^3.0.8",
|
|
38
|
-
"@ai-sdk/provider": "4.0.0-beta.
|
|
38
|
+
"@ai-sdk/provider": "4.0.0-beta.19"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@types/node": "
|
|
41
|
+
"@types/node": "22.19.19",
|
|
42
42
|
"msw": "2.7.0",
|
|
43
|
-
"tsup": "^8",
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
44
|
"typescript": "5.8.3",
|
|
45
45
|
"zod": "3.25.76",
|
|
46
46
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"zod": "^3.25.76 || ^4.1.8"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
|
-
"node": ">=
|
|
52
|
+
"node": ">=22"
|
|
53
53
|
},
|
|
54
54
|
"publishConfig": {
|
|
55
55
|
"access": "public",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancels a response body to release the underlying connection.
|
|
3
|
+
*
|
|
4
|
+
* When a fetch Response is rejected without consuming its body (e.g. a failed
|
|
5
|
+
* status code, an open-redirect rejection, or a Content-Length that exceeds the
|
|
6
|
+
* size limit), the underlying TCP socket is not returned to the connection pool
|
|
7
|
+
* and may stay open until the process runs out of file descriptors. Cancelling
|
|
8
|
+
* the body avoids this leak.
|
|
9
|
+
*
|
|
10
|
+
* Errors thrown while cancelling are ignored: the body may already be locked,
|
|
11
|
+
* disturbed, or absent, none of which should mask the original rejection.
|
|
12
|
+
*/
|
|
13
|
+
export async function cancelResponseBody(response: Response): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
await response.body?.cancel();
|
|
16
|
+
} catch {
|
|
17
|
+
// Ignore cancel errors so the original rejection is preserved.
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/download-blob.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { cancelResponseBody } from './cancel-response-body';
|
|
1
2
|
import { DownloadError } from './download-error';
|
|
3
|
+
import { fetchWithValidatedRedirects } from './fetch-with-validated-redirects';
|
|
2
4
|
import {
|
|
3
5
|
readResponseWithSizeLimit,
|
|
4
6
|
DEFAULT_MAX_DOWNLOAD_SIZE,
|
|
5
7
|
} from './read-response-with-size-limit';
|
|
6
|
-
import { validateDownloadUrl } from './validate-download-url';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Download a file from a URL and return it as a Blob.
|
|
@@ -20,18 +21,16 @@ export async function downloadBlob(
|
|
|
20
21
|
url: string,
|
|
21
22
|
options?: { maxBytes?: number; abortSignal?: AbortSignal },
|
|
22
23
|
): Promise<Blob> {
|
|
23
|
-
validateDownloadUrl(url);
|
|
24
24
|
try {
|
|
25
|
-
const response = await
|
|
26
|
-
|
|
25
|
+
const response = await fetchWithValidatedRedirects({
|
|
26
|
+
url,
|
|
27
|
+
abortSignal: options?.abortSignal,
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
// Validate final URL after redirects to prevent SSRF via open redirect
|
|
30
|
-
if (response.redirected) {
|
|
31
|
-
validateDownloadUrl(response.url);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
30
|
if (!response.ok) {
|
|
31
|
+
// Release the connection before rejecting so an error status from an
|
|
32
|
+
// attacker-controlled origin cannot leak open sockets.
|
|
33
|
+
await cancelResponseBody(response);
|
|
35
34
|
throw new DownloadError({
|
|
36
35
|
url,
|
|
37
36
|
statusCode: response.status,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts a 1-based inclusive line range from `text`, auto-detecting the
|
|
3
|
+
* file's line ending (`\r\n`, `\n`, or `\r`, in that priority).
|
|
4
|
+
*
|
|
5
|
+
* Mixed line endings are not supported: detection picks one and uses it for
|
|
6
|
+
* both the split and the rejoin, so files that mix conventions will not slice
|
|
7
|
+
* cleanly. When neither `startLine` nor `endLine` is provided, the input is
|
|
8
|
+
* returned unchanged. `endLine` past EOF clamps to the last line.
|
|
9
|
+
*/
|
|
10
|
+
export function extractLines({
|
|
11
|
+
text,
|
|
12
|
+
startLine,
|
|
13
|
+
endLine,
|
|
14
|
+
}: {
|
|
15
|
+
text: string;
|
|
16
|
+
startLine?: number;
|
|
17
|
+
endLine?: number;
|
|
18
|
+
}): string {
|
|
19
|
+
if (startLine == null && endLine == null) return text;
|
|
20
|
+
const lineEnding = text.includes('\r\n')
|
|
21
|
+
? '\r\n'
|
|
22
|
+
: text.includes('\n')
|
|
23
|
+
? '\n'
|
|
24
|
+
: text.includes('\r')
|
|
25
|
+
? '\r'
|
|
26
|
+
: '\n';
|
|
27
|
+
const lines = text.split(lineEnding);
|
|
28
|
+
const start = Math.max(1, startLine ?? 1) - 1;
|
|
29
|
+
const end = Math.min(lines.length, endLine ?? lines.length);
|
|
30
|
+
return lines.slice(start, end).join(lineEnding);
|
|
31
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { cancelResponseBody } from './cancel-response-body';
|
|
2
|
+
import { DownloadError } from './download-error';
|
|
3
|
+
import { isBrowserRuntime } from './is-browser-runtime';
|
|
4
|
+
import { validateDownloadUrl } from './validate-download-url';
|
|
5
|
+
|
|
6
|
+
const MAX_DOWNLOAD_REDIRECTS = 10;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a URL while enforcing the SSRF download guard on every hop.
|
|
10
|
+
*
|
|
11
|
+
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
|
|
12
|
+
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
|
|
13
|
+
* on the default `redirect: 'follow'` would issue the request to a redirect
|
|
14
|
+
* target (e.g. an internal address) before we ever see its URL, defeating the
|
|
15
|
+
* SSRF guard.
|
|
16
|
+
*
|
|
17
|
+
* A `redirect: 'manual'` request yields an unreadable opaque response in the
|
|
18
|
+
* browser (and in other spec-compliant fetch implementations), so the redirect
|
|
19
|
+
* target cannot be validated here. In a real browser this is safe to follow
|
|
20
|
+
* natively because SSRF is not reachable (fetch is constrained by CORS and
|
|
21
|
+
* cannot reach a server's internal network or cloud-metadata). On any other
|
|
22
|
+
* runtime we cannot validate the hop, so we fail closed rather than follow it
|
|
23
|
+
* blindly and bypass the SSRF guard.
|
|
24
|
+
*
|
|
25
|
+
* The returned response is the final (non-redirect) response. The caller is
|
|
26
|
+
* responsible for checking `response.ok` and reading the body.
|
|
27
|
+
*
|
|
28
|
+
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
29
|
+
* a redirect cannot be validated on a non-browser runtime.
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchWithValidatedRedirects({
|
|
32
|
+
url,
|
|
33
|
+
headers,
|
|
34
|
+
abortSignal,
|
|
35
|
+
maxRedirects = MAX_DOWNLOAD_REDIRECTS,
|
|
36
|
+
}: {
|
|
37
|
+
url: string;
|
|
38
|
+
headers?: HeadersInit;
|
|
39
|
+
abortSignal?: AbortSignal;
|
|
40
|
+
maxRedirects?: number;
|
|
41
|
+
}): Promise<Response> {
|
|
42
|
+
// Per-hop request options. Only the `redirect` mode varies between hops, so
|
|
43
|
+
// the rest is assembled once. `headers` is omitted entirely when not provided
|
|
44
|
+
// so callers that send none issue a bare request.
|
|
45
|
+
const baseInit: RequestInit = { signal: abortSignal };
|
|
46
|
+
if (headers !== undefined) {
|
|
47
|
+
baseInit.headers = headers;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let currentUrl = url;
|
|
51
|
+
// The bound also acts as a backstop against an unterminated redirect chain.
|
|
52
|
+
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
53
|
+
validateDownloadUrl(currentUrl);
|
|
54
|
+
|
|
55
|
+
const response = await fetch(currentUrl, {
|
|
56
|
+
...baseInit,
|
|
57
|
+
redirect: 'manual',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (response.type === 'opaqueredirect') {
|
|
61
|
+
if (!isBrowserRuntime()) {
|
|
62
|
+
throw new DownloadError({
|
|
63
|
+
url,
|
|
64
|
+
message: `Redirect from ${currentUrl} could not be validated and was blocked`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return await fetch(currentUrl, { ...baseInit, redirect: 'follow' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const location = response.headers.get('location');
|
|
71
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
72
|
+
// Release the redirect response's connection before moving to the next
|
|
73
|
+
// hop. Whether that hop is followed or rejected by the SSRF guard, an
|
|
74
|
+
// unconsumed 3xx body would leak the underlying socket.
|
|
75
|
+
await cancelResponseBody(response);
|
|
76
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return response;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new DownloadError({
|
|
84
|
+
url,
|
|
85
|
+
message: `Too many redirects (max ${maxRedirects})`,
|
|
86
|
+
});
|
|
87
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ export {
|
|
|
18
18
|
} from './detect-media-type';
|
|
19
19
|
export { downloadBlob } from './download-blob';
|
|
20
20
|
export { DownloadError } from './download-error';
|
|
21
|
+
export { fetchWithValidatedRedirects } from './fetch-with-validated-redirects';
|
|
22
|
+
export { extractLines } from './extract-lines';
|
|
21
23
|
export * from './extract-response-headers';
|
|
22
24
|
export * from './fetch-function';
|
|
23
25
|
export { filterNullable } from './filter-nullable';
|
|
@@ -28,7 +30,9 @@ export { getRuntimeEnvironmentUserAgent } from './get-runtime-environment-user-a
|
|
|
28
30
|
export type { HasRequiredKey } from './has-required-key';
|
|
29
31
|
export { injectJsonInstructionIntoMessages } from './inject-json-instruction';
|
|
30
32
|
export * from './is-abort-error';
|
|
33
|
+
export { isBrowserRuntime } from './is-browser-runtime';
|
|
31
34
|
export { isBuffer } from './is-buffer';
|
|
35
|
+
export { isSameOrigin } from './is-same-origin';
|
|
32
36
|
export { isNonNullable } from './is-non-nullable';
|
|
33
37
|
export { isProviderReference } from './is-provider-reference';
|
|
34
38
|
export { isUrlSupported } from './is-url-supported';
|
|
@@ -57,6 +61,7 @@ export {
|
|
|
57
61
|
createProviderExecutedToolFactory,
|
|
58
62
|
type ProviderExecutedToolFactory,
|
|
59
63
|
} from './provider-executed-tool-factory';
|
|
64
|
+
export { cancelResponseBody } from './cancel-response-body';
|
|
60
65
|
export {
|
|
61
66
|
DEFAULT_MAX_DOWNLOAD_SIZE,
|
|
62
67
|
readResponseWithSizeLimit,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `true` when running in a browser.
|
|
3
|
+
*
|
|
4
|
+
* Detection keys on the presence of a global `window`, matching the browser
|
|
5
|
+
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
|
|
6
|
+
* so the SDK has a single, consistent definition of "browser". Server runtimes
|
|
7
|
+
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
|
|
8
|
+
*/
|
|
9
|
+
export function isBrowserRuntime(
|
|
10
|
+
globalThisAny: any = globalThis as any,
|
|
11
|
+
): boolean {
|
|
12
|
+
return globalThisAny.window != null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when `url` has the same origin (scheme + host + port) as
|
|
3
|
+
* `baseUrl`.
|
|
4
|
+
*
|
|
5
|
+
* Used to decide whether provider credentials may be attached to a request to a
|
|
6
|
+
* URL taken from a provider response (e.g. a polling or media-download URL).
|
|
7
|
+
* Credentials must only be sent to the provider's own origin; a response that
|
|
8
|
+
* names a foreign host (a CDN, or an attacker-controlled host if the response
|
|
9
|
+
* is tampered with) must not receive the API key.
|
|
10
|
+
*
|
|
11
|
+
* Returns false if either value is not a valid absolute URL (fail-closed).
|
|
12
|
+
*/
|
|
13
|
+
export function isSameOrigin(url: string, baseUrl: string): boolean {
|
|
14
|
+
try {
|
|
15
|
+
return new URL(url).origin === new URL(baseUrl).origin;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cancelResponseBody } from './cancel-response-body';
|
|
1
2
|
import { DownloadError } from './download-error';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -40,6 +41,9 @@ export async function readResponseWithSizeLimit({
|
|
|
40
41
|
if (contentLength != null) {
|
|
41
42
|
const length = parseInt(contentLength, 10);
|
|
42
43
|
if (!isNaN(length) && length > maxBytes) {
|
|
44
|
+
// Cancel the body so the underlying connection is released back to the
|
|
45
|
+
// pool instead of being left open until the socket is exhausted.
|
|
46
|
+
await cancelResponseBody(response);
|
|
43
47
|
throw new DownloadError({
|
|
44
48
|
url,
|
|
45
49
|
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`,
|
package/src/schema.ts
CHANGED
|
@@ -136,7 +136,11 @@ export function asSchema<OBJECT>(
|
|
|
136
136
|
schema: FlexibleSchema<OBJECT> | undefined,
|
|
137
137
|
): Schema<OBJECT> {
|
|
138
138
|
return schema == null
|
|
139
|
-
? jsonSchema({
|
|
139
|
+
? jsonSchema({
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {},
|
|
142
|
+
additionalProperties: false,
|
|
143
|
+
})
|
|
140
144
|
: isSchema(schema)
|
|
141
145
|
? schema
|
|
142
146
|
: '~standard' in schema
|
|
@@ -14,16 +14,18 @@ export const parsePipelineDef = (
|
|
|
14
14
|
return parseDef(def.out._def, refs);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const inputSchema = parseDef(def.in._def, {
|
|
18
18
|
...refs,
|
|
19
19
|
currentPath: [...refs.currentPath, 'allOf', '0'],
|
|
20
20
|
});
|
|
21
|
-
const
|
|
21
|
+
const outputSchema = parseDef(def.out._def, {
|
|
22
22
|
...refs,
|
|
23
|
-
currentPath: [...refs.currentPath, 'allOf',
|
|
23
|
+
currentPath: [...refs.currentPath, 'allOf', inputSchema ? '1' : '0'],
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
return {
|
|
27
|
-
allOf: [
|
|
27
|
+
allOf: [inputSchema, outputSchema].filter(
|
|
28
|
+
(schema): schema is JsonSchema7Type => schema !== undefined,
|
|
29
|
+
),
|
|
28
30
|
};
|
|
29
31
|
};
|
|
@@ -317,6 +317,50 @@ export type ToolResultOutput =
|
|
|
317
317
|
providerOptions?: ProviderOptions;
|
|
318
318
|
}
|
|
319
319
|
| {
|
|
320
|
+
type: 'file';
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* File data as a tagged discriminated union:
|
|
324
|
+
*
|
|
325
|
+
* - `{ type: 'data', data }`: raw bytes
|
|
326
|
+
* (base64 string, Uint8Array, ArrayBuffer, Buffer)
|
|
327
|
+
* - `{ type: 'url', url }`: a URL that points to the file
|
|
328
|
+
* - `{ type: 'reference', reference }`: a provider reference
|
|
329
|
+
* from `uploadFile`
|
|
330
|
+
* - `{ type: 'text', text }`: inline text content (e.g. an inline
|
|
331
|
+
* text document)
|
|
332
|
+
*/
|
|
333
|
+
data: FileData;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Either a full IANA media type (`type/subtype`, e.g. `image/png`) or just
|
|
337
|
+
* the top-level IANA segment (e.g. `image`, `audio`, `video`, `text`).
|
|
338
|
+
*
|
|
339
|
+
* `*`-subtype wildcards (e.g. `image/*`) are normalized as equivalent to the
|
|
340
|
+
* top-level segment alone (e.g. `image`). Providers can use the helpers in
|
|
341
|
+
* `@ai-sdk/provider-utils` (`isFullMediaType`, `getTopLevelMediaType`,
|
|
342
|
+
* `detectMediaType`) to resolve the field according to their API
|
|
343
|
+
* requirements.
|
|
344
|
+
*
|
|
345
|
+
* @see https://www.iana.org/assignments/media-types/media-types.xhtml
|
|
346
|
+
*/
|
|
347
|
+
mediaType: string;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Optional filename of the file.
|
|
351
|
+
*/
|
|
352
|
+
filename?: string;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Provider-specific options.
|
|
356
|
+
*/
|
|
357
|
+
providerOptions?: ProviderOptions;
|
|
358
|
+
}
|
|
359
|
+
| {
|
|
360
|
+
/**
|
|
361
|
+
* @deprecated Use 'file' with mediaType + tagged data instead:
|
|
362
|
+
* `{ type: 'file', mediaType, data: { type: 'data', data } }`.
|
|
363
|
+
*/
|
|
320
364
|
type: 'file-data';
|
|
321
365
|
|
|
322
366
|
/**
|
|
@@ -341,6 +385,10 @@ export type ToolResultOutput =
|
|
|
341
385
|
providerOptions?: ProviderOptions;
|
|
342
386
|
}
|
|
343
387
|
| {
|
|
388
|
+
/**
|
|
389
|
+
* @deprecated Use 'file' with mediaType and tagged data instead:
|
|
390
|
+
* `{ type: 'file', mediaType, data: { type: 'url', url: new URL(url) } }`.
|
|
391
|
+
*/
|
|
344
392
|
type: 'file-url';
|
|
345
393
|
|
|
346
394
|
/**
|
|
@@ -352,7 +400,7 @@ export type ToolResultOutput =
|
|
|
352
400
|
* IANA media type.
|
|
353
401
|
* @see https://www.iana.org/assignments/media-types/media-types.xhtml
|
|
354
402
|
*/
|
|
355
|
-
mediaType?: string;
|
|
403
|
+
mediaType?: string;
|
|
356
404
|
|
|
357
405
|
/**
|
|
358
406
|
* Provider-specific options.
|
|
@@ -361,7 +409,8 @@ export type ToolResultOutput =
|
|
|
361
409
|
}
|
|
362
410
|
| {
|
|
363
411
|
/**
|
|
364
|
-
* @deprecated Use file
|
|
412
|
+
* @deprecated Use 'file' with tagged data instead:
|
|
413
|
+
* `{ type: 'file', mediaType, data: { type: 'reference', reference } }`.
|
|
365
414
|
*/
|
|
366
415
|
type: 'file-id';
|
|
367
416
|
|
|
@@ -381,6 +430,10 @@ export type ToolResultOutput =
|
|
|
381
430
|
providerOptions?: ProviderOptions;
|
|
382
431
|
}
|
|
383
432
|
| {
|
|
433
|
+
/**
|
|
434
|
+
* @deprecated Use 'file' with tagged data instead:
|
|
435
|
+
* `{ type: 'file', mediaType, data: { type: 'reference', reference } }`.
|
|
436
|
+
*/
|
|
384
437
|
type: 'file-reference';
|
|
385
438
|
|
|
386
439
|
/**
|
|
@@ -396,7 +449,9 @@ export type ToolResultOutput =
|
|
|
396
449
|
}
|
|
397
450
|
| {
|
|
398
451
|
/**
|
|
399
|
-
* @deprecated Use file
|
|
452
|
+
* @deprecated Use 'file' with mediaType (e.g. 'image' or a specific
|
|
453
|
+
* `image/*` subtype) and tagged data instead:
|
|
454
|
+
* `{ type: 'file', mediaType: 'image', data: { type: 'data', data } }`.
|
|
400
455
|
*/
|
|
401
456
|
type: 'image-data';
|
|
402
457
|
|
|
@@ -418,7 +473,9 @@ export type ToolResultOutput =
|
|
|
418
473
|
}
|
|
419
474
|
| {
|
|
420
475
|
/**
|
|
421
|
-
* @deprecated Use file
|
|
476
|
+
* @deprecated Use 'file' with `mediaType: 'image'` (or a specific
|
|
477
|
+
* `image/*` subtype) and tagged data instead:
|
|
478
|
+
* `{ type: 'file', mediaType: 'image', data: { type: 'url', url: new URL(url) } }`.
|
|
422
479
|
*/
|
|
423
480
|
type: 'image-url';
|
|
424
481
|
|
|
@@ -434,7 +491,9 @@ export type ToolResultOutput =
|
|
|
434
491
|
}
|
|
435
492
|
| {
|
|
436
493
|
/**
|
|
437
|
-
* @deprecated Use file
|
|
494
|
+
* @deprecated Use 'file' with `mediaType: 'image'` (or a specific
|
|
495
|
+
* `image/*` subtype) and tagged data instead:
|
|
496
|
+
* `{ type: 'file', mediaType: 'image', data: { type: 'reference', reference } }`.
|
|
438
497
|
*/
|
|
439
498
|
type: 'image-file-id';
|
|
440
499
|
|
|
@@ -455,7 +514,9 @@ export type ToolResultOutput =
|
|
|
455
514
|
}
|
|
456
515
|
| {
|
|
457
516
|
/**
|
|
458
|
-
* @deprecated Use file
|
|
517
|
+
* @deprecated Use 'file' with `mediaType: 'image'` (or a specific
|
|
518
|
+
* `image/*` subtype) and tagged data instead:
|
|
519
|
+
* `{ type: 'file', mediaType: 'image', data: { type: 'reference', reference } }`.
|
|
459
520
|
*/
|
|
460
521
|
type: 'image-file-reference';
|
|
461
522
|
|
package/src/types/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type {
|
|
|
15
15
|
} from './content-part';
|
|
16
16
|
export type { Context } from './context';
|
|
17
17
|
export type { DataContent } from './data-content';
|
|
18
|
+
export { isExecutableTool, type ExecutableTool } from './executable-tool';
|
|
18
19
|
export { executeTool } from './execute-tool';
|
|
19
20
|
export type {
|
|
20
21
|
FileData,
|
|
@@ -23,7 +24,6 @@ export type {
|
|
|
23
24
|
FileDataText,
|
|
24
25
|
FileDataUrl,
|
|
25
26
|
} from './file-data';
|
|
26
|
-
export { isExecutableTool, type ExecutableTool } from './executable-tool';
|
|
27
27
|
export type { InferToolContext } from './infer-tool-context';
|
|
28
28
|
export type { InferToolInput } from './infer-tool-input';
|
|
29
29
|
export type { InferToolOutput } from './infer-tool-output';
|
|
@@ -31,7 +31,10 @@ export type { InferToolSetContext } from './infer-tool-set-context';
|
|
|
31
31
|
export type { ModelMessage } from './model-message';
|
|
32
32
|
export type { ProviderOptions } from './provider-options';
|
|
33
33
|
export type { ProviderReference } from './provider-reference';
|
|
34
|
-
export type {
|
|
34
|
+
export type {
|
|
35
|
+
SandboxSession as Experimental_SandboxSession,
|
|
36
|
+
SandboxProcess as Experimental_SandboxProcess,
|
|
37
|
+
} from './sandbox';
|
|
35
38
|
export type { SystemModelMessage } from './system-model-message';
|
|
36
39
|
export {
|
|
37
40
|
dynamicTool,
|
|
@@ -42,15 +45,15 @@ export {
|
|
|
42
45
|
type ProviderExecutedTool,
|
|
43
46
|
type Tool,
|
|
44
47
|
} from './tool';
|
|
48
|
+
export type { ToolApprovalRequest } from './tool-approval-request';
|
|
49
|
+
export type { ToolApprovalResponse } from './tool-approval-response';
|
|
50
|
+
export type { ToolCall } from './tool-call';
|
|
45
51
|
export type {
|
|
46
52
|
ToolExecuteFunction,
|
|
47
53
|
ToolExecutionOptions,
|
|
48
54
|
} from './tool-execute-function';
|
|
49
|
-
export type { ToolNeedsApprovalFunction } from './tool-needs-approval-function';
|
|
50
|
-
export type { ToolSet } from './tool-set';
|
|
51
|
-
export type { ToolApprovalRequest } from './tool-approval-request';
|
|
52
|
-
export type { ToolApprovalResponse } from './tool-approval-response';
|
|
53
|
-
export type { ToolCall } from './tool-call';
|
|
54
55
|
export type { ToolContent, ToolModelMessage } from './tool-model-message';
|
|
56
|
+
export type { ToolNeedsApprovalFunction } from './tool-needs-approval-function';
|
|
55
57
|
export type { ToolResult } from './tool-result';
|
|
58
|
+
export type { ToolSet } from './tool-set';
|
|
56
59
|
export type { UserContent, UserModelMessage } from './user-model-message';
|
|
@@ -1,12 +1,41 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Context } from './context';
|
|
2
2
|
import type { Tool } from './tool';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Detects the `any` type so untyped tools can be treated as having no explicit
|
|
6
|
+
* context type.
|
|
7
|
+
*/
|
|
8
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detects exact empty object contexts, including `{}` combined with
|
|
12
|
+
* `undefined`, which do not provide tool-specific context properties.
|
|
13
|
+
*/
|
|
14
|
+
type IsEmptyObject<T> = keyof NonNullable<T> extends never ? true : false;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detects context types that come from omitted or broad context declarations
|
|
18
|
+
* rather than a concrete tool context schema.
|
|
19
|
+
*/
|
|
20
|
+
type IsUntypedContext<CONTEXT> =
|
|
21
|
+
IsAny<CONTEXT> extends true
|
|
22
|
+
? true
|
|
23
|
+
: unknown extends CONTEXT
|
|
24
|
+
? true
|
|
25
|
+
: IsEmptyObject<CONTEXT> extends true
|
|
26
|
+
? true
|
|
27
|
+
: string extends keyof CONTEXT
|
|
28
|
+
? CONTEXT extends Context
|
|
29
|
+
? true
|
|
30
|
+
: false
|
|
31
|
+
: false;
|
|
32
|
+
|
|
4
33
|
/**
|
|
5
34
|
* Infer the context type of a tool.
|
|
6
35
|
*/
|
|
7
36
|
export type InferToolContext<TOOL extends Tool> =
|
|
8
37
|
TOOL extends Tool<any, any, infer CONTEXT>
|
|
9
|
-
?
|
|
10
|
-
?
|
|
11
|
-
:
|
|
38
|
+
? IsUntypedContext<CONTEXT> extends true
|
|
39
|
+
? never
|
|
40
|
+
: CONTEXT
|
|
12
41
|
: never;
|
|
@@ -2,14 +2,43 @@ import type { InferToolContext } from './infer-tool-context';
|
|
|
2
2
|
import type { ToolSet } from './tool-set';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
* Builds the required portion of the tool context map for tools whose context
|
|
6
|
+
* type does not include `undefined`.
|
|
7
|
+
*/
|
|
8
|
+
type RequiredToolSetContext<TOOLS extends ToolSet> = {
|
|
9
|
+
[K in keyof TOOLS as InferToolContext<NoInfer<TOOLS[K]>> extends never
|
|
10
|
+
? never
|
|
11
|
+
: undefined extends InferToolContext<NoInfer<TOOLS[K]>>
|
|
12
|
+
? never
|
|
13
|
+
: K]: InferToolContext<NoInfer<TOOLS[K]>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Builds the optional portion of the tool context map for tools whose context
|
|
18
|
+
* object itself may be `undefined`.
|
|
10
19
|
*/
|
|
11
|
-
|
|
20
|
+
type OptionalToolSetContext<TOOLS extends ToolSet> = {
|
|
12
21
|
[K in keyof TOOLS as InferToolContext<NoInfer<TOOLS[K]>> extends never
|
|
13
22
|
? never
|
|
14
|
-
:
|
|
23
|
+
: undefined extends InferToolContext<NoInfer<TOOLS[K]>>
|
|
24
|
+
? K
|
|
25
|
+
: never]?: InferToolContext<NoInfer<TOOLS[K]>>;
|
|
15
26
|
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Flattens intersected mapped types so type equality assertions and editor
|
|
30
|
+
* hovers show the resulting object shape.
|
|
31
|
+
*/
|
|
32
|
+
type Normalize<OBJECT> = { [KEY in keyof OBJECT]: OBJECT[KEY] };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Infer the context type for a tool set.
|
|
36
|
+
*
|
|
37
|
+
* The inferred type maps each contextual tool name to its context type.
|
|
38
|
+
*
|
|
39
|
+
* Tools without concrete context are omitted. Tool contexts that include
|
|
40
|
+
* `undefined` are represented as optional properties.
|
|
41
|
+
*/
|
|
42
|
+
export type InferToolSetContext<TOOLS extends ToolSet> = Normalize<
|
|
43
|
+
RequiredToolSetContext<TOOLS> & OptionalToolSetContext<TOOLS>
|
|
44
|
+
>;
|