@alexkroman1/aai 0.10.1 → 0.10.3
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/_embeddings.d.ts +31 -0
- package/dist/_internal-types-IfPcaJd5.js +61 -0
- package/dist/_internal-types.js +1 -60
- package/dist/_ssrf-DCp_27V4.js +123 -0
- package/dist/_ssrf.js +1 -122
- package/dist/_utils-DgzpOMSV.js +61 -0
- package/dist/_utils.js +1 -60
- package/dist/{direct-executor-Ca0wt5H0.js → direct-executor-B-5mq3cu.js} +15 -17
- package/dist/index.js +1 -1
- package/dist/kv-iXtikQmR.js +32 -0
- package/dist/kv.js +1 -31
- package/dist/matchers.js +1 -1
- package/dist/middleware-core-BwyBIPed.js +107 -0
- package/dist/middleware-core.js +1 -106
- package/dist/protocol-B-H2Q4ox.js +162 -0
- package/dist/protocol.js +1 -161
- package/dist/runtime-CxcwaK68.js +58 -0
- package/dist/runtime.js +1 -52
- package/dist/s2s-M7JqtgFw.js +272 -0
- package/dist/s2s.js +1 -271
- package/dist/server.d.ts +6 -6
- package/dist/server.js +47 -43
- package/dist/{session-BkN9u0ni.js → session-BYlwcrya.js} +6 -6
- package/dist/session.js +1 -1
- package/dist/telemetry-CJlaDFNc.js +95 -0
- package/dist/telemetry.js +1 -94
- package/dist/{testing-MRl3SXsI.js → testing-BbitshLb.js} +7 -9
- package/dist/testing.js +1 -1
- package/dist/types-D8ZBxTL_.js +192 -0
- package/dist/types.js +1 -191
- package/dist/unstorage-kv-CDgP-frt.js +64 -0
- package/dist/unstorage-kv.d.ts +33 -0
- package/dist/unstorage-kv.js +2 -0
- package/dist/unstorage-vector-Cj5llNhg.js +172 -0
- package/dist/unstorage-vector.d.ts +47 -0
- package/dist/unstorage-vector.js +2 -0
- package/dist/vector.d.ts +3 -2
- package/dist/worker-entry-2jaiqIj0.js +70 -0
- package/dist/worker-entry.js +1 -69
- package/dist/ws-handler-C0Q6eSay.js +207 -0
- package/dist/ws-handler.js +1 -206
- package/package.json +14 -9
- package/dist/sqlite-kv.d.ts +0 -34
- package/dist/sqlite-kv.js +0 -133
- package/dist/sqlite-vector.d.ts +0 -58
- package/dist/sqlite-vector.js +0 -149
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared embedding helpers for vector stores.
|
|
3
|
+
*
|
|
4
|
+
* Provides local embedding via `all-MiniLM-L6-v2` (384 dims),
|
|
5
|
+
* a deterministic test embedding function, and cosine similarity.
|
|
6
|
+
*/
|
|
7
|
+
/** Function that converts text into an embedding vector. */
|
|
8
|
+
export type EmbedFn = (text: string) => Promise<number[]>;
|
|
9
|
+
export declare const DEFAULT_DIMENSIONS = 384;
|
|
10
|
+
/**
|
|
11
|
+
* Create a local embedding function using `all-MiniLM-L6-v2`.
|
|
12
|
+
*
|
|
13
|
+
* The model is downloaded on first use (~86 MB) and cached locally.
|
|
14
|
+
* Subsequent calls load from cache in ~90ms. Each embedding takes <2ms.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createLocalEmbedFn(cacheDir: string): EmbedFn;
|
|
17
|
+
/**
|
|
18
|
+
* Create a deterministic hash-based embedding function for testing.
|
|
19
|
+
*
|
|
20
|
+
* Produces repeatable vectors where similar text yields similar embeddings.
|
|
21
|
+
* Not suitable for production — use the default local model instead.
|
|
22
|
+
*
|
|
23
|
+
* @param dimensions - Vector dimensions (default: 384).
|
|
24
|
+
*/
|
|
25
|
+
export declare function createTestEmbedFn(dimensions?: number): EmbedFn;
|
|
26
|
+
/** Cosine similarity between two Float32Array vectors. */
|
|
27
|
+
export declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
|
|
28
|
+
/** Encode an embedding as a base64 string. */
|
|
29
|
+
export declare function encodeEmbedding(vec: Float32Array | number[]): string;
|
|
30
|
+
/** Decode a base64 string back to a Float32Array embedding. */
|
|
31
|
+
export declare function decodeEmbedding(b64: string): Float32Array;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { i as ToolChoiceSchema, t as BuiltinToolSchema } from "./types-D8ZBxTL_.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region _internal-types.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for serializable agent configuration sent over the wire.
|
|
6
|
+
*
|
|
7
|
+
* This is the JSON-safe subset of the agent definition that can be
|
|
8
|
+
* transmitted between the worker and the host process via structured clone.
|
|
9
|
+
*/
|
|
10
|
+
const AgentConfigSchema = z.object({
|
|
11
|
+
name: z.string().min(1),
|
|
12
|
+
instructions: z.string(),
|
|
13
|
+
greeting: z.string(),
|
|
14
|
+
sttPrompt: z.string().optional(),
|
|
15
|
+
maxSteps: z.number().int().positive().optional(),
|
|
16
|
+
toolChoice: ToolChoiceSchema.optional(),
|
|
17
|
+
builtinTools: z.array(BuiltinToolSchema).readonly().optional(),
|
|
18
|
+
idleTimeoutMs: z.number().nonnegative().optional()
|
|
19
|
+
});
|
|
20
|
+
/** Extract the serializable {@link AgentConfig} subset from a source object. */
|
|
21
|
+
function toAgentConfig(src) {
|
|
22
|
+
const config = {
|
|
23
|
+
name: src.name,
|
|
24
|
+
instructions: src.instructions,
|
|
25
|
+
greeting: src.greeting
|
|
26
|
+
};
|
|
27
|
+
if (src.sttPrompt !== void 0) config.sttPrompt = src.sttPrompt;
|
|
28
|
+
if (typeof src.maxSteps !== "function" && src.maxSteps !== void 0) config.maxSteps = src.maxSteps;
|
|
29
|
+
if (src.toolChoice !== void 0) config.toolChoice = src.toolChoice;
|
|
30
|
+
if (src.builtinTools) config.builtinTools = [...src.builtinTools];
|
|
31
|
+
if (src.idleTimeoutMs !== void 0) config.idleTimeoutMs = src.idleTimeoutMs;
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Zod schema for serialized tool definitions sent over the wire.
|
|
36
|
+
*
|
|
37
|
+
* `parameters` must be a valid JSON Schema object (with `type`, `properties`,
|
|
38
|
+
* etc.) — the Vercel AI SDK wraps it via `jsonSchema()`.
|
|
39
|
+
*/
|
|
40
|
+
const ToolSchemaSchema = z.object({
|
|
41
|
+
name: z.string().min(1),
|
|
42
|
+
description: z.string().min(1),
|
|
43
|
+
parameters: z.record(z.string(), z.unknown())
|
|
44
|
+
});
|
|
45
|
+
/** Empty Zod object schema used as default when tools have no parameters. */
|
|
46
|
+
const EMPTY_PARAMS = z.object({});
|
|
47
|
+
/**
|
|
48
|
+
* Convert agent tool definitions to JSON Schema format for wire transport.
|
|
49
|
+
*
|
|
50
|
+
* Transforms the Zod-based `parameters` of each tool into a plain JSON Schema
|
|
51
|
+
* object suitable for structured clone / JSON serialization.
|
|
52
|
+
*/
|
|
53
|
+
function agentToolsToSchemas(tools) {
|
|
54
|
+
return Object.entries(tools).map(([name, def]) => ({
|
|
55
|
+
name,
|
|
56
|
+
description: def.description,
|
|
57
|
+
parameters: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { toAgentConfig as a, agentToolsToSchemas as i, EMPTY_PARAMS as n, ToolSchemaSchema as r, AgentConfigSchema as t };
|
package/dist/_internal-types.js
CHANGED
|
@@ -1,61 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
//#region _internal-types.ts
|
|
4
|
-
/**
|
|
5
|
-
* Zod schema for serializable agent configuration sent over the wire.
|
|
6
|
-
*
|
|
7
|
-
* This is the JSON-safe subset of the agent definition that can be
|
|
8
|
-
* transmitted between the worker and the host process via structured clone.
|
|
9
|
-
*/
|
|
10
|
-
const AgentConfigSchema = z.object({
|
|
11
|
-
name: z.string().min(1),
|
|
12
|
-
instructions: z.string(),
|
|
13
|
-
greeting: z.string(),
|
|
14
|
-
sttPrompt: z.string().optional(),
|
|
15
|
-
maxSteps: z.number().int().positive().optional(),
|
|
16
|
-
toolChoice: ToolChoiceSchema.optional(),
|
|
17
|
-
builtinTools: z.array(BuiltinToolSchema).readonly().optional(),
|
|
18
|
-
idleTimeoutMs: z.number().nonnegative().optional()
|
|
19
|
-
});
|
|
20
|
-
/** Extract the serializable {@link AgentConfig} subset from a source object. */
|
|
21
|
-
function toAgentConfig(src) {
|
|
22
|
-
const config = {
|
|
23
|
-
name: src.name,
|
|
24
|
-
instructions: src.instructions,
|
|
25
|
-
greeting: src.greeting
|
|
26
|
-
};
|
|
27
|
-
if (src.sttPrompt !== void 0) config.sttPrompt = src.sttPrompt;
|
|
28
|
-
if (typeof src.maxSteps !== "function" && src.maxSteps !== void 0) config.maxSteps = src.maxSteps;
|
|
29
|
-
if (src.toolChoice !== void 0) config.toolChoice = src.toolChoice;
|
|
30
|
-
if (src.builtinTools) config.builtinTools = [...src.builtinTools];
|
|
31
|
-
if (src.idleTimeoutMs !== void 0) config.idleTimeoutMs = src.idleTimeoutMs;
|
|
32
|
-
return config;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Zod schema for serialized tool definitions sent over the wire.
|
|
36
|
-
*
|
|
37
|
-
* `parameters` must be a valid JSON Schema object (with `type`, `properties`,
|
|
38
|
-
* etc.) — the Vercel AI SDK wraps it via `jsonSchema()`.
|
|
39
|
-
*/
|
|
40
|
-
const ToolSchemaSchema = z.object({
|
|
41
|
-
name: z.string().min(1),
|
|
42
|
-
description: z.string().min(1),
|
|
43
|
-
parameters: z.record(z.string(), z.unknown())
|
|
44
|
-
});
|
|
45
|
-
/** Empty Zod object schema used as default when tools have no parameters. */
|
|
46
|
-
const EMPTY_PARAMS = z.object({});
|
|
47
|
-
/**
|
|
48
|
-
* Convert agent tool definitions to JSON Schema format for wire transport.
|
|
49
|
-
*
|
|
50
|
-
* Transforms the Zod-based `parameters` of each tool into a plain JSON Schema
|
|
51
|
-
* object suitable for structured clone / JSON serialization.
|
|
52
|
-
*/
|
|
53
|
-
function agentToolsToSchemas(tools) {
|
|
54
|
-
return Object.entries(tools).map(([name, def]) => ({
|
|
55
|
-
name,
|
|
56
|
-
description: def.description,
|
|
57
|
-
parameters: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
1
|
+
import { a as toAgentConfig, i as agentToolsToSchemas, n as EMPTY_PARAMS, r as ToolSchemaSchema, t as AgentConfigSchema } from "./_internal-types-IfPcaJd5.js";
|
|
61
2
|
export { AgentConfigSchema, EMPTY_PARAMS, ToolSchemaSchema, agentToolsToSchemas, toAgentConfig };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { BlockList } from "node:net";
|
|
3
|
+
//#region _ssrf.ts
|
|
4
|
+
/**
|
|
5
|
+
* SSRF protection for AAI network tools.
|
|
6
|
+
*
|
|
7
|
+
* Validates URLs against private/reserved IP ranges and handles redirects
|
|
8
|
+
* safely by re-validating each redirect target. Used by both the SDK
|
|
9
|
+
* (self-hosted built-in tools) and the platform server.
|
|
10
|
+
*/
|
|
11
|
+
const privateBlocks = new BlockList();
|
|
12
|
+
for (const [prefix, bits] of [
|
|
13
|
+
["0.0.0.0", 8],
|
|
14
|
+
["10.0.0.0", 8],
|
|
15
|
+
["100.64.0.0", 10],
|
|
16
|
+
["127.0.0.0", 8],
|
|
17
|
+
["169.254.0.0", 16],
|
|
18
|
+
["172.16.0.0", 12],
|
|
19
|
+
["192.0.0.0", 24],
|
|
20
|
+
["192.168.0.0", 16],
|
|
21
|
+
["198.18.0.0", 15],
|
|
22
|
+
["224.0.0.0", 4],
|
|
23
|
+
["240.0.0.0", 4]
|
|
24
|
+
]) privateBlocks.addSubnet(prefix, bits, "ipv4");
|
|
25
|
+
for (const [prefix, bits] of [
|
|
26
|
+
["::1", 128],
|
|
27
|
+
["::", 128],
|
|
28
|
+
["fc00::", 7],
|
|
29
|
+
["fe80::", 10],
|
|
30
|
+
["ff00::", 8]
|
|
31
|
+
]) privateBlocks.addSubnet(prefix, bits, "ipv6");
|
|
32
|
+
/**
|
|
33
|
+
* Check whether an IP address falls within a private or reserved range.
|
|
34
|
+
*
|
|
35
|
+
* @param ip - An IPv4 or IPv6 address string.
|
|
36
|
+
* @returns `true` if the address is in a private/reserved range.
|
|
37
|
+
*/
|
|
38
|
+
function isPrivateIp(ip) {
|
|
39
|
+
const type = ip.includes(":") ? "ipv6" : "ipv4";
|
|
40
|
+
return privateBlocks.check(ip, type);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Detect IPv4-mapped IPv6 addresses and extract the embedded IPv4.
|
|
44
|
+
* Handles both dotted form (`::ffff:127.0.0.1`) and hex form (`::ffff:7f00:1`).
|
|
45
|
+
*/
|
|
46
|
+
function extractMappedIp(ip) {
|
|
47
|
+
const mappedDotted = ip.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
|
|
48
|
+
if (mappedDotted) return mappedDotted[1];
|
|
49
|
+
const mappedHex = ip.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
50
|
+
if (mappedHex) {
|
|
51
|
+
const hi = Number.parseInt(mappedHex[1], 16);
|
|
52
|
+
const lo = Number.parseInt(mappedHex[2], 16);
|
|
53
|
+
return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
|
|
54
|
+
}
|
|
55
|
+
return ip;
|
|
56
|
+
}
|
|
57
|
+
function isBlockedHostname(hostname) {
|
|
58
|
+
const lower = hostname.toLowerCase();
|
|
59
|
+
return lower === "localhost" || lower.endsWith(".local") || lower.endsWith(".internal") || lower === "169.254.169.254";
|
|
60
|
+
}
|
|
61
|
+
function isLiteralIp(hostname) {
|
|
62
|
+
return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.includes(":");
|
|
63
|
+
}
|
|
64
|
+
/** Resolve hostname and block if it points to a private IP (DNS rebinding). */
|
|
65
|
+
async function assertDnsResolvesPublic(hostname) {
|
|
66
|
+
try {
|
|
67
|
+
const { address } = await Promise.race([lookup(hostname), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("DNS lookup timed out")), 2e3))]);
|
|
68
|
+
const resolved = extractMappedIp(address);
|
|
69
|
+
if (isPrivateIp(address) || isPrivateIp(resolved)) throw new Error(`Blocked request: ${hostname} resolves to private address ${address}`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err instanceof Error && err.message.startsWith("Blocked request")) throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* SSRF guard: assert that a URL targets a public internet address.
|
|
76
|
+
*
|
|
77
|
+
* Blocks requests to:
|
|
78
|
+
* - Private IPv4 ranges (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
|
|
79
|
+
* - Loopback (127.0.0.0/8), link-local (169.254.0.0/16), shared (100.64.0.0/10)
|
|
80
|
+
* - IPv4-mapped IPv6 addresses embedding private IPs (e.g. `::ffff:127.0.0.1`)
|
|
81
|
+
* - IPv6 loopback (`::1`), ULA (`fc00::/7`), link-local (`fe80::/10`)
|
|
82
|
+
* - `localhost`, `.local` (mDNS), `.internal` domains
|
|
83
|
+
* - Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`)
|
|
84
|
+
* - Non-http(s) schemes (e.g. `file:`, `ftp:`)
|
|
85
|
+
* - Hostnames that resolve to private IPs via DNS (prevents DNS rebinding)
|
|
86
|
+
*
|
|
87
|
+
* @param url - The URL to validate (must be parseable by `new URL()`).
|
|
88
|
+
* @throws {Error} If the URL targets a private/reserved address or uses a
|
|
89
|
+
* disallowed protocol scheme.
|
|
90
|
+
*/
|
|
91
|
+
async function assertPublicUrl(url) {
|
|
92
|
+
const parsed = new URL(url);
|
|
93
|
+
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
94
|
+
const effective = extractMappedIp(hostname);
|
|
95
|
+
if (isPrivateIp(hostname) || isPrivateIp(effective)) throw new Error(`Blocked request to private address: ${hostname}`);
|
|
96
|
+
if (isBlockedHostname(hostname)) throw new Error(`Blocked request to private address: ${hostname}`);
|
|
97
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Blocked request with disallowed protocol: ${parsed.protocol}`);
|
|
98
|
+
if (!isLiteralIp(hostname)) await assertDnsResolvesPublic(hostname);
|
|
99
|
+
}
|
|
100
|
+
/** Maximum number of redirects to follow manually. */
|
|
101
|
+
const MAX_REDIRECTS = 5;
|
|
102
|
+
/**
|
|
103
|
+
* Fetch with SSRF-safe redirect handling: validates each redirect URL
|
|
104
|
+
* against private/reserved IP ranges before following.
|
|
105
|
+
*/
|
|
106
|
+
async function ssrfSafeFetch(url, init, fetchFn) {
|
|
107
|
+
await assertPublicUrl(url);
|
|
108
|
+
let currentUrl = url;
|
|
109
|
+
for (let i = 0; i <= MAX_REDIRECTS; i++) {
|
|
110
|
+
const resp = await fetchFn(currentUrl, {
|
|
111
|
+
...init,
|
|
112
|
+
redirect: "manual"
|
|
113
|
+
});
|
|
114
|
+
if (resp.status < 300 || resp.status >= 400) return resp;
|
|
115
|
+
const location = resp.headers.get("location");
|
|
116
|
+
if (!location) return resp;
|
|
117
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
118
|
+
await assertPublicUrl(currentUrl);
|
|
119
|
+
}
|
|
120
|
+
throw new Error("Too many redirects");
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { isPrivateIp as n, ssrfSafeFetch as r, assertPublicUrl as t };
|
package/dist/_ssrf.js
CHANGED
|
@@ -1,123 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BlockList } from "node:net";
|
|
3
|
-
//#region _ssrf.ts
|
|
4
|
-
/**
|
|
5
|
-
* SSRF protection for AAI network tools.
|
|
6
|
-
*
|
|
7
|
-
* Validates URLs against private/reserved IP ranges and handles redirects
|
|
8
|
-
* safely by re-validating each redirect target. Used by both the SDK
|
|
9
|
-
* (self-hosted built-in tools) and the platform server.
|
|
10
|
-
*/
|
|
11
|
-
const privateBlocks = new BlockList();
|
|
12
|
-
for (const [prefix, bits] of [
|
|
13
|
-
["0.0.0.0", 8],
|
|
14
|
-
["10.0.0.0", 8],
|
|
15
|
-
["100.64.0.0", 10],
|
|
16
|
-
["127.0.0.0", 8],
|
|
17
|
-
["169.254.0.0", 16],
|
|
18
|
-
["172.16.0.0", 12],
|
|
19
|
-
["192.0.0.0", 24],
|
|
20
|
-
["192.168.0.0", 16],
|
|
21
|
-
["198.18.0.0", 15],
|
|
22
|
-
["224.0.0.0", 4],
|
|
23
|
-
["240.0.0.0", 4]
|
|
24
|
-
]) privateBlocks.addSubnet(prefix, bits, "ipv4");
|
|
25
|
-
for (const [prefix, bits] of [
|
|
26
|
-
["::1", 128],
|
|
27
|
-
["::", 128],
|
|
28
|
-
["fc00::", 7],
|
|
29
|
-
["fe80::", 10],
|
|
30
|
-
["ff00::", 8]
|
|
31
|
-
]) privateBlocks.addSubnet(prefix, bits, "ipv6");
|
|
32
|
-
/**
|
|
33
|
-
* Check whether an IP address falls within a private or reserved range.
|
|
34
|
-
*
|
|
35
|
-
* @param ip - An IPv4 or IPv6 address string.
|
|
36
|
-
* @returns `true` if the address is in a private/reserved range.
|
|
37
|
-
*/
|
|
38
|
-
function isPrivateIp(ip) {
|
|
39
|
-
const type = ip.includes(":") ? "ipv6" : "ipv4";
|
|
40
|
-
return privateBlocks.check(ip, type);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Detect IPv4-mapped IPv6 addresses and extract the embedded IPv4.
|
|
44
|
-
* Handles both dotted form (`::ffff:127.0.0.1`) and hex form (`::ffff:7f00:1`).
|
|
45
|
-
*/
|
|
46
|
-
function extractMappedIp(ip) {
|
|
47
|
-
const mappedDotted = ip.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
|
|
48
|
-
if (mappedDotted) return mappedDotted[1];
|
|
49
|
-
const mappedHex = ip.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
50
|
-
if (mappedHex) {
|
|
51
|
-
const hi = Number.parseInt(mappedHex[1], 16);
|
|
52
|
-
const lo = Number.parseInt(mappedHex[2], 16);
|
|
53
|
-
return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
|
|
54
|
-
}
|
|
55
|
-
return ip;
|
|
56
|
-
}
|
|
57
|
-
function isBlockedHostname(hostname) {
|
|
58
|
-
const lower = hostname.toLowerCase();
|
|
59
|
-
return lower === "localhost" || lower.endsWith(".local") || lower.endsWith(".internal") || lower === "169.254.169.254";
|
|
60
|
-
}
|
|
61
|
-
function isLiteralIp(hostname) {
|
|
62
|
-
return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.includes(":");
|
|
63
|
-
}
|
|
64
|
-
/** Resolve hostname and block if it points to a private IP (DNS rebinding). */
|
|
65
|
-
async function assertDnsResolvesPublic(hostname) {
|
|
66
|
-
try {
|
|
67
|
-
const { address } = await Promise.race([lookup(hostname), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("DNS lookup timed out")), 2e3))]);
|
|
68
|
-
const resolved = extractMappedIp(address);
|
|
69
|
-
if (isPrivateIp(address) || isPrivateIp(resolved)) throw new Error(`Blocked request: ${hostname} resolves to private address ${address}`);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
if (err instanceof Error && err.message.startsWith("Blocked request")) throw err;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* SSRF guard: assert that a URL targets a public internet address.
|
|
76
|
-
*
|
|
77
|
-
* Blocks requests to:
|
|
78
|
-
* - Private IPv4 ranges (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
|
|
79
|
-
* - Loopback (127.0.0.0/8), link-local (169.254.0.0/16), shared (100.64.0.0/10)
|
|
80
|
-
* - IPv4-mapped IPv6 addresses embedding private IPs (e.g. `::ffff:127.0.0.1`)
|
|
81
|
-
* - IPv6 loopback (`::1`), ULA (`fc00::/7`), link-local (`fe80::/10`)
|
|
82
|
-
* - `localhost`, `.local` (mDNS), `.internal` domains
|
|
83
|
-
* - Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`)
|
|
84
|
-
* - Non-http(s) schemes (e.g. `file:`, `ftp:`)
|
|
85
|
-
* - Hostnames that resolve to private IPs via DNS (prevents DNS rebinding)
|
|
86
|
-
*
|
|
87
|
-
* @param url - The URL to validate (must be parseable by `new URL()`).
|
|
88
|
-
* @throws {Error} If the URL targets a private/reserved address or uses a
|
|
89
|
-
* disallowed protocol scheme.
|
|
90
|
-
*/
|
|
91
|
-
async function assertPublicUrl(url) {
|
|
92
|
-
const parsed = new URL(url);
|
|
93
|
-
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
94
|
-
const effective = extractMappedIp(hostname);
|
|
95
|
-
if (isPrivateIp(hostname) || isPrivateIp(effective)) throw new Error(`Blocked request to private address: ${hostname}`);
|
|
96
|
-
if (isBlockedHostname(hostname)) throw new Error(`Blocked request to private address: ${hostname}`);
|
|
97
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Blocked request with disallowed protocol: ${parsed.protocol}`);
|
|
98
|
-
if (!isLiteralIp(hostname)) await assertDnsResolvesPublic(hostname);
|
|
99
|
-
}
|
|
100
|
-
/** Maximum number of redirects to follow manually. */
|
|
101
|
-
const MAX_REDIRECTS = 5;
|
|
102
|
-
/**
|
|
103
|
-
* Fetch with SSRF-safe redirect handling: validates each redirect URL
|
|
104
|
-
* against private/reserved IP ranges before following.
|
|
105
|
-
*/
|
|
106
|
-
async function ssrfSafeFetch(url, init, fetchFn) {
|
|
107
|
-
await assertPublicUrl(url);
|
|
108
|
-
let currentUrl = url;
|
|
109
|
-
for (let i = 0; i <= MAX_REDIRECTS; i++) {
|
|
110
|
-
const resp = await fetchFn(currentUrl, {
|
|
111
|
-
...init,
|
|
112
|
-
redirect: "manual"
|
|
113
|
-
});
|
|
114
|
-
if (resp.status < 300 || resp.status >= 400) return resp;
|
|
115
|
-
const location = resp.headers.get("location");
|
|
116
|
-
if (!location) return resp;
|
|
117
|
-
currentUrl = new URL(location, currentUrl).href;
|
|
118
|
-
await assertPublicUrl(currentUrl);
|
|
119
|
-
}
|
|
120
|
-
throw new Error("Too many redirects");
|
|
121
|
-
}
|
|
122
|
-
//#endregion
|
|
1
|
+
import { n as isPrivateIp, r as ssrfSafeFetch, t as assertPublicUrl } from "./_ssrf-DCp_27V4.js";
|
|
123
2
|
export { assertPublicUrl, isPrivateIp, ssrfSafeFetch };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region _utils.ts
|
|
2
|
+
/** Shared utility functions. */
|
|
3
|
+
/** Extract an error message from an unknown thrown value. */
|
|
4
|
+
function errorMessage(err) {
|
|
5
|
+
return err instanceof Error ? err.message : String(err);
|
|
6
|
+
}
|
|
7
|
+
/** Extract a detailed error string (message + stack) for diagnostic logging. */
|
|
8
|
+
function errorDetail(err) {
|
|
9
|
+
if (err instanceof Error) return err.stack ?? err.message;
|
|
10
|
+
return String(err);
|
|
11
|
+
}
|
|
12
|
+
/** Filter out undefined values from an env record. */
|
|
13
|
+
function filterEnv(env) {
|
|
14
|
+
return Object.fromEntries(Object.entries(env).filter((e) => e[1] !== void 0));
|
|
15
|
+
}
|
|
16
|
+
/** Set of filesystem operations that are safe for read-only access. */
|
|
17
|
+
const READ_ONLY_FS_OPS = new Set([
|
|
18
|
+
"read",
|
|
19
|
+
"stat",
|
|
20
|
+
"readdir",
|
|
21
|
+
"exists"
|
|
22
|
+
]);
|
|
23
|
+
/** Check whether a filesystem operation is a read-only operation. */
|
|
24
|
+
function isReadOnlyFsOp(op) {
|
|
25
|
+
return READ_ONLY_FS_OPS.has(op);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Safely extract the port from `server.address()`, guarding against the
|
|
29
|
+
* string (pipe/socket) and null return types.
|
|
30
|
+
*/
|
|
31
|
+
function getServerPort(addr) {
|
|
32
|
+
if (addr && typeof addr === "object" && "port" in addr && typeof addr.port === "number") return addr.port;
|
|
33
|
+
throw new Error(`Expected server address with numeric port, got: ${JSON.stringify(addr)}`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Lazily initialized per-session state manager.
|
|
37
|
+
*
|
|
38
|
+
* On first access for a given session, calls `initState()` (if provided) to
|
|
39
|
+
* create the initial state. Returns `{}` if no initializer and no prior state.
|
|
40
|
+
*/
|
|
41
|
+
function createSessionStateMap(initState) {
|
|
42
|
+
const map = /* @__PURE__ */ new Map();
|
|
43
|
+
return {
|
|
44
|
+
get(sessionId) {
|
|
45
|
+
if (!map.has(sessionId) && initState) map.set(sessionId, initState());
|
|
46
|
+
return map.get(sessionId) ?? {};
|
|
47
|
+
},
|
|
48
|
+
set(sessionId, state) {
|
|
49
|
+
map.set(sessionId, state);
|
|
50
|
+
},
|
|
51
|
+
delete(sessionId) {
|
|
52
|
+
return map.delete(sessionId);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Return a JSON error string for the LLM: `'{"error":"<message>"}'`. */
|
|
57
|
+
function toolError(message) {
|
|
58
|
+
return JSON.stringify({ error: message });
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { getServerPort as a, filterEnv as i, errorDetail as n, isReadOnlyFsOp as o, errorMessage as r, toolError as s, createSessionStateMap as t };
|
package/dist/_utils.js
CHANGED
|
@@ -1,61 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/** Shared utility functions. */
|
|
3
|
-
/** Extract an error message from an unknown thrown value. */
|
|
4
|
-
function errorMessage(err) {
|
|
5
|
-
return err instanceof Error ? err.message : String(err);
|
|
6
|
-
}
|
|
7
|
-
/** Extract a detailed error string (message + stack) for diagnostic logging. */
|
|
8
|
-
function errorDetail(err) {
|
|
9
|
-
if (err instanceof Error) return err.stack ?? err.message;
|
|
10
|
-
return String(err);
|
|
11
|
-
}
|
|
12
|
-
/** Filter out undefined values from an env record. */
|
|
13
|
-
function filterEnv(env) {
|
|
14
|
-
return Object.fromEntries(Object.entries(env).filter((e) => e[1] !== void 0));
|
|
15
|
-
}
|
|
16
|
-
/** Set of filesystem operations that are safe for read-only access. */
|
|
17
|
-
const READ_ONLY_FS_OPS = new Set([
|
|
18
|
-
"read",
|
|
19
|
-
"stat",
|
|
20
|
-
"readdir",
|
|
21
|
-
"exists"
|
|
22
|
-
]);
|
|
23
|
-
/** Check whether a filesystem operation is a read-only operation. */
|
|
24
|
-
function isReadOnlyFsOp(op) {
|
|
25
|
-
return READ_ONLY_FS_OPS.has(op);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Safely extract the port from `server.address()`, guarding against the
|
|
29
|
-
* string (pipe/socket) and null return types.
|
|
30
|
-
*/
|
|
31
|
-
function getServerPort(addr) {
|
|
32
|
-
if (addr && typeof addr === "object" && "port" in addr && typeof addr.port === "number") return addr.port;
|
|
33
|
-
throw new Error(`Expected server address with numeric port, got: ${JSON.stringify(addr)}`);
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Lazily initialized per-session state manager.
|
|
37
|
-
*
|
|
38
|
-
* On first access for a given session, calls `initState()` (if provided) to
|
|
39
|
-
* create the initial state. Returns `{}` if no initializer and no prior state.
|
|
40
|
-
*/
|
|
41
|
-
function createSessionStateMap(initState) {
|
|
42
|
-
const map = /* @__PURE__ */ new Map();
|
|
43
|
-
return {
|
|
44
|
-
get(sessionId) {
|
|
45
|
-
if (!map.has(sessionId) && initState) map.set(sessionId, initState());
|
|
46
|
-
return map.get(sessionId) ?? {};
|
|
47
|
-
},
|
|
48
|
-
set(sessionId, state) {
|
|
49
|
-
map.set(sessionId, state);
|
|
50
|
-
},
|
|
51
|
-
delete(sessionId) {
|
|
52
|
-
return map.delete(sessionId);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/** Return a JSON error string for the LLM: `'{"error":"<message>"}'`. */
|
|
57
|
-
function toolError(message) {
|
|
58
|
-
return JSON.stringify({ error: message });
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
1
|
+
import { a as getServerPort, i as filterEnv, n as errorDetail, o as isReadOnlyFsOp, r as errorMessage, s as toolError, t as createSessionStateMap } from "./_utils-DgzpOMSV.js";
|
|
61
2
|
export { createSessionStateMap, errorDetail, errorMessage, filterEnv, getServerPort, isReadOnlyFsOp, toolError };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { defineTool } from "./types.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { n as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { executeToolCall } from "./worker-entry.js";
|
|
1
|
+
import { s as defineTool } from "./types-D8ZBxTL_.js";
|
|
2
|
+
import { t as createUnstorageVectorStore } from "./unstorage-vector-Cj5llNhg.js";
|
|
3
|
+
import { a as toAgentConfig, i as agentToolsToSchemas, n as EMPTY_PARAMS } from "./_internal-types-IfPcaJd5.js";
|
|
4
|
+
import { r as ssrfSafeFetch } from "./_ssrf-DCp_27V4.js";
|
|
5
|
+
import { o as isReadOnlyFsOp, r as errorMessage, s as toolError, t as createSessionStateMap } from "./_utils-DgzpOMSV.js";
|
|
6
|
+
import { a as runOutputFilters, i as runInputFilters, n as runAfterTurnMiddleware, o as runToolCallInterceptors, r as runBeforeTurnMiddleware, t as runAfterToolCallMiddleware } from "./middleware-core-BwyBIPed.js";
|
|
7
|
+
import { n as consoleLogger, t as DEFAULT_S2S_CONFIG } from "./runtime-CxcwaK68.js";
|
|
8
|
+
import { n as createS2sSession } from "./session-BYlwcrya.js";
|
|
9
|
+
import { t as createUnstorageKv } from "./unstorage-kv-CDgP-frt.js";
|
|
10
|
+
import { t as executeToolCall } from "./worker-entry-2jaiqIj0.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import {
|
|
12
|
+
import { createStorage } from "unstorage";
|
|
13
13
|
//#region _run-code.ts
|
|
14
14
|
/**
|
|
15
15
|
* run_code built-in tool — executes user JavaScript in a fresh secure-exec
|
|
@@ -415,15 +415,13 @@ function getBuiltinToolSchemas(names) {
|
|
|
415
415
|
* In self-hosted mode, agent code is trusted (you're running your own code).
|
|
416
416
|
* Tools execute directly in-process — no sandbox, no RPC.
|
|
417
417
|
*/
|
|
418
|
-
/** Create
|
|
418
|
+
/** Create an in-memory KV store (default for self-hosted). */
|
|
419
419
|
function createLocalKv() {
|
|
420
|
-
|
|
421
|
-
return createSqliteKv();
|
|
420
|
+
return createUnstorageKv({ storage: createStorage() });
|
|
422
421
|
}
|
|
423
|
-
/** Create
|
|
422
|
+
/** Create an in-memory vector store (default for self-hosted). */
|
|
424
423
|
function createLocalVectorStore() {
|
|
425
|
-
|
|
426
|
-
return createSqliteVectorStore({ path: ".aai/vectors.db" });
|
|
424
|
+
return createUnstorageVectorStore({ storage: createStorage() });
|
|
427
425
|
}
|
|
428
426
|
/**
|
|
429
427
|
* Create a direct (in-process) tool executor and hook invoker for an agent.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { createToolFactory, defineAgent, defineTool } from "./types.js";
|
|
1
|
+
import { a as createToolFactory, o as defineAgent, s as defineTool } from "./types-D8ZBxTL_.js";
|
|
2
2
|
export { createToolFactory, defineAgent, defineTool, defineTool as tool };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region kv.ts
|
|
2
|
+
const MAX_VALUE_SIZE = 65536;
|
|
3
|
+
/** Sort entries by key and apply reverse/limit options. Mutates the array. */
|
|
4
|
+
function sortAndPaginate(entries, options) {
|
|
5
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
6
|
+
if (options?.reverse) entries.reverse();
|
|
7
|
+
if (options?.limit && options.limit > 0) entries.length = Math.min(entries.length, options.limit);
|
|
8
|
+
return entries;
|
|
9
|
+
}
|
|
10
|
+
/** Maximum allowed glob pattern length to prevent ReDoS. */
|
|
11
|
+
const MAX_GLOB_PATTERN_LENGTH = 1024;
|
|
12
|
+
/** Simple glob matcher — supports `*` as a wildcard for any characters. */
|
|
13
|
+
function matchGlob(key, pattern) {
|
|
14
|
+
if (pattern.length > MAX_GLOB_PATTERN_LENGTH) throw new Error(`Glob pattern exceeds maximum length of ${MAX_GLOB_PATTERN_LENGTH}`);
|
|
15
|
+
const parts = pattern.split("*");
|
|
16
|
+
if (parts.length === 1) return key === pattern;
|
|
17
|
+
const first = parts[0];
|
|
18
|
+
if (!key.startsWith(first)) return false;
|
|
19
|
+
const last = parts.at(-1);
|
|
20
|
+
if (key.length < first.length + last.length) return false;
|
|
21
|
+
if (!key.endsWith(last)) return false;
|
|
22
|
+
let pos = first.length;
|
|
23
|
+
const end = key.length - last.length;
|
|
24
|
+
for (const part of parts.slice(1, -1)) {
|
|
25
|
+
const idx = key.indexOf(part, pos);
|
|
26
|
+
if (idx === -1 || idx > end) return false;
|
|
27
|
+
pos = idx + part.length;
|
|
28
|
+
}
|
|
29
|
+
return pos <= end;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { matchGlob as n, sortAndPaginate as r, MAX_VALUE_SIZE as t };
|
package/dist/kv.js
CHANGED
|
@@ -1,32 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
const MAX_VALUE_SIZE = 65536;
|
|
3
|
-
/** Sort entries by key and apply reverse/limit options. Mutates the array. */
|
|
4
|
-
function sortAndPaginate(entries, options) {
|
|
5
|
-
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
6
|
-
if (options?.reverse) entries.reverse();
|
|
7
|
-
if (options?.limit && options.limit > 0) entries.length = Math.min(entries.length, options.limit);
|
|
8
|
-
return entries;
|
|
9
|
-
}
|
|
10
|
-
/** Maximum allowed glob pattern length to prevent ReDoS. */
|
|
11
|
-
const MAX_GLOB_PATTERN_LENGTH = 1024;
|
|
12
|
-
/** Simple glob matcher — supports `*` as a wildcard for any characters. */
|
|
13
|
-
function matchGlob(key, pattern) {
|
|
14
|
-
if (pattern.length > MAX_GLOB_PATTERN_LENGTH) throw new Error(`Glob pattern exceeds maximum length of ${MAX_GLOB_PATTERN_LENGTH}`);
|
|
15
|
-
const parts = pattern.split("*");
|
|
16
|
-
if (parts.length === 1) return key === pattern;
|
|
17
|
-
const first = parts[0];
|
|
18
|
-
if (!key.startsWith(first)) return false;
|
|
19
|
-
const last = parts.at(-1);
|
|
20
|
-
if (key.length < first.length + last.length) return false;
|
|
21
|
-
if (!key.endsWith(last)) return false;
|
|
22
|
-
let pos = first.length;
|
|
23
|
-
const end = key.length - last.length;
|
|
24
|
-
for (const part of parts.slice(1, -1)) {
|
|
25
|
-
const idx = key.indexOf(part, pos);
|
|
26
|
-
if (idx === -1 || idx > end) return false;
|
|
27
|
-
pos = idx + part.length;
|
|
28
|
-
}
|
|
29
|
-
return pos <= end;
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
1
|
+
import { n as matchGlob, r as sortAndPaginate, t as MAX_VALUE_SIZE } from "./kv-iXtikQmR.js";
|
|
32
2
|
export { MAX_VALUE_SIZE, matchGlob, sortAndPaginate };
|
package/dist/matchers.js
CHANGED