@copilotkit/runtime 1.81.0 → 1.83.0
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/dist/agent/index.cjs +18 -10
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +41 -1
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +42 -2
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +18 -10
- package/dist/agent/index.mjs.map +1 -1
- package/dist/agent/url-download.cjs +504 -0
- package/dist/agent/url-download.cjs.map +1 -0
- package/dist/agent/url-download.d.cts +80 -0
- package/dist/agent/url-download.d.cts.map +1 -0
- package/dist/agent/url-download.d.mts +80 -0
- package/dist/agent/url-download.d.mts.map +1 -0
- package/dist/agent/url-download.mjs +501 -0
- package/dist/agent/url-download.mjs.map +1 -0
- package/dist/lib/runtime/copilot-runtime.cjs +2 -1
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts +5 -0
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts +5 -0
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +2 -1
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/lib/runtime/telemetry-agent-runner.cjs +4 -1
- package/dist/lib/runtime/telemetry-agent-runner.cjs.map +1 -1
- package/dist/lib/runtime/telemetry-agent-runner.d.cts +7 -1
- package/dist/lib/runtime/telemetry-agent-runner.d.cts.map +1 -1
- package/dist/lib/runtime/telemetry-agent-runner.d.mts +7 -1
- package/dist/lib/runtime/telemetry-agent-runner.d.mts.map +1 -1
- package/dist/lib/runtime/telemetry-agent-runner.mjs +4 -1
- package/dist/lib/runtime/telemetry-agent-runner.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/v2/index.cjs +12 -3
- package/dist/v2/index.d.cts +5 -3
- package/dist/v2/index.d.mts +5 -3
- package/dist/v2/index.mjs +6 -5
- package/dist/v2/runtime/core/channel-manager.cjs +2 -1
- package/dist/v2/runtime/core/channel-manager.cjs.map +1 -1
- package/dist/v2/runtime/core/channel-manager.mjs +2 -1
- package/dist/v2/runtime/core/channel-manager.mjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.cjs +3 -3
- package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.mjs +3 -3
- package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
- package/dist/v2/runtime/core/package-info.cjs +1 -1
- package/dist/v2/runtime/core/package-info.mjs +1 -1
- package/dist/v2/runtime/core/runtime.cjs +12 -2
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +40 -1
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +40 -1
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +12 -2
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.cjs +6 -6
- package/dist/v2/runtime/handlers/get-runtime-info.cjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.mjs +6 -6
- package/dist/v2/runtime/handlers/get-runtime-info.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs +9 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs +9 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-inspector-learning.cjs +1 -1
- package/dist/v2/runtime/handlers/handle-inspector-learning.mjs +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +16 -2
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +16 -2
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.cjs +14 -3
- package/dist/v2/runtime/handlers/handle-stop.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.mjs +14 -3
- package/dist/v2/runtime/handlers/handle-stop.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-trajectory-connect.cjs +1 -1
- package/dist/v2/runtime/handlers/handle-trajectory-connect.mjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/annotate.cjs +2 -2
- package/dist/v2/runtime/handlers/intelligence/annotate.mjs +2 -2
- package/dist/v2/runtime/handlers/intelligence/memories.cjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/memories.mjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/run.cjs +7 -4
- package/dist/v2/runtime/handlers/intelligence/run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/run.mjs +7 -4
- package/dist/v2/runtime/handlers/intelligence/run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/threads.cjs +137 -31
- package/dist/v2/runtime/handlers/intelligence/threads.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/threads.mjs +138 -32
- package/dist/v2/runtime/handlers/intelligence/threads.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs +3 -3
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs +3 -3
- package/dist/v2/runtime/handlers/shared/mcp-apps-servers.cjs +2 -1
- package/dist/v2/runtime/handlers/shared/mcp-apps-servers.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/mcp-apps-servers.mjs +3 -1
- package/dist/v2/runtime/handlers/shared/mcp-apps-servers.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/platform-error.cjs +1 -1
- package/dist/v2/runtime/handlers/shared/platform-error.mjs +1 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs +22 -3
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs +22 -4
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs +20 -3
- package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs +20 -3
- package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/thread-access.cjs +138 -0
- package/dist/v2/runtime/handlers/shared/thread-access.cjs.map +1 -0
- package/dist/v2/runtime/handlers/shared/thread-access.d.cts +29 -0
- package/dist/v2/runtime/handlers/shared/thread-access.d.cts.map +1 -0
- package/dist/v2/runtime/handlers/shared/thread-access.d.mts +29 -0
- package/dist/v2/runtime/handlers/shared/thread-access.d.mts.map +1 -0
- package/dist/v2/runtime/handlers/shared/thread-access.mjs +133 -0
- package/dist/v2/runtime/handlers/shared/thread-access.mjs.map +1 -0
- package/dist/v2/runtime/handlers/sse/connect.cjs +2 -1
- package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/connect.mjs +2 -1
- package/dist/v2/runtime/handlers/sse/connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.cjs +23 -6
- package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.mjs +23 -6
- package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
- package/dist/v2/runtime/index.d.cts +3 -2
- package/dist/v2/runtime/index.d.cts.map +1 -1
- package/dist/v2/runtime/index.d.mts +3 -2
- package/dist/v2/runtime/index.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.cjs +17 -4
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +9 -37
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +9 -37
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +17 -4
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.cjs +55 -0
- package/dist/v2/runtime/runner/agent-runner.cjs.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.d.cts +90 -6
- package/dist/v2/runtime/runner/agent-runner.d.cts.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.d.mts +90 -6
- package/dist/v2/runtime/runner/agent-runner.d.mts.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.mjs +50 -1
- package/dist/v2/runtime/runner/agent-runner.mjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.cjs +205 -25
- package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.cts +112 -15
- package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.mts +112 -15
- package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.mjs +206 -27
- package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
- package/dist/v2/runtime/runner/index.d.cts +2 -2
- package/dist/v2/runtime/runner/index.d.mts +2 -2
- package/dist/v2/runtime/runner/index.mjs +2 -2
- package/dist/v2/runtime/telemetry/telemetry-client.cjs +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.cjs.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.mjs +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.mjs.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { Experimental_DownloadFunction } from "ai";
|
|
3
|
+
|
|
4
|
+
//#region src/agent/url-download.d.ts
|
|
5
|
+
/** Options for the default, public-network-only URL downloader. */
|
|
6
|
+
interface SafeUrlDownloadOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Hostnames that may be downloaded. When omitted, any hostname is allowed.
|
|
9
|
+
* Every resolved address must be public in both cases, and every redirect
|
|
10
|
+
* target must match as well.
|
|
11
|
+
*
|
|
12
|
+
* - `cdn.example.com` matches exactly that host.
|
|
13
|
+
* - `*.example.com` matches any subdomain (`a.example.com`,
|
|
14
|
+
* `a.b.example.com`) but not `example.com` itself; list both if needed.
|
|
15
|
+
*
|
|
16
|
+
* Matching ignores case and a trailing dot. Unicode names match their
|
|
17
|
+
* punycode form, and an IPv6 address may be given with or without
|
|
18
|
+
* brackets. An entry with a scheme, port, path or user, and a wildcard over
|
|
19
|
+
* an IP address (`*.1.2.3.4`), are rejected when the downloader is created.
|
|
20
|
+
* An empty list refuses every server-side download; `data:` URLs and URLs
|
|
21
|
+
* that the model takes directly are not downloaded, so they still pass.
|
|
22
|
+
*/
|
|
23
|
+
allowedHosts?: string[];
|
|
24
|
+
/** Largest response body to accept, in bytes (at least 1). Default: 20 MiB. */
|
|
25
|
+
maxBytes?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Time limit for one download, including redirects, in ms. From 1 to
|
|
28
|
+
* 2147483647 (the largest delay `setTimeout` supports). Default: 30000.
|
|
29
|
+
*/
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** Largest number of redirects to follow; 0 follows none. Default: 3. */
|
|
32
|
+
maxRedirects?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Largest number of distinct URLs to download for one model call (at
|
|
35
|
+
* least 1); a URL that appears twice is downloaded once. The AI SDK
|
|
36
|
+
* downloads the attachments of every user message again for each step of
|
|
37
|
+
* a multi-step run. Default: 20.
|
|
38
|
+
*/
|
|
39
|
+
maxDownloads?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Largest number of bytes to download for one model call, over all its
|
|
42
|
+
* URLs (at least 1). Default: 50 MiB.
|
|
43
|
+
*/
|
|
44
|
+
maxTotalBytes?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Options for {@link createSafeUrlDownload}. */
|
|
47
|
+
interface CreateSafeUrlDownloadOptions extends SafeUrlDownloadOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Stops every download of this function when it aborts, for example the
|
|
50
|
+
* run's `ctx.abortSignal` in BuiltInAgent's factory mode. Without it, a
|
|
51
|
+
* download that is running when the run is aborted goes on until it ends
|
|
52
|
+
* or reaches `timeoutMs`.
|
|
53
|
+
*/
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates an AI SDK download function (`experimental_download`) that only
|
|
58
|
+
* fetches public internet addresses, with the same checks and limits as
|
|
59
|
+
* BuiltInAgent's default downloader. Pass it to `streamText` yourself when
|
|
60
|
+
* you use BuiltInAgent's AI SDK factory mode. Create it in the factory with
|
|
61
|
+
* the run's abort signal, so that an aborted run stops its downloads:
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createSafeUrlDownload } from "@copilotkit/runtime/v2";
|
|
65
|
+
*
|
|
66
|
+
* factory: (ctx) =>
|
|
67
|
+
* streamText({
|
|
68
|
+
* model,
|
|
69
|
+
* messages,
|
|
70
|
+
* abortSignal: ctx.abortSignal,
|
|
71
|
+
* experimental_download: createSafeUrlDownload({ signal: ctx.abortSignal }),
|
|
72
|
+
* }),
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Invalid options throw here, when the function is created.
|
|
76
|
+
*/
|
|
77
|
+
declare function createSafeUrlDownload(options?: CreateSafeUrlDownloadOptions): Experimental_DownloadFunction;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { CreateSafeUrlDownloadOptions, SafeUrlDownloadOptions, createSafeUrlDownload };
|
|
80
|
+
//# sourceMappingURL=url-download.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-download.d.cts","names":[],"sources":["../../src/agent/url-download.ts"],"mappings":";;;;;UAwCiB,sBAAA;EAAsB;;;;;;;;;;;AA42BvC;;;;;EA31BE,YAAA;EAk2BS;EAh2BT,QAAA;EAg2BoB;AAwBtB;;;EAn3BE,SAAA;EAo3BS;EAl3BT,YAAA;EAm3BC;;;;;;EA52BD,YAAA;;;;;EAKA,aAAA;AAAA;;UAs0Be,4BAAA,SAAqC,sBAAA;;;;;;;EAOpD,MAAA,GAAS,WAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;iBAwBK,qBAAA,CACd,OAAA,GAAS,4BAAA,GACR,6BAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { Experimental_DownloadFunction } from "ai";
|
|
3
|
+
|
|
4
|
+
//#region src/agent/url-download.d.ts
|
|
5
|
+
/** Options for the default, public-network-only URL downloader. */
|
|
6
|
+
interface SafeUrlDownloadOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Hostnames that may be downloaded. When omitted, any hostname is allowed.
|
|
9
|
+
* Every resolved address must be public in both cases, and every redirect
|
|
10
|
+
* target must match as well.
|
|
11
|
+
*
|
|
12
|
+
* - `cdn.example.com` matches exactly that host.
|
|
13
|
+
* - `*.example.com` matches any subdomain (`a.example.com`,
|
|
14
|
+
* `a.b.example.com`) but not `example.com` itself; list both if needed.
|
|
15
|
+
*
|
|
16
|
+
* Matching ignores case and a trailing dot. Unicode names match their
|
|
17
|
+
* punycode form, and an IPv6 address may be given with or without
|
|
18
|
+
* brackets. An entry with a scheme, port, path or user, and a wildcard over
|
|
19
|
+
* an IP address (`*.1.2.3.4`), are rejected when the downloader is created.
|
|
20
|
+
* An empty list refuses every server-side download; `data:` URLs and URLs
|
|
21
|
+
* that the model takes directly are not downloaded, so they still pass.
|
|
22
|
+
*/
|
|
23
|
+
allowedHosts?: string[];
|
|
24
|
+
/** Largest response body to accept, in bytes (at least 1). Default: 20 MiB. */
|
|
25
|
+
maxBytes?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Time limit for one download, including redirects, in ms. From 1 to
|
|
28
|
+
* 2147483647 (the largest delay `setTimeout` supports). Default: 30000.
|
|
29
|
+
*/
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** Largest number of redirects to follow; 0 follows none. Default: 3. */
|
|
32
|
+
maxRedirects?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Largest number of distinct URLs to download for one model call (at
|
|
35
|
+
* least 1); a URL that appears twice is downloaded once. The AI SDK
|
|
36
|
+
* downloads the attachments of every user message again for each step of
|
|
37
|
+
* a multi-step run. Default: 20.
|
|
38
|
+
*/
|
|
39
|
+
maxDownloads?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Largest number of bytes to download for one model call, over all its
|
|
42
|
+
* URLs (at least 1). Default: 50 MiB.
|
|
43
|
+
*/
|
|
44
|
+
maxTotalBytes?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Options for {@link createSafeUrlDownload}. */
|
|
47
|
+
interface CreateSafeUrlDownloadOptions extends SafeUrlDownloadOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Stops every download of this function when it aborts, for example the
|
|
50
|
+
* run's `ctx.abortSignal` in BuiltInAgent's factory mode. Without it, a
|
|
51
|
+
* download that is running when the run is aborted goes on until it ends
|
|
52
|
+
* or reaches `timeoutMs`.
|
|
53
|
+
*/
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates an AI SDK download function (`experimental_download`) that only
|
|
58
|
+
* fetches public internet addresses, with the same checks and limits as
|
|
59
|
+
* BuiltInAgent's default downloader. Pass it to `streamText` yourself when
|
|
60
|
+
* you use BuiltInAgent's AI SDK factory mode. Create it in the factory with
|
|
61
|
+
* the run's abort signal, so that an aborted run stops its downloads:
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createSafeUrlDownload } from "@copilotkit/runtime/v2";
|
|
65
|
+
*
|
|
66
|
+
* factory: (ctx) =>
|
|
67
|
+
* streamText({
|
|
68
|
+
* model,
|
|
69
|
+
* messages,
|
|
70
|
+
* abortSignal: ctx.abortSignal,
|
|
71
|
+
* experimental_download: createSafeUrlDownload({ signal: ctx.abortSignal }),
|
|
72
|
+
* }),
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Invalid options throw here, when the function is created.
|
|
76
|
+
*/
|
|
77
|
+
declare function createSafeUrlDownload(options?: CreateSafeUrlDownloadOptions): Experimental_DownloadFunction;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { CreateSafeUrlDownloadOptions, SafeUrlDownloadOptions, createSafeUrlDownload };
|
|
80
|
+
//# sourceMappingURL=url-download.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-download.d.mts","names":[],"sources":["../../src/agent/url-download.ts"],"mappings":";;;;;UAwCiB,sBAAA;EAAsB;;;;;;;;;;;AA42BvC;;;;;EA31BE,YAAA;EAk2BS;EAh2BT,QAAA;EAg2BoB;AAwBtB;;;EAn3BE,SAAA;EAo3BS;EAl3BT,YAAA;EAm3BC;;;;;;EA52BD,YAAA;;;;;EAKA,aAAA;AAAA;;UAs0Be,4BAAA,SAAqC,sBAAA;;;;;;;EAOpD,MAAA,GAAS,WAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;iBAwBK,qBAAA,CACd,OAAA,GAAS,4BAAA,GACR,6BAAA"}
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { COPILOTKIT_VERSION } from "@copilotkit/shared";
|
|
3
|
+
import { DownloadError } from "ai";
|
|
4
|
+
|
|
5
|
+
//#region src/agent/url-download.ts
|
|
6
|
+
const DEFAULT_MAX_BYTES = 20 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
8
|
+
const DEFAULT_MAX_REDIRECTS = 3;
|
|
9
|
+
const DEFAULT_MAX_DOWNLOADS = 20;
|
|
10
|
+
const DEFAULT_MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
11
|
+
const HOST_REFUSED = "The URL host is not a public address, or it could not be resolved.";
|
|
12
|
+
const DOWNLOAD_FAILED = "The download failed.";
|
|
13
|
+
const USER_AGENT = `CopilotKit-Runtime/${COPILOTKIT_VERSION}`;
|
|
14
|
+
const REDIRECT_STATUSES = new Set([
|
|
15
|
+
301,
|
|
16
|
+
302,
|
|
17
|
+
303,
|
|
18
|
+
307,
|
|
19
|
+
308
|
|
20
|
+
]);
|
|
21
|
+
let nodeModules;
|
|
22
|
+
function loadNodeModules() {
|
|
23
|
+
nodeModules ??= Promise.all([
|
|
24
|
+
import("node:dns/promises"),
|
|
25
|
+
import("node:http"),
|
|
26
|
+
import("node:https"),
|
|
27
|
+
import("node:net")
|
|
28
|
+
]).then(([dns, http, https, net]) => ({
|
|
29
|
+
dns,
|
|
30
|
+
http,
|
|
31
|
+
https,
|
|
32
|
+
net
|
|
33
|
+
})).catch((error) => {
|
|
34
|
+
nodeModules = void 0;
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
37
|
+
return nodeModules;
|
|
38
|
+
}
|
|
39
|
+
let defaultPolicy;
|
|
40
|
+
function buildAddressPolicy(net) {
|
|
41
|
+
const nonPublicV4 = new net.BlockList();
|
|
42
|
+
for (const [address, prefix] of [
|
|
43
|
+
["0.0.0.0", 8],
|
|
44
|
+
["10.0.0.0", 8],
|
|
45
|
+
["100.64.0.0", 10],
|
|
46
|
+
["127.0.0.0", 8],
|
|
47
|
+
["169.254.0.0", 16],
|
|
48
|
+
["172.16.0.0", 12],
|
|
49
|
+
["192.0.0.0", 24],
|
|
50
|
+
["192.0.2.0", 24],
|
|
51
|
+
["192.88.99.0", 24],
|
|
52
|
+
["192.168.0.0", 16],
|
|
53
|
+
["198.18.0.0", 15],
|
|
54
|
+
["198.51.100.0", 24],
|
|
55
|
+
["203.0.113.0", 24],
|
|
56
|
+
["224.0.0.0", 4],
|
|
57
|
+
["240.0.0.0", 4]
|
|
58
|
+
]) nonPublicV4.addSubnet(address, prefix, "ipv4");
|
|
59
|
+
const globalV6 = new net.BlockList();
|
|
60
|
+
globalV6.addSubnet("2000::", 3, "ipv6");
|
|
61
|
+
const nonPublicV6 = new net.BlockList();
|
|
62
|
+
for (const [address, prefix] of [
|
|
63
|
+
["2001::", 23],
|
|
64
|
+
["2001:db8::", 32],
|
|
65
|
+
["2002::", 16],
|
|
66
|
+
["3fff::", 20]
|
|
67
|
+
]) nonPublicV6.addSubnet(address, prefix, "ipv6");
|
|
68
|
+
return (address) => {
|
|
69
|
+
const family = net.isIP(address);
|
|
70
|
+
if (family === 4) return !nonPublicV4.check(address, "ipv4");
|
|
71
|
+
if (family !== 6 || address.includes("%")) return false;
|
|
72
|
+
return globalV6.check(address, "ipv6") && !nonPublicV6.check(address, "ipv6");
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* True when the server must not fetch this URL itself and returns `null` so
|
|
77
|
+
* the AI SDK handles it: the provider takes the URL as-is, or it is a `data:`
|
|
78
|
+
* URL, which the AI SDK decodes locally without any network request.
|
|
79
|
+
*/
|
|
80
|
+
function leftToAiSdk({ url, isUrlSupportedByModel }) {
|
|
81
|
+
return isUrlSupportedByModel || url.protocol === "data:";
|
|
82
|
+
}
|
|
83
|
+
/** The URL without its query and fragment, which can hold credentials. */
|
|
84
|
+
function urlForLog(url) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(String(url));
|
|
87
|
+
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
88
|
+
} catch {
|
|
89
|
+
return "(invalid URL)";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** `text` with the query and fragment removed from every URL in it. */
|
|
93
|
+
function redactUrls(text) {
|
|
94
|
+
return text.replace(/\b([a-z][a-z0-9+.-]*:\/\/[^\s?#"'`<>]*)[?#][^\s"'`<>]*/gi, "$1");
|
|
95
|
+
}
|
|
96
|
+
const loggedErrors = /* @__PURE__ */ new WeakSet();
|
|
97
|
+
/**
|
|
98
|
+
* Returns the error for a failed or refused download and logs it on the
|
|
99
|
+
* server. `detail` goes only to the log: the client sees `message`, which
|
|
100
|
+
* never holds the URL's query, a resolved address or a raw network error.
|
|
101
|
+
* For a refused host, `message` is the same whether the name did not resolve
|
|
102
|
+
* or resolved to a non-public address, so that it does not reveal internal
|
|
103
|
+
* DNS names.
|
|
104
|
+
*/
|
|
105
|
+
function refuse(url, message, detail, statusCode) {
|
|
106
|
+
console.warn(redactUrls(`[BuiltInAgent] URL download failed for ${urlForLog(url)}: ${message}${detail ? ` (${detail})` : ""}`));
|
|
107
|
+
const error = new DownloadError({
|
|
108
|
+
url: urlForLog(url),
|
|
109
|
+
message,
|
|
110
|
+
statusCode
|
|
111
|
+
});
|
|
112
|
+
loggedErrors.add(error);
|
|
113
|
+
return error;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* @internal BuiltInAgent's `onError` for its `streamText` call. The AI SDK's
|
|
117
|
+
* default handler prints the whole error, which for a download error holds
|
|
118
|
+
* the URL with its query. This logs an error once, with the same detail
|
|
119
|
+
* (for a provider API error, its status code and response body) but with the
|
|
120
|
+
* query and fragment removed from every URL, and skips the download errors
|
|
121
|
+
* that were already logged here.
|
|
122
|
+
*/
|
|
123
|
+
function logModelCallError(error) {
|
|
124
|
+
if (typeof error === "object" && error !== null && loggedErrors.has(error)) return;
|
|
125
|
+
const frames = error instanceof Error && error.stack ? error.stack.split("\n").filter((line) => /^\s+at /.test(line)).join("\n") : "";
|
|
126
|
+
console.error(redactUrls(`[BuiltInAgent] The model call failed: ${describeModelError(error)}${frames ? `\n${frames}` : ""}`));
|
|
127
|
+
}
|
|
128
|
+
const MAX_LOGGED_FIELD_LENGTH = 2e3;
|
|
129
|
+
function truncateForLog(text) {
|
|
130
|
+
return text.length > MAX_LOGGED_FIELD_LENGTH ? `${text.slice(0, MAX_LOGGED_FIELD_LENGTH)}… (${text.length} characters)` : text;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* {@link describeError}, followed by the other fields of the error, such as
|
|
134
|
+
* the `statusCode`, `responseBody` and `url` of the AI SDK's APICallError. A
|
|
135
|
+
* value that is not an Error, such as the plain object that a provider
|
|
136
|
+
* stream sends as its error, is logged as JSON.
|
|
137
|
+
*/
|
|
138
|
+
function describeModelError(error) {
|
|
139
|
+
if (!(error instanceof Error)) return truncateForLog(describeValue(error));
|
|
140
|
+
const fields = Object.entries(error).filter(([key]) => ![
|
|
141
|
+
"name",
|
|
142
|
+
"message",
|
|
143
|
+
"stack",
|
|
144
|
+
"cause",
|
|
145
|
+
"code"
|
|
146
|
+
].includes(key)).map(([key, value]) => {
|
|
147
|
+
return `${key}: ${truncateForLog(value instanceof Error ? describeError(value) : typeof value === "string" ? value : describeValue(value))}`;
|
|
148
|
+
});
|
|
149
|
+
return `${error.name && error.name !== "Error" ? `${error.name}: ` : ""}${describeError(error)}${fields.length > 0 ? ` {${fields.join("; ")}}` : ""}`;
|
|
150
|
+
}
|
|
151
|
+
/** Message, error code and causes of an error, for the server log. */
|
|
152
|
+
function describeError(error, depth = 0) {
|
|
153
|
+
if (typeof error === "string") return error;
|
|
154
|
+
if (!(error instanceof Error)) return truncateForLog(describeValue(error));
|
|
155
|
+
const code = error.code;
|
|
156
|
+
const text = typeof code === "string" && !error.message.includes(code) ? `${code}: ${error.message}` : error.message;
|
|
157
|
+
return error.cause !== void 0 && depth < 3 ? `${text}; caused by ${describeError(error.cause, depth + 1)}` : text;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A printable form of a configuration value for an error message. Unlike
|
|
161
|
+
* `JSON.stringify`, it never throws (BigInt) and never gives `undefined`
|
|
162
|
+
* (Symbol, function).
|
|
163
|
+
*/
|
|
164
|
+
function describeValue(value) {
|
|
165
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
166
|
+
if (typeof value === "function") return "a function";
|
|
167
|
+
try {
|
|
168
|
+
const text = JSON.stringify(value);
|
|
169
|
+
if (text !== void 0) return text;
|
|
170
|
+
} catch {}
|
|
171
|
+
try {
|
|
172
|
+
return String(value);
|
|
173
|
+
} catch {
|
|
174
|
+
return Object.prototype.toString.call(value);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The form in which hostnames are compared: a URL hostname (lowercase ASCII,
|
|
179
|
+
* IDNA as punycode, IPv6 in canonical form) without IPv6 brackets and without
|
|
180
|
+
* one trailing dot.
|
|
181
|
+
*/
|
|
182
|
+
function comparableHost(hostname) {
|
|
183
|
+
return hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validates one `allowedHosts` entry and returns it in comparable form: an
|
|
187
|
+
* exact host, or `*.` followed by a host suffix.
|
|
188
|
+
*/
|
|
189
|
+
function normalizeAllowedHost(entry) {
|
|
190
|
+
const invalid = (reason) => /* @__PURE__ */ new Error(`urlDownload.allowedHosts entry ${describeValue(entry)} is invalid: ${reason}`);
|
|
191
|
+
if (typeof entry !== "string" || entry === "") throw invalid("it must be a non-empty hostname");
|
|
192
|
+
if (/[\s/?#@\\]/.test(entry)) throw invalid("give only a hostname, without a scheme, path or user");
|
|
193
|
+
const wildcard = entry.startsWith("*.");
|
|
194
|
+
const host = wildcard ? entry.slice(2) : entry;
|
|
195
|
+
if (host === "" || host === "." || host.includes("*")) throw invalid("\"*\" is allowed only as a leading \"*.\" label");
|
|
196
|
+
if (/^[^:[\]]*:\d*$/.test(host) || /^\[.*\]:\d*$/.test(host)) throw invalid("give only a hostname, without a port");
|
|
197
|
+
const bare = host.replace(/^\[(.*)\]$/, "$1");
|
|
198
|
+
const authority = bare.includes(":") ? `[${bare}]` : host;
|
|
199
|
+
let parsed;
|
|
200
|
+
try {
|
|
201
|
+
parsed = new URL(`http://${authority}/`);
|
|
202
|
+
} catch {
|
|
203
|
+
throw invalid("it is not a valid hostname");
|
|
204
|
+
}
|
|
205
|
+
const normalized = comparableHost(parsed.hostname);
|
|
206
|
+
if (wildcard && (normalized.includes(":") || /^\d+\.\d+\.\d+\.\d+$/.test(normalized))) throw invalid("a wildcard needs a hostname suffix, not an IP address");
|
|
207
|
+
return (wildcard ? "*." : "") + normalized;
|
|
208
|
+
}
|
|
209
|
+
function normalizeAllowedHosts(entries) {
|
|
210
|
+
if (entries === void 0) return void 0;
|
|
211
|
+
if (!Array.isArray(entries)) throw new Error("urlDownload.allowedHosts must be an array of hostnames");
|
|
212
|
+
return entries.map(normalizeAllowedHost);
|
|
213
|
+
}
|
|
214
|
+
function hostAllowed(hostname, allowedHosts) {
|
|
215
|
+
if (!allowedHosts) return true;
|
|
216
|
+
const host = comparableHost(hostname);
|
|
217
|
+
return allowedHosts.some((pattern) => pattern.startsWith("*.") ? host.endsWith(pattern.slice(1)) && host.length > pattern.length - 1 : host === pattern);
|
|
218
|
+
}
|
|
219
|
+
function checkUrl(url, allowedHosts) {
|
|
220
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw refuse(url, "Only http and https URLs can be downloaded.");
|
|
221
|
+
if (url.username !== "" || url.password !== "") throw refuse(url, "URLs with credentials cannot be downloaded.");
|
|
222
|
+
if (!hostAllowed(url.hostname, allowedHosts)) throw refuse(url, "The URL host is not in the allowed hosts list.");
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Waits for `work`, or rejects with the abort reason when `signal` aborts.
|
|
226
|
+
* The handlers are attached to `work` first, so that a later rejection of
|
|
227
|
+
* `work` is always handled: an unhandled rejection ends a Node process in
|
|
228
|
+
* the default mode.
|
|
229
|
+
*/
|
|
230
|
+
function withDeadline(work, signal) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const onAbort = () => reject(signal.reason);
|
|
233
|
+
work.then(resolve, reject).finally(() => {
|
|
234
|
+
signal.removeEventListener("abort", onAbort);
|
|
235
|
+
});
|
|
236
|
+
if (signal.aborted) return onAbort();
|
|
237
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function requestPinned(modules, url, addresses, signal, ca) {
|
|
241
|
+
const lookup = (_hostname, options, callback) => {
|
|
242
|
+
const wanted = typeof options === "number" ? options : options?.family;
|
|
243
|
+
const available = wanted === 4 || wanted === 6 ? addresses.filter((a) => a.family === wanted) : addresses;
|
|
244
|
+
if (available.length === 0) callback(refuse(url, "No checked address is available for this URL."), "", 4);
|
|
245
|
+
else if (typeof options === "object" && options?.all) callback(null, available);
|
|
246
|
+
else callback(null, available[0].address, available[0].family);
|
|
247
|
+
};
|
|
248
|
+
const isHttps = url.protocol === "https:";
|
|
249
|
+
const transport = isHttps ? modules.https : modules.http;
|
|
250
|
+
const agent = isHttps ? new modules.https.Agent({
|
|
251
|
+
keepAlive: false,
|
|
252
|
+
maxCachedSessions: 0
|
|
253
|
+
}) : new modules.http.Agent({ keepAlive: false });
|
|
254
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const request = transport.request({
|
|
257
|
+
protocol: url.protocol,
|
|
258
|
+
hostname,
|
|
259
|
+
servername: isHttps && modules.net.isIP(hostname) === 0 ? hostname.replace(/\.$/, "") : void 0,
|
|
260
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
261
|
+
path: url.pathname + url.search,
|
|
262
|
+
method: "GET",
|
|
263
|
+
headers: {
|
|
264
|
+
"accept-encoding": "identity",
|
|
265
|
+
"user-agent": USER_AGENT
|
|
266
|
+
},
|
|
267
|
+
lookup,
|
|
268
|
+
agent,
|
|
269
|
+
signal,
|
|
270
|
+
...isHttps && ca !== void 0 ? { ca } : {}
|
|
271
|
+
}, (response) => {
|
|
272
|
+
response.once("close", () => agent.destroy());
|
|
273
|
+
resolve(response);
|
|
274
|
+
});
|
|
275
|
+
request.on("error", (error) => {
|
|
276
|
+
agent.destroy();
|
|
277
|
+
reject(error);
|
|
278
|
+
});
|
|
279
|
+
request.once("upgrade", (_response, socket) => {
|
|
280
|
+
socket.destroy();
|
|
281
|
+
agent.destroy();
|
|
282
|
+
reject(refuse(url, "Protocol upgrade refused."));
|
|
283
|
+
});
|
|
284
|
+
request.end();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async function readBody$1(url, response, maxBytes, budget) {
|
|
288
|
+
const tooLarge = () => refuse(url, `The response is larger than the ${maxBytes}-byte limit.`);
|
|
289
|
+
const overBudget = () => refuse(url, `The downloads are larger than the ${budget.limit}-byte total limit.`);
|
|
290
|
+
const declared = Number(response.headers["content-length"]);
|
|
291
|
+
if (Number.isFinite(declared) && declared > maxBytes) throw tooLarge();
|
|
292
|
+
if (Number.isFinite(declared) && declared > budget.remaining) throw overBudget();
|
|
293
|
+
const encoding = response.headers["content-encoding"];
|
|
294
|
+
if (encoding && encoding !== "identity") throw refuse(url, "Compressed responses are not accepted.");
|
|
295
|
+
const chunks = [];
|
|
296
|
+
let total = 0;
|
|
297
|
+
for await (const chunk of response) {
|
|
298
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
299
|
+
total += buffer.byteLength;
|
|
300
|
+
if (total > maxBytes) throw tooLarge();
|
|
301
|
+
budget.remaining -= buffer.byteLength;
|
|
302
|
+
if (budget.remaining < 0) throw overBudget();
|
|
303
|
+
chunks.push(buffer);
|
|
304
|
+
}
|
|
305
|
+
return new Uint8Array(Buffer.concat(chunks, total));
|
|
306
|
+
}
|
|
307
|
+
async function downloadOne(original, options, internals, outer, budget) {
|
|
308
|
+
const modules = await loadNodeModules().catch((error) => {
|
|
309
|
+
throw refuse(original, "URL download needs Node.js networking modules, which are not available here.", `loading them failed: ${describeError(error)}. On a runtime without them, set urlDownload to "disabled" or to your own download function`);
|
|
310
|
+
});
|
|
311
|
+
defaultPolicy ??= buildAddressPolicy(modules.net);
|
|
312
|
+
const isPublic = internals.isPublicAddress ?? defaultPolicy;
|
|
313
|
+
const resolveHost = internals.resolveHost ?? ((hostname) => modules.dns.lookup(hostname, {
|
|
314
|
+
all: true,
|
|
315
|
+
verbatim: true
|
|
316
|
+
}));
|
|
317
|
+
let url = original;
|
|
318
|
+
const controller = new AbortController();
|
|
319
|
+
const timer = setTimeout(() => controller.abort(refuse(url, `The download timed out after ${options.timeoutMs} ms (timeoutMs).`)), options.timeoutMs);
|
|
320
|
+
const onOuterAbort = () => controller.abort(outer.reason);
|
|
321
|
+
if (outer.aborted) onOuterAbort();
|
|
322
|
+
else outer.addEventListener("abort", onOuterAbort, { once: true });
|
|
323
|
+
const { signal } = controller;
|
|
324
|
+
try {
|
|
325
|
+
for (let hop = 0;; hop++) {
|
|
326
|
+
signal.throwIfAborted();
|
|
327
|
+
checkUrl(url, options.allowedHosts);
|
|
328
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
329
|
+
const literalFamily = modules.net.isIP(hostname);
|
|
330
|
+
let addresses;
|
|
331
|
+
try {
|
|
332
|
+
signal.throwIfAborted();
|
|
333
|
+
addresses = literalFamily ? [{
|
|
334
|
+
address: hostname,
|
|
335
|
+
family: literalFamily
|
|
336
|
+
}] : await withDeadline(resolveHost(hostname), signal);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (signal.aborted) throw signal.reason;
|
|
339
|
+
throw refuse(url, HOST_REFUSED, `DNS lookup failed: ${describeError(error)}`);
|
|
340
|
+
}
|
|
341
|
+
const verdicts = await Promise.all(addresses.map(async ({ address, family }) => (family === 4 || family === 6) && await isPublic(address) === true));
|
|
342
|
+
const rejected = addresses.filter((_address, i) => !verdicts[i]);
|
|
343
|
+
if (addresses.length === 0 || rejected.length > 0) throw refuse(url, HOST_REFUSED, addresses.length === 0 ? "DNS lookup returned no addresses" : `non-public address: ${rejected.map((a) => a.address).join(", ")}`);
|
|
344
|
+
let response;
|
|
345
|
+
try {
|
|
346
|
+
response = await requestPinned(modules, url, addresses, signal, internals.ca);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (signal.aborted) throw signal.reason;
|
|
349
|
+
if (DownloadError.isInstance(error)) throw error;
|
|
350
|
+
throw refuse(url, DOWNLOAD_FAILED, describeError(error));
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const status = response.statusCode ?? 0;
|
|
354
|
+
if (REDIRECT_STATUSES.has(status)) {
|
|
355
|
+
if (hop >= options.maxRedirects) throw refuse(url, `Too many redirects: the limit is ${options.maxRedirects} (maxRedirects).`);
|
|
356
|
+
const location = response.headers.location;
|
|
357
|
+
if (!location) throw refuse(url, "Redirect without a location.");
|
|
358
|
+
let next;
|
|
359
|
+
try {
|
|
360
|
+
next = new URL(location, url);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
throw refuse(url, "Redirect to an invalid URL.", `Location ${JSON.stringify(location.slice(0, 200))}: ${describeError(error)}`);
|
|
363
|
+
}
|
|
364
|
+
if (url.protocol === "https:" && next.protocol === "http:") throw refuse(url, "A redirect from https to http is refused.");
|
|
365
|
+
url = next;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (status < 200 || status >= 300) throw refuse(url, `The server answered with HTTP status ${status}.`, response.statusMessage, status);
|
|
369
|
+
return {
|
|
370
|
+
data: await readBody$1(url, response, options.maxBytes, budget),
|
|
371
|
+
mediaType: response.headers["content-type"] || void 0
|
|
372
|
+
};
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (signal.aborted) throw signal.reason;
|
|
375
|
+
if (DownloadError.isInstance(error)) throw error;
|
|
376
|
+
throw refuse(url, DOWNLOAD_FAILED, describeError(error));
|
|
377
|
+
} finally {
|
|
378
|
+
response.destroy();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} finally {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
outer.removeEventListener("abort", onOuterAbort);
|
|
384
|
+
controller.abort();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const MAX_TIMEOUT_MS = 2147483647;
|
|
388
|
+
/** Returns `value` (or `fallback`) if it is an integer from `min` to `max`. */
|
|
389
|
+
function integerOption(name, value, fallback, min, max = Number.MAX_SAFE_INTEGER) {
|
|
390
|
+
const result = value ?? fallback;
|
|
391
|
+
if (!Number.isSafeInteger(result) || result < min || result > max) {
|
|
392
|
+
const range = max === Number.MAX_SAFE_INTEGER ? `an integer of at least ${min}` : `an integer from ${min} to ${max}`;
|
|
393
|
+
throw new Error(`urlDownload.${name} must be ${range}, got ${describeValue(value)}`);
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
const OPTION_NAMES = {
|
|
398
|
+
allowedHosts: true,
|
|
399
|
+
maxBytes: true,
|
|
400
|
+
timeoutMs: true,
|
|
401
|
+
maxRedirects: true,
|
|
402
|
+
maxDownloads: true,
|
|
403
|
+
maxTotalBytes: true
|
|
404
|
+
};
|
|
405
|
+
function checkOptionsObject(options) {
|
|
406
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) throw new Error(`urlDownload options must be an object, got ${describeValue(options)}`);
|
|
407
|
+
}
|
|
408
|
+
function checkOptionNames(options) {
|
|
409
|
+
checkOptionsObject(options);
|
|
410
|
+
for (const name of Object.keys(options)) if (!Object.prototype.hasOwnProperty.call(OPTION_NAMES, name)) throw new Error(`urlDownload has an unknown option ${describeValue(name)}; the valid options are ${Object.keys(OPTION_NAMES).join(", ")}`);
|
|
411
|
+
}
|
|
412
|
+
/** @internal Factory with test seams; use {@link createSafeUrlDownload}. */
|
|
413
|
+
function createGuardedUrlDownload(options = {}, internals = {}) {
|
|
414
|
+
checkOptionNames(options);
|
|
415
|
+
const resolved = {
|
|
416
|
+
allowedHosts: normalizeAllowedHosts(options.allowedHosts),
|
|
417
|
+
maxBytes: integerOption("maxBytes", options.maxBytes, DEFAULT_MAX_BYTES, 1),
|
|
418
|
+
timeoutMs: integerOption("timeoutMs", options.timeoutMs, DEFAULT_TIMEOUT_MS, 1, MAX_TIMEOUT_MS),
|
|
419
|
+
maxRedirects: integerOption("maxRedirects", options.maxRedirects, DEFAULT_MAX_REDIRECTS, 0),
|
|
420
|
+
maxDownloads: integerOption("maxDownloads", options.maxDownloads, DEFAULT_MAX_DOWNLOADS, 1),
|
|
421
|
+
maxTotalBytes: integerOption("maxTotalBytes", options.maxTotalBytes, DEFAULT_MAX_TOTAL_BYTES, 1)
|
|
422
|
+
};
|
|
423
|
+
const downloadAll = async (requests, runSignal) => {
|
|
424
|
+
const batch = new AbortController();
|
|
425
|
+
const onRunAbort = () => batch.abort(runSignal?.reason);
|
|
426
|
+
if (runSignal?.aborted) onRunAbort();
|
|
427
|
+
else runSignal?.addEventListener("abort", onRunAbort, { once: true });
|
|
428
|
+
try {
|
|
429
|
+
const pending = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const request of requests) if (!leftToAiSdk(request)) pending.set(request.url.href, request.url);
|
|
431
|
+
if (pending.size > resolved.maxDownloads) throw refuse([...pending.values()][resolved.maxDownloads], `A model call can have at most ${resolved.maxDownloads} URL downloads; this one has ${pending.size}.`);
|
|
432
|
+
const budget = {
|
|
433
|
+
remaining: resolved.maxTotalBytes,
|
|
434
|
+
limit: resolved.maxTotalBytes
|
|
435
|
+
};
|
|
436
|
+
const downloads = new Map([...pending].map(([href, url]) => [href, downloadOne(url, resolved, internals, batch.signal, budget).catch((error) => {
|
|
437
|
+
batch.abort(error);
|
|
438
|
+
throw error;
|
|
439
|
+
})]));
|
|
440
|
+
return await Promise.all(requests.map((request) => leftToAiSdk(request) ? null : downloads.get(request.url.href)));
|
|
441
|
+
} finally {
|
|
442
|
+
runSignal?.removeEventListener("abort", onRunAbort);
|
|
443
|
+
batch.abort();
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
return Object.assign((requests) => downloadAll(requests), { forRun: (signal) => (requests) => downloadAll(requests, signal) });
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* @internal Resolves BuiltInAgent's `urlDownload` setting once, so that an
|
|
450
|
+
* invalid setting fails when the agent is constructed. The result gives the
|
|
451
|
+
* download function for one run; the default downloader stops when the run
|
|
452
|
+
* is aborted.
|
|
453
|
+
*/
|
|
454
|
+
function resolveUrlDownloadSetting(setting) {
|
|
455
|
+
if (setting === "disabled") return () => disabledUrlDownload;
|
|
456
|
+
if (typeof setting === "function") return () => setting;
|
|
457
|
+
if (setting === void 0 || setting === null) return createGuardedUrlDownload().forRun;
|
|
458
|
+
if (typeof setting === "object" && !Array.isArray(setting)) return createGuardedUrlDownload(setting).forRun;
|
|
459
|
+
throw new Error(`urlDownload must be "disabled", an options object or a download function, got ${describeValue(setting)}`);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Creates an AI SDK download function (`experimental_download`) that only
|
|
463
|
+
* fetches public internet addresses, with the same checks and limits as
|
|
464
|
+
* BuiltInAgent's default downloader. Pass it to `streamText` yourself when
|
|
465
|
+
* you use BuiltInAgent's AI SDK factory mode. Create it in the factory with
|
|
466
|
+
* the run's abort signal, so that an aborted run stops its downloads:
|
|
467
|
+
*
|
|
468
|
+
* ```ts
|
|
469
|
+
* import { createSafeUrlDownload } from "@copilotkit/runtime/v2";
|
|
470
|
+
*
|
|
471
|
+
* factory: (ctx) =>
|
|
472
|
+
* streamText({
|
|
473
|
+
* model,
|
|
474
|
+
* messages,
|
|
475
|
+
* abortSignal: ctx.abortSignal,
|
|
476
|
+
* experimental_download: createSafeUrlDownload({ signal: ctx.abortSignal }),
|
|
477
|
+
* }),
|
|
478
|
+
* ```
|
|
479
|
+
*
|
|
480
|
+
* Invalid options throw here, when the function is created.
|
|
481
|
+
*/
|
|
482
|
+
function createSafeUrlDownload(options = {}) {
|
|
483
|
+
checkOptionsObject(options);
|
|
484
|
+
const { signal, ...downloadOptions } = options;
|
|
485
|
+
if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new Error(`urlDownload signal must be an AbortSignal, got ${describeValue(signal)}`);
|
|
486
|
+
const download = createGuardedUrlDownload(downloadOptions);
|
|
487
|
+
return signal ? download.forRun(signal) : download;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* A download function that never fetches from the network. URLs that the
|
|
491
|
+
* model takes as-is and `data:` URLs are left to the AI SDK; any other URL,
|
|
492
|
+
* whatever its scheme, fails the run.
|
|
493
|
+
*/
|
|
494
|
+
const disabledUrlDownload = async (requests) => requests.map((request) => {
|
|
495
|
+
if (leftToAiSdk(request)) return null;
|
|
496
|
+
throw refuse(request.url, "Server-side URL download is disabled (urlDownload: \"disabled\").");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
//#endregion
|
|
500
|
+
export { createSafeUrlDownload, logModelCallError, resolveUrlDownloadSetting };
|
|
501
|
+
//# sourceMappingURL=url-download.mjs.map
|